feat: implementa busca de produtos com API externa e corrige URL base
- Adiciona integração com API de produtos em http://10.1.1.212:8805/api/v1/data-consult/products/{id} - Corrige mapeamento de resposta da API para formato Product interface - Atualiza ProductSearchInput para usar description como nome do produto - Corrige API_BASE_URL adicionando protocolo http:// para evitar URLs relativas - Resolve erro 404 causado por URLs malformadas em requisições de API
This commit is contained in:
@@ -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() {
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="filter-field">
|
||||
<ProductSearchInput
|
||||
id="productSearch"
|
||||
label="Produto"
|
||||
placeholder="Buscar por código, nome ou descrição"
|
||||
value={searchParams.productId || ""}
|
||||
onChange={(value) => 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"
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
<div className="flex gap-4">
|
||||
<div className="filter-small-field">
|
||||
<FilterInput
|
||||
@@ -413,8 +444,6 @@ export default function FindOrdersPage() {
|
||||
visibleOrdersCount={visibleOrdersCount}
|
||||
stores={stores}
|
||||
transfers={[]}
|
||||
status={status}
|
||||
leadtime={leadtime}
|
||||
/>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
@@ -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<string, {icon: LucideIcon, color: string, status: string}>}
|
||||
*/
|
||||
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<Leadtime[]>([]);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 }) => (
|
||||
<TableRow>
|
||||
<TableCell colSpan={colSpan} className="h-24 text-center text-xs">
|
||||
@@ -25,7 +39,11 @@ const EmptyRow = memo(({ colSpan }: { colSpan: number }) => (
|
||||
</TableRow>
|
||||
));
|
||||
|
||||
// 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 <EmptyRow colSpan={visibleColumns.length} />;
|
||||
@@ -90,4 +112,8 @@ function OrdersTableBodyComponent({
|
||||
return <TableBody>{content}</TableBody>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Exporta o componente memoizado do corpo da tabela de pedidos
|
||||
* @type {React.NamedExoticComponent<OrdersTableBodyProps>}
|
||||
*/
|
||||
export const OrdersTableBody = memo(OrdersTableBodyComponent);
|
||||
256
src/components/orders/ProductSearchInput.tsx
Normal file
256
src/components/orders/ProductSearchInput.tsx
Normal file
@@ -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<Product[]>([]);
|
||||
const [loading, setLoading] = React.useState(false);
|
||||
const [searchQuery, setSearchQuery] = React.useState("");
|
||||
const [searchError, setSearchError] = React.useState<string | null>(null);
|
||||
const searchInputRef = React.useRef<HTMLInputElement>(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 (
|
||||
<div className="flex flex-col space-y-1">
|
||||
<div className="flex justify-between items-center">
|
||||
<Label
|
||||
htmlFor={id}
|
||||
className="text-xs font-medium"
|
||||
>
|
||||
{label}
|
||||
{selectedProduct && (
|
||||
<Badge variant="outline" className="ml-2 text-xs py-0">
|
||||
{selectedProduct.code ? `Cód: ${selectedProduct.code}` : `ID: ${selectedProduct.id}`}
|
||||
</Badge>
|
||||
)}
|
||||
</Label>
|
||||
</div>
|
||||
|
||||
{searchError && (
|
||||
<p className="text-xs text-red-500 mt-0.5">{searchError}</p>
|
||||
)}
|
||||
|
||||
<div className="relative">
|
||||
<Popover open={open} onOpenChange={setOpen}>
|
||||
<PopoverTrigger asChild>
|
||||
<Button
|
||||
variant="outline"
|
||||
role="combobox"
|
||||
aria-expanded={open}
|
||||
aria-label={ariaLabel || "Selecione um produto"}
|
||||
disabled={disabled}
|
||||
className={cn(
|
||||
"w-full justify-between h-8 transition-all",
|
||||
selectedProduct ? "pr-8" : "text-muted-foreground",
|
||||
disabled && "opacity-50 cursor-not-allowed"
|
||||
)}
|
||||
>
|
||||
{selectedProduct ? (
|
||||
<div className="flex items-center gap-2 overflow-hidden">
|
||||
<Package className="h-4 w-4 shrink-0" />
|
||||
<span className="truncate">
|
||||
{selectedProduct.name}
|
||||
{selectedProduct.code ? ` (${selectedProduct.code})` : ""}
|
||||
</span>
|
||||
</div>
|
||||
) : (
|
||||
<span className="truncate">{placeholder}</span>
|
||||
)}
|
||||
{selectedProduct ? null : (
|
||||
<ChevronsUpDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />
|
||||
)}
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
{selectedProduct && (
|
||||
<div
|
||||
className="absolute right-0 top-0 h-full flex items-center pr-2"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
handleClearSelection();
|
||||
}}
|
||||
>
|
||||
<X className="h-4 w-4 cursor-pointer" />
|
||||
<span className="sr-only">Limpar seleção</span>
|
||||
</div>
|
||||
)}
|
||||
<PopoverContent className="min-w-[400px] p-0">
|
||||
<Command>
|
||||
<CommandInput
|
||||
ref={searchInputRef}
|
||||
placeholder="Buscar produto por código, nome ou descrição..."
|
||||
value={searchQuery}
|
||||
onValueChange={setSearchQuery}
|
||||
className="h-9"
|
||||
/>
|
||||
{loading && (
|
||||
<div className="flex items-center justify-center py-6">
|
||||
<Loader2 className="h-6 w-6 animate-spin text-gray-400" />
|
||||
</div>
|
||||
)}
|
||||
{!loading && (
|
||||
<>
|
||||
<CommandEmpty>
|
||||
{searchQuery.length >= 2
|
||||
? "Nenhum produto encontrado com esse termo"
|
||||
: "Digite pelo menos 2 caracteres para buscar"}
|
||||
</CommandEmpty>
|
||||
{products.length > 0 && (
|
||||
<CommandGroup heading="Produtos">
|
||||
<CommandList>
|
||||
{products.map((product, index) => (
|
||||
<CommandItem
|
||||
key={product.id || product.code || `product-${index}`}
|
||||
value={product.name}
|
||||
onSelect={() => handleSelectProduct(product)}
|
||||
>
|
||||
<Package className="mr-2 h-4 w-4" />
|
||||
<div className="flex flex-col">
|
||||
<span className="font-medium">{product.name}</span>
|
||||
<div className="flex gap-2 text-xs text-gray-500">
|
||||
{product.code && (
|
||||
<span>Cód: {product.code}</span>
|
||||
)}
|
||||
{product.brand && (
|
||||
<span>Marca: {product.brand}</span>
|
||||
)}
|
||||
{product.department && (
|
||||
<span>Depto: {product.department}</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</CommandItem>
|
||||
))}
|
||||
</CommandList>
|
||||
</CommandGroup>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</Command>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -18,6 +18,18 @@ export interface TimelineEvent {
|
||||
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;
|
||||
@@ -202,6 +214,16 @@ export type PaymentStatus =
|
||||
document?: string;
|
||||
}
|
||||
|
||||
export interface Product {
|
||||
id: string;
|
||||
code: string;
|
||||
name: string;
|
||||
description?: string;
|
||||
brand?: string;
|
||||
department?: string;
|
||||
color?: string;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
@@ -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 {
|
||||
|
||||
@@ -84,7 +84,8 @@ const paramMapping: Record<string, keyof OrderSearchParams> = {
|
||||
createDateEnd: "createDateEnd",
|
||||
customerId: "customerId",
|
||||
document: "document",
|
||||
invoiceNumber: "invoiceNumber"
|
||||
invoiceNumber: "invoiceNumber",
|
||||
productId: "productId"
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -159,6 +160,15 @@ export function useOrderSearch(ordersPerPage: number = 8): UseOrderSearchReturn
|
||||
}));
|
||||
}, []);
|
||||
|
||||
// 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(() => {
|
||||
@@ -237,10 +247,13 @@ 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) {
|
||||
|
||||
@@ -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.
|
||||
@@ -179,10 +182,10 @@ export const dataConsultApi = {
|
||||
|
||||
/**
|
||||
* @param {string} filter - Filtro de produto (nome ou código).
|
||||
* @returns {Promise<any[]>} Produtos filtrados.
|
||||
* @returns {Promise<{success: boolean, data: any[]}>} Produtos filtrados.
|
||||
*/
|
||||
getProducts: (filter: string) =>
|
||||
fetchApi<any[]>(`/api/v1/data-consult/products/${filter}`),
|
||||
fetchApi<{ success: boolean; data: any[] }>(`/api/v1/data-consult/products/${filter}`),
|
||||
|
||||
/** @returns {Promise<any[]>} Todos os produtos */
|
||||
getAllProducts: () => fetchApi<any[]>("/api/v1/data-consult/all"),
|
||||
|
||||
Reference in New Issue
Block a user