From 2d9891732273b090330471c157c80c8d25556a0d Mon Sep 17 00:00:00 2001 From: Carter Date: Tue, 7 Jan 2025 10:34:18 -0600 Subject: [PATCH] committing all group related code --- package-lock.json | 66 +++------ package.json | 2 +- src/common/ResponsiveTable.tsx | 15 +- src/common/SmartBreadcrumb.tsx | 25 ++-- src/group/GroupActions.tsx | 204 ++++++++++++++++++++++++++++ src/navigation/Router.tsx | 6 +- src/pages/Device.tsx | 2 - src/pages/Devices.tsx | 25 ++-- src/pages/Group.tsx | 233 ++++++++++++++++++++++++++++++-- src/pages/Groups.tsx | 29 ++-- src/pages/PageContainer.tsx | 11 +- src/providers/pond/groupAPI.tsx | 23 ++++ src/teams/TeamList.tsx | 1 + tsconfig.app.json | 1 + 14 files changed, 541 insertions(+), 102 deletions(-) create mode 100644 src/group/GroupActions.tsx diff --git a/package-lock.json b/package-lock.json index 7094767..d97d0c0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -23,7 +23,7 @@ "moment-timezone": "^0.5.46", "mui-tel-input": "^7.0.0", "notistack": "^3.0.1", - "protobuf-ts": "git+https://gitlab+deploy-token-50627:hv8mB4WkyvtjBpJKU1rN@gitlab.com/brandx/protobuf-ts.git#staging", + "protobuf-ts": "git+https://gitlab+deploy-token-50627:hv8mB4WkyvtjBpJKU1rN@gitlab.com/brandx/protobuf-ts.git#dev", "react": "^18.3.1", "react-dom": "^18.3.1", "react-image": "^4.1.0", @@ -1515,9 +1515,7 @@ }, "node_modules/@mui/x-date-pickers": { "version": "7.22.3", - "resolved": "https://registry.npmjs.org/@mui/x-date-pickers/-/x-date-pickers-7.22.3.tgz", "integrity": "sha512-shNp92IrST5BiVy2f4jbrmRaD32QhyUthjh1Oexvpcn0v6INyuWgxfodoTi5ZCnE5Ue5UVFSs4R9Xre0UbJ5DQ==", - "license": "MIT", "dependencies": { "@babel/runtime": "^7.25.7", "@mui/utils": "^5.16.6 || ^6.0.0", @@ -1583,7 +1581,6 @@ "version": "7.21.0", "resolved": "https://registry.npmjs.org/@mui/x-internals/-/x-internals-7.21.0.tgz", "integrity": "sha512-94YNyZ0BhK5Z+Tkr90RKf47IVCW8R/1MvdUhh6MCQg6sZa74jsX+x+gEZ4kzuCqOsuyTyxikeQ8vVuCIQiP7UQ==", - "license": "MIT", "dependencies": { "@babel/runtime": "^7.25.7", "@mui/utils": "^5.16.6 || ^6.0.0" @@ -1646,32 +1643,27 @@ "node_modules/@protobufjs/aspromise": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", - "integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==", - "license": "BSD-3-Clause" + "integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==" }, "node_modules/@protobufjs/base64": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz", - "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==", - "license": "BSD-3-Clause" + "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==" }, "node_modules/@protobufjs/codegen": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.4.tgz", - "integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==", - "license": "BSD-3-Clause" + "integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==" }, "node_modules/@protobufjs/eventemitter": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz", - "integrity": "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==", - "license": "BSD-3-Clause" + "integrity": "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==" }, "node_modules/@protobufjs/fetch": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz", "integrity": "sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==", - "license": "BSD-3-Clause", "dependencies": { "@protobufjs/aspromise": "^1.1.1", "@protobufjs/inquire": "^1.1.0" @@ -1680,32 +1672,27 @@ "node_modules/@protobufjs/float": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz", - "integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==", - "license": "BSD-3-Clause" + "integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==" }, "node_modules/@protobufjs/inquire": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.0.tgz", - "integrity": "sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q==", - "license": "BSD-3-Clause" + "integrity": "sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q==" }, "node_modules/@protobufjs/path": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz", - "integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==", - "license": "BSD-3-Clause" + "integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==" }, "node_modules/@protobufjs/pool": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz", - "integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==", - "license": "BSD-3-Clause" + "integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==" }, "node_modules/@protobufjs/utf8": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz", - "integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==", - "license": "BSD-3-Clause" + "integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==" }, "node_modules/@remix-run/router": { "version": "1.21.0", @@ -2136,14 +2123,12 @@ "node_modules/@types/long": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/@types/long/-/long-4.0.2.tgz", - "integrity": "sha512-MqTGEo5bj5t157U6fA/BiDynNkn0YknVdh48CMPkTSpFTVmvao5UQmm7uEF6xBEo7qIMAlY/JSleYaE6VOdpaA==", - "license": "MIT" + "integrity": "sha512-MqTGEo5bj5t157U6fA/BiDynNkn0YknVdh48CMPkTSpFTVmvao5UQmm7uEF6xBEo7qIMAlY/JSleYaE6VOdpaA==" }, "node_modules/@types/node": { "version": "22.9.1", "resolved": "https://registry.npmjs.org/@types/node/-/node-22.9.1.tgz", "integrity": "sha512-p8Yy/8sw1caA8CdRIQBG5tiLHmxtQKObCijiAa9Ez+d4+PRffM4054xbju0msf+cvhJpnFEeNjxmVT/0ipktrg==", - "license": "MIT", "dependencies": { "undici-types": "~6.19.8" } @@ -2180,7 +2165,6 @@ "resolved": "https://registry.npmjs.org/@types/react-infinite-scroller/-/react-infinite-scroller-1.2.5.tgz", "integrity": "sha512-fJU1jhMgoL6NJFrqTM0Ob7tnd2sQWGxe2ESwiU6FZWbJK/VO/Er5+AOhc+e2zbT0dk5pLygqctsulOLJ8xnSzw==", "dev": true, - "license": "MIT", "dependencies": { "@types/react": "*" } @@ -3764,8 +3748,7 @@ "node_modules/libphonenumber-js": { "version": "1.11.15", "resolved": "https://registry.npmjs.org/libphonenumber-js/-/libphonenumber-js-1.11.15.tgz", - "integrity": "sha512-M7+rtYi9l5RvMmHyjyoF3BHHUpXTYdJ0PezZGHNs0GyW1lO+K7jxlXpbdIb7a56h0nqLYdjIw+E+z0ciGaJP7g==", - "license": "MIT" + "integrity": "sha512-M7+rtYi9l5RvMmHyjyoF3BHHUpXTYdJ0PezZGHNs0GyW1lO+K7jxlXpbdIb7a56h0nqLYdjIw+E+z0ciGaJP7g==" }, "node_modules/lines-and-columns": { "version": "1.2.4", @@ -3794,14 +3777,12 @@ "node_modules/lodash.debounce": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", - "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==", - "license": "MIT" + "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==" }, "node_modules/lodash.memoize": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", - "integrity": "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==", - "license": "MIT" + "integrity": "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==" }, "node_modules/lodash.merge": { "version": "4.6.2", @@ -3812,20 +3793,17 @@ "node_modules/lodash.reduce": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/lodash.reduce/-/lodash.reduce-4.6.0.tgz", - "integrity": "sha512-6raRe2vxCYBhpBu+B+TtNGUzah+hQjVdu3E17wfusjyrXBka2nBS8OH/gjVZ5PvHOhWmIZTYri09Z6n/QfnNMw==", - "license": "MIT" + "integrity": "sha512-6raRe2vxCYBhpBu+B+TtNGUzah+hQjVdu3E17wfusjyrXBka2nBS8OH/gjVZ5PvHOhWmIZTYri09Z6n/QfnNMw==" }, "node_modules/lodash.startswith": { "version": "4.2.1", "resolved": "https://registry.npmjs.org/lodash.startswith/-/lodash.startswith-4.2.1.tgz", - "integrity": "sha512-XClYR1h4/fJ7H+mmCKppbiBmljN/nGs73iq2SjCT9SF4CBPoUHzLvWmH1GtZMhMBZSiRkHXfeA2RY1eIlJ75ww==", - "license": "MIT" + "integrity": "sha512-XClYR1h4/fJ7H+mmCKppbiBmljN/nGs73iq2SjCT9SF4CBPoUHzLvWmH1GtZMhMBZSiRkHXfeA2RY1eIlJ75ww==" }, "node_modules/long": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz", - "integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==", - "license": "Apache-2.0" + "integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==" }, "node_modules/loose-envify": { "version": "1.4.0", @@ -3941,9 +3919,7 @@ }, "node_modules/mui-tel-input": { "version": "7.0.0", - "resolved": "https://registry.npmjs.org/mui-tel-input/-/mui-tel-input-7.0.0.tgz", "integrity": "sha512-kLxFmuoSwX5qG3PRO2BQ2hNquy8JfQBOofBhtoQsiRwPsAtwW77OIzQPmXPnftRjuc7uYJt6PW7oVeqx7wkK3g==", - "license": "MIT", "dependencies": { "@types/node": "^22.9.0", "libphonenumber-js": "^1.11.14" @@ -4220,7 +4196,7 @@ }, "node_modules/protobuf-ts": { "version": "1.0.0", - "resolved": "git+https://gitlab+deploy-token-50627:hv8mB4WkyvtjBpJKU1rN@gitlab.com/brandx/protobuf-ts.git#7fa04da5ae9ec99cf1caa6971ca1541fe44f10c4", + "resolved": "git+https://gitlab+deploy-token-50627:hv8mB4WkyvtjBpJKU1rN@gitlab.com/brandx/protobuf-ts.git#e3d1689e1bf3842b0601cf166be9aff78ef093ff", "dependencies": { "protobufjs": "^6.8.8" } @@ -4230,7 +4206,6 @@ "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-6.11.4.tgz", "integrity": "sha512-5kQWPaJHi1WoCpjTGszzQ32PG2F4+wRY6BmAT4Vfw56Q2FZ4YZzK20xUYQH4YkfehY1e6QSICrJquM6xXZNcrw==", "hasInstallScript": true, - "license": "BSD-3-Clause", "dependencies": { "@protobufjs/aspromise": "^1.1.2", "@protobufjs/base64": "^1.1.2", @@ -4332,9 +4307,7 @@ }, "node_modules/react-phone-input-2": { "version": "2.15.1", - "resolved": "https://registry.npmjs.org/react-phone-input-2/-/react-phone-input-2-2.15.1.tgz", "integrity": "sha512-W03abwhXcwUoq+vUFvC6ch2+LJYMN8qSOiO889UH6S7SyMCQvox/LF3QWt+cZagZrRdi5z2ON3omnjoCUmlaYw==", - "license": "MIT", "dependencies": { "classnames": "^2.2.6", "lodash.debounce": "^4.0.8", @@ -4763,8 +4736,7 @@ "node_modules/undici-types": { "version": "6.19.8", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", - "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==", - "license": "MIT" + "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==" }, "node_modules/update-browserslist-db": { "version": "1.1.1", diff --git a/package.json b/package.json index 3ebf2cf..292a34e 100644 --- a/package.json +++ b/package.json @@ -32,7 +32,7 @@ "moment-timezone": "^0.5.46", "mui-tel-input": "^7.0.0", "notistack": "^3.0.1", - "protobuf-ts": "git+https://gitlab+deploy-token-50627:hv8mB4WkyvtjBpJKU1rN@gitlab.com/brandx/protobuf-ts.git#staging", + "protobuf-ts": "git+https://gitlab+deploy-token-50627:hv8mB4WkyvtjBpJKU1rN@gitlab.com/brandx/protobuf-ts.git#dev", "react": "^18.3.1", "react-dom": "^18.3.1", "react-image": "^4.1.0", diff --git a/src/common/ResponsiveTable.tsx b/src/common/ResponsiveTable.tsx index bb975d7..11d87a9 100644 --- a/src/common/ResponsiveTable.tsx +++ b/src/common/ResponsiveTable.tsx @@ -20,12 +20,21 @@ const useStyles = makeStyles((theme: Theme) => { // width: "auto" }, title: { - padding: theme.spacing(1) + padding: theme.spacing(1), }, subtitle: { padding: theme.spacing(1), marginTop: theme.spacing(-2), }, + titleComp: { + padding: theme.spacing(1), + marginLeft: theme.spacing(-1) + }, + subtitleComp: { + padding: theme.spacing(1), + marginTop: theme.spacing(-2), + marginLeft: theme.spacing(-1), + }, titleContainer: { border: "none", }, @@ -184,7 +193,7 @@ export default function ResponsiveTable(props: Props) { ) if (title) return ( - + {title} ) @@ -198,7 +207,7 @@ export default function ResponsiveTable(props: Props) { ) if (subtitle) return ( - + {subtitle} ) diff --git a/src/common/SmartBreadcrumb.tsx b/src/common/SmartBreadcrumb.tsx index 1f9a6ae..b252502 100644 --- a/src/common/SmartBreadcrumb.tsx +++ b/src/common/SmartBreadcrumb.tsx @@ -1,4 +1,5 @@ import { + Box, Breadcrumbs, Chip, IconButton, @@ -13,7 +14,7 @@ import { Replay } from "@mui/icons-material"; // import { Skeleton } from "@mui/l"; import { useMobile } from "hooks"; // import { MatchParams } from "navigation/Routes"; -import React, { ReactNode } from "react"; +import { ReactNode } from "react"; import { useLocation } from "react-router"; import { Link as RouterLink } from "react-router-dom"; import { or } from "utils/types"; @@ -52,6 +53,11 @@ interface Props { reportTarget?: string; loading?: boolean; reload?: () => void; + padding?: number; + paddingLeft?: number; + paddingRight?: number; + paddingTop?: number; + paddingBottom?: number; } interface LinkRouterProps extends LinkProps { @@ -251,19 +257,17 @@ export default function SmartBreadcrumb(props: Props) { const pathnames = location.pathname.split("/").filter((x: any) => x); const blacklist = breadcrumbBlacklist(); - // console.log(pathnames) - if (pathnames.length < 1) { return [linkComponent("/", "Dashboard", true)]; } pathnames.forEach((_value: any, index: any) => { + const lastPath = index === pathnames.length - 1; const to = `/${pathnames.slice(0, index + 1).join("/")}`; let label = breadcrumbMap[to]; let result = to.split("/").filter(str => str !== ""); - // console.log(result) if (!label) { if (result.length > 1) { if (result[result.length - 1] !== "components") { @@ -278,9 +282,8 @@ export default function SmartBreadcrumb(props: Props) { if (!blacklist.includes(to) && label) { links.push(linkComponent(to, label, lastPath)); } - }); - // console.log(links) + }); return links; }; @@ -315,7 +318,13 @@ export default function SmartBreadcrumb(props: Props) { if (loading === true) return ; return ( - + )} - + ); } diff --git a/src/group/GroupActions.tsx b/src/group/GroupActions.tsx new file mode 100644 index 0000000..5b5f669 --- /dev/null +++ b/src/group/GroupActions.tsx @@ -0,0 +1,204 @@ +import { + IconButton, + ListItemIcon, + ListItemText, + Menu, + MenuItem, + Theme, + Tooltip +} from "@mui/material"; +import { blue } from "@mui/material/colors"; +import RemoveSelfIcon from "@mui/icons-material/ExitToApp"; +import MoreIcon from "@mui/icons-material/MoreVert"; +import GroupSettingsIcon from "@mui/icons-material/Settings"; +import ShareObjectIcon from "@mui/icons-material/Share"; +import ObjectUsersIcon from "@mui/icons-material/AccountCircle"; +import ObjectTeamsIcon from "@mui/icons-material/SupervisedUserCircle"; +import GroupSettings from "group/GroupSettings"; +import { Device, Group, groupScope } from "models"; +import { pond } from "protobuf-ts/pond"; +import { useState } from "react"; +// import ObjectTeams from "teams/ObjectTeams"; +import ObjectUsers from "user/ObjectUsers"; +import RemoveSelfFromObject from "user/RemoveSelfFromObject"; +import ShareObject from "user/ShareObject"; +import { isOffline } from "utils/environment"; +import { makeStyles } from "@mui/styles"; + +// const useStyles = makeStyles((theme: Theme) => { +// // const isMobile = useMobile() +// return ({ + +// }); +// }); + +const useStyles = makeStyles((theme: Theme) => { + return ({ + shareIcon: { + color: blue["500"], + "&:hover": { + color: blue["600"] + } + }, + removeIcon: { + color: "var(--status-alert)" + }, + red: { + color: "var(--status-alert)" + } + }); +}); + +interface Props { + group: Group; + devices: Device[]; + permissions: pond.Permission[]; + refreshCallback: () => void; +} + +export default function GroupActions(props: Props) { + const classes = useStyles(); + const { group, permissions, refreshCallback, devices } = props; + const [anchorEl, setAnchorEl] = useState(null); + const [groupSettingsOpen, setGroupSettingsOpen] = useState(false); + const [shareOpen, setShareOpen] = useState(false); + const [usersOpen, setUsersOpen] = useState(false); + const [teamsOpen, setTeamsOpen] = useState(false); + const [removeSelfOpen, setRemoveSelfOpen] = useState(false); + + const groupMenu = () => { + const canShare = permissions.includes(pond.Permission.PERMISSION_SHARE); + const canManageUsers = permissions.includes(pond.Permission.PERMISSION_USERS); + const canTeams = canManageUsers; + return ( + setAnchorEl(null)} + keepMounted + disableAutoFocusItem> + {!isOffline() && canShare && ( + { + setShareOpen(true); + setAnchorEl(null); + }} + > + + + + + + )} + {!isOffline() && canManageUsers && ( + { + setUsersOpen(true); + setAnchorEl(null); + }} + > + + + + + + )} + {!isOffline() && canTeams && ( + { + setTeamsOpen(true); + setAnchorEl(null); + }} + > + + + + + + )} + { + setRemoveSelfOpen(true); + setAnchorEl(null); + }} + > + + + + + + + ); + }; + + const dialogs = () => { + const hasWritePermission = permissions.includes(pond.Permission.PERMISSION_WRITE); + const key = group.id().toString(); + const label = group.name(); + return ( + <> + setGroupSettingsOpen(false)} + groupDevices={devices} + /> + setShareOpen(false)} + /> + setUsersOpen(false)} + refreshCallback={refreshCallback} + /> + {/* setTeamsOpen(false)} + refreshCallback={refreshCallback} + /> */} + setRemoveSelfOpen(false)} + /> + + ); + }; + + return ( + <> + + setGroupSettingsOpen(true)}> + + + + ) => setAnchorEl(event.currentTarget)}> + + + {groupMenu()} + {dialogs()} + + ); +} diff --git a/src/navigation/Router.tsx b/src/navigation/Router.tsx index 507226a..dedf8e6 100644 --- a/src/navigation/Router.tsx +++ b/src/navigation/Router.tsx @@ -14,6 +14,7 @@ import Logout from "pages/Logout"; import Devices from "pages/Devices"; import DevicePage from "pages/Device"; import GroupsPage from "pages/Groups"; +import GroupPage from "pages/Group"; interface Props { open: boolean, @@ -90,7 +91,6 @@ export default function Router(props: Props) { const GroupsRoute = () => { console.log("groups route") - console.log(window.location.pathname) return (
@@ -98,6 +98,10 @@ export default function Router(props: Props) { path="/" element={} /> + } + /> { }); export default function DevicePage() { - console.log("device") - const deviceAPI = useDeviceAPI() const snackbar = useSnackbar() const isMobile = useMobile() diff --git a/src/pages/Devices.tsx b/src/pages/Devices.tsx index d2c475a..c734374 100644 --- a/src/pages/Devices.tsx +++ b/src/pages/Devices.tsx @@ -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(undefined); @@ -152,7 +160,6 @@ export default function Devices() { }) } - const loadDevices = () => { setDevicesLoading(true) deviceAPI.list( @@ -359,14 +366,14 @@ export default function Devices() { return( - + {/* */} 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 }} > - + {groupsLoading ? }/> : groups.map((group, index) => { if (group.id()) return ( - + {group.name()}} key={"group-tab-"+index}/> ) })} - } onClick={() => openGroupSettings(undefined, "add")} /> + } onClick={() => openGroupSettings(undefined, "add")} /> - title="Devices" + title={} subtitle={subtitle()} rows={devices} columns={columns()} diff --git a/src/pages/Group.tsx b/src/pages/Group.tsx index edefc31..3db563f 100644 --- a/src/pages/Group.tsx +++ b/src/pages/Group.tsx @@ -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(state?.device ? Group.create(state.device) : Group.create()) - + const [group, setGroup] = useState(state?.group ? Group.create(state.group) : Group.create()) + const [loadingGroup, setLoadingGroup] = useState(false) + const [loadingDevices, setLoadingDevices] = useState(false) + const [devices, setDevices] = useState([]) + const [permissions, setPermissions] = useState([]) + 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[] => { + return [ + { + title: "State", + render: (device) => { + const status = device.status ?? pond.DeviceStatus.create() + const deviceStateHelper = getDeviceStateHelper(status.state); + return ( + + {deviceStateHelper.icon} + + ) + } + }, + { + title: "ID", + render: device => + {device.settings?.deviceId} + + }, + { + title: "Device", + render: (device) => { + return ( + + + + ) + } + }, + { + title: "Description", + render: device => { + const description = device.settings?.description ?? "" + return ( + + + + {description.length > 20 + ? description.substring(0, 20) + "..." + : description} + + + + ) + } + }, + ] + } + + const handleChange = (event: any) => { + setLimit(event.target.value); + }; return ( - - - + + + + + + + {}} + /> + - - {/* */} - - + + 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} + /> + ) } \ No newline at end of file diff --git a/src/pages/Groups.tsx b/src/pages/Groups.tsx index 61dfd5e..1493e84 100644 --- a/src/pages/Groups.tsx +++ b/src/pages/Groups.tsx @@ -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 ( - - title={} - rows={groups} - columns={columns()} - total={total} - pageSize={limit} - page={page} - setPage={setPage} - handleRowsPerPageChange={handleRowsPerPageChange} - isLoading={loading} - setSearchText={setSearch} - onRowClick={handleRowClick} - /> + + + title={} + rows={groups} + columns={columns()} + total={total} + pageSize={limit} + page={page} + setPage={setPage} + handleRowsPerPageChange={handleRowsPerPageChange} + isLoading={loading} + setSearchText={setSearch} + onRowClick={handleRowClick} + /> + ) } \ No newline at end of file diff --git a/src/pages/PageContainer.tsx b/src/pages/PageContainer.tsx index 6bdf657..a8822a7 100644 --- a/src/pages/PageContainer.tsx +++ b/src/pages/PageContainer.tsx @@ -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 => { +export const PageContainer: React.FunctionComponent = (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 ( = props => { }} disableGutters maxWidth={false} - children={{children}} + children={<>{children}} /> ); }; diff --git a/src/providers/pond/groupAPI.tsx b/src/providers/pond/groupAPI.tsx index 407cb7e..eb9ff4d 100644 --- a/src/providers/pond/groupAPI.tsx +++ b/src/providers/pond/groupAPI.tsx @@ -15,6 +15,7 @@ export interface IGroupAPIContext { updateGroup: (id: number, group: pond.GroupSettings) => Promise; removeGroup: (id: number) => Promise; getGroup: (id: number) => Promise; + getGroupAndPermissions: (id: number) => Promise>; listGroups: ( limit: number, offset: number, @@ -78,6 +79,27 @@ export default function GroupProvider(props: PropsWithChildren) { return get(pondURL("/groups/" + id)); }; + const getGroupAndPermissions = ( + id: number, + keys?: string[], + types?: string[], + ) => { + keys = keys ? keys : getContextKeys(); + types = types? types : getContextTypes(); + let url = "/groupAndPermissions/" + id + if (as) { + url = url + "?as=" + as + + + + (keys ? "?keys=" + keys.toString() : "") + + (types ? "&types=" + types.toString() : "") + } else { + url = url + + (keys ? "?keys=" + keys.toString() : "") + + (types ? "&types=" + types.toString() : "") + } + return get(pondURL(url)); + }; + const listGroups = ( limit: number, offset: number, @@ -152,6 +174,7 @@ export default function GroupProvider(props: PropsWithChildren) { updateGroup, removeGroup, getGroup, + getGroupAndPermissions, listGroups, listGroupDevices, updateGroupPermissions diff --git a/src/teams/TeamList.tsx b/src/teams/TeamList.tsx index 533c657..147dc19 100644 --- a/src/teams/TeamList.tsx +++ b/src/teams/TeamList.tsx @@ -47,6 +47,7 @@ const useStyles = makeStyles((theme: Theme) => ({ titleBox: { display: "flex", alignItems: "center", + marginLeft: theme.spacing(1), } })) diff --git a/tsconfig.app.json b/tsconfig.app.json index d323d83..4824ad8 100644 --- a/tsconfig.app.json +++ b/tsconfig.app.json @@ -12,6 +12,7 @@ //"allowImportingTsExtensions": true, "isolatedModules": true, //"moduleDetection": "force", + "allowSyntheticDefaultImports": true, "noEmit": true, "jsx": "react-jsx",