From f593d558f81c3f8b3cd85a4e00832fbee3a4b85f Mon Sep 17 00:00:00 2001 From: Nathan Arthur Date: Tue, 8 Nov 2022 19:15:34 -0500 Subject: [PATCH] tabs --- global-setup.ts | 1 + src/App.tsx | 11 ++++- src/components/organisms/NavBar.tsx | 46 +++++++++++++++----- src/components/organisms/TaskList.tsx | 5 +-- src/components/pages/Archive.spec.tsx | 40 ++++++++++++++---- src/components/pages/Archive.tsx | 61 ++++++++++++++++++++++++++- src/lib/createListItems.tsx | 21 +++++++-- src/lib/isTask.ts | 3 ++ 8 files changed, 157 insertions(+), 31 deletions(-) create mode 100644 src/lib/isTask.ts diff --git a/global-setup.ts b/global-setup.ts index 60eada11..ca51792a 100644 --- a/global-setup.ts +++ b/global-setup.ts @@ -28,6 +28,7 @@ vi.mock('./src/lib/api/getTasks'); vi.mock('./src/lib/api/getMe'); vi.mock('./src/lib/api/updateTask'); vi.mock('./src/lib/api/addTask'); +vi.mock('react-list'); global.scrollTo = vi.fn() as any; diff --git a/src/App.tsx b/src/App.tsx index 3a30411d..6b61ac94 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -21,6 +21,7 @@ import { ErrorBoundary } from '@highlight-run/react'; import Account from './components/pages/Account'; import Tasks from './components/pages/Tasks'; import ga from './lib/ga'; +import Archive from './components/pages/Archive'; toast.configure(); @@ -86,7 +87,15 @@ export function App(): JSX.Element { > maybe} /> - archive} /> + + + + + } + /> } /> diff --git a/src/components/organisms/NavBar.tsx b/src/components/organisms/NavBar.tsx index abcb0877..674296d6 100644 --- a/src/components/organisms/NavBar.tsx +++ b/src/components/organisms/NavBar.tsx @@ -8,6 +8,8 @@ import { Box, Button, IconButton, + Tab, + Tabs, Toolbar, Tooltip, } from '@mui/material'; @@ -31,6 +33,7 @@ export default function NavBar(): JSX.Element { sx={{ justifyContent: 'space-between', }} + variant="dense" > - - - - - + + + + + diff --git a/src/components/organisms/TaskList.tsx b/src/components/organisms/TaskList.tsx index 98f19ec8..884e5dc0 100644 --- a/src/components/organisms/TaskList.tsx +++ b/src/components/organisms/TaskList.tsx @@ -6,15 +6,12 @@ import { Alert, AlertTitle, ListSubheader } from '@mui/material'; import Task from '../molecules/Task'; import useFilters from '../../lib/useFilters'; import browser from '../../lib/Browser'; +import isTask from '../../lib/isTask'; interface TaskListProps { newTask?: TaskType; } -function isTask(value: unknown): value is TaskType { - return Object.prototype.hasOwnProperty.call(value || {}, 'task'); -} - const TaskList = ({ newTask }: TaskListProps): JSX.Element => { const { data: tasks, isFetched } = useTasks(); const { filters } = useFilters(); diff --git a/src/components/pages/Archive.spec.tsx b/src/components/pages/Archive.spec.tsx index 5fe3812f..9623c710 100644 --- a/src/components/pages/Archive.spec.tsx +++ b/src/components/pages/Archive.spec.tsx @@ -1,4 +1,4 @@ -import { vi, Mock, describe, it, expect, beforeEach } from 'vitest'; +import { vi, describe, it, expect } from 'vitest'; import { renderWithQueryProvider } from '../../lib/test/renderWithQueryProvider'; import Archive from './Archive'; import React from 'react'; @@ -11,15 +11,37 @@ describe('Archive', () => { renderWithQueryProvider(); }); - // it('lists old tasks', () => { - // vi.setSystemTime(new Date('2021-01-01T00:00:00.000Z')); + it('lists old tasks', async () => { + vi.setSystemTime(new Date('2021-01-01T00:00:00.000Z')); - // loadTasksApiData({ - // tasks: [makeTask({ id: 'old_task', due: '2020-12-31T00:00:00.000Z' })], - // }); + loadTasksApiData({ + tasks: [makeTask({ task: 'old_task', due: '5/22/2020, 11:59 PM' })], + }); - // renderWithQueryProvider(); + renderWithQueryProvider(); + + expect(await screen.findByText('old_task')).toBeInTheDocument(); + }); - // expect(screen.getByText('old_task')).toBeInTheDocument(); - // }); + it('lists tasks in reverse chronological order', async () => { + vi.setSystemTime(new Date('2021-01-01T00:00:00.000Z')); + + loadTasksApiData({ + tasks: [ + makeTask({ task: 'task', due: '5/21/2020, 11:59 PM' }), + makeTask({ task: 'task', due: '5/22/2020, 11:59 PM' }), + ], + }); + + renderWithQueryProvider(); + + await screen.findByText(/5\/21/); + await screen.findByText(/5\/22/); + + const tasks = await screen.findAllByText(/11:59 PM/); + + expect(tasks).toHaveLength(2); + + expect(tasks[0]).toHaveTextContent('5/22'); + }); }); diff --git a/src/components/pages/Archive.tsx b/src/components/pages/Archive.tsx index fc69c8a6..16b24af9 100644 --- a/src/components/pages/Archive.tsx +++ b/src/components/pages/Archive.tsx @@ -1,5 +1,62 @@ -import React from 'react'; +import React, { useEffect, useRef, useState } from 'react'; +import { useTasks } from '../../lib/api/useTasks'; +import createListItems from '../../lib/createListItems'; +import ReactList from 'react-list'; +import { Alert, AlertTitle, ListSubheader } from '@mui/material'; +import Task from '../molecules/Task'; +import useFilters from '../../lib/useFilters'; +import browser from '../../lib/Browser'; +import isTask from '../../lib/isTask'; export default function Archive(): JSX.Element { - return <>Archive; + const { data: tasks, isFetched } = useTasks(); + const { filters } = useFilters(); + const listRef = useRef(null); + const [entries, setEntries] = useState<(TaskType | string)[]>([]); + + useEffect(() => { + const filtered = filters + ? (tasks ?? []).filter((t) => filters[t.status]) + : tasks ?? []; + + const { entries: e } = createListItems({ + tasks: filtered, + maxDue: browser.getLastMidnight(), + reverse: true, + }); + + setEntries(e); + }, [tasks, filters]); + + return ( + <> + {isFetched && !(tasks || []).length && ( + + Nothing here! + Maybe add a task? + + )} + { + const entry = entries[i]; + return isTask(entry) ? ( + + ) : ( + + {entry} + + ); + }} + itemSizeEstimator={(i) => (isTask(entries[i]) ? 60 : 48)} + length={entries.length} + type={'variable'} + ref={listRef} + /> + + ); } diff --git a/src/lib/createListItems.tsx b/src/lib/createListItems.tsx index 2e8cd166..1f50bf83 100644 --- a/src/lib/createListItems.tsx +++ b/src/lib/createListItems.tsx @@ -15,21 +15,34 @@ type Entries = (TaskType | string)[]; type Options = { tasks: TaskType[]; - newTask: TaskType | undefined; - minDue: Date; + newTask?: TaskType; + minDue?: Date; + maxDue?: Date; + reverse?: boolean; }; -export default function createListItems({ tasks, newTask, minDue }: Options): { +export default function createListItems({ + tasks, + newTask, + minDue, + maxDue, + reverse, +}: Options): { entries: Entries; newTaskIndex: number | undefined; } { const sortedTasks = sortTasks(tasks); + if (reverse) { + sortedTasks.reverse(); + } + let lastTitle: string; let newTaskIndex: number | undefined = undefined; const entries = sortedTasks.reduce((acc: Entries, t: TaskType): Entries => { - if (new Date(t.due) < minDue) return acc; + if (minDue && new Date(t.due) < minDue) return acc; + if (maxDue && new Date(t.due) > maxDue) return acc; const title = makeTitle(t); const shouldAddHeading = title !== lastTitle || !acc.length; diff --git a/src/lib/isTask.ts b/src/lib/isTask.ts new file mode 100644 index 00000000..7fa7a380 --- /dev/null +++ b/src/lib/isTask.ts @@ -0,0 +1,3 @@ +export default function isTask(value: unknown): value is TaskType { + return Object.prototype.hasOwnProperty.call(value || {}, 'task'); +}