feat: implement partner filter in orders search
This commit is contained in:
parent
c66bc9620b
commit
275cbfd29c
|
|
@ -15,6 +15,7 @@ import {
|
|||
storesResponseSchema,
|
||||
customersResponseSchema,
|
||||
sellersResponseSchema,
|
||||
partnersResponseSchema,
|
||||
unwrapApiData,
|
||||
} from '../schemas/order.schema';
|
||||
import {
|
||||
|
|
@ -178,5 +179,23 @@ export const orderService = {
|
|||
return unwrapApiData(response, cuttingItemResponseSchema, []);
|
||||
},
|
||||
|
||||
/**
|
||||
* Busca parceiros por nome/CPF.
|
||||
* Retorna array vazio se o termo de busca tiver menos de 2 caracteres.
|
||||
*
|
||||
* @param {string} filter - O termo de busca (mínimo 2 caracteres)
|
||||
* @returns {Promise<Array<{id: number, cpf: string, nome: string}>>} Array de parceiros correspondentes
|
||||
*/
|
||||
findPartners: async (
|
||||
filter: string
|
||||
): Promise<Array<{ id: number; cpf: string; nome: string }>> => {
|
||||
if (!filter || filter.trim().length < 2) return [];
|
||||
|
||||
const response = await ordersApi.get(
|
||||
`/api/v1/parceiros/${encodeURIComponent(filter)}`
|
||||
);
|
||||
return unwrapApiData(response, partnersResponseSchema, []);
|
||||
},
|
||||
|
||||
|
||||
};
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ import {
|
|||
import { useStores } from '../store/useStores';
|
||||
import { useCustomers } from '../hooks/useCustomers';
|
||||
import { useSellers } from '../hooks/useSellers';
|
||||
import { usePartners } from '../hooks/usePartners';
|
||||
import { DatePicker } from '@mui/x-date-pickers/DatePicker';
|
||||
import { LocalizationProvider } from '@mui/x-date-pickers/LocalizationProvider';
|
||||
import { AdapterMoment } from '@mui/x-date-pickers/AdapterMoment';
|
||||
|
|
@ -39,6 +40,8 @@ interface LocalFilters {
|
|||
orderId: number | null;
|
||||
customerId: number | null;
|
||||
customerName: string | null;
|
||||
partnerId: string | null;
|
||||
partnerName: string | null;
|
||||
createDateIni: string | null;
|
||||
createDateEnd: string | null;
|
||||
store: string[] | null;
|
||||
|
|
@ -54,6 +57,8 @@ const getInitialLocalFilters = (
|
|||
orderId: urlFilters.orderId ?? null,
|
||||
customerId: urlFilters.customerId ?? null,
|
||||
customerName: urlFilters.customerName ?? null,
|
||||
partnerId: urlFilters.partnerId ?? null,
|
||||
partnerName: urlFilters.partnerName ?? null,
|
||||
createDateIni: urlFilters.createDateIni ?? null,
|
||||
createDateEnd: urlFilters.createDateEnd ?? null,
|
||||
store: urlFilters.store ?? null,
|
||||
|
|
@ -73,6 +78,8 @@ export const SearchBar = () => {
|
|||
const sellers = useSellers();
|
||||
const [customerSearchTerm, setCustomerSearchTerm] = useState('');
|
||||
const customers = useCustomers(customerSearchTerm);
|
||||
const [partnerSearchTerm, setPartnerSearchTerm] = useState('');
|
||||
const partners = usePartners(partnerSearchTerm);
|
||||
const [showAdvancedFilters, setShowAdvancedFilters] = useState(false);
|
||||
const { isFetching } = useOrders();
|
||||
const [touchedFields, setTouchedFields] = useState<{
|
||||
|
|
@ -94,6 +101,7 @@ export const SearchBar = () => {
|
|||
const handleReset = useCallback(() => {
|
||||
setTouchedFields({});
|
||||
setCustomerSearchTerm('');
|
||||
setPartnerSearchTerm('');
|
||||
|
||||
const resetState = {
|
||||
status: null,
|
||||
|
|
@ -112,6 +120,8 @@ export const SearchBar = () => {
|
|||
searchTriggered: false,
|
||||
customerId: null,
|
||||
customerName: null,
|
||||
partnerId: null,
|
||||
partnerName: null,
|
||||
};
|
||||
|
||||
setLocalFilters(getInitialLocalFilters(resetState));
|
||||
|
|
@ -172,6 +182,7 @@ export const SearchBar = () => {
|
|||
localFilters.store?.length,
|
||||
localFilters.stockId?.length,
|
||||
localFilters.sellerId,
|
||||
localFilters.partnerId,
|
||||
].filter(Boolean).length;
|
||||
|
||||
return (
|
||||
|
|
@ -601,6 +612,75 @@ export const SearchBar = () => {
|
|||
}}
|
||||
/>
|
||||
</Grid>
|
||||
|
||||
{/* Autocomplete do MUI para Parceiro */}
|
||||
<Grid size={{ xs: 12, sm: 6, md: 3 }}>
|
||||
<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
|
||||
}
|
||||
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('');
|
||||
}
|
||||
}
|
||||
}}
|
||||
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..."
|
||||
filterOptions={(x) => x}
|
||||
clearOnBlur={false}
|
||||
selectOnFocus
|
||||
handleHomeEndKeys
|
||||
/>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Collapse>
|
||||
</Grid>
|
||||
|
|
|
|||
|
|
@ -16,6 +16,8 @@ export const useOrderFilters = () => {
|
|||
sellerId: parseAsString,
|
||||
customerName: parseAsString,
|
||||
customerId: parseAsInteger,
|
||||
partnerName: parseAsString,
|
||||
partnerId: parseAsString,
|
||||
|
||||
codfilial: parseAsArrayOf(parseAsString, ','),
|
||||
codusur2: parseAsArrayOf(parseAsString, ','),
|
||||
|
|
|
|||
|
|
@ -0,0 +1,48 @@
|
|||
'use client';
|
||||
|
||||
import { useState, useEffect } from 'react';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { orderService } from '../api/order.service';
|
||||
|
||||
export interface Partner {
|
||||
id: number;
|
||||
cpf: string;
|
||||
nome: string;
|
||||
}
|
||||
|
||||
export function usePartners(searchTerm: string) {
|
||||
const [debouncedSearchTerm, setDebouncedSearchTerm] = useState(searchTerm);
|
||||
|
||||
useEffect(() => {
|
||||
const timer = setTimeout(() => {
|
||||
setDebouncedSearchTerm(searchTerm);
|
||||
}, 300);
|
||||
|
||||
return () => clearTimeout(timer);
|
||||
}, [searchTerm]);
|
||||
|
||||
const isEnabled = debouncedSearchTerm.length >= 2;
|
||||
|
||||
const query = useQuery({
|
||||
queryKey: ['partners', debouncedSearchTerm],
|
||||
queryFn: () => orderService.findPartners(debouncedSearchTerm),
|
||||
enabled: isEnabled,
|
||||
staleTime: 1000 * 60 * 5,
|
||||
retry: 1,
|
||||
retryOnMount: false,
|
||||
refetchOnWindowFocus: false,
|
||||
});
|
||||
|
||||
const options =
|
||||
query.data?.map((partner, index) => ({
|
||||
value: partner.id.toString(),
|
||||
label: partner.nome,
|
||||
id: `partner-${partner.id}-${index}`,
|
||||
partner: partner,
|
||||
})) ?? [];
|
||||
|
||||
return {
|
||||
...query,
|
||||
options,
|
||||
};
|
||||
}
|
||||
|
|
@ -2,6 +2,7 @@ import { z } from 'zod';
|
|||
import { storeSchema } from './store.schema';
|
||||
import { customerSchema } from './customer.schema';
|
||||
import { sellerSchema } from './seller.schema';
|
||||
import { partnerSchema } from './partner.schema';
|
||||
import { createApiSchema } from './api-response.schema';
|
||||
|
||||
/**
|
||||
|
|
@ -39,3 +40,9 @@ export const customersResponseSchema = createApiSchema(z.array(customerSchema));
|
|||
* Formato: { success: boolean, data: Seller[] }
|
||||
*/
|
||||
export const sellersResponseSchema = createApiSchema(z.array(sellerSchema));
|
||||
|
||||
/**
|
||||
* Schema para validar a resposta da API ao buscar parceiros.
|
||||
* Formato: { success: boolean, data: Partner[] }
|
||||
*/
|
||||
export const partnersResponseSchema = createApiSchema(z.array(partnerSchema));
|
||||
|
|
|
|||
|
|
@ -26,6 +26,11 @@ export { sellerSchema } from './seller.schema';
|
|||
export type { Seller } from './seller.schema';
|
||||
export { sellersResponseSchema } from './api-responses.schema';
|
||||
|
||||
// Schema de parceiros
|
||||
export { partnerSchema } from './partner.schema';
|
||||
export type { Partner } from './partner.schema';
|
||||
export { partnersResponseSchema } from './api-responses.schema';
|
||||
|
||||
// Schema de itens de pedido
|
||||
export { orderItemSchema, orderItemsResponseSchema } from './order.item.schema';
|
||||
export type { OrderItem, OrderItemsResponse } from './order.item.schema';
|
||||
|
|
|
|||
|
|
@ -0,0 +1,15 @@
|
|||
import { z } from 'zod';
|
||||
|
||||
/**
|
||||
* Schema Zod para validar um parceiro.
|
||||
*/
|
||||
export const partnerSchema = z.object({
|
||||
id: z.number(),
|
||||
cpf: z.string(),
|
||||
nome: z.string(),
|
||||
});
|
||||
|
||||
/**
|
||||
* Type para parceiro inferido do schema.
|
||||
*/
|
||||
export type Partner = z.infer<typeof partnerSchema>;
|
||||
Loading…
Reference in New Issue