Vendaweb-portal/components/dashboard/PreorderView.tsx

994 lines
33 KiB
TypeScript
Raw Normal View History

2026-01-08 12:09:16 +00:00
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<PreOrder[]>([]);
const [stores, setStores] = useState<Store[]>([]);
const [loading, setLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
// Filtros
const [selectedStore, setSelectedStore] = useState<string>("");
const [startDate, setStartDate] = useState<string>("");
const [endDate, setEndDate] = useState<string>("");
const [preOrderId, setPreOrderId] = useState<string>("");
const [document, setDocument] = useState<string>("");
const [customerName, setCustomerName] = useState<string>("");
// Modais
const [showOrderItems, setShowOrderItems] = useState(false);
const [orderItems, setOrderItems] = useState<PreOrderItem[]>([]);
const [selectedPreOrder, setSelectedPreOrder] = useState<PreOrder | null>(
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<PreOrder | null>(null);
const [showPrintViewer, setShowPrintViewer] = useState(false);
const [printUrl, setPrintUrl] = useState<string>("");
const [printPreOrderId, setPrintPreOrderId] = useState<number | undefined>(
undefined
);
const [printModel, setPrintModel] = useState<string | undefined>(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 (
<Box sx={{ display: "flex", gap: 0.5 }}>
<IconButton
size="small"
onClick={() => handleEditPreOrder(preOrder)}
sx={{
color: "#64748b",
"&:hover": { backgroundColor: "#f1f5f9", color: "#475569" },
}}
title="Editar orçamento"
>
<Edit fontSize="small" />
</IconButton>
<IconButton
size="small"
onClick={() => handleViewItems(preOrder)}
sx={{
color: "#64748b",
"&:hover": { backgroundColor: "#f1f5f9", color: "#475569" },
}}
title="Ver itens do orçamento"
>
<Visibility fontSize="small" />
</IconButton>
<IconButton
size="small"
onClick={() => handlePrintPreOrder(preOrder)}
sx={{
color: "#64748b",
"&:hover": { backgroundColor: "#f1f5f9", color: "#475569" },
}}
title="Imprimir orçamento"
>
<Print fontSize="small" />
</IconButton>
</Box>
);
},
},
{
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 (
<span
className={`px-2 py-1 rounded-md text-xs font-bold uppercase ${getStatusColor(
status
)}`}
>
{status}
</span>
);
},
},
{
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 (
<div className="flex items-center gap-2">
<span>{getCustomerDisplay(preOrder)}</span>
{preOrder.cpfPreCustomer && preOrder.idCustomer === 1 && (
<span className="px-2 py-0.5 bg-red-100 text-red-800 text-xs font-bold rounded">
PRE
</span>
)}
</div>
);
},
},
{
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 (
<div className="space-y-0">
{/* Header */}
<header>
<h2 className="text-2xl font-black text-[#002147] mb-2">
Orçamentos Pendentes
</h2>
</header>
{/* Filtros */}
<div className="bg-white p-6 rounded-2xl border border-slate-100 shadow-sm">
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
{/* Filial de venda */}
<div>
<Label htmlFor="store">Filial de venda</Label>
<CustomAutocomplete
id="store"
options={stores.map((store) => ({
value: store.id,
label: store.shortName,
}))}
value={selectedStore}
onValueChange={setSelectedStore}
placeholder="Selecione a filial de venda..."
/>
</div>
{/* Número do orçamento */}
<div>
<Label htmlFor="preOrderId">Número do orçamento</Label>
<Input
id="preOrderId"
type="text"
value={preOrderId}
onChange={(e) => setPreOrderId(e.target.value)}
placeholder="Informe o número do orçamento"
/>
</div>
{/* Data inicial */}
<div>
<Label htmlFor="startDate">Data inicial</Label>
<DateInput
id="startDate"
value={startDate}
onChange={(e) => setStartDate(e.target.value)}
/>
</div>
{/* Data final */}
<div>
<Label htmlFor="endDate">Data final</Label>
<DateInput
id="endDate"
value={endDate}
onChange={(e) => setEndDate(e.target.value)}
/>
</div>
{/* CPF/CNPJ */}
<div>
<Label htmlFor="document">CPF / CNPJ</Label>
<Input
id="document"
type="text"
value={document}
onChange={(e) => setDocument(e.target.value)}
placeholder="Informe o CPF ou CNPJ do cliente"
/>
</div>
{/* Nome do cliente */}
<div>
<Label htmlFor="customerName">Nome do cliente</Label>
<Input
id="customerName"
type="text"
value={customerName}
onChange={(e) => setCustomerName(e.target.value)}
placeholder="Informe o nome ou razão social do cliente"
/>
</div>
</div>
{/* Botões de Ação */}
<div className="mt-6 flex gap-3">
<Button onClick={handleSearch} disabled={loading} className="flex-1">
{loading ? "Pesquisando..." : "Pesquisar"}
</Button>
<Button
onClick={handleClear}
disabled={loading}
variant="outline"
className="flex-1"
>
Limpar
</Button>
</div>
</div>
{/* Tabela de Orçamentos */}
{error && (
<div className="bg-red-50 border border-red-200 rounded-2xl p-4">
<p className="text-red-600 text-sm font-medium">{error}</p>
</div>
)}
{loading && (
<div className="flex items-center justify-center py-20">
<LoadingSpinner />
</div>
)}
{!loading && preOrders.length > 0 && (
<div className="bg-white rounded-2xl border border-slate-100 overflow-hidden">
<div className="p-5 border-b border-slate-50">
<h3 className="text-[9px] font-black text-slate-400 uppercase tracking-[0.2em]">
Orçamentos encontrados: {preOrders.length}
</h3>
</div>
<Box sx={{ height: 600, width: "100%" }}>
<DataGridPremium
rows={preOrders}
columns={columns}
getRowId={(row) => 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",
},
}}
/>
</Box>
</div>
)}
{!loading && preOrders.length === 0 && !error && (
<div className="bg-white rounded-2xl border border-slate-100 overflow-hidden">
<NoData
title="Nenhum orçamento encontrado"
description="Não foram encontrados orçamentos com os filtros informados. Tente ajustar os parâmetros de pesquisa ou verifique se há orçamentos no período selecionado."
icon="clipboard"
variant="outline"
/>
</div>
)}
{/* Modal de Itens do Orçamento */}
<OrderItemsModal
isOpen={showOrderItems}
onClose={handleCloseOrderItemsModal}
orderId={selectedPreOrder?.idPreOrder || 0}
orderItems={orderItems}
/>
{/* Dialog de Informação */}
<ConfirmDialog
isOpen={showInfoDialog}
onClose={() => setShowInfoDialog(false)}
onConfirm={() => setShowInfoDialog(false)}
type="info"
title={infoMessage}
message={infoDescription}
confirmText="OK"
showWarning={false}
/>
{/* Dialog de Carrinho Carregado */}
<ConfirmDialog
isOpen={showCartLoadedDialog}
onClose={() => {
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 && (
<PrintOrderDialog
isOpen={showPrintDialog}
onClose={() => {
setShowPrintDialog(false);
setPreOrderToPrint(null);
}}
onConfirm={handleConfirmPrint}
orderId={preOrderToPrint.idPreOrder}
includeModelP={true}
/>
)}
{/* Modal do Viewer de Impressão */}
{showPrintViewer && printUrl && (
<div className="fixed inset-0 z-[300] flex items-center justify-center bg-black/80">
<div className="relative bg-white rounded-3xl shadow-2xl w-[95%] h-[90vh] max-w-7xl overflow-hidden flex flex-col">
{/* Header */}
<div className="p-6 bg-[#002147] text-white rounded-t-3xl relative overflow-hidden flex items-center justify-between">
<div>
<h3 className="text-xl font-black">Orçamento de venda</h3>
<p className="text-xs text-blue-400 font-bold uppercase tracking-wider mt-0.5">
Visualização e Impressão
</p>
</div>
<button
onClick={() => {
setShowPrintViewer(false);
setPrintUrl("");
setPrintPreOrderId(undefined);
setPrintModel(undefined);
}}
className="p-2 text-white/70 hover:text-white hover:bg-white/10 rounded-lg transition-colors"
>
<svg
className="w-6 h-6"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M6 18L18 6M6 6l12 12"
/>
</svg>
</button>
</div>
{/* Viewer Content */}
<div className="flex-1 overflow-hidden">
<StimulsoftViewer
requestUrl={printUrl}
action="InitViewer"
width="100%"
height="100%"
onClose={() => {
setShowPrintViewer(false);
setPrintUrl("");
setPrintPreOrderId(undefined);
setPrintModel(undefined);
}}
/>
</div>
</div>
</div>
)}
</div>
);
};
export default PreorderView;