responsive table now has optional mobile render prop; users now has functional mobile view

This commit is contained in:
Carter 2024-11-26 11:51:11 -06:00
parent 6234f13eb0
commit 3f96724422
5 changed files with 238 additions and 8 deletions

View file

@ -1,9 +1,10 @@
import { Box, CircularProgress, Collapse, darken, Grid2, IconButton, InputAdornment, Paper, SxProps, Table, TableBody, TableCell, TableContainer, TableHead, TablePagination, TableRow, TextField, Theme, Typography } from "@mui/material";
import { Box, Button, Card, CircularProgress, Collapse, darken, Divider, Grid2, IconButton, InputAdornment, List, ListItem, Paper, SxProps, Table, TableBody, TableCell, TableContainer, TableHead, TablePagination, TableRow, TextField, Theme, Typography } from "@mui/material";
import { makeStyles } from "@mui/styles";
import { ChevronRight, Close, Search } from "@mui/icons-material"
import { useEffect, useState } from "react";
import React from "react";
import { useMobile } from "hooks";
import { render } from "react-dom";
const useStyles = makeStyles((theme: Theme) => {
// const isMobile = useMobile()
@ -29,13 +30,22 @@ const useStyles = makeStyles((theme: Theme) => {
border: "none",
textAlign: "right",
},
mobileTitleBox: {
margin: theme.spacing(1),
marginBottom: theme.spacing(1.5),
},
card: {
// padding: theme.spacing(1),
margin: theme.spacing(1),
}
})},
);
export interface Column<T> {
title: string | JSX.Element;
cellStyle?: SxProps<Theme> | undefined
render?: (row: T) => JSX.Element;
cellStyle?: SxProps<Theme>;
render: (row: T) => JSX.Element;
}
interface Props<T> {
@ -53,6 +63,7 @@ interface Props<T> {
multiGutter?: boolean;
rowsPerPageOptions?: number[];
noDataMessage?: string;
renderMobile?: (row: T) => JSX.Element;
}
export default function ResponsiveTable<T extends Object>(props: Props<T>) {
@ -71,6 +82,7 @@ export default function ResponsiveTable<T extends Object>(props: Props<T>) {
columns,
rowsPerPageOptions,
noDataMessage,
renderMobile,
} = props
const classes = useStyles();
@ -125,6 +137,7 @@ export default function ResponsiveTable<T extends Object>(props: Props<T>) {
const searchBar = () => {
return (
<TextField
fullWidth
variant="standard"
placeholder="Search..."
className={classes.title}
@ -149,10 +162,116 @@ export default function ResponsiveTable<T extends Object>(props: Props<T>) {
)
}
const renderTitle = () => {
if (typeof(title) === "string" ) return (
<Typography variant="h5" className={classes.title}>
{title}
</Typography>
)
if (title) return (
<Box className={classes.title}>
title
</Box>
)
return null
}
function capitalize(val: any) {
return String(val).charAt(0).toUpperCase() + String(val).slice(1);
}
const renderMobileRow = (row: T) => {
if (renderMobile) return (
renderMobile(row)
)
if (columns) {
return (
<Grid2 container direction={"row"} spacing={2} padding={2}>
{columns.map((column, index) => {
return (
<React.Fragment key={"mobile-row-column-"+index}>
<Grid2 size={{ xs: 3 }} alignContent={"center"}>
<Typography fontWeight={"bold"} >
{column.title+": "}
</Typography>
</Grid2>
<Grid2 size={{ xs: 9 }} alignContent={"center"}>
{column.render(row)}
</Grid2>
</React.Fragment>
)
})}
</Grid2>
)
}
return (
Object.keys(row).map((key, index) => {
let value = row[key as keyof typeof row]
return (
<Grid2 container direction={"row"} key={"mobile-list-"+index} spacing={1} padding={1}>
<Grid2 size={{ xs: 4 }} alignContent={"center"} >
<Typography fontWeight={"bold"} >
{capitalize(key)+": "}
</Typography>
</Grid2>
<Grid2 size={{ xs: 8 }}>
<Typography>
{value+""}
</Typography>
</Grid2>
</Grid2>
)
})
)
}
if (isMobile) return (
<Typography>
Hello!
</Typography>
<Box>
<Box className={classes.mobileTitleBox}>
{renderTitle()}
{setSearchText && searchBar()}
</Box>
{rows.map((row, index) => {
return (
<Card className={classes.card} key={"mobile-card-"+index}>
{renderMobileRow(row)}
{renderGutter &&
<>
<Divider/>
<Collapse className={classes.gutter} in={openGutters.includes(index)} >
{renderGutter(row)}
</Collapse>
{openGutters.includes(index) && <Divider/>}
<IconButton
onClick={() => handleCollapse(index)}
sx={{
transform: openGutters.includes(index) ?
'rotate(-90deg)' : 'rotate(90deg)',
transition: 'transform 0.3s ease',
width: "100%",
}}
>
<ChevronRight/>
</IconButton>
</>
}
</Card>
)
})}
{rows.length < 1 &&
(!isLoading ?
<Typography sx={{ textAlign: "center" }} color="textSecondary">
{ noDataMessage ? noDataMessage : "No data."}
</Typography>
:
<Box sx={{ width: "100%", textAlign: "center" }} >
<CircularProgress/>
</Box>
)
}
</Box>
)
return (
@ -222,6 +341,7 @@ export default function ResponsiveTable<T extends Object>(props: Props<T>) {
transform: openGutters.includes(index) ?
'rotate(90deg)' : 'rotate(0deg)',
transition: 'transform 0.3s ease',
marginRight: -1
}}
onClick={() => handleCollapse(index)}>
<ChevronRight/>

View file

@ -1,5 +1,6 @@
import {
Avatar,
Box,
Button,
Chip,
darken,
@ -326,9 +327,28 @@ export default function Users() {
);
};
const renderMobile = (user: User) => {
const avatarURL = user.settings.avatar;
const avatar = avatarURL ? (
<Avatar alt={user.name()} src={avatarURL} />
) : (
<Avatar alt={user.name()}>
<Face />
</Avatar>
);
return (
<Box sx={{padding: 1}}>
<ListItem>
<ListItemAvatar>{avatar}</ListItemAvatar>
<ListItemText primary={user.name()} secondary={user.settings.email} />
</ListItem>
</Box>
)
}
return (
<PageContainer>
<ResponsiveTable<User>
<ResponsiveTable<User>
title="Users"
rows={rows}
columns={columns()}
@ -340,6 +360,7 @@ export default function Users() {
setSearchText={setSearchText}
isLoading={isLoading}
renderGutter={details}
renderMobile={renderMobile}
// multiGutter
/>
<ResponsiveDialog

View file

@ -509,7 +509,8 @@ export default function ShareObject(props: Props) {
// inputRef={props => <TextField {...props} helperText="" />}
label="Expiration Date"
value={newLinkExpiration}
onChange={date => setNewLinkExpiration(date as Moment)}
// onChange={(date: moment.Moment) => setNewLinkExpiration(date as Moment)}
onChange={(value) => setNewLinkExpiration(value as Moment)}
disablePast
/>
<FormControlLabel