Skip to content

Commit 6b6cdfb

Browse files
authored
Merge pull request #1045 from objectstack-ai/copilot/fix-minified-react-error-again
2 parents 8c16f62 + 25db469 commit 6b6cdfb

File tree

1 file changed

+111
-103
lines changed

1 file changed

+111
-103
lines changed

apps/console/src/components/RecordDetailView.tsx

Lines changed: 111 additions & 103 deletions
Original file line numberDiff line numberDiff line change
@@ -311,6 +311,117 @@ export function RecordDetailView({ dataSource, objects, onEdit }: RecordDetailVi
311311
queueMicrotask(() => setIsLoading(false));
312312
}, [objectName, recordId]);
313313

314+
// Build detail schema — must be before early returns to keep hook count
315+
// consistent across renders and avoid React error #310.
316+
const detailSchema: DetailViewSchema = useMemo(() => {
317+
if (!objectDef) {
318+
return { type: 'detail-view' } as DetailViewSchema;
319+
}
320+
321+
// Auto-detect primary field: prefer objectDef metadata, then 'name' or 'title' heuristic
322+
const primaryField = objectDef.primaryField
323+
|| Object.keys(objectDef.fields || {}).find(
324+
(key) => key === 'name' || key === 'title'
325+
);
326+
327+
// Build sections: prefer form sections from objectDef, fallback to flat field list
328+
const formSections = objectDef.views?.form?.sections;
329+
const sections = formSections && formSections.length > 0
330+
? formSections.map((sec: any) => ({
331+
title: sec.title,
332+
collapsible: sec.collapsible,
333+
defaultCollapsed: sec.defaultCollapsed,
334+
fields: (sec.fields || []).map((f: any) => {
335+
const fieldName = typeof f === 'string' ? f : f.name;
336+
const fieldDef = objectDef.fields[fieldName];
337+
if (!fieldDef) {
338+
console.warn(`[RecordDetailView] Field "${fieldName}" not found in ${objectDef.name} definition`);
339+
return { name: fieldName, label: fieldName };
340+
}
341+
const refTarget = fieldDef.reference_to || fieldDef.reference;
342+
return {
343+
name: fieldName,
344+
label: fieldDef.label || fieldName,
345+
type: fieldDef.type || 'text',
346+
...(fieldDef.options && { options: fieldDef.options }),
347+
...(refTarget && { reference_to: refTarget }),
348+
...(fieldDef.reference_field && { reference_field: fieldDef.reference_field }),
349+
...(fieldDef.currency && { currency: fieldDef.currency }),
350+
};
351+
}),
352+
}))
353+
: [
354+
{
355+
title: t('detail.details', { defaultValue: 'Details' }),
356+
fields: Object.keys(objectDef.fields || {}).map(key => {
357+
const fieldDef = objectDef.fields[key];
358+
const refTarget = fieldDef.reference_to || fieldDef.reference;
359+
return {
360+
name: key,
361+
label: fieldDef.label || key,
362+
type: fieldDef.type || 'text',
363+
...(fieldDef.options && { options: fieldDef.options }),
364+
...(refTarget && { reference_to: refTarget }),
365+
...(fieldDef.reference_field && { reference_field: fieldDef.reference_field }),
366+
...(fieldDef.currency && { currency: fieldDef.currency }),
367+
};
368+
}),
369+
},
370+
];
371+
372+
// Filter actions for record_header location and deduplicate by name
373+
const recordHeaderActions = (() => {
374+
const seen = new Set<string>();
375+
return (objectDef.actions || []).filter((a: any) => {
376+
if (!a.locations?.includes('record_header')) return false;
377+
if (!a.name) return true;
378+
if (seen.has(a.name)) return false;
379+
seen.add(a.name);
380+
return true;
381+
});
382+
})();
383+
384+
// Build highlightFields: exclusively from objectDef metadata (no hardcoded fallback)
385+
const highlightFields: HighlightField[] = objectDef.views?.detail?.highlightFields ?? [];
386+
387+
// Build sectionGroups from objectDef detail/form config if available
388+
const sectionGroups: SectionGroup[] | undefined =
389+
objectDef.views?.detail?.sectionGroups ?? objectDef.views?.form?.sectionGroups;
390+
391+
// Build related entries from reverse-reference child objects
392+
const related = childRelations.map(({ childObject, childLabel }) => ({
393+
title: childLabel,
394+
type: 'table' as const,
395+
api: childObject,
396+
data: childRelatedData[childObject] || [],
397+
}));
398+
399+
return {
400+
type: 'detail-view' as const,
401+
objectName: objectDef.name,
402+
resourceId: pureRecordId,
403+
showBack: true,
404+
onBack: 'history',
405+
showEdit: true,
406+
title: objectDef.label,
407+
primaryField,
408+
sections,
409+
autoTabs: true,
410+
autoDiscoverRelated: true,
411+
...(related.length > 0 && { related }),
412+
...(highlightFields.length > 0 && { highlightFields }),
413+
...(sectionGroups && sectionGroups.length > 0 && { sectionGroups }),
414+
...(recordHeaderActions.length > 0 && {
415+
actions: [{
416+
type: 'action:bar',
417+
location: 'record_header',
418+
actions: recordHeaderActions,
419+
} as any],
420+
}),
421+
};
422+
// eslint-disable-next-line react-hooks/exhaustive-deps
423+
}, [objectDef?.name, pureRecordId, childRelatedData, actionRefreshKey]);
424+
314425
if (isLoading) {
315426
return <SkeletonDetail />;
316427
}
@@ -332,109 +443,6 @@ export function RecordDetailView({ dataSource, objects, onEdit }: RecordDetailVi
332443
);
333444
}
334445

