filter in config and fixes
This commit is contained in:
@@ -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"
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user