/* JRN — Camada de comunicação com o backend PHP Carregado antes dos demais JSX. Expõe funções de API no window. */ // ============================================================= // CONFIGURAÇÃO — URL base do backend PHP // ============================================================= const API_BASE = 'https://jrn.serdata.com.br/backend'; // ============================================================= async function apiFetch(path, opts = {}) { const res = await fetch(API_BASE + path, { credentials: 'include', headers: { 'Content-Type': 'application/json', ...(opts.headers || {}) }, ...opts, }); const data = await res.json().catch(() => ({})); if (!res.ok) { const err = new Error(data.error || `Erro ${res.status}`); err.status = res.status; throw err; } return data; } /* Converte empresa da API para o formato do frontend */ function _empToFront(e) { const staticC = (window.COMPANIES || []).find(c => c.id === e.slug) || {}; return { ...staticC, id: e.slug, _dbId: Number(e.id), nome: e.nome, tipo: e.tipo || staticC.tipo || '', setor: e.setor || staticC.setor || '', cor: e.cor_hex || staticC.cor || '#15233f', cor_hex: e.cor_hex || staticC.cor || '#15233f', logo: e.logo_path || `assets/logos/${e.slug}.png`, logo_path: e.logo_path || `assets/logos/${e.slug}.png`, ativo: e.ativo !== undefined ? !!Number(e.ativo) : true, slug: e.slug, // corSoft vem do staticC (não está no banco) }; } /* Converte dashboard da API para o formato do frontend */ function _dashToFront(d) { return { id: 'dash-' + d.id, _dbId: Number(d.id), companyId: d.empresa_slug || '', _companyDbId: Number(d.empresa_id), nome: d.nome, categoria: d.categoria || 'Financeiro', embedUrl: d.embed_url || '', status: d.status || 'ativo', atualizacao: (d.atualizado_em || '').substring(0, 10), }; } /* Converte usuário da API para o formato do frontend */ function _userToFront(u) { const initials = (u.nome || '') .split(' ').filter(Boolean).map(p => p[0]).slice(0, 2).join('').toUpperCase(); return { id: 'u' + u.id, _dbId: Number(u.id), nome: u.nome, email: u.email, perfil: u.perfil, cargo: u.cargo || '', status: u.status, ultimoAcesso: u.ultimo_acesso || '—', avatar: initials, acessosEmpresas: [], acessosDashboards: [], funcoes: [], }; } /* ---- Autenticação ---- */ async function apiLogin(email, senha) { return apiFetch('/login.php', { method: 'POST', body: JSON.stringify({ email, senha }), }); } async function apiLogout() { return apiFetch('/logout.php', { method: 'POST' }); } async function apiForgotPassword(email) { return apiFetch('/forgot_password.php', { method: 'POST', body: JSON.stringify({ email }), }); } /* ---- Sessão atual ---- Retorna { user, empresas (formato frontend), funcoes } */ async function apiMe() { const data = await apiFetch('/api/me.php'); const u = data.user; const funcoes = data.funcoes || []; const empresas = (data.empresas || []).map(e => { const company = _empToFront(e); company.dashboards = (e.dashboards || []).map(d => ({ ..._dashToFront(d), companyId: company.id, _companyDbId: company._dbId, })); return company; }); const allDashIds = empresas.flatMap(c => (c.dashboards || []).map(d => d.id)); const user = { id: 'u' + u.id, _dbId: Number(u.id), nome: u.nome, email: u.email, perfil: u.perfil, cargo: u.cargo || '', status: u.status || 'ativo', ultimoAcesso: u.ultimo_acesso || '—', avatar: (u.nome || '').split(' ').filter(Boolean).map(p => p[0]).slice(0, 2).join('').toUpperCase(), acessosEmpresas: empresas.map(c => c.id), acessosDashboards: allDashIds, funcoes, primeiroAcesso: !!u.primeiro_acesso, }; return { user, empresas, funcoes }; } /* ---- Usuários ---- */ async function apiUsuariosGet() { const data = await apiFetch('/api/usuarios.php'); return (data.usuarios || []).map(_userToFront); } async function apiUsuariosCreate(f) { return apiFetch('/api/usuarios.php', { method: 'POST', body: JSON.stringify({ nome: f.nome, email: f.email, cargo: f.cargo, perfil: f.perfil, status: f.status, }), }); } async function apiUsuariosUpdate(f) { return apiFetch('/api/usuarios.php', { method: 'PUT', body: JSON.stringify({ id: f._dbId, nome: f.nome, cargo: f.cargo, perfil: f.perfil, status: f.status, }), }); } async function apiUsuariosDelete(user) { return apiFetch('/api/usuarios.php', { method: 'DELETE', body: JSON.stringify({ id: user._dbId }), }); } /* ---- Permissões ---- apiPermissoesGet devolve slugs/dash-N já convertidos. apiPermissoesSet recebe slugs/dash-N e converte para IDs numéricos. */ async function apiPermissoesGet(userId, allCompanies) { const dbId = Number(String(userId).replace(/^u/, '')); const data = await apiFetch('/api/permissoes.php?usuario_id=' + dbId); const empresasSlug = (data.empresas || []).map(eid => { const c = (allCompanies || []).find(x => x._dbId === Number(eid)); return c ? c.id : null; }).filter(Boolean); const dashFront = (data.dashboards || []).map(did => 'dash-' + did); return { empresas: empresasSlug, dashboards: dashFront, funcoes: data.funcoes || [] }; } async function apiPermissoesSet(userId, empresasSlugs, dashFrontIds, funcoes, allCompanies, allDashboards) { const dbUserId = Number(String(userId).replace(/^u/, '')); const empresasDb = (empresasSlugs || []).map(slug => { const c = (allCompanies || []).find(x => x.id === slug); return c ? c._dbId : null; }).filter(Boolean); const dashboardsDb = (dashFrontIds || []).map(did => { const d = (allDashboards || []).find(x => x.id === did); return d ? d._dbId : Number(did.replace('dash-', '')) || null; }).filter(Boolean); return apiFetch('/api/permissoes.php', { method: 'POST', body: JSON.stringify({ usuario_id: dbUserId, empresas: empresasDb, dashboards: dashboardsDb, funcoes: funcoes || [], }), }); } /* ---- Dashboards ---- */ async function apiDashboardsGet() { const data = await apiFetch('/api/dashboards.php'); return (data.dashboards || []).map(_dashToFront); } async function apiDashboardsCreate(f, allCompanies) { const company = (allCompanies || []).find(c => c.id === f.companyId); return apiFetch('/api/dashboards.php', { method: 'POST', body: JSON.stringify({ empresa_id: company ? company._dbId : null, nome: f.nome, categoria: f.categoria, embed_url: f.embedUrl, status: f.status, }), }); } async function apiDashboardsUpdate(f, allCompanies) { const company = (allCompanies || []).find(c => c.id === f.companyId); return apiFetch('/api/dashboards.php', { method: 'PUT', body: JSON.stringify({ id: f._dbId, empresa_id: company ? company._dbId : (f._companyDbId || null), nome: f.nome, categoria: f.categoria, embed_url: f.embedUrl, status: f.status, }), }); } async function apiDashboardsDelete(dash) { return apiFetch('/api/dashboards.php', { method: 'DELETE', body: JSON.stringify({ id: dash._dbId }), }); } /* ---- Empresas (admin) ---- */ async function apiEmpresasGet() { const data = await apiFetch('/api/empresas.php'); return (data.empresas || []).map(e => ({ ..._empToFront(e), qtdDashboards: Number(e.qtd_dashboards) || 0, qtdUsuarios: Number(e.qtd_usuarios) || 0, })); } async function apiEmpresasCreate(f) { return apiFetch('/api/empresas.php', { method: 'POST', body: JSON.stringify({ slug: f.slug, nome: f.nome, tipo: f.tipo, setor: f.setor, cor_hex: f.cor_hex, logo_path: f.logo_path || `assets/logos/${f.slug}.png`, }), }); } async function apiEmpresasUpdate(f) { return apiFetch('/api/empresas.php', { method: 'PUT', body: JSON.stringify({ id: f._dbId, nome: f.nome, tipo: f.tipo, setor: f.setor, cor_hex: f.cor_hex, logo_path: f.logo_path, ativo: f.ativo, }), }); } async function apiEmpresasDelete(empresa) { return apiFetch('/api/empresas.php', { method: 'DELETE', body: JSON.stringify({ id: empresa._dbId }), }); } async function apiEmpresasUploadLogo(empresaDbId, file) { const form = new FormData(); form.append('empresa_id', String(empresaDbId)); form.append('logo', file); // Sem Content-Type — o browser define o boundary do multipart automaticamente const res = await fetch(API_BASE + '/api/upload_logo.php', { credentials: 'include', method: 'POST', body: form, }); const data = await res.json().catch(() => ({})); if (!res.ok) throw new Error(data.error || `Erro ${res.status}`); return data; } /* ---- Auditoria ---- */ async function apiAuditoriaGet(q = '', limit = 50, offset = 0) { const params = new URLSearchParams({ q, limit, offset }); const data = await apiFetch('/api/auditoria.php?' + params.toString()); return (data.logs || []).map(l => ({ id: l.id, usuario: l.usuario_nome || '—', acao: l.acao, alvo: l.alvo || '—', ip: l.ip || '', data: l.criado_em, alerta: !!(Number(l.alerta)), })); } Object.assign(window, { apiFetch, apiLogin, apiLogout, apiForgotPassword, apiMe, apiUsuariosGet, apiUsuariosCreate, apiUsuariosUpdate, apiUsuariosDelete, apiPermissoesGet, apiPermissoesSet, apiDashboardsGet, apiDashboardsCreate, apiDashboardsUpdate, apiDashboardsDelete, apiEmpresasGet, apiEmpresasCreate, apiEmpresasUpdate, apiEmpresasDelete, apiEmpresasUploadLogo, apiAuditoriaGet, });