diff options
author | Anton Tananaev <anton@traccar.org> | 2022-10-19 17:13:35 -0700 |
---|---|---|
committer | Anton Tananaev <anton@traccar.org> | 2022-10-19 17:13:35 -0700 |
commit | 6d733a77314eb2877689e652dd5a0da2737c42b0 (patch) | |
tree | dc1e477c77d82d6e27d3fa63817ed788d14f66b8 | |
parent | 139bedf867a5cb7731cb94c38b93d75f0e8b9ac3 (diff) | |
download | trackermap-web-6d733a77314eb2877689e652dd5a0da2737c42b0.tar.gz trackermap-web-6d733a77314eb2877689e652dd5a0da2737c42b0.tar.bz2 trackermap-web-6d733a77314eb2877689e652dd5a0da2737c42b0.zip |
Add main toolbar component
-rw-r--r-- | modern/src/main/MainPage.js | 133 | ||||
-rw-r--r-- | modern/src/main/MainToolbar.js | 135 | ||||
-rw-r--r-- | modern/src/main/useFilter.js | 6 |
3 files changed, 152 insertions, 122 deletions
diff --git a/modern/src/main/MainPage.js b/modern/src/main/MainPage.js index a7c41d5e..ec941323 100644 --- a/modern/src/main/MainPage.js +++ b/modern/src/main/MainPage.js @@ -1,18 +1,13 @@ import React, { - useState, useEffect, useCallback, useRef, + useState, useEffect, useCallback, } from 'react'; -import { useNavigate } from 'react-router-dom'; import { - Paper, Toolbar, IconButton, Button, OutlinedInput, InputAdornment, Popover, FormControl, InputLabel, Select, MenuItem, FormGroup, FormControlLabel, Checkbox, Badge, + Paper, Button, } from '@mui/material'; import { makeStyles } from '@mui/styles'; import { useTheme } from '@mui/material/styles'; import useMediaQuery from '@mui/material/useMediaQuery'; -import AddIcon from '@mui/icons-material/Add'; -import CloseIcon from '@mui/icons-material/Close'; -import ArrowBackIcon from '@mui/icons-material/ArrowBack'; import ListIcon from '@mui/icons-material/ViewList'; -import TuneIcon from '@mui/icons-material/Tune'; import { useDispatch, useSelector } from 'react-redux'; import DevicesList from './DevicesList'; import MapView from '../map/core/MapView'; @@ -29,7 +24,6 @@ import { devicesActions } from '../store'; import MapDefaultCamera from '../map/main/MapDefaultCamera'; import usePersistedState from '../common/util/usePersistedState'; import MapLiveRoutes from '../map/main/MapLiveRoutes'; -import { useDeviceReadonly } from '../common/util/permissions'; import MapPositions from '../map/MapPositions'; import MapOverlay from '../map/overlay/MapOverlay'; import MapGeocoder from '../map/geocoder/MapGeocoder'; @@ -38,6 +32,7 @@ import MapNotification from '../map/notification/MapNotification'; import EventsDrawer from './EventsDrawer'; import useFeatures from '../common/util/useFeatures'; import useFilter from './useFilter'; +import MainToolbar from './MainToolbar'; const useStyles = makeStyles((theme) => ({ root: { @@ -70,13 +65,6 @@ const useStyles = makeStyles((theme) => ({ toolbarContainer: { zIndex: 4, }, - toolbar: { - display: 'flex', - padding: theme.spacing(0, 1), - '& > *': { - margin: theme.spacing(0, 1), - }, - }, deviceList: { flex: 1, }, @@ -110,23 +98,14 @@ const useStyles = makeStyles((theme) => ({ zIndex: 4, width: theme.dimensions.drawerWidthDesktop, }, - filterPanel: { - display: 'flex', - flexDirection: 'column', - padding: theme.spacing(2), - gap: theme.spacing(2), - width: theme.dimensions.drawerWidthTablet, - }, })); const MainPage = () => { const classes = useStyles(); - const navigate = useNavigate(); const dispatch = useDispatch(); const theme = useTheme(); const t = useTranslation(); - const deviceReadonly = useDeviceReadonly(); const desktop = useMediaQuery(theme.breakpoints.up('md')); const phone = useMediaQuery(theme.breakpoints.down('sm')); @@ -141,8 +120,6 @@ const MainPage = () => { const [filteredPositions, setFilteredPositions] = useState([]); const selectedPosition = filteredPositions.find((position) => selectedDeviceId && position.deviceId === selectedDeviceId); - const groups = useSelector((state) => state.groups.items); - const devices = useSelector((state) => state.devices.items); const [filteredDevices, setFilteredDevices] = useState([]); const [filter, setFilter] = useState({ @@ -153,17 +130,12 @@ const MainPage = () => { const [filterSort, setFilterSort] = usePersistedState('filterSort', ''); const [filterMap, setFilterMap] = usePersistedState('filterMap', false); - const filterRef = useRef(); - const [filterAnchorEl, setFilterAnchorEl] = useState(null); - const [devicesOpen, setDevicesOpen] = useState(false); const [eventsOpen, setEventsOpen] = useState(false); const eventHandler = useCallback(() => setEventsOpen(true), [setEventsOpen]); const eventsAvailable = useSelector((state) => !!state.events.items.length); - const deviceStatusCount = (status) => Object.values(devices).filter((d) => d.status === status).length; - useEffect(() => setDevicesOpen(desktop), [desktop]); useEffect(() => { @@ -176,7 +148,7 @@ const MainPage = () => { dispatch(devicesActions.select(deviceId)); }, [dispatch]); - useFilter(filter, filterSort, filterMap, groups, devices, positions, setFilteredDevices, setFilteredPositions); + useFilter(filter, filterSort, filterMap, positions, setFilteredDevices, setFilteredPositions); return ( <div className={classes.root}> @@ -208,95 +180,14 @@ const MainPage = () => { </Button> <Paper square elevation={3} className={`${classes.sidebar} ${!devicesOpen && classes.sidebarCollapsed}`}> <Paper square elevation={3} className={classes.toolbarContainer}> - <Toolbar className={classes.toolbar} disableGutters> - {!desktop && ( - <IconButton edge="start" sx={{ mr: 2 }} onClick={() => setDevicesOpen(!devicesOpen)}> - <ArrowBackIcon /> - </IconButton> - )} - <OutlinedInput - ref={filterRef} - placeholder={t('sharedSearchDevices')} - value={filter.keyword} - onChange={(e) => setFilter({ ...filter, keyword: e.target.value })} - endAdornment={( - <InputAdornment position="end"> - <IconButton size="small" edge="end" onClick={() => setFilterAnchorEl(filterRef.current)}> - <Badge color="info" variant="dot" invisible={!filter.statuses.length && !filter.groups.length}> - <TuneIcon fontSize="small" /> - </Badge> - </IconButton> - </InputAdornment> - )} - size="small" - fullWidth - /> - <Popover - open={!!filterAnchorEl} - anchorEl={filterAnchorEl} - onClose={() => setFilterAnchorEl(null)} - anchorOrigin={{ - vertical: 'bottom', - horizontal: 'left', - }} - > - <div className={classes.filterPanel}> - <FormControl> - <InputLabel>{t('deviceStatus')}</InputLabel> - <Select - label={t('deviceStatus')} - value={filter.statuses} - onChange={(e) => setFilter({ ...filter, statuses: e.target.value })} - multiple - > - <MenuItem value="online">{`${t('deviceStatusOnline')} (${deviceStatusCount('online')})`}</MenuItem> - <MenuItem value="offline">{`${t('deviceStatusOffline')} (${deviceStatusCount('offline')})`}</MenuItem> - <MenuItem value="unknown">{`${t('deviceStatusUnknown')} (${deviceStatusCount('unknown')})`}</MenuItem> - </Select> - </FormControl> - <FormControl> - <InputLabel>{t('settingsGroups')}</InputLabel> - <Select - label={t('settingsGroups')} - value={filter.groups} - onChange={(e) => setFilter({ ...filter, groups: e.target.value })} - multiple - > - {Object.values(groups).sort((a, b) => a.name.localeCompare(b.name)).map((group) => ( - <MenuItem key={group.id} value={group.id}>{group.name}</MenuItem> - ))} - </Select> - </FormControl> - <FormControl> - <InputLabel>{t('sharedSortBy')}</InputLabel> - <Select - label={t('sharedSortBy')} - value={filterSort} - onChange={(e) => setFilterSort(e.target.value)} - displayEmpty - > - <MenuItem value="">{'\u00a0'}</MenuItem> - <MenuItem value="name">{t('sharedName')}</MenuItem> - <MenuItem value="lastUpdate">{t('deviceLastUpdate')}</MenuItem> - </Select> - </FormControl> - <FormGroup> - <FormControlLabel - control={<Checkbox checked={filterMap} onChange={(e) => setFilterMap(e.target.checked)} />} - label={t('sharedFilterMap')} - /> - </FormGroup> - </div> - </Popover> - <IconButton onClick={() => navigate('/settings/device')} disabled={deviceReadonly}> - <AddIcon /> - </IconButton> - {desktop && ( - <IconButton onClick={() => setDevicesOpen(!devicesOpen)}> - <CloseIcon /> - </IconButton> - )} - </Toolbar> + <MainToolbar + filter={filter} + setFilter={setFilter} + filterSort={filterSort} + setFilterSort={setFilterSort} + filterMap={filterMap} + setFilterMap={setFilterMap} + /> </Paper> <div className={classes.deviceList}> <DevicesList devices={filteredDevices} /> diff --git a/modern/src/main/MainToolbar.js b/modern/src/main/MainToolbar.js new file mode 100644 index 00000000..67a8a731 --- /dev/null +++ b/modern/src/main/MainToolbar.js @@ -0,0 +1,135 @@ +import React, { useState, useRef } from 'react'; +import { useSelector } from 'react-redux'; +import { useNavigate } from 'react-router-dom'; +import { + Toolbar, IconButton, OutlinedInput, InputAdornment, Popover, FormControl, InputLabel, Select, MenuItem, FormGroup, FormControlLabel, Checkbox, Badge, +} from '@mui/material'; +import { makeStyles } from '@mui/styles'; +import AddIcon from '@mui/icons-material/Add'; +import TuneIcon from '@mui/icons-material/Tune'; +import { useTranslation } from '../common/components/LocalizationProvider'; +import { useDeviceReadonly } from '../common/util/permissions'; + +const useStyles = makeStyles((theme) => ({ + toolbar: { + display: 'flex', + padding: theme.spacing(0, 1), + '& > *': { + margin: theme.spacing(0, 1), + }, + }, + filterPanel: { + display: 'flex', + flexDirection: 'column', + padding: theme.spacing(2), + gap: theme.spacing(2), + width: theme.dimensions.drawerWidthTablet, + }, +})); + +const MainToolbar = ({ + filter, + setFilter, + filterSort, + setFilterSort, + filterMap, + setFilterMap, +}) => { + const classes = useStyles(); + const navigate = useNavigate(); + const t = useTranslation(); + + const deviceReadonly = useDeviceReadonly(); + + const groups = useSelector((state) => state.groups.items); + const devices = useSelector((state) => state.devices.items); + + const filterRef = useRef(); + const [filterAnchorEl, setFilterAnchorEl] = useState(null); + + const deviceStatusCount = (status) => Object.values(devices).filter((d) => d.status === status).length; + + return ( + <Toolbar className={classes.toolbar} disableGutters> + <OutlinedInput + ref={filterRef} + placeholder={t('sharedSearchDevices')} + value={filter.keyword} + onChange={(e) => setFilter({ ...filter, keyword: e.target.value })} + endAdornment={( + <InputAdornment position="end"> + <IconButton size="small" edge="end" onClick={() => setFilterAnchorEl(filterRef.current)}> + <Badge color="info" variant="dot" invisible={!filter.statuses.length && !filter.groups.length}> + <TuneIcon fontSize="small" /> + </Badge> + </IconButton> + </InputAdornment> + )} + size="small" + fullWidth + /> + <Popover + open={!!filterAnchorEl} + anchorEl={filterAnchorEl} + onClose={() => setFilterAnchorEl(null)} + anchorOrigin={{ + vertical: 'bottom', + horizontal: 'left', + }} + > + <div className={classes.filterPanel}> + <FormControl> + <InputLabel>{t('deviceStatus')}</InputLabel> + <Select + label={t('deviceStatus')} + value={filter.statuses} + onChange={(e) => setFilter({ ...filter, statuses: e.target.value })} + multiple + > + <MenuItem value="online">{`${t('deviceStatusOnline')} (${deviceStatusCount('online')})`}</MenuItem> + <MenuItem value="offline">{`${t('deviceStatusOffline')} (${deviceStatusCount('offline')})`}</MenuItem> + <MenuItem value="unknown">{`${t('deviceStatusUnknown')} (${deviceStatusCount('unknown')})`}</MenuItem> + </Select> + </FormControl> + <FormControl> + <InputLabel>{t('settingsGroups')}</InputLabel> + <Select + label={t('settingsGroups')} + value={filter.groups} + onChange={(e) => setFilter({ ...filter, groups: e.target.value })} + multiple + > + {Object.values(groups).sort((a, b) => a.name.localeCompare(b.name)).map((group) => ( + <MenuItem key={group.id} value={group.id}>{group.name}</MenuItem> + ))} + </Select> + </FormControl> + <FormControl> + <InputLabel>{t('sharedSortBy')}</InputLabel> + <Select + label={t('sharedSortBy')} + value={filterSort} + onChange={(e) => setFilterSort(e.target.value)} + displayEmpty + > + <MenuItem value="">{'\u00a0'}</MenuItem> + <MenuItem value="name">{t('sharedName')}</MenuItem> + <MenuItem value="lastUpdate">{t('deviceLastUpdate')}</MenuItem> + </Select> + </FormControl> + <FormGroup> + <FormControlLabel + control={<Checkbox checked={filterMap} onChange={(e) => setFilterMap(e.target.checked)} />} + label={t('sharedFilterMap')} + /> + </FormGroup> + </div> + </Popover> + <IconButton onClick={() => navigate('/settings/device')} disabled={deviceReadonly}> + <AddIcon /> + </IconButton> + </Toolbar> + ); +}; + +export default MainToolbar; diff --git a/modern/src/main/useFilter.js b/modern/src/main/useFilter.js index e73847bd..66215c43 100644 --- a/modern/src/main/useFilter.js +++ b/modern/src/main/useFilter.js @@ -1,7 +1,11 @@ import { useEffect } from 'react'; +import { useSelector } from 'react-redux'; import moment from 'moment'; -export default (filter, filterSort, filterMap, groups, devices, positions, setFilteredDevices, setFilteredPositions) => { +export default (filter, filterSort, filterMap, positions, setFilteredDevices, setFilteredPositions) => { + const groups = useSelector((state) => state.groups.items); + const devices = useSelector((state) => state.devices.items); + useEffect(() => { const deviceGroups = (device) => { const groupIds = []; |