Add loader and transition for details page tabs (#5935)
Closes https://github.com/twentyhq/twenty/issues/5656 https://github.com/twentyhq/twenty/assets/22936103/3e4beea2-9aa9-4015-bb99-ee22adb53b63
This commit is contained in:
@ -9,6 +9,7 @@ import { useCalendarEvents } from '@/activities/calendar/hooks/useCalendarEvents
|
|||||||
import { getTimelineCalendarEventsFromCompanyId } from '@/activities/calendar/queries/getTimelineCalendarEventsFromCompanyId';
|
import { getTimelineCalendarEventsFromCompanyId } from '@/activities/calendar/queries/getTimelineCalendarEventsFromCompanyId';
|
||||||
import { getTimelineCalendarEventsFromPersonId } from '@/activities/calendar/queries/getTimelineCalendarEventsFromPersonId';
|
import { getTimelineCalendarEventsFromPersonId } from '@/activities/calendar/queries/getTimelineCalendarEventsFromPersonId';
|
||||||
import { CustomResolverFetchMoreLoader } from '@/activities/components/CustomResolverFetchMoreLoader';
|
import { CustomResolverFetchMoreLoader } from '@/activities/components/CustomResolverFetchMoreLoader';
|
||||||
|
import { SkeletonLoader } from '@/activities/components/SkeletonLoader';
|
||||||
import { useCustomResolver } from '@/activities/hooks/useCustomResolver';
|
import { useCustomResolver } from '@/activities/hooks/useCustomResolver';
|
||||||
import { ActivityTargetableObject } from '@/activities/types/ActivityTargetableEntity';
|
import { ActivityTargetableObject } from '@/activities/types/ActivityTargetableEntity';
|
||||||
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
||||||
@ -18,6 +19,7 @@ import {
|
|||||||
AnimatedPlaceholderEmptySubTitle,
|
AnimatedPlaceholderEmptySubTitle,
|
||||||
AnimatedPlaceholderEmptyTextContainer,
|
AnimatedPlaceholderEmptyTextContainer,
|
||||||
AnimatedPlaceholderEmptyTitle,
|
AnimatedPlaceholderEmptyTitle,
|
||||||
|
EMPTY_PLACEHOLDER_TRANSITION_PROPS,
|
||||||
} from '@/ui/layout/animated-placeholder/components/EmptyPlaceholderStyled';
|
} from '@/ui/layout/animated-placeholder/components/EmptyPlaceholderStyled';
|
||||||
import { Section } from '@/ui/layout/section/components/Section';
|
import { Section } from '@/ui/layout/section/components/Section';
|
||||||
import { TimelineCalendarEventsWithTotal } from '~/generated/graphql';
|
import { TimelineCalendarEventsWithTotal } from '~/generated/graphql';
|
||||||
@ -74,14 +76,16 @@ export const Calendar = ({
|
|||||||
} = useCalendarEvents(timelineCalendarEvents || []);
|
} = useCalendarEvents(timelineCalendarEvents || []);
|
||||||
|
|
||||||
if (firstQueryLoading) {
|
if (firstQueryLoading) {
|
||||||
// TODO: implement loader
|
return <SkeletonLoader />;
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!firstQueryLoading && !timelineCalendarEvents?.length) {
|
if (!firstQueryLoading && !timelineCalendarEvents?.length) {
|
||||||
// TODO: change animated placeholder
|
// TODO: change animated placeholder
|
||||||
return (
|
return (
|
||||||
<AnimatedPlaceholderEmptyContainer>
|
<AnimatedPlaceholderEmptyContainer
|
||||||
|
// eslint-disable-next-line react/jsx-props-no-spreading
|
||||||
|
{...EMPTY_PLACEHOLDER_TRANSITION_PROPS}
|
||||||
|
>
|
||||||
<AnimatedPlaceholder type="noMatchRecord" />
|
<AnimatedPlaceholder type="noMatchRecord" />
|
||||||
<AnimatedPlaceholderEmptyTextContainer>
|
<AnimatedPlaceholderEmptyTextContainer>
|
||||||
<AnimatedPlaceholderEmptyTitle>
|
<AnimatedPlaceholderEmptyTitle>
|
||||||
|
|||||||
@ -18,18 +18,14 @@ const StyledSkeletonSubSection = styled.div`
|
|||||||
gap: ${({ theme }) => theme.spacing(4)};
|
gap: ${({ theme }) => theme.spacing(4)};
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const StyledSkeletonColumn = styled.div`
|
const StyledSkeletonSubSectionContent = styled.div`
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: ${({ theme }) => theme.spacing(3)};
|
gap: ${({ theme }) => theme.spacing(3)};
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const StyledSkeletonLoader = ({
|
const SkeletonColumnLoader = ({ height }: { height: number }) => {
|
||||||
isSecondColumn,
|
|
||||||
}: {
|
|
||||||
isSecondColumn: boolean;
|
|
||||||
}) => {
|
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
return (
|
return (
|
||||||
<SkeletonTheme
|
<SkeletonTheme
|
||||||
@ -37,12 +33,16 @@ const StyledSkeletonLoader = ({
|
|||||||
highlightColor={theme.background.transparent.lighter}
|
highlightColor={theme.background.transparent.lighter}
|
||||||
borderRadius={80}
|
borderRadius={80}
|
||||||
>
|
>
|
||||||
<Skeleton width={24} height={isSecondColumn ? 120 : 84} />
|
<Skeleton width={24} height={height} />
|
||||||
</SkeletonTheme>
|
</SkeletonTheme>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const TimelineSkeletonLoader = () => {
|
export const SkeletonLoader = ({
|
||||||
|
withSubSections = false,
|
||||||
|
}: {
|
||||||
|
withSubSections?: boolean;
|
||||||
|
}) => {
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const skeletonItems = Array.from({ length: 3 }).map((_, index) => ({
|
const skeletonItems = Array.from({ length: 3 }).map((_, index) => ({
|
||||||
id: `skeleton-item-${index}`,
|
id: `skeleton-item-${index}`,
|
||||||
@ -56,16 +56,17 @@ export const TimelineSkeletonLoader = () => {
|
|||||||
>
|
>
|
||||||
<StyledSkeletonContainer>
|
<StyledSkeletonContainer>
|
||||||
<Skeleton width={440} height={16} />
|
<Skeleton width={440} height={16} />
|
||||||
{skeletonItems.map(({ id }, index) => (
|
{withSubSections &&
|
||||||
<StyledSkeletonSubSection key={id}>
|
skeletonItems.map(({ id }, index) => (
|
||||||
<StyledSkeletonLoader isSecondColumn={index === 1} />
|
<StyledSkeletonSubSection key={id}>
|
||||||
<StyledSkeletonColumn>
|
<SkeletonColumnLoader height={index === 1 ? 120 : 84} />
|
||||||
<Skeleton width={400} height={24} />
|
<StyledSkeletonSubSectionContent>
|
||||||
<Skeleton width={400} height={24} />
|
<Skeleton width={400} height={24} />
|
||||||
{index === 1 && <Skeleton width={400} height={24} />}
|
<Skeleton width={400} height={24} />
|
||||||
</StyledSkeletonColumn>
|
{index === 1 && <Skeleton width={400} height={24} />}
|
||||||
</StyledSkeletonSubSection>
|
</StyledSkeletonSubSectionContent>
|
||||||
))}
|
</StyledSkeletonSubSection>
|
||||||
|
))}
|
||||||
</StyledSkeletonContainer>
|
</StyledSkeletonContainer>
|
||||||
</SkeletonTheme>
|
</SkeletonTheme>
|
||||||
);
|
);
|
||||||
@ -2,7 +2,7 @@ import styled from '@emotion/styled';
|
|||||||
import { H1Title, H1TitleFontColor } from 'twenty-ui';
|
import { H1Title, H1TitleFontColor } from 'twenty-ui';
|
||||||
|
|
||||||
import { CustomResolverFetchMoreLoader } from '@/activities/components/CustomResolverFetchMoreLoader';
|
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 { EmailThreadPreview } from '@/activities/emails/components/EmailThreadPreview';
|
||||||
import { TIMELINE_THREADS_DEFAULT_PAGE_SIZE } from '@/activities/emails/constants/Messaging';
|
import { TIMELINE_THREADS_DEFAULT_PAGE_SIZE } from '@/activities/emails/constants/Messaging';
|
||||||
import { getTimelineThreadsFromCompanyId } from '@/activities/emails/queries/getTimelineThreadsFromCompanyId';
|
import { getTimelineThreadsFromCompanyId } from '@/activities/emails/queries/getTimelineThreadsFromCompanyId';
|
||||||
@ -16,6 +16,7 @@ import {
|
|||||||
AnimatedPlaceholderEmptySubTitle,
|
AnimatedPlaceholderEmptySubTitle,
|
||||||
AnimatedPlaceholderEmptyTextContainer,
|
AnimatedPlaceholderEmptyTextContainer,
|
||||||
AnimatedPlaceholderEmptyTitle,
|
AnimatedPlaceholderEmptyTitle,
|
||||||
|
EMPTY_PLACEHOLDER_TRANSITION_PROPS,
|
||||||
} from '@/ui/layout/animated-placeholder/components/EmptyPlaceholderStyled';
|
} from '@/ui/layout/animated-placeholder/components/EmptyPlaceholderStyled';
|
||||||
import { Card } from '@/ui/layout/card/components/Card';
|
import { Card } from '@/ui/layout/card/components/Card';
|
||||||
import { Section } from '@/ui/layout/section/components/Section';
|
import { Section } from '@/ui/layout/section/components/Section';
|
||||||
@ -61,12 +62,15 @@ export const EmailThreads = ({
|
|||||||
const { totalNumberOfThreads, timelineThreads } = data?.[queryName] ?? {};
|
const { totalNumberOfThreads, timelineThreads } = data?.[queryName] ?? {};
|
||||||
|
|
||||||
if (firstQueryLoading) {
|
if (firstQueryLoading) {
|
||||||
return <EmailLoader />;
|
return <SkeletonLoader />;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!firstQueryLoading && !timelineThreads?.length) {
|
if (!firstQueryLoading && !timelineThreads?.length) {
|
||||||
return (
|
return (
|
||||||
<AnimatedPlaceholderEmptyContainer>
|
<AnimatedPlaceholderEmptyContainer
|
||||||
|
// eslint-disable-next-line react/jsx-props-no-spreading
|
||||||
|
{...EMPTY_PLACEHOLDER_TRANSITION_PROPS}
|
||||||
|
>
|
||||||
<AnimatedPlaceholder type="emptyInbox" />
|
<AnimatedPlaceholder type="emptyInbox" />
|
||||||
<AnimatedPlaceholderEmptyTextContainer>
|
<AnimatedPlaceholderEmptyTextContainer>
|
||||||
<AnimatedPlaceholderEmptyTitle>
|
<AnimatedPlaceholderEmptyTitle>
|
||||||
|
|||||||
@ -1,8 +1,8 @@
|
|||||||
import { ChangeEvent, useRef, useState } from 'react';
|
import { ChangeEvent, useRef, useState } from 'react';
|
||||||
import styled from '@emotion/styled';
|
import styled from '@emotion/styled';
|
||||||
import { isNonEmptyArray } from '@sniptt/guards';
|
|
||||||
import { IconPlus } from 'twenty-ui';
|
import { IconPlus } from 'twenty-ui';
|
||||||
|
|
||||||
|
import { SkeletonLoader } from '@/activities/components/SkeletonLoader';
|
||||||
import { AttachmentList } from '@/activities/files/components/AttachmentList';
|
import { AttachmentList } from '@/activities/files/components/AttachmentList';
|
||||||
import { DropZone } from '@/activities/files/components/DropZone';
|
import { DropZone } from '@/activities/files/components/DropZone';
|
||||||
import { useAttachments } from '@/activities/files/hooks/useAttachments';
|
import { useAttachments } from '@/activities/files/hooks/useAttachments';
|
||||||
@ -15,6 +15,7 @@ import {
|
|||||||
AnimatedPlaceholderEmptySubTitle,
|
AnimatedPlaceholderEmptySubTitle,
|
||||||
AnimatedPlaceholderEmptyTextContainer,
|
AnimatedPlaceholderEmptyTextContainer,
|
||||||
AnimatedPlaceholderEmptyTitle,
|
AnimatedPlaceholderEmptyTitle,
|
||||||
|
EMPTY_PLACEHOLDER_TRANSITION_PROPS,
|
||||||
} from '@/ui/layout/animated-placeholder/components/EmptyPlaceholderStyled';
|
} from '@/ui/layout/animated-placeholder/components/EmptyPlaceholderStyled';
|
||||||
import { isDefined } from '~/utils/isDefined';
|
import { isDefined } from '~/utils/isDefined';
|
||||||
|
|
||||||
@ -41,7 +42,7 @@ export const Attachments = ({
|
|||||||
targetableObject: ActivityTargetableObject;
|
targetableObject: ActivityTargetableObject;
|
||||||
}) => {
|
}) => {
|
||||||
const inputFileRef = useRef<HTMLInputElement>(null);
|
const inputFileRef = useRef<HTMLInputElement>(null);
|
||||||
const { attachments } = useAttachments(targetableObject);
|
const { attachments, loading } = useAttachments(targetableObject);
|
||||||
const { uploadAttachmentFile } = useUploadAttachmentFile();
|
const { uploadAttachmentFile } = useUploadAttachmentFile();
|
||||||
|
|
||||||
const [isDraggingFile, setIsDraggingFile] = useState(false);
|
const [isDraggingFile, setIsDraggingFile] = useState(false);
|
||||||
@ -58,7 +59,13 @@ export const Attachments = ({
|
|||||||
await uploadAttachmentFile(file, targetableObject);
|
await uploadAttachmentFile(file, targetableObject);
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!isNonEmptyArray(attachments)) {
|
const isAttachmentsEmpty = !attachments || attachments.length === 0;
|
||||||
|
|
||||||
|
if (loading && isAttachmentsEmpty) {
|
||||||
|
return <SkeletonLoader />;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isAttachmentsEmpty) {
|
||||||
return (
|
return (
|
||||||
<StyledDropZoneContainer onDragEnter={() => setIsDraggingFile(true)}>
|
<StyledDropZoneContainer onDragEnter={() => setIsDraggingFile(true)}>
|
||||||
{isDraggingFile ? (
|
{isDraggingFile ? (
|
||||||
@ -67,7 +74,10 @@ export const Attachments = ({
|
|||||||
onUploadFile={onUploadFile}
|
onUploadFile={onUploadFile}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<AnimatedPlaceholderEmptyContainer>
|
<AnimatedPlaceholderEmptyContainer
|
||||||
|
// eslint-disable-next-line react/jsx-props-no-spreading
|
||||||
|
{...EMPTY_PLACEHOLDER_TRANSITION_PROPS}
|
||||||
|
>
|
||||||
<AnimatedPlaceholder type="noFile" />
|
<AnimatedPlaceholder type="noFile" />
|
||||||
<AnimatedPlaceholderEmptyTextContainer>
|
<AnimatedPlaceholderEmptyTextContainer>
|
||||||
<AnimatedPlaceholderEmptyTitle>
|
<AnimatedPlaceholderEmptyTitle>
|
||||||
|
|||||||
@ -4,13 +4,12 @@ import { getActivityTargetObjectFieldIdName } from '@/activities/utils/getActivi
|
|||||||
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
||||||
import { useFindManyRecords } from '@/object-record/hooks/useFindManyRecords';
|
import { useFindManyRecords } from '@/object-record/hooks/useFindManyRecords';
|
||||||
|
|
||||||
// do we need to test this?
|
|
||||||
export const useAttachments = (targetableObject: ActivityTargetableObject) => {
|
export const useAttachments = (targetableObject: ActivityTargetableObject) => {
|
||||||
const targetableObjectFieldIdName = getActivityTargetObjectFieldIdName({
|
const targetableObjectFieldIdName = getActivityTargetObjectFieldIdName({
|
||||||
nameSingular: targetableObject.targetObjectNameSingular,
|
nameSingular: targetableObject.targetObjectNameSingular,
|
||||||
});
|
});
|
||||||
|
|
||||||
const { records: attachments } = useFindManyRecords<Attachment>({
|
const { records: attachments, loading } = useFindManyRecords<Attachment>({
|
||||||
objectNameSingular: CoreObjectNameSingular.Attachment,
|
objectNameSingular: CoreObjectNameSingular.Attachment,
|
||||||
filter: {
|
filter: {
|
||||||
[targetableObjectFieldIdName]: {
|
[targetableObjectFieldIdName]: {
|
||||||
@ -26,5 +25,6 @@ export const useAttachments = (targetableObject: ActivityTargetableObject) => {
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
attachments,
|
attachments,
|
||||||
|
loading,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
import styled from '@emotion/styled';
|
import styled from '@emotion/styled';
|
||||||
import { IconPlus } from 'twenty-ui';
|
import { IconPlus } from 'twenty-ui';
|
||||||
|
|
||||||
|
import { SkeletonLoader } from '@/activities/components/SkeletonLoader';
|
||||||
import { useOpenCreateActivityDrawer } from '@/activities/hooks/useOpenCreateActivityDrawer';
|
import { useOpenCreateActivityDrawer } from '@/activities/hooks/useOpenCreateActivityDrawer';
|
||||||
import { NoteList } from '@/activities/notes/components/NoteList';
|
import { NoteList } from '@/activities/notes/components/NoteList';
|
||||||
import { useNotes } from '@/activities/notes/hooks/useNotes';
|
import { useNotes } from '@/activities/notes/hooks/useNotes';
|
||||||
@ -12,6 +13,7 @@ import {
|
|||||||
AnimatedPlaceholderEmptySubTitle,
|
AnimatedPlaceholderEmptySubTitle,
|
||||||
AnimatedPlaceholderEmptyTextContainer,
|
AnimatedPlaceholderEmptyTextContainer,
|
||||||
AnimatedPlaceholderEmptyTitle,
|
AnimatedPlaceholderEmptyTitle,
|
||||||
|
EMPTY_PLACEHOLDER_TRANSITION_PROPS,
|
||||||
} from '@/ui/layout/animated-placeholder/components/EmptyPlaceholderStyled';
|
} from '@/ui/layout/animated-placeholder/components/EmptyPlaceholderStyled';
|
||||||
|
|
||||||
const StyledNotesContainer = styled.div`
|
const StyledNotesContainer = styled.div`
|
||||||
@ -27,13 +29,22 @@ export const Notes = ({
|
|||||||
}: {
|
}: {
|
||||||
targetableObject: ActivityTargetableObject;
|
targetableObject: ActivityTargetableObject;
|
||||||
}) => {
|
}) => {
|
||||||
const { notes } = useNotes(targetableObject);
|
const { notes, loading } = useNotes(targetableObject);
|
||||||
|
|
||||||
const openCreateActivity = useOpenCreateActivityDrawer();
|
const openCreateActivity = useOpenCreateActivityDrawer();
|
||||||
|
|
||||||
if (notes?.length === 0) {
|
const isNotesEmpty = !notes || notes.length === 0;
|
||||||
|
|
||||||
|
if (loading && isNotesEmpty) {
|
||||||
|
return <SkeletonLoader />;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isNotesEmpty) {
|
||||||
return (
|
return (
|
||||||
<AnimatedPlaceholderEmptyContainer>
|
<AnimatedPlaceholderEmptyContainer
|
||||||
|
// eslint-disable-next-line react/jsx-props-no-spreading
|
||||||
|
{...EMPTY_PLACEHOLDER_TRANSITION_PROPS}
|
||||||
|
>
|
||||||
<AnimatedPlaceholder type="noNote" />
|
<AnimatedPlaceholder type="noNote" />
|
||||||
<AnimatedPlaceholderEmptyTextContainer>
|
<AnimatedPlaceholderEmptyTextContainer>
|
||||||
<AnimatedPlaceholderEmptyTitle>
|
<AnimatedPlaceholderEmptyTitle>
|
||||||
@ -62,7 +73,7 @@ export const Notes = ({
|
|||||||
<StyledNotesContainer>
|
<StyledNotesContainer>
|
||||||
<NoteList
|
<NoteList
|
||||||
title="All"
|
title="All"
|
||||||
notes={notes ?? []}
|
notes={notes}
|
||||||
button={
|
button={
|
||||||
<Button
|
<Button
|
||||||
Icon={IconPlus}
|
Icon={IconPlus}
|
||||||
|
|||||||
@ -2,6 +2,7 @@ import styled from '@emotion/styled';
|
|||||||
import { useRecoilValue } from 'recoil';
|
import { useRecoilValue } from 'recoil';
|
||||||
import { IconPlus } from 'twenty-ui';
|
import { IconPlus } from 'twenty-ui';
|
||||||
|
|
||||||
|
import { SkeletonLoader } from '@/activities/components/SkeletonLoader';
|
||||||
import { useOpenCreateActivityDrawer } from '@/activities/hooks/useOpenCreateActivityDrawer';
|
import { useOpenCreateActivityDrawer } from '@/activities/hooks/useOpenCreateActivityDrawer';
|
||||||
import { TASKS_TAB_LIST_COMPONENT_ID } from '@/activities/tasks/constants/TasksTabListComponentId';
|
import { TASKS_TAB_LIST_COMPONENT_ID } from '@/activities/tasks/constants/TasksTabListComponentId';
|
||||||
import { useTasks } from '@/activities/tasks/hooks/useTasks';
|
import { useTasks } from '@/activities/tasks/hooks/useTasks';
|
||||||
@ -13,6 +14,7 @@ import {
|
|||||||
AnimatedPlaceholderEmptySubTitle,
|
AnimatedPlaceholderEmptySubTitle,
|
||||||
AnimatedPlaceholderEmptyTextContainer,
|
AnimatedPlaceholderEmptyTextContainer,
|
||||||
AnimatedPlaceholderEmptyTitle,
|
AnimatedPlaceholderEmptyTitle,
|
||||||
|
EMPTY_PLACEHOLDER_TRANSITION_PROPS,
|
||||||
} from '@/ui/layout/animated-placeholder/components/EmptyPlaceholderStyled';
|
} from '@/ui/layout/animated-placeholder/components/EmptyPlaceholderStyled';
|
||||||
import { useTabList } from '@/ui/layout/tab/hooks/useTabList';
|
import { useTabList } from '@/ui/layout/tab/hooks/useTabList';
|
||||||
|
|
||||||
@ -40,6 +42,8 @@ export const TaskGroups = ({
|
|||||||
upcomingTasks,
|
upcomingTasks,
|
||||||
unscheduledTasks,
|
unscheduledTasks,
|
||||||
completedTasks,
|
completedTasks,
|
||||||
|
incompleteTasksLoading,
|
||||||
|
completeTasksLoading,
|
||||||
} = useTasks({
|
} = useTasks({
|
||||||
filterDropdownId: filterDropdownId,
|
filterDropdownId: filterDropdownId,
|
||||||
targetableObjects: targetableObjects ?? [],
|
targetableObjects: targetableObjects ?? [],
|
||||||
@ -50,15 +54,27 @@ export const TaskGroups = ({
|
|||||||
const { activeTabIdState } = useTabList(TASKS_TAB_LIST_COMPONENT_ID);
|
const { activeTabIdState } = useTabList(TASKS_TAB_LIST_COMPONENT_ID);
|
||||||
const activeTabId = useRecoilValue(activeTabIdState);
|
const activeTabId = useRecoilValue(activeTabIdState);
|
||||||
|
|
||||||
if (
|
const isLoading =
|
||||||
|
(activeTabId !== 'done' && incompleteTasksLoading) ||
|
||||||
|
(activeTabId === 'done' && completeTasksLoading);
|
||||||
|
|
||||||
|
const isTasksEmpty =
|
||||||
(activeTabId !== 'done' &&
|
(activeTabId !== 'done' &&
|
||||||
todayOrPreviousTasks?.length === 0 &&
|
todayOrPreviousTasks?.length === 0 &&
|
||||||
upcomingTasks?.length === 0 &&
|
upcomingTasks?.length === 0 &&
|
||||||
unscheduledTasks?.length === 0) ||
|
unscheduledTasks?.length === 0) ||
|
||||||
(activeTabId === 'done' && completedTasks?.length === 0)
|
(activeTabId === 'done' && completedTasks?.length === 0);
|
||||||
) {
|
|
||||||
|
if (isLoading && isTasksEmpty) {
|
||||||
|
return <SkeletonLoader />;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isTasksEmpty) {
|
||||||
return (
|
return (
|
||||||
<AnimatedPlaceholderEmptyContainer>
|
<AnimatedPlaceholderEmptyContainer
|
||||||
|
// eslint-disable-next-line react/jsx-props-no-spreading
|
||||||
|
{...EMPTY_PLACEHOLDER_TRANSITION_PROPS}
|
||||||
|
>
|
||||||
<AnimatedPlaceholder type="noTask" />
|
<AnimatedPlaceholder type="noTask" />
|
||||||
<AnimatedPlaceholderEmptyTextContainer>
|
<AnimatedPlaceholderEmptyTextContainer>
|
||||||
<AnimatedPlaceholderEmptyTitle>
|
<AnimatedPlaceholderEmptyTitle>
|
||||||
|
|||||||
@ -107,17 +107,19 @@ export const useTasks = ({
|
|||||||
setCurrentIncompleteTaskQueryVariables,
|
setCurrentIncompleteTaskQueryVariables,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const { activities: completeTasksData } = useActivities({
|
const { activities: completeTasksData, loading: completeTasksLoading } =
|
||||||
targetableObjects,
|
useActivities({
|
||||||
activitiesFilters: completedQueryVariables.filter ?? {},
|
targetableObjects,
|
||||||
activitiesOrderByVariables: completedQueryVariables.orderBy ?? [{}],
|
activitiesFilters: completedQueryVariables.filter ?? {},
|
||||||
});
|
activitiesOrderByVariables: completedQueryVariables.orderBy ?? [{}],
|
||||||
|
});
|
||||||
|
|
||||||
const { activities: incompleteTaskData } = useActivities({
|
const { activities: incompleteTaskData, loading: incompleteTasksLoading } =
|
||||||
targetableObjects,
|
useActivities({
|
||||||
activitiesFilters: incompleteQueryVariables.filter ?? {},
|
targetableObjects,
|
||||||
activitiesOrderByVariables: incompleteQueryVariables.orderBy ?? [{}],
|
activitiesFilters: incompleteQueryVariables.filter ?? {},
|
||||||
});
|
activitiesOrderByVariables: incompleteQueryVariables.orderBy ?? [{}],
|
||||||
|
});
|
||||||
|
|
||||||
const todayOrPreviousTasks = incompleteTaskData?.filter((task) => {
|
const todayOrPreviousTasks = incompleteTaskData?.filter((task) => {
|
||||||
if (!task.dueAt) {
|
if (!task.dueAt) {
|
||||||
@ -148,5 +150,7 @@ export const useTasks = ({
|
|||||||
upcomingTasks: (upcomingTasks ?? []) as Activity[],
|
upcomingTasks: (upcomingTasks ?? []) as Activity[],
|
||||||
unscheduledTasks: (unscheduledTasks ?? []) as Activity[],
|
unscheduledTasks: (unscheduledTasks ?? []) as Activity[],
|
||||||
completedTasks: (completedTasks ?? []) as Activity[],
|
completedTasks: (completedTasks ?? []) as Activity[],
|
||||||
|
completeTasksLoading,
|
||||||
|
incompleteTasksLoading,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,8 +1,8 @@
|
|||||||
import styled from '@emotion/styled';
|
import styled from '@emotion/styled';
|
||||||
import { useRecoilValue } from 'recoil';
|
import { useRecoilValue } from 'recoil';
|
||||||
|
|
||||||
|
import { SkeletonLoader } from '@/activities/components/SkeletonLoader';
|
||||||
import { TimelineCreateButtonGroup } from '@/activities/timeline/components/TimelineCreateButtonGroup';
|
import { TimelineCreateButtonGroup } from '@/activities/timeline/components/TimelineCreateButtonGroup';
|
||||||
import { TimelineSkeletonLoader } from '@/activities/timeline/components/TimelineSkeletonLoader';
|
|
||||||
import { timelineActivitiesForGroupState } from '@/activities/timeline/states/timelineActivitiesForGroupState';
|
import { timelineActivitiesForGroupState } from '@/activities/timeline/states/timelineActivitiesForGroupState';
|
||||||
import { ActivityTargetableObject } from '@/activities/types/ActivityTargetableEntity';
|
import { ActivityTargetableObject } from '@/activities/types/ActivityTargetableEntity';
|
||||||
import AnimatedPlaceholder from '@/ui/layout/animated-placeholder/components/AnimatedPlaceholder';
|
import AnimatedPlaceholder from '@/ui/layout/animated-placeholder/components/AnimatedPlaceholder';
|
||||||
@ -11,6 +11,7 @@ import {
|
|||||||
AnimatedPlaceholderEmptySubTitle,
|
AnimatedPlaceholderEmptySubTitle,
|
||||||
AnimatedPlaceholderEmptyTextContainer,
|
AnimatedPlaceholderEmptyTextContainer,
|
||||||
AnimatedPlaceholderEmptyTitle,
|
AnimatedPlaceholderEmptyTitle,
|
||||||
|
EMPTY_PLACEHOLDER_TRANSITION_PROPS,
|
||||||
} from '@/ui/layout/animated-placeholder/components/EmptyPlaceholderStyled';
|
} from '@/ui/layout/animated-placeholder/components/EmptyPlaceholderStyled';
|
||||||
import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile';
|
import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile';
|
||||||
|
|
||||||
@ -40,12 +41,15 @@ export const Timeline = ({
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (loading) {
|
if (loading) {
|
||||||
return <TimelineSkeletonLoader />;
|
return <SkeletonLoader withSubSections />;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (timelineActivitiesForGroup.length === 0) {
|
if (timelineActivitiesForGroup.length === 0) {
|
||||||
return (
|
return (
|
||||||
<AnimatedPlaceholderEmptyContainer>
|
<AnimatedPlaceholderEmptyContainer
|
||||||
|
// eslint-disable-next-line react/jsx-props-no-spreading
|
||||||
|
{...EMPTY_PLACEHOLDER_TRANSITION_PROPS}
|
||||||
|
>
|
||||||
<AnimatedPlaceholder type="emptyTimeline" />
|
<AnimatedPlaceholder type="emptyTimeline" />
|
||||||
<AnimatedPlaceholderEmptyTextContainer>
|
<AnimatedPlaceholderEmptyTextContainer>
|
||||||
<AnimatedPlaceholderEmptyTitle>
|
<AnimatedPlaceholderEmptyTitle>
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import styled from '@emotion/styled';
|
import styled from '@emotion/styled';
|
||||||
import { isNonEmptyArray } from '@sniptt/guards';
|
|
||||||
|
|
||||||
import { CustomResolverFetchMoreLoader } from '@/activities/components/CustomResolverFetchMoreLoader';
|
import { CustomResolverFetchMoreLoader } from '@/activities/components/CustomResolverFetchMoreLoader';
|
||||||
|
import { SkeletonLoader } from '@/activities/components/SkeletonLoader';
|
||||||
import { TimelineCreateButtonGroup } from '@/activities/timeline/components/TimelineCreateButtonGroup';
|
import { TimelineCreateButtonGroup } from '@/activities/timeline/components/TimelineCreateButtonGroup';
|
||||||
import { EventList } from '@/activities/timelineActivities/components/EventList';
|
import { EventList } from '@/activities/timelineActivities/components/EventList';
|
||||||
import { useTimelineActivities } from '@/activities/timelineActivities/hooks/useTimelineActivities';
|
import { useTimelineActivities } from '@/activities/timelineActivities/hooks/useTimelineActivities';
|
||||||
@ -12,6 +12,7 @@ import {
|
|||||||
AnimatedPlaceholderEmptySubTitle,
|
AnimatedPlaceholderEmptySubTitle,
|
||||||
AnimatedPlaceholderEmptyTextContainer,
|
AnimatedPlaceholderEmptyTextContainer,
|
||||||
AnimatedPlaceholderEmptyTitle,
|
AnimatedPlaceholderEmptyTitle,
|
||||||
|
EMPTY_PLACEHOLDER_TRANSITION_PROPS,
|
||||||
} from '@/ui/layout/animated-placeholder/components/EmptyPlaceholderStyled';
|
} from '@/ui/layout/animated-placeholder/components/EmptyPlaceholderStyled';
|
||||||
import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile';
|
import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile';
|
||||||
|
|
||||||
@ -40,9 +41,19 @@ export const TimelineActivities = ({
|
|||||||
const { timelineActivities, loading, fetchMoreRecords } =
|
const { timelineActivities, loading, fetchMoreRecords } =
|
||||||
useTimelineActivities(targetableObject);
|
useTimelineActivities(targetableObject);
|
||||||
|
|
||||||
if (!isNonEmptyArray(timelineActivities)) {
|
const isTimelineActivitiesEmpty =
|
||||||
|
!timelineActivities || timelineActivities.length === 0;
|
||||||
|
|
||||||
|
if (loading && isTimelineActivitiesEmpty) {
|
||||||
|
return <SkeletonLoader withSubSections />;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isTimelineActivitiesEmpty) {
|
||||||
return (
|
return (
|
||||||
<AnimatedPlaceholderEmptyContainer>
|
<AnimatedPlaceholderEmptyContainer
|
||||||
|
// eslint-disable-next-line react/jsx-props-no-spreading
|
||||||
|
{...EMPTY_PLACEHOLDER_TRANSITION_PROPS}
|
||||||
|
>
|
||||||
<AnimatedPlaceholder type="emptyTimeline" />
|
<AnimatedPlaceholder type="emptyTimeline" />
|
||||||
<AnimatedPlaceholderEmptyTextContainer>
|
<AnimatedPlaceholderEmptyTextContainer>
|
||||||
<AnimatedPlaceholderEmptyTitle>
|
<AnimatedPlaceholderEmptyTitle>
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
import styled from '@emotion/styled';
|
import styled from '@emotion/styled';
|
||||||
|
import { motion } from 'framer-motion';
|
||||||
|
|
||||||
const StyledEmptyContainer = styled.div`
|
const StyledEmptyContainer = styled(motion.div)`
|
||||||
align-items: center;
|
align-items: center;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
@ -13,6 +14,14 @@ const StyledEmptyContainer = styled.div`
|
|||||||
|
|
||||||
export { StyledEmptyContainer as AnimatedPlaceholderEmptyContainer };
|
export { StyledEmptyContainer as AnimatedPlaceholderEmptyContainer };
|
||||||
|
|
||||||
|
export const EMPTY_PLACEHOLDER_TRANSITION_PROPS = {
|
||||||
|
initial: { opacity: 0 },
|
||||||
|
animate: { opacity: 1 },
|
||||||
|
transition: {
|
||||||
|
duration: 0.15,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
const StyledEmptyTextContainer = styled.div`
|
const StyledEmptyTextContainer = styled.div`
|
||||||
align-items: center;
|
align-items: center;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|||||||
Reference in New Issue
Block a user