Vendaweb-portal/components/ConfirmDialog.tsx

374 lines
12 KiB
TypeScript
Raw Normal View History

2026-01-08 12:09:16 +00:00
import React, { useState, useEffect } from "react";
export type DialogType =
| "info"
| "warning"
| "error"
| "success"
| "delete"
| "confirm";
interface DialogConfig {
title: string;
icon: React.ReactNode;
confirmButtonColor: "red" | "orange" | "blue" | "green";
headerBgColor: string;
iconBgColor: string;
iconColor: string;
subtitle?: string;
}
interface ConfirmDialogProps {
isOpen: boolean;
onClose: () => void;
onConfirm: () => void;
type?: DialogType;
title?: string;
message: string | React.ReactNode;
confirmText?: string;
cancelText?: string;
icon?: React.ReactNode;
showWarning?: boolean;
}
/**
* ConfirmDialog Component
* Componente reutilizável para exibir diálogos de confirmação customizados
* Mantém o tema do projeto com cores e estilos consistentes
* Suporta diferentes tipos: info, warning, error, success, delete, confirm
*
* @param isOpen - Controla se o dialog está aberto
* @param onClose - Callback chamado ao fechar/cancelar
* @param onConfirm - Callback chamado ao confirmar
* @param type - Tipo do diálogo (info, warning, error, success, delete, confirm)
* @param title - Título do dialog (opcional, será definido pelo tipo se não fornecido)
* @param message - Mensagem principal do dialog
* @param confirmText - Texto do botão de confirmação (opcional, será definido pelo tipo se não fornecido)
* @param cancelText - Texto do botão de cancelamento (padrão: "Cancelar")
* @param icon - Ícone customizado (opcional, será definido pelo tipo se não fornecido)
* @param showWarning - Se deve mostrar subtítulo de atenção (padrão: true para warning/error/delete)
*/
const ConfirmDialog: React.FC<ConfirmDialogProps> = ({
isOpen,
onClose,
onConfirm,
type = "confirm",
title,
message,
confirmText,
cancelText = "Cancelar",
icon,
showWarning,
}) => {
const [isAnimating, setIsAnimating] = useState(false);
const [shouldRender, setShouldRender] = useState(false);
// Configurações por tipo de diálogo
const getDialogConfig = (dialogType: DialogType): DialogConfig => {
const configs: Record<DialogType, DialogConfig> = {
info: {
title: "Informação",
icon: (
<svg
className="w-6 h-6 text-blue-400"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2.5"
d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"
/>
</svg>
),
confirmButtonColor: "blue",
headerBgColor: "bg-[#002147]",
iconBgColor: "bg-blue-500/20",
iconColor: "text-blue-400",
subtitle: "Informação",
},
warning: {
title: "Atenção",
icon: (
<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 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z"
/>
</svg>
),
confirmButtonColor: "orange",
headerBgColor: "bg-[#002147]",
iconBgColor: "bg-orange-500/20",
iconColor: "text-orange-400",
subtitle: "Atenção",
},
error: {
title: "Erro",
icon: (
<svg
className="w-6 h-6 text-red-400"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2.5"
d="M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"
/>
</svg>
),
confirmButtonColor: "red",
headerBgColor: "bg-[#002147]",
iconBgColor: "bg-red-500/20",
iconColor: "text-red-400",
subtitle: "Erro",
},
success: {
title: "Sucesso",
icon: (
<svg
className="w-6 h-6 text-green-400"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2.5"
d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"
/>
</svg>
),
confirmButtonColor: "green",
headerBgColor: "bg-[#002147]",
iconBgColor: "bg-green-500/20",
iconColor: "text-green-400",
subtitle: "Sucesso",
},
delete: {
title: "Confirmar Exclusão",
icon: (
<svg
className="w-6 h-6 text-red-400"
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>
),
confirmButtonColor: "red",
headerBgColor: "bg-[#002147]",
iconBgColor: "bg-red-500/20",
iconColor: "text-red-400",
subtitle: "Atenção",
},
confirm: {
title: "Confirmar Ação",
icon: (
<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="M8.228 9c.549-1.165 2.03-2 3.772-2 2.21 0 4 1.343 4 3 0 1.4-1.278 2.575-3.006 2.907-.542.104-.994.54-.994 1.093m0 3h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"
/>
</svg>
),
confirmButtonColor: "orange",
headerBgColor: "bg-[#002147]",
iconBgColor: "bg-orange-500/20",
iconColor: "text-orange-400",
subtitle: "Confirmação",
},
};
return configs[dialogType];
};
// Obter configuração do tipo
const config = getDialogConfig(type as DialogType);
const finalTitle = title || config.title;
const finalIcon = icon || config.icon;
const finalConfirmText =
confirmText ||
(type === "delete"
? "Excluir"
: type === "success" || type === "error" || type === "info"
? "OK"
: "Confirmar");
const shouldShowWarning =
showWarning !== undefined
? showWarning
: ["warning", "error", "delete"].includes(type);
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), 300);
return () => clearTimeout(timer);
}
}, [isOpen]);
// Não renderizar se não deve estar visível
if (!shouldRender) return null;
const handleConfirm = () => {
setIsAnimating(false);
setTimeout(() => {
onConfirm();
onClose();
}, 300);
};
const handleCancel = () => {
setIsAnimating(false);
setTimeout(() => {
onClose();
}, 300);
};
// Cores do botão de confirmação
const confirmButtonClasses = {
red: "bg-red-500 hover:bg-red-600 shadow-red-500/20",
orange: "bg-orange-500 hover:bg-orange-600 shadow-orange-500/20",
blue: "bg-blue-500 hover:bg-blue-600 shadow-blue-500/20",
green: "bg-green-500 hover:bg-green-600 shadow-green-500/20",
};
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 - Altura consistente em todos os dispositivos */}
<div
className={`relative bg-white rounded-3xl shadow-2xl max-w-md w-full mx-4 h-auto max-h-[90vh] flex flex-col transform transition-all duration-300 ${
isAnimating ? "scale-100 opacity-100" : "scale-95 opacity-0"
}`}
>
{/* Header */}
<div
className={`p-4 lg:p-6 ${config.headerBgColor} text-white rounded-t-3xl relative overflow-hidden`}
>
<div className="relative z-10">
<div className="flex items-center gap-3 mb-2">
{finalIcon && (
<div
className={`w-12 h-12 ${config.iconBgColor} rounded-2xl flex items-center justify-center`}
>
{finalIcon}
</div>
)}
<div className="flex-1">
<h3 className="text-xl font-black">{finalTitle}</h3>
{shouldShowWarning && config.subtitle && (
<p
className={`text-xs ${config.iconColor} font-bold uppercase tracking-wider mt-0.5`}
>
{config.subtitle}
</p>
)}
</div>
</div>
</div>
<div
className={`absolute right-[-10%] top-[-10%] w-32 h-32 ${
type === "info"
? "bg-blue-400/10"
: type === "warning"
? "bg-orange-400/10"
: type === "error"
? "bg-red-400/10"
: type === "success"
? "bg-green-400/10"
: type === "delete"
? "bg-red-400/10"
: "bg-orange-400/10"
} rounded-full blur-2xl`}
></div>
</div>
{/* Content */}
<div className="p-4 lg:p-6 overflow-y-auto custom-scrollbar flex-1">
{typeof message === "string" ? (
<p className="text-slate-600 text-sm leading-relaxed whitespace-pre-line">
{message}
</p>
) : (
<div className="text-slate-600 text-sm leading-relaxed">
{message}
</div>
)}
</div>
{/* Actions */}
<div className="p-4 lg:p-6 pt-0 flex gap-3">
{/* Mostrar botão de cancelar se:
- For tipo info (permite dois botões) OU
- Não for tipo success/error E finalConfirmText não é "OK"
*/}
{type === "info" ||
(type !== "success" &&
type !== "error" &&
finalConfirmText !== "OK") ? (
<button
onClick={handleCancel}
className="flex-1 py-3 px-4 bg-white border-2 border-slate-300 text-slate-700 font-bold uppercase text-xs tracking-wider rounded-xl hover:bg-slate-50 transition-all"
>
{cancelText}
</button>
) : null}
<button
onClick={handleConfirm}
className={`${
(type === "success" || type === "error") &&
finalConfirmText === "OK"
? "w-full"
: "flex-1"
} py-3 px-4 bg-blue-600 text-white font-bold uppercase text-xs tracking-wider rounded-xl hover:bg-blue-700 transition-all shadow-lg active:scale-95`}
>
{finalConfirmText}
</button>
</div>
</div>
</div>
);
};
export default ConfirmDialog;