101 lines
2.7 KiB
TypeScript
101 lines
2.7 KiB
TypeScript
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<TransportMode, string> = {
|
|
car: 'Car',
|
|
bicycle: 'Bicycle',
|
|
walking: 'Walking',
|
|
transit: 'Transit',
|
|
};
|
|
|
|
export const MODE_DESCRIPTIONS: Record<TransportMode, string> = {
|
|
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<TravelTimeEntry[]>(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,
|
|
};
|
|
}
|