From a3970d6a7bc38639f7f5a0881682524e88bb9822 Mon Sep 17 00:00:00 2001 From: Vishesh 'ironeagle' Bangotra Date: Sun, 24 May 2026 01:21:51 +0530 Subject: [PATCH] filter in config and fixes --- react-openapi/components/FilterBar.tsx | 159 ++++++++++------------ react-openapi/components/ResourceView.tsx | 26 +++- react-openapi/types/config.ts | 1 + react-openapi/types/overrides.ts | 1 + src/openapi-config.ts | 7 + 5 files changed, 101 insertions(+), 93 deletions(-) diff --git a/react-openapi/components/FilterBar.tsx b/react-openapi/components/FilterBar.tsx index bf8af63..d9f226d 100644 --- a/react-openapi/components/FilterBar.tsx +++ b/react-openapi/components/FilterBar.tsx @@ -14,16 +14,6 @@ import { import FilterListIcon from "@mui/icons-material/FilterList"; import { ResourceField, ResourceMode } from "../types/config"; -function getDisplayValue(item: any, field: ResourceField): string { - if (!item) return ""; - const df = field.displayField; - if (!df) return item.name || item.title || item.label || String(item.id ?? ""); - if (Array.isArray(df)) { - return df.map((k) => item[k]).filter((v) => v != null).join(" "); - } - return item[df] ?? String(item.id ?? ""); -} - function extractOptions( fieldName: string, field: ResourceField, @@ -31,33 +21,45 @@ function extractOptions( ): string[] { const values = new Set(); - if (field.options) { - return field.options; - } - + if (field.options) return field.options; if (!data) return []; - for (const item of data) { - const v = item[fieldName]; + const pull = (item: any): string | null => { + if (item == null) return null; + if (typeof item === "string") return item; + if (typeof item !== "object") return String(item); + + const df = field.displayField; + if (!df) { debugger; return null; } + + if (Array.isArray(df)) { + const parts = df.map((k) => item[k]).filter((v) => v != null); + if (parts.length > 0) return parts.join(" "); + } else { + const v = item[df]; + if (v != null) return String(v); + } + + debugger; + return null; + }; + + for (const row of data) { + const v = row[fieldName]; if (v == null) continue; - if (field.type === "array" && Array.isArray(v)) { + if (Array.isArray(v)) { for (const el of v) { - if (el != null && typeof el === "object") { - const d = getDisplayValue(el, field); - if (d) values.add(d); - } else if (el != null) { - values.add(String(el)); - } + const label = pull(el); + if (label) values.add(label); } - } else if (typeof v === "object") { - const d = getDisplayValue(v, field); - if (d) values.add(d); } else { - values.add(String(v)); + const label = pull(v); + if (label) values.add(label); } } + console.log('extracted', fieldName, Array.from(values).sort()) return Array.from(values).sort(); } @@ -68,49 +70,17 @@ function renderFilterInput( value: any, onChange: (key: string, val: any) => void ) { - const isRange = - field.type === "number" || field.type === "datetime" || field.type === "date"; - - if (isRange) { - const rangeVal = (value as { min?: string; max?: string; start?: string; end?: string }) || {}; - const isDate = field.type === "datetime" || field.type === "date"; - const inputType = isDate ? "datetime-local" : "number"; - - if (isDate) { - return ( - - - {field.label} - - onChange("start", e.target.value || undefined)} - InputLabelProps={{ shrink: true }} - sx={{ width: 190 }} - /> - onChange("end", e.target.value || undefined)} - InputLabelProps={{ shrink: true }} - sx={{ width: 190 }} - /> - - ); - } + const filterType = field.filterType; + if (filterType === "number-range") { + const rangeVal = (value as { min?: string; max?: string }) || {}; return ( {field.label} - {field.label} - - + + + {field.label} + + onChange("start", e.target.value || undefined)} + InputLabelProps={{ shrink: true }} + sx={{ width: 190 }} + /> + onChange("end", e.target.value || undefined)} + InputLabelProps={{ shrink: true }} + sx={{ width: 190 }} + /> + ); } - if (options.length <= 20) { + if (filterType === "multiselect") { + const selected = Array.isArray(value) ? value : []; return ( onChange("value", val || undefined)} + value={selected} + onChange={(_, val) => onChange("value", val.length > 0 ? val : undefined)} renderInput={(params) => ( )} - sx={{ minWidth: 180 }} + sx={{ minWidth: 220 }} size="small" /> ); } return ( - onChange("value", e.target.value || undefined)} - size="small" + options={options} + value={value ?? null} + onChange={(_, val) => onChange("value", val || undefined)} + renderInput={(params) => ( + + )} sx={{ minWidth: 180 }} + size="small" /> ); } diff --git a/react-openapi/components/ResourceView.tsx b/react-openapi/components/ResourceView.tsx index 4c69014..b239be4 100644 --- a/react-openapi/components/ResourceView.tsx +++ b/react-openapi/components/ResourceView.tsx @@ -16,7 +16,7 @@ interface ResourceViewProps { import { GridPaginationModel } from '@mui/x-data-grid'; function getFilterDisplayFields(field: ResourceField): string[] { - if (!field.displayField) return ["name", "title", "label"]; + if (!field.displayField) return []; return (Array.isArray(field.displayField) ? field.displayField : [field.displayField]).filter( (df): df is string => !!df ); @@ -57,20 +57,32 @@ function applyClientFilters( return true; } + if (Array.isArray(filterValue)) { + if (itemValue && typeof itemValue === "object") { + const dispFields = getFilterDisplayFields(field); + const itemDisplay = dispFields.map((df) => itemValue[df]).filter((v) => v != null).join(" "); + return filterValue.includes(itemDisplay); + } + return filterValue.includes(String(itemValue)); + } + if (!filterValue) return true; if (field.type === "boolean") { return String(itemValue) === filterValue; } - if (field.type === "array" && Array.isArray(itemValue) && field.relation) { - const dispFields = getFilterDisplayFields(field); - return itemValue.some((el: any) => - dispFields.some((df) => String(el[df]) === String(filterValue)) - ); + if (field.type === "array" && Array.isArray(itemValue)) { + return itemValue.some((el: any) => { + if (el != null && typeof el === "object") { + const dispFields = getFilterDisplayFields(field); + return dispFields.some((df) => String(el[df]) === String(filterValue)); + } + return String(el) === String(filterValue); + }); } - if (field.relation && itemValue && typeof itemValue === "object") { + if (itemValue && typeof itemValue === "object") { const dispFields = getFilterDisplayFields(field); return dispFields.some((df) => String(itemValue[df]) === String(filterValue)); } diff --git a/react-openapi/types/config.ts b/react-openapi/types/config.ts index 25ef31a..a0cba15 100644 --- a/react-openapi/types/config.ts +++ b/react-openapi/types/config.ts @@ -20,6 +20,7 @@ export interface ResourceField { displayField?: string | string[]; formatter?: (value: any) => string; relation?: string; // Name of the target resource + filterType?: "autocomplete" | "multiselect" | "number-range" | "date-range"; } export type ResourceMode = "server" | "client"; diff --git a/react-openapi/types/overrides.ts b/react-openapi/types/overrides.ts index bb11784..56c6a8a 100644 --- a/react-openapi/types/overrides.ts +++ b/react-openapi/types/overrides.ts @@ -7,6 +7,7 @@ export interface FieldOverride { displayField?: string | string[]; display?: boolean; formatter?: (value: any) => string; + filterType?: "autocomplete" | "multiselect" | "number-range" | "date-range"; } export interface ResourceOverride { diff --git a/src/openapi-config.ts b/src/openapi-config.ts index d837480..3269912 100644 --- a/src/openapi-config.ts +++ b/src/openapi-config.ts @@ -7,6 +7,7 @@ export const configuration: Record = { fields: { payee: { displayField: "name", + filterType: "autocomplete", }, payor: { display: false, @@ -14,11 +15,14 @@ export const configuration: Record = { }, account: { displayField: "name", + filterType: "multiselect", }, tags: { displayField: ["name", "icon"], + filterType: "autocomplete", }, occurred_at: { + filterType: "date-range", formatter: (val: string) => { const date = new Date(val); const day = date.getDate(); @@ -36,6 +40,9 @@ export const configuration: Record = { return `${day}${suffix(day)} ${month} ${year}`; } }, + amount: { + filterType: "number-range", + }, created_at: { display: false }