Add tasks page (#1015)

* Refactor top bar component

* Add task page with tabs

* Add tasks

* Add logic for task status

* Fix isoweek definition

* Enable click on task

* Deduplicate component

* Lint

---------

Co-authored-by: Charles Bochet <charlesBochet@users.noreply.github.com>
This commit is contained in:
Emilien Chauvet
2023-07-31 16:14:35 -07:00
committed by GitHub
parent 700b567320
commit 22ca00bb67
22 changed files with 625 additions and 143 deletions
@@ -0,0 +1,13 @@
import { useTasks } from '../hooks/useTasks';
import { TaskList } from './TaskList';
export function TaskGroups() {
const { todayTasks, otherTasks } = useTasks();
return (
<>
<TaskList title="Today" tasks={todayTasks ?? []} />
<TaskList title="Others" tasks={otherTasks ?? []} />
</>
);
}
@@ -0,0 +1,61 @@
import styled from '@emotion/styled';
import { TaskForList } from '../types/TaskForList';
import { TaskRow } from './TaskRow';
type OwnProps = {
title: string;
tasks: TaskForList[];
};
const StyledContainer = styled.div`
align-items: flex-start;
align-self: stretch;
display: flex;
flex-direction: column;
justify-content: center;
padding: 8px 24px;
`;
const StyledTitle = styled.h3`
color: ${({ theme }) => theme.font.color.primary};
margin-bottom: ${({ theme }) => theme.spacing(4)};
margin-top: ${({ theme }) => theme.spacing(4)};
`;
const StyledCount = styled.span`
color: ${({ theme }) => theme.font.color.light};
margin-left: ${({ theme }) => theme.spacing(2)};
`;
const StyledTaskRows = styled.div`
background-color: ${({ theme }) => theme.background.secondary};
border: 1px solid ${({ theme }) => theme.border.color.light};
border-radius: ${({ theme }) => theme.border.radius.md};
width: 100%;
`;
const StyledEmptyListMessage = styled.div`
color: ${({ theme }) => theme.font.color.secondary};
padding: ${({ theme }) => theme.spacing(4)};
`;
export function TaskList({ title, tasks }: OwnProps) {
return (
<StyledContainer>
<StyledTitle>
{title} <StyledCount>{tasks ? tasks.length : 0}</StyledCount>
</StyledTitle>
{tasks && tasks.length > 0 ? (
<StyledTaskRows>
{tasks.map((task) => (
<TaskRow key={task.id} task={task} />
))}
</StyledTaskRows>
) : (
<StyledEmptyListMessage>No task in this section</StyledEmptyListMessage>
)}
</StyledContainer>
);
}
@@ -0,0 +1,105 @@
import { useTheme } from '@emotion/react';
import styled from '@emotion/styled';
import { ActivityTargetChips } from '@/activities/components/ActivityTargetChips';
import { useOpenActivityRightDrawer } from '@/activities/hooks/useOpenActivityRightDrawer';
import { IconCalendar, IconComment } from '@/ui/icon';
import {
Checkbox,
CheckboxShape,
} from '@/ui/input/checkbox/components/Checkbox';
import { useGetCompaniesQuery, useGetPeopleQuery } from '~/generated/graphql';
import { beautifyExactDate } from '~/utils/date-utils';
import { TaskForList } from '../types/TaskForList';
const StyledContainer = styled.div`
align-items: center;
align-self: stretch;
border-bottom: 1px solid ${({ theme }) => theme.border.color.light};
cursor: pointer;
display: flex;
height: ${({ theme }) => theme.spacing(12)};
padding: 0 ${({ theme }) => theme.spacing(4)};
`;
const StyledSeparator = styled.div`
flex: 1;
`;
const StyledTaskTitle = styled.div`
padding: 0 ${({ theme }) => theme.spacing(2)};
`;
const StyledCommentIcon = styled.div`
color: ${({ theme }) => theme.font.color.light};
`;
const StyledDueDate = styled.div`
color: ${({ theme }) => theme.font.color.secondary};
gap: ${({ theme }) => theme.spacing(1)};
padding-left: ${({ theme }) => theme.spacing(2)};
`;
export function TaskRow({ task }: { task: TaskForList }) {
const theme = useTheme();
const openActivityRightDrawer = useOpenActivityRightDrawer();
const { data: targetPeople } = useGetPeopleQuery({
variables: {
where: {
id: {
in: task?.activityTargets
? task?.activityTargets
.filter((target) => target.commentableType === 'Person')
.map((target) => target.commentableId ?? '')
: [],
},
},
},
});
const { data: targetCompanies } = useGetCompaniesQuery({
variables: {
where: {
id: {
in: task?.activityTargets
? task?.activityTargets
.filter((target) => target.commentableType === 'Company')
.map((target) => target.commentableId ?? '')
: [],
},
},
},
});
return (
<StyledContainer
onClick={() => {
openActivityRightDrawer(task.id);
}}
>
<div
onClick={(e) => {
e.stopPropagation();
}}
>
<Checkbox checked={false} shape={CheckboxShape.Rounded} />
</div>
<StyledTaskTitle>{task.title}</StyledTaskTitle>
{task.comments && task.comments.length > 0 && (
<StyledCommentIcon>
<IconComment size={theme.icon.size.md} />
</StyledCommentIcon>
)}
<StyledSeparator />
<ActivityTargetChips
targetCompanies={targetCompanies}
targetPeople={targetPeople}
/>
<StyledDueDate>
<IconCalendar size={theme.icon.size.md} />
{task.dueAt && beautifyExactDate(task.dueAt)}
</StyledDueDate>
</StyledContainer>
);
}
+47
View File
@@ -0,0 +1,47 @@
import { activeTabIdScopedState } from '@/ui/tab/states/activeTabIdScopedState';
import { useRecoilScopedState } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedState';
import { ActivityType, useGetActivitiesQuery } from '~/generated/graphql';
import { parseDate } from '~/utils/date-utils';
import { TasksContext } from '../states/TasksContext';
export function useTasks() {
const [activeTabId] = useRecoilScopedState(
activeTabIdScopedState,
TasksContext,
);
const { data, loading } = useGetActivitiesQuery({
variables: {
where: {
type: { equals: ActivityType.Task },
completedAt:
activeTabId === 'done' ? { not: { equals: null } } : { equals: null },
},
},
});
const todayTasks = data?.findManyActivities.filter((task) => {
if (!task.dueAt) {
return false;
}
const dueDate = parseDate(task.dueAt).toJSDate();
const today = new Date();
return dueDate.getDate() === today.getDate();
});
const otherTasks = data?.findManyActivities.filter((task) => {
if (!task.dueAt) {
return false;
}
const dueDate = parseDate(task.dueAt).toJSDate();
const today = new Date();
return dueDate.getDate() !== today.getDate();
});
return {
todayTasks,
otherTasks,
loading,
};
}
@@ -0,0 +1,3 @@
import { createContext } from 'react';
export const TasksContext = createContext<string | null>(null);
@@ -0,0 +1,3 @@
import { GetActivitiesQuery } from '~/generated/graphql';
export type TaskForList = GetActivitiesQuery['findManyActivities'][0];