LMSN-217
Update slicing hospital portal
This commit is contained in:
98
frontend/hospital-portal/src/components/Label.tsx
Normal file
98
frontend/hospital-portal/src/components/Label.tsx
Normal file
@@ -0,0 +1,98 @@
|
||||
// @mui
|
||||
import { alpha, Theme, useTheme, styled } from '@mui/material/styles';
|
||||
import { BoxProps } from '@mui/material';
|
||||
// theme
|
||||
import { ColorSchema } from '../theme/palette';
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
type LabelColor = 'default' | 'primary' | 'secondary' | 'info' | 'success' | 'warning' | 'error';
|
||||
|
||||
type LabelVariant = 'filled' | 'outlined' | 'ghost';
|
||||
|
||||
const RootStyle = styled('span')(
|
||||
({
|
||||
theme,
|
||||
ownerState,
|
||||
}: {
|
||||
theme: Theme;
|
||||
ownerState: {
|
||||
color: LabelColor;
|
||||
variant: LabelVariant;
|
||||
};
|
||||
}) => {
|
||||
const isLight = theme.palette.mode === 'light';
|
||||
const { color, variant } = ownerState;
|
||||
|
||||
const styleFilled = (color: ColorSchema) => ({
|
||||
color: theme.palette[color].contrastText,
|
||||
backgroundColor: theme.palette[color].main,
|
||||
});
|
||||
|
||||
const styleOutlined = (color: ColorSchema) => ({
|
||||
color: theme.palette[color].main,
|
||||
backgroundColor: 'transparent',
|
||||
border: `1px solid ${theme.palette[color].main}`,
|
||||
});
|
||||
|
||||
const styleGhost = (color: ColorSchema) => ({
|
||||
color: theme.palette[color][isLight ? 'dark' : 'light'],
|
||||
backgroundColor: alpha(theme.palette[color].main, 0.16),
|
||||
});
|
||||
|
||||
return {
|
||||
height: 22,
|
||||
minWidth: 22,
|
||||
lineHeight: 0,
|
||||
borderRadius: 6,
|
||||
// cursor: 'default',
|
||||
alignItems: 'center',
|
||||
whiteSpace: 'nowrap',
|
||||
display: 'inline-flex',
|
||||
justifyContent: 'center',
|
||||
padding: theme.spacing(0, 1),
|
||||
color: theme.palette.grey[800],
|
||||
fontSize: theme.typography.pxToRem(12),
|
||||
fontFamily: theme.typography.fontFamily,
|
||||
backgroundColor: theme.palette.grey[300],
|
||||
fontWeight: theme.typography.fontWeightBold,
|
||||
|
||||
...(color !== 'default'
|
||||
? {
|
||||
...(variant === 'filled' && { ...styleFilled(color) }),
|
||||
...(variant === 'outlined' && { ...styleOutlined(color) }),
|
||||
...(variant === 'ghost' && { ...styleGhost(color) }),
|
||||
}
|
||||
: {
|
||||
...(variant === 'outlined' && {
|
||||
backgroundColor: 'transparent',
|
||||
color: theme.palette.text.primary,
|
||||
border: `1px solid ${theme.palette.grey[500_32]}`,
|
||||
}),
|
||||
...(variant === 'ghost' && {
|
||||
color: isLight ? theme.palette.text.secondary : theme.palette.common.white,
|
||||
backgroundColor: theme.palette.grey[500_16],
|
||||
}),
|
||||
}),
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
interface Props extends BoxProps {
|
||||
color?: LabelColor;
|
||||
variant?: LabelVariant;
|
||||
}
|
||||
|
||||
export default function Label({ color = 'default', variant = 'ghost', children, sx }: Props) {
|
||||
const theme = useTheme();
|
||||
|
||||
return (
|
||||
<RootStyle ownerState={{ color, variant }} sx={sx} theme={theme}>
|
||||
{children}
|
||||
</RootStyle>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
387
frontend/hospital-portal/src/components/Table.tsx
Normal file
387
frontend/hospital-portal/src/components/Table.tsx
Normal file
@@ -0,0 +1,387 @@
|
||||
/* ---------------------------------- @mui ---------------------------------- */
|
||||
import { styled } from '@mui/material/styles';
|
||||
import {
|
||||
Paper,
|
||||
Table as TableContent,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableContainer,
|
||||
TableHead,
|
||||
TableRow,
|
||||
TextField,
|
||||
Button,
|
||||
TableSortLabel,
|
||||
Box,
|
||||
Card,
|
||||
Grid,
|
||||
FormControl,
|
||||
InputLabel,
|
||||
Select,
|
||||
MenuItem,
|
||||
SelectChangeEvent,
|
||||
Stack,
|
||||
Typography,
|
||||
LinearProgress,
|
||||
linearProgressClasses,
|
||||
} from '@mui/material';
|
||||
import { visuallyHidden } from '@mui/utils';
|
||||
/* ---------------------------------- axios --------------------------------- */
|
||||
import axios from '../utils/axios';
|
||||
/* ---------------------------------- react --------------------------------- */
|
||||
import { Fragment, useContext, useEffect, useState } from 'react';
|
||||
import { useSearchParams } from 'react-router-dom';
|
||||
/* -------------------------------- component ------------------------------- */
|
||||
import BaseTablePagination from './BaseTablePagination';
|
||||
/* ---------------------------------- theme --------------------------------- */
|
||||
import palette from '../theme/palette';
|
||||
/* ---------------------------------- utils --------------------------------- */
|
||||
import { UserCurrentCorporateContext } from '../contexts/UserCurrentCorporate';
|
||||
import { fSplit } from '../utils/formatNumber';
|
||||
import { Download, Search as SearchIcon, Upload } from '@mui/icons-material';
|
||||
/* ---------------------------------- types --------------------------------- */
|
||||
import { DivisionDataProps, Order, PaginationTableProps, TableListProps } from '../@types/table';
|
||||
import { InputAdornment } from '@mui/material';
|
||||
import GetAppIcon from '@mui/icons-material/GetApp';
|
||||
/* --------------------------------- styled --------------------------------- */
|
||||
const BorderLinearProgress = styled(LinearProgress)(({ theme }) => ({
|
||||
height: 10,
|
||||
borderRadius: 6,
|
||||
[`&.${linearProgressClasses.colorPrimary}`]: {
|
||||
backgroundColor: '#D1F1F1',
|
||||
},
|
||||
[`& .${linearProgressClasses.bar}`]: {
|
||||
borderRadius: 6,
|
||||
backgroundColor: theme.palette.primary.main,
|
||||
},
|
||||
}));
|
||||
/* -------------------------------------------------------------------------- */
|
||||
|
||||
export default function Table<T>({
|
||||
headCells,
|
||||
rows,
|
||||
paginations,
|
||||
orders,
|
||||
loadings,
|
||||
params,
|
||||
filters,
|
||||
filterStatus,
|
||||
filterStartDate,
|
||||
filterEndDate,
|
||||
searchs,
|
||||
exportReport,
|
||||
}: TableListProps<T>) {
|
||||
/* ------------------------------- handle sort ------------------------------ */
|
||||
const handleRequestSort = async (event: React.MouseEvent<unknown>, property: string) => {
|
||||
const isAsc = orders?.orderBy === property && orders?.order === 'asc';
|
||||
|
||||
orders?.setOrder(isAsc ? 'desc' : 'asc');
|
||||
orders?.setOrderBy(property);
|
||||
const parameters = Object.fromEntries([
|
||||
...params.searchParams.entries(),
|
||||
['order', isAsc ? 'desc' : 'asc'],
|
||||
['orderBy', property],
|
||||
]);
|
||||
params.setAppliedParams(parameters);
|
||||
};
|
||||
/* -------------------------------------------------------------------------- */
|
||||
|
||||
/* -------------------------- enchanced table head -------------------------- */
|
||||
const EnhancedTableHead = () => {
|
||||
const createSortHandler = (property: string) => (event: React.MouseEvent<unknown>) => {
|
||||
handleRequestSort(event, property);
|
||||
};
|
||||
|
||||
return (
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
{headCells &&
|
||||
headCells.map((headCell, index) => (
|
||||
<TableCell
|
||||
key={index}
|
||||
sortDirection={orders?.orderBy === headCell.id ? orders.order : false}
|
||||
// @ts-ignore
|
||||
align={headCell.align}
|
||||
sx={{ padding: 2 }}
|
||||
width={headCell.width ? headCell.width : 'auto'}
|
||||
>
|
||||
{headCell.isSort ? (
|
||||
<TableSortLabel
|
||||
active={orders?.orderBy === headCell.id}
|
||||
direction={orders?.orderBy === headCell.id ? orders.order : 'asc'}
|
||||
onClick={createSortHandler(headCell.id)}
|
||||
>
|
||||
{headCell.label}
|
||||
{orders?.orderBy === headCell.id ? (
|
||||
<Box component="span" sx={visuallyHidden}>
|
||||
{orders.order === 'desc' ? 'sorted descending' : 'sorted ascending'}
|
||||
</Box>
|
||||
) : null}
|
||||
</TableSortLabel>
|
||||
) : (
|
||||
headCell.label
|
||||
)}
|
||||
</TableCell>
|
||||
))}
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
);
|
||||
};
|
||||
/* -------------------------------------------------------------------------- */
|
||||
|
||||
/* ------------------------ button change pagination ------------------------ */
|
||||
const onPageChangeHandle = async (
|
||||
event: React.MouseEvent<HTMLButtonElement> | null,
|
||||
newPage: number
|
||||
) => {
|
||||
const parameters = Object.fromEntries([
|
||||
...params.searchParams.entries(),
|
||||
['page', newPage + 1],
|
||||
['per_page', paginations.rowsPerPage]
|
||||
]);
|
||||
paginations.setPage(newPage);
|
||||
await new Promise((resolve) => setTimeout(resolve, 500));
|
||||
params.setAppliedParams(parameters);
|
||||
};
|
||||
/* -------------------------------------------------------------------------- */
|
||||
|
||||
/* --------------------------- row page per limit --------------------------- */
|
||||
const onRowsPerPageChangeHandle = async (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
params.searchParams.delete('page');
|
||||
const parameters = Object.fromEntries([
|
||||
...params.searchParams.entries(),
|
||||
['per_page', parseInt(event.target.value, 10)],
|
||||
]);
|
||||
|
||||
paginations.setPage(0);
|
||||
paginations.setRowsPerPage(parseInt(event.target.value, 10));
|
||||
await new Promise((resolve) => setTimeout(resolve, 500));
|
||||
params.setAppliedParams(parameters);
|
||||
};
|
||||
/* -------------------------------------------------------------------------- */
|
||||
|
||||
return (
|
||||
// <Card>
|
||||
<Grid container>
|
||||
{/* Field 1 */}
|
||||
<Grid item xs={12} paddingX="24px" paddingY="20px">
|
||||
<Grid container spacing={2}>
|
||||
{filters && filters.useFilter ? (
|
||||
<Fragment>
|
||||
<Grid item xs={12} lg={3} xl={2}>
|
||||
<FormControl fullWidth>
|
||||
<InputLabel id="simple-division-select-lable">Division</InputLabel>
|
||||
<Select
|
||||
labelId="simple-division-select-lable"
|
||||
id="division-select-lable"
|
||||
value={filters.config.divisionValue}
|
||||
label="Division"
|
||||
onChange={filters.config.handleDivisionChange}
|
||||
>
|
||||
<MenuItem value="all">All</MenuItem>
|
||||
{filters.config.divisionData.map((row: DivisionDataProps, index) => (
|
||||
<MenuItem key={index} value={row.id}>
|
||||
{row.name}
|
||||
</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
</FormControl>
|
||||
</Grid>
|
||||
<Grid item xs={12} lg={9} xl={10}>
|
||||
<form onSubmit={searchs.handleSearchSubmit}>
|
||||
<TextField
|
||||
id="search-input"
|
||||
label="Search"
|
||||
variant="outlined"
|
||||
onChange={(event) => searchs.setSearchText(event.target.value)}
|
||||
value={searchs.searchText}
|
||||
fullWidth
|
||||
/>
|
||||
</form>
|
||||
</Grid>
|
||||
</Fragment>
|
||||
) : null }
|
||||
|
||||
{searchs && searchs.useSearchs ? (
|
||||
<Fragment>
|
||||
{filterStatus && filterStatus.useFilter ? (
|
||||
<Grid item xs={12} lg={4} xl={4}>
|
||||
<form onSubmit={searchs.handleSearchSubmit}>
|
||||
<TextField
|
||||
id="search-input"
|
||||
variant="outlined"
|
||||
onChange={(event) => searchs.setSearchText(event.target.value)}
|
||||
value={searchs.searchText}
|
||||
fullWidth
|
||||
InputProps={{
|
||||
startAdornment: (
|
||||
<InputAdornment position="start">
|
||||
<SearchIcon />
|
||||
</InputAdornment>
|
||||
),
|
||||
}}
|
||||
placeholder="Search Name or Member ID... "
|
||||
/>
|
||||
</form>
|
||||
</Grid>
|
||||
) :
|
||||
<Grid item xs={12} lg={6} xl={6}>
|
||||
<form onSubmit={searchs.handleSearchSubmit}>
|
||||
<TextField
|
||||
id="search-input"
|
||||
variant="outlined"
|
||||
onChange={(event) => searchs.setSearchText(event.target.value)}
|
||||
value={searchs.searchText}
|
||||
fullWidth
|
||||
InputProps={{
|
||||
startAdornment: (
|
||||
<InputAdornment position="start">
|
||||
<SearchIcon />
|
||||
</InputAdornment>
|
||||
),
|
||||
}}
|
||||
placeholder="Search Name or Member ID... "
|
||||
/>
|
||||
</form>
|
||||
</Grid>
|
||||
}
|
||||
|
||||
</Fragment>
|
||||
) : null }
|
||||
|
||||
{/* Start date */}
|
||||
{filterStartDate && filterStartDate.useFilter ? (
|
||||
<Grid item xs={12} lg={2} xl={2}>
|
||||
<form onChange={filterStartDate.handleStartDateChange}>
|
||||
<TextField
|
||||
id="date-input"
|
||||
type="date"
|
||||
variant="outlined"
|
||||
value={filterStartDate.startDate}
|
||||
onChange={(event) => filterStartDate.setStartDate(event.target.value)}
|
||||
fullWidth
|
||||
label="Start Date"
|
||||
InputLabelProps={{
|
||||
shrink: true,
|
||||
}}
|
||||
/>
|
||||
</form>
|
||||
</Grid>
|
||||
) : null }
|
||||
|
||||
{/* End Date */}
|
||||
|
||||
{filterEndDate && filterEndDate.useFilter ? (
|
||||
<Grid item xs={12} lg={2} xl={2}>
|
||||
<form onChange={filterEndDate.handleEndDateChange}>
|
||||
<TextField
|
||||
id="date-input"
|
||||
type="date"
|
||||
variant="outlined"
|
||||
value={filterEndDate.endDate}
|
||||
onChange={(event) => filterEndDate.setEndDate(event.target.value)}
|
||||
fullWidth
|
||||
label="End Date"
|
||||
InputLabelProps={{
|
||||
shrink: true,
|
||||
}}
|
||||
/>
|
||||
</form>
|
||||
</Grid>
|
||||
) : null }
|
||||
|
||||
{/* Filter status */}
|
||||
{filterStatus && filterStatus.useFilter ? (
|
||||
<Grid item xs={12} lg={2} xl={2}>
|
||||
<FormControl fullWidth>
|
||||
<InputLabel id="simple-status-select-lable">Status</InputLabel>
|
||||
<Select
|
||||
labelId="simple-status-select-lable"
|
||||
id="status-select-lable"
|
||||
value={filterStatus.config.statusValue}
|
||||
label="Status"
|
||||
onChange={filterStatus.config.handleStatusChange}
|
||||
>
|
||||
<MenuItem value="all">All</MenuItem>
|
||||
{filterStatus.config.filterData.map((row: StatusDataProps, index) => (
|
||||
<MenuItem key={index} value={row.id}>
|
||||
{row.name}
|
||||
</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
</FormControl>
|
||||
</Grid>
|
||||
) : null }
|
||||
|
||||
{/* Export Report */}
|
||||
|
||||
{exportReport && exportReport.useExport ? (
|
||||
<Grid item xs={12} lg={2} xl={2}>
|
||||
<FormControl fullWidth>
|
||||
<Button variant='contained' sx={{p:2}}>
|
||||
<Download />
|
||||
<Typography variant='inherit' sx={{marginLeft: 1}}>Export</Typography>
|
||||
</Button>
|
||||
</FormControl>
|
||||
</Grid>
|
||||
) : null }
|
||||
|
||||
</Grid>
|
||||
</Grid>
|
||||
{/* End Field 1 */}
|
||||
{/* Field 2 */}
|
||||
<Grid item xs={12}>
|
||||
{/* Table */}
|
||||
<TableContainer component={Paper}>
|
||||
<TableContent aria-label="collapsible table" size="small">
|
||||
{/* Table Header */}
|
||||
<EnhancedTableHead />
|
||||
{/* End Table Header */}
|
||||
{/* Table Body */}
|
||||
<TableBody>
|
||||
{loadings.isLoading && rows.length >= 1 ? (
|
||||
<TableRow>
|
||||
<TableCell colSpan={headCells?.length} align="center">
|
||||
Loading . . .
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
) : rows && rows.length >= 1 ? (
|
||||
rows.map((row, rowIndex) => (
|
||||
<TableRow key={rowIndex}>
|
||||
{headCells &&
|
||||
//@ts-ignore
|
||||
headCells.map((head, headIndex) => (
|
||||
//@ts-ignore
|
||||
<TableCell align={head.align} key={headIndex}>
|
||||
{row[head.id]}
|
||||
</TableCell>
|
||||
))}
|
||||
</TableRow>
|
||||
))
|
||||
) : (
|
||||
<TableRow>
|
||||
<TableCell colSpan={6} align="center">
|
||||
No Data Found
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
)}
|
||||
</TableBody>
|
||||
{/* End Table Body */}
|
||||
</TableContent>
|
||||
</TableContainer>
|
||||
{/* End Table */}
|
||||
|
||||
{/* Pagination */}
|
||||
<BaseTablePagination
|
||||
count={paginations.paginationTable.total}
|
||||
onPageChange={onPageChangeHandle}
|
||||
page={paginations.page}
|
||||
rowsPerPage={paginations.rowsPerPage}
|
||||
onRowsPerPageChange={onRowsPerPageChangeHandle}
|
||||
/>
|
||||
{/* End Pagination */}
|
||||
</Grid>
|
||||
{/* End Field 2 */}
|
||||
</Grid>
|
||||
// </Card>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user