Files
thrilltrack-explorer/src/components/admin/RecentActivityTimeline.tsx
gpt-engineer-app[bot] 99c917deaf Implement monitoring overview features
Add comprehensive monitoring dashboard scaffolding:
- Extend queryKeys with monitoring keys
- Create hooks: useCombinedAlerts, useRecentActivity, useDatabaseHealth, useModerationHealth
- Build UI components: SystemHealthStatus, CriticalAlertsPanel, MonitoringQuickStats, RecentActivityTimeline, MonitoringNavCards
- Create MonitoringOverview page and integrate routing (MonitoringOverview route)
- Wire AdminSidebar to include Monitoring navigation
- Introduce supporting routing and admin layout hooks/utilities
- Align with TanStack Query patterns and plan for auto-refresh and optimistic updates
2025-11-11 01:33:53 +00:00

139 lines
4.2 KiB
TypeScript

import { AlertTriangle, Database, ShieldAlert, XCircle } from 'lucide-react';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import { Badge } from '@/components/ui/badge';
import { ScrollArea } from '@/components/ui/scroll-area';
import { formatDistanceToNow } from 'date-fns';
import { Link } from 'react-router-dom';
import type { ActivityEvent } from '@/hooks/admin/useRecentActivity';
interface RecentActivityTimelineProps {
activity?: ActivityEvent[];
isLoading: boolean;
}
export function RecentActivityTimeline({ activity, isLoading }: RecentActivityTimelineProps) {
if (isLoading) {
return (
<Card>
<CardHeader>
<CardTitle>Recent Activity</CardTitle>
</CardHeader>
<CardContent>
<div className="text-center text-muted-foreground py-8">Loading activity...</div>
</CardContent>
</Card>
);
}
if (!activity || activity.length === 0) {
return (
<Card>
<CardHeader>
<CardTitle>Recent Activity (Last Hour)</CardTitle>
</CardHeader>
<CardContent>
<div className="text-center text-muted-foreground py-8">No recent activity</div>
</CardContent>
</Card>
);
}
const getEventIcon = (event: ActivityEvent) => {
switch (event.type) {
case 'error':
return XCircle;
case 'approval':
return Database;
case 'alert':
return AlertTriangle;
}
};
const getEventColor = (event: ActivityEvent) => {
switch (event.type) {
case 'error':
return 'text-red-500';
case 'approval':
return 'text-orange-500';
case 'alert':
return 'text-yellow-500';
}
};
const getEventDescription = (event: ActivityEvent) => {
switch (event.type) {
case 'error':
return `${event.error_type} in ${event.endpoint}`;
case 'approval':
return `Approval failed: ${event.error_message}`;
case 'alert':
return event.message;
}
};
const getEventLink = (event: ActivityEvent) => {
switch (event.type) {
case 'error':
return `/admin/error-monitoring`;
case 'approval':
return `/admin/error-monitoring?tab=approvals`;
case 'alert':
return `/admin/error-monitoring`;
default:
return undefined;
}
};
return (
<Card>
<CardHeader>
<div className="flex items-center justify-between">
<CardTitle>Recent Activity (Last Hour)</CardTitle>
<Badge variant="outline">{activity.length} events</Badge>
</div>
</CardHeader>
<CardContent>
<ScrollArea className="h-[400px] pr-4">
<div className="space-y-3">
{activity.map((event) => {
const Icon = getEventIcon(event);
const color = getEventColor(event);
const description = getEventDescription(event);
const link = getEventLink(event);
const content = (
<div
className={`flex items-start gap-3 p-3 rounded-lg border border-border transition-colors ${
link ? 'hover:bg-accent/50 cursor-pointer' : ''
}`}
>
<Icon className={`w-5 h-5 mt-0.5 flex-shrink-0 ${color}`} />
<div className="flex-1 min-w-0">
<div className="flex items-center gap-2 flex-wrap">
<Badge variant="outline" className="text-xs capitalize">
{event.type}
</Badge>
<span className="text-xs text-muted-foreground">
{formatDistanceToNow(new Date(event.created_at), { addSuffix: true })}
</span>
</div>
<p className="text-sm mt-1 break-words">{description}</p>
</div>
</div>
);
return link ? (
<Link key={event.id} to={link}>
{content}
</Link>
) : (
<div key={event.id}>{content}</div>
);
})}
</div>
</ScrollArea>
</CardContent>
</Card>
);
}