diff --git a/src/admin/pages/AuditLog.tsx b/src/admin/pages/AuditLog.tsx index 3cfb160..678a83d 100644 --- a/src/admin/pages/AuditLog.tsx +++ b/src/admin/pages/AuditLog.tsx @@ -1,17 +1,31 @@ import { useState } from "react"; import { useQuery } from "@tanstack/react-query"; import { motion } from "framer-motion"; +import Box from "@mui/material/Box"; +import Typography from "@mui/material/Typography"; import { useAuth } from "../context/AuthContext"; import { useAlert } from "../context/AlertContext"; import Forbidden from "../components/Forbidden"; -import Pagination from "../components/Pagination"; -import FormField from "../components/FormField"; -import FormModal from "../components/FormModal"; -import AdminDatePicker from "../components/AdminDatePicker"; import { czechPlural } from "../utils/formatters"; import apiFetch from "../utils/api"; import { useApiMutation } from "../lib/queries/mutations"; import { ENTITY_TYPE_LABELS } from "../lib/entityTypeLabels"; +import { + Button, + Card, + DataTable, + Pagination, + Modal, + Select, + DateField, + TextField, + StatusChip, + PageHeader, + FilterBar, + EmptyState, + LoadingState, + type DataColumn, +} from "../ui"; const API_BASE = "/api/admin"; @@ -30,19 +44,23 @@ const ACTION_LABELS: Record = { access_denied: "Přístup odepřen", }; -const ACTION_BADGE_CLASS: Record = { - create: "admin-badge-success", - update: "admin-badge-info", - delete: "admin-badge-danger", - login: "admin-badge-secondary", - login_failed: "admin-badge-danger", - logout: "admin-badge-secondary", - view: "admin-badge-info", - activate: "admin-badge-success", - deactivate: "admin-badge-warning", - password_change: "admin-badge-info", - permission_change: "admin-badge-warning", - access_denied: "admin-badge-danger", +// Maps legacy admin-badge-* CSS class suffixes → StatusChip color +const ACTION_CHIP_COLOR: Record< + string, + "success" | "info" | "warning" | "error" | "default" +> = { + create: "success", + update: "info", + delete: "error", + login: "default", + login_failed: "error", + logout: "default", + view: "info", + activate: "success", + deactivate: "warning", + password_change: "info", + permission_change: "warning", + access_denied: "error", }; const ACTION_OPTIONS = Object.entries(ACTION_LABELS).map(([value, label]) => ({ @@ -53,6 +71,29 @@ const ENTITY_OPTIONS = Object.entries(ENTITY_TYPE_LABELS).map( ([value, label]) => ({ value, label }), ); +const CLEANUP_DAYS_OPTIONS = [ + { value: "30", label: "30 dní" }, + { value: "60", label: "60 dní" }, + { value: "90", label: "90 dní" }, + { value: "180", label: "180 dní" }, + { value: "365", label: "1 rok" }, + { value: "0", label: "Vše" }, +]; + +const TrashIcon = ( + + + + +); + interface AuditLogEntry { id: number; created_at: string; @@ -82,9 +123,10 @@ export default function AuditLog() { date_to: "", }); const [page, setPage] = useState(1); - const [perPage, setPerPage] = useState(50); + const [perPage] = useState(50); const [showCleanup, setShowCleanup] = useState(false); - const [cleanupDays, setCleanupDays] = useState(90); + // cleanupDays stored as string for the kit Select; converted to number at the boundary + const [cleanupDaysStr, setCleanupDaysStr] = useState("90"); const { data: logsData, isPending } = useQuery({ queryKey: [ @@ -154,16 +196,8 @@ export default function AuditLog() { setPage(1); }; - const handlePageChange = (newPage: number) => { - setPage(newPage); - }; - - const handlePerPageChange = (newPerPage: number) => { - setPage(1); - setPerPage(newPerPage); - }; - const handleCleanup = async () => { + const cleanupDays = Number(cleanupDaysStr); try { // Backend requires this literal confirmation token when wiping everything. await cleanupMutation.mutateAsync({ @@ -181,229 +215,162 @@ export default function AuditLog() { }; if (isPending && logs.length === 0) { - return ( -
-
-
- ); + return ; } + const columns: DataColumn[] = [ + { + key: "created_at", + header: "Čas", + width: "16%", + mono: true, + render: (log) => formatDatetime(log.created_at), + }, + { + key: "username", + header: "Uživatel", + width: "14%", + bold: true, + render: (log) => log.username || "-", + }, + { + key: "action", + header: "Akce", + width: "14%", + render: (log) => ( + + ), + }, + { + key: "entity_type", + header: "Typ entity", + width: "14%", + render: (log) => + ENTITY_TYPE_LABELS[log.entity_type || ""] || log.entity_type || "-", + }, + { + key: "description", + header: "Popis", + width: "30%", + render: (log) => log.description || "-", + }, + { + key: "user_ip", + header: "IP", + width: "12%", + mono: true, + render: (log) => log.user_ip || "-", + }, + ]; + return ( -
+ -
-

Audit log

- {pagination && ( -

- {pagination.total}{" "} - {czechPlural(pagination.total, "záznam", "záznamy", "záznamů")} -

- )} -
- + setShowCleanup(true)} + > + Vyčistit + + } + />
- !cleanupMutation.isPending && setShowCleanup(false)} onSubmit={handleCleanup} title="Vyčistit audit log" - submitLabel="Smazat" + submitText="Smazat" loading={cleanupMutation.isPending} > -

Smazat záznamy starší než:

-
- -
-

- Tato akce je nevratná. -

-
- - -
-
- - handleFilterChange("search", e.target.value)} - /> - - - - - - - - - handleFilterChange("date_from", val)} - /> - - - handleFilterChange("date_to", val)} - /> - -
-
-
- - -
-
- {isPending ? ( -
-
-
- ) : ( - - - - - - - - - - - - - {logs.length === 0 && ( - - - - )} - {logs.length > 0 && - logs.map((log) => ( - - - - - - - - - ))} - -
ČasUživatelAkceTyp entityPopisIP
-
-
- - - - - - -
-

Žádné záznamy k zobrazení

-
-
- {formatDatetime(log.created_at)} - {log.username || "-"} - - {ACTION_LABELS[log.action] || log.action} - - - {ENTITY_TYPE_LABELS[log.entity_type || ""] || - log.entity_type || - "-"} - {log.description || "-"}{log.user_ip || "-"}
- )} -
- - + Smazat záznamy starší než: + + + handleFilterChange("action", val)} + options={[{ value: "", label: "Všechny akce" }, ...ACTION_OPTIONS]} + /> + + +