Enhance admin stats dashboard

Add data quality metrics, growth trends visualization, entity comparison views, and automated health checks to the AdminDatabaseStats dashboard, including new TS types, hooks, UI components, and integrated tabbed layout.
This commit is contained in:
gpt-engineer-app[bot]
2025-11-11 17:11:11 +00:00
parent f036776dce
commit 947964482f
14 changed files with 1579 additions and 88 deletions

View File

@@ -0,0 +1,159 @@
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
import { useDatabaseHealthCheck } from '@/hooks/useDatabaseHealthCheck';
import { AlertCircle, AlertTriangle, Info, CheckCircle2 } from 'lucide-react';
import { Progress } from '@/components/ui/progress';
import { HealthIssueCard } from './HealthIssueCard';
import { Accordion } from '@/components/ui/accordion';
export function DatabaseHealthDashboard() {
const { data, isLoading } = useDatabaseHealthCheck();
if (isLoading || !data) {
return (
<Card>
<CardHeader>
<CardTitle>Database Health</CardTitle>
<CardDescription>Loading health checks...</CardDescription>
</CardHeader>
<CardContent>
<div className="animate-pulse space-y-4">
<div className="h-32 bg-muted rounded" />
<div className="h-64 bg-muted rounded" />
</div>
</CardContent>
</Card>
);
}
const { overall_score, critical_issues, warning_issues, info_issues, issues } = data;
const getScoreColor = (score: number) => {
if (score >= 80) return 'text-green-600';
if (score >= 60) return 'text-yellow-600';
if (score >= 40) return 'text-orange-600';
return 'text-red-600';
};
const getScoreBackground = (score: number) => {
if (score >= 80) return 'bg-green-600';
if (score >= 60) return 'bg-yellow-600';
if (score >= 40) return 'bg-orange-600';
return 'bg-red-600';
};
const criticalIssues = issues.filter(i => i.severity === 'critical');
const warningIssues = issues.filter(i => i.severity === 'warning');
const infoIssues = issues.filter(i => i.severity === 'info');
return (
<Card>
<CardHeader>
<CardTitle>Database Health</CardTitle>
<CardDescription>Automated health checks and data quality issues</CardDescription>
</CardHeader>
<CardContent className="space-y-6">
{/* Overall Health Score */}
<div className="flex items-center justify-between p-6 border rounded-lg bg-card">
<div className="space-y-2">
<h3 className="text-sm font-medium text-muted-foreground">Overall Health Score</h3>
<div className={`text-6xl font-bold ${getScoreColor(overall_score)}`}>
{overall_score}
</div>
<p className="text-sm text-muted-foreground">Out of 100</p>
</div>
<div className="flex flex-col gap-3">
<div className="flex items-center gap-3">
<AlertCircle className="h-5 w-5 text-red-600" />
<span className="text-sm font-medium">Critical Issues:</span>
<span className="text-lg font-bold">{critical_issues}</span>
</div>
<div className="flex items-center gap-3">
<AlertTriangle className="h-5 w-5 text-yellow-600" />
<span className="text-sm font-medium">Warnings:</span>
<span className="text-lg font-bold">{warning_issues}</span>
</div>
<div className="flex items-center gap-3">
<Info className="h-5 w-5 text-blue-600" />
<span className="text-sm font-medium">Info:</span>
<span className="text-lg font-bold">{info_issues}</span>
</div>
</div>
</div>
{/* Progress Bar */}
<div className="space-y-2">
<div className="flex justify-between text-sm">
<span>Database Health</span>
<span className={getScoreColor(overall_score)}>{overall_score}%</span>
</div>
<div className="relative">
<Progress value={overall_score} className="h-3" />
<div
className={`absolute inset-0 rounded-full ${getScoreBackground(overall_score)} transition-all`}
style={{ width: `${overall_score}%` }}
/>
</div>
</div>
{/* Issues List */}
{issues.length === 0 ? (
<div className="text-center py-12">
<CheckCircle2 className="h-16 w-16 text-green-600 mx-auto mb-4" />
<h3 className="text-xl font-semibold mb-2">All Systems Healthy!</h3>
<p className="text-muted-foreground">
No database health issues detected at this time.
</p>
</div>
) : (
<div className="space-y-4">
{/* Critical Issues */}
{criticalIssues.length > 0 && (
<div className="space-y-2">
<h3 className="text-lg font-semibold text-red-600 flex items-center gap-2">
<AlertCircle className="h-5 w-5" />
Critical Issues ({criticalIssues.length})
</h3>
<Accordion type="multiple" className="space-y-2">
{criticalIssues.map((issue, index) => (
<HealthIssueCard key={index} issue={issue} />
))}
</Accordion>
</div>
)}
{/* Warnings */}
{warningIssues.length > 0 && (
<div className="space-y-2">
<h3 className="text-lg font-semibold text-yellow-600 flex items-center gap-2">
<AlertTriangle className="h-5 w-5" />
Warnings ({warningIssues.length})
</h3>
<Accordion type="multiple" className="space-y-2">
{warningIssues.map((issue, index) => (
<HealthIssueCard key={index} issue={issue} />
))}
</Accordion>
</div>
)}
{/* Info */}
{infoIssues.length > 0 && (
<div className="space-y-2">
<h3 className="text-lg font-semibold text-blue-600 flex items-center gap-2">
<Info className="h-5 w-5" />
Information ({infoIssues.length})
</h3>
<Accordion type="multiple" className="space-y-2">
{infoIssues.map((issue, index) => (
<HealthIssueCard key={index} issue={issue} />
))}
</Accordion>
</div>
)}
</div>
)}
</CardContent>
</Card>
);
}