Tasks: add search functionality
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} className={classes.topBar}>
|
||||
{/* spacer */}
|
||||
</Grid>
|
||||
|
||||
<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>
|
||||
<Toolbar
|
||||
defaultCollection={props.collections?.[0]}
|
||||
onItemSave={props.onItemSave}
|
||||
showCompleted={showCompleted}
|
||||
setShowCompleted={setShowCompleted}
|
||||
/>
|
||||
|
||||
{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…
Reference in New Issue