fixed navigation bug in devices page; added option to seperate dust from sen5x to different column
This commit is contained in:
parent
4bd66d3d24
commit
ebf6bdb71a
5 changed files with 82 additions and 60 deletions
|
|
@ -5,8 +5,10 @@ import { Device } from "models";
|
||||||
import PulseBox from "./PulseBox";
|
import PulseBox from "./PulseBox";
|
||||||
import { or } from "utils";
|
import { or } from "utils";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
device: Device
|
device: Device
|
||||||
|
noDust?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const useStyles = makeStyles((theme: Theme) => {
|
const useStyles = makeStyles((theme: Theme) => {
|
||||||
|
|
@ -19,8 +21,8 @@ const useStyles = makeStyles((theme: Theme) => {
|
||||||
padding: theme.spacing(0.75),
|
padding: theme.spacing(0.75),
|
||||||
marginRight: "auto",
|
marginRight: "auto",
|
||||||
margin: theme.spacing(1),
|
margin: theme.spacing(1),
|
||||||
width: "72px",
|
width: theme.spacing(16),
|
||||||
height: "50px",
|
height: theme.spacing(11),
|
||||||
},
|
},
|
||||||
carouselContainer: {
|
carouselContainer: {
|
||||||
position: 'relative',
|
position: 'relative',
|
||||||
|
|
@ -48,7 +50,7 @@ const useStyles = makeStyles((theme: Theme) => {
|
||||||
})
|
})
|
||||||
|
|
||||||
export default function StatusPlenum(props: Props) {
|
export default function StatusPlenum(props: Props) {
|
||||||
const { device } = props;
|
const { device, noDust } = props;
|
||||||
const classes = useStyles()
|
const classes = useStyles()
|
||||||
|
|
||||||
const colors = or(device.status.sen5x?.overlays.map(overlay => overlay.color), []);
|
const colors = or(device.status.sen5x?.overlays.map(overlay => overlay.color), []);
|
||||||
|
|
@ -73,8 +75,9 @@ export default function StatusPlenum(props: Props) {
|
||||||
|
|
||||||
// Auto-cycle through measurements every 3 seconds
|
// Auto-cycle through measurements every 3 seconds
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
if (noDust) return
|
||||||
const interval = setInterval(() => {
|
const interval = setInterval(() => {
|
||||||
setCurrentIndex((prevIndex) => (prevIndex + 1) % 4);
|
setCurrentIndex((prevIndex) => (prevIndex + 1) % 2);
|
||||||
}, 3000); // Adjust timing as needed (3000ms = 3s)
|
}, 3000); // Adjust timing as needed (3000ms = 3s)
|
||||||
|
|
||||||
return () => clearInterval(interval); // Cleanup on unmount
|
return () => clearInterval(interval); // Cleanup on unmount
|
||||||
|
|
@ -125,66 +128,52 @@ export default function StatusPlenum(props: Props) {
|
||||||
>
|
>
|
||||||
<Grid2>
|
<Grid2>
|
||||||
<Typography variant="body2" style={{ color: orange[700]}}>
|
<Typography variant="body2" style={{ color: orange[700]}}>
|
||||||
{device.status.sen5x?.temperature.toFixed(1)}°C
|
Temp: {device.status.sen5x?.temperature.toFixed(1)}°C
|
||||||
</Typography>
|
</Typography>
|
||||||
</Grid2>
|
</Grid2>
|
||||||
<Grid2>
|
<Grid2>
|
||||||
<Typography variant="body2" style={{ color: blue[700]}}>
|
<Typography variant="body2" style={{ color: blue[700]}}>
|
||||||
{device.status.sen5x?.humidity.toFixed(1)}%
|
Humidity: {device.status.sen5x?.humidity.toFixed(1)}%
|
||||||
|
</Typography>
|
||||||
|
</Grid2>
|
||||||
|
<Grid2>
|
||||||
|
<Typography variant="body2" style={{ color: blue[300]}}>
|
||||||
|
Voc: {device.status.sen5x?.voc.toFixed(1)}%
|
||||||
|
</Typography>
|
||||||
|
</Grid2>
|
||||||
|
<Grid2>
|
||||||
|
<Typography variant="body2" style={{ color: blue[400]}}>
|
||||||
|
Nox: {device.status.sen5x?.nox.toFixed(1)}%
|
||||||
</Typography>
|
</Typography>
|
||||||
</Grid2>
|
</Grid2>
|
||||||
</Grid2>
|
</Grid2>
|
||||||
|
|
||||||
<Grid2 container direction="column"
|
{!noDust && <Grid2 container direction="column"
|
||||||
className={`${classes.carouselItem} ${
|
className={`${classes.carouselItem} ${
|
||||||
1 === currentIndex ? classes.active : classes.inactive
|
1 === currentIndex ? classes.active : classes.inactive
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
<Grid2>
|
<Grid2>
|
||||||
<Typography variant="body2" style={{ color: green[300]}}>
|
<Typography variant="body2" style={{ color: green[300]}}>
|
||||||
{device.status.sen5x?.dust1ug.toFixed(1)}ug/m3
|
{"<1um:"} {device.status.sen5x?.dust1ug.toFixed(1)}ug/m3
|
||||||
</Typography>
|
</Typography>
|
||||||
</Grid2>
|
</Grid2>
|
||||||
<Grid2>
|
<Grid2>
|
||||||
<Typography variant="body2" style={{ color: green[400]}}>
|
<Typography variant="body2" style={{ color: green[400]}}>
|
||||||
{device.status.sen5x?.dust2ug.toFixed(1)}ug/m3
|
{"<2.5um:"} {device.status.sen5x?.dust2ug.toFixed(1)}ug/m3
|
||||||
</Typography>
|
</Typography>
|
||||||
</Grid2>
|
</Grid2>
|
||||||
</Grid2>
|
|
||||||
|
|
||||||
<Grid2 container direction="column"
|
|
||||||
className={`${classes.carouselItem} ${
|
|
||||||
2 === currentIndex ? classes.active : classes.inactive
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
<Grid2>
|
<Grid2>
|
||||||
<Typography variant="body2" style={{ color: green[500]}}>
|
<Typography variant="body2" style={{ color: green[500]}}>
|
||||||
{device.status.sen5x?.dust4ug.toFixed(1)}ug/m3
|
{"<4um: "}{device.status.sen5x?.dust4ug.toFixed(1)}ug/m3
|
||||||
</Typography>
|
</Typography>
|
||||||
</Grid2>
|
</Grid2>
|
||||||
<Grid2>
|
<Grid2>
|
||||||
<Typography variant="body2" style={{ color: green[600]}}>
|
<Typography variant="body2" style={{ color: green[600]}}>
|
||||||
{device.status.sen5x?.dust10ug.toFixed(1)}ug/m3
|
{"<10um: "}{device.status.sen5x?.dust10ug.toFixed(1)}ug/m3
|
||||||
</Typography>
|
</Typography>
|
||||||
</Grid2>
|
</Grid2>
|
||||||
</Grid2>
|
</Grid2>}
|
||||||
|
|
||||||
<Grid2 container direction="column"
|
|
||||||
className={`${classes.carouselItem} ${
|
|
||||||
3 === currentIndex ? classes.active : classes.inactive
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
<Grid2>
|
|
||||||
<Typography variant="body2" style={{ color: blue[300]}}>
|
|
||||||
V: {device.status.sen5x?.voc.toFixed(1)}%
|
|
||||||
</Typography>
|
|
||||||
</Grid2>
|
|
||||||
<Grid2>
|
|
||||||
<Typography variant="body2" style={{ color: blue[400]}}>
|
|
||||||
N: {device.status.sen5x?.nox.toFixed(1)}%
|
|
||||||
</Typography>
|
|
||||||
</Grid2>
|
|
||||||
</Grid2>
|
|
||||||
</Grid2>
|
</Grid2>
|
||||||
</PulseBox>
|
</PulseBox>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@ import { isController, isSource } from "pbHelpers/ComponentType";
|
||||||
import { DeviceAvailabilityMap, OffsetAvailabilityMap } from "pbHelpers/DeviceAvailability";
|
import { DeviceAvailabilityMap, OffsetAvailabilityMap } from "pbHelpers/DeviceAvailability";
|
||||||
import { pond } from "protobuf-ts/pond";
|
import { pond } from "protobuf-ts/pond";
|
||||||
import { useGlobalState } from "providers";
|
import { useGlobalState } from "providers";
|
||||||
import React, { useState } from "react";
|
import React, { useEffect, useState } from "react";
|
||||||
import { or } from "utils/types";
|
import { or } from "utils/types";
|
||||||
import ComponentSettings from "./ComponentSettings";
|
import ComponentSettings from "./ComponentSettings";
|
||||||
import { green, teal } from "@mui/material/colors";
|
import { green, teal } from "@mui/material/colors";
|
||||||
|
|
@ -94,6 +94,7 @@ export default function ComponentActions(props: Props) {
|
||||||
};
|
};
|
||||||
|
|
||||||
const openComponentSettingsDialog = (mode: string) => {
|
const openComponentSettingsDialog = (mode: string) => {
|
||||||
|
console.log("delete")
|
||||||
setIsComponentSettingsOpen(true);
|
setIsComponentSettingsOpen(true);
|
||||||
setComponentSettingsMode(or(mode, ""));
|
setComponentSettingsMode(or(mode, ""));
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,6 @@ import { pond, quack } from "protobuf-ts/pond";
|
||||||
import { cloneDeep } from "lodash";
|
import { cloneDeep } from "lodash";
|
||||||
import DeviceOverview from "device/DeviceOverview";
|
import DeviceOverview from "device/DeviceOverview";
|
||||||
import { DeviceAvailabilityMap, FindAvailablePositions, OffsetAvailabilityMap } from "pbHelpers/DeviceAvailability";
|
import { DeviceAvailabilityMap, FindAvailablePositions, OffsetAvailabilityMap } from "pbHelpers/DeviceAvailability";
|
||||||
import AddComponentManualDialog from "component/AddComponentManualDialog";
|
|
||||||
import ComponentCard from "component/ComponentCard";
|
import ComponentCard from "component/ComponentCard";
|
||||||
import { sameComponentID, sortComponents } from "pbHelpers/Component";
|
import { sameComponentID, sortComponents } from "pbHelpers/Component";
|
||||||
import { isController } from "pbHelpers/ComponentType";
|
import { isController } from "pbHelpers/ComponentType";
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,7 @@ 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";
|
import StatusSen5x from "common/StatusSen5x";
|
||||||
|
import StatusDust from "common/StatusDust";
|
||||||
|
|
||||||
const useStyles = makeStyles((theme: Theme) => {
|
const useStyles = makeStyles((theme: Theme) => {
|
||||||
return ({
|
return ({
|
||||||
|
|
@ -122,6 +123,7 @@ export default function Devices() {
|
||||||
const [groupSettingsIsOpen, setGroupSettingsIsOpen] = useState<boolean>(false);
|
const [groupSettingsIsOpen, setGroupSettingsIsOpen] = useState<boolean>(false);
|
||||||
|
|
||||||
const [fieldContains, setFieldContains] = useState<Map<string, string>>(new Map<string, string>())
|
const [fieldContains, setFieldContains] = useState<Map<string, string>>(new Map<string, string>())
|
||||||
|
const [seperateDust, setSeperateDust] = useState(true)
|
||||||
|
|
||||||
const [{ as }] = useGlobalState()
|
const [{ as }] = useGlobalState()
|
||||||
|
|
||||||
|
|
@ -250,12 +252,8 @@ export default function Devices() {
|
||||||
).then(resp => {
|
).then(resp => {
|
||||||
let newDevices: Device[] = []
|
let newDevices: Device[] = []
|
||||||
resp.data.devices.forEach(device => {
|
resp.data.devices.forEach(device => {
|
||||||
if (device.status?.plenum?.temperature) {
|
if (device.status?.plenum?.temperature) setHasPlenums(true)
|
||||||
setHasPlenums(true)
|
if (device.status?.sen5x?.temperature) setHasSen5x(true)
|
||||||
}
|
|
||||||
if (device.status?.sen5x?.temperature) {
|
|
||||||
setHasSen5x(true)
|
|
||||||
}
|
|
||||||
newDevices.push(Device.create(device))
|
newDevices.push(Device.create(device))
|
||||||
})
|
})
|
||||||
setDevices(newDevices)
|
setDevices(newDevices)
|
||||||
|
|
@ -277,15 +275,41 @@ export default function Devices() {
|
||||||
setLimit(event.target.value);
|
setLimit(event.target.value);
|
||||||
};
|
};
|
||||||
|
|
||||||
const prependToUrl = (prependage: number | string) => {
|
// const prependToUrl = (prependage: number | string) => {
|
||||||
const basePath = location.pathname.replace(/\/$/, "");
|
// const basePath = location.pathname.replace(/\/$/, "");
|
||||||
return(`/${prependage}/${basePath}`.replace(/\/{2,}/g, "/").replace(/(\/[^/]+)\/\1+/g, "$1"));
|
// return(`/${prependage}/${basePath}`.replace(/\/{2,}/g, "/").replace(/(\/[^/]+)\/\1+/g, "$1"));
|
||||||
};
|
// };
|
||||||
|
|
||||||
|
function insertGroupContext(groupID: string, deviceID: string): string {
|
||||||
|
const path = location.pathname
|
||||||
|
console.log(path)
|
||||||
|
const segments = path.split('/').filter(Boolean);
|
||||||
|
const deviceIndex = segments.findIndex(segment => segment === 'devices');
|
||||||
|
|
||||||
|
if (deviceIndex === -1) {
|
||||||
|
throw new Error('Path does not contain "devices"');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build the new segments
|
||||||
|
const newSegments = [
|
||||||
|
...segments.slice(0, deviceIndex),
|
||||||
|
'groups',
|
||||||
|
groupID,
|
||||||
|
...segments.slice(deviceIndex),
|
||||||
|
deviceID
|
||||||
|
];
|
||||||
|
|
||||||
|
return '/' + newSegments.join('/');
|
||||||
|
}
|
||||||
|
|
||||||
const toDevice = (device: Device) => {
|
const toDevice = (device: Device) => {
|
||||||
let url = prependToUrl(getGroup() ? "groups/" + getGroup().id() : "")
|
|
||||||
url = url + "/" + device.id()
|
let url = getGroup() ? insertGroupContext(getGroup().id().toString(), device.id().toString()) : ""
|
||||||
navigate(url, { replace: true, state: {device: device} })
|
console.log(url)
|
||||||
|
if (url.length < 1) url = device.id().toString()
|
||||||
|
// url = url + "/" + device.id()
|
||||||
|
// navigate(url, { replace: true, state: {device: device} })
|
||||||
|
navigate(url, { state: {device: device} })
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleTabChange = (_event: React.SyntheticEvent, newValue: string) => {
|
const handleTabChange = (_event: React.SyntheticEvent, newValue: string) => {
|
||||||
|
|
@ -384,7 +408,23 @@ export default function Devices() {
|
||||||
if (device.status.sen5x?.temperature) {
|
if (device.status.sen5x?.temperature) {
|
||||||
return (
|
return (
|
||||||
<Box sx={{ marginLeft: 1 }}>
|
<Box sx={{ marginLeft: 1 }}>
|
||||||
<StatusSen5x device={device} />
|
<StatusSen5x device={device} noDust={seperateDust} />
|
||||||
|
</Box>
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
return (
|
||||||
|
<></>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
if (seperateDust) columns.push({
|
||||||
|
title: "Dust",
|
||||||
|
render: (device: Device) => {
|
||||||
|
if (device.status.sen5x?.temperature) {
|
||||||
|
return (
|
||||||
|
<Box sx={{ marginLeft: 1 }}>
|
||||||
|
<StatusDust device={device} />
|
||||||
</Box>
|
</Box>
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,9 @@
|
||||||
import axios, { AxiosRequestConfig, AxiosResponse } from "axios";
|
import axios, { AxiosRequestConfig, AxiosResponse } from "axios";
|
||||||
// import { useAuth } from "hooks";
|
|
||||||
import moment from "moment";
|
import moment from "moment";
|
||||||
import { createContext, PropsWithChildren, useContext } from "react";
|
import { createContext, PropsWithChildren, useContext } from "react";
|
||||||
// import BillingProvider from "./billing";
|
|
||||||
// import GitlabProvider from "./gitlab";
|
|
||||||
import PondProvider from "./pond/pond";
|
import PondProvider from "./pond/pond";
|
||||||
import { useAuth0 } from "@auth0/auth0-react";
|
import { useAuth0 } from "@auth0/auth0-react";
|
||||||
import SnackbarProvider from "./Snackbar";
|
import SnackbarProvider from "./Snackbar";
|
||||||
// import SecurityProvider from "./security";
|
|
||||||
// import { useAuth0 } from "@auth0/auth0-react";
|
|
||||||
|
|
||||||
interface IHTTPContext {
|
interface IHTTPContext {
|
||||||
get: <T>(url: string, spreadOptions?: AxiosRequestConfig) => Promise<AxiosResponse<T>>;
|
get: <T>(url: string, spreadOptions?: AxiosRequestConfig) => Promise<AxiosResponse<T>>;
|
||||||
|
|
@ -78,9 +73,7 @@ export default function HTTPProvider(props: Props) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function get<T>(url: string, spreadOptions?: AxiosRequestConfig): Promise<AxiosResponse<T>> {
|
function get<T>(url: string, spreadOptions?: AxiosRequestConfig): Promise<AxiosResponse<T>> {
|
||||||
if (isTokenExpired(token)) {
|
if (isTokenExpired(token)) loginWithRedirect()
|
||||||
loginWithRedirect()
|
|
||||||
}
|
|
||||||
return axios.get(url, {...defaultOptions(), ...spreadOptions});
|
return axios.get(url, {...defaultOptions(), ...spreadOptions});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue