Tasks: adds filter by tag feature

Squash of #94
master
Andrew P Maney 5 years ago committed by Tom Hacohen
parent ec9d8d3329
commit effd74e0b2

@ -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 FormControlLabel from '@material-ui/core/FormControlLabel';
import Checkbox from '@material-ui/core/Checkbox'; import Checkbox from '@material-ui/core/Checkbox';
import Divider from '@material-ui/core/Divider'; 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 TaskListItem from './TaskListItem';
import QuickAdd from './QuickAdd'; import QuickAdd from './QuickAdd';
import Sidebar from './Sidebar';
import { StoreState } from '../../store';
const sortSelector = createSelector( const sortSelector = createSelector(
(entries: TaskType[]) => entries, (entries: TaskType[]) => entries,
@ -31,7 +38,26 @@ interface PropsType {
export default React.memo(function TaskList(props: PropsType) { export default React.memo(function TaskList(props: PropsType) {
const [showCompleted, setShowCompleted] = React.useState(false); 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 sortedEntries = sortSelector(entries);
const itemList = sortedEntries.map((entry) => { const itemList = sortedEntries.map((entry) => {
@ -48,8 +74,13 @@ export default React.memo(function TaskList(props: PropsType) {
}); });
return ( 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' }}> <div style={{ display: 'flex', justifyContent: 'space-between' }}>
{props.collections && <QuickAdd onSubmit={props.onItemSave} defaultCollection={props.collections[0]} />} {props.collections && <QuickAdd onSubmit={props.onItemSave} defaultCollection={props.collections[0]} />}
@ -65,6 +96,7 @@ export default React.memo(function TaskList(props: PropsType) {
<List> <List>
{itemList} {itemList}
</List> </List>
</> </Grid>
</Grid>
); );
}); });

@ -272,6 +272,9 @@ export const errorsReducer = handleActions(
// FIXME Move all the below (potentially the fetchCount ones too) to their own file // FIXME Move all the below (potentially the fetchCount ones too) to their own file
export interface SettingsType { export interface SettingsType {
locale: string; locale: string;
taskSettings: {
filterBy: string | null;
};
} }
export const settingsReducer = handleActions( export const settingsReducer = handleActions(
@ -280,5 +283,10 @@ export const settingsReducer = handleActions(
{ ...state, ...action.payload } { ...state, ...action.payload }
), ),
}, },
{ locale: 'en-gb' } {
locale: 'en-gb',
taskSettings: {
filterBy: null,
},
}
); );

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

Loading…
Cancel
Save