Add one change & crime
This commit is contained in:
parent
8e4c56bb0d
commit
54fbcb1ea6
17 changed files with 306 additions and 154 deletions
|
|
@ -92,6 +92,7 @@ interface FiltersProps {
|
|||
onTravelTimeDragEnd: (index: number) => void;
|
||||
onTravelTimeToggleBest: (index: number) => void;
|
||||
onTravelTimeToggleNoChange: (index: number) => void;
|
||||
onTravelTimeToggleOneChange: (index: number) => void;
|
||||
onTravelTimeToggleNoBuses: (index: number) => void;
|
||||
aiFilterLoading: boolean;
|
||||
aiFilterError: string | null;
|
||||
|
|
@ -139,6 +140,7 @@ export default memo(function Filters({
|
|||
onTravelTimeDragEnd,
|
||||
onTravelTimeToggleBest,
|
||||
onTravelTimeToggleNoChange,
|
||||
onTravelTimeToggleOneChange,
|
||||
onTravelTimeToggleNoBuses,
|
||||
aiFilterLoading,
|
||||
aiFilterError,
|
||||
|
|
@ -667,6 +669,7 @@ export default memo(function Filters({
|
|||
onTravelTimeDragEnd={onTravelTimeDragEnd}
|
||||
onTravelTimeToggleBest={onTravelTimeToggleBest}
|
||||
onTravelTimeToggleNoChange={onTravelTimeToggleNoChange}
|
||||
onTravelTimeToggleOneChange={onTravelTimeToggleOneChange}
|
||||
onTravelTimeToggleNoBuses={onTravelTimeToggleNoBuses}
|
||||
/>
|
||||
|
||||
|
|
|
|||
|
|
@ -181,6 +181,7 @@ export default function MapPage({
|
|||
handleTimeRangeChange,
|
||||
handleToggleBest,
|
||||
handleToggleNoChange,
|
||||
handleToggleOneChange,
|
||||
handleToggleNoBuses,
|
||||
} = useTravelTime(initialTravelTime);
|
||||
|
||||
|
|
@ -248,6 +249,7 @@ export default function MapPage({
|
|||
representable.map(({ tt, parsed }) => ({
|
||||
mode: parsed.mode,
|
||||
noChange: parsed.noChange,
|
||||
oneChange: parsed.oneChange,
|
||||
noBuses: parsed.noBuses,
|
||||
slug: tt.slug,
|
||||
label: tt.label,
|
||||
|
|
@ -800,6 +802,7 @@ export default function MapPage({
|
|||
onTravelTimeDragEnd={handleTravelTimeDragEnd}
|
||||
onTravelTimeToggleBest={handleToggleBest}
|
||||
onTravelTimeToggleNoChange={handleToggleNoChange}
|
||||
onTravelTimeToggleOneChange={handleToggleOneChange}
|
||||
onTravelTimeToggleNoBuses={handleToggleNoBuses}
|
||||
aiFilterLoading={aiFilterLoading}
|
||||
aiFilterError={aiFilterError}
|
||||
|
|
@ -853,6 +856,7 @@ export default function MapPage({
|
|||
handleToggleBest,
|
||||
handleToggleNoBuses,
|
||||
handleToggleNoChange,
|
||||
handleToggleOneChange,
|
||||
handleTogglePin,
|
||||
handleTravelTimeDragEnd,
|
||||
handleTravelTimeRemoveEntry,
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@ interface TravelTimeCardProps {
|
|||
timeRange: [number, number] | null;
|
||||
useBest: boolean;
|
||||
noChange: boolean;
|
||||
oneChange: boolean;
|
||||
noBuses: boolean;
|
||||
isPinned: boolean;
|
||||
isActive: boolean;
|
||||
|
|
@ -39,6 +40,7 @@ interface TravelTimeCardProps {
|
|||
onDragEnd: () => void;
|
||||
onToggleBest: () => void;
|
||||
onToggleNoChange: () => void;
|
||||
onToggleOneChange: () => void;
|
||||
onToggleNoBuses: () => void;
|
||||
onRemove: () => void;
|
||||
filterImpact?: number;
|
||||
|
|
@ -52,6 +54,7 @@ export function TravelTimeCard({
|
|||
timeRange,
|
||||
useBest,
|
||||
noChange,
|
||||
oneChange,
|
||||
noBuses,
|
||||
isPinned,
|
||||
isActive,
|
||||
|
|
@ -64,6 +67,7 @@ export function TravelTimeCard({
|
|||
onDragEnd,
|
||||
onToggleBest,
|
||||
onToggleNoChange,
|
||||
onToggleOneChange,
|
||||
onToggleNoBuses,
|
||||
onRemove,
|
||||
filterImpact,
|
||||
|
|
@ -75,6 +79,7 @@ export function TravelTimeCard({
|
|||
const [showInfo, setShowInfo] = useState(false);
|
||||
const [showBestInfo, setShowBestInfo] = useState(false);
|
||||
const [showNoChangeInfo, setShowNoChangeInfo] = useState(false);
|
||||
const [showOneChangeInfo, setShowOneChangeInfo] = useState(false);
|
||||
const [showNoBusesInfo, setShowNoBusesInfo] = useState(false);
|
||||
|
||||
const handleDestinationSelect = useCallback(
|
||||
|
|
@ -169,6 +174,20 @@ export function TravelTimeCard({
|
|||
<InfoIcon className="w-3 h-3" />
|
||||
</IconButton>
|
||||
</div>
|
||||
<div className="flex items-center gap-0.5">
|
||||
<PillToggle
|
||||
label={t('travel.oneChange')}
|
||||
active={oneChange}
|
||||
onClick={onToggleOneChange}
|
||||
size="xs"
|
||||
/>
|
||||
<IconButton
|
||||
onClick={() => setShowOneChangeInfo(true)}
|
||||
title={t('travel.oneChangeTitle')}
|
||||
>
|
||||
<InfoIcon className="w-3 h-3" />
|
||||
</IconButton>
|
||||
</div>
|
||||
<div className="flex items-center gap-0.5">
|
||||
<PillToggle
|
||||
label={t('travel.noBuses')}
|
||||
|
|
@ -201,6 +220,14 @@ export function TravelTimeCard({
|
|||
</InfoPopup>
|
||||
)}
|
||||
|
||||
{showOneChangeInfo && (
|
||||
<InfoPopup title={t('travel.oneChangeTitle')} onClose={() => setShowOneChangeInfo(false)}>
|
||||
<p className="text-sm text-warm-700 dark:text-warm-300 leading-relaxed">
|
||||
<Trans i18nKey="travel.oneChangeDesc" components={{ strong: <strong /> }} />
|
||||
</p>
|
||||
</InfoPopup>
|
||||
)}
|
||||
|
||||
{showNoBusesInfo && (
|
||||
<InfoPopup title={t('travel.noBusesTitle')} onClose={() => setShowNoBusesInfo(false)}>
|
||||
<p className="text-sm text-warm-700 dark:text-warm-300 leading-relaxed">
|
||||
|
|
|
|||
|
|
@ -59,6 +59,7 @@ interface ActiveFilterListProps {
|
|||
onTravelTimeDragEnd: (index: number) => void;
|
||||
onTravelTimeToggleBest: (index: number) => void;
|
||||
onTravelTimeToggleNoChange: (index: number) => void;
|
||||
onTravelTimeToggleOneChange: (index: number) => void;
|
||||
onTravelTimeToggleNoBuses: (index: number) => void;
|
||||
}
|
||||
|
||||
|
|
@ -88,6 +89,7 @@ export function ActiveFilterList({
|
|||
onTravelTimeDragEnd,
|
||||
onTravelTimeToggleBest,
|
||||
onTravelTimeToggleNoChange,
|
||||
onTravelTimeToggleOneChange,
|
||||
onTravelTimeToggleNoBuses,
|
||||
}: ActiveFilterListProps) {
|
||||
const travelCards = (
|
||||
|
|
@ -105,6 +107,7 @@ export function ActiveFilterList({
|
|||
onTravelTimeDragEnd={onTravelTimeDragEnd}
|
||||
onTravelTimeToggleBest={onTravelTimeToggleBest}
|
||||
onTravelTimeToggleNoChange={onTravelTimeToggleNoChange}
|
||||
onTravelTimeToggleOneChange={onTravelTimeToggleOneChange}
|
||||
onTravelTimeToggleNoBuses={onTravelTimeToggleNoBuses}
|
||||
onDragStart={onDragStart}
|
||||
onDragChange={onDragChange}
|
||||
|
|
|
|||
|
|
@ -57,6 +57,7 @@ interface ActiveFiltersPanelProps {
|
|||
onTravelTimeDragEnd: (index: number) => void;
|
||||
onTravelTimeToggleBest: (index: number) => void;
|
||||
onTravelTimeToggleNoChange: (index: number) => void;
|
||||
onTravelTimeToggleOneChange: (index: number) => void;
|
||||
onTravelTimeToggleNoBuses: (index: number) => void;
|
||||
}
|
||||
|
||||
|
|
@ -102,6 +103,7 @@ export function ActiveFiltersPanel({
|
|||
onTravelTimeDragEnd,
|
||||
onTravelTimeToggleBest,
|
||||
onTravelTimeToggleNoChange,
|
||||
onTravelTimeToggleOneChange,
|
||||
onTravelTimeToggleNoBuses,
|
||||
}: ActiveFiltersPanelProps) {
|
||||
const { t } = useTranslation();
|
||||
|
|
@ -206,6 +208,7 @@ export function ActiveFiltersPanel({
|
|||
onTravelTimeDragEnd={onTravelTimeDragEnd}
|
||||
onTravelTimeToggleBest={onTravelTimeToggleBest}
|
||||
onTravelTimeToggleNoChange={onTravelTimeToggleNoChange}
|
||||
onTravelTimeToggleOneChange={onTravelTimeToggleOneChange}
|
||||
onTravelTimeToggleNoBuses={onTravelTimeToggleNoBuses}
|
||||
/>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ interface TravelTimeFilterCardsProps {
|
|||
onTravelTimeDragEnd: (index: number) => void;
|
||||
onTravelTimeToggleBest: (index: number) => void;
|
||||
onTravelTimeToggleNoChange: (index: number) => void;
|
||||
onTravelTimeToggleOneChange: (index: number) => void;
|
||||
onTravelTimeToggleNoBuses: (index: number) => void;
|
||||
onDragStart: (name: string, initialValue?: [number, number]) => void;
|
||||
onDragChange: (value: [number, number]) => void;
|
||||
|
|
@ -40,6 +41,7 @@ export function TravelTimeFilterCards({
|
|||
onTravelTimeDragEnd,
|
||||
onTravelTimeToggleBest,
|
||||
onTravelTimeToggleNoChange,
|
||||
onTravelTimeToggleOneChange,
|
||||
onTravelTimeToggleNoBuses,
|
||||
onDragStart,
|
||||
onDragChange,
|
||||
|
|
@ -57,6 +59,7 @@ export function TravelTimeFilterCards({
|
|||
timeRange={entry.timeRange}
|
||||
useBest={entry.useBest}
|
||||
noChange={entry.noChange ?? false}
|
||||
oneChange={entry.oneChange ?? false}
|
||||
noBuses={entry.noBuses ?? false}
|
||||
isPinned={pinnedFeature === fieldKey}
|
||||
isActive={activeFeature === fieldKey}
|
||||
|
|
@ -71,6 +74,7 @@ export function TravelTimeFilterCards({
|
|||
onDragEnd={() => onTravelTimeDragEnd(index)}
|
||||
onToggleBest={() => onTravelTimeToggleBest(index)}
|
||||
onToggleNoChange={() => onTravelTimeToggleNoChange(index)}
|
||||
onToggleOneChange={() => onTravelTimeToggleOneChange(index)}
|
||||
onToggleNoBuses={() => onTravelTimeToggleNoBuses(index)}
|
||||
onRemove={() => onTravelTimeRemoveEntry(index)}
|
||||
filterImpact={filterImpacts?.[fieldKey]}
|
||||
|
|
|
|||
|
|
@ -150,6 +150,12 @@ export function useExportController({
|
|||
const params = new URLSearchParams();
|
||||
if (isListMode) {
|
||||
params.set('postcodes', postcodeList.join(','));
|
||||
// Pass the active filters so the export keeps its two sheets — "Selected"
|
||||
// (the filtered feature columns) and "All Data" — even in list mode. The
|
||||
// server uses them only to pick columns; the supplied postcodes still
|
||||
// drive which rows appear.
|
||||
const filterStr = buildFilterString(filters, features);
|
||||
if (filterStr) params.set('filters', filterStr);
|
||||
if (shareCode) params.set('share', shareCode);
|
||||
} else {
|
||||
const { south, west, north, east } = bounds!;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue