/* 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} />
{loading ? (
) : (
| Dashboard | Empresa | Categoria | Atualização | Status | |
{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 (
|
{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 ? (

) : (
)}
{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} />
{loading ? (
) : (
| Data / hora | Usuário | Ação | Alvo | IP |
{logs.map(l => (
| {l.data} |
{l.usuario} |
{l.alerta && }
{l.acao}
|
{l.alvo} |
{l.ip} |
))}
{logs.length === 0 && }
)}
);
}
Object.assign(window, { DashboardsAdmin, CompaniesAdmin, AuditAdmin, DashboardFormModal });