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"),