committing all group related code
This commit is contained in:
parent
0ec54b2d31
commit
2d98917322
14 changed files with 541 additions and 102 deletions
|
|
@ -21,8 +21,6 @@ const useStyles = makeStyles((theme: Theme) => {
|
|||
});
|
||||
|
||||
export default function DevicePage() {
|
||||
console.log("device")
|
||||
|
||||
const deviceAPI = useDeviceAPI()
|
||||
const snackbar = useSnackbar()
|
||||
const isMobile = useMobile()
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { DeveloperBoard as ProvisionIcon, LibraryAdd as CreateGroupIcon } from "@mui/icons-material";
|
||||
import { Box, Chip, CircularProgress, IconButton, Tab, Tabs, Theme, Tooltip } from "@mui/material";
|
||||
import { Box, Chip, CircularProgress, IconButton, Tab, Tabs, Theme, Tooltip, Typography } from "@mui/material";
|
||||
import { blue, green } from "@mui/material/colors";
|
||||
import { makeStyles } from "@mui/styles";
|
||||
import ResponsiveTable, { Column } from "common/ResponsiveTable";
|
||||
|
|
@ -47,6 +47,14 @@ const useStyles = makeStyles((theme: Theme) => {
|
|||
green: {
|
||||
color: green["600"]
|
||||
},
|
||||
tab: {
|
||||
display: "block",
|
||||
overflow: "hidden",
|
||||
textOverflow: "ellipsis",
|
||||
maxWidth: theme.spacing(26),
|
||||
whiteSpace: "nowrap",
|
||||
padding: theme.spacing(1.5)
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -78,7 +86,7 @@ export default function Devices() {
|
|||
// const [totalGroups, setTotalGroups] = useState(0);
|
||||
|
||||
const updateGroups = (newGroups: Group[]) => {
|
||||
setGroups(newGroups); // Trigger UI update if needed
|
||||
setGroups(newGroups);
|
||||
};
|
||||
|
||||
const [selectedGroup, setSelectedGroup] = useState<Group | undefined>(undefined);
|
||||
|
|
@ -152,7 +160,6 @@ export default function Devices() {
|
|||
})
|
||||
}
|
||||
|
||||
|
||||
const loadDevices = () => {
|
||||
setDevicesLoading(true)
|
||||
deviceAPI.list(
|
||||
|
|
@ -359,14 +366,14 @@ export default function Devices() {
|
|||
|
||||
return(
|
||||
<PageContainer padding={isMobile ? 0 : 2}>
|
||||
<SmartBreadcrumb key={"Device page crumbs"}/>
|
||||
{/* <SmartBreadcrumb /> */}
|
||||
<Box
|
||||
// onMouseEnter={() => setTabsHovered(true)}
|
||||
// onMouseLeave={() => setTabsHovered(false)}
|
||||
sx={{
|
||||
borderRadius: 1,
|
||||
border: "1px solid rgba(150, 150, 150, 0.2)",
|
||||
marginTop: 1,
|
||||
marginTop: 0,
|
||||
marginBottom: 1
|
||||
}}
|
||||
>
|
||||
|
|
@ -377,19 +384,19 @@ export default function Devices() {
|
|||
scrollButtons="auto"
|
||||
sx={{ borderRadius: 1 }}
|
||||
>
|
||||
<Tab value={"all"} label="All" />
|
||||
<Tab wrapped value={"all"} label="All" />
|
||||
{groupsLoading ?
|
||||
<Tab value={"loading"} label={<CircularProgress />}/>
|
||||
: groups.map((group, index) => {
|
||||
if (group.id()) return (
|
||||
<Tab value={group.id().toString()} label={group.name()} key={"group-tab-"+index}/>
|
||||
<Tab className={classes.tab} wrapped value={group.id().toString()} label={<Typography variant="inherit" noWrap>{group.name()}</Typography>} key={"group-tab-"+index}/>
|
||||
)
|
||||
})}
|
||||
<Tab value={"never"} label={<CreateGroupIcon className={classes.green} />} onClick={() => openGroupSettings(undefined, "add")} />
|
||||
<Tab wrapped value={"never"} label={<CreateGroupIcon className={classes.green} />} onClick={() => openGroupSettings(undefined, "add")} />
|
||||
</Tabs>
|
||||
</Box>
|
||||
<ResponsiveTable<pond.Device>
|
||||
title="Devices"
|
||||
title={<SmartBreadcrumb paddingBottom={1}/>}
|
||||
subtitle={subtitle()}
|
||||
rows={devices}
|
||||
columns={columns()}
|
||||
|
|
|
|||
|
|
@ -1,25 +1,230 @@
|
|||
import { Grid2 } from "@mui/material";
|
||||
import { Box, Chip, Grid2, Theme, Tooltip, Typography } from "@mui/material";
|
||||
import SmartBreadcrumb from "common/SmartBreadcrumb";
|
||||
import { Group } from "models"
|
||||
import { useState } from "react"
|
||||
import { useLocation, useParams } from "react-router-dom";
|
||||
import { Device, Group } from "models"
|
||||
import { useEffect, useState } from "react"
|
||||
import { useLocation, useNavigate, useParams } from "react-router-dom";
|
||||
import PageContainer from "./PageContainer";
|
||||
import { useDeviceAPI, useGroupAPI } from "providers";
|
||||
import GroupActions from "group/GroupActions";
|
||||
import { pond } from "protobuf-ts/pond";
|
||||
import { getContextKeys, getContextTypes } from "pbHelpers/Context";
|
||||
import ResponsiveTable, { Column } from "common/ResponsiveTable";
|
||||
import { or } from "utils/types";
|
||||
import { appendToUrl } from "navigation/Router";
|
||||
import { getDeviceStateHelper } from "pbHelpers/DeviceState";
|
||||
import { makeStyles } from "@mui/styles";
|
||||
import { blue, green } from "@mui/material/colors";
|
||||
import { permissionToString } from "pbHelpers/Permission";
|
||||
|
||||
const useStyles = makeStyles((theme: Theme) => {
|
||||
return ({
|
||||
buttonBox: {
|
||||
position: "absolute",
|
||||
zIndex: 1,
|
||||
right: theme.spacing(4),
|
||||
transform: "translateY(-50%)", // Move it up by half its height
|
||||
transition: "opacity 0.2s ease-in-out",
|
||||
opacity: 1,
|
||||
// top: 0,
|
||||
},
|
||||
fadeBox: {
|
||||
opacity: 0, // Fully transparent by default
|
||||
transition: "opacity 0.2s ease-in-out", // Smooth transition
|
||||
"&:hover": {
|
||||
opacity: 1, // Fully opaque on hover
|
||||
},
|
||||
},
|
||||
provisionIcon: {
|
||||
color: blue["700"]
|
||||
},
|
||||
cellContainer: {
|
||||
margin: theme.spacing(1),
|
||||
marginLeft: theme.spacing(2),
|
||||
display: "flex",
|
||||
// justifyContent: "center",
|
||||
},
|
||||
green: {
|
||||
color: green["600"]
|
||||
},
|
||||
tab: {
|
||||
display: "block",
|
||||
overflow: "hidden",
|
||||
textOverflow: "ellipsis",
|
||||
maxWidth: theme.spacing(26),
|
||||
whiteSpace: "nowrap",
|
||||
padding: theme.spacing(1.5)
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
export default function GroupPage() {
|
||||
|
||||
const groupAPI = useGroupAPI()
|
||||
const deviceAPI = useDeviceAPI()
|
||||
const navigate = useNavigate()
|
||||
const classes = useStyles()
|
||||
|
||||
const groupID = useParams<{ groupID: string }>()?.groupID ?? "";
|
||||
const groupID = parseInt(useParams<{ groupID: string }>()?.groupID ?? "0");
|
||||
const { state } = useLocation();
|
||||
const [group, setGroup] = useState<Group>(state?.device ? Group.create(state.device) : Group.create())
|
||||
|
||||
const [group, setGroup] = useState<Group>(state?.group ? Group.create(state.group) : Group.create())
|
||||
const [loadingGroup, setLoadingGroup] = useState(false)
|
||||
const [loadingDevices, setLoadingDevices] = useState(false)
|
||||
const [devices, setDevices] = useState<Device[]>([])
|
||||
const [permissions, setPermissions] = useState<pond.Permission[]>([])
|
||||
const [limit, setLimit] = useState(0)
|
||||
const [total, setTotal] = useState(0)
|
||||
const [page, setPage] = useState(1)
|
||||
const [order, setOrder] = useState<"asc" | "desc">("asc")
|
||||
const [orderBy, setOrderBy] = useState("")
|
||||
const [search, setSearch] = useState("")
|
||||
|
||||
const loadGroup = () => {
|
||||
setLoadingGroup(true)
|
||||
groupAPI.getGroupAndPermissions(groupID).then(resp => {
|
||||
if (resp.data.group) setGroup(Group.create(resp.data.group))
|
||||
let newPerms: pond.Permission[] = []
|
||||
resp.data.permissions.forEach(perm => {
|
||||
if (typeof(perm) === "string") {
|
||||
const permNumber = pond.Permission[perm as keyof typeof pond.Permission]
|
||||
newPerms.push(permNumber)
|
||||
} else {
|
||||
newPerms.push(perm)
|
||||
}
|
||||
})
|
||||
setPermissions(newPerms)
|
||||
}).finally(() => {
|
||||
setLoadingGroup(false)
|
||||
})
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (group.id() === groupID) return;
|
||||
loadGroup()
|
||||
}, [group, groupID])
|
||||
|
||||
const loadDevices = () => {
|
||||
setLoadingDevices(true)
|
||||
deviceAPI.list(
|
||||
limit,
|
||||
page*limit,
|
||||
order,
|
||||
orderBy,
|
||||
search,
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
getContextKeys(),
|
||||
getContextTypes()
|
||||
).then(resp => {
|
||||
let newDevices: Device[] = [];
|
||||
resp.data.devices.forEach(device => {
|
||||
newDevices.push(Device.create(device))
|
||||
})
|
||||
setDevices(newDevices)
|
||||
setTotal(resp.data.total)
|
||||
}).finally(() => {
|
||||
setLoadingDevices(false)
|
||||
})
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
loadDevices()
|
||||
}, [group, limit, page, order, orderBy, search])
|
||||
|
||||
const toDevice = (device: Device) => {
|
||||
navigate(appendToUrl(or(device.settings?.deviceId, "")), { state: {device: device} })
|
||||
};
|
||||
|
||||
const columns = (): Column<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 > 20
|
||||
? description.substring(0, 20) + "..."
|
||||
: description}
|
||||
</span>
|
||||
</Tooltip>
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
const handleChange = (event: any) => {
|
||||
setLimit(event.target.value);
|
||||
};
|
||||
|
||||
return (
|
||||
<Grid2 container justifyContent={"space-between"}>
|
||||
<Grid2>
|
||||
<SmartBreadcrumb groupName={group.name()} />
|
||||
<PageContainer>
|
||||
<Grid2 container justifyContent={"space-between"} alignItems={"center"} paddingBottom={2}>
|
||||
<Grid2 >
|
||||
<SmartBreadcrumb groupName={group.name()} loading={loadingGroup} />
|
||||
</Grid2>
|
||||
<Grid2 marginTop={"auto"} marginBottom="auto">
|
||||
<GroupActions
|
||||
group={group}
|
||||
permissions={permissions ? permissions : []}
|
||||
devices={devices}
|
||||
refreshCallback={() => {}}
|
||||
/>
|
||||
</Grid2>
|
||||
</Grid2>
|
||||
<Grid2>
|
||||
{/* <GroupActions /> */}
|
||||
</Grid2>
|
||||
</Grid2>
|
||||
<ResponsiveTable<Device>
|
||||
title={group.name()}
|
||||
subtitle={group.settings.description}
|
||||
rows={devices}
|
||||
columns={columns()}
|
||||
total={total}
|
||||
pageSize={limit}
|
||||
page={page}
|
||||
setPage={setPage}
|
||||
handleRowsPerPageChange={handleChange}
|
||||
onRowClick={toDevice}
|
||||
setSearchText={setSearch}
|
||||
isLoading={loadingDevices}
|
||||
/>
|
||||
</PageContainer>
|
||||
)
|
||||
}
|
||||
|
|
@ -9,6 +9,7 @@ import { useGroupAPI } from "providers";
|
|||
import { useEffect, useState } from "react";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { or } from "utils/types";
|
||||
import PageContainer from "./PageContainer";
|
||||
|
||||
const useStyles = makeStyles((theme: Theme) => {
|
||||
const isMobile = useMobile()
|
||||
|
|
@ -115,18 +116,20 @@ export default function GroupsPage() {
|
|||
}
|
||||
|
||||
return (
|
||||
<ResponsiveTable<pond.Group>
|
||||
title={<SmartBreadcrumb />}
|
||||
rows={groups}
|
||||
columns={columns()}
|
||||
total={total}
|
||||
pageSize={limit}
|
||||
page={page}
|
||||
setPage={setPage}
|
||||
handleRowsPerPageChange={handleRowsPerPageChange}
|
||||
isLoading={loading}
|
||||
setSearchText={setSearch}
|
||||
onRowClick={handleRowClick}
|
||||
/>
|
||||
<PageContainer >
|
||||
<ResponsiveTable<pond.Group>
|
||||
title={<SmartBreadcrumb />}
|
||||
rows={groups}
|
||||
columns={columns()}
|
||||
total={total}
|
||||
pageSize={limit}
|
||||
page={page}
|
||||
setPage={setPage}
|
||||
handleRowsPerPageChange={handleRowsPerPageChange}
|
||||
isLoading={loading}
|
||||
setSearchText={setSearch}
|
||||
onRowClick={handleRowClick}
|
||||
/>
|
||||
</PageContainer>
|
||||
)
|
||||
}
|
||||
|
|
@ -1,7 +1,8 @@
|
|||
import { Container, SxProps, Theme } from "@mui/material";
|
||||
import { makeStyles } from "@mui/styles";
|
||||
import classNames from "classnames";
|
||||
import React, { PropsWithChildren } from "react";
|
||||
import { useMobile } from "hooks";
|
||||
import { PropsWithChildren } from "react";
|
||||
|
||||
const useStyles = makeStyles((theme: Theme) => ({
|
||||
pageContainer: {
|
||||
|
|
@ -41,11 +42,13 @@ interface Props extends PropsWithChildren{
|
|||
padding?: number;
|
||||
}
|
||||
|
||||
export const PageContainer: React.FunctionComponent<Props> = props => {
|
||||
export const PageContainer: React.FunctionComponent<Props> = (props: Props) => {
|
||||
const classes = useStyles();
|
||||
const { children, fullViewport, isCenterCenter, sx, padding } = props;
|
||||
const isMobile = useMobile();
|
||||
const { children, fullViewport, isCenterCenter, sx } = props;
|
||||
// let fullViewport = false
|
||||
// let isCenterCenter = false
|
||||
const padding = props.padding ? props.padding : isMobile ? 1 : 2;
|
||||
return (
|
||||
<Container
|
||||
className={classNames(
|
||||
|
|
@ -58,7 +61,7 @@ export const PageContainer: React.FunctionComponent<Props> = props => {
|
|||
}}
|
||||
disableGutters
|
||||
maxWidth={false}
|
||||
children={<React.Fragment>{children}</React.Fragment>}
|
||||
children={<>{children}</>}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue