diff --git a/packages/twenty-front/src/modules/activities/timelineActivities/components/TimelineCreateButtonGroup.tsx b/packages/twenty-front/src/modules/activities/timelineActivities/components/TimelineCreateButtonGroup.tsx
index 2889dfc77..4e8ec1c64 100644
--- a/packages/twenty-front/src/modules/activities/timelineActivities/components/TimelineCreateButtonGroup.tsx
+++ b/packages/twenty-front/src/modules/activities/timelineActivities/components/TimelineCreateButtonGroup.tsx
@@ -3,7 +3,7 @@ import { IconCheckbox, IconNotes, IconPaperclip } from 'twenty-ui';
import { Button } from '@/ui/input/button/components/Button';
import { ButtonGroup } from '@/ui/input/button/components/ButtonGroup';
-import { TAB_LIST_COMPONENT_ID } from '@/ui/layout/show-page/components/ShowPageRightContainer';
+import { TAB_LIST_COMPONENT_ID } from '@/ui/layout/show-page/components/ShowPageSubContainer';
import { useTabList } from '@/ui/layout/tab/hooks/useTabList';
export const TimelineCreateButtonGroup = ({
diff --git a/packages/twenty-front/src/modules/object-record/record-show/components/FieldsCard.tsx b/packages/twenty-front/src/modules/object-record/record-show/components/FieldsCard.tsx
new file mode 100644
index 000000000..22f77e501
--- /dev/null
+++ b/packages/twenty-front/src/modules/object-record/record-show/components/FieldsCard.tsx
@@ -0,0 +1,188 @@
+import groupBy from 'lodash.groupby';
+
+import { ActivityTargetsInlineCell } from '@/activities/inline-cell/components/ActivityTargetsInlineCell';
+import { Note } from '@/activities/types/Note';
+import { Task } from '@/activities/types/Task';
+import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
+import { formatFieldMetadataItemAsColumnDefinition } from '@/object-metadata/utils/formatFieldMetadataItemAsColumnDefinition';
+import { FieldContext } from '@/object-record/record-field/contexts/FieldContext';
+import { RecordInlineCell } from '@/object-record/record-inline-cell/components/RecordInlineCell';
+import { PropertyBox } from '@/object-record/record-inline-cell/property-box/components/PropertyBox';
+import { PropertyBoxSkeletonLoader } from '@/object-record/record-inline-cell/property-box/components/PropertyBoxSkeletonLoader';
+import { InlineCellHotkeyScope } from '@/object-record/record-inline-cell/types/InlineCellHotkeyScope';
+import { useRecordShowContainerActions } from '@/object-record/record-show/hooks/useRecordShowContainerActions';
+import { useRecordShowContainerData } from '@/object-record/record-show/hooks/useRecordShowContainerData';
+import { RecordDetailDuplicatesSection } from '@/object-record/record-show/record-detail-section/components/RecordDetailDuplicatesSection';
+import { RecordDetailRelationSection } from '@/object-record/record-show/record-detail-section/components/RecordDetailRelationSection';
+import { isFieldCellSupported } from '@/object-record/utils/isFieldCellSupported';
+import { FieldMetadataType } from '~/generated/graphql';
+import { isDefined } from '~/utils/isDefined';
+
+type FieldsCardProps = {
+ objectNameSingular: string;
+ objectRecordId: string;
+};
+
+export const FieldsCard = ({
+ objectNameSingular,
+ objectRecordId,
+}: FieldsCardProps) => {
+ const {
+ recordFromStore,
+ recordLoading,
+ objectMetadataItem,
+ labelIdentifierFieldMetadataItem,
+ isPrefetchLoading,
+ objectMetadataItems,
+ } = useRecordShowContainerData({
+ objectNameSingular,
+ objectRecordId,
+ });
+
+ const { useUpdateOneObjectRecordMutation } = useRecordShowContainerActions({
+ objectNameSingular,
+ objectRecordId,
+ recordFromStore,
+ });
+
+ const availableFieldMetadataItems = objectMetadataItem.fields
+ .filter(
+ (fieldMetadataItem) =>
+ isFieldCellSupported(fieldMetadataItem, objectMetadataItems) &&
+ fieldMetadataItem.id !== labelIdentifierFieldMetadataItem?.id,
+ )
+ .sort((fieldMetadataItemA, fieldMetadataItemB) =>
+ fieldMetadataItemA.name.localeCompare(fieldMetadataItemB.name),
+ );
+
+ const { inlineFieldMetadataItems, relationFieldMetadataItems } = groupBy(
+ availableFieldMetadataItems.filter(
+ (fieldMetadataItem) =>
+ fieldMetadataItem.name !== 'createdAt' &&
+ fieldMetadataItem.name !== 'deletedAt',
+ ),
+ (fieldMetadataItem) =>
+ fieldMetadataItem.type === FieldMetadataType.Relation
+ ? 'relationFieldMetadataItems'
+ : 'inlineFieldMetadataItems',
+ );
+
+ const inlineRelationFieldMetadataItems = relationFieldMetadataItems?.filter(
+ (fieldMetadataItem) =>
+ (objectNameSingular === CoreObjectNameSingular.Note &&
+ fieldMetadataItem.name === 'noteTargets') ||
+ (objectNameSingular === CoreObjectNameSingular.Task &&
+ fieldMetadataItem.name === 'taskTargets'),
+ );
+
+ const boxedRelationFieldMetadataItems = relationFieldMetadataItems?.filter(
+ (fieldMetadataItem) =>
+ objectNameSingular !== CoreObjectNameSingular.Note &&
+ fieldMetadataItem.name !== 'noteTargets' &&
+ objectNameSingular !== CoreObjectNameSingular.Task &&
+ fieldMetadataItem.name !== 'taskTargets',
+ );
+ const isReadOnly = objectMetadataItem.isRemote;
+
+ return (
+ <>
+ {isDefined(recordFromStore) && (
+ <>
+
+ {isPrefetchLoading ? (
+
+ ) : (
+ <>
+ {inlineRelationFieldMetadataItems?.map(
+ (fieldMetadataItem, index) => (
+
+
+
+ ),
+ )}
+ {inlineFieldMetadataItems?.map((fieldMetadataItem, index) => (
+
+
+
+ ))}
+ >
+ )}
+
+
+ {boxedRelationFieldMetadataItems?.map((fieldMetadataItem, index) => (
+
+
+
+ ))}
+ >
+ )}
+ >
+ );
+};
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 9b1e10601..8e911edac 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
@@ -1,47 +1,10 @@
-import groupBy from 'lodash.groupby';
-import { useRecoilState, useRecoilValue } from 'recoil';
-
-import { ActivityTargetsInlineCell } from '@/activities/inline-cell/components/ActivityTargetsInlineCell';
-import { Note } from '@/activities/types/Note';
-import { Task } from '@/activities/types/Task';
import { InformationBannerDeletedRecord } from '@/information-banner/components/deleted-record/InformationBannerDeletedRecord';
-import { useGetStandardObjectIcon } from '@/object-metadata/hooks/useGetStandardObjectIcon';
-import { useLabelIdentifierFieldMetadataItem } from '@/object-metadata/hooks/useLabelIdentifierFieldMetadataItem';
-import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
-import { useObjectMetadataItems } from '@/object-metadata/hooks/useObjectMetadataItems';
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
-import { formatFieldMetadataItemAsColumnDefinition } from '@/object-metadata/utils/formatFieldMetadataItemAsColumnDefinition';
-import { useUpdateOneRecord } from '@/object-record/hooks/useUpdateOneRecord';
-import {
- FieldContext,
- RecordUpdateHook,
- RecordUpdateHookParams,
-} from '@/object-record/record-field/contexts/FieldContext';
-import { RecordInlineCell } from '@/object-record/record-inline-cell/components/RecordInlineCell';
-import { PropertyBox } from '@/object-record/record-inline-cell/property-box/components/PropertyBox';
-import { PropertyBoxSkeletonLoader } from '@/object-record/record-inline-cell/property-box/components/PropertyBoxSkeletonLoader';
-import { InlineCellHotkeyScope } from '@/object-record/record-inline-cell/types/InlineCellHotkeyScope';
-import { RecordDetailDuplicatesSection } from '@/object-record/record-show/record-detail-section/components/RecordDetailDuplicatesSection';
-import { RecordDetailRelationSection } from '@/object-record/record-show/record-detail-section/components/RecordDetailRelationSection';
-import { recordLoadingFamilyState } from '@/object-record/record-store/states/recordLoadingFamilyState';
-import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
-import { recordStoreIdentifierFamilySelector } from '@/object-record/record-store/states/selectors/recordStoreIdentifierSelector';
-import { ObjectRecord } from '@/object-record/types/ObjectRecord';
-import { isFieldCellSupported } from '@/object-record/utils/isFieldCellSupported';
-import { useIsPrefetchLoading } from '@/prefetch/hooks/useIsPrefetchLoading';
import { ShowPageContainer } from '@/ui/layout/page/ShowPageContainer';
-import { ShowPageLeftContainer } from '@/ui/layout/show-page/components/ShowPageLeftContainer';
-import { ShowPageRightContainer } from '@/ui/layout/show-page/components/ShowPageRightContainer';
-import { ShowPageSummaryCard } from '@/ui/layout/show-page/components/ShowPageSummaryCard';
-import { ShowPageSummaryCardSkeletonLoader } from '@/ui/layout/show-page/components/ShowPageSummaryCardSkeletonLoader';
-import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile';
-import {
- FieldMetadataType,
- FileFolder,
- useUploadImageMutation,
-} from '~/generated/graphql';
-import { isDefined } from '~/utils/isDefined';
-import { isUndefinedOrNull } from '~/utils/isUndefinedOrNull';
+
+import { useRecordShowContainerData } from '@/object-record/record-show/hooks/useRecordShowContainerData';
+import { useRecordShowContainerTabs } from '@/object-record/record-show/hooks/useRecordShowContainerTabs';
+import { ShowPageSubContainer } from '@/ui/layout/show-page/components/ShowPageSubContainer';
type RecordShowContainerProps = {
objectNameSingular: string;
@@ -58,261 +21,20 @@ export const RecordShowContainer = ({
isInRightDrawer = false,
isNewRightDrawerItemLoading = false,
}: RecordShowContainerProps) => {
- const { objectMetadataItem } = useObjectMetadataItem({
+ const {
+ recordFromStore,
+ objectMetadataItem,
+ isPrefetchLoading,
+ recordLoading,
+ } = useRecordShowContainerData({
objectNameSingular,
+ objectRecordId,
});
- const { objectMetadataItems } = useObjectMetadataItems();
-
- const { labelIdentifierFieldMetadataItem } =
- useLabelIdentifierFieldMetadataItem({
- objectNameSingular,
- });
-
- const [recordLoading] = useRecoilState(
- recordLoadingFamilyState(objectRecordId),
- );
-
- const [recordFromStore] = useRecoilState(
- recordStoreFamilyState(objectRecordId),
- );
-
- const recordIdentifier = useRecoilValue(
- recordStoreIdentifierFamilySelector({
- objectNameSingular,
- recordId: objectRecordId,
- }),
- );
- const [uploadImage] = useUploadImageMutation();
- const { updateOneRecord } = useUpdateOneRecord({ objectNameSingular });
-
- const useUpdateOneObjectRecordMutation: RecordUpdateHook = () => {
- const updateEntity = ({ variables }: RecordUpdateHookParams) => {
- updateOneRecord?.({
- idToUpdate: variables.where.id as string,
- updateOneRecordInput: variables.updateOneRecordInput,
- });
- };
-
- return [updateEntity, { loading: false }];
- };
-
- const onUploadPicture = async (file: File) => {
- if (objectNameSingular !== 'person') {
- return;
- }
-
- const result = await uploadImage({
- variables: {
- file,
- fileFolder: FileFolder.PersonPicture,
- },
- });
-
- const avatarUrl = result?.data?.uploadImage;
-
- if (!avatarUrl || isUndefinedOrNull(updateOneRecord) || !recordFromStore) {
- return;
- }
-
- await updateOneRecord({
- idToUpdate: objectRecordId,
- updateOneRecordInput: {
- avatarUrl,
- },
- });
- };
-
- const availableFieldMetadataItems = objectMetadataItem.fields
- .filter(
- (fieldMetadataItem) =>
- isFieldCellSupported(fieldMetadataItem, objectMetadataItems) &&
- fieldMetadataItem.id !== labelIdentifierFieldMetadataItem?.id,
- )
- .sort((fieldMetadataItemA, fieldMetadataItemB) =>
- fieldMetadataItemA.name.localeCompare(fieldMetadataItemB.name),
- );
-
- const { inlineFieldMetadataItems, relationFieldMetadataItems } = groupBy(
- availableFieldMetadataItems.filter(
- (fieldMetadataItem) =>
- fieldMetadataItem.name !== 'createdAt' &&
- fieldMetadataItem.name !== 'deletedAt',
- ),
- (fieldMetadataItem) =>
- fieldMetadataItem.type === FieldMetadataType.Relation
- ? 'relationFieldMetadataItems'
- : 'inlineFieldMetadataItems',
- );
-
- const inlineRelationFieldMetadataItems = relationFieldMetadataItems?.filter(
- (fieldMetadataItem) =>
- (objectNameSingular === CoreObjectNameSingular.Note &&
- fieldMetadataItem.name === 'noteTargets') ||
- (objectNameSingular === CoreObjectNameSingular.Task &&
- fieldMetadataItem.name === 'taskTargets'),
- );
-
- const boxedRelationFieldMetadataItems = relationFieldMetadataItems?.filter(
- (fieldMetadataItem) =>
- objectNameSingular !== CoreObjectNameSingular.Note &&
- fieldMetadataItem.name !== 'noteTargets' &&
- objectNameSingular !== CoreObjectNameSingular.Task &&
- fieldMetadataItem.name !== 'taskTargets',
- );
- const { Icon, IconColor } = useGetStandardObjectIcon(objectNameSingular);
- const isReadOnly = objectMetadataItem.isRemote;
- const isMobile = useIsMobile() || isInRightDrawer;
- const isPrefetchLoading = useIsPrefetchLoading();
-
- const summaryCard =
- !isNewRightDrawerItemLoading && isDefined(recordFromStore) ? (
-
-
-
- }
- avatarType={recordIdentifier?.avatarType ?? 'rounded'}
- onUploadPicture={
- objectNameSingular === 'person' ? onUploadPicture : undefined
- }
- />
- ) : (
-
- );
-
- const fieldsBox = (
- <>
- {isDefined(recordFromStore) && (
- <>
-
- {isPrefetchLoading ? (
-
- ) : (
- <>
- {inlineRelationFieldMetadataItems?.map(
- (fieldMetadataItem, index) => (
-
-
-
- ),
- )}
- {inlineFieldMetadataItems?.map((fieldMetadataItem, index) => (
-
-
-
- ))}
- >
- )}
-
-
- {boxedRelationFieldMetadataItems?.map((fieldMetadataItem, index) => (
-
-
-
- ))}
- >
- )}
- >
+ const tabs = useRecordShowContainerTabs(
+ loading,
+ objectNameSingular as CoreObjectNameSingular,
+ isInRightDrawer,
);
return (
@@ -324,23 +46,15 @@ export const RecordShowContainer = ({
/>
)}
-
- {!isMobile && summaryCard}
- {!isMobile && fieldsBox}
-
- >}
- fieldsBox={fieldsBox}
loading={isPrefetchLoading || loading || recordLoading}
+ isNewRightDrawerItemLoading={isNewRightDrawerItemLoading}
/>
>
diff --git a/packages/twenty-front/src/modules/object-record/record-show/components/SummaryCard.tsx b/packages/twenty-front/src/modules/object-record/record-show/components/SummaryCard.tsx
new file mode 100644
index 000000000..05b76a1e9
--- /dev/null
+++ b/packages/twenty-front/src/modules/object-record/record-show/components/SummaryCard.tsx
@@ -0,0 +1,100 @@
+import { useGetStandardObjectIcon } from '@/object-metadata/hooks/useGetStandardObjectIcon';
+import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
+import { FieldContext } from '@/object-record/record-field/contexts/FieldContext';
+import { RecordInlineCell } from '@/object-record/record-inline-cell/components/RecordInlineCell';
+import { InlineCellHotkeyScope } from '@/object-record/record-inline-cell/types/InlineCellHotkeyScope';
+import { useRecordShowContainerActions } from '@/object-record/record-show/hooks/useRecordShowContainerActions';
+import { useRecordShowContainerData } from '@/object-record/record-show/hooks/useRecordShowContainerData';
+import { ShowPageSummaryCard } from '@/ui/layout/show-page/components/ShowPageSummaryCard';
+import { ShowPageSummaryCardSkeletonLoader } from '@/ui/layout/show-page/components/ShowPageSummaryCardSkeletonLoader';
+import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile';
+import { FieldMetadataType } from '~/generated/graphql';
+import { isDefined } from '~/utils/isDefined';
+
+type SummaryCardProps = {
+ objectNameSingular: string;
+ objectRecordId: string;
+ isNewRightDrawerItemLoading: boolean;
+ isInRightDrawer: boolean;
+};
+
+export const SummaryCard = ({
+ objectNameSingular,
+ objectRecordId,
+ isNewRightDrawerItemLoading,
+ isInRightDrawer,
+}: SummaryCardProps) => {
+ const {
+ recordFromStore,
+ recordLoading,
+ objectMetadataItem,
+ labelIdentifierFieldMetadataItem,
+ isPrefetchLoading,
+ recordIdentifier,
+ } = useRecordShowContainerData({
+ objectNameSingular,
+ objectRecordId,
+ });
+
+ const { onUploadPicture, useUpdateOneObjectRecordMutation } =
+ useRecordShowContainerActions({
+ objectNameSingular,
+ objectRecordId,
+ recordFromStore,
+ });
+
+ const { Icon, IconColor } = useGetStandardObjectIcon(objectNameSingular);
+ const isMobile = useIsMobile() || isInRightDrawer;
+ const isReadOnly = objectMetadataItem.isRemote;
+
+ if (isNewRightDrawerItemLoading || !isDefined(recordFromStore)) {
+ return ;
+ }
+
+ return (
+
+
+
+ }
+ avatarType={recordIdentifier?.avatarType ?? 'rounded'}
+ onUploadPicture={
+ objectNameSingular === CoreObjectNameSingular.Person
+ ? onUploadPicture
+ : undefined
+ }
+ />
+ );
+};
diff --git a/packages/twenty-front/src/modules/object-record/record-show/hooks/useRecordShowContainerActions.ts b/packages/twenty-front/src/modules/object-record/record-show/hooks/useRecordShowContainerActions.ts
new file mode 100644
index 000000000..0188f48f6
--- /dev/null
+++ b/packages/twenty-front/src/modules/object-record/record-show/hooks/useRecordShowContainerActions.ts
@@ -0,0 +1,66 @@
+import { useUpdateOneRecord } from '@/object-record/hooks/useUpdateOneRecord';
+import {
+ RecordUpdateHook,
+ RecordUpdateHookParams,
+} from '@/object-record/record-field/contexts/FieldContext';
+import { ObjectRecord } from '@/object-record/types/ObjectRecord';
+import { FileFolder } from '~/generated-metadata/graphql';
+import { useUploadImageMutation } from '~/generated/graphql';
+import { isUndefinedOrNull } from '~/utils/isUndefinedOrNull';
+
+interface UseRecordShowContainerActionsProps {
+ objectNameSingular: string;
+ objectRecordId: string;
+ recordFromStore: ObjectRecord | null;
+}
+
+export const useRecordShowContainerActions = ({
+ objectNameSingular,
+ objectRecordId,
+ recordFromStore,
+}: UseRecordShowContainerActionsProps) => {
+ const [uploadImage] = useUploadImageMutation();
+ const { updateOneRecord } = useUpdateOneRecord({ objectNameSingular });
+
+ const useUpdateOneObjectRecordMutation: RecordUpdateHook = () => {
+ const updateEntity = ({ variables }: RecordUpdateHookParams) => {
+ updateOneRecord?.({
+ idToUpdate: variables.where.id as string,
+ updateOneRecordInput: variables.updateOneRecordInput,
+ });
+ };
+
+ return [updateEntity, { loading: false }];
+ };
+
+ const onUploadPicture = async (file: File) => {
+ if (objectNameSingular !== 'person') {
+ return;
+ }
+
+ const result = await uploadImage({
+ variables: {
+ file,
+ fileFolder: FileFolder.PersonPicture,
+ },
+ });
+
+ const avatarUrl = result?.data?.uploadImage;
+
+ if (!avatarUrl || isUndefinedOrNull(updateOneRecord) || !recordFromStore) {
+ return;
+ }
+
+ await updateOneRecord({
+ idToUpdate: objectRecordId,
+ updateOneRecordInput: {
+ avatarUrl,
+ },
+ });
+ };
+
+ return {
+ onUploadPicture,
+ useUpdateOneObjectRecordMutation,
+ };
+};
diff --git a/packages/twenty-front/src/modules/object-record/record-show/hooks/useRecordShowContainerData.ts b/packages/twenty-front/src/modules/object-record/record-show/hooks/useRecordShowContainerData.ts
new file mode 100644
index 000000000..15eeb056b
--- /dev/null
+++ b/packages/twenty-front/src/modules/object-record/record-show/hooks/useRecordShowContainerData.ts
@@ -0,0 +1,57 @@
+import { useLabelIdentifierFieldMetadataItem } from '@/object-metadata/hooks/useLabelIdentifierFieldMetadataItem';
+import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
+import { useObjectMetadataItems } from '@/object-metadata/hooks/useObjectMetadataItems';
+import { recordLoadingFamilyState } from '@/object-record/record-store/states/recordLoadingFamilyState';
+import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
+import { recordStoreIdentifierFamilySelector } from '@/object-record/record-store/states/selectors/recordStoreIdentifierSelector';
+import { ObjectRecord } from '@/object-record/types/ObjectRecord';
+import { useIsPrefetchLoading } from '@/prefetch/hooks/useIsPrefetchLoading';
+import { useRecoilState, useRecoilValue } from 'recoil';
+
+type UseRecordShowContainerDataProps = {
+ objectNameSingular: string;
+ objectRecordId: string;
+};
+
+export const useRecordShowContainerData = ({
+ objectNameSingular,
+ objectRecordId,
+}: UseRecordShowContainerDataProps) => {
+ const { objectMetadataItem } = useObjectMetadataItem({
+ objectNameSingular,
+ });
+
+ const { labelIdentifierFieldMetadataItem } =
+ useLabelIdentifierFieldMetadataItem({
+ objectNameSingular,
+ });
+
+ const [recordLoading] = useRecoilState(
+ recordLoadingFamilyState(objectRecordId),
+ );
+
+ const [recordFromStore] = useRecoilState(
+ recordStoreFamilyState(objectRecordId),
+ );
+
+ const recordIdentifier = useRecoilValue(
+ recordStoreIdentifierFamilySelector({
+ objectNameSingular,
+ recordId: objectRecordId,
+ }),
+ );
+
+ const isPrefetchLoading = useIsPrefetchLoading();
+
+ const { objectMetadataItems } = useObjectMetadataItems();
+
+ return {
+ recordFromStore,
+ recordLoading,
+ objectMetadataItem,
+ labelIdentifierFieldMetadataItem,
+ isPrefetchLoading,
+ recordIdentifier,
+ objectMetadataItems,
+ };
+};
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
new file mode 100644
index 000000000..1d029f487
--- /dev/null
+++ b/packages/twenty-front/src/modules/object-record/record-show/hooks/useRecordShowContainerTabs.ts
@@ -0,0 +1,110 @@
+import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
+import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile';
+import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
+import {
+ IconCalendarEvent,
+ IconCheckbox,
+ IconList,
+ IconMail,
+ IconNotes,
+ IconPaperclip,
+ IconSettings,
+ IconTimelineEvent,
+} from 'twenty-ui';
+
+export const useRecordShowContainerTabs = (
+ loading: boolean,
+ targetObjectNameSingular: CoreObjectNameSingular,
+ isInRightDrawer: boolean,
+) => {
+ const isMobile = useIsMobile();
+ const isWorkflowEnabled = useIsFeatureEnabled('IS_WORKFLOW_ENABLED');
+
+ const isWorkflow =
+ isWorkflowEnabled &&
+ targetObjectNameSingular === CoreObjectNameSingular.Workflow;
+ const isWorkflowVersion =
+ isWorkflowEnabled &&
+ targetObjectNameSingular === CoreObjectNameSingular.WorkflowVersion;
+
+ const isCompanyOrPerson = [
+ CoreObjectNameSingular.Company,
+ CoreObjectNameSingular.Person,
+ ].includes(targetObjectNameSingular);
+ const shouldDisplayCalendarTab = isCompanyOrPerson;
+ const shouldDisplayEmailsTab = isCompanyOrPerson;
+
+ return [
+ {
+ id: 'richText',
+ title: 'Note',
+ Icon: IconNotes,
+ hide:
+ loading ||
+ (targetObjectNameSingular !== CoreObjectNameSingular.Note &&
+ targetObjectNameSingular !== CoreObjectNameSingular.Task),
+ },
+ {
+ id: 'fields',
+ title: 'Fields',
+ Icon: IconList,
+ hide: !(isMobile || isInRightDrawer),
+ },
+ {
+ id: 'timeline',
+ title: 'Timeline',
+ Icon: IconTimelineEvent,
+ hide: isInRightDrawer || isWorkflow || isWorkflowVersion,
+ },
+ {
+ id: 'tasks',
+ title: 'Tasks',
+ Icon: IconCheckbox,
+ hide:
+ targetObjectNameSingular === CoreObjectNameSingular.Note ||
+ targetObjectNameSingular === CoreObjectNameSingular.Task ||
+ isWorkflow ||
+ isWorkflowVersion,
+ },
+ {
+ id: 'notes',
+ title: 'Notes',
+ Icon: IconNotes,
+ hide:
+ targetObjectNameSingular === CoreObjectNameSingular.Note ||
+ targetObjectNameSingular === CoreObjectNameSingular.Task ||
+ isWorkflow ||
+ isWorkflowVersion,
+ },
+ {
+ id: 'files',
+ title: 'Files',
+ Icon: IconPaperclip,
+ hide: isWorkflow || isWorkflowVersion,
+ },
+ {
+ id: 'emails',
+ title: 'Emails',
+ Icon: IconMail,
+ hide: !shouldDisplayEmailsTab,
+ },
+ {
+ id: 'calendar',
+ title: 'Calendar',
+ Icon: IconCalendarEvent,
+ hide: !shouldDisplayCalendarTab,
+ },
+ {
+ id: 'workflow',
+ title: 'Workflow',
+ Icon: IconSettings,
+ hide: !isWorkflow,
+ },
+ {
+ id: 'workflowVersion',
+ title: 'Workflow Version',
+ Icon: IconSettings,
+ hide: !isWorkflowVersion,
+ },
+ ];
+};
diff --git a/packages/twenty-front/src/modules/ui/layout/show-page/components/ShowPageRightContainer.tsx b/packages/twenty-front/src/modules/ui/layout/show-page/components/ShowPageSubContainer.tsx
similarity index 59%
rename from packages/twenty-front/src/modules/ui/layout/show-page/components/ShowPageRightContainer.tsx
rename to packages/twenty-front/src/modules/ui/layout/show-page/components/ShowPageSubContainer.tsx
index 449963c01..017f38fd9 100644
--- a/packages/twenty-front/src/modules/ui/layout/show-page/components/ShowPageRightContainer.tsx
+++ b/packages/twenty-front/src/modules/ui/layout/show-page/components/ShowPageSubContainer.tsx
@@ -8,32 +8,24 @@ import { ActivityTargetableObject } from '@/activities/types/ActivityTargetableE
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
import { useDeleteOneRecord } from '@/object-record/hooks/useDeleteOneRecord';
import { isNewViewableRecordLoadingState } from '@/object-record/record-right-drawer/states/isNewViewableRecordLoading';
+import { FieldsCard } from '@/object-record/record-show/components/FieldsCard';
+import { SummaryCard } from '@/object-record/record-show/components/SummaryCard';
import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
import { ObjectRecord } from '@/object-record/types/ObjectRecord';
import { Button } from '@/ui/input/button/components/Button';
import { ShowPageActivityContainer } from '@/ui/layout/show-page/components/ShowPageActivityContainer';
-import { TabList } from '@/ui/layout/tab/components/TabList';
+import { ShowPageLeftContainer } from '@/ui/layout/show-page/components/ShowPageLeftContainer';
+import { SingleTabProps, TabList } from '@/ui/layout/tab/components/TabList';
import { useTabList } from '@/ui/layout/tab/hooks/useTabList';
import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile';
import { WorkflowVersionVisualizer } from '@/workflow/components/WorkflowVersionVisualizer';
import { WorkflowVersionVisualizerEffect } from '@/workflow/components/WorkflowVersionVisualizerEffect';
import { WorkflowVisualizer } from '@/workflow/components/WorkflowVisualizer';
import { WorkflowVisualizerEffect } from '@/workflow/components/WorkflowVisualizerEffect';
-import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
import styled from '@emotion/styled';
import { useState } from 'react';
import { useRecoilState, useRecoilValue } from 'recoil';
-import {
- IconCalendarEvent,
- IconCheckbox,
- IconList,
- IconMail,
- IconNotes,
- IconPaperclip,
- IconSettings,
- IconTimelineEvent,
- IconTrash,
-} from 'twenty-ui';
+import { IconTrash } from 'twenty-ui';
const StyledShowPageRightContainer = styled.div<{ isMobile: boolean }>`
display: flex;
@@ -89,145 +81,51 @@ const StyledContentContainer = styled.div<{ isInRightDrawer: boolean }>`
export const TAB_LIST_COMPONENT_ID = 'show-page-right-tab-list';
-type ShowPageRightContainerProps = {
+type ShowPageSubContainerProps = {
+ tabs: SingleTabProps[];
targetableObject: Pick<
ActivityTargetableObject,
'targetObjectNameSingular' | 'id'
>;
- timeline?: boolean;
- tasks?: boolean;
- notes?: boolean;
- emails?: boolean;
- fieldsBox?: JSX.Element;
- summaryCard?: JSX.Element;
isInRightDrawer?: boolean;
loading: boolean;
+ isNewRightDrawerItemLoading?: boolean;
};
-export const ShowPageRightContainer = ({
+export const ShowPageSubContainer = ({
+ tabs,
targetableObject,
- timeline,
- tasks,
- notes,
- emails,
loading,
- fieldsBox,
- summaryCard,
isInRightDrawer = false,
-}: ShowPageRightContainerProps) => {
+ isNewRightDrawerItemLoading = false,
+}: ShowPageSubContainerProps) => {
const { activeTabIdState } = useTabList(
`${TAB_LIST_COMPONENT_ID}-${isInRightDrawer}`,
);
const activeTabId = useRecoilValue(activeTabIdState);
- const targetObjectNameSingular =
- targetableObject.targetObjectNameSingular as CoreObjectNameSingular;
-
- const isCompanyOrPerson = [
- CoreObjectNameSingular.Company,
- CoreObjectNameSingular.Person,
- ].includes(targetObjectNameSingular);
-
- const isWorkflowEnabled = useIsFeatureEnabled('IS_WORKFLOW_ENABLED');
- const isWorkflow =
- isWorkflowEnabled &&
- targetableObject.targetObjectNameSingular ===
- CoreObjectNameSingular.Workflow;
- const isWorkflowVersion =
- isWorkflowEnabled &&
- targetableObject.targetObjectNameSingular ===
- CoreObjectNameSingular.WorkflowVersion;
-
- const shouldDisplayCalendarTab = isCompanyOrPerson;
- const shouldDisplayEmailsTab = emails && isCompanyOrPerson;
-
const isMobile = useIsMobile();
const isNewViewableRecordLoading = useRecoilValue(
isNewViewableRecordLoadingState,
);
- const tabs = [
- {
- id: 'richText',
- title: 'Note',
- Icon: IconNotes,
- hide:
- loading ||
- (targetableObject.targetObjectNameSingular !==
- CoreObjectNameSingular.Note &&
- targetableObject.targetObjectNameSingular !==
- CoreObjectNameSingular.Task),
- },
- {
- id: 'fields',
- title: 'Fields',
- Icon: IconList,
- hide: !(isMobile || isInRightDrawer),
- },
- {
- id: 'timeline',
- title: 'Timeline',
- Icon: IconTimelineEvent,
- hide: !timeline || isInRightDrawer || isWorkflow || isWorkflowVersion,
- },
- {
- id: 'tasks',
- title: 'Tasks',
- Icon: IconCheckbox,
- hide:
- !tasks ||
- targetableObject.targetObjectNameSingular ===
- CoreObjectNameSingular.Note ||
- targetableObject.targetObjectNameSingular ===
- CoreObjectNameSingular.Task ||
- isWorkflow ||
- isWorkflowVersion,
- },
- {
- id: 'notes',
- title: 'Notes',
- Icon: IconNotes,
- hide:
- !notes ||
- targetableObject.targetObjectNameSingular ===
- CoreObjectNameSingular.Note ||
- targetableObject.targetObjectNameSingular ===
- CoreObjectNameSingular.Task ||
- isWorkflow ||
- isWorkflowVersion,
- },
- {
- id: 'files',
- title: 'Files',
- Icon: IconPaperclip,
- hide: !notes || isWorkflow || isWorkflowVersion,
- },
- {
- id: 'emails',
- title: 'Emails',
- Icon: IconMail,
- hide: !shouldDisplayEmailsTab,
- },
- {
- id: 'calendar',
- title: 'Calendar',
- Icon: IconCalendarEvent,
- hide: !shouldDisplayCalendarTab,
- },
- {
- id: 'workflow',
- title: 'Workflow',
- Icon: IconSettings,
- hide: !isWorkflow,
- },
- {
- id: 'workflowVersion',
- title: 'Workflow Version',
- Icon: IconSettings,
- hide: !isWorkflowVersion,
- },
- ];
+ const summaryCard = (
+
+ );
+
+ const fieldsCard = (
+
+ );
+
const renderActiveTabContent = () => {
switch (activeTabId) {
case 'timeline':
@@ -251,10 +149,9 @@ export const ShowPageRightContainer = ({
case 'fields':
return (
- {fieldsBox}
+ {fieldsCard}
);
-
case 'tasks':
return ;
case 'notes':
@@ -307,28 +204,36 @@ export const ShowPageRightContainer = ({
);
return (
-
-
-
-
- {summaryCard}
-
- {renderActiveTabContent()}
-
- {isInRightDrawer && recordFromStore && !recordFromStore.deletedAt && (
-
-
-
+ <>
+ {!isMobile && !isInRightDrawer && (
+
+ {summaryCard}
+ {fieldsCard}
+
)}
-
+
+
+
+
+ {(isMobile || isInRightDrawer) && summaryCard}
+
+ {renderActiveTabContent()}
+
+ {isInRightDrawer && recordFromStore && !recordFromStore.deletedAt && (
+
+
+
+ )}
+
+ >
);
};
diff --git a/packages/twenty-front/src/modules/ui/layout/tab/components/TabList.tsx b/packages/twenty-front/src/modules/ui/layout/tab/components/TabList.tsx
index c66767a09..8375cc082 100644
--- a/packages/twenty-front/src/modules/ui/layout/tab/components/TabList.tsx
+++ b/packages/twenty-front/src/modules/ui/layout/tab/components/TabList.tsx
@@ -9,7 +9,7 @@ import { ScrollWrapper } from '@/ui/utilities/scroll/components/ScrollWrapper';
import { Tab } from './Tab';
-type SingleTabProps = {
+export type SingleTabProps = {
title: string;
Icon?: IconComponent;
id: string;