);
}
// ─── POPOVER SHELL ───────────────────────────────────────────────────────────
function Popover({ onClose, anchor = 'right-0', width = 380, children }) {
useOvEffect(() => {
const h = (e) => { if (e.key === 'Escape') onClose(); };
window.addEventListener('keydown', h);
return () => window.removeEventListener('keydown', h);
}, [onClose]);
// anchor is rough mapping to the icon position in TopNav.
// The popover anchors top: 88 (below topnav) right: variable
const rightMap = { 'right-0': 14, 'right-1': 60, 'right-2': 106 };
const right = rightMap[anchor] ?? 14;
return (
<>
{children}
>
);
}
// ─── WELCOME TOUR ─────────────────────────────────────────────────────────────
function WelcomeTour({ onFinish, goto }) {
const [step, setStep] = useOvState(0);
const [spot, setSpot] = useOvState(null); // BoundingClientRect do elemento alvo
const CARD_W = 440;
// Cada passo: qual elemento destacar e onde colocar o card em relação a ele
const STEPS = [
{
icon: '🧭',
screen: 'jornadas',
target: '.sidebar',
place: 'right',
title: 'Navegue com um clique',
body: 'A barra lateral dá acesso a todas as telas do sistema. Cada ícone é um atalho direto — Pipeline, Jornadas, Contatos, Oportunidades, Calendário, Relatórios, Casos e Configurações.',
},
{
icon: '📊',
screen: 'painel',
target: '.main',
place: 'center',
title: 'Painel de Controle',
body: 'O dashboard reúne tudo em um só lugar: KPIs de receita e conversão, funil de pipeline ponderado, próximas tarefas do dia e histórico de interações recentes. Ponto de partida do seu dia.',
},
{
icon: '🎯',
screen: 'jornadas',
target: '.main',
place: 'center',
title: 'Jornadas do Cliente',
body: 'O kanban operacional é o coração da Ezza. Cada card é um atendimento percorrendo etapas — da triagem ao encerramento. Clique no avatar de um card para abrir a conversa com o cliente.',
},
{
icon: '⚡',
screen: 'pipeline',
target: '.main',
place: 'center',
title: 'Pipeline com IA SDR',
body: 'Gerencie seus leads comerciais em colunas por etapa. Arraste os cards para avançar no funil. Clique no ícone WhatsApp para abrir a conversa — a IA SDR pode responder automaticamente em seu lugar.',
},
{
icon: '👥',
screen: 'contatos',
target: '.main',
place: 'center',
title: 'Gestão de Contatos',
body: 'Cadastro completo de clientes e prospects: histórico de interações, oportunidades vinculadas, agendamentos, e-mail e ligação com um clique. Filtre por tag ou ordene como preferir.',
},
{
icon: '💼',
screen: 'oportunidades',
target: '.main',
place: 'center',
title: 'Oportunidades',
body: 'Acompanhe todos os negócios em andamento em lista ou kanban. Filtre por etapa, responsável e probabilidade. Use os três pontinhos para editar, atribuir, reagendar ou marcar como ganha.',
},
{
icon: '🎫',
screen: 'casos',
target: '.main',
place: 'center',
title: 'Casos & Atendimentos',
body: 'Central de suporte integrada: abra tickets, acompanhe SLA, atribua responsáveis e mude o status de cada caso. Os atalhos rápidos permitem ações em lote como arquivar e enviar pesquisa de satisfação.',
},
{
icon: '📈',
screen: 'relatorios',
target: '.main',
place: 'center',
title: 'Relatórios e Analytics',
body: 'Visualize receita, ticket médio, ciclo de venda e taxa de conversão por período. Ative "Comparar" para ver a evolução frente ao período anterior. Exporte em PDF, CSV ou Excel com um clique.',
},
{
icon: '⚙️',
screen: 'configuracoes',
target: '.set-aside',
place: 'right',
title: 'Configurações',
body: 'Aqui você personaliza tudo: dados da empresa, WhatsApp Business, Agente IA, status do sistema, integrações, automações, notificações e faturamento. Cada aba é uma área independente.',
},
{
icon: '🤖',
screen: 'configuracoes',
target: '.set-body',
place: 'left',
title: 'Agente IA — SDR Virtual',
body: 'Configure o robô de vendas: defina o nome, objetivo, persona e o script que ele usa nas conversas. Ajuste os horários de atendimento e faça chamadas de teste direto do painel. O agente responde clientes no WhatsApp enquanto você foca no que importa.',
onEnter: () => {
// Clica na aba "Agente IA" dentro de Configurações
const tabs = document.querySelectorAll('.set-tab');
tabs.forEach(t => { if (t.textContent.includes('Agente')) t.click(); });
},
},
{
icon: '🔍',
screen: 'painel',
target: '.topnav',
place: 'below',
title: 'Busca global e atalhos',
body: 'Pressione ⌘K (ou Ctrl+K) a qualquer momento para encontrar contatos, leads e casos instantaneamente. Os ícones no canto superior direito abrem mensagens, notificações e seu perfil.',
},
];
const s = STEPS[step];
const isLast = step === STEPS.length - 1;
// Navega para a tela do passo
useOvEffect(() => {
if (s.screen && goto) goto(s.screen);
}, [step]);
// Mede o elemento alvo após a tela renderizar; executa onEnter se definido
useOvEffect(() => {
const run = () => {
// 1. onEnter opcional (ex: clicar em uma aba)
if (s.onEnter) s.onEnter();
// 2. mede depois do onEnter ter tido tempo de atualizar o DOM
setTimeout(() => {
const el = s.target ? document.querySelector(s.target) : null;
setSpot(el ? el.getBoundingClientRect() : null);
}, 80);
};
const t = setTimeout(run, 150);
return () => clearTimeout(t);
}, [step]);
// Calcula posição do spotlight (com padding)
const PAD = 6;
const spotRect = spot ? {
left: spot.left - PAD,
top: spot.top - PAD,
width: spot.width + PAD * 2,
height: spot.height + PAD * 2,
borderRadius: 18,
} : { left: 0, top: 0, width: 0, height: 0, borderRadius: 0 };
// Calcula posição do card baseado no spotlight real
const vw = window.innerWidth;
const vh = window.innerHeight;
const MARGIN = 24;
let cardLeft, cardTop;
if (!spot) {
// fallback: centro da tela
cardLeft = (vw - CARD_W) / 2;
cardTop = (vh - 300) / 2;
} else if (s.place === 'right') {
// à direita do elemento, centralizado verticalmente
cardLeft = spotRect.left + spotRect.width + MARGIN;
cardTop = Math.max(MARGIN, Math.min(vh - 320 - MARGIN,
spotRect.top + spotRect.height / 2 - 150));
} else if (s.place === 'left') {
// à esquerda do elemento, centralizado verticalmente
cardLeft = Math.max(MARGIN, spotRect.left - CARD_W - MARGIN);
cardTop = Math.max(MARGIN, Math.min(vh - 320 - MARGIN,
spotRect.top + spotRect.height / 2 - 150));
} else if (s.place === 'below') {
// abaixo do elemento, centralizado horizontalmente
cardLeft = Math.max(MARGIN, Math.min(vw - CARD_W - MARGIN,
spotRect.left + spotRect.width / 2 - CARD_W / 2));
cardTop = spotRect.top + spotRect.height + MARGIN;
} else {
// 'center': centro do spotlight
cardLeft = Math.max(MARGIN, Math.min(vw - CARD_W - MARGIN,
spotRect.left + spotRect.width / 2 - CARD_W / 2));
cardTop = Math.max(MARGIN, Math.min(vh - 320 - MARGIN,
spotRect.top + spotRect.height / 2 - 150));
}
const next = () => isLast ? onFinish() : setStep(step + 1);
const back = () => { if (step > 0) setStep(step - 1); };
// Portal → document.body. Spotlight e card com position:fixed + coordenadas reais em px.
return ReactDOM.createPortal(
<>
{/* Spotlight — usa getBoundingClientRect do elemento alvo */}
{/* Card — coordenadas calculadas em px baseadas no rect do elemento alvo */}