mirror of
https://github.com/lingble/twenty.git
synced 2025-10-30 20:27:55 +00:00
Hide tabs (#7841)
@FelixMalfait WDYT? We can refactor shouldDisplay Files/Tasks/Notes Tab etc into a hook. --------- Co-authored-by: Félix Malfait <felix@twenty.com>
This commit is contained in:
@@ -0,0 +1,106 @@
|
|||||||
|
import { Calendar } from '@/activities/calendar/components/Calendar';
|
||||||
|
import { EmailThreads } from '@/activities/emails/components/EmailThreads';
|
||||||
|
import { Attachments } from '@/activities/files/components/Attachments';
|
||||||
|
import { Notes } from '@/activities/notes/components/Notes';
|
||||||
|
import { ObjectTasks } from '@/activities/tasks/components/ObjectTasks';
|
||||||
|
import { TimelineActivities } from '@/activities/timeline-activities/components/TimelineActivities';
|
||||||
|
import { ActivityTargetableObject } from '@/activities/types/ActivityTargetableEntity';
|
||||||
|
import { FieldsCard } from '@/object-record/record-show/components/FieldsCard';
|
||||||
|
import { CardType } from '@/object-record/record-show/types/CardType';
|
||||||
|
import { ShowPageActivityContainer } from '@/ui/layout/show-page/components/ShowPageActivityContainer';
|
||||||
|
import { WorkflowRunOutputVisualizer } from '@/workflow/components/WorkflowRunOutputVisualizer';
|
||||||
|
import { WorkflowRunVersionVisualizer } from '@/workflow/components/WorkflowRunVersionVisualizer';
|
||||||
|
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 styled from '@emotion/styled';
|
||||||
|
|
||||||
|
const StyledGreyBox = styled.div<{ isInRightDrawer?: boolean }>`
|
||||||
|
background: ${({ theme, isInRightDrawer }) =>
|
||||||
|
isInRightDrawer ? theme.background.secondary : ''};
|
||||||
|
border: ${({ isInRightDrawer, theme }) =>
|
||||||
|
isInRightDrawer ? `1px solid ${theme.border.color.medium}` : ''};
|
||||||
|
border-radius: ${({ isInRightDrawer, theme }) =>
|
||||||
|
isInRightDrawer ? theme.border.radius.md : ''};
|
||||||
|
height: ${({ isInRightDrawer }) => (isInRightDrawer ? 'auto' : '100%')};
|
||||||
|
|
||||||
|
margin: ${({ isInRightDrawer, theme }) =>
|
||||||
|
isInRightDrawer ? theme.spacing(4) : ''};
|
||||||
|
`;
|
||||||
|
|
||||||
|
type CardComponentProps = {
|
||||||
|
targetableObject: Pick<
|
||||||
|
ActivityTargetableObject,
|
||||||
|
'targetObjectNameSingular' | 'id'
|
||||||
|
>;
|
||||||
|
isInRightDrawer?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
type CardComponentType = (props: CardComponentProps) => JSX.Element | null;
|
||||||
|
|
||||||
|
export const CardComponents: Record<CardType, CardComponentType> = {
|
||||||
|
[CardType.TimelineCard]: ({ targetableObject, isInRightDrawer }) => (
|
||||||
|
<TimelineActivities
|
||||||
|
targetableObject={targetableObject}
|
||||||
|
isInRightDrawer={isInRightDrawer}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
|
||||||
|
[CardType.FieldCard]: ({ targetableObject, isInRightDrawer }) => (
|
||||||
|
<StyledGreyBox isInRightDrawer={isInRightDrawer}>
|
||||||
|
<FieldsCard
|
||||||
|
objectNameSingular={targetableObject.targetObjectNameSingular}
|
||||||
|
objectRecordId={targetableObject.id}
|
||||||
|
/>
|
||||||
|
</StyledGreyBox>
|
||||||
|
),
|
||||||
|
|
||||||
|
[CardType.RichTextCard]: ({ targetableObject }) => (
|
||||||
|
<ShowPageActivityContainer targetableObject={targetableObject} />
|
||||||
|
),
|
||||||
|
|
||||||
|
[CardType.TaskCard]: ({ targetableObject }) => (
|
||||||
|
<ObjectTasks targetableObject={targetableObject} />
|
||||||
|
),
|
||||||
|
|
||||||
|
[CardType.NoteCard]: ({ targetableObject }) => (
|
||||||
|
<Notes targetableObject={targetableObject} />
|
||||||
|
),
|
||||||
|
|
||||||
|
[CardType.FileCard]: ({ targetableObject }) => (
|
||||||
|
<Attachments targetableObject={targetableObject} />
|
||||||
|
),
|
||||||
|
|
||||||
|
[CardType.EmailCard]: ({ targetableObject }) => (
|
||||||
|
<EmailThreads targetableObject={targetableObject} />
|
||||||
|
),
|
||||||
|
|
||||||
|
[CardType.CalendarCard]: ({ targetableObject }) => (
|
||||||
|
<Calendar targetableObject={targetableObject} />
|
||||||
|
),
|
||||||
|
|
||||||
|
[CardType.WorkflowCard]: ({ targetableObject }) => (
|
||||||
|
<>
|
||||||
|
<WorkflowVisualizerEffect workflowId={targetableObject.id} />
|
||||||
|
<WorkflowVisualizer targetableObject={targetableObject} />
|
||||||
|
</>
|
||||||
|
),
|
||||||
|
|
||||||
|
[CardType.WorkflowVersionCard]: ({ targetableObject }) => (
|
||||||
|
<>
|
||||||
|
<WorkflowVersionVisualizerEffect
|
||||||
|
workflowVersionId={targetableObject.id}
|
||||||
|
/>
|
||||||
|
<WorkflowVersionVisualizer workflowVersionId={targetableObject.id} />
|
||||||
|
</>
|
||||||
|
),
|
||||||
|
|
||||||
|
[CardType.WorkflowRunCard]: ({ targetableObject }) => (
|
||||||
|
<WorkflowRunVersionVisualizer workflowRunId={targetableObject.id} />
|
||||||
|
),
|
||||||
|
|
||||||
|
[CardType.WorkflowRunOutputCard]: ({ targetableObject }) => (
|
||||||
|
<WorkflowRunOutputVisualizer workflowRunId={targetableObject.id} />
|
||||||
|
),
|
||||||
|
};
|
||||||
@@ -39,6 +39,7 @@ export const RecordShowContainer = ({
|
|||||||
loading,
|
loading,
|
||||||
objectNameSingular as CoreObjectNameSingular,
|
objectNameSingular as CoreObjectNameSingular,
|
||||||
isInRightDrawer,
|
isInRightDrawer,
|
||||||
|
objectMetadataItem,
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -0,0 +1,83 @@
|
|||||||
|
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
||||||
|
import { CardType } from '@/object-record/record-show/types/CardType';
|
||||||
|
import { RecordLayoutTab } from '@/ui/layout/tab/types/RecordLayoutTab';
|
||||||
|
import {
|
||||||
|
IconCheckbox,
|
||||||
|
IconList,
|
||||||
|
IconNotes,
|
||||||
|
IconPaperclip,
|
||||||
|
IconTimelineEvent,
|
||||||
|
} from 'twenty-ui';
|
||||||
|
|
||||||
|
export const BASE_RECORD_LAYOUT: Record<string, RecordLayoutTab> = {
|
||||||
|
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: [],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
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'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
files: {
|
||||||
|
title: 'Files',
|
||||||
|
Icon: IconPaperclip,
|
||||||
|
position: 500,
|
||||||
|
cards: [{ type: CardType.FileCard }],
|
||||||
|
hide: {
|
||||||
|
ifMobile: false,
|
||||||
|
ifDesktop: false,
|
||||||
|
ifInRightDrawer: false,
|
||||||
|
ifFeaturesDisabled: [],
|
||||||
|
ifRequiredObjectsInactive: [CoreObjectNameSingular.Attachment],
|
||||||
|
ifRelationsMissing: ['attachments'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
@@ -1,125 +1,265 @@
|
|||||||
|
import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState';
|
||||||
|
import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
|
||||||
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
||||||
|
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
||||||
|
import { BASE_RECORD_LAYOUT } from '@/object-record/record-show/constants/BaseRecordLayout';
|
||||||
|
import { CardType } from '@/object-record/record-show/types/CardType';
|
||||||
|
import { RecordLayout } from '@/object-record/record-show/types/RecordLayout';
|
||||||
|
import { SingleTabProps } from '@/ui/layout/tab/components/TabList';
|
||||||
|
import { RecordLayoutTab } from '@/ui/layout/tab/types/RecordLayoutTab';
|
||||||
import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile';
|
import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile';
|
||||||
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
|
import { useRecoilValue } from 'recoil';
|
||||||
import {
|
import {
|
||||||
IconCalendarEvent,
|
IconCalendarEvent,
|
||||||
IconCheckbox,
|
|
||||||
IconList,
|
|
||||||
IconMail,
|
IconMail,
|
||||||
IconNotes,
|
IconNotes,
|
||||||
IconPaperclip,
|
|
||||||
IconPrinter,
|
IconPrinter,
|
||||||
IconSettings,
|
IconSettings,
|
||||||
IconTimelineEvent,
|
|
||||||
} from 'twenty-ui';
|
} from 'twenty-ui';
|
||||||
|
import { FeatureFlag, FieldMetadataType } from '~/generated-metadata/graphql';
|
||||||
|
|
||||||
export const useRecordShowContainerTabs = (
|
export const useRecordShowContainerTabs = (
|
||||||
loading: boolean,
|
loading: boolean,
|
||||||
targetObjectNameSingular: CoreObjectNameSingular,
|
targetObjectNameSingular: CoreObjectNameSingular,
|
||||||
isInRightDrawer: boolean,
|
isInRightDrawer: boolean,
|
||||||
) => {
|
objectMetadataItem: ObjectMetadataItem,
|
||||||
|
): SingleTabProps[] => {
|
||||||
const isMobile = useIsMobile();
|
const isMobile = useIsMobile();
|
||||||
const isWorkflowEnabled = useIsFeatureEnabled('IS_WORKFLOW_ENABLED');
|
const objectMetadataItems = useRecoilValue(objectMetadataItemsState);
|
||||||
|
|
||||||
const isWorkflow =
|
const currentWorkspace = useRecoilValue(currentWorkspaceState);
|
||||||
isWorkflowEnabled &&
|
|
||||||
targetObjectNameSingular === CoreObjectNameSingular.Workflow;
|
|
||||||
const isWorkflowVersion =
|
|
||||||
isWorkflowEnabled &&
|
|
||||||
targetObjectNameSingular === CoreObjectNameSingular.WorkflowVersion;
|
|
||||||
const isWorkflowRun =
|
|
||||||
isWorkflowEnabled &&
|
|
||||||
targetObjectNameSingular === CoreObjectNameSingular.WorkflowRun;
|
|
||||||
const isWorkflowRelated = isWorkflow || isWorkflowVersion || isWorkflowRun;
|
|
||||||
|
|
||||||
const isCompanyOrPerson = [
|
// Object-specific layouts that override or extend the base layout
|
||||||
CoreObjectNameSingular.Company,
|
const OBJECT_SPECIFIC_LAYOUTS: Partial<
|
||||||
CoreObjectNameSingular.Person,
|
Record<CoreObjectNameSingular, RecordLayout>
|
||||||
].includes(targetObjectNameSingular);
|
> = {
|
||||||
const shouldDisplayCalendarTab = isCompanyOrPerson;
|
[CoreObjectNameSingular.Note]: {
|
||||||
const shouldDisplayEmailsTab = isCompanyOrPerson;
|
richText: {
|
||||||
|
|
||||||
return [
|
|
||||||
{
|
|
||||||
id: 'richText',
|
|
||||||
title: 'Note',
|
title: 'Note',
|
||||||
|
position: 0,
|
||||||
Icon: IconNotes,
|
Icon: IconNotes,
|
||||||
|
cards: [{ type: CardType.RichTextCard }],
|
||||||
|
hide: {
|
||||||
|
ifMobile: false,
|
||||||
|
ifDesktop: false,
|
||||||
|
ifInRightDrawer: false,
|
||||||
|
ifFeaturesDisabled: [],
|
||||||
|
ifRequiredObjectsInactive: [],
|
||||||
|
ifRelationsMissing: [],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
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: [],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
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: [],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
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: [],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
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: 'Workflow',
|
||||||
|
position: 0,
|
||||||
|
Icon: IconSettings,
|
||||||
|
cards: [{ type: CardType.WorkflowCard }],
|
||||||
|
hide: {
|
||||||
|
ifMobile: false,
|
||||||
|
ifDesktop: false,
|
||||||
|
ifInRightDrawer: false,
|
||||||
|
ifFeaturesDisabled: ['IS_WORKFLOW_ENABLED'],
|
||||||
|
ifRequiredObjectsInactive: [],
|
||||||
|
ifRelationsMissing: [],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
[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: [],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
[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: [],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
workflowRunFlow: {
|
||||||
|
title: 'Flow',
|
||||||
|
position: 0,
|
||||||
|
Icon: IconSettings,
|
||||||
|
cards: [{ type: CardType.WorkflowRunCard }],
|
||||||
|
hide: {
|
||||||
|
ifMobile: false,
|
||||||
|
ifDesktop: false,
|
||||||
|
ifInRightDrawer: false,
|
||||||
|
ifFeaturesDisabled: ['IS_WORKFLOW_ENABLED'],
|
||||||
|
ifRequiredObjectsInactive: [],
|
||||||
|
ifRelationsMissing: [],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
// Merge base layout with object-specific layout
|
||||||
|
const tabDefinitions: RecordLayout = {
|
||||||
|
...BASE_RECORD_LAYOUT,
|
||||||
|
...(OBJECT_SPECIFIC_LAYOUTS[targetObjectNameSingular] || {}),
|
||||||
|
};
|
||||||
|
|
||||||
|
return Object.entries(tabDefinitions)
|
||||||
|
.filter(
|
||||||
|
(entry): entry is [string, NonNullable<RecordLayoutTab>] =>
|
||||||
|
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:
|
hide:
|
||||||
loading ||
|
loading ||
|
||||||
(targetObjectNameSingular !== CoreObjectNameSingular.Note &&
|
baseHide ||
|
||||||
targetObjectNameSingular !== CoreObjectNameSingular.Task),
|
featureNotEnabled ||
|
||||||
},
|
requiredObjectsInactive ||
|
||||||
{
|
relationsDontExist,
|
||||||
id: 'fields',
|
};
|
||||||
title: 'Fields',
|
});
|
||||||
Icon: IconList,
|
|
||||||
hide: !(isMobile || isInRightDrawer),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'timeline',
|
|
||||||
title: 'Timeline',
|
|
||||||
Icon: IconTimelineEvent,
|
|
||||||
hide: isInRightDrawer || isWorkflowRelated,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'tasks',
|
|
||||||
title: 'Tasks',
|
|
||||||
Icon: IconCheckbox,
|
|
||||||
hide:
|
|
||||||
targetObjectNameSingular === CoreObjectNameSingular.Note ||
|
|
||||||
targetObjectNameSingular === CoreObjectNameSingular.Task ||
|
|
||||||
isWorkflowRelated,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'notes',
|
|
||||||
title: 'Notes',
|
|
||||||
Icon: IconNotes,
|
|
||||||
hide:
|
|
||||||
targetObjectNameSingular === CoreObjectNameSingular.Note ||
|
|
||||||
targetObjectNameSingular === CoreObjectNameSingular.Task ||
|
|
||||||
isWorkflowRelated,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'files',
|
|
||||||
title: 'Files',
|
|
||||||
Icon: IconPaperclip,
|
|
||||||
hide: isWorkflowRelated,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
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: 'Flow',
|
|
||||||
Icon: IconSettings,
|
|
||||||
hide: !isWorkflowVersion,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'workflowRunOutput',
|
|
||||||
title: 'Output',
|
|
||||||
Icon: IconPrinter,
|
|
||||||
hide: !isWorkflowRun,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'workflowRunFlow',
|
|
||||||
title: 'Flow',
|
|
||||||
Icon: IconSettings,
|
|
||||||
hide: !isWorkflowRun,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -0,0 +1,14 @@
|
|||||||
|
export enum CardType {
|
||||||
|
FieldCard = 'FieldCard',
|
||||||
|
TimelineCard = 'TimelineCard',
|
||||||
|
TaskCard = 'TaskCard',
|
||||||
|
NoteCard = 'NoteCard',
|
||||||
|
FileCard = 'FileCard',
|
||||||
|
EmailCard = 'EmailCard',
|
||||||
|
CalendarCard = 'CalendarCard',
|
||||||
|
WorkflowCard = 'WorkflowCard',
|
||||||
|
WorkflowVersionCard = 'WorkflowVersionCard',
|
||||||
|
WorkflowRunCard = 'WorkflowRunCard',
|
||||||
|
WorkflowRunOutputCard = 'WorkflowRunOutputCard',
|
||||||
|
RichTextCard = 'RichTextCard',
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
import { RecordLayoutTab } from '@/ui/layout/tab/types/RecordLayoutTab';
|
||||||
|
|
||||||
|
export type RecordLayout = Record<string, RecordLayoutTab | null>;
|
||||||
@@ -1,28 +1,15 @@
|
|||||||
import { RecordShowRightDrawerActionMenu } from '@/action-menu/components/RecordShowRightDrawerActionMenu';
|
import { RecordShowRightDrawerActionMenu } from '@/action-menu/components/RecordShowRightDrawerActionMenu';
|
||||||
import { Calendar } from '@/activities/calendar/components/Calendar';
|
|
||||||
import { EmailThreads } from '@/activities/emails/components/EmailThreads';
|
|
||||||
import { Attachments } from '@/activities/files/components/Attachments';
|
|
||||||
import { Notes } from '@/activities/notes/components/Notes';
|
|
||||||
import { ObjectTasks } from '@/activities/tasks/components/ObjectTasks';
|
|
||||||
import { TimelineActivities } from '@/activities/timeline-activities/components/TimelineActivities';
|
|
||||||
import { ActivityTargetableObject } from '@/activities/types/ActivityTargetableEntity';
|
import { ActivityTargetableObject } from '@/activities/types/ActivityTargetableEntity';
|
||||||
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
|
||||||
import { isNewViewableRecordLoadingState } from '@/object-record/record-right-drawer/states/isNewViewableRecordLoading';
|
import { isNewViewableRecordLoadingState } from '@/object-record/record-right-drawer/states/isNewViewableRecordLoading';
|
||||||
|
import { CardComponents } from '@/object-record/record-show/components/CardComponents';
|
||||||
import { FieldsCard } from '@/object-record/record-show/components/FieldsCard';
|
import { FieldsCard } from '@/object-record/record-show/components/FieldsCard';
|
||||||
import { SummaryCard } from '@/object-record/record-show/components/SummaryCard';
|
import { SummaryCard } from '@/object-record/record-show/components/SummaryCard';
|
||||||
import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
|
import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
|
||||||
import { ObjectRecord } from '@/object-record/types/ObjectRecord';
|
import { ObjectRecord } from '@/object-record/types/ObjectRecord';
|
||||||
import { ShowPageActivityContainer } from '@/ui/layout/show-page/components/ShowPageActivityContainer';
|
|
||||||
import { ShowPageLeftContainer } from '@/ui/layout/show-page/components/ShowPageLeftContainer';
|
import { ShowPageLeftContainer } from '@/ui/layout/show-page/components/ShowPageLeftContainer';
|
||||||
import { SingleTabProps, TabList } from '@/ui/layout/tab/components/TabList';
|
import { SingleTabProps, TabList } from '@/ui/layout/tab/components/TabList';
|
||||||
import { useTabList } from '@/ui/layout/tab/hooks/useTabList';
|
import { useTabList } from '@/ui/layout/tab/hooks/useTabList';
|
||||||
import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile';
|
import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile';
|
||||||
import { WorkflowRunOutputVisualizer } from '@/workflow/components/WorkflowRunOutputVisualizer';
|
|
||||||
import { WorkflowRunVersionVisualizer } from '@/workflow/components/WorkflowRunVersionVisualizer';
|
|
||||||
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 styled from '@emotion/styled';
|
import styled from '@emotion/styled';
|
||||||
import { useRecoilState, useRecoilValue } from 'recoil';
|
import { useRecoilState, useRecoilValue } from 'recoil';
|
||||||
|
|
||||||
@@ -46,19 +33,6 @@ const StyledTabListContainer = styled.div`
|
|||||||
height: 40px;
|
height: 40px;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const StyledGreyBox = styled.div<{ isInRightDrawer: boolean }>`
|
|
||||||
background: ${({ theme, isInRightDrawer }) =>
|
|
||||||
isInRightDrawer ? theme.background.secondary : ''};
|
|
||||||
border: ${({ isInRightDrawer, theme }) =>
|
|
||||||
isInRightDrawer ? `1px solid ${theme.border.color.medium}` : ''};
|
|
||||||
border-radius: ${({ isInRightDrawer, theme }) =>
|
|
||||||
isInRightDrawer ? theme.border.radius.md : ''};
|
|
||||||
height: ${({ isInRightDrawer }) => (isInRightDrawer ? 'auto' : '100%')};
|
|
||||||
|
|
||||||
margin: ${({ isInRightDrawer, theme }) =>
|
|
||||||
isInRightDrawer ? theme.spacing(4) : ''};
|
|
||||||
`;
|
|
||||||
|
|
||||||
const StyledButtonContainer = styled.div`
|
const StyledButtonContainer = styled.div`
|
||||||
align-items: center;
|
align-items: center;
|
||||||
background: ${({ theme }) => theme.background.secondary};
|
background: ${({ theme }) => theme.background.secondary};
|
||||||
@@ -127,72 +101,19 @@ export const ShowPageSubContainer = ({
|
|||||||
);
|
);
|
||||||
|
|
||||||
const renderActiveTabContent = () => {
|
const renderActiveTabContent = () => {
|
||||||
switch (activeTabId) {
|
const activeTab = tabs.find((tab) => tab.id === activeTabId);
|
||||||
case 'timeline':
|
if (!activeTab?.cards?.length) return null;
|
||||||
return (
|
|
||||||
<>
|
return activeTab.cards.map((card, index) => {
|
||||||
<TimelineActivities
|
const CardComponent = CardComponents[card.type];
|
||||||
|
return CardComponent ? (
|
||||||
|
<CardComponent
|
||||||
|
key={`${activeTab.id}-card-${index}`}
|
||||||
targetableObject={targetableObject}
|
targetableObject={targetableObject}
|
||||||
isInRightDrawer={isInRightDrawer}
|
isInRightDrawer={isInRightDrawer}
|
||||||
/>
|
/>
|
||||||
</>
|
) : null;
|
||||||
);
|
});
|
||||||
case 'richText':
|
|
||||||
return (
|
|
||||||
(targetableObject.targetObjectNameSingular ===
|
|
||||||
CoreObjectNameSingular.Note ||
|
|
||||||
targetableObject.targetObjectNameSingular ===
|
|
||||||
CoreObjectNameSingular.Task) && (
|
|
||||||
<ShowPageActivityContainer targetableObject={targetableObject} />
|
|
||||||
)
|
|
||||||
);
|
|
||||||
case 'fields':
|
|
||||||
return (
|
|
||||||
<StyledGreyBox isInRightDrawer={isInRightDrawer}>
|
|
||||||
{fieldsCard}
|
|
||||||
</StyledGreyBox>
|
|
||||||
);
|
|
||||||
case 'tasks':
|
|
||||||
return <ObjectTasks targetableObject={targetableObject} />;
|
|
||||||
case 'notes':
|
|
||||||
return <Notes targetableObject={targetableObject} />;
|
|
||||||
case 'files':
|
|
||||||
return <Attachments targetableObject={targetableObject} />;
|
|
||||||
case 'emails':
|
|
||||||
return <EmailThreads targetableObject={targetableObject} />;
|
|
||||||
case 'calendar':
|
|
||||||
return <Calendar targetableObject={targetableObject} />;
|
|
||||||
case 'workflow':
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<WorkflowVisualizerEffect workflowId={targetableObject.id} />
|
|
||||||
|
|
||||||
<WorkflowVisualizer targetableObject={targetableObject} />
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
case 'workflowVersion':
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<WorkflowVersionVisualizerEffect
|
|
||||||
workflowVersionId={targetableObject.id}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<WorkflowVersionVisualizer
|
|
||||||
workflowVersionId={targetableObject.id}
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
case 'workflowRunFlow':
|
|
||||||
return (
|
|
||||||
<WorkflowRunVersionVisualizer workflowRunId={targetableObject.id} />
|
|
||||||
);
|
|
||||||
case 'workflowRunOutput':
|
|
||||||
return (
|
|
||||||
<WorkflowRunOutputVisualizer workflowRunId={targetableObject.id} />
|
|
||||||
);
|
|
||||||
default:
|
|
||||||
return <></>;
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const [recordFromStore] = useRecoilState<ObjectRecord | null>(
|
const [recordFromStore] = useRecoilState<ObjectRecord | null>(
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import { useTabList } from '@/ui/layout/tab/hooks/useTabList';
|
|||||||
import { TabListScope } from '@/ui/layout/tab/scopes/TabListScope';
|
import { TabListScope } from '@/ui/layout/tab/scopes/TabListScope';
|
||||||
import { ScrollWrapper } from '@/ui/utilities/scroll/components/ScrollWrapper';
|
import { ScrollWrapper } from '@/ui/utilities/scroll/components/ScrollWrapper';
|
||||||
|
|
||||||
|
import { LayoutCard } from '@/ui/layout/tab/types/LayoutCard';
|
||||||
import { Tab } from './Tab';
|
import { Tab } from './Tab';
|
||||||
|
|
||||||
export type SingleTabProps = {
|
export type SingleTabProps = {
|
||||||
@@ -16,6 +17,7 @@ export type SingleTabProps = {
|
|||||||
hide?: boolean;
|
hide?: boolean;
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
pill?: string | React.ReactElement;
|
pill?: string | React.ReactElement;
|
||||||
|
cards?: LayoutCard[];
|
||||||
};
|
};
|
||||||
|
|
||||||
type TabListProps = {
|
type TabListProps = {
|
||||||
|
|||||||
@@ -0,0 +1,5 @@
|
|||||||
|
import { CardType } from '@/object-record/record-show/types/CardType';
|
||||||
|
|
||||||
|
export type LayoutCard = {
|
||||||
|
type: CardType;
|
||||||
|
};
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
import { LayoutCard } from '@/ui/layout/tab/types/LayoutCard';
|
||||||
|
import { TabVisibilityConfig } from '@/ui/layout/tab/types/TabVisibilityConfig';
|
||||||
|
import { IconComponent } from 'twenty-ui';
|
||||||
|
|
||||||
|
export type RecordLayoutTab = {
|
||||||
|
title: string;
|
||||||
|
position: number;
|
||||||
|
Icon: IconComponent;
|
||||||
|
hide: TabVisibilityConfig;
|
||||||
|
cards: LayoutCard[];
|
||||||
|
};
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
||||||
|
import { FeatureFlagKey } from '@/workspace/types/FeatureFlagKey';
|
||||||
|
|
||||||
|
export type TabVisibilityConfig = {
|
||||||
|
ifMobile: boolean;
|
||||||
|
ifDesktop: boolean;
|
||||||
|
ifInRightDrawer: boolean;
|
||||||
|
ifFeaturesDisabled: FeatureFlagKey[];
|
||||||
|
ifRequiredObjectsInactive: CoreObjectNameSingular[];
|
||||||
|
ifRelationsMissing: string[];
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user