Tasks: adds filter by tag feature

Squash of #94
master
Tom Hacohen 5 years ago
parent a2d6dacbe4
commit b14697474c

@ -0,0 +1,62 @@
import * as React from 'react';
import { useSelector, useDispatch } from 'react-redux';
import InboxIcon from '@material-ui/icons/Inbox';
import LabelIcon from '@material-ui/icons/LabelOutlined';
import { setSettings } from '../../store/actions';
import { StoreState } from '../../store';
import { List, ListItem, ListSubheader } from '../../widgets/List';
interface ListItemPropsType {
name: string | null;
icon?: React.ReactElement;
primaryText: string;
amount?: number;
}
function SidebarListItem(props: ListItemPropsType) {
const { name, icon, primaryText, amount } = props;
const dispatch = useDispatch();
const taskSettings = useSelector((state: StoreState) => state.settings.taskSettings);
const { filterBy } = taskSettings;
const handleClick = () => {
dispatch(setSettings({ taskSettings: { ...taskSettings, filterBy: name } }));
};
return (
<ListItem
onClick={handleClick}
selected={name === filterBy}
leftIcon={icon}
rightIcon={<span style={{ width: '100%', textAlign: 'right' }}>{amount}</span>}
primaryText={primaryText}
/>
);
}
export default function Sidebar(props: { tags: Map<string, number>, totalTasks: number }) {
const { tags, totalTasks } = props;
const tagsList = [...tags].sort(([a], [b]) => a.localeCompare(b)).map(([tag, amount]) => (
<SidebarListItem
key={tag}
name={`tag:${tag}`}
primaryText={tag}
icon={<LabelIcon />}
amount={amount}
/>
));
return (
<List dense>
<SidebarListItem name={null} primaryText="All" icon={<InboxIcon />} amount={totalTasks} />
<ListSubheader>Tags</ListSubheader>
{tagsList}
</List>
);
}

@ -13,9 +13,16 @@ import { TaskType, PimType } from '../../pim-types';
import FormControlLabel from '@material-ui/core/FormControlLabel';
import Checkbox from '@material-ui/core/Checkbox';
import Divider from '@material-ui/core/Divider';
import Grid from '@material-ui/core/Grid';
import { useTheme } from '@material-ui/core/styles';
import { useSelector } from 'react-redux';
import TaskListItem from './TaskListItem';
import QuickAdd from './QuickAdd';
import Sidebar from './Sidebar';
import { StoreState } from '../../store';
const sortSelector = createSelector(
(entries: TaskType[]) => entries,
@ -31,7 +38,26 @@ interface PropsType {
export default React.memo(function TaskList(props: PropsType) {
const [showCompleted, setShowCompleted] = React.useState(false);
const entries = props.entries.filter((x) => showCompleted || !x.finished);
const settings = useSelector((state: StoreState) => state.settings.taskSettings);
const { filterBy } = settings;
const theme = useTheme();
const potentialEntries = props.entries.filter((x) => showCompleted || !x.finished);
let entries;
const tagPrefix = 'tag:';
if (filterBy?.startsWith(tagPrefix)) {
const tag = filterBy.slice(tagPrefix.length);
entries = potentialEntries.filter((x) => x.tags.includes(tag));
} else {
entries = potentialEntries;
}
// TODO: memoize
const tags = new Map<string, number>();
potentialEntries.forEach((entry) => entry.tags.forEach((tag) => {
tags.set(tag, (tags.get(tag) ?? 0) + 1);
}));
const sortedEntries = sortSelector(entries);
const itemList = sortedEntries.map((entry) => {
@ -48,8 +74,13 @@ export default React.memo(function TaskList(props: PropsType) {
});
return (
<>
<Grid container spacing={4}>
<Grid item xs={3} style={{ borderRight: `1px solid ${theme.palette.divider}` }}>
<Sidebar totalTasks={potentialEntries.length} tags={tags} />
</Grid>
<Grid item xs>
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
{props.collections && <QuickAdd onSubmit={props.onItemSave} defaultCollection={props.collections[0]} />}
@ -65,6 +96,7 @@ export default React.memo(function TaskList(props: PropsType) {
<List>
{itemList}
</List>
</>
</Grid>
</Grid>
);
});

@ -28,13 +28,13 @@ export type UserInfoData = EteSync.UserInfo;
export const encryptionKeyReducer = handleActions(
{
[actions.deriveKey.toString()]: (_state: {key: string | null}, action: any) => (
[actions.deriveKey.toString()]: (_state: { key: string | null }, action: any) => (
{ key: action.payload }
),
[actions.resetKey.toString()]: (_state: {key: string | null}, _action: any) => (
[actions.resetKey.toString()]: (_state: { key: string | null }, _action: any) => (
{ key: null }
),
[actions.logout.toString()]: (_state: {key: string | null}, _action: any) => {
[actions.logout.toString()]: (_state: { key: string | null }, _action: any) => {
return { out: true, key: null };
},
},
@ -272,6 +272,9 @@ export const errorsReducer = handleActions(
// FIXME Move all the below (potentially the fetchCount ones too) to their own file
export interface SettingsType {
locale: string;
taskSettings: {
filterBy: string | null;
};
}
export const settingsReducer = handleActions(
@ -280,5 +283,10 @@ export const settingsReducer = handleActions(
{ ...state, ...action.payload }
),
},
{ locale: 'en-gb' }
{
locale: 'en-gb',
taskSettings: {
filterBy: null,
},
}
);

@ -45,6 +45,7 @@ interface ListItemPropsType {
href?: string;
insetChildren?: boolean;
nestedItems?: React.ReactNode[];
selected?: boolean;
}
export const ListItem = React.memo(function ListItem(_props: ListItemPropsType) {
@ -60,6 +61,7 @@ export const ListItem = React.memo(function ListItem(_props: ListItemPropsType)
style,
insetChildren,
nestedItems,
selected,
} = _props;
const extraProps = (onClick || href) ? {
@ -74,6 +76,7 @@ export const ListItem = React.memo(function ListItem(_props: ListItemPropsType)
<MuiListItem
style={style}
onClick={onClick}
selected={selected}
{...(extraProps as any)}
>
{leftIcon && (

Loading…
Cancel
Save