responsive table now has optional mobile render prop; users now has functional mobile view
This commit is contained in:
parent
6234f13eb0
commit
3f96724422
5 changed files with 238 additions and 8 deletions
87
package-lock.json
generated
87
package-lock.json
generated
|
|
@ -14,6 +14,7 @@
|
||||||
"@mui/icons-material": "^6.1.6",
|
"@mui/icons-material": "^6.1.6",
|
||||||
"@mui/material": "^6.1.6",
|
"@mui/material": "^6.1.6",
|
||||||
"@mui/styles": "^6.1.6",
|
"@mui/styles": "^6.1.6",
|
||||||
|
"@mui/x-date-pickers": "^7.22.3",
|
||||||
"@sentry/react": "^8.38.0",
|
"@sentry/react": "^8.38.0",
|
||||||
"@types/classnames": "^2.3.0",
|
"@types/classnames": "^2.3.0",
|
||||||
"axios": "^1.7.7",
|
"axios": "^1.7.7",
|
||||||
|
|
@ -1510,6 +1511,92 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@mui/x-date-pickers": {
|
||||||
|
"version": "7.22.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@mui/x-date-pickers/-/x-date-pickers-7.22.3.tgz",
|
||||||
|
"integrity": "sha512-shNp92IrST5BiVy2f4jbrmRaD32QhyUthjh1Oexvpcn0v6INyuWgxfodoTi5ZCnE5Ue5UVFSs4R9Xre0UbJ5DQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/runtime": "^7.25.7",
|
||||||
|
"@mui/utils": "^5.16.6 || ^6.0.0",
|
||||||
|
"@mui/x-internals": "7.21.0",
|
||||||
|
"@types/react-transition-group": "^4.4.11",
|
||||||
|
"clsx": "^2.1.1",
|
||||||
|
"prop-types": "^15.8.1",
|
||||||
|
"react-transition-group": "^4.4.5"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/mui-org"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@emotion/react": "^11.9.0",
|
||||||
|
"@emotion/styled": "^11.8.1",
|
||||||
|
"@mui/material": "^5.15.14 || ^6.0.0",
|
||||||
|
"@mui/system": "^5.15.14 || ^6.0.0",
|
||||||
|
"date-fns": "^2.25.0 || ^3.2.0 || ^4.0.0",
|
||||||
|
"date-fns-jalali": "^2.13.0-0 || ^3.2.0-0",
|
||||||
|
"dayjs": "^1.10.7",
|
||||||
|
"luxon": "^3.0.2",
|
||||||
|
"moment": "^2.29.4",
|
||||||
|
"moment-hijri": "^2.1.2 || ^3.0.0",
|
||||||
|
"moment-jalaali": "^0.7.4 || ^0.8.0 || ^0.9.0 || ^0.10.0",
|
||||||
|
"react": "^17.0.0 || ^18.0.0",
|
||||||
|
"react-dom": "^17.0.0 || ^18.0.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@emotion/react": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"@emotion/styled": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"date-fns": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"date-fns-jalali": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"dayjs": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"luxon": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"moment": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"moment-hijri": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"moment-jalaali": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@mui/x-internals": {
|
||||||
|
"version": "7.21.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@mui/x-internals/-/x-internals-7.21.0.tgz",
|
||||||
|
"integrity": "sha512-94YNyZ0BhK5Z+Tkr90RKf47IVCW8R/1MvdUhh6MCQg6sZa74jsX+x+gEZ4kzuCqOsuyTyxikeQ8vVuCIQiP7UQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/runtime": "^7.25.7",
|
||||||
|
"@mui/utils": "^5.16.6 || ^6.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/mui-org"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": "^17.0.0 || ^18.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@nodelib/fs.scandir": {
|
"node_modules/@nodelib/fs.scandir": {
|
||||||
"version": "2.1.5",
|
"version": "2.1.5",
|
||||||
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
|
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,7 @@
|
||||||
"@mui/icons-material": "^6.1.6",
|
"@mui/icons-material": "^6.1.6",
|
||||||
"@mui/material": "^6.1.6",
|
"@mui/material": "^6.1.6",
|
||||||
"@mui/styles": "^6.1.6",
|
"@mui/styles": "^6.1.6",
|
||||||
|
"@mui/x-date-pickers": "^7.22.3",
|
||||||
"@sentry/react": "^8.38.0",
|
"@sentry/react": "^8.38.0",
|
||||||
"@types/classnames": "^2.3.0",
|
"@types/classnames": "^2.3.0",
|
||||||
"axios": "^1.7.7",
|
"axios": "^1.7.7",
|
||||||
|
|
|
||||||
|
|
@ -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 { makeStyles } from "@mui/styles";
|
||||||
import { ChevronRight, Close, Search } from "@mui/icons-material"
|
import { ChevronRight, Close, Search } from "@mui/icons-material"
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { useMobile } from "hooks";
|
import { useMobile } from "hooks";
|
||||||
|
import { render } from "react-dom";
|
||||||
|
|
||||||
const useStyles = makeStyles((theme: Theme) => {
|
const useStyles = makeStyles((theme: Theme) => {
|
||||||
// const isMobile = useMobile()
|
// const isMobile = useMobile()
|
||||||
|
|
@ -29,13 +30,22 @@ const useStyles = makeStyles((theme: Theme) => {
|
||||||
border: "none",
|
border: "none",
|
||||||
textAlign: "right",
|
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> {
|
export interface Column<T> {
|
||||||
title: string | JSX.Element;
|
title: string | JSX.Element;
|
||||||
cellStyle?: SxProps<Theme> | undefined
|
cellStyle?: SxProps<Theme>;
|
||||||
render?: (row: T) => JSX.Element;
|
render: (row: T) => JSX.Element;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Props<T> {
|
interface Props<T> {
|
||||||
|
|
@ -53,6 +63,7 @@ interface Props<T> {
|
||||||
multiGutter?: boolean;
|
multiGutter?: boolean;
|
||||||
rowsPerPageOptions?: number[];
|
rowsPerPageOptions?: number[];
|
||||||
noDataMessage?: string;
|
noDataMessage?: string;
|
||||||
|
renderMobile?: (row: T) => JSX.Element;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function ResponsiveTable<T extends Object>(props: Props<T>) {
|
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,
|
columns,
|
||||||
rowsPerPageOptions,
|
rowsPerPageOptions,
|
||||||
noDataMessage,
|
noDataMessage,
|
||||||
|
renderMobile,
|
||||||
} = props
|
} = props
|
||||||
const classes = useStyles();
|
const classes = useStyles();
|
||||||
|
|
||||||
|
|
@ -125,6 +137,7 @@ export default function ResponsiveTable<T extends Object>(props: Props<T>) {
|
||||||
const searchBar = () => {
|
const searchBar = () => {
|
||||||
return (
|
return (
|
||||||
<TextField
|
<TextField
|
||||||
|
fullWidth
|
||||||
variant="standard"
|
variant="standard"
|
||||||
placeholder="Search..."
|
placeholder="Search..."
|
||||||
className={classes.title}
|
className={classes.title}
|
||||||
|
|
@ -149,11 +162,117 @@ export default function ResponsiveTable<T extends Object>(props: Props<T>) {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isMobile) return (
|
const renderTitle = () => {
|
||||||
<Typography>
|
if (typeof(title) === "string" ) return (
|
||||||
Hello!
|
<Typography variant="h5" className={classes.title}>
|
||||||
|
{title}
|
||||||
</Typography>
|
</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 (
|
||||||
|
<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 (
|
return (
|
||||||
<TableContainer className={classes.tableContainer} component={Paper}>
|
<TableContainer className={classes.tableContainer} component={Paper}>
|
||||||
|
|
@ -222,6 +341,7 @@ export default function ResponsiveTable<T extends Object>(props: Props<T>) {
|
||||||
transform: openGutters.includes(index) ?
|
transform: openGutters.includes(index) ?
|
||||||
'rotate(90deg)' : 'rotate(0deg)',
|
'rotate(90deg)' : 'rotate(0deg)',
|
||||||
transition: 'transform 0.3s ease',
|
transition: 'transform 0.3s ease',
|
||||||
|
marginRight: -1
|
||||||
}}
|
}}
|
||||||
onClick={() => handleCollapse(index)}>
|
onClick={() => handleCollapse(index)}>
|
||||||
<ChevronRight/>
|
<ChevronRight/>
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
import {
|
import {
|
||||||
Avatar,
|
Avatar,
|
||||||
|
Box,
|
||||||
Button,
|
Button,
|
||||||
Chip,
|
Chip,
|
||||||
darken,
|
darken,
|
||||||
|
|
@ -326,6 +327,25 @@ 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 (
|
return (
|
||||||
<PageContainer>
|
<PageContainer>
|
||||||
<ResponsiveTable<User>
|
<ResponsiveTable<User>
|
||||||
|
|
@ -340,6 +360,7 @@ export default function Users() {
|
||||||
setSearchText={setSearchText}
|
setSearchText={setSearchText}
|
||||||
isLoading={isLoading}
|
isLoading={isLoading}
|
||||||
renderGutter={details}
|
renderGutter={details}
|
||||||
|
renderMobile={renderMobile}
|
||||||
// multiGutter
|
// multiGutter
|
||||||
/>
|
/>
|
||||||
<ResponsiveDialog
|
<ResponsiveDialog
|
||||||
|
|
|
||||||
|
|
@ -509,7 +509,8 @@ export default function ShareObject(props: Props) {
|
||||||
// inputRef={props => <TextField {...props} helperText="" />}
|
// inputRef={props => <TextField {...props} helperText="" />}
|
||||||
label="Expiration Date"
|
label="Expiration Date"
|
||||||
value={newLinkExpiration}
|
value={newLinkExpiration}
|
||||||
onChange={date => setNewLinkExpiration(date as Moment)}
|
// onChange={(date: moment.Moment) => setNewLinkExpiration(date as Moment)}
|
||||||
|
onChange={(value) => setNewLinkExpiration(value as Moment)}
|
||||||
disablePast
|
disablePast
|
||||||
/>
|
/>
|
||||||
<FormControlLabel
|
<FormControlLabel
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue