diff options
-rw-r--r-- | modern/src/DevicesList.js | 81 | ||||
-rw-r--r-- | modern/src/MainPage.js | 246 |
2 files changed, 281 insertions, 46 deletions
diff --git a/modern/src/DevicesList.js b/modern/src/DevicesList.js index 85b936ce..b06f7f76 100644 --- a/modern/src/DevicesList.js +++ b/modern/src/DevicesList.js @@ -3,21 +3,23 @@ import { useDispatch, useSelector } from 'react-redux'; import { makeStyles } from '@material-ui/core/styles'; import Avatar from '@material-ui/core/Avatar'; import Divider from '@material-ui/core/Divider'; -import IconButton from '@material-ui/core/IconButton'; import List from '@material-ui/core/List'; import ListItem from '@material-ui/core/ListItem'; import ListItemAvatar from '@material-ui/core/ListItemAvatar'; import ListItemSecondaryAction from '@material-ui/core/ListItemSecondaryAction'; +import Grid from '@material-ui/core/Grid'; import ListItemText from '@material-ui/core/ListItemText'; -import MoreVertIcon from '@material-ui/icons/MoreVert'; import { FixedSizeList } from 'react-window'; import AutoSizer from 'react-virtualized-auto-sizer'; +import BatteryFullIcon from '@material-ui/icons/BatteryFull'; +import VpnKeyIcon from '@material-ui/icons/VpnKey'; import { devicesActions } from './store'; import EditCollectionView from './EditCollectionView'; import { useEffectAsync } from './reactHelper'; +import { formatPosition } from './common/formatter'; -const useStyles = makeStyles(() => ({ +const useStyles = makeStyles((theme) => ({ list: { maxHeight: '100%', }, @@ -26,29 +28,88 @@ const useStyles = makeStyles(() => ({ height: '25px', filter: 'brightness(0) invert(1)', }, + listItem: { + backgroundColor: 'white', + '&:hover': { + backgroundColor: 'white', + }, + }, + batteryText: { + fontSize: '0.75rem', + fontWeight: 'normal', + lineHeight: '0.875rem', + }, + green: { + color: theme.palette.common.green, + }, + red: { + color: theme.palette.common.red, + }, + gray: { + color: theme.palette.common.gray, + }, })); +const getStatusColor = (status) => { + switch (status) { + case 'online': + return 'green'; + case 'offline': + return 'red'; + case 'unknown': + default: + return 'gray'; + } +}; + +const getBatteryStatus = (batteryLevel) => { + if (batteryLevel >= 70) { + return 'green'; + } + if (batteryLevel > 30) { + return 'gray'; + } + return 'red'; +}; + const DeviceRow = ({ data, index, style }) => { const classes = useStyles(); const dispatch = useDispatch(); - const { items, onMenuClick } = data; + const { items } = data; const item = items[index]; + const position = useSelector((state) => state.positions.items[item.id]); return ( <div style={style}> <Fragment key={index}> - <ListItem button key={item.id} onClick={() => dispatch(devicesActions.select(item))}> + <ListItem button key={item.id} className={classes.listItem} onClick={() => dispatch(devicesActions.select(item))}> <ListItemAvatar> <Avatar> <img className={classes.icon} src={`images/icon/${item.category || 'default'}.svg`} alt="" /> </Avatar> </ListItemAvatar> - <ListItemText primary={item.name} secondary={item.uniqueId} /> + <ListItemText primary={item.name} secondary={item.status} classes={{ secondary: classes[getStatusColor(item.status)] }} /> <ListItemSecondaryAction> - <IconButton onClick={(event) => onMenuClick(event.currentTarget, item.id)}> - <MoreVertIcon /> - </IconButton> + {position && ( + <Grid container direction="row" alignItems="center" alignContent="center" spacing={1}> + {position.attributes.hasOwnProperty('ignition') && ( + <Grid item> + <VpnKeyIcon className={`${position.attributes.ignition ? classes.green : classes.gray}`} /> + </Grid> + )} + {position.attributes.hasOwnProperty('batteryLevel') && ( + <Grid item container xs alignItems="center" alignContent="center"> + <Grid item> + <span className={classes.batteryText}>{formatPosition(position.attributes.batteryLevel, 'batteryLevel')}</span> + </Grid> + <Grid item> + <BatteryFullIcon className={classes[getBatteryStatus(position.attributes.batteryLevel)]} /> + </Grid> + </Grid> + )} + </Grid> + )} </ListItemSecondaryAction> </ListItem> {index < items.length - 1 ? <Divider /> : null} @@ -90,7 +151,7 @@ const DeviceView = ({ updateTimestamp, onMenuClick }) => { }; const DevicesList = () => ( - <EditCollectionView content={DeviceView} editPath="/device" endpoint="devices" /> + <EditCollectionView content={DeviceView} editPath="/device" endpoint="devices" disableAdd /> ); export default DevicesList; diff --git a/modern/src/MainPage.js b/modern/src/MainPage.js index 88608df7..d5447e55 100644 --- a/modern/src/MainPage.js +++ b/modern/src/MainPage.js @@ -1,7 +1,20 @@ -import React from 'react'; -import { isWidthUp, makeStyles, withWidth } from '@material-ui/core'; -import Drawer from '@material-ui/core/Drawer'; -import ContainerDimensions from 'react-container-dimensions'; +import React, { useState } from 'react'; +import { useHistory } from 'react-router-dom'; +import { + makeStyles, Paper, Toolbar, Grid, TextField, IconButton, Button, +} from '@material-ui/core'; + +import { useTheme } from '@material-ui/core/styles'; +import useMediaQuery from '@material-ui/core/useMediaQuery'; +import AddIcon from '@material-ui/icons/Add'; +import CloseIcon from '@material-ui/icons/Close'; +import ArrowBackIcon from '@material-ui/icons/ArrowBack'; +import ListIcon from '@material-ui/icons/List'; +import ReplayIcon from '@material-ui/icons/Replay'; +import DescriptionIcon from '@material-ui/icons/Description'; +import ShuffleIcon from '@material-ui/icons/Shuffle'; +import PersonIcon from '@material-ui/icons/Person'; + import DevicesList from './DevicesList'; import MainToolbar from './MainToolbar'; import Map from './map/Map'; @@ -10,22 +23,14 @@ import AccuracyMap from './map/AccuracyMap'; import GeofenceMap from './map/GeofenceMap'; import CurrentPositionsMap from './map/CurrentPositionsMap'; import CurrentLocationMap from './map/CurrentLocationMap'; +import t from './common/localization'; const useStyles = makeStyles((theme) => ({ root: { - height: '100%', + height: '100vh', display: 'flex', flexDirection: 'column', }, - content: { - flexGrow: 1, - overflow: 'hidden', - display: 'flex', - flexDirection: 'row', - [theme.breakpoints.down('xs')]: { - flexDirection: 'column-reverse', - }, - }, drawerPaper: { position: 'relative', [theme.breakpoints.up('sm')]: { @@ -36,39 +41,208 @@ const useStyles = makeStyles((theme) => ({ }, overflow: 'hidden', }, - mapContainer: { - flexGrow: 1, + listContainer: { + position: 'absolute', + left: theme.spacing(1.5), + top: theme.spacing(10.5), + width: theme.dimensions.drawerWidthDesktop, + zIndex: 1301, + height: '100%', + maxHeight: `calc(100vh - ${theme.spacing(20)}px)`, + [theme.breakpoints.down('md')]: { + top: theme.spacing(7), + left: '0px', + width: '100%', + maxHeight: `calc(100vh - ${theme.spacing(14)}px)`, + }, + }, + paper: { + borderRadius: '0px', + }, + toolbar: { + paddingLeft: theme.spacing(2), + paddingRight: theme.spacing(2), + }, + deviceList: { + height: '100%', + backgroundColor: 'transparent', + [theme.breakpoints.down('md')]: { + backgroundColor: 'white', + }, + }, + collapsed: { + transform: `translateX(-${360 + 16}px)`, + transition: 'transform .5s ease', + [theme.breakpoints.down('md')]: { + transform: 'translateX(-100vw)', + }, + }, + uncollapsed: { + transform: `translateX(${theme.spacing(1.5)})`, + transition: 'transform .5s ease', + [theme.breakpoints.down('md')]: { + transform: 'translateX(0)', + }, + }, + deviceButton: { + position: 'absolute', + left: theme.spacing(1), + top: theme.spacing(10.5), + borderRadius: '0px', + [theme.breakpoints.down('md')]: { + left: theme.spacing(0), + top: theme.spacing(7), + }, + }, + deviceButtonBackground: { + backgroundColor: 'white', + color: '#777777', + '&:hover': { + backgroundColor: 'white', + }, + }, + bottomMenuContainer: { + position: 'absolute', + left: theme.spacing(1.5), + bottom: theme.spacing(1.5), + width: theme.dimensions.drawerWidthDesktop, + zIndex: 1301, + [theme.breakpoints.down('md')]: { + bottom: theme.spacing(0), + left: '0px', + width: '100%', + }, + }, + menuButton: { + display: 'flex', + flexDirection: 'column', + fontSize: '0.75rem', + fontWeight: 'normal', + color: '#222222', }, })); -const MainPage = ({ width }) => { +const MainPage = () => { const classes = useStyles(); + const history = useHistory(); + const theme = useTheme(); + + const [deviceName, setDeviceName] = useState(); + const [collapsed, setCollapsed] = useState(false); + + const isTablet = useMediaQuery(theme.breakpoints.down('md')); + const isPhone = useMediaQuery(theme.breakpoints.down('xs')); + + const handleClose = () => { + setCollapsed(!collapsed); + }; return ( <div className={classes.root}> <MainToolbar /> - <div className={classes.content}> - <Drawer - anchor={isWidthUp('sm', width) ? 'left' : 'bottom'} - variant="permanent" - classes={{ paper: classes.drawerPaper }} + <Map> + <CurrentLocationMap /> + <GeofenceMap /> + <AccuracyMap /> + <CurrentPositionsMap /> + <SelectedDeviceMap /> + </Map> + {collapsed + && ( + <Button + variant="contained" + color={isPhone ? 'secondary' : 'primary'} + classes={{ containedPrimary: classes.deviceButtonBackground }} + className={classes.deviceButton} + onClick={handleClose} + startIcon={<ListIcon />} + disableElevation > - <DevicesList /> - </Drawer> - <div className={classes.mapContainer}> - <ContainerDimensions> - <Map> - <CurrentLocationMap /> - <GeofenceMap /> - <AccuracyMap /> - <CurrentPositionsMap /> - <SelectedDeviceMap /> - </Map> - </ContainerDimensions> - </div> + {!isPhone ? t('deviceTitle') : ''} + </Button> + )} + <div className={`${classes.listContainer} ${collapsed ? classes.collapsed : classes.uncollapsed}`}> + <Grid container direction="column" spacing={isTablet ? 0 : 2} style={{ height: '100%' }}> + <Grid item> + <Paper className={classes.paper}> + <Toolbar className={classes.toolbar} disableGutters> + <Grid container direction="row" alignItems="center" spacing={2}> + {isTablet && ( + <Grid item> + <IconButton onClick={handleClose}> + <ArrowBackIcon /> + </IconButton> + </Grid> + )} + <Grid item xs> + <TextField + fullWidth + name="deviceName" + value={deviceName || ''} + autoComplete="deviceName" + autoFocus + onChange={(event) => setDeviceName(event.target.value)} + placeholder="Search Devices" + variant="filled" + /> + </Grid> + <Grid item> + <IconButton onClick={() => history.push('/device')}> + <AddIcon /> + </IconButton> + </Grid> + {!isTablet && ( + <Grid item> + <IconButton onClick={handleClose}> + <CloseIcon /> + </IconButton> + </Grid> + )} + </Grid> + </Toolbar> + </Paper> + </Grid> + <Grid item xs> + <div className={classes.deviceList}> + <DevicesList /> + </div> + </Grid> + </Grid> + </div> + <div className={classes.bottomMenuContainer}> + <Paper className={classes.paper}> + <Toolbar className={classes.toolbar} disableGutters> + <Grid container justify="space-around"> + <Grid item> + <IconButton classes={{ label: classes.menuButton }}> + <ReplayIcon /> + {t('reportReplay')} + </IconButton> + </Grid> + <Grid item> + <IconButton classes={{ label: classes.menuButton }}> + <DescriptionIcon /> + {t('reportTitle')} + </IconButton> + </Grid> + <Grid item> + <IconButton classes={{ label: classes.menuButton }}> + <ShuffleIcon /> + Options + </IconButton> + </Grid> + <Grid item> + <IconButton classes={{ label: classes.menuButton }}> + <PersonIcon /> + {t('settingsUser')} + </IconButton> + </Grid> + </Grid> + </Toolbar> + </Paper> </div> </div> ); }; -export default withWidth()(MainPage); +export default MainPage; |