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;