UI improvements

This commit is contained in:
Andras Schmelczer 2026-03-15 09:02:10 +00:00
parent 83dd2ca87e
commit 1569d116a9
14 changed files with 222 additions and 92 deletions

View file

@ -414,27 +414,63 @@ export function useDeckLayers({
data: postcodeData as PostcodeFeature[],
getFillColor: (f) => {
const d = f.properties;
const dark = isDarkRef.current;
const entries = travelTimeEntriesRef.current;
// Dim-filter: all travel entries with timeRange dim postcodes outside range
for (let i = 0; i < entries.length; i++) {
const entry = entries[i];
if (!entry.timeRange || !entry.slug) continue;
const fk = travelFieldKey(entry);
const modeVal = d[`avg_${fk}`];
if (modeVal == null || (modeVal as number) < entry.timeRange[0] || (modeVal as number) > entry.timeRange[1]) {
return (dark ? [60, 55, 50, 60] : [180, 180, 180, 60]) as [number, number, number, number];
}
}
const vf = viewFeatureRef.current;
const clr = colorRangeRef.current;
const fr = filterRangeRef.current;
const cfm = colorFeatureMetaRef.current;
const dark = isDarkRef.current;
if (vf && clr && cfm) {
const val = d[`avg_${vf}`] ?? d[`min_${vf}`];
const minVal = d[`min_${vf}`] as number | undefined;
const maxVal = d[`max_${vf}`] as number | undefined;
return getFeatureFillColor(
val as number | null | undefined,
minVal,
maxVal,
clr,
fr,
0,
densityGradientRef.current,
dark,
180,
enumCountRef.current
);
if (vf && clr) {
// Travel time feature: dim postcodes with no data
if (vf.startsWith('tt_')) {
const ttVal = d[`avg_${vf}`];
if (ttVal == null) {
return (dark ? [80, 70, 65, 80] : [128, 128, 128, 80]) as [number, number, number, number];
}
return getFeatureFillColor(
ttVal as number,
ttVal as number,
ttVal as number,
clr,
null,
0,
densityGradientRef.current,
dark,
180
);
}
// Regular feature
if (cfm) {
const val = d[`avg_${vf}`] ?? d[`min_${vf}`];
const minVal = d[`min_${vf}`] as number | undefined;
const maxVal = d[`max_${vf}`] as number | undefined;
return getFeatureFillColor(
val as number | null | undefined,
minVal,
maxVal,
clr,
fr,
0,
densityGradientRef.current,
dark,
180,
enumCountRef.current
);
}
}
const cr = postcodeCountRangeRef.current;
const c = d.count;

View file

@ -0,0 +1,34 @@
import { useState, useEffect } from 'react';
import { logNonAbortError } from '../lib/api';
import type { TransportMode } from './useTravelTime';
interface TravelModeInfo {
mode: TransportMode;
destinations: number;
}
/** Fetches which transport modes have precomputed travel time data. */
export function useTravelModes() {
const [availableModes, setAvailableModes] = useState<Set<TransportMode> | null>(null);
useEffect(() => {
const controller = new AbortController();
fetch('/api/travel-modes', { signal: controller.signal })
.then((res) => {
if (!res.ok) throw new Error(`HTTP ${res.status}`);
return res.json();
})
.then((data: { modes: TravelModeInfo[] }) => {
const modes = new Set<TransportMode>(
data.modes.filter((m) => m.destinations > 0).map((m) => m.mode),
);
setAvailableModes(modes);
})
.catch((err) => logNonAbortError('travel modes', err));
return () => controller.abort();
}, []);
return availableModes;
}

View file

@ -13,6 +13,14 @@ const STEPS: Step[] = [
placement: 'right',
disableBeacon: true,
},
{
target: '[data-tutorial="ai-filters"]',
title: 'AI-Powered Filters',
content:
'Describe your ideal area in plain English — like "quiet neighbourhood with good schools" — and AI will set up the right filters for you automatically.',
placement: 'right',
disableBeacon: true,
},
{
target: '[data-tutorial="map"]',
title: 'Explore the Map',