217 lines
6.4 KiB
TypeScript
217 lines
6.4 KiB
TypeScript
// src/sections/invoice/invoice-table-toolbar.tsx
|
|
|
|
import type { IDatePickerControl } from 'src/types/common';
|
|
import type { IInvoiceTableFilters } from 'src/types/invoice';
|
|
import type { SelectChangeEvent } from '@mui/material/Select';
|
|
import type { UseSetStateReturn } from 'minimal-shared/hooks';
|
|
|
|
import { useCallback } from 'react';
|
|
import { usePopover } from 'minimal-shared/hooks';
|
|
|
|
import Box from '@mui/material/Box';
|
|
import Select from '@mui/material/Select';
|
|
import MenuList from '@mui/material/MenuList';
|
|
import MenuItem from '@mui/material/MenuItem';
|
|
import Checkbox from '@mui/material/Checkbox';
|
|
import TextField from '@mui/material/TextField';
|
|
import InputLabel from '@mui/material/InputLabel';
|
|
import IconButton from '@mui/material/IconButton';
|
|
import FormControl from '@mui/material/FormControl';
|
|
import OutlinedInput from '@mui/material/OutlinedInput';
|
|
import InputAdornment from '@mui/material/InputAdornment';
|
|
import { DatePicker } from '@mui/x-date-pickers/DatePicker';
|
|
import { formHelperTextClasses } from '@mui/material/FormHelperText';
|
|
|
|
import { Iconify } from 'src/components/iconify';
|
|
import { CustomPopover } from 'src/components/custom-popover';
|
|
import { useTranslation } from 'react-i18next';
|
|
|
|
// ----------------------------------------------------------------------
|
|
|
|
type Props = {
|
|
dateError: boolean;
|
|
onResetPage: () => void;
|
|
filters: UseSetStateReturn<IInvoiceTableFilters>;
|
|
options: {
|
|
services: string[];
|
|
};
|
|
};
|
|
|
|
export function InvoiceTableToolbar({
|
|
//
|
|
filters,
|
|
options,
|
|
dateError,
|
|
onResetPage,
|
|
}: Props) {
|
|
const { t } = useTranslation();
|
|
const menuActions = usePopover();
|
|
|
|
const { state: currentFilters, setState: updateFilters } = filters;
|
|
|
|
const handleFilterName = useCallback(
|
|
(event: React.ChangeEvent<HTMLInputElement>) => {
|
|
onResetPage();
|
|
updateFilters({ name: event.target.value });
|
|
},
|
|
[onResetPage, updateFilters]
|
|
);
|
|
|
|
const handleFilterService = useCallback(
|
|
(event: SelectChangeEvent<string[]>) => {
|
|
const newValue =
|
|
typeof event.target.value === 'string' ? event.target.value.split(',') : event.target.value;
|
|
|
|
onResetPage();
|
|
updateFilters({ service: newValue });
|
|
},
|
|
[onResetPage, updateFilters]
|
|
);
|
|
|
|
const handleFilterStartDate = useCallback(
|
|
(newValue: IDatePickerControl) => {
|
|
onResetPage();
|
|
updateFilters({ startDate: newValue });
|
|
},
|
|
[onResetPage, updateFilters]
|
|
);
|
|
|
|
const handleFilterEndDate = useCallback(
|
|
(newValue: IDatePickerControl) => {
|
|
onResetPage();
|
|
updateFilters({ endDate: newValue });
|
|
},
|
|
[onResetPage, updateFilters]
|
|
);
|
|
|
|
const renderMenuActions = () => (
|
|
<CustomPopover
|
|
open={menuActions.open}
|
|
anchorEl={menuActions.anchorEl}
|
|
onClose={menuActions.onClose}
|
|
slotProps={{ arrow: { placement: 'right-top' } }}
|
|
>
|
|
<MenuList>
|
|
<MenuItem onClick={() => menuActions.onClose()}>
|
|
<Iconify icon="solar:printer-minimalistic-bold" />
|
|
{t('Print')}
|
|
</MenuItem>
|
|
|
|
<MenuItem onClick={() => menuActions.onClose()}>
|
|
<Iconify icon="solar:import-bold" />
|
|
{t('Import')}
|
|
</MenuItem>
|
|
|
|
<MenuItem onClick={() => menuActions.onClose()}>
|
|
<Iconify icon="solar:export-bold" />
|
|
{t('Export')}
|
|
</MenuItem>
|
|
</MenuList>
|
|
</CustomPopover>
|
|
);
|
|
|
|
return (
|
|
<>
|
|
<Box
|
|
sx={{
|
|
p: 2.5,
|
|
gap: 2,
|
|
display: 'flex',
|
|
pr: { xs: 2.5, md: 1 },
|
|
flexDirection: { xs: 'column', md: 'row' },
|
|
alignItems: { xs: 'flex-end', md: 'center' },
|
|
}}
|
|
>
|
|
<FormControl sx={{ flexShrink: 0, width: { xs: 1, md: 180 } }}>
|
|
<InputLabel htmlFor="filter-service-select">{t('Service')}</InputLabel>
|
|
<Select
|
|
multiple
|
|
value={currentFilters.service}
|
|
onChange={handleFilterService}
|
|
input={<OutlinedInput label="Service" />}
|
|
renderValue={(selected) => selected.map((value) => value).join(', ')}
|
|
inputProps={{ id: 'filter-service-select' }}
|
|
sx={{ textTransform: 'capitalize' }}
|
|
>
|
|
{options.services.map((option) => (
|
|
<MenuItem key={option} value={option}>
|
|
<Checkbox
|
|
disableRipple
|
|
size="small"
|
|
checked={currentFilters.service.includes(option)}
|
|
slotProps={{
|
|
input: {
|
|
id: `${option}-checkbox`,
|
|
'aria-label': `${option} checkbox`,
|
|
},
|
|
}}
|
|
/>
|
|
{option}
|
|
</MenuItem>
|
|
))}
|
|
</Select>
|
|
</FormControl>
|
|
|
|
<DatePicker
|
|
label={t('Start date')}
|
|
value={currentFilters.endDate}
|
|
onChange={handleFilterStartDate}
|
|
slotProps={{ textField: { fullWidth: true } }}
|
|
sx={{ maxWidth: { md: 180 } }}
|
|
/>
|
|
|
|
<DatePicker
|
|
label={t('End date')}
|
|
value={currentFilters.endDate}
|
|
onChange={handleFilterEndDate}
|
|
slotProps={{
|
|
textField: {
|
|
fullWidth: true,
|
|
error: dateError,
|
|
helperText: dateError ? t('End date must be later than start date') : null,
|
|
},
|
|
}}
|
|
sx={{
|
|
maxWidth: { md: 180 },
|
|
[`& .${formHelperTextClasses.root}`]: {
|
|
bottom: { md: -40 },
|
|
position: { md: 'absolute' },
|
|
},
|
|
}}
|
|
/>
|
|
|
|
<Box
|
|
sx={{
|
|
gap: 2,
|
|
width: 1,
|
|
flexGrow: 1,
|
|
display: 'flex',
|
|
alignItems: 'center',
|
|
}}
|
|
>
|
|
<TextField
|
|
fullWidth
|
|
value={currentFilters.name}
|
|
onChange={handleFilterName}
|
|
placeholder={t('Search customer or invoice number...')}
|
|
slotProps={{
|
|
input: {
|
|
startAdornment: (
|
|
<InputAdornment position="start">
|
|
<Iconify icon="eva:search-fill" sx={{ color: 'text.disabled' }} />
|
|
</InputAdornment>
|
|
),
|
|
},
|
|
}}
|
|
/>
|
|
<IconButton onClick={menuActions.onOpen}>
|
|
<Iconify icon="eva:more-vertical-fill" />
|
|
</IconButton>
|
|
</Box>
|
|
</Box>
|
|
|
|
{renderMenuActions()}
|
|
</>
|
|
);
|
|
}
|