Compare commits
5 Commits
f345dafb46
...
openapi-no
| Author | SHA1 | Date | |
|---|---|---|---|
| 2ea8cf1f8a | |||
| b198f6748e | |||
| 8e2bd70e5f | |||
| dd7c2246df | |||
| f2edf7ade7 |
@@ -45,12 +45,17 @@ export default function GenericForm({
|
||||
let relations: string[] = [];
|
||||
Object.values(fields).forEach(field => {
|
||||
if (field.relation) relations.push(field.relation);
|
||||
if (field.refers) relations.push(field.refers);
|
||||
if (field.schema) relations = [...relations, ...getRelationFields(field.schema)];
|
||||
});
|
||||
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
|
||||
const queries = useQueries({
|
||||
@@ -58,10 +63,12 @@ export default function GenericForm({
|
||||
const relatedRes = appConfig?.resources.find(r => r.name === relName);
|
||||
// eslint-disable-next-line react-hooks/rules-of-hooks
|
||||
const { getListQueryOptions } = useResource(relatedRes!, { fieldComponents });
|
||||
return {
|
||||
...getListQueryOptions(),
|
||||
enabled: !!relatedRes,
|
||||
const queryOpts = {
|
||||
...getListQueryOptions(),
|
||||
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 map: Record<string, any[]> = {};
|
||||
allRelations.forEach((relName, index) => {
|
||||
// @ts-ignore
|
||||
map[relName] = queries[index].data || [];
|
||||
});
|
||||
const queryResult = queries[index];
|
||||
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;
|
||||
}, [allRelations, queries]);
|
||||
|
||||
|
||||
@@ -3,23 +3,37 @@ import { getFieldOptions } from '../../utils/options';
|
||||
import { FieldComponentProps } from '../../types/overrides';
|
||||
|
||||
export default function RelationField({ field, value, onChange, disabled, relationDataMap = {} }: FieldComponentProps) {
|
||||
if (!field.relation || !relationDataMap[field.relation]) {
|
||||
return null;
|
||||
}
|
||||
// console.log('RelationField render', field.label, 'enumOption:', field.enumOption, 'value prop:', value);
|
||||
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 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 normalizedValue = (() => {
|
||||
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') {
|
||||
return String(value[keyField] ?? '');
|
||||
}
|
||||
return value ?? (isArrayRelation ? [] : "");
|
||||
// Primitive (number/string) – coerce to string for Select compatibility
|
||||
return value != null ? String(value) : (isArrayRelation ? [] : "");
|
||||
})();
|
||||
|
||||
return (
|
||||
@@ -33,10 +47,13 @@ export default function RelationField({ field, value, onChange, disabled, relati
|
||||
onChange={(e) => onChange(e.target.value)}
|
||||
disabled={disabled}
|
||||
renderValue={(selected: any) => {
|
||||
// console.log('Select renderValue for', field.label, 'selected:', selected);
|
||||
if (isArrayRelation) {
|
||||
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) => (
|
||||
|
||||
@@ -65,7 +65,7 @@ function parseSchemaFields(
|
||||
const fields: Record<string, ResourceField> = {};
|
||||
const { properties, required } = mergeProperties(schema);
|
||||
const overrides = configuration[resourceName]?.fields || {};
|
||||
console.log('inside parseSchemaFields configuration...', configuration['accounts']['referenceOptions'])
|
||||
// console.log('inside parseSchemaFields configuration...', configuration['accounts']['referenceOptions'])
|
||||
|
||||
for (const [key, prop] of Object.entries(properties) as [string, any]) {
|
||||
// Resolve oneOf/anyOf by merging all branch properties
|
||||
@@ -117,7 +117,7 @@ function parseSchemaFields(
|
||||
|
||||
// Propagate enumOption from target resource config, or derive from target schema
|
||||
const explicitEnumOption = configuration[relation].referenceOptions.enumOption;
|
||||
console.log('if relation configuration...', configuration['accounts']['referenceOptions'])
|
||||
// console.log('if relation configuration...', configuration['accounts']['referenceOptions'])
|
||||
if (explicitEnumOption) {
|
||||
fields[key].enumOption = explicitEnumOption;
|
||||
} else {
|
||||
@@ -133,7 +133,7 @@ function parseSchemaFields(
|
||||
|
||||
// Recursively parse nested objects (only if not a relation)
|
||||
if (fields[key].type === "object" && resolvedProp.properties && !relation) {
|
||||
console.log('recursive configuration...', configuration['accounts']['referenceOptions'])
|
||||
// console.log('recursive configuration...', configuration['accounts']['referenceOptions'])
|
||||
fields[key].schema = parseSchemaFields(resolvedProp, resourceName, schemaToResourceMap, configuration);
|
||||
}
|
||||
}
|
||||
@@ -145,7 +145,7 @@ function parseSchemaFields(
|
||||
* Scans paths to identify resources and their basic configuration
|
||||
*/
|
||||
export async function loadConfigFromOpenApi(baseUrl: string, configuration: Record<string, any> = {}, profileConfiguration: any = {}): Promise<AppConfig> {
|
||||
console.log('init configuration...', configuration['accounts']['referenceOptions'])
|
||||
// console.log('init configuration...', configuration['accounts']['referenceOptions'])
|
||||
// Use SwaggerParser to dereference the spec.
|
||||
// Dereferencing preserves object identity for $ref targets.
|
||||
const api = await SwaggerParser.dereference(
|
||||
@@ -192,38 +192,38 @@ export async function loadConfigFromOpenApi(baseUrl: string, configuration: Reco
|
||||
}
|
||||
|
||||
// 2. Generate ResourceConfig for each identified resource
|
||||
for (const [name, info] of Object.entries(resourcePaths)) {
|
||||
const listPath = info.listPath || `/${name}`;
|
||||
const listOp = paths[listPath]?.get;
|
||||
if (!listOp || !info.schemaObj) continue;
|
||||
for (const [name, info] of Object.entries(resourcePaths)) {
|
||||
const listPath = info.listPath || `/${name}`;
|
||||
const listOp = paths[listPath]?.get;
|
||||
// Always create a resource entry even if the list operation or schema is missing.
|
||||
// This enables relation look‑ups 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 label = name.charAt(0).toUpperCase() + name.slice(1, -1);
|
||||
const pluralLabel = name.charAt(0).toUpperCase() + name.slice(1);
|
||||
const fields = schema ? parseSchemaFields(schema, name, schemaToResourceMap, configuration) : {};
|
||||
|
||||
console.log('before parseSchemaFields configuration...', configuration['accounts']['referenceOptions'])
|
||||
const fields = parseSchemaFields(schema, name, schemaToResourceMap, configuration);
|
||||
|
||||
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,
|
||||
},
|
||||
});
|
||||
}
|
||||
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,
|
||||
},
|
||||
});
|
||||
// console.log('Loaded resource:', name, 'endpoint:', listPath, 'fields count:', Object.keys(fields).length);
|
||||
}
|
||||
// Collect standalone enum schemas (e.g. FetchRequestStatus, AccountType, etc.)
|
||||
const enums: Record<string, string[]> = {};
|
||||
const apiDoc = api as any;
|
||||
|
||||
@@ -8,6 +8,7 @@ export function resolveTemplate(template: string, item: any): string {
|
||||
}
|
||||
|
||||
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') {
|
||||
return (field.options ?? []).map(opt => ({
|
||||
key: opt,
|
||||
@@ -17,6 +18,10 @@ export function getFieldOptions(field: ResourceField, relationData?: any[]): Sel
|
||||
|
||||
if (field.relation) {
|
||||
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;
|
||||
if (!enumOption) {
|
||||
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}".`
|
||||
);
|
||||
}
|
||||
|
||||
return data.map(item => ({
|
||||
key: String(item[enumOption.key]),
|
||||
const result = data.map(item => ({
|
||||
key: String(item[enumOption.key] ?? item.id ?? item._id),
|
||||
value: resolveTemplate(enumOption.value, item),
|
||||
}));
|
||||
// console.log('Option map for', field.relation, 'first entry:', data[0], 'result key:', result[0]?.key);
|
||||
return result;
|
||||
}
|
||||
|
||||
return [];
|
||||
|
||||
@@ -76,7 +76,7 @@ export const configuration: Record<string, ResourceOverride> = {
|
||||
accounts: {
|
||||
referenceOptions: {
|
||||
enumOption: {
|
||||
key: 'id',
|
||||
key: '_id',
|
||||
value: '{name} - XX{number}',
|
||||
},
|
||||
autoComplete: true,
|
||||
@@ -86,7 +86,7 @@ export const configuration: Record<string, ResourceOverride> = {
|
||||
tags: {
|
||||
referenceOptions: {
|
||||
enumOption: {
|
||||
key: 'id',
|
||||
key: '_id',
|
||||
value: '{icon} {name}',
|
||||
},
|
||||
autoComplete: true,
|
||||
|
||||
Reference in New Issue
Block a user