mirror of
https://github.com/pacnpal/thrilltrack-explorer.git
synced 2025-12-20 05:51: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.
108 lines
3.6 KiB
TypeScript
108 lines
3.6 KiB
TypeScript
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table';
|
|
import { Progress } from '@/components/ui/progress';
|
|
import { Link } from 'react-router-dom';
|
|
import { ExternalLink } from 'lucide-react';
|
|
|
|
interface Column {
|
|
key: string;
|
|
label: string;
|
|
numeric?: boolean;
|
|
linkBase?: string;
|
|
}
|
|
|
|
interface ComparisonTableProps {
|
|
title: string;
|
|
data: any[];
|
|
columns: Column[];
|
|
slugKey: string;
|
|
parkSlugKey?: string;
|
|
}
|
|
|
|
export function ComparisonTable({ title, data, columns, slugKey, parkSlugKey }: ComparisonTableProps) {
|
|
if (!data || data.length === 0) {
|
|
return (
|
|
<div className="text-center py-8 text-muted-foreground">
|
|
No data available
|
|
</div>
|
|
);
|
|
}
|
|
|
|
// Find the max value for each numeric column (for progress bars)
|
|
const maxValues: Record<string, number> = {};
|
|
columns.forEach(col => {
|
|
if (col.numeric) {
|
|
maxValues[col.key] = Math.max(...data.map(row => row[col.key] || 0));
|
|
}
|
|
});
|
|
|
|
return (
|
|
<div className="space-y-2">
|
|
<h3 className="text-lg font-semibold">{title}</h3>
|
|
<div className="border rounded-lg">
|
|
<Table>
|
|
<TableHeader>
|
|
<TableRow>
|
|
<TableHead className="w-12">Rank</TableHead>
|
|
{columns.map(col => (
|
|
<TableHead key={col.key} className={col.numeric ? 'text-right' : ''}>
|
|
{col.label}
|
|
</TableHead>
|
|
))}
|
|
</TableRow>
|
|
</TableHeader>
|
|
<TableBody>
|
|
{data.map((row, index) => {
|
|
const slug = row[slugKey];
|
|
const parkSlug = parkSlugKey ? row[parkSlugKey] : null;
|
|
|
|
return (
|
|
<TableRow key={index}>
|
|
<TableCell className="font-medium text-muted-foreground">
|
|
#{index + 1}
|
|
</TableCell>
|
|
{columns.map(col => {
|
|
const value = row[col.key];
|
|
const isFirst = col === columns[0];
|
|
|
|
if (isFirst && col.linkBase && slug) {
|
|
const linkPath = parkSlug
|
|
? `${col.linkBase}/${parkSlug}/rides/${slug}`
|
|
: `${col.linkBase}/${slug}`;
|
|
|
|
return (
|
|
<TableCell key={col.key}>
|
|
<Link
|
|
to={linkPath}
|
|
className="flex items-center gap-2 hover:text-primary transition-colors"
|
|
>
|
|
{value}
|
|
<ExternalLink className="h-3 w-3" />
|
|
</Link>
|
|
</TableCell>
|
|
);
|
|
}
|
|
|
|
if (col.numeric) {
|
|
const percentage = (value / maxValues[col.key]) * 100;
|
|
return (
|
|
<TableCell key={col.key} className="text-right">
|
|
<div className="flex items-center justify-end gap-2">
|
|
<span className="font-semibold min-w-12">{value}</span>
|
|
<Progress value={percentage} className="h-2 w-24" />
|
|
</div>
|
|
</TableCell>
|
|
);
|
|
}
|
|
|
|
return <TableCell key={col.key}>{value}</TableCell>;
|
|
})}
|
|
</TableRow>
|
|
);
|
|
})}
|
|
</TableBody>
|
|
</Table>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|