added group tabs:
This commit is contained in:
parent
26f0b7081b
commit
bc8b0a3405
7 changed files with 380 additions and 41 deletions
|
|
@ -70,6 +70,7 @@ export default function SmartBreadcrumb(props: Props) {
|
||||||
|
|
||||||
const groupID = (): string => {
|
const groupID = (): string => {
|
||||||
const regexMatch = or(location.pathname.match(/\/groups\/([^/]+)/), [])
|
const regexMatch = or(location.pathname.match(/\/groups\/([^/]+)/), [])
|
||||||
|
// console.log(or(regexMatch[1], "0").toString())
|
||||||
return or(regexMatch[1], "0").toString();
|
return or(regexMatch[1], "0").toString();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -244,6 +245,7 @@ export default function SmartBreadcrumb(props: Props) {
|
||||||
|
|
||||||
const breadcrumbLinks = (): ReactNode[] => {
|
const breadcrumbLinks = (): ReactNode[] => {
|
||||||
const breadcrumbMap = getBreadcrumbMap();
|
const breadcrumbMap = getBreadcrumbMap();
|
||||||
|
// console.log(breadcrumbMap)
|
||||||
|
|
||||||
let links: ReactNode[] = [];
|
let links: ReactNode[] = [];
|
||||||
const pathnames = location.pathname.split("/").filter((x: any) => x);
|
const pathnames = location.pathname.split("/").filter((x: any) => x);
|
||||||
|
|
@ -266,12 +268,10 @@ export default function SmartBreadcrumb(props: Props) {
|
||||||
if (result.length > 1) {
|
if (result.length > 1) {
|
||||||
if (result[result.length - 1] !== "components") {
|
if (result[result.length - 1] !== "components") {
|
||||||
label = breadcrumbMap["/" + result[result.length - 1]];
|
label = breadcrumbMap["/" + result[result.length - 1]];
|
||||||
console.log(label)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!label && result[result.length - 1] !== "components") {
|
if (!label && result[result.length - 1] !== "components") {
|
||||||
label = breadcrumbMap["/" + result[result.length - 2] + "/" + result[result.length - 1]];
|
label = breadcrumbMap["/" + result[result.length - 2] + "/" + result[result.length - 1]];
|
||||||
console.log(label)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { Suspense } from "react";
|
import { Suspense, useEffect, useState } from "react";
|
||||||
import LoadingScreen from "../app/LoadingScreen";
|
import LoadingScreen from "../app/LoadingScreen";
|
||||||
import { BrowserRouter, Navigate, Route, Routes } from "react-router-dom";
|
import { BrowserRouter, Navigate, Route, Routes } from "react-router-dom";
|
||||||
import { Typography } from "@mui/material";
|
import { Typography } from "@mui/material";
|
||||||
|
|
@ -13,6 +13,9 @@ import Header from "app/Header";
|
||||||
import Logout from "pages/Logout";
|
import Logout from "pages/Logout";
|
||||||
import Devices from "pages/Devices";
|
import Devices from "pages/Devices";
|
||||||
import DevicePage from "pages/Device";
|
import DevicePage from "pages/Device";
|
||||||
|
import { Groups } from "@mui/icons-material";
|
||||||
|
import GroupsPage from "pages/Groups";
|
||||||
|
import { Group } from "models";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
open: boolean,
|
open: boolean,
|
||||||
|
|
@ -21,11 +24,17 @@ interface Props {
|
||||||
toggleTheme: () => void;
|
toggleTheme: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const appendToUrl = (appendage: number | string) => {
|
||||||
|
const basePath = location.pathname.replace(/\/$/, "");
|
||||||
|
return(`${basePath}/${appendage}`);
|
||||||
|
};
|
||||||
|
|
||||||
export default function Router(props: Props) {
|
export default function Router(props: Props) {
|
||||||
|
|
||||||
const {open, onOpen, onClose, toggleTheme } = props;
|
const {open, onOpen, onClose, toggleTheme } = props;
|
||||||
const { isAuthenticated, loginWithRedirect, isLoading } = useAuth0();
|
const { isAuthenticated, loginWithRedirect, isLoading } = useAuth0();
|
||||||
const isMobile = useMobile();
|
const isMobile = useMobile();
|
||||||
|
const [groups, setGroups] = useState<Group[]>([]);
|
||||||
|
|
||||||
const RelativeRoutes = () => {
|
const RelativeRoutes = () => {
|
||||||
console.log("relative route")
|
console.log("relative route")
|
||||||
|
|
@ -33,6 +42,7 @@ export default function Router(props: Props) {
|
||||||
<Routes>
|
<Routes>
|
||||||
<Route path="teams/*" element={<TeamsRoute/>} />
|
<Route path="teams/*" element={<TeamsRoute/>} />
|
||||||
<Route path="devices/*" element={<DevicesRoute/>} />
|
<Route path="devices/*" element={<DevicesRoute/>} />
|
||||||
|
<Route path="groups/*" element={<GroupsRoute/>} />
|
||||||
</Routes>
|
</Routes>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -59,17 +69,55 @@ export default function Router(props: Props) {
|
||||||
|
|
||||||
const DevicesRoute = () => {
|
const DevicesRoute = () => {
|
||||||
console.log("devices route")
|
console.log("devices route")
|
||||||
|
console.log(window.location.pathname)
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<Routes>
|
<Routes>
|
||||||
<Route
|
<Route
|
||||||
|
key="Devices page route"
|
||||||
path="" // "/settings/basic"
|
path="" // "/settings/basic"
|
||||||
element={<Devices />}
|
element={<Devices groups={groups} setGroups={setGroups} key={"Devices page"} />}
|
||||||
/>
|
/>
|
||||||
<Route
|
<Route
|
||||||
path="/:deviceID" // "/settings/basic"
|
path="/:deviceID" // "/settings/basic"
|
||||||
element={<DevicePage />}
|
element={<DevicePage />}
|
||||||
/>
|
/>
|
||||||
|
<Route
|
||||||
|
path="/:deviceID/*" // "/settings/basic"
|
||||||
|
element={<RelativeRoutes />}
|
||||||
|
/>
|
||||||
|
</Routes>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
console.log(groups)
|
||||||
|
}, [groups])
|
||||||
|
|
||||||
|
const GroupsRoute = () => {
|
||||||
|
console.log("groups route")
|
||||||
|
console.log(window.location.pathname)
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<Routes>
|
||||||
|
<Route
|
||||||
|
path="/"
|
||||||
|
element={<GroupsPage />}
|
||||||
|
/>
|
||||||
|
<Route
|
||||||
|
key="Devices page route"
|
||||||
|
path="/:groupID/devices/"
|
||||||
|
element={<Devices groups={groups} setGroups={setGroups} key={"Devices page"}/>}
|
||||||
|
/>
|
||||||
|
<Route
|
||||||
|
path="/:groupID/devices/:deviceID"
|
||||||
|
element={<DevicePage />}
|
||||||
|
/>
|
||||||
|
<Route
|
||||||
|
path="/:groupID/devices/:deviceID/*"
|
||||||
|
element={<RelativeRoutes />}
|
||||||
|
/>
|
||||||
</Routes>
|
</Routes>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,7 @@ const useStyles = makeStyles((theme: Theme) => {
|
||||||
});
|
});
|
||||||
|
|
||||||
export default function DevicePage() {
|
export default function DevicePage() {
|
||||||
|
console.log("device")
|
||||||
|
|
||||||
const deviceAPI = useDeviceAPI()
|
const deviceAPI = useDeviceAPI()
|
||||||
const snackbar = useSnackbar()
|
const snackbar = useSnackbar()
|
||||||
|
|
@ -51,7 +52,6 @@ export default function DevicePage() {
|
||||||
setPermissions(resp.data.permissions)
|
setPermissions(resp.data.permissions)
|
||||||
let u = User.any(resp.data.user);
|
let u = User.any(resp.data.user);
|
||||||
setPreferences(u.preferences)
|
setPreferences(u.preferences)
|
||||||
console.log(u.preferences)
|
|
||||||
}).catch(err => {
|
}).catch(err => {
|
||||||
setDevice(Device.create());
|
setDevice(Device.create());
|
||||||
// setComponents(new Map());
|
// setComponents(new Map());
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import { DeveloperBoard as ProvisionIcon, LibraryAdd as CreateGroupIcon } from "@mui/icons-material";
|
import { DeveloperBoard as ProvisionIcon, LibraryAdd as CreateGroupIcon } from "@mui/icons-material";
|
||||||
import { Box, Chip, CircularProgress, IconButton, Tab, Tabs, Theme, Tooltip, Typography } from "@mui/material";
|
import { Box, Chip, CircularProgress, IconButton, Tab, Tabs, Theme, Tooltip } from "@mui/material";
|
||||||
import { blue, green } from "@mui/material/colors";
|
import { blue, green } from "@mui/material/colors";
|
||||||
import { makeStyles } from "@mui/styles";
|
import { makeStyles } from "@mui/styles";
|
||||||
import ResponsiveTable, { Column } from "common/ResponsiveTable";
|
import ResponsiveTable, { Column } from "common/ResponsiveTable";
|
||||||
|
|
@ -9,7 +9,7 @@ import { useDeviceAPI, useGroupAPI } from "providers";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import PageContainer from "./PageContainer";
|
import PageContainer from "./PageContainer";
|
||||||
import { useMobile } from "hooks";
|
import { useMobile } from "hooks";
|
||||||
import { useLocation, useNavigate } from "react-router-dom";
|
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";
|
||||||
|
|
@ -19,6 +19,22 @@ import SmartBreadcrumb from "common/SmartBreadcrumb";
|
||||||
|
|
||||||
const useStyles = makeStyles((theme: Theme) => {
|
const useStyles = makeStyles((theme: Theme) => {
|
||||||
return ({
|
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: {
|
provisionIcon: {
|
||||||
color: blue["700"]
|
color: blue["700"]
|
||||||
},
|
},
|
||||||
|
|
@ -34,7 +50,13 @@ const useStyles = makeStyles((theme: Theme) => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
export default function Devices() {
|
interface Props {
|
||||||
|
groups: Group[];
|
||||||
|
setGroups: React.Dispatch<React.SetStateAction<Group[]>>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function Devices(props: Props) {
|
||||||
|
const { groups, setGroups } = props;
|
||||||
const isMobile = useMobile();
|
const isMobile = useMobile();
|
||||||
const classes = useStyles();
|
const classes = useStyles();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
@ -52,13 +74,17 @@ export default function Devices() {
|
||||||
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");
|
||||||
const [orderGroupBy, ] = useState("name");
|
const [orderGroupBy, ] = useState("name");
|
||||||
const [searchGroup, ] = useState("");
|
const [searchGroup, ] = useState("");
|
||||||
const [totalGroups, setTotalGroups] = useState(0);
|
// const [totalGroups, setTotalGroups] = useState(0);
|
||||||
|
|
||||||
|
const updateGroups = (newGroups: Group[]) => {
|
||||||
|
setGroups(newGroups); // Trigger UI update if needed
|
||||||
|
};
|
||||||
|
|
||||||
const [selectedGroup, setSelectedGroup] = useState<Group | undefined>(undefined);
|
const [selectedGroup, setSelectedGroup] = useState<Group | undefined>(undefined);
|
||||||
const [groupSettingsMode, setGroupSettingsMode] = useState<
|
const [groupSettingsMode, setGroupSettingsMode] = useState<
|
||||||
|
|
@ -66,7 +92,8 @@ export default function Devices() {
|
||||||
>(undefined);
|
>(undefined);
|
||||||
const [groupSettingsIsOpen, setGroupSettingsIsOpen] = useState<boolean>(false);
|
const [groupSettingsIsOpen, setGroupSettingsIsOpen] = useState<boolean>(false);
|
||||||
|
|
||||||
const [tab, setTab] = useState("all");
|
const groupID = useParams<{ groupID: string }>()?.groupID ?? "all";
|
||||||
|
const [tab, setTab] = useState(groupID==="all" ? groupID : groupID);
|
||||||
|
|
||||||
const openProvisionDialog = () => {
|
const openProvisionDialog = () => {
|
||||||
setIsProvisionDialogOpen(true);
|
setIsProvisionDialogOpen(true);
|
||||||
|
|
@ -90,8 +117,26 @@ export default function Devices() {
|
||||||
setGroupSettingsMode(undefined);
|
setGroupSettingsMode(undefined);
|
||||||
setSelectedGroup(undefined);
|
setSelectedGroup(undefined);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const getKeys = () => {
|
||||||
|
if (tab !== "all") {
|
||||||
|
let keys = getContextKeys()
|
||||||
|
keys.splice(keys.length - 1, 0, tab);
|
||||||
|
return keys
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const getTypes = () => {
|
||||||
|
if (tab !== "all") {
|
||||||
|
let types = getContextTypes()
|
||||||
|
types.splice(types.length - 1, 0, "group");
|
||||||
|
return types
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const loadGroups = () => {
|
const loadGroups = () => {
|
||||||
|
if (groupsLoading) return
|
||||||
|
if (groups.length > 0) return
|
||||||
setGroupsLoading(true)
|
setGroupsLoading(true)
|
||||||
groupAPI.listGroups(
|
groupAPI.listGroups(
|
||||||
groupLimit,
|
groupLimit,
|
||||||
|
|
@ -99,22 +144,19 @@ export default function Devices() {
|
||||||
orderGroup,
|
orderGroup,
|
||||||
orderGroupBy,
|
orderGroupBy,
|
||||||
searchGroup,
|
searchGroup,
|
||||||
|
undefined
|
||||||
).then(resp => {
|
).then(resp => {
|
||||||
// console.log(resp.data)
|
|
||||||
let newGroups: Group[] = []
|
let newGroups: Group[] = []
|
||||||
resp.data.groups.forEach((group: pond.Group) => {
|
resp.data.groups?.forEach((group: pond.Group) => {
|
||||||
newGroups.push(Group.create(group))
|
newGroups.push(Group.create(group))
|
||||||
})
|
})
|
||||||
setGroups(newGroups)
|
updateGroups(newGroups)
|
||||||
setTotalGroups(resp.data.total)
|
// setTotalGroups(resp.data.total)
|
||||||
}).finally(() => {
|
}).finally(() => {
|
||||||
setGroupsLoading(false)
|
setGroupsLoading(false)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
console.log(groups)
|
|
||||||
}, [groups])
|
|
||||||
|
|
||||||
const loadDevices = () => {
|
const loadDevices = () => {
|
||||||
setDevicesLoading(true)
|
setDevicesLoading(true)
|
||||||
|
|
@ -130,8 +172,8 @@ export default function Devices() {
|
||||||
undefined,
|
undefined,
|
||||||
undefined,
|
undefined,
|
||||||
undefined,
|
undefined,
|
||||||
getContextKeys(),
|
getKeys(),
|
||||||
getContextTypes()
|
getTypes()
|
||||||
).then(resp => {
|
).then(resp => {
|
||||||
setDevices(resp.data.devices)
|
setDevices(resp.data.devices)
|
||||||
setTotal(resp.data.total)
|
setTotal(resp.data.total)
|
||||||
|
|
@ -142,13 +184,13 @@ export default function Devices() {
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
loadDevices()
|
loadDevices()
|
||||||
}, [limit, page, order, orderBy, search])
|
}, [limit, page, order, orderBy, search, tab])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
// console.log("groups loaded")
|
||||||
loadGroups()
|
loadGroups()
|
||||||
}, [groupLimit, groupPage, orderGroup, orderGroupBy, searchGroup])
|
}, [groupLimit, groupPage, orderGroup, orderGroupBy, searchGroup])
|
||||||
|
|
||||||
|
|
||||||
const handleChange = (event: any) => {
|
const handleChange = (event: any) => {
|
||||||
setLimit(event.target.value);
|
setLimit(event.target.value);
|
||||||
};
|
};
|
||||||
|
|
@ -208,8 +250,8 @@ export default function Devices() {
|
||||||
<Box className={classes.cellContainer}>
|
<Box className={classes.cellContainer}>
|
||||||
<Tooltip title={device.settings?.description}>
|
<Tooltip title={device.settings?.description}>
|
||||||
<span>
|
<span>
|
||||||
{description.length > 10
|
{description.length > 20
|
||||||
? description.substring(0, 10) + "..."
|
? description.substring(0, 20) + "..."
|
||||||
: description}
|
: description}
|
||||||
</span>
|
</span>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
|
|
@ -251,22 +293,104 @@ export default function Devices() {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// interface MyTabButtonProps {
|
||||||
|
// onClick?: React.MouseEventHandler<HTMLButtonElement>;
|
||||||
|
// disabled?: boolean;
|
||||||
|
// }
|
||||||
|
// const [leftButtonProps, setLeftButtonProps] = useState<MyTabButtonProps>({disabled: true})
|
||||||
|
// const [rightButtonProps, setRightButtonProps] = useState<MyTabButtonProps>({disabled: true})
|
||||||
|
// Use refs to store the props received during render
|
||||||
|
// const leftButtonRef = useRef<MyTabButtonProps>({});
|
||||||
|
// const rightButtonRef = useRef<MyTabButtonProps>({});
|
||||||
|
// const [tabsHovered, setTabsHovered] = useState(false)
|
||||||
|
// const location = useLocation()
|
||||||
|
|
||||||
|
// Update state using useEffect based on ref changes (the props received)
|
||||||
|
// useEffect(() => {
|
||||||
|
// if (leftButtonRef.current.disabled !== leftButtonProps.disabled) {
|
||||||
|
// setLeftButtonProps({ disabled: leftButtonRef.current.disabled, onClick: leftButtonRef.current.onClick });
|
||||||
|
// }
|
||||||
|
// if (rightButtonRef.current.disabled !== rightButtonProps.disabled) {
|
||||||
|
// setRightButtonProps({ disabled: rightButtonRef.current.disabled, onClick: rightButtonRef.current.onClick });
|
||||||
|
// }
|
||||||
|
// }, [leftButtonRef.current, rightButtonRef.current]);
|
||||||
|
|
||||||
|
// const handleScrollButtonProps = (props: TabScrollButtonProps) => {
|
||||||
|
// if ((props.direction === "left" &&
|
||||||
|
// leftButtonProps.disabled !== props.disabled)
|
||||||
|
// ) {
|
||||||
|
// let newProps: any = {}
|
||||||
|
// newProps.disabled = props.disabled
|
||||||
|
// newProps.onClick = props.onClick
|
||||||
|
// setLeftButtonProps(newProps)
|
||||||
|
// // leftButtonRef.current = newProps
|
||||||
|
// }
|
||||||
|
// if (props.direction === "right" &&
|
||||||
|
// rightButtonProps.disabled !== props.disabled
|
||||||
|
// ) {
|
||||||
|
// let newProps: any = {}
|
||||||
|
// newProps.disabled = props.disabled
|
||||||
|
// newProps.onClick = props.onClick
|
||||||
|
// setRightButtonProps(newProps)
|
||||||
|
// // rightButtonRef.current = newProps
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// useEffect(() => {
|
||||||
|
// if (tab !== "all") {
|
||||||
|
// const group = groups[typeof(tab) === "number" ? tab-1 : parseInt(tab)-1]
|
||||||
|
// const url = location.pathname;
|
||||||
|
// if (group) {
|
||||||
|
// const updatedUrl = url.replace(
|
||||||
|
// /(\/groups\/[^/]+)?\/devices$/, // Matches "/groups/some-id/devices" or just "/devices"
|
||||||
|
// `/groups/${group.id()}/devices`
|
||||||
|
// );
|
||||||
|
// navigate(updatedUrl, { replace: true });
|
||||||
|
// loadDevices()
|
||||||
|
// }
|
||||||
|
// } else {
|
||||||
|
// const url = location.pathname;
|
||||||
|
// const updatedUrl = url.replace(
|
||||||
|
// /(\/groups\/[^/]+)?\/devices$/, // Matches "/groups/some-id/devices" or just "/devices"
|
||||||
|
// `/devices`
|
||||||
|
// );
|
||||||
|
// navigate(updatedUrl, { replace: true });
|
||||||
|
// loadDevices()
|
||||||
|
// }
|
||||||
|
// }, [tab])
|
||||||
|
|
||||||
return(
|
return(
|
||||||
<PageContainer padding={isMobile ? 0 : 2}>
|
<PageContainer padding={isMobile ? 0 : 2}>
|
||||||
<SmartBreadcrumb />
|
<SmartBreadcrumb key={"Device page crumbs"}/>
|
||||||
<Tabs
|
<Box
|
||||||
value={tab}
|
// onMouseEnter={() => setTabsHovered(true)}
|
||||||
onChange={handleTabChange}
|
// onMouseLeave={() => setTabsHovered(false)}
|
||||||
|
sx={{
|
||||||
|
borderRadius: 1,
|
||||||
|
border: "1px solid rgba(150, 150, 150, 0.2)",
|
||||||
|
marginTop: 1,
|
||||||
|
marginBottom: 1
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<Tab value={"all"} label="All" />
|
<Tabs
|
||||||
{groups.map((group, index) => {
|
{...{}}
|
||||||
if (group.id()) return (
|
value={groups.length > 0 ? tab : "all"}
|
||||||
<Tab value={group.id()} label={group.name()} key={"group-tab-"+index}/>
|
onChange={handleTabChange}
|
||||||
)
|
variant="scrollable"
|
||||||
})}
|
scrollButtons="auto"
|
||||||
{groupsLoading && <CircularProgress />}
|
sx={{ borderRadius: 1 }}
|
||||||
<Tab value={"never"} label={groupButton()} />
|
>
|
||||||
</Tabs>
|
<Tab {...{}} value={"all"} label="All" />
|
||||||
|
{groupsLoading ?
|
||||||
|
<CircularProgress/>
|
||||||
|
: groups.map((group, index) => {
|
||||||
|
if (group.id()) return (
|
||||||
|
<Tab {...{}} value={group.id().toString()} label={group.name()} key={"group-tab-"+index}/>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
<Tab {...{}} value={"never"} label={<CreateGroupIcon className={classes.green} />} onClick={() => openGroupSettings(undefined, "add")} />
|
||||||
|
</Tabs>
|
||||||
|
</Box>
|
||||||
<ResponsiveTable<pond.Device>
|
<ResponsiveTable<pond.Device>
|
||||||
title="Devices"
|
title="Devices"
|
||||||
subtitle={subtitle()}
|
subtitle={subtitle()}
|
||||||
|
|
|
||||||
25
src/pages/Group.tsx
Normal file
25
src/pages/Group.tsx
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
import { Grid2 } from "@mui/material";
|
||||||
|
import SmartBreadcrumb from "common/SmartBreadcrumb";
|
||||||
|
import { Group } from "models"
|
||||||
|
import { useState } from "react"
|
||||||
|
import { useLocation, useParams } from "react-router-dom";
|
||||||
|
|
||||||
|
|
||||||
|
export default function GroupPage() {
|
||||||
|
|
||||||
|
const groupID = useParams<{ groupID: string }>()?.groupID ?? "";
|
||||||
|
const { state } = useLocation();
|
||||||
|
const [group, setGroup] = useState<Group>(state?.device ? Group.create(state.device) : Group.create())
|
||||||
|
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Grid2 container justifyContent={"space-between"}>
|
||||||
|
<Grid2>
|
||||||
|
<SmartBreadcrumb groupName={group.name()} />
|
||||||
|
</Grid2>
|
||||||
|
<Grid2>
|
||||||
|
{/* <GroupActions /> */}
|
||||||
|
</Grid2>
|
||||||
|
</Grid2>
|
||||||
|
)
|
||||||
|
}
|
||||||
132
src/pages/Groups.tsx
Normal file
132
src/pages/Groups.tsx
Normal file
|
|
@ -0,0 +1,132 @@
|
||||||
|
import { Box, Chip, Theme, Tooltip, Typography } from "@mui/material";
|
||||||
|
import { makeStyles } from "@mui/styles";
|
||||||
|
import ResponsiveTable, { Column } from "common/ResponsiveTable";
|
||||||
|
import SmartBreadcrumb from "common/SmartBreadcrumb";
|
||||||
|
import { useMobile } from "hooks";
|
||||||
|
import { appendToUrl } from "navigation/Router";
|
||||||
|
import { pond } from "protobuf-ts/pond";
|
||||||
|
import { useGroupAPI } from "providers";
|
||||||
|
import { useEffect, useState } from "react";
|
||||||
|
import { useNavigate } from "react-router-dom";
|
||||||
|
import { or } from "utils/types";
|
||||||
|
|
||||||
|
const useStyles = makeStyles((theme: Theme) => {
|
||||||
|
const isMobile = useMobile()
|
||||||
|
return ({
|
||||||
|
cellContainer: {
|
||||||
|
margin: theme.spacing(1),
|
||||||
|
marginLeft: theme.spacing(2),
|
||||||
|
display: "flex",
|
||||||
|
maxWidth: theme.spacing(isMobile ? 18 : 32),
|
||||||
|
},
|
||||||
|
bigCellContainer: {
|
||||||
|
margin: theme.spacing(1),
|
||||||
|
marginLeft: theme.spacing(2),
|
||||||
|
display: "flex",
|
||||||
|
width: "auto",
|
||||||
|
},
|
||||||
|
smallCellContainer: {
|
||||||
|
margin: theme.spacing(1),
|
||||||
|
marginLeft: theme.spacing(2),
|
||||||
|
display: "flex",
|
||||||
|
width: theme.spacing(1),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
export default function GroupsPage() {
|
||||||
|
const groupAPI = useGroupAPI()
|
||||||
|
const navigate = useNavigate()
|
||||||
|
const [limit, setLimit] = useState(10)
|
||||||
|
const [page, setPage] = useState(0)
|
||||||
|
const [order, setOrder] = useState<"asc" | "desc">("asc")
|
||||||
|
const [orderBy, setOrderBy] = useState("")
|
||||||
|
const [search, setSearch] = useState("")
|
||||||
|
const [total, setTotal] = useState(0)
|
||||||
|
const [loading, setLoading] = useState(false)
|
||||||
|
|
||||||
|
const [groups, setGroups] = useState<pond.Group[]>([])
|
||||||
|
|
||||||
|
const classes = useStyles()
|
||||||
|
|
||||||
|
const loadGroups = () => {
|
||||||
|
setLoading(true)
|
||||||
|
groupAPI.listGroups(limit, page*limit, order, orderBy, search).then(resp => {
|
||||||
|
setGroups(resp.data.groups)
|
||||||
|
setTotal(resp.data.total)
|
||||||
|
}).finally(() => {
|
||||||
|
setLoading(false)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
loadGroups()
|
||||||
|
}, [limit, page, order, orderBy, search])
|
||||||
|
|
||||||
|
const handleRowsPerPageChange = (event: any) => {
|
||||||
|
setLimit(event.target.value);
|
||||||
|
};
|
||||||
|
|
||||||
|
const columns = (): Column<pond.Group>[] => {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
title: "Size",
|
||||||
|
render: (group) => {
|
||||||
|
return (
|
||||||
|
<Box className={classes.smallCellContainer}>
|
||||||
|
{group.settings?.devices?.length ? group.settings.devices.length : 0}
|
||||||
|
</Box>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Group Name",
|
||||||
|
render: (group) => {
|
||||||
|
return (
|
||||||
|
<Box className={classes.cellContainer}>
|
||||||
|
<Chip
|
||||||
|
variant="outlined"
|
||||||
|
label={group.settings?.name ? group.settings.name : "Group " + group.settings?.groupId}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Description",
|
||||||
|
render: device => {
|
||||||
|
const description = device.settings?.description ?? ""
|
||||||
|
return (
|
||||||
|
<Box className={classes.bigCellContainer}>
|
||||||
|
<Tooltip title={device.settings?.description}>
|
||||||
|
<Typography variant="body2">
|
||||||
|
{description}
|
||||||
|
</Typography>
|
||||||
|
</Tooltip>
|
||||||
|
</Box>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleRowClick = (row: pond.Group) => {
|
||||||
|
if (row.settings?.groupId) navigate(appendToUrl(or(row.settings?.groupId, "")), { state: {group: row} })
|
||||||
|
}
|
||||||
|
|
||||||
|
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}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
@ -21,7 +21,9 @@ export interface IGroupAPIContext {
|
||||||
order?: "asc" | "desc",
|
order?: "asc" | "desc",
|
||||||
orderBy?: string,
|
orderBy?: string,
|
||||||
search?: string,
|
search?: string,
|
||||||
asRoot?: boolean
|
asRoot?: boolean,
|
||||||
|
keys?: string[],
|
||||||
|
types?: string[],
|
||||||
) => Promise<any>;
|
) => Promise<any>;
|
||||||
listGroupDevices: (
|
listGroupDevices: (
|
||||||
id: number,
|
id: number,
|
||||||
|
|
@ -82,10 +84,18 @@ export default function GroupProvider(props: PropsWithChildren<Props>) {
|
||||||
order?: "asc" | "desc",
|
order?: "asc" | "desc",
|
||||||
orderBy?: string,
|
orderBy?: string,
|
||||||
search?: string,
|
search?: string,
|
||||||
asRoot?: boolean
|
asRoot?: boolean,
|
||||||
|
keys?: string[],
|
||||||
|
types?: string[]
|
||||||
) => {
|
) => {
|
||||||
let keys = getContextKeys()
|
keys = keys ? keys : getContextKeys();
|
||||||
let types = getContextTypes()
|
types = types? types : getContextTypes();
|
||||||
|
types.forEach((type, index) => {
|
||||||
|
if (type === "group") {
|
||||||
|
types.splice(index, 1)
|
||||||
|
keys.splice(index, 1)
|
||||||
|
}
|
||||||
|
})
|
||||||
return get(
|
return get(
|
||||||
pondURL(
|
pondURL(
|
||||||
"/groups" +
|
"/groups" +
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue