From fefa37b30043c761c835bd1f8e1a9588ac9682ec Mon Sep 17 00:00:00 2001 From: bosiraphael <71827178+bosiraphael@users.noreply.github.com> Date: Tue, 26 Mar 2024 14:50:32 +0100 Subject: [PATCH] 4488 connect calendar tab to backend (#4624) * create states and hooks * implement fetch more records * add empty state * update types * fix error * add fetchmoreloader and add scroll to container * fix visibility in calendarEventFragment * fix fetchMoreRecords * update TIMELINE_CALENDAR_EVENTS_DEFAULT_PAGE_SIZE * add test * modify empty state subtitle * replace entity by activityTargetableObject * create useCustomResolver hook * refactor * refactoring * use generic component * rename FetchMoreLoader * remove deprecated states and hooks * fix typing * update typing * update error message * renaming * improve typing * fix bug on contact creation from same company --- .../calendar/components/Calendar.tsx | 84 +++++++++--- .../components/CalendarCurrentEventCursor.tsx | 4 +- .../components/CalendarDayCardContent.tsx | 4 +- .../calendar/components/CalendarEventRow.tsx | 4 +- .../activities/calendar/constants/Calendar.ts | 1 + .../calendar/contexts/CalendarContext.ts | 10 +- .../calendar/hooks/useCalendarEvents.ts | 13 +- .../fragments/calendarEventFragment.ts | 1 + ...getTimelineCalendarEventsFromCompanyId.ts} | 0 ... getTimelineCalendarEventsFromPersonId.ts} | 0 .../CustomResolverFetchMoreLoader.tsx} | 6 +- .../emails/components/EmailThreads.tsx | 118 +++-------------- .../__tests__/useEmailThreadStates.test.ts | 37 ------ .../hooks/internal/useEmailThreadStates.ts | 25 ---- .../components/RightDrawerEmailThread.tsx | 4 +- .../states/emailThreadsPageComponentState.ts | 12 -- .../activities/hooks/useCustomResolver.ts | 121 ++++++++++++++++++ .../components/ShowPageRightContainer.tsx | 8 +- .../accounts/SettingsAccountsCalendars.tsx | 20 ++- .../create-company-and-contact.service.ts | 13 +- 20 files changed, 263 insertions(+), 222 deletions(-) create mode 100644 packages/twenty-front/src/modules/activities/calendar/constants/Calendar.ts rename packages/twenty-front/src/modules/activities/calendar/queries/{getCalendarEventsFromCompanyId.ts => getTimelineCalendarEventsFromCompanyId.ts} (100%) rename packages/twenty-front/src/modules/activities/calendar/queries/{getCalendarEventsFromPersonId.ts => getTimelineCalendarEventsFromPersonId.ts} (100%) rename packages/twenty-front/src/modules/activities/{emails/components/EmailThreadFetchMoreLoader.tsx => components/CustomResolverFetchMoreLoader.tsx} (84%) delete mode 100644 packages/twenty-front/src/modules/activities/emails/hooks/internal/__tests__/useEmailThreadStates.test.ts delete mode 100644 packages/twenty-front/src/modules/activities/emails/hooks/internal/useEmailThreadStates.ts delete mode 100644 packages/twenty-front/src/modules/activities/emails/states/emailThreadsPageComponentState.ts create mode 100644 packages/twenty-front/src/modules/activities/hooks/useCustomResolver.ts 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 59981e9479a4..11a41c8c2505 100644 --- a/packages/twenty-front/src/modules/activities/calendar/components/Calendar.tsx +++ b/packages/twenty-front/src/modules/activities/calendar/components/Calendar.tsx @@ -2,13 +2,25 @@ import styled from '@emotion/styled'; import { format, getYear } from 'date-fns'; import { CalendarMonthCard } from '@/activities/calendar/components/CalendarMonthCard'; +import { TIMELINE_CALENDAR_EVENTS_DEFAULT_PAGE_SIZE } from '@/activities/calendar/constants/Calendar'; import { CalendarContext } from '@/activities/calendar/contexts/CalendarContext'; import { useCalendarEvents } from '@/activities/calendar/hooks/useCalendarEvents'; -import { CalendarEvent } from '@/activities/calendar/types/CalendarEvent'; +import { getTimelineCalendarEventsFromCompanyId } from '@/activities/calendar/queries/getTimelineCalendarEventsFromCompanyId'; +import { getTimelineCalendarEventsFromPersonId } from '@/activities/calendar/queries/getTimelineCalendarEventsFromPersonId'; +import { FetchMoreLoader } from '@/activities/components/CustomResolverFetchMoreLoader'; +import { useCustomResolver } from '@/activities/hooks/useCustomResolver'; +import { ActivityTargetableObject } from '@/activities/types/ActivityTargetableEntity'; import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular'; -import { useFindManyRecords } from '@/object-record/hooks/useFindManyRecords'; import { H3Title } from '@/ui/display/typography/components/H3Title'; +import AnimatedPlaceholder from '@/ui/layout/animated-placeholder/components/AnimatedPlaceholder'; +import { + AnimatedPlaceholderEmptyContainer, + AnimatedPlaceholderEmptySubTitle, + AnimatedPlaceholderEmptyTextContainer, + AnimatedPlaceholderEmptyTitle, +} from '@/ui/layout/animated-placeholder/components/EmptyPlaceholderStyled'; import { Section } from '@/ui/layout/section/components/Section'; +import { TimelineCalendarEventsWithTotal } from '~/generated/graphql'; const StyledContainer = styled.div` box-sizing: border-box; @@ -17,18 +29,39 @@ const StyledContainer = styled.div` gap: ${({ theme }) => theme.spacing(8)}; padding: ${({ theme }) => theme.spacing(6)}; width: 100%; + overflow: scroll; `; const StyledYear = styled.span` color: ${({ theme }) => theme.font.color.light}; `; -export const Calendar = () => { - const { records: calendarEvents } = useFindManyRecords({ - objectNameSingular: CoreObjectNameSingular.CalendarEvent, - orderBy: { startsAt: 'DescNullsLast', endsAt: 'DescNullsLast' }, - useRecordsWithoutConnection: true, - }); +export const Calendar = ({ + targetableObject, +}: { + targetableObject: ActivityTargetableObject; +}) => { + const [query, queryName] = + targetableObject.targetObjectNameSingular === CoreObjectNameSingular.Person + ? [ + getTimelineCalendarEventsFromPersonId, + 'getTimelineCalendarEventsFromPersonId', + ] + : [ + getTimelineCalendarEventsFromCompanyId, + 'getTimelineCalendarEventsFromCompanyId', + ]; + + const { data, firstQueryLoading, isFetchingMore, fetchMoreRecords } = + useCustomResolver( + query, + queryName, + 'timelineCalendarEvents', + targetableObject, + TIMELINE_CALENDAR_EVENTS_DEFAULT_PAGE_SIZE, + ); + + const { timelineCalendarEvents } = data?.[queryName] ?? {}; const { calendarEventsByDayTime, @@ -38,13 +71,30 @@ export const Calendar = () => { monthTimes, monthTimesByYear, updateCurrentCalendarEvent, - } = useCalendarEvents( - calendarEvents.map((calendarEvent) => ({ - ...calendarEvent, - // TODO: retrieve CalendarChannel visibility from backend - visibility: 'SHARE_EVERYTHING', - })), - ); + } = useCalendarEvents(timelineCalendarEvents || []); + + if (firstQueryLoading) { + // TODO: implement loader + return; + } + + if (!firstQueryLoading && !timelineCalendarEvents?.length) { + // TODO: change animated placeholder + return ( + + + + + No Events + + + No events have been scheduled with this{' '} + {targetableObject.targetObjectNameSingular} yet. + + + + ); + } return ( { ); })} + ); diff --git a/packages/twenty-front/src/modules/activities/calendar/components/CalendarCurrentEventCursor.tsx b/packages/twenty-front/src/modules/activities/calendar/components/CalendarCurrentEventCursor.tsx index 009d5226a4c7..02d11fa87e7a 100644 --- a/packages/twenty-front/src/modules/activities/calendar/components/CalendarCurrentEventCursor.tsx +++ b/packages/twenty-front/src/modules/activities/calendar/components/CalendarCurrentEventCursor.tsx @@ -10,14 +10,14 @@ import { import { AnimatePresence, motion } from 'framer-motion'; import { CalendarContext } from '@/activities/calendar/contexts/CalendarContext'; -import { CalendarEvent } from '@/activities/calendar/types/CalendarEvent'; import { getCalendarEventEndDate } from '@/activities/calendar/utils/getCalendarEventEndDate'; import { getCalendarEventStartDate } from '@/activities/calendar/utils/getCalendarEventStartDate'; import { hasCalendarEventEnded } from '@/activities/calendar/utils/hasCalendarEventEnded'; import { hasCalendarEventStarted } from '@/activities/calendar/utils/hasCalendarEventStarted'; +import { TimelineCalendarEvent } from '~/generated-metadata/graphql'; type CalendarCurrentEventCursorProps = { - calendarEvent: CalendarEvent; + calendarEvent: TimelineCalendarEvent; }; const StyledCurrentEventCursor = styled(motion.div)` diff --git a/packages/twenty-front/src/modules/activities/calendar/components/CalendarDayCardContent.tsx b/packages/twenty-front/src/modules/activities/calendar/components/CalendarDayCardContent.tsx index 9cd72f47b38d..c4ff26571109 100644 --- a/packages/twenty-front/src/modules/activities/calendar/components/CalendarDayCardContent.tsx +++ b/packages/twenty-front/src/modules/activities/calendar/components/CalendarDayCardContent.tsx @@ -3,12 +3,12 @@ import styled from '@emotion/styled'; import { differenceInSeconds, endOfDay, format } from 'date-fns'; import { CalendarEventRow } from '@/activities/calendar/components/CalendarEventRow'; -import { CalendarEvent } from '@/activities/calendar/types/CalendarEvent'; import { getCalendarEventStartDate } from '@/activities/calendar/utils/getCalendarEventStartDate'; import { CardContent } from '@/ui/layout/card/components/CardContent'; +import { TimelineCalendarEvent } from '~/generated-metadata/graphql'; type CalendarDayCardContentProps = { - calendarEvents: CalendarEvent[]; + calendarEvents: TimelineCalendarEvent[]; divider?: boolean; }; diff --git a/packages/twenty-front/src/modules/activities/calendar/components/CalendarEventRow.tsx b/packages/twenty-front/src/modules/activities/calendar/components/CalendarEventRow.tsx index 8ea22bc6dbc7..c4d279f3a473 100644 --- a/packages/twenty-front/src/modules/activities/calendar/components/CalendarEventRow.tsx +++ b/packages/twenty-front/src/modules/activities/calendar/components/CalendarEventRow.tsx @@ -7,7 +7,6 @@ import { useRecoilValue } from 'recoil'; import { CalendarCurrentEventCursor } from '@/activities/calendar/components/CalendarCurrentEventCursor'; import { CalendarContext } from '@/activities/calendar/contexts/CalendarContext'; import { useOpenCalendarEventRightDrawer } from '@/activities/calendar/right-drawer/hooks/useOpenCalendarEventRightDrawer'; -import { CalendarEvent } from '@/activities/calendar/types/CalendarEvent'; import { getCalendarEventEndDate } from '@/activities/calendar/utils/getCalendarEventEndDate'; import { getCalendarEventStartDate } from '@/activities/calendar/utils/getCalendarEventStartDate'; import { hasCalendarEventEnded } from '@/activities/calendar/utils/hasCalendarEventEnded'; @@ -17,10 +16,11 @@ import { Card } from '@/ui/layout/card/components/Card'; import { CardContent } from '@/ui/layout/card/components/CardContent'; import { Avatar } from '@/users/components/Avatar'; import { AvatarGroup } from '@/users/components/AvatarGroup'; +import { TimelineCalendarEvent } from '~/generated-metadata/graphql'; import { isDefined } from '~/utils/isDefined'; type CalendarEventRowProps = { - calendarEvent: CalendarEvent; + calendarEvent: TimelineCalendarEvent; className?: string; }; diff --git a/packages/twenty-front/src/modules/activities/calendar/constants/Calendar.ts b/packages/twenty-front/src/modules/activities/calendar/constants/Calendar.ts new file mode 100644 index 000000000000..e958b80883d1 --- /dev/null +++ b/packages/twenty-front/src/modules/activities/calendar/constants/Calendar.ts @@ -0,0 +1 @@ +export const TIMELINE_CALENDAR_EVENTS_DEFAULT_PAGE_SIZE = 10; diff --git a/packages/twenty-front/src/modules/activities/calendar/contexts/CalendarContext.ts b/packages/twenty-front/src/modules/activities/calendar/contexts/CalendarContext.ts index 4370ec88bcb6..423b48f822e1 100644 --- a/packages/twenty-front/src/modules/activities/calendar/contexts/CalendarContext.ts +++ b/packages/twenty-front/src/modules/activities/calendar/contexts/CalendarContext.ts @@ -1,14 +1,14 @@ import { createContext } from 'react'; -import { CalendarEvent } from '@/activities/calendar/types/CalendarEvent'; +import { TimelineCalendarEvent } from '~/generated-metadata/graphql'; type CalendarContextValue = { - calendarEventsByDayTime: Record; - currentCalendarEvent?: CalendarEvent; + calendarEventsByDayTime: Record; + currentCalendarEvent?: TimelineCalendarEvent; displayCurrentEventCursor?: boolean; getNextCalendarEvent: ( - calendarEvent: CalendarEvent, - ) => CalendarEvent | undefined; + calendarEvent: TimelineCalendarEvent, + ) => TimelineCalendarEvent | undefined; updateCurrentCalendarEvent: () => void; }; diff --git a/packages/twenty-front/src/modules/activities/calendar/hooks/useCalendarEvents.ts b/packages/twenty-front/src/modules/activities/calendar/hooks/useCalendarEvents.ts index 6552541bfb08..1f10163fbcc1 100644 --- a/packages/twenty-front/src/modules/activities/calendar/hooks/useCalendarEvents.ts +++ b/packages/twenty-front/src/modules/activities/calendar/hooks/useCalendarEvents.ts @@ -8,7 +8,14 @@ import { groupArrayItemsBy } from '~/utils/array/groupArrayItemsBy'; import { isDefined } from '~/utils/isDefined'; import { sortDesc } from '~/utils/sort'; -export const useCalendarEvents = (calendarEvents: CalendarEvent[]) => { +type CalendarEventGeneric = Omit< + CalendarEvent, + 'attendees' | 'externalCreatedAt' +>; + +export const useCalendarEvents = ( + calendarEvents: T[], +) => { const calendarEventsByDayTime = groupArrayItemsBy( calendarEvents, (calendarEvent) => @@ -29,14 +36,14 @@ export const useCalendarEvents = (calendarEvents: CalendarEvent[]) => { const monthTimesByYear = groupArrayItemsBy(sortedMonthTimes, getYear); - const getPreviousCalendarEvent = (calendarEvent: CalendarEvent) => { + const getPreviousCalendarEvent = (calendarEvent: T) => { const calendarEventIndex = calendarEvents.indexOf(calendarEvent); return calendarEventIndex < calendarEvents.length - 1 ? calendarEvents[calendarEventIndex + 1] : undefined; }; - const getNextCalendarEvent = (calendarEvent: CalendarEvent) => { + const getNextCalendarEvent = (calendarEvent: T) => { const calendarEventIndex = calendarEvents.indexOf(calendarEvent); return calendarEventIndex > 0 ? calendarEvents[calendarEventIndex - 1] diff --git a/packages/twenty-front/src/modules/activities/calendar/queries/fragments/calendarEventFragment.ts b/packages/twenty-front/src/modules/activities/calendar/queries/fragments/calendarEventFragment.ts index 5188d3d77b94..6a6e0f54a2f1 100644 --- a/packages/twenty-front/src/modules/activities/calendar/queries/fragments/calendarEventFragment.ts +++ b/packages/twenty-front/src/modules/activities/calendar/queries/fragments/calendarEventFragment.ts @@ -11,6 +11,7 @@ export const calendarEventFragment = gql` startsAt endsAt isFullDay + visibility attendees { ...AttendeeFragment } diff --git a/packages/twenty-front/src/modules/activities/calendar/queries/getCalendarEventsFromCompanyId.ts b/packages/twenty-front/src/modules/activities/calendar/queries/getTimelineCalendarEventsFromCompanyId.ts similarity index 100% rename from packages/twenty-front/src/modules/activities/calendar/queries/getCalendarEventsFromCompanyId.ts rename to packages/twenty-front/src/modules/activities/calendar/queries/getTimelineCalendarEventsFromCompanyId.ts diff --git a/packages/twenty-front/src/modules/activities/calendar/queries/getCalendarEventsFromPersonId.ts b/packages/twenty-front/src/modules/activities/calendar/queries/getTimelineCalendarEventsFromPersonId.ts similarity index 100% rename from packages/twenty-front/src/modules/activities/calendar/queries/getCalendarEventsFromPersonId.ts rename to packages/twenty-front/src/modules/activities/calendar/queries/getTimelineCalendarEventsFromPersonId.ts diff --git a/packages/twenty-front/src/modules/activities/emails/components/EmailThreadFetchMoreLoader.tsx b/packages/twenty-front/src/modules/activities/components/CustomResolverFetchMoreLoader.tsx similarity index 84% rename from packages/twenty-front/src/modules/activities/emails/components/EmailThreadFetchMoreLoader.tsx rename to packages/twenty-front/src/modules/activities/components/CustomResolverFetchMoreLoader.tsx index c133b5531018..5bc3c1eb1252 100644 --- a/packages/twenty-front/src/modules/activities/emails/components/EmailThreadFetchMoreLoader.tsx +++ b/packages/twenty-front/src/modules/activities/components/CustomResolverFetchMoreLoader.tsx @@ -3,7 +3,7 @@ import styled from '@emotion/styled'; import { GRAY_SCALE } from '@/ui/theme/constants/GrayScale'; -type EmailThreadFetchMoreLoaderProps = { +type FetchMoreLoaderProps = { loading: boolean; onLastRowVisible: (...args: any[]) => any; }; @@ -18,10 +18,10 @@ const StyledText = styled.div` padding-left: ${({ theme }) => theme.spacing(2)}; `; -export const EmailThreadFetchMoreLoader = ({ +export const FetchMoreLoader = ({ loading, onLastRowVisible, -}: EmailThreadFetchMoreLoaderProps) => { +}: FetchMoreLoaderProps) => { 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 91cc33ece60e..8577d7f1a320 100644 --- a/packages/twenty-front/src/modules/activities/emails/components/EmailThreads.tsx +++ b/packages/twenty-front/src/modules/activities/emails/components/EmailThreads.tsx @@ -1,22 +1,18 @@ -import { useState } from 'react'; -import { useQuery } from '@apollo/client'; import styled from '@emotion/styled'; -import { useRecoilState } from 'recoil'; +import { FetchMoreLoader } from '@/activities/components/CustomResolverFetchMoreLoader'; import { EmailLoader } from '@/activities/emails/components/EmailLoader'; -import { EmailThreadFetchMoreLoader } from '@/activities/emails/components/EmailThreadFetchMoreLoader'; import { EmailThreadPreview } from '@/activities/emails/components/EmailThreadPreview'; import { TIMELINE_THREADS_DEFAULT_PAGE_SIZE } from '@/activities/emails/constants/Messaging'; -import { useEmailThreadStates } from '@/activities/emails/hooks/internal/useEmailThreadStates'; import { getTimelineThreadsFromCompanyId } from '@/activities/emails/queries/getTimelineThreadsFromCompanyId'; import { getTimelineThreadsFromPersonId } from '@/activities/emails/queries/getTimelineThreadsFromPersonId'; +import { useCustomResolver } from '@/activities/hooks/useCustomResolver'; import { ActivityTargetableObject } from '@/activities/types/ActivityTargetableEntity'; import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular'; import { H1Title, H1TitleFontColor, } from '@/ui/display/typography/components/H1Title'; -import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar'; import AnimatedPlaceholder from '@/ui/layout/animated-placeholder/components/AnimatedPlaceholder'; import { AnimatedPlaceholderEmptyContainer, @@ -26,12 +22,7 @@ import { } from '@/ui/layout/animated-placeholder/components/EmptyPlaceholderStyled'; import { Card } from '@/ui/layout/card/components/Card'; import { Section } from '@/ui/layout/section/components/Section'; -import { getScopeIdFromComponentId } from '@/ui/utilities/recoil-scope/utils/getScopeIdFromComponentId'; -import { - GetTimelineThreadsFromPersonIdQueryVariables, - TimelineThread, - TimelineThreadsWithTotal, -} from '~/generated/graphql'; +import { TimelineThread, TimelineThreadsWithTotal } from '~/generated/graphql'; const StyledContainer = styled.div` display: flex; @@ -52,98 +43,25 @@ const StyledEmailCount = styled.span` `; export const EmailThreads = ({ - entity, + targetableObject, }: { - entity: ActivityTargetableObject; + targetableObject: ActivityTargetableObject; }) => { - const { enqueueSnackBar } = useSnackBar(); - - const { emailThreadsPageState } = useEmailThreadStates({ - emailThreadScopeId: getScopeIdFromComponentId(entity.id), - }); - - const [emailThreadsPage, setEmailThreadsPage] = useRecoilState( - emailThreadsPageState, - ); - - const [isFetchingMoreEmails, setIsFetchingMoreEmails] = useState(false); - - const [threadQuery, queryName] = - entity.targetObjectNameSingular === CoreObjectNameSingular.Person + const [query, queryName] = + targetableObject.targetObjectNameSingular === CoreObjectNameSingular.Person ? [getTimelineThreadsFromPersonId, 'getTimelineThreadsFromPersonId'] : [getTimelineThreadsFromCompanyId, 'getTimelineThreadsFromCompanyId']; - const threadQueryVariables = { - ...(entity.targetObjectNameSingular === CoreObjectNameSingular.Person - ? { personId: entity.id } - : { companyId: entity.id }), - page: 1, - pageSize: TIMELINE_THREADS_DEFAULT_PAGE_SIZE, - } as GetTimelineThreadsFromPersonIdQueryVariables; - - const { - data, - loading: firstQueryLoading, - fetchMore, - } = useQuery(threadQuery, { - variables: threadQueryVariables, - onError: (error) => { - enqueueSnackBar(error.message || 'Error loading email threads', { - variant: 'error', - }); - }, - }); - - const fetchMoreRecords = async () => { - if ( - emailThreadsPage.hasNextPage && - !isFetchingMoreEmails && - !firstQueryLoading - ) { - setIsFetchingMoreEmails(true); - - await fetchMore({ - variables: { - ...threadQueryVariables, - page: emailThreadsPage.pageNumber + 1, - }, - updateQuery: (prev, { fetchMoreResult }) => { - if (!fetchMoreResult?.[queryName]?.timelineThreads?.length) { - setEmailThreadsPage((emailThreadsPage) => ({ - ...emailThreadsPage, - hasNextPage: false, - })); - return { - [queryName]: { - ...prev?.[queryName], - timelineThreads: [ - ...(prev?.[queryName]?.timelineThreads ?? []), - ], - }, - }; - } - - return { - [queryName]: { - ...prev?.[queryName], - timelineThreads: [ - ...(prev?.[queryName]?.timelineThreads ?? []), - ...(fetchMoreResult?.[queryName]?.timelineThreads ?? []), - ], - }, - }; - }, - }); - setEmailThreadsPage((emailThreadsPage) => ({ - ...emailThreadsPage, - pageNumber: emailThreadsPage.pageNumber + 1, - })); - setIsFetchingMoreEmails(false); - } - }; + const { data, firstQueryLoading, isFetchingMore, fetchMoreRecords } = + useCustomResolver( + query, + queryName, + 'timelineThreads', + targetableObject, + TIMELINE_THREADS_DEFAULT_PAGE_SIZE, + ); - const { totalNumberOfThreads, timelineThreads }: TimelineThreadsWithTotal = - data?.[queryName] ?? []; + const { totalNumberOfThreads, timelineThreads } = data?.[queryName] ?? {}; if (firstQueryLoading) { return ; @@ -187,8 +105,8 @@ export const EmailThreads = ({ ))} )} - diff --git a/packages/twenty-front/src/modules/activities/emails/hooks/internal/__tests__/useEmailThreadStates.test.ts b/packages/twenty-front/src/modules/activities/emails/hooks/internal/__tests__/useEmailThreadStates.test.ts deleted file mode 100644 index 0f77de3a96c1..000000000000 --- a/packages/twenty-front/src/modules/activities/emails/hooks/internal/__tests__/useEmailThreadStates.test.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { renderHook } from '@testing-library/react'; - -import { useEmailThreadStates } from '@/activities/emails/hooks/internal/useEmailThreadStates'; - -const mockScopeId = 'mockScopeId'; -const mockGetEmailThreadsPageState = jest.fn(); - -jest.mock( - '@/ui/utilities/recoil-scope/scopes-internal/hooks/useAvailableScopeId', - () => ({ - useAvailableScopeIdOrThrow: () => mockScopeId, - }), -); - -jest.mock( - '@/ui/utilities/state/component-state/utils/extractComponentState', - () => ({ - extractComponentState: () => mockGetEmailThreadsPageState, - }), -); - -describe('useEmailThreadStates hook', () => { - beforeEach(() => { - jest.clearAllMocks(); - }); - - it('returns the correct scopeId and getEmailThreadsPageState', () => { - const { result } = renderHook(() => - useEmailThreadStates({ emailThreadScopeId: 'mockEmailThreadScopeId' }), - ); - - expect(result.current.scopeId).toBe(mockScopeId); - expect(result.current.emailThreadsPageState).toBe( - mockGetEmailThreadsPageState, - ); - }); -}); diff --git a/packages/twenty-front/src/modules/activities/emails/hooks/internal/useEmailThreadStates.ts b/packages/twenty-front/src/modules/activities/emails/hooks/internal/useEmailThreadStates.ts deleted file mode 100644 index 472377dfecdf..000000000000 --- a/packages/twenty-front/src/modules/activities/emails/hooks/internal/useEmailThreadStates.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { emailThreadsPageComponentState } from '@/activities/emails/states/emailThreadsPageComponentState'; -import { TabListScopeInternalContext } from '@/ui/layout/tab/scopes/scope-internal-context/TabListScopeInternalContext'; -import { useAvailableScopeIdOrThrow } from '@/ui/utilities/recoil-scope/scopes-internal/hooks/useAvailableScopeId'; -import { extractComponentState } from '@/ui/utilities/state/component-state/utils/extractComponentState'; - -type useEmailThreadStatesProps = { - emailThreadScopeId?: string; -}; - -export const useEmailThreadStates = ({ - emailThreadScopeId, -}: useEmailThreadStatesProps) => { - const scopeId = useAvailableScopeIdOrThrow( - TabListScopeInternalContext, - emailThreadScopeId, - ); - - return { - scopeId, - emailThreadsPageState: extractComponentState( - emailThreadsPageComponentState, - scopeId, - ), - }; -}; 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 e353f1dd4a9b..e80b0a4e9104 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,8 +1,8 @@ import styled from '@emotion/styled'; import { useRecoilCallback } from 'recoil'; +import { FetchMoreLoader } from '@/activities/components/CustomResolverFetchMoreLoader'; import { EmailLoader } from '@/activities/emails/components/EmailLoader'; -import { EmailThreadFetchMoreLoader } from '@/activities/emails/components/EmailThreadFetchMoreLoader'; import { EmailThreadHeader } from '@/activities/emails/components/EmailThreadHeader'; import { EmailThreadMessage } from '@/activities/emails/components/EmailThreadMessage'; import { useRightDrawerEmailThread } from '@/activities/emails/right-drawer/hooks/useRightDrawerEmailThread'; @@ -62,7 +62,7 @@ export const RightDrawerEmailThread = () => { sentAt={message.receivedAt} /> ))} - diff --git a/packages/twenty-front/src/modules/activities/emails/states/emailThreadsPageComponentState.ts b/packages/twenty-front/src/modules/activities/emails/states/emailThreadsPageComponentState.ts deleted file mode 100644 index a1d52b61d5de..000000000000 --- a/packages/twenty-front/src/modules/activities/emails/states/emailThreadsPageComponentState.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { createComponentState } from '@/ui/utilities/state/component-state/utils/createComponentState'; - -export type EmailThreadsPageType = { - pageNumber: number; - hasNextPage: boolean; -}; - -export const emailThreadsPageComponentState = - createComponentState({ - key: 'emailThreadsPageComponentState', - defaultValue: { pageNumber: 1, hasNextPage: true }, - }); diff --git a/packages/twenty-front/src/modules/activities/hooks/useCustomResolver.ts b/packages/twenty-front/src/modules/activities/hooks/useCustomResolver.ts new file mode 100644 index 000000000000..ed32eb3f0d0f --- /dev/null +++ b/packages/twenty-front/src/modules/activities/hooks/useCustomResolver.ts @@ -0,0 +1,121 @@ +import { useState } from 'react'; +import { + DocumentNode, + OperationVariables, + TypedDocumentNode, + useQuery, +} from '@apollo/client'; + +import { ActivityTargetableObject } from '@/activities/types/ActivityTargetableEntity'; +import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular'; +import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar'; + +type CustomResolverQueryResult< + T extends { + [key: string]: any; + }, +> = { + [queryName: string]: T; +}; + +export const useCustomResolver = < + T extends { + [key: string]: any; + }, +>( + query: + | DocumentNode + | TypedDocumentNode, OperationVariables>, + queryName: string, + objectName: string, + activityTargetableObject: ActivityTargetableObject, + pageSize: number, +): { + data: CustomResolverQueryResult | undefined; + firstQueryLoading: boolean; + isFetchingMore: boolean; + fetchMoreRecords: () => Promise; +} => { + const { enqueueSnackBar } = useSnackBar(); + + const [page, setPage] = useState({ + pageNumber: 1, + hasNextPage: true, + }); + + const [isFetchingMore, setIsFetchingMore] = useState(false); + + const queryVariables = { + ...(activityTargetableObject.targetObjectNameSingular === + CoreObjectNameSingular.Person + ? { personId: activityTargetableObject.id } + : { companyId: activityTargetableObject.id }), + page: 1, + pageSize, + }; + + const { + data, + loading: firstQueryLoading, + fetchMore, + } = useQuery>(query, { + variables: queryVariables, + onError: (error) => { + enqueueSnackBar(error.message || `Error loading ${objectName}`, { + variant: 'error', + }); + }, + }); + + const fetchMoreRecords = async () => { + if (page.hasNextPage && !isFetchingMore && !firstQueryLoading) { + setIsFetchingMore(true); + + await fetchMore({ + variables: { + ...queryVariables, + page: page.pageNumber + 1, + }, + updateQuery: (prev, { fetchMoreResult }) => { + if (!fetchMoreResult?.[queryName]?.[objectName]?.length) { + setPage((page) => ({ + ...page, + hasNextPage: false, + })); + + return { + [queryName]: { + ...prev?.[queryName], + [objectName]: [...(prev?.[queryName]?.[objectName] ?? [])], + }, + }; + } + + return { + [queryName]: { + ...prev?.[queryName], + [objectName]: [ + ...(prev?.[queryName]?.[objectName] ?? []), + ...(fetchMoreResult?.[queryName]?.[objectName] ?? []), + ], + }, + }; + }, + }); + + setPage((page) => ({ + ...page, + pageNumber: page.pageNumber + 1, + })); + + setIsFetchingMore(false); + } + }; + + return { + data, + firstQueryLoading, + isFetchingMore, + fetchMoreRecords, + }; +}; diff --git a/packages/twenty-front/src/modules/ui/layout/show-page/components/ShowPageRightContainer.tsx b/packages/twenty-front/src/modules/ui/layout/show-page/components/ShowPageRightContainer.tsx index 001904181cf3..ab1c70b8479a 100644 --- a/packages/twenty-front/src/modules/ui/layout/show-page/components/ShowPageRightContainer.tsx +++ b/packages/twenty-front/src/modules/ui/layout/show-page/components/ShowPageRightContainer.tsx @@ -138,8 +138,12 @@ export const ShowPageRightContainer = ({ {activeTabId === 'files' && ( )} - {activeTabId === 'emails' && } - {activeTabId === 'calendar' && } + {activeTabId === 'emails' && ( + + )} + {activeTabId === 'calendar' && ( + + )} {activeTabId === 'logs' && } ); diff --git a/packages/twenty-front/src/pages/settings/accounts/SettingsAccountsCalendars.tsx b/packages/twenty-front/src/pages/settings/accounts/SettingsAccountsCalendars.tsx index 8fb0cb66e118..227ad97de784 100644 --- a/packages/twenty-front/src/pages/settings/accounts/SettingsAccountsCalendars.tsx +++ b/packages/twenty-front/src/pages/settings/accounts/SettingsAccountsCalendars.tsx @@ -4,7 +4,6 @@ import { useRecoilValue } from 'recoil'; import { ConnectedAccount } from '@/accounts/types/ConnectedAccount'; import { CalendarMonthCard } from '@/activities/calendar/components/CalendarMonthCard'; import { CalendarContext } from '@/activities/calendar/contexts/CalendarContext'; -import { CalendarEvent } from '@/activities/calendar/types/CalendarEvent'; import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState'; import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular'; import { useFindManyRecords } from '@/object-record/hooks/useFindManyRecords'; @@ -18,6 +17,10 @@ import { H2Title } from '@/ui/display/typography/components/H2Title'; import { SubMenuTopBarContainer } from '@/ui/layout/page/SubMenuTopBarContainer'; import { Section } from '@/ui/layout/section/components/Section'; import { Breadcrumb } from '@/ui/navigation/bread-crumb/components/Breadcrumb'; +import { + TimelineCalendarEvent, + TimelineCalendarEventVisibility, +} from '~/generated-metadata/graphql'; import { mockedConnectedAccounts } from '~/testing/mock-data/accounts'; export const SettingsAccountsCalendars = () => { @@ -37,25 +40,32 @@ export const SettingsAccountsCalendars = () => { endOfDay(exampleStartDate), ]); const exampleDayTime = startOfDay(exampleStartDate).getTime(); - const exampleCalendarEvent: CalendarEvent = { + const exampleCalendarEvent: TimelineCalendarEvent = { id: '', attendees: [ { + firstName: currentWorkspaceMember?.name.firstName || '', + lastName: currentWorkspaceMember?.name.lastName || '', displayName: currentWorkspaceMember ? [ currentWorkspaceMember.name.firstName, currentWorkspaceMember.name.lastName, ].join(' ') : '', - workspaceMemberId: currentWorkspaceMember?.id ?? '', + avatarUrl: currentWorkspaceMember?.avatarUrl || '', + handle: '', }, ], endsAt: exampleEndDate.toISOString(), - externalCreatedAt: new Date().toISOString(), isFullDay: false, startsAt: exampleStartDate.toISOString(), + conferenceSolution: '', + conferenceUri: '', + description: '', + isCanceled: false, + location: '', title: 'Onboarding call', - visibility: 'SHARE_EVERYTHING', + visibility: TimelineCalendarEventVisibility.ShareEverything, }; return ( diff --git a/packages/twenty-server/src/modules/connected-account/auto-companies-and-contacts-creation/services/create-company-and-contact.service.ts b/packages/twenty-server/src/modules/connected-account/auto-companies-and-contacts-creation/services/create-company-and-contact.service.ts index ca8d53fc4317..f805c7d7fb53 100644 --- a/packages/twenty-server/src/modules/connected-account/auto-companies-and-contacts-creation/services/create-company-and-contact.service.ts +++ b/packages/twenty-server/src/modules/connected-account/auto-companies-and-contacts-creation/services/create-company-and-contact.service.ts @@ -60,13 +60,12 @@ export class CreateCompanyAndContactService { transactionManager, ); - const contactsToCreateFromOtherCompanies = contactsToCreate; - - filterOutContactsFromCompanyOrWorkspace( - contactsToCreate, - connectedAccountHandle, - workspaceMembers, - ); + const contactsToCreateFromOtherCompanies = + filterOutContactsFromCompanyOrWorkspace( + contactsToCreate, + connectedAccountHandle, + workspaceMembers, + ); const { uniqueContacts, uniqueHandles } = getUniqueContactsAndHandles( contactsToCreateFromOtherCompanies,