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/material": "^6.1.6",
|
||||
"@mui/styles": "^6.1.6",
|
||||
"@mui/x-date-pickers": "^7.22.3",
|
||||
"@sentry/react": "^8.38.0",
|
||||
"@types/classnames": "^2.3.0",
|
||||
"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": {
|
||||
"version": "2.1.5",
|
||||
"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/material": "^6.1.6",
|
||||
"@mui/styles": "^6.1.6",
|
||||
"@mui/x-date-pickers": "^7.22.3",
|
||||
"@sentry/react": "^8.38.0",
|
||||
"@types/classnames": "^2.3.0",
|
||||
"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 { 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/>
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue