Vendaweb-portal/components/dashboard/OrdersView.tsx

1146 lines
37 KiB
TypeScript
Raw Permalink 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 ReceivePixDialog from "../ReceivePixDialog";
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 Order {
createDate: string;
orderId: number;
sellerId: number;
store: string;
status: string;
customerId: number;
customerName: string;
orderValue: number;
itens: number;
pixCreate: string;
}
interface OrderItem {
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 OrdersView: React.FC = () => {
const [orders, setOrders] = useState<Order[]>([]);
const [stores, setStores] = useState<Store[]>([]);
const [loading, setLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
// Filtros
const [selectedStore, setSelectedStore] = useState<string>("");
const [orderId, setOrderId] = useState<string>("");
const [startDate, setStartDate] = useState<string>("");
const [endDate, setEndDate] = useState<string>("");
const [document, setDocument] = useState<string>("");
const [customerName, setCustomerName] = useState<string>("");
// Modais
const [showOrderItems, setShowOrderItems] = useState(false);
const [orderItems, setOrderItems] = useState<OrderItem[]>([]);
const [selectedOrder, setSelectedOrder] = useState<Order | null>(null);
const [showConfirmDialog, setShowConfirmDialog] = useState(false);
const [confirmDialogType, setConfirmDialogType] = useState<
"info" | "warning" | "error" | "success" | "delete" | "confirm"
>("info");
const [confirmDialogTitle, setConfirmDialogTitle] = useState("");
const [confirmDialogMessage, setConfirmDialogMessage] = useState("");
const [showCartLoadedDialog, setShowCartLoadedDialog] = useState(false);
const [showPrintDialog, setShowPrintDialog] = useState(false);
const [orderToPrint, setOrderToPrint] = useState<Order | null>(null);
const [showPrintViewer, setShowPrintViewer] = useState(false);
const [printUrl, setPrintUrl] = useState<string>("");
const [printOrderId, setPrintOrderId] = useState<number | undefined>(
undefined
);
const [printModel, setPrintModel] = useState<string | undefined>(undefined);
// Estados PIX
const [showReceivePix, setShowReceivePix] = useState(false);
const [orderForPix, setOrderForPix] = useState<Order | null>(null);
const [pixValue, setPixValue] = useState<number>(0);
const [showQrCode, setShowQrCode] = useState(false);
const [qrCodePix, setQrCodePix] = useState<string>("");
const [processingPix, setProcessingPix] = useState(false);
const [showPixReceipt, setShowPixReceipt] = useState(false);
const [pixReceiptUrl, setPixReceiptUrl] = useState<string>("");
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 o padrão do Angular: sellerId sempre 0
let sellerId = 0;
if (authService.isManager()) {
sellerId = 0;
}
const params = new URLSearchParams({
"x-store": selectedStore || "",
document: document || "",
name: customerName.toUpperCase() || "",
initialDate: startDate || "",
finalDate: endDate || "",
sellerId: sellerId.toString(),
idOrder: orderId || "",
});
const response = await fetch(
`${apiUrl}/order/list?${params.toString()}`,
{
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 pedidos: ${response.statusText}`
);
}
const data = await response.json();
setOrders(data || []);
} catch (err) {
console.error("Erro ao buscar pedidos:", err);
setError(
err instanceof Error
? err.message
: "Erro ao buscar pedidos. Tente novamente."
);
} finally {
setLoading(false);
}
};
const handleClear = () => {
setSelectedStore("");
setOrderId("");
setStartDate("");
setEndDate("");
setDocument("");
setCustomerName("");
setOrders([]);
setError(null);
};
const handleViewItems = async (order: Order) => {
try {
const token = authService.getToken();
const apiUrl = env.API_URL.replace(/\/$/, "");
const response = await fetch(`${apiUrl}/order/itens/${order.orderId}`, {
method: "GET",
headers: {
"Content-Type": "application/json",
...(token && { Authorization: `Basic ${token}` }),
},
});
if (response.ok) {
const items = await response.json();
setOrderItems(items);
setSelectedOrder(order);
setShowOrderItems(true);
}
} catch (err) {
console.error("Erro ao buscar itens do pedido:", err);
setConfirmDialogType("error");
setConfirmDialogTitle("Erro");
setConfirmDialogMessage("Não foi possível carregar os itens do pedido.");
setShowConfirmDialog(true);
}
};
const handleCloseOrderItemsModal = () => {
setShowOrderItems(false);
setOrderItems([]);
setSelectedOrder(null);
};
const handleEditOrder = async (order: Order) => {
// Validação 1: Status FATURADO ou MONTADO (exatamente como no Angular)
if (order.status === "FATURADO" || order.status === "MONTADO") {
setConfirmDialogType("warning");
setConfirmDialogTitle("Pedido não pode ser alterado!");
setConfirmDialogMessage(
`Pedido encontra-se ${order.status}, sua alteração não é permitida.`
);
setShowConfirmDialog(true);
return;
}
// Validação 2: PIX já gerado (exatamente como no Angular)
if (order.pixCreate === "S") {
setConfirmDialogType("warning");
setConfirmDialogTitle("Pedido não pode ser alterado!");
setConfirmDialogMessage(
"PIX já gerado para este pedido, alteração não é permitida."
);
setShowConfirmDialog(true);
return;
}
// Validação 3: Vendedor diferente e não é manager (exatamente como no Angular)
const sellerId = authService.getSeller();
const isManager = authService.isManager();
if (order.sellerId !== Number(sellerId) && !isManager) {
setConfirmDialogType("warning");
setConfirmDialogTitle("Pedido não pode ser alterado!");
setConfirmDialogMessage(
"Pedido de outro vendedor, somente o vendedor responsável da venda poderá alterar o pedido."
);
setShowConfirmDialog(true);
return;
}
// Buscar dados do carrinho e navegar para edição
try {
const token = authService.getToken();
const apiUrl = env.API_URL.replace(/\/$/, "");
console.log(
"📦 [ORDER] Buscando dados do carrinho para pedido:",
order.orderId
);
const response = await fetch(`${apiUrl}/order/cart/${order.orderId}`, {
method: "GET",
headers: {
"Content-Type": "application/json",
...(token && { Authorization: `Basic ${token}` }),
},
});
if (response.ok) {
const result = await response.json();
console.log("📦 [ORDER] Dados recebidos do backend:", {
cartId: result.cartId,
hasCustomer: !!result.customer,
hasPaymentPlan: !!result.paymentPlan,
hasBilling: !!result.billing,
hasPartner: !!result.partner,
hasAddress: !!result.address,
hasInvoiceStore: !!result.invoiceStore,
});
// Salvar dados no localStorage exatamente como no Angular
console.log(
"📦 [ORDER] 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("📦 [ORDER] Salvando partner");
localStorage.setItem("partner", JSON.stringify(result.partner));
}
if (result.address) {
console.log("📦 [ORDER] Salvando address");
localStorage.setItem("address", JSON.stringify(result.address));
}
console.log("📦 [ORDER] Salvando invoiceStore");
localStorage.setItem(
"invoiceStore",
JSON.stringify(result.invoiceStore)
);
// Criar OrderDelivery exatamente como no Angular
// Nota: No Angular é 'scheduleDelivery', não 'squeduleDelivery'
const orderDelivery = {
notification: result.notification1 ?? "",
notification1: result.notification2 ?? "",
notification2: "",
notificationDelivery1: result.notificationDelivery1 ?? "",
notificationDelivery2: result.notificationDelivery2 ?? "",
notificationDelivery3: result.notificationDelivery3 ?? "",
dateDelivery: result.deliveryDate,
scheduleDelivery:
result.scheduleDelivery ?? result.squeduleDelivery ?? "",
priorityDelivery: result.priorityDelivery,
};
console.log("📦 [ORDER] Salvando dataDelivery");
localStorage.setItem("dataDelivery", JSON.stringify(orderDelivery));
// 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(
"📦 [ORDER] 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("📦 [ORDER] Itens do carrinho carregados:", items.length);
console.log("📦 [ORDER] 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(
"📦 [ORDER] Itens salvos no sessionStorage para carregamento após navegação"
);
} catch (loadError) {
console.error(
"📦 [ORDER] 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("📦 [ORDER] 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("📦 [ORDER] 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 pedido"
);
}
} catch (err) {
console.error("📦 [ORDER] Erro ao editar pedido:", err);
setConfirmDialogType("error");
setConfirmDialogTitle("Erro");
setConfirmDialogMessage(
err instanceof Error
? err.message
: "Não foi possível carregar os dados do pedido."
);
setShowConfirmDialog(true);
}
};
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 "FATURADO":
return "bg-green-100 text-green-800";
case "MONTADO":
return "bg-blue-100 text-blue-800";
case "LIBERADO":
return "bg-yellow-100 text-yellow-800";
case "BLOQUEADO":
return "bg-red-100 text-red-800";
default:
return "bg-slate-100 text-slate-800";
}
};
const handlePrintOrder = (order: Order) => {
setOrderToPrint(order);
setShowPrintDialog(true);
};
const handleConfirmPrint = (model: "A" | "B") => {
if (!orderToPrint) return;
// Construir URL do viewer seguindo o padrão do Angular
const viewerUrl = env.PRINT_VIEWER_URL.replace(
"{action}",
"InitViewerOrder"
);
const url = `${viewerUrl}?orderId=${orderToPrint.orderId}&model=${model}`;
// Configurar e mostrar o viewer
setPrintUrl(url);
setPrintOrderId(orderToPrint.orderId);
setPrintModel(model);
setShowPrintViewer(true);
setShowPrintDialog(false);
};
const handlePixOrder = async (order: Order) => {
// Validações
if (order.status === "FATURADO") {
setConfirmDialogType("warning");
setConfirmDialogTitle("Pedido já esta FATURADO!");
setConfirmDialogMessage(
"Para geração do PIX o pedido deve estar na posição LIBERADO ou BLOQUEADO."
);
setShowConfirmDialog(true);
return;
}
if (order.customerId === 1) {
setConfirmDialogType("warning");
setConfirmDialogTitle("Não é permido gerar PIX para CONSUMIDOR FINAL!");
setConfirmDialogMessage(
"Para geração do PIX é necessário identificar o cliente no pedido de venda!"
);
setShowConfirmDialog(true);
return;
}
setOrderForPix(order);
setPixValue(parseFloat(order.orderValue.toFixed(2)));
setShowQrCode(false);
setQrCodePix("");
// Buscar PIX existente
try {
const token = authService.getToken();
const apiUrl = env.API_URL_PIX.replace(/\/$/, "");
const response = await fetch(
`${apiUrl}/payment/pix/santander/find/${order.orderId}`,
{
method: "GET",
headers: {
"Content-Type": "application/json",
...(token && { Authorization: `Basic ${token}` }),
},
}
);
if (response.ok) {
const pix = await response.json();
if (!pix || pix === null) {
// Não existe PIX, mostrar dialog para criar
setShowReceivePix(true);
} else {
if (pix.status === "CONCLUIDA") {
// PIX já foi concluído, abrir comprovante
openPixReceipt(order.orderId);
} else {
// PIX existe mas não foi concluído, mostrar QR Code
setQrCodePix(pix.qrCode);
setPixValue(pix.valor?.original || order.orderValue);
setShowQrCode(true);
setShowReceivePix(true);
}
}
} else {
// Erro ao buscar, mostrar dialog para criar
setShowReceivePix(true);
}
} catch (err) {
console.error("Erro ao consultar PIX:", err);
setConfirmDialogType("error");
setConfirmDialogTitle("Pedido de venda");
setConfirmDialogMessage(
"Erro ao consultar dados do PIX, comunique ao TI."
);
setShowConfirmDialog(true);
}
};
const handleConfirmPix = async (value: number) => {
if (!orderForPix) return;
// Validação: valor não pode ser superior ao valor do pedido
if (value > orderForPix.orderValue) {
setConfirmDialogType("warning");
setConfirmDialogTitle("Geração pix");
setConfirmDialogMessage(
"O Valor do PIX não pode ser superior ao valor do pedido de venda!"
);
setShowConfirmDialog(true);
setShowReceivePix(false);
return;
}
setProcessingPix(true);
try {
const token = authService.getToken();
const apiUrl = env.API_URL_PIX.replace(/\/$/, "");
const pixOrder = {
orderId: orderForPix.orderId,
customerId: orderForPix.customerId,
amount: value,
};
const response = await fetch(`${apiUrl}/payment/pix/santander/create`, {
method: "POST",
headers: {
"Content-Type": "application/json",
...(token && { Authorization: `Basic ${token}` }),
},
body: JSON.stringify(pixOrder),
});
if (response.ok) {
const result = await response.json();
setQrCodePix(result.qrCode);
setPixValue(value);
setShowQrCode(true);
setProcessingPix(false);
} else {
const errorData = await response.json().catch(() => ({}));
throw new Error(errorData.message || "Erro ao criar cobrança PIX");
}
} catch (err) {
console.error("Erro ao gerar PIX:", err);
setProcessingPix(false);
setShowReceivePix(false);
setConfirmDialogType("error");
setConfirmDialogTitle("Pedido de venda");
setConfirmDialogMessage(
err instanceof Error ? err.message : "Erro ao criar cobrança PIX"
);
setShowConfirmDialog(true);
}
};
const openPixReceipt = (orderId: number) => {
const viewerUrl = env.PRINT_VIEWER_URL.replace(
"{action}",
"InitViewerComprovantePix"
);
const url = `${viewerUrl}?order=${orderId}`;
setPixReceiptUrl(url);
setShowPixReceipt(true);
};
// Definir colunas do DataGrid
const columns: GridColDef[] = [
{
field: "actions",
headerName: "Ações",
width: 200,
sortable: false,
filterable: false,
disableColumnMenu: true,
renderCell: (params) => {
const order = params.row as Order;
return (
<Box sx={{ display: "flex", gap: 0.5 }}>
<IconButton
size="small"
onClick={() => handleEditOrder(order)}
sx={{
color: "#64748b",
"&:hover": { backgroundColor: "#f1f5f9", color: "#475569" },
}}
title="Editar pedido"
>
<Edit fontSize="small" />
</IconButton>
<IconButton
size="small"
onClick={() => handleViewItems(order)}
sx={{
color: "#64748b",
"&:hover": { backgroundColor: "#f1f5f9", color: "#475569" },
}}
title="Ver itens do pedido"
>
<Visibility fontSize="small" />
</IconButton>
<IconButton
size="small"
onClick={() => handlePrintOrder(order)}
sx={{
color: "#64748b",
"&:hover": { backgroundColor: "#f1f5f9", color: "#475569" },
}}
title="Imprimir pedido"
>
<Print fontSize="small" />
</IconButton>
<IconButton
size="small"
onClick={() => handlePixOrder(order)}
sx={{
color: "#14b8a6",
"&:hover": { backgroundColor: "#f0fdfa", color: "#0d9488" },
}}
title="Gerar PIX"
>
<svg
width="18"
height="18"
viewBox="0 0 18 18"
fill="currentColor"
xmlns="http://www.w3.org/2000/svg"
>
<path d="M11.917 11.71a2.046 2.046 0 0 1-1.454-.602l-2.1-2.1a.4.4 0 0 0-.551 0l-2.108 2.108a2.044 2.044 0 0 1-1.454.602h-.414l2.66 2.66c.83.83 2.177.83 3.007 0l2.667-2.668h-.253zM4.25 4.282c.55 0 1.066.214 1.454.602l2.108 2.108a.39.39 0 0 0 .552 0l2.1-2.1a2.044 2.044 0 0 1 1.453-.602h.253L9.503 1.623a2.127 2.127 0 0 0-3.007 0l-2.66 2.66h.414z" />
<path d="m14.377 6.496-1.612-1.612a.307.307 0 0 1-.114.023h-.733c-.379 0-.75.154-1.017.422l-2.1 2.1a1.005 1.005 0 0 1-1.425 0L5.268 5.32a1.448 1.448 0 0 0-1.018-.422h-.9a.306.306 0 0 1-.109-.021L1.623 6.496c-.83.83-.83 2.177 0 3.008l1.618 1.618a.305.305 0 0 1 .108-.022h.901c.38 0 .75-.153 1.018-.421L7.375 8.57a1.034 1.034 0 0 1 1.426 0l2.1 2.1c.267.268.638.421 1.017.421h.733c.04 0 .079.01.114.024l1.612-1.612c.83-.83.83-2.178 0-3.008z" />
</svg>
</IconButton>
</Box>
);
},
},
{
field: "createDate",
headerName: "Data",
width: 120,
valueFormatter: (value) => {
if (!value) return "";
return formatDate(String(value));
},
},
{
field: "orderId",
headerName: "N.Pedido",
width: 130,
headerAlign: "left",
},
{
field: "status",
headerName: "Situação",
width: 130,
renderCell: (params) => {
const status = params.value as string;
return (
<span
className={`px-2 py-1 rounded-md text-xs font-bold uppercase ${getStatusColor(
status
)}`}
>
{status}
</span>
);
},
},
{
field: "customerId",
headerName: "Cód.Cliente",
width: 120,
headerAlign: "left",
},
{
field: "customerName",
headerName: "Cliente",
width: 300,
flex: 1,
},
{
field: "orderValue",
headerName: "Valor",
width: 130,
headerAlign: "right",
align: "right",
valueFormatter: (value) => formatCurrency(value as number),
},
];
return (
// <div className="space-y-6">
<div>
{/* Header */}
<header>
<h2 className="text-2xl font-black text-[#002147] mb-2">
Consulta de pedidos
</h2>
</header>
{/* Filtros */}
<div className="bg-white p-6 mb-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 pedido */}
<div>
<Label htmlFor="orderId">Número do pedido</Label>
<Input
id="orderId"
type="text"
value={orderId}
onChange={(e) => setOrderId(e.target.value)}
placeholder="Informe o número do pedido"
/>
</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 Pedidos */}
{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 && orders.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]">
Pedidos encontrados: {orders.length}
</h3>
</div>
<Box sx={{ height: 600, width: "100%" }}>
<DataGridPremium
rows={orders}
columns={columns}
getRowId={(row) => row.orderId}
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='orderId']": {
fontWeight: 700,
color: "#0f172a",
},
"& .MuiDataGrid-cell[data-field='orderValue']": {
fontWeight: 700,
color: "#0f172a",
},
"& .MuiDataGrid-cell[data-field='createDate']": {
color: "#64748b",
},
"& .MuiDataGrid-cell[data-field='customerId']": {
color: "#64748b",
},
"& .MuiDataGrid-cell[data-field='customerName']": {
color: "#64748b",
},
"& .MuiDataGrid-virtualScroller": {
overflowY: "auto",
},
"& .MuiDataGrid-virtualScrollerContent": {
height: "auto !important",
},
}}
/>
</Box>
</div>
)}
{!loading && orders.length === 0 && !error && (
<div className="bg-white rounded-2xl border border-slate-100 overflow-hidden">
<NoData
title="Nenhum pedido encontrado"
description="Não foram encontrados pedidos com os filtros informados. Tente ajustar os parâmetros de pesquisa ou verifique se há pedidos no período selecionado."
icon="folder"
variant="outline"
/>
</div>
)}
{/* Modal de Itens do Pedido */}
<OrderItemsModal
isOpen={showOrderItems}
onClose={handleCloseOrderItemsModal}
orderId={selectedOrder?.orderId || 0}
orderItems={orderItems}
/>
{/* Dialog de Confirmação/Informação */}
<ConfirmDialog
isOpen={showConfirmDialog}
onClose={() => setShowConfirmDialog(false)}
onConfirm={() => setShowConfirmDialog(false)}
type={confirmDialogType}
title={confirmDialogTitle}
message={confirmDialogMessage}
confirmText="OK"
showWarning={
confirmDialogType === "warning" || confirmDialogType === "error"
}
/>
{/* 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 */}
{orderToPrint && (
<PrintOrderDialog
isOpen={showPrintDialog}
onClose={() => {
setShowPrintDialog(false);
setOrderToPrint(null);
}}
onConfirm={handleConfirmPrint}
orderId={orderToPrint.orderId}
/>
)}
{/* Dialog de Recebimento PIX */}
{orderForPix && (
<ReceivePixDialog
isOpen={showReceivePix}
onClose={() => {
setShowReceivePix(false);
setOrderForPix(null);
setShowQrCode(false);
setQrCodePix("");
setPixValue(0);
}}
onConfirm={handleConfirmPix}
orderId={orderForPix.orderId}
customerName={orderForPix.customerName}
orderValue={orderForPix.orderValue}
showQrCode={showQrCode}
qrCodeValue={qrCodePix}
pixValue={pixValue}
processing={processingPix}
/>
)}
{/* Modal do Viewer de Comprovante PIX */}
{showPixReceipt && pixReceiptUrl && (
<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">Recibo de PIX</h3>
<p className="text-xs text-blue-400 font-bold uppercase tracking-wider mt-0.5">
Comprovante de pagamento
</p>
</div>
<button
onClick={() => {
setShowPixReceipt(false);
setPixReceiptUrl("");
}}
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={pixReceiptUrl}
action="InitViewerComprovantePix"
width="100%"
height="100%"
onClose={() => {
setShowPixReceipt(false);
setPixReceiptUrl("");
}}
/>
</div>
</div>
</div>
)}
{/* 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">Pedido 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("");
}}
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="InitViewerOrder"
orderId={printOrderId}
model={printModel}
width="100%"
height="100%"
onClose={() => {
setShowPrintViewer(false);
setPrintUrl("");
setPrintOrderId(undefined);
setPrintModel(undefined);
}}
/>
</div>
</div>
</div>
)}
</div>
);
};
export default OrdersView;