diff --git a/src/components/map-filters-provider.tsx b/src/components/map-filters-provider.tsx index 6fcdb96..c1583e7 100644 --- a/src/components/map-filters-provider.tsx +++ b/src/components/map-filters-provider.tsx @@ -4,17 +4,20 @@ import { createContext, useCallback, useContext, + useEffect, useMemo, useState, type ReactNode, } from 'react' +import { usePathname, useRouter, useSearchParams } from 'next/navigation' import { REPORT_GROUPS, type ReportCategory, type ReportGroup, } from '@/lib/reports/categories' import { - allCategoriesSelected, + selectionToUrlParam, + paramToSelection, toggleCategory, toggleGroup, type CategorySelection, @@ -50,9 +53,29 @@ export function MapFiltersProvider({ observations: boolean children: ReactNode }) { - const [selection, setSelection] = useState( - allCategoriesSelected, + const router = useRouter() + const pathname = usePathname() + const searchParams = useSearchParams() + + const [selection, setSelection] = useState(() => + paramToSelection(searchParams.get('categories')), ) + + useEffect(() => { + const params = new URLSearchParams(searchParams.toString()) + const value = selectionToUrlParam(selection) + + if (value === null) { + params.delete('categories') + } else { + params.set('categories', value) + } + + const query = params.toString() + router.replace(query ? `${pathname}?${query}` : pathname, { scroll: false }) + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [selection]) + const [heatmapVisible, setHeatmapVisible] = useState(true) const [heatmapAvailable, setHeatmapAvailable] = useState(false) diff --git a/src/lib/reports/filters.ts b/src/lib/reports/filters.ts index c4f0db1..6d28da5 100644 --- a/src/lib/reports/filters.ts +++ b/src/lib/reports/filters.ts @@ -85,6 +85,35 @@ export function selectionToParam(selection: CategorySelection): string | null { return chosen.join(',') } +export function selectionToUrlParam( + selection: CategorySelection, +): string | null { + if (selection.size >= REPORT_CATEGORIES.length) { + return null + } + + return REPORT_CATEGORIES.filter((category) => selection.has(category)).join( + ',', + ) +} + +export function paramToSelection(param: string | null): Set { + if (param === null) { + return allCategoriesSelected() + } + + if (param === '') { + return new Set() + } + + return new Set( + param + .split(',') + .filter((value): value is ReportCategory => + REPORT_CATEGORIES.includes(value as ReportCategory), + ), + ) +} export function observationCategoriesOf( selection: CategorySelection,