/* JRN — Admin: Dashboards, Empresas e Log de auditoria */ /* ---------- Modal: cadastro / edição de dashboard ---------- */ function DashboardFormModal({ dash, onClose, onSave, empresas }) { const isEdit = !!dash; const defaultCompany = (empresas && empresas[0]) ? empresas[0].id : 'jrn'; const [f, setF] = useState(dash ? { nome: dash.nome, companyId: dash.companyId, categoria: dash.categoria, embedUrl: dash.embedUrl, status: dash.status, id: dash.id, _dbId: dash._dbId, _companyDbId: dash._companyDbId } : { nome: '', companyId: defaultCompany, categoria: 'Financeiro', embedUrl: '', status: 'ativo' }); const [saving, setSaving] = useState(false); const [err, setErr] = useState(''); const set = (k, v) => setF(s => ({ ...s, [k]: v })); const valid = f.nome.trim() && f.companyId; const company = (empresas || []).find(c => c.id === f.companyId) || {}; const handleSave = async () => { setSaving(true); setErr(''); try { await onSave(f); } catch (e) { setErr(e.message || 'Erro ao salvar dashboard.'); setSaving(false); } }; return ( Cancelar {saving ? : null} {isEdit ? 'Salvar' : 'Cadastrar dashboard'} }> {err &&
{err}
}
set('nome', e.target.value)} placeholder="Ex.: Faturamento Mensal" /> set('embedUrl', e.target.value)} placeholder="https://app.powerbi.com/reportEmbed?reportId=..." /> {company.cor ? (
{company.nome}
) : (
Selecione uma empresa
)}
); } /* ---------- Página: dashboards ---------- */ function DashboardsAdmin({ empresas, toast }) { const [dashboards, setDashboards] = useState([]); const [loading, setLoading] = useState(true); const [q, setQ] = useState(''); const [comp, setComp] = useState('todas'); const [modal, setModal] = useState(null); const loadDashboards = () => { setLoading(true); apiDashboardsGet().then(setDashboards) .catch(() => toast('Erro ao carregar dashboards.', 'danger')) .finally(() => setLoading(false)); }; useEffect(loadDashboards, []); const filtered = dashboards.filter(d => (comp === 'todas' || d.companyId === comp) && d.nome.toLowerCase().includes(q.toLowerCase()) ); const save = async (f) => { if (f._dbId) { await apiDashboardsUpdate(f, empresas); toast('Dashboard atualizado.'); } else { await apiDashboardsCreate(f, empresas); toast('Dashboard cadastrado.'); } setModal(null); loadDashboards(); }; const remove = async (d) => { if (!confirm(`Remover "${d.nome}"?`)) return; try { await apiDashboardsDelete(d); toast('Dashboard removido.', 'danger'); loadDashboards(); } catch (e) { toast(e.message || 'Erro ao remover.', 'danger'); } }; return (
setModal({})}>Novo dashboard} />
setQ(e.target.value)} />
{loading ? (
) : ( {filtered.map(d => { const c = (empresas || []).find(x => x.id === d.companyId) || COMPANIES.find(x => x.id === d.companyId) || { cor: '#aaa', corSoft: '#f5f5f5', nome: d.companyId, logo: '' }; return ( ); })}
DashboardEmpresaCategoriaAtualizaçãoStatus
{d.nome} {d.embedUrl && {d.embedUrl.slice(0, 44)}…}
{c.nome}
{d.categoria} {d.atualizacao ? d.atualizacao.split('-').reverse().join('/') : '—'}
{filtered.length === 0 && }
)}
{modal !== null && ( setModal(null)} onSave={save} empresas={empresas} /> )}
); } /* ---------- Modal: cadastro / edição de empresa ---------- */ function EmpresaFormModal({ empresa, onClose, onSave }) { const isEdit = !!empresa; const [f, setF] = useState(empresa ? { nome: empresa.nome, slug: empresa.slug, tipo: empresa.tipo, setor: empresa.setor, cor_hex: empresa.cor_hex, logo_path: empresa.logo_path, ativo: empresa.ativo, _dbId: empresa._dbId } : { nome: '', slug: '', tipo: '', setor: '', cor_hex: '#15233f', logo_path: '', ativo: true }); const [saving, setSaving] = useState(false); const [savingStep, setSavingStep] = useState(''); const [err, setErr] = useState(''); const [logoFile, setLogoFile] = useState(null); const [logoPreview, setLogoPreview] = useState(null); const fileInputRef = React.useRef(null); const set = (k, v) => setF(s => ({ ...s, [k]: v })); const valid = f.nome.trim() && f.slug.trim(); // Auto-gera slug a partir do nome (só na criação) const handleNome = (v) => { set('nome', v); if (!isEdit) { const slug = v.toLowerCase().normalize('NFD').replace(/[̀-ͯ]/g, '').replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, ''); set('slug', slug); } }; const handleFileChange = (e) => { const file = e.target.files && e.target.files[0]; if (!file) return; if (logoPreview) URL.revokeObjectURL(logoPreview); setLogoFile(file); setLogoPreview(URL.createObjectURL(file)); }; const clearFile = () => { if (logoPreview) URL.revokeObjectURL(logoPreview); setLogoFile(null); setLogoPreview(null); if (fileInputRef.current) fileInputRef.current.value = ''; }; const handleSave = async () => { setSaving(true); setErr(''); try { if (isEdit) { setSavingStep('Salvando dados…'); await apiEmpresasUpdate(f); if (logoFile) { setSavingStep('Enviando logo…'); await apiEmpresasUploadLogo(f._dbId, logoFile); } } else { setSavingStep('Cadastrando empresa…'); const result = await apiEmpresasCreate(f); if (logoFile && result.id) { setSavingStep('Enviando logo…'); await apiEmpresasUploadLogo(result.id, logoFile); } } onSave(); } catch (e) { setErr(e.message || 'Erro ao salvar.'); setSaving(false); setSavingStep(''); } }; // Exibe o logo atual ou a prévia do arquivo selecionado const currentLogo = logoPreview || (f.logo_path ? f.logo_path : null); return ( Cancelar {saving ? {savingStep} : (isEdit ? 'Salvar alterações' : 'Cadastrar empresa')} }> {err &&
{err}
}
handleNome(e.target.value)} placeholder="Ex.: Horizon Media" /> set('slug', e.target.value.toLowerCase().replace(/[^a-z0-9-]/g, ''))} placeholder="ex.: horizon" disabled={isEdit} /> set('tipo', e.target.value)} placeholder="Ex.: Mídia & Publicidade" /> set('setor', e.target.value)} placeholder="Ex.: Comunicação" />
set('cor_hex', e.target.value)} style={{ width: 42, height: 38, padding: 3, border: '1px solid var(--line)', borderRadius: 8, cursor: 'pointer', background: '#fff' }} /> set('cor_hex', e.target.value)} placeholder="#15233f" style={{ flex: 1 }} />
!saving && fileInputRef.current && fileInputRef.current.click()} style={{ display: 'flex', alignItems: 'center', gap: 12, padding: '9px 13px', border: '1px dashed var(--line)', borderRadius: 9, cursor: saving ? 'default' : 'pointer', background: 'var(--surface-2)', transition: 'border-color .15s', }} onMouseEnter={e => { if (!saving) e.currentTarget.style.borderColor = 'var(--navy-400)'; }} onMouseLeave={e => { e.currentTarget.style.borderColor = 'var(--line)'; }}> {currentLogo ? ( logo ) : (
)}
{logoFile ? logoFile.name : 'Clique para anexar logo'}
{logoFile && (
{(logoFile.size / 1024).toFixed(0)} KB
)}
{logoFile && ( )}
{isEdit && ( )}
); } /* ---------- Página: empresas ---------- */ function CompaniesAdmin() { const [empresas, setEmpresas] = useState([]); const [loading, setLoading] = useState(true); const [modal, setModal] = useState(null); // null | {} (nova) | empresa (editar) const load = () => { setLoading(true); apiEmpresasGet().then(setEmpresas).finally(() => setLoading(false)); }; useEffect(load, []); const save = () => { setModal(null); load(); }; const toggleAtivo = async (c) => { await apiEmpresasUpdate({ ...c, ativo: !c.ativo }); load(); }; const remove = async (c) => { if (!confirm(`Remover "${c.nome}"? Esta ação removerá também todos os dashboards vinculados.`)) return; try { await apiEmpresasDelete(c); load(); } catch (e) { alert(e.message || 'Erro ao remover empresa.'); } }; if (loading) { return (
); } return (
setModal({})}>Nova empresa} />
{empresas.map(c => (
{!c.ativo && Inativa} {c.setor && {c.setor}}

{c.nome}

{c.tipo}
{c.qtdDashboards}
painéis
{c.qtdUsuarios}
com acesso
))}
{empresas.length === 0 && (
)} {modal !== null && ( setModal(null)} onSave={save} /> )}
); } /* ---------- Página: log de auditoria ---------- */ function AuditAdmin() { const [logs, setLogs] = useState([]); const [loading, setLoading] = useState(true); const [q, setQ] = useState(''); useEffect(() => { let timer; const fetch = () => { setLoading(true); apiAuditoriaGet(q).then(setLogs) .finally(() => setLoading(false)); }; timer = setTimeout(fetch, q ? 350 : 0); return () => clearTimeout(timer); }, [q]); return (
Exportar CSV} />
setQ(e.target.value)} />
{loading ? (
) : ( {logs.map(l => ( ))}
Data / horaUsuárioAçãoAlvoIP
{l.data} {l.usuario} {l.alerta && } {l.acao} {l.alvo} {l.ip}
{logs.length === 0 && }
)}
); } Object.assign(window, { DashboardsAdmin, CompaniesAdmin, AuditAdmin, DashboardFormModal });