mirror of
https://github.com/pacnpal/thrilltrack-explorer.git
synced 2025-12-26 11:06:59 -05:00
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:
204
src/components/admin/database-stats/GrowthTrendsChart.tsx
Normal file
204
src/components/admin/database-stats/GrowthTrendsChart.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user