feat: redesign searchbar with drawer lateral for better scalability
This commit is contained in:
parent
2b750ed1a3
commit
5558ca6a0d
|
|
@ -12,9 +12,13 @@ import {
|
|||
Autocomplete,
|
||||
Paper,
|
||||
Tooltip,
|
||||
Collapse,
|
||||
CircularProgress,
|
||||
Badge,
|
||||
Drawer,
|
||||
IconButton,
|
||||
Typography,
|
||||
Divider,
|
||||
Stack,
|
||||
type AutocompleteRenderInputParams,
|
||||
} from '@mui/material';
|
||||
import { useStores } from '../store/useStores';
|
||||
|
|
@ -28,8 +32,8 @@ import { AdapterMoment } from '@mui/x-date-pickers/AdapterMoment';
|
|||
import {
|
||||
Search as SearchIcon,
|
||||
RestartAlt as ResetIcon,
|
||||
ExpandLess as ExpandLessIcon,
|
||||
ExpandMore as ExpandMoreIcon,
|
||||
Tune as TuneIcon,
|
||||
Close as CloseIcon,
|
||||
} from '@mui/icons-material';
|
||||
import moment from 'moment';
|
||||
import 'moment/locale/pt-br';
|
||||
|
|
@ -186,8 +190,10 @@ export const SearchBar = () => {
|
|||
touchedFields.createDateIni && !localFilters.createDateIni;
|
||||
const showDateEndError = touchedFields.createDateEnd && dateError;
|
||||
|
||||
// Contador de filtros avançados ativos
|
||||
const advancedFiltersCount = [
|
||||
// Contador de filtros ativos no Drawer
|
||||
const activeDrawerFiltersCount = [
|
||||
localFilters.status,
|
||||
localFilters.customerId,
|
||||
localFilters.store?.length,
|
||||
localFilters.stockId?.length,
|
||||
localFilters.sellerId,
|
||||
|
|
@ -195,10 +201,21 @@ export const SearchBar = () => {
|
|||
localFilters.productId,
|
||||
].filter(Boolean).length;
|
||||
|
||||
const toggleDrawer = (open: boolean) => (event: React.KeyboardEvent | React.MouseEvent) => {
|
||||
if (
|
||||
event.type === 'keydown' &&
|
||||
((event as React.KeyboardEvent).key === 'Tab' ||
|
||||
(event as React.KeyboardEvent).key === 'Shift')
|
||||
) {
|
||||
return;
|
||||
}
|
||||
setShowAdvancedFilters(open);
|
||||
};
|
||||
|
||||
return (
|
||||
<Paper
|
||||
sx={{
|
||||
p: { xs: 2, md: 3 },
|
||||
p: { xs: 2, md: 2 },
|
||||
mb: 2,
|
||||
bgcolor: 'background.paper',
|
||||
borderRadius: 2,
|
||||
|
|
@ -207,10 +224,8 @@ export const SearchBar = () => {
|
|||
elevation={0}
|
||||
onKeyDown={handleKeyDown}
|
||||
>
|
||||
<Grid container spacing={{ xs: 1.5, md: 2 }} alignItems="flex-end">
|
||||
{/* --- Primary Filters (Always Visible) --- */}
|
||||
|
||||
{/* Campo de Texto Simples (Nº Pedido) */}
|
||||
<Grid container spacing={2} alignItems="center">
|
||||
{/* Nº do Pedido */}
|
||||
<Grid size={{ xs: 12, sm: 6, md: 2 }}>
|
||||
<TextField
|
||||
fullWidth
|
||||
|
|
@ -228,537 +243,227 @@ export const SearchBar = () => {
|
|||
/>
|
||||
</Grid>
|
||||
|
||||
{/* Select do MUI (Situação) */}
|
||||
<Grid size={{ xs: 12, sm: 6, md: 2 }}>
|
||||
<TextField
|
||||
select
|
||||
fullWidth
|
||||
label="Situação"
|
||||
size="small"
|
||||
value={localFilters.status ?? ''}
|
||||
onChange={(e) => {
|
||||
const value = e.target.value || null;
|
||||
updateLocalFilter('status', value);
|
||||
}}
|
||||
>
|
||||
<MenuItem value="">Todos</MenuItem>
|
||||
<MenuItem value="P">Pendente</MenuItem>
|
||||
<MenuItem value="F">Faturado</MenuItem>
|
||||
<MenuItem value="C">Cancelado</MenuItem>
|
||||
</TextField>
|
||||
</Grid>
|
||||
|
||||
{/* Autocomplete do MUI para Cliente */}
|
||||
<Grid size={{ xs: 12, sm: 6, md: 2 }}>
|
||||
<Autocomplete
|
||||
size="small"
|
||||
options={customers.options}
|
||||
getOptionLabel={(option) => option.label}
|
||||
isOptionEqualToValue={(option, value) => option.id === value.id}
|
||||
value={
|
||||
customers.options.find(
|
||||
(option) => localFilters.customerId === option.customer?.id
|
||||
) || null
|
||||
}
|
||||
onChange={(_, newValue) => {
|
||||
if (!newValue) {
|
||||
updateLocalFilter('customerName', null);
|
||||
updateLocalFilter('customerId', null);
|
||||
setCustomerSearchTerm('');
|
||||
return;
|
||||
}
|
||||
|
||||
updateLocalFilter('customerId', newValue.customer?.id || null);
|
||||
updateLocalFilter(
|
||||
'customerName',
|
||||
newValue.customer?.name || null
|
||||
);
|
||||
}}
|
||||
onInputChange={(_, newInputValue, reason) => {
|
||||
if (reason === 'clear') {
|
||||
updateLocalFilter('customerName', null);
|
||||
updateLocalFilter('customerId', null);
|
||||
setCustomerSearchTerm('');
|
||||
return;
|
||||
}
|
||||
|
||||
if (reason === 'input') {
|
||||
setCustomerSearchTerm(newInputValue);
|
||||
if (!newInputValue) {
|
||||
updateLocalFilter('customerName', null);
|
||||
updateLocalFilter('customerId', null);
|
||||
setCustomerSearchTerm('');
|
||||
}
|
||||
}
|
||||
}}
|
||||
loading={customers.isLoading}
|
||||
renderInput={(params: AutocompleteRenderInputParams) => (
|
||||
<TextField
|
||||
{...params}
|
||||
label="Cliente"
|
||||
placeholder="Digite para buscar..."
|
||||
/>
|
||||
)}
|
||||
noOptionsText={
|
||||
customerSearchTerm.length < 2
|
||||
? 'Digite pelo menos 2 caracteres'
|
||||
: 'Nenhum cliente encontrado'
|
||||
}
|
||||
loadingText="Buscando clientes..."
|
||||
filterOptions={(x) => x}
|
||||
clearOnBlur={false}
|
||||
selectOnFocus
|
||||
handleHomeEndKeys
|
||||
/>
|
||||
</Grid>
|
||||
|
||||
{/* Campos de Data */}
|
||||
<Grid size={{ xs: 12, sm: 12, md: 3.5 }}>
|
||||
<LocalizationProvider
|
||||
dateAdapter={AdapterMoment}
|
||||
adapterLocale="pt-br"
|
||||
>
|
||||
<Box display="flex" gap={{ xs: 1.5, md: 2 }} flexDirection={{ xs: 'column', sm: 'row' }}>
|
||||
<Box flex={1}>
|
||||
{/* Datas */}
|
||||
<Grid size={{ xs: 12, sm: 12, md: 5 }}>
|
||||
<LocalizationProvider dateAdapter={AdapterMoment} adapterLocale="pt-br">
|
||||
<Box display="flex" gap={1.5}>
|
||||
<DatePicker
|
||||
label="Data Inicial"
|
||||
value={
|
||||
localFilters.createDateIni
|
||||
? moment(localFilters.createDateIni, 'YYYY-MM-DD')
|
||||
: null
|
||||
}
|
||||
onChange={(date: moment.Moment | null) => {
|
||||
setTouchedFields((prev) => ({
|
||||
...prev,
|
||||
createDateIni: true,
|
||||
}));
|
||||
updateLocalFilter(
|
||||
'createDateIni',
|
||||
date ? date.format('YYYY-MM-DD') : null
|
||||
);
|
||||
value={localFilters.createDateIni ? moment(localFilters.createDateIni, 'YYYY-MM-DD') : null}
|
||||
onChange={(date) => {
|
||||
setTouchedFields(prev => ({ ...prev, createDateIni: true }));
|
||||
updateLocalFilter('createDateIni', date ? date.format('YYYY-MM-DD') : null);
|
||||
}}
|
||||
format="DD/MM/YYYY"
|
||||
maxDate={
|
||||
localFilters.createDateEnd
|
||||
? moment(localFilters.createDateEnd, 'YYYY-MM-DD')
|
||||
: undefined
|
||||
}
|
||||
slotProps={{
|
||||
textField: {
|
||||
size: 'small',
|
||||
fullWidth: true,
|
||||
required: true,
|
||||
error: showDateIniError,
|
||||
helperText: showDateIniError
|
||||
? 'Data inicial é obrigatória'
|
||||
: '',
|
||||
onBlur: () =>
|
||||
setTouchedFields((prev) => ({
|
||||
...prev,
|
||||
createDateIni: true,
|
||||
})),
|
||||
inputProps: {
|
||||
'aria-required': true,
|
||||
},
|
||||
},
|
||||
helperText: showDateIniError ? 'Obrigatório' : '',
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
<Box flex={1}>
|
||||
<DatePicker
|
||||
label="Data Final (opcional)"
|
||||
value={
|
||||
localFilters.createDateEnd
|
||||
? moment(localFilters.createDateEnd, 'YYYY-MM-DD')
|
||||
: null
|
||||
}
|
||||
onChange={(date: moment.Moment | null) => {
|
||||
setTouchedFields((prev) => ({
|
||||
...prev,
|
||||
createDateEnd: true,
|
||||
}));
|
||||
updateLocalFilter(
|
||||
'createDateEnd',
|
||||
date ? date.format('YYYY-MM-DD') : null
|
||||
);
|
||||
label="Data Final"
|
||||
value={localFilters.createDateEnd ? moment(localFilters.createDateEnd, 'YYYY-MM-DD') : null}
|
||||
onChange={(date) => {
|
||||
setTouchedFields(prev => ({ ...prev, createDateEnd: true }));
|
||||
updateLocalFilter('createDateEnd', date ? date.format('YYYY-MM-DD') : null);
|
||||
}}
|
||||
format="DD/MM/YYYY"
|
||||
minDate={
|
||||
localFilters.createDateIni
|
||||
? moment(localFilters.createDateIni, 'YYYY-MM-DD')
|
||||
: undefined
|
||||
}
|
||||
slotProps={{
|
||||
textField: {
|
||||
size: 'small',
|
||||
fullWidth: true,
|
||||
error: !!showDateEndError,
|
||||
helperText: showDateEndError || '',
|
||||
onBlur: () =>
|
||||
setTouchedFields((prev) => ({
|
||||
...prev,
|
||||
createDateEnd: true,
|
||||
})),
|
||||
inputProps: {
|
||||
placeholder: 'Opcional',
|
||||
},
|
||||
},
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
</Box>
|
||||
</LocalizationProvider>
|
||||
</Grid>
|
||||
|
||||
{/* Botão Mais Filtros - inline com filtros primários */}
|
||||
<Grid
|
||||
size={{ xs: 12, sm: 12, md: 2.5 }}
|
||||
sx={{ display: 'flex', alignItems: 'flex-end', justifyContent: { xs: 'flex-start', md: 'flex-end' } }}
|
||||
>
|
||||
<Badge
|
||||
badgeContent={advancedFiltersCount}
|
||||
color="primary"
|
||||
invisible={advancedFiltersCount === 0}
|
||||
>
|
||||
<Button
|
||||
size="small"
|
||||
onClick={() => setShowAdvancedFilters(!showAdvancedFilters)}
|
||||
endIcon={
|
||||
showAdvancedFilters ? <ExpandLessIcon /> : <ExpandMoreIcon />
|
||||
}
|
||||
aria-label={showAdvancedFilters ? 'Ocultar filtros avançados' : 'Mostrar filtros avançados'}
|
||||
sx={{ textTransform: 'none', color: 'text.secondary', minHeight: 40 }}
|
||||
>
|
||||
{showAdvancedFilters ? 'Menos filtros' : 'Mais filtros'}
|
||||
</Button>
|
||||
</Badge>
|
||||
</Grid>
|
||||
|
||||
{/* Botões de Ação - nova linha abaixo */}
|
||||
<Grid
|
||||
size={{ xs: 12 }}
|
||||
sx={{ display: 'flex', justifyContent: 'flex-end', mt: 1 }}
|
||||
>
|
||||
<Box sx={{ display: 'flex', gap: 1, width: { xs: '100%', sm: 'auto' }, flexWrap: 'nowrap' }}>
|
||||
<Tooltip title="Limpar filtros" arrow>
|
||||
<span>
|
||||
{/* Ações */}
|
||||
<Grid size={{ xs: 12, md: 5 }} sx={{ display: 'flex', justifyContent: 'flex-end', gap: 1 }}>
|
||||
<Button
|
||||
variant="outlined"
|
||||
color="inherit"
|
||||
size="small"
|
||||
onClick={handleReset}
|
||||
aria-label="Limpar filtros"
|
||||
sx={{
|
||||
minWidth: { xs: 'auto', sm: 90 },
|
||||
minHeight: 32,
|
||||
px: 1.5,
|
||||
flexShrink: 0,
|
||||
flex: { xs: 1, sm: 'none' },
|
||||
fontSize: '0.8125rem',
|
||||
'&:hover': {
|
||||
bgcolor: 'action.hover',
|
||||
},
|
||||
}}
|
||||
startIcon={<ResetIcon />}
|
||||
sx={{ textTransform: 'none', minWidth: 90 }}
|
||||
>
|
||||
<ResetIcon sx={{ mr: 0.5, fontSize: 18 }} />
|
||||
Limpar
|
||||
</Button>
|
||||
</span>
|
||||
</Tooltip>
|
||||
<Tooltip
|
||||
title={
|
||||
isDateValid
|
||||
? 'Buscar pedidos'
|
||||
: 'Preencha a data inicial para buscar'
|
||||
}
|
||||
arrow
|
||||
|
||||
<Badge badgeContent={activeDrawerFiltersCount} color="primary">
|
||||
<Button
|
||||
variant="outlined"
|
||||
color="primary"
|
||||
size="small"
|
||||
onClick={toggleDrawer(true)}
|
||||
startIcon={<TuneIcon />}
|
||||
sx={{ textTransform: 'none', minWidth: 100 }}
|
||||
>
|
||||
<span>
|
||||
Filtros
|
||||
</Button>
|
||||
</Badge>
|
||||
|
||||
<Button
|
||||
variant="contained"
|
||||
color="primary"
|
||||
size="small"
|
||||
onClick={handleFilter}
|
||||
disabled={!isDateValid || !!dateError || isFetching}
|
||||
aria-label="Buscar pedidos"
|
||||
sx={{
|
||||
minWidth: { xs: 'auto', sm: 90 },
|
||||
minHeight: 32,
|
||||
px: 1.5,
|
||||
flexShrink: 0,
|
||||
flex: { xs: 1, sm: 'none' },
|
||||
fontSize: '0.8125rem',
|
||||
'&:disabled': {
|
||||
opacity: 0.6,
|
||||
},
|
||||
startIcon={isFetching ? <CircularProgress size={16} color="inherit" /> : <SearchIcon />}
|
||||
sx={{ textTransform: 'none', minWidth: 100 }}
|
||||
>
|
||||
Buscar
|
||||
</Button>
|
||||
</Grid>
|
||||
</Grid>
|
||||
|
||||
{/* Sidebar de Filtros */}
|
||||
<Drawer
|
||||
anchor="right"
|
||||
open={showAdvancedFilters}
|
||||
onClose={toggleDrawer(false)}
|
||||
PaperProps={{
|
||||
sx: { width: { xs: '100%', sm: 400 }, p: 0 }
|
||||
}}
|
||||
>
|
||||
{isFetching ? (
|
||||
<CircularProgress size={16} color="inherit" />
|
||||
) : (
|
||||
<>
|
||||
<SearchIcon sx={{ mr: 0.5, fontSize: 18 }} />
|
||||
Buscar
|
||||
</>
|
||||
)}
|
||||
</Button>
|
||||
</span>
|
||||
</Tooltip>
|
||||
<Box sx={{ display: 'flex', flexDirection: 'column', height: '100%' }}>
|
||||
{/* Header */}
|
||||
<Box sx={{ p: 2, display: 'flex', alignItems: 'center', justifyContent: 'space-between', borderBottom: '1px solid', borderColor: 'divider' }}>
|
||||
<Typography variant="h6" sx={{ fontWeight: 600 }}>Todos os Filtros</Typography>
|
||||
<IconButton onClick={toggleDrawer(false)} size="small">
|
||||
<CloseIcon />
|
||||
</IconButton>
|
||||
</Box>
|
||||
</Grid>
|
||||
|
||||
{/* --- Advanced Filters (Collapsible) --- */}
|
||||
<Grid size={{ xs: 12 }}>
|
||||
<Collapse in={showAdvancedFilters}>
|
||||
<Grid container spacing={2} sx={{ pt: 2 }}>
|
||||
{/* Autocomplete do MUI para Múltiplas Filiais (codfilial) */}
|
||||
<Grid size={{ xs: 12, sm: 6, md: 3 }}>
|
||||
{/* Filtros Content */}
|
||||
<Box sx={{ p: 3, flex: 1, overflowY: 'auto' }}>
|
||||
<Stack spacing={3}>
|
||||
{/* Situação */}
|
||||
<TextField
|
||||
select
|
||||
fullWidth
|
||||
label="Situação"
|
||||
size="small"
|
||||
value={localFilters.status ?? ''}
|
||||
onChange={(e) => updateLocalFilter('status', e.target.value || null)}
|
||||
>
|
||||
<MenuItem value="">Todos</MenuItem>
|
||||
<MenuItem value="P">Pendente</MenuItem>
|
||||
<MenuItem value="F">Faturado</MenuItem>
|
||||
<MenuItem value="C">Cancelado</MenuItem>
|
||||
</TextField>
|
||||
|
||||
{/* Cliente */}
|
||||
<Autocomplete
|
||||
size="small"
|
||||
options={customers.options}
|
||||
getOptionLabel={(option) => option.label}
|
||||
isOptionEqualToValue={(option, value) => option.id === value.id}
|
||||
value={customers.options.find(opt => localFilters.customerId === opt.customer?.id) || null}
|
||||
onChange={(_, newValue) => {
|
||||
updateLocalFilter('customerId', newValue?.customer?.id || null);
|
||||
updateLocalFilter('customerName', newValue?.customer?.name || null);
|
||||
}}
|
||||
onInputChange={(_, val) => setCustomerSearchTerm(val)}
|
||||
loading={customers.isLoading}
|
||||
renderInput={(params) => <TextField {...params} label="Cliente" placeholder="Buscar cliente..." />}
|
||||
noOptionsText={customerSearchTerm.length < 2 ? 'Digite 2+ caracteres' : 'Nenhum cliente'}
|
||||
filterOptions={(x) => x}
|
||||
/>
|
||||
|
||||
{/* Filiais */}
|
||||
<Autocomplete
|
||||
multiple
|
||||
size="small"
|
||||
options={stores.options}
|
||||
getOptionLabel={(option) => option.label}
|
||||
isOptionEqualToValue={(option, value) =>
|
||||
option.id === value.id
|
||||
}
|
||||
value={stores.options.filter((option) =>
|
||||
localFilters.store?.includes(option.value)
|
||||
)}
|
||||
onChange={(_, newValue) => {
|
||||
updateLocalFilter(
|
||||
'store',
|
||||
newValue.map((option) => option.value)
|
||||
);
|
||||
}}
|
||||
isOptionEqualToValue={(option, value) => option.id === value.id}
|
||||
value={stores.options.filter((opt) => localFilters.store?.includes(opt.value))}
|
||||
onChange={(_, newValue) => updateLocalFilter('store', newValue.map((opt) => opt.value))}
|
||||
loading={stores.isLoading}
|
||||
renderInput={(params: AutocompleteRenderInputParams) => (
|
||||
<TextField
|
||||
{...params}
|
||||
label="Filiais"
|
||||
placeholder={
|
||||
localFilters.store?.length
|
||||
? `${localFilters.store.length} selecionadas`
|
||||
: 'Selecione'
|
||||
}
|
||||
renderInput={(params) => <TextField {...params} label="Filiais" placeholder="Selecione filiais" />}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</Grid>
|
||||
|
||||
{/* Autocomplete do MUI para Filial de Estoque */}
|
||||
<Grid size={{ xs: 12, sm: 6, md: 3 }}>
|
||||
{/* Filial de Estoque */}
|
||||
<Autocomplete
|
||||
multiple
|
||||
size="small"
|
||||
options={stores.options}
|
||||
getOptionLabel={(option) => option.label}
|
||||
isOptionEqualToValue={(option, value) =>
|
||||
option.id === value.id
|
||||
}
|
||||
value={stores.options.filter((option) =>
|
||||
localFilters.stockId?.includes(option.value)
|
||||
)}
|
||||
onChange={(_, newValue) => {
|
||||
updateLocalFilter(
|
||||
'stockId',
|
||||
newValue.map((option) => option.value)
|
||||
);
|
||||
}}
|
||||
isOptionEqualToValue={(option, value) => option.id === value.id}
|
||||
value={stores.options.filter((opt) => localFilters.stockId?.includes(opt.value))}
|
||||
onChange={(_, newValue) => updateLocalFilter('stockId', newValue.map((opt) => opt.value))}
|
||||
loading={stores.isLoading}
|
||||
renderInput={(params: AutocompleteRenderInputParams) => (
|
||||
<TextField
|
||||
{...params}
|
||||
label="Filial de Estoque"
|
||||
placeholder={
|
||||
localFilters.stockId?.length
|
||||
? `${localFilters.stockId.length} selecionadas`
|
||||
: 'Selecione'
|
||||
}
|
||||
renderInput={(params) => <TextField {...params} label="Filial de Estoque" placeholder="Selecione filial" />}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</Grid>
|
||||
|
||||
{/* Autocomplete do MUI para Vendedor */}
|
||||
<Grid size={{ xs: 12, sm: 6, md: 3 }}>
|
||||
{/* Vendedor */}
|
||||
<Autocomplete
|
||||
size="small"
|
||||
options={sellers.options}
|
||||
getOptionLabel={(option) => option.label}
|
||||
isOptionEqualToValue={(option, value) =>
|
||||
option.id === value.id
|
||||
}
|
||||
value={
|
||||
sellers.options.find(
|
||||
(option) =>
|
||||
localFilters.sellerId === option.seller.id.toString()
|
||||
) || null
|
||||
}
|
||||
value={sellers.options.find(opt => localFilters.sellerId === opt.seller.id.toString()) || null}
|
||||
onChange={(_, newValue) => {
|
||||
updateLocalFilter(
|
||||
'sellerId',
|
||||
newValue?.seller.id.toString() || null
|
||||
);
|
||||
updateLocalFilter(
|
||||
'sellerName',
|
||||
newValue?.seller.name || null
|
||||
);
|
||||
updateLocalFilter('sellerId', newValue?.seller.id.toString() || null);
|
||||
updateLocalFilter('sellerName', newValue?.seller.name || null);
|
||||
}}
|
||||
loading={sellers.isLoading}
|
||||
renderInput={(params) => (
|
||||
<TextField
|
||||
{...params}
|
||||
label="Vendedor"
|
||||
placeholder="Selecione um vendedor"
|
||||
renderInput={(params) => <TextField {...params} label="Vendedor" placeholder="Selecione vendedor" />}
|
||||
/>
|
||||
)}
|
||||
renderOption={(props, option) => {
|
||||
const { key, ...otherProps } = props;
|
||||
return (
|
||||
<li key={option.id} {...otherProps}>
|
||||
{option.label}
|
||||
</li>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
</Grid>
|
||||
|
||||
{/* Autocomplete do MUI para Parceiro */}
|
||||
<Grid size={{ xs: 12, sm: 6, md: 3 }}>
|
||||
{/* Parceiro */}
|
||||
<Autocomplete
|
||||
size="small"
|
||||
options={partners.options}
|
||||
getOptionLabel={(option) => option.label}
|
||||
isOptionEqualToValue={(option, value) => option.id === value.id}
|
||||
value={
|
||||
partners.options.find(
|
||||
(option) =>
|
||||
localFilters.partnerId ===
|
||||
option.partner?.id?.toString()
|
||||
) || null
|
||||
}
|
||||
value={partners.options.find(opt => localFilters.partnerId === opt.partner?.id?.toString()) || null}
|
||||
onChange={(_, newValue) => {
|
||||
if (!newValue) {
|
||||
updateLocalFilter('partnerName', null);
|
||||
updateLocalFilter('partnerId', null);
|
||||
setPartnerSearchTerm('');
|
||||
return;
|
||||
}
|
||||
|
||||
updateLocalFilter(
|
||||
'partnerId',
|
||||
newValue.partner?.id?.toString() || null
|
||||
);
|
||||
updateLocalFilter(
|
||||
'partnerName',
|
||||
newValue.partner?.nome || null
|
||||
);
|
||||
}}
|
||||
onInputChange={(_, newInputValue, reason) => {
|
||||
if (reason === 'clear') {
|
||||
updateLocalFilter('partnerName', null);
|
||||
updateLocalFilter('partnerId', null);
|
||||
setPartnerSearchTerm('');
|
||||
return;
|
||||
}
|
||||
|
||||
if (reason === 'input') {
|
||||
setPartnerSearchTerm(newInputValue);
|
||||
if (!newInputValue) {
|
||||
updateLocalFilter('partnerName', null);
|
||||
updateLocalFilter('partnerId', null);
|
||||
setPartnerSearchTerm('');
|
||||
}
|
||||
}
|
||||
updateLocalFilter('partnerId', newValue?.partner?.id?.toString() || null);
|
||||
updateLocalFilter('partnerName', newValue?.partner?.nome || null);
|
||||
}}
|
||||
onInputChange={(_, val) => setPartnerSearchTerm(val)}
|
||||
loading={partners.isLoading}
|
||||
renderInput={(params: AutocompleteRenderInputParams) => (
|
||||
<TextField
|
||||
{...params}
|
||||
label="Parceiro"
|
||||
placeholder="Digite para buscar..."
|
||||
/>
|
||||
)}
|
||||
noOptionsText={
|
||||
partnerSearchTerm.length < 2
|
||||
? 'Digite pelo menos 2 caracteres'
|
||||
: 'Nenhum parceiro encontrado'
|
||||
}
|
||||
loadingText="Buscando parceiros..."
|
||||
renderInput={(params) => <TextField {...params} label="Parceiro" placeholder="Buscar parceiro..." />}
|
||||
noOptionsText={partnerSearchTerm.length < 2 ? 'Digite 2+ caracteres' : 'Nenhum parceiro'}
|
||||
filterOptions={(x) => x}
|
||||
clearOnBlur={false}
|
||||
selectOnFocus
|
||||
handleHomeEndKeys
|
||||
/>
|
||||
</Grid>
|
||||
|
||||
{/* Autocomplete do MUI para Produto */}
|
||||
<Grid size={{ xs: 12, sm: 12, md: 6 }}>
|
||||
{/* Produto */}
|
||||
<Autocomplete
|
||||
size="small"
|
||||
options={products.options}
|
||||
getOptionLabel={(option) => option.label}
|
||||
isOptionEqualToValue={(option, value) => option.id === value.id}
|
||||
value={
|
||||
products.options.find(
|
||||
(option) => localFilters.productId === option.product?.id
|
||||
) || null
|
||||
}
|
||||
value={products.options.find(opt => localFilters.productId === opt.product?.id) || null}
|
||||
onChange={(_, newValue) => {
|
||||
if (!newValue) {
|
||||
updateLocalFilter('productName', null);
|
||||
updateLocalFilter('productId', null);
|
||||
setProductSearchTerm('');
|
||||
return;
|
||||
}
|
||||
|
||||
updateLocalFilter('productId', newValue.product?.id || null);
|
||||
updateLocalFilter(
|
||||
'productName',
|
||||
newValue.product?.description || null
|
||||
);
|
||||
}}
|
||||
onInputChange={(_, newInputValue, reason) => {
|
||||
if (reason === 'clear') {
|
||||
updateLocalFilter('productName', null);
|
||||
updateLocalFilter('productId', null);
|
||||
setProductSearchTerm('');
|
||||
return;
|
||||
}
|
||||
|
||||
if (reason === 'input') {
|
||||
setProductSearchTerm(newInputValue);
|
||||
if (!newInputValue) {
|
||||
updateLocalFilter('productName', null);
|
||||
updateLocalFilter('productId', null);
|
||||
setProductSearchTerm('');
|
||||
}
|
||||
}
|
||||
updateLocalFilter('productId', newValue?.product?.id || null);
|
||||
updateLocalFilter('productName', newValue?.product?.description || null);
|
||||
}}
|
||||
onInputChange={(_, val) => setProductSearchTerm(val)}
|
||||
loading={products.isLoading}
|
||||
renderInput={(params: AutocompleteRenderInputParams) => (
|
||||
<TextField
|
||||
{...params}
|
||||
label="Produto"
|
||||
placeholder="Busque por nome ou código..."
|
||||
/>
|
||||
)}
|
||||
noOptionsText={
|
||||
productSearchTerm.length < 2
|
||||
? 'Digite pelo menos 2 caracteres'
|
||||
: 'Nenhum produto encontrado'
|
||||
}
|
||||
loadingText="Buscando produtos..."
|
||||
renderInput={(params) => <TextField {...params} label="Produto" placeholder="Buscar produto..." />}
|
||||
noOptionsText={productSearchTerm.length < 2 ? 'Digite 2+ caracteres' : 'Nenhum produto'}
|
||||
filterOptions={(x) => x}
|
||||
clearOnBlur={false}
|
||||
selectOnFocus
|
||||
handleHomeEndKeys
|
||||
/>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Collapse>
|
||||
</Grid>
|
||||
</Grid>
|
||||
|
||||
</Stack>
|
||||
</Box>
|
||||
|
||||
{/* Footer */}
|
||||
<Box sx={{ p: 2, borderTop: '1px solid', borderColor: 'divider', display: 'flex', gap: 2 }}>
|
||||
<Button fullWidth variant="outlined" color="inherit" onClick={handleReset}>Limpar</Button>
|
||||
<Button fullWidth variant="contained" color="primary" onClick={() => { handleFilter(); setShowAdvancedFilters(false); }}>Aplicar</Button>
|
||||
</Box>
|
||||
</Box>
|
||||
</Drawer>
|
||||
</Paper>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
Loading…
Reference in New Issue