@@ -21,6 +21,10 @@ import {
2121 TooltipContent ,
2222 TooltipProvider ,
2323 TooltipTrigger ,
24+ Tabs ,
25+ TabsList ,
26+ TabsTrigger ,
27+ TabsContent ,
2428} from '@object-ui/components' ;
2529import {
2630 ArrowLeft ,
@@ -40,6 +44,8 @@ import {
4044import { DetailSection } from './DetailSection' ;
4145import { DetailTabs } from './DetailTabs' ;
4246import { RelatedList } from './RelatedList' ;
47+ import { SectionGroup } from './SectionGroup' ;
48+ import { HeaderHighlight } from './HeaderHighlight' ;
4349import { RecordComments } from './RecordComments' ;
4450import { ActivityTimeline } from './ActivityTimeline' ;
4551import { SchemaRenderer } from '@object-ui/react' ;
@@ -288,6 +294,40 @@ export const DetailView: React.FC<DetailViewProps> = ({
288294 return ( ) => document . removeEventListener ( 'keydown' , handler ) ;
289295 } , [ schema . recordNavigation ] ) ;
290296
297+ // Auto-discover related lists from objectSchema reference fields
298+ const discoveredRelated = React . useMemo ( ( ) => {
299+ if ( ! schema . autoDiscoverRelated || ! objectSchema ?. fields ) return [ ] ;
300+ // Only auto-discover when no explicit related config is provided
301+ if ( schema . related && schema . related . length > 0 ) return [ ] ;
302+ const refs : Array < { title : string ; type : 'list' | 'grid' | 'table' ; objectName : string ; referenceField : string } > = [ ] ;
303+ const fields = objectSchema . fields ;
304+ for ( const [ fieldName , fieldDef ] of Object . entries < any > ( fields ) ) {
305+ if (
306+ fieldDef &&
307+ ( fieldDef . type === 'lookup' || fieldDef . type === 'master_detail' ) &&
308+ fieldDef . reference_to
309+ ) {
310+ refs . push ( {
311+ title : fieldDef . label || fieldName . charAt ( 0 ) . toUpperCase ( ) + fieldName . slice ( 1 ) ,
312+ type : 'table' ,
313+ objectName : fieldDef . reference_to ,
314+ referenceField : fieldName ,
315+ } ) ;
316+ }
317+ }
318+ return refs ;
319+ } , [ schema . autoDiscoverRelated , schema . related , objectSchema ] ) ;
320+
321+ // Merge explicit and auto-discovered related lists
322+ const effectiveRelated = React . useMemo ( ( ) => {
323+ if ( schema . related && schema . related . length > 0 ) return schema . related ;
324+ return discoveredRelated . map ( ( r ) => ( {
325+ title : r . title ,
326+ type : r . type ,
327+ data : [ ] as any [ ] ,
328+ } ) ) ;
329+ } , [ schema . related , discoveredRelated ] ) ;
330+
291331 if ( loading || schema . loading ) {
292332 return (
293333 < div className = { cn ( 'space-y-4' , className ) } >
@@ -528,70 +568,205 @@ export const DetailView: React.FC<DetailViewProps> = ({
528568 </ div >
529569 ) }
530570
531- { /* Sections */ }
532- { schema . sections && schema . sections . length > 0 && (
533- < div className = "space-y-3 sm:space-y-4" >
534- { schema . sections . map ( ( section , index ) => (
571+ { /* Header Highlight Area */ }
572+ { schema . highlightFields && schema . highlightFields . length > 0 && (
573+ < HeaderHighlight fields = { schema . highlightFields } data = { data } />
574+ ) }
575+
576+ { /* Auto Tabs mode: wrap sections, related, activity into tabs */ }
577+ { schema . autoTabs && ! schema . tabs ?. length ? (
578+ < Tabs defaultValue = "details" className = "w-full" >
579+ < TabsList className = "w-full justify-start border-b rounded-none bg-transparent p-0" >
580+ < TabsTrigger
581+ value = "details"
582+ className = "relative rounded-none border-b-2 border-transparent data-[state=active]:border-primary data-[state=active]:bg-transparent"
583+ >
584+ { t ( 'detail.details' ) }
585+ </ TabsTrigger >
586+ { effectiveRelated . length > 0 && (
587+ < TabsTrigger
588+ value = "related"
589+ className = "relative rounded-none border-b-2 border-transparent data-[state=active]:border-primary data-[state=active]:bg-transparent"
590+ >
591+ < span className = "flex items-center gap-1.5" >
592+ { t ( 'detail.related' ) }
593+ < Badge variant = "secondary" className = "text-xs" > { effectiveRelated . length } </ Badge >
594+ </ span >
595+ </ TabsTrigger >
596+ ) }
597+ { schema . activities && schema . activities . length > 0 && (
598+ < TabsTrigger
599+ value = "activity"
600+ className = "relative rounded-none border-b-2 border-transparent data-[state=active]:border-primary data-[state=active]:bg-transparent"
601+ >
602+ < span className = "flex items-center gap-1.5" >
603+ { t ( 'detail.activity' ) }
604+ < Badge variant = "secondary" className = "text-xs" > { schema . activities . length } </ Badge >
605+ </ span >
606+ </ TabsTrigger >
607+ ) }
608+ </ TabsList >
609+
610+ { /* Details Tab Content */ }
611+ < TabsContent value = "details" className = "mt-4" >
612+ < div className = "space-y-3 sm:space-y-4" >
613+ { /* Section Groups */ }
614+ { schema . sectionGroups && schema . sectionGroups . length > 0 && (
615+ schema . sectionGroups . map ( ( group , index ) => (
616+ < SectionGroup
617+ key = { index }
618+ group = { group }
619+ data = { { ...data , ...editedValues } }
620+ objectSchema = { objectSchema }
621+ isEditing = { isInlineEditing }
622+ onFieldChange = { handleInlineFieldChange }
623+ />
624+ ) )
625+ ) }
626+ { schema . sections && schema . sections . length > 0 && (
627+ schema . sections . map ( ( section , index ) => (
628+ < DetailSection
629+ key = { index }
630+ section = { section }
631+ data = { { ...data , ...editedValues } }
632+ objectSchema = { objectSchema }
633+ isEditing = { isInlineEditing }
634+ onFieldChange = { handleInlineFieldChange }
635+ />
636+ ) )
637+ ) }
638+ { schema . fields && schema . fields . length > 0 && ! schema . sections ?. length && (
639+ < DetailSection
640+ section = { {
641+ fields : schema . fields ,
642+ columns : schema . columns ,
643+ } }
644+ data = { { ...data , ...editedValues } }
645+ objectSchema = { objectSchema }
646+ isEditing = { isInlineEditing }
647+ onFieldChange = { handleInlineFieldChange }
648+ />
649+ ) }
650+ { /* Comments in details tab */ }
651+ { schema . comments && (
652+ < RecordComments
653+ comments = { schema . comments }
654+ onAddComment = { schema . onAddComment }
655+ />
656+ ) }
657+ </ div >
658+ </ TabsContent >
659+
660+ { /* Related Tab Content */ }
661+ { effectiveRelated . length > 0 && (
662+ < TabsContent value = "related" className = "mt-4" >
663+ < div className = "space-y-4" >
664+ { effectiveRelated . map ( ( related , index ) => (
665+ < RelatedList
666+ key = { index }
667+ title = { related . title }
668+ type = { related . type }
669+ api = { related . api }
670+ data = { related . data }
671+ columns = { related . columns as any }
672+ dataSource = { dataSource }
673+ />
674+ ) ) }
675+ </ div >
676+ </ TabsContent >
677+ ) }
678+
679+ { /* Activity Tab Content */ }
680+ { schema . activities && schema . activities . length > 0 && (
681+ < TabsContent value = "activity" className = "mt-4" >
682+ < ActivityTimeline activities = { schema . activities } />
683+ </ TabsContent >
684+ ) }
685+ </ Tabs >
686+ ) : (
687+ < >
688+ { /* Section Groups */ }
689+ { schema . sectionGroups && schema . sectionGroups . length > 0 && (
690+ < div className = "space-y-3 sm:space-y-4" >
691+ { schema . sectionGroups . map ( ( group , index ) => (
692+ < SectionGroup
693+ key = { index }
694+ group = { group }
695+ data = { { ...data , ...editedValues } }
696+ objectSchema = { objectSchema }
697+ isEditing = { isInlineEditing }
698+ onFieldChange = { handleInlineFieldChange }
699+ />
700+ ) ) }
701+ </ div >
702+ ) }
703+
704+ { /* Sections */ }
705+ { schema . sections && schema . sections . length > 0 && (
706+ < div className = "space-y-3 sm:space-y-4" >
707+ { schema . sections . map ( ( section , index ) => (
708+ < DetailSection
709+ key = { index }
710+ section = { section }
711+ data = { { ...data , ...editedValues } }
712+ objectSchema = { objectSchema }
713+ isEditing = { isInlineEditing }
714+ onFieldChange = { handleInlineFieldChange }
715+ />
716+ ) ) }
717+ </ div >
718+ ) }
719+
720+ { /* Direct Fields (if no sections) */ }
721+ { schema . fields && schema . fields . length > 0 && ! schema . sections ?. length && (
535722 < DetailSection
536- key = { index }
537- section = { section }
723+ section = { {
724+ fields : schema . fields ,
725+ columns : schema . columns ,
726+ } }
538727 data = { { ...data , ...editedValues } }
539728 objectSchema = { objectSchema }
540729 isEditing = { isInlineEditing }
541730 onFieldChange = { handleInlineFieldChange }
542731 />
543- ) ) }
544- </ div >
545- ) }
546-
547- { /* Direct Fields (if no sections) */ }
548- { schema . fields && schema . fields . length > 0 && ! schema . sections ?. length && (
549- < DetailSection
550- section = { {
551- fields : schema . fields ,
552- columns : schema . columns ,
553- } }
554- data = { { ...data , ...editedValues } }
555- objectSchema = { objectSchema }
556- isEditing = { isInlineEditing }
557- onFieldChange = { handleInlineFieldChange }
558- />
559- ) }
560-
561- { /* Tabs */ }
562- { schema . tabs && schema . tabs . length > 0 && (
563- < DetailTabs tabs = { schema . tabs } data = { data } />
564- ) }
732+ ) }
733+
734+ { /* Tabs */ }
735+ { schema . tabs && schema . tabs . length > 0 && (
736+ < DetailTabs tabs = { schema . tabs } data = { data } />
737+ ) }
738+
739+ { /* Related Lists */ }
740+ { effectiveRelated . length > 0 && (
741+ < div className = "space-y-4" >
742+ < h2 className = "text-xl font-semibold" > { t ( 'detail.related' ) } </ h2 >
743+ { effectiveRelated . map ( ( related , index ) => (
744+ < RelatedList
745+ key = { index }
746+ title = { related . title }
747+ type = { related . type }
748+ api = { related . api }
749+ data = { related . data }
750+ columns = { related . columns as any }
751+ dataSource = { dataSource }
752+ />
753+ ) ) }
754+ </ div >
755+ ) }
565756
566- { /* Related Lists */ }
567- { schema . related && schema . related . length > 0 && (
568- < div className = "space-y-4" >
569- < h2 className = "text-xl font-semibold" > { t ( 'detail.related' ) } </ h2 >
570- { schema . related . map ( ( related , index ) => (
571- < RelatedList
572- key = { index }
573- title = { related . title }
574- type = { related . type }
575- api = { related . api }
576- data = { related . data }
577- columns = { related . columns as any }
578- dataSource = { dataSource }
757+ { /* Comments */ }
758+ { schema . comments && (
759+ < RecordComments
760+ comments = { schema . comments }
761+ onAddComment = { schema . onAddComment }
579762 />
580- ) ) }
581- </ div >
582- ) }
583-
584- { /* Comments */ }
585- { schema . comments && (
586- < RecordComments
587- comments = { schema . comments }
588- onAddComment = { schema . onAddComment }
589- />
590- ) }
763+ ) }
591764
592- { /* Activity Timeline */ }
593- { schema . activities && schema . activities . length > 0 && (
594- < ActivityTimeline activities = { schema . activities } />
765+ { /* Activity Timeline */ }
766+ { schema . activities && schema . activities . length > 0 && (
767+ < ActivityTimeline activities = { schema . activities } />
768+ ) }
769+ </ >
595770 ) }
596771
597772 { /* Custom Footer */ }
0 commit comments