feat: enhance DashboardPage with data fetching improvements, auto-refresh, and UI updates

This commit is contained in:
JSC
2025-08-12 20:51:29 +02:00
parent ee05bc8a64
commit e55c5fd4b9

View File

@@ -1,9 +1,11 @@
import { useEffect, useState } from 'react'
import { useCallback, useEffect, useState } from 'react'
import { AppLayout } from '@/components/AppLayout'
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'
import { Volume2, Play, Clock, HardDrive, Music, Trophy, Loader2 } from 'lucide-react'
import { Button } from '@/components/ui/button'
import { Volume2, Play, Clock, HardDrive, Music, Trophy, Loader2, RefreshCw } from 'lucide-react'
import { formatDuration, formatFileSize } from '@/lib/format'
import NumberFlow from '@number-flow/react'
interface SoundboardStatistics {
sound_count: number
@@ -40,10 +42,37 @@ export function DashboardPage() {
const [soundType, setSoundType] = useState('SDB')
const [period, setPeriod] = useState('all_time')
const [limit, setLimit] = useState(5)
const [refreshing, setRefreshing] = useState(false)
const fetchTopSounds = async () => {
const fetchStatistics = useCallback(async () => {
try {
setTopSoundsLoading(true)
const [soundboardResponse, trackResponse] = await Promise.all([
fetch('/api/v1/dashboard/soundboard-statistics', { credentials: 'include' }),
fetch('/api/v1/dashboard/track-statistics', { credentials: 'include' })
])
if (!soundboardResponse.ok || !trackResponse.ok) {
throw new Error('Failed to fetch statistics')
}
const [soundboardData, trackData] = await Promise.all([
soundboardResponse.json(),
trackResponse.json()
])
setSoundboardStatistics(soundboardData)
setTrackStatistics(trackData)
} catch (err) {
setError(err instanceof Error ? err.message : 'An error occurred')
}
}, [])
const fetchTopSounds = useCallback(async (showLoading = false) => {
try {
if (showLoading) {
setTopSoundsLoading(true)
}
const response = await fetch(
`/api/v1/dashboard/top-sounds?sound_type=${soundType}&period=${period}&limit=${limit}`,
{ credentials: 'include' }
@@ -54,46 +83,75 @@ export function DashboardPage() {
}
const data = await response.json()
setTopSounds(data)
// Graceful update: merge new data while preserving animations
setTopSounds(prevTopSounds => {
// Create a map of existing sounds for efficient lookup
const existingSoundsMap = new Map(prevTopSounds.map(sound => [sound.id, sound]))
// Update existing sounds and add new ones
return data.map((newSound: TopSound) => {
const existingSound = existingSoundsMap.get(newSound.id)
if (existingSound) {
// Preserve object reference if data hasn't changed to avoid re-renders
if (
existingSound.name === newSound.name &&
existingSound.type === newSound.type &&
existingSound.play_count === newSound.play_count &&
existingSound.duration === newSound.duration
) {
return existingSound
}
}
return newSound
})
})
} catch (err) {
console.error('Failed to fetch top sounds:', err)
} finally {
setTopSoundsLoading(false)
if (showLoading) {
setTopSoundsLoading(false)
}
}
}
}, [soundType, period, limit])
const refreshAll = useCallback(async () => {
setRefreshing(true)
try {
await Promise.all([
fetchStatistics(),
fetchTopSounds()
])
} finally {
setRefreshing(false)
}
}, [fetchStatistics, fetchTopSounds])
useEffect(() => {
const fetchStatistics = async () => {
const loadInitialData = async () => {
setLoading(true)
try {
const [soundboardResponse, trackResponse] = await Promise.all([
fetch('/api/v1/dashboard/soundboard-statistics', { credentials: 'include' }),
fetch('/api/v1/dashboard/track-statistics', { credentials: 'include' })
])
if (!soundboardResponse.ok || !trackResponse.ok) {
throw new Error('Failed to fetch statistics')
}
const [soundboardData, trackData] = await Promise.all([
soundboardResponse.json(),
trackResponse.json()
])
setSoundboardStatistics(soundboardData)
setTrackStatistics(trackData)
} catch (err) {
setError(err instanceof Error ? err.message : 'An error occurred')
await fetchStatistics()
} finally {
setLoading(false)
}
}
fetchStatistics()
loadInitialData()
}, [])
// Auto-refresh every 5 seconds
useEffect(() => {
fetchTopSounds()
}, [soundType, period, limit])
const interval = setInterval(() => {
refreshAll()
}, 5000)
return () => clearInterval(interval)
}, [refreshAll])
useEffect(() => {
fetchTopSounds(true) // Show loading on initial load and filter changes
}, [fetchTopSounds])
if (loading) {
return (
@@ -192,6 +250,14 @@ export function DashboardPage() {
Overview of your soundboard and track statistics
</p>
</div>
<Button
onClick={refreshAll}
variant="outline"
size="sm"
disabled={refreshing}
>
<RefreshCw className={`h-4 w-4 ${refreshing ? 'animate-spin' : ''}`} />
</Button>
</div>
<div className="space-y-6">
@@ -218,7 +284,7 @@ export function DashboardPage() {
<Play className="h-4 w-4 text-muted-foreground" />
</CardHeader>
<CardContent>
<div className="text-2xl font-bold">{soundboardStatistics.total_play_count}</div>
<div className="text-2xl font-bold"><NumberFlow value={soundboardStatistics.total_play_count} /></div>
<p className="text-xs text-muted-foreground">
All-time play count
</p>
@@ -390,10 +456,6 @@ export function DashboardPage() {
<div className="flex-1 min-w-0">
<div className="font-medium truncate">{sound.name}</div>
<div className="flex items-center gap-4 text-xs text-muted-foreground mt-1">
<span className="flex items-center gap-1">
<Play className="h-3 w-3" />
{sound.play_count} plays
</span>
{sound.duration && (
<span className="flex items-center gap-1">
<Clock className="h-3 w-3" />
@@ -405,6 +467,10 @@ export function DashboardPage() {
</span>
</div>
</div>
<div className="text-right">
<div className="text-2xl font-bold text-primary"><NumberFlow value={sound.play_count} /></div>
<div className="text-xs text-muted-foreground">plays</div>
</div>
</div>
))}
</div>