diff options
author | Anton Tananaev <anton@traccar.org> | 2022-10-21 18:14:12 -0700 |
---|---|---|
committer | Anton Tananaev <anton@traccar.org> | 2022-10-21 18:14:12 -0700 |
commit | 23f2332c7ce6cea72d91df102d35c03d0d3ce49e (patch) | |
tree | d44071ff0b198017028978165c58d69f91374e57 /modern/src/main/DeviceRow.js | |
parent | 52421e5f09687d4ae57386a69f9ffc69c011f9bb (diff) | |
download | trackermap-web-23f2332c7ce6cea72d91df102d35c03d0d3ce49e.tar.gz trackermap-web-23f2332c7ce6cea72d91df102d35c03d0d3ce49e.tar.bz2 trackermap-web-23f2332c7ce6cea72d91df102d35c03d0d3ce49e.zip |
Extract device row
Diffstat (limited to 'modern/src/main/DeviceRow.js')
-rw-r--r-- | modern/src/main/DeviceRow.js | 163 |
1 files changed, 163 insertions, 0 deletions
diff --git a/modern/src/main/DeviceRow.js b/modern/src/main/DeviceRow.js new file mode 100644 index 00000000..440e6f5c --- /dev/null +++ b/modern/src/main/DeviceRow.js @@ -0,0 +1,163 @@ +import React from 'react'; +import { useDispatch, useSelector } from 'react-redux'; +import makeStyles from '@mui/styles/makeStyles'; +import { + IconButton, Tooltip, Avatar, ListItemAvatar, ListItemText, ListItemButton, +} from '@mui/material'; +import BatteryFullIcon from '@mui/icons-material/BatteryFull'; +import BatteryChargingFullIcon from '@mui/icons-material/BatteryChargingFull'; +import Battery60Icon from '@mui/icons-material/Battery60'; +import BatteryCharging60Icon from '@mui/icons-material/BatteryCharging60'; +import Battery20Icon from '@mui/icons-material/Battery20'; +import BatteryCharging20Icon from '@mui/icons-material/BatteryCharging20'; +import ErrorIcon from '@mui/icons-material/Error'; +import moment from 'moment'; +import { devicesActions } from '../store'; +import { + formatAlarm, formatBoolean, formatPercentage, formatStatus, getStatusColor, +} from '../common/util/formatter'; +import { useTranslation } from '../common/components/LocalizationProvider'; +import { mapIconKey, mapIcons } from '../map/core/preloadImages'; +import { useAdministrator } from '../common/util/permissions'; +import usePersistedState from '../common/util/usePersistedState'; +import { ReactComponent as EngineIcon } from '../resources/images/data/engine.svg'; + +const useStyles = makeStyles((theme) => ({ + icon: { + width: '25px', + height: '25px', + filter: 'brightness(0) invert(1)', + }, + listItem: { + backgroundColor: 'white', + '&:hover': { + backgroundColor: 'white', + }, + }, + batteryText: { + fontSize: '0.75rem', + fontWeight: 'normal', + lineHeight: '0.875rem', + }, + positive: { + color: theme.palette.colors.positive, + }, + medium: { + color: theme.palette.colors.medium, + }, + negative: { + color: theme.palette.colors.negative, + }, + neutral: { + color: theme.palette.colors.neutral, + }, +})); + +const DeviceRow = ({ data, index, style }) => { + const classes = useStyles(); + const dispatch = useDispatch(); + const t = useTranslation(); + + const admin = useAdministrator(); + + const { items } = data; + const item = items[index]; + const position = useSelector((state) => state.positions.items[item.id]); + + const geofences = useSelector((state) => state.geofences.items); + + const [devicePrimary] = usePersistedState('devicePrimary', 'name'); + const [deviceSecondary] = usePersistedState('deviceSecondary', ''); + + const formatProperty = (key) => { + if (key === 'geofenceIds') { + const geofenceIds = item[key] || []; + return geofenceIds + .filter((id) => geofences.hasOwnProperty(id)) + .map((id) => geofences[id].name) + .join(', '); + } + return item[key]; + }; + + const secondaryText = () => { + let status; + if (item.status === 'online' || !item.lastUpdate) { + status = formatStatus(item.status, t); + } else { + status = moment(item.lastUpdate).fromNow(); + } + return ( + <> + {deviceSecondary && item[deviceSecondary] && `${formatProperty(deviceSecondary)} • `} + <span className={classes[getStatusColor(item.status)]}>{status}</span> + </> + ); + }; + + return ( + <div style={style}> + <ListItemButton + key={item.id} + className={classes.listItem} + onClick={() => dispatch(devicesActions.select(item.id))} + disabled={!admin && item.disabled} + > + <ListItemAvatar> + <Avatar> + <img className={classes.icon} src={mapIcons[mapIconKey(item.category)]} alt="" /> + </Avatar> + </ListItemAvatar> + <ListItemText + primary={formatProperty(devicePrimary)} + primaryTypographyProps={{ noWrap: true }} + secondary={secondaryText()} + secondaryTypographyProps={{ noWrap: true }} + /> + {position && ( + <> + {position.attributes.hasOwnProperty('alarm') && ( + <Tooltip title={`${t('eventAlarm')}: ${formatAlarm(position.attributes.alarm, t)}`}> + <IconButton size="small"> + <ErrorIcon fontSize="small" className={classes.negative} /> + </IconButton> + </Tooltip> + )} + {position.attributes.hasOwnProperty('ignition') && ( + <Tooltip title={`${t('positionIgnition')}: ${formatBoolean(position.attributes.ignition, t)}`}> + <IconButton size="small"> + {position.attributes.ignition ? ( + <EngineIcon width={20} height={20} className={classes.positive} /> + ) : ( + <EngineIcon width={20} height={20} className={classes.neutral} /> + )} + </IconButton> + </Tooltip> + )} + {position.attributes.hasOwnProperty('batteryLevel') && ( + <Tooltip title={`${t('positionBatteryLevel')}: ${formatPercentage(position.attributes.batteryLevel)}`}> + <IconButton size="small"> + {position.attributes.batteryLevel > 70 ? ( + position.attributes.charge + ? (<BatteryChargingFullIcon fontSize="small" className={classes.positive} />) + : (<BatteryFullIcon fontSize="small" className={classes.positive} />) + ) : position.attributes.batteryLevel > 30 ? ( + position.attributes.charge + ? (<BatteryCharging60Icon fontSize="small" className={classes.medium} />) + : (<Battery60Icon fontSize="small" className={classes.medium} />) + ) : ( + position.attributes.charge + ? (<BatteryCharging20Icon fontSize="small" className={classes.negative} />) + : (<Battery20Icon fontSize="small" className={classes.negative} />) + )} + </IconButton> + </Tooltip> + )} + </> + )} + </ListItemButton> + </div> + ); +}; + +export default DeviceRow; |