From 1de7a2ebdd3a9cd090cea3d92cf9ea644d3d9cc0 Mon Sep 17 00:00:00 2001 From: Andrew P Maney Date: Sun, 22 Mar 2020 04:55:48 -0700 Subject: [PATCH] Tasks: implement sorting Merge of #104 --- src/components/Tasks/TaskList.tsx | 76 +++++++++++++++++++++++++++---- src/components/Tasks/Toolbar.tsx | 58 +++++++++++++++++++---- src/store/construct.ts | 14 ++++++ src/store/reducers.ts | 2 + 4 files changed, 132 insertions(+), 18 deletions(-) diff --git a/src/components/Tasks/TaskList.tsx b/src/components/Tasks/TaskList.tsx index 438be17..bfa93d8 100644 --- a/src/components/Tasks/TaskList.tsx +++ b/src/components/Tasks/TaskList.tsx @@ -3,8 +3,6 @@ import * as React from 'react'; -import { createSelector } from 'reselect'; - import * as EteSync from 'etesync'; import { List } from '../../widgets/List'; @@ -22,10 +20,72 @@ import Toolbar from './Toolbar'; import { StoreState } from '../../store'; -const sortSelector = createSelector( - (entries: TaskType[]) => entries, - (entries) => entries.sort((a, b) => a.title.localeCompare(b.title)) -); +function sortCompleted(a: TaskType, b: TaskType) { + return (!!a.finished === !!b.finished) ? 0 : (a.finished) ? 1 : -1; +} + +function sortLastModifiedDate(aIn: TaskType, bIn: TaskType) { + const a = aIn.lastModified?.toJSDate() ?? new Date(0); + const b = bIn.lastModified?.toJSDate() ?? new Date(0); + return (a > b) ? -1 : (a < b) ? 1 : 0; +} + +function sortDueDate(aIn: TaskType, bIn: TaskType) { + const impossiblyLargeDate = 8640000000000000; + const a = aIn.dueDate?.toJSDate() ?? new Date(impossiblyLargeDate); + const b = bIn.dueDate?.toJSDate() ?? new Date(impossiblyLargeDate); + return (a < b) ? -1 : (a > b) ? 1 : 0; +} + +function sortPriority(aIn: TaskType, bIn: TaskType) { + // Intentionally converts 0/undefined to Infinity to sort to back of the list + const a = aIn.priority || Infinity; + const b = bIn.priority || Infinity; + return a - b; +} + +function sortTitle(aIn: TaskType, bIn: TaskType) { + const a = aIn.title ?? ''; + const b = bIn.title ?? ''; + return a.localeCompare(b); +} + +function getSortFunction(sortOrder: string) { + const sortFunctions: (typeof sortTitle)[] = [sortCompleted]; + + switch (sortOrder) { + case 'smart': + sortFunctions.push(sortPriority); + sortFunctions.push(sortDueDate); + sortFunctions.push(sortTitle); + break; + case 'dueDate': + sortFunctions.push(sortDueDate); + break; + case 'priority': + sortFunctions.push(sortPriority); + break; + case 'title': + sortFunctions.push(sortTitle); + break; + case 'lastModifiedDate': + // Do nothing because it's the last sort function anyway + break; + } + + sortFunctions.push(sortLastModifiedDate); + + return (a: TaskType, b: TaskType) => { + for (const sortFunction of sortFunctions) { + const ret = sortFunction(a, b); + if (ret !== 0) { + return ret; + } + } + + return 0; + }; +} interface PropsType { entries: TaskType[]; @@ -37,7 +97,7 @@ interface PropsType { export default function TaskList(props: PropsType) { const [showCompleted, setShowCompleted] = React.useState(false); const settings = useSelector((state: StoreState) => state.settings.taskSettings); - const { filterBy } = settings; + const { filterBy, sortBy } = settings; const theme = useTheme(); const potentialEntries = React.useMemo( @@ -57,7 +117,7 @@ export default function TaskList(props: PropsType) { entries = potentialEntries; } - const sortedEntries = sortSelector(entries); + const sortedEntries = entries.sort(getSortFunction(sortBy)); const itemList = sortedEntries.map((entry) => { const uid = entry.uid; diff --git a/src/components/Tasks/Toolbar.tsx b/src/components/Tasks/Toolbar.tsx index 26c5c16..5737afb 100644 --- a/src/components/Tasks/Toolbar.tsx +++ b/src/components/Tasks/Toolbar.tsx @@ -8,11 +8,17 @@ import IconButton from '@material-ui/core/IconButton'; import MoreVertIcon from '@material-ui/icons/MoreVert'; import Menu from '@material-ui/core/Menu'; import MenuItem from '@material-ui/core/MenuItem'; +import SortIcon from '@material-ui/icons/Sort'; import QuickAdd from './QuickAdd'; import { PimType } from '../../pim-types'; +import { useSelector, useDispatch } from 'react-redux'; + +import { setSettings } from '../../store/actions'; +import { StoreState } from '../../store'; + interface PropsType { defaultCollection: EteSync.CollectionInfo; onItemSave: (item: PimType, journalUid: string, originalItem?: PimType) => Promise; @@ -23,16 +29,24 @@ interface PropsType { export default function Toolbar(props: PropsType) { const { defaultCollection, onItemSave, showCompleted, setShowCompleted } = props; - const [anchorEl, setAnchorEl] = React.useState(null); + const [sortAnchorEl, setSortAnchorEl] = React.useState(null); + const [optionsAnchorEl, setOptionsAnchorEl] = React.useState(null); - const handleClick = (event: React.MouseEvent) => { - setAnchorEl(event.currentTarget); - }; + const dispatch = useDispatch(); + const taskSettings = useSelector((state: StoreState) => state.settings.taskSettings); + const { sortBy } = taskSettings; - const handleClose = () => { - setAnchorEl(null); + const handleSortChange = (sort: string) => { + dispatch(setSettings({ taskSettings: { ...taskSettings, sortBy: sort } })); + setSortAnchorEl(null); }; + const SortMenuItem = React.forwardRef(function SortMenuItem(props: { name: string, label: string }, ref) { + return ( + handleSortChange(props.name)}>{props.label} + ); + }); + return (
{defaultCollection && } @@ -42,16 +56,40 @@ export default function Toolbar(props: PropsType) { aria-label="more" aria-controls="long-menu" aria-haspopup="true" - onClick={handleClick} + onClick={(e) => setSortAnchorEl(e.currentTarget)} + > + + + setSortAnchorEl(null)} + > + + + + + + +
+ + +
+ setOptionsAnchorEl(e.currentTarget)} > setOptionsAnchorEl(null)} > ; } +const settingsMigrations = { + 0: (state: any) => { + return { + ...state, + taskSettings: { + filterBy: null, + sortBy: 'smart', + }, + }; + }, +}; + const settingsPersistConfig = { key: 'settings', + version: 0, storage: localforage, + migrate: createMigrate(settingsMigrations, { debug: false }), }; const credentialsMigrations = { diff --git a/src/store/reducers.ts b/src/store/reducers.ts index 9480459..3915146 100644 --- a/src/store/reducers.ts +++ b/src/store/reducers.ts @@ -274,6 +274,7 @@ export interface SettingsType { locale: string; taskSettings: { filterBy: string | null; + sortBy: string; }; } @@ -287,6 +288,7 @@ export const settingsReducer = handleActions( locale: 'en-gb', taskSettings: { filterBy: null, + sortBy: 'smart', }, } );