From ebf6bdb71a2d2765b0e7fd1d1d4b15cd01de150d Mon Sep 17 00:00:00 2001 From: Carter Date: Fri, 16 May 2025 10:53:26 -0600 Subject: [PATCH 01/32] fixed navigation bug in devices page; added option to seperate dust from sen5x to different column --- src/common/StatusSen5x.tsx | 61 +++++++++++---------------- src/component/ComponentActions.tsx | 3 +- src/pages/Device.tsx | 1 - src/pages/Devices.tsx | 68 ++++++++++++++++++++++++------ src/providers/http.tsx | 9 +--- 5 files changed, 82 insertions(+), 60 deletions(-) diff --git a/src/common/StatusSen5x.tsx b/src/common/StatusSen5x.tsx index 912f128..887fd10 100644 --- a/src/common/StatusSen5x.tsx +++ b/src/common/StatusSen5x.tsx @@ -5,8 +5,10 @@ import { Device } from "models"; import PulseBox from "./PulseBox"; import { or } from "utils"; import { useEffect, useState } from "react"; + interface Props { device: Device + noDust?: boolean; } const useStyles = makeStyles((theme: Theme) => { @@ -19,8 +21,8 @@ const useStyles = makeStyles((theme: Theme) => { padding: theme.spacing(0.75), marginRight: "auto", margin: theme.spacing(1), - width: "72px", - height: "50px", + width: theme.spacing(16), + height: theme.spacing(11), }, carouselContainer: { position: 'relative', @@ -48,7 +50,7 @@ const useStyles = makeStyles((theme: Theme) => { }) export default function StatusPlenum(props: Props) { - const { device } = props; + const { device, noDust } = props; const classes = useStyles() 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 useEffect(() => { + if (noDust) return const interval = setInterval(() => { - setCurrentIndex((prevIndex) => (prevIndex + 1) % 4); + setCurrentIndex((prevIndex) => (prevIndex + 1) % 2); }, 3000); // Adjust timing as needed (3000ms = 3s) return () => clearInterval(interval); // Cleanup on unmount @@ -125,66 +128,52 @@ export default function StatusPlenum(props: Props) { > - {device.status.sen5x?.temperature.toFixed(1)}°C + Temp: {device.status.sen5x?.temperature.toFixed(1)}°C - {device.status.sen5x?.humidity.toFixed(1)}% + Humidity: {device.status.sen5x?.humidity.toFixed(1)}% + + + + + Voc: {device.status.sen5x?.voc.toFixed(1)}% + + + + + Nox: {device.status.sen5x?.nox.toFixed(1)}% - - {device.status.sen5x?.dust1ug.toFixed(1)}ug/m3 + {"<1um:"} {device.status.sen5x?.dust1ug.toFixed(1)}ug/m3 - {device.status.sen5x?.dust2ug.toFixed(1)}ug/m3 + {"<2.5um:"} {device.status.sen5x?.dust2ug.toFixed(1)}ug/m3 - - - - {device.status.sen5x?.dust4ug.toFixed(1)}ug/m3 + {"<4um: "}{device.status.sen5x?.dust4ug.toFixed(1)}ug/m3 - {device.status.sen5x?.dust10ug.toFixed(1)}ug/m3 + {"<10um: "}{device.status.sen5x?.dust10ug.toFixed(1)}ug/m3 - - - - - - V: {device.status.sen5x?.voc.toFixed(1)}% - - - - - N: {device.status.sen5x?.nox.toFixed(1)}% - - - + } diff --git a/src/component/ComponentActions.tsx b/src/component/ComponentActions.tsx index f633240..3d1ebc9 100644 --- a/src/component/ComponentActions.tsx +++ b/src/component/ComponentActions.tsx @@ -17,7 +17,7 @@ import { isController, isSource } from "pbHelpers/ComponentType"; import { DeviceAvailabilityMap, OffsetAvailabilityMap } from "pbHelpers/DeviceAvailability"; import { pond } from "protobuf-ts/pond"; import { useGlobalState } from "providers"; -import React, { useState } from "react"; +import React, { useEffect, useState } from "react"; import { or } from "utils/types"; import ComponentSettings from "./ComponentSettings"; import { green, teal } from "@mui/material/colors"; @@ -94,6 +94,7 @@ export default function ComponentActions(props: Props) { }; const openComponentSettingsDialog = (mode: string) => { + console.log("delete") setIsComponentSettingsOpen(true); setComponentSettingsMode(or(mode, "")); }; diff --git a/src/pages/Device.tsx b/src/pages/Device.tsx index 911e9f0..eabaf80 100644 --- a/src/pages/Device.tsx +++ b/src/pages/Device.tsx @@ -14,7 +14,6 @@ import { pond, quack } from "protobuf-ts/pond"; import { cloneDeep } from "lodash"; import DeviceOverview from "device/DeviceOverview"; import { DeviceAvailabilityMap, FindAvailablePositions, OffsetAvailabilityMap } from "pbHelpers/DeviceAvailability"; -import AddComponentManualDialog from "component/AddComponentManualDialog"; import ComponentCard from "component/ComponentCard"; import { sameComponentID, sortComponents } from "pbHelpers/Component"; import { isController } from "pbHelpers/ComponentType"; diff --git a/src/pages/Devices.tsx b/src/pages/Devices.tsx index c4239c5..21edc95 100644 --- a/src/pages/Devices.tsx +++ b/src/pages/Devices.tsx @@ -24,6 +24,7 @@ import { Tag as TagUI } from "common/Tag"; import moment from "moment"; import StatusPlenum from "common/StatusPlenum"; import StatusSen5x from "common/StatusSen5x"; +import StatusDust from "common/StatusDust"; const useStyles = makeStyles((theme: Theme) => { return ({ @@ -122,6 +123,7 @@ export default function Devices() { const [groupSettingsIsOpen, setGroupSettingsIsOpen] = useState(false); const [fieldContains, setFieldContains] = useState>(new Map()) + const [seperateDust, setSeperateDust] = useState(true) const [{ as }] = useGlobalState() @@ -250,12 +252,8 @@ export default function Devices() { ).then(resp => { let newDevices: Device[] = [] resp.data.devices.forEach(device => { - if (device.status?.plenum?.temperature) { - setHasPlenums(true) - } - if (device.status?.sen5x?.temperature) { - setHasSen5x(true) - } + if (device.status?.plenum?.temperature) setHasPlenums(true) + if (device.status?.sen5x?.temperature) setHasSen5x(true) newDevices.push(Device.create(device)) }) setDevices(newDevices) @@ -277,15 +275,41 @@ export default function Devices() { setLimit(event.target.value); }; - const prependToUrl = (prependage: number | string) => { - const basePath = location.pathname.replace(/\/$/, ""); - return(`/${prependage}/${basePath}`.replace(/\/{2,}/g, "/").replace(/(\/[^/]+)\/\1+/g, "$1")); - }; + // const prependToUrl = (prependage: number | string) => { + // const basePath = location.pathname.replace(/\/$/, ""); + // 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) => { - let url = prependToUrl(getGroup() ? "groups/" + getGroup().id() : "") - url = url + "/" + device.id() - navigate(url, { replace: true, state: {device: device} }) + + let url = getGroup() ? insertGroupContext(getGroup().id().toString(), device.id().toString()) : "" + 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) => { @@ -384,7 +408,23 @@ export default function Devices() { if (device.status.sen5x?.temperature) { return ( - + + + ) + } else { + return ( + <> + ) + } + } + }) + if (seperateDust) columns.push({ + title: "Dust", + render: (device: Device) => { + if (device.status.sen5x?.temperature) { + return ( + + ) } else { diff --git a/src/providers/http.tsx b/src/providers/http.tsx index 6811355..c4d9f64 100644 --- a/src/providers/http.tsx +++ b/src/providers/http.tsx @@ -1,14 +1,9 @@ import axios, { AxiosRequestConfig, AxiosResponse } from "axios"; -// import { useAuth } from "hooks"; import moment from "moment"; import { createContext, PropsWithChildren, useContext } from "react"; -// import BillingProvider from "./billing"; -// import GitlabProvider from "./gitlab"; import PondProvider from "./pond/pond"; import { useAuth0 } from "@auth0/auth0-react"; import SnackbarProvider from "./Snackbar"; -// import SecurityProvider from "./security"; -// import { useAuth0 } from "@auth0/auth0-react"; interface IHTTPContext { get: (url: string, spreadOptions?: AxiosRequestConfig) => Promise>; @@ -78,9 +73,7 @@ export default function HTTPProvider(props: Props) { } function get(url: string, spreadOptions?: AxiosRequestConfig): Promise> { - if (isTokenExpired(token)) { - loginWithRedirect() - } + if (isTokenExpired(token)) loginWithRedirect() return axios.get(url, {...defaultOptions(), ...spreadOptions}); } From 33988e368466a3f2917df98fc8455c5b2b942422 Mon Sep 17 00:00:00 2001 From: Carter Date: Fri, 16 May 2025 11:33:25 -0600 Subject: [PATCH 02/32] added optional element to responsive table to render an element before the search bar --- src/common/ResponsiveTable.tsx | 11 ++++++++--- src/pages/Devices.tsx | 35 ++++++++++++++++++++++++++++------ 2 files changed, 37 insertions(+), 9 deletions(-) diff --git a/src/common/ResponsiveTable.tsx b/src/common/ResponsiveTable.tsx index de23f1b..dea576a 100644 --- a/src/common/ResponsiveTable.tsx +++ b/src/common/ResponsiveTable.tsx @@ -92,6 +92,7 @@ interface Props { customElement?: JSX.Element //element that will be placed in the table head between the table actions and the column header rows mobileView?: boolean //can be used to force the table to use its mobile view for narrow containing elements ie, drawer hidePagination?: boolean //when passed in will hide the pagination at the bottom of the table + endTitleElement?: string | JSX.Element | (() => string | JSX.Element); } export default function ResponsiveTable(props: Props) { @@ -126,6 +127,7 @@ export default function ResponsiveTable(props: Props) { const columns = typeof(props.columns) === "function" ? props.columns() : props.columns const subtitle = typeof(props.subtitle) === "function" ? props.subtitle() : props.subtitle const title = typeof(props.title) === "function" ? props.title() : props.title + const endTitleElement = typeof(props.endTitleElement) === "function" ? props.endTitleElement() : props.endTitleElement const isMobile = useMobile() @@ -307,11 +309,13 @@ export default function ResponsiveTable(props: Props) { if (isMobile || mobileView) return ( + {renderTitle()} {renderSubtitle()} {setSearchText && searchBar()} + {rows.map((row, index) => { return ( onRowClick&&onRowClick(row)}> @@ -375,10 +379,9 @@ export default function ResponsiveTable(props: Props) { return ( - + - @@ -393,6 +396,9 @@ export default function ResponsiveTable(props: Props) { {actions && actions} + + {endTitleElement} + {setSearchText && {searchBar()} @@ -418,7 +424,6 @@ export default function ResponsiveTable(props: Props) { } - { rowSelect && } diff --git a/src/pages/Devices.tsx b/src/pages/Devices.tsx index 21edc95..d4518fe 100644 --- a/src/pages/Devices.tsx +++ b/src/pages/Devices.tsx @@ -1,5 +1,5 @@ -import { DeveloperBoard as ProvisionIcon, LibraryAdd as CreateGroupIcon } from "@mui/icons-material"; -import { Box, Chip, Grid2, IconButton, Skeleton, Tab, Tabs, Theme, Tooltip, Typography, useTheme } from "@mui/material"; +import { DeveloperBoard as ProvisionIcon, LibraryAdd as CreateGroupIcon, Tune } from "@mui/icons-material"; +import { Box, Chip, Dialog, DialogTitle, Grid2, IconButton, Menu, MenuItem, Skeleton, Tab, Tabs, Theme, Tooltip, Typography, useTheme } from "@mui/material"; import { blue, green, orange } from "@mui/material/colors"; import { makeStyles } from "@mui/styles"; import ResponsiveTable, { Column } from "common/ResponsiveTable"; @@ -125,6 +125,8 @@ export default function Devices() { const [fieldContains, setFieldContains] = useState>(new Map()) const [seperateDust, setSeperateDust] = useState(true) + const [preferencesAnchor, setPreferencesAnchor] = useState(null) + const [{ as }] = useGlobalState() const updateGroups = (newGroups: Group[]) => { @@ -282,7 +284,6 @@ export default function Devices() { 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'); @@ -305,10 +306,7 @@ export default function Devices() { const toDevice = (device: Device) => { let url = getGroup() ? insertGroupContext(getGroup().id().toString(), device.id().toString()) : "" - 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} }) }; @@ -527,6 +525,29 @@ export default function Devices() { ) } + const preferencesButton = () => { + return ( + setPreferencesAnchor(event.currentTarget)}> + + + ) + } + + const preferencesMenu = () => { + return ( + setPreferencesAnchor(null)} + disableAutoFocusItem> + + hi + + + ) + } + return( } + endTitleElement={preferencesButton} /> + {preferencesMenu()} ) } \ No newline at end of file From c19974edd9109d1cec66751f261e83266a8e4229 Mon Sep 17 00:00:00 2001 From: Carter Date: Fri, 16 May 2025 11:58:22 -0600 Subject: [PATCH 03/32] made a preferences element to group/ungroup sen5x stuff --- src/pages/Devices.tsx | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/src/pages/Devices.tsx b/src/pages/Devices.tsx index d4518fe..562ca25 100644 --- a/src/pages/Devices.tsx +++ b/src/pages/Devices.tsx @@ -1,5 +1,5 @@ import { DeveloperBoard as ProvisionIcon, LibraryAdd as CreateGroupIcon, Tune } from "@mui/icons-material"; -import { Box, Chip, Dialog, DialogTitle, Grid2, IconButton, Menu, MenuItem, Skeleton, Tab, Tabs, Theme, Tooltip, Typography, useTheme } from "@mui/material"; +import { Box, Checkbox, Chip, Grid2, IconButton, ListItemIcon, ListItemText, Menu, MenuItem, Skeleton, Tab, Tabs, Theme, Tooltip, Typography, useTheme } from "@mui/material"; import { blue, green, orange } from "@mui/material/colors"; import { makeStyles } from "@mui/styles"; import ResponsiveTable, { Column } from "common/ResponsiveTable"; @@ -123,7 +123,7 @@ export default function Devices() { const [groupSettingsIsOpen, setGroupSettingsIsOpen] = useState(false); const [fieldContains, setFieldContains] = useState>(new Map()) - const [seperateDust, setSeperateDust] = useState(true) + const [seperateDust, setSeperateDust] = useState(false) const [preferencesAnchor, setPreferencesAnchor] = useState(null) @@ -533,6 +533,8 @@ export default function Devices() { ) } + + const preferencesMenu = () => { return ( setPreferencesAnchor(null)} disableAutoFocusItem> - - hi + setSeperateDust(!seperateDust)} + sx={{ marginLeft: -0.75 }} + dense + > + + + + ) From 2a578a947e36a019b7594e12952771c61aaf0527 Mon Sep 17 00:00:00 2001 From: Carter Date: Fri, 16 May 2025 12:27:22 -0600 Subject: [PATCH 04/32] fixed sen5x status component not rotating after switching setting --- src/common/StatusSen5x.tsx | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/src/common/StatusSen5x.tsx b/src/common/StatusSen5x.tsx index 887fd10..632709e 100644 --- a/src/common/StatusSen5x.tsx +++ b/src/common/StatusSen5x.tsx @@ -52,7 +52,7 @@ const useStyles = makeStyles((theme: Theme) => { export default function StatusPlenum(props: Props) { const { device, noDust } = props; const classes = useStyles() - + // console.log(noDust) const colors = or(device.status.sen5x?.overlays.map(overlay => overlay.color), []); const messages = or(device.status.sen5x?.overlays.map(overlay => overlay.message), []); @@ -73,15 +73,19 @@ export default function StatusPlenum(props: Props) { const [currentIndex, setCurrentIndex] = useState(0); - // Auto-cycle through measurements every 3 seconds + // Auto-cycle through measurements every 5 seconds useEffect(() => { - if (noDust) return + if (noDust) { + setCurrentIndex(0) + return + } const interval = setInterval(() => { - setCurrentIndex((prevIndex) => (prevIndex + 1) % 2); - }, 3000); // Adjust timing as needed (3000ms = 3s) + console.log(noDust) + if (!noDust) setCurrentIndex((prevIndex) => (prevIndex + 1) % 2); + }, 5000); return () => clearInterval(interval); // Cleanup on unmount - }, []); + }, [noDust]); const tooltip = () => { if (messages.length === 0) return null From abde5c6499ed74487219ffff5fa19afe60aaa96f Mon Sep 17 00:00:00 2001 From: Carter Date: Fri, 16 May 2025 13:26:17 -0600 Subject: [PATCH 05/32] separateDust setting will persist in local storage --- src/common/StatusDust.tsx | 149 +++++++++++++++++++++++++++++++++++++ src/common/StatusSen5x.tsx | 1 - src/pages/Devices.tsx | 19 +++-- 3 files changed, 162 insertions(+), 7 deletions(-) create mode 100644 src/common/StatusDust.tsx diff --git a/src/common/StatusDust.tsx b/src/common/StatusDust.tsx new file mode 100644 index 0000000..2333318 --- /dev/null +++ b/src/common/StatusDust.tsx @@ -0,0 +1,149 @@ +import { Box, Grid2, Theme, Tooltip, Typography } from "@mui/material"; +import { green } 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: theme.spacing(16), + height: theme.spacing(11), + }, + 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%', + overflow: "hidden", + // height: '100%', + }, + active: { + opacity: 1, + transform: 'translateX(0)', + }, + inactive: { + transform: 'translateX(-100%)', + }, + }) +}) + +export default function StatusDust(props: Props) { + const { device } = props; + const classes = useStyles() + + const colors = or(device.status.sen5x?.overlays.map(overlay => overlay.color), []); + const messages = or(device.status.sen5x?.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; + }); + }, 7500); + + return () => clearInterval(interval); + }, []); + + // const [currentIndex, setCurrentIndex] = useState(0); + + // Auto-cycle through measurements every 3 seconds + // useEffect(() => { + // const interval = setInterval(() => { + // setCurrentIndex((prevIndex) => (prevIndex + 1) % 2); + // }, 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 ( + + + + Overlay Messages + + + {messages.map((message, index) => { + return ( + + + + {message} + + + ) + })} + + ) + } + + return ( + + + + + + + {"<1um:"} {device.status.sen5x?.dust1ug.toFixed(1)}ug/m3 + + + + + {"<2.5um:"} {device.status.sen5x?.dust2ug.toFixed(1)}ug/m3 + + + + + {"<4um: "}{device.status.sen5x?.dust4ug.toFixed(1)}ug/m3 + + + + + {"<10um: "}{device.status.sen5x?.dust10ug.toFixed(1)}ug/m3 + + + + + + + ) +} \ No newline at end of file diff --git a/src/common/StatusSen5x.tsx b/src/common/StatusSen5x.tsx index 632709e..8d32f35 100644 --- a/src/common/StatusSen5x.tsx +++ b/src/common/StatusSen5x.tsx @@ -80,7 +80,6 @@ export default function StatusPlenum(props: Props) { return } const interval = setInterval(() => { - console.log(noDust) if (!noDust) setCurrentIndex((prevIndex) => (prevIndex + 1) % 2); }, 5000); diff --git a/src/pages/Devices.tsx b/src/pages/Devices.tsx index 562ca25..a394d18 100644 --- a/src/pages/Devices.tsx +++ b/src/pages/Devices.tsx @@ -123,7 +123,15 @@ export default function Devices() { const [groupSettingsIsOpen, setGroupSettingsIsOpen] = useState(false); const [fieldContains, setFieldContains] = useState>(new Map()) - const [seperateDust, setSeperateDust] = useState(false) + + const [separateDust, setSeparateDust] = useState(() => { + const stored = localStorage.getItem('separateDust'); + return stored !== null ? stored === 'true' : false; + }); + + useEffect(() => { + localStorage.setItem('separateDust', separateDust.toString()); + }, [separateDust]); const [preferencesAnchor, setPreferencesAnchor] = useState(null) @@ -304,7 +312,6 @@ export default function Devices() { } const toDevice = (device: Device) => { - let url = getGroup() ? insertGroupContext(getGroup().id().toString(), device.id().toString()) : "" if (url.length < 1) url = device.id().toString() navigate(url, { state: {device: device} }) @@ -406,7 +413,7 @@ export default function Devices() { if (device.status.sen5x?.temperature) { return ( - + ) } else { @@ -416,7 +423,7 @@ export default function Devices() { } } }) - if (seperateDust) columns.push({ + if (separateDust) columns.push({ title: "Dust", render: (device: Device) => { if (device.status.sen5x?.temperature) { @@ -544,13 +551,13 @@ export default function Devices() { onClose={() => setPreferencesAnchor(null)} disableAutoFocusItem> setSeperateDust(!seperateDust)} + onClick={() => setSeparateDust(!separateDust)} sx={{ marginLeft: -0.75 }} dense > From 6dc181b24cccdc087a83cbcefdef3e4c5b5f98b2 Mon Sep 17 00:00:00 2001 From: Carter Date: Wed, 21 May 2025 13:49:00 -0600 Subject: [PATCH 06/32] status card for gas readings --- package-lock.json | 4 +- package.json | 2 +- src/common/StatusGas.tsx | 162 +++++++++++++++++++++++++++++++++++++ src/common/StatusSen5x.tsx | 2 +- src/pages/Devices.tsx | 85 +++++++++++++++++++ 5 files changed, 251 insertions(+), 4 deletions(-) create mode 100644 src/common/StatusGas.tsx diff --git a/package-lock.json b/package-lock.json index e369745..a656312 100644 --- a/package-lock.json +++ b/package-lock.json @@ -42,7 +42,7 @@ "mui-tel-input": "^7.0.0", "notistack": "^3.0.1", "openweathermap-ts": "^1.2.10", - "protobuf-ts": "git+https://gitlab+deploy-token-50627:hv8mB4WkyvtjBpJKU1rN@gitlab.com/brandx/protobuf-ts.git#master", + "protobuf-ts": "git+https://gitlab+deploy-token-50627:hv8mB4WkyvtjBpJKU1rN@gitlab.com/brandx/protobuf-ts.git#staging", "react": "^18.3.1", "react-beautiful-dnd": "^13.1.1", "react-color": "^2.19.3", @@ -10864,7 +10864,7 @@ }, "node_modules/protobuf-ts": { "version": "1.0.0", - "resolved": "git+https://gitlab+deploy-token-50627:hv8mB4WkyvtjBpJKU1rN@gitlab.com/brandx/protobuf-ts.git#f8704561c094b208029340cdf2ce0c510ef1eb12", + "resolved": "git+https://gitlab+deploy-token-50627:hv8mB4WkyvtjBpJKU1rN@gitlab.com/brandx/protobuf-ts.git#cead0be8708fa2c6679c77ed38f51239341c4ed4", "dependencies": { "protobufjs": "^6.8.8" } diff --git a/package.json b/package.json index 8e3ebd3..5a32392 100644 --- a/package.json +++ b/package.json @@ -52,7 +52,7 @@ "mui-tel-input": "^7.0.0", "notistack": "^3.0.1", "openweathermap-ts": "^1.2.10", - "protobuf-ts": "git+https://gitlab+deploy-token-50627:hv8mB4WkyvtjBpJKU1rN@gitlab.com/brandx/protobuf-ts.git#master", + "protobuf-ts": "git+https://gitlab+deploy-token-50627:hv8mB4WkyvtjBpJKU1rN@gitlab.com/brandx/protobuf-ts.git#staging", "react": "^18.3.1", "react-beautiful-dnd": "^13.1.1", "react-color": "^2.19.3", diff --git a/src/common/StatusGas.tsx b/src/common/StatusGas.tsx new file mode 100644 index 0000000..b0ebb85 --- /dev/null +++ b/src/common/StatusGas.tsx @@ -0,0 +1,162 @@ +import { Box, Grid2, Theme, Tooltip, Typography } from "@mui/material"; +import { blue, deepOrange, green, grey, orange, teal } 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 + // noDust?: boolean; + gasType: "co2" | "co" | "no2" | "o2" | "all" +} + +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: theme.spacing(14), + height: theme.spacing(6), + }, + 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%', + overflow: "hidden", + // height: '100%', + }, + active: { + opacity: 1, + transform: 'translateX(0)', + }, + inactive: { + transform: 'translateX(-100%)', + }, + }) +}) + +export default function StatusGas(props: Props) { + const { device, gasType } = props; + const classes = useStyles() + // console.log(noDust) + // const colors = or(device.status.sen5x?.overlays.map(overlay => overlay.color), []); + // const messages = or(device.status.sen5x?.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; + // }); + // }, 7500); + + // return () => clearInterval(interval); + // }, []); + + // const [currentIndex, setCurrentIndex] = useState(0); + + // Auto-cycle through measurements every 5 seconds + // useEffect(() => { + // if (noDust) { + // setCurrentIndex(0) + // return + // } + // const interval = setInterval(() => { + // if (!noDust) setCurrentIndex((prevIndex) => (prevIndex + 1) % 2); + // setCurrentIndex((prevIndex) => (prevIndex + 1) % 2); + // }, 5000); + + // return () => clearInterval(interval); // Cleanup on unmount + // }, [noDust]); + + // const tooltip = () => { + // if (messages.length === 0) return null + // if (messages.length < 2) return ( + // messages[0] + // ) + // return ( + // + // + // + // Overlay Messages + // + // + // {messages.map((message, index) => { + // return ( + // + // + // + // {message} + // + // + // ) + // })} + // + // ) + // } + + const gasDisplay = () => { + if (gasType !== "all") { + return( + // + + + + + {gasType !== "o2" ? + + Gas: {device.status[gasType]?.ppm.toFixed(1)}ppm + + : + + Gas: {(device.status[gasType]?.ppm!/10000).toFixed(1)}% + + } + + + + Volt: {device.status[gasType]?.millivolts.toFixed(1)}mV + + + + + + // + ) + + } + } + + return ( + <> + {gasDisplay()} + + ) +} \ No newline at end of file diff --git a/src/common/StatusSen5x.tsx b/src/common/StatusSen5x.tsx index 8d32f35..288eb10 100644 --- a/src/common/StatusSen5x.tsx +++ b/src/common/StatusSen5x.tsx @@ -49,7 +49,7 @@ const useStyles = makeStyles((theme: Theme) => { }) }) -export default function StatusPlenum(props: Props) { +export default function StatusSen5x(props: Props) { const { device, noDust } = props; const classes = useStyles() // console.log(noDust) diff --git a/src/pages/Devices.tsx b/src/pages/Devices.tsx index a394d18..dcd2c1c 100644 --- a/src/pages/Devices.tsx +++ b/src/pages/Devices.tsx @@ -25,6 +25,7 @@ import moment from "moment"; import StatusPlenum from "common/StatusPlenum"; import StatusSen5x from "common/StatusSen5x"; import StatusDust from "common/StatusDust"; +import StatusGas from "common/StatusGas"; const useStyles = makeStyles((theme: Theme) => { return ({ @@ -101,6 +102,10 @@ export default function Devices() { const [isProvisionDialogOpen, setIsProvisionDialogOpen] = useState(false); const [hasPlenums, setHasPlenums] = useState(false) const [hasSen5x, setHasSen5x] = useState(false) + const [hasCo, setHasCo] = useState(false) + const [hasCo2, setHasCo2] = useState(false) + const [hasNo2, setHasNo2] = useState(false) + const [hasO2, setHasO2] = useState(false) const [groupsLoading, setGroupsLoading] = useState(false) const [groups, setGroups] = useState([]); @@ -264,6 +269,10 @@ export default function Devices() { resp.data.devices.forEach(device => { if (device.status?.plenum?.temperature) setHasPlenums(true) if (device.status?.sen5x?.temperature) setHasSen5x(true) + if (device.status?.o2) setHasO2(true) + if (device.status?.co) setHasCo(true) + if (device.status?.co2) setHasCo2(true) + if (device.status?.no2) setHasNo2(true) newDevices.push(Device.create(device)) }) setDevices(newDevices) @@ -311,6 +320,10 @@ export default function Devices() { return '/' + newSegments.join('/'); } + useEffect(() => { + console.log(devices) + }, [devices]) + const toDevice = (device: Device) => { let url = getGroup() ? insertGroupContext(getGroup().id().toString(), device.id().toString()) : "" if (url.length < 1) url = device.id().toString() @@ -440,6 +453,78 @@ export default function Devices() { } }) } + if (hasCo) { + columns.push({ + title: "CO", + render: (device: Device) => { + if (device.status.co) { + return ( + + + + ) + } else { + return ( + <> + ) + } + } + }) + } + if (hasCo2) { + columns.push({ + title: "CO2", + render: (device: Device) => { + if (device.status.co2) { + return ( + + + + ) + } else { + return ( + <> + ) + } + } + }) + } + if (hasNo2) { + columns.push({ + title: "NO2", + render: (device: Device) => { + if (device.status.no2) { + return ( + + + + ) + } else { + return ( + <> + ) + } + } + }) + } + if (hasCo2) { + columns.push({ + title: "O2", + render: (device: Device) => { + if (device.status.o2) { + return ( + + + + ) + } else { + return ( + <> + ) + } + } + }) + } return columns } From 8143ef4c37b6dbaa1f5d403e621bab5d42644b86 Mon Sep 17 00:00:00 2001 From: Carter Date: Thu, 22 May 2025 11:16:08 -0600 Subject: [PATCH 07/32] added table sorting to responsive table --- src/common/ResponsiveTable.tsx | 48 +++++++++++++++++++++++++++++----- src/pages/Devices.tsx | 22 +++++++++++++--- 2 files changed, 60 insertions(+), 10 deletions(-) diff --git a/src/common/ResponsiveTable.tsx b/src/common/ResponsiveTable.tsx index de23f1b..5e48220 100644 --- a/src/common/ResponsiveTable.tsx +++ b/src/common/ResponsiveTable.tsx @@ -1,6 +1,6 @@ import { Box, Card, Checkbox, CircularProgress, Collapse, darken, Divider, Grid2, IconButton, InputAdornment, ListItemButton, ListItemIcon, ListItemText, Menu, MenuItem, Paper, SxProps, Table, TableBody, TableCell, TableContainer, TableHead, TablePagination, TableRow, TextField, Theme, Typography } from "@mui/material"; import { makeStyles } from "@mui/styles"; -import { ChevronRight, Close, Search, ViewColumn } from "@mui/icons-material" +import { ArrowDownward, ArrowUpward, ChevronRight, Close, Search, ViewColumn } from "@mui/icons-material" import { useEffect, useState } from "react"; import React from "react"; import { useMobile } from "hooks"; @@ -64,6 +64,8 @@ export interface Column { cellStyle?: SxProps; hidden?: boolean; render: (row: T) => JSX.Element; + sortKey?: string; + disableSort?: boolean; } interface Props { @@ -92,6 +94,10 @@ interface Props { customElement?: JSX.Element //element that will be placed in the table head between the table actions and the column header rows mobileView?: boolean //can be used to force the table to use its mobile view for narrow containing elements ie, drawer hidePagination?: boolean //when passed in will hide the pagination at the bottom of the table + orderBy?: string; + setOrderBy?: React.Dispatch>; + order?: string; + setOrder?: React.Dispatch>; } export default function ResponsiveTable(props: Props) { @@ -120,12 +126,17 @@ export default function ResponsiveTable(props: Props) { selectedRows, customElement, mobileView, - hidePagination + hidePagination, + orderBy, + setOrderBy, + order, + setOrder } = props const classes = useStyles(); const columns = typeof(props.columns) === "function" ? props.columns() : props.columns const subtitle = typeof(props.subtitle) === "function" ? props.subtitle() : props.subtitle const title = typeof(props.title) === "function" ? props.title() : props.title + // const order = props.order ? props.order : "asc" const isMobile = useMobile() @@ -373,12 +384,30 @@ export default function ResponsiveTable(props: Props) { setFilterList(newFilterList) } + const columnClick = (column: Column) => { + const sortKey = column.sortKey ? column.sortKey : column.title + console.log(column.sortKey) + if (setOrderBy) setOrderBy(sortKey) + if (setOrder && order) setOrder(order === "asc" ? "desc" : "asc") + } + + const showOrderIcon = (column: Column) => { + if (!orderBy) return + const sortKey = column.sortKey ? column.sortKey : column.title + if (sortKey === orderBy) { + if (order === "asc") { + return + } else { + return + } + } + return null; + } return ( - +
- @@ -418,7 +447,6 @@ export default function ResponsiveTable(props: Props) { } - { rowSelect && } @@ -427,8 +455,14 @@ export default function ResponsiveTable(props: Props) { columns.map((column, index) => { if (filterList.includes(column.title) || column.hidden) return null return ( - - {column.title} + !column.disableSort && columnClick(column)} + sx={{ cursor: column.disableSort ? "default" : "pointer" }} + > + + {column.title} {showOrderIcon(column)} + ) }) diff --git a/src/pages/Devices.tsx b/src/pages/Devices.tsx index c4239c5..036d718 100644 --- a/src/pages/Devices.tsx +++ b/src/pages/Devices.tsx @@ -92,8 +92,8 @@ export default function Devices() { const [devicesLoading, setDevicesLoading] = useState(false) const [limit, setLimit] = useState(10); const [page, setPage] = useState(0); - const [order, ] = useState<"asc" | "desc">("asc"); - const [orderBy, ] = useState("name"); + const [order, setOrder] = useState<"asc" | "desc">("asc"); + const [orderBy, setOrderBy] = useState("name"); const [search, setSearch] = useState(""); const [total, setTotal] = useState(0); const [devices, setDevices] = useState([]) @@ -293,10 +293,15 @@ export default function Devices() { if (newValue === "never") openGroupSettings(undefined, "add"); }; + useEffect(() => { + console.log(devices) + }, [devices]) + const columns = (): Column[] => { - let columns = [ + let columns: Column[] = [ { title: "State", + sortKey: "state", render: (device: Device) => { const status = device.status ?? pond.DeviceStatus.create() const deviceStateHelper = getDeviceStateHelper(status.state); @@ -309,12 +314,14 @@ export default function Devices() { }, { title: "ID", + sortKey: "deviceId", render: (device: Device) => {device.settings?.deviceId} }, { title: "Device", + sortKey: "name", render: (device: Device) => { return ( @@ -346,6 +353,7 @@ export default function Devices() { }, { title: "Tags", + sortKey: "tags", render: (device: Device) => { return ( @@ -362,6 +370,8 @@ export default function Devices() { if (hasPlenums) { columns.push({ title: "Plenum", + sortKey: "hi", + disableSort: true, render: (device: Device) => { if (device.status.plenum?.temperature) { return ( @@ -380,6 +390,8 @@ export default function Devices() { if (hasSen5x) { columns.push({ title: "Sen5X", + sortKey: "hi", + disableSort: true, render: (device: Device) => { if (device.status.sen5x?.temperature) { return ( @@ -532,6 +544,10 @@ export default function Devices() { setSearchText={setSearch} isLoading={devicesLoading} actions={getGroup() && } + order={order} + setOrder={setOrder} + orderBy={orderBy} + setOrderBy={setOrderBy} /> Date: Thu, 22 May 2025 12:29:51 -0600 Subject: [PATCH 08/32] implemented column ordering in bin cable estimator --- src/cableEstimator/cableEstimator.tsx | 115 ++++++++++++++++---------- src/common/ResponsiveTable.tsx | 10 +-- src/pages/Devices.tsx | 10 +-- 3 files changed, 83 insertions(+), 52 deletions(-) diff --git a/src/cableEstimator/cableEstimator.tsx b/src/cableEstimator/cableEstimator.tsx index f587efd..ac9991f 100644 --- a/src/cableEstimator/cableEstimator.tsx +++ b/src/cableEstimator/cableEstimator.tsx @@ -61,6 +61,8 @@ export default function CableEstimator() { const [tablePage, setTablePage] = useState(0) const [pageSize, setPageSize] = useState(10) const [rowCount, setRowCount] = useState(0) + const [orderBy, setOrderBy] = useState("") + const [order, setOrder] = useState<"asc" | "desc">("asc") const [selectedBin, setSelectedBin] = useState(); const [quoteOpen, setQuoteOpen] = useState(false); const [useCustomBin, setUseCustomBin] = useState(false); @@ -106,47 +108,57 @@ export default function CableEstimator() { const [loading, setLoading] = useState(false) const columns: Column[] = [ - { - title: "Manufacturer", - render: (row: jsonBin) => {row.Manufacturer} - }, - { - title: "Model", - render: (row: jsonBin) => {row.Model} - }, - { - title: "Capacity", - render: (row: jsonBin) => {row.Capacity} - }, - { - title: "Hopper Angle", - render: (row: jsonBin) => {row.HopperAngle} - }, - { - title: "Diameter " + (getDistanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_FEET ? "(ft)" : "(m)"), - render: (row: jsonBin) => {distanceConversion(row.Diameter).toFixed(2)} - }, - { - title: "Sidewall" + (getDistanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_FEET ? "(ft)" : "(m)"), - render: (row: jsonBin) => {distanceConversion(row.Sidewall).toFixed(2)} - }, - { - title: "Peak Height" + (getDistanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_FEET ? "(ft)" : "(m)"), - render: (row: jsonBin) => {distanceConversion(row.Peak).toFixed(2)} - }, - { - title: "Eave To Peak" + (getDistanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_FEET ? "(ft)" : "(m)"), - render: (row: jsonBin) => {distanceConversion(row.EaveToPeak).toFixed(2)} - }, - { - title: "Roof Angle", - render: (row: jsonBin) => {row.RoofAngle} - }, - { - title: "Rings", - render: (row: jsonBin) => {row.Rings} - } - ] + { + title: "Manufacturer", + sortKey: "Manufacturer", + render: (row: jsonBin) => {row.Manufacturer} + }, + { + title: "Model", + sortKey: "Model", + render: (row: jsonBin) => {row.Model} + }, + { + title: "Capacity", + sortKey: "Capacity", + render: (row: jsonBin) => {row.Capacity} + }, + { + title: "Hopper Angle", + sortKey: "HopperAngle", + render: (row: jsonBin) => {row.HopperAngle} + }, + { + title: "Diameter " + (getDistanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_FEET ? "(ft)" : "(m)"), + sortKey: "Diameter", + render: (row: jsonBin) => {distanceConversion(row.Diameter).toFixed(2)} + }, + { + title: "Sidewall" + (getDistanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_FEET ? "(ft)" : "(m)"), + sortKey: "Sidewall", + render: (row: jsonBin) => {distanceConversion(row.Sidewall).toFixed(2)} + }, + { + title: "Peak Height" + (getDistanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_FEET ? "(ft)" : "(m)"), + sortKey: "PeakHeight", + render: (row: jsonBin) => {distanceConversion(row.Peak).toFixed(2)} + }, + { + title: "Eave To Peak" + (getDistanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_FEET ? "(ft)" : "(m)"), + sortKey: "EaveToPeak", + render: (row: jsonBin) => {distanceConversion(row.EaveToPeak).toFixed(2)} + }, + { + title: "Roof Angle", + sortKey: "RoofAngle", + render: (row: jsonBin) => {row.RoofAngle} + }, + { + title: "Rings", + sortKey: "Rings", + render: (row: jsonBin) => {row.Rings} + } + ] useEffect(()=>{ setLoading(true) @@ -154,6 +166,21 @@ export default function CableEstimator() { let clone: jsonBin[] = cloneDeep(binOptions) //having the search filter in here is quite inefficient as it will filter each time you change pages as well //TODO: re-work the filter so it only filters when the search is changed + + // + clone.sort((a, b) => { + if (Object.keys(a).includes(orderBy)) { + let by = orderBy as keyof jsonBin + if (typeof(a[by]) === "string") { + // return a.localeCompare(b) + return (a[by].localeCompare(b[by] as string)) + } + return ((b[by] as any) - (a[by] as any)) + } + return 0 + }) + if (order === "desc") clone.reverse() + if(tableSearch !== ""){ //filter the data in the cloned list clone = clone.filter(bin => { @@ -175,7 +202,7 @@ export default function CableEstimator() { let rows = clone.splice(tablePage * pageSize, pageSize) setDisplayedRows(rows) setLoading(false) - },[binOptions, tablePage, pageSize, tableSearch]) + },[binOptions, tablePage, pageSize, tableSearch, orderBy, order]) const binOpTable = () => { return ( @@ -193,6 +220,10 @@ export default function CableEstimator() { handleRowsPerPageChange={(e) => {setPageSize(e.target.value)}} rows={displayedRows} total={rowCount} + order={order} + setOrder={setOrder} + orderBy={orderBy} + setOrderBy={setOrderBy} /> ); }; diff --git a/src/common/ResponsiveTable.tsx b/src/common/ResponsiveTable.tsx index 5e48220..8cdfe3c 100644 --- a/src/common/ResponsiveTable.tsx +++ b/src/common/ResponsiveTable.tsx @@ -385,20 +385,20 @@ export default function ResponsiveTable(props: Props) { } const columnClick = (column: Column) => { - const sortKey = column.sortKey ? column.sortKey : column.title - console.log(column.sortKey) + const sortKey = column.sortKey ? column.sortKey : column.title.toLowerCase() + // console.log(column.sortKey) if (setOrderBy) setOrderBy(sortKey) if (setOrder && order) setOrder(order === "asc" ? "desc" : "asc") } const showOrderIcon = (column: Column) => { if (!orderBy) return - const sortKey = column.sortKey ? column.sortKey : column.title + const sortKey = column.sortKey ? column.sortKey : column.title.toLowerCase() if (sortKey === orderBy) { if (order === "asc") { - return + return } else { - return + return } } return null; diff --git a/src/pages/Devices.tsx b/src/pages/Devices.tsx index 036d718..09c9ae6 100644 --- a/src/pages/Devices.tsx +++ b/src/pages/Devices.tsx @@ -301,7 +301,7 @@ export default function Devices() { let columns: Column[] = [ { title: "State", - sortKey: "state", + // sortKey: "state", render: (device: Device) => { const status = device.status ?? pond.DeviceStatus.create() const deviceStateHelper = getDeviceStateHelper(status.state); @@ -370,8 +370,8 @@ export default function Devices() { if (hasPlenums) { columns.push({ title: "Plenum", - sortKey: "hi", - disableSort: true, + // sortKey: "hi", + // disableSort: true, render: (device: Device) => { if (device.status.plenum?.temperature) { return ( @@ -390,8 +390,8 @@ export default function Devices() { if (hasSen5x) { columns.push({ title: "Sen5X", - sortKey: "hi", - disableSort: true, + // sortKey: "hi", + // disableSort: true, render: (device: Device) => { if (device.status.sen5x?.temperature) { return ( From ff6a7096dee93f96ef6e6a16aad0eb58465d889a Mon Sep 17 00:00:00 2001 From: Carter Date: Thu, 22 May 2025 12:40:56 -0600 Subject: [PATCH 09/32] minor styling --- src/cableEstimator/cableEstimator.tsx | 2 +- src/common/ResponsiveTable.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/cableEstimator/cableEstimator.tsx b/src/cableEstimator/cableEstimator.tsx index ac9991f..960e06a 100644 --- a/src/cableEstimator/cableEstimator.tsx +++ b/src/cableEstimator/cableEstimator.tsx @@ -167,7 +167,7 @@ export default function CableEstimator() { //having the search filter in here is quite inefficient as it will filter each time you change pages as well //TODO: re-work the filter so it only filters when the search is changed - // + // sort the array according to orderBy clone.sort((a, b) => { if (Object.keys(a).includes(orderBy)) { let by = orderBy as keyof jsonBin diff --git a/src/common/ResponsiveTable.tsx b/src/common/ResponsiveTable.tsx index 8cdfe3c..3f637d3 100644 --- a/src/common/ResponsiveTable.tsx +++ b/src/common/ResponsiveTable.tsx @@ -460,7 +460,7 @@ export default function ResponsiveTable(props: Props) { onClick={() => !column.disableSort && columnClick(column)} sx={{ cursor: column.disableSort ? "default" : "pointer" }} > - + {column.title} {showOrderIcon(column)} From f39c0d1102b9b50bd1a899ecc59084eebcc5f4f6 Mon Sep 17 00:00:00 2001 From: csawatzky Date: Thu, 22 May 2025 13:31:37 -0600 Subject: [PATCH 10/32] updated the platform stuff to include the ethernet blue board --- package-lock.json | 4 ++-- package.json | 2 +- src/firmware/UploadFirmware.tsx | 6 +++++- src/models/Device.ts | 6 +++++- src/pages/DeviceSupport.tsx | 3 +++ src/pbHelpers/Connectivity.tsx | 2 ++ src/pbHelpers/DevicePlatform.tsx | 16 ++++++++++++++-- 7 files changed, 32 insertions(+), 7 deletions(-) diff --git a/package-lock.json b/package-lock.json index e369745..64aa39b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -42,7 +42,7 @@ "mui-tel-input": "^7.0.0", "notistack": "^3.0.1", "openweathermap-ts": "^1.2.10", - "protobuf-ts": "git+https://gitlab+deploy-token-50627:hv8mB4WkyvtjBpJKU1rN@gitlab.com/brandx/protobuf-ts.git#master", + "protobuf-ts": "git+https://gitlab+deploy-token-50627:hv8mB4WkyvtjBpJKU1rN@gitlab.com/brandx/protobuf-ts.git#staging", "react": "^18.3.1", "react-beautiful-dnd": "^13.1.1", "react-color": "^2.19.3", @@ -10864,7 +10864,7 @@ }, "node_modules/protobuf-ts": { "version": "1.0.0", - "resolved": "git+https://gitlab+deploy-token-50627:hv8mB4WkyvtjBpJKU1rN@gitlab.com/brandx/protobuf-ts.git#f8704561c094b208029340cdf2ce0c510ef1eb12", + "resolved": "git+https://gitlab+deploy-token-50627:hv8mB4WkyvtjBpJKU1rN@gitlab.com/brandx/protobuf-ts.git#3f9a71a350651fb151d18514f990794f62a8e0cc", "dependencies": { "protobufjs": "^6.8.8" } diff --git a/package.json b/package.json index 8e3ebd3..5a32392 100644 --- a/package.json +++ b/package.json @@ -52,7 +52,7 @@ "mui-tel-input": "^7.0.0", "notistack": "^3.0.1", "openweathermap-ts": "^1.2.10", - "protobuf-ts": "git+https://gitlab+deploy-token-50627:hv8mB4WkyvtjBpJKU1rN@gitlab.com/brandx/protobuf-ts.git#master", + "protobuf-ts": "git+https://gitlab+deploy-token-50627:hv8mB4WkyvtjBpJKU1rN@gitlab.com/brandx/protobuf-ts.git#staging", "react": "^18.3.1", "react-beautiful-dnd": "^13.1.1", "react-color": "^2.19.3", diff --git a/src/firmware/UploadFirmware.tsx b/src/firmware/UploadFirmware.tsx index d985f7a..aa5aa41 100644 --- a/src/firmware/UploadFirmware.tsx +++ b/src/firmware/UploadFirmware.tsx @@ -79,7 +79,8 @@ class UploadFirmware extends React.Component { pond.DevicePlatform.DEVICE_PLATFORM_V2_CELLULAR_BLACK, pond.DevicePlatform.DEVICE_PLATFORM_V2_CELLULAR_GREEN, pond.DevicePlatform.DEVICE_PLATFORM_V2_CELLULAR_BLUE, - pond.DevicePlatform.DEVICE_PLATFORM_V2_WIFI_BLUE + pond.DevicePlatform.DEVICE_PLATFORM_V2_WIFI_BLUE, + pond.DevicePlatform.DEVICE_PLATFORM_V2_ETHERNET_BLUE ].includes(platform); var isChannelValid = [ pond.UpgradeChannel.UPGRADE_CHANNEL_ALPHA, @@ -229,6 +230,9 @@ class UploadFirmware extends React.Component { V2 Wifi Blue + + + V2 Ethernet Blue = new Map([ @@ -29,7 +30,8 @@ const featureVersions: Map = new Map([ v2CellBlack: "2.0.44", v2CellGreen: "2.0.44", v2WifiBlue: "2.0.44", - v2CellBlue: "2.0.44" + v2CellBlue: "2.0.44", + v2EthBlue: "2.0.44" } ] ]); @@ -118,6 +120,8 @@ export class Device { return this.status.firmwareVersion >= versions.v2WifiBlue; case pond.DevicePlatform.DEVICE_PLATFORM_V2_CELLULAR_BLUE: return this.status.firmwareVersion >= versions.v2CellBlue; + case pond.DevicePlatform.DEVICE_PLATFORM_V2_ETHERNET_BLUE: + return this.status.firmwareVersion >= versions.v2EthBlue; default: return false; } diff --git a/src/pages/DeviceSupport.tsx b/src/pages/DeviceSupport.tsx index 20bf476..a6a19de 100644 --- a/src/pages/DeviceSupport.tsx +++ b/src/pages/DeviceSupport.tsx @@ -301,6 +301,9 @@ export default function DeviceSupport() { V2 Cellular Blue + + V2 Ethernet Blue + + + } ) @@ -426,9 +435,6 @@ export default function ResponsiveTable(props: Props) { {actions && actions} - - {endTitleElement} - {setSearchText && {searchBar()} diff --git a/src/pages/DeviceComponent.tsx b/src/pages/DeviceComponent.tsx index 424de58..c04a1b2 100644 --- a/src/pages/DeviceComponent.tsx +++ b/src/pages/DeviceComponent.tsx @@ -23,6 +23,7 @@ import { } from "hooks"; import InteractionChip from "interactions/InteractionChip"; import InteractionSettings from "interactions/InteractionSettings"; +import { cloneDeep } from "lodash"; // import MaterialTable from "material-table"; import { Component, Device, deviceScope, Group, Interaction } from "models"; import { convertedUnitMeasurement, MeasurementsFor, UnitMeasurement } from "models/UnitMeasurement"; @@ -450,7 +451,6 @@ export default function DeviceComponent() { // }); }) .catch((err: any) => { - // resolve({ // data: [], // page: 0, @@ -613,6 +613,32 @@ export default function DeviceComponent() { pageSize={pageSize} setPage={setPage} handleRowsPerPageChange={handleRowsPerPageChange} + loadMore={() => { + let currentRows = cloneDeep(rows) + let newOffset = rows.length + componentAPI.listUnitMeasurements( + deviceID, + componentID, + startDate, + endDate, + pageSize, + newOffset, + order, + undefined, + getContextKeys(), + getContextTypes(), + showErrors, + as) + .then(resp => { + let newRows = UnitMeasurement.convertMeasurements( + resp.data.measurements.map(um => UnitMeasurement.any(um, user)) + ) + setRows(currentRows.concat(newRows)) + }).catch(err => { + + }) + + }} /> ); From 159a0638e75f96a756bc582e9740772a3a024d8c Mon Sep 17 00:00:00 2001 From: csawatzky Date: Fri, 23 May 2025 15:23:19 -0600 Subject: [PATCH 25/32] merging issues resolved --- src/common/ResponsiveTable.tsx | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/common/ResponsiveTable.tsx b/src/common/ResponsiveTable.tsx index d371ebd..628836c 100644 --- a/src/common/ResponsiveTable.tsx +++ b/src/common/ResponsiveTable.tsx @@ -1,6 +1,6 @@ import { Box, Button, Card, Checkbox, CircularProgress, Collapse, darken, Divider, Grid2, IconButton, InputAdornment, ListItemButton, ListItemIcon, ListItemText, Menu, MenuItem, Paper, SxProps, Table, TableBody, TableCell, TableContainer, TableHead, TablePagination, TableRow, TextField, Theme, Typography } from "@mui/material"; import { makeStyles } from "@mui/styles"; -import { ArrowDownward, ArrowLeft, ArrowRight, ArrowUpward, ChevronRight, Close, Search, ViewColumn } from "@mui/icons-material" +import { ArrowDownward, ArrowUpward, ChevronRight, Close, Search, ViewColumn } from "@mui/icons-material" import { useEffect, useState } from "react"; import React from "react"; import { useMobile } from "hooks"; @@ -98,6 +98,7 @@ interface Props { setOrderBy?: React.Dispatch>; order?: string; setOrder?: React.Dispatch>; + endTitleElement?: string | JSX.Element | (() => string | JSX.Element); loadMore?: () => void } @@ -139,6 +140,7 @@ export default function ResponsiveTable(props: Props) { const subtitle = typeof(props.subtitle) === "function" ? props.subtitle() : props.subtitle const title = typeof(props.title) === "function" ? props.title() : props.title // const order = props.order ? props.order : "asc" + const endTitleElement = typeof(props.endTitleElement) === "function" ? props.endTitleElement() : props.endTitleElement const isMobile = useMobile() @@ -447,6 +449,9 @@ export default function ResponsiveTable(props: Props) { {actions && actions} + + {endTitleElement} + {setSearchText && {searchBar()} From 3368a9112f3e358d8de90753b0988bc67331e3d2 Mon Sep 17 00:00:00 2001 From: csawatzky Date: Fri, 23 May 2025 16:13:39 -0600 Subject: [PATCH 26/32] adding load more functionality to the devices and teams mobile tables --- src/pages/Devices.tsx | 36 ++++++++++++++++++++++++++++++++++++ src/teams/TeamList.tsx | 41 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 77 insertions(+) diff --git a/src/pages/Devices.tsx b/src/pages/Devices.tsx index faf2f6d..77fe11d 100644 --- a/src/pages/Devices.tsx +++ b/src/pages/Devices.tsx @@ -26,6 +26,7 @@ import StatusPlenum from "common/StatusPlenum"; import StatusSen5x from "common/StatusSen5x"; import StatusDust from "common/StatusDust"; import StatusGas from "common/StatusGas"; +import { cloneDeep } from "lodash"; const useStyles = makeStyles((theme: Theme) => { return ({ @@ -751,6 +752,41 @@ export default function Devices() { orderBy={orderBy} setOrderBy={setOrderBy} endTitleElement={preferencesButton} + loadMore={()=>{ + let currentRows = cloneDeep(devices) + deviceAPI.list( + limit, + currentRows.length, + order, + orderBy, + undefined, + undefined, + undefined, + undefined, + undefined, + undefined, + undefined, + getKeys(), + getTypes(), + fieldContains, + search, + as + ).then(resp => { + let newDevices: Device[] = [] + resp.data.devices.forEach(device => { + if (device.status?.plenum?.temperature) setHasPlenums(true) + if (device.status?.sen5x?.temperature) setHasSen5x(true) + if (device.status?.o2) setHasO2(true) + if (device.status?.co) setHasCo(true) + if (device.status?.co2) setHasCo2(true) + if (device.status?.no2) setHasNo2(true) + newDevices.push(Device.create(device)) + }) + setDevices(currentRows.concat(newDevices)) + }).catch(err => { + + }) + }} /> { + let current = cloneDeep(teams) + setLoading(true); + let newTeamPerms: Map = new Map(); + let newTeamPrefs: Map = new Map(); + let viewAs: string | undefined = as + if (as === team.key()) { + viewAs = undefined + } + teamAPI + .listTeams(limit, current.length, undefined, "name", undefined, undefined, " ", search) + .then(resp => { + setTotal(resp.data.total ? resp.data.total : 0); + let newTeams: Team[] = [] + resp.data.teams.forEach(team => { + let u = viewAs ? viewAs : user.id(); + let t = Team.create(team) + if (team.settings && u !== "") { + userAPI + .getUser(u, teamScope(team.settings.key)) + .then(resp => { + newTeamPerms.set(t.key(), resp.permissions) + newTeamPrefs.set(t.key(), resp.preferences) + t.preferences = cloneDeep(resp.preferences) + t.permissions = cloneDeep(resp.permissions) + }).catch(() => { + snackbar.error("Error loading teams.") + }) + .finally(() => { + setTeamPerms(newTeamPerms); + setTeamPrefs(newTeamPrefs); + }); + } + newTeams.push(t) + }); + setTeams(current.concat(newTeams)); + }) + .finally(() => { + setLoading(false); + }); + }} /> Date: Mon, 26 May 2025 09:42:39 -0600 Subject: [PATCH 27/32] fixed column sizing when all gas readings are visible --- src/pages/Devices.tsx | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/pages/Devices.tsx b/src/pages/Devices.tsx index bc43773..334115d 100644 --- a/src/pages/Devices.tsx +++ b/src/pages/Devices.tsx @@ -59,14 +59,14 @@ const useStyles = makeStyles((theme: Theme) => { margin: theme.spacing(1), marginLeft: theme.spacing(2), display: "flex", - width: theme.spacing(32) + // width: theme.spacing(26) // justifyContent: "center", }, tagsCellContainer: { margin: theme.spacing(1), marginLeft: theme.spacing(2), display: "flex", - width: theme.spacing(24) + width: theme.spacing(18) // justifyContent: "center", }, green: { @@ -382,12 +382,13 @@ export default function Devices() { title: "Description", render: (device: Device) => { const description = device.settings?.description ?? "" + let size = (hasCo2 && !groupGas) ? 26 : 36 return ( - - + + - {description.length > 36 - ? description.substring(0, 36) + "..." + {description.length > size + ? description.substring(0, size) + "..." : description} From fde0eb731c1d0ddc8afb9c0bce839471e98d9d01 Mon Sep 17 00:00:00 2001 From: csawatzky Date: Mon, 26 May 2025 13:21:42 -0600 Subject: [PATCH 28/32] doing some more mobile views to load more --- src/common/QrCodeGenerator.tsx | 56 ++++++++++------------- src/common/ResponsiveTable.tsx | 8 ++-- src/contract/contractTransactionTable.tsx | 6 +++ src/gate/GateFlowGraph.tsx | 1 + src/gate/GateList.tsx | 14 ++++++ src/maps/mapDrawers/GroupDrawer.tsx | 2 +- src/pages/Bins.tsx | 1 + src/pages/Firmware.tsx | 17 +++++++ 8 files changed, 68 insertions(+), 37 deletions(-) diff --git a/src/common/QrCodeGenerator.tsx b/src/common/QrCodeGenerator.tsx index 8d5ddcb..27eaff0 100644 --- a/src/common/QrCodeGenerator.tsx +++ b/src/common/QrCodeGenerator.tsx @@ -1,4 +1,4 @@ -import { Box, Button, DialogActions, DialogContent, DialogTitle } from "@mui/material"; +import { Box, Button, Checkbox, DialogActions, DialogContent, DialogTitle, Typography } from "@mui/material"; // import MaterialTable from "material-table"; import React, { useEffect, useState } from "react"; import ResponsiveDialog from "./ResponsiveDialog"; @@ -36,7 +36,7 @@ export default function(props: Props) { width: 500 }, viewerMobile: { - height: "100%", + height: 500, width: "100%" }, page: { @@ -103,12 +103,12 @@ export default function(props: Props) { selected.push(row) }else{ //remove the page/index from the map if(pageSelect){ - pageSelect.splice(pageSelect.indexOf(index)) + pageSelect.splice(pageSelect.indexOf(index), 1) } //remove the selected object from the array selected.forEach((codeKey, index) => { if(row.key === codeKey.key){ - selected.splice(index) + selected.splice(index, 1) } }) } @@ -137,35 +137,27 @@ export default function(props: Props) { total={keyList.length} setPage={(page) => {setTablePage(page)}} handleRowsPerPageChange={handleRowsPerPageChange} + loadMore={() => { + let clone: QrCodeKey[] = cloneDeep(keyList) + let current = cloneDeep(displayedRows) + let newRows = clone.splice(current.length, pageSize) + setDisplayedRows(current.concat(newRows)) + }} + renderMobile={(row, index) => { + return ( + + { + rowSelected(row, index, checked) + }} + /> + + {row.name} + + + ) + }} /> - {/* { - setSelectedKeys(data); - }} - columns={[ - { - cellStyle: { - padding: 0 - }, - title: "Name", - field: "name" - } - ]}> */} ); }; diff --git a/src/common/ResponsiveTable.tsx b/src/common/ResponsiveTable.tsx index 628836c..ee1e9dd 100644 --- a/src/common/ResponsiveTable.tsx +++ b/src/common/ResponsiveTable.tsx @@ -85,7 +85,7 @@ interface Props { multiGutter?: boolean; rowsPerPageOptions?: number[]; noDataMessage?: string; - renderMobile?: (row: T) => JSX.Element; + renderMobile?: (row: T, index: number) => JSX.Element; hideKeys?: boolean; onRowClick?: (row: T) => void; actions?: string | JSX.Element; @@ -273,9 +273,9 @@ export default function ResponsiveTable(props: Props) { return String(val).charAt(0).toUpperCase() + String(val).slice(1); } - const renderMobileRow = (row: T) => { + const renderMobileRow = (row: T, index: number) => { if (renderMobile) return ( - renderMobile(row) + renderMobile(row, index) ) if (columns) { return ( @@ -330,7 +330,7 @@ export default function ResponsiveTable(props: Props) { {rows.map((row, index) => { return ( onRowClick&&onRowClick(row)}> - {renderMobileRow(row)} + {renderMobileRow(row, index)} {renderGutter && <> diff --git a/src/contract/contractTransactionTable.tsx b/src/contract/contractTransactionTable.tsx index 4508377..ebee9ba 100644 --- a/src/contract/contractTransactionTable.tsx +++ b/src/contract/contractTransactionTable.tsx @@ -369,6 +369,12 @@ export default function ContractTransactionTable(props: Props) { total={tableData.length} handleRowsPerPageChange={(e)=>{setPageSize(e.target.value)}} setPage={(page)=>{setTablePage(page)}} + loadMore={()=>{ + let clone: Transaction[] = cloneDeep(tableData) + let current = cloneDeep(displayData) + let newRows = clone.splice(current.length, pageSize) + setDisplayData(current.concat(newRows)) + }} /> {/* 1 && data[data.length - 1].value > gate.lowerFlow() && data[data.length - 1].value < gate.upperFlow() ) { diff --git a/src/gate/GateList.tsx b/src/gate/GateList.tsx index 8f0c8bc..5c548e2 100644 --- a/src/gate/GateList.tsx +++ b/src/gate/GateList.tsx @@ -12,6 +12,7 @@ import AddGateFab from "./AddGateFab"; import GateSettings from "./GateSettings"; import { useGateAPI, useGlobalState } from "providers"; import { Settings } from "@mui/icons-material"; +import { cloneDeep } from "lodash"; interface Props { //gates: Gate[]; @@ -219,6 +220,19 @@ export default function GateList(props: Props) { onRowClick={(gate) => {goToGate(gate)}} hideKeys={isMobile || props.useMobile} setSearchText={(search) => setSearch(search)} + loadMore={()=>{ + let current = cloneDeep(gates) + let keys = undefined + let types = undefined + if(currentTerminal) { + keys = [currentTerminal] + types = ["terminal"] + } + gateAPI.listGates(pageSize, current.length , "asc", "name", search, undefined, keys, types, undefined, undefined, as).then(resp => { + let newGates = resp.data.gates.map(gate => Gate.create(gate)) + setGates(current.concat(newGates)) + }) + }} /> ) }; diff --git a/src/maps/mapDrawers/GroupDrawer.tsx b/src/maps/mapDrawers/GroupDrawer.tsx index 4048245..8eae42d 100644 --- a/src/maps/mapDrawers/GroupDrawer.tsx +++ b/src/maps/mapDrawers/GroupDrawer.tsx @@ -183,7 +183,7 @@ export default function GroupDrawer(props: Props) { onRowClick={toDevice} handleRowsPerPageChange={()=>{}} setPage={()=>{}} - total={0} + total={grpDevs.length} rows={grpDevs} renderMobile={mobile} renderGutter={gutter} diff --git a/src/pages/Bins.tsx b/src/pages/Bins.tsx index ae8f6b5..64a40a2 100644 --- a/src/pages/Bins.tsx +++ b/src/pages/Bins.tsx @@ -1117,6 +1117,7 @@ export default function Bins(props: Props) { { setOpenQrGenerator(true); + setBinMenuAnchorEl(null) }}> Generate QR Codes diff --git a/src/pages/Firmware.tsx b/src/pages/Firmware.tsx index ecd628a..b3a7c58 100644 --- a/src/pages/Firmware.tsx +++ b/src/pages/Firmware.tsx @@ -39,6 +39,7 @@ import { useGlobalState } from "providers"; import { useCallback, useEffect, useState } from "react"; import { downloadFile } from "utils/download"; import { or } from "utils/types"; +import { cloneDeep } from "lodash"; const useStyles = makeStyles((theme: Theme) => { const themeType = useThemeType() @@ -463,6 +464,22 @@ export default function Firmware() { columns={columns(width === "xs" || width === "sm")} setPage={(page)=>{setTablePage(page)}} handleRowsPerPageChange={(e)=>{setPageSize(e.target.value)}} + loadMore={() => { + let current = cloneDeep(tableData) + firmwareAPI.listFirmware(pageSize, current.length, "desc", "version", searchText) + .then((response) => { + let newRows: FirmwareModel[] = or(response.data.firmware, []).map( + (firmwareData: any) => { + return FirmwareModel.create(pond.Firmware.fromObject(firmwareData)); + } + ); + setTableData(current.concat(newRows)) + }) + .catch(() => { + //failed to load + error("Failed to load more firmware versions") + }); + }} /> Date: Mon, 26 May 2025 15:33:25 -0600 Subject: [PATCH 29/32] modified the heaters to use the mobile view of the responsive table rahter than define their own list --- src/pages/Heaters.tsx | 34 +++++++++++++++++++--------------- 1 file changed, 19 insertions(+), 15 deletions(-) diff --git a/src/pages/Heaters.tsx b/src/pages/Heaters.tsx index ebe56f0..df716a0 100644 --- a/src/pages/Heaters.tsx +++ b/src/pages/Heaters.tsx @@ -3,6 +3,7 @@ import DeviceLinkDrawer from "common/DeviceLinkDrawer"; import ResponsiveTable, { Column } from "common/ResponsiveTable"; //import { getTableIcons } from "common/ResponsiveTable"; import { useMobile } from "hooks"; +import { cloneDeep } from "lodash"; //import MaterialTable from "material-table"; import { Device } from "models"; import { ObjectHeater } from "models/ObjectHeater"; @@ -109,17 +110,19 @@ export default function Heaters() { setPage={(page)=>{setTablePage(page)}} handleRowsPerPageChange={handleChange} onRowClick={goToHeater} - /> - ) - }; - - const mobileList = () => { - return ( - - {heaters.map(h => ( - { + let current = cloneDeep(heaters) + objectHeaterAPI + .listObjectHeatersPageData(pageSize, current.length, undefined, undefined, searchText, as) + .then(resp => { + setHeaters(heaters.concat(resp.data.heaterData)); + }); + }} + renderMobile={(row)=>{ + return ( + { setHeaterToLink(heater); setOpenLinkDrawer(true); @@ -131,9 +134,10 @@ export default function Heaters() { setDevices(d); }} /> - ))} - - ); + ) + }} + /> + ) }; return ( @@ -180,7 +184,7 @@ export default function Heaters() { }} pulse={heaters.length < 1} /> - {isMobile ? mobileList() : desktopTable()} + {desktopTable()} ); } From e97773ffc9a803ec908ab3de32f669ade6ce8d56 Mon Sep 17 00:00:00 2001 From: csawatzky Date: Mon, 26 May 2025 16:04:13 -0600 Subject: [PATCH 30/32] added load more function to mobile view of responsive table to the remaining uses --- src/maps/mapDrawers/GroupDrawer.tsx | 36 +++++++++++++++++++------- src/objectHeater/ObjectHeaterCard.tsx | 2 +- src/pages/Group.tsx | 31 ++++++++++++++++++++++ src/pages/Groups.tsx | 10 ++++++++ src/pages/Mines.tsx | 19 ++++++++++++++ src/pages/Sites.tsx | 37 ++++++++++++++++++++++++--- 6 files changed, 122 insertions(+), 13 deletions(-) diff --git a/src/maps/mapDrawers/GroupDrawer.tsx b/src/maps/mapDrawers/GroupDrawer.tsx index 8eae42d..e9d2768 100644 --- a/src/maps/mapDrawers/GroupDrawer.tsx +++ b/src/maps/mapDrawers/GroupDrawer.tsx @@ -3,6 +3,7 @@ import DisplayDrawer from "common/DisplayDrawer"; import ResponsiveTable from "common/ResponsiveTable"; //import DeviceCardList from "device/DeviceCardList"; import { useGroupAPI, useSnackbar } from "hooks"; +import { cloneDeep } from "lodash"; import { Group } from "models"; import moment from "moment"; import { describePower } from "pbHelpers/Power"; @@ -28,18 +29,19 @@ export default function GroupDrawer(props: Props) { const { openSnack } = useSnackbar(); const [grpDevs, setGrpDevs] = useState([]); const navigate = useNavigate(); - const [tablePage, setTablePage] = useState(0) - const [pageSize, setPageSize] = useState(10) + const loadLimit = 10 + const [groupTotal, setGroupTotal] = useState(0) const loadDevs = useCallback(() => { groupAPI - .listGroupDevices(group.id(), 100, 0, "asc", undefined, true) + .listGroupDevices(group.id(), loadLimit, 0, "asc", undefined, true) .then(resp => { const groupDevices: pond.ComprehensiveDevice[] = or( resp.data.comprehensiveDevices, [] ).map(d => pond.ComprehensiveDevice.fromObject(d)); setGrpDevs(groupDevices); + setGroupTotal(resp.data.total) }) .catch(err => { openSnack("Failed to get devices for group"); @@ -174,21 +176,37 @@ export default function GroupDrawer(props: Props) { } const drawerBody = () => { - // TODO: will need to update the rows and setPage functions if we decide to have the table only show a number of items matching its page size rether than everything passed in return ( - page={tablePage} - pageSize={pageSize} + page={0} + pageSize={loadLimit} onRowClick={toDevice} - handleRowsPerPageChange={()=>{}} - setPage={()=>{}} - total={grpDevs.length} + total={groupTotal} rows={grpDevs} renderMobile={mobile} renderGutter={gutter} gutterPadding={0} mobileView + loadMore={() => { + let current = cloneDeep(grpDevs) + groupAPI + .listGroupDevices(group.id(), loadLimit, current.length, "asc", undefined, true) + .then(resp => { + let newDevices: pond.ComprehensiveDevice[] = or( + resp.data.comprehensiveDevices, + [] + ).map(d => pond.ComprehensiveDevice.fromObject(d)); + setGrpDevs(current.concat(newDevices)); + setGroupTotal(resp.data.total) + }) + .catch(err => { + openSnack("Failed to get devices for group"); + }); + }} + //these two function are uneccessary as this will always be in a list as mobile view + handleRowsPerPageChange={()=>{}} + setPage={()=>{}} /> ) diff --git a/src/objectHeater/ObjectHeaterCard.tsx b/src/objectHeater/ObjectHeaterCard.tsx index 4255b1f..651ae7c 100644 --- a/src/objectHeater/ObjectHeaterCard.tsx +++ b/src/objectHeater/ObjectHeaterCard.tsx @@ -371,7 +371,7 @@ export default function ObjectHeaterCard(props: Props) { }else { return ( - + { return ({ @@ -241,6 +242,36 @@ export default function GroupPage() { onRowClick={toDevice} setSearchText={setSearch} isLoading={loadingDevices} + loadMore={()=>{ + let current = cloneDeep(devices) + setLoadingDevices(true) + deviceAPI.list( + limit, + current.length, + order, + orderBy, + search, + undefined, + undefined, + undefined, + undefined, + undefined, + undefined, + getKeys(), + getTypes(), + undefined, + undefined, + as + ).then(resp => { + let newDevices: Device[] = []; + resp.data.devices.forEach(device => { + newDevices.push(Device.create(device)) + }) + setDevices(current.concat(newDevices)) + }).finally(() => { + setLoadingDevices(false) + }) + }} /> ) diff --git a/src/pages/Groups.tsx b/src/pages/Groups.tsx index 0d06947..acc6673 100644 --- a/src/pages/Groups.tsx +++ b/src/pages/Groups.tsx @@ -13,6 +13,7 @@ import { or } from "utils/types"; import PageContainer from "./PageContainer"; import GroupSettings from "group/GroupSettings"; import { green } from "@mui/material/colors"; +import { cloneDeep } from "lodash"; const useStyles = makeStyles((theme: Theme) => { const isMobile = useMobile() @@ -163,6 +164,15 @@ export default function GroupsPage() { isLoading={loading} setSearchText={setSearch} onRowClick={handleRowClick} + loadMore={()=>{ + let current = cloneDeep(groups) + setLoading(true) + groupAPI.listGroups(limit, current.length, order, orderBy, search).then(resp => { + setGroups(or(current.concat(resp.data.groups), current)) + }).finally(() => { + setLoading(false) + }) + }} /> { + let current = cloneDeep(mineTableData) + setIsLoading(true); + mineAPI + .listMines(pageSize, current.length, "desc", undefined, searchValue, false, as) + .then(resp => { + let newMines: MineRow[] = []; + resp.data.mines.forEach(mine => { + newMines.push({ + mine: mine + }); + }); + setMineTableData(current.concat(newMines)); + }) + .finally(() => { + setIsLoading(false); + }); + }} /> diff --git a/src/pages/Sites.tsx b/src/pages/Sites.tsx index e749643..91ac28b 100644 --- a/src/pages/Sites.tsx +++ b/src/pages/Sites.tsx @@ -1,4 +1,4 @@ -import { Avatar, Box, Card, Grid2 as Grid, Switch, Tab, Tabs, Tooltip, Typography } from "@mui/material"; +import { Avatar, Box, Button, Card, Grid2 as Grid, Switch, Tab, Tabs, Tooltip, Typography } from "@mui/material"; import { Group } from "@mui/icons-material"; //import MaterialTable from "material-table"; import { Site, User } from "models"; @@ -12,6 +12,7 @@ import { useMobile } from "hooks"; import JobsiteIcon from "products/Construction/JobSiteIcon"; import ResponsiveTable, { Column } from "common/ResponsiveTable"; import { useNavigate } from "react-router-dom"; +import { cloneDeep } from "lodash"; interface SiteWithData { site: Site; @@ -46,7 +47,7 @@ export default function Sites() { .listSitesPageData(pageSize, tablePage * pageSize, "asc", "siteName", undefined, as) .then(resp => { setTotal(resp.data.total); - buildTableData(resp.data.sites); + setSites(buildTableData(resp.data.sites)); }) .finally(() => { setLoading(false); @@ -71,7 +72,7 @@ export default function Sites() { data.push(newTableData); } }); - setSites([...data]); + return data; }; useEffect(() => { @@ -186,6 +187,19 @@ export default function Sites() { rows={sites} handleRowsPerPageChange={handleTablePage} onRowClick={goToSite} + loadMore={()=>{ + let current = cloneDeep(sites) + setLoading(true); + siteAPI + .listSitesPageData(pageSize, current.length, "asc", "siteName", undefined, as) + .then(resp => { + let newData = buildTableData(resp.data.sites); + setSites(current.concat(newData)) + }) + .finally(() => { + setLoading(false); + }); + }} /> ) }; @@ -208,6 +222,20 @@ export default function Sites() { setCurrentTab(newValue); }; + const loadMore = () => { + let current = cloneDeep(sites) + setLoading(true); + siteAPI + .listSitesPageData(pageSize, current.length, "asc", "siteName", undefined, as) + .then(resp => { + let newData = buildTableData(resp.data.sites); + setSites(current.concat(newData)) + }) + .finally(() => { + setLoading(false); + }); + } + const mobileView = () => { return ( @@ -297,6 +325,9 @@ export default function Sites() { return siteCards; })} + {sites.length < total && + + } ); }; From 24fcb63ae62c20ac502bcaf62db645b76c041ef9 Mon Sep 17 00:00:00 2001 From: csawatzky Date: Mon, 26 May 2025 16:25:38 -0600 Subject: [PATCH 31/32] trying a new push --- src/pages/Sites.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/Sites.tsx b/src/pages/Sites.tsx index 91ac28b..b17fe0e 100644 --- a/src/pages/Sites.tsx +++ b/src/pages/Sites.tsx @@ -258,7 +258,7 @@ export default function Sites() { siteCards.push( { goToSite(site); }}> From a0af3cfae7a38da84c9022d3a282aa6fb96af5d8 Mon Sep 17 00:00:00 2001 From: Carter Date: Mon, 26 May 2025 16:37:25 -0600 Subject: [PATCH 32/32] updated staging protobuf --- package-lock.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package-lock.json b/package-lock.json index 64aa39b..a615335 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10864,7 +10864,7 @@ }, "node_modules/protobuf-ts": { "version": "1.0.0", - "resolved": "git+https://gitlab+deploy-token-50627:hv8mB4WkyvtjBpJKU1rN@gitlab.com/brandx/protobuf-ts.git#3f9a71a350651fb151d18514f990794f62a8e0cc", + "resolved": "git+https://gitlab+deploy-token-50627:hv8mB4WkyvtjBpJKU1rN@gitlab.com/brandx/protobuf-ts.git#d0bcb220e6c30be5395bee28728787e7922558ce", "dependencies": { "protobufjs": "^6.8.8" }