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
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>
|
||||
);
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue