users page added and functional with basic mui table components
This commit is contained in:
parent
d050fdee55
commit
dd522732db
7 changed files with 547 additions and 183 deletions
128
package-lock.json
generated
128
package-lock.json
generated
|
|
@ -14,8 +14,6 @@
|
||||||
"@mui/icons-material": "^6.1.6",
|
"@mui/icons-material": "^6.1.6",
|
||||||
"@mui/material": "^6.1.6",
|
"@mui/material": "^6.1.6",
|
||||||
"@mui/styles": "^6.1.6",
|
"@mui/styles": "^6.1.6",
|
||||||
"@mui/x-data-grid": "^7.22.2",
|
|
||||||
"@mui/x-date-pickers": "^7.22.2",
|
|
||||||
"@sentry/react": "^8.38.0",
|
"@sentry/react": "^8.38.0",
|
||||||
"@types/classnames": "^2.3.0",
|
"@types/classnames": "^2.3.0",
|
||||||
"axios": "^1.7.7",
|
"axios": "^1.7.7",
|
||||||
|
|
@ -1512,126 +1510,6 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@mui/x-data-grid": {
|
|
||||||
"version": "7.22.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/@mui/x-data-grid/-/x-data-grid-7.22.2.tgz",
|
|
||||||
"integrity": "sha512-yfy2s5A6tbajQZiEdsba49T4FYb9F0WPrzbbG30dl1+sIiX4ZRX7ma44UIDGPZrsZv8xkkE+p8qeJxZ7OaMteA==",
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"@babel/runtime": "^7.25.7",
|
|
||||||
"@mui/utils": "^5.16.6 || ^6.0.0",
|
|
||||||
"@mui/x-internals": "7.21.0",
|
|
||||||
"clsx": "^2.1.1",
|
|
||||||
"prop-types": "^15.8.1",
|
|
||||||
"reselect": "^5.1.1"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=14.0.0"
|
|
||||||
},
|
|
||||||
"funding": {
|
|
||||||
"type": "opencollective",
|
|
||||||
"url": "https://opencollective.com/mui-org"
|
|
||||||
},
|
|
||||||
"peerDependencies": {
|
|
||||||
"@emotion/react": "^11.9.0",
|
|
||||||
"@emotion/styled": "^11.8.1",
|
|
||||||
"@mui/material": "^5.15.14 || ^6.0.0",
|
|
||||||
"@mui/system": "^5.15.14 || ^6.0.0",
|
|
||||||
"react": "^17.0.0 || ^18.0.0",
|
|
||||||
"react-dom": "^17.0.0 || ^18.0.0"
|
|
||||||
},
|
|
||||||
"peerDependenciesMeta": {
|
|
||||||
"@emotion/react": {
|
|
||||||
"optional": true
|
|
||||||
},
|
|
||||||
"@emotion/styled": {
|
|
||||||
"optional": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@mui/x-date-pickers": {
|
|
||||||
"version": "7.22.2",
|
|
||||||
"integrity": "sha512-1KHSlIlnSoY3oHm820By8X344pIdGYqPvCCvfVHrEeeIQ/pHdxDD8tjZFWkFl4Jgm9oVFK90fMcqNZAzc+WaCw==",
|
|
||||||
"dependencies": {
|
|
||||||
"@babel/runtime": "^7.25.7",
|
|
||||||
"@mui/utils": "^5.16.6 || ^6.0.0",
|
|
||||||
"@mui/x-internals": "7.21.0",
|
|
||||||
"@types/react-transition-group": "^4.4.11",
|
|
||||||
"clsx": "^2.1.1",
|
|
||||||
"prop-types": "^15.8.1",
|
|
||||||
"react-transition-group": "^4.4.5"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=14.0.0"
|
|
||||||
},
|
|
||||||
"funding": {
|
|
||||||
"type": "opencollective",
|
|
||||||
"url": "https://opencollective.com/mui-org"
|
|
||||||
},
|
|
||||||
"peerDependencies": {
|
|
||||||
"@emotion/react": "^11.9.0",
|
|
||||||
"@emotion/styled": "^11.8.1",
|
|
||||||
"@mui/material": "^5.15.14 || ^6.0.0",
|
|
||||||
"@mui/system": "^5.15.14 || ^6.0.0",
|
|
||||||
"date-fns": "^2.25.0 || ^3.2.0 || ^4.0.0",
|
|
||||||
"date-fns-jalali": "^2.13.0-0 || ^3.2.0-0",
|
|
||||||
"dayjs": "^1.10.7",
|
|
||||||
"luxon": "^3.0.2",
|
|
||||||
"moment": "^2.29.4",
|
|
||||||
"moment-hijri": "^2.1.2 || ^3.0.0",
|
|
||||||
"moment-jalaali": "^0.7.4 || ^0.8.0 || ^0.9.0 || ^0.10.0",
|
|
||||||
"react": "^17.0.0 || ^18.0.0",
|
|
||||||
"react-dom": "^17.0.0 || ^18.0.0"
|
|
||||||
},
|
|
||||||
"peerDependenciesMeta": {
|
|
||||||
"@emotion/react": {
|
|
||||||
"optional": true
|
|
||||||
},
|
|
||||||
"@emotion/styled": {
|
|
||||||
"optional": true
|
|
||||||
},
|
|
||||||
"date-fns": {
|
|
||||||
"optional": true
|
|
||||||
},
|
|
||||||
"date-fns-jalali": {
|
|
||||||
"optional": true
|
|
||||||
},
|
|
||||||
"dayjs": {
|
|
||||||
"optional": true
|
|
||||||
},
|
|
||||||
"luxon": {
|
|
||||||
"optional": true
|
|
||||||
},
|
|
||||||
"moment": {
|
|
||||||
"optional": true
|
|
||||||
},
|
|
||||||
"moment-hijri": {
|
|
||||||
"optional": true
|
|
||||||
},
|
|
||||||
"moment-jalaali": {
|
|
||||||
"optional": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@mui/x-internals": {
|
|
||||||
"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==",
|
|
||||||
"dependencies": {
|
|
||||||
"@babel/runtime": "^7.25.7",
|
|
||||||
"@mui/utils": "^5.16.6 || ^6.0.0"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=14.0.0"
|
|
||||||
},
|
|
||||||
"funding": {
|
|
||||||
"type": "opencollective",
|
|
||||||
"url": "https://opencollective.com/mui-org"
|
|
||||||
},
|
|
||||||
"peerDependencies": {
|
|
||||||
"react": "^17.0.0 || ^18.0.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@nodelib/fs.scandir": {
|
"node_modules/@nodelib/fs.scandir": {
|
||||||
"version": "2.1.5",
|
"version": "2.1.5",
|
||||||
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
|
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
|
||||||
|
|
@ -4368,12 +4246,6 @@
|
||||||
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz",
|
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz",
|
||||||
"integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw=="
|
"integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw=="
|
||||||
},
|
},
|
||||||
"node_modules/reselect": {
|
|
||||||
"version": "5.1.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/reselect/-/reselect-5.1.1.tgz",
|
|
||||||
"integrity": "sha512-K/BG6eIky/SBpzfHZv/dd+9JBFiS4SWV7FIujVyJRux6e45+73RaUHXLmIR1f7WOMaQ0U1km6qwklRQxpJJY0w==",
|
|
||||||
"license": "MIT"
|
|
||||||
},
|
|
||||||
"node_modules/resolve": {
|
"node_modules/resolve": {
|
||||||
"version": "1.22.8",
|
"version": "1.22.8",
|
||||||
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz",
|
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz",
|
||||||
|
|
|
||||||
|
|
@ -23,8 +23,6 @@
|
||||||
"@mui/icons-material": "^6.1.6",
|
"@mui/icons-material": "^6.1.6",
|
||||||
"@mui/material": "^6.1.6",
|
"@mui/material": "^6.1.6",
|
||||||
"@mui/styles": "^6.1.6",
|
"@mui/styles": "^6.1.6",
|
||||||
"@mui/x-data-grid": "^7.22.2",
|
|
||||||
"@mui/x-date-pickers": "^7.22.2",
|
|
||||||
"@sentry/react": "^8.38.0",
|
"@sentry/react": "^8.38.0",
|
||||||
"@types/classnames": "^2.3.0",
|
"@types/classnames": "^2.3.0",
|
||||||
"axios": "^1.7.7",
|
"axios": "^1.7.7",
|
||||||
|
|
|
||||||
|
|
@ -52,7 +52,7 @@ export default function SearchSelect(props: Props) {
|
||||||
multiple,
|
multiple,
|
||||||
variant,
|
variant,
|
||||||
style,
|
style,
|
||||||
getOptionSelected,
|
// getOptionSelected,
|
||||||
onChange,
|
onChange,
|
||||||
onKeyDown
|
onKeyDown
|
||||||
} = props;
|
} = props;
|
||||||
|
|
@ -65,6 +65,17 @@ export default function SearchSelect(props: Props) {
|
||||||
return { value: s, label: s } as Option;
|
return { value: s, label: s } as Option;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const anyToLabel = (o: any): string => {
|
||||||
|
if (o.text && o.value) {
|
||||||
|
return o.text + " - " + o.value
|
||||||
|
} else if (o.value) {
|
||||||
|
return o.value
|
||||||
|
} else if (o.label) {
|
||||||
|
return o.label
|
||||||
|
}
|
||||||
|
return o.toString();
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Autocomplete
|
<Autocomplete
|
||||||
id={label + "-autocomplete"}
|
id={label + "-autocomplete"}
|
||||||
|
|
@ -123,10 +134,15 @@ export default function SearchSelect(props: Props) {
|
||||||
return filtered;
|
return filtered;
|
||||||
}}
|
}}
|
||||||
getOptionLabel={option => {
|
getOptionLabel={option => {
|
||||||
|
// return anyToLabel(option)
|
||||||
|
// console.log(option)
|
||||||
|
// console.log("Is Option: ",option instanceof Option)
|
||||||
if (option instanceof Option) {
|
if (option instanceof Option) {
|
||||||
return option.text + " - " + option.value
|
return option.text + " - " + option.value
|
||||||
|
// } else if (option.text && option.value) {
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
return option as string
|
return anyToLabel(option)
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
// getOptionSelected={
|
// getOptionSelected={
|
||||||
|
|
|
||||||
|
|
@ -53,7 +53,7 @@ export default function Router(props: Props) {
|
||||||
{/* Page routes */}
|
{/* Page routes */}
|
||||||
<Route index element={hello()} />
|
<Route index element={hello()} />
|
||||||
<Route path="/teams" element={<Teams/>} />
|
<Route path="/teams" element={<Teams/>} />
|
||||||
<Route path="/users" element={<Users/>} />
|
<Route path="/users" element={<Users title="Users"/>} />
|
||||||
{/* <Route
|
{/* <Route
|
||||||
path="/teams/:teamID"
|
path="/teams/:teamID"
|
||||||
element={<Team />}
|
element={<Team />}
|
||||||
|
|
|
||||||
468
src/pages/Users.tsx
Normal file
468
src/pages/Users.tsx
Normal file
|
|
@ -0,0 +1,468 @@
|
||||||
|
import {
|
||||||
|
Avatar,
|
||||||
|
Box,
|
||||||
|
Button,
|
||||||
|
Collapse,
|
||||||
|
darken,
|
||||||
|
DialogActions,
|
||||||
|
DialogContent,
|
||||||
|
DialogTitle,
|
||||||
|
Divider,
|
||||||
|
IconButton,
|
||||||
|
List,
|
||||||
|
ListItem,
|
||||||
|
ListItemAvatar,
|
||||||
|
ListItemButton,
|
||||||
|
ListItemIcon,
|
||||||
|
ListItemText,
|
||||||
|
ListSubheader,
|
||||||
|
PaletteColor,
|
||||||
|
Paper,
|
||||||
|
Table,
|
||||||
|
TableBody,
|
||||||
|
TableCell,
|
||||||
|
TableContainer,
|
||||||
|
TableHead,
|
||||||
|
TablePagination,
|
||||||
|
TableRow,
|
||||||
|
Theme,
|
||||||
|
Typography,
|
||||||
|
} from "@mui/material";
|
||||||
|
import ResponsiveDialog from "common/ResponsiveDialog";
|
||||||
|
// import { getTableIcons } from "common/ResponsiveTable";
|
||||||
|
import SearchSelect, { Option } from "common/SearchSelect";
|
||||||
|
// import SmartBreadcrumb from "common/SmartBreadcrumb";
|
||||||
|
import { useMobile, useSnackbar, useUserAPI } from "hooks";
|
||||||
|
// import MaterialTable, { Column } from "material-table";
|
||||||
|
import { User } from "models";
|
||||||
|
import PageContainer from "pages/PageContainer";
|
||||||
|
// import { ListUsersResponse } from "providers/pond/userAPI";
|
||||||
|
import React, { useEffect, useState } from "react";
|
||||||
|
import { makeStyles } from "@mui/styles";
|
||||||
|
import { pond } from "protobuf-ts/pond";
|
||||||
|
import { getSecondaryColour, getSignatureAccentColour } from "services/whiteLabel";
|
||||||
|
import { Add, ChevronRight, Face, Remove } from "@mui/icons-material";
|
||||||
|
|
||||||
|
const useStyles = makeStyles((theme: Theme) => {
|
||||||
|
// const isMobile = useMobile()
|
||||||
|
return ({
|
||||||
|
gutter: {
|
||||||
|
backgroundColor: darken(theme.palette.background.paper, 0.03),
|
||||||
|
border: "none"
|
||||||
|
},
|
||||||
|
dataTable: {
|
||||||
|
backgroundColor: theme.palette.background.paper
|
||||||
|
},
|
||||||
|
tableContainer: {
|
||||||
|
margin: theme.spacing(1),
|
||||||
|
[theme.breakpoints.up("sm")]: {
|
||||||
|
margin: theme.spacing(2)
|
||||||
|
},
|
||||||
|
width: "auto"
|
||||||
|
},
|
||||||
|
title: {
|
||||||
|
padding: theme.spacing(1)
|
||||||
|
},
|
||||||
|
titleContainer: {
|
||||||
|
border: "none"
|
||||||
|
},
|
||||||
|
chipContainer: {
|
||||||
|
maxWidth: "100%",
|
||||||
|
maxHeight: "100%",
|
||||||
|
justifyContent: "flex-start",
|
||||||
|
alignItems: "flex-start"
|
||||||
|
},
|
||||||
|
deleteIcon: {
|
||||||
|
color: "var(--status-alert)"
|
||||||
|
},
|
||||||
|
userAvatar: {
|
||||||
|
color: "#fff",
|
||||||
|
backgroundColor: getSecondaryColour()["700" as keyof PaletteColor],
|
||||||
|
[theme.breakpoints.down("xs")]: {
|
||||||
|
width: "32px",
|
||||||
|
height: "32px"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
listItemButton: {
|
||||||
|
'&:hover': {
|
||||||
|
color: getSignatureAccentColour()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
avatarContainer: {
|
||||||
|
textAlign: "center",
|
||||||
|
justifyContent: "center",
|
||||||
|
},
|
||||||
|
addIcon: {
|
||||||
|
color: "var(--status-ok)"
|
||||||
|
}})},
|
||||||
|
);
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
title: string | JSX.Element;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function Users(props: Props) {
|
||||||
|
const { title } = props;
|
||||||
|
const classes = useStyles();
|
||||||
|
const userAPI = useUserAPI();
|
||||||
|
// let tableRef: any = React.createRef();
|
||||||
|
const { error, success } = useSnackbar();
|
||||||
|
const [pageSize, setPageSize] = useState(5);
|
||||||
|
const [page, setPage] = useState(0);
|
||||||
|
const [total, setTotal] = useState(0);
|
||||||
|
const [showAllowAction, setShowAllowAction] = useState(false);
|
||||||
|
const [selectedAction, setSelectedAction] = useState<Option | undefined>();
|
||||||
|
const [showEnableFeature, setShowEnableFeature] = useState(false);
|
||||||
|
const [selectedFeature, setSelectedFeature] = useState<Option | undefined>();
|
||||||
|
const [selectedUser, setSelectedUser] = useState(User.create());
|
||||||
|
const TitleMap = new Map([
|
||||||
|
["Uploaded", "uploaded"],
|
||||||
|
["Version", "version"],
|
||||||
|
["Platform", "platform"],
|
||||||
|
["Channel", "channel"],
|
||||||
|
["Size", "size"]
|
||||||
|
]);
|
||||||
|
const actions = [
|
||||||
|
"provision",
|
||||||
|
"upload-firmware",
|
||||||
|
"remove-devices",
|
||||||
|
"copy-token",
|
||||||
|
"recluse",
|
||||||
|
"pause-data"
|
||||||
|
].sort();
|
||||||
|
const features = [
|
||||||
|
"admin",
|
||||||
|
"beta",
|
||||||
|
"billing",
|
||||||
|
"security",
|
||||||
|
"dev-channel",
|
||||||
|
"docs",
|
||||||
|
"sleep",
|
||||||
|
"support",
|
||||||
|
"tasks",
|
||||||
|
"teams",
|
||||||
|
"maps",
|
||||||
|
"json",
|
||||||
|
"john-deere",
|
||||||
|
"grain-composition",
|
||||||
|
"developer",
|
||||||
|
"marketplace",
|
||||||
|
"installer"
|
||||||
|
].sort();
|
||||||
|
|
||||||
|
const allow = () => {
|
||||||
|
const action = selectedAction ? selectedAction.label : "";
|
||||||
|
if (selectedUser.id() === "" || action === "") return;
|
||||||
|
if (!selectedUser.settings.actions.includes(action)) {
|
||||||
|
selectedUser.settings.actions.push(action);
|
||||||
|
}
|
||||||
|
userAPI
|
||||||
|
.updateUser(selectedUser.id(), selectedUser.protobuf())
|
||||||
|
.then(() => {
|
||||||
|
success("Allowed " + selectedUser.name() + " to " + action);
|
||||||
|
setShowAllowAction(false);
|
||||||
|
setSelectedAction({} as Option);
|
||||||
|
})
|
||||||
|
.catch(() => error("Failed to allow " + selectedUser.name() + " to " + action));
|
||||||
|
};
|
||||||
|
|
||||||
|
const enable = () => {
|
||||||
|
const feature = selectedFeature ? selectedFeature.label : "";
|
||||||
|
if (selectedUser.id() === "" || feature === "") return;
|
||||||
|
if (!selectedUser.settings.features.includes(feature)) {
|
||||||
|
selectedUser.settings.features.push(feature);
|
||||||
|
}
|
||||||
|
userAPI
|
||||||
|
.updateUser(selectedUser.id(), selectedUser.protobuf())
|
||||||
|
.then(() => {
|
||||||
|
success("Enabled " + feature + " for " + selectedUser.name());
|
||||||
|
setShowEnableFeature(false);
|
||||||
|
setSelectedFeature({} as Option);
|
||||||
|
})
|
||||||
|
.catch(() => error("Failed to enable " + feature + " for " + selectedUser.name()));
|
||||||
|
};
|
||||||
|
|
||||||
|
const remove = (user: User, type: "action" | "feature", key: string) => {
|
||||||
|
if (user.id() === "" || key === "") return;
|
||||||
|
if (type === "action") {
|
||||||
|
user.settings.actions = user.settings.actions.filter(v => v !== key);
|
||||||
|
}
|
||||||
|
if (type === "feature") {
|
||||||
|
user.settings.features = user.settings.features.filter(v => v !== key);
|
||||||
|
}
|
||||||
|
userAPI
|
||||||
|
.updateUser(user.id(), user.protobuf())
|
||||||
|
.then(() => {
|
||||||
|
if (type === "action") {
|
||||||
|
success("Disallowed " + user.name() + " from " + key);
|
||||||
|
setSelectedAction({} as Option);
|
||||||
|
}
|
||||||
|
if (type === "feature") {
|
||||||
|
success("Disabled " + key + " for " + user.name());
|
||||||
|
setSelectedFeature({} as Option);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
if (type === "action") {
|
||||||
|
error("Failed to disallow " + user.name() + " from " + key);
|
||||||
|
}
|
||||||
|
if (type === "feature") {
|
||||||
|
error("Failed to disable " + key + " for " + user.name());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const details = (user: User) => {
|
||||||
|
const item = (text: string, type: "action" | "feature") => (
|
||||||
|
<ListItem key={text}>
|
||||||
|
<ListItemIcon>
|
||||||
|
<IconButton
|
||||||
|
onClick={() => {
|
||||||
|
setSelectedUser(user);
|
||||||
|
remove(user, type, text);
|
||||||
|
}}>
|
||||||
|
<Remove className={classes.deleteIcon} />
|
||||||
|
</IconButton>
|
||||||
|
</ListItemIcon>
|
||||||
|
<ListItemText>{text}</ListItemText>
|
||||||
|
</ListItem>
|
||||||
|
);
|
||||||
|
const actions = user.settings.actions;
|
||||||
|
const features = user.settings.features;
|
||||||
|
// const showActions = view !== "features";
|
||||||
|
// const showFeatures = view !== "actions";
|
||||||
|
return (
|
||||||
|
<List disablePadding dense>
|
||||||
|
<ListSubheader className={classes.gutter}>Actions</ListSubheader>
|
||||||
|
{actions.map(action => item(action, "action"))}
|
||||||
|
<ListItemButton
|
||||||
|
onClick={() => {
|
||||||
|
setSelectedUser(user);
|
||||||
|
setShowAllowAction(true);
|
||||||
|
}}>
|
||||||
|
<ListItemIcon>
|
||||||
|
<IconButton>
|
||||||
|
<Add className={classes.addIcon} />
|
||||||
|
</IconButton>
|
||||||
|
</ListItemIcon>
|
||||||
|
<ListItemText>Allow an action</ListItemText>
|
||||||
|
</ListItemButton>
|
||||||
|
<Divider />
|
||||||
|
<ListSubheader className={classes.gutter}>Features</ListSubheader>
|
||||||
|
{features.map(feature => item(feature, "feature"))}
|
||||||
|
<ListItemButton
|
||||||
|
onClick={() => {
|
||||||
|
setSelectedUser(user);
|
||||||
|
setShowEnableFeature(true);
|
||||||
|
}}>
|
||||||
|
<ListItemIcon>
|
||||||
|
<IconButton>
|
||||||
|
<Add className={classes.addIcon} />
|
||||||
|
</IconButton>
|
||||||
|
</ListItemIcon>
|
||||||
|
<ListItemText>Enable a feature</ListItemText>
|
||||||
|
</ListItemButton>
|
||||||
|
</List>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const renderUser = (user: User) => {
|
||||||
|
const avatarURL = user.settings.avatar;
|
||||||
|
const avatar = avatarURL ? (
|
||||||
|
<Avatar alt={user.name()} src={avatarURL} />
|
||||||
|
) : (
|
||||||
|
<Avatar alt={user.name()}>
|
||||||
|
<Face />
|
||||||
|
</Avatar>
|
||||||
|
);
|
||||||
|
return (
|
||||||
|
<ListItem style={{ padding: 0 }}>
|
||||||
|
<ListItemAvatar>{avatar}</ListItemAvatar>
|
||||||
|
<ListItemText primary={user.name()} secondary={user.settings.email} />
|
||||||
|
</ListItem>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// const renderGutter = (user: UserData) => {
|
||||||
|
// return (
|
||||||
|
|
||||||
|
// )
|
||||||
|
// }
|
||||||
|
|
||||||
|
// interface UserData {
|
||||||
|
// settings: pond.UserSettings;
|
||||||
|
// }
|
||||||
|
const [rows, setRows] = useState<User[]>([])
|
||||||
|
|
||||||
|
const loadUsers = () => {
|
||||||
|
userAPI.listUsers(pageSize, page*pageSize, "desc", "name").then(resp => {
|
||||||
|
let r: User[] = []
|
||||||
|
resp.users.forEach((user, _index) => {
|
||||||
|
r.push(user)
|
||||||
|
})
|
||||||
|
setRows(r)
|
||||||
|
setTotal(resp.total)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
loadUsers();
|
||||||
|
}, [page, pageSize]); // Trigger fetching data when page or pageSize changes
|
||||||
|
|
||||||
|
const handleRowsPerPageChange = (event: any) => {
|
||||||
|
const newRowsPerPage = parseInt(event.target.value, 10); // Get selected value
|
||||||
|
setPageSize(newRowsPerPage);
|
||||||
|
setPage(0); // Reset to the first page
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleCollapse = (user: User) => {
|
||||||
|
if (selectedUser.id() === user.id()) {
|
||||||
|
setSelectedUser(User.create())
|
||||||
|
} else {
|
||||||
|
setSelectedUser(user)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
// console.log("Open Row: ", openRow);
|
||||||
|
console.log("Selected User: ", selectedUser);
|
||||||
|
console.log("Selected ID: ", selectedUser.id())
|
||||||
|
}, [selectedUser])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<PageContainer>
|
||||||
|
<TableContainer className={classes.tableContainer} component={Paper}>
|
||||||
|
<Table>
|
||||||
|
<TableHead>
|
||||||
|
<TableRow>
|
||||||
|
<TableCell colSpan={6} className={classes.titleContainer}>
|
||||||
|
{typeof(title) === "string" ?
|
||||||
|
<Typography variant="h5" className={classes.title}>
|
||||||
|
{title}
|
||||||
|
</Typography>
|
||||||
|
:
|
||||||
|
<Box className={classes.title}>
|
||||||
|
title
|
||||||
|
</Box>
|
||||||
|
}
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
<TableRow>
|
||||||
|
<TableCell></TableCell>
|
||||||
|
<TableCell>Profile</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
</TableHead>
|
||||||
|
<TableBody>
|
||||||
|
{rows.map((row, index) => (
|
||||||
|
<React.Fragment key={"user-row-"+index}>
|
||||||
|
<TableRow>
|
||||||
|
<TableCell width={1}>
|
||||||
|
<IconButton sx={{
|
||||||
|
transform: selectedUser.settings.id === row.settings.id ?
|
||||||
|
'rotate(90deg)' : 'rotate(0deg)',
|
||||||
|
transition: 'transform 0.3s ease',
|
||||||
|
}}
|
||||||
|
onClick={() => handleCollapse(row)}>
|
||||||
|
<ChevronRight/>
|
||||||
|
</IconButton>
|
||||||
|
</TableCell>
|
||||||
|
<TableCell>
|
||||||
|
{renderUser(row)}
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
<TableRow className={classes.gutter}>
|
||||||
|
<TableCell colSpan={6} style={{padding:0, margin: 0, border: "none"}}>
|
||||||
|
<Collapse in={selectedUser.settings.id === row.settings.id} timeout="auto" unmountOnExit>
|
||||||
|
{details(row)}
|
||||||
|
</Collapse>
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
</React.Fragment>
|
||||||
|
))}
|
||||||
|
</TableBody>
|
||||||
|
</Table>
|
||||||
|
<TablePagination
|
||||||
|
rowsPerPageOptions={[5, 10, 20]}
|
||||||
|
component="div"
|
||||||
|
count={total}
|
||||||
|
rowsPerPage={pageSize}
|
||||||
|
page={page}
|
||||||
|
onPageChange={(_event, page) => setPage(page)}
|
||||||
|
onRowsPerPageChange={handleRowsPerPageChange}
|
||||||
|
/>
|
||||||
|
</TableContainer>
|
||||||
|
<ResponsiveDialog
|
||||||
|
maxWidth="sm"
|
||||||
|
fullWidth
|
||||||
|
open={showAllowAction}
|
||||||
|
onClose={() => setShowAllowAction(false)}
|
||||||
|
aria-labelledby="allow-action">
|
||||||
|
<DialogTitle id="allow-action-title">Allow an action</DialogTitle>
|
||||||
|
<DialogContent>
|
||||||
|
<SearchSelect
|
||||||
|
label="Select an action to allow"
|
||||||
|
selected={selectedAction}
|
||||||
|
// options={actions}
|
||||||
|
options={actions
|
||||||
|
.filter(a => !selectedUser.settings.actions.includes(a))
|
||||||
|
.map(a => {
|
||||||
|
return { value: a, label: a };
|
||||||
|
})}
|
||||||
|
changeSelection={(option: Option | null) =>
|
||||||
|
setSelectedAction(option ? option : undefined)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</DialogContent>
|
||||||
|
<DialogActions>
|
||||||
|
<Button color="primary" onClick={() => setShowAllowAction(false)}>
|
||||||
|
Close
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
color="primary"
|
||||||
|
disabled={
|
||||||
|
!selectedAction || !actions.includes(selectedAction ? selectedAction.label : "")
|
||||||
|
}
|
||||||
|
onClick={allow}>
|
||||||
|
Allow
|
||||||
|
</Button>
|
||||||
|
</DialogActions>
|
||||||
|
</ResponsiveDialog>
|
||||||
|
<ResponsiveDialog
|
||||||
|
maxWidth="sm"
|
||||||
|
fullWidth
|
||||||
|
open={showEnableFeature}
|
||||||
|
onClose={() => setShowEnableFeature(false)}
|
||||||
|
aria-labelledby="enable-feature">
|
||||||
|
<DialogTitle id="enable-feature-title">Enable a feature</DialogTitle>
|
||||||
|
<DialogContent>
|
||||||
|
<SearchSelect
|
||||||
|
label="Select a feature to enable"
|
||||||
|
selected={selectedFeature}
|
||||||
|
// options={features}
|
||||||
|
options={features
|
||||||
|
.filter(a => !selectedUser.settings.features.includes(a))
|
||||||
|
.map(a => {
|
||||||
|
return { value: a, label: a };
|
||||||
|
})}
|
||||||
|
changeSelection={(option: Option | null) =>
|
||||||
|
setSelectedFeature(option ? option : undefined)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</DialogContent>
|
||||||
|
<DialogActions>
|
||||||
|
<Button color="primary" onClick={() => setShowEnableFeature(false)}>
|
||||||
|
Close
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
color="primary"
|
||||||
|
disabled={
|
||||||
|
!selectedFeature || !features.includes(selectedFeature ? selectedFeature.label : "")
|
||||||
|
}
|
||||||
|
onClick={enable}>
|
||||||
|
Enable
|
||||||
|
</Button>
|
||||||
|
</DialogActions>
|
||||||
|
</ResponsiveDialog>
|
||||||
|
</PageContainer>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -11,20 +11,21 @@ import { AxiosResponse } from "axios";
|
||||||
import { pond } from "protobuf-ts/pond";
|
import { pond } from "protobuf-ts/pond";
|
||||||
import { User } from "../../models/user";
|
import { User } from "../../models/user";
|
||||||
import { Scope } from "models";
|
import { Scope } from "models";
|
||||||
|
import { or } from "utils/types";
|
||||||
|
|
||||||
// export interface ListUsersResponse {
|
export interface ListUsersResponse {
|
||||||
// users: User[];
|
users: User[];
|
||||||
// nextOffset: number;
|
nextOffset: number;
|
||||||
// total: number;
|
total: number;
|
||||||
// }
|
}
|
||||||
export interface IUserAPIContext {
|
export interface IUserAPIContext {
|
||||||
// listUsers: (
|
listUsers: (
|
||||||
// limit: number,
|
limit: number,
|
||||||
// offset: number,
|
offset: number,
|
||||||
// order?: "asc" | "desc",
|
order?: "asc" | "desc",
|
||||||
// by?: string,
|
by?: string,
|
||||||
// search?: string
|
search?: string
|
||||||
// ) => Promise<ListUsersResponse>;
|
) => Promise<ListUsersResponse>;
|
||||||
// listProfiles: () => Promise<pond.UserProfile[]>;
|
// listProfiles: () => Promise<pond.UserProfile[]>;
|
||||||
listObjectUsers: (scope: Scope) => Promise<any>;
|
listObjectUsers: (scope: Scope) => Promise<any>;
|
||||||
updateObjectUsers: (scope: Scope, users: pond.IUser[]) => Promise<any>;
|
updateObjectUsers: (scope: Scope, users: pond.IUser[]) => Promise<any>;
|
||||||
|
|
@ -56,39 +57,39 @@ export default function UserProvider(props: PropsWithChildren<any>) {
|
||||||
const { get, put } = useHTTP();
|
const { get, put } = useHTTP();
|
||||||
const [{ as }] = useGlobalState();
|
const [{ as }] = useGlobalState();
|
||||||
|
|
||||||
// const listUsers = (
|
const listUsers = (
|
||||||
// limit: number,
|
limit: number,
|
||||||
// offset: number,
|
offset: number,
|
||||||
// order?: "asc" | "desc",
|
order?: "asc" | "desc",
|
||||||
// by?: string,
|
by?: string,
|
||||||
// search?: string
|
search?: string
|
||||||
// ): Promise<ListUsersResponse> => {
|
): Promise<ListUsersResponse> => {
|
||||||
// return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
// get(
|
get(
|
||||||
// pondURL(
|
pondURL(
|
||||||
// "/users?limit=" +
|
"/users?limit=" +
|
||||||
// limit +
|
limit +
|
||||||
// "&offset=" +
|
"&offset=" +
|
||||||
// offset +
|
offset +
|
||||||
// ("&order=" + (order ? order : "asc")) +
|
("&order=" + (order ? order : "asc")) +
|
||||||
// ("&by=" + (by ? by : "name")) +
|
("&by=" + (by ? by : "name")) +
|
||||||
// (search ? "&search=" + search : "")
|
(search ? "&search=" + search : "")
|
||||||
// )
|
)
|
||||||
// )
|
)
|
||||||
// .then((res: any) => {
|
.then((res: any) => {
|
||||||
// let users: User[] = [];
|
let users: User[] = [];
|
||||||
// if (res && res.data && res.data.users) {
|
if (res && res.data && res.data.users) {
|
||||||
// res.data.users.forEach((raw: any) => users.push(User.any(raw)));
|
res.data.users.forEach((raw: any) => users.push(User.any(raw)));
|
||||||
// }
|
}
|
||||||
// resolve({
|
resolve({
|
||||||
// users: users,
|
users: users,
|
||||||
// nextOffset: or(res.data.nextOffset, 0),
|
nextOffset: or(res.data.nextOffset, 0),
|
||||||
// total: or(res.data.total, 0)
|
total: or(res.data.total, 0)
|
||||||
// });
|
});
|
||||||
// })
|
})
|
||||||
// .catch((err: any) => reject(err));
|
.catch((err: any) => reject(err));
|
||||||
// });
|
});
|
||||||
// };
|
};
|
||||||
|
|
||||||
// const listProfiles = (): Promise<pond.UserProfile[]> => {
|
// const listProfiles = (): Promise<pond.UserProfile[]> => {
|
||||||
// return new Promise((resolve, reject) => {
|
// return new Promise((resolve, reject) => {
|
||||||
|
|
@ -230,7 +231,7 @@ export default function UserProvider(props: PropsWithChildren<any>) {
|
||||||
return (
|
return (
|
||||||
<UserAPIContext.Provider
|
<UserAPIContext.Provider
|
||||||
value={{
|
value={{
|
||||||
// listUsers,
|
listUsers,
|
||||||
// listProfiles,
|
// listProfiles,
|
||||||
listObjectUsers,
|
listObjectUsers,
|
||||||
updateObjectUsers,
|
updateObjectUsers,
|
||||||
|
|
|
||||||
|
|
@ -18,8 +18,8 @@ function generateBackgroundShades(themeType: "light" | "dark") {
|
||||||
})
|
})
|
||||||
|
|
||||||
return {
|
return {
|
||||||
default: "#303030", // Lightens the base color for background.default
|
default: "#303030",
|
||||||
paper: "#424242", // Slightly less light for background.paper
|
paper: "#373737",
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -185,7 +185,8 @@ function options(themeType: "light" | "dark"): ThemeOptions {
|
||||||
// color: "white"
|
// color: "white"
|
||||||
},
|
},
|
||||||
'&:hover': {
|
'&:hover': {
|
||||||
backgroundColor: "rgba(125, 125, 125, 0.2)"
|
backgroundColor: "rgba(125, 125, 125, 0.2)",
|
||||||
|
color: accent,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
@ -198,8 +199,9 @@ function options(themeType: "light" | "dark"): ThemeOptions {
|
||||||
// color: "white"
|
// color: "white"
|
||||||
},
|
},
|
||||||
'&:hover': {
|
'&:hover': {
|
||||||
color: themeType === "light" ? signature : "black",
|
// color: themeType === "light" ? signature : "black",
|
||||||
// color: "white"
|
// color: "white"
|
||||||
|
color: accent
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
@ -220,6 +222,13 @@ function options(themeType: "light" | "dark"): ThemeOptions {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
// MuiDataGrid: {
|
||||||
|
// styleOverrides: {
|
||||||
|
// root: {
|
||||||
|
// backgroundColor: "black"
|
||||||
|
// },
|
||||||
|
// },
|
||||||
|
// },
|
||||||
}
|
}
|
||||||
} as ThemeOptions;
|
} as ThemeOptions;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue