duelfi_admin/app/dashboard/page.tsx
2025-06-08 22:04:00 +05:30

292 lines
7.6 KiB
TypeScript

'use client';
import { useState, useEffect } from 'react';
import { Game, User, fetchGames, fetchUsers, getDisplayGameName } from '../utils/api';
import Link from 'next/link';
import { Line, Bar } from 'react-chartjs-2';
import {
Chart as ChartJS,
CategoryScale,
LinearScale,
PointElement,
LineElement,
BarElement,
Title,
Tooltip,
Legend
} from 'chart.js';
ChartJS.register(
CategoryScale,
LinearScale,
PointElement,
LineElement,
BarElement,
Title,
Tooltip,
Legend
);
type TimeRange = '7d' | '30d' | '90d';
export default function Dashboard() {
const [users, setUsers] = useState<User[]>([]);
const [games, setGames] = useState<Game[]>([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
const [gameTimeRange, setGameTimeRange] = useState<TimeRange>('30d');
useEffect(() => {
const loadData = async () => {
try {
const [gamesData, usersData] = await Promise.all([
fetchGames(),
fetchUsers()
]);
setGames(gamesData);
setUsers(usersData);
} catch (err) {
setError('Failed to load data. Please try again later.');
console.error('Error loading data:', err);
} finally {
setLoading(false);
}
};
loadData();
}, []);
const getDaysFromTimeRange = (range: TimeRange): number => {
switch (range) {
case '7d': return 7;
case '30d': return 30;
case '90d': return 90;
}
};
const getGameCountData = () => {
const days = getDaysFromTimeRange(gameTimeRange);
const lastDays = Array.from({ length: days }, (_, i) => {
const date = new Date();
date.setDate(date.getDate() - i);
return date.toISOString().split('T')[0];
}).reverse();
const gameCounts = lastDays.map(date => {
return games.filter(game => {
try {
const gameDate = new Date(game.ended_time).toISOString().split('T')[0];
return gameDate === date;
} catch (err) {
console.error('Error parsing date:', err);
return false;
}
}).length;
});
return {
labels: lastDays.map(date => new Date(date).toLocaleDateString('en-US', { month: 'short', day: 'numeric' })),
datasets: [
{
label: 'Games per Day',
data: gameCounts,
borderColor: '#FFA500',
backgroundColor: 'rgba(255, 165, 0, 0.2)',
tension: 0.4,
fill: true
}
]
};
};
const getGamePopularityData = () => {
const gameTypes = Array.from(new Set(games.map(game => game.game)));
const gameCounts = gameTypes.map(type => ({
type,
displayName: getDisplayGameName(type),
count: games.filter(game => game.game === type).length
})).sort((a, b) => b.count - a.count);
return {
labels: gameCounts.map(game => game.displayName),
datasets: [
{
label: 'Games Played',
data: gameCounts.map(game => game.count),
backgroundColor: 'rgba(255, 165, 0, 0.7)',
borderColor: '#FFA500',
borderWidth: 1
}
]
};
};
const chartOptions = {
responsive: true,
plugins: {
legend: {
display: false,
labels: {
color: '#ffffff'
}
},
tooltip: {
titleColor: '#ffffff',
bodyColor: '#ffffff',
backgroundColor: 'rgba(0, 0, 0, 0.8)',
borderColor: 'rgba(255, 255, 255, 0.1)',
borderWidth: 1
}
},
scales: {
y: {
beginAtZero: true,
grid: {
color: 'rgba(255, 255, 255, 0.1)'
},
ticks: {
color: '#ffffff'
}
},
x: {
grid: {
color: 'rgba(255, 255, 255, 0.1)'
},
ticks: {
color: '#ffffff'
}
}
}
};
const barChartOptions = {
responsive: true,
plugins: {
legend: {
display: false,
labels: {
color: '#ffffff'
}
},
tooltip: {
titleColor: '#ffffff',
bodyColor: '#ffffff',
backgroundColor: 'rgba(0, 0, 0, 0.8)',
borderColor: 'rgba(255, 255, 255, 0.1)',
borderWidth: 1
}
},
scales: {
y: {
beginAtZero: true,
grid: {
color: 'rgba(255, 255, 255, 0.1)'
},
ticks: {
color: '#ffffff'
}
},
x: {
grid: {
color: 'rgba(255, 255, 255, 0.1)'
},
ticks: {
color: '#ffffff'
}
}
}
};
const TimeRangeButton = ({
range,
currentRange,
onChange
}: {
range: TimeRange;
currentRange: TimeRange;
onChange: (range: TimeRange) => void;
}) => (
<button
onClick={() => onChange(range)}
className={`px-3 py-1 text-sm rounded-md transition-colors ${
currentRange === range
? 'bg-[var(--accent)] text-white'
: 'bg-[var(--card-bg)] text-[var(--text-secondary)] hover:bg-[var(--card-border)]'
}`}
>
{range}
</button>
);
if (loading) {
return (
<div className="flex items-center justify-center min-h-screen">
<div className="text-lg text-[var(--text-primary)]">Loading...</div>
</div>
);
}
if (error) {
return (
<div className="flex items-center justify-center min-h-screen">
<div className="text-[var(--accent)]">{error}</div>
</div>
);
}
return (
<div className="space-y-6">
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
{/* Users Card */}
<div className="bg-[var(--card-bg)] border border-[var(--card-border)] rounded-lg p-6">
<div className="flex justify-between items-center">
<h2 className="text-xl font-semibold text-[var(--text-primary)]">
Users - {users.length}
</h2>
<Link
href="/users"
className="text-[var(--accent)] hover:text-[var(--accent-hover)] transition-colors"
>
View All
</Link>
</div>
</div>
{/* Game Count Card */}
<div className="bg-[var(--card-bg)] border border-[var(--card-border)] rounded-lg p-6">
<div className="flex justify-between items-center mb-4">
<h2 className="text-xl font-semibold text-[var(--text-primary)]">
Games - {games.length}
</h2>
<Link
href="/games"
className="text-[var(--accent)] hover:text-[var(--accent-hover)] transition-colors"
>
View All
</Link>
</div>
<div className="flex justify-end space-x-2 mb-4">
<TimeRangeButton range="7d" currentRange={gameTimeRange} onChange={setGameTimeRange} />
<TimeRangeButton range="30d" currentRange={gameTimeRange} onChange={setGameTimeRange} />
<TimeRangeButton range="90d" currentRange={gameTimeRange} onChange={setGameTimeRange} />
</div>
<div className="h-[300px]">
<Line data={getGameCountData()} options={chartOptions} />
</div>
</div>
</div>
{/* Game Popularity Card */}
<div className="bg-[var(--card-bg)] border border-[var(--card-border)] rounded-lg p-6">
<div className="flex justify-between items-center mb-4">
<h2 className="text-xl font-semibold text-[var(--text-primary)]">
Game Popularity
</h2>
</div>
<div className="h-[300px]">
<Bar data={getGamePopularityData()} options={barChartOptions} />
</div>
</div>
</div>
);
}