added ability to prepend paths to smart breadcrumb

This commit is contained in:
Carter 2025-01-07 14:02:39 -06:00
parent 2d98917322
commit 77fb5a6bcf
5 changed files with 49 additions and 25 deletions

View file

@ -85,6 +85,7 @@ interface Props<T> {
renderMobile?: (row: T) => JSX.Element; renderMobile?: (row: T) => JSX.Element;
hideKeys?: boolean; hideKeys?: boolean;
onRowClick?: (row: T) => void; onRowClick?: (row: T) => void;
actions?: string | JSX.Element;
} }
export default function ResponsiveTable<T extends Object>(props: Props<T>) { export default function ResponsiveTable<T extends Object>(props: Props<T>) {
@ -106,7 +107,8 @@ export default function ResponsiveTable<T extends Object>(props: Props<T>) {
noDataMessage, noDataMessage,
renderMobile, renderMobile,
hideKeys, hideKeys,
onRowClick onRowClick,
actions
} = props } = props
const classes = useStyles(); const classes = useStyles();
@ -334,9 +336,16 @@ export default function ResponsiveTable<T extends Object>(props: Props<T>) {
</Box> </Box>
</Grid2> </Grid2>
<Grid2> <Grid2>
{setSearchText &&<Box className={classes.searchContainer}> <Grid2 container alignItems={"center"} spacing={1}>
{searchBar()} <Grid2>
</Box>} {actions && actions}
</Grid2>
<Grid2>
{setSearchText &&<Box className={classes.searchContainer}>
{searchBar()}
</Box>}
</Grid2>
</Grid2>
</Grid2> </Grid2>
</Grid2> </Grid2>
</TableCell> </TableCell>

View file

@ -58,6 +58,7 @@ interface Props {
paddingRight?: number; paddingRight?: number;
paddingTop?: number; paddingTop?: number;
paddingBottom?: number; paddingBottom?: number;
prependPaths?: string[];
} }
interface LinkRouterProps extends LinkProps { interface LinkRouterProps extends LinkProps {
@ -68,6 +69,7 @@ interface LinkRouterProps extends LinkProps {
const LinkRouter = (props: LinkRouterProps) => <Link {...props} component={RouterLink as any} />; const LinkRouter = (props: LinkRouterProps) => <Link {...props} component={RouterLink as any} />;
export default function SmartBreadcrumb(props: Props) { export default function SmartBreadcrumb(props: Props) {
const prependPaths = props.prependPaths;
const theme = useTheme(); const theme = useTheme();
const location = useLocation(); const location = useLocation();
const isMobile = useMobile(); const isMobile = useMobile();
@ -75,6 +77,12 @@ export default function SmartBreadcrumb(props: Props) {
const { loading, reload } = props; const { loading, reload } = props;
const groupID = (): string => { const groupID = (): string => {
let index = prependPaths?.indexOf("groups")
if (index !== undefined && index > -1) {
if (prependPaths && prependPaths[index+1]) {
return prependPaths[index+1]
}
}
const regexMatch = or(location.pathname.match(/\/groups\/([^/]+)/), []) const regexMatch = or(location.pathname.match(/\/groups\/([^/]+)/), [])
// console.log(or(regexMatch[1], "0").toString()) // console.log(or(regexMatch[1], "0").toString())
return or(regexMatch[1], "0").toString(); return or(regexMatch[1], "0").toString();
@ -254,12 +262,14 @@ export default function SmartBreadcrumb(props: Props) {
// console.log(breadcrumbMap) // console.log(breadcrumbMap)
let links: ReactNode[] = []; let links: ReactNode[] = [];
const pathnames = location.pathname.split("/").filter((x: any) => x); let pathnames = location.pathname.split("/").filter((x: any) => x);
const blacklist = breadcrumbBlacklist(); const blacklist = breadcrumbBlacklist();
if (pathnames.length < 1) { if (pathnames.length < 1) {
return [linkComponent("/", "Dashboard", true)]; return [linkComponent("/", "Dashboard", true)];
} }
if (prependPaths) pathnames.unshift(...prependPaths)
pathnames.forEach((_value: any, index: any) => { pathnames.forEach((_value: any, index: any) => {

View file

@ -13,9 +13,10 @@ import { useLocation, useNavigate, useParams } 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"; import { getDeviceStateHelper } from "pbHelpers/DeviceState";
import { Group } from "models"; import { Device, Group } from "models";
import GroupSettings from "group/GroupSettings"; import GroupSettings from "group/GroupSettings";
import SmartBreadcrumb from "common/SmartBreadcrumb"; import SmartBreadcrumb from "common/SmartBreadcrumb";
import GroupActions from "group/GroupActions";
const useStyles = makeStyles((theme: Theme) => { const useStyles = makeStyles((theme: Theme) => {
return ({ return ({
@ -59,7 +60,6 @@ const useStyles = makeStyles((theme: Theme) => {
}); });
export default function Devices() { export default function Devices() {
const [groups, setGroups] = useState<Group[]>([]);
const isMobile = useMobile(); const isMobile = useMobile();
const classes = useStyles(); const classes = useStyles();
const navigate = useNavigate(); const navigate = useNavigate();
@ -73,11 +73,11 @@ export default function Devices() {
const [orderBy, ] = useState("name"); const [orderBy, ] = useState("name");
const [search, setSearch] = useState(""); const [search, setSearch] = useState("");
const [total, setTotal] = useState(0); const [total, setTotal] = useState(0);
const [devices, setDevices] = useState<pond.Device[]>([]) const [devices, setDevices] = useState<Device[]>([])
const [isProvisionDialogOpen, setIsProvisionDialogOpen] = useState<boolean>(false); const [isProvisionDialogOpen, setIsProvisionDialogOpen] = useState<boolean>(false);
const [groupsLoading, setGroupsLoading] = useState(false) const [groupsLoading, setGroupsLoading] = useState(false)
// const [groups, setGroups] = useState<Group[]>([]); const [groups, setGroups] = useState<Group[]>([]);
const [groupLimit, ] = useState(10); const [groupLimit, ] = useState(10);
const [groupPage, ] = useState(0); const [groupPage, ] = useState(0);
const [orderGroup, ] = useState<"asc" | "desc">("asc"); const [orderGroup, ] = useState<"asc" | "desc">("asc");
@ -153,8 +153,7 @@ export default function Devices() {
resp.data.groups?.forEach((group: pond.Group) => { resp.data.groups?.forEach((group: pond.Group) => {
newGroups.push(Group.create(group)) newGroups.push(Group.create(group))
}) })
updateGroups(newGroups) updateGroups(newGroups)
// setTotalGroups(resp.data.total)
}).finally(() => { }).finally(() => {
setGroupsLoading(false) setGroupsLoading(false)
}) })
@ -177,18 +176,19 @@ export default function Devices() {
getKeys(), getKeys(),
getTypes() getTypes()
).then(resp => { ).then(resp => {
setDevices(resp.data.devices) let newDevices: Device[] = []
resp.data.devices.forEach(device => {
newDevices.push(Device.create(device))
})
setDevices(newDevices)
setTotal(resp.data.total) setTotal(resp.data.total)
}).finally(() => { }).finally(() => {
setDevicesLoading(false) setDevicesLoading(false)
}) })
} }
useEffect(() => { useEffect(() => {
loadDevices() loadDevices()
}, [limit, page, order, orderBy, search, tab]) }, [limit, page, order, orderBy, search, tab])
useEffect(() => { useEffect(() => {
@ -205,7 +205,7 @@ export default function Devices() {
return(`${basePath}/${appendage}`); return(`${basePath}/${appendage}`);
}; };
const toDevice = (device: pond.Device) => { const toDevice = (device: Device) => {
navigate(appendToUrl(or(device.settings?.deviceId, "")), { state: {device: device} }) navigate(appendToUrl(or(device.settings?.deviceId, "")), { state: {device: device} })
}; };
@ -214,7 +214,7 @@ export default function Devices() {
if (newValue === "never") openGroupSettings(undefined, "add"); if (newValue === "never") openGroupSettings(undefined, "add");
}; };
const columns = (): Column<pond.Device>[] => { const columns = (): Column<Device>[] => {
return [ return [
{ {
title: "State", title: "State",
@ -298,6 +298,11 @@ export default function Devices() {
) )
} }
const getGroup = () => {
let group = groups[parseInt(tab)-1]
return group
}
// interface MyTabButtonProps { // interface MyTabButtonProps {
// onClick?: React.MouseEventHandler<HTMLButtonElement>; // onClick?: React.MouseEventHandler<HTMLButtonElement>;
// disabled?: boolean; // disabled?: boolean;
@ -395,8 +400,8 @@ export default function Devices() {
<Tab wrapped 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> </Tabs>
</Box> </Box>
<ResponsiveTable<pond.Device> <ResponsiveTable<Device>
title={<SmartBreadcrumb paddingBottom={1}/>} title={<SmartBreadcrumb prependPaths={getGroup() && ["groups", getGroup().id().toString()]} groupName={getGroup() && getGroup().name()} paddingBottom={1}/>}
subtitle={subtitle()} subtitle={subtitle()}
rows={devices} rows={devices}
columns={columns()} columns={columns()}
@ -408,6 +413,7 @@ export default function Devices() {
onRowClick={toDevice} onRowClick={toDevice}
setSearchText={setSearch} setSearchText={setSearch}
isLoading={devicesLoading} isLoading={devicesLoading}
actions={getGroup() && <GroupActions group={getGroup()} permissions={[]} devices={devices} refreshCallback={loadGroups}/>}
/> />
<ProvisionDevice <ProvisionDevice
isOpen={isProvisionDialogOpen} isOpen={isProvisionDialogOpen}

View file

@ -1,4 +1,4 @@
import { Box, Chip, Grid2, Theme, Tooltip, Typography } from "@mui/material"; import { Box, Chip, Grid2, Theme, Tooltip } from "@mui/material";
import SmartBreadcrumb from "common/SmartBreadcrumb"; import SmartBreadcrumb from "common/SmartBreadcrumb";
import { Device, Group } from "models" import { Device, Group } from "models"
import { useEffect, useState } from "react" import { useEffect, useState } from "react"
@ -14,7 +14,6 @@ import { appendToUrl } from "navigation/Router";
import { getDeviceStateHelper } from "pbHelpers/DeviceState"; import { getDeviceStateHelper } from "pbHelpers/DeviceState";
import { makeStyles } from "@mui/styles"; import { makeStyles } from "@mui/styles";
import { blue, green } from "@mui/material/colors"; import { blue, green } from "@mui/material/colors";
import { permissionToString } from "pbHelpers/Permission";
const useStyles = makeStyles((theme: Theme) => { const useStyles = makeStyles((theme: Theme) => {
return ({ return ({
@ -75,8 +74,8 @@ export default function GroupPage() {
const [limit, setLimit] = useState(0) const [limit, setLimit] = useState(0)
const [total, setTotal] = useState(0) const [total, setTotal] = useState(0)
const [page, setPage] = useState(1) const [page, setPage] = useState(1)
const [order, setOrder] = useState<"asc" | "desc">("asc") const [order, ] = useState<"asc" | "desc">("asc")
const [orderBy, setOrderBy] = useState("") const [orderBy, ] = useState("")
const [search, setSearch] = useState("") const [search, setSearch] = useState("")
const loadGroup = () => { const loadGroup = () => {

View file

@ -40,8 +40,8 @@ export default function GroupsPage() {
const navigate = useNavigate() const navigate = useNavigate()
const [limit, setLimit] = useState(10) const [limit, setLimit] = useState(10)
const [page, setPage] = useState(0) const [page, setPage] = useState(0)
const [order, setOrder] = useState<"asc" | "desc">("asc") const [order, ] = useState<"asc" | "desc">("asc")
const [orderBy, setOrderBy] = useState("") const [orderBy, ] = useState("")
const [search, setSearch] = useState("") const [search, setSearch] = useState("")
const [total, setTotal] = useState(0) const [total, setTotal] = useState(0)
const [loading, setLoading] = useState(false) const [loading, setLoading] = useState(false)