aboutsummaryrefslogtreecommitdiff
path: root/modern/src/main/DeviceRow.js
diff options
context:
space:
mode:
authorAnton Tananaev <anton@traccar.org>2022-10-21 18:14:12 -0700
committerAnton Tananaev <anton@traccar.org>2022-10-21 18:14:12 -0700
commit23f2332c7ce6cea72d91df102d35c03d0d3ce49e (patch)
treed44071ff0b198017028978165c58d69f91374e57 /modern/src/main/DeviceRow.js
parent52421e5f09687d4ae57386a69f9ffc69c011f9bb (diff)
downloadtrackermap-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.js163
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;