Vendaweb-portal/components/checkout/DiscountItemModal.tsx

285 lines
9.5 KiB
TypeScript
Raw Normal View History

2026-01-08 12:09:16 +00:00
import React, { useState, useEffect } from "react";
import { OrderItem } from "../../types";
import { shoppingService, ShoppingItem } from "../../src/services/shopping.service";
import { authService } from "../../src/services/auth.service";
interface DiscountItemModalProps {
isOpen: boolean;
item: OrderItem | null;
onClose: () => void;
onConfirm: () => void;
}
const DiscountItemModal: React.FC<DiscountItemModalProps> = ({
isOpen,
item,
onClose,
onConfirm,
}) => {
const [discount, setDiscount] = useState<number>(0);
const [discountValue, setDiscountValue] = useState<number>(0);
const [salePrice, setSalePrice] = useState<number>(0);
const [listPrice, setListPrice] = useState<number>(0);
const [isUpdating, setIsUpdating] = useState(false);
const [error, setError] = useState<string>("");
const [isAnimating, setIsAnimating] = useState(false);
const [shouldRender, setShouldRender] = useState(false);
useEffect(() => {
if (item && isOpen) {
const initialDiscount = item.discount || 0;
const initialListPrice = item.originalPrice || item.price || 0;
const initialDiscountValue = item.discountValue || 0;
const initialSalePrice = item.price || 0;
setDiscount(initialDiscount);
setDiscountValue(initialDiscountValue);
setSalePrice(initialSalePrice);
setListPrice(initialListPrice);
setError("");
}
}, [item, isOpen]);
// Animação de entrada/saída
useEffect(() => {
if (isOpen) {
setShouldRender(true);
setTimeout(() => setIsAnimating(true), 10);
} else {
setIsAnimating(false);
const timer = setTimeout(() => setShouldRender(false), 300);
return () => clearTimeout(timer);
}
}, [isOpen]);
const calcDiscountValue = () => {
if (!item) return;
const percent = discount;
const newDiscountValue = Number.parseFloat(
((listPrice * percent) / 100).toFixed(2)
);
const newSalePrice = Number.parseFloat(
(listPrice - newDiscountValue).toFixed(2)
);
setDiscountValue(newDiscountValue);
setSalePrice(newSalePrice);
};
const calcPercentDiscount = () => {
if (!item) return;
const newPercent =
Number.parseFloat((discountValue / listPrice).toFixed(2)) * 100;
const newSalePrice = Number.parseFloat(
(listPrice - discountValue).toFixed(2)
);
setDiscount(newPercent);
setSalePrice(newSalePrice);
};
const handleConfirm = async () => {
if (!item) return;
// Validações (paymentPlan já foi validado antes de abrir o modal)
const paymentPlan = localStorage.getItem("paymentPlan");
if (!paymentPlan) {
setError(
"Venda sem plano de pagamento informado, desconto não permitido!"
);
return;
}
try {
setIsUpdating(true);
setError("");
// Converter OrderItem para ShoppingItem
const shoppingItem = shoppingService.productToShoppingItem(item);
shoppingItem.id = item.id;
shoppingItem.discount = discount;
shoppingItem.discountValue = discountValue;
shoppingItem.price = salePrice;
shoppingItem.userDiscount = authService.getUser();
// Atualizar no backend
await shoppingService.updatePriceItemShopping(shoppingItem);
setIsAnimating(false);
setTimeout(() => {
onConfirm();
onClose();
}, 300);
} catch (err: any) {
setError(err.message || "Erro ao aplicar desconto");
} finally {
setIsUpdating(false);
}
};
const handleCancel = () => {
setIsAnimating(false);
setTimeout(() => {
onClose();
}, 300);
};
if (!shouldRender || !item) return null;
return (
<div className="fixed inset-0 z-[200] flex items-center justify-center">
{/* Overlay */}
<div
className={`absolute inset-0 bg-[#001f3f]/60 backdrop-blur-sm transition-opacity duration-300 ${
isAnimating ? "opacity-100" : "opacity-0"
}`}
onClick={handleCancel}
></div>
{/* Dialog */}
<div
className={`relative bg-white rounded-3xl shadow-2xl max-w-md w-full mx-4 transform transition-all duration-300 ${
isAnimating ? "scale-100 opacity-100" : "scale-95 opacity-0"
}`}
>
{/* Header */}
<div className="p-6 bg-[#002147] text-white rounded-t-3xl relative overflow-hidden">
<div className="relative z-10">
<div className="flex items-center gap-3 mb-2">
<div className="w-12 h-12 bg-orange-500/20 rounded-2xl flex items-center justify-center">
<svg
className="w-6 h-6 text-orange-400"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2.5"
d="M12 8c-1.657 0-3 .895-3 2s1.343 2 3 2 3 .895 3 2-1.343 2-3 2m0-8c1.11 0 2.08.402 2.599 1M12 8V7m0 1v8m0 0v1m0-1c-1.11 0-2.08-.402-2.599-1M21 12a9 9 0 11-18 0 9 9 0 0118 0z"
/>
</svg>
</div>
<div className="flex-1">
<h3 className="text-xl font-black">Desconto por produto</h3>
<p className="text-xs text-orange-400 font-bold uppercase tracking-wider mt-0.5">
Desconto sobre item de venda
</p>
</div>
</div>
</div>
<div className="absolute right-[-10%] top-[-10%] w-32 h-32 bg-orange-400/10 rounded-full blur-2xl"></div>
</div>
{/* Content */}
<div className="p-6">
<form className="space-y-4">
{/* Produto */}
<div>
<label className="block text-sm font-medium text-slate-700 mb-1">
Produto
</label>
<input
type="text"
value={item.name}
disabled
className="w-full px-3 py-2 border border-slate-300 rounded-md bg-slate-50 text-slate-600"
/>
</div>
{/* Preço de tabela */}
<div>
<label className="block text-sm font-medium text-slate-700 mb-1">
Preço de tabela
</label>
<input
type="number"
value={listPrice.toFixed(2)}
disabled
className="w-full px-3 py-2 border border-slate-300 rounded-md bg-slate-50 text-slate-600"
/>
</div>
{/* % Desconto e Valor de desconto */}
<div className="grid grid-cols-2 gap-4">
<div>
<label className="block text-sm font-medium text-slate-700 mb-1">
% Desconto
</label>
<input
type="number"
value={discount}
onChange={(e) => {
setDiscount(Number.parseFloat(e.target.value) || 0);
}}
onBlur={calcDiscountValue}
step="0.01"
min="0"
max="100"
className="w-full px-3 py-2 border border-slate-300 rounded-md focus:outline-none focus:ring-2 focus:ring-[#002147]"
/>
</div>
<div>
<label className="block text-sm font-medium text-slate-700 mb-1">
Valor de desconto
</label>
<input
type="number"
value={discountValue.toFixed(2)}
onChange={(e) => {
setDiscountValue(Number.parseFloat(e.target.value) || 0);
}}
onBlur={calcPercentDiscount}
step="0.01"
min="0"
className="w-full px-3 py-2 border border-slate-300 rounded-md focus:outline-none focus:ring-2 focus:ring-[#002147]"
/>
</div>
</div>
{/* Preço de venda */}
<div>
<label className="block text-sm font-medium text-slate-700 mb-1">
Preço de venda
</label>
<input
type="number"
value={salePrice.toFixed(2)}
disabled
className="w-full px-3 py-2 border border-slate-300 rounded-md bg-slate-50 text-slate-600"
/>
</div>
{/* Erro */}
{error && (
<div className="bg-red-50 border border-red-200 text-red-700 px-4 py-3 rounded-md text-sm">
{error}
</div>
)}
</form>
</div>
{/* Actions */}
<div className="p-6 pt-0 flex gap-3">
<button
onClick={handleCancel}
disabled={isUpdating}
className="flex-1 py-3 px-4 bg-slate-100 text-slate-600 font-bold uppercase text-xs tracking-wider rounded-xl hover:bg-slate-200 transition-all disabled:opacity-50"
>
Cancelar
</button>
<button
onClick={handleConfirm}
disabled={isUpdating}
className="flex-1 py-3 px-4 text-white font-black uppercase text-xs tracking-wider rounded-xl transition-all shadow-lg bg-orange-500 hover:bg-orange-600 shadow-orange-500/20 active:scale-95 disabled:opacity-50"
>
{isUpdating ? "Aplicando..." : "Confirmar"}
</button>
</div>
</div>
</div>
);
};
export default DiscountItemModal;