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:
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -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];
|
||||
Reference in New Issue
Block a user