import { useCallback, useEffect, useMemo, useRef, useState } from "react"; import { useTranslation } from "react-i18next"; import { CustomColumnFilter, CustomButton } from "../../components/custom"; import { DataTable } from "../../components/custom/CustomTable"; import type { ColumnDef } from "../../components/custom/CustomTable"; import { buildColumnFilterOptions, resolveColumnSortState, } from "../../components/custom/CustomColumnFilter.utils"; import type { ColumnSortDirection } from "../../components/custom/CustomColumnFilter"; import { useDebounce } from "../../components/hooks/useDebounce"; import { apiClient } from "../../lib/apiClient"; import { buildQueryString } from "../../lib/queryParams"; import { DEFAULT_TABLE_PAGE_SIZE_OPTIONS, SHARED_ADMIN_TABLE_PAGE_SIZE_STORAGE_KEY, resolveStoredTablePageSize, } from "../../lib/tablePageSize"; import { RefreshCcw } from "lucide-react"; interface AuditLog extends Record { id: string; module_name: string; action_type: string; entity_name: string; performed_by_email: string; ip_address: string; description: string; created_at: string; } interface AuditLogListResponse { items: AuditLog[]; total: number; limit: number; offset: number; } const LogsPage = () => { const { t, i18n } = useTranslation(["logs", "common"]); const [logs, setLogs] = useState([]); const [allLogsForCounts, setAllLogsForCounts] = useState([]); const [isLoading, setIsLoading] = useState(true); const [page, setPage] = useState(1); const [pageSize, setPageSize] = useState(() => resolveStoredTablePageSize({ storageKey: SHARED_ADMIN_TABLE_PAGE_SIZE_STORAGE_KEY, pageSizeOptions: DEFAULT_TABLE_PAGE_SIZE_OPTIONS, defaultPageSize: 10, }) ); const [search, setSearch] = useState(""); const [moduleFilter, setModuleFilter] = useState([]); const [actionFilter, setActionFilter] = useState([]); const [emailFilter, setEmailFilter] = useState([]); const [totalRows, setTotalRows] = useState(0); const [activeSort, setActiveSort] = useState<{ column: "module_name" | "action_type" | "performed_by_email" | "created_at" | null; direction: ColumnSortDirection; }>({ column: "created_at", direction: "desc" }); const debouncedSearch = useDebounce(search, 500); const searchInputRef = useRef(null); const prevLoadingRef = useRef(isLoading); const latestLogsRequestRef = useRef(0); const fetchLogs = useCallback(async () => { const requestId = ++latestLogsRequestRef.current; try { setIsLoading(true); const queryString = buildQueryString({ limit: pageSize, offset: (page - 1) * pageSize, search: debouncedSearch || undefined, module_names: moduleFilter, action_types: actionFilter, performed_by_emails: emailFilter, sort_by: activeSort.column ?? undefined, sort_order: activeSort.direction ?? undefined, }); const response = await apiClient.get( `/api/admin/audit-logs/${queryString}`, { silent: true } ); if (requestId === latestLogsRequestRef.current) { setLogs(response.items); setTotalRows(response.total); } } catch (error) { console.error(t("messages.loadError"), error); } finally { if (requestId === latestLogsRequestRef.current) { setIsLoading(false); } } }, [actionFilter, activeSort, debouncedSearch, emailFilter, page, pageSize, moduleFilter, t]); const fetchFilterOptions = useCallback(async () => { try { const response = await apiClient.get( "/api/admin/audit-logs/?limit=500&offset=0", { silent: true } ); setAllLogsForCounts(response.items); } catch (error) { console.error(t("messages.loadError"), error); } }, [t]); useEffect(() => { fetchLogs(); }, [fetchLogs]); useEffect(() => { fetchFilterOptions(); }, [fetchFilterOptions]); useEffect(() => { setPage(1); }, [debouncedSearch, moduleFilter, actionFilter, emailFilter, activeSort]); useEffect(() => { if (prevLoadingRef.current && !isLoading && search.trim()) { searchInputRef.current?.focus({ preventScroll: true }); } prevLoadingRef.current = isLoading; }, [isLoading, search]); const moduleCounts = useMemo(() => { const counts = new Map(); allLogsForCounts.forEach((log) => { counts.set(log.module_name, (counts.get(log.module_name) ?? 0) + 1); }); return counts; }, [allLogsForCounts]); const actionCounts = useMemo(() => { const counts = new Map(); allLogsForCounts.forEach((log) => { counts.set(log.action_type, (counts.get(log.action_type) ?? 0) + 1); }); return counts; }, [allLogsForCounts]); const emailCounts = useMemo(() => { const counts = new Map(); allLogsForCounts.forEach((log) => { counts.set(log.performed_by_email, (counts.get(log.performed_by_email) ?? 0) + 1); }); return counts; }, [allLogsForCounts]); const columns: ColumnDef[] = useMemo( () => [ { key: "module_name", visibilityLabel: t("columns.module"), header: (
{t("columns.module")} { setPage(1); setModuleFilter(values); setActiveSort(resolveColumnSortState(activeSort, "module_name", direction)); }} />
), searchable: false, }, { key: "action_type", visibilityLabel: t("columns.action"), header: (
{t("columns.action")} { setPage(1); setActionFilter(values); setActiveSort(resolveColumnSortState(activeSort, "action_type", direction)); }} />
), searchable: false, render: (row) => ( {row.action_type} ), }, { key: "description", header: t("columns.description") }, { key: "performed_by_email", visibilityLabel: t("columns.performedBy"), header: (
{t("columns.performedBy")} { setPage(1); setEmailFilter(values); setActiveSort(resolveColumnSortState(activeSort, "performed_by_email", direction)); }} />
), searchable: false, }, { key: "ip_address", header: t("columns.ipAddress") }, { key: "created_at", visibilityLabel: t("columns.timestamp"), header: (
{t("columns.timestamp")} { setPage(1); setActiveSort(resolveColumnSortState(activeSort, "created_at", direction)); }} />
), searchable: false, render: (row) => new Date(row.created_at).toLocaleString( i18n.language === "ar" ? "ar-EG" : "en-GB" ), }, ], [actionCounts, actionFilter, activeSort, emailCounts, emailFilter, i18n.language, moduleCounts, moduleFilter, t] ); return (

{t("title")}

Track and audit all system activities and user actions.

} > {t("common:actions.refresh")}
data={logs} columns={columns} columnVisibilityEnabled columnVisibilityStorageKey="logs-table-columns" pageSizeStorageKey={SHARED_ADMIN_TABLE_PAGE_SIZE_STORAGE_KEY} isLoading={isLoading} exportEnabled={false} exportFileName={t("exportFileName")} manualPagination manualFiltering totalRows={totalRows} page={page} pageSize={pageSize} search={search} onPageChange={setPage} onPageSizeChange={setPageSize} onSearchChange={setSearch} searchInputRef={searchInputRef} />
); }; export default LogsPage;