feat: restyle statistics tab — stat cards, charts, recent sessions table
This commit is contained in:
@@ -29,8 +29,8 @@ export function DailyActivityChart({ data }: DailyActivityChartProps) {
|
|||||||
{
|
{
|
||||||
label: 'Sessions',
|
label: 'Sessions',
|
||||||
data: data.map((item) => item.sessionsCount),
|
data: data.map((item) => item.sessionsCount),
|
||||||
backgroundColor: 'rgba(59, 130, 246, 0.8)',
|
backgroundColor: '#3fb950',
|
||||||
borderColor: 'rgb(59, 130, 246)',
|
borderColor: '#3fb950',
|
||||||
borderWidth: 1
|
borderWidth: 1
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@@ -43,27 +43,27 @@ export function DailyActivityChart({ data }: DailyActivityChartProps) {
|
|||||||
legend: {
|
legend: {
|
||||||
position: 'top' as const,
|
position: 'top' as const,
|
||||||
labels: {
|
labels: {
|
||||||
color: '#e5e7eb'
|
color: '#e6edf3'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
scales: {
|
scales: {
|
||||||
x: {
|
x: {
|
||||||
ticks: {
|
ticks: {
|
||||||
color: '#9ca3af'
|
color: '#8b949e'
|
||||||
},
|
},
|
||||||
grid: {
|
grid: {
|
||||||
color: '#374151'
|
color: '#21262d'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
y: {
|
y: {
|
||||||
beginAtZero: true,
|
beginAtZero: true,
|
||||||
ticks: {
|
ticks: {
|
||||||
color: '#9ca3af',
|
color: '#8b949e',
|
||||||
stepSize: 1
|
stepSize: 1
|
||||||
},
|
},
|
||||||
grid: {
|
grid: {
|
||||||
color: '#374151'
|
color: '#21262d'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -43,8 +43,8 @@ export function PlayerCountChart({ data }: PlayerCountChartProps) {
|
|||||||
x: new Date(point.timestamp),
|
x: new Date(point.timestamp),
|
||||||
y: point.count
|
y: point.count
|
||||||
})),
|
})),
|
||||||
borderColor: 'rgb(59, 130, 246)',
|
borderColor: '#58a6ff',
|
||||||
backgroundColor: 'rgba(59, 130, 246, 0.1)',
|
backgroundColor: '#58a6ff1a',
|
||||||
tension: 0.4
|
tension: 0.4
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@@ -57,7 +57,7 @@ export function PlayerCountChart({ data }: PlayerCountChartProps) {
|
|||||||
legend: {
|
legend: {
|
||||||
position: 'top' as const,
|
position: 'top' as const,
|
||||||
labels: {
|
labels: {
|
||||||
color: '#e5e7eb'
|
color: '#e6edf3'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -68,20 +68,20 @@ export function PlayerCountChart({ data }: PlayerCountChartProps) {
|
|||||||
unit: 'hour' as const
|
unit: 'hour' as const
|
||||||
},
|
},
|
||||||
ticks: {
|
ticks: {
|
||||||
color: '#9ca3af'
|
color: '#8b949e'
|
||||||
},
|
},
|
||||||
grid: {
|
grid: {
|
||||||
color: '#374151'
|
color: '#21262d'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
y: {
|
y: {
|
||||||
beginAtZero: true,
|
beginAtZero: true,
|
||||||
ticks: {
|
ticks: {
|
||||||
color: '#9ca3af',
|
color: '#8b949e',
|
||||||
stepSize: 1
|
stepSize: 1
|
||||||
},
|
},
|
||||||
grid: {
|
grid: {
|
||||||
color: '#374151'
|
color: '#21262d'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,51 +29,52 @@ export function RecentSessions({ sessions }: RecentSessionsProps) {
|
|||||||
|
|
||||||
if (sessions.length === 0) {
|
if (sessions.length === 0) {
|
||||||
return (
|
return (
|
||||||
<div className="py-8 text-center">
|
<div className="px-4 py-8 text-center text-sm text-gh-muted">No recent sessions found</div>
|
||||||
<div className="mb-2 text-4xl">📊</div>
|
|
||||||
<p className="text-gray-400">No recent sessions found</p>
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="overflow-x-auto">
|
<div className="overflow-x-auto">
|
||||||
<table className="min-w-full divide-y divide-gray-700">
|
<table className="min-w-full">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr className="border-b border-gh-border-muted bg-gh-base">
|
||||||
<th className="px-6 py-3 text-left text-xs font-medium tracking-wider text-gray-400 uppercase">
|
<th className="px-4 py-2 text-left text-xs font-medium uppercase tracking-wider text-gh-subtle">
|
||||||
Date
|
Date
|
||||||
</th>
|
</th>
|
||||||
<th className="px-6 py-3 text-left text-xs font-medium tracking-wider text-gray-400 uppercase">
|
<th className="px-4 py-2 text-left text-xs font-medium uppercase tracking-wider text-gh-subtle">
|
||||||
Type
|
Type
|
||||||
</th>
|
</th>
|
||||||
<th className="px-6 py-3 text-left text-xs font-medium tracking-wider text-gray-400 uppercase">
|
<th className="px-4 py-2 text-left text-xs font-medium uppercase tracking-wider text-gh-subtle">
|
||||||
Track
|
Track
|
||||||
</th>
|
</th>
|
||||||
<th className="px-6 py-3 text-left text-xs font-medium tracking-wider text-gray-400 uppercase">
|
<th className="px-4 py-2 text-left text-xs font-medium uppercase tracking-wider text-gh-subtle">
|
||||||
Duration
|
Duration
|
||||||
</th>
|
</th>
|
||||||
<th className="px-6 py-3 text-left text-xs font-medium tracking-wider text-gray-400 uppercase">
|
<th className="px-4 py-2 text-left text-xs font-medium uppercase tracking-wider text-gh-subtle">
|
||||||
Players
|
Players
|
||||||
</th>
|
</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody className="divide-y divide-gray-700">
|
<tbody>
|
||||||
{sessions.map((session) => (
|
{sessions.map((session) => (
|
||||||
<tr key={session.id} className="transition-colors hover:bg-gray-700">
|
<tr key={session.id} className="border-b border-gh-border-muted transition-colors hover:bg-gh-hover last:border-0">
|
||||||
<td className="px-6 py-4 text-sm whitespace-nowrap text-gray-300">
|
<td className="px-4 py-3 text-sm text-gh-muted">
|
||||||
{formatDate(session.date)}
|
{formatDate(session.date)}
|
||||||
</td>
|
</td>
|
||||||
<td className="px-6 py-4 text-sm whitespace-nowrap text-gray-300">
|
<td className="px-4 py-3">
|
||||||
<span className="inline-flex items-center rounded-full bg-blue-900 px-2.5 py-0.5 text-xs font-medium text-blue-300">
|
<span className={`inline-flex items-center rounded-full px-2 py-0.5 text-xs font-medium ${
|
||||||
{session.type}
|
session.type === 'R' ? 'bg-gh-green-bg text-gh-green' :
|
||||||
|
session.type === 'Q' ? 'bg-gh-yellow-bg text-gh-yellow' :
|
||||||
|
'bg-gh-blue-bg text-gh-blue'
|
||||||
|
}`}>
|
||||||
|
{session.type === 'R' ? 'Race' : session.type === 'Q' ? 'Qualify' : 'Practice'}
|
||||||
</span>
|
</span>
|
||||||
</td>
|
</td>
|
||||||
<td className="px-6 py-4 text-sm whitespace-nowrap text-gray-300">{session.track}</td>
|
<td className="px-4 py-3 text-sm text-gh-secondary">{session.track}</td>
|
||||||
<td className="px-6 py-4 text-sm whitespace-nowrap text-gray-300">
|
<td className="px-4 py-3 text-sm text-gh-muted">
|
||||||
{formatDuration(session.duration)}
|
{formatDuration(session.duration)}
|
||||||
</td>
|
</td>
|
||||||
<td className="px-6 py-4 text-sm whitespace-nowrap text-gray-300">
|
<td className="px-4 py-3 text-sm text-gh-secondary">
|
||||||
{session.players}
|
{session.players}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|||||||
@@ -15,12 +15,10 @@ interface SessionTypesChartProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const colors = [
|
const colors = [
|
||||||
'#3b82f6', // blue
|
'#3fb950', // gh-green
|
||||||
'#10b981', // emerald
|
'#58a6ff', // gh-blue
|
||||||
'#f59e0b', // amber
|
'#f78166', // gh-red
|
||||||
'#ef4444', // red
|
'#d29922' // gh-yellow
|
||||||
'#8b5cf6', // violet
|
|
||||||
'#06b6d4' // cyan
|
|
||||||
];
|
];
|
||||||
|
|
||||||
export function SessionTypesChart({ data }: SessionTypesChartProps) {
|
export function SessionTypesChart({ data }: SessionTypesChartProps) {
|
||||||
@@ -43,7 +41,7 @@ export function SessionTypesChart({ data }: SessionTypesChartProps) {
|
|||||||
legend: {
|
legend: {
|
||||||
position: 'bottom' as const,
|
position: 'bottom' as const,
|
||||||
labels: {
|
labels: {
|
||||||
color: '#e5e7eb',
|
color: '#e6edf3',
|
||||||
padding: 20
|
padding: 20
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -6,16 +6,12 @@ interface StatCardProps {
|
|||||||
|
|
||||||
export function StatCard({ title, value, icon }: StatCardProps) {
|
export function StatCard({ title, value, icon }: StatCardProps) {
|
||||||
return (
|
return (
|
||||||
<div className="rounded-lg bg-gray-800 p-6">
|
<div className="rounded-lg border border-gh-border bg-gh-canvas p-4">
|
||||||
<div className="flex items-center">
|
<div className="mb-1 flex items-center gap-2">
|
||||||
<div className="flex-shrink-0">
|
<span className="text-base">{icon}</span>
|
||||||
<span className="text-3xl">{icon}</span>
|
<span className="text-xs text-gh-muted">{title}</span>
|
||||||
</div>
|
|
||||||
<div className="ml-4">
|
|
||||||
<div className="text-2xl font-semibold text-white">{value}</div>
|
|
||||||
<div className="text-sm text-gray-400">{title}</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
<div className="text-2xl font-bold text-gh-primary">{value}</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,17 +14,18 @@ interface StatisticsDashboardProps {
|
|||||||
export function StatisticsDashboard({ stats }: StatisticsDashboardProps) {
|
export function StatisticsDashboard({ stats }: StatisticsDashboardProps) {
|
||||||
if (!stats) {
|
if (!stats) {
|
||||||
return (
|
return (
|
||||||
<div className="py-12 text-center">
|
<div className="py-16 text-center">
|
||||||
<div className="mb-4 text-6xl">📊</div>
|
<div className="mb-3 text-5xl">📊</div>
|
||||||
<h3 className="mb-2 text-xl font-semibold text-white">No Statistics Available</h3>
|
<h3 className="mb-1 text-base font-semibold text-gh-primary">No Statistics Available</h3>
|
||||||
<p className="text-gray-400">No data found for the selected date range.</p>
|
<p className="text-sm text-gh-muted">No data found for the selected date range.</p>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-6">
|
<div className="flex flex-col gap-4">
|
||||||
<div className="grid grid-cols-1 gap-6 md:grid-cols-2 lg:grid-cols-4">
|
{/* 4 stat cards */}
|
||||||
|
<div className="grid grid-cols-1 gap-3 sm:grid-cols-2 lg:grid-cols-4">
|
||||||
<StatCard title="Total Sessions" value={stats.totalSessions ?? 0} icon="🏁" />
|
<StatCard title="Total Sessions" value={stats.totalSessions ?? 0} icon="🏁" />
|
||||||
<StatCard
|
<StatCard
|
||||||
title="Total Playtime"
|
title="Total Playtime"
|
||||||
@@ -39,27 +40,26 @@ export function StatisticsDashboard({ stats }: StatisticsDashboardProps) {
|
|||||||
<StatCard title="Peak Players" value={stats.peakPlayers ?? 0} icon="🔥" />
|
<StatCard title="Peak Players" value={stats.peakPlayers ?? 0} icon="🔥" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Charts */}
|
{/* Charts row */}
|
||||||
<div className="grid grid-cols-12 gap-4">
|
<div className="grid grid-cols-1 gap-4 lg:grid-cols-3">
|
||||||
<div className="col-span-9 rounded-lg bg-gray-800 p-6">
|
<div className="lg:col-span-2 rounded-lg border border-gh-border bg-gh-canvas p-4">
|
||||||
<h3 className="mb-4 text-lg font-medium text-white">Player Count Over Time</h3>
|
<h3 className="mb-3 text-sm font-semibold text-gh-primary">Player Count Over Time</h3>
|
||||||
<PlayerCountChart data={stats.playerCountOverTime ?? []} />
|
<PlayerCountChart data={stats.playerCountOverTime ?? []} />
|
||||||
</div>
|
</div>
|
||||||
|
<div className="rounded-lg border border-gh-border bg-gh-canvas p-4">
|
||||||
<div className="col-span-3 rounded-lg bg-gray-800 p-6">
|
<h3 className="mb-3 text-sm font-semibold text-gh-primary">Session Types</h3>
|
||||||
<h3 className="mb-4 text-lg font-medium text-white">Session Types</h3>
|
|
||||||
<SessionTypesChart data={stats.sessionTypes ?? []} />
|
<SessionTypesChart data={stats.sessionTypes ?? []} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="rounded-lg bg-gray-800 p-6">
|
<div className="rounded-lg border border-gh-border bg-gh-canvas p-4">
|
||||||
<h3 className="mb-4 text-lg font-medium text-white">Daily Activity</h3>
|
<h3 className="mb-3 text-sm font-semibold text-gh-primary">Daily Activity</h3>
|
||||||
<DailyActivityChart data={stats.dailyActivity ?? []} />
|
<DailyActivityChart data={stats.dailyActivity ?? []} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Recent Sessions */}
|
{/* Recent sessions */}
|
||||||
<div className="rounded-lg bg-gray-800 p-6">
|
<div className="overflow-hidden rounded-lg border border-gh-border bg-gh-canvas">
|
||||||
<h3 className="mb-4 text-lg font-medium text-white">Recent Sessions</h3>
|
<div className="border-b border-gh-border-muted px-4 py-3 text-sm font-semibold text-gh-primary">Recent Sessions</div>
|
||||||
<RecentSessions sessions={stats.recentSessions ?? []} />
|
<RecentSessions sessions={stats.recentSessions ?? []} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user