duelfi_admin/app/dashboard/page.tsx
2025-06-05 20:50:51 +05:30

282 lines
10 KiB
TypeScript

'use client';
import { useState, useEffect, useMemo } from 'react';
import { Game, User, fetchGames, fetchUsers, truncateAddress } from '../utils/api';
import Pagination from '../components/Pagination';
import SearchInput from '../components/SearchInput';
const ITEMS_PER_PAGE = 20;
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 [currentUserPage, setCurrentUserPage] = useState(1);
const [currentGamePage, setCurrentGamePage] = useState(1);
const [userSearch, setUserSearch] = useState('');
const [gameSearch, setGameSearch] = useState('');
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();
}, []);
useEffect(() => {
// Reset to first page when search changes
setCurrentUserPage(1);
}, [userSearch]);
useEffect(() => {
// Reset to first page when search changes
setCurrentGamePage(1);
}, [gameSearch]);
const formatDate = (dateStr: string) => {
const date = new Date(dateStr);
return date.toLocaleString('en-US', {
year: 'numeric',
month: 'short',
day: '2-digit',
hour: '2-digit',
minute: '2-digit'
});
};
const getGameResult = (game: Game) => {
const masterScore = parseInt(game.master_score);
const clientScore = parseInt(game.client_score);
if (masterScore === clientScore) return 'Draw';
return game.winner === 'master' ? 'Master Won' : 'Client Won';
};
// Filter and paginate users
const filteredUsers = useMemo(() => {
if (!userSearch) return users;
const searchLower = userSearch.toLowerCase();
return users.filter(user =>
user.ref_id.toLowerCase().includes(searchLower) ||
(user.username || '').toLowerCase().includes(searchLower) ||
(user.active_wallet || '').toLowerCase().includes(searchLower) ||
(user.x_profile_url || '').toLowerCase().includes(searchLower) ||
(user.referred_id || '').toLowerCase().includes(searchLower)
);
}, [users, userSearch]);
// Filter and paginate games
const filteredGames = useMemo(() => {
if (!gameSearch) return games;
const searchLower = gameSearch.toLowerCase();
return games.filter(game =>
game.ended_time.toLowerCase().includes(searchLower) ||
game.game.toLowerCase().includes(searchLower) ||
game.winner.toLowerCase().includes(searchLower) ||
game.master_id.toLowerCase().includes(searchLower) ||
game.client_id.toLowerCase().includes(searchLower) ||
game.master_score.includes(searchLower) ||
game.client_score.includes(searchLower) ||
game.wager.includes(searchLower)
);
}, [games, gameSearch]);
const currentUsers = filteredUsers.slice(
(currentUserPage - 1) * ITEMS_PER_PAGE,
currentUserPage * ITEMS_PER_PAGE
);
const currentGames = filteredGames.slice(
(currentGamePage - 1) * ITEMS_PER_PAGE,
currentGamePage * ITEMS_PER_PAGE
);
if (loading) {
return (
<div className="flex items-center justify-center min-h-screen">
<div className="text-lg">Loading...</div>
</div>
);
}
if (error) {
return (
<div className="flex items-center justify-center min-h-screen">
<div className="text-red-500">{error}</div>
</div>
);
}
return (
<div className="space-y-6">
{/* Users Section */}
<div className="bg-white shadow rounded-lg p-6">
<div className="flex justify-between items-center mb-4">
<h2 className="text-xl font-semibold text-gray-900">Users</h2>
<div className="flex items-center space-x-4">
<SearchInput
placeholder="Search users..."
value={userSearch}
onChange={setUserSearch}
/>
<div className="text-sm text-gray-600">
Total Users: {filteredUsers.length}
</div>
</div>
</div>
<div className="overflow-x-auto">
<table className="min-w-full divide-y divide-gray-200">
<thead className="bg-gray-50">
<tr>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Ref ID
</th>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Username
</th>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Wallet
</th>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
X Profile
</th>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Referred By
</th>
</tr>
</thead>
<tbody className="bg-white divide-y divide-gray-200">
{currentUsers.map((user) => (
<tr key={user.ref_id}>
<td className="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-900">
{user.ref_id}
</td>
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
{user.username || '-'}
</td>
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
{truncateAddress(user.active_wallet) || '-'}
</td>
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
{user.x_profile_url ? (
<a
href={user.x_profile_url}
target="_blank"
rel="noopener noreferrer"
className="text-blue-600 hover:text-blue-800"
>
View Profile
</a>
) : '-'}
</td>
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
{user.referred_id === '-1' ? '-' : user.referred_id}
</td>
</tr>
))}
</tbody>
</table>
<Pagination
totalItems={filteredUsers.length}
itemsPerPage={ITEMS_PER_PAGE}
currentPage={currentUserPage}
onPageChange={setCurrentUserPage}
/>
</div>
</div>
{/* Games History Section */}
<div className="bg-white shadow rounded-lg p-6">
<div className="flex justify-between items-center mb-4">
<h2 className="text-xl font-semibold text-gray-900">Game History</h2>
<div className="flex items-center space-x-4">
<SearchInput
placeholder="Search games..."
value={gameSearch}
onChange={setGameSearch}
/>
<div className="text-sm text-gray-600">
Total Games: {filteredGames.length}
</div>
</div>
</div>
<div className="overflow-x-auto">
<table className="min-w-full divide-y divide-gray-200">
<thead className="bg-gray-50">
<tr>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Date
</th>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Game
</th>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Winner
</th>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Players
</th>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Score
</th>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Result
</th>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Wager (SOL)
</th>
</tr>
</thead>
<tbody className="bg-white divide-y divide-gray-200">
{currentGames.map((game) => (
<tr key={game.address}>
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
{formatDate(game.ended_time)}
</td>
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-900 capitalize">
{game.game}
</td>
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-500 capitalize">
{game.winner}
</td>
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
<div>Master: {truncateAddress(game.master_id)}</div>
<div>Client: {truncateAddress(game.client_id)}</div>
</td>
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
{game.master_score} - {game.client_score}
</td>
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
{getGameResult(game)}
</td>
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
{(parseInt(game.wager) / 1000000).toFixed(2)}
</td>
</tr>
))}
</tbody>
</table>
<Pagination
totalItems={filteredGames.length}
itemsPerPage={ITEMS_PER_PAGE}
currentPage={currentGamePage}
onPageChange={setCurrentGamePage}
/>
</div>
</div>
</div>
);
}