From e55c5fd4b94e10312169df3d9e3655f343f720fa Mon Sep 17 00:00:00 2001
From: JSC
Date: Tue, 12 Aug 2025 20:51:29 +0200
Subject: [PATCH] feat: enhance DashboardPage with data fetching improvements,
auto-refresh, and UI updates
---
src/pages/DashboardPage.tsx | 134 +++++++++++++++++++++++++++---------
1 file changed, 100 insertions(+), 34 deletions(-)
diff --git a/src/pages/DashboardPage.tsx b/src/pages/DashboardPage.tsx
index 8decb55..bba2396 100644
--- a/src/pages/DashboardPage.tsx
+++ b/src/pages/DashboardPage.tsx
@@ -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(() => {
+ const interval = setInterval(() => {
+ refreshAll()
+ }, 5000)
+
+ return () => clearInterval(interval)
+ }, [refreshAll])
useEffect(() => {
- fetchTopSounds()
- }, [soundType, period, limit])
+ 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
+
@@ -218,7 +284,7 @@ export function DashboardPage() {
- {soundboardStatistics.total_play_count}
+
All-time play count
@@ -390,10 +456,6 @@ export function DashboardPage() {
{sound.name}
-
-
- {sound.play_count} plays
-
{sound.duration && (
@@ -405,6 +467,10 @@ export function DashboardPage() {
+
))}