Files
khata-ui/react-openapi/components/ResourceView.tsx
2026-06-07 14:05:14 +05:30

218 lines
7.5 KiB
TypeScript

import * as React from 'react';
import { Box, Paper, CircularProgress } from '@mui/material';
import { ResourceConfig } from '../types/config';
import type { ResourceField } from '../types/config';
import { FieldComponents } from '../types/overrides';
import { useResource } from '../hooks/useResource';
import { resolveTemplate } from '../utils/options';
import EnhancedTable from './EnhancedTable';
import FilterBar from './FilterBar';
import { useParams, useLocation, useNavigate } from 'react-router-dom';
interface ResourceViewProps {
config: ResourceConfig;
onNavigateToResource?: (resourceName: string, id: string) => void;
fieldComponents: FieldComponents;
}
import { GridPaginationModel } from '@mui/x-data-grid';
function getDisplayString(item: any, field: ResourceField): string {
if (item == null || typeof item !== 'object') return String(item ?? '');
if (field.enumOption?.value) return resolveTemplate(field.enumOption.value, item);
const df = field.displayField;
if (!df) return item.name ?? item.title ?? item.label ?? item.id ?? JSON.stringify(item);
if (Array.isArray(df)) {
const parts = df.map((k: string) => item[k]).filter((v: any) => v != null);
return parts.length > 0 ? parts.join(' ') : '';
}
return String(item[df] ?? '');
}
function applyClientFilters(
data: any[],
filters: Record<string, any>,
fields: Record<string, ResourceField>
): any[] {
const entries = Object.entries(filters).filter(([_, v]) => {
if (v == null || v === "" || (Array.isArray(v) && v.length === 0)) return false;
if (typeof v === "object" && !Array.isArray(v) && Object.values(v).every((x) => x == null || x === "")) return false;
return true;
});
if (entries.length === 0) return data;
return data.filter((item) =>
entries.every(([fieldName, filterValue]) => {
const field = fields[fieldName];
if (!field) return true;
const itemValue = item[fieldName];
if (typeof filterValue === "object" && !Array.isArray(filterValue)) {
if (field.type === "number") {
if (filterValue.min != null && filterValue.min !== "" && Number(itemValue) < Number(filterValue.min)) return false;
if (filterValue.max != null && filterValue.max !== "" && Number(itemValue) > Number(filterValue.max)) return false;
return true;
}
if (field.type === "datetime" || field.type === "date") {
const itemTime = new Date(itemValue).getTime();
if (filterValue.start && new Date(filterValue.start).getTime() > itemTime) return false;
if (filterValue.end && new Date(filterValue.end).getTime() < itemTime) return false;
return true;
}
return true;
}
if (Array.isArray(filterValue)) {
if (field.type === "array" && Array.isArray(itemValue)) {
return itemValue.some((el: any) =>
filterValue.includes(getDisplayString(el, field))
);
}
if (itemValue && typeof itemValue === "object") {
return filterValue.includes(getDisplayString(itemValue, field));
}
return filterValue.includes(String(itemValue));
}
if (!filterValue) return true;
if (field.type === "boolean") {
return String(itemValue) === filterValue;
}
if (field.type === "array" && Array.isArray(itemValue)) {
return itemValue.some((el: any) =>
getDisplayString(el, field) === String(filterValue)
);
}
if (itemValue && typeof itemValue === "object") {
return getDisplayString(itemValue, field) === String(filterValue);
}
return String(itemValue) === String(filterValue);
})
);
}
export default function ResourceView({ config, onNavigateToResource, fieldComponents }: ResourceViewProps) {
const { id } = useParams();
const location = useLocation();
const navigate = useNavigate();
const isCreate = location.pathname.endsWith('/create');
const isEdit = location.pathname.includes('/edit/');
const isView = !!id && !isEdit;
const isList = !id && !isCreate;
const isServer = config.filterOptions?.mode !== "client";
const [paginationModel, setPaginationModel] = React.useState<GridPaginationModel>({
page: 0,
pageSize: 10,
});
const [appliedFilters, setAppliedFilters] = React.useState<Record<string, any>>({});
const { useList, useRead, useCreate, useUpdate, useDelete, components } = useResource(config, { fieldComponents });
const queryParams = React.useMemo(() => {
if (!isServer) return { limit: 10 };
return {
skip: paginationModel.page * paginationModel.pageSize,
limit: paginationModel.pageSize,
};
}, [isServer, paginationModel]);
const listQuery = useList(queryParams);
const itemQuery = useRead(id || "");
const rawData = listQuery.data?.data || [];
const totalCount = listQuery.data?.total;
const filteredData = React.useMemo(
() => (isServer ? rawData : applyClientFilters(rawData, appliedFilters, config.fields)),
[isServer, rawData, appliedFilters, config.fields]
);
const createMutation = useCreate();
const updateMutation = useUpdate();
const deleteMutation = useDelete();
const handleEdit = (item: any) => {
navigate(`/admin/${config.name}/edit/${item[config.primaryKey]}`);
};
const handleCreate = () => {
navigate(`/admin/${config.name}/create`);
};
const handleSave = async (formData: any) => {
try {
if (isEdit) {
await updateMutation.mutateAsync({ id: id!, data: formData });
} else {
await createMutation.mutateAsync(formData);
}
navigate(`/admin/${config.name}`);
} catch (err) {
console.error('Save failed:', err);
}
};
const handleDelete = async (itemId: string) => {
if (window.confirm('Are you sure you want to delete this item?')) {
await deleteMutation.mutateAsync(itemId);
}
};
if (isList && listQuery.isLoading) return <CircularProgress />;
if ((isEdit || isView) && itemQuery.isLoading) return <CircularProgress />;
return (
<Box>
{isList ? (
<Box>
{!isServer && config.filterOptions?.fields && config.filterOptions.fields.length > 0 && (
<FilterBar
fields={config.fields}
filterableFields={config.filterOptions.fields}
mode={config.filterOptions?.mode || "server"}
data={rawData}
appliedValues={appliedFilters}
onApply={setAppliedFilters}
onClear={() => setAppliedFilters({})}
/>
)}
<EnhancedTable
config={config}
data={filteredData}
total={isServer ? totalCount : filteredData.length}
paginationModel={isServer ? paginationModel : undefined}
onPaginationModelChange={isServer ? setPaginationModel : undefined}
loading={listQuery.isFetching}
onEdit={handleEdit}
onDelete={handleDelete}
onCreate={handleCreate}
onNavigateToResource={(res, id) => navigate(`/admin/${res}/${id}`)}
/>
</Box>
) : (
<Paper sx={{ p: 4 }}>
{components && <components.GenericForm
config={config}
initialData={isCreate ? null : itemQuery.data}
onSave={handleSave}
onCancel={() => navigate(`/admin/${config.name}`)}
loading={createMutation.isPending || updateMutation.isPending}
readOnly={isView}
onEditClick={() => navigate(`/admin/${config.name}/edit/${id}`)}
/>}
</Paper>
)}
</Box>
);
}