frontend/src/device/DevicesSummary.tsx
2025-05-23 15:04:27 -06:00

226 lines
No EOL
6.7 KiB
TypeScript

import {
AutoAwesomeMosaic,
Battery20,
Check,
PermScanWifi,
} from "@mui/icons-material";
import { Card, Grid2, Theme, Typography } from "@mui/material";
import { green } from "@mui/material/colors";
import { makeStyles } from "@mui/styles";
import classNames from "classnames";
import CircleGraphIcon from "common/CircleGraphIcon";
import { Group } from "models";
import { getContextKeys, getContextTypes } from "pbHelpers/Context";
import { pond } from "protobuf-ts/pond";
import { useDeviceAPI } from "providers";
import { useEffect, useState } from "react";
const useStyles = makeStyles((theme: Theme) => {
return ({
card: {
padding: theme.spacing(1),
height: theme.spacing(10),
alignContent: "center",
"&:hover": {
cursor: "pointer",
backgroundColor: "rgba(150, 150, 150, 0.15)",
// border: "1px solid rgba(255, 255, 255, 0.25)"
}
},
cardSelected: {
border: "1px solid " + green[400]
}
});
});
interface Props {
setFieldContains: React.Dispatch<React.SetStateAction<Map<string, string>>>,
group?: Group;
}
export default function DevicesSummary(props: Props) {
const { setFieldContains, group } = props;
const deviceAPI = useDeviceAPI()
// const theme = useTheme()
const classes = useStyles()
const [stats, setStats] = useState<pond.DeviceStatistics>(pond.DeviceStatistics.create())
const [loadingStats, setLoadingStats] = useState(false)
const [selected, setSelected] = useState("");
const getKeys = () => {
let keys = getContextKeys()
if (group) keys.push(group.id().toString())
return keys
}
const getTypes = () => {
let types = getContextTypes()
if (group) types.push("group")
return types
}
const loadDeviceStatistics = () => {
setLoadingStats(true)
deviceAPI.statistics(
getKeys(),
getTypes()
).then(resp => {
let stats = pond.DeviceStatistics.fromObject(resp.data.stats)
setStats(stats)
}).finally(() => {
setLoadingStats(false)
})
}
useEffect(() => {
loadDeviceStatistics()
}, [group])
useEffect(() => {
switch (selected) {
case "total":
setFieldContains(new Map<string, string>());
break;
case "active":
setFieldContains(new Map<string, string>([["state", "DEVICE_STATE_OK"]]));
break;
case "warning":
setFieldContains(new Map<string, string>([["state", "DEVICE_STATE_LOW_POWER"]]));
break;
case "offline":
setFieldContains(new Map<string, string>([["state", "DEVICE_STATE_MISSING"]]));
break;
}
}, [selected])
const getPercent = (devices: number) => {
const ratio = devices/stats.total
return ratio*100
}
return (
<Grid2 container spacing={1} marginBlock={1} >
<Grid2 size={{xs: 6, sm: 3}}>
<Card
className={
classNames(
classes.card,
selected==="total" ? classes.cardSelected : undefined
)
}
onClick={() => setSelected("total")}
>
<Grid2 container spacing={1} justifyContent={"center"} alignItems={"center"} >
<Grid2>
<CircleGraphIcon
sx={{ marginRight: 1, marginLeft: 1 }}
progress={loadingStats ? undefined : 100}
color={"info"}
icon={<AutoAwesomeMosaic />}
/>
</Grid2>
<Grid2 marginRight={1}>
<Typography variant="h6" >
{stats.total} Devices
</Typography>
<Typography variant="subtitle2" color="textSecondary">
Total
</Typography>
</Grid2>
</Grid2>
</Card>
</Grid2>
<Grid2 size={{xs: 6, sm: 3}}>
<Card
className={
classNames(
classes.card,
selected==="active" ? classes.cardSelected : undefined
)
}
onClick={() => setSelected("active")}
>
<Grid2 container spacing={1} justifyContent={"center"} alignItems={"center"} >
<Grid2>
<CircleGraphIcon
sx={{ marginRight: 1, marginLeft: 1 }}
progress={!loadingStats ? getPercent(stats.active) : undefined}
color={"success"}
icon={<Check />}
/>
</Grid2>
<Grid2 marginRight={1}>
<Typography variant="h6" >
{stats.active} Devices
</Typography>
<Typography variant="subtitle2" color="textSecondary">
Active
</Typography>
</Grid2>
</Grid2>
</Card>
</Grid2>
<Grid2 size={{xs: 6, sm: 3}}>
<Card
className={
classNames(
classes.card,
selected==="warning" ? classes.cardSelected : undefined
)
}
onClick={() => setSelected("warning")}
>
<Grid2 container spacing={1} justifyContent={"center"} alignItems={"center"} >
<Grid2>
<CircleGraphIcon
sx={{ marginRight: 1, marginLeft: 1 }}
progress={!loadingStats ? getPercent(stats.warning) : undefined}
color="warning"
icon={<Battery20 />}
/>
</Grid2>
<Grid2 marginRight={1}>
<Typography variant="h6" >
{stats.warning} Devices
</Typography>
<Typography variant="subtitle2" color="textSecondary">
Low Power
</Typography>
</Grid2>
</Grid2>
</Card>
</Grid2>
<Grid2 size={{xs: 6, sm: 3}}>
<Card
className={
classNames(
classes.card,
selected==="offline" ? classes.cardSelected : undefined
)
}
onClick={() => setSelected("offline")}
>
<Grid2 container spacing={1} justifyContent={"center"} alignItems={"center"} >
<Grid2>
<CircleGraphIcon
sx={{ marginRight: 1, marginLeft: 1 }}
progress={!loadingStats ? getPercent(stats.offline) : undefined}
color={green[50]}
icon={<PermScanWifi />}
/>
</Grid2>
<Grid2 marginRight={1}>
<Typography variant="h6" >
{stats.offline} Devices
</Typography>
<Typography variant="subtitle2" color="textSecondary">
Offline
</Typography>
</Grid2>
</Grid2>
</Card>
</Grid2>
</Grid2>
)
}