70 lines
2.9 KiB
TypeScript
70 lines
2.9 KiB
TypeScript
import React from 'react';
|
|
import { useQuery } from '@tanstack/react-query';
|
|
import { formatDistanceToNow } from 'date-fns';
|
|
import { useAuth } from '@/hooks/useAuth';
|
|
import { api } from '@/lib/api';
|
|
import type { AuditLogEntry } from '@emberclone/shared';
|
|
|
|
export default function ActivityFeed() {
|
|
const { user } = useAuth();
|
|
|
|
const { data: logs, isLoading, error } = useQuery({
|
|
queryKey: ['audit-log', 'recent'],
|
|
queryFn: async () => {
|
|
const res = await api.get<AuditLogEntry[]>('/audit-log/recent');
|
|
return res;
|
|
},
|
|
enabled: !!user,
|
|
});
|
|
|
|
if (!user) return null;
|
|
|
|
// Hide widget if not admin and no logs available (or logic handled by backend)
|
|
// Backend should filter, but we ensure UI consistency here
|
|
if (!user.isAdmin && (!logs || logs.length === 0)) {
|
|
return null;
|
|
}
|
|
|
|
return (
|
|
<div className="flex flex-col w-full max-w-2xl bg-white dark:bg-slate-900 border border-slate-200 dark:border-slate-800 rounded-lg shadow-sm">
|
|
<div className="px-4 py-3 border-b border-slate-200 dark:border-slate-800 flex justify-between items-center">
|
|
<h3 className="text-sm font-semibold text-slate-700 dark:text-slate-200">Recent Activity</h3>
|
|
<span className="text-xs text-slate-500">{logs?.length || 0} entries</span>
|
|
</div>
|
|
|
|
<div className="max-h-96 overflow-y-auto">
|
|
{isLoading ? (
|
|
<div className="p-4 text-center text-sm text-slate-500">Loading activity...</div>
|
|
) : error ? (
|
|
<div className="p-4 text-center text-sm text-red-500">Failed to load activity feed.</div>
|
|
) : logs && logs.length > 0 ? (
|
|
<ul className="divide-y divide-slate-100 dark:divide-slate-800">
|
|
{logs.map((log) => (
|
|
<li key={log.id} className="px-4 py-3 hover:bg-slate-50 dark:hover:bg-slate-800/50 transition-colors">
|
|
<div className="flex items-start gap-3">
|
|
<div className="flex-1 min-w-0">
|
|
<div className="flex items-baseline gap-2">
|
|
<span className="text-sm font-medium text-slate-900 dark:text-slate-100 truncate">
|
|
{log.userName}
|
|
</span>
|
|
<span className="text-xs text-slate-500 shrink-0">
|
|
{formatDistanceToNow(new Date(log.createdAt), { addSuffix: true })}
|
|
</span>
|
|
</div>
|
|
<p className="text-xs text-slate-600 dark:text-slate-400 truncate">
|
|
<span className="font-medium">{log.action}</span>
|
|
<span className="text-slate-400 mx-1">→</span>
|
|
<span className="italic">{log.resource}</span>
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</li>
|
|
))}
|
|
</ul>
|
|
) : (
|
|
<div className="p-8 text-center text-sm text-slate-500">No recent activity found.</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
);
|
|
} |