1146 lines
37 KiB
TypeScript
1146 lines
37 KiB
TypeScript
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;
|