diff --git a/packages/twenty-front/src/modules/object-record/record-show/components/RecordShowContainer.tsx b/packages/twenty-front/src/modules/object-record/record-show/components/RecordShowContainer.tsx index 09e4ff633..d1002c89b 100644 --- a/packages/twenty-front/src/modules/object-record/record-show/components/RecordShowContainer.tsx +++ b/packages/twenty-front/src/modules/object-record/record-show/components/RecordShowContainer.tsx @@ -35,7 +35,7 @@ export const RecordShowContainer = ({ objectRecordId, }); - const tabs = useRecordShowContainerTabs( + const { layout, tabs } = useRecordShowContainerTabs( loading, objectNameSingular as CoreObjectNameSingular, isInRightDrawer, @@ -61,6 +61,7 @@ export const RecordShowContainer = ({ = { - fields: { - title: 'Fields', - Icon: IconList, - position: 100, - cards: [{ type: CardType.FieldCard }], - hide: { - ifMobile: false, - ifDesktop: true, - ifInRightDrawer: false, - ifFeaturesDisabled: [], - ifRequiredObjectsInactive: [], - ifRelationsMissing: [], +export const BASE_RECORD_LAYOUT: RecordLayout = { + tabs: { + fields: { + title: 'Fields', + Icon: IconList, + position: 100, + cards: [{ type: CardType.FieldCard }], + hide: { + ifMobile: false, + ifDesktop: true, + ifInRightDrawer: false, + ifFeaturesDisabled: [], + ifRequiredObjectsInactive: [], + ifRelationsMissing: [], + }, }, - }, - timeline: { - title: 'Timeline', - Icon: IconTimelineEvent, - position: 200, - cards: [{ type: CardType.TimelineCard }], - hide: { - ifMobile: false, - ifDesktop: false, - ifInRightDrawer: true, - ifFeaturesDisabled: [], - ifRequiredObjectsInactive: [], - ifRelationsMissing: [], + timeline: { + title: 'Timeline', + Icon: IconTimelineEvent, + position: 200, + cards: [{ type: CardType.TimelineCard }], + hide: { + ifMobile: false, + ifDesktop: false, + ifInRightDrawer: true, + ifFeaturesDisabled: [], + ifRequiredObjectsInactive: [], + ifRelationsMissing: [], + }, }, - }, - tasks: { - title: 'Tasks', - Icon: IconCheckbox, - position: 300, - cards: [{ type: CardType.TaskCard }], - hide: { - ifMobile: false, - ifDesktop: false, - ifInRightDrawer: false, - ifFeaturesDisabled: [], - ifRequiredObjectsInactive: [CoreObjectNameSingular.Task], - ifRelationsMissing: ['taskTargets'], + tasks: { + title: 'Tasks', + Icon: IconCheckbox, + position: 300, + cards: [{ type: CardType.TaskCard }], + hide: { + ifMobile: false, + ifDesktop: false, + ifInRightDrawer: false, + ifFeaturesDisabled: [], + ifRequiredObjectsInactive: [CoreObjectNameSingular.Task], + ifRelationsMissing: ['taskTargets'], + }, }, - }, - notes: { - title: 'Notes', - Icon: IconNotes, - position: 400, - cards: [{ type: CardType.NoteCard }], - hide: { - ifMobile: false, - ifDesktop: false, - ifInRightDrawer: false, - ifFeaturesDisabled: [], - ifRequiredObjectsInactive: [CoreObjectNameSingular.Note], - ifRelationsMissing: ['noteTargets'], + notes: { + title: 'Notes', + Icon: IconNotes, + position: 400, + cards: [{ type: CardType.NoteCard }], + hide: { + ifMobile: false, + ifDesktop: false, + ifInRightDrawer: false, + ifFeaturesDisabled: [], + ifRequiredObjectsInactive: [CoreObjectNameSingular.Note], + ifRelationsMissing: ['noteTargets'], + }, }, - }, - files: { - title: 'Files', - Icon: IconPaperclip, - position: 500, - cards: [{ type: CardType.FileCard }], - hide: { - ifMobile: false, - ifDesktop: false, - ifInRightDrawer: false, - ifFeaturesDisabled: [], - ifRequiredObjectsInactive: [CoreObjectNameSingular.Attachment], - ifRelationsMissing: ['attachments'], + files: { + title: 'Files', + Icon: IconPaperclip, + position: 500, + cards: [{ type: CardType.FileCard }], + hide: { + ifMobile: false, + ifDesktop: false, + ifInRightDrawer: false, + ifFeaturesDisabled: [], + ifRequiredObjectsInactive: [CoreObjectNameSingular.Attachment], + ifRelationsMissing: ['attachments'], + }, }, }, }; diff --git a/packages/twenty-front/src/modules/object-record/record-show/hooks/useRecordShowContainerTabs.ts b/packages/twenty-front/src/modules/object-record/record-show/hooks/useRecordShowContainerTabs.ts index 3917de656..7c4200024 100644 --- a/packages/twenty-front/src/modules/object-record/record-show/hooks/useRecordShowContainerTabs.ts +++ b/packages/twenty-front/src/modules/object-record/record-show/hooks/useRecordShowContainerTabs.ts @@ -23,7 +23,7 @@ export const useRecordShowContainerTabs = ( targetObjectNameSingular: CoreObjectNameSingular, isInRightDrawer: boolean, objectMetadataItem: ObjectMetadataItem, -): SingleTabProps[] => { +): { layout: RecordLayout; tabs: SingleTabProps[] } => { const isMobile = useIsMobile(); const objectMetadataItems = useRecoilValue(objectMetadataItemsState); @@ -34,235 +34,258 @@ export const useRecordShowContainerTabs = ( Record > = { [CoreObjectNameSingular.Note]: { - richText: { - title: 'Note', - position: 0, - Icon: IconNotes, - cards: [{ type: CardType.RichTextCard }], - hide: { - ifMobile: false, - ifDesktop: false, - ifInRightDrawer: false, - ifFeaturesDisabled: [], - ifRequiredObjectsInactive: [], - ifRelationsMissing: [], + tabs: { + richText: { + title: 'Note', + position: 0, + Icon: IconNotes, + cards: [{ type: CardType.RichTextCard }], + hide: { + ifMobile: false, + ifDesktop: false, + ifInRightDrawer: false, + ifFeaturesDisabled: [], + ifRequiredObjectsInactive: [], + ifRelationsMissing: [], + }, }, + tasks: null, + notes: null, }, - tasks: null, - notes: null, }, [CoreObjectNameSingular.Task]: { - richText: { - title: 'Note', - position: 0, - Icon: IconNotes, - cards: [{ type: CardType.RichTextCard }], - hide: { - ifMobile: false, - ifDesktop: false, - ifInRightDrawer: false, - ifFeaturesDisabled: [], - ifRequiredObjectsInactive: [], - ifRelationsMissing: [], + tabs: { + richText: { + title: 'Note', + position: 0, + Icon: IconNotes, + cards: [{ type: CardType.RichTextCard }], + hide: { + ifMobile: false, + ifDesktop: false, + ifInRightDrawer: false, + ifFeaturesDisabled: [], + ifRequiredObjectsInactive: [], + ifRelationsMissing: [], + }, }, + tasks: null, + notes: null, }, - tasks: null, - notes: null, }, [CoreObjectNameSingular.Company]: { - emails: { - title: 'Emails', - position: 600, - Icon: IconMail, - cards: [{ type: CardType.EmailCard }], - hide: { - ifMobile: false, - ifDesktop: false, - ifInRightDrawer: false, - ifFeaturesDisabled: [], - ifRequiredObjectsInactive: [], - ifRelationsMissing: [], + tabs: { + emails: { + title: 'Emails', + position: 600, + Icon: IconMail, + cards: [{ type: CardType.EmailCard }], + hide: { + ifMobile: false, + ifDesktop: false, + ifInRightDrawer: false, + ifFeaturesDisabled: [], + ifRequiredObjectsInactive: [], + ifRelationsMissing: [], + }, }, - }, - calendar: { - title: 'Calendar', - position: 700, - Icon: IconCalendarEvent, - cards: [{ type: CardType.CalendarCard }], - hide: { - ifMobile: false, - ifDesktop: false, - ifInRightDrawer: false, - ifFeaturesDisabled: [], - ifRequiredObjectsInactive: [], - ifRelationsMissing: [], + calendar: { + title: 'Calendar', + position: 700, + Icon: IconCalendarEvent, + cards: [{ type: CardType.CalendarCard }], + hide: { + ifMobile: false, + ifDesktop: false, + ifInRightDrawer: false, + ifFeaturesDisabled: [], + ifRequiredObjectsInactive: [], + ifRelationsMissing: [], + }, }, }, }, [CoreObjectNameSingular.Person]: { - emails: { - title: 'Emails', - position: 600, - Icon: IconMail, - cards: [{ type: CardType.EmailCard }], - hide: { - ifMobile: false, - ifDesktop: false, - ifInRightDrawer: false, - ifFeaturesDisabled: [], - ifRequiredObjectsInactive: [], - ifRelationsMissing: [], + tabs: { + emails: { + title: 'Emails', + position: 600, + Icon: IconMail, + cards: [{ type: CardType.EmailCard }], + hide: { + ifMobile: false, + ifDesktop: false, + ifInRightDrawer: false, + ifFeaturesDisabled: [], + ifRequiredObjectsInactive: [], + ifRelationsMissing: [], + }, }, - }, - calendar: { - title: 'Calendar', - position: 700, - Icon: IconCalendarEvent, - cards: [{ type: CardType.CalendarCard }], - hide: { - ifMobile: false, - ifDesktop: false, - ifInRightDrawer: false, - ifFeaturesDisabled: [], - ifRequiredObjectsInactive: [], - ifRelationsMissing: [], + calendar: { + title: 'Calendar', + position: 700, + Icon: IconCalendarEvent, + cards: [{ type: CardType.CalendarCard }], + hide: { + ifMobile: false, + ifDesktop: false, + ifInRightDrawer: false, + ifFeaturesDisabled: [], + ifRequiredObjectsInactive: [], + ifRelationsMissing: [], + }, }, }, }, [CoreObjectNameSingular.Workflow]: { - workflow: { - title: 'Flow', - position: 0, - Icon: IconSettings, - cards: [{ type: CardType.WorkflowCard }], - hide: { - ifMobile: false, - ifDesktop: false, - ifInRightDrawer: false, - ifFeaturesDisabled: ['IS_WORKFLOW_ENABLED'], - ifRequiredObjectsInactive: [], - ifRelationsMissing: [], + hideSummaryAndFields: true, + tabs: { + workflow: { + title: 'Flow', + position: 0, + Icon: IconSettings, + cards: [{ type: CardType.WorkflowCard }], + hide: { + ifMobile: false, + ifDesktop: false, + ifInRightDrawer: false, + ifFeaturesDisabled: ['IS_WORKFLOW_ENABLED'], + ifRequiredObjectsInactive: [], + ifRelationsMissing: [], + }, }, + timeline: null, + fields: null, }, - timeline: null, }, [CoreObjectNameSingular.WorkflowVersion]: { - workflowVersion: { - title: 'Flow', - position: 0, - Icon: IconSettings, - cards: [{ type: CardType.WorkflowVersionCard }], - hide: { - ifMobile: false, - ifDesktop: false, - ifInRightDrawer: false, - ifFeaturesDisabled: ['IS_WORKFLOW_ENABLED'], - ifRequiredObjectsInactive: [], - ifRelationsMissing: [], + tabs: { + workflowVersion: { + title: 'Flow', + position: 0, + Icon: IconSettings, + cards: [{ type: CardType.WorkflowVersionCard }], + hide: { + ifMobile: false, + ifDesktop: false, + ifInRightDrawer: false, + ifFeaturesDisabled: ['IS_WORKFLOW_ENABLED'], + ifRequiredObjectsInactive: [], + ifRelationsMissing: [], + }, }, + timeline: null, }, - timeline: null, }, [CoreObjectNameSingular.WorkflowRun]: { - workflowRunOutput: { - title: 'Output', - position: 0, - Icon: IconPrinter, - cards: [{ type: CardType.WorkflowRunOutputCard }], - hide: { - ifMobile: false, - ifDesktop: false, - ifInRightDrawer: false, - ifFeaturesDisabled: ['IS_WORKFLOW_ENABLED'], - ifRequiredObjectsInactive: [], - ifRelationsMissing: [], + tabs: { + workflowRunOutput: { + title: 'Output', + position: 0, + Icon: IconPrinter, + cards: [{ type: CardType.WorkflowRunOutputCard }], + hide: { + ifMobile: false, + ifDesktop: false, + ifInRightDrawer: false, + ifFeaturesDisabled: ['IS_WORKFLOW_ENABLED'], + ifRequiredObjectsInactive: [], + ifRelationsMissing: [], + }, }, - }, - workflowRunFlow: { - title: 'Flow', - position: 0, - Icon: IconSettings, - cards: [{ type: CardType.WorkflowRunCard }], - hide: { - ifMobile: false, - ifDesktop: false, - ifInRightDrawer: false, - ifFeaturesDisabled: ['IS_WORKFLOW_ENABLED'], - ifRequiredObjectsInactive: [], - ifRelationsMissing: [], + workflowRunFlow: { + title: 'Flow', + position: 0, + Icon: IconSettings, + cards: [{ type: CardType.WorkflowRunCard }], + hide: { + ifMobile: false, + ifDesktop: false, + ifInRightDrawer: false, + ifFeaturesDisabled: ['IS_WORKFLOW_ENABLED'], + ifRequiredObjectsInactive: [], + ifRelationsMissing: [], + }, }, + timeline: null, }, - timeline: null, }, }; // Merge base layout with object-specific layout - const tabDefinitions: RecordLayout = { + const recordLayout: RecordLayout = { ...BASE_RECORD_LAYOUT, ...(OBJECT_SPECIFIC_LAYOUTS[targetObjectNameSingular] || {}), + tabs: { + ...BASE_RECORD_LAYOUT.tabs, + ...(OBJECT_SPECIFIC_LAYOUTS[targetObjectNameSingular]?.tabs || {}), + }, }; - return Object.entries(tabDefinitions) - .filter( - (entry): entry is [string, NonNullable] => - entry[1] !== null && entry[1] !== undefined, - ) - .sort(([, a], [, b]) => a.position - b.position) - .map(([key, { title, Icon, hide, cards }]) => { - // Special handling for fields tab - if (key === 'fields') { + return { + layout: recordLayout, + tabs: Object.entries(recordLayout.tabs) + .filter( + (entry): entry is [string, NonNullable] => + entry[1] !== null && entry[1] !== undefined, + ) + .sort(([, a], [, b]) => a.position - b.position) + .map(([key, { title, Icon, hide, cards }]) => { + // Special handling for fields tab + if (key === 'fields') { + return { + id: key, + title, + Icon, + cards, + hide: !(isMobile || isInRightDrawer), + }; + } + + const baseHide = + (hide.ifMobile && isMobile) || + (hide.ifDesktop && !isMobile) || + (hide.ifInRightDrawer && isInRightDrawer); + + const featureNotEnabled = + hide.ifFeaturesDisabled.length > 0 && + !hide.ifFeaturesDisabled.every((flagKey) => { + return !!currentWorkspace?.featureFlags?.find( + (flag: FeatureFlag) => flag.key === flagKey && flag.value, + ); + }); + + const requiredObjectsInactive = + hide.ifRequiredObjectsInactive.length > 0 && + !hide.ifRequiredObjectsInactive.every((obj) => + objectMetadataItems.some( + (item) => item.nameSingular === obj && item.isActive, + ), + ); + + const relationsDontExist = + hide.ifRelationsMissing.length > 0 && + !hide.ifRelationsMissing.every((rel) => + objectMetadataItem.fields.some( + (field) => + field.type === FieldMetadataType.Relation && + field.name === rel && + field.isActive, + ), + ); + return { id: key, title, Icon, cards, - hide: !(isMobile || isInRightDrawer), + hide: + loading || + baseHide || + featureNotEnabled || + requiredObjectsInactive || + relationsDontExist, }; - } - - const baseHide = - (hide.ifMobile && isMobile) || - (hide.ifDesktop && !isMobile) || - (hide.ifInRightDrawer && isInRightDrawer); - - const featureNotEnabled = - hide.ifFeaturesDisabled.length > 0 && - !hide.ifFeaturesDisabled.every((flagKey) => { - return !!currentWorkspace?.featureFlags?.find( - (flag: FeatureFlag) => flag.key === flagKey && flag.value, - ); - }); - - const requiredObjectsInactive = - hide.ifRequiredObjectsInactive.length > 0 && - !hide.ifRequiredObjectsInactive.every((obj) => - objectMetadataItems.some( - (item) => item.nameSingular === obj && item.isActive, - ), - ); - - const relationsDontExist = - hide.ifRelationsMissing.length > 0 && - !hide.ifRelationsMissing.every((rel) => - objectMetadataItem.fields.some( - (field) => - field.type === FieldMetadataType.Relation && - field.name === rel && - field.isActive, - ), - ); - - return { - id: key, - title, - Icon, - cards, - hide: - loading || - baseHide || - featureNotEnabled || - requiredObjectsInactive || - relationsDontExist, - }; - }); + }), + }; }; diff --git a/packages/twenty-front/src/modules/object-record/record-show/types/RecordLayout.ts b/packages/twenty-front/src/modules/object-record/record-show/types/RecordLayout.ts index e38825b76..c6b3e99ff 100644 --- a/packages/twenty-front/src/modules/object-record/record-show/types/RecordLayout.ts +++ b/packages/twenty-front/src/modules/object-record/record-show/types/RecordLayout.ts @@ -1,3 +1,6 @@ import { RecordLayoutTab } from '@/ui/layout/tab/types/RecordLayoutTab'; -export type RecordLayout = Record; +export type RecordLayout = { + hideSummaryAndFields?: boolean; + tabs: Record; +}; diff --git a/packages/twenty-front/src/modules/object-record/record-show/types/RecordLayoutMap.ts b/packages/twenty-front/src/modules/object-record/record-show/types/RecordLayoutMap.ts new file mode 100644 index 000000000..7f3d96828 --- /dev/null +++ b/packages/twenty-front/src/modules/object-record/record-show/types/RecordLayoutMap.ts @@ -0,0 +1,3 @@ +import { RecordLayout } from '@/object-record/record-show/types/RecordLayout'; + +export type RecordLayoutMap = Record; diff --git a/packages/twenty-front/src/modules/ui/layout/show-page/components/ShowPageSubContainer.tsx b/packages/twenty-front/src/modules/ui/layout/show-page/components/ShowPageSubContainer.tsx index beb0961bb..6dfea510e 100644 --- a/packages/twenty-front/src/modules/ui/layout/show-page/components/ShowPageSubContainer.tsx +++ b/packages/twenty-front/src/modules/ui/layout/show-page/components/ShowPageSubContainer.tsx @@ -4,6 +4,7 @@ import { isNewViewableRecordLoadingState } from '@/object-record/record-right-dr import { CardComponents } from '@/object-record/record-show/components/CardComponents'; import { FieldsCard } from '@/object-record/record-show/components/FieldsCard'; import { SummaryCard } from '@/object-record/record-show/components/SummaryCard'; +import { RecordLayout } from '@/object-record/record-show/types/RecordLayout'; import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState'; import { ObjectRecord } from '@/object-record/types/ObjectRecord'; import { ShowPageLeftContainer } from '@/ui/layout/show-page/components/ShowPageLeftContainer'; @@ -23,12 +24,12 @@ const StyledShowPageRightContainer = styled.div<{ isMobile: boolean }>` overflow: auto; `; -const StyledTabListContainer = styled.div` +const StyledTabListContainer = styled.div<{ shouldDisplay: boolean }>` align-items: center; padding-left: ${({ theme }) => theme.spacing(2)}; border-bottom: ${({ theme }) => `1px solid ${theme.border.color.light}`}; box-sizing: border-box; - display: flex; + display: ${({ shouldDisplay }) => (shouldDisplay ? 'flex' : 'none')}; gap: ${({ theme }) => theme.spacing(2)}; height: 40px; `; @@ -56,6 +57,7 @@ const StyledContentContainer = styled.div<{ isInRightDrawer: boolean }>` export const TAB_LIST_COMPONENT_ID = 'show-page-right-tab-list'; type ShowPageSubContainerProps = { + layout: RecordLayout; tabs: SingleTabProps[]; targetableObject: Pick< ActivityTargetableObject, @@ -68,6 +70,7 @@ type ShowPageSubContainerProps = { export const ShowPageSubContainer = ({ tabs, + layout, targetableObject, loading, isInRightDrawer = false, @@ -120,16 +123,18 @@ export const ShowPageSubContainer = ({ recordStoreFamilyState(targetableObject.id), ); + const visibleTabs = tabs.filter((tab) => !tab.hide); + return ( <> - {!isMobile && !isInRightDrawer && ( + {!layout.hideSummaryAndFields && !isMobile && !isInRightDrawer && ( {summaryCard} {fieldsCard} )} - + 1}> { - const initialActiveTabId = tabs.find((tab) => !tab.hide)?.id || ''; + const visibleTabs = tabs.filter((tab) => !tab.hide); + + const initialActiveTabId = visibleTabs[0]?.id || ''; const { activeTabIdState, setActiveTabId } = useTabList(tabListInstanceId); @@ -56,6 +58,10 @@ export const TabList = ({ setActiveTabId(initialActiveTabId); }, [initialActiveTabId, setActiveTabId]); + if (visibleTabs.length <= 1) { + return null; + } + return ( - {tabs - .filter((tab) => !tab.hide) - .map((tab) => ( - { - if (!behaveAsLinks) { - setActiveTabId(tab.id); - } - }} - /> - ))} + {visibleTabs.map((tab) => ( + { + if (!behaveAsLinks) { + setActiveTabId(tab.id); + } + }} + /> + ))}