Vendaweb-portal/components/CartDrawer.tsx

470 lines
17 KiB
TypeScript
Raw Permalink Normal View History

2026-01-08 12:09:16 +00:00
import React, { useState, useEffect } from "react";
import { OrderItem } from "../types";
import ConfirmDialog from "./ConfirmDialog";
interface CartDrawerProps {
isOpen: boolean;
onClose: () => void;
items: OrderItem[];
onUpdateQuantity: (id: string, delta: number) => void;
onRemove: (id: string) => void;
onCheckout: () => void;
onNewOrder?: () => void;
onNavigate?: (path: string) => void;
}
const CartDrawer: React.FC<CartDrawerProps> = ({
isOpen,
onClose,
items,
onUpdateQuantity,
onRemove,
onCheckout,
onNewOrder,
onNavigate,
}) => {
const [isAnimating, setIsAnimating] = useState(false);
const [shouldRender, setShouldRender] = useState(false);
const [confirmDialog, setConfirmDialog] = useState<{
isOpen: boolean;
itemId: string;
productName: string;
}>({
isOpen: false,
itemId: "",
productName: "",
});
const [showNewOrderDialog, setShowNewOrderDialog] = useState(false);
const [showContinueOrNewDialog, setShowContinueOrNewDialog] = useState(false);
const total = items.reduce(
(acc, item) => acc + item.price * item.quantity,
0
);
// Controlar renderização e animação
useEffect(() => {
if (isOpen) {
setShouldRender(true);
// Pequeno delay para garantir que o DOM está pronto antes de iniciar a animação
setTimeout(() => setIsAnimating(true), 10);
} else {
// Iniciar animação de saída
setIsAnimating(false);
// Remover do DOM após a animação terminar
const timer = setTimeout(() => setShouldRender(false), 400); // Duração da animação
return () => clearTimeout(timer);
}
}, [isOpen]);
// Não renderizar se não deve estar visível
if (!shouldRender) return null;
const handleClose = () => {
// Chamar onClose imediatamente para atualizar o estado
// O useEffect vai cuidar da animação de saída
onClose();
};
// Verificar se há carrinho (itens no carrinho)
const hasCart = () => {
return items.length > 0 || localStorage.getItem("cart");
};
// Verificar se há dados do pedido atual
const hasOrderData = () => {
const hasCartItems = items.length > 0;
const hasCustomer = localStorage.getItem("customer");
const hasAddress = localStorage.getItem("address");
const hasPaymentPlan = localStorage.getItem("paymentPlan");
const hasBilling = localStorage.getItem("billing");
const hasDataDelivery = localStorage.getItem("dataDelivery");
const hasInvoiceStore = localStorage.getItem("invoiceStore");
const hasPartner = localStorage.getItem("partner");
return (
hasCartItems ||
hasCustomer ||
hasAddress ||
hasPaymentPlan ||
hasBilling ||
hasDataDelivery ||
hasInvoiceStore ||
hasPartner
);
};
const handleNewOrderClick = () => {
// Se houver carrinho, perguntar se quer continuar ou iniciar novo
if (hasCart()) {
setShowContinueOrNewDialog(true);
return;
}
// Se não houver carrinho mas houver outros dados, mostrar confirmação normal
if (hasOrderData()) {
setShowNewOrderDialog(true);
return;
}
// Se não houver nenhum dado, limpar direto sem confirmação
if (onNewOrder) {
onNewOrder();
handleClose();
}
};
const handleContinueOrder = () => {
// Fechar o dialog e navegar para /sales/home
setShowContinueOrNewDialog(false);
handleClose(); // Fechar o drawer também
window.location.href = "/#/sales/home";
};
const handleStartNewOrder = () => {
// Fechar o dialog de continuar/iniciar e abrir o de confirmação
setShowContinueOrNewDialog(false);
setShowNewOrderDialog(true);
};
const handleConfirmNewOrder = () => {
if (onNewOrder) {
onNewOrder();
setShowNewOrderDialog(false);
handleClose();
}
};
return (
<div className="fixed inset-0 z-[100] flex justify-end">
{/* Overlay */}
<div
className={`absolute inset-0 bg-[#001f3f]/40 backdrop-blur-[2px] transition-opacity duration-400 ${
isAnimating ? "opacity-100" : "opacity-0"
}`}
onClick={handleClose}
></div>
{/* Panel - Fullscreen em mobile, sidebar em desktop */}
<div
className={`relative w-full lg:max-w-[440px] bg-white h-full shadow-2xl flex flex-col transition-transform duration-400 ease-out-quart ${
isAnimating ? "translate-x-0" : "translate-x-full"
}`}
>
<div className="p-4 lg:p-6 bg-[#002147] text-white flex justify-between items-center relative overflow-hidden safe-area-top">
<div className="relative z-10">
<span className="text-orange-400 text-[9px] font-black uppercase tracking-[0.2em] block mb-0.5">
Seu Carrinho
</span>
<h3 className="text-lg lg:text-xl font-black">
{items.length} {items.length === 1 ? "Produto" : "Produtos"}
</h3>
</div>
<button
onClick={handleClose}
className="relative z-10 w-10 h-10 lg:w-12 lg:h-12 flex items-center justify-center rounded-2xl bg-white/10 hover:bg-white/20 transition-colors touch-manipulation"
>
<svg
className="w-6 h-6"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2.5"
d="M6 18L18 6M6 6l12 12"
/>
</svg>
</button>
<div className="absolute right-[-20%] top-[-20%] w-64 h-64 bg-blue-400/10 rounded-full blur-3xl"></div>
</div>
<div className="flex-1 overflow-auto p-4 lg:p-5 space-y-4 lg:space-y-5 custom-scrollbar">
{items.length === 0 ? (
<div className="h-full flex flex-col items-center justify-center text-center opacity-40">
<div className="w-16 h-16 bg-slate-100 rounded-full flex items-center justify-center mb-3">
<svg
className="w-8 h-8"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
d="M16 11V7a4 4 0 00-8 0v4M5 9h14l1 12H4L5 9z"
/>
</svg>
</div>
<p className="font-bold text-sm">Seu carrinho está vazio</p>
<p className="text-xs">Adicione produtos para começar.</p>
</div>
) : (
items.map((item) => (
<div key={item.id} className="flex space-x-4 group">
<div className="w-20 h-20 bg-slate-50 rounded-xl p-3 mt-1.5 flex items-center justify-center shrink-0 border border-slate-100">
{item.image && item.image.trim() !== "" ? (
<img
src={item.image}
alt={item.name}
className="max-h-full mix-blend-multiply"
/>
) : (
<svg
className="w-8 h-8 text-slate-300"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
d="M4 16l4.586-4.586a2 2 0 012.828 0L16 16m-2-2l1.586-1.586a2 2 0 012.828 0L20 14m-6-6h.01M6 20h12a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v12a2 2 0 002 2z"
/>
</svg>
)}
</div>
<div className="flex-1 min-w-0">
<h5 className="text-xs font-bold text-[#002147] line-clamp-1 mb-1 group-hover:text-orange-600 transition-colors">
{item.name}
</h5>
<span className="text-lg font-black block mb-0.5">
R$ {item.price.toFixed(2)}
</span>
<div className="flex items-center justify-between">
<div className="flex bg-slate-100 rounded-lg p-0.5 items-center border border-slate-200">
<button
onClick={(e) => {
e.preventDefault();
e.stopPropagation();
console.log(
"🛒 [CartDrawer] Botão - clicado:",
item.id
);
onUpdateQuantity(item.id, -1).catch((err) => {
console.error(
"🛒 [CartDrawer] Erro ao diminuir quantidade:",
err
);
});
}}
className="w-7 h-7 flex items-center justify-center font-bold text-slate-400 hover:text-[#002147] transition-colors text-sm touch-manipulation"
>
-
</button>
<span className="px-3 text-xs font-black min-w-[32px] text-center">
{item.quantity}
</span>
<button
onClick={(e) => {
e.preventDefault();
e.stopPropagation();
console.log(
"🛒 [CartDrawer] Botão + clicado:",
item.id
);
onUpdateQuantity(item.id, 1).catch((err) => {
console.error(
"🛒 [CartDrawer] Erro ao aumentar quantidade:",
err
);
});
}}
className="w-7 h-7 flex items-center justify-center font-bold text-slate-400 hover:text-[#002147] transition-colors text-sm touch-manipulation"
>
+
</button>
</div>
<button
onClick={() => {
setConfirmDialog({
isOpen: true,
itemId: item.id,
productName: item.name,
});
}}
className="text-slate-300 hover:text-red-500 transition-colors p-1.5"
>
<svg
className="w-4 h-4"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2.5"
d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"
/>
</svg>
</button>
</div>
</div>
</div>
))
)}
</div>
{items.length > 0 && (
<div className="p-4 lg:p-6 bg-slate-50 border-t border-slate-200 space-y-4 safe-area-bottom">
<div className="flex justify-between items-end">
<div>
<span className="text-slate-400 font-bold uppercase text-[9px] tracking-widest block mb-0.5">
Total do Pedido
</span>
<span className="text-3xl font-black text-[#002147]">
R$ {total.toFixed(2)}
</span>
</div>
</div>
<div className="grid grid-cols-1 gap-2">
<button
onClick={onCheckout}
className="w-full py-3.5 bg-orange-500 text-white font-black uppercase text-xs tracking-[0.2em] rounded-xl shadow-lg shadow-orange-500/20 hover:bg-orange-600 transition-all flex items-center justify-center active:scale-95"
>
Finalizar Venda
<svg
className="w-4 h-4 ml-2"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2.5"
d="M14 5l7 7m0 0l-7 7m7-7H3"
/>
</svg>
</button>
{onNewOrder && (
<button
onClick={handleNewOrderClick}
className="w-full py-3 bg-slate-200 text-slate-700 font-black uppercase text-xs tracking-[0.1em] rounded-xl hover:bg-slate-300 transition-all flex items-center justify-center active:scale-95"
>
<svg
className="w-4 h-4 mr-2"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2.5"
d="M12 4v16m8-8H4"
/>
</svg>
Novo Pedido
</button>
)}
<button
onClick={handleClose}
className="w-full py-3 text-slate-400 font-bold uppercase text-[9px] tracking-widest hover:text-[#002147] transition-colors"
>
Continuar Comprando
</button>
</div>
</div>
)}
</div>
<style>{`
/* Função de easing elegante para animações suaves */
.ease-out-quart {
transition-timing-function: cubic-bezier(0.25, 1, 0.5, 1);
}
.duration-400 {
transition-duration: 400ms;
}
`}</style>
{/* Dialog de Confirmação - Remover Item */}
<ConfirmDialog
isOpen={confirmDialog.isOpen}
onClose={() =>
setConfirmDialog({ isOpen: false, itemId: "", productName: "" })
}
onConfirm={() => {
onRemove(confirmDialog.itemId);
setConfirmDialog({ isOpen: false, itemId: "", productName: "" });
}}
type="delete"
message={
<>
Deseja remover o produto{" "}
<span className="font-black text-[#002147]">
"{confirmDialog.productName}"
</span>{" "}
do carrinho?
<br />
<span className="text-xs text-slate-400 mt-2 block">
Esta ação não pode ser desfeita.
</span>
</>
}
/>
{/* Dialog - Continuar ou Iniciar Novo Pedido */}
<ConfirmDialog
isOpen={showContinueOrNewDialog}
onClose={handleContinueOrder}
onConfirm={handleStartNewOrder}
type="info"
title="Carrinho Existente"
message={
<>
Você possui um carrinho com itens.
<br />
<br />
Deseja iniciar um novo pedido e limpar todos os dados do pedido
atual?
</>
}
confirmText="Iniciar Novo Pedido"
cancelText="Cancelar"
/>
{/* Dialog de Confirmação - Novo Pedido */}
<ConfirmDialog
isOpen={showNewOrderDialog}
onClose={() => setShowNewOrderDialog(false)}
onConfirm={handleConfirmNewOrder}
type="warning"
title="Novo Pedido"
message={
<>
Deseja iniciar um novo pedido?
<br />
<br />
<span className="text-sm font-bold text-slate-700">
Todos os dados do pedido atual serão perdidos:
</span>
<ul className="text-xs text-slate-600 mt-2 space-y-1 list-disc list-inside">
<li>Itens do carrinho</li>
<li>Dados do cliente</li>
<li>Endereço de entrega</li>
<li>Plano de pagamento</li>
<li>Dados financeiros</li>
<li>Informações de entrega</li>
</ul>
<br />
<span className="text-xs text-slate-400 block">
Esta ação não pode ser desfeita.
</span>
</>
}
confirmText="Sim, Iniciar Novo Pedido"
cancelText="Cancelar"
/>
</div>
);
};
export default CartDrawer;