From 6103ee604ba08dab9dd8de0bf6e2021df454600b Mon Sep 17 00:00:00 2001 From: Josh Hawkins <32435876+hawkeye217@users.noreply.github.com> Date: Wed, 6 Aug 2025 21:45:52 -0500 Subject: [PATCH] Fix api filter hook cameras, labels, sub labels, plates, and zones could be parsed as numeric values rather than strings, which would break the explore filter. This change adds an optional param to the useApiFilterArgs hook to always parse keys as string[] --- web/src/hooks/use-api-filter.ts | 43 +++++++++++++++++++-------------- web/src/pages/Explore.tsx | 8 +++++- 2 files changed, 32 insertions(+), 19 deletions(-) diff --git a/web/src/hooks/use-api-filter.ts b/web/src/hooks/use-api-filter.ts index bffbff7e8..f9baead6e 100644 --- a/web/src/hooks/use-api-filter.ts +++ b/web/src/hooks/use-api-filter.ts @@ -47,9 +47,9 @@ export default function useApiFilter< return [filter, setFilter, searchParams]; } -export function useApiFilterArgs< - F extends FilterType, ->(): useApiFilterReturn { +export function useApiFilterArgs( + arrayKeys: string[], +): useApiFilterReturn { const [rawParams, setRawParams] = useSearchParams(); const setFilter = useCallback( @@ -64,30 +64,37 @@ export function useApiFilterArgs< const filter: { [key: string]: unknown } = {}; - rawParams.forEach((value, key) => { - const isValidNumber = /^-?\d+(\.\d+)?(?!.)/.test(value); - const isValidEventID = /^\d+\.\d+-[a-zA-Z0-9]+$/.test(value); + // always treat these keys as string[], not as a number or event id + const arrayKeySet = new Set(arrayKeys); - if ( - value != "true" && - value != "false" && - !isValidNumber && - !isValidEventID - ) { + rawParams.forEach((value, key) => { + if (arrayKeySet.has(key)) { filter[key] = value.includes(",") ? value.split(",") : [value]; } else { - if (value != undefined) { - try { - filter[key] = JSON.parse(value); - } catch { - filter[key] = `${value}`; + const isValidNumber = /^-?\d+(\.\d+)?(?!.)/.test(value); + const isValidEventID = /^\d+\.\d+-[a-zA-Z0-9]+$/.test(value); + + if ( + value != "true" && + value != "false" && + !isValidNumber && + !isValidEventID + ) { + filter[key] = value.includes(",") ? value.split(",") : [value]; + } else { + if (value != undefined) { + try { + filter[key] = JSON.parse(value); + } catch { + filter[key] = `${value}`; + } } } } }); return filter as F; - }, [rawParams]); + }, [rawParams, arrayKeys]); const searchParams = useMemo(() => { if (filter == undefined || Object.keys(filter).length == 0) { diff --git a/web/src/pages/Explore.tsx b/web/src/pages/Explore.tsx index 62d55fee9..9cb97ae2a 100644 --- a/web/src/pages/Explore.tsx +++ b/web/src/pages/Explore.tsx @@ -58,7 +58,13 @@ export default function Explore() { const [search, setSearch] = useState(""); const [searchFilter, setSearchFilter, searchSearchParams] = - useApiFilterArgs(); + useApiFilterArgs([ + "cameras", + "labels", + "sub_labels", + "recognized_license_plate", + "zones", + ]); const searchTerm = useMemo( () => searchSearchParams?.["query"] || "",