Skip to content

Commit 6b1548e

Browse files
authored
1 parent cff8561 commit 6b1548e

File tree

11 files changed

+130
-56
lines changed

11 files changed

+130
-56
lines changed

packages/twenty-front/src/modules/activities/calendar/components/Calendar.tsx

+7-3
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { useCalendarEvents } from '@/activities/calendar/hooks/useCalendarEvents
99
import { getTimelineCalendarEventsFromCompanyId } from '@/activities/calendar/queries/getTimelineCalendarEventsFromCompanyId';
1010
import { getTimelineCalendarEventsFromPersonId } from '@/activities/calendar/queries/getTimelineCalendarEventsFromPersonId';
1111
import { CustomResolverFetchMoreLoader } from '@/activities/components/CustomResolverFetchMoreLoader';
12+
import { SkeletonLoader } from '@/activities/components/SkeletonLoader';
1213
import { useCustomResolver } from '@/activities/hooks/useCustomResolver';
1314
import { ActivityTargetableObject } from '@/activities/types/ActivityTargetableEntity';
1415
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
@@ -18,6 +19,7 @@ import {
1819
AnimatedPlaceholderEmptySubTitle,
1920
AnimatedPlaceholderEmptyTextContainer,
2021
AnimatedPlaceholderEmptyTitle,
22+
EMPTY_PLACEHOLDER_TRANSITION_PROPS,
2123
} from '@/ui/layout/animated-placeholder/components/EmptyPlaceholderStyled';
2224
import { Section } from '@/ui/layout/section/components/Section';
2325
import { TimelineCalendarEventsWithTotal } from '~/generated/graphql';
@@ -74,14 +76,16 @@ export const Calendar = ({
7476
} = useCalendarEvents(timelineCalendarEvents || []);
7577

7678
if (firstQueryLoading) {
77-
// TODO: implement loader
78-
return;
79+
return <SkeletonLoader />;
7980
}
8081

8182
if (!firstQueryLoading && !timelineCalendarEvents?.length) {
8283
// TODO: change animated placeholder
8384
return (
84-
<AnimatedPlaceholderEmptyContainer>
85+
<AnimatedPlaceholderEmptyContainer
86+
// eslint-disable-next-line react/jsx-props-no-spreading
87+
{...EMPTY_PLACEHOLDER_TRANSITION_PROPS}
88+
>
8589
<AnimatedPlaceholder type="noMatchRecord" />
8690
<AnimatedPlaceholderEmptyTextContainer>
8791
<AnimatedPlaceholderEmptyTitle>
+19-18
Original file line numberDiff line numberDiff line change
@@ -18,31 +18,31 @@ const StyledSkeletonSubSection = styled.div`
1818
gap: ${({ theme }) => theme.spacing(4)};
1919
`;
2020

21-
const StyledSkeletonColumn = styled.div`
21+
const StyledSkeletonSubSectionContent = styled.div`
2222
display: flex;
2323
flex-direction: column;
2424
gap: ${({ theme }) => theme.spacing(3)};
2525
justify-content: center;
2626
`;
2727

28-
const StyledSkeletonLoader = ({
29-
isSecondColumn,
30-
}: {
31-
isSecondColumn: boolean;
32-
}) => {
28+
const SkeletonColumnLoader = ({ height }: { height: number }) => {
3329
const theme = useTheme();
3430
return (
3531
<SkeletonTheme
3632
baseColor={theme.background.tertiary}
3733
highlightColor={theme.background.transparent.lighter}
3834
borderRadius={80}
3935
>
40-
<Skeleton width={24} height={isSecondColumn ? 120 : 84} />
36+
<Skeleton width={24} height={height} />
4137
</SkeletonTheme>
4238
);
4339
};
4440

45-
export const TimelineSkeletonLoader = () => {
41+
export const SkeletonLoader = ({
42+
withSubSections = false,
43+
}: {
44+
withSubSections?: boolean;
45+
}) => {
4646
const theme = useTheme();
4747
const skeletonItems = Array.from({ length: 3 }).map((_, index) => ({
4848
id: `skeleton-item-${index}`,
@@ -56,16 +56,17 @@ export const TimelineSkeletonLoader = () => {
5656
>
5757
<StyledSkeletonContainer>
5858
<Skeleton width={440} height={16} />
59-
{skeletonItems.map(({ id }, index) => (
60-
<StyledSkeletonSubSection key={id}>
61-
<StyledSkeletonLoader isSecondColumn={index === 1} />
62-
<StyledSkeletonColumn>
63-
<Skeleton width={400} height={24} />
64-
<Skeleton width={400} height={24} />
65-
{index === 1 && <Skeleton width={400} height={24} />}
66-
</StyledSkeletonColumn>
67-
</StyledSkeletonSubSection>
68-
))}
59+
{withSubSections &&
60+
skeletonItems.map(({ id }, index) => (
61+
<StyledSkeletonSubSection key={id}>
62+
<SkeletonColumnLoader height={index === 1 ? 120 : 84} />
63+
<StyledSkeletonSubSectionContent>
64+
<Skeleton width={400} height={24} />
65+
<Skeleton width={400} height={24} />
66+
{index === 1 && <Skeleton width={400} height={24} />}
67+
</StyledSkeletonSubSectionContent>
68+
</StyledSkeletonSubSection>
69+
))}
6970
</StyledSkeletonContainer>
7071
</SkeletonTheme>
7172
);

packages/twenty-front/src/modules/activities/emails/components/EmailThreads.tsx

+7-3
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import styled from '@emotion/styled';
22
import { H1Title, H1TitleFontColor } from 'twenty-ui';
33

44
import { CustomResolverFetchMoreLoader } from '@/activities/components/CustomResolverFetchMoreLoader';
5-
import { EmailLoader } from '@/activities/emails/components/EmailLoader';
5+
import { SkeletonLoader } from '@/activities/components/SkeletonLoader';
66
import { EmailThreadPreview } from '@/activities/emails/components/EmailThreadPreview';
77
import { TIMELINE_THREADS_DEFAULT_PAGE_SIZE } from '@/activities/emails/constants/Messaging';
88
import { getTimelineThreadsFromCompanyId } from '@/activities/emails/queries/getTimelineThreadsFromCompanyId';
@@ -16,6 +16,7 @@ import {
1616
AnimatedPlaceholderEmptySubTitle,
1717
AnimatedPlaceholderEmptyTextContainer,
1818
AnimatedPlaceholderEmptyTitle,
19+
EMPTY_PLACEHOLDER_TRANSITION_PROPS,
1920
} from '@/ui/layout/animated-placeholder/components/EmptyPlaceholderStyled';
2021
import { Card } from '@/ui/layout/card/components/Card';
2122
import { Section } from '@/ui/layout/section/components/Section';
@@ -61,12 +62,15 @@ export const EmailThreads = ({
6162
const { totalNumberOfThreads, timelineThreads } = data?.[queryName] ?? {};
6263

6364
if (firstQueryLoading) {
64-
return <EmailLoader />;
65+
return <SkeletonLoader />;
6566
}
6667

6768
if (!firstQueryLoading && !timelineThreads?.length) {
6869
return (
69-
<AnimatedPlaceholderEmptyContainer>
70+
<AnimatedPlaceholderEmptyContainer
71+
// eslint-disable-next-line react/jsx-props-no-spreading
72+
{...EMPTY_PLACEHOLDER_TRANSITION_PROPS}
73+
>
7074
<AnimatedPlaceholder type="emptyInbox" />
7175
<AnimatedPlaceholderEmptyTextContainer>
7276
<AnimatedPlaceholderEmptyTitle>

packages/twenty-front/src/modules/activities/files/components/Attachments.tsx

+14-4
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
import { ChangeEvent, useRef, useState } from 'react';
22
import styled from '@emotion/styled';
3-
import { isNonEmptyArray } from '@sniptt/guards';
43
import { IconPlus } from 'twenty-ui';
54

5+
import { SkeletonLoader } from '@/activities/components/SkeletonLoader';
66
import { AttachmentList } from '@/activities/files/components/AttachmentList';
77
import { DropZone } from '@/activities/files/components/DropZone';
88
import { useAttachments } from '@/activities/files/hooks/useAttachments';
@@ -15,6 +15,7 @@ import {
1515
AnimatedPlaceholderEmptySubTitle,
1616
AnimatedPlaceholderEmptyTextContainer,
1717
AnimatedPlaceholderEmptyTitle,
18+
EMPTY_PLACEHOLDER_TRANSITION_PROPS,
1819
} from '@/ui/layout/animated-placeholder/components/EmptyPlaceholderStyled';
1920
import { isDefined } from '~/utils/isDefined';
2021

@@ -41,7 +42,7 @@ export const Attachments = ({
4142
targetableObject: ActivityTargetableObject;
4243
}) => {
4344
const inputFileRef = useRef<HTMLInputElement>(null);
44-
const { attachments } = useAttachments(targetableObject);
45+
const { attachments, loading } = useAttachments(targetableObject);
4546
const { uploadAttachmentFile } = useUploadAttachmentFile();
4647

4748
const [isDraggingFile, setIsDraggingFile] = useState(false);
@@ -58,7 +59,13 @@ export const Attachments = ({
5859
await uploadAttachmentFile(file, targetableObject);
5960
};
6061

61-
if (!isNonEmptyArray(attachments)) {
62+
const isAttachmentsEmpty = !attachments || attachments.length === 0;
63+
64+
if (loading && isAttachmentsEmpty) {
65+
return <SkeletonLoader />;
66+
}
67+
68+
if (isAttachmentsEmpty) {
6269
return (
6370
<StyledDropZoneContainer onDragEnter={() => setIsDraggingFile(true)}>
6471
{isDraggingFile ? (
@@ -67,7 +74,10 @@ export const Attachments = ({
6774
onUploadFile={onUploadFile}
6875
/>
6976
) : (
70-
<AnimatedPlaceholderEmptyContainer>
77+
<AnimatedPlaceholderEmptyContainer
78+
// eslint-disable-next-line react/jsx-props-no-spreading
79+
{...EMPTY_PLACEHOLDER_TRANSITION_PROPS}
80+
>
7181
<AnimatedPlaceholder type="noFile" />
7282
<AnimatedPlaceholderEmptyTextContainer>
7383
<AnimatedPlaceholderEmptyTitle>

packages/twenty-front/src/modules/activities/files/hooks/useAttachments.tsx

+2-2
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,12 @@ import { getActivityTargetObjectFieldIdName } from '@/activities/utils/getActivi
44
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
55
import { useFindManyRecords } from '@/object-record/hooks/useFindManyRecords';
66

7-
// do we need to test this?
87
export const useAttachments = (targetableObject: ActivityTargetableObject) => {
98
const targetableObjectFieldIdName = getActivityTargetObjectFieldIdName({
109
nameSingular: targetableObject.targetObjectNameSingular,
1110
});
1211

13-
const { records: attachments } = useFindManyRecords<Attachment>({
12+
const { records: attachments, loading } = useFindManyRecords<Attachment>({
1413
objectNameSingular: CoreObjectNameSingular.Attachment,
1514
filter: {
1615
[targetableObjectFieldIdName]: {
@@ -26,5 +25,6 @@ export const useAttachments = (targetableObject: ActivityTargetableObject) => {
2625

2726
return {
2827
attachments,
28+
loading,
2929
};
3030
};

packages/twenty-front/src/modules/activities/notes/components/Notes.tsx

+15-4
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import styled from '@emotion/styled';
22
import { IconPlus } from 'twenty-ui';
33

4+
import { SkeletonLoader } from '@/activities/components/SkeletonLoader';
45
import { useOpenCreateActivityDrawer } from '@/activities/hooks/useOpenCreateActivityDrawer';
56
import { NoteList } from '@/activities/notes/components/NoteList';
67
import { useNotes } from '@/activities/notes/hooks/useNotes';
@@ -12,6 +13,7 @@ import {
1213
AnimatedPlaceholderEmptySubTitle,
1314
AnimatedPlaceholderEmptyTextContainer,
1415
AnimatedPlaceholderEmptyTitle,
16+
EMPTY_PLACEHOLDER_TRANSITION_PROPS,
1517
} from '@/ui/layout/animated-placeholder/components/EmptyPlaceholderStyled';
1618

1719
const StyledNotesContainer = styled.div`
@@ -27,13 +29,22 @@ export const Notes = ({
2729
}: {
2830
targetableObject: ActivityTargetableObject;
2931
}) => {
30-
const { notes } = useNotes(targetableObject);
32+
const { notes, loading } = useNotes(targetableObject);
3133

3234
const openCreateActivity = useOpenCreateActivityDrawer();
3335

34-
if (notes?.length === 0) {
36+
const isNotesEmpty = !notes || notes.length === 0;
37+
38+
if (loading && isNotesEmpty) {
39+
return <SkeletonLoader />;
40+
}
41+
42+
if (isNotesEmpty) {
3543
return (
36-
<AnimatedPlaceholderEmptyContainer>
44+
<AnimatedPlaceholderEmptyContainer
45+
// eslint-disable-next-line react/jsx-props-no-spreading
46+
{...EMPTY_PLACEHOLDER_TRANSITION_PROPS}
47+
>
3748
<AnimatedPlaceholder type="noNote" />
3849
<AnimatedPlaceholderEmptyTextContainer>
3950
<AnimatedPlaceholderEmptyTitle>
@@ -62,7 +73,7 @@ export const Notes = ({
6273
<StyledNotesContainer>
6374
<NoteList
6475
title="All"
65-
notes={notes ?? []}
76+
notes={notes}
6677
button={
6778
<Button
6879
Icon={IconPlus}

packages/twenty-front/src/modules/activities/tasks/components/TaskGroups.tsx

+20-4
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import styled from '@emotion/styled';
22
import { useRecoilValue } from 'recoil';
33
import { IconPlus } from 'twenty-ui';
44

5+
import { SkeletonLoader } from '@/activities/components/SkeletonLoader';
56
import { useOpenCreateActivityDrawer } from '@/activities/hooks/useOpenCreateActivityDrawer';
67
import { TASKS_TAB_LIST_COMPONENT_ID } from '@/activities/tasks/constants/TasksTabListComponentId';
78
import { useTasks } from '@/activities/tasks/hooks/useTasks';
@@ -13,6 +14,7 @@ import {
1314
AnimatedPlaceholderEmptySubTitle,
1415
AnimatedPlaceholderEmptyTextContainer,
1516
AnimatedPlaceholderEmptyTitle,
17+
EMPTY_PLACEHOLDER_TRANSITION_PROPS,
1618
} from '@/ui/layout/animated-placeholder/components/EmptyPlaceholderStyled';
1719
import { useTabList } from '@/ui/layout/tab/hooks/useTabList';
1820

@@ -40,6 +42,8 @@ export const TaskGroups = ({
4042
upcomingTasks,
4143
unscheduledTasks,
4244
completedTasks,
45+
incompleteTasksLoading,
46+
completeTasksLoading,
4347
} = useTasks({
4448
filterDropdownId: filterDropdownId,
4549
targetableObjects: targetableObjects ?? [],
@@ -50,15 +54,27 @@ export const TaskGroups = ({
5054
const { activeTabIdState } = useTabList(TASKS_TAB_LIST_COMPONENT_ID);
5155
const activeTabId = useRecoilValue(activeTabIdState);
5256

53-
if (
57+
const isLoading =
58+
(activeTabId !== 'done' && incompleteTasksLoading) ||
59+
(activeTabId === 'done' && completeTasksLoading);
60+
61+
const isTasksEmpty =
5462
(activeTabId !== 'done' &&
5563
todayOrPreviousTasks?.length === 0 &&
5664
upcomingTasks?.length === 0 &&
5765
unscheduledTasks?.length === 0) ||
58-
(activeTabId === 'done' && completedTasks?.length === 0)
59-
) {
66+
(activeTabId === 'done' && completedTasks?.length === 0);
67+
68+
if (isLoading && isTasksEmpty) {
69+
return <SkeletonLoader />;
70+
}
71+
72+
if (isTasksEmpty) {
6073
return (
61-
<AnimatedPlaceholderEmptyContainer>
74+
<AnimatedPlaceholderEmptyContainer
75+
// eslint-disable-next-line react/jsx-props-no-spreading
76+
{...EMPTY_PLACEHOLDER_TRANSITION_PROPS}
77+
>
6278
<AnimatedPlaceholder type="noTask" />
6379
<AnimatedPlaceholderEmptyTextContainer>
6480
<AnimatedPlaceholderEmptyTitle>

packages/twenty-front/src/modules/activities/tasks/hooks/useTasks.ts

+15-11
Original file line numberDiff line numberDiff line change
@@ -107,17 +107,19 @@ export const useTasks = ({
107107
setCurrentIncompleteTaskQueryVariables,
108108
]);
109109

110-
const { activities: completeTasksData } = useActivities({
111-
targetableObjects,
112-
activitiesFilters: completedQueryVariables.filter ?? {},
113-
activitiesOrderByVariables: completedQueryVariables.orderBy ?? [{}],
114-
});
115-
116-
const { activities: incompleteTaskData } = useActivities({
117-
targetableObjects,
118-
activitiesFilters: incompleteQueryVariables.filter ?? {},
119-
activitiesOrderByVariables: incompleteQueryVariables.orderBy ?? [{}],
120-
});
110+
const { activities: completeTasksData, loading: completeTasksLoading } =
111+
useActivities({
112+
targetableObjects,
113+
activitiesFilters: completedQueryVariables.filter ?? {},
114+
activitiesOrderByVariables: completedQueryVariables.orderBy ?? [{}],
115+
});
116+
117+
const { activities: incompleteTaskData, loading: incompleteTasksLoading } =
118+
useActivities({
119+
targetableObjects,
120+
activitiesFilters: incompleteQueryVariables.filter ?? {},
121+
activitiesOrderByVariables: incompleteQueryVariables.orderBy ?? [{}],
122+
});
121123

122124
const todayOrPreviousTasks = incompleteTaskData?.filter((task) => {
123125
if (!task.dueAt) {
@@ -148,5 +150,7 @@ export const useTasks = ({
148150
upcomingTasks: (upcomingTasks ?? []) as Activity[],
149151
unscheduledTasks: (unscheduledTasks ?? []) as Activity[],
150152
completedTasks: (completedTasks ?? []) as Activity[],
153+
completeTasksLoading,
154+
incompleteTasksLoading,
151155
};
152156
};

packages/twenty-front/src/modules/activities/timeline/components/Timeline.tsx

+7-3
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
import styled from '@emotion/styled';
22
import { useRecoilValue } from 'recoil';
33

4+
import { SkeletonLoader } from '@/activities/components/SkeletonLoader';
45
import { TimelineCreateButtonGroup } from '@/activities/timeline/components/TimelineCreateButtonGroup';
5-
import { TimelineSkeletonLoader } from '@/activities/timeline/components/TimelineSkeletonLoader';
66
import { timelineActivitiesForGroupState } from '@/activities/timeline/states/timelineActivitiesForGroupState';
77
import { ActivityTargetableObject } from '@/activities/types/ActivityTargetableEntity';
88
import AnimatedPlaceholder from '@/ui/layout/animated-placeholder/components/AnimatedPlaceholder';
@@ -11,6 +11,7 @@ import {
1111
AnimatedPlaceholderEmptySubTitle,
1212
AnimatedPlaceholderEmptyTextContainer,
1313
AnimatedPlaceholderEmptyTitle,
14+
EMPTY_PLACEHOLDER_TRANSITION_PROPS,
1415
} from '@/ui/layout/animated-placeholder/components/EmptyPlaceholderStyled';
1516
import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile';
1617

@@ -40,12 +41,15 @@ export const Timeline = ({
4041
);
4142

4243
if (loading) {
43-
return <TimelineSkeletonLoader />;
44+
return <SkeletonLoader withSubSections />;
4445
}
4546

4647
if (timelineActivitiesForGroup.length === 0) {
4748
return (
48-
<AnimatedPlaceholderEmptyContainer>
49+
<AnimatedPlaceholderEmptyContainer
50+
// eslint-disable-next-line react/jsx-props-no-spreading
51+
{...EMPTY_PLACEHOLDER_TRANSITION_PROPS}
52+
>
4953
<AnimatedPlaceholder type="emptyTimeline" />
5054
<AnimatedPlaceholderEmptyTextContainer>
5155
<AnimatedPlaceholderEmptyTitle>

0 commit comments

Comments
 (0)