import React, { useState, useEffect } from "react"; import LoadingSpinner from "../LoadingSpinner"; import { env } from "../../src/config/env"; import { authService } from "../../src/services/auth.service"; import { formatCurrency } from "../../utils/formatters"; import ConfirmDialog from "../ConfirmDialog"; import OrderItemsModal from "../OrderItemsModal"; import PrintOrderDialog from "../PrintOrderDialog"; import StimulsoftViewer from "../StimulsoftViewer"; import NoData from "../NoData"; import { Input } from "../ui/input"; import { Label } from "../ui/label"; import { Button } from "../ui/button"; import { CustomAutocomplete } from "../ui/autocomplete"; import { DateInput } from "../ui/date-input"; import { DataGridPremium, GridColDef } from "@mui/x-data-grid-premium"; import "../../lib/mui-license"; import { Box, IconButton } from "@mui/material"; import { Edit, Visibility, Print } from "@mui/icons-material"; interface PreOrder { data: string; idPreOrder: number; value: number; listValue: number; idCustomer: number; customer: string | number; idSeller: number; seller: string | number; status?: string; cpfPreCustomer?: string; namePreCustomer?: string; } interface PreOrderItem { productId: number; description: string; package: string; color?: string; local: string; quantity: number; price: number; subTotal: number; } interface Store { id: string; shortName: string; name: string; } const PreorderView: React.FC = () => { const [preOrders, setPreOrders] = useState([]); const [stores, setStores] = useState([]); const [loading, setLoading] = useState(false); const [error, setError] = useState(null); // Filtros const [selectedStore, setSelectedStore] = useState(""); const [startDate, setStartDate] = useState(""); const [endDate, setEndDate] = useState(""); const [preOrderId, setPreOrderId] = useState(""); const [document, setDocument] = useState(""); const [customerName, setCustomerName] = useState(""); // Modais const [showOrderItems, setShowOrderItems] = useState(false); const [orderItems, setOrderItems] = useState([]); const [selectedPreOrder, setSelectedPreOrder] = useState( null ); const [showInfoDialog, setShowInfoDialog] = useState(false); const [infoMessage, setInfoMessage] = useState(""); const [infoDescription, setInfoDescription] = useState(""); const [showCartLoadedDialog, setShowCartLoadedDialog] = useState(false); const [showPrintDialog, setShowPrintDialog] = useState(false); const [preOrderToPrint, setPreOrderToPrint] = useState(null); const [showPrintViewer, setShowPrintViewer] = useState(false); const [printUrl, setPrintUrl] = useState(""); const [printPreOrderId, setPrintPreOrderId] = useState( undefined ); const [printModel, setPrintModel] = useState(undefined); useEffect(() => { fetchStores(); }, []); const fetchStores = async () => { try { const token = authService.getToken(); const apiUrl = env.API_URL.replace(/\/$/, ""); const response = await fetch(`${apiUrl}/lists/store`, { method: "GET", headers: { "Content-Type": "application/json", ...(token && { Authorization: `Basic ${token}` }), }, }); if (response.ok) { const data = await response.json(); setStores(data); } } catch (err) { console.error("Erro ao buscar filiais:", err); } }; const handleSearch = async () => { setLoading(true); setError(null); try { const token = authService.getToken(); const apiUrl = env.API_URL.replace(/\/$/, ""); // Seguindo exatamente o padrão do Angular: primeiro obtém o seller, depois verifica se é gerente // O Angular getSeller() retorna user.seller diretamente (pode ser number ou string) let sellerId: string | number = authService.getSeller() || 0; // Se for gerente, sellerId = 0 (como no Angular) if (authService.isManager()) { sellerId = 0; } // Converter para número se for string, mas manter como está se já for número // O HttpParams do Angular aceita qualquer tipo e converte para string automaticamente if (typeof sellerId === "string") { const parsed = parseInt(sellerId, 10); sellerId = isNaN(parsed) ? 0 : parsed; } // Converter datas do formato YYYY-MM-DD para strings completas de data // O Angular envia objetos Date que são convertidos para strings completas pelo HttpParams // Exemplo: "Wed Jan 01 2025 00:00:00 GMT-0300 (Horário Padrão de Brasília)" let startDateValue: string = ""; let endDateValue: string = ""; if (startDate) { // Criar Date a partir da string YYYY-MM-DD no timezone local // Usar meia-noite local para garantir o formato correto const [year, month, day] = startDate.split("-").map(Number); const startDateObj = new Date(year, month - 1, day, 0, 0, 0, 0); startDateValue = startDateObj.toString(); } if (endDate) { // Criar Date a partir da string YYYY-MM-DD no timezone local // Usar meia-noite local para garantir o formato correto const [year, month, day] = endDate.split("-").map(Number); const endDateObj = new Date(year, month - 1, day, 0, 0, 0, 0); endDateValue = endDateObj.toString(); } // Seguindo exatamente o padrão do Angular: HttpParams.append() adiciona TODOS os parâmetros, // mesmo quando são null ou strings vazias. Isso é importante para o backend processar corretamente. // O Angular HttpParams usa uma codificação similar ao encodeURIComponent, mas preserva alguns caracteres // como ':' (dois pontos) que são seguros em query strings const encodeParam = (value: string): string => { // Primeiro codifica tudo let encoded = encodeURIComponent(value); // Depois decodifica os caracteres que o Angular preserva (dois pontos são seguros em query strings) encoded = encoded.replace(/%3A/g, ":"); return encoded; }; // O Angular HttpParams.append() converte automaticamente qualquer tipo para string // e sempre adiciona o parâmetro, mesmo quando o valor é null, undefined ou string vazia const buildQueryParam = ( key: string, value: string | number | null | undefined ): string => { // Se o valor for null ou undefined, enviar como string vazia (não como "null" ou "undefined") if (value === null || value === undefined) { return `${key}=`; } const strValue = value.toString(); // Se a string estiver vazia, ainda enviar o parâmetro (como o Angular faz) return strValue ? `${key}=${encodeParam(strValue)}` : `${key}=`; }; // Seguindo exatamente a ordem do Angular HttpParams.append() const queryParams: string[] = []; queryParams.push(buildQueryParam("seller", sellerId)); queryParams.push(buildQueryParam("store", selectedStore || null)); queryParams.push(buildQueryParam("start", startDateValue || null)); queryParams.push(buildQueryParam("end", endDateValue || null)); queryParams.push( buildQueryParam("idPreOrder", preOrderId ? parseInt(preOrderId, 10) : 0) ); queryParams.push(buildQueryParam("document", document || null)); queryParams.push(buildQueryParam("nameCustomer", customerName || null)); const url = `${apiUrl}/preorder/list?${queryParams.join("&")}`; const response = await fetch(url, { method: "GET", headers: { Accept: "application/json, text/plain, */*", "Content-Type": "application/json", ...(token && { Authorization: `Basic ${token}` }), }, }); if (!response.ok) { const errorData = await response.json().catch(() => ({})); throw new Error( errorData.message || `Erro ao buscar orçamentos: ${response.statusText}` ); } const result = await response.json(); // Seguindo exatamente o padrão do Angular: verificar result.success antes de usar result.data // O Angular faz: if (result.success) { this.preOrders = result.data; } if (result.success) { setPreOrders(result.data || []); } else { // Se result.success for false, não há dados para exibir setPreOrders([]); if (result.message) { setError(result.message); } } } catch (err) { console.error("Erro ao buscar orçamentos:", err); setError( err instanceof Error ? err.message : "Erro ao buscar orçamentos. Tente novamente." ); } finally { setLoading(false); } }; const handleClear = () => { setSelectedStore(""); setStartDate(""); setEndDate(""); setPreOrderId(""); setDocument(""); setCustomerName(""); setPreOrders([]); setError(null); }; const handleEditPreOrder = async (preOrder: PreOrder) => { // Validação: não pode editar se já foi utilizado (exatamente como no Angular) if (preOrder.status && preOrder.status === "ORÇAMENTO UTILIZADO") { setInfoMessage("Alterar Orçamento"); setInfoDescription( "Orçamento não pode ser editado.\nOrçamento já foi convertido em pedido de venda, alteração não permitida." ); setShowInfoDialog(true); return; } try { const token = authService.getToken(); const apiUrl = env.API_URL.replace(/\/$/, ""); // Seguindo exatamente o padrão do Angular: usar HttpParams com preOrderId const response = await fetch( `${apiUrl}/preorder/cart?preOrderId=${preOrder.idPreOrder}`, { method: "GET", headers: { "Content-Type": "application/json", ...(token && { Authorization: `Basic ${token}` }), }, } ); if (response.ok) { const result = await response.json(); console.log("📦 [PREORDER] Dados recebidos do backend:", { cartId: result.cartId, hasCustomer: !!result.customer, hasPaymentPlan: !!result.paymentPlan, hasBilling: !!result.billing, hasPartner: !!result.partner, hasAddress: !!result.address, hasPreCustomer: !!result.preCustomer, hasInvoiceStore: !!result.invoiceStore, }); // Salvar dados no localStorage exatamente como no Angular console.log( "📦 [PREORDER] Salvando cartId no localStorage:", result.cartId ); localStorage.setItem("cart", result.cartId); localStorage.setItem("customer", JSON.stringify(result.customer)); localStorage.setItem("paymentPlan", JSON.stringify(result.paymentPlan)); localStorage.setItem("billing", JSON.stringify(result.billing)); if (result.partner) { console.log("📦 [PREORDER] Salvando partner"); localStorage.setItem("partner", JSON.stringify(result.partner)); } if (result.address) { console.log("📦 [PREORDER] Salvando address"); localStorage.setItem("address", JSON.stringify(result.address)); } if (result.preCustomer) { console.log("📦 [PREORDER] Salvando preCustomer"); localStorage.setItem( "preCustomer", JSON.stringify(result.preCustomer) ); } console.log("📦 [PREORDER] Salvando invoiceStore"); localStorage.setItem( "invoiceStore", JSON.stringify(result.invoiceStore) ); // Criar OrderDelivery exatamente como no Angular const orderDelivery = { notification: result.notification1 ?? "", notification1: result.notification2 ?? "", notification2: "", notificationDelivery1: result.notificationDelivery1 ?? "", notificationDelivery2: result.notificationDelivery2 ?? "", notificationDelivery3: result.notificationDelivery3 ?? "", dateDelivery: result.deliveryDate, scheduleDelivery: result.squeduleDelivery, priorityDelivery: result.priorityDelivery, }; console.log("📦 [PREORDER] Salvando dataDelivery"); localStorage.setItem("dataDelivery", JSON.stringify(orderDelivery)); // Verificar se o cartId foi salvo corretamente const savedCartId = localStorage.getItem("cart"); console.log("📦 [PREORDER] CartId salvo no localStorage:", savedCartId); console.log("📦 [PREORDER] CartId recebido do backend:", result.cartId); console.log( "📦 [PREORDER] CartIds são iguais?", savedCartId === result.cartId ); // IMPORTANTE: Carregar os itens do carrinho ANTES de navegar // No Angular, o componente home-sales dispara LoadShoppingAction no ngOnInit // No React, precisamos garantir que os itens sejam carregados antes da navegação console.log( "📦 [PREORDER] Carregando itens do carrinho antes de navegar..." ); try { const { shoppingService } = await import( "../../src/services/shopping.service" ); const items = await shoppingService.getShoppingItems(result.cartId); console.log( "📦 [PREORDER] Itens do carrinho carregados:", items.length ); console.log("📦 [PREORDER] Itens:", items); // Salvar os itens no sessionStorage para garantir que sejam carregados após navegação sessionStorage.setItem("pendingCartItems", JSON.stringify(items)); sessionStorage.setItem("pendingCartId", result.cartId); console.log( "📦 [PREORDER] Itens salvos no sessionStorage para carregamento após navegação" ); } catch (loadError) { console.error( "📦 [PREORDER] Erro ao carregar itens antes de navegar:", loadError ); // Continuar mesmo se houver erro, o useCart tentará carregar depois } // Disparar evento customizado para notificar mudança no cartId console.log("📦 [PREORDER] Disparando evento cartUpdated"); const storageEvent = new Event("cartUpdated") as any; storageEvent.key = "cart"; storageEvent.newValue = result.cartId; window.dispatchEvent(storageEvent); // Mostrar mensagem de sucesso informando que os dados do carrinho foram carregados console.log("📦 [PREORDER] Dados do carrinho carregados com sucesso"); setShowCartLoadedDialog(true); } else { const errorData = await response.json().catch(() => ({})); throw new Error( errorData.message || "Erro ao carregar dados do orçamento" ); } } catch (err) { console.error("Erro ao editar orçamento:", err); // Tratamento de erro exatamente como no Angular setInfoMessage("Consulta de orçamentos"); setInfoDescription( err instanceof Error ? `Ops! Houve um erro ao consultar os orçamentos.\n${err.message}` : "Ops! Houve um erro ao consultar os orçamentos." ); setShowInfoDialog(true); } }; const handleViewItems = async (preOrder: PreOrder) => { try { const token = authService.getToken(); const apiUrl = env.API_URL.replace(/\/$/, ""); const response = await fetch( `${apiUrl}/preorder/itens/${preOrder.idPreOrder}`, { method: "GET", headers: { "Content-Type": "application/json", ...(token && { Authorization: `Basic ${token}` }), }, } ); if (response.ok) { const items = await response.json(); // Converter para o formato esperado pelo OrderItemsModal const convertedItems = items.map((item: PreOrderItem) => ({ productId: item.productId, description: item.description, package: item.package, color: item.color, local: item.local, quantity: item.quantity, price: item.price, subTotal: item.subTotal, })); setOrderItems(convertedItems); setSelectedPreOrder(preOrder); setShowOrderItems(true); } } catch (err) { console.error("Erro ao buscar itens do orçamento:", err); setInfoMessage("Erro"); setInfoDescription("Não foi possível carregar os itens do orçamento."); setShowInfoDialog(true); } }; const handleCloseOrderItemsModal = () => { setShowOrderItems(false); setOrderItems([]); setSelectedPreOrder(null); }; const handlePrintPreOrder = (preOrder: PreOrder) => { setPreOrderToPrint(preOrder); setShowPrintDialog(true); }; const handleConfirmPrint = (model: "A" | "B" | "P") => { if (!preOrderToPrint) return; // Construir URL do viewer seguindo o padrão do Angular const viewerUrl = env.PRINT_VIEWER_URL.replace("{action}", "InitViewer"); const url = `${viewerUrl}?order=${preOrderToPrint.idPreOrder}&model=${model}`; // Configurar e mostrar o viewer setPrintUrl(url); setPrintPreOrderId(preOrderToPrint.idPreOrder); setPrintModel(model); setShowPrintViewer(true); setShowPrintDialog(false); }; const formatDate = (dateString: string | null | undefined): string => { if (!dateString || dateString === "null" || dateString === "undefined") { return ""; } try { // Tentar criar a data let date: Date; // Se já for uma string de data válida, usar diretamente if (typeof dateString === "string") { // Tentar parsear diferentes formatos date = new Date(dateString); // Se falhar, tentar formatos alternativos if (isNaN(date.getTime())) { // Tentar formato brasileiro DD/MM/YYYY const parts = dateString.split("/"); if (parts.length === 3) { date = new Date( parseInt(parts[2]), parseInt(parts[1]) - 1, parseInt(parts[0]) ); } else { // Tentar formato ISO date = new Date( dateString.replace(/(\d{2})\/(\d{2})\/(\d{4})/, "$3-$2-$1") ); } } } else { date = new Date(dateString); } // Verificar se a data é válida if (isNaN(date.getTime())) { console.warn("Data inválida:", dateString); return ""; } // Formatar no padrão DD/MM/YYYY const day = String(date.getDate()).padStart(2, "0"); const month = String(date.getMonth() + 1).padStart(2, "0"); const year = date.getFullYear(); return `${day}/${month}/${year}`; } catch (error) { console.error("Erro ao formatar data:", dateString, error); return ""; } }; const getStatusColor = (status: string): string => { switch (status) { case "ORÇAMENTO UTILIZADO": return "bg-green-100 text-green-800"; case "PENDENTE": return "bg-yellow-100 text-yellow-800"; default: return "bg-slate-100 text-slate-800"; } }; const getCustomerDisplay = (preOrder: PreOrder): string => { if (preOrder.cpfPreCustomer && preOrder.idCustomer === 1) { return `${preOrder.namePreCustomer} (PRE)`; } return typeof preOrder.customer === "string" ? preOrder.customer : String(preOrder.customer); }; // Definir colunas do DataGrid const columns: GridColDef[] = [ { field: "actions", headerName: "Ações", width: 200, sortable: false, filterable: false, disableColumnMenu: true, renderCell: (params) => { const preOrder = params.row as PreOrder; return ( handleEditPreOrder(preOrder)} sx={{ color: "#64748b", "&:hover": { backgroundColor: "#f1f5f9", color: "#475569" }, }} title="Editar orçamento" > handleViewItems(preOrder)} sx={{ color: "#64748b", "&:hover": { backgroundColor: "#f1f5f9", color: "#475569" }, }} title="Ver itens do orçamento" > handlePrintPreOrder(preOrder)} sx={{ color: "#64748b", "&:hover": { backgroundColor: "#f1f5f9", color: "#475569" }, }} title="Imprimir orçamento" > ); }, }, { field: "data", headerName: "Data", width: 120, valueFormatter: (value) => { if (!value) return ""; return formatDate(String(value)); }, }, { field: "idPreOrder", headerName: "N.Orçamento", width: 130, headerAlign: "left", }, { field: "status", headerName: "Situação", width: 180, renderCell: (params) => { const status = (params.value as string) || "PENDENTE"; return ( {status} ); }, }, { field: "idCustomer", headerName: "Cód.Cliente", width: 120, headerAlign: "left", }, { field: "customer", headerName: "Cliente", width: 300, flex: 1, renderCell: (params) => { const preOrder = params.row as PreOrder; return (
{getCustomerDisplay(preOrder)} {preOrder.cpfPreCustomer && preOrder.idCustomer === 1 && ( PRE )}
); }, }, { field: "value", headerName: "Valor", width: 130, headerAlign: "right", align: "right", valueFormatter: (value) => formatCurrency(value as number), }, { field: "seller", headerName: "Vendedor", width: 200, valueFormatter: (value) => { return typeof value === "string" ? value : String(value); }, }, ]; return (
{/* Header */}

Orçamentos Pendentes

{/* Filtros */}
{/* Filial de venda */}
({ value: store.id, label: store.shortName, }))} value={selectedStore} onValueChange={setSelectedStore} placeholder="Selecione a filial de venda..." />
{/* Número do orçamento */}
setPreOrderId(e.target.value)} placeholder="Informe o número do orçamento" />
{/* Data inicial */}
setStartDate(e.target.value)} />
{/* Data final */}
setEndDate(e.target.value)} />
{/* CPF/CNPJ */}
setDocument(e.target.value)} placeholder="Informe o CPF ou CNPJ do cliente" />
{/* Nome do cliente */}
setCustomerName(e.target.value)} placeholder="Informe o nome ou razão social do cliente" />
{/* Botões de Ação */}
{/* Tabela de Orçamentos */} {error && (

{error}

)} {loading && (
)} {!loading && preOrders.length > 0 && (

Orçamentos encontrados: {preOrders.length}

row.idPreOrder} disableRowSelectionOnClick hideFooter sx={{ border: "none", "& .MuiDataGrid-columnHeaders": { backgroundColor: "#f8fafc", borderBottom: "1px solid #e2e8f0", "& .MuiDataGrid-columnHeader": { fontSize: "9px", fontWeight: 900, color: "#94a3b8", textTransform: "uppercase", letterSpacing: "0.2em", padding: "12px 16px", "& .MuiDataGrid-columnHeaderTitle": { fontWeight: 900, }, }, }, "& .MuiDataGrid-row": { "&:hover": { backgroundColor: "#f8fafc", }, }, "& .MuiDataGrid-cell": { fontSize: "12px", color: "#475569", padding: "16px", borderBottom: "1px solid #f1f5f9", "&:focus": { outline: "none", }, "&:focus-within": { outline: "none", }, }, "& .MuiDataGrid-cell[data-field='idPreOrder']": { fontWeight: 700, color: "#0f172a", }, "& .MuiDataGrid-cell[data-field='value']": { fontWeight: 700, color: "#0f172a", }, "& .MuiDataGrid-cell[data-field='data']": { color: "#64748b", }, "& .MuiDataGrid-cell[data-field='idCustomer']": { color: "#64748b", }, "& .MuiDataGrid-cell[data-field='customer']": { color: "#64748b", }, "& .MuiDataGrid-virtualScroller": { overflowY: "auto", }, "& .MuiDataGrid-virtualScrollerContent": { height: "auto !important", }, }} />
)} {!loading && preOrders.length === 0 && !error && (
)} {/* Modal de Itens do Orçamento */} {/* Dialog de Informação */} setShowInfoDialog(false)} onConfirm={() => setShowInfoDialog(false)} type="info" title={infoMessage} message={infoDescription} confirmText="OK" showWarning={false} /> {/* Dialog de Carrinho Carregado */} { setShowCartLoadedDialog(false); // Navegar para página de produtos após fechar o dialog setTimeout(() => { window.location.href = "/#/sales/home"; }, 100); }} onConfirm={() => { setShowCartLoadedDialog(false); // Navegar para página de produtos após confirmar setTimeout(() => { window.location.href = "/#/sales/home"; }, 100); }} type="success" title="Carrinho carregado" message="Os dados do carrinho foram carregados com sucesso!\n\nVocê será redirecionado para a página de produtos." confirmText="OK" showWarning={false} /> {/* Dialog de Seleção de Modelo de Impressão */} {preOrderToPrint && ( { setShowPrintDialog(false); setPreOrderToPrint(null); }} onConfirm={handleConfirmPrint} orderId={preOrderToPrint.idPreOrder} includeModelP={true} /> )} {/* Modal do Viewer de Impressão */} {showPrintViewer && printUrl && (
{/* Header */}

Orçamento de venda

Visualização e Impressão

{/* Viewer Content */}
{ setShowPrintViewer(false); setPrintUrl(""); setPrintPreOrderId(undefined); setPrintModel(undefined); }} />
)}
); }; export default PreorderView;