Implement StatsContext for centralized statistics management and refactor App component to utilize context
This commit is contained in:
123
src/App.tsx
123
src/App.tsx
@@ -1,29 +1,9 @@
|
|||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import { Link } from 'react-router-dom'
|
import { Link } from 'react-router-dom'
|
||||||
import { useState, useEffect } from 'react'
|
|
||||||
import Layout from './components/Layout'
|
import Layout from './components/Layout'
|
||||||
|
import { useStats } from './contexts/StatsContext'
|
||||||
import './App.css'
|
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
|
// Loading Skeleton Component
|
||||||
const LoadingSkeleton = ({ width = '60px', height = '40px', className = '' }: { width?: string, height?: string, className?: string }) => (
|
const LoadingSkeleton = ({ width = '60px', height = '40px', className = '' }: { width?: string, height?: string, className?: string }) => (
|
||||||
<div
|
<div
|
||||||
@@ -56,106 +36,7 @@ const LoadingSkeleton = ({ width = '60px', height = '40px', className = '' }: {
|
|||||||
|
|
||||||
function App() {
|
function App() {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
|
const { stats, recentChats, topPlayers, loading } = useStats()
|
||||||
const [stats, setStats] = useState<Stats>({
|
|
||||||
totalPlayers: 0,
|
|
||||||
totalConnects: 0,
|
|
||||||
totalPlayTime: 0,
|
|
||||||
totalKills: 0
|
|
||||||
})
|
|
||||||
const [loading, setLoading] = useState(true)
|
|
||||||
const [recentChats, setRecentChats] = useState<ChatMessage[]>([])
|
|
||||||
const [topPlayers, setTopPlayers] = useState<TopPlayer[]>([])
|
|
||||||
|
|
||||||
// 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))
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
// Format playtime from seconds to hours and minutes
|
// Format playtime from seconds to hours and minutes
|
||||||
const formatPlayTime = (seconds: number) => {
|
const formatPlayTime = (seconds: number) => {
|
||||||
|
|||||||
151
src/contexts/StatsContext.tsx
Normal file
151
src/contexts/StatsContext.tsx
Normal file
@@ -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<StatsContextType | undefined>(undefined)
|
||||||
|
|
||||||
|
export function StatsProvider({ children }: { children: ReactNode }) {
|
||||||
|
const [stats, setStats] = useState<Stats>({
|
||||||
|
totalPlayers: 0,
|
||||||
|
totalConnects: 0,
|
||||||
|
totalPlayTime: 0,
|
||||||
|
totalKills: 0
|
||||||
|
})
|
||||||
|
const [recentChats, setRecentChats] = useState<ChatMessage[]>([])
|
||||||
|
const [topPlayers, setTopPlayers] = useState<TopPlayer[]>([])
|
||||||
|
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 (
|
||||||
|
<StatsContext.Provider value={{ stats, recentChats, topPlayers, loading, lastUpdated }}>
|
||||||
|
{children}
|
||||||
|
</StatsContext.Provider>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useStats() {
|
||||||
|
const context = useContext(StatsContext)
|
||||||
|
if (context === undefined) {
|
||||||
|
throw new Error('useStats must be used within a StatsProvider')
|
||||||
|
}
|
||||||
|
return context
|
||||||
|
}
|
||||||
@@ -5,6 +5,7 @@ import './index.css'
|
|||||||
import './i18n'
|
import './i18n'
|
||||||
import { ThemeProvider } from './contexts/ThemeContext'
|
import { ThemeProvider } from './contexts/ThemeContext'
|
||||||
import { ServerProvider } from './contexts/ServerContext'
|
import { ServerProvider } from './contexts/ServerContext'
|
||||||
|
import { StatsProvider } from './contexts/StatsContext'
|
||||||
import ScrollToTop from './components/ScrollToTop'
|
import ScrollToTop from './components/ScrollToTop'
|
||||||
import App from './App.tsx'
|
import App from './App.tsx'
|
||||||
import Friends from './pages/Friends.tsx'
|
import Friends from './pages/Friends.tsx'
|
||||||
@@ -15,6 +16,7 @@ import Forum from './pages/Forum.tsx'
|
|||||||
createRoot(document.getElementById('root')!).render(
|
createRoot(document.getElementById('root')!).render(
|
||||||
<StrictMode>
|
<StrictMode>
|
||||||
<ThemeProvider>
|
<ThemeProvider>
|
||||||
|
<StatsProvider>
|
||||||
<ServerProvider>
|
<ServerProvider>
|
||||||
<Router>
|
<Router>
|
||||||
<ScrollToTop />
|
<ScrollToTop />
|
||||||
@@ -27,6 +29,7 @@ createRoot(document.getElementById('root')!).render(
|
|||||||
</Routes>
|
</Routes>
|
||||||
</Router>
|
</Router>
|
||||||
</ServerProvider>
|
</ServerProvider>
|
||||||
|
</StatsProvider>
|
||||||
</ThemeProvider>
|
</ThemeProvider>
|
||||||
</StrictMode>,
|
</StrictMode>,
|
||||||
)
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user