init
This commit is contained in:
26
src/sections/user/table-empty-rows.tsx
Normal file
26
src/sections/user/table-empty-rows.tsx
Normal file
@@ -0,0 +1,26 @@
|
||||
import type { TableRowProps } from '@mui/material/TableRow';
|
||||
|
||||
import TableRow from '@mui/material/TableRow';
|
||||
import TableCell from '@mui/material/TableCell';
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
type TableEmptyRowsProps = TableRowProps & {
|
||||
emptyRows: number;
|
||||
height?: number;
|
||||
};
|
||||
|
||||
export function TableEmptyRows({ emptyRows, height, sx, ...other }: TableEmptyRowsProps) {
|
||||
if (!emptyRows) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<TableRow
|
||||
sx={[height && { height: height * emptyRows }, ...(Array.isArray(sx) ? sx : [sx])]}
|
||||
{...other}
|
||||
>
|
||||
<TableCell colSpan={9} />
|
||||
</TableRow>
|
||||
);
|
||||
}
|
||||
32
src/sections/user/table-no-data.tsx
Normal file
32
src/sections/user/table-no-data.tsx
Normal file
@@ -0,0 +1,32 @@
|
||||
import type { TableRowProps } from '@mui/material/TableRow';
|
||||
|
||||
import Box from '@mui/material/Box';
|
||||
import TableRow from '@mui/material/TableRow';
|
||||
import TableCell from '@mui/material/TableCell';
|
||||
import Typography from '@mui/material/Typography';
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
type TableNoDataProps = TableRowProps & {
|
||||
searchQuery: string;
|
||||
};
|
||||
|
||||
export function TableNoData({ searchQuery, ...other }: TableNoDataProps) {
|
||||
return (
|
||||
<TableRow {...other}>
|
||||
<TableCell align="center" colSpan={7}>
|
||||
<Box sx={{ py: 15, textAlign: 'center' }}>
|
||||
<Typography variant="h6" sx={{ mb: 1 }}>
|
||||
Not found
|
||||
</Typography>
|
||||
|
||||
<Typography variant="body2">
|
||||
No results found for
|
||||
<strong>"{searchQuery}"</strong>.
|
||||
<br /> Try checking for typos or using complete words.
|
||||
</Typography>
|
||||
</Box>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
);
|
||||
}
|
||||
69
src/sections/user/user-table-head.tsx
Normal file
69
src/sections/user/user-table-head.tsx
Normal file
@@ -0,0 +1,69 @@
|
||||
import Box from '@mui/material/Box';
|
||||
import TableRow from '@mui/material/TableRow';
|
||||
import Checkbox from '@mui/material/Checkbox';
|
||||
import TableHead from '@mui/material/TableHead';
|
||||
import TableCell from '@mui/material/TableCell';
|
||||
import TableSortLabel from '@mui/material/TableSortLabel';
|
||||
|
||||
import { visuallyHidden } from './utils';
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
type UserTableHeadProps = {
|
||||
orderBy: string;
|
||||
rowCount: number;
|
||||
numSelected: number;
|
||||
order: 'asc' | 'desc';
|
||||
onSort: (id: string) => void;
|
||||
headLabel: Record<string, any>[];
|
||||
onSelectAllRows: (checked: boolean) => void;
|
||||
};
|
||||
|
||||
export function UserTableHead({
|
||||
order,
|
||||
onSort,
|
||||
orderBy,
|
||||
rowCount,
|
||||
headLabel,
|
||||
numSelected,
|
||||
onSelectAllRows,
|
||||
}: UserTableHeadProps) {
|
||||
return (
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
<TableCell padding="checkbox">
|
||||
<Checkbox
|
||||
indeterminate={numSelected > 0 && numSelected < rowCount}
|
||||
checked={rowCount > 0 && numSelected === rowCount}
|
||||
onChange={(event: React.ChangeEvent<HTMLInputElement>) =>
|
||||
onSelectAllRows(event.target.checked)
|
||||
}
|
||||
/>
|
||||
</TableCell>
|
||||
|
||||
{headLabel.map((headCell) => (
|
||||
<TableCell
|
||||
key={headCell.id}
|
||||
align={headCell.align || 'left'}
|
||||
sortDirection={orderBy === headCell.id ? order : false}
|
||||
sx={{ width: headCell.width, minWidth: headCell.minWidth }}
|
||||
>
|
||||
<TableSortLabel
|
||||
hideSortIcon
|
||||
active={orderBy === headCell.id}
|
||||
direction={orderBy === headCell.id ? order : 'asc'}
|
||||
onClick={() => onSort(headCell.id)}
|
||||
>
|
||||
{headCell.label}
|
||||
{orderBy === headCell.id ? (
|
||||
<Box sx={{ ...visuallyHidden }}>
|
||||
{order === 'desc' ? 'sorted descending' : 'sorted ascending'}
|
||||
</Box>
|
||||
) : null}
|
||||
</TableSortLabel>
|
||||
</TableCell>
|
||||
))}
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
);
|
||||
}
|
||||
124
src/sections/user/user-table-row.tsx
Normal file
124
src/sections/user/user-table-row.tsx
Normal file
@@ -0,0 +1,124 @@
|
||||
import { useState, useCallback } from 'react';
|
||||
|
||||
import Box from '@mui/material/Box';
|
||||
import Avatar from '@mui/material/Avatar';
|
||||
import Popover from '@mui/material/Popover';
|
||||
import TableRow from '@mui/material/TableRow';
|
||||
import Checkbox from '@mui/material/Checkbox';
|
||||
import MenuList from '@mui/material/MenuList';
|
||||
import TableCell from '@mui/material/TableCell';
|
||||
import IconButton from '@mui/material/IconButton';
|
||||
import MenuItem, { menuItemClasses } from '@mui/material/MenuItem';
|
||||
|
||||
import { Label } from 'src/components/label';
|
||||
import { Iconify } from 'src/components/iconify';
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
export type UserProps = {
|
||||
id: string;
|
||||
name: string;
|
||||
role: string;
|
||||
status: string;
|
||||
company: string;
|
||||
avatarUrl: string;
|
||||
isVerified: boolean;
|
||||
};
|
||||
|
||||
type UserTableRowProps = {
|
||||
row: UserProps;
|
||||
selected: boolean;
|
||||
onSelectRow: () => void;
|
||||
};
|
||||
|
||||
export function UserTableRow({ row, selected, onSelectRow }: UserTableRowProps) {
|
||||
const [openPopover, setOpenPopover] = useState<HTMLButtonElement | null>(null);
|
||||
|
||||
const handleOpenPopover = useCallback((event: React.MouseEvent<HTMLButtonElement>) => {
|
||||
setOpenPopover(event.currentTarget);
|
||||
}, []);
|
||||
|
||||
const handleClosePopover = useCallback(() => {
|
||||
setOpenPopover(null);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<>
|
||||
<TableRow hover tabIndex={-1} role="checkbox" selected={selected}>
|
||||
<TableCell padding="checkbox">
|
||||
<Checkbox disableRipple checked={selected} onChange={onSelectRow} />
|
||||
</TableCell>
|
||||
|
||||
<TableCell component="th" scope="row">
|
||||
<Box
|
||||
sx={{
|
||||
gap: 2,
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
}}
|
||||
>
|
||||
<Avatar alt={row.name} src={row.avatarUrl} />
|
||||
{row.name}
|
||||
</Box>
|
||||
</TableCell>
|
||||
|
||||
<TableCell>{row.company}</TableCell>
|
||||
|
||||
<TableCell>{row.role}</TableCell>
|
||||
|
||||
<TableCell align="center">
|
||||
{row.isVerified ? (
|
||||
<Iconify width={22} icon="solar:check-circle-bold" sx={{ color: 'success.main' }} />
|
||||
) : (
|
||||
'-'
|
||||
)}
|
||||
</TableCell>
|
||||
|
||||
<TableCell>
|
||||
<Label color={(row.status === 'banned' && 'error') || 'success'}>{row.status}</Label>
|
||||
</TableCell>
|
||||
|
||||
<TableCell align="right">
|
||||
<IconButton onClick={handleOpenPopover}>
|
||||
<Iconify icon="eva:more-vertical-fill" />
|
||||
</IconButton>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
|
||||
<Popover
|
||||
open={!!openPopover}
|
||||
anchorEl={openPopover}
|
||||
onClose={handleClosePopover}
|
||||
anchorOrigin={{ vertical: 'top', horizontal: 'left' }}
|
||||
transformOrigin={{ vertical: 'top', horizontal: 'right' }}
|
||||
>
|
||||
<MenuList
|
||||
disablePadding
|
||||
sx={{
|
||||
p: 0.5,
|
||||
gap: 0.5,
|
||||
width: 140,
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
[`& .${menuItemClasses.root}`]: {
|
||||
px: 1,
|
||||
gap: 2,
|
||||
borderRadius: 0.75,
|
||||
[`&.${menuItemClasses.selected}`]: { bgcolor: 'action.selected' },
|
||||
},
|
||||
}}
|
||||
>
|
||||
<MenuItem onClick={handleClosePopover}>
|
||||
<Iconify icon="solar:pen-bold" />
|
||||
Edit
|
||||
</MenuItem>
|
||||
|
||||
<MenuItem onClick={handleClosePopover} sx={{ color: 'error.main' }}>
|
||||
<Iconify icon="solar:trash-bin-trash-bold" />
|
||||
Delete
|
||||
</MenuItem>
|
||||
</MenuList>
|
||||
</Popover>
|
||||
</>
|
||||
);
|
||||
}
|
||||
66
src/sections/user/user-table-toolbar.tsx
Normal file
66
src/sections/user/user-table-toolbar.tsx
Normal file
@@ -0,0 +1,66 @@
|
||||
import Tooltip from '@mui/material/Tooltip';
|
||||
import Toolbar from '@mui/material/Toolbar';
|
||||
import Typography from '@mui/material/Typography';
|
||||
import IconButton from '@mui/material/IconButton';
|
||||
import OutlinedInput from '@mui/material/OutlinedInput';
|
||||
import InputAdornment from '@mui/material/InputAdornment';
|
||||
|
||||
import { Iconify } from 'src/components/iconify';
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
type UserTableToolbarProps = {
|
||||
numSelected: number;
|
||||
filterName: string;
|
||||
onFilterName: (event: React.ChangeEvent<HTMLInputElement>) => void;
|
||||
};
|
||||
|
||||
export function UserTableToolbar({ numSelected, filterName, onFilterName }: UserTableToolbarProps) {
|
||||
return (
|
||||
<Toolbar
|
||||
sx={{
|
||||
height: 96,
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
p: (theme) => theme.spacing(0, 1, 0, 3),
|
||||
...(numSelected > 0 && {
|
||||
color: 'primary.main',
|
||||
bgcolor: 'primary.lighter',
|
||||
}),
|
||||
}}
|
||||
>
|
||||
{numSelected > 0 ? (
|
||||
<Typography component="div" variant="subtitle1">
|
||||
{numSelected} selected
|
||||
</Typography>
|
||||
) : (
|
||||
<OutlinedInput
|
||||
fullWidth
|
||||
value={filterName}
|
||||
onChange={onFilterName}
|
||||
placeholder="Search user..."
|
||||
startAdornment={
|
||||
<InputAdornment position="start">
|
||||
<Iconify width={20} icon="eva:search-fill" sx={{ color: 'text.disabled' }} />
|
||||
</InputAdornment>
|
||||
}
|
||||
sx={{ maxWidth: 320 }}
|
||||
/>
|
||||
)}
|
||||
|
||||
{numSelected > 0 ? (
|
||||
<Tooltip title="Delete">
|
||||
<IconButton>
|
||||
<Iconify icon="solar:trash-bin-trash-bold" />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
) : (
|
||||
<Tooltip title="Filter list">
|
||||
<IconButton>
|
||||
<Iconify icon="ic:round-filter-list" />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
)}
|
||||
</Toolbar>
|
||||
);
|
||||
}
|
||||
79
src/sections/user/utils.ts
Normal file
79
src/sections/user/utils.ts
Normal file
@@ -0,0 +1,79 @@
|
||||
import type { UserProps } from './user-table-row';
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
export const visuallyHidden = {
|
||||
border: 0,
|
||||
margin: -1,
|
||||
padding: 0,
|
||||
width: '1px',
|
||||
height: '1px',
|
||||
overflow: 'hidden',
|
||||
position: 'absolute',
|
||||
whiteSpace: 'nowrap',
|
||||
clip: 'rect(0 0 0 0)',
|
||||
} as const;
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
export function emptyRows(page: number, rowsPerPage: number, arrayLength: number) {
|
||||
return page ? Math.max(0, (1 + page) * rowsPerPage - arrayLength) : 0;
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
function descendingComparator<T>(a: T, b: T, orderBy: keyof T) {
|
||||
if (b[orderBy] < a[orderBy]) {
|
||||
return -1;
|
||||
}
|
||||
if (b[orderBy] > a[orderBy]) {
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
export function getComparator<Key extends keyof any>(
|
||||
order: 'asc' | 'desc',
|
||||
orderBy: Key
|
||||
): (
|
||||
a: {
|
||||
[key in Key]: number | string;
|
||||
},
|
||||
b: {
|
||||
[key in Key]: number | string;
|
||||
}
|
||||
) => number {
|
||||
return order === 'desc'
|
||||
? (a, b) => descendingComparator(a, b, orderBy)
|
||||
: (a, b) => -descendingComparator(a, b, orderBy);
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
type ApplyFilterProps = {
|
||||
inputData: UserProps[];
|
||||
filterName: string;
|
||||
comparator: (a: any, b: any) => number;
|
||||
};
|
||||
|
||||
export function applyFilter({ inputData, comparator, filterName }: ApplyFilterProps) {
|
||||
const stabilizedThis = inputData.map((el, index) => [el, index] as const);
|
||||
|
||||
stabilizedThis.sort((a, b) => {
|
||||
const order = comparator(a[0], b[0]);
|
||||
if (order !== 0) return order;
|
||||
return a[1] - b[1];
|
||||
});
|
||||
|
||||
inputData = stabilizedThis.map((el) => el[0]);
|
||||
|
||||
if (filterName) {
|
||||
inputData = inputData.filter(
|
||||
(user) => user.name.toLowerCase().indexOf(filterName.toLowerCase()) !== -1
|
||||
);
|
||||
}
|
||||
|
||||
return inputData;
|
||||
}
|
||||
1
src/sections/user/view/index.ts
Normal file
1
src/sections/user/view/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * from './user-view';
|
||||
203
src/sections/user/view/user-view.tsx
Normal file
203
src/sections/user/view/user-view.tsx
Normal file
@@ -0,0 +1,203 @@
|
||||
import { useState, useCallback } from 'react';
|
||||
|
||||
import Box from '@mui/material/Box';
|
||||
import Card from '@mui/material/Card';
|
||||
import Table from '@mui/material/Table';
|
||||
import Button from '@mui/material/Button';
|
||||
import TableBody from '@mui/material/TableBody';
|
||||
import Typography from '@mui/material/Typography';
|
||||
import TableContainer from '@mui/material/TableContainer';
|
||||
import TablePagination from '@mui/material/TablePagination';
|
||||
|
||||
import { _users } from 'src/_mock';
|
||||
import { DashboardContent } from 'src/layouts/dashboard';
|
||||
|
||||
import { Iconify } from 'src/components/iconify';
|
||||
import { Scrollbar } from 'src/components/scrollbar';
|
||||
|
||||
import { TableNoData } from '../table-no-data';
|
||||
import { UserTableRow } from '../user-table-row';
|
||||
import { UserTableHead } from '../user-table-head';
|
||||
import { TableEmptyRows } from '../table-empty-rows';
|
||||
import { UserTableToolbar } from '../user-table-toolbar';
|
||||
import { emptyRows, applyFilter, getComparator } from '../utils';
|
||||
|
||||
import type { UserProps } from '../user-table-row';
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
export function UserView() {
|
||||
const table = useTable();
|
||||
|
||||
const [filterName, setFilterName] = useState('');
|
||||
|
||||
const dataFiltered: UserProps[] = applyFilter({
|
||||
inputData: _users,
|
||||
comparator: getComparator(table.order, table.orderBy),
|
||||
filterName,
|
||||
});
|
||||
|
||||
const notFound = !dataFiltered.length && !!filterName;
|
||||
|
||||
return (
|
||||
<DashboardContent>
|
||||
<Box
|
||||
sx={{
|
||||
mb: 5,
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
}}
|
||||
>
|
||||
<Typography variant="h4" sx={{ flexGrow: 1 }}>
|
||||
Users
|
||||
</Typography>
|
||||
<Button
|
||||
variant="contained"
|
||||
color="inherit"
|
||||
startIcon={<Iconify icon="mingcute:add-line" />}
|
||||
>
|
||||
New user
|
||||
</Button>
|
||||
</Box>
|
||||
|
||||
<Card>
|
||||
<UserTableToolbar
|
||||
numSelected={table.selected.length}
|
||||
filterName={filterName}
|
||||
onFilterName={(event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setFilterName(event.target.value);
|
||||
table.onResetPage();
|
||||
}}
|
||||
/>
|
||||
|
||||
<Scrollbar>
|
||||
<TableContainer sx={{ overflow: 'unset' }}>
|
||||
<Table sx={{ minWidth: 800 }}>
|
||||
<UserTableHead
|
||||
order={table.order}
|
||||
orderBy={table.orderBy}
|
||||
rowCount={_users.length}
|
||||
numSelected={table.selected.length}
|
||||
onSort={table.onSort}
|
||||
onSelectAllRows={(checked) =>
|
||||
table.onSelectAllRows(
|
||||
checked,
|
||||
_users.map((user) => user.id)
|
||||
)
|
||||
}
|
||||
headLabel={[
|
||||
{ id: 'name', label: 'Name' },
|
||||
{ id: 'company', label: 'Company' },
|
||||
{ id: 'role', label: 'Role' },
|
||||
{ id: 'isVerified', label: 'Verified', align: 'center' },
|
||||
{ id: 'status', label: 'Status' },
|
||||
{ id: '' },
|
||||
]}
|
||||
/>
|
||||
<TableBody>
|
||||
{dataFiltered
|
||||
.slice(
|
||||
table.page * table.rowsPerPage,
|
||||
table.page * table.rowsPerPage + table.rowsPerPage
|
||||
)
|
||||
.map((row) => (
|
||||
<UserTableRow
|
||||
key={row.id}
|
||||
row={row}
|
||||
selected={table.selected.includes(row.id)}
|
||||
onSelectRow={() => table.onSelectRow(row.id)}
|
||||
/>
|
||||
))}
|
||||
|
||||
<TableEmptyRows
|
||||
height={68}
|
||||
emptyRows={emptyRows(table.page, table.rowsPerPage, _users.length)}
|
||||
/>
|
||||
|
||||
{notFound && <TableNoData searchQuery={filterName} />}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
</Scrollbar>
|
||||
|
||||
<TablePagination
|
||||
component="div"
|
||||
page={table.page}
|
||||
count={_users.length}
|
||||
rowsPerPage={table.rowsPerPage}
|
||||
onPageChange={table.onChangePage}
|
||||
rowsPerPageOptions={[5, 10, 25]}
|
||||
onRowsPerPageChange={table.onChangeRowsPerPage}
|
||||
/>
|
||||
</Card>
|
||||
</DashboardContent>
|
||||
);
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
export function useTable() {
|
||||
const [page, setPage] = useState(0);
|
||||
const [orderBy, setOrderBy] = useState('name');
|
||||
const [rowsPerPage, setRowsPerPage] = useState(5);
|
||||
const [selected, setSelected] = useState<string[]>([]);
|
||||
const [order, setOrder] = useState<'asc' | 'desc'>('asc');
|
||||
|
||||
const onSort = useCallback(
|
||||
(id: string) => {
|
||||
const isAsc = orderBy === id && order === 'asc';
|
||||
setOrder(isAsc ? 'desc' : 'asc');
|
||||
setOrderBy(id);
|
||||
},
|
||||
[order, orderBy]
|
||||
);
|
||||
|
||||
const onSelectAllRows = useCallback((checked: boolean, newSelecteds: string[]) => {
|
||||
if (checked) {
|
||||
setSelected(newSelecteds);
|
||||
return;
|
||||
}
|
||||
setSelected([]);
|
||||
}, []);
|
||||
|
||||
const onSelectRow = useCallback(
|
||||
(inputValue: string) => {
|
||||
const newSelected = selected.includes(inputValue)
|
||||
? selected.filter((value) => value !== inputValue)
|
||||
: [...selected, inputValue];
|
||||
|
||||
setSelected(newSelected);
|
||||
},
|
||||
[selected]
|
||||
);
|
||||
|
||||
const onResetPage = useCallback(() => {
|
||||
setPage(0);
|
||||
}, []);
|
||||
|
||||
const onChangePage = useCallback((event: unknown, newPage: number) => {
|
||||
setPage(newPage);
|
||||
}, []);
|
||||
|
||||
const onChangeRowsPerPage = useCallback(
|
||||
(event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setRowsPerPage(parseInt(event.target.value, 10));
|
||||
onResetPage();
|
||||
},
|
||||
[onResetPage]
|
||||
);
|
||||
|
||||
return {
|
||||
page,
|
||||
order,
|
||||
onSort,
|
||||
orderBy,
|
||||
selected,
|
||||
rowsPerPage,
|
||||
onSelectRow,
|
||||
onResetPage,
|
||||
onChangePage,
|
||||
onSelectAllRows,
|
||||
onChangeRowsPerPage,
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user