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() {
+
+
+
plays
+
))}