const { Card, DataTable, Button, Icon, IconButton, SearchField, Select, Input, Badge, Tabs, Avatar, EmptyState, StatCard, ImportExportBar } = window.ACRFlowDesignSystem_ba1e9c; const SEVERIDADE = { info: { tone: "neutral", label: "Informativo" }, aviso: { tone: "warning", label: "Atenção" }, critico: { tone: "danger", label: "Crítico" } }; const ROTULO_ACAO = { "login.local": "Entrada (conta local)", "login.google": "Entrada (Google)", "login.falhou": "Tentativa de entrada recusada", logout: "Saída do sistema", "sessao.encerrada_pelo_supervisor": "Sessão encerrada pelo supervisor", "login.bloqueado": "Conta bloqueada por tentativas", "acesso.ip_bloqueado": "Estação bloqueada por tentativas", "demanda.exclusao_negada": "Tentativa de exclusão negada", "demanda.cancelamento_negada": "Tentativa de cancelamento negada", "demanda.reabertura_negada": "Tentativa de reabertura negada", "acesso.ip_liberado": "Estação liberada pelo supervisor", "demanda.prazo_corrigido": "Prazo corrigido pelo sistema", "demanda.gerada_por_recorrencia": "Demanda gerada por recorrência", "demanda.criada": "Demanda aberta", "demanda.status_alterado": "Status alterado", "demanda.prioridade_alterada": "Prioridade alterada", "demanda.prazo_alterado": "Prazo alterado", "demanda.responsaveis_alterados": "Responsáveis alterados", "demanda.encerramento_solicitado": "Encerramento solicitado", "demanda.encerrada": "Encerramento aprovado", "demanda.devolvida": "Demanda devolvida", "demanda.cancelada": "Demanda cancelada", "demanda.reaberta": "Demanda reaberta", "demanda.excluida": "Demanda excluída", "usuario.criado": "Colaborador cadastrado", "usuario.perfil_alterado": "Perfil alterado", "usuario.senha_redefinida": "Senha redefinida", "usuario.desativado": "Colaborador desativado", "usuario.reativado": "Colaborador reativado", "cadastro.criado": "Cadastro criado", "cadastro.editado": "Cadastro alterado", "cadastro.removido": "Cadastro removido", }; const ICONE_ACAO = (a) => { if (a.startsWith("login") || a === "logout" || a.startsWith("sessao")) return "key-round"; if (a.includes("excluida") || a.includes("removido") || a.includes("desativado")) return "trash-2"; if (a.includes("cancelada") || a.includes("devolvida")) return "undo-2"; if (a.includes("encerrada")) return "shield-check"; if (a.startsWith("usuario")) return "user-cog"; if (a.startsWith("cadastro")) return "database"; return "activity"; }; const dataHora = (iso) => (iso ? new Date(iso).toLocaleString("pt-BR", { dateStyle: "short", timeStyle: "short" }) : "—"); const desde = (iso) => { if (!iso) return "nunca acessou"; const min = Math.round((Date.now() - new Date(iso)) / 60000); if (min < 1) return "agora mesmo"; if (min < 60) return "há " + min + " min"; const h = Math.round(min / 60); if (h < 24) return "há " + h + (h === 1 ? " hora" : " horas"); const dias = Math.round(h / 24); return "há " + dias + (dias === 1 ? " dia" : " dias"); }; function AuditScreen({ user, onToast }) { const supervisor = user && user.profile === "supervisor"; const [aba, setAba] = React.useState("eventos"); const [resumo, setResumo] = React.useState(null); const [sessoes, setSessoes] = React.useState([]); const [acessos, setAcessos] = React.useState([]); const [eventos, setEventos] = React.useState([]); const [prod, setProd] = React.useState(null); const [bloqueios, setBloqueios] = React.useState([]); const [janela, setJanela] = React.useState(30); const [carregando, setCarregando] = React.useState(true); const hoje = new Date().toISOString().slice(0, 10); const trintaDias = new Date(Date.now() - 29 * 86400000).toISOString().slice(0, 10); const [de, setDe] = React.useState(trintaDias); const [ate, setAte] = React.useState(hoje); const [acao, setAcao] = React.useState("Todas"); const [sev, setSev] = React.useState("Todas"); const [q, setQ] = React.useState(""); const online = window.ACRStore && window.ACRStore.online(); const carregar = React.useCallback(async () => { if (!online) { setCarregando(false); return; } setCarregando(true); const API = window.ACRApi.audit; const filtros = { from: de, to: ate, limit: 500 }; if (acao !== "Todas") filtros.action = Object.keys(ROTULO_ACAO).find((k) => ROTULO_ACAO[k] === acao) || acao; if (sev !== "Todas") filtros.severity = Object.keys(SEVERIDADE).find((k) => SEVERIDADE[k].label === sev); if (q.trim()) filtros.q = q.trim(); try { const [r, s, a, e, b] = await Promise.all([API.summary(), API.sessions(), API.access(), API.events(filtros), API.blocks().catch(() => [])]); setResumo(r); setSessoes(s); setAcessos(a); setEventos(e); setBloqueios(b || []); } catch (err) { onToast && onToast({ tone: "danger", title: "Não foi possível carregar a auditoria", message: err.message }); } finally { setCarregando(false); } }, [de, ate, acao, sev, q, online]); React.useEffect(() => { carregar(); }, [carregar]); /** Produtividade carrega sozinha: uma falha aqui nao pode esvaziar as outras abas. */ const [erroProd, setErroProd] = React.useState(null); React.useEffect(() => { if (!online) return; let vivo = true; setErroProd(null); window.ACRApi.audit.productivity(janela) .then((p) => { if (vivo) setProd(p); }) .catch((err) => { if (vivo) { setProd({ dias: janela, linhas: [] }); setErroProd(err.message || "falha ao consultar"); } }); return () => { vivo = false; }; }, [janela, online]); if (!supervisor) { return } title="Acesso restrito" description="O módulo de auditoria é exclusivo do perfil Supervisor." />; } const ativos = sessoes.filter((s) => s.ativa); const acoesDisponiveis = ["Todas"].concat([...new Set((resumo ? resumo.porAcao : []).map((a) => ROTULO_ACAO[a.action] || a.action))]); const vazio = (t, d) => } title={t} description={d} />; return (

Auditoria

Sessões, acessos e o registro de tudo que acontece no sistema — visível apenas ao supervisor.
dataHora(r.created_at) }, { key: "user_name", label: "Usuário" }, { key: "action", label: "Evento", valor: (r) => ROTULO_ACAO[r.action] || r.action }, { key: "severity", label: "Gravidade", valor: (r) => (SEVERIDADE[r.severity] || {}).label || r.severity }, { key: "entity", label: "Tipo" }, { key: "entity_id", label: "Registro" }, { key: "description", label: "Descrição" }, { key: "ip", label: "Origem (IP)" }, ]} linhas={eventos} />
{!online ? } title="Sem conexão com o servidor" description="A auditoria só existe no banco de dados — reconecte para consultar." /> : ( <>
} hint="Sessão vista nos últimos 30 min" /> } /> } hint="Exclusões e redefinições de senha" /> } />
b.ativo).length ? " (" + bloqueios.filter((b) => b.ativo).length + ")" : "") }, ]} /> {aba === "eventos" ? ( <>
setDe(e.target.value)} /> setAte(e.target.value)} /> setSev(e.target.value)} options={["Todas", "Informativo", "Atenção", "Crítico"]} />
setQ(e.target.value)} onClear={() => setQ("")} />
{eventos.length === 0 ? vazio("Nenhum evento encontrado", "Amplie o período ou limpe os filtros.") : ( ( {dataHora(r.created_at)} {desde(r.created_at)} ) }, { key: "action", label: "Evento", width: 230, render: (r) => ( {ROTULO_ACAO[r.action] || r.action} ) }, { key: "user_name", label: "Usuário", width: 170, render: (r) => (r.user_name ? : Sistema) }, { key: "description", label: "Descrição", render: (r) => ( {r.description || "—"} ) }, { key: "severity", label: "Gravidade", width: 120, render: (r) => ( {(SEVERIDADE[r.severity] || {}).label || r.severity} ) }, { key: "ip", label: "Origem", width: 130, render: (r) => ( {r.ip || "—"} ) }, ]} /> )} ) : null} {aba === "sessoes" ? ( {sessoes.length === 0 ? vazio("Nenhuma sessão aberta", "Ninguém está conectado no momento.") : ( ( {r.name} {r.role_title || (r.profile === "supervisor" ? "Supervisor" : "Operacional")} ) }, { key: "ativa", label: "Situação", width: 120, render: (r) => ( {r.ativa ? "Ativa" : "Inativa"} ) }, { key: "started_at", label: "Entrou", width: 150, render: (r) => ( {dataHora(r.started_at)} ) }, { key: "last_seen_at", label: "Visto por último", width: 150, render: (r) => ( {desde(r.last_seen_at)} ) }, { key: "auth_type", label: "Acesso", width: 110, render: (r) => ( {r.auth_type === "google" ? "Google" : "Local"} ) }, { key: "ip", label: "Origem", width: 130, render: (r) => ( {r.ip || "—"} ) }, { key: "acao", label: "", width: 60, align: "right", render: (r) => ( } onClick={async () => { await window.ACRApi.audit.endSessions(r.user_id); onToast && onToast({ tone: "info", title: "Sessão encerrada", message: r.name + " precisará entrar novamente." }); carregar(); }} /> ) }, ]} /> )} ) : null} {aba === "acessos" ? ( {acessos.length === 0 ? vazio("Nenhum colaborador cadastrado", "Cadastre a equipe em Controle de Acesso.") : ( ( {r.name} {r.username || r.email || "—"} ) }, { key: "profile", label: "Perfil", width: 130, render: (r) => ( {r.profile === "supervisor" ? "Supervisor" : "Operacional"} ) }, { key: "online", label: "Agora", width: 100, render: (r) => ( {r.online ? "Online" : "Offline"} ) }, { key: "last_login_at", label: "Último acesso", width: 200, render: (r) => { // Sem login gravado, mas com rastro na auditoria: mostra a última atividade. const ativ = r.ultima_atividade && new Date(r.ultima_atividade).getFullYear() > 1970 ? r.ultima_atividade : null; const inferido = !r.last_login_at && !!ativ; const quando = r.last_login_at || ativ; return ( {dataHora(quando)} {quando ? desde(quando) + (inferido ? " · por atividade" : "") : "nunca acessou"} ); } }, { key: "login_count", label: "Entradas", width: 100, align: "right", render: (r) => ( r.login_count ? {r.login_count} : {r.n_eventos ? "—" : "0"} ) }, { key: "last_login_ip", label: "Origem", width: 130, render: (r) => ( {r.last_login_ip || "—"} ) }, { key: "active", label: "Conta", width: 100, render: (r) => ( {r.active ? "Ativa" : "Inativa"} ) }, ]} /> )} ) : null} {aba === "bloqueios" ? ( {bloqueios.length === 0 ? vazio("Nenhum bloqueio registrado", "Nenhuma estação atingiu o limite de tentativas.") : ( ( {r.ip} ) }, { key: "ativo", label: "Situação", width: 150, render: (r) => ( {r.ativo ? (r.minutos_restantes > 60 ? "Bloqueado · " + Math.ceil(r.minutos_restantes / 60) + " h" : "Bloqueado · " + r.minutos_restantes + " min") : "Liberado"} ) }, { key: "strikes", label: "Reincidências", width: 130, align: "right", render: (r) => ( 1 ? "var(--text-strong)" : "var(--text-body)" }}>{r.strikes} ) }, { key: "updated_at", label: "Último bloqueio", width: 190, render: (r) => ( {dataHora(r.updated_at)} ) }, { key: "last_reason", label: "Motivo", render: (r) => ( {r.last_reason || "—"} ) }, { key: "acao", label: "", width: 70, align: "right", render: (r) => ( r.ativo ? ( } onClick={async () => { await window.ACRApi.audit.releaseBlock(r.ip); onToast && onToast({ tone: "info", title: "IP liberado", message: r.ip + " pode tentar entrar novamente." }); carregar(); }} /> ) : null ) }, ]} /> )} ) : null} {aba === "produtividade" ? (() => { if (erroProd) return } title="Não foi possível calcular a produtividade" description={erroProd} />; const linhas = (prod && prod.linhas) || []; const ativas = linhas.filter((r) => r.active); const top = ativas.filter((r) => r.concluidas > 0 || r.eventos > 0).slice(0, 5); const maior = Math.max(1, ...top.map((r) => r.concluidas || 0)); const soma = (k) => ativas.reduce((a, r) => a + (Number(r[k]) || 0), 0); const pct = (r) => (r.concluidas ? Math.round((r.no_prazo / r.concluidas) * 100) : null); return (
Janela de apuração — conta apenas o que o sistema registra: entradas, demandas, comentários e anexos.