2772 lines
94 KiB
TypeScript
2772 lines
94 KiB
TypeScript
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;
|