filter in config and fixes

This commit is contained in:
2026-05-24 01:21:51 +05:30
parent 7ab5ce74b3
commit a3970d6a7b
5 changed files with 101 additions and 93 deletions

View File

@@ -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<string>();
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 (
<Box key={fieldName} sx={{ display: "flex", gap: 1, alignItems: "center" }}>
<Typography variant="caption" sx={{ minWidth: 80, color: "text.secondary" }}>
{field.label}
</Typography>
<TextField
type={inputType}
placeholder="From"
size="small"
value={rangeVal.start ?? ""}
onChange={(e) => onChange("start", e.target.value || undefined)}
InputLabelProps={{ shrink: true }}
sx={{ width: 190 }}
/>
<TextField
type={inputType}
placeholder="To"
size="small"
value={rangeVal.end ?? ""}
onChange={(e) => onChange("end", e.target.value || undefined)}
InputLabelProps={{ shrink: true }}
sx={{ width: 190 }}
/>
</Box>
);
}
const filterType = field.filterType;
if (filterType === "number-range") {
const rangeVal = (value as { min?: string; max?: string }) || {};
return (
<Box key={fieldName} sx={{ display: "flex", gap: 1, alignItems: "center" }}>
<Typography variant="caption" sx={{ minWidth: 80, color: "text.secondary" }}>
{field.label}
</Typography>
<TextField
type={inputType}
type="number"
placeholder="Min"
size="small"
value={rangeVal.min ?? ""}
@@ -118,7 +88,7 @@ function renderFilterInput(
sx={{ width: 120 }}
/>
<TextField
type={inputType}
type="number"
placeholder="Max"
size="small"
value={rangeVal.max ?? ""}
@@ -129,47 +99,64 @@ function renderFilterInput(
);
}
if (field.type === "boolean") {
if (filterType === "date-range") {
const rangeVal = (value as { start?: string; end?: string }) || {};
return (
<FormControl key={fieldName} size="small" sx={{ minWidth: 140 }}>
<InputLabel>{field.label}</InputLabel>
<Select
value={value ?? ""}
label={field.label}
onChange={(e) => onChange("value", e.target.value || undefined)}
>
<MenuItem value="">All</MenuItem>
<MenuItem value="true">Yes</MenuItem>
<MenuItem value="false">No</MenuItem>
</Select>
</FormControl>
<Box key={fieldName} sx={{ display: "flex", gap: 1, alignItems: "center" }}>
<Typography variant="caption" sx={{ minWidth: 80, color: "text.secondary" }}>
{field.label}
</Typography>
<TextField
type="datetime-local"
placeholder="From"
size="small"
value={rangeVal.start ?? ""}
onChange={(e) => onChange("start", e.target.value || undefined)}
InputLabelProps={{ shrink: true }}
sx={{ width: 190 }}
/>
<TextField
type="datetime-local"
placeholder="To"
size="small"
value={rangeVal.end ?? ""}
onChange={(e) => onChange("end", e.target.value || undefined)}
InputLabelProps={{ shrink: true }}
sx={{ width: 190 }}
/>
</Box>
);
}
if (options.length <= 20) {
if (filterType === "multiselect") {
const selected = Array.isArray(value) ? value : [];
return (
<Autocomplete
key={fieldName}
multiple
options={options}
value={value ?? null}
onChange={(_, val) => onChange("value", val || undefined)}
value={selected}
onChange={(_, val) => onChange("value", val.length > 0 ? val : undefined)}
renderInput={(params) => (
<TextField {...params} label={field.label} size="small" />
)}
sx={{ minWidth: 180 }}
sx={{ minWidth: 220 }}
size="small"
/>
);
}
return (
<TextField
<Autocomplete
key={fieldName}
label={field.label}
value={value ?? ""}
onChange={(e) => onChange("value", e.target.value || undefined)}
size="small"
options={options}
value={value ?? null}
onChange={(_, val) => onChange("value", val || undefined)}
renderInput={(params) => (
<TextField {...params} label={field.label} size="small" />
)}
sx={{ minWidth: 180 }}
size="small"
/>
);
}

View File

@@ -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));
}

View File

@@ -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";

View File

@@ -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 {

View File

@@ -7,6 +7,7 @@ export const configuration: Record<string, ResourceOverride> = {
fields: {
payee: {
displayField: "name",
filterType: "autocomplete",
},
payor: {
display: false,
@@ -14,11 +15,14 @@ export const configuration: Record<string, ResourceOverride> = {
},
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<string, ResourceOverride> = {
return `${day}${suffix(day)} ${month} ${year}`;
}
},
amount: {
filterType: "number-range",
},
created_at: {
display: false
}