'use client'; import { useEffect, useMemo, useRef, useState } from 'react'; import { Search, Filter, RefreshCw, Loader2, ChevronDown, Download, FileSpreadsheet } from 'lucide-react'; import { format } from 'date-fns'; import { toast } from 'sonner'; import { adminService } from '@/services/api/adminService'; import { CustomSelect } from '@/components/ui/CustomSelect'; import { TablePagination, useClientPagination } from '@/components/ui/TablePagination'; type ActivityAction = 'Create' | 'Update' | 'Delete' | 'Export' | 'Failed Login'; interface MergedActivityLog { logId: string; auditId: string; user: { name: string; avatarInitials: string; }; action: ActivityAction; module: string; recordId: string; actionDate: string; ipAddress: string; } function formatAction(action: string): ActivityAction { if (action === 'failed_login') return 'Failed Login'; const label = action.charAt(0).toUpperCase() + action.slice(1).toLowerCase(); if (label === 'Create' || label === 'Update' || label === 'Delete' || label === 'Export') return label; return 'Update'; } function actionBadgeClass(action: string) { const act = action.toLowerCase(); if (act === 'create') return 'bg-success text-white'; if (act === 'update') return 'bg-primary text-white'; if (act === 'delete') return 'bg-destructive text-white'; if (act === 'failed login') return 'bg-warning text-white'; return 'bg-muted text-muted-foreground'; } export default function ActivityLogsPage() { const [logs, setLogs] = useState([]); const [loading, setLoading] = useState(true); const [search, setSearch] = useState(''); const [actionFilter, setActionFilter] = useState(''); const [showFilters, setShowFilters] = useState(false); const [draftAction, setDraftAction] = useState(''); const filterRef = useRef(null); const fetchLogs = async () => { setLoading(true); try { const res = await adminService.getAuditLogs(100); const backendLogs: MergedActivityLog[] = res.map((item) => ({ logId: item.audit_id.substring(0, 8).toUpperCase(), auditId: item.audit_id, user: { name: item.user?.name || 'System / Guest', avatarInitials: item.user?.avatarInitials || 'SYS', }, action: formatAction(item.action), module: item.entity_type, recordId: item.entity_id, actionDate: item.created_at, ipAddress: item.ip_address, })); setLogs(backendLogs); } catch (err: unknown) { const message = err instanceof Error ? err.message : 'Failed to load activity logs'; toast.error(message); setLogs([]); } finally { setLoading(false); } }; useEffect(() => { fetchLogs(); }, []); useEffect(() => { if (!showFilters) return; const onPointerDown = (event: MouseEvent) => { if (!filterRef.current?.contains(event.target as Node)) setShowFilters(false); }; document.addEventListener('mousedown', onPointerDown); return () => document.removeEventListener('mousedown', onPointerDown); }, [showFilters]); const filteredLogs = useMemo(() => { const q = search.trim().toLowerCase(); return logs.filter((log) => { const matchesSearch = !q || log.user.name.toLowerCase().includes(q) || log.module.toLowerCase().includes(q) || log.logId.toLowerCase().includes(q) || log.recordId.toLowerCase().includes(q); const matchesAction = actionFilter ? log.action === actionFilter : true; return matchesSearch && matchesAction; }); }, [logs, search, actionFilter]); const pager = useClientPagination(filteredLogs); const filtersActive = Boolean(actionFilter); const handleExportCSV = () => { const headers = ['Log ID', 'User', 'Action', 'Module', 'Record ID', 'Action Date', 'IP Address']; const rows = filteredLogs.map((log) => [ log.logId, log.user.name, log.action, log.module, log.recordId, format(new Date(log.actionDate), 'dd MMM yyyy, hh:mm a'), log.ipAddress, ]); const csv = [headers, ...rows] .map((row) => row.map((cell) => `"${String(cell).replace(/"/g, '""')}"`).join(',')) .join('\n'); const blob = new Blob([csv], { type: 'text/csv;charset=utf-8;' }); const url = URL.createObjectURL(blob); const link = document.createElement('a'); link.href = url; link.download = 'user-activity-logs.csv'; link.click(); URL.revokeObjectURL(url); }; const dataCardShell = 'crm-radius-section border border-border bg-card shadow-[0_1px_3px_rgba(16,24,40,0.06)] overflow-visible min-w-0'; const labelClass = 'block text-[11px] font-semibold text-muted-foreground uppercase mb-1.5'; const primaryButton = 'inline-flex items-center justify-center gap-2 h-9 px-4 crm-radius-control bg-primary hover:bg-primary/90 text-white text-[13px] font-semibold cursor-pointer disabled:opacity-50'; const secondaryButton = 'inline-flex items-center justify-center gap-2 h-9 px-4 crm-radius-control border border-border bg-card text-foreground text-[13px] font-medium hover:bg-muted cursor-pointer disabled:opacity-50'; return (

User Activity Logs

{logs.length}
{showFilters && (
)}
setSearch(e.target.value)} className="w-full h-9 pl-8 pr-3 crm-radius-control border border-border bg-card text-[13px] text-foreground placeholder:text-muted-foreground focus:outline-none focus:border-primary transition-colors" />
{loading ? ( ) : filteredLogs.length === 0 ? ( ) : ( pager.items.map((log) => ( )) )}
Log ID User Action Module Record ID Action date IP address

Loading activity logs...

{search || actionFilter ? 'No logs match your search' : 'No activity logs recorded'}
{log.logId}
{log.user.avatarInitials}
{log.user.name}
{log.action} {log.module} {log.recordId} {format(new Date(log.actionDate), 'dd MMM yyyy, hh:mm a')} {log.ipAddress}
); }