Added map filter indication
Added map filter indication + some more type fixes...
This commit is contained in:
@@ -6,7 +6,7 @@ interface BooleanFilter {
|
|||||||
key: string;
|
key: string;
|
||||||
label: string;
|
label: string;
|
||||||
type: "boolean";
|
type: "boolean";
|
||||||
predicate: (node: Node, value: boolean) => boolean;
|
predicate: (node: Protobuf.Mesh.NodeInfo, value: boolean) => boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface RangeFilter {
|
interface RangeFilter {
|
||||||
@@ -14,14 +14,14 @@ interface RangeFilter {
|
|||||||
label: string;
|
label: string;
|
||||||
type: "range";
|
type: "range";
|
||||||
bounds: [number, number];
|
bounds: [number, number];
|
||||||
predicate: (node: Node, value: [number, number]) => boolean;
|
predicate: (node: Protobuf.Mesh.NodeInfo, value: [number, number]) => boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface SearchFilter {
|
interface SearchFilter {
|
||||||
key: string;
|
key: string;
|
||||||
label: string;
|
label: string;
|
||||||
type: "search";
|
type: "search";
|
||||||
predicate: (node: Node, value: string) => boolean;
|
predicate: (node: Protobuf.Mesh.NodeInfo, value: string) => boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type FilterConfig = BooleanFilter | RangeFilter | SearchFilter;
|
export type FilterConfig = BooleanFilter | RangeFilter | SearchFilter;
|
||||||
@@ -113,7 +113,7 @@ export function useNodeFilters(nodes: Protobuf.Mesh.NodeInfo[]) {
|
|||||||
acc[cfg.key] = false;
|
acc[cfg.key] = false;
|
||||||
break;
|
break;
|
||||||
case "range":
|
case "range":
|
||||||
acc[cfg.key] = cfg.bounds!;
|
acc[cfg.key] = cfg.bounds;
|
||||||
break;
|
break;
|
||||||
case "search":
|
case "search":
|
||||||
acc[cfg.key] = "";
|
acc[cfg.key] = "";
|
||||||
@@ -141,13 +141,36 @@ export function useNodeFilters(nodes: Protobuf.Mesh.NodeInfo[]) {
|
|||||||
const filteredNodes = useMemo(
|
const filteredNodes = useMemo(
|
||||||
() =>
|
() =>
|
||||||
nodes.filter((node) =>
|
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],
|
[nodes, filters],
|
||||||
);
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
filters,
|
filters,
|
||||||
|
defaultState,
|
||||||
onFilterChange,
|
onFilterChange,
|
||||||
resetFilters,
|
resetFilters,
|
||||||
filteredNodes,
|
filteredNodes,
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import type {
|
|||||||
FilterConfig,
|
FilterConfig,
|
||||||
FilterValueMap,
|
FilterValueMap,
|
||||||
} from "@core/hooks/useNodeFilters.ts";
|
} from "@core/hooks/useNodeFilters.ts";
|
||||||
|
import { cn } from "@core/utils/cn.ts";
|
||||||
|
|
||||||
interface FilterControlProps {
|
interface FilterControlProps {
|
||||||
configs: FilterConfig[];
|
configs: FilterConfig[];
|
||||||
@@ -19,18 +20,25 @@ interface FilterControlProps {
|
|||||||
value: FilterValueMap[K],
|
value: FilterValueMap[K],
|
||||||
) => void;
|
) => void;
|
||||||
resetFilters: () => void;
|
resetFilters: () => void;
|
||||||
|
isDirty: boolean;
|
||||||
children?: React.ReactNode;
|
children?: React.ReactNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function FilterControl(
|
export function FilterControl(
|
||||||
{ configs, values, onChange, resetFilters, children }: FilterControlProps,
|
{ configs, values, onChange, resetFilters, isDirty, children }:
|
||||||
|
FilterControlProps,
|
||||||
) {
|
) {
|
||||||
return (
|
return (
|
||||||
<Popover>
|
<Popover>
|
||||||
<PopoverTrigger asChild>
|
<PopoverTrigger asChild>
|
||||||
<button
|
<button
|
||||||
type="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"
|
aria-label="Filter"
|
||||||
>
|
>
|
||||||
<FunnelIcon />
|
<FunnelIcon />
|
||||||
|
|||||||
+14
-1
@@ -56,13 +56,25 @@ const MapPage = () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
filteredNodes,
|
|
||||||
filters,
|
filters,
|
||||||
|
defaultState,
|
||||||
onFilterChange,
|
onFilterChange,
|
||||||
resetFilters,
|
resetFilters,
|
||||||
|
filteredNodes,
|
||||||
filterConfigs,
|
filterConfigs,
|
||||||
} = useNodeFilters(validNodes);
|
} = 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(
|
const handleMarkerClick = useCallback(
|
||||||
(node: Protobuf.Mesh.NodeInfo, event: { originalEvent: MouseEvent }) => {
|
(node: Protobuf.Mesh.NodeInfo, event: { originalEvent: MouseEvent }) => {
|
||||||
event?.originalEvent?.stopPropagation();
|
event?.originalEvent?.stopPropagation();
|
||||||
@@ -213,6 +225,7 @@ const MapPage = () => {
|
|||||||
values={filters}
|
values={filters}
|
||||||
onChange={onFilterChange}
|
onChange={onFilterChange}
|
||||||
resetFilters={resetFilters}
|
resetFilters={resetFilters}
|
||||||
|
isDirty={isDirty}
|
||||||
/>
|
/>
|
||||||
</PageLayout>
|
</PageLayout>
|
||||||
</>
|
</>
|
||||||
|
|||||||
Reference in New Issue
Block a user