loading device page with relative pathing
This commit is contained in:
parent
05f7765f82
commit
438451690b
8 changed files with 235 additions and 21 deletions
|
|
@ -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
60
src/pages/Device.tsx
Normal 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>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -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= () => {
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
101
src/pbHelpers/DeviceState.tsx
Normal file
101
src/pbHelpers/DeviceState.tsx
Normal 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);
|
||||||
|
}
|
||||||
|
|
@ -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 {
|
||||||
|
|
|
||||||
|
|
@ -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
11
src/theme/text.ts
Normal 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)";
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue