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,107 @@
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>
);
}

View File

@@ -0,0 +1,124 @@
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>
);
}

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>
);
}

View File

@@ -0,0 +1,136 @@
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
import { useEntityComparisons } from '@/hooks/useEntityComparisons';
import { ComparisonTable } from './ComparisonTable';
import { Building2, Factory, Users, Pencil, Image as ImageIcon } from 'lucide-react';
export function EntityComparisonDashboard() {
const { data, isLoading } = useEntityComparisons();
if (isLoading || !data) {
return (
<Card>
<CardHeader>
<CardTitle>Entity Comparisons</CardTitle>
<CardDescription>Loading comparison data...</CardDescription>
</CardHeader>
<CardContent>
<div className="animate-pulse space-y-4">
<div className="h-64 bg-muted rounded" />
</div>
</CardContent>
</Card>
);
}
return (
<Card>
<CardHeader>
<CardTitle>Entity Comparisons</CardTitle>
<CardDescription>Top entities by content volume</CardDescription>
</CardHeader>
<CardContent>
<Tabs defaultValue="parks-rides" className="space-y-4">
<TabsList className="grid w-full grid-cols-5">
<TabsTrigger value="parks-rides">
<Building2 className="h-4 w-4 mr-2" />
Parks
</TabsTrigger>
<TabsTrigger value="manufacturers">
<Factory className="h-4 w-4 mr-2" />
Manufacturers
</TabsTrigger>
<TabsTrigger value="operators">
<Users className="h-4 w-4 mr-2" />
Operators
</TabsTrigger>
<TabsTrigger value="designers">
<Pencil className="h-4 w-4 mr-2" />
Designers
</TabsTrigger>
<TabsTrigger value="photos">
<ImageIcon className="h-4 w-4 mr-2" />
Photos
</TabsTrigger>
</TabsList>
<TabsContent value="parks-rides" className="space-y-4">
<ComparisonTable
title="Top Parks by Ride Count"
data={data.top_parks_by_rides}
columns={[
{ key: 'park_name', label: 'Park Name', linkBase: '/parks' },
{ key: 'ride_count', label: 'Rides', numeric: true },
{ key: 'photo_count', label: 'Photos', numeric: true },
]}
slugKey="park_slug"
/>
</TabsContent>
<TabsContent value="manufacturers" className="space-y-4">
<ComparisonTable
title="Top Manufacturers"
data={data.top_manufacturers}
columns={[
{ key: 'manufacturer_name', label: 'Manufacturer', linkBase: '/manufacturers' },
{ key: 'ride_count', label: 'Rides', numeric: true },
{ key: 'model_count', label: 'Models', numeric: true },
]}
slugKey="slug"
/>
</TabsContent>
<TabsContent value="operators" className="space-y-4">
<ComparisonTable
title="Top Operators"
data={data.top_operators}
columns={[
{ key: 'operator_name', label: 'Operator', linkBase: '/operators' },
{ key: 'park_count', label: 'Parks', numeric: true },
{ key: 'ride_count', label: 'Total Rides', numeric: true },
]}
slugKey="slug"
/>
</TabsContent>
<TabsContent value="designers" className="space-y-4">
<ComparisonTable
title="Top Designers"
data={data.top_designers}
columns={[
{ key: 'designer_name', label: 'Designer', linkBase: '/designers' },
{ key: 'ride_count', label: 'Rides', numeric: true },
]}
slugKey="slug"
/>
</TabsContent>
<TabsContent value="photos" className="space-y-4">
<div className="space-y-6">
<ComparisonTable
title="Top Parks by Photo Count"
data={data.top_parks_by_photos}
columns={[
{ key: 'park_name', label: 'Park Name', linkBase: '/parks' },
{ key: 'photo_count', label: 'Photos', numeric: true },
]}
slugKey="park_slug"
/>
<ComparisonTable
title="Top Rides by Photo Count"
data={data.top_rides_by_photos}
columns={[
{ key: 'ride_name', label: 'Ride Name', linkBase: '/parks' },
{ key: 'photo_count', label: 'Photos', numeric: true },
]}
slugKey="ride_slug"
parkSlugKey="park_slug"
/>
</div>
</TabsContent>
</Tabs>
</CardContent>
</Card>
);
}

View File

@@ -0,0 +1,204 @@
import { useState } from 'react';
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
import { Button } from '@/components/ui/button';
import { useGrowthTrends } from '@/hooks/useGrowthTrends';
import { LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, Legend, ResponsiveContainer } from 'recharts';
import { ChartContainer, ChartTooltip, ChartTooltipContent } from '@/components/ui/chart';
import type { GranularityType } from '@/types/database-analytics';
import { format } from 'date-fns';
const chartConfig = {
parks_added: {
label: "Parks",
color: "hsl(var(--chart-1))",
},
rides_added: {
label: "Rides",
color: "hsl(var(--chart-2))",
},
companies_added: {
label: "Companies",
color: "hsl(var(--chart-3))",
},
ride_models_added: {
label: "Models",
color: "hsl(var(--chart-4))",
},
photos_added: {
label: "Photos",
color: "hsl(var(--chart-5))",
},
} as const;
export function GrowthTrendsChart() {
const [timeRange, setTimeRange] = useState<number>(90);
const [granularity, setGranularity] = useState<GranularityType>('daily');
const [activeLines, setActiveLines] = useState({
parks_added: true,
rides_added: true,
companies_added: true,
ride_models_added: true,
photos_added: true,
});
const { data, isLoading } = useGrowthTrends(timeRange, granularity);
const toggleLine = (key: keyof typeof activeLines) => {
setActiveLines(prev => ({ ...prev, [key]: !prev[key] }));
};
if (isLoading) {
return (
<Card>
<CardHeader>
<CardTitle>Growth Trends</CardTitle>
<CardDescription>Loading growth data...</CardDescription>
</CardHeader>
<CardContent>
<div className="h-80 bg-muted rounded animate-pulse" />
</CardContent>
</Card>
);
}
const formattedData = data?.map(point => ({
...point,
date: format(new Date(point.period), granularity === 'daily' ? 'MMM dd' : granularity === 'weekly' ? 'MMM dd' : 'MMM yyyy'),
})) || [];
return (
<Card>
<CardHeader>
<div className="flex items-center justify-between flex-wrap gap-4">
<div>
<CardTitle>Growth Trends</CardTitle>
<CardDescription>Entity additions over time</CardDescription>
</div>
<div className="flex gap-2 flex-wrap">
{/* Time Range Controls */}
<div className="flex gap-1">
{[
{ label: '7D', days: 7 },
{ label: '30D', days: 30 },
{ label: '90D', days: 90 },
{ label: '1Y', days: 365 },
].map(({ label, days }) => (
<Button
key={label}
variant={timeRange === days ? 'default' : 'outline'}
size="sm"
onClick={() => setTimeRange(days)}
>
{label}
</Button>
))}
</div>
{/* Granularity Controls */}
<div className="flex gap-1">
{(['daily', 'weekly', 'monthly'] as GranularityType[]).map((g) => (
<Button
key={g}
variant={granularity === g ? 'default' : 'outline'}
size="sm"
onClick={() => setGranularity(g)}
className="capitalize"
>
{g}
</Button>
))}
</div>
</div>
</div>
</CardHeader>
<CardContent>
{/* Entity Type Toggles */}
<div className="flex gap-2 mb-4 flex-wrap">
{Object.entries(chartConfig).map(([key, config]) => (
<Button
key={key}
variant={activeLines[key as keyof typeof activeLines] ? 'default' : 'outline'}
size="sm"
onClick={() => toggleLine(key as keyof typeof activeLines)}
>
{config.label}
</Button>
))}
</div>
{/* Chart */}
<ChartContainer config={chartConfig} className="h-80">
<ResponsiveContainer width="100%" height="100%">
<LineChart data={formattedData}>
<CartesianGrid strokeDasharray="3 3" className="stroke-muted" />
<XAxis
dataKey="date"
className="text-xs"
tick={{ fill: 'hsl(var(--muted-foreground))' }}
/>
<YAxis
className="text-xs"
tick={{ fill: 'hsl(var(--muted-foreground))' }}
/>
<ChartTooltip content={<ChartTooltipContent />} />
<Legend />
{activeLines.parks_added && (
<Line
type="monotone"
dataKey="parks_added"
stroke={chartConfig.parks_added.color}
strokeWidth={2}
dot={false}
name={chartConfig.parks_added.label}
/>
)}
{activeLines.rides_added && (
<Line
type="monotone"
dataKey="rides_added"
stroke={chartConfig.rides_added.color}
strokeWidth={2}
dot={false}
name={chartConfig.rides_added.label}
/>
)}
{activeLines.companies_added && (
<Line
type="monotone"
dataKey="companies_added"
stroke={chartConfig.companies_added.color}
strokeWidth={2}
dot={false}
name={chartConfig.companies_added.label}
/>
)}
{activeLines.ride_models_added && (
<Line
type="monotone"
dataKey="ride_models_added"
stroke={chartConfig.ride_models_added.color}
strokeWidth={2}
dot={false}
name={chartConfig.ride_models_added.label}
/>
)}
{activeLines.photos_added && (
<Line
type="monotone"
dataKey="photos_added"
stroke={chartConfig.photos_added.color}
strokeWidth={2}
dot={false}
name={chartConfig.photos_added.label}
/>
)}
</LineChart>
</ResponsiveContainer>
</ChartContainer>
</CardContent>
</Card>
);
}

View File

@@ -0,0 +1,110 @@
import { AccordionContent, AccordionItem, AccordionTrigger } from '@/components/ui/accordion';
import { Badge } from '@/components/ui/badge';
import { Button } from '@/components/ui/button';
import type { HealthIssue } from '@/types/database-analytics';
import { AlertCircle, AlertTriangle, Info, Lightbulb } from 'lucide-react';
interface HealthIssueCardProps {
issue: HealthIssue;
}
export function HealthIssueCard({ issue }: HealthIssueCardProps) {
const getSeverityIcon = () => {
switch (issue.severity) {
case 'critical':
return <AlertCircle className="h-4 w-4 text-red-600" />;
case 'warning':
return <AlertTriangle className="h-4 w-4 text-yellow-600" />;
case 'info':
return <Info className="h-4 w-4 text-blue-600" />;
}
};
const getSeverityColor = () => {
switch (issue.severity) {
case 'critical':
return 'border-red-600 bg-red-50 dark:bg-red-950/20';
case 'warning':
return 'border-yellow-600 bg-yellow-50 dark:bg-yellow-950/20';
case 'info':
return 'border-blue-600 bg-blue-50 dark:bg-blue-950/20';
}
};
const getSeverityBadgeVariant = () => {
switch (issue.severity) {
case 'critical':
return 'destructive';
case 'warning':
return 'default';
case 'info':
return 'secondary';
}
};
return (
<AccordionItem
value={`issue-${issue.category}-${issue.count}`}
className={`border rounded-lg ${getSeverityColor()}`}
>
<AccordionTrigger className="px-4 hover:no-underline">
<div className="flex items-center justify-between w-full pr-4">
<div className="flex items-center gap-3">
{getSeverityIcon()}
<div className="text-left">
<div className="font-semibold">{issue.description}</div>
<div className="text-sm text-muted-foreground capitalize">
{issue.category.replace(/_/g, ' ')}
</div>
</div>
</div>
<Badge variant={getSeverityBadgeVariant()}>
{issue.count} {issue.count === 1 ? 'entity' : 'entities'}
</Badge>
</div>
</AccordionTrigger>
<AccordionContent className="px-4 pb-4 space-y-4">
{/* Suggested Action */}
<div className="flex items-start gap-2 p-3 bg-background rounded border">
<Lightbulb className="h-4 w-4 text-yellow-600 mt-0.5 flex-shrink-0" />
<div className="space-y-1">
<div className="text-sm font-medium">Suggested Action</div>
<div className="text-sm text-muted-foreground">{issue.suggested_action}</div>
</div>
</div>
{/* Entity IDs (first 10) */}
{issue.entity_ids && issue.entity_ids.length > 0 && (
<div className="space-y-2">
<div className="text-sm font-medium">
Affected Entities ({issue.entity_ids.length})
</div>
<div className="flex flex-wrap gap-2">
{issue.entity_ids.slice(0, 10).map((id) => (
<Badge key={id} variant="outline" className="font-mono text-xs">
{id.substring(0, 8)}...
</Badge>
))}
{issue.entity_ids.length > 10 && (
<Badge variant="secondary" className="text-xs">
+{issue.entity_ids.length - 10} more
</Badge>
)}
</div>
</div>
)}
{/* Action Buttons */}
<div className="flex gap-2">
<Button size="sm" variant="default">
View Entities
</Button>
<Button size="sm" variant="outline">
Export List
</Button>
</div>
</AccordionContent>
</AccordionItem>
);
}