parent
b69b51f558
commit
1de7a2ebdd
|
@ -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;
|
||||
|
|
|
@ -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<void>;
|
||||
|
@ -23,15 +29,23 @@ interface PropsType {
|
|||
export default function Toolbar(props: PropsType) {
|
||||
const { defaultCollection, onItemSave, showCompleted, setShowCompleted } = props;
|
||||
|
||||
const [anchorEl, setAnchorEl] = React.useState<null | HTMLElement>(null);
|
||||
const [sortAnchorEl, setSortAnchorEl] = React.useState<null | HTMLElement>(null);
|
||||
const [optionsAnchorEl, setOptionsAnchorEl] = React.useState<null | HTMLElement>(null);
|
||||
|
||||
const handleClick = (event: React.MouseEvent<HTMLElement>) => {
|
||||
setAnchorEl(event.currentTarget);
|
||||
const dispatch = useDispatch();
|
||||
const taskSettings = useSelector((state: StoreState) => state.settings.taskSettings);
|
||||
const { sortBy } = taskSettings;
|
||||
|
||||
const handleSortChange = (sort: string) => {
|
||||
dispatch(setSettings({ taskSettings: { ...taskSettings, sortBy: sort } }));
|
||||
setSortAnchorEl(null);
|
||||
};
|
||||
|
||||
const handleClose = () => {
|
||||
setAnchorEl(null);
|
||||
};
|
||||
const SortMenuItem = React.forwardRef(function SortMenuItem(props: { name: string, label: string }, ref) {
|
||||
return (
|
||||
<MenuItem innerRef={ref} selected={sortBy === props.name} onClick={() => handleSortChange(props.name)}>{props.label}</MenuItem>
|
||||
);
|
||||
});
|
||||
|
||||
return (
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
|
||||
|
@ -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)}
|
||||
>
|
||||
<SortIcon />
|
||||
</IconButton>
|
||||
<Menu
|
||||
anchorEl={sortAnchorEl}
|
||||
keepMounted
|
||||
open={!!sortAnchorEl}
|
||||
onClose={() => setSortAnchorEl(null)}
|
||||
>
|
||||
<SortMenuItem name="smart" label="Smart" />
|
||||
<SortMenuItem name="dueDate" label="Due Date" />
|
||||
<SortMenuItem name="priority" label="Priority" />
|
||||
<SortMenuItem name="title" label="Title" />
|
||||
<SortMenuItem name="lastModifiedDate" label="Last Modified" />
|
||||
</Menu>
|
||||
</div>
|
||||
|
||||
|
||||
<div>
|
||||
<IconButton
|
||||
aria-label="more"
|
||||
aria-controls="long-menu"
|
||||
aria-haspopup="true"
|
||||
onClick={(e) => setOptionsAnchorEl(e.currentTarget)}
|
||||
>
|
||||
<MoreVertIcon />
|
||||
</IconButton>
|
||||
<Menu
|
||||
id="simple-menu"
|
||||
anchorEl={anchorEl}
|
||||
anchorEl={optionsAnchorEl}
|
||||
keepMounted
|
||||
open={!!anchorEl}
|
||||
onClose={handleClose}
|
||||
open={!!optionsAnchorEl}
|
||||
onClose={() => setOptionsAnchorEl(null)}
|
||||
>
|
||||
<MenuItem>
|
||||
<FormControlLabel
|
||||
|
|
|
@ -28,9 +28,23 @@ export interface StoreState {
|
|||
errors: List<Error>;
|
||||
}
|
||||
|
||||
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 = {
|
||||
|
|
|
@ -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',
|
||||
},
|
||||
}
|
||||
);
|
||||
|
|
Loading…
Reference in New Issue