4 Commits

Author SHA1 Message Date
b198f6748e fixes 2026-06-13 21:36:14 +05:30
8e2bd70e5f fixes 2026-06-13 19:56:55 +05:30
dd7c2246df fixes 2026-06-13 19:54:11 +05:30
f2edf7ade7 fixes 2026-06-13 16:19:40 +05:30
5 changed files with 81 additions and 48 deletions

View File

@@ -45,12 +45,17 @@ export default function GenericForm({
let relations: string[] = []; let relations: string[] = [];
Object.values(fields).forEach(field => { Object.values(fields).forEach(field => {
if (field.relation) relations.push(field.relation); if (field.relation) relations.push(field.relation);
if (field.refers) relations.push(field.refers);
if (field.schema) relations = [...relations, ...getRelationFields(field.schema)]; if (field.schema) relations = [...relations, ...getRelationFields(field.schema)];
}); });
return Array.from(new Set(relations)); return Array.from(new Set(relations));
}; };
const allRelations = React.useMemo(() => getRelationFields(config.fields), [config.fields]); const allRelations = React.useMemo(() => {
const rels = getRelationFields(config.fields);
console.log('Form resource', config.name, 'relations discovered:', rels);
return rels;
}, [config.fields]);
// 2. Parallel fetch for all related resource lists // 2. Parallel fetch for all related resource lists
const queries = useQueries({ const queries = useQueries({
@@ -58,10 +63,12 @@ export default function GenericForm({
const relatedRes = appConfig?.resources.find(r => r.name === relName); const relatedRes = appConfig?.resources.find(r => r.name === relName);
// eslint-disable-next-line react-hooks/rules-of-hooks // eslint-disable-next-line react-hooks/rules-of-hooks
const { getListQueryOptions } = useResource(relatedRes!, { fieldComponents }); const { getListQueryOptions } = useResource(relatedRes!, { fieldComponents });
return { const queryOpts = {
...getListQueryOptions(), ...getListQueryOptions(),
enabled: !!relatedRes, enabled: !!relatedRes,
}; };
console.log('Query for relation', relName, 'resource', relatedRes?.name, 'enabled', !!relatedRes);
return queryOpts;
}), }),
}); });
@@ -70,9 +77,12 @@ export default function GenericForm({
const relationDataMap = React.useMemo(() => { const relationDataMap = React.useMemo(() => {
const map: Record<string, any[]> = {}; const map: Record<string, any[]> = {};
allRelations.forEach((relName, index) => { allRelations.forEach((relName, index) => {
// @ts-ignore const queryResult = queries[index];
map[relName] = queries[index].data || []; const dataArray = queryResult?.data && Array.isArray(queryResult.data) ? queryResult.data : (queryResult?.data?.data ?? []);
}); console.log('Relation query result for', relName, 'raw:', queryResult?.data);
console.log('Relation data for', relName, ':', dataArray.slice(0, 1));
map[relName] = dataArray;
});
return map; return map;
}, [allRelations, queries]); }, [allRelations, queries]);

View File

@@ -3,23 +3,37 @@ import { getFieldOptions } from '../../utils/options';
import { FieldComponentProps } from '../../types/overrides'; import { FieldComponentProps } from '../../types/overrides';
export default function RelationField({ field, value, onChange, disabled, relationDataMap = {} }: FieldComponentProps) { export default function RelationField({ field, value, onChange, disabled, relationDataMap = {} }: FieldComponentProps) {
if (!field.relation || !relationDataMap[field.relation]) { console.log('RelationField render', field.label, 'enumOption:', field.enumOption, 'value prop:', value);
return null; const relationName = field.relation ?? (field as any).refers;
} if (!relationName || !relationDataMap[relationName]) {
throw new Error(`Relation data for "${relationName}" is missing cannot render options for field "${field.label}"`);
}
const relationData = relationDataMap[field.relation];
const relationData = relationDataMap[relationName];
const isArrayRelation = field.type === 'array'; const isArrayRelation = field.type === 'array';
const options = getFieldOptions(field, relationData); const options = getFieldOptions(field, relationData);
console.log('Options for', field.label, 'keys:', options.map(o=>o.key));
if (options.length === 0) {
throw new Error(`No selectable options available for field "${field.label}" (relation "${relationName}")`);
}
const keyField = field.enumOption?.key ?? 'id'; const keyField = field.enumOption?.key ?? 'id';
const normalizedValue = (() => { const normalizedValue = (() => {
if (isArrayRelation && Array.isArray(value)) { if (isArrayRelation && Array.isArray(value)) {
return value.map((v: any) => (v != null && typeof v === 'object' ? String(v[keyField] ?? '') : String(v))); return value.map((v: any) => {
if (v != null && typeof v === 'object') {
return String(v[keyField] ?? '');
}
return String(v);
});
} }
if (value != null && typeof value === 'object') { if (value != null && typeof value === 'object') {
return String(value[keyField] ?? ''); return String(value[keyField] ?? '');
} }
return value ?? (isArrayRelation ? [] : ""); // Primitive (number/string) coerce to string for Select compatibility
return value != null ? String(value) : (isArrayRelation ? [] : "");
})(); })();
return ( return (
@@ -33,10 +47,13 @@ export default function RelationField({ field, value, onChange, disabled, relati
onChange={(e) => onChange(e.target.value)} onChange={(e) => onChange(e.target.value)}
disabled={disabled} disabled={disabled}
renderValue={(selected: any) => { renderValue={(selected: any) => {
console.log('Select renderValue for', field.label, 'selected:', selected);
if (isArrayRelation) { if (isArrayRelation) {
return (selected as string[]).map(k => options.find(o => o.key === k)?.value ?? k).join(', '); return (selected as string[]).map(k => options.find(o => o.key === k)?.value ?? k).join(', ');
} }
return options.find(o => o.key === selected)?.value ?? selected; const display = options.find(o => o.key === selected)?.value ?? selected;
console.log('Display value for', field.label, ':', display);
return display;
}} }}
> >
{options.map((opt) => ( {options.map((opt) => (

View File

@@ -192,38 +192,38 @@ export async function loadConfigFromOpenApi(baseUrl: string, configuration: Reco
} }
// 2. Generate ResourceConfig for each identified resource // 2. Generate ResourceConfig for each identified resource
for (const [name, info] of Object.entries(resourcePaths)) { for (const [name, info] of Object.entries(resourcePaths)) {
const listPath = info.listPath || `/${name}`; const listPath = info.listPath || `/${name}`;
const listOp = paths[listPath]?.get; const listOp = paths[listPath]?.get;
if (!listOp || !info.schemaObj) continue; // Always create a resource entry even if the list operation or schema is missing.
// This enables relation lookups for resources that only have overrides (e.g., accounts, tags).
// If we lack a schema we fall back to an empty field map.
const hasList = !!listOp;
const schema = info.schemaObj;
const label = name.charAt(0).toUpperCase() + name.slice(1, -1);
const pluralLabel = name.charAt(0).toUpperCase() + name.slice(1);
const schema = info.schemaObj; const fields = schema ? parseSchemaFields(schema, name, schemaToResourceMap, configuration) : {};
const label = name.charAt(0).toUpperCase() + name.slice(1, -1);
const pluralLabel = name.charAt(0).toUpperCase() + name.slice(1);
console.log('before parseSchemaFields configuration...', configuration['accounts']['referenceOptions']) const resourceOverride = configuration[name] || {};
const fields = parseSchemaFields(schema, name, schemaToResourceMap, configuration); const fo = resourceOverride.filterOptions || {};
const resourceOverride = configuration[name] || {};
const fo = resourceOverride.filterOptions || {};
resources.push({
name,
label: schema.title || label,
pluralLabel: pluralLabel,
endpoint: listPath,
primaryKey: "id",
fields,
pagination: resourceOverride.pagination,
hidden: resourceOverride.hidden,
filterOptions: {
mode: fo.mode || "server",
fields: fo.fields,
},
});
}
resources.push({
name,
label: schema?.title || label,
pluralLabel: pluralLabel,
endpoint: listPath,
primaryKey: "id",
fields,
pagination: resourceOverride.pagination,
hidden: resourceOverride.hidden,
filterOptions: {
mode: fo.mode || "server",
fields: fo.fields,
},
});
console.log('Loaded resource:', name, 'endpoint:', listPath, 'fields count:', Object.keys(fields).length);
}
// Collect standalone enum schemas (e.g. FetchRequestStatus, AccountType, etc.) // Collect standalone enum schemas (e.g. FetchRequestStatus, AccountType, etc.)
const enums: Record<string, string[]> = {}; const enums: Record<string, string[]> = {};
const apiDoc = api as any; const apiDoc = api as any;

View File

@@ -8,6 +8,7 @@ export function resolveTemplate(template: string, item: any): string {
} }
export function getFieldOptions(field: ResourceField, relationData?: any[]): SelectOption[] { export function getFieldOptions(field: ResourceField, relationData?: any[]): SelectOption[] {
console.log('getFieldOptions called for field', field.label, 'type', field.type, 'enumOption', field.enumOption);
if (field.type === 'enum') { if (field.type === 'enum') {
return (field.options ?? []).map(opt => ({ return (field.options ?? []).map(opt => ({
key: opt, key: opt,
@@ -17,6 +18,10 @@ export function getFieldOptions(field: ResourceField, relationData?: any[]): Sel
if (field.relation) { if (field.relation) {
const data = Array.isArray(relationData) ? relationData : []; const data = Array.isArray(relationData) ? relationData : [];
console.log('Getting options for relation', field.relation, 'data count:', data.length);
if (data.length === 0) {
throw new Error(`Relation data for "${field.relation}" is missing or empty cannot build options for field "${field.label}"`);
}
const enumOption = field.enumOption; const enumOption = field.enumOption;
if (!enumOption) { if (!enumOption) {
throw new Error( throw new Error(
@@ -24,11 +29,12 @@ export function getFieldOptions(field: ResourceField, relationData?: any[]): Sel
`Define referenceOptions.enumOption in the configuration for resource "${field.relation}".` `Define referenceOptions.enumOption in the configuration for resource "${field.relation}".`
); );
} }
const result = data.map(item => ({
return data.map(item => ({ key: String(item[enumOption.key] ?? item.id ?? item._id),
key: String(item[enumOption.key]),
value: resolveTemplate(enumOption.value, item), value: resolveTemplate(enumOption.value, item),
})); }));
console.log('Option map for', field.relation, 'first entry:', data[0], 'result key:', result[0]?.key);
return result;
} }
return []; return [];

View File

@@ -76,7 +76,7 @@ export const configuration: Record<string, ResourceOverride> = {
accounts: { accounts: {
referenceOptions: { referenceOptions: {
enumOption: { enumOption: {
key: 'id', key: '_id',
value: '{name} - XX{number}', value: '{name} - XX{number}',
}, },
autoComplete: true, autoComplete: true,
@@ -86,7 +86,7 @@ export const configuration: Record<string, ResourceOverride> = {
tags: { tags: {
referenceOptions: { referenceOptions: {
enumOption: { enumOption: {
key: 'id', key: '_id',
value: '{icon} {name}', value: '{icon} {name}',
}, },
autoComplete: true, autoComplete: true,