335-
// Auto-detect primary field: prefer objectDef metadata, then 'name' or 'title' heuristic
336-
const primaryField = objectDef.primaryField
337-
|| Object.keys(objectDef.fields || {}).find(
338-
(key) => key === 'name' || key === 'title'
339-
);
340-
341-
// Build sections: prefer form sections from objectDef, fallback to flat field list
342-
const formSections = objectDef.views?.form?.sections;
343-
const sections = formSections && formSections.length > 0
344-
? formSections.map((sec: any) => ({
345-
title: sec.title,
346-
collapsible: sec.collapsible,
347-
defaultCollapsed: sec.defaultCollapsed,
348-
fields: (sec.fields || []).map((f: any) => {
349-
const fieldName = typeof f === 'string' ? f : f.name;
350-
const fieldDef = objectDef.fields[fieldName];
351-
if (!fieldDef) {
352-
console.warn(`[RecordDetailView] Field "${fieldName}" not found in ${objectDef.name} definition`);
353-
return { name: fieldName, label: fieldName };
354-
}
355-
const refTarget = fieldDef.reference_to || fieldDef.reference;
356-
return {
357-
name: fieldName,
358-
label: fieldDef.label || fieldName,
359-
type: fieldDef.type || 'text',
360-
...(fieldDef.options && { options: fieldDef.options }),
361-
...(refTarget && { reference_to: refTarget }),
362-
...(fieldDef.reference_field && { reference_field: fieldDef.reference_field }),
363-
...(fieldDef.currency && { currency: fieldDef.currency }),
364-
};
365-
}),
366-
}))
367-
: [
368-
{
369-
title: t('detail.details', { defaultValue: 'Details' }),
370-
fields: Object.keys(objectDef.fields || {}).map(key => {
371-
const fieldDef = objectDef.fields[key];
372-
const refTarget = fieldDef.reference_to || fieldDef.reference;
373-
return {
374-
name: key,
375-
label: fieldDef.label || key,
376-
type: fieldDef.type || 'text',
377-
...(fieldDef.options && { options: fieldDef.options }),
378-
...(refTarget && { reference_to: refTarget }),
379-
...(fieldDef.reference_field && { reference_field: fieldDef.reference_field }),
380-
...(fieldDef.currency && { currency: fieldDef.currency }),
381-
};
382-
}),
383-
},
384-
];
385-
386-
// Filter actions for record_header location and deduplicate by name
387-
const recordHeaderActions = (() => {
388-
const seen = new Set<string>();
389-
return (objectDef.actions || []).filter((a: any) => {
390-
if (!a.locations?.includes('record_header')) return false;
391-
if (!a.name) return true;
392-
if (seen.has(a.name)) return false;
393-
seen.add(a.name);
394-
return true;
395-
});
396-
})();
397-
398-
// Build highlightFields: exclusively from objectDef metadata (no hardcoded fallback)
399-
const highlightFields: HighlightField[] = objectDef.views?.detail?.highlightFields ?? [];
400-
401-
// Build sectionGroups from objectDef detail/form config if available
402-
const sectionGroups: SectionGroup[] | undefined =
403-
objectDef.views?.detail?.sectionGroups ?? objectDef.views?.form?.sectionGroups;
404-
405-
// Build related entries from reverse-reference child objects
406-
const related = childRelations.map(({ childObject, childLabel }) => ({
407-
title: childLabel,
408-
type: 'table' as const,
409-
api: childObject,
410-
data: childRelatedData[childObject] || [],
411-
}));
412-
413-
const detailSchema: DetailViewSchema = useMemo(() => ({
414-
type: 'detail-view',
415-
objectName: objectDef.name,
416-
resourceId: pureRecordId,
417-
showBack: true,
418-
onBack: 'history',
419-
showEdit: true,
420-
title: objectDef.label,
421-
primaryField,
422-
sections,
423-
autoTabs: true,
424-
autoDiscoverRelated: true,
425-
...(related.length > 0 && { related }),
426-
...(highlightFields.length > 0 && { highlightFields }),
427-
...(sectionGroups && sectionGroups.length > 0 && { sectionGroups }),
428-
...(recordHeaderActions.length > 0 && {
429-
actions: [{
430-
type: 'action:bar',
431-
location: 'record_header',
432-
actions: recordHeaderActions,
433-
} as any],
434-
}),
435-
// eslint-disable-next-line react-hooks/exhaustive-deps
436-
}), [objectDef.name, pureRecordId, childRelatedData, actionRefreshKey]);
437-
438446
return (
439447
<div className="h-full bg-background overflow-hidden flex flex-col relative">
440448
<div className="absolute top-2 sm:top-4 right-2 sm:right-4 z-50 flex items-center gap-2">

0 commit comments

Comments
 (0)