v0.1 - макет интерфейса, аутентификация через логин, аккаунт админа, создание партии в 2 этапа, базовые настройки профиля и группы, переключение между группами, статистика

This commit is contained in:
2026-06-16 17:41:06 +03:00
parent 6ab74f01aa
commit 56b5d09a4d
123 changed files with 13572 additions and 21 deletions
@@ -0,0 +1,67 @@
import { useState } from "react";
import { Spinner } from "../../components/Spinner";
import { formatDateTime } from "../../domain/format";
import { useAdminLogs } from "../../hooks/admin";
const ACTIONS = ["", "login", "create", "update", "delete"];
const ENTITIES = ["", "user", "group", "group_member", "match", "admin"];
export function AdminLogsPage() {
const [action, setAction] = useState("");
const [entityType, setEntityType] = useState("");
const { data, isLoading } = useAdminLogs({
action: action || undefined,
entity_type: entityType || undefined,
});
return (
<div>
<h3>Журнал событий</h3>
<div className="row" style={{ marginBottom: 10, gap: 8 }}>
<select style={{ width: "auto" }} value={action} onChange={(e) => setAction(e.target.value)}>
{ACTIONS.map((a) => (
<option key={a} value={a}>
{a || "все действия"}
</option>
))}
</select>
<select style={{ width: "auto" }} value={entityType} onChange={(e) => setEntityType(e.target.value)}>
{ENTITIES.map((en) => (
<option key={en} value={en}>
{en || "все сущности"}
</option>
))}
</select>
</div>
{isLoading ? (
<Spinner />
) : (
<div className="stack">
{(data?.items ?? []).map((log) => (
<div key={log.id} className="card" style={{ margin: 0 }}>
<div className="row-between">
<b>
{log.action} · {log.entity_type}
{log.entity_id != null ? ` #${log.entity_id}` : ""}
</b>
<span className="muted small">{formatDateTime(log.created_at)}</span>
</div>
<div className="muted small">
actor: {log.actor_id ?? "—"}
{log.ip ? ` · ${log.ip}` : ""}
</div>
{log.payload && (
<pre className="small muted" style={{ whiteSpace: "pre-wrap", margin: "4px 0 0" }}>
{JSON.stringify(log.payload)}
</pre>
)}
</div>
))}
{(data?.items ?? []).length === 0 && <div className="muted small">Событий нет.</div>}
</div>
)}
</div>
);
}