diff --git a/packages/twenty-front/src/modules/activities/calendar/components/Calendar.tsx b/packages/twenty-front/src/modules/activities/calendar/components/Calendar.tsx index c9d5f80f6594..7032895aadd1 100644 --- a/packages/twenty-front/src/modules/activities/calendar/components/Calendar.tsx +++ b/packages/twenty-front/src/modules/activities/calendar/components/Calendar.tsx @@ -8,7 +8,7 @@ import { CalendarContext } from '@/activities/calendar/contexts/CalendarContext' import { useCalendarEvents } from '@/activities/calendar/hooks/useCalendarEvents'; import { getTimelineCalendarEventsFromCompanyId } from '@/activities/calendar/queries/getTimelineCalendarEventsFromCompanyId'; import { getTimelineCalendarEventsFromPersonId } from '@/activities/calendar/queries/getTimelineCalendarEventsFromPersonId'; -import { FetchMoreLoader } from '@/activities/components/CustomResolverFetchMoreLoader'; +import { CustomResolverFetchMoreLoader } from '@/activities/components/CustomResolverFetchMoreLoader'; import { useCustomResolver } from '@/activities/hooks/useCustomResolver'; import { ActivityTargetableObject } from '@/activities/types/ActivityTargetableEntity'; import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular'; @@ -128,7 +128,7 @@ export const Calendar = ({ ); })} - diff --git a/packages/twenty-front/src/modules/activities/components/CustomResolverFetchMoreLoader.tsx b/packages/twenty-front/src/modules/activities/components/CustomResolverFetchMoreLoader.tsx index 7bd31dc70b52..b10ff28025bf 100644 --- a/packages/twenty-front/src/modules/activities/components/CustomResolverFetchMoreLoader.tsx +++ b/packages/twenty-front/src/modules/activities/components/CustomResolverFetchMoreLoader.tsx @@ -2,7 +2,7 @@ import { useInView } from 'react-intersection-observer'; import styled from '@emotion/styled'; import { GRAY_SCALE } from 'twenty-ui'; -type FetchMoreLoaderProps = { +type CustomResolverFetchMoreLoaderProps = { loading: boolean; onLastRowVisible: (...args: any[]) => any; }; @@ -17,10 +17,10 @@ const StyledText = styled.div` padding-left: ${({ theme }) => theme.spacing(2)}; `; -export const FetchMoreLoader = ({ +export const CustomResolverFetchMoreLoader = ({ loading, onLastRowVisible, -}: FetchMoreLoaderProps) => { +}: CustomResolverFetchMoreLoaderProps) => { const { ref: tbodyRef } = useInView({ onChange: onLastRowVisible, }); diff --git a/packages/twenty-front/src/modules/activities/emails/components/EmailThreads.tsx b/packages/twenty-front/src/modules/activities/emails/components/EmailThreads.tsx index 31cc98f36134..d0766c517b17 100644 --- a/packages/twenty-front/src/modules/activities/emails/components/EmailThreads.tsx +++ b/packages/twenty-front/src/modules/activities/emails/components/EmailThreads.tsx @@ -1,7 +1,7 @@ import styled from '@emotion/styled'; import { H1Title, H1TitleFontColor } from 'twenty-ui'; -import { FetchMoreLoader } from '@/activities/components/CustomResolverFetchMoreLoader'; +import { CustomResolverFetchMoreLoader } from '@/activities/components/CustomResolverFetchMoreLoader'; import { EmailLoader } from '@/activities/emails/components/EmailLoader'; import { EmailThreadPreview } from '@/activities/emails/components/EmailThreadPreview'; import { TIMELINE_THREADS_DEFAULT_PAGE_SIZE } from '@/activities/emails/constants/Messaging'; @@ -102,7 +102,7 @@ export const EmailThreads = ({ ))} )} - diff --git a/packages/twenty-front/src/modules/activities/emails/right-drawer/components/RightDrawerEmailThread.tsx b/packages/twenty-front/src/modules/activities/emails/right-drawer/components/RightDrawerEmailThread.tsx index 1ec76b8a97ea..d7d040726145 100644 --- a/packages/twenty-front/src/modules/activities/emails/right-drawer/components/RightDrawerEmailThread.tsx +++ b/packages/twenty-front/src/modules/activities/emails/right-drawer/components/RightDrawerEmailThread.tsx @@ -1,7 +1,7 @@ import styled from '@emotion/styled'; import { useRecoilCallback } from 'recoil'; -import { FetchMoreLoader } from '@/activities/components/CustomResolverFetchMoreLoader'; +import { CustomResolverFetchMoreLoader } from '@/activities/components/CustomResolverFetchMoreLoader'; import { EmailLoader } from '@/activities/emails/components/EmailLoader'; import { EmailThreadHeader } from '@/activities/emails/components/EmailThreadHeader'; import { EmailThreadMessage } from '@/activities/emails/components/EmailThreadMessage'; @@ -98,7 +98,7 @@ export const RightDrawerEmailThread = () => { sentAt={lastMessage.receivedAt} isExpanded /> - diff --git a/packages/twenty-front/src/modules/activities/timelineActivities/components/EventRow.tsx b/packages/twenty-front/src/modules/activities/timelineActivities/components/EventRow.tsx index a70cba5e1b57..b22b02340e64 100644 --- a/packages/twenty-front/src/modules/activities/timelineActivities/components/EventRow.tsx +++ b/packages/twenty-front/src/modules/activities/timelineActivities/components/EventRow.tsx @@ -4,19 +4,14 @@ import { useRecoilValue } from 'recoil'; import { useLinkedObject } from '@/activities/timeline/hooks/useLinkedObject'; import { TimelineActivityContext } from '@/activities/timelineActivities/contexts/TimelineActivityContext'; -import { - EventIconDynamicComponent, - EventRowDynamicComponent, -} from '@/activities/timelineActivities/rows/components/EventRowDynamicComponent'; +import { EventIconDynamicComponent } from '@/activities/timelineActivities/rows/components/EventIconDynamicComponent'; +import { EventRowDynamicComponent } from '@/activities/timelineActivities/rows/components/EventRowDynamicComponent'; import { TimelineActivity } from '@/activities/timelineActivities/types/TimelineActivity'; -import { - CurrentWorkspaceMember, - currentWorkspaceMemberState, -} from '@/auth/states/currentWorkspaceMemberState'; +import { getTimelineActivityAuthorFullName } from '@/activities/timelineActivities/utils/getTimelineActivityAuthorFullName'; +import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState'; import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile'; import { beautifyPastDateRelativeToNow } from '~/utils/date-utils'; -import { isDefined } from '~/utils/isDefined'; import { isUndefinedOrNull } from '~/utils/isUndefinedOrNull'; const StyledIconContainer = styled.div` @@ -93,18 +88,6 @@ type EventRowProps = { event: TimelineActivity; }; -const getAuthorFullName = ( - event: TimelineActivity, - currentWorkspaceMember: CurrentWorkspaceMember, -) => { - if (isDefined(event.workspaceMember)) { - return currentWorkspaceMember.id === event.workspaceMember.id - ? 'You' - : `${event.workspaceMember?.name.firstName} ${event.workspaceMember?.name.lastName}`; - } - return 'Twenty'; -}; - export const EventRow = ({ isLastEvent, event, @@ -122,10 +105,13 @@ export const EventRow = ({ return null; } - const authorFullName = getAuthorFullName(event, currentWorkspaceMember); + const authorFullName = getTimelineActivityAuthorFullName( + event, + currentWorkspaceMember, + ); if (isUndefinedOrNull(mainObjectMetadataItem)) { - return null; + throw new Error('mainObjectMetadataItem is required'); } return ( diff --git a/packages/twenty-front/src/modules/activities/timelineActivities/components/TimelineActivities.tsx b/packages/twenty-front/src/modules/activities/timelineActivities/components/TimelineActivities.tsx index fac968ef1df6..c0b47efdac09 100644 --- a/packages/twenty-front/src/modules/activities/timelineActivities/components/TimelineActivities.tsx +++ b/packages/twenty-front/src/modules/activities/timelineActivities/components/TimelineActivities.tsx @@ -1,7 +1,7 @@ import styled from '@emotion/styled'; import { isNonEmptyArray } from '@sniptt/guards'; -import { FetchMoreLoader } from '@/activities/components/CustomResolverFetchMoreLoader'; +import { CustomResolverFetchMoreLoader } from '@/activities/components/CustomResolverFetchMoreLoader'; import { TimelineCreateButtonGroup } from '@/activities/timeline/components/TimelineCreateButtonGroup'; import { EventList } from '@/activities/timelineActivities/components/EventList'; import { useTimelineActivities } from '@/activities/timelineActivities/hooks/useTimelineActivities'; @@ -64,7 +64,10 @@ export const TimelineActivities = ({ title="All" events={timelineActivities ?? []} /> - + ); }; diff --git a/packages/twenty-front/src/modules/activities/timelineActivities/rows/activity/components/EventRowActivity.tsx b/packages/twenty-front/src/modules/activities/timelineActivities/rows/activity/components/EventRowActivity.tsx index 00cfa83958a8..50d58b9c12da 100644 --- a/packages/twenty-front/src/modules/activities/timelineActivities/rows/activity/components/EventRowActivity.tsx +++ b/packages/twenty-front/src/modules/activities/timelineActivities/rows/activity/components/EventRowActivity.tsx @@ -3,8 +3,8 @@ import styled from '@emotion/styled'; import { useOpenActivityRightDrawer } from '@/activities/hooks/useOpenActivityRightDrawer'; import { EventRowDynamicComponentProps, - StyledItemAction, - StyledItemAuthorText, + StyledEventRowItemAction, + StyledEventRowItemColumn, } from '@/activities/timelineActivities/rows/components/EventRowDynamicComponent'; type EventRowActivityProps = EventRowDynamicComponentProps; @@ -14,7 +14,7 @@ const StyledLinkedActivity = styled.span` text-decoration: underline; `; -export const EventRowActivity: React.FC = ({ +export const EventRowActivity = ({ event, authorFullName, }: EventRowActivityProps) => { @@ -28,8 +28,8 @@ export const EventRowActivity: React.FC = ({ return ( <> - {authorFullName} - {eventAction} + {authorFullName} + {eventAction} openActivityRightDrawer(event.linkedRecordId)} > diff --git a/packages/twenty-front/src/modules/activities/timelineActivities/rows/calendar/components/EventCardCalendarEvent.tsx b/packages/twenty-front/src/modules/activities/timelineActivities/rows/calendar/components/EventCardCalendarEvent.tsx index 943473d8c573..affd62affaee 100644 --- a/packages/twenty-front/src/modules/activities/timelineActivities/rows/calendar/components/EventCardCalendarEvent.tsx +++ b/packages/twenty-front/src/modules/activities/timelineActivities/rows/calendar/components/EventCardCalendarEvent.tsx @@ -10,7 +10,7 @@ import { formatToHumanReadableDay, formatToHumanReadableMonth, formatToHumanReadableTime, -} from '~/utils'; +} from '~/utils/format/formatDate'; import { isDefined } from '~/utils/isDefined'; import { isUndefinedOrNull } from '~/utils/isUndefinedOrNull'; @@ -121,7 +121,7 @@ export const EventCardCalendarEvent = ({ return
Calendar event not found
; } - return
Error loading message
; + return
Error loading calendar event
; } if (loading || isUndefined(calendarEvent)) { diff --git a/packages/twenty-front/src/modules/activities/timelineActivities/rows/calendar/components/EventRowCalendarEvent.tsx b/packages/twenty-front/src/modules/activities/timelineActivities/rows/calendar/components/EventRowCalendarEvent.tsx index 53447661f3c6..c1ffd1094ef3 100644 --- a/packages/twenty-front/src/modules/activities/timelineActivities/rows/calendar/components/EventRowCalendarEvent.tsx +++ b/packages/twenty-front/src/modules/activities/timelineActivities/rows/calendar/components/EventRowCalendarEvent.tsx @@ -2,14 +2,12 @@ import { useState } from 'react'; import styled from '@emotion/styled'; import { EventCardCalendarEvent } from '@/activities/timelineActivities/rows/calendar/components/EventCardCalendarEvent'; -import { - EventCard, - EventCardToggleButton, -} from '@/activities/timelineActivities/rows/components/EventCard'; +import { EventCard } from '@/activities/timelineActivities/rows/components/EventCard'; +import { EventCardToggleButton } from '@/activities/timelineActivities/rows/components/EventCardToggleButton'; import { EventRowDynamicComponentProps, - StyledItemAction, - StyledItemAuthorText, + StyledEventRowItemAction, + StyledEventRowItemColumn, } from '@/activities/timelineActivities/rows/components/EventRowDynamicComponent'; type EventRowCalendarEventProps = EventRowDynamicComponentProps; @@ -26,7 +24,7 @@ const StyledRowContainer = styled.div` gap: ${({ theme }) => theme.spacing(1)}; `; -export const EventRowCalendarEvent: React.FC = ({ +export const EventRowCalendarEvent = ({ event, authorFullName, labelIdentifierValue, @@ -34,24 +32,17 @@ export const EventRowCalendarEvent: React.FC = ({ const [, eventAction] = event.name.split('.'); const [isOpen, setIsOpen] = useState(false); - const renderRow = () => { - switch (eventAction) { - case 'linked': { - return ( - - linked a calendar event with {labelIdentifierValue} - - ); - } - default: - throw new Error('Invalid event action for calendarEvent event type.'); - } - }; + if (['linked'].includes(eventAction) === false) { + throw new Error('Invalid event action for calendarEvent event type.'); + } + return ( - {authorFullName} - {renderRow()} + {authorFullName} + + linked a calendar event with {labelIdentifierValue} + diff --git a/packages/twenty-front/src/modules/activities/timelineActivities/rows/calendar/components/__stories__/EventCardCalendarEvent.stories.tsx b/packages/twenty-front/src/modules/activities/timelineActivities/rows/calendar/components/__stories__/EventCardCalendarEvent.stories.tsx new file mode 100644 index 000000000000..b0c3fd7ca54c --- /dev/null +++ b/packages/twenty-front/src/modules/activities/timelineActivities/rows/calendar/components/__stories__/EventCardCalendarEvent.stories.tsx @@ -0,0 +1,68 @@ +import { Meta, StoryObj } from '@storybook/react'; +import { graphql, HttpResponse } from 'msw'; +import { ComponentDecorator } from 'twenty-ui'; + +import { EventCardCalendarEvent } from '@/activities/timelineActivities/rows/calendar/components/EventCardCalendarEvent'; +import { ObjectMetadataItemsDecorator } from '~/testing/decorators/ObjectMetadataItemsDecorator'; +import { SnackBarDecorator } from '~/testing/decorators/SnackBarDecorator'; + +const meta: Meta = { + title: 'Modules/TimelineActivities/Rows/CalendarEvent/EventCardCalendarEvent', + component: EventCardCalendarEvent, + decorators: [ + ComponentDecorator, + ObjectMetadataItemsDecorator, + SnackBarDecorator, + ], +}; + +export default meta; +type Story = StoryObj; + +export const Default: Story = { + args: { + calendarEventId: '1', + }, + parameters: { + msw: { + handlers: [ + graphql.query('FindOneCalendarEvent', () => { + return HttpResponse.json({ + data: { + calendarEvent: { + id: '1', + title: 'Mock title', + startsAt: '2022-01-01T00:00:00Z', + endsAt: '2022-01-01T01:00:00Z', + }, + }, + }); + }), + ], + }, + }, +}; + +export const NotShared: Story = { + args: { + calendarEventId: '1', + }, + parameters: { + msw: { + handlers: [ + graphql.query('FindOneCalendarEvent', () => { + return HttpResponse.json({ + errors: [ + { + message: 'Forbidden', + extensions: { + code: 'FORBIDDEN', + }, + }, + ], + }); + }), + ], + }, + }, +}; diff --git a/packages/twenty-front/src/modules/activities/timelineActivities/rows/components/EventCard.tsx b/packages/twenty-front/src/modules/activities/timelineActivities/rows/components/EventCard.tsx index b802c3047d15..baed69eb738a 100644 --- a/packages/twenty-front/src/modules/activities/timelineActivities/rows/components/EventCard.tsx +++ b/packages/twenty-front/src/modules/activities/timelineActivities/rows/components/EventCard.tsx @@ -1,7 +1,5 @@ import styled from '@emotion/styled'; -import { IconChevronDown, IconChevronUp } from 'twenty-ui'; -import { IconButton } from '@/ui/input/button/components/IconButton'; import { Card } from '@/ui/layout/card/components/Card'; type EventCardProps = { @@ -9,15 +7,6 @@ type EventCardProps = { isOpen: boolean; }; -type EventCardToggleButtonProps = { - isOpen: boolean; - setIsOpen: (isOpen: boolean) => void; -}; - -const StyledButtonContainer = styled.div` - border-radius: ${({ theme }) => theme.border.radius.sm}; -`; - const StyledCardContainer = styled.div` align-items: flex-start; display: flex; @@ -52,19 +41,3 @@ export const EventCard = ({ children, isOpen }: EventCardProps) => { ) ); }; - -export const EventCardToggleButton = ({ - isOpen, - setIsOpen, -}: EventCardToggleButtonProps) => { - return ( - - setIsOpen(!isOpen)} - size="small" - variant="secondary" - /> - - ); -}; diff --git a/packages/twenty-front/src/modules/activities/timelineActivities/rows/components/EventCardToggleButton.tsx b/packages/twenty-front/src/modules/activities/timelineActivities/rows/components/EventCardToggleButton.tsx new file mode 100644 index 000000000000..44a2cb4066a2 --- /dev/null +++ b/packages/twenty-front/src/modules/activities/timelineActivities/rows/components/EventCardToggleButton.tsx @@ -0,0 +1,29 @@ +import styled from '@emotion/styled'; +import { IconChevronDown, IconChevronUp } from 'twenty-ui'; + +import { IconButton } from '@/ui/input/button/components/IconButton'; + +type EventCardToggleButtonProps = { + isOpen: boolean; + setIsOpen: (isOpen: boolean) => void; +}; + +const StyledButtonContainer = styled.div` + border-radius: ${({ theme }) => theme.border.radius.sm}; +`; + +export const EventCardToggleButton = ({ + isOpen, + setIsOpen, +}: EventCardToggleButtonProps) => { + return ( + + setIsOpen(!isOpen)} + size="small" + variant="secondary" + /> + + ); +}; diff --git a/packages/twenty-front/src/modules/activities/timelineActivities/rows/components/EventIconDynamicComponent.tsx b/packages/twenty-front/src/modules/activities/timelineActivities/rows/components/EventIconDynamicComponent.tsx new file mode 100644 index 000000000000..d1b35d7f2293 --- /dev/null +++ b/packages/twenty-front/src/modules/activities/timelineActivities/rows/components/EventIconDynamicComponent.tsx @@ -0,0 +1,26 @@ +import { IconCirclePlus, IconEditCircle, useIcons } from 'twenty-ui'; + +import { TimelineActivity } from '@/activities/timelineActivities/types/TimelineActivity'; +import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; + +export const EventIconDynamicComponent = ({ + event, + linkedObjectMetadataItem, +}: { + event: TimelineActivity; + linkedObjectMetadataItem: ObjectMetadataItem | null; +}) => { + const { getIcon } = useIcons(); + const [, eventAction] = event.name.split('.'); + + if (eventAction === 'created') { + return ; + } + if (eventAction === 'updated') { + return ; + } + + const IconComponent = getIcon(linkedObjectMetadataItem?.icon); + + return ; +}; diff --git a/packages/twenty-front/src/modules/activities/timelineActivities/rows/components/EventRowDynamicComponent.tsx b/packages/twenty-front/src/modules/activities/timelineActivities/rows/components/EventRowDynamicComponent.tsx index 0c51cdf65876..955450d67ca9 100644 --- a/packages/twenty-front/src/modules/activities/timelineActivities/rows/components/EventRowDynamicComponent.tsx +++ b/packages/twenty-front/src/modules/activities/timelineActivities/rows/components/EventRowDynamicComponent.tsx @@ -1,13 +1,11 @@ import styled from '@emotion/styled'; -import { IconCirclePlus, IconEditCircle, useIcons } from 'twenty-ui'; import { EventRowActivity } from '@/activities/timelineActivities/rows/activity/components/EventRowActivity'; import { EventRowCalendarEvent } from '@/activities/timelineActivities/rows/calendar/components/EventRowCalendarEvent'; -import { EventRowMainObject } from '@/activities/timelineActivities/rows/mainObject/components/EventRowMainObject'; +import { EventRowMainObject } from '@/activities/timelineActivities/rows/main-object/components/EventRowMainObject'; import { EventRowMessage } from '@/activities/timelineActivities/rows/message/components/EventRowMessage'; import { TimelineActivity } from '@/activities/timelineActivities/types/TimelineActivity'; import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; -import { isDefined } from '~/utils/isDefined'; export interface EventRowDynamicComponentProps { labelIdentifierValue: string; @@ -17,7 +15,7 @@ export interface EventRowDynamicComponentProps { authorFullName: string; } -const StyledItemColumn = styled.div` +export const StyledEventRowItemColumn = styled.div` align-items: center; color: ${({ theme }) => theme.font.color.primary}; display: flex; @@ -25,23 +23,10 @@ const StyledItemColumn = styled.div` gap: ${({ theme }) => theme.spacing(1)}; `; -export const StyledItemAuthorText = styled(StyledItemColumn)``; - -export const StyledItemLabelIdentifier = styled(StyledItemColumn)``; - -export const StyledItemAction = styled(StyledItemColumn)` +export const StyledEventRowItemAction = styled(StyledEventRowItemColumn)` color: ${({ theme }) => theme.font.color.secondary}; `; -const eventRowComponentMap: { - [key: string]: React.FC; -} = { - calendarEvent: EventRowCalendarEvent, - message: EventRowMessage, - task: EventRowActivity, - note: EventRowActivity, -}; - export const EventRowDynamicComponent = ({ labelIdentifierValue, event, @@ -50,53 +35,52 @@ export const EventRowDynamicComponent = ({ authorFullName, }: EventRowDynamicComponentProps) => { const [eventName] = event.name.split('.'); - const EventRowComponent = eventRowComponentMap[eventName]; - - if (isDefined(EventRowComponent)) { - return ( - - ); - } - - if (eventName === mainObjectMetadataItem?.nameSingular) { - return ( - - ); - } - - throw new Error(`Cannot find event component for event name ${eventName}`); -}; -export const EventIconDynamicComponent = ({ - event, - linkedObjectMetadataItem, -}: { - event: TimelineActivity; - linkedObjectMetadataItem: ObjectMetadataItem | null; -}) => { - const { getIcon } = useIcons(); - const [, eventAction] = event.name.split('.'); - - if (eventAction === 'created') { - return ; - } - if (eventAction === 'updated') { - return ; + switch (eventName) { + case 'calendarEvent': + return ( + + ); + case 'message': + return ( + + ); + case 'task': + case 'note': + return ( + + ); + case mainObjectMetadataItem?.nameSingular: + return ( + + ); + default: + throw new Error( + `Cannot find event component for event name ${eventName}`, + ); } - - const IconComponent = getIcon(linkedObjectMetadataItem?.icon); - - return ; }; diff --git a/packages/twenty-front/src/modules/activities/timelineActivities/rows/mainObject/components/EventFieldDiff.tsx b/packages/twenty-front/src/modules/activities/timelineActivities/rows/main-object/components/EventFieldDiff.tsx similarity index 76% rename from packages/twenty-front/src/modules/activities/timelineActivities/rows/mainObject/components/EventFieldDiff.tsx rename to packages/twenty-front/src/modules/activities/timelineActivities/rows/main-object/components/EventFieldDiff.tsx index d948b1c4ce28..1f55059a9c03 100644 --- a/packages/twenty-front/src/modules/activities/timelineActivities/rows/mainObject/components/EventFieldDiff.tsx +++ b/packages/twenty-front/src/modules/activities/timelineActivities/rows/main-object/components/EventFieldDiff.tsx @@ -1,8 +1,8 @@ import styled from '@emotion/styled'; -import { EventFieldDiffLabel } from '@/activities/timelineActivities/rows/mainObject/components/EventFieldDiffLabel'; -import { EventFieldDiffValue } from '@/activities/timelineActivities/rows/mainObject/components/EventFieldDiffValue'; -import { EventFieldDiffValueEffect } from '@/activities/timelineActivities/rows/mainObject/components/EventFieldDiffValueEffect'; +import { EventFieldDiffLabel } from '@/activities/timelineActivities/rows/main-object/components/EventFieldDiffLabel'; +import { EventFieldDiffValue } from '@/activities/timelineActivities/rows/main-object/components/EventFieldDiffValue'; +import { EventFieldDiffValueEffect } from '@/activities/timelineActivities/rows/main-object/components/EventFieldDiffValueEffect'; import { FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem'; import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; @@ -10,7 +10,7 @@ type EventFieldDiffProps = { diffRecord: Record; mainObjectMetadataItem: ObjectMetadataItem; fieldMetadataItem: FieldMetadataItem | undefined; - forgedRecordId: string; + diffArtificialRecordStoreId: string; }; const StyledEventFieldDiffContainer = styled.div` @@ -26,23 +26,23 @@ export const EventFieldDiff = ({ diffRecord, mainObjectMetadataItem, fieldMetadataItem, - forgedRecordId, + diffArtificialRecordStoreId, }: EventFieldDiffProps) => { if (!fieldMetadataItem) { - return null; + throw new Error('fieldMetadataItem is required'); } return ( diff --git a/packages/twenty-front/src/modules/activities/timelineActivities/rows/main-object/components/EventFieldDiffContainer.tsx b/packages/twenty-front/src/modules/activities/timelineActivities/rows/main-object/components/EventFieldDiffContainer.tsx new file mode 100644 index 000000000000..3b4cf60396f8 --- /dev/null +++ b/packages/twenty-front/src/modules/activities/timelineActivities/rows/main-object/components/EventFieldDiffContainer.tsx @@ -0,0 +1,39 @@ +import { EventFieldDiff } from '@/activities/timelineActivities/rows/main-object/components/EventFieldDiff'; +import { FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem'; +import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; + +type EventFieldDiffContainerProps = { + mainObjectMetadataItem: ObjectMetadataItem; + diffKey: string; + diffValue: any; + eventId: string; + fieldMetadataItemMap: Record; +}; + +export const EventFieldDiffContainer = ({ + mainObjectMetadataItem, + diffKey, + diffValue, + eventId, + fieldMetadataItemMap, +}: EventFieldDiffContainerProps) => { + const fieldMetadataItem = fieldMetadataItemMap[diffKey]; + + if (!fieldMetadataItem) { + throw new Error( + `Cannot find field metadata item for field name ${diffKey} on object ${mainObjectMetadataItem.nameSingular}`, + ); + } + + const diffArtificialRecordStoreId = eventId + '--' + fieldMetadataItem.id; + + return ( + + ); +}; diff --git a/packages/twenty-front/src/modules/activities/timelineActivities/rows/mainObject/components/EventFieldDiffLabel.tsx b/packages/twenty-front/src/modules/activities/timelineActivities/rows/main-object/components/EventFieldDiffLabel.tsx similarity index 87% rename from packages/twenty-front/src/modules/activities/timelineActivities/rows/mainObject/components/EventFieldDiffLabel.tsx rename to packages/twenty-front/src/modules/activities/timelineActivities/rows/main-object/components/EventFieldDiffLabel.tsx index 9ee7393ef6a6..6bb302ae9638 100644 --- a/packages/twenty-front/src/modules/activities/timelineActivities/rows/mainObject/components/EventFieldDiffLabel.tsx +++ b/packages/twenty-front/src/modules/activities/timelineActivities/rows/main-object/components/EventFieldDiffLabel.tsx @@ -22,8 +22,6 @@ const StyledUpdatedFieldIconContainer = styled.div` width: 14px; `; -const StyledUpdatedFieldLabel = styled.div``; - export const EventFieldDiffLabel = ({ fieldMetadataItem, }: EventFieldDiffLabelProps) => { @@ -38,9 +36,7 @@ export const EventFieldDiffLabel = ({ - - {fieldMetadataItem.label} - + {fieldMetadataItem.label} ); }; diff --git a/packages/twenty-front/src/modules/activities/timelineActivities/rows/mainObject/components/EventFieldDiffValue.tsx b/packages/twenty-front/src/modules/activities/timelineActivities/rows/main-object/components/EventFieldDiffValue.tsx similarity index 93% rename from packages/twenty-front/src/modules/activities/timelineActivities/rows/mainObject/components/EventFieldDiffValue.tsx rename to packages/twenty-front/src/modules/activities/timelineActivities/rows/main-object/components/EventFieldDiffValue.tsx index e37cbdec3f8f..0cfc1c6b74ce 100644 --- a/packages/twenty-front/src/modules/activities/timelineActivities/rows/mainObject/components/EventFieldDiffValue.tsx +++ b/packages/twenty-front/src/modules/activities/timelineActivities/rows/main-object/components/EventFieldDiffValue.tsx @@ -7,7 +7,7 @@ import { FieldDisplay } from '@/object-record/record-field/components/FieldDispl import { FieldContext } from '@/object-record/record-field/contexts/FieldContext'; type EventFieldDiffValueProps = { - forgedRecordId: string; + diffArtificialRecordStoreId: string; mainObjectMetadataItem: ObjectMetadataItem; fieldMetadataItem: FieldMetadataItem; }; @@ -18,7 +18,7 @@ const StyledEventFieldDiffValue = styled.div` `; export const EventFieldDiffValue = ({ - forgedRecordId, + diffArtificialRecordStoreId, mainObjectMetadataItem, fieldMetadataItem, }: EventFieldDiffValueProps) => { @@ -26,7 +26,7 @@ export const EventFieldDiffValue = ({ | undefined; mainObjectMetadataItem: ObjectMetadataItem; fieldMetadataItem: FieldMetadataItem; }) => { const setEntityFields = useSetRecoilState( - recordStoreFamilyState(forgedRecordId), + recordStoreFamilyState(diffArtificialRecordStoreId), ); useEffect(() => { @@ -25,14 +25,14 @@ export const EventFieldDiffValueEffect = ({ const forgedObjectRecord = { __typename: mainObjectMetadataItem.nameSingular, - id: forgedRecordId, + id: diffArtificialRecordStoreId, [fieldMetadataItem.name]: diffRecord, }; setEntityFields(forgedObjectRecord); }, [ diffRecord, - forgedRecordId, + diffArtificialRecordStoreId, fieldMetadataItem.name, mainObjectMetadataItem.nameSingular, setEntityFields, diff --git a/packages/twenty-front/src/modules/activities/timelineActivities/rows/mainObject/components/EventRowMainObject.tsx b/packages/twenty-front/src/modules/activities/timelineActivities/rows/main-object/components/EventRowMainObject.tsx similarity index 75% rename from packages/twenty-front/src/modules/activities/timelineActivities/rows/mainObject/components/EventRowMainObject.tsx rename to packages/twenty-front/src/modules/activities/timelineActivities/rows/main-object/components/EventRowMainObject.tsx index f2aa06509847..f2dcc69055fa 100644 --- a/packages/twenty-front/src/modules/activities/timelineActivities/rows/mainObject/components/EventRowMainObject.tsx +++ b/packages/twenty-front/src/modules/activities/timelineActivities/rows/main-object/components/EventRowMainObject.tsx @@ -2,11 +2,10 @@ import styled from '@emotion/styled'; import { EventRowDynamicComponentProps, - StyledItemAction, - StyledItemAuthorText, - StyledItemLabelIdentifier, + StyledEventRowItemAction, + StyledEventRowItemColumn, } from '@/activities/timelineActivities/rows/components/EventRowDynamicComponent'; -import { EventRowMainObjectUpdated } from '@/activities/timelineActivities/rows/mainObject/components/EventRowMainObjectUpdated'; +import { EventRowMainObjectUpdated } from '@/activities/timelineActivities/rows/main-object/components/EventRowMainObjectUpdated'; type EventRowMainObjectProps = EventRowDynamicComponentProps; @@ -28,11 +27,11 @@ export const EventRowMainObject = ({ case 'created': { return ( - + {labelIdentifierValue} - - was created by - {authorFullName} + + was created by + {authorFullName} ); } diff --git a/packages/twenty-front/src/modules/activities/timelineActivities/rows/mainObject/components/EventRowMainObjectUpdated.tsx b/packages/twenty-front/src/modules/activities/timelineActivities/rows/main-object/components/EventRowMainObjectUpdated.tsx similarity index 55% rename from packages/twenty-front/src/modules/activities/timelineActivities/rows/mainObject/components/EventRowMainObjectUpdated.tsx rename to packages/twenty-front/src/modules/activities/timelineActivities/rows/main-object/components/EventRowMainObjectUpdated.tsx index 435a834bb5ad..30e6343bd708 100644 --- a/packages/twenty-front/src/modules/activities/timelineActivities/rows/mainObject/components/EventRowMainObjectUpdated.tsx +++ b/packages/twenty-front/src/modules/activities/timelineActivities/rows/main-object/components/EventRowMainObjectUpdated.tsx @@ -1,15 +1,13 @@ import { useState } from 'react'; import styled from '@emotion/styled'; +import { EventCard } from '@/activities/timelineActivities/rows/components/EventCard'; +import { EventCardToggleButton } from '@/activities/timelineActivities/rows/components/EventCardToggleButton'; import { - EventCard, - EventCardToggleButton, -} from '@/activities/timelineActivities/rows/components/EventCard'; -import { - StyledItemAction, - StyledItemAuthorText, + StyledEventRowItemAction, + StyledEventRowItemColumn, } from '@/activities/timelineActivities/rows/components/EventRowDynamicComponent'; -import { EventFieldDiff } from '@/activities/timelineActivities/rows/mainObject/components/EventFieldDiff'; +import { EventFieldDiffContainer } from '@/activities/timelineActivities/rows/main-object/components/EventFieldDiffContainer'; import { TimelineActivity } from '@/activities/timelineActivities/types/TimelineActivity'; import { FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem'; import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; @@ -33,34 +31,6 @@ const StyledEventRowMainObjectUpdatedContainer = styled.div` gap: ${({ theme }) => theme.spacing(1)}; `; -const renderUpdateDescription = ( - mainObjectMetadataItem: ObjectMetadataItem, - diffKey: string, - diffValue: any, - eventId: string, - fieldMetadataItemMap: Record, -) => { - const fieldMetadataItem = fieldMetadataItemMap[diffKey]; - - if (!fieldMetadataItem) { - throw new Error( - `Cannot find field metadata item for field name ${diffKey} on object ${mainObjectMetadataItem.nameSingular}`, - ); - } - - const forgedRecordId = eventId + '--' + fieldMetadataItem.id; - - return ( - - ); -}; - export const EventRowMainObjectUpdated = ({ authorFullName, labelIdentifierValue, @@ -86,17 +56,18 @@ export const EventRowMainObjectUpdated = ({ return ( - {authorFullName} - + {authorFullName} + updated - {diffEntries.length === 1 && - renderUpdateDescription( - mainObjectMetadataItem, - diffEntries[0][0], - diffEntries[0][1].after, - event.id, - fieldMetadataItemMap, - )} + {diffEntries.length === 1 && ( + + )} {diffEntries.length > 1 && ( <> @@ -105,19 +76,20 @@ export const EventRowMainObjectUpdated = ({ )} - + {diffEntries.length > 1 && ( - {diffEntries.map(([diffKey, diffValue]) => - renderUpdateDescription( - mainObjectMetadataItem, - diffKey, - diffValue.after, - event.id, - fieldMetadataItemMap, - ), - )} + {diffEntries.map(([diffKey, diffValue]) => ( + + ))} )} diff --git a/packages/twenty-front/src/modules/activities/timelineActivities/rows/main-object/components/__stories__/EventRowMainObjectUpdated.stories.tsx b/packages/twenty-front/src/modules/activities/timelineActivities/rows/main-object/components/__stories__/EventRowMainObjectUpdated.stories.tsx new file mode 100644 index 000000000000..0cb945bdc4b2 --- /dev/null +++ b/packages/twenty-front/src/modules/activities/timelineActivities/rows/main-object/components/__stories__/EventRowMainObjectUpdated.stories.tsx @@ -0,0 +1,51 @@ +import { Meta, StoryObj } from '@storybook/react'; +import { ComponentDecorator, RouterDecorator } from 'twenty-ui'; + +import { EventRowMainObjectUpdated } from '@/activities/timelineActivities/rows/main-object/components/EventRowMainObjectUpdated'; +import { TimelineActivity } from '@/activities/timelineActivities/types/TimelineActivity'; +import { ObjectMetadataItemsDecorator } from '~/testing/decorators/ObjectMetadataItemsDecorator'; +import { SnackBarDecorator } from '~/testing/decorators/SnackBarDecorator'; +import { mockedPersonObjectMetadataItem } from '~/testing/mock-data/metadata'; + +const meta: Meta = { + title: 'Modules/TimelineActivities/Rows/MainObject/EventRowMainObjectUpdated', + component: EventRowMainObjectUpdated, + args: { + authorFullName: 'John Doe', + labelIdentifierValue: 'Mock', + event: { + id: '1', + name: 'mock.updated', + properties: { + diff: { + jobTitle: { + after: 'mock job title', + before: '', + }, + linkedinLink: { + after: { + url: 'mock.linkedin', + label: 'mock linkedin url', + }, + before: { + url: '', + label: '', + }, + }, + }, + }, + } as TimelineActivity, + mainObjectMetadataItem: mockedPersonObjectMetadataItem, + }, + decorators: [ + ComponentDecorator, + ObjectMetadataItemsDecorator, + SnackBarDecorator, + RouterDecorator, + ], +}; + +export default meta; +type Story = StoryObj; + +export const Default: Story = {}; diff --git a/packages/twenty-front/src/modules/activities/timelineActivities/rows/message/components/EventCardMessage.tsx b/packages/twenty-front/src/modules/activities/timelineActivities/rows/message/components/EventCardMessage.tsx index 4f85b7463cea..d39f46c677ab 100644 --- a/packages/twenty-front/src/modules/activities/timelineActivities/rows/message/components/EventCardMessage.tsx +++ b/packages/twenty-front/src/modules/activities/timelineActivities/rows/message/components/EventCardMessage.tsx @@ -101,7 +101,7 @@ export const EventCardMessage = ({ return
Loading...
; } - const messageParticipantHandles = message?.messageParticipants + const messageParticipantHandles = message.messageParticipants .map((participant) => participant.handle) .join(', '); diff --git a/packages/twenty-front/src/modules/activities/timelineActivities/rows/message/components/EventRowMessage.tsx b/packages/twenty-front/src/modules/activities/timelineActivities/rows/message/components/EventRowMessage.tsx index 95e34fa8c159..83513994517f 100644 --- a/packages/twenty-front/src/modules/activities/timelineActivities/rows/message/components/EventRowMessage.tsx +++ b/packages/twenty-front/src/modules/activities/timelineActivities/rows/message/components/EventRowMessage.tsx @@ -1,15 +1,12 @@ import { useState } from 'react'; import styled from '@emotion/styled'; -import { - EventCard, - EventCardToggleButton, -} from '@/activities/timelineActivities/rows/components/EventCard'; +import { EventCard } from '@/activities/timelineActivities/rows/components/EventCard'; +import { EventCardToggleButton } from '@/activities/timelineActivities/rows/components/EventCardToggleButton'; import { EventRowDynamicComponentProps, - StyledItemAction, - StyledItemAuthorText, - StyledItemLabelIdentifier, + StyledEventRowItemAction, + StyledEventRowItemColumn, } from '@/activities/timelineActivities/rows/components/EventRowDynamicComponent'; import { EventCardMessage } from '@/activities/timelineActivities/rows/message/components/EventCardMessage'; @@ -27,36 +24,28 @@ const StyledRowContainer = styled.div` gap: ${({ theme }) => theme.spacing(1)}; `; -export const EventRowMessage: React.FC = ({ - labelIdentifierValue, +export const EventRowMessage = ({ event, authorFullName, + labelIdentifierValue, }: EventRowMessageProps) => { const [, eventAction] = event.name.split('.'); const [isOpen, setIsOpen] = useState(false); - const renderRow = () => { - switch (eventAction) { - case 'linked': { - return ( - <> - {authorFullName} - linked an email with - - {labelIdentifierValue} - - - ); - } - default: - throw new Error('Invalid event action for message event type.'); - } - }; + if (['linked'].includes(eventAction) === false) { + throw new Error('Invalid event action for message event type.'); + } return ( - {renderRow()} + {authorFullName} + + linked an email with + + + {labelIdentifierValue} + diff --git a/packages/twenty-front/src/modules/activities/timelineActivities/rows/message/components/__stories__/EventCardMessage.stories.tsx b/packages/twenty-front/src/modules/activities/timelineActivities/rows/message/components/__stories__/EventCardMessage.stories.tsx new file mode 100644 index 000000000000..3e8e08cd06dc --- /dev/null +++ b/packages/twenty-front/src/modules/activities/timelineActivities/rows/message/components/__stories__/EventCardMessage.stories.tsx @@ -0,0 +1,82 @@ +import { Meta, StoryObj } from '@storybook/react'; +import { graphql, HttpResponse } from 'msw'; +import { ComponentDecorator } from 'twenty-ui'; + +import { TimelineActivityContext } from '@/activities/timelineActivities/contexts/TimelineActivityContext'; +import { EventCardMessage } from '@/activities/timelineActivities/rows/message/components/EventCardMessage'; +import { ObjectMetadataItemsDecorator } from '~/testing/decorators/ObjectMetadataItemsDecorator'; +import { SnackBarDecorator } from '~/testing/decorators/SnackBarDecorator'; + +const meta: Meta = { + title: 'Modules/TimelineActivities/Rows/Message/EventCardMessage', + component: EventCardMessage, + decorators: [ + ComponentDecorator, + ObjectMetadataItemsDecorator, + SnackBarDecorator, + (Story) => { + return ( + + + + ); + }, + ], +}; + +export default meta; +type Story = StoryObj; + +export const Default: Story = { + args: { + messageId: '1', + authorFullName: 'John Doe', + }, + parameters: { + msw: { + handlers: [ + graphql.query('FindOneMessage', () => { + return HttpResponse.json({ + data: { + message: { + id: '1', + subject: 'Mock title', + text: 'Mock body', + messageParticipants: [], + }, + }, + }); + }), + ], + }, + }, +}; + +export const NotShared: Story = { + args: { + messageId: '1', + authorFullName: 'John Doe', + }, + parameters: { + msw: { + handlers: [ + graphql.query('FindOneMessage', () => { + return HttpResponse.json({ + errors: [ + { + message: 'Forbidden', + extensions: { + code: 'FORBIDDEN', + }, + }, + ], + }); + }), + ], + }, + }, +}; diff --git a/packages/twenty-front/src/modules/activities/timelineActivities/utils/__tests__/getTimelineActivityAuthorFullName.test.ts b/packages/twenty-front/src/modules/activities/timelineActivities/utils/__tests__/getTimelineActivityAuthorFullName.test.ts new file mode 100644 index 000000000000..85ac636ea096 --- /dev/null +++ b/packages/twenty-front/src/modules/activities/timelineActivities/utils/__tests__/getTimelineActivityAuthorFullName.test.ts @@ -0,0 +1,63 @@ +import { TimelineActivity } from '@/activities/timelineActivities/types/TimelineActivity'; +import { getTimelineActivityAuthorFullName } from '@/activities/timelineActivities/utils/getTimelineActivityAuthorFullName'; +import { CurrentWorkspaceMember } from '@/auth/states/currentWorkspaceMemberState'; + +describe('getTimelineActivityAuthorFullName', () => { + it('should return "You" if the current workspace member is the author', () => { + const event = { + workspaceMember: { + id: '123', + name: { + firstName: 'John', + lastName: 'Doe', + }, + }, + }; + const currentWorkspaceMember = { + id: '123', + }; + + const result = getTimelineActivityAuthorFullName( + event as TimelineActivity, + currentWorkspaceMember as CurrentWorkspaceMember, + ); + + expect(result).toBe('You'); + }); + + it('should return the full name of the workspace member if they are not the current workspace member', () => { + const event = { + workspaceMember: { + id: '456', + name: { + firstName: 'Jane', + lastName: 'Smith', + }, + }, + }; + const currentWorkspaceMember = { + id: '123', + }; + + const result = getTimelineActivityAuthorFullName( + event as TimelineActivity, + currentWorkspaceMember as CurrentWorkspaceMember, + ); + + expect(result).toBe('Jane Smith'); + }); + + it('should return "Twenty" if the workspace member is not defined', () => { + const event = {}; + const currentWorkspaceMember = { + id: '123', + }; + + const result = getTimelineActivityAuthorFullName( + event as TimelineActivity, + currentWorkspaceMember as CurrentWorkspaceMember, + ); + + expect(result).toBe('Twenty'); + }); +}); diff --git a/packages/twenty-front/src/modules/activities/timelineActivities/utils/getTimelineActivityAuthorFullName.ts b/packages/twenty-front/src/modules/activities/timelineActivities/utils/getTimelineActivityAuthorFullName.ts new file mode 100644 index 000000000000..e97b27fa9450 --- /dev/null +++ b/packages/twenty-front/src/modules/activities/timelineActivities/utils/getTimelineActivityAuthorFullName.ts @@ -0,0 +1,15 @@ +import { TimelineActivity } from '@/activities/timelineActivities/types/TimelineActivity'; +import { CurrentWorkspaceMember } from '@/auth/states/currentWorkspaceMemberState'; +import { isDefined } from '~/utils/isDefined'; + +export const getTimelineActivityAuthorFullName = ( + event: TimelineActivity, + currentWorkspaceMember: CurrentWorkspaceMember, +) => { + if (isDefined(event.workspaceMember)) { + return currentWorkspaceMember.id === event.workspaceMember.id + ? 'You' + : `${event.workspaceMember?.name.firstName} ${event.workspaceMember?.name.lastName}`; + } + return 'Twenty'; +}; diff --git a/packages/twenty-front/src/modules/object-metadata/utils/getObjectMetadataItemsMock.ts b/packages/twenty-front/src/modules/object-metadata/utils/getObjectMetadataItemsMock.ts index f4baef7cb995..c50755a51f92 100644 --- a/packages/twenty-front/src/modules/object-metadata/utils/getObjectMetadataItemsMock.ts +++ b/packages/twenty-front/src/modules/object-metadata/utils/getObjectMetadataItemsMock.ts @@ -3638,7 +3638,7 @@ export const getObjectMetadataItemsMock = () => { }, { __typename: 'object', - id: '20202020-049d-4d0c-9e7c-e74fee3f88b2', + id: '20202020-6736-4337-b5c4-8b39fae325a5', dataSourceId: '20202020-7f63-47a9-b1b3-6c7290ca9fb1', nameSingular: 'timelineActivity', namePlural: 'timelineActivities', @@ -3654,6 +3654,24 @@ export const getObjectMetadataItemsMock = () => { updatedAt: '2023-11-30T11:13:15.206Z', fields: [], }, + { + __typename: 'object', + id: '20202020-3f6b-4425-80ab-e468899ab4b2', + dataSourceId: '20202020-7f63-47a9-b1b3-6c7290ca9fb1', + nameSingular: 'message', + namePlural: 'messages', + labelSingular: 'Message', + labelPlural: 'Messages', + description: 'A message', + icon: 'IconMessage', + isCustom: false, + isRemote: false, + isActive: true, + isSystem: true, + createdAt: '2023-11-30T11:13:15.206Z', + updatedAt: '2023-11-30T11:13:15.206Z', + fields: [], + }, ]; // Todo fix typing here (the backend is not in sync with the frontend) diff --git a/packages/twenty-front/src/utils/format/__tests__/formatDate.test.ts b/packages/twenty-front/src/utils/format/__tests__/formatDate.test.ts new file mode 100644 index 000000000000..a80c11d3e7f5 --- /dev/null +++ b/packages/twenty-front/src/utils/format/__tests__/formatDate.test.ts @@ -0,0 +1,29 @@ +import { + formatToHumanReadableDay, + formatToHumanReadableMonth, + formatToHumanReadableTime, +} from '../formatDate'; + +describe('formatToHumanReadableMonth', () => { + it('should format the date to a human-readable month', () => { + const date = new Date('2022-01-01'); + const result = formatToHumanReadableMonth(date); + expect(result).toBe('Jan'); + }); +}); + +describe('formatToHumanReadableDay', () => { + it('should format the date to a human-readable day', () => { + const date = new Date('2022-01-01'); + const result = formatToHumanReadableDay(date); + expect(result).toBe('1'); + }); +}); + +describe('formatToHumanReadableTime', () => { + it('should format the date to a human-readable time', () => { + const date = new Date('2022-01-01T12:30:00'); + const result = formatToHumanReadableTime(date); + expect(result).toBe('12:30 PM'); + }); +}); diff --git a/packages/twenty-front/src/utils/format/formatDate.ts b/packages/twenty-front/src/utils/format/formatDate.ts new file mode 100644 index 000000000000..7bed724a89ad --- /dev/null +++ b/packages/twenty-front/src/utils/format/formatDate.ts @@ -0,0 +1,26 @@ +import { parseDate } from '~/utils/date-utils'; + +export const formatToHumanReadableMonth = (date: Date | string) => { + const parsedJSDate = parseDate(date).toJSDate(); + + return new Intl.DateTimeFormat(undefined, { + month: 'short', + }).format(parsedJSDate); +}; + +export const formatToHumanReadableDay = (date: Date | string) => { + const parsedJSDate = parseDate(date).toJSDate(); + + return new Intl.DateTimeFormat(undefined, { + day: 'numeric', + }).format(parsedJSDate); +}; + +export const formatToHumanReadableTime = (date: Date | string) => { + const parsedJSDate = parseDate(date).toJSDate(); + + return new Intl.DateTimeFormat(undefined, { + hour: 'numeric', + minute: 'numeric', + }).format(parsedJSDate); +}; diff --git a/packages/twenty-front/src/utils/index.ts b/packages/twenty-front/src/utils/index.ts index 25015d069f44..9edcbb6b973d 100644 --- a/packages/twenty-front/src/utils/index.ts +++ b/packages/twenty-front/src/utils/index.ts @@ -22,31 +22,6 @@ export const formatToHumanReadableDateTime = (date: Date | string) => { }).format(parsedJSDate); }; -export const formatToHumanReadableMonth = (date: Date | string) => { - const parsedJSDate = parseDate(date).toJSDate(); - - return new Intl.DateTimeFormat(undefined, { - month: 'short', - }).format(parsedJSDate); -}; - -export const formatToHumanReadableDay = (date: Date | string) => { - const parsedJSDate = parseDate(date).toJSDate(); - - return new Intl.DateTimeFormat(undefined, { - day: 'numeric', - }).format(parsedJSDate); -}; - -export const formatToHumanReadableTime = (date: Date | string) => { - const parsedJSDate = parseDate(date).toJSDate(); - - return new Intl.DateTimeFormat(undefined, { - hour: 'numeric', - minute: 'numeric', - }).format(parsedJSDate); -}; - export const sanitizeURL = (link: string | null | undefined) => { return link ? link.replace(/(https?:\/\/)|(www\.)/g, '').replace(/\/$/, '')