import { useState, useCallback, useMemo } from 'react'; export type TransportMode = 'car' | 'bicycle' | 'walking' | 'transit'; export const TRANSPORT_MODES: TransportMode[] = ['car', 'bicycle', 'walking', 'transit']; export const MODE_LABELS: Record = { car: 'Car', bicycle: 'Bicycle', walking: 'Walking', transit: 'Transit', }; export const MODE_DESCRIPTIONS: Record = { car: 'Drive time via the fastest road route', bicycle: 'Cycling time using bike-friendly routes', walking: 'Walking time along pedestrian paths and pavements', transit: 'Journey time by train, tube, and bus', }; export interface TravelTimeEntry { mode: TransportMode; slug: string; label: string; timeRange: [number, number] | null; /** Use best-case (5th percentile) travel time instead of median. Transit only. */ useBest: boolean; } /** Field key matching the backend response: tt_{mode}_{slug} */ export function travelFieldKey(entry: TravelTimeEntry): string { return `tt_${entry.mode}_${entry.slug}`; } export interface TravelTimeInitial { entries?: TravelTimeEntry[]; } export function useTravelTime(initial?: TravelTimeInitial) { const [entries, setEntries] = useState(initial?.entries ?? []); const handleAddEntry = useCallback((mode: TransportMode) => { setEntries((prev) => [ ...prev, { mode, slug: '', label: '', timeRange: null, useBest: false }, ]); }, []); const handleRemoveEntry = useCallback((index: number) => { setEntries((prev) => prev.filter((_, i) => i !== index)); }, []); const handleSetDestination = useCallback( (index: number, slug: string, label: string) => { setEntries((prev) => prev.map((entry, i) => i === index ? { ...entry, slug, label, timeRange: slug ? [0, 120] : null } : entry ) ); }, [] ); const handleTimeRangeChange = useCallback( (index: number, range: [number, number]) => { setEntries((prev) => prev.map((entry, i) => i === index ? { ...entry, timeRange: range } : entry ) ); }, [] ); const handleToggleBest = useCallback( (index: number) => { setEntries((prev) => prev.map((entry, i) => i === index ? { ...entry, useBest: !entry.useBest } : entry ) ); }, [] ); /** Entries that have a destination selected (slug is set) */ const activeEntries = useMemo( () => entries.filter((e) => e.slug !== ''), [entries] ); return { entries, activeEntries, handleAddEntry, handleRemoveEntry, handleSetDestination, handleTimeRangeChange, handleToggleBest, }; }