// SPDX-FileCopyrightText: © 2017 EteSync Authors // SPDX-License-Identifier: AGPL-3.0-only import * as React from 'react'; import * as EteSync from 'etesync'; import { List } from '../../widgets/List'; import { TaskType, PimType } from '../../pim-types'; import Divider from '@material-ui/core/Divider'; import Grid from '@material-ui/core/Grid'; import { useTheme, makeStyles } from '@material-ui/core/styles'; import { useSelector } from 'react-redux'; import Fuse from 'fuse.js'; import TaskListItem from './TaskListItem'; import Sidebar from './Sidebar'; import Toolbar from './Toolbar'; import QuickAdd from './QuickAdd'; import { StoreState } from '../../store'; 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 10 (1 more than lowest priority) to sort to back of the list const a = aIn.priority || 10; const b = bIn.priority || 10; 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); sortFunctions.push(sortDueDate); 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; }; } const useStyles = makeStyles((theme) => ({ topBar: { backgroundColor: theme.palette.primary[500], }, })); interface PropsType { entries: TaskType[]; collections: EteSync.CollectionInfo[]; onItemClick: (entry: TaskType) => void; onItemSave: (item: PimType, journalUid: string, originalItem?: PimType) => Promise; } export default function TaskList(props: PropsType) { const [showCompleted, setShowCompleted] = React.useState(false); const [showHidden, setShowHidden] = React.useState(false); const [searchTerm, setSearchTerm] = React.useState(''); const settings = useSelector((state: StoreState) => state.settings.taskSettings); const { filterBy, sortBy } = settings; const theme = useTheme(); const classes = useStyles(); const potentialEntries = React.useMemo( () => { if (searchTerm) { const result = new Fuse(props.entries, { shouldSort: true, threshold: 0.6, maxPatternLength: 32, minMatchCharLength: 2, keys: [ 'title', 'desc', ], }).search(searchTerm); return result.map((x) => x.item); } else { return props.entries.filter((x) => (showCompleted || !x.finished) && (showHidden || !x.hidden)); } }, [showCompleted, props.entries, searchTerm, showHidden] ); let entries; const tagPrefix = 'tag:'; if (filterBy?.startsWith(tagPrefix)) { const tag = filterBy.slice(tagPrefix.length); entries = potentialEntries.filter((x) => x.tags.includes(tag)); } else if (filterBy === 'today') { entries = potentialEntries.filter((x) => x.dueToday); } else { entries = potentialEntries; } const sortedEntries = entries.sort(getSortFunction(sortBy)); const itemList = sortedEntries.map((entry) => { const uid = entry.uid; return ( ); }); return ( {/* spacer */} {props.collections?.[0] && } {itemList} ); }