loading device page with relative pathing

This commit is contained in:
Carter 2024-12-12 14:11:08 -06:00
parent 05f7765f82
commit 438451690b
8 changed files with 235 additions and 21 deletions

View file

@ -12,6 +12,7 @@ import TeamPage from "pages/Team";
import Header from "app/Header"; import Header from "app/Header";
import Logout from "pages/Logout"; import Logout from "pages/Logout";
import Devices from "pages/Devices"; import Devices from "pages/Devices";
import DevicePage from "pages/Device";
interface Props { interface Props {
open: boolean, open: boolean,
@ -31,7 +32,7 @@ export default function Router(props: Props) {
return ( return (
<Routes> <Routes>
<Route path="teams/*" element={<TeamsRoute/>} /> <Route path="teams/*" element={<TeamsRoute/>} />
<Route path="devices" element={<DevicesRoute/>} /> <Route path="devices/*" element={<DevicesRoute/>} />
</Routes> </Routes>
) )
} }
@ -65,10 +66,10 @@ export default function Router(props: Props) {
path="" // "/settings/basic" path="" // "/settings/basic"
element={<Devices />} element={<Devices />}
/> />
{/* <Route <Route
path="/:deviceID" // "/settings/basic" path="/:deviceID" // "/settings/basic"
element={<TeamPage />} element={<DevicePage />}
/> */} />
</Routes> </Routes>
</div> </div>
); );

60
src/pages/Device.tsx Normal file
View file

@ -0,0 +1,60 @@
import { Theme, Typography } from "@mui/material";
import { makeStyles } from "@mui/styles";
import { Device } from "models";
import { getContextKeys, getContextTypes } from "pbHelpers/Context";
import { useDeviceAPI, useSnackbar } from "providers";
import { useEffect, useState } from "react";
import { useLocation, useParams } from "react-router-dom";
import PageContainer from "./PageContainer";
import { useMobile } from "hooks";
import LoadingScreen from "app/LoadingScreen";
const useStyles = makeStyles((theme: Theme) => {
// const isMobile = useMobile()
return ({
});
});
export default function DevicePage() {
const deviceAPI = useDeviceAPI()
const snackbar = useSnackbar()
const isMobile = useMobile()
const deviceID = useParams<{ deviceID: string }>()?.deviceID ?? "";
const { state } = useLocation();
const [device, setDevice] = useState<Device>(state?.device ? Device.create(state.device) : Device.create())
const [loading, setLoading] = useState(false)
const loadDevice = () => {
if (loading) return
if (state?.device && parseInt(state.device.settings.deviceId) === device.id()) return
deviceAPI.get(deviceID, false, getContextKeys(), getContextTypes()).then(resp => {
setDevice(Device.create(resp.data))
}).catch(() => {
snackbar.error("Error loading device.")
}).finally(() => {
setLoading(false)
})
}
useEffect(() => {
console.log(device)
}, [device])
useEffect(() => {
loadDevice()
}, [])
if (loading) return (
<LoadingScreen message="Loading device"/>
)
return (
<PageContainer padding={isMobile ? 0 : 2}>
<Typography>
{device.name()}
</Typography>
</PageContainer>
);
}

View file

