frontend/src/pages/Devices.tsx
2024-12-18 14:00:21 -06:00

258 lines
No EOL
7.2 KiB
TypeScript

import { DeveloperBoard as ProvisionIcon, LibraryAdd as CreateGroupIcon } from "@mui/icons-material";
import { Box, Chip, IconButton, Theme, Tooltip, Typography } from "@mui/material";
import { blue, green } from "@mui/material/colors";
import { makeStyles } from "@mui/styles";
import ResponsiveTable, { Column } from "common/ResponsiveTable";
import ProvisionDevice from "device/ProvisionDevice";
import { pond } from "protobuf-ts/pond";
import { useDeviceAPI, useGroupAPI } from "providers";
import { useEffect, useState } from "react";
import PageContainer from "./PageContainer";
import { useMobile } from "hooks";
import { useLocation, useNavigate } from "react-router-dom";
import { or } from "utils/types";
import { getContextKeys, getContextTypes } from "pbHelpers/Context";
import { getDeviceStateHelper } from "pbHelpers/DeviceState";
import { Group } from "models";
import GroupSettings from "group/GroupSettings";
const useStyles = makeStyles((theme: Theme) => {
return ({
provisionIcon: {
color: blue["700"]
},
cellContainer: {
margin: theme.spacing(1),
marginLeft: theme.spacing(2),
display: "flex",
// justifyContent: "center",
},
green: {
color: green["600"]
},
});
});
export default function Devices() {
const isMobile = useMobile();
const classes = useStyles();
const navigate = useNavigate();
const location = useLocation();
const deviceAPI = useDeviceAPI();
const groupAPI = useGroupAPI();
const [limit, setLimit] = useState(10);
const [page, setPage] = useState(0);
const [order, ] = useState<"asc" | "desc">("asc");
const [orderBy, ] = useState("name");
const [search, setSearch] = useState("");
const [total, setTotal] = useState(0);
const [devices, setDevices] = useState<pond.Device[]>([])
const [isProvisionDialogOpen, setIsProvisionDialogOpen] = useState<boolean>(false);
const [groupLimit, setGroupLimit] = useState(10);
const [groupPage, setGroupPage] = useState(0);
const [orderGroup, ] = useState<"asc" | "desc">("asc");
const [orderGroupBy, ] = useState("name");
const [searchGroup, setGroupSearch] = useState("");
const [totalGroups, setTotalGroups] = useState(0);
const [selectedGroup, setSelectedGroup] = useState<Group | undefined>(undefined);
const [groupSettingsMode, setGroupSettingsMode] = useState<
"add" | "update" | "remove" | undefined
>(undefined);
const [groupSettingsIsOpen, setGroupSettingsIsOpen] = useState<boolean>(false);
const openProvisionDialog = () => {
setIsProvisionDialogOpen(true);
};
const closeProvisionDialog = () => {
setIsProvisionDialogOpen(false);
};
const openGroupSettings = (
group: Group | undefined,
mode: "add" | "update" | "remove" | undefined
) => {
setGroupSettingsIsOpen(true);
setGroupSettingsMode(mode);
setSelectedGroup(group);
};
const closeGroupSettings = (event: any) => {
setGroupSettingsIsOpen(false);
setGroupSettingsMode(undefined);
setSelectedGroup(undefined);
};
const loadGroups = () => {
groupAPI.listGroups(
groupLimit,
groupPage*groupLimit,
orderGroup,
orderGroupBy,
searchGroup,
).then(resp => {
console.log(resp.data)
})
}
const loadDevices = () => {
deviceAPI.list(
limit,
page*limit,
order,
orderBy,
search,
undefined,
undefined,
undefined,
undefined,
undefined,
undefined,
getContextKeys(),
getContextTypes()
).then(resp => {
setDevices(resp.data.devices)
setTotal(resp.data.total)
})
}
useEffect(() => {
loadDevices()
}, [limit, page, order, orderBy, search])
useEffect(() => {
loadGroups()
}, [groupLimit, groupPage, orderGroup, orderGroupBy, searchGroup])
const handleChange = (event: any) => {
setLimit(event.target.value);
};
const appendToUrl = (appendage: number | string) => {
const basePath = location.pathname.replace(/\/$/, "");
return(`${basePath}/${appendage}`);
};
const toDevice = (device: pond.Device) => {
navigate(appendToUrl(or(device.settings?.deviceId, "")), { state: {device: device} })
};
const columns = (): Column<pond.Device>[] => {
return [
{
title: "State",
render: (device) => {
const status = device.status ?? pond.DeviceStatus.create()
const deviceStateHelper = getDeviceStateHelper(status.state);
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= () => {
return (
<Tooltip title="Provision Device">
<IconButton onClick={openProvisionDialog} aria-label="Provision Device">
<ProvisionIcon className={classes.provisionIcon} />
</IconButton>
</Tooltip>
)
}
const groupButton = () => {
return (
<Tooltip title="Create Group">
<IconButton
onClick={() => openGroupSettings(undefined, "add")}
aria-label="Create Group">
<CreateGroupIcon className={classes.green} />
</IconButton>
</Tooltip>
)
}
const subtitle = () => {
return (
<>
{provisionButton()}
{groupButton()}
</>
)
}
return(
<PageContainer padding={isMobile ? 0 : 2}>
<ResponsiveTable<pond.Device>
title="Devices"
subtitle={subtitle()}
rows={devices}
columns={columns()}
total={total}
pageSize={limit}
page={page}
setPage={setPage}
handleRowsPerPageChange={handleChange}
onRowClick={toDevice}
setSearchText={setSearch}
/>
<ProvisionDevice
isOpen={isProvisionDialogOpen}
refreshCallback={loadDevices}
closeDialogCallback={closeProvisionDialog}
/>
<GroupSettings
initialGroup={selectedGroup}
mode={groupSettingsMode}
isDialogOpen={groupSettingsIsOpen}
closeDialogCallback={closeGroupSettings}
refreshCallback={loadGroups}
canEdit={true}
/>
</PageContainer>
)
}