'use client'; import { Activity, Clock, Eye, Pencil, Plus, Trash2, User } from 'lucide-react'; import { formatDistanceToNow } from 'date-fns'; import { ListCard, ListCardAvatar, ListCardMeta } from '@/components/shared/list-card'; import { cn } from '@/lib/utils'; interface AuditEntry { id: string; userId: string | null; action: string; entityType: string; entityId: string | null; fieldChanged: string | null; oldValue: Record | null; newValue: Record | null; metadata: Record | null; ipAddress: string | null; createdAt: string; actor: { id: string; email: string; name: string } | null; } const ACTION_ACCENT: Record = { create: 'bg-emerald-400', update: 'bg-blue-400', delete: 'bg-rose-400', viewed: 'bg-slate-300', }; const ACTION_BADGE_COLORS: Record = { create: 'bg-green-600', update: 'bg-blue-500', delete: 'bg-red-600', archive: 'bg-orange-500', restore: 'bg-teal-500', login: 'bg-gray-500', permission_denied: 'bg-red-800', merge: 'bg-purple-500', revert: 'bg-amber-500', }; function ActionIcon({ action }: { action: string }) { if (action === 'create') return ; if (action === 'update') return ; if (action === 'delete') return ; if (action === 'viewed') return ; return ; } function actionVerb(action: string): string { const map: Record = { create: 'Created', update: 'Updated', delete: 'Deleted', archive: 'Archived', restore: 'Restored', login: 'Logged in', permission_denied: 'Permission denied', merge: 'Merged', revert: 'Reverted', viewed: 'Viewed', }; return map[action] ?? action.charAt(0).toUpperCase() + action.slice(1); } interface AuditLogCardProps { entry: AuditEntry; } export function AuditLogCard({ entry }: AuditLogCardProps) { const accentClass = ACTION_ACCENT[entry.action] ?? 'bg-slate-300'; const badgeColor = ACTION_BADGE_COLORS[entry.action] ?? 'bg-gray-500'; const entityTitle = `${entry.entityType.charAt(0).toUpperCase()}${entry.entityType.slice(1)}${ entry.entityId ? ` ${entry.entityId.slice(0, 8)}…` : '' }`; const actorName = entry.actor?.name ?? (entry.userId ? `${entry.userId.slice(0, 8)}…` : 'system'); // Changed-fields chip line: prefer fieldChanged (single field), then newValue keys let changedFields: string[] = []; if (entry.fieldChanged) { changedFields = [entry.fieldChanged]; } else if (entry.newValue) { changedFields = Object.keys(entry.newValue); } const visibleFields = changedFields.slice(0, 3); const overflowCount = changedFields.length - visibleFields.length; return (
} />
{/* Title: entity type + short ID */}

{entityTitle}

{/* Subtitle: action verb + actor */}

{actionVerb(entry.action)} by {actorName}

{/* Timestamp meta line */}
}> {formatDistanceToNow(new Date(entry.createdAt), { addSuffix: true })}
{/* Action badge + changed-fields chips */}
{entry.action} {visibleFields.length > 0 ? ( <> {visibleFields.map((field) => ( {field} ))} {overflowCount > 0 ? ( +{overflowCount} ) : null} ) : null}
); }