@ -1,5 +1,5 @@
import { DeveloperBoard as ProvisionIcon } from "@mui/icons-material"; import { DeveloperBoard as ProvisionIcon } from "@mui/icons-material";
import { IconButton, Theme, Tooltip, Typography } from "@mui/material"; import { Box, Chip, IconButton, Theme, Tooltip, Typography } from "@mui/material";
import { blue } from "@mui/material/colors"; import { blue } from "@mui/material/colors";
import { makeStyles } from "@mui/styles"; import { makeStyles } from "@mui/styles";
import ResponsiveTable, { Column } from "common/ResponsiveTable"; import ResponsiveTable, { Column } from "common/ResponsiveTable";
@ -12,6 +12,7 @@ import { useMobile } from "hooks";
import { useLocation, useNavigate } from "react-router-dom"; import { useLocation, useNavigate } from "react-router-dom";
import { or } from "utils/types"; import { or } from "utils/types";
import { getContextKeys, getContextTypes } from "pbHelpers/Context"; import { getContextKeys, getContextTypes } from "pbHelpers/Context";
import { getDeviceStateHelper } from "pbHelpers/DeviceState";
const useStyles = makeStyles((theme: Theme) => { const useStyles = makeStyles((theme: Theme) => {
return ({ return ({
@ -19,7 +20,10 @@ const useStyles = makeStyles((theme: Theme) => {
color: blue["700"] color: blue["700"]
}, },
cellContainer: { cellContainer: {
padding: theme.spacing(1) margin: theme.spacing(1),
marginLeft: theme.spacing(2),
display: "flex",
// justifyContent: "center",
} }
}); });
}); });
@ -82,20 +86,61 @@ export default function Devices() {
}; };
const toDevice = (device: pond.Device) => { const toDevice = (device: pond.Device) => {
navigate(appendToUrl(or(device.settings?.deviceId, ""))) navigate(appendToUrl(or(device.settings?.deviceId, "")), { state: {device: device} })
}; };
const columns = (): Column<pond.Device>[] => { const columns = (): Column<pond.Device>[] => {
return [{ return [
title: "Device", {
render: (device) => { title: "State",
return ( render: (device) => {
<Typography className={classes.cellContainer}> const status = device.status ?? pond.DeviceStatus.create()
{device.settings?.name ? device.settings.name : "Device " + device.settings?.deviceId} const deviceStateHelper = getDeviceStateHelper(status.state);
</Typography> return (
) <Box className={classes.cellContainer}>
} <Tooltip title={deviceStateHelper.description}>{deviceStateHelper.icon}</Tooltip>
}] </Box>
)
}
},
{
title: "ID",
render: device => <span className={classes.cellContainer}>
{device.settings?.deviceId}
</span>
},
{
title: "Device",
render: (device) => {
return (
<Box className={classes.cellContainer} >
<Chip
variant="outlined"
label={device.settings?.name ? device.settings.name : "Device " + device.settings?.deviceId}
/>
</Box>
)
}
},
{
title: "Description",
render: device => {
const description = device.settings?.description ?? ""
return (
<Box className={classes.cellContainer}>
<Tooltip title={device.settings?.description}>
<span>
{description.length > 10
? description.substring(0, 10) + "..."
: description}
</span>
</Tooltip>
</Box>
)
}
},
]
} }
const provisionButton= () => { const provisionButton= () => {

View file

@ -1,6 +1,5 @@
import { useMobile } from "hooks"; import { useMobile } from "hooks";
import PageContainer from "pages/PageContainer"; import PageContainer from "pages/PageContainer";
import { Outlet } from "react-router-dom";
import TeamList from "teams/TeamList"; import TeamList from "teams/TeamList";
export default function Teams() { export default function Teams() {
@ -8,7 +7,6 @@ export default function Teams() {
return ( return (
<PageContainer padding={isMobile ? 0 : 2}> <PageContainer padding={isMobile ? 0 : 2}>
<TeamList /> <TeamList />
<Outlet/>
</PageContainer> </PageContainer>
); );
} }

View file

@ -0,0 +1,101 @@
import {
CheckCircle as OKIcon,
Error as AlarmingIcon,
Help as UnknownIcon,
Warning as WarningIcon
} from "@mui/icons-material";
import { pond } from "protobuf-ts/pond";
import { getTextSecondary } from "theme/text";
export interface DeviceStateHelper {
description: string;
icon: any;
colour: string;
}
const Unknown: DeviceStateHelper = {
description: "Unknown",
icon: <UnknownIcon style={{ color: "var(--status-unknown)" }} />,
colour: "var(--status-unknown)"
};
const OK: DeviceStateHelper = {
description: "OK",
icon: <OKIcon style={{ color: "var(--status-ok)" }} />,
colour: "var(--status-ok)"
};
const LowPower: DeviceStateHelper = {
description: "Low Power",
icon: <WarningIcon style={{ color: "var(--status-warning)" }} />,
colour: "var(--status-warning)"
};
const Missing: DeviceStateHelper = {
description: "Missing",
icon: <AlarmingIcon style={{ color: getTextSecondary() }} />,
colour: getTextSecondary()
};
const DEVICE_STATE_MAP = new Map<pond.DeviceState, DeviceStateHelper>([
[pond.DeviceState.DEVICE_STATE_UNKNOWN, Unknown],
[pond.DeviceState.DEVICE_STATE_OK, OK],
[pond.DeviceState.DEVICE_STATE_LOW_POWER, LowPower],
[pond.DeviceState.DEVICE_STATE_MISSING, Missing]
]);
export function compareEnum<T>(value: string | number, enumMember: T): boolean {
// Get the enum object from the enum member
const enumObj = Object.freeze(enumMember as any).constructor;
// Check if the value matches either the key or the value of the enum member
return Object.keys(enumObj).some(
key => enumObj[key] === value || key === value
);
}
export function getDeviceStateHelper(state: pond.DeviceState): DeviceStateHelper {
if (compareEnum(state, pond.DeviceState.DEVICE_STATE_OK)) {
return OK;
}
const deviceStates = Array.from(DEVICE_STATE_MAP.keys());
for (var i = 0; i < deviceStates.length; i++) {
let key = deviceStates[i];
if (state === key) {
return DEVICE_STATE_MAP.get(key) as DeviceStateHelper;
}
}
return Unknown;
}
export function getDeviceStateDescription(state: pond.DeviceState): string {
return getDeviceStateHelper(state).description;
}
export function getDeviceStateIcon(state: pond.DeviceState): any {
return getDeviceStateHelper(state).icon;
}
export function getDeviceStateColour(state: pond.DeviceState): any {
return getDeviceStateHelper(state).colour;
}
export function filterByDeviceState(searchValue: string, state: pond.DeviceState) {
let matches: string[] = [];
switch (state) {
case pond.DeviceState.DEVICE_STATE_OK:
matches = ["okay", "good", "safe", "stable"];
break;
case pond.DeviceState.DEVICE_STATE_MISSING:
matches = ["alarming", "alarmed", "bad", "error", "alert"];
break;
case pond.DeviceState.DEVICE_STATE_LOW_POWER:
matches = ["warning", "caution", "low power"];
break;
default:
matches = ["unknown", "?"];
break;
}
return matches.some(match => match.indexOf(searchValue.replace("state:", "").trim()) > -1);
}

View file

@ -6,7 +6,6 @@ import { createContext, PropsWithChildren, useContext } from "react";
import { pondURL } from "./pond"; import { pondURL } from "./pond";
import { AxiosResponse } from "axios"; import { AxiosResponse } from "axios";
import { useGlobalState } from "providers"; import { useGlobalState } from "providers";
import { dateRange } from "providers/http";
import moment from "moment"; import moment from "moment";
export interface IDeviceAPIContext { export interface IDeviceAPIContext {

View file

@ -138,7 +138,6 @@ export default function TeamList() {
if (preferences) { if (preferences) {
let newTeamPrefs = cloneDeep(teamPrefs); let newTeamPrefs = cloneDeep(teamPrefs);
let prefs = newTeamPrefs.get(team.id()) ?? pond.TeamPreferences.create() let prefs = newTeamPrefs.get(team.id()) ?? pond.TeamPreferences.create()
console.log(prefs)
prefs.notify = !newTeamPrefs.get(team.id())?.notify prefs.notify = !newTeamPrefs.get(team.id())?.notify
newTeamPrefs.set(team.id(), prefs) newTeamPrefs.set(team.id(), prefs)
teamAPI teamAPI

11
src/theme/text.ts Normal file
View file

@ -0,0 +1,11 @@
import { getThemeType } from "./themeType";
export function getTextPrimary() {
let themeType = getThemeType();
return themeType === "light" ? "rgba(0, 0, 0, 0.87)" : "#FFF";
}
export function getTextSecondary() {
let themeType = getThemeType();
return themeType === "light" ? "rgba(0, 0, 0, 0.54)" : "rgba(255, 255, 255, 0.7)";
}