Tasks: add search functionality

master
Andrew P Maney 5 years ago committed by Tom Hacohen
parent ba00be300e
commit 212dfc7095

@ -10,6 +10,7 @@
"@material-ui/pickers": "^3.2.10",
"@material-ui/styles": "^4.6.0",
"etesync": "^0.3.1",
"fuse.js": "^5.0.9-beta",
"ical.js": "^1.2.2",
"immutable": "^4.0.0-rc.12",
"localforage": "^1.7.3",
@ -22,6 +23,7 @@
"react-router": "^4.3.1",
"react-router-dom": "^4.3.1",
"react-scripts": "3.3.0",
"react-transition-group": "^4.3.0",
"react-virtualized": "^9.21.2",
"redux": "^4.0.1",
"redux-actions": "^2.6.4",

@ -10,13 +10,16 @@ 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 } from '@material-ui/core/styles';
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';
@ -87,6 +90,12 @@ function getSortFunction(sortOrder: string) {
};
}
const useStyles = makeStyles((theme) => ({
topBar: {
backgroundColor: theme.palette.primary[500],
},
}));
interface PropsType {
entries: TaskType[];
collections: EteSync.CollectionInfo[];
@ -96,13 +105,31 @@ interface PropsType {
export default function TaskList(props: PropsType) {
const [showCompleted, setShowCompleted] = 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(
() => props.entries.filter((x) => showCompleted || !x.finished),
[showCompleted, props.entries]
() => {
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);
}
},
[showCompleted, props.entries, searchTerm]
);
let entries;
@ -134,20 +161,31 @@ export default function TaskList(props: PropsType) {
return (
<Grid container spacing={4}>
<Grid item xs={3} style={{ borderRight: `1px solid ${theme.palette.divider}` }}>
<Sidebar tasks={potentialEntries} />
<Grid item xs={3} className={classes.topBar}>
{/* spacer */}
</Grid>
<Grid item xs>
<Grid item xs={9} className={classes.topBar}>
<Toolbar
defaultCollection={props.collections?.[0]}
onItemSave={props.onItemSave}
showCompleted={showCompleted}
setShowCompleted={setShowCompleted}
searchTerm={searchTerm}
setSearchTerm={setSearchTerm}
/>
</Grid>
<Grid item xs={3} style={{ borderRight: `1px solid ${theme.palette.divider}` }}>
<Sidebar tasks={potentialEntries} />
</Grid>
<Grid item xs>
{props.collections?.[0] && <QuickAdd style={{ flexGrow: 1, marginRight: '0.75em' }} onSubmit={props.onItemSave} defaultCollection={props.collections?.[0]} />}
<Divider style={{ marginTop: '1em' }} />
<List>
{itemList}
</List>

@ -8,8 +8,12 @@ import IconButton from '@material-ui/core/IconButton';
import MoreVertIcon from '@material-ui/icons/MoreVert';
import MenuItem from '@material-ui/core/MenuItem';
import SortIcon from '@material-ui/icons/Sort';
import QuickAdd from './QuickAdd';
import SearchIcon from '@material-ui/icons/Search';
import CloseIcon from '@material-ui/icons/Close';
import TextField from '@material-ui/core/TextField';
import { makeStyles } from '@material-ui/core/styles';
import { Transition } from 'react-transition-group';
import InputAdornment from '@material-ui/core/InputAdornment';
import { PimType } from '../../pim-types';
@ -20,23 +24,54 @@ import { StoreState } from '../../store';
import Menu from '../../widgets/Menu';
const transitionTimeout = 300;
const transitionStyles = {
entering: { visibility: 'visible', width: '100%', overflow: 'hidden' },
entered: { visibility: 'visible', width: '100%' },
exiting: { visibility: 'visible', width: '0%', overflow: 'hidden' },
exited: { visibility: 'hidden', width: '0%' },
};
const useStyles = makeStyles((theme) => ({
button: {
marginRight: theme.spacing(1),
},
textField: {
transition: `width ${transitionTimeout}ms`,
marginRight: theme.spacing(1),
},
}));
interface PropsType {
defaultCollection: EteSync.CollectionInfo;
onItemSave: (item: PimType, journalUid: string, originalItem?: PimType) => Promise<void>;
showCompleted: boolean;
setShowCompleted: (completed: boolean) => void;
searchTerm: string;
setSearchTerm: (term: string) => void;
}
export default function Toolbar(props: PropsType) {
const { defaultCollection, onItemSave, showCompleted, setShowCompleted } = props;
const { showCompleted, setShowCompleted, searchTerm, setSearchTerm } = props;
const [showSearchField, setShowSearchField] = React.useState(false);
const [sortAnchorEl, setSortAnchorEl] = React.useState<null | HTMLElement>(null);
const [optionsAnchorEl, setOptionsAnchorEl] = React.useState<null | HTMLElement>(null);
const classes = useStyles();
const dispatch = useDispatch();
const taskSettings = useSelector((state: StoreState) => state.settings.taskSettings);
const { sortBy } = taskSettings;
const toggleSearchField = () => {
if (showSearchField) {
setSearchTerm('');
}
setShowSearchField(!showSearchField);
};
const handleSortChange = (sort: string) => {
dispatch(setSettings({ taskSettings: { ...taskSettings, sortBy: sort } }));
setSortAnchorEl(null);
@ -49,11 +84,38 @@ export default function Toolbar(props: PropsType) {
});
return (
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
{defaultCollection && <QuickAdd style={{ flexGrow: 1, marginRight: '0.75em' }} onSubmit={onItemSave} defaultCollection={defaultCollection} />}
<div style={{ display: 'flex', justifyContent: 'flex-end', alignItems: 'center' }}>
<Transition in={showSearchField} timeout={transitionTimeout}>
{(state) => (
<TextField
fullWidth
placeholder="Search"
value={searchTerm}
color="secondary"
variant="standard"
className={classes.textField}
style={transitionStyles[state]}
onChange={(e) => setSearchTerm(e.target.value)}
InputProps={{
startAdornment: (
<InputAdornment position="start">
<SearchIcon />
</InputAdornment>
),
}}
/>
)}
</Transition>
<div>
<div className={classes.button}>
<IconButton size="small" onClick={toggleSearchField}>
{showSearchField ? <CloseIcon /> : <SearchIcon />}
</IconButton>
</div>
<div className={classes.button}>
<IconButton
size="small"
aria-label="more"
aria-controls="long-menu"
aria-haspopup="true"
@ -75,9 +137,9 @@ export default function Toolbar(props: PropsType) {
</Menu>
</div>
<div>
<div className={classes.button}>
<IconButton
size="small"
aria-label="more"
aria-controls="long-menu"
aria-haspopup="true"

@ -5430,6 +5430,11 @@ functional-red-black-tree@^1.0.1:
resolved "https://registry.yarnpkg.com/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz#1b0ab3bd553b2a0d6399d29c0e3ea0b252078327"
integrity sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=
fuse.js@^5.0.9-beta:
version "5.0.9-beta"
resolved "https://registry.yarnpkg.com/fuse.js/-/fuse.js-5.0.9-beta.tgz#5122612868bf0a65f451085f5bd134b879502729"
integrity sha512-gx62Ba4GPfAaPJhE6ubfPk2OeVVMhPFmNCrLeLRVZybgDUxcgHz+tTNLC42yGs79DVQCSl17TN8fI8i9tyPv/g==
gauge@~2.7.3:
version "2.7.4"
resolved "https://registry.yarnpkg.com/gauge/-/gauge-2.7.4.tgz#2c03405c7538c39d7eb37b317022e325fb018bf7"

Loading…
Cancel
Save