added carousel display for sen5x status
This commit is contained in:
parent
539dd667c7
commit
deb8983b1e
4 changed files with 231 additions and 3 deletions
4
package-lock.json
generated
4
package-lock.json
generated
|
|
@ -43,7 +43,7 @@
|
||||||
"mui-tel-input": "^7.0.0",
|
"mui-tel-input": "^7.0.0",
|
||||||
"notistack": "^3.0.1",
|
"notistack": "^3.0.1",
|
||||||
"openweathermap-ts": "^1.2.10",
|
"openweathermap-ts": "^1.2.10",
|
||||||
"protobuf-ts": "git+https://gitlab+deploy-token-50627:hv8mB4WkyvtjBpJKU1rN@gitlab.com/brandx/protobuf-ts.git#dev",
|
"protobuf-ts": "git+https://gitlab+deploy-token-50627:hv8mB4WkyvtjBpJKU1rN@gitlab.com/brandx/protobuf-ts.git#staging",
|
||||||
"react": "^18.3.1",
|
"react": "^18.3.1",
|
||||||
"react-beautiful-dnd": "^13.1.1",
|
"react-beautiful-dnd": "^13.1.1",
|
||||||
"react-color": "^2.19.3",
|
"react-color": "^2.19.3",
|
||||||
|
|
@ -10993,7 +10993,7 @@
|
||||||
},
|
},
|
||||||
"node_modules/protobuf-ts": {
|
"node_modules/protobuf-ts": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "git+https://gitlab+deploy-token-50627:hv8mB4WkyvtjBpJKU1rN@gitlab.com/brandx/protobuf-ts.git#43253de7ee8c4d3b55b4b2cbbce41d782222fcfe",
|
"resolved": "git+https://gitlab+deploy-token-50627:hv8mB4WkyvtjBpJKU1rN@gitlab.com/brandx/protobuf-ts.git#aa8f16032048128898ec624f215fd03427957962",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"protobufjs": "^6.8.8"
|
"protobufjs": "^6.8.8"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -53,7 +53,7 @@
|
||||||
"mui-tel-input": "^7.0.0",
|
"mui-tel-input": "^7.0.0",
|
||||||
"notistack": "^3.0.1",
|
"notistack": "^3.0.1",
|
||||||
"openweathermap-ts": "^1.2.10",
|
"openweathermap-ts": "^1.2.10",
|
||||||
"protobuf-ts": "git+https://gitlab+deploy-token-50627:hv8mB4WkyvtjBpJKU1rN@gitlab.com/brandx/protobuf-ts.git#dev",
|
"protobuf-ts": "git+https://gitlab+deploy-token-50627:hv8mB4WkyvtjBpJKU1rN@gitlab.com/brandx/protobuf-ts.git#staging",
|
||||||
"react": "^18.3.1",
|
"react": "^18.3.1",
|
||||||
"react-beautiful-dnd": "^13.1.1",
|
"react-beautiful-dnd": "^13.1.1",
|
||||||
"react-color": "^2.19.3",
|
"react-color": "^2.19.3",
|
||||||
|
|
|
||||||
205
src/common/StatusSen5x.tsx
Normal file
205
src/common/StatusSen5x.tsx
Normal file
|
|
@ -0,0 +1,205 @@
|
||||||
|
import { Box, Grid2, Theme, Tooltip, Typography } from "@mui/material";
|
||||||
|
import { blue, green, orange } from "@mui/material/colors";
|
||||||
|
import { makeStyles } from "@mui/styles";
|
||||||
|
import { Device } from "models";
|
||||||
|
import PulseBox from "./PulseBox";
|
||||||
|
import { or } from "utils";
|
||||||
|
import { useEffect, useState } from "react";
|
||||||
|
interface Props {
|
||||||
|
device: Device
|
||||||
|
}
|
||||||
|
|
||||||
|
const useStyles = makeStyles((theme: Theme) => {
|
||||||
|
const lightMode = theme.palette?.mode === "light";
|
||||||
|
return ({
|
||||||
|
box: {
|
||||||
|
display: "inline-block",
|
||||||
|
borderRadius: "6px",
|
||||||
|
backgroundColor: lightMode ? "rgb(210, 210, 210)" : "rgb(50, 50, 50)",
|
||||||
|
padding: theme.spacing(0.75),
|
||||||
|
marginRight: "auto",
|
||||||
|
margin: theme.spacing(1),
|
||||||
|
width: "72px",
|
||||||
|
height: "50px",
|
||||||
|
},
|
||||||
|
carouselContainer: {
|
||||||
|
position: 'relative',
|
||||||
|
height: '40px',
|
||||||
|
overflow: 'hidden',
|
||||||
|
textAlign: "center",
|
||||||
|
},
|
||||||
|
carouselItem: {
|
||||||
|
opacity: 0,
|
||||||
|
transform: 'translateX(100%)',
|
||||||
|
transition: 'opacity 0.5s ease, transform 0.5s ease',
|
||||||
|
position: 'absolute',
|
||||||
|
width: '100%',
|
||||||
|
// height: '100%',
|
||||||
|
},
|
||||||
|
active: {
|
||||||
|
opacity: 1,
|
||||||
|
transform: 'translateX(0)',
|
||||||
|
},
|
||||||
|
inactive: {
|
||||||
|
transform: 'translateX(-100%)',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
export default function StatusPlenum(props: Props) {
|
||||||
|
const { device } = props;
|
||||||
|
const classes = useStyles()
|
||||||
|
|
||||||
|
const colors = or(device.status.plenum?.overlays.map(overlay => overlay.color), []);
|
||||||
|
const messages = or(device.status.plenum?.overlays.map(overlay => overlay.message), []);
|
||||||
|
|
||||||
|
const [color, setColor] = useState(colors.length > 0 ? colors[0] : "");
|
||||||
|
const [, setColorIndex] = useState(0)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const interval = setInterval(() => {
|
||||||
|
setColorIndex(prevIndex => {
|
||||||
|
const newIndex = prevIndex >= colors.length - 1 ? 0 : prevIndex + 1;
|
||||||
|
setColor(colors[newIndex]); // Use the new index here
|
||||||
|
return newIndex;
|
||||||
|
});
|
||||||
|
}, 5000);
|
||||||
|
|
||||||
|
return () => clearInterval(interval);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const [currentIndex, setCurrentIndex] = useState(0);
|
||||||
|
|
||||||
|
// Auto-cycle through measurements every 3 seconds
|
||||||
|
useEffect(() => {
|
||||||
|
const interval = setInterval(() => {
|
||||||
|
setCurrentIndex((prevIndex) => (prevIndex + 1) % 4);
|
||||||
|
}, 3000); // Adjust timing as needed (3000ms = 3s)
|
||||||
|
|
||||||
|
return () => clearInterval(interval); // Cleanup on unmount
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const tooltip = () => {
|
||||||
|
if (messages.length === 0) return null
|
||||||
|
if (messages.length < 2) return (
|
||||||
|
messages[0]
|
||||||
|
)
|
||||||
|
return (
|
||||||
|
<Grid2 container direction={"column"}>
|
||||||
|
<Grid2 container direction="row">
|
||||||
|
<Typography fontWeight={"bold"} variant="caption">
|
||||||
|
Overlay Messages
|
||||||
|
</Typography>
|
||||||
|
</Grid2>
|
||||||
|
{messages.map((message, index) => {
|
||||||
|
return (
|
||||||
|
<Grid2 key={"tooltip-overly-message-" + message} container direction="row" alignItems={"center"}>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
width: 10, // Size of the square
|
||||||
|
height: 10,
|
||||||
|
backgroundColor: colors[index], // Passed color prop
|
||||||
|
borderRadius: 1, // Optional: slight rounding, remove for sharp square
|
||||||
|
border: "1px solid black",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Typography variant="caption" sx={{ marginLeft: 1 }}>
|
||||||
|
{message}
|
||||||
|
</Typography>
|
||||||
|
</Grid2>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</Grid2>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Tooltip title={tooltip()}>
|
||||||
|
<PulseBox color={color} className={classes.box} >
|
||||||
|
{/* <Grid2 container direction="column" > */}
|
||||||
|
<Grid2 container direction="column" >
|
||||||
|
<Grid2 container direction="column"
|
||||||
|
className={`${classes.carouselItem} ${
|
||||||
|
0 === currentIndex ? classes.active : classes.inactive
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
<Grid2>
|
||||||
|
<Typography variant="body2" style={{ color: orange[700]}}>
|
||||||
|
{device.status.sen5x?.temperature.toFixed(1)}°C
|
||||||
|
</Typography>
|
||||||
|
</Grid2>
|
||||||
|
<Grid2>
|
||||||
|
<Typography variant="body2" style={{ color: blue[700]}}>
|
||||||
|
{device.status.sen5x?.humidity.toFixed(1)}%
|
||||||
|
</Typography>
|
||||||
|
</Grid2>
|
||||||
|
</Grid2>
|
||||||
|
|
||||||
|
<Grid2 container direction="column"
|
||||||
|
className={`${classes.carouselItem} ${
|
||||||
|
1 === currentIndex ? classes.active : classes.inactive
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
<Grid2>
|
||||||
|
<Typography variant="body2" style={{ color: green[300]}}>
|
||||||
|
{device.status.sen5x?.dust_1ug.toFixed(1)}ug/m3
|
||||||
|
</Typography>
|
||||||
|
</Grid2>
|
||||||
|
<Grid2>
|
||||||
|
<Typography variant="body2" style={{ color: green[400]}}>
|
||||||
|
{device.status.sen5x?.dust_2ug.toFixed(1)}ug/m3
|
||||||
|
</Typography>
|
||||||
|
</Grid2>
|
||||||
|
</Grid2>
|
||||||
|
|
||||||
|
<Grid2 container direction="column"
|
||||||
|
className={`${classes.carouselItem} ${
|
||||||
|
2 === currentIndex ? classes.active : classes.inactive
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
<Grid2>
|
||||||
|
<Typography variant="body2" style={{ color: green[500]}}>
|
||||||
|
{device.status.sen5x?.dust_4ug.toFixed(1)}ug/m3
|
||||||
|
</Typography>
|
||||||
|
</Grid2>
|
||||||
|
<Grid2>
|
||||||
|
<Typography variant="body2" style={{ color: green[600]}}>
|
||||||
|
{device.status.sen5x?.dust_10ug.toFixed(1)}ug/m3
|
||||||
|
</Typography>
|
||||||
|
</Grid2>
|
||||||
|
</Grid2>
|
||||||
|
|
||||||
|
<Grid2 container direction="column"
|
||||||
|
className={`${classes.carouselItem} ${
|
||||||
|
3 === currentIndex ? classes.active : classes.inactive
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
<Grid2>
|
||||||
|
<Typography variant="body2" style={{ color: blue[300]}}>
|
||||||
|
{device.status.sen5x?.voc.toFixed(1)}%
|
||||||
|
</Typography>
|
||||||
|
</Grid2>
|
||||||
|
<Grid2>
|
||||||
|
<Typography variant="body2" style={{ color: blue[400]}}>
|
||||||
|
{device.status.sen5x?.nox.toFixed(1)}%
|
||||||
|
</Typography>
|
||||||
|
</Grid2>
|
||||||
|
</Grid2>
|
||||||
|
|
||||||
|
</Grid2>
|
||||||
|
|
||||||
|
{/* <Grid2>
|
||||||
|
<Typography variant="body2" style={{ color: orange[700]}}>
|
||||||
|
{device.status.sen5x?.temperature.toFixed(1)}°C
|
||||||
|
</Typography>
|
||||||
|
</Grid2>
|
||||||
|
<Grid2>
|
||||||
|
<Typography variant="body2" style={{ color: blue[700]}}>
|
||||||
|
{device.status.sen5x?.humidity.toFixed(1)}%
|
||||||
|
</Typography>
|
||||||
|
</Grid2> */}
|
||||||
|
{/* </Grid2> */}
|
||||||
|
</PulseBox>
|
||||||
|
</Tooltip>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
@ -23,6 +23,7 @@ import { describePower } from "pbHelpers/Power";
|
||||||
import { Tag as TagUI } from "common/Tag";
|
import { Tag as TagUI } from "common/Tag";
|
||||||
import moment from "moment";
|
import moment from "moment";
|
||||||
import StatusPlenum from "common/StatusPlenum";
|
import StatusPlenum from "common/StatusPlenum";
|
||||||
|
import StatusSen5x from "common/StatusSen5x";
|
||||||
|
|
||||||
const useStyles = makeStyles((theme: Theme) => {
|
const useStyles = makeStyles((theme: Theme) => {
|
||||||
return ({
|
return ({
|
||||||
|
|
@ -91,6 +92,7 @@ export default function Devices() {
|
||||||
const [devices, setDevices] = useState<Device[]>([])
|
const [devices, setDevices] = useState<Device[]>([])
|
||||||
const [isProvisionDialogOpen, setIsProvisionDialogOpen] = useState<boolean>(false);
|
const [isProvisionDialogOpen, setIsProvisionDialogOpen] = useState<boolean>(false);
|
||||||
const [hasPlenums, setHasPlenums] = useState(false)
|
const [hasPlenums, setHasPlenums] = useState(false)
|
||||||
|
const [hasSen5x, setHasSen5x] = useState(false)
|
||||||
|
|
||||||
const [groupsLoading, setGroupsLoading] = useState(false)
|
const [groupsLoading, setGroupsLoading] = useState(false)
|
||||||
const [groups, setGroups] = useState<Group[]>([]);
|
const [groups, setGroups] = useState<Group[]>([]);
|
||||||
|
|
@ -244,6 +246,9 @@ export default function Devices() {
|
||||||
if (device.status?.plenum?.temperature) {
|
if (device.status?.plenum?.temperature) {
|
||||||
setHasPlenums(true)
|
setHasPlenums(true)
|
||||||
}
|
}
|
||||||
|
if (device.status?.sen5x?.temperature) {
|
||||||
|
setHasSen5x(true)
|
||||||
|
}
|
||||||
newDevices.push(Device.create(device))
|
newDevices.push(Device.create(device))
|
||||||
})
|
})
|
||||||
setDevices(newDevices)
|
setDevices(newDevices)
|
||||||
|
|
@ -365,6 +370,24 @@ export default function Devices() {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
if (hasSen5x) {
|
||||||
|
columns.push({
|
||||||
|
title: "Sen5X",
|
||||||
|
render: (device: Device) => {
|
||||||
|
if (device.status.sen5x?.temperature) {
|
||||||
|
return (
|
||||||
|
<Box sx={{ marginLeft: 1 }}>
|
||||||
|
<StatusSen5x device={device} />
|
||||||
|
</Box>
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
return (
|
||||||
|
<></>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
return columns
|
return columns
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue