responsive table added and partly implemented. Has optional gutter component for rendering a subcomponent on the press of a button
This commit is contained in:
parent
dd522732db
commit
8265914627
2 changed files with 217 additions and 6 deletions
194
src/common/ResponsiveTable.tsx
Normal file
194
src/common/ResponsiveTable.tsx
Normal file
|
|
@ -0,0 +1,194 @@
|
||||||
|
import { Box, CircularProgress, Collapse, darken, IconButton, InputAdornment, Paper, 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";
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
|
const useStyles = makeStyles((theme: Theme) => {
|
||||||
|
// const isMobile = useMobile()
|
||||||
|
return ({
|
||||||
|
gutter: {
|
||||||
|
backgroundColor: darken(theme.palette.background.paper, 0.03),
|
||||||
|
border: "none"
|
||||||
|
},
|
||||||
|
tableContainer: {
|
||||||
|
margin: theme.spacing(1),
|
||||||
|
[theme.breakpoints.up("sm")]: {
|
||||||
|
margin: theme.spacing(2)
|
||||||
|
},
|
||||||
|
width: "auto"
|
||||||
|
},
|
||||||
|
title: {
|
||||||
|
padding: theme.spacing(1)
|
||||||
|
},
|
||||||
|
titleContainer: {
|
||||||
|
border: "none"
|
||||||
|
},
|
||||||
|
searchContainer: {
|
||||||
|
border: "none",
|
||||||
|
textAlign: "right",
|
||||||
|
},
|
||||||
|
addIcon: {
|
||||||
|
color: "var(--status-ok)"
|
||||||
|
}
|
||||||
|
})},
|
||||||
|
);
|
||||||
|
|
||||||
|
interface Props<T> {
|
||||||
|
rows: T[],
|
||||||
|
columns?: any,
|
||||||
|
title?: string;
|
||||||
|
total: number,
|
||||||
|
pageSize: number,
|
||||||
|
page: number,
|
||||||
|
setPage: (value: React.SetStateAction<number>) => void,
|
||||||
|
handleRowsPerPageChange: (event: any) => void,
|
||||||
|
renderGutter?: (row: T) => JSX.Element,
|
||||||
|
setSearchText?: React.Dispatch<React.SetStateAction<string>>,
|
||||||
|
isLoading?: boolean;
|
||||||
|
multiGutter?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function ResponsiveTable<T>(props: Props<T>) {
|
||||||
|
const {
|
||||||
|
rows,
|
||||||
|
total,
|
||||||
|
page,
|
||||||
|
pageSize,
|
||||||
|
setPage,
|
||||||
|
handleRowsPerPageChange,
|
||||||
|
title,
|
||||||
|
renderGutter,
|
||||||
|
setSearchText,
|
||||||
|
isLoading,
|
||||||
|
multiGutter
|
||||||
|
} = props
|
||||||
|
const classes = useStyles();
|
||||||
|
|
||||||
|
const [inputSearchText, setInputSearchText] = useState("");
|
||||||
|
const [openGutters, setOpenGutters] = useState<number[]>([])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!setSearchText) return;
|
||||||
|
if (inputSearchText === undefined) return;
|
||||||
|
const handler = setTimeout(() => {
|
||||||
|
setSearchText(inputSearchText);
|
||||||
|
}, 750); // Delay of 750ms
|
||||||
|
|
||||||
|
return () => clearTimeout(handler); // Cleanup on change
|
||||||
|
}, [inputSearchText]);
|
||||||
|
|
||||||
|
const handleCollapse = (index: number) => {
|
||||||
|
if (multiGutter) {
|
||||||
|
if (openGutters.includes(index)) {
|
||||||
|
setOpenGutters(openGutters => openGutters.filter(num => num !== index));
|
||||||
|
} else {
|
||||||
|
setOpenGutters(openGutters => [...openGutters, index]);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (openGutters.includes(index)) {
|
||||||
|
setOpenGutters([])
|
||||||
|
} else {
|
||||||
|
setOpenGutters([index])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handlePageChange = (page: number) => {
|
||||||
|
setOpenGutters([])
|
||||||
|
setPage(page)
|
||||||
|
}
|
||||||
|
|
||||||
|
return(
|
||||||
|
<TableContainer className={classes.tableContainer} component={Paper}>
|
||||||
|
<Table>
|
||||||
|
<TableHead>
|
||||||
|
<TableRow>
|
||||||
|
{title && <TableCell colSpan={6} className={classes.titleContainer}>
|
||||||
|
{typeof(title) === "string" ?
|
||||||
|
<Typography variant="h5" className={classes.title}>
|
||||||
|
{title}
|
||||||
|
</Typography>
|
||||||
|
:
|
||||||
|
<Box className={classes.title}>
|
||||||
|
title
|
||||||
|
</Box>
|
||||||
|
}
|
||||||
|
</TableCell>}
|
||||||
|
{ setSearchText &&
|
||||||
|
<TableCell colSpan={6} className={classes.searchContainer}>
|
||||||
|
<TextField
|
||||||
|
variant="standard"
|
||||||
|
className={classes.title}
|
||||||
|
value={inputSearchText}
|
||||||
|
onChange={e => setInputSearchText(e.currentTarget.value)}
|
||||||
|
InputProps={{
|
||||||
|
startAdornment: (
|
||||||
|
<InputAdornment position="start">
|
||||||
|
<Search />
|
||||||
|
</InputAdornment>
|
||||||
|
),
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</TableCell>
|
||||||
|
}
|
||||||
|
</TableRow>
|
||||||
|
<TableRow>
|
||||||
|
{ renderGutter && <TableCell></TableCell>}
|
||||||
|
<TableCell>Profile</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
</TableHead>
|
||||||
|
<TableBody>
|
||||||
|
{ isLoading ?
|
||||||
|
<TableRow>
|
||||||
|
{ isLoading &&
|
||||||
|
<TableCell colSpan={6} sx={{ textAlign: "center"}}>
|
||||||
|
<CircularProgress />
|
||||||
|
</TableCell>
|
||||||
|
}
|
||||||
|
</TableRow>
|
||||||
|
:
|
||||||
|
rows.map((row, index) => {
|
||||||
|
return (
|
||||||
|
<React.Fragment key={"user-row-"+index}>
|
||||||
|
<TableRow>
|
||||||
|
{renderGutter &&
|
||||||
|
<TableCell width={1}>
|
||||||
|
<IconButton sx={{
|
||||||
|
transform: openGutters.includes(index) ?
|
||||||
|
'rotate(90deg)' : 'rotate(0deg)',
|
||||||
|
transition: 'transform 0.3s ease',
|
||||||
|
}}
|
||||||
|
onClick={() => handleCollapse(index)}>
|
||||||
|
<ChevronRight/>
|
||||||
|
</IconButton>
|
||||||
|
</TableCell>
|
||||||
|
}
|
||||||
|
</TableRow>
|
||||||
|
{renderGutter &&
|
||||||
|
<TableRow className={classes.gutter}>
|
||||||
|
<TableCell colSpan={6} style={{padding:0, margin: 0, border: "none"}}>
|
||||||
|
<Collapse in={openGutters.includes(index)} timeout="auto" unmountOnExit>
|
||||||
|
{renderGutter(row)}
|
||||||
|
</Collapse>
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
}
|
||||||
|
</React.Fragment>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</TableBody>
|
||||||
|
</Table>
|
||||||
|
<TablePagination
|
||||||
|
rowsPerPageOptions={[5, 10, 20]}
|
||||||
|
component="div"
|
||||||
|
count={total}
|
||||||
|
rowsPerPage={pageSize}
|
||||||
|
page={page}
|
||||||
|
onPageChange={(_event, page) => handlePageChange(page)}
|
||||||
|
onRowsPerPageChange={handleRowsPerPageChange}
|
||||||
|
/>
|
||||||
|
</TableContainer>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
@ -42,6 +42,7 @@ import { makeStyles } from "@mui/styles";
|
||||||
import { pond } from "protobuf-ts/pond";
|
import { pond } from "protobuf-ts/pond";
|
||||||
import { getSecondaryColour, getSignatureAccentColour } from "services/whiteLabel";
|
import { getSecondaryColour, getSignatureAccentColour } from "services/whiteLabel";
|
||||||
import { Add, ChevronRight, Face, Remove } from "@mui/icons-material";
|
import { Add, ChevronRight, Face, Remove } from "@mui/icons-material";
|
||||||
|
import ResponsiveTable from "common/ResponsiveTable";
|
||||||
|
|
||||||
const useStyles = makeStyles((theme: Theme) => {
|
const useStyles = makeStyles((theme: Theme) => {
|
||||||
// const isMobile = useMobile()
|
// const isMobile = useMobile()
|
||||||
|
|
@ -108,6 +109,7 @@ export default function Users(props: Props) {
|
||||||
// let tableRef: any = React.createRef();
|
// let tableRef: any = React.createRef();
|
||||||
const { error, success } = useSnackbar();
|
const { error, success } = useSnackbar();
|
||||||
const [pageSize, setPageSize] = useState(5);
|
const [pageSize, setPageSize] = useState(5);
|
||||||
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
const [page, setPage] = useState(0);
|
const [page, setPage] = useState(0);
|
||||||
const [total, setTotal] = useState(0);
|
const [total, setTotal] = useState(0);
|
||||||
const [showAllowAction, setShowAllowAction] = useState(false);
|
const [showAllowAction, setShowAllowAction] = useState(false);
|
||||||
|
|
@ -115,6 +117,7 @@ export default function Users(props: Props) {
|
||||||
const [showEnableFeature, setShowEnableFeature] = useState(false);
|
const [showEnableFeature, setShowEnableFeature] = useState(false);
|
||||||
const [selectedFeature, setSelectedFeature] = useState<Option | undefined>();
|
const [selectedFeature, setSelectedFeature] = useState<Option | undefined>();
|
||||||
const [selectedUser, setSelectedUser] = useState(User.create());
|
const [selectedUser, setSelectedUser] = useState(User.create());
|
||||||
|
const [searchText, setSearchText] = useState("");
|
||||||
const TitleMap = new Map([
|
const TitleMap = new Map([
|
||||||
["Uploaded", "uploaded"],
|
["Uploaded", "uploaded"],
|
||||||
["Version", "version"],
|
["Version", "version"],
|
||||||
|
|
@ -295,19 +298,22 @@ export default function Users(props: Props) {
|
||||||
const [rows, setRows] = useState<User[]>([])
|
const [rows, setRows] = useState<User[]>([])
|
||||||
|
|
||||||
const loadUsers = () => {
|
const loadUsers = () => {
|
||||||
userAPI.listUsers(pageSize, page*pageSize, "desc", "name").then(resp => {
|
setIsLoading(true)
|
||||||
|
userAPI.listUsers(pageSize, page*pageSize, "desc", "name", searchText).then(resp => {
|
||||||
let r: User[] = []
|
let r: User[] = []
|
||||||
resp.users.forEach((user, _index) => {
|
resp.users.forEach((user, _index) => {
|
||||||
r.push(user)
|
r.push(user)
|
||||||
})
|
})
|
||||||
setRows(r)
|
setRows(r)
|
||||||
setTotal(resp.total)
|
setTotal(resp.total)
|
||||||
|
}).then(() => {
|
||||||
|
setIsLoading(false);
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
loadUsers();
|
loadUsers();
|
||||||
}, [page, pageSize]); // Trigger fetching data when page or pageSize changes
|
}, [page, pageSize, searchText]); // Trigger fetching data when page or pageSize changes
|
||||||
|
|
||||||
const handleRowsPerPageChange = (event: any) => {
|
const handleRowsPerPageChange = (event: any) => {
|
||||||
const newRowsPerPage = parseInt(event.target.value, 10); // Get selected value
|
const newRowsPerPage = parseInt(event.target.value, 10); // Get selected value
|
||||||
|
|
@ -324,10 +330,8 @@ export default function Users(props: Props) {
|
||||||
}
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// console.log("Open Row: ", openRow);
|
console.log(isLoading)
|
||||||
console.log("Selected User: ", selectedUser);
|
}, [isLoading])
|
||||||
console.log("Selected ID: ", selectedUser.id())
|
|
||||||
}, [selectedUser])
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<PageContainer>
|
<PageContainer>
|
||||||
|
|
@ -463,6 +467,19 @@ export default function Users(props: Props) {
|
||||||
</Button>
|
</Button>
|
||||||
</DialogActions>
|
</DialogActions>
|
||||||
</ResponsiveDialog>
|
</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>
|
</PageContainer>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue