letsbe-hub/src/components/admin/enterprise-error-summary-wi...

194 lines
7.3 KiB
TypeScript
Raw Normal View History

'use client'
import Link from 'next/link'
import {
AlertCircle,
AlertTriangle,
Zap,
TrendingUp,
TrendingDown,
Minus,
RefreshCw,
Loader2,
Building2,
Server as ServerIcon,
ArrowRight,
} from 'lucide-react'
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
import { Button } from '@/components/ui/button'
import { useAllClientsErrorSummary } from '@/hooks/use-enterprise-clients'
const TREND_CONFIG = {
increasing: {
icon: TrendingUp,
color: 'text-red-600',
bg: 'bg-red-50',
label: 'increasing',
},
decreasing: {
icon: TrendingDown,
color: 'text-green-600',
bg: 'bg-green-50',
label: 'decreasing',
},
stable: {
icon: Minus,
color: 'text-slate-600',
bg: 'bg-slate-50',
label: 'stable',
},
}
export function EnterpriseErrorSummaryWidget() {
const { data, isLoading, isError, refetch, isFetching } = useAllClientsErrorSummary()
if (isLoading) {
return (
<Card>
<CardContent className="flex items-center justify-center h-48">
<Loader2 className="h-6 w-6 animate-spin text-muted-foreground" />
</CardContent>
</Card>
)
}
if (isError || !data) {
return (
<Card>
<CardContent className="flex flex-col items-center justify-center h-48 gap-2">
<AlertCircle className="h-8 w-8 text-destructive" />
<p className="text-sm text-muted-foreground">Failed to load error summary</p>
<Button variant="outline" size="sm" onClick={() => refetch()}>
Retry
</Button>
</CardContent>
</Card>
)
}
const { totals, clients } = data
const hasCriticalIssues = totals.criticalErrors24h > 0 || totals.crashes24h > 0
const clientsWithIssues = clients.filter(
(c) => c.criticalErrors24h > 0 || c.crashes24h > 0
).slice(0, 3)
const trendConfig = TREND_CONFIG[totals.overallTrend]
const TrendIcon = trendConfig.icon
return (
<Card className={hasCriticalIssues ? 'border-red-200 dark:border-red-900' : ''}>
<CardHeader className="flex flex-row items-center justify-between pb-2">
<div className="flex items-center gap-2">
<div className={`p-2 rounded-lg ${hasCriticalIssues ? 'bg-red-100 dark:bg-red-900/30' : 'bg-slate-100 dark:bg-slate-800'}`}>
<AlertTriangle className={`h-4 w-4 ${hasCriticalIssues ? 'text-red-600 dark:text-red-400' : 'text-slate-600 dark:text-slate-400'}`} />
</div>
<CardTitle className="text-base font-semibold">System Health</CardTitle>
{hasCriticalIssues && (
<span className="relative flex h-2 w-2">
<span className="animate-ping absolute inline-flex h-full w-full rounded-full bg-red-400 opacity-75"></span>
<span className="relative inline-flex rounded-full h-2 w-2 bg-red-500"></span>
</span>
)}
</div>
<Button
variant="ghost"
size="sm"
onClick={() => refetch()}
disabled={isFetching}
className="h-8 w-8 p-0"
>
<RefreshCw className={`h-4 w-4 ${isFetching ? 'animate-spin' : ''}`} />
</Button>
</CardHeader>
<CardContent className="space-y-4">
{/* Main Stats */}
<div className="grid grid-cols-2 gap-4">
<div className="space-y-1">
<p className="text-xs text-muted-foreground">Critical Errors (24h)</p>
<div className="flex items-center gap-2">
<Zap className={`h-4 w-4 ${totals.criticalErrors24h > 0 ? 'text-red-600' : 'text-muted-foreground'}`} />
<span className={`text-2xl font-bold tabular-nums ${totals.criticalErrors24h > 0 ? 'text-red-600' : ''}`}>
{totals.criticalErrors24h}
</span>
</div>
</div>
<div className="space-y-1">
<p className="text-xs text-muted-foreground">Crashed Containers (24h)</p>
<div className="flex items-center gap-2">
<ServerIcon className={`h-4 w-4 ${totals.crashes24h > 0 ? 'text-orange-600' : 'text-muted-foreground'}`} />
<span className={`text-2xl font-bold tabular-nums ${totals.crashes24h > 0 ? 'text-orange-600' : ''}`}>
{totals.crashes24h}
</span>
</div>
</div>
</div>
{/* Trend */}
<div className={`flex items-center gap-2 p-2 rounded-lg ${trendConfig.bg}`}>
<TrendIcon className={`h-4 w-4 ${trendConfig.color}`} />
<span className={`text-sm ${trendConfig.color}`}>
Error trend: <span className="font-medium">{trendConfig.label}</span>
</span>
<span className="text-xs text-muted-foreground">
({totals.totalErrors24h} total errors today)
</span>
</div>
{/* Clients with Issues */}
{clientsWithIssues.length > 0 && (
<div className="space-y-2">
<p className="text-xs text-muted-foreground font-medium">
Clients with Issues ({totals.clientsWithIssues} total)
</p>
<div className="space-y-2">
{clientsWithIssues.map((client) => (
<Link
key={client.clientId}
href={`/admin/enterprise-clients/${client.clientId}`}
className="flex items-center justify-between p-2 rounded-lg border hover:bg-muted/50 transition-colors group"
>
<div className="flex items-center gap-2">
<Building2 className="h-4 w-4 text-muted-foreground" />
<span className="text-sm font-medium">{client.clientName}</span>
</div>
<div className="flex items-center gap-3">
{client.criticalErrors24h > 0 && (
<span className="inline-flex items-center gap-1 text-xs bg-red-100 text-red-700 dark:bg-red-900/30 dark:text-red-400 px-2 py-0.5 rounded">
<Zap className="h-3 w-3" />
{client.criticalErrors24h} critical
</span>
)}
{client.crashes24h > 0 && (
<span className="inline-flex items-center gap-1 text-xs bg-orange-100 text-orange-700 dark:bg-orange-900/30 dark:text-orange-400 px-2 py-0.5 rounded">
<AlertCircle className="h-3 w-3" />
{client.crashes24h} crashed
</span>
)}
<ArrowRight className="h-4 w-4 text-muted-foreground opacity-0 group-hover:opacity-100 transition-opacity" />
</div>
</Link>
))}
</div>
</div>
)}
{/* No Issues State */}
{clientsWithIssues.length === 0 && totals.totalClients > 0 && (
<div className="text-center py-4 text-sm text-muted-foreground">
<span className="inline-flex items-center gap-2">
All {totals.totalClients} clients are healthy
</span>
</div>
)}
{/* No Clients State */}
{totals.totalClients === 0 && (
<div className="text-center py-4 text-sm text-muted-foreground">
No enterprise clients configured
</div>
)}
</CardContent>
</Card>
)
}