Vendaweb-portal/views/CheckoutView.tsx

2772 lines
94 KiB
TypeScript
Raw Normal View History

2026-01-08 12:09:16 +00:00
import React, { useState, useEffect, useCallback } from "react";
import { OrderItem } from "../types";
import {
validateCustomerForm,
validateAddressForm,
validatePaymentForm,
validateOrder,
} from "../lib/utils";
import {
customerService,
Customer,
CustomerAddress,
} from "../src/services/customer.service";
import {
lookupService,
StoreERP,
Billing,
PaymentPlan,
PartnerSales,
} from "../src/services/lookup.service";
import {
orderService,
CartModel,
CartItensModel,
DeliveryTaxTable,
CalculateDeliveryTaxRequest,
} from "../src/services/order.service";
import { authService } from "../src/services/auth.service";
import CreateCustomerDialog from "../components/CreateCustomerDialog";
import CheckoutProductsTable from "../components/checkout/CheckoutProductsTable";
import CheckoutSummary from "../components/checkout/CheckoutSummary";
import CheckoutWizard from "../components/checkout/CheckoutWizard";
import CustomerStep from "../components/checkout/CustomerStep";
import PaymentStep from "../components/checkout/PaymentStep";
import AddressStep from "../components/checkout/AddressStep";
import NotesStep from "../components/checkout/NotesStep";
import CustomerSearchModal from "../components/checkout/CustomerSearchModal";
import InfoModal from "../components/checkout/InfoModal";
import DeliveryTaxModal from "../components/checkout/DeliveryTaxModal";
import DiscountOrderModal from "../components/checkout/DiscountOrderModal";
import LoadingModal from "../components/checkout/LoadingModal";
import ConfirmDialog from "../components/ConfirmDialog";
import { shoppingService } from "../src/services/shopping.service";
import DiscountItemModal from "../components/checkout/DiscountItemModal";
import ProductDetailModal from "../components/ProductDetailModal";
import EditItemModal from "../components/EditItemModal";
import { productService } from "../src/services/product.service";
import { Product } from "../types";
import {
shippingService,
DeliveryScheduleItem,
} from "../src/services/shipping.service";
interface CheckoutViewProps {
cart: OrderItem[];
onBack: () => void;
onCartUpdate?: () => void;
onClearCart?: () => void;
}
type Step = "customer" | "payment" | "address" | "notes";
const CheckoutView: React.FC<CheckoutViewProps> = ({
cart,
onBack,
onCartUpdate,
onClearCart,
}) => {
// Estados dos passos
const [currentStep, setCurrentStep] = useState<Step>("customer");
// Estados do formulário de cliente
const [customerForm, setCustomerForm] = useState({
name: "",
document: "",
cellPhone: "",
cep: "",
address: "",
number: "",
city: "",
state: "",
complement: "",
});
const [customerErrors, setCustomerErrors] = useState<Record<string, string>>(
{}
);
const [selectedCustomer, setSelectedCustomer] = useState<Customer | null>(
null
);
const [showCustomerModal, setShowCustomerModal] = useState(false);
const [showCreateCustomerModal, setShowCreateCustomerModal] = useState(false);
const [customerSearchTerm, setCustomerSearchTerm] = useState("");
const [customerSearchResults, setCustomerSearchResults] = useState<
Customer[]
>([]);
const [isSearchingCustomers, setIsSearchingCustomers] = useState(false);
// Estados do formulário de pagamento
const [paymentForm, setPaymentForm] = useState({
invoiceStore: null as StoreERP | null,
billing: null as Billing | null,
paymentPlan: null as PaymentPlan | null,
partner: null as PartnerSales | null,
});
const [paymentErrors, setPaymentErrors] = useState<Record<string, string>>(
{}
);
const [stores, setStores] = useState<StoreERP[]>([]);
const [billings, setBillings] = useState<Billing[]>([]);
const [paymentPlans, setPaymentPlans] = useState<PaymentPlan[]>([]);
const [partners, setPartners] = useState<PartnerSales[]>([]);
const [isLoadingPaymentData, setIsLoadingPaymentData] = useState(false);
// Estados do formulário de endereço
const [addressForm, setAddressForm] = useState({
zipCode: "",
address: "",
number: "",
city: "",
state: "",
complement: "",
referencePoint: "",
note: "",
});
const [addressErrors, setAddressErrors] = useState<Record<string, string>>(
{}
);
const [selectedAddress, setSelectedAddress] =
useState<CustomerAddress | null>(null);
const [showAddressModal, setShowAddressModal] = useState(false);
const [customerAddresses, setCustomerAddresses] = useState<CustomerAddress[]>(
[]
);
// Estados do formulário de observações
const [notesForm, setNotesForm] = useState({
shippingDate: null as Date | null,
scheduleDelivery: false,
shippingPriority: "B" as "B" | "M" | "A",
notesText1: "",
notesText2: "",
notesDeliveryText1: "",
notesDeliveryText2: "",
notesDeliveryText3: "",
});
// Estados do resumo do pedido
const [taxValue, setTaxValue] = useState("0");
const [discountValue, setDiscountValue] = useState("0,00");
const [deliveryTaxId, setDeliveryTaxId] = useState<number | null>(null);
const [carrierId, setCarrierId] = useState<number | null>(null);
const [isLoadingOrder, setIsLoadingOrder] = useState(false);
const [isLoadingPreOrder, setIsLoadingPreOrder] = useState(false);
// Estados dos modais de taxa e desconto
const [showDeliveryTaxModal, setShowDeliveryTaxModal] = useState(false);
const [deliveryTaxOptions, setDeliveryTaxOptions] = useState<
DeliveryTaxTable[]
>([]);
const [isLoadingDeliveryTax, setIsLoadingDeliveryTax] = useState(false);
const [showDiscountModal, setShowDiscountModal] = useState(false);
const [showCalculatingTaxModal, setShowCalculatingTaxModal] = useState(false);
const [isFirstPriorityRender, setIsFirstPriorityRender] = useState(true);
// Estados de modais e confirmações
const [showInfoModal, setShowInfoModal] = useState(false);
const [infoModal, setInfoModal] = useState({
title: "",
message: "",
description: "",
});
const [showConfirmDialog, setShowConfirmDialog] = useState(false);
const [confirmDialog, setConfirmDialog] = useState({
type: "confirm" as
| "info"
| "warning"
| "error"
| "success"
| "delete"
| "confirm",
title: "",
message: "",
onConfirm: () => {},
});
const [showTaxErrorDialog, setShowTaxErrorDialog] = useState(false);
const [taxErrorDialog, setTaxErrorDialog] = useState({
title: "",
message: "",
type: "error" as "error" | "info" | "warning" | "success",
});
// Estado para dialog de validação de data mínima
const [showDateValidationDialog, setShowDateValidationDialog] =
useState(false);
// Estados para ações dos itens do carrinho
const [showDiscountItemModal, setShowDiscountItemModal] = useState(false);
const [selectedItemForDiscount, setSelectedItemForDiscount] =
useState<OrderItem | null>(null);
const [showProductEditModal, setShowProductEditModal] = useState(false);
const [selectedItemForEdit, setSelectedItemForEdit] =
useState<OrderItem | null>(null);
const [selectedItemForRemove, setSelectedItemForRemove] =
useState<OrderItem | null>(null);
// Cálculos
const subtotal = cart.reduce(
(acc, item) => acc + item.price * item.quantity,
0
);
const totalWeight = cart.reduce(
(acc, item) => acc + (item.weight || 1.0) * item.quantity,
0
);
const taxValueNum = parseFloat(taxValue.replace(",", ".")) || 0;
const discountValueNum =
parseFloat(discountValue.replace(",", ".").replace(/[^\d,.-]/g, "")) || 0;
const total = subtotal + taxValueNum - discountValueNum;
// Buscar próximo dia disponível do baldinho ao montar o componente
// Usa a mesma lógica do componente Baldinho.tsx
useEffect(() => {
const loadNextAvailableDeliveryDate = async () => {
console.log("📅 [CHECKOUT] ========================================");
console.log("📅 [CHECKOUT] Iniciando busca do próximo dia disponível");
// Só buscar se não houver data já salva no localStorage
const savedDataDelivery = localStorage.getItem("dataDelivery");
if (savedDataDelivery) {
try {
const dataDelivery = JSON.parse(savedDataDelivery);
if (dataDelivery.dateDelivery) {
const parsedDate = new Date(dataDelivery.dateDelivery);
if (!isNaN(parsedDate.getTime())) {
console.log(
"📅 [CHECKOUT] Data de entrega já existe no localStorage:",
parsedDate.toLocaleDateString("pt-BR")
);
console.log("📅 [CHECKOUT] Pulando busca na API");
console.log(
"📅 [CHECKOUT] ========================================"
);
return; // Já tem data salva, não precisa buscar
}
}
} catch (e) {
console.error(
"📅 [CHECKOUT] Erro ao verificar dataDelivery salva:",
e
);
}
}
try {
console.log("📅 [CHECKOUT] Chamando API do baldinho...");
console.log(
"📅 [CHECKOUT] Endpoint: shippingService.getScheduleDelivery()"
);
const response = await shippingService.getScheduleDelivery();
console.log("📅 [CHECKOUT] Resposta da API recebida:", {
hasResponse: !!response,
hasDeliveries: !!(response && response.deliveries),
isArray: Array.isArray(response?.deliveries),
deliveriesCount: response?.deliveries?.length || 0,
});
if (
response &&
response.deliveries &&
Array.isArray(response.deliveries)
) {
console.log(
"📅 [CHECKOUT] Total de dias recebidos da API:",
response.deliveries.length
);
// Data de hoje para comparação
const today = new Date();
today.setHours(0, 0, 0, 0);
console.log(
"📅 [CHECKOUT] Data de hoje:",
today.toLocaleDateString("pt-BR")
);
// Filtrar dias que atendem às condições:
// 1. delivery === "S" (entrega disponível)
// 2. avaliableDelivery < deliverySize (ainda tem capacidade)
// 3. Data >= hoje (no futuro)
const availableDays = response.deliveries
.filter((item: DeliveryScheduleItem) => {
// Converter dateDelivery (ISO string) para Date
const itemDate = new Date(item.dateDelivery);
// Criar data local a partir dos componentes UTC (igual ao Baldinho)
const localDate = new Date(
itemDate.getUTCFullYear(),
itemDate.getUTCMonth(),
itemDate.getUTCDate()
);
localDate.setHours(0, 0, 0, 0);
// Verificar condições
const isDeliveryAvailable = item.delivery === "S";
// IMPORTANTE: avaliableDelivery deve ser MENOR que deliverySize
const hasCapacity = item.avaliableDelivery < item.deliverySize;
// Data deve ser >= hoje (incluindo hoje)
const isFuture = localDate >= today;
// Log detalhado para cada dia
console.log("📅 [CHECKOUT] Verificando dia:", {
dateISO: item.dateDelivery,
dateLocal: localDate.toISOString(),
dateFormatted: localDate.toLocaleDateString("pt-BR"),
delivery: item.delivery,
deliverySize: item.deliverySize,
avaliableDelivery: item.avaliableDelivery,
saleWeigth: item.saleWeigth,
conditions: {
isDeliveryAvailable: `${item.delivery} === "S" = ${isDeliveryAvailable}`,
hasCapacity: `${item.avaliableDelivery} < ${item.deliverySize} = ${hasCapacity}`,
isFuture: `${localDate.toLocaleDateString(
"pt-BR"
)} >= ${today.toLocaleDateString("pt-BR")} = ${isFuture}`,
},
passesAllConditions:
isDeliveryAvailable && hasCapacity && isFuture,
});
return isDeliveryAvailable && hasCapacity && isFuture;
})
.map((item: DeliveryScheduleItem) => {
const itemDate = new Date(item.dateDelivery);
const localDate = new Date(
itemDate.getUTCFullYear(),
itemDate.getUTCMonth(),
itemDate.getUTCDate()
);
localDate.setHours(0, 0, 0, 0);
// Formatar data como DD/MM/YYYY
const formattedDate = localDate.toLocaleDateString("pt-BR", {
day: "2-digit",
month: "2-digit",
year: "numeric",
});
return {
dateString: formattedDate, // DD/MM/YYYY
date: localDate, // Date object para comparação
delivery: item.delivery,
deliverySize: item.deliverySize,
avaliableDelivery: item.avaliableDelivery,
saleWeigth: item.saleWeigth,
rawItem: item,
};
})
.sort((a, b) => a.date.getTime() - b.date.getTime()); // Ordenar por data (mais próximo primeiro)
console.log("📅 [CHECKOUT] ========================================");
console.log("📅 [CHECKOUT] RESUMO DA BUSCA:");
console.log(
"📅 [CHECKOUT] Total de dias na API:",
response.deliveries.length
);
console.log(
"📅 [CHECKOUT] Dias que atendem às condições (delivery='S' e avaliableDelivery < deliverySize e data >= hoje):",
availableDays.length
);
if (availableDays.length > 0) {
// Listar todos os dias disponíveis encontrados
console.log("📅 [CHECKOUT] Dias disponíveis encontrados:");
availableDays.forEach((day, index) => {
console.log(
`📅 [CHECKOUT] ${index + 1}. ${
day.dateString
} - DeliverySize: ${day.deliverySize}, AvaliableDelivery: ${
day.avaliableDelivery
}, Capacidade: ${
day.avaliableDelivery < day.deliverySize ? "OK" : "ESGOTADO"
}`
);
});
// Já está ordenado por data (mais próximo primeiro)
const nextAvailableDay = availableDays[0];
console.log(
"📅 [CHECKOUT] ========================================"
);
console.log("📅 [CHECKOUT] PRÓXIMO DIA DISPONÍVEL ENCONTRADO:");
console.log("📅 [CHECKOUT] Data:", nextAvailableDay.dateString);
console.log(
"📅 [CHECKOUT] Data (Date object):",
nextAvailableDay.date.toLocaleDateString("pt-BR")
);
console.log("📅 [CHECKOUT] Delivery:", nextAvailableDay.delivery);
console.log(
"📅 [CHECKOUT] DeliverySize:",
nextAvailableDay.deliverySize
);
console.log(
"📅 [CHECKOUT] AvaliableDelivery:",
nextAvailableDay.avaliableDelivery
);
console.log(
"📅 [CHECKOUT] Capacidade disponível:",
nextAvailableDay.avaliableDelivery,
"<",
nextAvailableDay.deliverySize
);
console.log(
"📅 [CHECKOUT] ========================================"
);
// Setar a data no formulário
setNotesForm((prev) => {
console.log("📅 [CHECKOUT] Setando data no notesForm...");
console.log(
"📅 [CHECKOUT] Data anterior:",
prev.shippingDate?.toLocaleDateString("pt-BR") || "null"
);
console.log(
"📅 [CHECKOUT] Nova data:",
nextAvailableDay.date.toLocaleDateString("pt-BR")
);
return {
...prev,
shippingDate: nextAvailableDay.date,
};
});
// Salvar no localStorage
const dataDelivery = {
dateDelivery: nextAvailableDay.date.toISOString(),
scheduleDelivery: notesForm.scheduleDelivery,
priorityDelivery: notesForm.shippingPriority,
notification: notesForm.notesText1,
notification1: notesForm.notesText2,
notificationDelivery1: notesForm.notesDeliveryText1,
notificationDelivery2: notesForm.notesDeliveryText2,
notificationDelivery3: notesForm.notesDeliveryText3,
};
localStorage.setItem("dataDelivery", JSON.stringify(dataDelivery));
console.log("📅 [CHECKOUT] Data salva no localStorage:", {
dateDelivery: dataDelivery.dateDelivery,
dateFormatted: nextAvailableDay.date.toLocaleDateString("pt-BR"),
});
console.log(
"📅 [CHECKOUT] ========================================"
);
} else {
console.warn(
"📅 [CHECKOUT] ========================================"
);
console.warn("📅 [CHECKOUT] NENHUM DIA DISPONÍVEL ENCONTRADO!");
console.warn(
"📅 [CHECKOUT] Total de dias na API:",
response.deliveries.length
);
console.warn(
"📅 [CHECKOUT] Dias com delivery='S':",
response.deliveries.filter(
(d: DeliveryScheduleItem) => d.delivery === "S"
).length
);
console.warn(
"📅 [CHECKOUT] Dias com capacidade (avaliableDelivery < deliverySize):",
response.deliveries.filter(
(d: DeliveryScheduleItem) =>
d.delivery === "S" && d.avaliableDelivery < d.deliverySize
).length
);
console.warn("📅 [CHECKOUT] Usando fallback D+3...");
// Se não encontrou nenhum dia disponível, usar D+3 como fallback
const today = new Date();
today.setHours(0, 0, 0, 0);
const fallbackDate = new Date(today);
fallbackDate.setDate(today.getDate() + 3);
fallbackDate.setHours(0, 0, 0, 0);
console.log(
"📅 [CHECKOUT] Data fallback (D+3):",
fallbackDate.toLocaleDateString("pt-BR")
);
setNotesForm((prev) => ({
...prev,
shippingDate: fallbackDate,
}));
const dataDelivery = {
dateDelivery: fallbackDate.toISOString(),
scheduleDelivery: notesForm.scheduleDelivery,
priorityDelivery: notesForm.shippingPriority,
notification: notesForm.notesText1,
notification1: notesForm.notesText2,
notificationDelivery1: notesForm.notesDeliveryText1,
notificationDelivery2: notesForm.notesDeliveryText2,
notificationDelivery3: notesForm.notesDeliveryText3,
};
localStorage.setItem("dataDelivery", JSON.stringify(dataDelivery));
console.log(
"📅 [CHECKOUT] ========================================"
);
}
} else {
console.error(
"📅 [CHECKOUT] ========================================"
);
console.error("📅 [CHECKOUT] Resposta da API inválida!");
console.error("📅 [CHECKOUT] Response:", response);
console.error(
"📅 [CHECKOUT] ========================================"
);
}
} catch (error) {
console.error("📅 [CHECKOUT] ========================================");
console.error("📅 [CHECKOUT] ERRO ao buscar próximo dia disponível:");
console.error("📅 [CHECKOUT] Error:", error);
console.error("📅 [CHECKOUT] Stack:", (error as any)?.stack);
console.error("📅 [CHECKOUT] Usando fallback D+3 devido a erro...");
// Em caso de erro, usar D+3 como fallback
const today = new Date();
today.setHours(0, 0, 0, 0);
const fallbackDate = new Date(today);
fallbackDate.setDate(today.getDate() + 3);
fallbackDate.setHours(0, 0, 0, 0);
console.log(
"📅 [CHECKOUT] Data fallback (D+3):",
fallbackDate.toLocaleDateString("pt-BR")
);
setNotesForm((prev) => ({
...prev,
shippingDate: fallbackDate,
}));
const dataDelivery = {
dateDelivery: fallbackDate.toISOString(),
scheduleDelivery: notesForm.scheduleDelivery,
priorityDelivery: notesForm.shippingPriority,
notification: notesForm.notesText1,
notification1: notesForm.notesText2,
notificationDelivery1: notesForm.notesDeliveryText1,
notificationDelivery2: notesForm.notesDeliveryText2,
notificationDelivery3: notesForm.notesDeliveryText3,
};
localStorage.setItem("dataDelivery", JSON.stringify(dataDelivery));
console.log("📅 [CHECKOUT] ========================================");
}
};
loadNextAvailableDeliveryDate();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []); // Executar apenas uma vez ao montar
// Carregar dados salvos do localStorage
useEffect(() => {
const loadSavedData = () => {
// Carregar cliente
const savedCustomer = localStorage.getItem("customer");
if (savedCustomer) {
try {
const customer = JSON.parse(savedCustomer) as Customer;
setSelectedCustomer(customer);
setCustomerForm({
name: customer.name || "",
document: customer.cpfCnpj || customer.document || "",
cellPhone: customer.cellPhone || customer.phone || "",
cep: customer.zipCode || customer.cep || "",
address: customer.address || "",
number: customer.addressNumber || customer.number || "",
city: customer.city || "",
state: customer.state || "",
complement: customer.complement || "",
});
} catch (e) {
console.error("Erro ao carregar cliente:", e);
}
}
// Carregar endereço
const savedAddress = localStorage.getItem("address");
if (savedAddress) {
try {
const address = JSON.parse(savedAddress) as CustomerAddress;
setSelectedAddress(address);
setAddressForm({
zipCode: address.zipCode || "",
address: address.address || "",
number: address.number || "",
city: address.city || "",
state: address.state || "",
complement: address.complement || "",
referencePoint: address.referencePoint || "",
note: address.note || "",
});
} catch (e) {
console.error("Erro ao carregar endereço:", e);
}
}
// Carregar pagamento
const savedInvoiceStore = localStorage.getItem("invoiceStore");
if (savedInvoiceStore) {
try {
setPaymentForm((prev) => ({
...prev,
invoiceStore: JSON.parse(savedInvoiceStore) as StoreERP,
}));
} catch (e) {
console.error("Erro ao carregar filial:", e);
}
}
const savedBilling = localStorage.getItem("billing");
if (savedBilling) {
try {
setPaymentForm((prev) => ({
...prev,
billing: JSON.parse(savedBilling) as Billing,
}));
} catch (e) {
console.error("Erro ao carregar cobrança:", e);
}
}
const savedPaymentPlan = localStorage.getItem("paymentPlan");
if (savedPaymentPlan) {
try {
setPaymentForm((prev) => ({
...prev,
paymentPlan: JSON.parse(savedPaymentPlan) as PaymentPlan,
}));
} catch (e) {
console.error("Erro ao carregar plano de pagamento:", e);
}
}
const savedPartner = localStorage.getItem("partner");
if (savedPartner) {
try {
setPaymentForm((prev) => ({
...prev,
partner: JSON.parse(savedPartner) as PartnerSales,
}));
} catch (e) {
console.error("Erro ao carregar parceiro:", e);
}
}
// Carregar observações
const savedDataDelivery = localStorage.getItem("dataDelivery");
if (savedDataDelivery) {
try {
const dataDelivery = JSON.parse(savedDataDelivery);
console.log(
"📅 [CHECKOUT] Dados de entrega carregados do localStorage:",
dataDelivery
);
// Converter dateDelivery para Date, tratando diferentes formatos
let shippingDate: Date | null = null;
if (dataDelivery.dateDelivery) {
if (typeof dataDelivery.dateDelivery === "string") {
// Se for string ISO, converter para Date
const parsedDate = new Date(dataDelivery.dateDelivery);
// Verificar se a data é válida
if (!isNaN(parsedDate.getTime())) {
shippingDate = parsedDate;
console.log(
"📅 [CHECKOUT] Data convertida com sucesso:",
shippingDate
);
} else {
console.warn(
"📅 [CHECKOUT] Data inválida após conversão:",
dataDelivery.dateDelivery
);
}
} else if (dataDelivery.dateDelivery instanceof Date) {
// Se já for Date (improvável após JSON.parse, mas por segurança)
shippingDate = dataDelivery.dateDelivery;
}
} else {
console.log("📅 [CHECKOUT] dateDelivery é null ou undefined");
}
setNotesForm((prev) => ({
...prev,
shippingDate: shippingDate,
scheduleDelivery: dataDelivery.scheduleDelivery || false,
shippingPriority: dataDelivery.priorityDelivery || "B",
notesText1: dataDelivery.notification || "",
notesText2: dataDelivery.notification1 || "",
notesDeliveryText1: dataDelivery.notificationDelivery1 || "",
notesDeliveryText2: dataDelivery.notificationDelivery2 || "",
notesDeliveryText3: dataDelivery.notificationDelivery3 || "",
}));
console.log(
"📅 [CHECKOUT] Estado notesForm atualizado com shippingDate:",
shippingDate
);
} catch (e) {
console.error("Erro ao carregar dados de entrega:", e);
}
} else {
console.log(
"📅 [CHECKOUT] Nenhum dado de entrega encontrado no localStorage"
);
}
// Carregar taxa e desconto
const savedTaxDelivery = localStorage.getItem("taxDelivery");
if (savedTaxDelivery) {
try {
const taxDelivery = JSON.parse(savedTaxDelivery);
setTaxValue(taxDelivery.taxValue?.toFixed(2) || "0");
} catch (e) {
console.error("Erro ao carregar taxa de entrega:", e);
}
}
};
loadSavedData();
}, []);
// Carregar dados de pagamento quando cliente for selecionado
useEffect(() => {
const loadPaymentData = async () => {
if (!selectedCustomer) return;
setIsLoadingPaymentData(true);
try {
const user = authService.getUser();
const storesData = await lookupService.getStores(user?.id?.toString());
setStores(storesData);
if (!paymentForm.invoiceStore && user?.store) {
const defaultStore = storesData.find((s) => s.id === user.store);
if (defaultStore) {
setPaymentForm((prev) => ({ ...prev, invoiceStore: defaultStore }));
localStorage.setItem("invoiceStore", JSON.stringify(defaultStore));
}
}
const billingsData = await lookupService.getBillings(
selectedCustomer.customerId
);
setBillings(billingsData);
const partnersData = await lookupService.getPartners();
setPartners(partnersData);
} catch (error) {
console.error("Erro ao carregar dados de pagamento:", error);
} finally {
setIsLoadingPaymentData(false);
}
};
loadPaymentData();
}, [selectedCustomer]);
// Recalcular taxa de entrega quando a prioridade de entrega mudar
useEffect(() => {
// Ignorar primeira renderização
if (isFirstPriorityRender) {
console.log("🔄 [TAX] Primeira renderização - ignorando cálculo");
setIsFirstPriorityRender(false);
return;
}
const recalculateTaxOnPriorityChange = async () => {
console.log("🔄 [TAX] ========================================");
console.log("🔄 [TAX] Iniciando recálculo de taxa de entrega");
console.log(
"🔄 [TAX] Prioridade selecionada:",
notesForm.shippingPriority
);
console.log("🔄 [TAX] Itens no carrinho:", cart.length);
// Verificar se há itens para entrega
// Se deliveryType não estiver definido, considerar todos os itens como entrega
const itemsDelivery = cart.filter((item) => {
// Se não tem deliveryType definido, considerar como entrega
if (!item.deliveryType) {
return true;
}
// Se tem deliveryType, verificar se é EN ou EF
return item.deliveryType === "EN" || item.deliveryType === "EF";
});
console.log(
"🔄 [TAX] Itens de entrega encontrados:",
itemsDelivery.length
);
console.log(
"🔄 [TAX] Detalhes dos itens:",
cart.map((item) => ({
id: item.id,
deliveryType: item.deliveryType || "NÃO DEFINIDO",
description: item.description || item.name,
code: item.code,
}))
);
// Se não há itens de entrega ou prioridade é 'M' (Retira), taxa é 0
if (itemsDelivery.length === 0 || notesForm.shippingPriority === "M") {
console.log(
"🔄 [TAX] Sem itens de entrega ou prioridade 'M' - Taxa = 0"
);
setTaxValue("0");
setDeliveryTaxId(null);
setCarrierId(null);
localStorage.setItem(
"taxDelivery",
JSON.stringify({ taxValue: 0, deliveryTaxId: null, carrierId: null })
);
setShowCalculatingTaxModal(false);
return;
}
// Se há itens de entrega e prioridade não é 'M', recalcular
// Mas só se já temos cliente e endereço selecionados
const customer = JSON.parse(
localStorage.getItem("customer") || "null"
) as Customer | null;
const addressCustomer = JSON.parse(
localStorage.getItem("address") || "null"
);
console.log(
"🔄 [TAX] Cliente:",
customer ? `ID ${customer.customerId}` : "não selecionado"
);
console.log(
"🔄 [TAX] Endereço:",
addressCustomer ? "selecionado" : "não selecionado"
);
if (!customer || (!addressCustomer && !customer.cityId)) {
console.log(
"🔄 [TAX] Dados insuficientes - aguardando cliente/endereço"
);
console.log(
"🔄 [TAX] Cliente:",
customer ? `ID ${customer.customerId}` : "não encontrado"
);
console.log(
"🔄 [TAX] Endereço:",
addressCustomer ? "encontrado" : "não encontrado"
);
console.log("🔄 [TAX] customer.cityId:", customer?.cityId);
setShowCalculatingTaxModal(false);
setTaxErrorDialog({
title: "Taxa de Entrega",
message:
"É necessário selecionar um cliente e um endereço de entrega para calcular a taxa de entrega.",
});
setShowTaxErrorDialog(true);
return;
}
// Mostrar modal de loading
console.log("🔄 [TAX] Mostrando modal de loading e iniciando cálculo...");
console.log(
"🔄 [TAX] Estado antes de mostrar modal:",
showCalculatingTaxModal
);
setShowCalculatingTaxModal(true);
console.log("🔄 [TAX] Estado após setShowCalculatingTaxModal(true)");
// Forçar re-render para mostrar o modal
await new Promise((resolve) => setTimeout(resolve, 100));
console.log("🔄 [TAX] Após delay de 100ms");
// Recalcular automaticamente
try {
const invoiceStore = JSON.parse(
localStorage.getItem("invoiceStore") || "null"
);
let cartId = shoppingService.getCart() || "";
console.log("🔄 [TAX] CartId obtido do localStorage:", cartId);
console.log("🔄 [TAX] Itens no carrinho local:", cart.length);
console.log(
"🔄 [TAX] Detalhes dos itens (IDs):",
cart.map((item) => ({ id: item.id, idLength: item.id?.length }))
);
// Se não houver cartId mas houver itens no carrinho, tentar criar o carrinho
if (!cartId && cart.length > 0) {
console.log(
"🔄 [TAX] CartId não encontrado, mas há itens no carrinho. Tentando criar carrinho..."
);
// Verificar se algum item já foi adicionado ao backend (tem ID longo, geralmente UUID)
const itemWithBackendId = cart.find(
(item) => item.id && item.id.length > 10
);
if (!itemWithBackendId) {
// Se nenhum item foi adicionado ao backend, precisamos criar o primeiro item
// para obter o cartId
console.log(
"🔄 [TAX] Nenhum item foi adicionado ao backend ainda. Criando primeiro item para obter cartId..."
);
try {
const firstItem = cart[0];
const shoppingItem =
shoppingService.productToShoppingItem(firstItem);
console.log("🔄 [TAX] Criando item no backend:", shoppingItem);
const result = await shoppingService.createItemShopping(
shoppingItem
);
if (result.idCart) {
cartId = result.idCart;
console.log("🔄 [TAX] CartId criado com sucesso:", cartId);
} else {
console.error(
"🔄 [TAX] CartId não foi retornado ao criar item. Resultado:",
result
);
setShowCalculatingTaxModal(false);
setTaxErrorDialog({
title: "Taxa de Entrega",
message:
"Não foi possível criar o carrinho. Por favor, adicione os itens novamente.",
type: "error",
});
setShowTaxErrorDialog(true);
return;
}
} catch (error: any) {
console.error(
"🔄 [TAX] Erro ao criar item para obter cartId:",
error
);
setShowCalculatingTaxModal(false);
setTaxErrorDialog({
title: "Taxa de Entrega",
message:
"Não foi possível criar o carrinho. Por favor, adicione os itens novamente.",
type: "error",
});
setShowTaxErrorDialog(true);
return;
}
} else {
console.log(
"🔄 [TAX] Item encontrado com ID do backend, mas cartId não está no localStorage."
);
console.log(
"🔄 [TAX] Isso pode indicar que o cartId foi perdido. Tentando recriar..."
);
// Se há item com ID do backend, o cartId deveria estar no localStorage
// Mas foi perdido. Vamos tentar criar um novo item para obter o cartId novamente
try {
const firstItem = cart[0];
const shoppingItem =
shoppingService.productToShoppingItem(firstItem);
// Não enviar idCart para criar um novo carrinho
shoppingItem.idCart = undefined;
console.log(
"🔄 [TAX] Criando novo item sem idCart para obter novo cartId..."
);
const result = await shoppingService.createItemShopping(
shoppingItem
);
if (result.idCart) {
cartId = result.idCart;
console.log("🔄 [TAX] Novo CartId criado:", cartId);
} else {
console.error("🔄 [TAX] Novo cartId não foi retornado");
}
} catch (error: any) {
console.error("🔄 [TAX] Erro ao recriar carrinho:", error);
}
}
}
const user = authService.getUser();
let storeId = invoiceStore ? invoiceStore.id : user?.store;
let cityId = 0;
let ibgeCode = 0;
if (addressCustomer && addressCustomer.cityCode) {
cityId = addressCustomer.cityCode;
// Converter ibgeCode para número, garantindo que seja válido
const ibgeCodeValue = addressCustomer.ibgeCode;
if (
ibgeCodeValue &&
ibgeCodeValue !== "" &&
String(ibgeCodeValue).trim() !== ""
) {
// Tentar converter para número
const parsed =
typeof ibgeCodeValue === "string"
? parseInt(ibgeCodeValue.trim(), 10)
: Number(ibgeCodeValue);
// Se for um número válido, usar; caso contrário, usar cityCode como fallback
if (!isNaN(parsed) && parsed > 0) {
ibgeCode = parsed;
} else {
// Se ibgeCode não for válido, usar cityCode como fallback
ibgeCode = cityId;
}
} else {
// Se não houver ibgeCode, usar cityCode como fallback
ibgeCode = cityId;
}
console.log(
"🔄 [TAX] Usando cityId e ibgeCode do endereço:",
cityId,
ibgeCode,
"tipo:",
typeof ibgeCode,
"valor original:",
ibgeCodeValue
);
} else if (customer.cityId) {
cityId = customer.cityId;
// Converter ibgeCode do cliente para número
const ibgeCodeValue = customer.ibgeCode;
if (
ibgeCodeValue &&
(typeof ibgeCodeValue === "number"
? ibgeCodeValue > 0
: String(ibgeCodeValue).trim() !== "")
) {
const parsed =
typeof ibgeCodeValue === "number"
? ibgeCodeValue
: parseInt(String(ibgeCodeValue).trim(), 10);
if (!isNaN(parsed) && parsed > 0) {
ibgeCode = parsed;
} else {
ibgeCode = cityId;
}
} else {
ibgeCode = cityId;
}
console.log(
"🔄 [TAX] Usando cityId e ibgeCode do cliente:",
cityId,
ibgeCode,
"tipo:",
typeof ibgeCode,
"valor original:",
ibgeCodeValue
);
}
if (cityId === 0 || ibgeCode === 0 || !cartId) {
console.error(
"🔄 [TAX] Dados incompletos - cityId:",
cityId,
"ibgeCode:",
ibgeCode,
"cartId:",
cartId
);
setShowCalculatingTaxModal(false);
setTaxErrorDialog({
title: "Taxa de Entrega",
message: `Dados incompletos: cityId=${cityId}, ibgeCode=${ibgeCode}, cartId=${
cartId ? "OK" : "FALTANDO"
}. Verifique se o endereço está completo.`,
});
setShowTaxErrorDialog(true);
return; // Dados incompletos
}
const dataDeliveryTax: CalculateDeliveryTaxRequest = {
cartId,
cityId,
ibgeCode,
priorityDelivery: notesForm.shippingPriority,
};
console.log("🔄 [TAX] Chamando API com dados:", dataDeliveryTax);
const options = await orderService.calculateDeliveryTax(
dataDeliveryTax
);
console.log(
"🔄 [TAX] Opções recebidas da API:",
options.length,
"opções"
);
if (options.length > 0) {
// Se já existe uma taxa selecionada, tentar manter
if (deliveryTaxId) {
console.log(
"🔄 [TAX] Tentando manter taxa selecionada ID:",
deliveryTaxId
);
const selected = options.find((opt) => opt.id === deliveryTaxId);
if (selected) {
console.log("🔄 [TAX] Taxa mantida:", selected.deliveryValue);
setTaxValue(selected.deliveryValue.toFixed(2));
setDeliveryTaxId(selected.id);
setCarrierId(selected.carrierId);
localStorage.setItem(
"taxDelivery",
JSON.stringify({
taxValue: selected.deliveryValue,
deliveryTaxId: selected.id,
carrierId: selected.carrierId,
})
);
setShowCalculatingTaxModal(false);
return;
}
}
// Caso contrário, usar a primeira opção
const firstOption = options[0];
console.log(
"🔄 [TAX] Usando primeira opção:",
firstOption.deliveryValue
);
setTaxValue(firstOption.deliveryValue.toFixed(2));
setDeliveryTaxId(firstOption.id);
setCarrierId(firstOption.carrierId);
localStorage.setItem(
"taxDelivery",
JSON.stringify({
taxValue: firstOption.deliveryValue,
deliveryTaxId: firstOption.id,
carrierId: firstOption.carrierId,
})
);
console.log(
"🔄 [TAX] Taxa atualizada com sucesso:",
firstOption.deliveryValue.toFixed(2)
);
} else {
console.warn("🔄 [TAX] Nenhuma opção de taxa retornada pela API");
setShowCalculatingTaxModal(false);
setTaxErrorDialog({
title: "Taxa de Entrega",
message:
"Não foram encontradas opções de taxa de entrega para os dados informados. Verifique o endereço de entrega.",
type: "info",
});
setShowTaxErrorDialog(true);
}
setShowCalculatingTaxModal(false);
console.log("🔄 [TAX] ========================================");
} catch (error: any) {
console.error("🔄 [TAX] Erro ao recalcular taxa de entrega:", error);
console.error("🔄 [TAX] Stack trace:", error?.stack);
setShowCalculatingTaxModal(false);
setTaxErrorDialog({
title: "Taxa de Entrega",
message:
error?.message ||
"Ocorreu um erro ao calcular a taxa de entrega. Por favor, tente novamente.",
});
setShowTaxErrorDialog(true);
}
};
recalculateTaxOnPriorityChange();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [notesForm.shippingPriority]);
// Carregar planos de pagamento quando cobrança for selecionada
useEffect(() => {
const loadPaymentPlans = async () => {
if (!paymentForm.billing) {
setPaymentPlans([]);
return;
}
try {
const plans = await lookupService.getPaymentPlans(
paymentForm.billing.codcob
);
setPaymentPlans(plans);
} catch (error) {
console.error("Erro ao carregar planos de pagamento:", error);
}
};
loadPaymentPlans();
}, [paymentForm.billing]);
// Carregar endereços do cliente quando cliente for selecionado
useEffect(() => {
const loadCustomerAddresses = async () => {
if (!selectedCustomer) {
setCustomerAddresses([]);
return;
}
try {
const addresses = await customerService.getCustomerAddresses(
selectedCustomer.customerId
);
setCustomerAddresses(addresses);
} catch (error) {
console.error("Erro ao carregar endereços do cliente:", error);
}
};
loadCustomerAddresses();
}, [selectedCustomer]);
// Buscar clientes
const handleSearchCustomers = useCallback(async () => {
if (customerSearchTerm.length < 3) {
setCustomerSearchResults([]);
return;
}
setIsSearchingCustomers(true);
try {
const results = await customerService.searchCustomers(customerSearchTerm);
setCustomerSearchResults(results);
} catch (error) {
console.error("Erro ao buscar clientes:", error);
setCustomerSearchResults([]);
} finally {
setIsSearchingCustomers(false);
}
}, [customerSearchTerm]);
useEffect(() => {
const timer = setTimeout(() => {
handleSearchCustomers();
}, 500);
return () => clearTimeout(timer);
}, [customerSearchTerm, handleSearchCustomers]);
// Selecionar cliente
const handleSelectCustomer = (customer: Customer) => {
console.log("Cliente selecionado:", customer);
// Se um cliente diferente foi selecionado, limpar o endereço
if (
selectedCustomer &&
selectedCustomer.customerId !== customer.customerId
) {
console.log(
"🔄 [CHECKOUT] Cliente diferente selecionado, limpando endereço..."
);
setSelectedAddress(null);
setAddressForm({
zipCode: "",
address: "",
number: "",
city: "",
state: "",
complement: "",
referencePoint: "",
note: "",
});
localStorage.removeItem("address");
setCustomerAddresses([]);
}
setSelectedCustomer(customer);
const formData = {
name: customer.name || "",
document: customer.cpfCnpj || customer.document || "",
cellPhone: customer.cellPhone || customer.phone || "",
cep: customer.zipCode || customer.cep || "",
address: customer.address || "",
number: customer.addressNumber || customer.number || "",
city: customer.city || "",
state: customer.state || "",
complement: customer.complement || "",
};
setCustomerForm(formData);
localStorage.setItem("customer", JSON.stringify(customer));
setShowCustomerModal(false);
setCustomerSearchTerm("");
setCustomerSearchResults([]);
setCustomerErrors({});
};
// Navegação entre passos
const goToStep = (step: Step) => {
setCurrentStep(step);
};
const handleNextStep = () => {
if (currentStep === "customer") {
const validation = validateCustomerForm(customerForm);
if (!validation.isValid) {
setCustomerErrors(validation.errors);
return;
}
if (!selectedCustomer) {
setInfoModal({
title: "Pedido de venda",
message: "Atenção! Não foi informado um cliente para a venda.",
description:
"Para gerar um pedido de venda é necessário selecionar um cliente.",
});
setShowInfoModal(true);
return;
}
if (selectedCustomer.place?.id === 19) {
setInfoModal({
title: "Pedido de venda",
message: "ATENÇÃO! Cliente com praça do E-COMMERCE!",
description:
"Altere a praça do cliente pela opção NOVO CLIENTE para continuar.",
});
setShowInfoModal(true);
return;
}
if (
selectedCustomer.place?.name === "INATIVO" ||
selectedCustomer.place?.name === "INATIVA"
) {
setInfoModal({
title: "Pedido de venda",
message: "ATENÇÃO! Cliente com praça INATIVA!",
description:
"Altere a praça do cliente pela opção NOVO CLIENTE para continuar.",
});
setShowInfoModal(true);
return;
}
setCustomerErrors({});
goToStep("payment");
} else if (currentStep === "payment") {
const validation = validatePaymentForm(paymentForm);
if (!validation.isValid) {
setPaymentErrors(validation.errors);
return;
}
setPaymentErrors({});
goToStep("address");
} else if (currentStep === "address") {
const validation = validateAddressForm(addressForm);
if (!validation.isValid) {
setAddressErrors(validation.errors);
return;
}
if (selectedAddress) {
localStorage.setItem("address", JSON.stringify(selectedAddress));
} else {
const tempAddress: CustomerAddress = {
zipCode: addressForm.zipCode,
address: addressForm.address,
number: addressForm.number,
city: addressForm.city,
state: addressForm.state,
complement: addressForm.complement,
referencePoint: addressForm.referencePoint,
note: addressForm.note,
};
localStorage.setItem("address", JSON.stringify(tempAddress));
}
setAddressErrors({});
goToStep("notes");
}
};
const handlePreviousStep = () => {
if (currentStep === "payment") {
goToStep("customer");
} else if (currentStep === "address") {
goToStep("payment");
} else if (currentStep === "notes") {
goToStep("address");
}
};
// Handlers de pagamento
const handleSetBilling = (billing: Billing | null) => {
setPaymentForm((prev) => ({
...prev,
billing,
paymentPlan: null,
}));
if (billing) {
localStorage.setItem("billing", JSON.stringify(billing));
localStorage.removeItem("paymentPlan");
} else {
localStorage.removeItem("billing");
}
};
const handleSetPaymentPlan = (paymentPlan: PaymentPlan | null) => {
setPaymentForm((prev) => ({ ...prev, paymentPlan }));
if (paymentPlan) {
localStorage.setItem("paymentPlan", JSON.stringify(paymentPlan));
} else {
localStorage.removeItem("paymentPlan");
}
};
const handleSetInvoiceStore = (store: StoreERP | null) => {
setPaymentForm((prev) => ({ ...prev, invoiceStore: store }));
if (store) {
localStorage.setItem("invoiceStore", JSON.stringify(store));
} else {
localStorage.removeItem("invoiceStore");
}
};
const handleSetPartner = (partner: PartnerSales | null) => {
setPaymentForm((prev) => ({ ...prev, partner }));
if (partner) {
localStorage.setItem("partner", JSON.stringify(partner));
} else {
localStorage.removeItem("partner");
}
};
// Salvar dados de entrega
const saveDeliveryData = () => {
const dataDelivery = {
dateDelivery: notesForm.shippingDate
? notesForm.shippingDate.toISOString()
: null,
scheduleDelivery: notesForm.scheduleDelivery,
priorityDelivery: notesForm.shippingPriority,
notification: notesForm.notesText1,
notification1: notesForm.notesText2,
notificationDelivery1: notesForm.notesDeliveryText1,
notificationDelivery2: notesForm.notesDeliveryText2,
notificationDelivery3: notesForm.notesDeliveryText3,
};
localStorage.setItem("dataDelivery", JSON.stringify(dataDelivery));
};
// Criar pedido
// Converter OrderItem[] para CartItensModel[] (igual ao Angular getItens())
const getItens = useCallback((): CartItensModel[] => {
return cart.map((item) => {
// Obter idProduct (pode vir de item.code, item.id ou item.idProduct)
let idProduct = 0;
if (item.idProduct) {
idProduct =
typeof item.idProduct === "number"
? item.idProduct
: parseInt(String(item.idProduct), 10);
} else if (item.code) {
idProduct = parseInt(String(item.code), 10);
} else if (item.id && !item.id.includes("-")) {
// Se id não é UUID, tentar converter
idProduct = parseInt(String(item.id), 10);
}
// Obter EAN (pode vir de item.ean ou usar idProduct como fallback)
let ean = idProduct;
if (item.ean) {
const parsedEan = parseInt(String(item.ean), 10);
ean = isNaN(parsedEan) ? idProduct : parsedEan;
}
// Obter stockStore (pode vir de item.stockStore ou item.stockLocal)
const stockStore = item.stockStore
? String(item.stockStore)
: item.stockLocal
? String(item.stockLocal)
: "";
return {
idProduct,
ean,
idStock: stockStore,
deliveryMethod: item.deliveryType || "EN",
quantity: item.quantity || 1,
listPrice: item.originalPrice || item.listPrice || item.price || 0,
salePrice: item.price || 0,
descriptionAux: item.auxDescription || null,
environment: item.environment || null,
};
});
}, [cart]);
// Validação completa do pedido (igual ao Angular orderValid())
const orderValid = useCallback((): boolean => {
const customer = JSON.parse(
localStorage.getItem("customer") || "null"
) as Customer | null;
if (!customer) {
setConfirmDialog({
type: "warning",
title: "Gravar pedido de venda",
message:
"Atenção! Não foi informado um cliente para a venda.\n\nPara gerar um pedido de venda é necessário selecionar um cliente.",
onConfirm: () => {
setShowConfirmDialog(false);
},
});
setShowConfirmDialog(true);
return false;
}
const paymentPlan = JSON.parse(
localStorage.getItem("paymentPlan") || "null"
) as PaymentPlan | null;
if (!paymentPlan) {
setConfirmDialog({
type: "warning",
title: "Gravar pedido de venda",
message:
"Atenção! Não foi informado um plano de pagamento para a venda.\n\nPara gerar um pedido de venda é necessário selecionar um plano de pagamento para o pedido.",
onConfirm: () => {
setShowConfirmDialog(false);
},
});
setShowConfirmDialog(true);
return false;
}
const billing = JSON.parse(
localStorage.getItem("billing") || "null"
) as Billing | null;
if (!billing) {
setConfirmDialog({
type: "warning",
title: "Gravar pedido de venda",
message:
"Atenção! Não foi informado uma cobrança para a venda.\n\nPara gerar um pedido de venda é necessário selecionar uma cobrança.",
onConfirm: () => {
setShowConfirmDialog(false);
},
});
setShowConfirmDialog(true);
return false;
}
return true;
}, []);
const handleCreateOrder = async () => {
// Validação básica
const validation = validateOrder();
if (!validation.isValid) {
const fullMessage = validation.description
? `${validation.message}\n\n${validation.description}`
: validation.message;
setConfirmDialog({
type: "warning",
title: "Gravar pedido de venda",
message: fullMessage,
onConfirm: () => {
setShowConfirmDialog(false);
},
});
setShowConfirmDialog(true);
return;
}
// Validação completa (igual ao Angular)
if (!orderValid()) {
return;
}
// Verificar se há data de entrega
if (!notesForm.shippingDate) {
setConfirmDialog({
type: "warning",
title: "Pedido de venda",
message:
"Não foi possível gerar o pedido de venda!\n\nFavor informe a data de entrega do pedido.",
onConfirm: () => {
setShowConfirmDialog(false);
},
});
setShowConfirmDialog(true);
return;
}
// Salvar dados de entrega
saveDeliveryData();
// Preparar mensagem de confirmação com informações de entrega
let informationDelivery = "";
if (notesForm.shippingDate) {
const dateDelivery = new Date(notesForm.shippingDate);
const dataTexto =
dateDelivery.getDate().toString().padStart(2, "0") +
"/" +
(dateDelivery.getMonth() + 1).toString().padStart(2, "0") +
"/" +
dateDelivery.getFullYear().toString();
informationDelivery = `Pedido com data de entrega para o dia ${dataTexto} - Tipo de entrega: ${
notesForm.scheduleDelivery ? "PROGRAMADA" : "NORMAL"
}`;
}
// Mostrar confirmação
setConfirmDialog({
type: "confirm",
title: "Gerar pedido de venda",
message: "Confirma a geração do pedido de venda?",
onConfirm: async () => {
setShowConfirmDialog(false);
setIsLoadingOrder(true);
try {
const customer = JSON.parse(
localStorage.getItem("customer")!
) as Customer;
const address = JSON.parse(
localStorage.getItem("address") || "null"
) as CustomerAddress | null;
const paymentPlan = JSON.parse(
localStorage.getItem("paymentPlan")!
) as PaymentPlan;
const billing = JSON.parse(
localStorage.getItem("billing")!
) as Billing;
const invoiceStore = JSON.parse(
localStorage.getItem("invoiceStore")!
) as StoreERP;
const partner = JSON.parse(
localStorage.getItem("partner") || "null"
) as PartnerSales | null;
const preCustomer = JSON.parse(
localStorage.getItem("preCustomer") || "null"
);
const user = authService.getUser();
// Validações adicionais (igual ao Angular)
if (!invoiceStore) {
setConfirmDialog({
type: "warning",
title: "Pedido de venda",
message:
"Não foi possível gerar o pedido de venda!\n\nFavor selecione uma filial de venda para o orçamento.",
onConfirm: () => {
setShowConfirmDialog(false);
},
});
setShowConfirmDialog(true);
setIsLoadingOrder(false);
return;
}
// Converter itens do carrinho
const itensCart = getItens();
// Preparar dados do pré-cadastro (se consumidor final)
let preCadastroDocument = "";
let preCadastroName = "";
let preCadastroPhone = "";
if (preCustomer) {
preCadastroDocument = preCustomer.document || "";
preCadastroName = preCustomer.name || "";
preCadastroPhone = preCustomer.phone || "";
}
// Obter idAddress
const idAddress = address
? address.idAddress || address.addressId || 0
: 0;
// Obter carrierId
const savedTaxDelivery = localStorage.getItem("taxDelivery");
let carrierIdValue = 0;
if (savedTaxDelivery) {
try {
const taxDelivery = JSON.parse(savedTaxDelivery);
carrierIdValue = taxDelivery.carrierId || 0;
} catch (e) {
console.error("Erro ao parsear taxDelivery:", e);
}
}
// Montar CartModel (igual ao Angular)
const cartModel: CartModel = {
id: shoppingService.getCart() || undefined,
userId: user?.id
? typeof user.id === "number"
? user.id
: parseInt(String(user.id), 10)
: undefined,
saleStore: invoiceStore.id,
idCustomer: customer.customerId,
idPaymentPlan: paymentPlan.codplpag,
idBilling: billing.codcob,
idSeller: authService.getSeller()
? typeof authService.getSeller() === "number"
? authService.getSeller()
: parseInt(String(authService.getSeller()), 10)
: undefined,
idProfessional: partner ? partner.id : 0,
scheduleDelivery: notesForm.scheduleDelivery,
shippingDate: notesForm.shippingDate,
shippingPriority: notesForm.shippingPriority,
idStorePlace: notesForm.shippingPriority === "M" ? null : null, // TODO: Implementar storePlaceSelected
shippingValue: taxValueNum,
idAddress,
notation1: notesForm.notesText1 || "",
notation2: notesForm.notesText2 || "",
notation3: "",
deliveryNote1: notesForm.notesDeliveryText1 || "",
deliveryNote2: notesForm.notesDeliveryText2 || "",
deliveryNote3: notesForm.notesDeliveryText3 || "",
itens: itensCart,
preCustomerDocument:
customer.customerId === 1 ? preCadastroDocument : null,
preCustomerName: customer.customerId === 1 ? preCadastroName : null,
preCustomerPhone:
customer.customerId === 1 ? preCadastroPhone : null,
carrierId: carrierIdValue,
};
console.log(
"📦 [ORDER] Criando pedido com dados:",
JSON.stringify(cartModel, null, 2)
);
const result = await orderService.createOrder(cartModel);
if (result.success && result.orderId) {
setConfirmDialog({
type: "success",
title: "Gravar pedido de venda",
message: `Pedido de venda gerado com sucesso!\n\nNúmero do pedido gerado: ${result.orderId}`,
onConfirm: () => {
setShowConfirmDialog(false);
// Limpar carrinho usando o hook (se disponível)
if (onClearCart) {
onClearCart();
}
// Limpar dados do localStorage
shoppingService.clearShoppingData();
onBack();
},
});
setShowConfirmDialog(true);
} else {
setConfirmDialog({
type: "error",
title: "Gravar pedido de venda",
message:
result.message ||
"Ops, houve um problema na geração do pedido de venda.\n\nTente novamente ou entre em contato com o suporte.",
onConfirm: () => {
setShowConfirmDialog(false);
},
});
setShowConfirmDialog(true);
}
} catch (error: any) {
console.error("📦 [ORDER] Erro ao criar pedido:", error);
// Extrair mensagem de erro mais detalhada
let errorMessage =
error.message ||
"Ops, houve um problema na geração do pedido de venda.";
let errorDescription =
"Tente novamente ou entre em contato com o suporte.";
// Se houver errorData com mais detalhes
if (error.errorData) {
const errorData = error.errorData;
// Se houver errors object, formatar
if (errorData.errors && typeof errorData.errors === "object") {
const errorDetails = Object.entries(errorData.errors)
.map(([key, value]) => {
if (Array.isArray(value)) {
return `${key}: ${value.join(", ")}`;
}
return `${key}: ${value}`;
})
.join("\n");
if (errorDetails) {
errorDescription = errorDetails;
}
}
// Se houver data com mensagem
if (errorData.data && typeof errorData.data === "string") {
errorMessage = errorData.data;
} else if (errorData.message) {
errorMessage = errorData.message;
}
}
// Combinar mensagem e descrição em uma única mensagem
const fullMessage =
errorDescription &&
errorDescription !==
"Tente novamente ou entre em contato com o suporte."
? `${errorMessage}\n\n${errorDescription}`
: errorMessage;
setConfirmDialog({
type: "error",
title: "Gravar pedido de venda",
message: fullMessage,
onConfirm: () => {
setShowConfirmDialog(false);
},
});
setShowConfirmDialog(true);
} finally {
setIsLoadingOrder(false);
}
},
});
setShowConfirmDialog(true);
};
// Criar orçamento
const handleCreatePreOrder = async () => {
// Validação básica
const validation = validateOrder();
if (!validation.isValid) {
const fullMessage = validation.description
? `${validation.message}\n\n${validation.description}`
: validation.message;
setConfirmDialog({
type: "warning",
title: "Gravar orçamento",
message: fullMessage,
onConfirm: () => {
setShowConfirmDialog(false);
},
});
setShowConfirmDialog(true);
return;
}
// Validação completa (igual ao Angular)
if (!orderValid()) {
return;
}
// Verificar se há data de entrega
if (!notesForm.shippingDate) {
setConfirmDialog({
type: "warning",
title: "Pedido de venda",
message:
"Não foi possível gerar o pedido de venda!\n\nFavor informe a data de entrega do pedido.",
onConfirm: () => {
setShowConfirmDialog(false);
},
});
setShowConfirmDialog(true);
return;
}
// Salvar dados de entrega
saveDeliveryData();
// Mostrar confirmação
setConfirmDialog({
type: "confirm",
title: "Gravar orçamento",
message: "Deseja gravar como orçamento?",
onConfirm: async () => {
setShowConfirmDialog(false);
setIsLoadingPreOrder(true);
try {
const customer = JSON.parse(
localStorage.getItem("customer")!
) as Customer;
const address = JSON.parse(
localStorage.getItem("address") || "null"
) as CustomerAddress | null;
const paymentPlan = JSON.parse(
localStorage.getItem("paymentPlan")!
) as PaymentPlan;
const billing = JSON.parse(
localStorage.getItem("billing")!
) as Billing;
const invoiceStore = JSON.parse(
localStorage.getItem("invoiceStore")!
) as StoreERP;
const partner = JSON.parse(
localStorage.getItem("partner") || "null"
) as PartnerSales | null;
const preCustomer = JSON.parse(
localStorage.getItem("preCustomer") || "null"
);
const user = authService.getUser();
// Validações adicionais (igual ao Angular)
if (!invoiceStore) {
setConfirmDialog({
type: "warning",
title: "Pedido de venda",
message:
"Não foi possível gerar o pedido de venda!\n\nFavor selecione uma filial de venda para o orçamento.",
onConfirm: () => {
setShowConfirmDialog(false);
},
});
setShowConfirmDialog(true);
setIsLoadingPreOrder(false);
return;
}
// Converter itens do carrinho
const itensCart = getItens();
// Obter idAddress
const idAddress = address
? address.idAddress || address.addressId || 0
: 0;
// Obter carrierId
const savedTaxDelivery = localStorage.getItem("taxDelivery");
let carrierIdValue = 0;
if (savedTaxDelivery) {
try {
const taxDelivery = JSON.parse(savedTaxDelivery);
carrierIdValue = taxDelivery.carrierId || 0;
} catch (e) {
console.error("Erro ao parsear taxDelivery:", e);
}
}
// Montar CartModel (igual ao Angular)
const cartModel: CartModel = {
id: shoppingService.getCart() || undefined,
userId: user?.id
? typeof user.id === "number"
? user.id
: parseInt(String(user.id), 10)
: undefined,
saleStore: invoiceStore.id,
idCustomer: customer.customerId,
idPaymentPlan: paymentPlan.codplpag,
idBilling: billing.codcob,
idSeller: authService.getSeller()
? typeof authService.getSeller() === "number"
? authService.getSeller()
: parseInt(String(authService.getSeller()), 10)
: undefined,
idProfessional: partner ? partner.id : 0,
shippingDate: notesForm.shippingDate,
scheduleDelivery: notesForm.scheduleDelivery,
shippingPriority: notesForm.shippingPriority,
shippingValue: taxValueNum,
carrierId: carrierIdValue || 0,
idAddress,
notation1: notesForm.notesText1 || "",
notation2: notesForm.notesText2 || "",
notation3: "",
deliveryNote1: notesForm.notesDeliveryText1 || "",
deliveryNote2: notesForm.notesDeliveryText2 || "",
deliveryNote3: notesForm.notesDeliveryText3 || "",
itens: itensCart,
preCustomerDocument:
customer.customerId === 1 ? preCustomer?.document || null : null,
preCustomerName:
customer.customerId === 1 ? preCustomer?.name || null : null,
preCustomerPhone:
customer.customerId === 1 ? preCustomer?.phone || null : null,
};
console.log(
"📋 [PREORDER] Criando orçamento com dados:",
JSON.stringify(cartModel, null, 2)
);
const result = await orderService.createPreOrder(cartModel);
if (result.success && result.preOrderId) {
setConfirmDialog({
type: "success",
title: "Gravar pedido de venda",
message: `Orçamento gerado com sucesso!\n\nNúmero do orçamento gerado: ${result.preOrderId}`,
onConfirm: () => {
setShowConfirmDialog(false);
// Limpar carrinho usando o hook (se disponível)
if (onClearCart) {
onClearCart();
}
// Limpar dados do localStorage
shoppingService.clearShoppingData();
onBack();
},
});
setShowConfirmDialog(true);
} else {
setConfirmDialog({
type: "error",
title: "Gravar pedido de venda",
message:
result.message ||
"Houve um erro ao gerar o orçamento!\n\nTente novamente ou entre em contato com o suporte.",
onConfirm: () => {
setShowConfirmDialog(false);
},
});
setShowConfirmDialog(true);
}
} catch (error: any) {
console.error("📋 [PREORDER] Erro ao criar orçamento:", error);
// Extrair mensagem de erro mais detalhada
let errorMessage =
error.message || "Houve um erro ao gerar o orçamento!";
let errorDescription =
"Tente novamente ou entre em contato com o suporte.";
// Se houver errorData com mais detalhes
if (error.errorData) {
const errorData = error.errorData;
// Se houver errors object, formatar
if (errorData.errors && typeof errorData.errors === "object") {
const errorDetails = Object.entries(errorData.errors)
.map(([key, value]) => {
if (Array.isArray(value)) {
return `${key}: ${value.join(", ")}`;
}
return `${key}: ${value}`;
})
.join("\n");
if (errorDetails) {
errorDescription = errorDetails;
}
}
// Se houver data com mensagem
if (errorData.data && typeof errorData.data === "string") {
errorMessage = errorData.data;
} else if (errorData.message) {
errorMessage = errorData.message;
}
}
// Combinar mensagem e descrição em uma única mensagem
const fullMessage =
errorDescription &&
errorDescription !==
"Tente novamente ou entre em contato com o suporte."
? `${errorMessage}\n\n${errorDescription}`
: errorMessage;
setConfirmDialog({
type: "error",
title: "Gravar pedido de venda",
message: fullMessage,
onConfirm: () => {
setShowConfirmDialog(false);
},
});
setShowConfirmDialog(true);
} finally {
setIsLoadingPreOrder(false);
}
},
});
setShowConfirmDialog(true);
};
// Handlers de formulário
const handleCustomerFormChange = (field: string, value: string) => {
setCustomerForm((prev) => ({ ...prev, [field]: value }));
if (customerErrors[field]) {
setCustomerErrors((prev) => {
const newErrors = { ...prev };
delete newErrors[field];
return newErrors;
});
}
};
const handleAddressFormChange = (field: string, value: string) => {
setAddressForm((prev) => ({ ...prev, [field]: value }));
if (addressErrors[field]) {
setAddressErrors((prev) => {
const newErrors = { ...prev };
delete newErrors[field];
return newErrors;
});
}
};
const handleNotesFormChange = (field: string, value: any) => {
// Validar data mínima (D+3) quando o campo shippingDate for alterado
if (field === "shippingDate" && value) {
const today = new Date();
today.setHours(0, 0, 0, 0); // Zerar horas para comparação apenas de data
// Calcular data mínima (D+3)
const minDate = new Date(today);
minDate.setDate(today.getDate() + 3);
minDate.setHours(0, 0, 0, 0);
// Normalizar a data selecionada para comparação (apenas data, sem hora)
// Criar uma nova data local a partir dos componentes da data recebida
const selectedDate = new Date(
value.getFullYear(),
value.getMonth(),
value.getDate()
);
selectedDate.setHours(0, 0, 0, 0);
console.log("📅 [CHECKOUT] Validando data de entrega:");
console.log("📅 [CHECKOUT] Data selecionada:", selectedDate);
console.log("📅 [CHECKOUT] Data mínima (D+3):", minDate);
console.log(
"📅 [CHECKOUT] Data selecionada < Data mínima?",
selectedDate < minDate
);
// Se a data selecionada for menor que D+3, mostrar dialog e ajustar
if (selectedDate < minDate) {
console.log("📅 [CHECKOUT] Data inválida! Ajustando para D+3");
setShowDateValidationDialog(true);
// Ajustar para D+3
setNotesForm((prev) => ({ ...prev, [field]: minDate }));
saveDeliveryData();
return;
}
}
setNotesForm((prev) => ({ ...prev, [field]: value }));
saveDeliveryData();
};
const handleRemoveAddress = () => {
setSelectedAddress(null);
setAddressForm({
zipCode: "",
address: "",
number: "",
city: "",
state: "",
complement: "",
referencePoint: "",
note: "",
});
localStorage.removeItem("address");
};
// Calcular taxa de entrega
const handleChangeTax = async () => {
// Verificar se há itens para entrega
const itemsDelivery = cart.filter(
(item) => item.deliveryType === "EN" || item.deliveryType === "EF"
);
if (itemsDelivery.length === 0 || notesForm.shippingPriority === "M") {
setTaxValue("0");
localStorage.setItem("taxDelivery", JSON.stringify({ taxValue: 0 }));
return;
}
// Buscar dados necessários
const customer = JSON.parse(
localStorage.getItem("customer") || "{}"
) as Customer;
const addressCustomer = JSON.parse(
localStorage.getItem("address") || "null"
);
const invoiceStore = JSON.parse(
localStorage.getItem("invoiceStore") || "null"
);
const cartId = shoppingService.getCart() || "";
if (!cartId) {
setTaxErrorDialog({
title: "Taxa de Entrega",
message: "Não foi possível identificar o carrinho. Tente novamente.",
});
setShowTaxErrorDialog(true);
return;
}
const user = authService.getUser();
let storeId = invoiceStore ? invoiceStore.id : user?.store;
let cityId = 0;
let ibgeCode = 0;
if (addressCustomer && addressCustomer.cityCode) {
cityId = addressCustomer.cityCode;
ibgeCode = addressCustomer.ibgeCode;
} else if (customer.cityId) {
cityId = customer.cityId;
ibgeCode = parseInt(String(customer.ibgeCode || "0"));
}
if (cityId === 0 || ibgeCode === 0) {
setInfoModal({
title: "Taxa de Entrega",
message: "Dados de endereço incompletos",
description:
"É necessário informar um endereço completo para calcular a taxa de entrega.",
});
setShowInfoModal(true);
return;
}
const dataDeliveryTax: CalculateDeliveryTaxRequest = {
cartId,
cityId,
ibgeCode,
priorityDelivery: notesForm.shippingPriority,
};
setIsLoadingDeliveryTax(true);
setShowDeliveryTaxModal(true);
try {
const options = await orderService.calculateDeliveryTax(dataDeliveryTax);
setDeliveryTaxOptions(options);
// Se já existe uma taxa selecionada, tentar manter
if (deliveryTaxId && options.length > 0) {
const selected = options.find((opt) => opt.id === deliveryTaxId);
if (selected) {
setTaxValue(selected.deliveryValue.toFixed(2));
setCarrierId(selected.carrierId);
localStorage.setItem(
"taxDelivery",
JSON.stringify({
taxValue: selected.deliveryValue,
deliveryTaxId: selected.id,
carrierId: selected.carrierId,
})
);
}
}
} catch (error: any) {
console.error("Erro ao calcular taxa de entrega:", error);
setTaxErrorDialog({
title: "Taxa de Entrega",
message:
error.message ||
"Ocorreu um erro ao calcular a taxa de entrega. Por favor, tente novamente.",
});
setShowTaxErrorDialog(true);
setShowDeliveryTaxModal(false);
} finally {
setIsLoadingDeliveryTax(false);
}
};
const handleSelectDeliveryTax = (deliveryTax: DeliveryTaxTable) => {
setTaxValue(deliveryTax.deliveryValue.toFixed(2));
setDeliveryTaxId(deliveryTax.id);
setCarrierId(deliveryTax.carrierId);
localStorage.setItem(
"taxDelivery",
JSON.stringify({
taxValue: deliveryTax.deliveryValue,
deliveryTaxId: deliveryTax.id,
carrierId: deliveryTax.carrierId,
})
);
};
// Aplicar desconto
const handleApplyDiscount = () => {
const payment = JSON.parse(
localStorage.getItem("paymentPlan") || "null"
) as PaymentPlan | null;
if (!payment) {
setInfoModal({
title: "Desconto para o pedido",
message:
"Venda sem plano de pagamento informado, desconto não permitido!",
description:
"Selecione primeiro um plano de pagamento para aplicar um desconto!",
});
setShowInfoModal(true);
return;
}
const isManager = authService.isManager();
if (payment.numdias && payment.numdias > 30 && !isManager) {
setInfoModal({
title: "Desconto para o pedido",
message: "Plano de pagamento parcelado, desconto não permitido!",
description:
"Desconto autorizado apenas para venda a vista ou em 1x no cartão!",
});
setShowInfoModal(true);
return;
}
// Calcular valor do pedido (sem promoções)
const productValue = cart
.filter((item) => !item.promotion || item.promotion === 0)
.reduce((acc, item) => acc + item.price * item.quantity, 0);
if (productValue <= 0) {
setInfoModal({
title: "Pedido de venda",
message: "Não existem itens disponíveis para aplicar desconto !",
description:
"Não existem itens adicionados no carrinho ou todos os itens estão em promoção!",
});
setShowInfoModal(true);
return;
}
// Calcular lucro
const profit = cart
.filter((item) => !item.promotion || item.promotion === 0)
.reduce((acc, item) => acc + (item.cost || 0) * item.quantity, 0);
const percentProfit =
productValue > 0
? (((productValue - profit) / productValue) * 100).toFixed(2)
: "0.00";
// Obter desconto atual
const currentDiscount =
parseFloat(discountValue.replace(/[^\d,.-]/g, "").replace(",", ".")) || 0;
setShowDiscountModal(true);
};
// Handlers para ações dos itens do carrinho
const handleEditItem = async (item: OrderItem) => {
try {
setSelectedItemForEdit(item);
setShowProductEditModal(true);
// O EditItemModal vai carregar os detalhes do produto internamente
} catch (error: any) {
console.error("Erro ao abrir modal de edição:", error);
setConfirmDialog({
type: "error",
title: "Erro",
message: error.message || "Não foi possível abrir o modal de edição.",
onConfirm: () => {
setShowConfirmDialog(false);
},
});
setShowConfirmDialog(true);
}
};
const handleDiscountItem = (item: OrderItem) => {
// Validações antes de abrir o modal
const paymentPlan = localStorage.getItem("paymentPlan");
if (!paymentPlan) {
setConfirmDialog({
type: "warning",
title: "Desconto sobre item de venda",
message:
"Venda sem plano de pagamento informado, desconto não permitido!",
onConfirm: () => {
setShowConfirmDialog(false);
},
});
setShowConfirmDialog(true);
return;
}
if (item.promotion && item.promotion > 0) {
setConfirmDialog({
type: "warning",
title: "Desconto sobre item de venda",
message: "Produto em promoção, desconto não permitido!",
onConfirm: () => {
setShowConfirmDialog(false);
},
});
setShowConfirmDialog(true);
return;
}
setSelectedItemForDiscount(item);
setShowDiscountItemModal(true);
};
const handleRemoveItem = (item: OrderItem) => {
setSelectedItemForRemove(item);
setConfirmDialog({
type: "delete",
title: "Pedido de venda",
message: "Confirma a exclusão do item do pedido?",
onConfirm: async () => {
try {
// O item.id deve ser o ID do item no carrinho (retornado pelo backend)
// Verificar se é um UUID (geralmente tem mais de 10 caracteres) ou se é o ID do backend
if (item.id) {
await shoppingService.deleteItemShopping(item.id);
} else {
console.error("Item sem ID válido para remoção:", item);
throw new Error("Item não possui ID válido para remoção");
}
setShowConfirmDialog(false);
setSelectedItemForRemove(null);
// Atualizar carrinho após remoção bem-sucedida
if (onCartUpdate) {
await onCartUpdate();
} else {
window.location.reload();
}
} catch (error: any) {
console.error("Erro ao remover item:", error);
setShowConfirmDialog(false);
setSelectedItemForRemove(null);
// Usar ConfirmDialog com type "error" em vez de InfoModal
setConfirmDialog({
type: "error",
title: "Erro",
message:
error.message || "Não foi possível remover o item do carrinho.",
onConfirm: () => {
setShowConfirmDialog(false);
},
});
setShowConfirmDialog(true);
}
},
});
setShowConfirmDialog(true);
};
const handleDiscountItemConfirm = () => {
// Atualizar carrinho após aplicar desconto
if (onCartUpdate) {
onCartUpdate();
} else {
window.location.reload();
}
};
const handleConfirmDiscount = (
discountValueNum: number,
discountPercentNum: number
) => {
setDiscountValue(
new Intl.NumberFormat("pt-BR", {
style: "currency",
currency: "BRL",
})
.format(discountValueNum)
.replace("R$", "")
.trim()
);
localStorage.setItem(
"discount",
JSON.stringify({ discountValue: discountValueNum })
);
};
return (
<div className="min-h-full bg-[#f8fafc] p-4 lg:p-10">
<div className="max-w-[1700px] mx-auto">
{/* Header */}
<div className="flex flex-col md:flex-row justify-between items-start md:items-center mb-6 gap-4">
<h2 className="text-3xl font-extrabold text-[#f97316]">
Resumo do Pedido
</h2>
<button
onClick={onBack}
className="bg-[#2d327d] text-white px-8 py-3 rounded-md font-bold uppercase text-xs tracking-widest hover:bg-[#1e2255] transition-all shadow-md active:scale-95"
>
ADICIONAR ITENS
</button>
</div>
{/* Tabela de Produtos */}
<CheckoutProductsTable
cart={cart}
onEdit={handleEditItem}
onDiscount={handleDiscountItem}
onRemove={handleRemoveItem}
/>
<div className="grid grid-cols-1 xl:grid-cols-12 gap-4 lg:gap-8 items-stretch">
{/* Esquerda: Formulários do Wizard */}
<div className="xl:col-span-7 flex flex-col">
<CheckoutWizard currentStep={currentStep} onStepChange={goToStep}>
{currentStep === "customer" && (
<CustomerStep
customerForm={customerForm}
customerErrors={customerErrors}
selectedCustomer={selectedCustomer}
onFormChange={handleCustomerFormChange}
onShowCreateModal={() => setShowCreateCustomerModal(true)}
onShowSearchModal={() => setShowCustomerModal(true)}
onNext={handleNextStep}
/>
)}
{currentStep === "payment" && (
<PaymentStep
paymentForm={paymentForm}
paymentErrors={paymentErrors}
stores={stores}
billings={billings}
paymentPlans={paymentPlans}
partners={partners}
isLoadingPaymentData={isLoadingPaymentData}
onInvoiceStoreChange={handleSetInvoiceStore}
onBillingChange={handleSetBilling}
onPaymentPlanChange={handleSetPaymentPlan}
onPartnerChange={handleSetPartner}
onPrevious={handlePreviousStep}
onNext={handleNextStep}
/>
)}
{currentStep === "address" && (
<AddressStep
addressForm={addressForm}
addressErrors={addressErrors}
selectedAddress={selectedAddress}
customerId={selectedCustomer?.customerId || null}
onFormChange={handleAddressFormChange}
onShowAddressModal={() => setShowAddressModal(true)}
onRemoveAddress={handleRemoveAddress}
onSelectAddress={(address) => {
setSelectedAddress(address);
setAddressForm({
zipCode: address.zipCode || "",
address: address.address || "",
number: address.number || "",
city: address.city || "",
state: address.state || "",
complement: address.complement || "",
referencePoint: address.referencePoint || "",
note: address.note || "",
});
localStorage.setItem("address", JSON.stringify(address));
}}
onPrevious={handlePreviousStep}
onNext={handleNextStep}
/>
)}
{currentStep === "notes" && (
<NotesStep
notesForm={notesForm}
onFormChange={handleNotesFormChange}
onPrevious={handlePreviousStep}
/>
)}
</CheckoutWizard>
</div>
{/* Direita: Revisar Detalhes do Pedido */}
<div className="xl:col-span-5 flex flex-col">
<CheckoutSummary
subtotal={subtotal}
totalWeight={totalWeight}
taxValue={taxValue}
discountValue={discountValue}
total={total}
isLoadingOrder={isLoadingOrder}
isLoadingPreOrder={isLoadingPreOrder}
shippingDate={notesForm.shippingDate}
onTaxChange={setTaxValue}
onDiscountChange={setDiscountValue}
onChangeTax={handleChangeTax}
onApplyDiscount={handleApplyDiscount}
onCreateOrder={handleCreateOrder}
onCreatePreOrder={handleCreatePreOrder}
/>
</div>
</div>
</div>
{/* Modais */}
<CreateCustomerDialog
isOpen={showCreateCustomerModal}
onClose={() => setShowCreateCustomerModal(false)}
onSuccess={(customer) => {
handleSelectCustomer(customer);
setShowCreateCustomerModal(false);
}}
/>
<CustomerSearchModal
isOpen={showCustomerModal}
searchTerm={customerSearchTerm}
searchResults={customerSearchResults}
isSearching={isSearchingCustomers}
onClose={() => {
setShowCustomerModal(false);
setCustomerSearchTerm("");
setCustomerSearchResults([]);
}}
onSearchChange={setCustomerSearchTerm}
onSelectCustomer={handleSelectCustomer}
/>
<InfoModal
isOpen={showInfoModal}
title={infoModal.title}
message={infoModal.message}
description={infoModal.description}
onClose={() => setShowInfoModal(false)}
/>
<ConfirmDialog
isOpen={showConfirmDialog}
type={confirmDialog.type}
title={confirmDialog.title}
message={confirmDialog.message}
onConfirm={confirmDialog.onConfirm}
onClose={() => setShowConfirmDialog(false)}
confirmText={
confirmDialog.type === "delete"
? "Excluir"
: confirmDialog.type === "warning" &&
confirmDialog.title === "Desconto sobre item de venda"
? "OK"
: "Confirmar"
}
cancelText="Cancelar"
/>
<DeliveryTaxModal
isOpen={showDeliveryTaxModal}
onClose={() => setShowDeliveryTaxModal(false)}
onSelect={handleSelectDeliveryTax}
deliveryTaxOptions={deliveryTaxOptions}
isLoading={isLoadingDeliveryTax}
/>
<DiscountOrderModal
isOpen={showDiscountModal}
onClose={() => setShowDiscountModal(false)}
onConfirm={handleConfirmDiscount}
orderValue={cart
.filter((item) => !item.promotion || item.promotion === 0)
.reduce((acc, item) => acc + item.price * item.quantity, 0)}
profit={cart
.filter((item) => !item.promotion || item.promotion === 0)
.reduce((acc, item) => acc + (item.cost || 0) * item.quantity, 0)}
isManager={authService.isManager()}
/>
<LoadingModal
isOpen={showCalculatingTaxModal}
title="Calculando Taxa de Entrega"
message="Aguarde enquanto calculamos a nova taxa de entrega com base na prioridade selecionada..."
/>
<ConfirmDialog
isOpen={showTaxErrorDialog}
onClose={() => setShowTaxErrorDialog(false)}
onConfirm={() => setShowTaxErrorDialog(false)}
type={taxErrorDialog.type || "error"}
title={taxErrorDialog.title}
message={taxErrorDialog.message}
confirmText="OK"
/>
<ConfirmDialog
isOpen={showDateValidationDialog}
onClose={() => setShowDateValidationDialog(false)}
onConfirm={() => setShowDateValidationDialog(false)}
type="info"
title="Data de Entrega"
message="A data de previsão de entrega deve ser de no mínimo 3 dias (D+3) a partir de hoje. A data foi ajustada automaticamente."
confirmText="OK"
/>
{/* Modal de Desconto do Item */}
<DiscountItemModal
isOpen={showDiscountItemModal}
item={selectedItemForDiscount}
onClose={() => {
setShowDiscountItemModal(false);
setSelectedItemForDiscount(null);
}}
onConfirm={handleDiscountItemConfirm}
/>
{/* Modal de Edição de Produto */}
{showProductEditModal && selectedItemForEdit && (
<EditItemModal
item={selectedItemForEdit}
isOpen={showProductEditModal}
onClose={() => {
setShowProductEditModal(false);
setSelectedItemForEdit(null);
}}
onConfirm={async (updatedItem) => {
// Atualizar item no carrinho
try {
const shoppingItem = shoppingService.productToShoppingItem({
...updatedItem,
quantity: updatedItem.quantity,
});
shoppingItem.id = selectedItemForEdit.id;
await shoppingService.updateQuantityItemShopping(shoppingItem);
setShowProductEditModal(false);
setSelectedItemForEdit(null);
// Atualizar carrinho
if (onCartUpdate) {
onCartUpdate();
} else {
window.location.reload();
}
} catch (error: any) {
console.error("Erro ao atualizar item:", error);
setConfirmDialog({
type: "error",
title: "Erro",
message:
error.message ||
"Não foi possível atualizar o item do carrinho.",
onConfirm: () => {
setShowConfirmDialog(false);
},
});
setShowConfirmDialog(true);
}
}}
/>
)}
<style>{`
@keyframes fade-in {
from {
opacity: 0;
transform: translateY(10px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.animate-fade-in {
animation: fade-in 0.4s ease-out forwards;
}
.custom-scrollbar::-webkit-scrollbar {
height: 6px;
width: 6px;
}
.custom-scrollbar::-webkit-scrollbar-thumb {
background: #cbd5e1;
border-radius: 10px;
}
`}</style>
</div>
);
};
export default CheckoutView;