66 * the object field definitions.
77 */
88
9- import { useState , useEffect , useCallback } from 'react' ;
9+ import { useState , useEffect , useCallback , useMemo } from 'react' ;
1010import { useParams } from 'react-router-dom' ;
1111import { DetailView , RecordChatterPanel } from '@object-ui/plugin-detail' ;
1212import { Empty , EmptyTitle , EmptyDescription } from '@object-ui/components' ;
@@ -35,13 +35,53 @@ export function RecordDetailView({ dataSource, objects, onEdit }: RecordDetailVi
3535 const [ isLoading , setIsLoading ] = useState ( true ) ;
3636 const [ feedItems , setFeedItems ] = useState < FeedItem [ ] > ( [ ] ) ;
3737 const [ recordViewers , setRecordViewers ] = useState < PresenceUser [ ] > ( [ ] ) ;
38+ const [ childRelatedData , setChildRelatedData ] = useState < Record < string , any [ ] > > ( { } ) ;
3839 const objectDef = objects . find ( ( o : any ) => o . name === objectName ) ;
3940
4041 // Use the URL recordId as-is — it contains the actual record _id.
4142 // Navigation code passes `record._id || record.id` directly into the URL
4243 // without adding any prefix, so no stripping is needed.
4344 const pureRecordId = recordId ;
4445
46+ // Discover reverse references: other objects with lookup/master_detail fields
47+ // pointing to the current object (e.g., order_item.order → order).
48+ const childRelations = useMemo ( ( ) => {
49+ if ( ! objectDef || ! objects ) return [ ] ;
50+ const relations : Array < { childObject : string ; childLabel : string ; referenceField : string } > = [ ] ;
51+ for ( const obj of objects ) {
52+ if ( obj . name === objectDef . name ) continue ;
53+ for ( const [ fieldName , fieldDef ] of Object . entries < any > ( obj . fields || { } ) ) {
54+ if (
55+ fieldDef &&
56+ ( fieldDef . type === 'lookup' || fieldDef . type === 'master_detail' ) &&
57+ fieldDef . reference_to === objectDef . name
58+ ) {
59+ relations . push ( {
60+ childObject : obj . name ,
61+ childLabel : obj . label || obj . name ,
62+ referenceField : fieldName ,
63+ } ) ;
64+ }
65+ }
66+ }
67+ return relations ;
68+ } , [ objectDef , objects ] ) ;
69+
70+ // Fetch related child records for each reverse reference
71+ useEffect ( ( ) => {
72+ if ( ! dataSource || ! pureRecordId || childRelations . length === 0 ) return ;
73+ for ( const { childObject, referenceField } of childRelations ) {
74+ dataSource . find ( childObject , {
75+ $filter : `${ referenceField } eq '${ pureRecordId } '` ,
76+ } )
77+ . then ( ( res : any ) => {
78+ const items = Array . isArray ( res ) ? res : res ?. data || [ ] ;
79+ setChildRelatedData ( prev => ( { ...prev , [ childObject ] : items } ) ) ;
80+ } )
81+ . catch ( ( ) => { } ) ;
82+ }
83+ } , [ dataSource , pureRecordId , childRelations ] ) ;
84+
4585 const currentUser = user
4686 ? { id : user . id , name : user . name , avatar : user . image }
4787 : FALLBACK_USER ;
@@ -275,6 +315,13 @@ export function RecordDetailView({ dataSource, objects, onEdit }: RecordDetailVi
275315 const sectionGroups : SectionGroup [ ] | undefined =
276316 objectDef . views ?. detail ?. sectionGroups ?? objectDef . views ?. form ?. sectionGroups ;
277317
318+ // Build related entries from reverse-reference child objects
319+ const related = childRelations . map ( ( { childObject, childLabel } ) => ( {
320+ title : childLabel ,
321+ type : 'table' as const ,
322+ data : childRelatedData [ childObject ] || [ ] ,
323+ } ) ) ;
324+
278325 const detailSchema : DetailViewSchema = {
279326 type : 'detail-view' ,
280327 objectName : objectDef . name ,
@@ -287,6 +334,7 @@ export function RecordDetailView({ dataSource, objects, onEdit }: RecordDetailVi
287334 sections,
288335 autoTabs : true ,
289336 autoDiscoverRelated : true ,
337+ ...( related . length > 0 && { related } ) ,
290338 ...( highlightFields . length > 0 && { highlightFields } ) ,
291339 ...( sectionGroups && sectionGroups . length > 0 && { sectionGroups } ) ,
292340 ...( recordHeaderActions . length > 0 && {
0 commit comments