/* Painel Nanda — componentes reutilizáveis. window.App.UI */
(function () {
const I = window.App.Icons;
const CFG = window.NANDA_CONFIG || {};
const BR = CFG.brand || {};
const initials = (name) => (name || '?').split(/\s+/).filter(Boolean).slice(0, 2).map((s) => s[0]).join('').toUpperCase();
const hueFor = (s) => { let h = 0; for (const c of (s || '')) h = (h * 31 + c.charCodeAt(0)) % 360; return h; };
function Avatar({ name, size = 40, ring }) {
const hu = hueFor(name);
return (
{initials(name)}
);
}
function Badge({ children, tone = 'brand', soft = true, className = '' }) {
const map = {
brand: ['#5aa2ff', 'rgba(46,111,214,.16)', 'rgba(46,111,214,.35)'],
ok: ['#57d8a3', 'rgba(38,162,105,.15)', 'rgba(38,162,105,.35)'],
warn: ['#e6bd6a', 'rgba(216,161,58,.15)', 'rgba(216,161,58,.35)'],
bad: ['#e08a8a', 'rgba(193,91,91,.15)', 'rgba(193,91,91,.35)'],
muted: ['#9fb0c3', 'rgba(140,151,168,.12)', 'rgba(140,151,168,.28)'],
};
const [fg, bg, bd] = map[tone] || map.brand;
return (
{children}
);
}
function Delta({ v }) {
if (v == null || v === 0) return –;
const up = v > 0;
return {up ? : }{(up ? '+' : '') + v}%;
}
function Card({ children, className = '', pad = true, style }) {
return (
{children}
);
}
function StatCard({ label, value, delta, icon }) {
const Ic = icon;
return (
{label}
{value}
);
}
function Button({ children, kind = 'ghost', size = 'md', className = '', ...p }) {
const base = 'inline-flex items-center justify-center gap-2 rounded-lg font-medium transition-colors whitespace-nowrap disabled:opacity-50';
const sz = size === 'sm' ? 'h-8 px-3 text-[12px]' : 'h-9 px-4 text-[13px]';
const kinds = {
primary: 'bg-brand hover:bg-brand-hover text-white',
ghost: 'border border-line bg-surface hover:bg-elevate text-fg',
subtle: 'text-muted hover:text-fg hover:bg-elevate',
};
return ;
}
function Search({ value, onChange, placeholder }) {
return (
onChange && onChange(e.target.value)} placeholder={placeholder}
className="h-9 w-full rounded-lg border border-line bg-surface pl-9 pr-3 text-[13px] text-fg placeholder:text-faint outline-none focus:border-brand/60" />
);
}
function Modal({ open, onClose, title, children, width = 420 }) {
if (!open) return null;
return (
e.stopPropagation()} className="w-full rounded-xl border border-line bg-surface2 p-5 shadow-2xl" style={{ maxWidth: width }}>
{title}
{children}
);
}
function PageHead({ title, subtitle, right, ro }) {
return (
{title}
{ro && Somente leitura}
{subtitle &&
{subtitle}
}
{right &&
{right}
}
);
}
const NAV = [
['dashboard', 'Dashboard', 'Dashboard'],
['pipeline', 'Pipeline', 'Pipeline'],
['chat', 'Chat Ao Vivo', 'Chat'],
['training', 'Treinamento', 'Training'],
['contacts', 'Contatos', 'Contacts'],
['scheduling', 'Agendamentos', 'Calendar'],
['team', 'Equipe', 'Team'],
['help', 'Ajuda', 'Help'],
['settings', 'Configurações', 'Settings'],
];
function Sidebar({ route, go, role, user, onLogout, open, onClose, verVendas }) {
const isAdmin = (role || 'admin') === 'admin';
let nav = NAV.filter(([key]) => isAdmin || (key !== 'team' && key !== 'settings'));
// verVendas=false esconde o menu de vendas mantendo só o Chat. A Nanda só
// tem UM fluxo, então isto na prática nunca dispara hoje; mantido como
// cinto de segurança caso a RLS um dia negue verVendas (falha aberta seria pior).
if (verVendas === false) nav = nav.filter(([key]) => key === 'chat');
const uname = (user && user.email) || BR.usuario || 'Equipe Nanda';
const rlabel = role === 'admin' ? 'Administrador' : role === 'atendente' ? 'Atendente' : (BR.email || '');
return (
<>
{/* Fundo escurecido do drawer (só mobile, quando aberto) */}
>
);
}
window.App = window.App || {};
window.App.UI = { Avatar, Badge, Delta, Card, StatCard, Button, Search, Modal, PageHead, Sidebar, initials, hueFor };
})();