Files
thrilltrack-explorer/src-old/components/admin/NotificationDebugPanel.tsx

220 lines
7.2 KiB
TypeScript

import { useState, useEffect } from 'react';
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
import { Button } from '@/components/ui/button';
import { Badge } from '@/components/ui/badge';
import { Alert, AlertDescription } from '@/components/ui/alert';
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table';
import { AlertTriangle, CheckCircle, RefreshCw, Loader2 } from 'lucide-react';
import { supabase } from '@/lib/supabaseClient';
import { format } from 'date-fns';
import { handleNonCriticalError } from '@/lib/errorHandler';
interface DuplicateStats {
date: string | null;
total_attempts: number | null;
duplicates_prevented: number | null;
prevention_rate: number | null;
health_status: 'healthy' | 'warning' | 'critical';
}
interface RecentDuplicate {
id: string;
user_id: string;
channel: string;
idempotency_key: string | null;
created_at: string;
profiles?: {
username: string;
display_name: string | null;
};
}
export function NotificationDebugPanel() {
const [stats, setStats] = useState<DuplicateStats[]>([]);
const [recentDuplicates, setRecentDuplicates] = useState<RecentDuplicate[]>([]);
const [isLoading, setIsLoading] = useState(true);
useEffect(() => {
loadData();
}, []);
const loadData = async () => {
setIsLoading(true);
try {
// Load health dashboard
const { data: healthData, error: healthError } = await supabase
.from('notification_health_dashboard')
.select('*')
.limit(7);
if (healthError) throw healthError;
if (healthData) {
setStats(healthData.map(stat => ({
...stat,
health_status: stat.health_status as 'healthy' | 'warning' | 'critical'
})));
}
// Load recent prevented duplicates
const { data: duplicates, error: duplicatesError } = await supabase
.from('notification_logs')
.select(`
id,
user_id,
channel,
idempotency_key,
created_at
`)
.eq('is_duplicate', true)
.order('created_at', { ascending: false })
.limit(10);
if (duplicatesError) throw duplicatesError;
if (duplicates) {
// Fetch profiles separately
const userIds = [...new Set(duplicates.map(d => d.user_id))];
const { data: profiles } = await supabase
.from('profiles')
.select('user_id, username, display_name')
.in('user_id', userIds);
const profileMap = new Map(profiles?.map(p => [p.user_id, p]) || []);
setRecentDuplicates(duplicates.map(dup => ({
...dup,
profiles: profileMap.get(dup.user_id)
})));
}
} catch (error: unknown) {
handleNonCriticalError(error, {
action: 'Load notification debug data'
});
} finally {
setIsLoading(false);
}
};
const getHealthBadge = (status: string) => {
switch (status) {
case 'healthy':
return (
<Badge variant="default" className="bg-green-500">
<CheckCircle className="h-3 w-3 mr-1" />
Healthy
</Badge>
);
case 'warning':
return (
<Badge variant="secondary">
<AlertTriangle className="h-3 w-3 mr-1" />
Warning
</Badge>
);
case 'critical':
return (
<Badge variant="destructive">
<AlertTriangle className="h-3 w-3 mr-1" />
Critical
</Badge>
);
default:
return <Badge>Unknown</Badge>;
}
};
if (isLoading) {
return (
<Card>
<CardContent className="pt-6">
<div className="flex items-center justify-center">
<Loader2 className="h-6 w-6 animate-spin text-muted-foreground" />
</div>
</CardContent>
</Card>
);
}
return (
<div className="space-y-4">
<Card>
<CardHeader>
<div className="flex items-center justify-between">
<div>
<CardTitle>Notification Health Dashboard</CardTitle>
<CardDescription>Monitor duplicate prevention and notification system health</CardDescription>
</div>
<Button variant="outline" size="sm" onClick={loadData} loading={isLoading} loadingText="Loading...">
<RefreshCw className="h-4 w-4 mr-2" />
Refresh
</Button>
</div>
</CardHeader>
<CardContent>
{stats.length === 0 ? (
<Alert>
<AlertDescription>No notification statistics available yet</AlertDescription>
</Alert>
) : (
<Table>
<TableHeader>
<TableRow>
<TableHead>Date</TableHead>
<TableHead className="text-right">Total Attempts</TableHead>
<TableHead className="text-right">Duplicates Prevented</TableHead>
<TableHead className="text-right">Prevention Rate</TableHead>
<TableHead>Status</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{stats.map((stat) => (
<TableRow key={stat.date || 'unknown'}>
<TableCell>{stat.date ? format(new Date(stat.date), 'MMM d, yyyy') : 'N/A'}</TableCell>
<TableCell className="text-right">{stat.total_attempts ?? 0}</TableCell>
<TableCell className="text-right">{stat.duplicates_prevented ?? 0}</TableCell>
<TableCell className="text-right">{stat.prevention_rate !== null ? stat.prevention_rate.toFixed(1) : 'N/A'}%</TableCell>
<TableCell>{getHealthBadge(stat.health_status)}</TableCell>
</TableRow>
))}
</TableBody>
</Table>
)}
</CardContent>
</Card>
<Card>
<CardHeader>
<CardTitle>Recent Prevented Duplicates</CardTitle>
<CardDescription>Notifications that were blocked due to duplication</CardDescription>
</CardHeader>
<CardContent>
{recentDuplicates.length === 0 ? (
<Alert>
<CheckCircle className="h-4 w-4" />
<AlertDescription>No recent duplicates detected</AlertDescription>
</Alert>
) : (
<div className="space-y-2">
{recentDuplicates.map((dup) => (
<div key={dup.id} className="flex items-center justify-between p-3 border rounded-lg">
<div>
<div className="font-medium">
{dup.profiles?.display_name || dup.profiles?.username || 'Unknown User'}
</div>
<div className="text-xs text-muted-foreground">
Channel: {dup.channel} Key: {dup.idempotency_key?.substring(0, 12)}...
</div>
</div>
<div className="text-xs text-muted-foreground">
{format(new Date(dup.created_at), 'PPp')}
</div>
</div>
))}
</div>
)}
</CardContent>
</Card>
</div>
);
}