feat: add TopUsersSection component to DashboardPage for displaying top users
This commit is contained in:
@@ -3,6 +3,7 @@ import { DashboardHeader } from '@/components/dashboard/DashboardHeader'
|
||||
import { ErrorState, LoadingSkeleton } from '@/components/dashboard/DashboardLoadingStates'
|
||||
import { StatisticsGrid } from '@/components/dashboard/StatisticsGrid'
|
||||
import { TopSoundsSection } from '@/components/dashboard/TopSoundsSection'
|
||||
import { TopUsersSection } from '@/components/dashboard/TopUsersSection'
|
||||
import { useCallback, useEffect, useState } from 'react'
|
||||
|
||||
interface SoundboardStatistics {
|
||||
@@ -35,6 +36,12 @@ interface TopSound {
|
||||
created_at: string | null
|
||||
}
|
||||
|
||||
interface TopUser {
|
||||
id: number
|
||||
name: string
|
||||
count: number
|
||||
}
|
||||
|
||||
export function DashboardPage() {
|
||||
const [soundboardStatistics, setSoundboardStatistics] =
|
||||
useState<SoundboardStatistics | null>(null)
|
||||
@@ -53,6 +60,13 @@ export function DashboardPage() {
|
||||
const [limit, setLimit] = useState(5)
|
||||
const [refreshing, setRefreshing] = useState(false)
|
||||
|
||||
// Top users state
|
||||
const [topUsers, setTopUsers] = useState<TopUser[]>([])
|
||||
const [topUsersLoading, setTopUsersLoading] = useState(false)
|
||||
const [metricType, setMetricType] = useState('sounds_played')
|
||||
const [userPeriod, setUserPeriod] = useState('all_time')
|
||||
const [userLimit, setUserLimit] = useState(5)
|
||||
|
||||
const fetchStatistics = useCallback(async () => {
|
||||
try {
|
||||
setError(null) // Clear previous errors
|
||||
@@ -156,18 +170,70 @@ export function DashboardPage() {
|
||||
[soundType, period, limit],
|
||||
)
|
||||
|
||||
const fetchTopUsers = useCallback(
|
||||
async (showLoading = false) => {
|
||||
try {
|
||||
if (showLoading) {
|
||||
setTopUsersLoading(true)
|
||||
}
|
||||
|
||||
const response = await fetch(
|
||||
`/api/v1/dashboard/top-users?metric_type=${metricType}&period=${userPeriod}&limit=${userLimit}`,
|
||||
{ credentials: 'include' },
|
||||
)
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error('Failed to fetch top users')
|
||||
}
|
||||
|
||||
const data = await response.json()
|
||||
|
||||
// Graceful update: merge new data while preserving animations
|
||||
setTopUsers(prevTopUsers => {
|
||||
// Create a map of existing users for efficient lookup
|
||||
const existingUsersMap = new Map(
|
||||
prevTopUsers.map(user => [user.id, user]),
|
||||
)
|
||||
|
||||
// Update existing users and add new ones
|
||||
return data.map((newUser: TopUser) => {
|
||||
const existingUser = existingUsersMap.get(newUser.id)
|
||||
if (existingUser) {
|
||||
// Preserve object reference if data hasn't changed to avoid re-renders
|
||||
if (
|
||||
existingUser.name === newUser.name &&
|
||||
existingUser.count === newUser.count
|
||||
) {
|
||||
return existingUser
|
||||
}
|
||||
}
|
||||
return newUser
|
||||
})
|
||||
})
|
||||
} catch (err) {
|
||||
console.error('Failed to fetch top users:', err)
|
||||
} finally {
|
||||
if (showLoading) {
|
||||
setTopUsersLoading(false)
|
||||
}
|
||||
}
|
||||
},
|
||||
[metricType, userPeriod, userLimit],
|
||||
)
|
||||
|
||||
const refreshAll = useCallback(async () => {
|
||||
setRefreshing(true)
|
||||
try {
|
||||
// Fetch statistics and top sounds sequentially to avoid Promise.all issues
|
||||
await fetchStatistics()
|
||||
await fetchTopSounds()
|
||||
await fetchTopUsers()
|
||||
} catch (err) {
|
||||
console.error('Error during refresh:', err)
|
||||
} finally {
|
||||
setRefreshing(false)
|
||||
}
|
||||
}, [fetchStatistics, fetchTopSounds])
|
||||
}, [fetchStatistics, fetchTopSounds, fetchTopUsers])
|
||||
|
||||
const retryFromError = useCallback(async () => {
|
||||
setLoading(true)
|
||||
@@ -208,6 +274,10 @@ export function DashboardPage() {
|
||||
fetchTopSounds(true) // Show loading on initial load and filter changes
|
||||
}, [fetchTopSounds])
|
||||
|
||||
useEffect(() => {
|
||||
fetchTopUsers(true) // Show loading on initial load and filter changes
|
||||
}, [fetchTopUsers])
|
||||
|
||||
if (loading) {
|
||||
return <LoadingSkeleton />
|
||||
}
|
||||
@@ -244,6 +314,17 @@ export function DashboardPage() {
|
||||
onPeriodChange={setPeriod}
|
||||
onLimitChange={setLimit}
|
||||
/>
|
||||
|
||||
<TopUsersSection
|
||||
topUsers={topUsers}
|
||||
loading={topUsersLoading}
|
||||
metricType={metricType}
|
||||
period={userPeriod}
|
||||
limit={userLimit}
|
||||
onMetricTypeChange={setMetricType}
|
||||
onPeriodChange={setUserPeriod}
|
||||
onLimitChange={setUserLimit}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</AppLayout>
|
||||
|
||||
Reference in New Issue
Block a user