diff --git a/src/features/orders/api/order.service.ts b/src/features/orders/api/order.service.ts index d21f0ea..7149289 100644 --- a/src/features/orders/api/order.service.ts +++ b/src/features/orders/api/order.service.ts @@ -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 de parceiros correspondentes + */ + findPartners: async ( + filter: string + ): Promise> => { + if (!filter || filter.trim().length < 2) return []; + + const response = await ordersApi.get( + `/api/v1/parceiros/${encodeURIComponent(filter)}` + ); + return unwrapApiData(response, partnersResponseSchema, []); + }, + }; diff --git a/src/features/orders/components/SearchBar.tsx b/src/features/orders/components/SearchBar.tsx index 626bfe5..130cb6f 100644 --- a/src/features/orders/components/SearchBar.tsx +++ b/src/features/orders/components/SearchBar.tsx @@ -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 = () => { }} /> + + {/* Autocomplete do MUI para Parceiro */} + + 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) => ( + + )} + noOptionsText={ + partnerSearchTerm.length < 2 + ? 'Digite pelo menos 2 caracteres' + : 'Nenhum parceiro encontrado' + } + loadingText="Buscando parceiros..." + filterOptions={(x) => x} + clearOnBlur={false} + selectOnFocus + handleHomeEndKeys + /> + diff --git a/src/features/orders/hooks/useOrderFilters.ts b/src/features/orders/hooks/useOrderFilters.ts index dabce54..ea72fa9 100644 --- a/src/features/orders/hooks/useOrderFilters.ts +++ b/src/features/orders/hooks/useOrderFilters.ts @@ -16,6 +16,8 @@ export const useOrderFilters = () => { sellerId: parseAsString, customerName: parseAsString, customerId: parseAsInteger, + partnerName: parseAsString, + partnerId: parseAsString, codfilial: parseAsArrayOf(parseAsString, ','), codusur2: parseAsArrayOf(parseAsString, ','), diff --git a/src/features/orders/hooks/usePartners.ts b/src/features/orders/hooks/usePartners.ts new file mode 100644 index 0000000..08a5691 --- /dev/null +++ b/src/features/orders/hooks/usePartners.ts @@ -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, + }; +} diff --git a/src/features/orders/schemas/api-responses.schema.ts b/src/features/orders/schemas/api-responses.schema.ts index c8fe4d4..1fd38f1 100644 --- a/src/features/orders/schemas/api-responses.schema.ts +++ b/src/features/orders/schemas/api-responses.schema.ts @@ -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)); diff --git a/src/features/orders/schemas/order.schema.ts b/src/features/orders/schemas/order.schema.ts index 7f87de1..31cad74 100644 --- a/src/features/orders/schemas/order.schema.ts +++ b/src/features/orders/schemas/order.schema.ts @@ -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'; diff --git a/src/features/orders/schemas/partner.schema.ts b/src/features/orders/schemas/partner.schema.ts new file mode 100644 index 0000000..92d180c --- /dev/null +++ b/src/features/orders/schemas/partner.schema.ts @@ -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;