Skip to content

Commit

Permalink
Add loader and transition for details page tabs (twentyhq#5935)
Browse files Browse the repository at this point in the history
  • Loading branch information
thomtrp authored Jun 18, 2024
1 parent cff8561 commit 6b1548e
Show file tree
Hide file tree
Showing 11 changed files with 130 additions and 56 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { useCalendarEvents } from '@/activities/calendar/hooks/useCalendarEvents
import { getTimelineCalendarEventsFromCompanyId } from '@/activities/calendar/queries/getTimelineCalendarEventsFromCompanyId';
import { getTimelineCalendarEventsFromPersonId } from '@/activities/calendar/queries/getTimelineCalendarEventsFromPersonId';
import { CustomResolverFetchMoreLoader } from '@/activities/components/CustomResolverFetchMoreLoader';
import { SkeletonLoader } from '@/activities/components/SkeletonLoader';
import { useCustomResolver } from '@/activities/hooks/useCustomResolver';
import { ActivityTargetableObject } from '@/activities/types/ActivityTargetableEntity';
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
Expand All @@ -18,6 +19,7 @@ import {
AnimatedPlaceholderEmptySubTitle,
AnimatedPlaceholderEmptyTextContainer,
AnimatedPlaceholderEmptyTitle,
EMPTY_PLACEHOLDER_TRANSITION_PROPS,
} from '@/ui/layout/animated-placeholder/components/EmptyPlaceholderStyled';
import { Section } from '@/ui/layout/section/components/Section';
import { TimelineCalendarEventsWithTotal } from '~/generated/graphql';
Expand Down Expand Up @@ -74,14 +76,16 @@ export const Calendar = ({
} = useCalendarEvents(timelineCalendarEvents || []);

if (firstQueryLoading) {
// TODO: implement loader
return;
return <SkeletonLoader />;
}

if (!firstQueryLoading && !timelineCalendarEvents?.length) {
// TODO: change animated placeholder
return (
<AnimatedPlaceholderEmptyContainer>
<AnimatedPlaceholderEmptyContainer
// eslint-disable-next-line react/jsx-props-no-spreading
{...EMPTY_PLACEHOLDER_TRANSITION_PROPS}
>
<AnimatedPlaceholder type="noMatchRecord" />
<AnimatedPlaceholderEmptyTextContainer>
<AnimatedPlaceholderEmptyTitle>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,31 +18,31 @@ const StyledSkeletonSubSection = styled.div`
gap: ${({ theme }) => theme.spacing(4)};
`;

const StyledSkeletonColumn = styled.div`
const StyledSkeletonSubSectionContent = styled.div`
display: flex;
flex-direction: column;
gap: ${({ theme }) => theme.spacing(3)};
justify-content: center;
`;

const StyledSkeletonLoader = ({
isSecondColumn,
}: {
isSecondColumn: boolean;
}) => {
const SkeletonColumnLoader = ({ height }: { height: number }) => {
const theme = useTheme();
return (
<SkeletonTheme
baseColor={theme.background.tertiary}
highlightColor={theme.background.transparent.lighter}
borderRadius={80}
>
<Skeleton width={24} height={isSecondColumn ? 120 : 84} />
<Skeleton width={24} height={height} />
</SkeletonTheme>
);
};

export const TimelineSkeletonLoader = () => {
export const SkeletonLoader = ({
withSubSections = false,
}: {
withSubSections?: boolean;
}) => {
const theme = useTheme();
const skeletonItems = Array.from({ length: 3 }).map((_, index) => ({
id: `skeleton-item-${index}`,
Expand All @@ -56,16 +56,17 @@ export const TimelineSkeletonLoader = () => {
>
<StyledSkeletonContainer>
<Skeleton width={440} height={16} />
{skeletonItems.map(({ id }, index) => (
<StyledSkeletonSubSection key={id}>
<StyledSkeletonLoader isSecondColumn={index === 1} />
<StyledSkeletonColumn>
<Skeleton width={400} height={24} />
<Skeleton width={400} height={24} />
{index === 1 && <Skeleton width={400} height={24} />}
</StyledSkeletonColumn>
</StyledSkeletonSubSection>
))}
{withSubSections &&
skeletonItems.map(({ id }, index) => (
<StyledSkeletonSubSection key={id}>
<SkeletonColumnLoader height={index === 1 ? 120 : 84} />
<StyledSkeletonSubSectionContent>
<Skeleton width={400} height={24} />
<Skeleton width={400} height={24} />
{index === 1 && <Skeleton width={400} height={24} />}
</StyledSkeletonSubSectionContent>
</StyledSkeletonSubSection>
))}
</StyledSkeletonContainer>
</SkeletonTheme>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import styled from '@emotion/styled';
import { H1Title, H1TitleFontColor } from 'twenty-ui';

import { CustomResolverFetchMoreLoader } from '@/activities/components/CustomResolverFetchMoreLoader';
import { EmailLoader } from '@/activities/emails/components/EmailLoader';
import { SkeletonLoader } from '@/activities/components/SkeletonLoader';
import { EmailThreadPreview } from '@/activities/emails/components/EmailThreadPreview';
import { TIMELINE_THREADS_DEFAULT_PAGE_SIZE } from '@/activities/emails/constants/Messaging';
import { getTimelineThreadsFromCompanyId } from '@/activities/emails/queries/getTimelineThreadsFromCompanyId';
Expand All @@ -16,6 +16,7 @@ import {
AnimatedPlaceholderEmptySubTitle,
AnimatedPlaceholderEmptyTextContainer,
AnimatedPlaceholderEmptyTitle,
EMPTY_PLACEHOLDER_TRANSITION_PROPS,
} from '@/ui/layout/animated-placeholder/components/EmptyPlaceholderStyled';
import { Card } from '@/ui/layout/card/components/Card';
import { Section } from '@/ui/layout/section/components/Section';
Expand Down Expand Up @@ -61,12 +62,15 @@ export const EmailThreads = ({
const { totalNumberOfThreads, timelineThreads } = data?.[queryName] ?? {};

if (firstQueryLoading) {
return <EmailLoader />;
return <SkeletonLoader />;
}

if (!firstQueryLoading && !timelineThreads?.length) {
return (
<AnimatedPlaceholderEmptyContainer>
<AnimatedPlaceholderEmptyContainer
// eslint-disable-next-line react/jsx-props-no-spreading
{...EMPTY_PLACEHOLDER_TRANSITION_PROPS}
>
<AnimatedPlaceholder type="emptyInbox" />
<AnimatedPlaceholderEmptyTextContainer>
<AnimatedPlaceholderEmptyTitle>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { ChangeEvent, useRef, useState } from 'react';
import styled from '@emotion/styled';
import { isNonEmptyArray } from '@sniptt/guards';
import { IconPlus } from 'twenty-ui';

import { SkeletonLoader } from '@/activities/components/SkeletonLoader';
import { AttachmentList } from '@/activities/files/components/AttachmentList';
import { DropZone } from '@/activities/files/components/DropZone';
import { useAttachments } from '@/activities/files/hooks/useAttachments';
Expand All @@ -15,6 +15,7 @@ import {
AnimatedPlaceholderEmptySubTitle,
AnimatedPlaceholderEmptyTextContainer,
AnimatedPlaceholderEmptyTitle,
EMPTY_PLACEHOLDER_TRANSITION_PROPS,
} from '@/ui/layout/animated-placeholder/components/EmptyPlaceholderStyled';
import { isDefined } from '~/utils/isDefined';

Expand All @@ -41,7 +42,7 @@ export const Attachments = ({
targetableObject: ActivityTargetableObject;
}) => {
const inputFileRef = useRef<HTMLInputElement>(null);
const { attachments } = useAttachments(targetableObject);
const { attachments, loading } = useAttachments(targetableObject);
const { uploadAttachmentFile } = useUploadAttachmentFile();

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

if (!isNonEmptyArray(attachments)) {
const isAttachmentsEmpty = !attachments || attachments.length === 0;

if (loading && isAttachmentsEmpty) {
return <SkeletonLoader />;
}

if (isAttachmentsEmpty) {
return (
<StyledDropZoneContainer onDragEnter={() => setIsDraggingFile(true)}>
{isDraggingFile ? (
Expand All @@ -67,7 +74,10 @@ export const Attachments = ({
onUploadFile={onUploadFile}
/>
) : (
<AnimatedPlaceholderEmptyContainer>
<AnimatedPlaceholderEmptyContainer
// eslint-disable-next-line react/jsx-props-no-spreading
{...EMPTY_PLACEHOLDER_TRANSITION_PROPS}
>
<AnimatedPlaceholder type="noFile" />
<AnimatedPlaceholderEmptyTextContainer>
<AnimatedPlaceholderEmptyTitle>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,12 @@ import { getActivityTargetObjectFieldIdName } from '@/activities/utils/getActivi
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
import { useFindManyRecords } from '@/object-record/hooks/useFindManyRecords';

// do we need to test this?
export const useAttachments = (targetableObject: ActivityTargetableObject) => {
const targetableObjectFieldIdName = getActivityTargetObjectFieldIdName({
nameSingular: targetableObject.targetObjectNameSingular,
});

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

return {
attachments,
loading,
};
};
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import styled from '@emotion/styled';
import { IconPlus } from 'twenty-ui';

import { SkeletonLoader } from '@/activities/components/SkeletonLoader';
import { useOpenCreateActivityDrawer } from '@/activities/hooks/useOpenCreateActivityDrawer';
import { NoteList } from '@/activities/notes/components/NoteList';
import { useNotes } from '@/activities/notes/hooks/useNotes';
Expand All @@ -12,6 +13,7 @@ import {
AnimatedPlaceholderEmptySubTitle,
AnimatedPlaceholderEmptyTextContainer,
AnimatedPlaceholderEmptyTitle,
EMPTY_PLACEHOLDER_TRANSITION_PROPS,
} from '@/ui/layout/animated-placeholder/components/EmptyPlaceholderStyled';

const StyledNotesContainer = styled.div`
Expand All @@ -27,13 +29,22 @@ export const Notes = ({
}: {
targetableObject: ActivityTargetableObject;
}) => {
const { notes } = useNotes(targetableObject);
const { notes, loading } = useNotes(targetableObject);

const openCreateActivity = useOpenCreateActivityDrawer();

if (notes?.length === 0) {
const isNotesEmpty = !notes || notes.length === 0;

if (loading && isNotesEmpty) {
return <SkeletonLoader />;
}

if (isNotesEmpty) {
return (
<AnimatedPlaceholderEmptyContainer>
<AnimatedPlaceholderEmptyContainer
// eslint-disable-next-line react/jsx-props-no-spreading
{...EMPTY_PLACEHOLDER_TRANSITION_PROPS}
>
<AnimatedPlaceholder type="noNote" />
<AnimatedPlaceholderEmptyTextContainer>
<AnimatedPlaceholderEmptyTitle>
Expand Down Expand Up @@ -62,7 +73,7 @@ export const Notes = ({
<StyledNotesContainer>
<NoteList
title="All"
notes={notes ?? []}
notes={notes}
button={
<Button
Icon={IconPlus}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import styled from '@emotion/styled';
import { useRecoilValue } from 'recoil';
import { IconPlus } from 'twenty-ui';

import { SkeletonLoader } from '@/activities/components/SkeletonLoader';
import { useOpenCreateActivityDrawer } from '@/activities/hooks/useOpenCreateActivityDrawer';
import { TASKS_TAB_LIST_COMPONENT_ID } from '@/activities/tasks/constants/TasksTabListComponentId';
import { useTasks } from '@/activities/tasks/hooks/useTasks';
Expand All @@ -13,6 +14,7 @@ import {
AnimatedPlaceholderEmptySubTitle,
AnimatedPlaceholderEmptyTextContainer,
AnimatedPlaceholderEmptyTitle,
EMPTY_PLACEHOLDER_TRANSITION_PROPS,
} from '@/ui/layout/animated-placeholder/components/EmptyPlaceholderStyled';
import { useTabList } from '@/ui/layout/tab/hooks/useTabList';

Expand Down Expand Up @@ -40,6 +42,8 @@ export const TaskGroups = ({
upcomingTasks,
unscheduledTasks,
completedTasks,
incompleteTasksLoading,
completeTasksLoading,
} = useTasks({
filterDropdownId: filterDropdownId,
targetableObjects: targetableObjects ?? [],
Expand All @@ -50,15 +54,27 @@ export const TaskGroups = ({
const { activeTabIdState } = useTabList(TASKS_TAB_LIST_COMPONENT_ID);
const activeTabId = useRecoilValue(activeTabIdState);

if (
const isLoading =
(activeTabId !== 'done' && incompleteTasksLoading) ||
(activeTabId === 'done' && completeTasksLoading);

const isTasksEmpty =
(activeTabId !== 'done' &&
todayOrPreviousTasks?.length === 0 &&
upcomingTasks?.length === 0 &&
unscheduledTasks?.length === 0) ||
(activeTabId === 'done' && completedTasks?.length === 0)
) {
(activeTabId === 'done' && completedTasks?.length === 0);

if (isLoading && isTasksEmpty) {
return <SkeletonLoader />;
}

if (isTasksEmpty) {
return (
<AnimatedPlaceholderEmptyContainer>
<AnimatedPlaceholderEmptyContainer
// eslint-disable-next-line react/jsx-props-no-spreading
{...EMPTY_PLACEHOLDER_TRANSITION_PROPS}
>
<AnimatedPlaceholder type="noTask" />
<AnimatedPlaceholderEmptyTextContainer>
<AnimatedPlaceholderEmptyTitle>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -107,17 +107,19 @@ export const useTasks = ({
setCurrentIncompleteTaskQueryVariables,
]);

const { activities: completeTasksData } = useActivities({
targetableObjects,
activitiesFilters: completedQueryVariables.filter ?? {},
activitiesOrderByVariables: completedQueryVariables.orderBy ?? [{}],
});

const { activities: incompleteTaskData } = useActivities({
targetableObjects,
activitiesFilters: incompleteQueryVariables.filter ?? {},
activitiesOrderByVariables: incompleteQueryVariables.orderBy ?? [{}],
});
const { activities: completeTasksData, loading: completeTasksLoading } =
useActivities({
targetableObjects,
activitiesFilters: completedQueryVariables.filter ?? {},
activitiesOrderByVariables: completedQueryVariables.orderBy ?? [{}],
});

const { activities: incompleteTaskData, loading: incompleteTasksLoading } =
useActivities({
targetableObjects,
activitiesFilters: incompleteQueryVariables.filter ?? {},
activitiesOrderByVariables: incompleteQueryVariables.orderBy ?? [{}],
});

const todayOrPreviousTasks = incompleteTaskData?.filter((task) => {
if (!task.dueAt) {
Expand Down Expand Up @@ -148,5 +150,7 @@ export const useTasks = ({
upcomingTasks: (upcomingTasks ?? []) as Activity[],
unscheduledTasks: (unscheduledTasks ?? []) as Activity[],
completedTasks: (completedTasks ?? []) as Activity[],
completeTasksLoading,
incompleteTasksLoading,
};
};
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import styled from '@emotion/styled';
import { useRecoilValue } from 'recoil';

import { SkeletonLoader } from '@/activities/components/SkeletonLoader';
import { TimelineCreateButtonGroup } from '@/activities/timeline/components/TimelineCreateButtonGroup';
import { TimelineSkeletonLoader } from '@/activities/timeline/components/TimelineSkeletonLoader';
import { timelineActivitiesForGroupState } from '@/activities/timeline/states/timelineActivitiesForGroupState';
import { ActivityTargetableObject } from '@/activities/types/ActivityTargetableEntity';
import AnimatedPlaceholder from '@/ui/layout/animated-placeholder/components/AnimatedPlaceholder';
Expand All @@ -11,6 +11,7 @@ import {
AnimatedPlaceholderEmptySubTitle,
AnimatedPlaceholderEmptyTextContainer,
AnimatedPlaceholderEmptyTitle,
EMPTY_PLACEHOLDER_TRANSITION_PROPS,
} from '@/ui/layout/animated-placeholder/components/EmptyPlaceholderStyled';
import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile';

Expand Down Expand Up @@ -40,12 +41,15 @@ export const Timeline = ({
);

if (loading) {
return <TimelineSkeletonLoader />;
return <SkeletonLoader withSubSections />;
}

if (timelineActivitiesForGroup.length === 0) {
return (
<AnimatedPlaceholderEmptyContainer>
<AnimatedPlaceholderEmptyContainer
// eslint-disable-next-line react/jsx-props-no-spreading
{...EMPTY_PLACEHOLDER_TRANSITION_PROPS}
>
<AnimatedPlaceholder type="emptyTimeline" />
<AnimatedPlaceholderEmptyTextContainer>
<AnimatedPlaceholderEmptyTitle>
Expand Down
Loading

0 comments on commit 6b1548e

Please sign in to comment.