Added map filter indication

Added map filter indication
+ some more type fixes...
This commit is contained in:
Jeremy Gallant
2025-04-24 20:12:41 +02:00
parent ce71f22316
commit f399d17721
3 changed files with 52 additions and 8 deletions
+28 -5
View File
@@ -6,7 +6,7 @@ interface BooleanFilter {
key: string;
label: string;
type: "boolean";
predicate: (node: Node, value: boolean) => boolean;
predicate: (node: Protobuf.Mesh.NodeInfo, value: boolean) => boolean;
}
interface RangeFilter {
@@ -14,14 +14,14 @@ interface RangeFilter {
label: string;
type: "range";
bounds: [number, number];
predicate: (node: Node, value: [number, number]) => boolean;
predicate: (node: Protobuf.Mesh.NodeInfo, value: [number, number]) => boolean;
}
interface SearchFilter {
key: string;
label: string;
type: "search";
predicate: (node: Node, value: string) => boolean;
predicate: (node: Protobuf.Mesh.NodeInfo, value: string) => boolean;
}
export type FilterConfig = BooleanFilter | RangeFilter | SearchFilter;
@@ -113,7 +113,7 @@ export function useNodeFilters(nodes: Protobuf.Mesh.NodeInfo[]) {
acc[cfg.key] = false;
break;
case "range":
acc[cfg.key] = cfg.bounds!;
acc[cfg.key] = cfg.bounds;
break;
case "search":
acc[cfg.key] = "";
@@ -141,13 +141,36 @@ export function useNodeFilters(nodes: Protobuf.Mesh.NodeInfo[]) {
const filteredNodes = useMemo(
() =>
nodes.filter((node) =>
filterConfigs.every((cfg) => cfg.predicate(node, filters[cfg.key]))
filterConfigs.every((cfg) => {
const val = filters[cfg.key];
switch (cfg.type) {
case "boolean":
if (typeof val !== "boolean") return true;
return cfg.predicate(node, val);
case "range":
if (
!Array.isArray(val) ||
val.length !== 2 ||
typeof val[0] !== "number" ||
typeof val[1] !== "number"
) {
return true;
}
return cfg.predicate(node, val);
case "search":
if (typeof val !== "string") return true;
return cfg.predicate(node, val);
}
})
),
[nodes, filters],
);
return {
filters,
defaultState,
onFilterChange,
resetFilters,
filteredNodes,
+10 -2
View File
@@ -10,6 +10,7 @@ import type {
FilterConfig,
FilterValueMap,
} from "@core/hooks/useNodeFilters.ts";
import { cn } from "@core/utils/cn.ts";
interface FilterControlProps {
configs: FilterConfig[];
@@ -19,18 +20,25 @@ interface FilterControlProps {
value: FilterValueMap[K],
) => void;
resetFilters: () => void;
isDirty: boolean;
children?: React.ReactNode;
}
export function FilterControl(
{ configs, values, onChange, resetFilters, children }: FilterControlProps,
{ configs, values, onChange, resetFilters, isDirty, children }:
FilterControlProps,
) {
return (
<Popover>
<PopoverTrigger asChild>
<button
type="button"
className="fixed bottom-17 right-2 px-1 py-1 bg-slate-100 text-slate-600 rounded shadow-md"
className={cn(
"fixed bottom-17 right-2 px-1 py-1 rounded shadow-md",
isDirty
? " text-slate-100 bg-green-600"
: "text-slate-600 bg-slate-100",
)}
aria-label="Filter"
>
<FunnelIcon />
+14 -1
View File
@@ -56,13 +56,25 @@ const MapPage = () => {
);
const {
filteredNodes,
filters,
defaultState,
onFilterChange,
resetFilters,
filteredNodes,
filterConfigs,
} = useNodeFilters(validNodes);
const isDirty = useMemo(() => {
return Object.keys(filters).some((key) => {
const a = filters[key];
const b = defaultState[key];
// simple deep‐equal for primitives and [number,number]
return Array.isArray(a) && Array.isArray(b)
? a[0] !== b[0] || a[1] !== b[1]
: a !== b;
});
}, [filters, defaultState]);
const handleMarkerClick = useCallback(
(node: Protobuf.Mesh.NodeInfo, event: { originalEvent: MouseEvent }) => {
event?.originalEvent?.stopPropagation();
@@ -213,6 +225,7 @@ const MapPage = () => {
values={filters}
onChange={onFilterChange}
resetFilters={resetFilters}
isDirty={isDirty}
/>
</PageLayout>
</>