rendering users page with the responsive container
This commit is contained in:
parent
39f5f6d452
commit
f2d3b32c97
4 changed files with 139 additions and 184 deletions
|
|
@ -67,8 +67,8 @@ export default function UserWrapper(props: Props) {
|
|||
if (hasFetched.current) return;
|
||||
setLoading(true)
|
||||
userAPI.getUserWithTeam(user_id).then(resp => {
|
||||
console.log(resp.data.team)
|
||||
console.log(resp.data.user)
|
||||
// console.log(resp.data.team)
|
||||
// console.log(resp.data.user)
|
||||
setGlobal({
|
||||
user: resp.data.user ? User.create(resp.data.user) : User.create(),
|
||||
team: resp.data.team ? Team.create(resp.data.team) : Team.create(),
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { Box, CircularProgress, Collapse, darken, IconButton, InputAdornment, Paper, Table, TableBody, TableCell, TableContainer, TableHead, TablePagination, TableRow, TextField, Theme, Typography } from "@mui/material";
|
||||
import { Box, CircularProgress, Collapse, darken, IconButton, InputAdornment, Paper, SxProps, Table, TableBody, TableCell, TableContainer, TableHead, TablePagination, TableRow, TextField, Theme, Typography } from "@mui/material";
|
||||
import { makeStyles } from "@mui/styles";
|
||||
import { ChevronRight, Search } from "@mui/icons-material"
|
||||
import { useEffect, useState } from "react";
|
||||
|
|
@ -28,15 +28,18 @@ const useStyles = makeStyles((theme: Theme) => {
|
|||
border: "none",
|
||||
textAlign: "right",
|
||||
},
|
||||
addIcon: {
|
||||
color: "var(--status-ok)"
|
||||
}
|
||||
})},
|
||||
);
|
||||
|
||||
export interface Column<T> {
|
||||
title: string | JSX.Element;
|
||||
cellStyle?: SxProps<Theme> | undefined
|
||||
render?: (row: T) => JSX.Element;
|
||||
}
|
||||
|
||||
interface Props<T> {
|
||||
rows: T[],
|
||||
columns?: any,
|
||||
columns?: Column<T>[],
|
||||
title?: string;
|
||||
total: number,
|
||||
pageSize: number,
|
||||
|
|
@ -100,12 +103,12 @@ export default function ResponsiveTable<T extends Object>(props: Props<T>) {
|
|||
setPage(page)
|
||||
}
|
||||
|
||||
return(
|
||||
return (
|
||||
<TableContainer className={classes.tableContainer} component={Paper}>
|
||||
<Table>
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
{title && <TableCell colSpan={6} className={classes.titleContainer}>
|
||||
{title && <TableCell colSpan={3} className={classes.titleContainer}>
|
||||
{typeof(title) === "string" ?
|
||||
<Typography variant="h5" className={classes.title}>
|
||||
{title}
|
||||
|
|
@ -117,7 +120,7 @@ export default function ResponsiveTable<T extends Object>(props: Props<T>) {
|
|||
}
|
||||
</TableCell>}
|
||||
{ setSearchText &&
|
||||
<TableCell colSpan={6} className={classes.searchContainer}>
|
||||
<TableCell colSpan={title ? 3 : 6} className={classes.searchContainer}>
|
||||
<TextField
|
||||
variant="standard"
|
||||
className={classes.title}
|
||||
|
|
@ -137,7 +140,13 @@ export default function ResponsiveTable<T extends Object>(props: Props<T>) {
|
|||
<TableRow>
|
||||
{ renderGutter && <TableCell></TableCell>}
|
||||
{ columns ?
|
||||
null
|
||||
columns.map((column, index) => {
|
||||
return (
|
||||
<TableCell key={"header-cell-"+index}>
|
||||
{column.title}
|
||||
</TableCell>
|
||||
)
|
||||
})
|
||||
: rows.length > 0 &&
|
||||
Object.keys(rows[0]).map((value, index) => {
|
||||
return(
|
||||
|
|
@ -176,7 +185,18 @@ export default function ResponsiveTable<T extends Object>(props: Props<T>) {
|
|||
</TableCell>
|
||||
}
|
||||
{columns ?
|
||||
null
|
||||
columns.map((column, j) => {
|
||||
if (column.render) return (
|
||||
<TableCell key={"row-"+index+"cell-"+j}>
|
||||
{column.render(row)}
|
||||
</TableCell>
|
||||
)
|
||||
return (
|
||||
<TableCell key={"row-"+index+"cell-"+j}>
|
||||
{Object.values(row)[j].toString()}
|
||||
</TableCell>
|
||||
)
|
||||
})
|
||||
:
|
||||
Object.values(row).map((value, j) => {
|
||||
return (
|
||||
|
|
|
|||
|
|
@ -53,7 +53,7 @@ export default function Router(props: Props) {
|
|||
{/* Page routes */}
|
||||
<Route index element={hello()} />
|
||||
<Route path="/teams" element={<Teams/>} />
|
||||
<Route path="/users" element={<Users title="Users"/>} />
|
||||
<Route path="/users" element={<Users/>} />
|
||||
{/* <Route
|
||||
path="/teams/:teamID"
|
||||
element={<Team />}
|
||||
|
|
|
|||
|
|
@ -1,13 +1,13 @@
|
|||
import {
|
||||
Avatar,
|
||||
Box,
|
||||
Button,
|
||||
Collapse,
|
||||
Chip,
|
||||
darken,
|
||||
DialogActions,
|
||||
DialogContent,
|
||||
DialogTitle,
|
||||
Divider,
|
||||
Grid2,
|
||||
IconButton,
|
||||
List,
|
||||
ListItem,
|
||||
|
|
@ -17,32 +17,19 @@ import {
|
|||
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 { useSnackbar, useUserAPI } from "hooks";
|
||||
import { User } from "models";
|
||||
import PageContainer from "pages/PageContainer";
|
||||
// import { ListUsersResponse } from "providers/pond/userAPI";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { 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";
|
||||
import ResponsiveTable from "common/ResponsiveTable";
|
||||
import { Add, Face, Remove } from "@mui/icons-material";
|
||||
import ResponsiveTable, { Column } from "common/ResponsiveTable";
|
||||
|
||||
const useStyles = makeStyles((theme: Theme) => {
|
||||
// const isMobile = useMobile()
|
||||
|
|
@ -98,12 +85,7 @@ const useStyles = makeStyles((theme: Theme) => {
|
|||
}})},
|
||||
);
|
||||
|
||||
interface Props {
|
||||
title: string | JSX.Element;
|
||||
}
|
||||
|
||||
export default function Users(props: Props) {
|
||||
const { title } = props;
|
||||
export default function Users() {
|
||||
const classes = useStyles();
|
||||
const userAPI = useUserAPI();
|
||||
// let tableRef: any = React.createRef();
|
||||
|
|
@ -118,13 +100,6 @@ export default function Users(props: Props) {
|
|||
const [selectedFeature, setSelectedFeature] = useState<Option | undefined>();
|
||||
const [selectedUser, setSelectedUser] = useState(User.create());
|
||||
const [searchText, setSearchText] = useState("");
|
||||
const TitleMap = new Map([
|
||||
["Uploaded", "uploaded"],
|
||||
["Version", "version"],
|
||||
["Platform", "platform"],
|
||||
["Channel", "channel"],
|
||||
["Size", "size"]
|
||||
]);
|
||||
const actions = [
|
||||
"provision",
|
||||
"upload-firmware",
|
||||
|
|
@ -153,6 +128,32 @@ export default function Users(props: Props) {
|
|||
"installer"
|
||||
].sort();
|
||||
|
||||
const [rows, setRows] = useState<User[]>([])
|
||||
|
||||
const loadUsers = () => {
|
||||
setIsLoading(true)
|
||||
userAPI.listUsers(pageSize, page*pageSize, "desc", "name", searchText).then(resp => {
|
||||
let r: User[] = []
|
||||
resp.users.forEach((user, _index) => {
|
||||
r.push(user)
|
||||
})
|
||||
setRows(r)
|
||||
setTotal(resp.total)
|
||||
}).then(() => {
|
||||
setIsLoading(false);
|
||||
})
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
loadUsers();
|
||||
}, [page, pageSize, searchText]); // 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 allow = () => {
|
||||
const action = selectedAction ? selectedAction.label : "";
|
||||
if (selectedUser.id() === "" || action === "") return;
|
||||
|
|
@ -215,6 +216,64 @@ export default function Users(props: Props) {
|
|||
});
|
||||
};
|
||||
|
||||
const flagChip = (flag: any) => {
|
||||
return (
|
||||
<Grid2 key={flag} size={{xs: 12}} >
|
||||
<Chip size="small" label={flag} color={"default"} />
|
||||
</Grid2>
|
||||
);
|
||||
};
|
||||
|
||||
const columns = (): Column<User>[] => {
|
||||
return [
|
||||
{
|
||||
title: "Profile",
|
||||
render: 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>
|
||||
);
|
||||
}
|
||||
},
|
||||
{
|
||||
title: "Actions",
|
||||
cellStyle: { verticalAlign: "top" },
|
||||
render: user => {
|
||||
const actions = user.settings.actions;
|
||||
return (
|
||||
<Grid2 container spacing={1} className={classes.chipContainer}>
|
||||
{actions.slice(0, 2).map(action => flagChip(action))}
|
||||
{actions.length > 3 && flagChip("+" + (actions.length - 3) + " more")}
|
||||
</Grid2>
|
||||
);
|
||||
}
|
||||
},
|
||||
{
|
||||
title: "Features",
|
||||
cellStyle: { verticalAlign: "top" },
|
||||
render: user => {
|
||||
const features = user.settings.features;
|
||||
return (
|
||||
<Grid2 container spacing={1} className={classes.chipContainer}>
|
||||
{features.slice(0, 2).map(feature => flagChip(feature))}
|
||||
{features.length > 3 && flagChip("+" + (features.length - 3) + " more")}
|
||||
</Grid2>
|
||||
);
|
||||
}
|
||||
}
|
||||
];
|
||||
};
|
||||
|
||||
const details = (user: User) => {
|
||||
const item = (text: string, type: "action" | "feature") => (
|
||||
<ListItem key={text}>
|
||||
|
|
@ -232,8 +291,6 @@ export default function Users(props: Props) {
|
|||
);
|
||||
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>
|
||||
|
|
@ -269,132 +326,24 @@ export default function Users(props: Props) {
|
|||
);
|
||||
};
|
||||
|
||||
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 = () => {
|
||||
setIsLoading(true)
|
||||
userAPI.listUsers(pageSize, page*pageSize, "desc", "name", searchText).then(resp => {
|
||||
let r: User[] = []
|
||||
resp.users.forEach((user, _index) => {
|
||||
r.push(user)
|
||||
})
|
||||
setRows(r)
|
||||
setTotal(resp.total)
|
||||
}).then(() => {
|
||||
setIsLoading(false);
|
||||
})
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
loadUsers();
|
||||
}, [page, pageSize, searchText]); // 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(isLoading)
|
||||
}, [isLoading])
|
||||
|
||||
|
||||
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>
|
||||
<ResponsiveTable<User>
|
||||
title="Users"
|
||||
rows={rows}
|
||||
columns={columns()}
|
||||
total={total}
|
||||
page={page}
|
||||
pageSize={pageSize}
|
||||
setPage={setPage}
|
||||
handleRowsPerPageChange={handleRowsPerPageChange}
|
||||
setSearchText={setSearchText}
|
||||
isLoading={isLoading}
|
||||
renderGutter={details}
|
||||
// multiGutter
|
||||
/>
|
||||
<ResponsiveDialog
|
||||
maxWidth="sm"
|
||||
fullWidth
|
||||
|
|
@ -442,7 +391,6 @@ export default function Users(props: Props) {
|
|||
<SearchSelect
|
||||
label="Select a feature to enable"
|
||||
selected={selectedFeature}
|
||||
// options={features}
|
||||
options={features
|
||||
.filter(a => !selectedUser.settings.features.includes(a))
|
||||
.map(a => {
|
||||
|
|
@ -467,19 +415,6 @@ export default function Users(props: Props) {
|
|||
</Button>
|
||||
</DialogActions>
|
||||
</ResponsiveDialog>
|
||||
<ResponsiveTable<User>
|
||||
// title="Users"
|
||||
rows={rows}
|
||||
total={total}
|
||||
page={page}
|
||||
pageSize={pageSize}
|
||||
setPage={setPage}
|
||||
handleRowsPerPageChange={handleRowsPerPageChange}
|
||||
setSearchText={setSearchText}
|
||||
isLoading={isLoading}
|
||||
renderGutter={details}
|
||||
// multiGutter
|
||||
/>
|
||||
</PageContainer>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue