listing devices and linking them to their respoective page
This commit is contained in:
parent
03f8ed20ca
commit
84d06d9783
7 changed files with 119 additions and 27 deletions
|
|
@ -13,11 +13,11 @@ const useStyles = makeStyles((theme: Theme) => {
|
||||||
border: "none"
|
border: "none"
|
||||||
},
|
},
|
||||||
tableContainer: {
|
tableContainer: {
|
||||||
margin: theme.spacing(1),
|
// margin: theme.spacing(1),
|
||||||
[theme.breakpoints.up("sm")]: {
|
// [theme.breakpoints.up("sm")]: {
|
||||||
margin: theme.spacing(2)
|
// margin: theme.spacing(2)
|
||||||
},
|
// },
|
||||||
width: "auto"
|
// width: "auto"
|
||||||
},
|
},
|
||||||
title: {
|
title: {
|
||||||
padding: theme.spacing(1)
|
padding: theme.spacing(1)
|
||||||
|
|
@ -41,6 +41,12 @@ const useStyles = makeStyles((theme: Theme) => {
|
||||||
// padding: theme.spacing(1),
|
// padding: theme.spacing(1),
|
||||||
margin: theme.spacing(1),
|
margin: theme.spacing(1),
|
||||||
|
|
||||||
|
},
|
||||||
|
rowHover: {
|
||||||
|
cursor: "pointer",
|
||||||
|
"&:hover": {
|
||||||
|
backgroundColor: "rgba(150, 150, 150, 0.05)"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})},
|
})},
|
||||||
);
|
);
|
||||||
|
|
@ -69,6 +75,7 @@ interface Props<T> {
|
||||||
noDataMessage?: string;
|
noDataMessage?: string;
|
||||||
renderMobile?: (row: T) => JSX.Element;
|
renderMobile?: (row: T) => JSX.Element;
|
||||||
hideKeys?: boolean;
|
hideKeys?: boolean;
|
||||||
|
onRowClick?: (row: T) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function ResponsiveTable<T extends Object>(props: Props<T>) {
|
export default function ResponsiveTable<T extends Object>(props: Props<T>) {
|
||||||
|
|
@ -90,6 +97,7 @@ export default function ResponsiveTable<T extends Object>(props: Props<T>) {
|
||||||
noDataMessage,
|
noDataMessage,
|
||||||
renderMobile,
|
renderMobile,
|
||||||
hideKeys,
|
hideKeys,
|
||||||
|
onRowClick
|
||||||
} = props
|
} = props
|
||||||
const classes = useStyles();
|
const classes = useStyles();
|
||||||
|
|
||||||
|
|
@ -191,7 +199,7 @@ export default function ResponsiveTable<T extends Object>(props: Props<T>) {
|
||||||
)
|
)
|
||||||
if (subtitle) return (
|
if (subtitle) return (
|
||||||
<Box className={classes.subtitle}>
|
<Box className={classes.subtitle}>
|
||||||
{subtitle} haha lol
|
{subtitle}
|
||||||
</Box>
|
</Box>
|
||||||
)
|
)
|
||||||
return null
|
return null
|
||||||
|
|
@ -358,7 +366,7 @@ export default function ResponsiveTable<T extends Object>(props: Props<T>) {
|
||||||
rows.map((row, index) => {
|
rows.map((row, index) => {
|
||||||
return (
|
return (
|
||||||
<React.Fragment key={"row-"+index}>
|
<React.Fragment key={"row-"+index}>
|
||||||
<TableRow >
|
<TableRow onClick={() => onRowClick&&onRowClick(row)} className={onRowClick ? classes.rowHover : undefined}>
|
||||||
{renderGutter &&
|
{renderGutter &&
|
||||||
<TableCell width={1}>
|
<TableCell width={1}>
|
||||||
<IconButton sx={{
|
<IconButton sx={{
|
||||||
|
|
|
||||||
|
|
@ -71,6 +71,7 @@ export default function Router(props: Props) {
|
||||||
<Route path="/teams" element={<Teams/>} />
|
<Route path="/teams" element={<Teams/>} />
|
||||||
<Route path="/teams/:teamID" element={<TeamPage/>} />
|
<Route path="/teams/:teamID" element={<TeamPage/>} />
|
||||||
<Route path="/users" element={<Users/>} />
|
<Route path="/users" element={<Users/>} />
|
||||||
|
<Route path="*/devices" element={<Devices/>} />
|
||||||
<Route path="/devices" element={<Devices/>} />
|
<Route path="/devices" element={<Devices/>} />
|
||||||
{/* <Route
|
{/* <Route
|
||||||
path="/teams/:teamID"
|
path="/teams/:teamID"
|
||||||
|
|
|
||||||
|
|
@ -1,20 +1,41 @@
|
||||||
import { DeveloperBoard as ProvisionIcon } from "@mui/icons-material";
|
import { DeveloperBoard as ProvisionIcon } from "@mui/icons-material";
|
||||||
import { IconButton, Theme, Tooltip } from "@mui/material";
|
import { 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 ProvisionDevice from "device/ProvisionDevice";
|
import ProvisionDevice from "device/ProvisionDevice";
|
||||||
import { useState } from "react";
|
import { pond } from "protobuf-ts/pond";
|
||||||
|
import { useDeviceAPI } 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";
|
||||||
|
|
||||||
const useStyles = makeStyles((theme: Theme) => {
|
const useStyles = makeStyles((theme: Theme) => {
|
||||||
return ({
|
return ({
|
||||||
provisionIcon: {
|
provisionIcon: {
|
||||||
color: blue["700"]
|
color: blue["700"]
|
||||||
|
},
|
||||||
|
cellContainer: {
|
||||||
|
padding: theme.spacing(1)
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
export default function Devices() {
|
export default function Devices() {
|
||||||
|
const isMobile = useMobile();
|
||||||
const classes = useStyles();
|
const classes = useStyles();
|
||||||
|
const navigate = useNavigate();
|
||||||
|
const location = useLocation();
|
||||||
|
const deviceAPI = useDeviceAPI();
|
||||||
|
const [limit, setLimit] = useState(10);
|
||||||
|
const [offset, setOffset] = 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 [isProvisionDialogOpen, setIsProvisionDialogOpen] = useState<boolean>(false);
|
||||||
|
|
||||||
const openProvisionDialog = () => {
|
const openProvisionDialog = () => {
|
||||||
|
|
@ -25,18 +46,73 @@ export default function Devices() {
|
||||||
setIsProvisionDialogOpen(false);
|
setIsProvisionDialogOpen(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
return(
|
const loadDevices = () => {
|
||||||
<>
|
deviceAPI.list(limit, offset*limit, order, orderBy, search).then(resp => {
|
||||||
|
setDevices(resp.data.devices)
|
||||||
|
setTotal(resp.data.total)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
loadDevices()
|
||||||
|
}, [limit, offset, order, orderBy, search])
|
||||||
|
|
||||||
|
const handleChange = (event: any) => {
|
||||||
|
setLimit(event.target.value);
|
||||||
|
};
|
||||||
|
|
||||||
|
const appendToUrl = (appendage: number | string) => {
|
||||||
|
const basePath = location.pathname.replace(/\/$/, ""); // Ensure no trailing slash
|
||||||
|
return(`${basePath}/${appendage}`);
|
||||||
|
};
|
||||||
|
|
||||||
|
const toDevice = (device: pond.Device) => {
|
||||||
|
navigate(appendToUrl(or(device.settings?.deviceId, "")))
|
||||||
|
};
|
||||||
|
|
||||||
|
const columns = (): Column<pond.Device>[] => {
|
||||||
|
return [{
|
||||||
|
title: "Device",
|
||||||
|
render: (device) => {
|
||||||
|
return (
|
||||||
|
<Typography className={classes.cellContainer}>
|
||||||
|
{device.settings?.name ? device.settings.name : "Device " + device.settings?.deviceId}
|
||||||
|
</Typography>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
|
||||||
|
const provisionButton= () => {
|
||||||
|
return (
|
||||||
<Tooltip title="Provision Device">
|
<Tooltip title="Provision Device">
|
||||||
<IconButton onClick={openProvisionDialog} aria-label="Provision Device">
|
<IconButton onClick={openProvisionDialog} aria-label="Provision Device">
|
||||||
<ProvisionIcon className={classes.provisionIcon} />
|
<ProvisionIcon className={classes.provisionIcon} />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return(
|
||||||
|
<PageContainer padding={isMobile ? 0 : 2}>
|
||||||
|
<ResponsiveTable<pond.Device>
|
||||||
|
title="Devices"
|
||||||
|
subtitle={provisionButton()}
|
||||||
|
rows={devices}
|
||||||
|
columns={columns()}
|
||||||
|
total={total}
|
||||||
|
pageSize={limit}
|
||||||
|
page={offset}
|
||||||
|
setPage={setOffset}
|
||||||
|
handleRowsPerPageChange={handleChange}
|
||||||
|
onRowClick={toDevice}
|
||||||
|
setSearchText={setSearch}
|
||||||
|
/>
|
||||||
<ProvisionDevice
|
<ProvisionDevice
|
||||||
isOpen={isProvisionDialogOpen}
|
isOpen={isProvisionDialogOpen}
|
||||||
refreshCallback={/*load*/()=>{}}
|
refreshCallback={loadDevices}
|
||||||
closeDialogCallback={closeProvisionDialog}
|
closeDialogCallback={closeProvisionDialog}
|
||||||
/>
|
/>
|
||||||
</>
|
</PageContainer>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -38,11 +38,12 @@ interface Props extends PropsWithChildren{
|
||||||
fullViewport?: boolean;
|
fullViewport?: boolean;
|
||||||
isCenterCenter?: boolean;
|
isCenterCenter?: boolean;
|
||||||
sx?: SxProps<Theme>;
|
sx?: SxProps<Theme>;
|
||||||
|
padding?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const PageContainer: React.FunctionComponent<Props> = props => {
|
export const PageContainer: React.FunctionComponent<Props> = props => {
|
||||||
const classes = useStyles();
|
const classes = useStyles();
|
||||||
const { children, fullViewport, isCenterCenter, sx } = props;
|
const { children, fullViewport, isCenterCenter, sx, padding } = props;
|
||||||
// let fullViewport = false
|
// let fullViewport = false
|
||||||
// let isCenterCenter = false
|
// let isCenterCenter = false
|
||||||
return (
|
return (
|
||||||
|
|
@ -51,7 +52,10 @@ export const PageContainer: React.FunctionComponent<Props> = props => {
|
||||||
fullViewport ? classes.fullViewportContainer : classes.pageContainer,
|
fullViewport ? classes.fullViewportContainer : classes.pageContainer,
|
||||||
isCenterCenter && classes.centerCenter
|
isCenterCenter && classes.centerCenter
|
||||||
)}
|
)}
|
||||||
sx={sx}
|
sx={{
|
||||||
|
...sx, // Spread existing sx
|
||||||
|
...(padding !== undefined && { padding }), // Conditionally add padding
|
||||||
|
}}
|
||||||
disableGutters
|
disableGutters
|
||||||
maxWidth={false}
|
maxWidth={false}
|
||||||
children={<React.Fragment>{children}</React.Fragment>}
|
children={<React.Fragment>{children}</React.Fragment>}
|
||||||
|
|
|
||||||
|
|
@ -199,7 +199,7 @@ export default function TeamPage() {
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<PageContainer sx={{ padding: isMobile ? 1 : 2 }}>
|
<PageContainer padding={isMobile ? 0 : 2}>
|
||||||
<Box className={classes.container}>
|
<Box className={classes.container}>
|
||||||
<Box className={classes.leftBox}>
|
<Box className={classes.leftBox}>
|
||||||
{title()}
|
{title()}
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,11 @@
|
||||||
|
import { useMobile } from "hooks";
|
||||||
import PageContainer from "pages/PageContainer";
|
import PageContainer from "pages/PageContainer";
|
||||||
import TeamList from "teams/TeamList";
|
import TeamList from "teams/TeamList";
|
||||||
|
|
||||||
export default function Teams() {
|
export default function Teams() {
|
||||||
|
const isMobile = useMobile()
|
||||||
return (
|
return (
|
||||||
<PageContainer>
|
<PageContainer padding={isMobile ? 0 : 2}>
|
||||||
<TeamList />
|
<TeamList />
|
||||||
</PageContainer>
|
</PageContainer>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,7 @@ import {
|
||||||
} from "@mui/material";
|
} from "@mui/material";
|
||||||
import ResponsiveDialog from "common/ResponsiveDialog";
|
import ResponsiveDialog from "common/ResponsiveDialog";
|
||||||
import SearchSelect, { Option } from "common/SearchSelect";
|
import SearchSelect, { Option } from "common/SearchSelect";
|
||||||
import { useSnackbar, useUserAPI } from "hooks";
|
import { useMobile, useSnackbar, useUserAPI } from "hooks";
|
||||||
import { User } from "models";
|
import { User } from "models";
|
||||||
import PageContainer from "pages/PageContainer";
|
import PageContainer from "pages/PageContainer";
|
||||||
// import { ListUsersResponse } from "providers/pond/userAPI";
|
// import { ListUsersResponse } from "providers/pond/userAPI";
|
||||||
|
|
@ -88,6 +88,7 @@ const useStyles = makeStyles((theme: Theme) => {
|
||||||
|
|
||||||
export default function Users() {
|
export default function Users() {
|
||||||
const classes = useStyles();
|
const classes = useStyles();
|
||||||
|
const isMobile = useMobile();
|
||||||
const userAPI = useUserAPI();
|
const userAPI = useUserAPI();
|
||||||
// let tableRef: any = React.createRef();
|
// let tableRef: any = React.createRef();
|
||||||
const { error, success } = useSnackbar();
|
const { error, success } = useSnackbar();
|
||||||
|
|
@ -347,7 +348,7 @@ export default function Users() {
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<PageContainer>
|
<PageContainer padding={isMobile ? 0 : 2}>
|
||||||
<ResponsiveTable<User>
|
<ResponsiveTable<User>
|
||||||
title="Users"
|
title="Users"
|
||||||
rows={rows}
|
rows={rows}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue