diff --git a/src/App.tsx b/src/App.tsx index e00a344..2ac8252 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,29 +1,9 @@ import { useTranslation } from 'react-i18next' import { Link } from 'react-router-dom' -import { useState, useEffect } from 'react' import Layout from './components/Layout' +import { useStats } from './contexts/StatsContext' import './App.css' -interface ChatMessage { - steamID64: string - userName: string - message: string - timeStamp: number -} - -interface TopPlayer { - steamID64: string - userName: string - playTime: number -} - -interface Stats { - totalPlayers: number - totalConnects: number - totalPlayTime: number - totalKills: number -} - // Loading Skeleton Component const LoadingSkeleton = ({ width = '60px', height = '40px', className = '' }: { width?: string, height?: string, className?: string }) => (
({ - totalPlayers: 0, - totalConnects: 0, - totalPlayTime: 0, - totalKills: 0 - }) - const [loading, setLoading] = useState(true) - const [recentChats, setRecentChats] = useState([]) - const [topPlayers, setTopPlayers] = useState([]) - - // Fetch statistics data from API - useEffect(() => { - const timeRangeEnd = Date.now() - const timeRangeStart = 0 // all time - const requestBody = { timeRangeStart, timeRangeEnd } - - Promise.all([ - fetch('/api/server/statistics/total-player-count', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify(requestBody) - }), - fetch('/api/server/statistics/total-connect-count', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify(requestBody) - }), - fetch('/api/server/statistics/total-play-time', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify(requestBody) - }), - fetch('/api/server/statistics/total-kill-count', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ - ...requestBody, - headshotOnly: false, - weaponFilter: [], - playerFilter: [] - }) - }) - ]) - .then(responses => Promise.all(responses.map(r => r.json()))) - .then(([players, connects, playTime, kills]) => { - setStats({ - totalPlayers: players.count || 0, - totalConnects: connects.totalConnectCount || 0, - totalPlayTime: playTime.totalPlayTime || 0, - totalKills: kills.totalKillCount || 0 - }) - setLoading(false) - }) - .catch(error => { - console.error('Error fetching stats:', error) - setLoading(false) - }) - }, []) - - // Fetch recent chat messages (limit to top 5) - useEffect(() => { - fetch('/api/server/statistics/recent-chat-message', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ - timeRangeStart: Date.now() - 3600000, // last hour - timeRangeEnd: Date.now() - }) - }) - .then(r => r.json()) - .then(data => { - // Limit to top 5 most recent messages - const messages = data.messages || [] - const top5 = messages - .sort((a: ChatMessage, b: ChatMessage) => b.timeStamp - a.timeStamp) - .slice(0, 5) - setRecentChats(top5) - }) - .catch(error => console.error('Error fetching chat messages:', error)) - }, []) - - // Fetch top players by playtime - useEffect(() => { - fetch('/api/server/statistics/top-play-time', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ - timeRangeStart: 0, // all time - timeRangeEnd: Date.now() - }) - }) - .then(r => r.json()) - .then(data => { - // Get top 3 players - const players = data.players || [] - setTopPlayers(players.slice(0, 3)) - }) - .catch(error => console.error('Error fetching top players:', error)) - }, []) + const { stats, recentChats, topPlayers, loading } = useStats() // Format playtime from seconds to hours and minutes const formatPlayTime = (seconds: number) => { diff --git a/src/contexts/StatsContext.tsx b/src/contexts/StatsContext.tsx new file mode 100644 index 0000000..3762e15 --- /dev/null +++ b/src/contexts/StatsContext.tsx @@ -0,0 +1,151 @@ +import { createContext, useContext, useState, useEffect } from 'react' +import type { ReactNode } from 'react' + +interface ChatMessage { + steamID64: string + userName: string + message: string + timeStamp: number +} + +interface TopPlayer { + steamID64: string + userName: string + playTime: number +} + +interface Stats { + totalPlayers: number + totalConnects: number + totalPlayTime: number + totalKills: number +} + +interface StatsContextType { + stats: Stats + recentChats: ChatMessage[] + topPlayers: TopPlayer[] + loading: boolean + lastUpdated: number +} + +const StatsContext = createContext(undefined) + +export function StatsProvider({ children }: { children: ReactNode }) { + const [stats, setStats] = useState({ + totalPlayers: 0, + totalConnects: 0, + totalPlayTime: 0, + totalKills: 0 + }) + const [recentChats, setRecentChats] = useState([]) + const [topPlayers, setTopPlayers] = useState([]) + const [loading, setLoading] = useState(true) + const [lastUpdated, setLastUpdated] = useState(Date.now()) + + useEffect(() => { + const fetchAllData = async () => { + const timeRangeEnd = Date.now() + const timeRangeStart = 0 // all time + const requestBody = { timeRangeStart, timeRangeEnd } + + try { + // Fetch statistics + const [playersRes, connectsRes, playTimeRes, killsRes, chatsRes, topPlayersRes] = await Promise.all([ + fetch('/api/server/statistics/total-player-count', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(requestBody) + }), + fetch('/api/server/statistics/total-connect-count', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(requestBody) + }), + fetch('/api/server/statistics/total-play-time', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(requestBody) + }), + fetch('/api/server/statistics/total-kill-count', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + ...requestBody, + headshotOnly: false, + weaponFilter: [], + playerFilter: [] + }) + }), + fetch('/api/server/statistics/recent-chat-message', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + timeRangeStart: Date.now() - 3600000, // last hour + timeRangeEnd: Date.now() + }) + }), + fetch('/api/server/statistics/top-play-time', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + timeRangeStart: 0, // all time + timeRangeEnd: Date.now() + }) + }) + ]) + + const [players, connects, playTime, kills, chats, topPlayersData] = await Promise.all([ + playersRes.json(), + connectsRes.json(), + playTimeRes.json(), + killsRes.json(), + chatsRes.json(), + topPlayersRes.json() + ]) + + // Update statistics + setStats({ + totalPlayers: players.count || 0, + totalConnects: connects.totalConnectCount || 0, + totalPlayTime: playTime.totalPlayTime || 0, + totalKills: kills.totalKillCount || 0 + }) + + // Update recent chats (limit to top 5) + const messages = chats.messages || [] + const top5 = messages + .sort((a: ChatMessage, b: ChatMessage) => b.timeStamp - a.timeStamp) + .slice(0, 5) + setRecentChats(top5) + + // Update top players (limit to top 3) + const playersArray = topPlayersData.players || [] + setTopPlayers(playersArray.slice(0, 3)) + + setLoading(false) + setLastUpdated(Date.now()) + } catch (error) { + console.error('Error fetching stats:', error) + setLoading(false) + } + } + + // Fetch data only once on mount + fetchAllData() + }, []) // Empty dependency array - runs only once + + return ( + + {children} + + ) +} + +export function useStats() { + const context = useContext(StatsContext) + if (context === undefined) { + throw new Error('useStats must be used within a StatsProvider') + } + return context +} diff --git a/src/main.tsx b/src/main.tsx index 63b69e4..54da5fe 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -5,6 +5,7 @@ import './index.css' import './i18n' import { ThemeProvider } from './contexts/ThemeContext' import { ServerProvider } from './contexts/ServerContext' +import { StatsProvider } from './contexts/StatsContext' import ScrollToTop from './components/ScrollToTop' import App from './App.tsx' import Friends from './pages/Friends.tsx' @@ -15,18 +16,20 @@ import Forum from './pages/Forum.tsx' createRoot(document.getElementById('root')!).render( - - - - - } /> - } /> - } /> - } /> - } /> - - - + + + + + + } /> + } /> + } /> + } /> + } /> + + + + , )