From 004a8a68769cb895d3fa72f9fdedab29f5baf1c0 Mon Sep 17 00:00:00 2001 From: Vishesh 'ironeagle' Bangotra Date: Thu, 2 Apr 2026 21:10:15 +0530 Subject: [PATCH] smarter single item components --- src_generic/components/GenericForm.tsx | 184 +++++++------------------ src_generic/hooks/useResource.ts | 10 ++ 2 files changed, 61 insertions(+), 133 deletions(-) diff --git a/src_generic/components/GenericForm.tsx b/src_generic/components/GenericForm.tsx index 131d07f..e12de41 100644 --- a/src_generic/components/GenericForm.tsx +++ b/src_generic/components/GenericForm.tsx @@ -1,20 +1,17 @@ import * as React from 'react'; import { Box, - TextField, Button, - FormControl, - InputLabel, - Select, - MenuItem, - FormControlLabel, - Checkbox, Typography, Divider, + CircularProgress, } from '@mui/material'; -import { ResourceConfig, ResourceField } from '../types/config'; +import { ResourceConfig } from '../types/config'; import { useUpload } from '../providers/UploadProvider'; -import ImageUploadField from './fields/ImageUploadField'; +import { useQueries } from '@tanstack/react-query'; +import { useResource } from '../hooks/useResource'; +import FormField from './fields/FormField'; +import { ConfigContext } from '../App'; interface GenericFormProps { config: ResourceConfig; @@ -26,8 +23,6 @@ interface GenericFormProps { onEditClick?: () => void; } -import { ConfigContext } from '../App'; - export default function GenericForm({ config, initialData = {}, @@ -42,6 +37,41 @@ export default function GenericForm({ const { uploadFile, uploading } = useUpload(); const appConfig = React.useContext(ConfigContext); + // 1. Identify all unique relations in the schema (including nested ones) + const getRelationFields = (fields: Record): string[] => { + let relations: string[] = []; + Object.values(fields).forEach(field => { + if (field.relation) relations.push(field.relation); + if (field.schema) relations = [...relations, ...getRelationFields(field.schema)]; + }); + return Array.from(new Set(relations)); + }; + + const allRelations = React.useMemo(() => getRelationFields(config.fields), [config.fields]); + + // 2. Parallel fetch for all related resource lists + const queries = useQueries({ + queries: allRelations.map(relName => { + const relatedRes = appConfig?.resources.find(r => r.name === relName); + // eslint-disable-next-line react-hooks/rules-of-hooks + const { getListQueryOptions } = useResource(relatedRes!); + return { + ...getListQueryOptions(), + enabled: !!relatedRes, + }; + }), + }); + + const isLoadingRelations = queries.some(q => q.isLoading); + + const relationDataMap = React.useMemo(() => { + const map: Record = {}; + allRelations.forEach((relName, index) => { + map[relName] = queries[index].data || []; + }); + return map; + }, [allRelations, queries]); + const handleChange = (key: string, value: any) => { if (readOnly) return; setFormData((prev: any) => ({ ...prev, [key]: value })); @@ -58,6 +88,15 @@ export default function GenericForm({ return initialData[config.primaryKey] ? `Edit ${config.label}` : `New ${config.label}`; }; + if (isLoadingRelations) { + return ( + + + Loading relationships... + + ); + } + return ( @@ -76,6 +115,7 @@ export default function GenericForm({ uploadFile={uploadFile} uploading={uploading} baseUrl={appConfig?.baseUrl || ""} + relationDataMap={relationDataMap} /> ))} @@ -96,125 +136,3 @@ export default function GenericForm({ ); } - -function FormField({ name, field, value, onChange, disabled, uploadFile, uploading, baseUrl }: any) { - const label = field.label; - - if (field.type === 'image') { - return ( - { - const url = await uploadFile(file); - if (url) onChange(url); - }} - uploading={uploading} - baseUrl={baseUrl} - /> - ); - } - - if (field.type === 'boolean') { - return ( - onChange(e.target.checked)} - disabled={disabled} - /> - } - label={label} - /> - ); - } - - if (field.type === 'enum' && field.options) { - return ( - - {label} - - - ); - } - - if (field.type === 'datetime') { - return ( - onChange(e.target.value)} - disabled={disabled} - required={field.required} - /> - ); - } - - if (field.type === 'date') { - return ( - onChange(e.target.value)} - disabled={disabled} - required={field.required} - /> - ); - } - - if (field.type === 'markdown' || field.type === 'string') { - return ( - onChange(e.target.value)} - disabled={disabled} - required={field.required} - /> - ); - } - - if (field.type === 'number') { - return ( - onChange(Number(e.target.value))} - disabled={disabled} - required={field.required} - /> - ); - } - - return ( - - ); -} diff --git a/src_generic/hooks/useResource.ts b/src_generic/hooks/useResource.ts index 5c14c4d..c732db6 100644 --- a/src_generic/hooks/useResource.ts +++ b/src_generic/hooks/useResource.ts @@ -67,11 +67,21 @@ export function useResource(config: ResourceConfig) { }, }); + // --- HELPERS FOR useQueries --- + const getListQueryOptions = (params?: any) => ({ + queryKey: [name, "list", params], + queryFn: async () => { + const res = await api.get(endpoint, { params }); + return res.data; + }, + }); + return { useList, useRead, useCreate, useUpdate, useDelete, + getListQueryOptions, }; }