mirror of
https://github.com/pacnpal/thrilltrack-explorer.git
synced 2025-12-25 18:31:12 -05:00
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.
125 lines
4.8 KiB
TypeScript
125 lines
4.8 KiB
TypeScript
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
|
|
import { Progress } from '@/components/ui/progress';
|
|
import { Link } from 'react-router-dom';
|
|
import { ArrowRight, CheckCircle2, AlertCircle } from 'lucide-react';
|
|
import { useDataCompleteness } from '@/hooks/useDataCompleteness';
|
|
|
|
export function DataQualityOverview() {
|
|
const { data, isLoading } = useDataCompleteness();
|
|
|
|
if (isLoading || !data) {
|
|
return (
|
|
<Card>
|
|
<CardHeader>
|
|
<CardTitle>Data Quality</CardTitle>
|
|
<CardDescription>Loading completeness metrics...</CardDescription>
|
|
</CardHeader>
|
|
<CardContent>
|
|
<div className="animate-pulse space-y-4">
|
|
<div className="h-20 bg-muted rounded" />
|
|
<div className="h-20 bg-muted rounded" />
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
);
|
|
}
|
|
|
|
const { summary } = data;
|
|
const avgScore = Math.round(summary.avg_completeness_score);
|
|
|
|
const getScoreColor = (score: number) => {
|
|
if (score >= 80) return 'text-green-600';
|
|
if (score >= 60) return 'text-blue-600';
|
|
if (score >= 40) return 'text-yellow-600';
|
|
return 'text-red-600';
|
|
};
|
|
|
|
const getProgressColor = (score: number) => {
|
|
if (score >= 80) return 'bg-green-600';
|
|
if (score >= 60) return 'bg-blue-600';
|
|
if (score >= 40) return 'bg-yellow-600';
|
|
return 'bg-red-600';
|
|
};
|
|
|
|
return (
|
|
<Card>
|
|
<CardHeader>
|
|
<div className="flex items-center justify-between">
|
|
<div>
|
|
<CardTitle>Data Quality</CardTitle>
|
|
<CardDescription>Overall completeness metrics across all entities</CardDescription>
|
|
</div>
|
|
<Link
|
|
to="/admin/data-completeness"
|
|
className="text-sm text-primary hover:text-primary/80 flex items-center gap-1"
|
|
>
|
|
View Details <ArrowRight className="h-4 w-4" />
|
|
</Link>
|
|
</div>
|
|
</CardHeader>
|
|
<CardContent className="space-y-6">
|
|
{/* Average Score */}
|
|
<div>
|
|
<div className="flex items-center justify-between mb-2">
|
|
<span className="text-sm font-medium">Average Completeness</span>
|
|
<span className={`text-3xl font-bold ${getScoreColor(avgScore)}`}>
|
|
{avgScore}%
|
|
</span>
|
|
</div>
|
|
<div className="relative">
|
|
<Progress value={avgScore} className="h-3" />
|
|
<div
|
|
className={`absolute inset-0 rounded-full ${getProgressColor(avgScore)} transition-all`}
|
|
style={{ width: `${avgScore}%` }}
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Quick Stats Grid */}
|
|
<div className="grid grid-cols-2 gap-4">
|
|
<div className="space-y-1">
|
|
<div className="flex items-center gap-2">
|
|
<CheckCircle2 className="h-4 w-4 text-green-600" />
|
|
<span className="text-sm font-medium">100% Complete</span>
|
|
</div>
|
|
<div className="text-2xl font-bold">{summary.entities_100_complete}</div>
|
|
<div className="text-xs text-muted-foreground">
|
|
{((summary.entities_100_complete / summary.total_entities) * 100).toFixed(1)}% of total
|
|
</div>
|
|
</div>
|
|
|
|
<div className="space-y-1">
|
|
<div className="flex items-center gap-2">
|
|
<AlertCircle className="h-4 w-4 text-yellow-600" />
|
|
<span className="text-sm font-medium">Below 50%</span>
|
|
</div>
|
|
<div className="text-2xl font-bold">{summary.entities_below_50}</div>
|
|
<div className="text-xs text-muted-foreground">
|
|
{((summary.entities_below_50 / summary.total_entities) * 100).toFixed(1)}% need attention
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* By Entity Type */}
|
|
<div className="space-y-3">
|
|
<h4 className="text-sm font-medium">By Entity Type</h4>
|
|
<div className="space-y-2">
|
|
{[
|
|
{ label: 'Parks', value: summary.by_entity_type.parks, total: summary.total_entities },
|
|
{ label: 'Rides', value: summary.by_entity_type.rides, total: summary.total_entities },
|
|
{ label: 'Companies', value: summary.by_entity_type.companies, total: summary.total_entities },
|
|
{ label: 'Models', value: summary.by_entity_type.ride_models, total: summary.total_entities },
|
|
].map((item) => (
|
|
<div key={item.label} className="flex items-center gap-2">
|
|
<span className="text-xs w-20">{item.label}</span>
|
|
<Progress value={(item.value / item.total) * 100} className="h-2 flex-1" />
|
|
<span className="text-xs text-muted-foreground w-12 text-right">{item.value}</span>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
);
|
|
}
|