Show Entity task/note tabs (#1282)
* - show task tab - tab bar * - add notes tab * - fixed unused style * - add button - fixed company edit note test * - fixed merge & dropdown * - added Tests - refactored directory structure activities - moved Task/Note Pages to corresponding modules - fixed TabList * lint
This commit is contained in:
@ -1,79 +0,0 @@
|
||||
import { useTheme } from '@emotion/react';
|
||||
import styled from '@emotion/styled';
|
||||
import { IconCheckbox } from '@tabler/icons-react';
|
||||
|
||||
import { Button, ButtonVariant } from '@/ui/button/components/Button';
|
||||
import { ActivityType } from '~/generated/graphql';
|
||||
|
||||
import { useOpenCreateActivityDrawer } from '../hooks/useOpenCreateActivityDrawer';
|
||||
import { useTasks } from '../hooks/useTasks';
|
||||
|
||||
import { TaskList } from './TaskList';
|
||||
|
||||
const StyledTaskGroupEmptyContainer = styled.div`
|
||||
align-items: center;
|
||||
align-self: stretch;
|
||||
display: flex;
|
||||
flex: 1 0 0;
|
||||
flex-direction: column;
|
||||
gap: ${({ theme }) => theme.spacing(2)};
|
||||
justify-content: center;
|
||||
padding-bottom: ${({ theme }) => theme.spacing(16)};
|
||||
padding-left: ${({ theme }) => theme.spacing(4)};
|
||||
padding-right: ${({ theme }) => theme.spacing(4)};
|
||||
padding-top: ${({ theme }) => theme.spacing(3)};
|
||||
`;
|
||||
|
||||
const StyledEmptyTaskGroupTitle = styled.div`
|
||||
color: ${({ theme }) => theme.font.color.secondary};
|
||||
font-size: ${({ theme }) => theme.font.size.xxl};
|
||||
font-weight: ${({ theme }) => theme.font.weight.semiBold};
|
||||
line-height: ${({ theme }) => theme.text.lineHeight.md};
|
||||
`;
|
||||
|
||||
const StyledEmptyTaskGroupSubTitle = styled.div`
|
||||
color: ${({ theme }) => theme.font.color.extraLight};
|
||||
font-size: ${({ theme }) => theme.font.size.xxl};
|
||||
font-weight: ${({ theme }) => theme.font.weight.semiBold};
|
||||
line-height: ${({ theme }) => theme.text.lineHeight.md};
|
||||
margin-bottom: ${({ theme }) => theme.spacing(2)};
|
||||
`;
|
||||
|
||||
const StyledContainer = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
`;
|
||||
|
||||
export function TaskGroups() {
|
||||
const { todayOrPreviousTasks, upcomingTasks, unscheduledTasks } = useTasks();
|
||||
const theme = useTheme();
|
||||
|
||||
const openCreateActivity = useOpenCreateActivityDrawer();
|
||||
|
||||
if (
|
||||
todayOrPreviousTasks?.length === 0 &&
|
||||
upcomingTasks?.length === 0 &&
|
||||
unscheduledTasks?.length === 0
|
||||
) {
|
||||
return (
|
||||
<StyledTaskGroupEmptyContainer>
|
||||
<StyledEmptyTaskGroupTitle>No task yet</StyledEmptyTaskGroupTitle>
|
||||
<StyledEmptyTaskGroupSubTitle>Create one:</StyledEmptyTaskGroupSubTitle>
|
||||
<Button
|
||||
icon={<IconCheckbox size={theme.icon.size.sm} />}
|
||||
title="New task"
|
||||
variant={ButtonVariant.Secondary}
|
||||
onClick={() => openCreateActivity(ActivityType.Task)}
|
||||
/>
|
||||
</StyledTaskGroupEmptyContainer>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<StyledContainer>
|
||||
<TaskList title="Today" tasks={todayOrPreviousTasks ?? []} />
|
||||
<TaskList title="Upcoming" tasks={upcomingTasks ?? []} />
|
||||
<TaskList title="Unscheduled" tasks={unscheduledTasks ?? []} />
|
||||
</StyledContainer>
|
||||
);
|
||||
}
|
||||
@ -1,57 +0,0 @@
|
||||
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};
|
||||
font-weight: ${({ theme }) => theme.font.weight.semiBold};
|
||||
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%;
|
||||
`;
|
||||
|
||||
export function TaskList({ title, tasks }: OwnProps) {
|
||||
return (
|
||||
<>
|
||||
{tasks && tasks.length > 0 && (
|
||||
<StyledContainer>
|
||||
<StyledTitle>
|
||||
{title} <StyledCount>{tasks.length}</StyledCount>
|
||||
</StyledTitle>
|
||||
<StyledTaskRows>
|
||||
{tasks.map((task) => (
|
||||
<TaskRow key={task.id} task={task} />
|
||||
))}
|
||||
</StyledTaskRows>
|
||||
</StyledContainer>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
@ -1,115 +0,0 @@
|
||||
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 { OverflowingTextWithTooltip } from '@/ui/tooltip/OverflowingTextWithTooltip';
|
||||
import { beautifyExactDate, hasDatePassed } from '~/utils/date-utils';
|
||||
|
||||
import { useCompleteTask } from '../hooks/useCompleteTask';
|
||||
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: inline-flex;
|
||||
height: ${({ theme }) => theme.spacing(12)};
|
||||
min-width: calc(100% - ${({ theme }) => theme.spacing(8)});
|
||||
padding: 0 ${({ theme }) => theme.spacing(4)};
|
||||
`;
|
||||
|
||||
const StyledTaskBody = styled.div`
|
||||
color: ${({ theme }) => theme.font.color.tertiary};
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-grow: 1;
|
||||
width: 1px;
|
||||
`;
|
||||
|
||||
const StyledTaskTitle = styled.div<{
|
||||
completed: boolean;
|
||||
}>`
|
||||
color: ${({ theme }) => theme.font.color.primary};
|
||||
font-weight: ${({ theme }) => theme.font.weight.medium};
|
||||
padding: 0 ${({ theme }) => theme.spacing(2)};
|
||||
text-decoration: ${({ completed }) => (completed ? 'line-through' : 'none')};
|
||||
`;
|
||||
|
||||
const StyledCommentIcon = styled.div`
|
||||
align-items: center;
|
||||
color: ${({ theme }) => theme.font.color.light};
|
||||
display: flex;
|
||||
margin-left: ${({ theme }) => theme.spacing(2)};
|
||||
`;
|
||||
|
||||
const StyledDueDate = styled.div<{
|
||||
isPast: boolean;
|
||||
}>`
|
||||
align-items: center;
|
||||
color: ${({ theme, isPast }) =>
|
||||
isPast ? theme.font.color.danger : theme.font.color.secondary};
|
||||
display: flex;
|
||||
gap: ${({ theme }) => theme.spacing(1)};
|
||||
padding-left: ${({ theme }) => theme.spacing(2)};
|
||||
`;
|
||||
|
||||
const StyledFieldsContainer = styled.div`
|
||||
display: flex;
|
||||
`;
|
||||
|
||||
export function TaskRow({ task }: { task: TaskForList }) {
|
||||
const theme = useTheme();
|
||||
const openActivityRightDrawer = useOpenActivityRightDrawer();
|
||||
|
||||
const body = JSON.parse(task.body ?? '{}')[0]?.content[0]?.text;
|
||||
const { completeTask } = useCompleteTask(task);
|
||||
|
||||
return (
|
||||
<StyledContainer
|
||||
onClick={() => {
|
||||
openActivityRightDrawer(task.id);
|
||||
}}
|
||||
>
|
||||
<div
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
}}
|
||||
>
|
||||
<Checkbox
|
||||
checked={!!task.completedAt}
|
||||
shape={CheckboxShape.Rounded}
|
||||
onCheckedChange={completeTask}
|
||||
/>
|
||||
</div>
|
||||
<StyledTaskTitle completed={task.completedAt !== null}>
|
||||
{task.title ?? 'Task Title'}
|
||||
</StyledTaskTitle>
|
||||
<StyledTaskBody>
|
||||
<OverflowingTextWithTooltip text={body} />
|
||||
{task.comments && task.comments.length > 0 && (
|
||||
<StyledCommentIcon>
|
||||
<IconComment size={theme.icon.size.md} />
|
||||
</StyledCommentIcon>
|
||||
)}
|
||||
</StyledTaskBody>
|
||||
<StyledFieldsContainer>
|
||||
<ActivityTargetChips targets={task.activityTargets} />
|
||||
<StyledDueDate
|
||||
isPast={
|
||||
!!task.dueAt && hasDatePassed(task.dueAt) && !task.completedAt
|
||||
}
|
||||
>
|
||||
<IconCalendar size={theme.icon.size.md} />
|
||||
{task.dueAt && beautifyExactDate(task.dueAt)}
|
||||
</StyledDueDate>
|
||||
</StyledFieldsContainer>
|
||||
</StyledContainer>
|
||||
);
|
||||
}
|
||||
@ -1,23 +0,0 @@
|
||||
import type { Meta, StoryObj } from '@storybook/react';
|
||||
|
||||
import { TasksRecoilScopeContext } from '@/activities/states/recoil-scope-contexts/TasksRecoilScopeContext';
|
||||
import { ComponentWithRecoilScopeDecorator } from '~/testing/decorators/ComponentWithRecoilScopeDecorator';
|
||||
import { ComponentWithRouterDecorator } from '~/testing/decorators/ComponentWithRouterDecorator';
|
||||
import { graphqlMocks } from '~/testing/graphqlMocks';
|
||||
|
||||
import { TaskGroups } from '../TaskGroups';
|
||||
|
||||
const meta: Meta<typeof TaskGroups> = {
|
||||
title: 'Modules/Activity/TaskGroups',
|
||||
component: TaskGroups,
|
||||
decorators: [ComponentWithRouterDecorator, ComponentWithRecoilScopeDecorator],
|
||||
parameters: {
|
||||
msw: graphqlMocks,
|
||||
recoilScopeContext: TasksRecoilScopeContext,
|
||||
},
|
||||
};
|
||||
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof TaskGroups>;
|
||||
|
||||
export const Default: Story = {};
|
||||
@ -1,47 +0,0 @@
|
||||
import { getOperationName } from '@apollo/client/utilities';
|
||||
import type { Meta, StoryObj } from '@storybook/react';
|
||||
import { graphql } from 'msw';
|
||||
|
||||
import { GET_ACTIVITIES } from '@/activities/graphql/queries/getActivities';
|
||||
import { TasksRecoilScopeContext } from '@/activities/states/recoil-scope-contexts/TasksRecoilScopeContext';
|
||||
import { ComponentWithRecoilScopeDecorator } from '~/testing/decorators/ComponentWithRecoilScopeDecorator';
|
||||
import { ComponentWithRouterDecorator } from '~/testing/decorators/ComponentWithRouterDecorator';
|
||||
import { graphqlMocks } from '~/testing/graphqlMocks';
|
||||
|
||||
import { TaskGroups } from '../TaskGroups';
|
||||
|
||||
const meta: Meta<typeof TaskGroups> = {
|
||||
title: 'Modules/Activity/TaskGroupsWithoutTasks',
|
||||
component: TaskGroups,
|
||||
decorators: [ComponentWithRouterDecorator, ComponentWithRecoilScopeDecorator],
|
||||
parameters: {
|
||||
msw: graphqlMocks,
|
||||
recoilScopeContext: TasksRecoilScopeContext,
|
||||
},
|
||||
};
|
||||
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof TaskGroups>;
|
||||
|
||||
export const Default: Story = {
|
||||
parameters: {
|
||||
msw: [
|
||||
...graphqlMocks.filter(
|
||||
(graphqlMock) =>
|
||||
graphqlMock.info.operationName !== getOperationName(GET_ACTIVITIES),
|
||||
),
|
||||
...[
|
||||
graphql.query(
|
||||
getOperationName(GET_ACTIVITIES) ?? '',
|
||||
(_req, res, ctx) => {
|
||||
return res(
|
||||
ctx.data({
|
||||
findManyActivities: [],
|
||||
}),
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
],
|
||||
},
|
||||
};
|
||||
@ -1,38 +0,0 @@
|
||||
import { MemoryRouter } from 'react-router-dom';
|
||||
import type { Meta, StoryObj } from '@storybook/react';
|
||||
|
||||
import { ComponentDecorator } from '~/testing/decorators/ComponentDecorator';
|
||||
import { graphqlMocks } from '~/testing/graphqlMocks';
|
||||
import { mockedActivities } from '~/testing/mock-data/activities';
|
||||
|
||||
import { TaskList } from '../TaskList';
|
||||
|
||||
const meta: Meta<typeof TaskList> = {
|
||||
title: 'Modules/Activity/TaskList',
|
||||
component: TaskList,
|
||||
decorators: [
|
||||
(Story) => (
|
||||
<MemoryRouter>
|
||||
<Story />
|
||||
</MemoryRouter>
|
||||
),
|
||||
ComponentDecorator,
|
||||
],
|
||||
args: {
|
||||
title: 'Tasks',
|
||||
tasks: mockedActivities,
|
||||
},
|
||||
parameters: {
|
||||
msw: graphqlMocks,
|
||||
},
|
||||
};
|
||||
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof TaskList>;
|
||||
|
||||
export const Default: Story = {
|
||||
args: {
|
||||
title: 'Tasks',
|
||||
tasks: mockedActivities,
|
||||
},
|
||||
};
|
||||
Reference in New Issue
Block a user