112 lines
3.1 KiB
TypeScript
112 lines
3.1 KiB
TypeScript
'use client'
|
|
|
|
import { trpc } from '@/lib/trpc/client'
|
|
import { Badge } from '@/components/ui/badge'
|
|
import {
|
|
Card,
|
|
CardContent,
|
|
CardDescription,
|
|
CardHeader,
|
|
CardTitle,
|
|
} from '@/components/ui/card'
|
|
import { Skeleton } from '@/components/ui/skeleton'
|
|
import { Clock, Activity } from 'lucide-react'
|
|
import { formatDate } from '@/lib/utils'
|
|
|
|
const actionColors: Record<string, 'default' | 'destructive' | 'secondary' | 'outline'> = {
|
|
CREATE: 'default',
|
|
UPDATE: 'secondary',
|
|
DELETE: 'destructive',
|
|
LOGIN_SUCCESS: 'outline',
|
|
LOGIN_FAILED: 'destructive',
|
|
INVITATION_ACCEPTED: 'default',
|
|
EVALUATION_SUBMITTED: 'default',
|
|
SUBMIT_EVALUATION: 'default',
|
|
ROLE_CHANGED: 'secondary',
|
|
PASSWORD_SET: 'outline',
|
|
PASSWORD_CHANGED: 'outline',
|
|
FILE_DOWNLOADED: 'outline',
|
|
ROUND_ACTIVATED: 'default',
|
|
ROUND_CLOSED: 'secondary',
|
|
}
|
|
|
|
interface UserActivityLogProps {
|
|
userId: string
|
|
limit?: number
|
|
}
|
|
|
|
export function UserActivityLog({ userId, limit = 20 }: UserActivityLogProps) {
|
|
const { data: logs, isLoading } = trpc.audit.getByUser.useQuery({
|
|
userId,
|
|
limit,
|
|
})
|
|
|
|
if (isLoading) {
|
|
return (
|
|
<Card>
|
|
<CardHeader>
|
|
<CardTitle>Activity Log</CardTitle>
|
|
</CardHeader>
|
|
<CardContent>
|
|
<div className="space-y-3">
|
|
{[...Array(5)].map((_, i) => (
|
|
<div key={i} className="flex items-center gap-3">
|
|
<Skeleton className="h-4 w-32" />
|
|
<Skeleton className="h-6 w-20" />
|
|
<Skeleton className="h-4 w-24" />
|
|
</div>
|
|
))}
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
)
|
|
}
|
|
|
|
return (
|
|
<Card>
|
|
<CardHeader>
|
|
<CardTitle className="flex items-center gap-2">
|
|
<Activity className="h-5 w-5" />
|
|
Activity Log
|
|
</CardTitle>
|
|
<CardDescription>Recent actions by this member</CardDescription>
|
|
</CardHeader>
|
|
<CardContent>
|
|
{logs && logs.length > 0 ? (
|
|
<div className="space-y-3">
|
|
{logs.map((log) => (
|
|
<div
|
|
key={log.id}
|
|
className="flex items-center gap-3 text-sm"
|
|
>
|
|
<div className="flex items-center gap-1 text-muted-foreground shrink-0 w-36">
|
|
<Clock className="h-3 w-3" />
|
|
<span className="text-xs font-mono">
|
|
{formatDate(log.timestamp)}
|
|
</span>
|
|
</div>
|
|
<Badge
|
|
variant={actionColors[log.action] || 'secondary'}
|
|
className="shrink-0"
|
|
>
|
|
{log.action.replace(/_/g, ' ')}
|
|
</Badge>
|
|
<span className="text-muted-foreground truncate">
|
|
{log.entityType}
|
|
{log.entityId && (
|
|
<span className="font-mono text-xs ml-1">
|
|
{log.entityId.slice(0, 8)}...
|
|
</span>
|
|
)}
|
|
</span>
|
|
</div>
|
|
))}
|
|
</div>
|
|
) : (
|
|
<p className="text-sm text-muted-foreground">No activity recorded yet.</p>
|
|
)}
|
|
</CardContent>
|
|
</Card>
|
|
)
|
|
}
|