aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorIván Ávalos <avalos@disroot.org>2024-04-18 23:30:49 -0600
committerIván Ávalos <avalos@disroot.org>2024-04-18 23:30:49 -0600
commit89f8af1e5c8a427b06a4e213d1fdade77ae090b8 (patch)
tree9c9af141405fca63acc2456b6b8557bd2dd6ad3c
parenta3e79c6572759f6c8f4544166ea34d0e6d789b4e (diff)
downloadtrackermap-web-89f8af1e5c8a427b06a4e213d1fdade77ae090b8.tar.gz
trackermap-web-89f8af1e5c8a427b06a4e213d1fdade77ae090b8.tar.bz2
trackermap-web-89f8af1e5c8a427b06a4e213d1fdade77ae090b8.zip
Show position attributes in device list
-rw-r--r--src/main/DeviceList.jsx17
-rw-r--r--src/main/DeviceRow.jsx81
2 files changed, 88 insertions, 10 deletions
diff --git a/src/main/DeviceList.jsx b/src/main/DeviceList.jsx
index eb51232f..3bb5f5e1 100644
--- a/src/main/DeviceList.jsx
+++ b/src/main/DeviceList.jsx
@@ -1,11 +1,12 @@
import React, { useEffect, useRef, useState } from 'react';
import { useDispatch } from 'react-redux';
import makeStyles from '@mui/styles/makeStyles';
-import { FixedSizeList } from 'react-window';
+import { VariableSizeList } from 'react-window';
import AutoSizer from 'react-virtualized-auto-sizer';
import { devicesActions } from '../store';
import { useEffectAsync } from '../reactHelper';
import DeviceRow from './DeviceRow';
+import { useAttributePreference } from '../common/util/preferences';
const useStyles = makeStyles((theme) => ({
list: {
@@ -28,6 +29,8 @@ const DeviceList = ({ devices }) => {
const [, setTime] = useState(Date.now());
+ const positionItems = useAttributePreference('positionItems', 'speed,address,totalDistance,course');
+
useEffect(() => {
const interval = setInterval(() => setTime(Date.now()), 60000);
return () => {
@@ -44,20 +47,26 @@ const DeviceList = ({ devices }) => {
}
}, []);
+ // RATIONALE: calculate row height to fit position attributes
+ const getItemSize = (index) => {
+ const item = devices[index];
+ return 72 + (item.positionId && (positionItems.split(',').length * 20) || 0);
+ };
+
return (
<AutoSizer className={classes.list}>
{({ height, width }) => (
- <FixedSizeList
+ <VariableSizeList
width={width}
height={height}
itemCount={devices.length}
itemData={devices}
- itemSize={72}
+ itemSize={getItemSize}
overscanCount={10}
innerRef={listInnerEl}
>
{DeviceRow}
- </FixedSizeList>
+ </VariableSizeList>
)}
</AutoSizer>
);
diff --git a/src/main/DeviceRow.jsx b/src/main/DeviceRow.jsx
index d9c1a189..8399da22 100644
--- a/src/main/DeviceRow.jsx
+++ b/src/main/DeviceRow.jsx
@@ -2,14 +2,20 @@ import React from 'react';
import { useDispatch, useSelector } from 'react-redux';
import makeStyles from '@mui/styles/makeStyles';
import {
- IconButton, Tooltip, Avatar, ListItemAvatar, ListItemText, ListItemButton,
+ IconButton, Tooltip, Avatar, ListItemAvatar, ListItemText, ListItemButton, Stack,
} from '@mui/material';
+import LockIcon from '@mui/icons-material/Lock';
+import LockOpenIcon from '@mui/icons-material/LockOpen';
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 SpeedIcon from '@mui/icons-material/Speed';
+import ScheduleIcon from '@mui/icons-material/Schedule';
+import PlaceIcon from '@mui/icons-material/Place';
+import AvTimerIcon from '@mui/icons-material/AvTimer';
import ErrorIcon from '@mui/icons-material/Error';
import dayjs from 'dayjs';
import relativeTime from 'dayjs/plugin/relativeTime';
@@ -22,6 +28,8 @@ import { mapIconKey, mapIcons } from '../map/core/preloadImages';
import { useAdministrator } from '../common/util/permissions';
import EngineIcon from '../resources/images/data/engine.svg?react';
import { useAttributePreference } from '../common/util/preferences';
+import usePositionAttributes from '../common/attributes/usePositionAttributes';
+import PositionValue from '../common/components/PositionValue';
dayjs.extend(relativeTime);
@@ -63,6 +71,9 @@ const DeviceRow = ({ data, index, style }) => {
const devicePrimary = useAttributePreference('devicePrimary', 'name');
const deviceSecondary = useAttributePreference('deviceSecondary', '');
+ const positionAttributes = usePositionAttributes(t);
+ const positionItems = useAttributePreference('positionItems', 'speed,address,totalDistance,course');
+
const secondaryText = () => {
let status;
if (item.status === 'online' || !item.lastUpdate) {
@@ -74,10 +85,45 @@ const DeviceRow = ({ data, index, style }) => {
<>
{deviceSecondary && item[deviceSecondary] && `${item[deviceSecondary]} • `}
<span className={classes[getStatusColor(item.status)]}>{status}</span>
+ {/* RATIONALE: clients want more info in the list */}
+ {position && positionItems.split(',').filter((key) => position.hasOwnProperty(key) || position.attributes.hasOwnProperty(key)).map((key) => (
+ <AttrItem
+ key={key}
+ attr={key}
+ name={positionAttributes[key]?.name || key}
+ content={(
+ <PositionValue
+ position={position}
+ property={position.hasOwnProperty(key) ? key : null}
+ attribute={position.hasOwnProperty(key) ? null : key}
+ />
+ )}
+ />
+ ))}
</>
);
};
+ // RATIONALE: we connect output/out1 to engine lock
+ const positionLock = () => {
+ let lock = false;
+ if (position.attributes.output !== undefined) {
+ lock = position.attributes.output === 1;
+ } else if (position.attributes.out1 !== undefined) {
+ lock = position.attributes.out1;
+ } else {
+ return <></>;
+ }
+
+ return (
+ <IconButton size="small">{
+ (lock)
+ ? <LockIcon fontSize="small" className={classes.error} />
+ : <LockOpenIcon fontSize="small" height={20} className={classes.success} />
+ }</IconButton>
+ )
+ };
+
return (
<div style={style}>
<ListItemButton
@@ -91,11 +137,11 @@ const DeviceRow = ({ data, index, style }) => {
</Avatar>
</ListItemAvatar>
<ListItemText
- primary={item[devicePrimary]}
- primaryTypographyProps={{ noWrap: true }}
- secondary={secondaryText()}
- secondaryTypographyProps={{ noWrap: true }}
- />
+ primary={item[devicePrimary]}
+ primaryTypographyProps={{ noWrap: true }}
+ secondary={secondaryText()}
+ secondaryTypographyProps={{ noWrap: true }}
+ />
{position && (
<>
{position.attributes.hasOwnProperty('alarm') && (
@@ -105,6 +151,8 @@ const DeviceRow = ({ data, index, style }) => {
</IconButton>
</Tooltip>
)}
+ {/* RATIONALE: clients want engine lock at a glance */}
+ {positionLock()}
{position.attributes.hasOwnProperty('ignition') && (
<Tooltip title={`${t('positionIgnition')}: ${formatBoolean(position.attributes.ignition, t)}`}>
<IconButton size="small">
@@ -142,4 +190,25 @@ const DeviceRow = ({ data, index, style }) => {
);
};
+const AttrItem = ({ attr, name, content }) => {
+ const attrIcon = () => {
+ switch(attr) {
+ case "address": return <PlaceIcon fontSize="inherit" />;
+ case "deviceTime": return <ScheduleIcon fontSize="inherit" />;
+ case "fixTime": return <ScheduleIcon fontSize="inherit" />;
+ case "hours": return <AvTimerIcon fontSize="inherit" />;
+ case "serverTime": return <ScheduleIcon fontSize="inherit" />;
+ case "speed": return <SpeedIcon fontSize="inherit" />;
+ default: return <b>{name}</b>;
+ }
+ };
+
+ return (
+ <>
+ <br></br>
+ {attrIcon()}&nbsp;{content}
+ </>
+ );
+};
+
export default DeviceRow;