Activity as standard object (#6219)
In this PR I layout the first steps to migrate Activity to a traditional Standard objects Since this is a big transition, I'd rather split it into several deployments / PRs <img width="1512" alt="image" src="https://github.com/user-attachments/assets/012e2bbf-9d1b-4723-aaf6-269ef588b050"> --------- Co-authored-by: Charles Bochet <charles@twenty.com> Co-authored-by: bosiraphael <71827178+bosiraphael@users.noreply.github.com> Co-authored-by: Weiko <corentin@twenty.com> Co-authored-by: Faisal-imtiyaz123 <142205282+Faisal-imtiyaz123@users.noreply.github.com> Co-authored-by: Prateek Jain <prateekj1171998@gmail.com>
This commit is contained in:
@ -0,0 +1,77 @@
|
||||
import { PartialBlock } from '@blocknote/core';
|
||||
import { getFirstNonEmptyLineOfRichText } from '../getFirstNonEmptyLineOfRichText';
|
||||
|
||||
describe('getFirstNonEmptyLineOfRichText', () => {
|
||||
it('should return an empty string if the input is null', () => {
|
||||
const result = getFirstNonEmptyLineOfRichText(null);
|
||||
expect(result).toBe('');
|
||||
});
|
||||
|
||||
it('should return an empty string if the input is an empty array', () => {
|
||||
const result = getFirstNonEmptyLineOfRichText([]);
|
||||
expect(result).toBe('');
|
||||
});
|
||||
|
||||
it('should return the first non-empty line of text', () => {
|
||||
const input: PartialBlock[] = [
|
||||
{ content: [{ text: '', type: 'text', styles: {} }] },
|
||||
{ content: [{ text: ' ', type: 'text', styles: {} }] },
|
||||
{ content: [{ text: 'First non-empty line', type: 'text', styles: {} }] },
|
||||
{ content: [{ text: 'Second line', type: 'text', styles: {} }] },
|
||||
];
|
||||
const result = getFirstNonEmptyLineOfRichText(input);
|
||||
expect(result).toBe('First non-empty line');
|
||||
});
|
||||
|
||||
it('should return an empty string if all lines are empty', () => {
|
||||
const input: PartialBlock[] = [
|
||||
{ content: [{ text: '', type: 'text', styles: {} }] },
|
||||
{ content: [{ text: ' ', type: 'text', styles: {} }] },
|
||||
{ content: [{ text: '\n', type: 'text', styles: {} }] },
|
||||
];
|
||||
const result = getFirstNonEmptyLineOfRichText(input);
|
||||
expect(result).toBe('');
|
||||
});
|
||||
|
||||
it('should handle mixed content correctly', () => {
|
||||
const input: PartialBlock[] = [
|
||||
{ content: [{ text: '', type: 'text', styles: {} }] },
|
||||
{ content: [{ text: ' ', type: 'text', styles: {} }] },
|
||||
{ content: [{ text: 'First non-empty line', type: 'text', styles: {} }] },
|
||||
{ content: [{ text: '', type: 'text', styles: {} }] },
|
||||
{
|
||||
content: [{ text: 'Second non-empty line', type: 'text', styles: {} }],
|
||||
},
|
||||
];
|
||||
const result = getFirstNonEmptyLineOfRichText(input);
|
||||
expect(result).toBe('First non-empty line');
|
||||
});
|
||||
|
||||
it('should handle content with multiple text objects correctly', () => {
|
||||
const input: PartialBlock[] = [
|
||||
{
|
||||
content: [
|
||||
{ text: '', type: 'text', styles: {} },
|
||||
{ text: ' ', type: 'text', styles: {} },
|
||||
],
|
||||
},
|
||||
{
|
||||
content: [
|
||||
{ text: 'First non-empty line', type: 'text', styles: {} },
|
||||
{ text: 'Second line', type: 'text', styles: {} },
|
||||
],
|
||||
},
|
||||
];
|
||||
const result = getFirstNonEmptyLineOfRichText(input);
|
||||
expect(result).toBe('First non-empty line');
|
||||
});
|
||||
|
||||
it('should handle content with undefined or null content', () => {
|
||||
const input: PartialBlock[] = [
|
||||
{ content: undefined },
|
||||
{ content: [{ text: 'First non-empty line', type: 'text', styles: {} }] },
|
||||
];
|
||||
const result = getFirstNonEmptyLineOfRichText(input);
|
||||
expect(result).toBe('First non-empty line');
|
||||
});
|
||||
});
|
||||
@ -0,0 +1,23 @@
|
||||
import { PartialBlock } from '@blocknote/core';
|
||||
import { isUndefinedOrNull } from '~/utils/isUndefinedOrNull';
|
||||
|
||||
export const getFirstNonEmptyLineOfRichText = (
|
||||
fieldValue: PartialBlock[] | null,
|
||||
): string => {
|
||||
if (fieldValue === null) {
|
||||
return '';
|
||||
}
|
||||
for (const node of fieldValue) {
|
||||
if (!isUndefinedOrNull(node.content)) {
|
||||
const contentArray = node.content as Array<{ text: string }>;
|
||||
if (contentArray.length > 0) {
|
||||
for (const content of contentArray) {
|
||||
if (content.text.trim() !== '') {
|
||||
return content.text;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return '';
|
||||
};
|
||||
@ -2,7 +2,12 @@ import { useTheme } from '@emotion/react';
|
||||
import styled from '@emotion/styled';
|
||||
import { motion } from 'framer-motion';
|
||||
import { useRef } from 'react';
|
||||
import { useRecoilCallback, useRecoilState, useRecoilValue } from 'recoil';
|
||||
import {
|
||||
useRecoilCallback,
|
||||
useRecoilState,
|
||||
useRecoilValue,
|
||||
useSetRecoilState,
|
||||
} from 'recoil';
|
||||
import { Key } from 'ts-key-enum';
|
||||
|
||||
import { RIGHT_DRAWER_CLICK_OUTSIDE_LISTENER_ID } from '@/ui/layout/right-drawer/constants/RightDrawerClickOutsideListener';
|
||||
@ -16,7 +21,6 @@ import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile';
|
||||
import { isDefined } from '~/utils/isDefined';
|
||||
|
||||
import { useRightDrawer } from '../hooks/useRightDrawer';
|
||||
import { isRightDrawerExpandedState } from '../states/isRightDrawerExpandedState';
|
||||
import { isRightDrawerOpenState } from '../states/isRightDrawerOpenState';
|
||||
import { rightDrawerPageState } from '../states/rightDrawerPageState';
|
||||
import { RightDrawerHotkeyScope } from '../types/RightDrawerHotkeyScope';
|
||||
@ -49,8 +53,7 @@ export const RightDrawer = () => {
|
||||
|
||||
const isRightDrawerMinimized = useRecoilValue(isRightDrawerMinimizedState);
|
||||
|
||||
const isRightDrawerExpanded = useRecoilValue(isRightDrawerExpandedState);
|
||||
const [, setIsRightDrawerAnimationCompleted] = useRecoilState(
|
||||
const setIsRightDrawerAnimationCompleted = useSetRecoilState(
|
||||
isRightDrawerAnimationCompletedState,
|
||||
);
|
||||
|
||||
@ -101,7 +104,7 @@ export const RightDrawer = () => {
|
||||
const isMobile = useIsMobile();
|
||||
|
||||
const rightDrawerWidth = isRightDrawerOpen
|
||||
? isMobile || isRightDrawerExpanded
|
||||
? isMobile
|
||||
? '100%'
|
||||
: theme.rightDrawerWidth
|
||||
: '0';
|
||||
|
||||
@ -4,8 +4,6 @@ import { useRecoilState, useRecoilValue } from 'recoil';
|
||||
import { RightDrawerCalendarEvent } from '@/activities/calendar/right-drawer/components/RightDrawerCalendarEvent';
|
||||
import { RightDrawerAIChat } from '@/activities/copilot/right-drawer/components/RightDrawerAIChat';
|
||||
import { RightDrawerEmailThread } from '@/activities/emails/right-drawer/components/RightDrawerEmailThread';
|
||||
import { RightDrawerCreateActivity } from '@/activities/right-drawer/components/create/RightDrawerCreateActivity';
|
||||
import { RightDrawerEditActivity } from '@/activities/right-drawer/components/edit/RightDrawerEditActivity';
|
||||
import { RightDrawerRecord } from '@/object-record/record-right-drawer/components/RightDrawerRecord';
|
||||
import { RightDrawerTopBar } from '@/ui/layout/right-drawer/components/RightDrawerTopBar';
|
||||
import { isRightDrawerMinimizedState } from '@/ui/layout/right-drawer/states/isRightDrawerMinimizedState';
|
||||
@ -31,14 +29,6 @@ const StyledRightDrawerBody = styled.div`
|
||||
`;
|
||||
|
||||
const RIGHT_DRAWER_PAGES_CONFIG = {
|
||||
[RightDrawerPages.CreateActivity]: {
|
||||
page: <RightDrawerCreateActivity />,
|
||||
topBar: <RightDrawerTopBar page={RightDrawerPages.CreateActivity} />,
|
||||
},
|
||||
[RightDrawerPages.EditActivity]: {
|
||||
page: <RightDrawerEditActivity />,
|
||||
topBar: <RightDrawerTopBar page={RightDrawerPages.EditActivity} />,
|
||||
},
|
||||
[RightDrawerPages.ViewEmailThread]: {
|
||||
page: <RightDrawerEmailThread />,
|
||||
topBar: <RightDrawerTopBar page={RightDrawerPages.ViewEmailThread} />,
|
||||
|
||||
@ -3,8 +3,9 @@ import styled from '@emotion/styled';
|
||||
import { useRecoilState, useRecoilValue } from 'recoil';
|
||||
import { Chip, ChipAccent, ChipSize, useIcons } from 'twenty-ui';
|
||||
|
||||
import { ActivityActionBar } from '@/activities/right-drawer/components/ActivityActionBar';
|
||||
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
|
||||
import { getBasePathToShowPage } from '@/object-metadata/utils/getBasePathToShowPage';
|
||||
import { viewableRecordIdState } from '@/object-record/record-right-drawer/states/viewableRecordIdState';
|
||||
import { viewableRecordNameSingularState } from '@/object-record/record-right-drawer/states/viewableRecordNameSingularState';
|
||||
import { RightDrawerTopBarCloseButton } from '@/ui/layout/right-drawer/components/RightDrawerTopBarCloseButton';
|
||||
import { RightDrawerTopBarExpandButton } from '@/ui/layout/right-drawer/components/RightDrawerTopBarExpandButton';
|
||||
@ -62,6 +63,8 @@ export const RightDrawerTopBar = ({ page }: { page: RightDrawerPages }) => {
|
||||
viewableRecordNameSingularState,
|
||||
);
|
||||
|
||||
const viewableRecordId = useRecoilValue(viewableRecordIdState);
|
||||
|
||||
const { objectMetadataItem } = useObjectMetadataItem({
|
||||
objectNameSingular: viewableRecordNameSingular ?? 'company',
|
||||
});
|
||||
@ -80,20 +83,15 @@ export const RightDrawerTopBar = ({ page }: { page: RightDrawerPages }) => {
|
||||
onClick={handleOnclick}
|
||||
isRightDrawerMinimized={isRightDrawerMinimized}
|
||||
>
|
||||
{!isRightDrawerMinimized &&
|
||||
(page === RightDrawerPages.EditActivity ||
|
||||
page === RightDrawerPages.CreateActivity) && <ActivityActionBar />}
|
||||
{!isRightDrawerMinimized &&
|
||||
page !== RightDrawerPages.EditActivity &&
|
||||
page !== RightDrawerPages.CreateActivity && (
|
||||
<Chip
|
||||
label={label}
|
||||
leftComponent={<Icon size={theme.icon.size.md} />}
|
||||
size={ChipSize.Large}
|
||||
accent={ChipAccent.TextSecondary}
|
||||
clickable={false}
|
||||
/>
|
||||
)}
|
||||
{!isRightDrawerMinimized && (
|
||||
<Chip
|
||||
label={label}
|
||||
leftComponent={<Icon size={theme.icon.size.md} />}
|
||||
size={ChipSize.Large}
|
||||
accent={ChipAccent.TextSecondary}
|
||||
clickable={false}
|
||||
/>
|
||||
)}
|
||||
{isRightDrawerMinimized && (
|
||||
<StyledMinimizeTopBarTitleContainer>
|
||||
<StyledMinimizeTopBarIcon>
|
||||
@ -106,8 +104,15 @@ export const RightDrawerTopBar = ({ page }: { page: RightDrawerPages }) => {
|
||||
{!isMobile && !isRightDrawerMinimized && (
|
||||
<RightDrawerTopBarMinimizeButton />
|
||||
)}
|
||||
|
||||
{!isMobile && !isRightDrawerMinimized && (
|
||||
<RightDrawerTopBarExpandButton />
|
||||
<RightDrawerTopBarExpandButton
|
||||
to={
|
||||
getBasePathToShowPage({
|
||||
objectNameSingular: viewableRecordNameSingular ?? '',
|
||||
}) + viewableRecordId
|
||||
}
|
||||
/>
|
||||
)}
|
||||
<RightDrawerTopBarCloseButton />
|
||||
</StyledTopBarWrapper>
|
||||
|
||||
@ -1,33 +1,19 @@
|
||||
import {
|
||||
IconLayoutSidebarRightCollapse,
|
||||
IconLayoutSidebarRightExpand,
|
||||
} from 'twenty-ui';
|
||||
|
||||
import { LightIconButton } from '@/ui/input/button/components/LightIconButton';
|
||||
import { useRightDrawer } from '@/ui/layout/right-drawer/hooks/useRightDrawer';
|
||||
import { UndecoratedLink } from '@/ui/navigation/link/components/UndecoratedLink';
|
||||
import { IconExternalLink } from 'twenty-ui';
|
||||
|
||||
export const RightDrawerTopBarExpandButton = () => {
|
||||
const { isRightDrawerExpanded, downsizeRightDrawer, expandRightDrawer } =
|
||||
useRightDrawer();
|
||||
|
||||
const handleButtonClick = () => {
|
||||
if (isRightDrawerExpanded === true) {
|
||||
downsizeRightDrawer();
|
||||
return;
|
||||
}
|
||||
expandRightDrawer();
|
||||
};
|
||||
export const RightDrawerTopBarExpandButton = ({ to }: { to: string }) => {
|
||||
const { closeRightDrawer } = useRightDrawer();
|
||||
|
||||
return (
|
||||
<LightIconButton
|
||||
size="medium"
|
||||
accent="tertiary"
|
||||
Icon={
|
||||
isRightDrawerExpanded
|
||||
? IconLayoutSidebarRightCollapse
|
||||
: IconLayoutSidebarRightExpand
|
||||
}
|
||||
onClick={handleButtonClick}
|
||||
/>
|
||||
<UndecoratedLink to={to}>
|
||||
<LightIconButton
|
||||
size="medium"
|
||||
accent="tertiary"
|
||||
Icon={IconExternalLink}
|
||||
onClick={closeRightDrawer}
|
||||
/>
|
||||
</UndecoratedLink>
|
||||
);
|
||||
};
|
||||
|
||||
@ -1,8 +1,6 @@
|
||||
import { RightDrawerPages } from '@/ui/layout/right-drawer/types/RightDrawerPages';
|
||||
|
||||
export const RIGHT_DRAWER_PAGE_ICONS = {
|
||||
[RightDrawerPages.CreateActivity]: 'IconNote',
|
||||
[RightDrawerPages.EditActivity]: 'IconNote',
|
||||
[RightDrawerPages.ViewEmailThread]: 'IconMail',
|
||||
[RightDrawerPages.ViewCalendarEvent]: 'IconCalendarEvent',
|
||||
[RightDrawerPages.ViewRecord]: 'Icon123',
|
||||
|
||||
@ -1,8 +1,6 @@
|
||||
import { RightDrawerPages } from '@/ui/layout/right-drawer/types/RightDrawerPages';
|
||||
|
||||
export const RIGHT_DRAWER_PAGE_TITLES = {
|
||||
[RightDrawerPages.CreateActivity]: 'Create Activity',
|
||||
[RightDrawerPages.EditActivity]: 'Edit Activity',
|
||||
[RightDrawerPages.ViewEmailThread]: 'Email Thread',
|
||||
[RightDrawerPages.ViewCalendarEvent]: 'Calendar Event',
|
||||
[RightDrawerPages.ViewRecord]: 'Record Editor',
|
||||
|
||||
@ -1,8 +1,7 @@
|
||||
import { act } from 'react-dom/test-utils';
|
||||
import { renderHook } from '@testing-library/react';
|
||||
import { act } from 'react-dom/test-utils';
|
||||
import { RecoilRoot, useRecoilValue } from 'recoil';
|
||||
|
||||
import { isRightDrawerExpandedState } from '../../states/isRightDrawerExpandedState';
|
||||
import { isRightDrawerOpenState } from '../../states/isRightDrawerOpenState';
|
||||
import { rightDrawerPageState } from '../../states/rightDrawerPageState';
|
||||
import { RightDrawerPages } from '../../types/RightDrawerPages';
|
||||
@ -13,7 +12,6 @@ describe('useRightDrawer', () => {
|
||||
const useCombinedHooks = () => {
|
||||
const { openRightDrawer, closeRightDrawer } = useRightDrawer();
|
||||
const isRightDrawerOpen = useRecoilValue(isRightDrawerOpenState);
|
||||
const isRightDrawerExpanded = useRecoilValue(isRightDrawerExpandedState);
|
||||
|
||||
const rightDrawerPage = useRecoilValue(rightDrawerPageState);
|
||||
|
||||
@ -21,7 +19,6 @@ describe('useRightDrawer', () => {
|
||||
openRightDrawer,
|
||||
closeRightDrawer,
|
||||
isRightDrawerOpen,
|
||||
isRightDrawerExpanded,
|
||||
rightDrawerPage,
|
||||
};
|
||||
};
|
||||
@ -31,26 +28,21 @@ describe('useRightDrawer', () => {
|
||||
});
|
||||
|
||||
expect(result.current.rightDrawerPage).toBeNull();
|
||||
expect(result.current.isRightDrawerExpanded).toBeFalsy();
|
||||
expect(result.current.isRightDrawerOpen).toBeFalsy();
|
||||
expect(result.current.openRightDrawer).toBeInstanceOf(Function);
|
||||
expect(result.current.closeRightDrawer).toBeInstanceOf(Function);
|
||||
|
||||
await act(async () => {
|
||||
result.current.openRightDrawer(RightDrawerPages.CreateActivity);
|
||||
result.current.openRightDrawer(RightDrawerPages.ViewRecord);
|
||||
});
|
||||
|
||||
expect(result.current.rightDrawerPage).toEqual(
|
||||
RightDrawerPages.CreateActivity,
|
||||
);
|
||||
expect(result.current.isRightDrawerExpanded).toBeFalsy();
|
||||
expect(result.current.rightDrawerPage).toEqual(RightDrawerPages.ViewRecord);
|
||||
expect(result.current.isRightDrawerOpen).toBeTruthy();
|
||||
|
||||
await act(async () => {
|
||||
result.current.closeRightDrawer();
|
||||
});
|
||||
|
||||
expect(result.current.isRightDrawerExpanded).toBeFalsy();
|
||||
expect(result.current.isRightDrawerOpen).toBeFalsy();
|
||||
});
|
||||
});
|
||||
|
||||
@ -1,25 +1,22 @@
|
||||
import { useRecoilCallback, useRecoilState } from 'recoil';
|
||||
import { useRecoilCallback, useRecoilValue } from 'recoil';
|
||||
|
||||
import { isRightDrawerMinimizedState } from '@/ui/layout/right-drawer/states/isRightDrawerMinimizedState';
|
||||
import { rightDrawerCloseEventState } from '@/ui/layout/right-drawer/states/rightDrawerCloseEventsState';
|
||||
|
||||
import { isRightDrawerExpandedState } from '../states/isRightDrawerExpandedState';
|
||||
import { isRightDrawerOpenState } from '../states/isRightDrawerOpenState';
|
||||
import { rightDrawerPageState } from '../states/rightDrawerPageState';
|
||||
import { RightDrawerPages } from '../types/RightDrawerPages';
|
||||
|
||||
export const useRightDrawer = () => {
|
||||
const [isRightDrawerOpen] = useRecoilState(isRightDrawerOpenState);
|
||||
const [isRightDrawerExpanded] = useRecoilState(isRightDrawerExpandedState);
|
||||
const [isRightDrawerMinimized] = useRecoilState(isRightDrawerMinimizedState);
|
||||
const isRightDrawerOpen = useRecoilValue(isRightDrawerOpenState);
|
||||
const isRightDrawerMinimized = useRecoilValue(isRightDrawerMinimizedState);
|
||||
|
||||
const [rightDrawerPage] = useRecoilState(rightDrawerPageState);
|
||||
const rightDrawerPage = useRecoilValue(rightDrawerPageState);
|
||||
|
||||
const openRightDrawer = useRecoilCallback(
|
||||
({ set }) =>
|
||||
(rightDrawerPage: RightDrawerPages) => {
|
||||
set(rightDrawerPageState, rightDrawerPage);
|
||||
set(isRightDrawerExpandedState, false);
|
||||
set(isRightDrawerOpenState, true);
|
||||
set(isRightDrawerMinimizedState, false);
|
||||
},
|
||||
@ -29,7 +26,6 @@ export const useRightDrawer = () => {
|
||||
const closeRightDrawer = useRecoilCallback(
|
||||
({ set }) =>
|
||||
() => {
|
||||
set(isRightDrawerExpandedState, false);
|
||||
set(isRightDrawerOpenState, false);
|
||||
set(isRightDrawerMinimizedState, false);
|
||||
},
|
||||
@ -39,7 +35,6 @@ export const useRightDrawer = () => {
|
||||
const minimizeRightDrawer = useRecoilCallback(
|
||||
({ set }) =>
|
||||
() => {
|
||||
set(isRightDrawerExpandedState, false);
|
||||
set(isRightDrawerOpenState, true);
|
||||
set(isRightDrawerMinimizedState, true);
|
||||
},
|
||||
@ -50,32 +45,11 @@ export const useRightDrawer = () => {
|
||||
({ set }) =>
|
||||
() => {
|
||||
set(isRightDrawerMinimizedState, false);
|
||||
set(isRightDrawerExpandedState, false);
|
||||
set(isRightDrawerOpenState, true);
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
const expandRightDrawer = useRecoilCallback(
|
||||
({ set }) =>
|
||||
() => {
|
||||
set(isRightDrawerExpandedState, true);
|
||||
set(isRightDrawerOpenState, true);
|
||||
set(isRightDrawerMinimizedState, false);
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
const downsizeRightDrawer = useRecoilCallback(
|
||||
({ set }) =>
|
||||
() => {
|
||||
set(isRightDrawerExpandedState, false);
|
||||
set(isRightDrawerOpenState, true);
|
||||
set(isRightDrawerMinimizedState, false);
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
const isSameEventThanRightDrawerClose = useRecoilCallback(
|
||||
({ snapshot }) =>
|
||||
(event: MouseEvent | TouchEvent) => {
|
||||
@ -95,14 +69,11 @@ export const useRightDrawer = () => {
|
||||
return {
|
||||
rightDrawerPage,
|
||||
isRightDrawerOpen,
|
||||
isRightDrawerExpanded,
|
||||
isRightDrawerMinimized,
|
||||
openRightDrawer,
|
||||
closeRightDrawer,
|
||||
minimizeRightDrawer,
|
||||
maximizeRightDrawer,
|
||||
expandRightDrawer,
|
||||
downsizeRightDrawer,
|
||||
isSameEventThanRightDrawerClose,
|
||||
};
|
||||
};
|
||||
|
||||
@ -1,6 +0,0 @@
|
||||
import { createState } from 'twenty-ui';
|
||||
|
||||
export const isRightDrawerExpandedState = createState<boolean>({
|
||||
key: 'isRightDrawerExpandedState',
|
||||
defaultValue: false,
|
||||
});
|
||||
@ -1,6 +1,4 @@
|
||||
export enum RightDrawerPages {
|
||||
CreateActivity = 'create-activity',
|
||||
EditActivity = 'edit-activity',
|
||||
ViewEmailThread = 'view-email-thread',
|
||||
ViewCalendarEvent = 'view-calendar-event',
|
||||
ViewRecord = 'view-record',
|
||||
|
||||
@ -0,0 +1,31 @@
|
||||
import { RichTextEditor } from '@/activities/components/RichTextEditor';
|
||||
import { ActivityTargetableObject } from '@/activities/types/ActivityTargetableEntity';
|
||||
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
||||
import styled from '@emotion/styled';
|
||||
|
||||
const StyledShowPageActivityContainer = styled.div`
|
||||
margin-top: ${({ theme }) => theme.spacing(2)};
|
||||
width: 100%;
|
||||
`;
|
||||
export const ShowPageActivityContainer = ({
|
||||
targetableObject,
|
||||
}: {
|
||||
targetableObject: Pick<
|
||||
ActivityTargetableObject,
|
||||
'targetObjectNameSingular' | 'id'
|
||||
>;
|
||||
}) => {
|
||||
return (
|
||||
<StyledShowPageActivityContainer>
|
||||
<RichTextEditor
|
||||
activityId={targetableObject.id}
|
||||
fillTitleFromBody={false}
|
||||
activityObjectNameSingular={
|
||||
targetableObject.targetObjectNameSingular as
|
||||
| CoreObjectNameSingular.Note
|
||||
| CoreObjectNameSingular.Task
|
||||
}
|
||||
/>
|
||||
</StyledShowPageActivityContainer>
|
||||
);
|
||||
};
|
||||
@ -2,7 +2,6 @@ import styled from '@emotion/styled';
|
||||
import { IconCheckbox, IconNotes, IconPlus } from 'twenty-ui';
|
||||
|
||||
import { useOpenCreateActivityDrawer } from '@/activities/hooks/useOpenCreateActivityDrawer';
|
||||
import { ActivityType } from '@/activities/types/Activity';
|
||||
import { ActivityTargetableObject } from '@/activities/types/ActivityTargetableEntity';
|
||||
import { PageHotkeyScope } from '@/types/PageHotkeyScope';
|
||||
import { IconButton } from '@/ui/input/button/components/IconButton';
|
||||
@ -11,6 +10,7 @@ import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown';
|
||||
import { SHOW_PAGE_ADD_BUTTON_DROPDOWN_ID } from '@/ui/layout/show-page/constants/ShowPageAddButtonDropdownId';
|
||||
import { MenuItem } from '@/ui/navigation/menu-item/components/MenuItem';
|
||||
|
||||
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
||||
import { Dropdown } from '../../dropdown/components/Dropdown';
|
||||
import { DropdownMenu } from '../../dropdown/components/DropdownMenu';
|
||||
|
||||
@ -24,17 +24,37 @@ export const ShowPageAddButton = ({
|
||||
activityTargetObject: ActivityTargetableObject;
|
||||
}) => {
|
||||
const { closeDropdown, toggleDropdown } = useDropdown('add-show-page');
|
||||
const openCreateActivity = useOpenCreateActivityDrawer();
|
||||
const openNote = useOpenCreateActivityDrawer({
|
||||
activityObjectNameSingular: CoreObjectNameSingular.Note,
|
||||
});
|
||||
const openTask = useOpenCreateActivityDrawer({
|
||||
activityObjectNameSingular: CoreObjectNameSingular.Task,
|
||||
});
|
||||
|
||||
const handleSelect = (type: ActivityType) => {
|
||||
openCreateActivity({
|
||||
type,
|
||||
targetableObjects: [activityTargetObject],
|
||||
});
|
||||
const handleSelect = (objectNameSingular: CoreObjectNameSingular) => {
|
||||
if (objectNameSingular === CoreObjectNameSingular.Note) {
|
||||
openNote({
|
||||
targetableObjects: [activityTargetObject],
|
||||
});
|
||||
}
|
||||
if (objectNameSingular === CoreObjectNameSingular.Task) {
|
||||
openTask({
|
||||
targetableObjects: [activityTargetObject],
|
||||
});
|
||||
}
|
||||
|
||||
closeDropdown();
|
||||
};
|
||||
|
||||
if (
|
||||
activityTargetObject.targetObjectNameSingular ===
|
||||
CoreObjectNameSingular.Task ||
|
||||
activityTargetObject.targetObjectNameSingular ===
|
||||
CoreObjectNameSingular.Note
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
return (
|
||||
<StyledContainer>
|
||||
<Dropdown
|
||||
@ -53,13 +73,13 @@ export const ShowPageAddButton = ({
|
||||
<DropdownMenu>
|
||||
<DropdownMenuItemsContainer>
|
||||
<MenuItem
|
||||
onClick={() => handleSelect('Note')}
|
||||
onClick={() => handleSelect(CoreObjectNameSingular.Note)}
|
||||
accent="default"
|
||||
LeftIcon={IconNotes}
|
||||
text="Note"
|
||||
/>
|
||||
<MenuItem
|
||||
onClick={() => handleSelect('Task')}
|
||||
onClick={() => handleSelect(CoreObjectNameSingular.Task)}
|
||||
accent="default"
|
||||
LeftIcon={IconCheckbox}
|
||||
text="Task"
|
||||
|
||||
@ -3,7 +3,7 @@ import { useRecoilValue } from 'recoil';
|
||||
import {
|
||||
IconCalendarEvent,
|
||||
IconCheckbox,
|
||||
IconHome,
|
||||
IconList,
|
||||
IconMail,
|
||||
IconNotes,
|
||||
IconPaperclip,
|
||||
@ -16,9 +16,9 @@ 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/timelineActivities/components/TimelineActivities';
|
||||
import { TimelineActivitiesQueryEffect } from '@/activities/timelineActivities/components/TimelineActivitiesQueryEffect';
|
||||
import { ActivityTargetableObject } from '@/activities/types/ActivityTargetableEntity';
|
||||
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
||||
import { ShowPageActivityContainer } from '@/ui/layout/show-page/components/ShowPageActivityContainer';
|
||||
import { TabList } from '@/ui/layout/tab/components/TabList';
|
||||
import { useTabList } from '@/ui/layout/tab/hooks/useTabList';
|
||||
import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile';
|
||||
@ -51,8 +51,9 @@ type ShowPageRightContainerProps = {
|
||||
tasks?: boolean;
|
||||
notes?: boolean;
|
||||
emails?: boolean;
|
||||
summary?: JSX.Element;
|
||||
isRightDrawer?: boolean;
|
||||
fieldsBox?: JSX.Element;
|
||||
summaryCard?: JSX.Element;
|
||||
isInRightDrawer?: boolean;
|
||||
loading: boolean;
|
||||
};
|
||||
|
||||
@ -63,10 +64,13 @@ export const ShowPageRightContainer = ({
|
||||
notes,
|
||||
emails,
|
||||
loading,
|
||||
summary,
|
||||
isRightDrawer = false,
|
||||
fieldsBox,
|
||||
summaryCard,
|
||||
isInRightDrawer = false,
|
||||
}: ShowPageRightContainerProps) => {
|
||||
const { activeTabIdState } = useTabList(TAB_LIST_COMPONENT_ID);
|
||||
const { activeTabIdState } = useTabList(
|
||||
`${TAB_LIST_COMPONENT_ID}-${isInRightDrawer}`,
|
||||
);
|
||||
const activeTabId = useRecoilValue(activeTabIdState);
|
||||
|
||||
const targetObjectNameSingular =
|
||||
@ -80,24 +84,60 @@ export const ShowPageRightContainer = ({
|
||||
const shouldDisplayCalendarTab = isCompanyOrPerson;
|
||||
const shouldDisplayEmailsTab = emails && isCompanyOrPerson;
|
||||
|
||||
const isMobile = useIsMobile() || isRightDrawer;
|
||||
const isMobile = useIsMobile() || isInRightDrawer;
|
||||
|
||||
const tabs = [
|
||||
{
|
||||
id: 'summary',
|
||||
title: 'Summary',
|
||||
Icon: IconHome,
|
||||
id: 'richText',
|
||||
title: 'Note',
|
||||
Icon: IconNotes,
|
||||
hide:
|
||||
loading ||
|
||||
(targetableObject.targetObjectNameSingular !==
|
||||
CoreObjectNameSingular.Note &&
|
||||
targetableObject.targetObjectNameSingular !==
|
||||
CoreObjectNameSingular.Task),
|
||||
},
|
||||
{
|
||||
id: 'fields',
|
||||
title: 'Fields',
|
||||
Icon: IconList,
|
||||
hide: !isMobile,
|
||||
},
|
||||
{
|
||||
id: 'timeline',
|
||||
title: 'Timeline',
|
||||
Icon: IconTimelineEvent,
|
||||
hide: !timeline || isRightDrawer,
|
||||
hide: !timeline || isInRightDrawer,
|
||||
},
|
||||
{
|
||||
id: 'tasks',
|
||||
title: 'Tasks',
|
||||
Icon: IconCheckbox,
|
||||
hide:
|
||||
!tasks ||
|
||||
targetableObject.targetObjectNameSingular ===
|
||||
CoreObjectNameSingular.Note ||
|
||||
targetableObject.targetObjectNameSingular ===
|
||||
CoreObjectNameSingular.Task,
|
||||
},
|
||||
{
|
||||
id: 'notes',
|
||||
title: 'Notes',
|
||||
Icon: IconNotes,
|
||||
hide:
|
||||
!notes ||
|
||||
targetableObject.targetObjectNameSingular ===
|
||||
CoreObjectNameSingular.Note ||
|
||||
targetableObject.targetObjectNameSingular ===
|
||||
CoreObjectNameSingular.Task,
|
||||
},
|
||||
{
|
||||
id: 'files',
|
||||
title: 'Files',
|
||||
Icon: IconPaperclip,
|
||||
hide: !notes,
|
||||
},
|
||||
{ id: 'tasks', title: 'Tasks', Icon: IconCheckbox, hide: !tasks },
|
||||
{ id: 'notes', title: 'Notes', Icon: IconNotes, hide: !notes },
|
||||
{ id: 'files', title: 'Files', Icon: IconPaperclip, hide: !notes },
|
||||
{
|
||||
id: 'emails',
|
||||
title: 'Emails',
|
||||
@ -117,14 +157,23 @@ export const ShowPageRightContainer = ({
|
||||
case 'timeline':
|
||||
return (
|
||||
<>
|
||||
<TimelineActivitiesQueryEffect
|
||||
<TimelineActivities
|
||||
targetableObject={targetableObject}
|
||||
isInRightDrawer={isInRightDrawer}
|
||||
/>
|
||||
<TimelineActivities targetableObject={targetableObject} />
|
||||
</>
|
||||
);
|
||||
case 'summary':
|
||||
return summary;
|
||||
case 'richText':
|
||||
return (
|
||||
(targetableObject.targetObjectNameSingular ===
|
||||
CoreObjectNameSingular.Note ||
|
||||
targetableObject.targetObjectNameSingular ===
|
||||
CoreObjectNameSingular.Task) && (
|
||||
<ShowPageActivityContainer targetableObject={targetableObject} />
|
||||
)
|
||||
);
|
||||
case 'fields':
|
||||
return fieldsBox;
|
||||
case 'tasks':
|
||||
return <ObjectTasks targetableObject={targetableObject} />;
|
||||
case 'notes':
|
||||
@ -142,10 +191,11 @@ export const ShowPageRightContainer = ({
|
||||
|
||||
return (
|
||||
<StyledShowPageRightContainer isMobile={isMobile}>
|
||||
{summaryCard}
|
||||
<StyledTabListContainer>
|
||||
<TabList
|
||||
loading={loading}
|
||||
tabListId={TAB_LIST_COMPONENT_ID}
|
||||
tabListId={`${TAB_LIST_COMPONENT_ID}-${isInRightDrawer}`}
|
||||
tabs={tabs}
|
||||
/>
|
||||
</StyledTabListContainer>
|
||||
|
||||
@ -1,8 +1,8 @@
|
||||
import { Link, useNavigate } from 'react-router-dom';
|
||||
import isPropValid from '@emotion/is-prop-valid';
|
||||
import { useTheme } from '@emotion/react';
|
||||
import styled from '@emotion/styled';
|
||||
import { isNonEmptyString } from '@sniptt/guards';
|
||||
import { Link, useNavigate } from 'react-router-dom';
|
||||
import { useSetRecoilState } from 'recoil';
|
||||
import { IconComponent, MOBILE_VIEWPORT, Pill } from 'twenty-ui';
|
||||
|
||||
|
||||
@ -0,0 +1,43 @@
|
||||
import {
|
||||
NavigationDrawerItem,
|
||||
NavigationDrawerItemProps,
|
||||
} from '@/ui/navigation/navigation-drawer/components/NavigationDrawerItem';
|
||||
import styled from '@emotion/styled';
|
||||
|
||||
const StyledItem = styled.div`
|
||||
margin-left: ${({ theme }) => theme.spacing(4)};
|
||||
`;
|
||||
|
||||
type NavigationDrawerSubItemProps = NavigationDrawerItemProps;
|
||||
|
||||
export const NavigationDrawerSubItem = ({
|
||||
className,
|
||||
label,
|
||||
level = 1,
|
||||
Icon,
|
||||
to,
|
||||
onClick,
|
||||
active,
|
||||
danger,
|
||||
soon,
|
||||
count,
|
||||
keyboard,
|
||||
}: NavigationDrawerSubItemProps) => {
|
||||
return (
|
||||
<StyledItem>
|
||||
<NavigationDrawerItem
|
||||
className={className}
|
||||
label={label}
|
||||
level={level}
|
||||
Icon={Icon}
|
||||
to={to}
|
||||
onClick={onClick}
|
||||
active={active}
|
||||
danger={danger}
|
||||
soon={soon}
|
||||
count={count}
|
||||
keyboard={keyboard}
|
||||
/>
|
||||
</StyledItem>
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user