// «Журнал действий» — audit log (кто/когда/на основании чего). Требование 2. // Human-readable labels for the action codes the backend emits. const AUDIT_ACTIONS = { "auth.login": { label: "Вход в систему", cls: "muted", cat: "auth" }, "task.create": { label: "Создание поручения", cls: "accepted", cat: "tasks" }, "task.update": { label: "Изменение поручения", cls: "tg", cat: "tasks" }, "task.reschedule": { label: "Перенос срока", cls: "warn", cat: "tasks" }, "task.urgent": { label: "Срочность", cls: "err", cat: "tasks" }, "task.overtime.consent": { label: "Согласие на сверхурочную", cls: "tg", cat: "tasks" }, "task.delete": { label: "Удаление поручения", cls: "err", cat: "tasks" }, "person.add": { label: "Добавление сотрудника", cls: "accepted", cat: "people" }, "person.dismiss": { label: "Увольнение сотрудника", cls: "err", cat: "people" }, "person.restore": { label: "Восстановление сотрудника", cls: "ok", cat: "people" }, "user.create": { label: "Создание пользователя", cls: "accepted", cat: "access" }, "user.update": { label: "Изменение пользователя", cls: "tg", cat: "access" }, "user.delete": { label: "Удаление пользователя", cls: "err", cat: "access" }, "access.update": { label: "Изменение доступа", cls: "warn", cat: "access" }, "settings.update": { label: "Изменение настроек", cls: "muted", cat: "settings" }, "fine.prepare": { label: "Подготовка взыскания", cls: "warn", cat: "fines" }, "fine.request": { label: "Запрос объяснений", cls: "tg", cat: "fines" }, "fine.approve": { label: "Утверждение взыскания", cls: "err", cat: "fines" }, "fine.remove": { label: "Снятие взыскания", cls: "ok", cat: "fines" }, }; const AUDIT_FILTERS = [ { key: "all", label: "Все" }, { key: "tasks", label: "Поручения" }, { key: "people", label: "Сотрудники" }, { key: "access", label: "Доступ" }, { key: "fines", label: "Взыскания" }, { key: "settings", label: "Настройки" }, ]; function auditActionMeta(code) { return AUDIT_ACTIONS[code] || { label: code, cls: "muted", cat: "other" }; } const AUDIT_ROLE_LABEL = { admin: "Администратор", controller: "Отдел контроля", executor: "Исполнитель" }; // Compact, readable rendering of the entity an action touched. function auditEntityLabel(e) { if (!e.entityType) return ""; if (e.entityType === "task") return "Поручение #" + e.entityId; if (e.entityType === "user") return "Пользователь #" + e.entityId; if (e.entityType === "person") return e.entityId || "Сотрудник"; if (e.entityType === "fine") return "Взыскание #" + e.entityId; if (e.entityType === "settings") return "Настройки"; if (e.entityType === "access") return "Матрица доступа"; return e.entityType + (e.entityId ? " #" + e.entityId : ""); } function AuditPage() { const [rows, setRows] = React.useState(null); const [cat, setCat] = React.useState("all"); const [q, setQ] = React.useState(""); const load = React.useCallback(() => { if (!window.API || !window.API.listAudit) { setRows([]); return; } window.API.listAudit({ limit: 500 }).then(r => setRows(Array.isArray(r) ? r : [])).catch(() => setRows([])); }, []); React.useEffect(() => { load(); }, [load]); const ql = q.trim().toLowerCase(); const filtered = (rows || []).filter(e => { const meta = auditActionMeta(e.action); if (cat !== "all" && meta.cat !== cat) return false; if (!ql) return true; return (e.actor + " " + meta.label + " " + auditEntityLabel(e) + " " + (e.basis || "")).toLowerCase().includes(ql); }); return (

Журнал действий

Кто, когда и на каком основании — для контроля и возможного обжалования (п.14.6) {rows ? ` · ${filtered.length} записей` : " · загрузка…"}

{AUDIT_FILTERS.map(f => ( ))}
setQ(e.target.value)} />
Время
Кто
Действие
Объект и основание
{rows === null && } {rows !== null && filtered.length === 0 && (
Записей по фильтру нет.
)} {filtered.map(e => { const meta = auditActionMeta(e.action); return (
{e.ts}
{e.actor || "—"}
{e.actorRole &&
{AUDIT_ROLE_LABEL[e.actorRole] || e.actorRole}
}
{meta.label}
{auditEntityLabel(e)}
{e.basis &&
Основание: {e.basis}
} {e.details && e.details !== "null" && (
{e.details}
)}
); })}
); } window.AuditPage = AuditPage;