feat(orders): implement manual search and information panel

- Refactor SearchBar to use local state (search only triggers on button click)
- Add InformationPanel with DataGrid displaying order details
- Create shared dataGridStyles for consistent table styling
- Simplify OrderItemsTable using shared styles
- Add useOrderDetails hook for fetching order by ID
- Translate status codes to readable text (F->Faturado, C->Cancelado, P->Pendente)
This commit is contained in:
JuruSysadmin 2026-01-14 15:18:51 -03:00
parent cf7e56e2b7
commit 544d723f1b
6 changed files with 443 additions and 70 deletions

View File

@ -1,6 +1,6 @@
'use client'; 'use client';
import { useState, useCallback } from 'react'; import { useState, useCallback, useEffect } from 'react';
import { useOrderFilters } from '../hooks/useOrderFilters'; import { useOrderFilters } from '../hooks/useOrderFilters';
import { import {
Box, Box,
@ -30,8 +30,40 @@ import 'moment/locale/pt-br';
moment.locale('pt-br'); moment.locale('pt-br');
// Tipo para os filtros locais (não precisam da flag searchTriggered)
interface LocalFilters {
status: string | null;
orderId: number | null;
customerId: number | null;
customerName: string | null;
createDateIni: string | null;
createDateEnd: string | null;
store: string[] | null;
stockId: string[] | null;
sellerId: string | null;
sellerName: string | null;
}
const getInitialLocalFilters = (urlFilters: any): LocalFilters => ({
status: urlFilters.status ?? null,
orderId: urlFilters.orderId ?? null,
customerId: urlFilters.customerId ?? null,
customerName: urlFilters.customerName ?? null,
createDateIni: urlFilters.createDateIni ?? null,
createDateEnd: urlFilters.createDateEnd ?? null,
store: urlFilters.store ?? null,
stockId: urlFilters.stockId ?? null,
sellerId: urlFilters.sellerId ?? null,
sellerName: urlFilters.sellerName ?? null,
});
export const SearchBar = () => { export const SearchBar = () => {
const [filters, setFilters] = useOrderFilters(); const [urlFilters, setUrlFilters] = useOrderFilters();
// Estado local para inputs (não dispara busca ao mudar)
const [localFilters, setLocalFilters] = useState<LocalFilters>(() =>
getInitialLocalFilters(urlFilters)
);
const stores = useStores(); const stores = useStores();
const sellers = useSellers(); const sellers = useSellers();
@ -43,6 +75,18 @@ export const SearchBar = () => {
createDateEnd?: boolean; createDateEnd?: boolean;
}>({}); }>({});
// Sync local state com URL (para navegação Back/Forward)
useEffect(() => {
setLocalFilters(getInitialLocalFilters(urlFilters));
}, [urlFilters]);
const updateLocalFilter = useCallback(<K extends keyof LocalFilters>(
key: K,
value: LocalFilters[K]
) => {
setLocalFilters(prev => ({ ...prev, [key]: value }));
}, []);
const handleReset = useCallback(() => { const handleReset = useCallback(() => {
setTouchedFields({}); setTouchedFields({});
setCustomerSearchTerm(''); setCustomerSearchTerm('');
@ -66,23 +110,25 @@ export const SearchBar = () => {
customerName: null, customerName: null,
}; };
setFilters(resetState); // Reset local + URL
}, [setFilters]); setLocalFilters(getInitialLocalFilters(resetState));
setUrlFilters(resetState);
}, [setUrlFilters]);
const validateDates = useCallback(() => { const validateDates = useCallback(() => {
if (!filters.createDateIni || !filters.createDateEnd) { if (!localFilters.createDateIni || !localFilters.createDateEnd) {
return null; return null;
} }
const dateIni = moment(filters.createDateIni, 'YYYY-MM-DD'); const dateIni = moment(localFilters.createDateIni, 'YYYY-MM-DD');
const dateEnd = moment(filters.createDateEnd, 'YYYY-MM-DD'); const dateEnd = moment(localFilters.createDateEnd, 'YYYY-MM-DD');
if (dateEnd.isBefore(dateIni)) { if (dateEnd.isBefore(dateIni)) {
return 'Data final não pode ser anterior à data inicial'; return 'Data final não pode ser anterior à data inicial';
} }
return null; return null;
}, [filters.createDateIni, filters.createDateEnd]); }, [localFilters.createDateIni, localFilters.createDateEnd]);
const handleFilter = useCallback(() => { const handleFilter = useCallback(() => {
if (!filters.createDateIni) { if (!localFilters.createDateIni) {
setTouchedFields(prev => ({ ...prev, createDateIni: true })); setTouchedFields(prev => ({ ...prev, createDateIni: true }));
return; return;
} }
@ -93,25 +139,26 @@ export const SearchBar = () => {
return; return;
} }
setFilters({ // Commit local filters to URL (triggers search)
...filters, setUrlFilters({
...localFilters,
searchTriggered: true, searchTriggered: true,
}); });
}, [filters, setFilters, validateDates]); }, [localFilters, setUrlFilters, validateDates]);
const handleKeyDown = useCallback((e: React.KeyboardEvent) => { const handleKeyDown = useCallback((e: React.KeyboardEvent) => {
if (e.key === 'Enter') { if (e.key === 'Enter') {
const isValid = !!filters.createDateIni; const isValid = !!localFilters.createDateIni;
const dateErr = validateDates(); const dateErr = validateDates();
if (isValid && !dateErr) { if (isValid && !dateErr) {
handleFilter(); handleFilter();
} }
} }
}, [filters.createDateIni, validateDates, handleFilter]); }, [localFilters.createDateIni, validateDates, handleFilter]);
const isDateValid = !!filters.createDateIni; const isDateValid = !!localFilters.createDateIni;
const dateError = validateDates(); const dateError = validateDates();
const showDateIniError = touchedFields.createDateIni && !filters.createDateIni; const showDateIniError = touchedFields.createDateIni && !localFilters.createDateIni;
const showDateEndError = touchedFields.createDateEnd && dateError; const showDateEndError = touchedFields.createDateEnd && dateError;
return ( return (
@ -138,10 +185,10 @@ export const SearchBar = () => {
variant="outlined" variant="outlined"
size="small" size="small"
type="number" type="number"
value={filters.orderId ?? ''} value={localFilters.orderId ?? ''}
onChange={(e) => { onChange={(e) => {
const value = e.target.value ? Number(e.target.value) : null; const value = e.target.value ? Number(e.target.value) : null;
setFilters({ orderId: value }); updateLocalFilter('orderId', value);
}} }}
slotProps={{ htmlInput: { min: 0 } }} slotProps={{ htmlInput: { min: 0 } }}
placeholder="Ex: 12345" placeholder="Ex: 12345"
@ -155,10 +202,10 @@ export const SearchBar = () => {
fullWidth fullWidth
label="Situação" label="Situação"
size="small" size="small"
value={filters.status ?? ''} value={localFilters.status ?? ''}
onChange={(e) => { onChange={(e) => {
const value = e.target.value || null; const value = e.target.value || null;
setFilters({ status: value }); updateLocalFilter('status', value);
}} }}
> >
<MenuItem value="">Todos</MenuItem> <MenuItem value="">Todos</MenuItem>
@ -176,29 +223,23 @@ export const SearchBar = () => {
getOptionLabel={(option) => option.label} getOptionLabel={(option) => option.label}
isOptionEqualToValue={(option, value) => option.id === value.id} isOptionEqualToValue={(option, value) => option.id === value.id}
value={customers.options.find(option => value={customers.options.find(option =>
filters.customerId === option.customer?.id localFilters.customerId === option.customer?.id
) || null} ) || null}
onChange={(_, newValue) => { onChange={(_, newValue) => {
if (!newValue) { if (!newValue) {
setFilters({ updateLocalFilter('customerName', null);
customerName: null, updateLocalFilter('customerId', null);
customerId: null,
});
setCustomerSearchTerm(''); setCustomerSearchTerm('');
return; return;
} }
setFilters({ updateLocalFilter('customerId', newValue.customer?.id || null);
customerId: newValue.customer?.id || null, updateLocalFilter('customerName', newValue.customer?.name || null);
customerName: newValue.customer?.name || null,
});
}} }}
onInputChange={(_, newInputValue, reason) => { onInputChange={(_, newInputValue, reason) => {
if (reason === 'clear') { if (reason === 'clear') {
setFilters({ updateLocalFilter('customerName', null);
customerName: null, updateLocalFilter('customerId', null);
customerId: null,
});
setCustomerSearchTerm(''); setCustomerSearchTerm('');
return; return;
} }
@ -206,10 +247,8 @@ export const SearchBar = () => {
if (reason === 'input') { if (reason === 'input') {
setCustomerSearchTerm(newInputValue); setCustomerSearchTerm(newInputValue);
if (!newInputValue) { if (!newInputValue) {
setFilters({ updateLocalFilter('customerName', null);
customerName: null, updateLocalFilter('customerId', null);
customerId: null,
});
setCustomerSearchTerm(''); setCustomerSearchTerm('');
} }
} }
@ -238,15 +277,13 @@ export const SearchBar = () => {
<Box flex={1}> <Box flex={1}>
<DatePicker <DatePicker
label="Data Inicial" label="Data Inicial"
value={filters.createDateIni ? moment(filters.createDateIni, 'YYYY-MM-DD') : null} value={localFilters.createDateIni ? moment(localFilters.createDateIni, 'YYYY-MM-DD') : null}
onChange={(date: moment.Moment | null) => { onChange={(date: moment.Moment | null) => {
setTouchedFields(prev => ({ ...prev, createDateIni: true })); setTouchedFields(prev => ({ ...prev, createDateIni: true }));
setFilters({ updateLocalFilter('createDateIni', date ? date.format('YYYY-MM-DD') : null);
createDateIni: date ? date.format('YYYY-MM-DD') : null,
});
}} }}
format="DD/MM/YYYY" format="DD/MM/YYYY"
maxDate={filters.createDateEnd ? moment(filters.createDateEnd, 'YYYY-MM-DD') : undefined} maxDate={localFilters.createDateEnd ? moment(localFilters.createDateEnd, 'YYYY-MM-DD') : undefined}
slotProps={{ slotProps={{
textField: { textField: {
size: 'small', size: 'small',
@ -265,15 +302,13 @@ export const SearchBar = () => {
<Box flex={1}> <Box flex={1}>
<DatePicker <DatePicker
label="Data Final" label="Data Final"
value={filters.createDateEnd ? moment(filters.createDateEnd, 'YYYY-MM-DD') : null} value={localFilters.createDateEnd ? moment(localFilters.createDateEnd, 'YYYY-MM-DD') : null}
onChange={(date: moment.Moment | null) => { onChange={(date: moment.Moment | null) => {
setTouchedFields(prev => ({ ...prev, createDateEnd: true })); setTouchedFields(prev => ({ ...prev, createDateEnd: true }));
setFilters({ updateLocalFilter('createDateEnd', date ? date.format('YYYY-MM-DD') : null);
createDateEnd: date ? date.format('YYYY-MM-DD') : null,
});
}} }}
format="DD/MM/YYYY" format="DD/MM/YYYY"
minDate={filters.createDateIni ? moment(filters.createDateIni, 'YYYY-MM-DD') : undefined} minDate={localFilters.createDateIni ? moment(localFilters.createDateIni, 'YYYY-MM-DD') : undefined}
slotProps={{ slotProps={{
textField: { textField: {
size: 'small', size: 'small',
@ -361,19 +396,17 @@ export const SearchBar = () => {
getOptionLabel={(option) => option.label} getOptionLabel={(option) => option.label}
isOptionEqualToValue={(option, value) => option.id === value.id} isOptionEqualToValue={(option, value) => option.id === value.id}
value={stores.options.filter(option => value={stores.options.filter(option =>
filters.store?.includes(option.value) localFilters.store?.includes(option.value)
)} )}
onChange={(_, newValue) => { onChange={(_, newValue) => {
setFilters({ updateLocalFilter('store', newValue.map(option => option.value));
store: newValue.map(option => option.value),
});
}} }}
loading={stores.isLoading} loading={stores.isLoading}
renderInput={(params: Readonly<any>) => ( renderInput={(params: Readonly<any>) => (
<TextField <TextField
{...params} {...params}
label="Filiais" label="Filiais"
placeholder={filters.store?.length ? `${filters.store.length} selecionadas` : 'Selecione'} placeholder={localFilters.store?.length ? `${localFilters.store.length} selecionadas` : 'Selecione'}
/> />
)} )}
/> />
@ -388,19 +421,17 @@ export const SearchBar = () => {
getOptionLabel={(option) => option.label} getOptionLabel={(option) => option.label}
isOptionEqualToValue={(option, value) => option.id === value.id} isOptionEqualToValue={(option, value) => option.id === value.id}
value={stores.options.filter(option => value={stores.options.filter(option =>
filters.stockId?.includes(option.value) localFilters.stockId?.includes(option.value)
)} )}
onChange={(_, newValue) => { onChange={(_, newValue) => {
setFilters({ updateLocalFilter('stockId', newValue.map(option => option.value));
stockId: newValue.map(option => option.value),
});
}} }}
loading={stores.isLoading} loading={stores.isLoading}
renderInput={(params: Readonly<any>) => ( renderInput={(params: Readonly<any>) => (
<TextField <TextField
{...params} {...params}
label="Filial de Estoque" label="Filial de Estoque"
placeholder={filters.stockId?.length ? `${filters.stockId.length} selecionadas` : 'Selecione'} placeholder={localFilters.stockId?.length ? `${localFilters.stockId.length} selecionadas` : 'Selecione'}
/> />
)} )}
/> />
@ -414,13 +445,11 @@ export const SearchBar = () => {
getOptionLabel={(option) => option.label} getOptionLabel={(option) => option.label}
isOptionEqualToValue={(option, value) => option.id === value.id} isOptionEqualToValue={(option, value) => option.id === value.id}
value={sellers.options.find(option => value={sellers.options.find(option =>
filters.sellerId === option.seller.id.toString() localFilters.sellerId === option.seller.id.toString()
) || null} ) || null}
onChange={(_, newValue) => { onChange={(_, newValue) => {
setFilters({ updateLocalFilter('sellerId', newValue?.seller.id.toString() || null);
sellerId: newValue?.seller.id.toString() || null, updateLocalFilter('sellerName', newValue?.seller.name || null);
sellerName: newValue?.seller.name || null,
});
}} }}
loading={sellers.isLoading} loading={sellers.isLoading}
renderInput={(params) => ( renderInput={(params) => (

View File

@ -1,21 +1,63 @@
'use client'; 'use client';
import { useMemo } from 'react';
import Box from '@mui/material/Box'; import Box from '@mui/material/Box';
import CircularProgress from '@mui/material/CircularProgress';
import Alert from '@mui/material/Alert'; import Alert from '@mui/material/Alert';
import Typography from '@mui/material/Typography'; import { DataGridPremium } from '@mui/x-data-grid-premium';
import { useOrderDetails } from '../../hooks/useOrderDetails';
import { createInformationPanelColumns } from './InformationPanelColumns';
import { dataGridStylesSimple } from '../../utils/dataGridStyles';
interface InformationPanelProps { interface InformationPanelProps {
orderId: number; orderId: number;
} }
export const InformationPanel = ({ orderId }: InformationPanelProps) => { export const InformationPanel = ({ orderId }: InformationPanelProps) => {
const { data: order, isLoading, error } = useOrderDetails(orderId);
const columns = useMemo(() => createInformationPanelColumns(), []);
const rows = useMemo(() => {
if (!order) return [];
return [{
id: order.orderId || orderId,
...order
}];
}, [order, orderId]);
if (isLoading) {
return (
<Box sx={{ display: 'flex', justifyContent: 'center', py: 4 }}>
<CircularProgress size={30} />
</Box>
);
}
if (error) {
return (
<Box sx={{ p: 2 }}>
<Alert severity="error">Erro ao carregar detalhes do pedido.</Alert>
</Box>
);
}
if (!order) {
return (
<Box sx={{ p: 2 }}>
<Alert severity="info">Informações do pedido não encontradas.</Alert>
</Box>
);
}
return ( return (
<Box> <DataGridPremium
<Alert severity="info" sx={{ mb: 2 }}> rows={rows}
<Typography variant="body2"> columns={columns}
Funcionalidade em desenvolvimento para o pedido {orderId} density="compact"
</Typography> autoHeight
</Alert> hideFooter
</Box> sx={dataGridStylesSimple}
/>
); );
}; };

View File

@ -0,0 +1,91 @@
import { GridColDef, GridRenderCellParams } from '@mui/x-data-grid-premium';
import Chip from '@mui/material/Chip';
import { formatCurrency, formatDate, getStatusColor } from '../../utils/orderFormatters';
/**
* Mapeia códigos de status para texto legível.
*/
const STATUS_LABELS: Record<string, string> = {
'F': 'Faturado',
'C': 'Cancelado',
'P': 'Pendente',
};
const getStatusLabel = (status: string): string => STATUS_LABELS[status] || status;
export const createInformationPanelColumns = (): GridColDef[] => [
{
field: 'customerName',
headerName: 'Cliente',
width: 250,
description: 'Nome do cliente do pedido',
},
{
field: 'storeId',
headerName: 'Filial',
width: 80,
align: 'center',
headerAlign: 'center',
description: 'Código da filial',
},
{
field: 'createDate',
headerName: 'Data Criação',
width: 110,
align: 'center',
headerAlign: 'center',
description: 'Data de criação do pedido',
valueFormatter: (value) => formatDate(value as string),
},
{
field: 'status',
headerName: 'Situação',
width: 120,
align: 'center',
headerAlign: 'center',
description: 'Situação atual do pedido',
renderCell: (params: Readonly<GridRenderCellParams>) => (
<Chip
label={getStatusLabel(params.value as string)}
size="small"
color={getStatusColor(params.value as string)}
variant="outlined"
sx={{ height: 24 }}
/>
),
},
{
field: 'paymentName',
headerName: 'Forma Pagamento',
width: 150,
description: 'Forma de pagamento utilizada',
},
{
field: 'billingName',
headerName: 'Cond. Pagamento',
width: 150,
description: 'Condição de pagamento',
},
{
field: 'amount',
headerName: 'Valor Total',
width: 120,
align: 'right',
headerAlign: 'right',
description: 'Valor total do pedido',
valueFormatter: (value) => formatCurrency(value as number),
},
{
field: 'deliveryType',
headerName: 'Tipo Entrega',
width: 150,
description: 'Tipo de entrega selecionado',
},
{
field: 'deliveryLocal',
headerName: 'Local Entrega',
width: 200,
flex: 1,
description: 'Local de entrega do pedido',
},
];

View File

@ -0,0 +1,99 @@
'use client';
import { useMemo } from 'react';
import Box from '@mui/material/Box';
import Typography from '@mui/material/Typography';
import CircularProgress from '@mui/material/CircularProgress';
import Alert from '@mui/material/Alert';
import { DataGridPremium } from '@mui/x-data-grid-premium';
import { useOrderItems } from '../../hooks/useOrderItems';
import { createOrderItemsColumns } from '../OrderItemsTableColumns';
import { OrderItem } from '../../schemas/order.item.schema';
import { dataGridStyles } from '../../utils/dataGridStyles';
interface OrderItemsTableProps {
orderId: number;
}
export const OrderItemsTable = ({ orderId }: OrderItemsTableProps) => {
const { data: items, isLoading, error } = useOrderItems(orderId);
const columns = useMemo(() => createOrderItemsColumns(), []);
const rows = useMemo(() => {
if (!Array.isArray(items) || items.length === 0) return [];
return items.map((item: OrderItem, index: number) => ({
id: `${orderId}-${item.productId}-${index}`,
...item,
}));
}, [items, orderId]);
if (isLoading) {
return (
<Box sx={{ display: 'flex', justifyContent: 'center', alignItems: 'center', py: 4 }}>
<CircularProgress size={40} />
<Typography variant="body2" color="text.secondary" sx={{ ml: 2 }}>
Carregando itens do pedido...
</Typography>
</Box>
);
}
if (error) {
return (
<Box sx={{ mt: 2 }}>
<Alert severity="error">
{error instanceof Error
? `Erro ao carregar itens: ${error.message}`
: 'Erro ao carregar itens do pedido.'}
</Alert>
</Box>
);
}
if (!items || items.length === 0) {
return (
<Box sx={{ mt: 2 }}>
<Alert severity="info">Nenhum item encontrado para este pedido.</Alert>
</Box>
);
}
return (
<DataGridPremium
rows={rows}
columns={columns}
density="compact"
autoHeight
hideFooter={rows.length <= 10}
initialState={{
pagination: {
paginationModel: {
pageSize: 10,
page: 0,
},
},
}}
pageSizeOptions={[10, 25, 50]}
sx={dataGridStyles}
localeText={{
noRowsLabel: 'Nenhum item encontrado.',
noResultsOverlayLabel: 'Nenhum resultado encontrado.',
footerTotalRows: 'Total de itens:',
footerTotalVisibleRows: (visibleCount, totalCount) =>
`${visibleCount.toLocaleString()} de ${totalCount.toLocaleString()}`,
}}
slotProps={{
pagination: {
labelRowsPerPage: 'Itens por página:',
labelDisplayedRows: ({ from, to, count }: { from: number; to: number; count: number }) => {
const pageSize = to >= from ? to - from + 1 : 10;
const currentPage = Math.floor((from - 1) / pageSize) + 1;
const totalPages = Math.ceil(count / pageSize);
return `${from}${to} de ${count} | Página ${currentPage} de ${totalPages}`;
},
},
}}
/>
);
};

View File

@ -0,0 +1,19 @@
import { useQuery } from '@tanstack/react-query';
import { orderService } from '../api/order.service';
/**
* Hook to fetch details for a specific order.
* Uses the general search endpoint filtering by ID as requested.
*/
export function useOrderDetails(orderId: number) {
return useQuery({
queryKey: ['orderDetails', orderId],
enabled: !!orderId,
queryFn: async () => {
// The findOrders method returns an array. We search by orderId and take the first result.
const orders = await orderService.findOrders({ orderId: orderId });
return orders.length > 0 ? orders[0] : null;
},
staleTime: 1000 * 60 * 5, // 5 minutes
});
}

View File

@ -0,0 +1,93 @@
import { SxProps, Theme } from '@mui/material/styles';
/**
* Estilos compartilhados para DataGridPremium utilizado em tabelas de pedidos.
* Garante consistência visual entre OrderItemsTable, InformationPanel, etc.
*/
export const dataGridStyles: SxProps<Theme> = {
border: '1px solid',
borderColor: 'divider',
fontFamily: '"Roboto", "Helvetica", "Arial", sans-serif',
backgroundColor: 'background.paper',
'& .MuiDataGrid-root': {
fontFamily: '"Roboto", "Helvetica", "Arial", sans-serif',
border: 'none',
},
'& .MuiDataGrid-columnHeaders': {
backgroundColor: 'grey.50',
fontWeight: 600,
fontSize: '0.75rem',
borderBottom: '2px solid',
borderColor: 'divider',
fontFamily: '"Roboto", "Helvetica", "Arial", sans-serif',
minHeight: '40px !important',
maxHeight: '40px !important',
},
'& .MuiDataGrid-columnHeader': {
borderRight: '1px solid',
borderColor: 'divider',
paddingLeft: '12px',
paddingRight: '12px',
'&:focus': { outline: 'none' },
'&:focus-within': { outline: 'none' },
'&:last-of-type': { borderRight: 'none' },
},
'& .MuiDataGrid-row': {
borderBottom: '1px solid',
borderColor: 'divider',
backgroundColor: 'background.paper',
minHeight: '36px !important',
maxHeight: '36px !important',
'&:hover': { backgroundColor: 'action.hover' },
'&:last-child': { borderBottom: 'none' },
},
'& .MuiDataGrid-row:nth-of-type(even)': {
backgroundColor: 'grey.50',
'&:hover': { backgroundColor: 'action.hover' },
},
'& .MuiDataGrid-cell': {
borderRight: '1px solid',
borderColor: 'divider',
borderBottom: 'none',
paddingLeft: '12px',
paddingRight: '12px',
fontFamily: '"Roboto", "Helvetica", "Arial", sans-serif',
fontSize: '0.75rem',
lineHeight: 1.2,
'&:focus': { outline: 'none' },
'&:focus-within': { outline: 'none' },
'&:last-of-type': { borderRight: 'none' },
},
'& .MuiDataGrid-footerContainer': {
borderTop: '2px solid',
borderColor: 'divider',
fontFamily: '"Roboto", "Helvetica", "Arial", sans-serif',
minHeight: '48px !important',
fontSize: '0.75rem',
backgroundColor: 'grey.50',
},
};
/**
* Versão simplificada sem zebra-striping (para tabelas de linha única).
*/
export const dataGridStylesSimple: SxProps<Theme> = {
...dataGridStyles,
'& .MuiDataGrid-row': {
borderBottom: 'none',
backgroundColor: 'background.paper',
minHeight: '36px !important',
maxHeight: '36px !important',
'&:hover': { backgroundColor: 'action.hover' },
},
'& .MuiDataGrid-row:nth-of-type(even)': {
backgroundColor: 'background.paper',
},
};