diff --git a/src/app/orders/find/page.tsx b/src/app/orders/find/page.tsx index 382a658..ba5bacd 100644 --- a/src/app/orders/find/page.tsx +++ b/src/app/orders/find/page.tsx @@ -23,6 +23,7 @@ import { CustomerSearchInput } from "@/src/components/orders/CustomerSearchInput import { useCallback, useState, useEffect } from "react"; import { User, useAuthValidation } from "../../../hooks/useAuthValidation"; import { SellerSearchInput } from "@/src/components/orders/SellerSearchInput"; +import { ProductSearchInput } from "@/src/components/orders/ProductSearchInput"; /** * Página de Consulta de Pedidos @@ -161,6 +162,36 @@ export default function FindOrdersPage() { /> +
+ handleInputChange("productId", value)} + onSelect={(product) => { + if (product) { + handleInputChange("productId", product.id); + setSearchParams(prev => ({ + ...prev, + selectedProduct: product + })); + } else { + handleInputChange("productId", ""); + setSearchParams(prev => ({ + ...prev, + selectedProduct: null + })); + } + }} + selectedProduct={searchParams.selectedProduct || null} + aria-label="Buscar Produto" + /> +
+ + + +
diff --git a/src/components/orders/OrderTimeline.tsx b/src/components/orders/OrderTimeline.tsx index 1c7e185..e3c4cfb 100644 --- a/src/components/orders/OrderTimeline.tsx +++ b/src/components/orders/OrderTimeline.tsx @@ -20,41 +20,44 @@ import { Badge } from "../../components/ui/badge"; import { ordersApi } from "../../lib/api"; /** - * Represents a timeline event in an order's lifecycle + * Representa um evento na linha do tempo do ciclo de vida de um pedido + * @typedef {Object} Leadtime + * @property {string} etapas - Descrição da etapa + * @property {string} descricao - Descrição detalhada do evento + * @property {string} date - Data e hora do evento + * @property {string} codigoFuncionario - ID do funcionário responsável + * @property {string} nomeFuncionario - Nome do funcionário responsável + * @property {string} numeroPedido - Número do pedido associado + * @property {LucideIcon} [icon] - Ícone para o evento + * @property {string} [status] - Rótulo de status do evento + * @property {string} [color] - Tema de cor do evento */ interface Leadtime { - /** Description of the stage/step */ etapas: string; - /** Detailed description of the event */ descricao: string; - /** Date and time of the event */ date: string; - /** Employee ID who handled this step */ codigoFuncionario: string; - /** Employee name who handled this step */ nomeFuncionario: string; - /** Order number associated with this event */ numeroPedido: string; - /** Icon to display for this event */ icon?: LucideIcon; - /** Status label for this event */ status?: string; - /** Color theme for this event */ color?: string; } /** - * Props for the Timeline component + * Propriedades do componente Timeline + * @typedef {Object} TimelineProps + * @property {string} [orderId] - ID do pedido para buscar dados da linha do tempo + * @property {string} [className] - Classe CSS opcional */ interface TimelineProps { - /** Order ID to fetch timeline data for */ orderId?: string; - /** Optional CSS class name */ className?: string; } /** - * Configuration for different order status types + * Configuração para diferentes tipos de status do pedido + * @type {Record} */ const statusConfig: Record< string, @@ -75,7 +78,8 @@ const statusConfig: Record< }; /** - * Keywords used to match description text to status types + * Palavras-chave usadas para mapear descrições de etapas para tipos de status + * @type {Object} */ const statusKeywords = { pedido: ['venda realizada', 'pedido', 'receb'], @@ -87,9 +91,9 @@ const statusKeywords = { }; /** - * Determines the status configuration based on the event description - * @param descricao - The description text to analyze - * @returns The matching status configuration object + * Determina a configuração de status com base na descrição do evento + * @param {string} descricao - Texto da descrição a ser analisado + * @returns {{icon: LucideIcon, color: string, status: string}} Objeto de configuração de status correspondente */ function getStatusConfig(descricao: string): { icon: LucideIcon; color: string; status: string } { const desc = descricao?.toLowerCase().trim() || ""; @@ -102,9 +106,9 @@ function getStatusConfig(descricao: string): { icon: LucideIcon; color: string; } /** - * Formats a date string to localized date and time format - * @param dateString - The date string to format - * @returns Formatted date string in Brazilian format (DD/MM/YYYY HH:MM) + * Formata uma string de data para o formato local brasileiro (DD/MM/AAAA HH:MM) + * @param {string} dateString - String de data a ser formatada + * @returns {string} Data formatada */ function formatDate(dateString: string): string { try { @@ -123,9 +127,9 @@ function formatDate(dateString: string): string { } /** - * Formats a date string to show only the time portion - * @param dateString - The date string to format - * @returns Formatted time string in Brazilian format (HH:MM) + * Formata uma string de data para exibir apenas o horário (HH:MM) + * @param {string} dateString - String de data a ser formatada + * @returns {string} Horário formatado */ function formatTimeOnly(dateString: string): string { try { @@ -141,10 +145,10 @@ function formatTimeOnly(dateString: string): string { } /** - * Maps API data to the Leadtime interface with proper status configuration - * @param apiData - Raw API data - * @param defaultOrderId - Fallback order ID if not present in API data - * @returns Array of processed Leadtime objects + * Mapeia os dados da API para o formato Leadtime com configuração de status + * @param {any[]} apiData - Dados brutos da API + * @param {string} [defaultOrderId] - ID do pedido padrão caso não esteja presente nos dados + * @returns {Leadtime[]} Array de objetos Leadtime processados */ function mapApiDataToLeadtimes(apiData: any[], defaultOrderId: string = ""): Leadtime[] { return apiData.map((item) => { @@ -164,7 +168,9 @@ function mapApiDataToLeadtimes(apiData: any[], defaultOrderId: string = ""): Lea } /** - * Timeline component that displays the history of events for an order + * Componente Timeline que exibe o histórico de eventos de um pedido + * @param {TimelineProps} props + * @returns {JSX.Element} */ export default function Timeline({ orderId = "", className }: TimelineProps) { const [leadtimes, setLeadtimes] = useState([]); diff --git a/src/components/orders/OrdersTable.tsx b/src/components/orders/OrdersTable.tsx index 71de439..05b61f2 100644 --- a/src/components/orders/OrdersTable.tsx +++ b/src/components/orders/OrdersTable.tsx @@ -38,6 +38,7 @@ export type ColumnKey = | "Motorista" | "Placa" + const ALL_COLUMNS: ColumnKey[] = [ "Data", "Data previsão de entrega", "Agendamento", "Pedido", "Tipo Pedido", "Cliente", "Vendedor", "Filial", "Situação", "Dt Carregamento", "Rota", "Valor", "Peso(kg)", "Carregamento", "Cobrança", "Trasportadora", "Praça", "Nota fiscal", "Dt Faturamento", "TV 7", "Motorista", "Placa" ]; @@ -87,12 +88,10 @@ export function OrdersTable({ }: OrdersTableProps) { const { getStoreName } = useStoreNameResolver(stores); - // Find the selected order based on selectedOrderId const selectedOrder = useMemo(() => selectedOrderId ? orders.find(order => order.orderId === selectedOrderId) : undefined , [selectedOrderId, orders]); - // Use custom hook for order-related data fetching const { orderStatus, orderLeadtime, @@ -108,7 +107,6 @@ export function OrdersTable({ "Peso(kg)": (order: Order) => parseFloat(String(order.totalWeigth || "0")), "Dt Faturamento": (order: Order) => new Date(order.invoiceDate || ""), "Dt Carregamento": (order: Order) => new Date(order.shipmentDate || ""), - // Add other column accessors as needed }), []); // Use our custom sort hook diff --git a/src/components/orders/OrdersTableBody.tsx b/src/components/orders/OrdersTableBody.tsx index 3814d70..fcbd52e 100644 --- a/src/components/orders/OrdersTableBody.tsx +++ b/src/components/orders/OrdersTableBody.tsx @@ -7,6 +7,16 @@ import { import { OrderRowExpandable } from "./tabela-pedidos/components/OrderRowExpandable"; import { Order } from "@/src/components/types"; +/** + * Propriedades para o componente OrdersTableBody + * @typedef {Object} OrdersTableBodyProps + * @property {Order[]} orders - Lista completa de pedidos + * @property {Order[]} currentOrders - Lista de pedidos filtrados/paginados + * @property {string|null} selectedOrderId - ID do pedido selecionado + * @property {string[]} visibleColumns - Colunas visíveis na tabela + * @property {(storeId?: string) => string} getStoreName - Função para obter o nome da loja + * @property {(orderId: string) => void} handleRowClick - Função chamada ao clicar em uma linha + */ interface OrdersTableBodyProps { orders: Order[]; currentOrders: Order[]; @@ -16,7 +26,11 @@ interface OrdersTableBodyProps { handleRowClick: (orderId: string) => void; } -// Componente de linha vazia memoizado +/** + * Linha vazia exibida quando não há pedidos + * @param {{ colSpan: number }} props + * @returns {JSX.Element} + */ const EmptyRow = memo(({ colSpan }: { colSpan: number }) => ( @@ -25,7 +39,11 @@ const EmptyRow = memo(({ colSpan }: { colSpan: number }) => ( )); -// Componente de linha de pedido memoizado +/** + * Linha de pedido individual, expansível + * @param {{ order: Order, isSelected: boolean, visibleColumns: string[], onSelect: (orderId: string) => void, getStoreName: (storeId?: string) => string }} props + * @returns {JSX.Element} + */ const OrderRow = memo(({ order, isSelected, @@ -39,11 +57,11 @@ const OrderRow = memo(({ onSelect: (orderId: string) => void; getStoreName: (storeId?: string) => string; }) => { - // Create a new order object with the store name only (without ID prefix) + // Cria um novo objeto de pedido com o nome da loja const orderWithStore = { ...order, store: getStoreName(order.storeId), - storeId: order.storeId, // Preserve original storeId + storeId: order.storeId, // Mantém o storeId original }; return ( @@ -60,7 +78,11 @@ const OrderRow = memo(({ ); }); -// Componente principal memoizado +/** + * Corpo da tabela de pedidos, renderiza linhas de pedidos ou linha vazia + * @param {OrdersTableBodyProps} props + * @returns {JSX.Element} + */ function OrdersTableBodyComponent({ orders, currentOrders, @@ -69,7 +91,7 @@ function OrdersTableBodyComponent({ getStoreName, handleRowClick, }: OrdersTableBodyProps) { - // Renderização condicional baseada na presença de orders, memoizada + // Renderização condicional baseada na presença de pedidos const content = useMemo(() => { if (orders.length === 0) { return ; @@ -90,4 +112,8 @@ function OrdersTableBodyComponent({ return {content}; } +/** + * Exporta o componente memoizado do corpo da tabela de pedidos + * @type {React.NamedExoticComponent} + */ export const OrdersTableBody = memo(OrdersTableBodyComponent); \ No newline at end of file diff --git a/src/components/orders/ProductSearchInput.tsx b/src/components/orders/ProductSearchInput.tsx new file mode 100644 index 0000000..58cf54d --- /dev/null +++ b/src/components/orders/ProductSearchInput.tsx @@ -0,0 +1,256 @@ +"use client"; + +import * as React from "react"; +import { Check, ChevronsUpDown, Package, Loader2, X } from "lucide-react"; +import { cn } from "@/src/lib/utils"; +import { Button } from "@/src/components/ui/button"; +import { + Command, + CommandEmpty, + CommandGroup, + CommandInput, + CommandItem, + CommandList, +} from "@/src/components/ui/command"; +import { + Popover, + PopoverContent, + PopoverTrigger, +} from "@/src/components/ui/popover"; +import { dataConsultApi } from "@/src/lib/api"; +import { Product } from "@/src/components/types"; +import { Label } from "@/src/components/ui/label"; +import { Badge } from "@/src/components/ui/badge"; + +interface ProductSearchInputProps { + id: string; + label: string; + placeholder: string; + value: string; + onChange: (value: string) => void; + onSelect: (product: Product | null) => void; + selectedProduct: Product | null; + disabled?: boolean; + "aria-label"?: string; +} + +/** + * Componente de busca de produtos com autocompletar + * Permite buscar produtos por código, nome ou descrição + */ +export function ProductSearchInput({ + id, + label, + placeholder, + value, + onChange, + onSelect, + selectedProduct, + disabled = false, + "aria-label": ariaLabel, +}: ProductSearchInputProps) { + const [open, setOpen] = React.useState(false); + const [products, setProducts] = React.useState([]); + const [loading, setLoading] = React.useState(false); + const [searchQuery, setSearchQuery] = React.useState(""); + const [searchError, setSearchError] = React.useState(null); + const searchInputRef = React.useRef(null); + + // Buscar produtos baseado na query + const searchProducts = React.useCallback(async (query: string) => { + if (!query || query.length < 2) { + setProducts([]); + return; + } + + try { + setLoading(true); + setSearchError(null); + + const response = await dataConsultApi.getProducts(query); + + if (response.success && response.data && response.data.length > 0) { + // Mapear os dados da API para o formato Product + const mappedProducts = response.data.map((product: any) => ({ + id: product.id?.toString() || "", + code: product.manufacturerCode || "", + name: product.description || product.name || "", + description: product.description || product.name || "", + brand: product.brand || "", + department: product.department || "", + color: product.color || "", + })); + + setProducts(mappedProducts.slice(0, 50)); // Limitar a 50 resultados + } else { + setSearchError(`Nenhum produto encontrado para "${query}"`); + setProducts([]); + } + } catch (error) { + console.error("Erro ao buscar produtos:", error); + setSearchError("Erro ao buscar produtos"); + setProducts([]); + } finally { + setLoading(false); + } + }, []); + + // Debounce para a busca + React.useEffect(() => { + const timer = setTimeout(() => { + if (open && searchQuery.length >= 2) { + searchProducts(searchQuery); + } + }, 300); + + return () => clearTimeout(timer); + }, [searchQuery, searchProducts, open]); + + // Selecionar produto + const handleSelectProduct = (product: Product) => { + onSelect(product); + setOpen(false); + setSearchQuery(""); + }; + + // Limpar seleção + const handleClearSelection = (e?: React.MouseEvent) => { + if (e) { + e.stopPropagation(); + } + onSelect(null); + onChange(""); + setSearchQuery(""); + setProducts([]); + }; + + // Quando o componente monta ou o valor muda + React.useEffect(() => { + // Se temos um value mas não temos selectedProduct, executar busca inicial + if (value && !selectedProduct && !loading && !open) { + searchProducts(value); + } + }, [value, selectedProduct, searchProducts, loading, open]); + + return ( +
+
+ +
+ + {searchError && ( +

{searchError}

+ )} + +
+ + + + + {selectedProduct && ( +
{ + e.stopPropagation(); + handleClearSelection(); + }} + > + + Limpar seleção +
+ )} + + + + {loading && ( +
+ +
+ )} + {!loading && ( + <> + + {searchQuery.length >= 2 + ? "Nenhum produto encontrado com esse termo" + : "Digite pelo menos 2 caracteres para buscar"} + + {products.length > 0 && ( + + + {products.map((product, index) => ( + handleSelectProduct(product)} + > + +
+ {product.name} +
+ {product.code && ( + Cód: {product.code} + )} + {product.brand && ( + Marca: {product.brand} + )} + {product.department && ( + Depto: {product.department} + )} +
+
+
+ ))} +
+
+ )} + + )} +
+
+
+
+
+ ); +} \ No newline at end of file diff --git a/src/components/types.ts b/src/components/types.ts index 3be0021..054f9ac 100644 --- a/src/components/types.ts +++ b/src/components/types.ts @@ -13,11 +13,23 @@ export interface TimelineEvent { description: string; date: string | Date; status?: string; - icon?: LucideIcon; + icon?: LucideIcon; color?: string; user?: string; } +export interface productsSearchParams { + productId?: string; + description?: string; + pacth?: string; + color?: string; + stockId?: string; + quantity?: string; + salePrice?: string; + department?: string; + brand?: string; +} + export interface cutitens { productId: number; description: string; @@ -81,7 +93,7 @@ export interface Order { sellerId: string; stockId: string; storeName: string; - store?: string; + store?: string; createDate: string | Date; updateDate?: string | Date; status?: string; @@ -159,7 +171,7 @@ export interface ClientData { totalPurchases: number; } -export type OrderStatus = +export type OrderStatus = | 'Aguardando pagamento' | 'Pagamento aprovado' | 'Em separação' @@ -179,13 +191,13 @@ export type PaymentStatus = name: string; document: string; }; - + export interface LoginDto { username: string; password: string; } - + export interface LoginResponseDto { id: number; sellerId: number; @@ -201,9 +213,19 @@ export type PaymentStatus = name?: string; document?: string; } - - + export interface Product { + id: string; + code: string; + name: string; + description?: string; + brand?: string; + department?: string; + color?: string; + } + + + /** * Interface para parâmetros de pesquisa de pedidos * Define todos os campos possíveis usados no formulário de busca @@ -241,6 +263,8 @@ export interface OrderSearchParams { deliveryLocal?: string; /** ID do vendedor */ sellerId?: string | number; + /** Nome do vendedor */ + sellerName?: string; /** Documento do cliente (CPF/CNPJ) - duplicado para compatibilidade */ document?: string; /** Número da nota fiscal */ @@ -251,6 +275,10 @@ export interface OrderSearchParams { invoiceId?: string; /** Número da nota fiscal (NFe) */ nfe?: string; + /** ID do produto para busca */ + productId?: string; + /** Produto selecionado pelo componente de busca */ + selectedProduct?: Product | null; } export interface CustomerSearchParams { diff --git a/src/hooks/useOrderSearch.ts b/src/hooks/useOrderSearch.ts index 8a5071f..d493dd1 100644 --- a/src/hooks/useOrderSearch.ts +++ b/src/hooks/useOrderSearch.ts @@ -11,7 +11,7 @@ import { clearUrlAndNavigate } from "@/src/utils/url-helpers"; /** * Função de utilidade para aplicar debounce em funções * Evita execuções repetidas em curtos intervalos de tempo - * + * * @template T - Tipo da função a ser debounced * @param {T} callback - Função a aplicar debounce * @param {number} delay - Tempo de espera em ms @@ -84,31 +84,32 @@ const paramMapping: Record = { createDateEnd: "createDateEnd", customerId: "customerId", document: "document", - invoiceNumber: "invoiceNumber" + invoiceNumber: "invoiceNumber", + productId: "productId" }; /** * Hook personalizado para gerenciar a consulta e exibição de pedidos * Gerencia busca, paginação, armazenamento de parâmetros e detalhes de pedidos - * + * * @param {number} ordersPerPage - Número de pedidos a serem exibidos por página * @returns {UseOrderSearchReturn} Objeto contendo estado e funções para lidar com pedidos */ export function useOrderSearch(ordersPerPage: number = 8): UseOrderSearchReturn { const router = useRouter(); const urlSearchParams = useSearchParams(); - + // Inicializar searchParams com valores da URL ou do localStorage ou um objeto vazio const [searchParams, setSearchParams] = useState(() => { const params: OrderSearchParams = {}; - + // Tentar recuperar do localStorage primeiro const savedParams = getStorage("orderSearchParams"); - + if (savedParams) { return savedParams; } - + // Se não houver no localStorage, extrair valores da URL para o estado Object.entries(paramMapping).forEach(([urlParam, stateParam]) => { const value = urlSearchParams.get(urlParam); @@ -117,7 +118,7 @@ export function useOrderSearch(ordersPerPage: number = 8): UseOrderSearchReturn (params as any)[stateParam] = value; } }); - + // Tratamento especial para parâmetros que podem ser arrays const typeParam = urlSearchParams.get('type'); if (typeParam) { @@ -128,10 +129,10 @@ export function useOrderSearch(ordersPerPage: number = 8): UseOrderSearchReturn params.type = typeParam; } } - + return params; }); - + const [orders, setOrders] = useState([]); const [loading, setLoading] = useState(false); const [error, setError] = useState(null); @@ -147,7 +148,7 @@ export function useOrderSearch(ordersPerPage: number = 8): UseOrderSearchReturn // Detectar parâmetro origem=detalhe para realizar pesquisa automática ao retornar const origin = urlSearchParams.get("origem"); - + // Limpar todos os campos relacionados ao cliente const clearCustomerFields = useCallback(() => { setSearchParams(prev => ({ @@ -158,12 +159,21 @@ export function useOrderSearch(ordersPerPage: number = 8): UseOrderSearchReturn selectedCustomer: null, })); }, []); - - // Efeito para realizar pesquisa automática quando a página é carregada + + // Limpar todos os campos relacionados ao produto + const clearProductFields = useCallback(() => { + setSearchParams(prev => ({ + ...prev, + productId: "", + selectedProduct: null, + })); + }, []); + + // Efeito para realizar pesquisa automática quando a página é carregada // e há parâmetros de pesquisa na URL ou se estamos voltando da página de detalhes useEffect(() => { const hasSearchParams = Object.keys(searchParams).length > 0; - + // Se temos parâmetros de busca e vamos retornando da página de detalhes, // ou se temos parâmetros e ainda não realizamos a pesquisa if ((hasSearchParams && origin === "detalhe") || (hasSearchParams && !hasSearched)) { @@ -177,7 +187,7 @@ export function useOrderSearch(ordersPerPage: number = 8): UseOrderSearchReturn */ const updateUrlWithSearchParams = useCallback(() => { const urlParams = new URLSearchParams(); - + // Adicionar apenas parâmetros com valores Object.entries(searchParams).forEach(([key, value]) => { if (value) { @@ -189,15 +199,15 @@ export function useOrderSearch(ordersPerPage: number = 8): UseOrderSearchReturn } } }); - + // Preservar parâmetro origem se existir if (origin) { urlParams.append("origem", origin); } - + // Salvar no localStorage setStorage("orderSearchParams", searchParams); - + // Atualizar URL sem causar recarregamento da página const url = window.location.pathname + (urlParams.toString() ? `?${urlParams.toString()}` : ""); window.history.replaceState({ path: url }, "", url); @@ -206,7 +216,7 @@ export function useOrderSearch(ordersPerPage: number = 8): UseOrderSearchReturn /** * Executa a busca de pedidos com base nos parâmetros definidos * Valida datas, formata parâmetros e faz a chamada à API - * + * * @returns {Promise} */ const handleSearch = async () => { @@ -236,27 +246,30 @@ export function useOrderSearch(ordersPerPage: number = 8): UseOrderSearchReturn // Transform the ALL value to empty string for the API call const paramsToSend: OrderSearchParams = { ...searchParams }; - - // Remover o campo selectedCustomer antes de enviar para a API + + // Remover campos que não devem ser enviados para a API if (paramsToSend.selectedCustomer) { delete paramsToSend.selectedCustomer; } - + if (paramsToSend.selectedProduct) { + delete paramsToSend.selectedProduct; + } + // Se tiver customerId definido, remover name e document para evitar conflito if (paramsToSend.customerId) { delete paramsToSend.name; delete paramsToSend.document; } - + // Se temos o nome do vendedor mas não temos o ID, tentar buscar o ID primeiro if (paramsToSend.sellerName && !paramsToSend.sellerId) { try { const sellers = await dataConsultApi.getSellers(); if (Array.isArray(sellers)) { - const matchingSeller = sellers.find((seller: any) => + const matchingSeller = sellers.find((seller: any) => seller.name.toLowerCase().includes(paramsToSend.sellerName?.toLowerCase() || '') ); - + if (matchingSeller) { // Extrair código numérico (887 de "887 - SAL-SIMONI") if (matchingSeller.name && matchingSeller.name.includes("-")) { @@ -276,18 +289,18 @@ export function useOrderSearch(ordersPerPage: number = 8): UseOrderSearchReturn console.error("Erro ao buscar vendedores:", err); } } - + // Mapear invoiceNumber para invoiceId que é o que a API espera if (paramsToSend.invoiceNumber) { paramsToSend.invoiceId = paramsToSend.invoiceNumber; delete paramsToSend.invoiceNumber; } - + // Remover campos que a API não espera if ('nfe' in paramsToSend) { delete paramsToSend.nfe; } - + if (paramsToSend.codfilial === "ALL") { paramsToSend.codfilial = ""; } @@ -305,7 +318,7 @@ export function useOrderSearch(ordersPerPage: number = 8): UseOrderSearchReturn // Construir manualmente o queryParams para lidar com arrays const queryParams = new URLSearchParams(); - + Object.entries(paramsToSend).forEach(([key, value]) => { if ((key === 'type' || key === 'status' || key === 'deliveryType') && Array.isArray(value)) { // Unir os valores do array em uma única string separada por vírgula @@ -314,21 +327,21 @@ export function useOrderSearch(ordersPerPage: number = 8): UseOrderSearchReturn queryParams.append(key, String(value)); } }); - + const queryString = queryParams.toString(); const response = await ordersApi.findOrders(paramsToSend, queryString); setOrders(extractApiData(response)); setError(null); - + // Reset selected order and items when new search is performed setSelectedOrderId(null); setOrderItems([]); setCutItems([]); - + // Reset pagination state setCurrentPage(1); setVisibleOrdersCount(ordersPerPage); - + // Atualizar a URL com os parâmetros de pesquisa updateUrlWithSearchParams(); } catch (err) { @@ -342,13 +355,13 @@ export function useOrderSearch(ordersPerPage: number = 8): UseOrderSearchReturn /** * Manipula alterações em campos de entrada do formulário * Atualiza o estado searchParams com o novo valor - * + * * @param {keyof OrderSearchParams} field - Nome do campo a atualizar * @param {any} value - Novo valor para o campo */ const handleInputChange = (field: keyof OrderSearchParams, value: any) => { // Garantir que valores apropriados sejam tratados como arrays - if ((field === 'type' || field === 'status' || field === 'deliveryType') && + if ((field === 'type' || field === 'status' || field === 'deliveryType') && typeof value === 'string' && value !== '') { // Converter string para array com um único item setSearchParams((prev) => ({ @@ -370,7 +383,7 @@ export function useOrderSearch(ordersPerPage: number = 8): UseOrderSearchReturn /** * Seleciona um cliente após busca, preenchendo campos relacionados - * + * * @param {Cliente | null} customer - Cliente selecionado ou null para limpar */ const handleCustomerSelect = (customer: Cliente | null) => { @@ -378,7 +391,7 @@ export function useOrderSearch(ordersPerPage: number = 8): UseOrderSearchReturn clearCustomerFields(); return; } - + setSearchParams((prev) => ({ ...prev, selectedCustomer: customer, @@ -391,7 +404,7 @@ export function useOrderSearch(ordersPerPage: number = 8): UseOrderSearchReturn /** * Manipula clique em linha da tabela de pedidos * Carrega detalhes do pedido selecionado - * + * * @param {string} orderId - ID do pedido selecionado * @returns {Promise} */ @@ -435,14 +448,14 @@ export function useOrderSearch(ordersPerPage: number = 8): UseOrderSearchReturn // Pagination calculations - corrigir para garantir 8 itens por página const totalPages = Math.ceil(orders.length / ordersPerPage); - + // Calcular o índice inicial baseado na página atual const indexOfFirstOrder = (currentPage - 1) * ordersPerPage; // Calcular o índice final const indexOfLastOrder = Math.min(indexOfFirstOrder + ordersPerPage, orders.length); // Selecionar apenas os itens da página atual const currentOrders = orders.slice(indexOfFirstOrder, indexOfLastOrder); - + // Navigate to next page const goToNextPage = useCallback(() => { if (currentPage < totalPages) { @@ -490,7 +503,7 @@ export function useOrderSearch(ordersPerPage: number = 8): UseOrderSearchReturn /** * Identifica o tipo de entrada para busca de cliente * Diferencia entre ID, documento ou nome baseado no formato - * + * * @param {string} value - Valor de entrada a ser analisado * @returns {'id' | 'document' | 'name'} Tipo identificado da entrada */ @@ -503,7 +516,7 @@ export function useOrderSearch(ordersPerPage: number = 8): UseOrderSearchReturn /** * Busca cliente por ID - * + * * @param {string} value - ID do cliente a ser buscado * @returns {Promise} */ @@ -514,10 +527,10 @@ export function useOrderSearch(ordersPerPage: number = 8): UseOrderSearchReturn clearCustomerFields(); return; } - + let customerData: any = null; const inputType = identifyInputType(value); - + // Atualizar o campo apropriado com base no tipo de entrada setSearchParams(prev => ({ ...prev, @@ -525,7 +538,7 @@ export function useOrderSearch(ordersPerPage: number = 8): UseOrderSearchReturn document: inputType === 'document' ? value : "", name: inputType === 'name' ? value : "", })); - + // Buscar cliente pelo tipo correto if (inputType === 'id') { const response = await dataConsultApi.getCustomer(value); @@ -538,7 +551,7 @@ export function useOrderSearch(ordersPerPage: number = 8): UseOrderSearchReturn customerData = response[0]; } } - + if (customerData) { // Create customer object from response const customer: Customer = { @@ -546,7 +559,7 @@ export function useOrderSearch(ordersPerPage: number = 8): UseOrderSearchReturn name: customerData.name, document: customerData.document || "" }; - + // Update selected customer in form state setSearchParams(prev => ({ ...prev, @@ -564,21 +577,21 @@ export function useOrderSearch(ordersPerPage: number = 8): UseOrderSearchReturn /** * Busca cliente por nome - * + * * @param {string} value - Nome parcial ou completo para busca * @returns {Promise} */ const searchCustomerByName = async (value: string) => { if (!value || value.length < 2) return; - + try { const matches = await clientesApi.search(value); if (matches.length > 0) { const c = matches[0]; - + const clientId = c.id || ""; const clientName = c.name || ""; - + setSearchParams(prev => ({ ...prev, customerId: clientId, @@ -596,14 +609,14 @@ export function useOrderSearch(ordersPerPage: number = 8): UseOrderSearchReturn setError("Erro ao buscar cliente. Por favor, tente novamente."); } }; - + // Criar versão com debounce da função de busca const debouncedSearchCustomer = useDebounce(searchCustomerByName, 500); /** * Manipula entrada de filtro de cliente * Identifica o tipo de filtro e direciona para a busca apropriada - * + * * @param {string} value - Termo de busca (ID, documento ou nome) * @returns {Promise} */ @@ -613,7 +626,7 @@ export function useOrderSearch(ordersPerPage: number = 8): UseOrderSearchReturn clearCustomerFields(); return; } - + // Atualiza o campo de nome no estado setSearchParams(prev => ({ ...prev, @@ -624,11 +637,11 @@ export function useOrderSearch(ordersPerPage: number = 8): UseOrderSearchReturn // Se ainda está digitando poucas letras, apenas atualiza o estado if (value.length < 2) return; - + // Aciona a busca com debounce para evitar chamadas excessivas à API debouncedSearchCustomer(value); }; - + /** * Restaura o estado da pesquisa com dados previamente salvos * Útil para preservar o estado ao voltar da página de detalhes @@ -638,12 +651,12 @@ export function useOrderSearch(ordersPerPage: number = 8): UseOrderSearchReturn setCurrentPage(savedPage); setHasSearched(savedHasSearched); setVisibleOrdersCount(ordersPerPage * savedPage); - + // Calcular total de páginas const calculatedTotalPages = Math.ceil(savedOrders.length / ordersPerPage); // Como não temos acesso direto à função setTotalPages, calcular localmente const totalPages = calculatedTotalPages; - + // Atualizar pedidos da página atual const indexOfLastOrder = savedPage * ordersPerPage; const indexOfFirstOrder = indexOfLastOrder - ordersPerPage; @@ -671,7 +684,7 @@ export function useOrderSearch(ordersPerPage: number = 8): UseOrderSearchReturn /** * Manipula a seleção de múltiplos valores em filtros de seleção - * + * * @param {string} field - Nome do campo no objeto searchParams * @param {string[]} values - Array de valores selecionados */ @@ -716,4 +729,4 @@ export function useOrderSearch(ordersPerPage: number = 8): UseOrderSearchReturn clearAndNavigateToOrders, handleMultiInputChange }; -} +} diff --git a/src/lib/api.ts b/src/lib/api.ts index 9c288b6..f1883aa 100644 --- a/src/lib/api.ts +++ b/src/lib/api.ts @@ -2,7 +2,10 @@ import { leadtime } from "../components/types"; import { Cliente } from "../components/types"; // URL base da API -const API_BASE_URL = process.env.NEXT_PUBLIC_API_URL || "https://portalapi.jurunense.com"; +//const API_BASE_URL = process.env.NEXT_PUBLIC_API_URL || "https://portalapi.jurunense.com"; +const API_BASE_URL = process.env.NEXT_PUBLIC_API_URL || "http://10.1.1.212:8805"; + + /** * Classe de erro personalizada para requisições de API. @@ -29,7 +32,7 @@ export class ApiError extends Error { */ export async function fetchApi(endpoint: string, options?: RequestInit): Promise { const url = `${API_BASE_URL}${endpoint}`; - + // Obtém o token de autenticação do localStorage let authToken = null; try { @@ -43,8 +46,8 @@ export async function fetchApi(endpoint: string, options?: RequestInit): Prom "Content-Type": "application/json", ...(options?.headers as Record), }; - - + + if (authToken) { headers["Authorization"] = `Bearer ${authToken}`; } @@ -98,7 +101,7 @@ export const dataConsultApi = { getStores: () => fetchApi("/api/v1/data-consult/stores"), /** @returns {Promise} Lista de vendedores */ - getSellers: () => + getSellers: () => fetchApi("/api/v1/data-consult/sellers") .then(response => { // Tentar extrair dados de vendedores em diferentes formatos @@ -126,7 +129,7 @@ export const dataConsultApi = { * @param {string} name - Nome do vendedor para filtrar * @returns {Promise} Lista de vendedores filtrados */ - searchSellers: (name: string) => + searchSellers: (name: string) => fetchApi(`/api/v1/data-consult/sellers/search?name=${encodeURIComponent(name)}`) .then(response => { // Tratar a resposta de forma similar a getSellers @@ -179,10 +182,10 @@ export const dataConsultApi = { /** * @param {string} filter - Filtro de produto (nome ou código). - * @returns {Promise} Produtos filtrados. + * @returns {Promise<{success: boolean, data: any[]}>} Produtos filtrados. */ getProducts: (filter: string) => - fetchApi(`/api/v1/data-consult/products/${filter}`), + fetchApi<{ success: boolean; data: any[] }>(`/api/v1/data-consult/products/${filter}`), /** @returns {Promise} Todos os produtos */ getAllProducts: () => fetchApi("/api/v1/data-consult/all"),