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:
Carter 2024-11-25 18:42:56 -06:00
parent dd522732db
commit 8265914627
2 changed files with 217 additions and 6 deletions

View 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>
)
}

View file

@ -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>
); );
} }