From c27236232eb072dbd08846b3be6a783f2caf7d54 Mon Sep 17 00:00:00 2001 From: JSC Date: Fri, 18 Jul 2025 21:10:14 +0200 Subject: [PATCH] feat: implement DashboardPage with statistics and top content display --- src/hooks/use-dashboard-stats.ts | 96 +++++++++++++ src/pages/DashboardPage.tsx | 238 ++++++++++++++++++++++++++++++- 2 files changed, 333 insertions(+), 1 deletion(-) create mode 100644 src/hooks/use-dashboard-stats.ts diff --git a/src/hooks/use-dashboard-stats.ts b/src/hooks/use-dashboard-stats.ts new file mode 100644 index 0000000..c96161d --- /dev/null +++ b/src/hooks/use-dashboard-stats.ts @@ -0,0 +1,96 @@ +import { useState, useEffect } from 'react' +import { apiService } from '@/services/api' + +interface DashboardStats { + soundboard_sounds: number + tracks: number + playlists: number + total_size: number + original_size: number + normalized_size: number +} + +interface TopSound { + id: number + name: string + filename: string + thumbnail: string | null + type: string + play_count: number +} + +interface TopSoundsResponse { + period: string + sounds: TopSound[] +} + +interface TopTracksResponse { + period: string + tracks: TopSound[] +} + +interface TopUser { + id: number + name: string + email: string + picture: string | null + play_count: number +} + +interface TopUsersResponse { + period: string + users: TopUser[] +} + +export type TimePeriod = 'today' | 'week' | 'month' | 'year' | 'all' + +export function useDashboardStats(period: TimePeriod = 'all', limit: number = 5) { + const [stats, setStats] = useState(null) + const [topSounds, setTopSounds] = useState(null) + const [topTracks, setTopTracks] = useState(null) + const [topUsers, setTopUsers] = useState(null) + const [loading, setLoading] = useState(true) + const [error, setError] = useState(null) + + useEffect(() => { + const fetchData = async () => { + try { + setLoading(true) + setError(null) + + // Fetch basic stats, top sounds, top tracks, and top users in parallel + const [statsResponse, topSoundsResponse, topTracksResponse, topUsersResponse] = await Promise.all([ + apiService.get('/api/dashboard/stats'), + apiService.get(`/api/dashboard/top-sounds?period=${period}&limit=${limit}`), + apiService.get(`/api/dashboard/top-tracks?period=${period}&limit=${limit}`), + apiService.get(`/api/dashboard/top-users?period=${period}&limit=${limit}`) + ]) + + if (statsResponse.ok && topSoundsResponse.ok && topTracksResponse.ok && topUsersResponse.ok) { + const [statsData, topSoundsData, topTracksData, topUsersData] = await Promise.all([ + statsResponse.json(), + topSoundsResponse.json(), + topTracksResponse.json(), + topUsersResponse.json() + ]) + + setStats(statsData) + setTopSounds(topSoundsData) + setTopTracks(topTracksData) + setTopUsers(topUsersData) + } else { + setError('Failed to fetch dashboard data') + } + } catch (err) { + setError('An error occurred while fetching dashboard data') + console.error('Dashboard fetch error:', err) + } finally { + setLoading(false) + } + } + + fetchData() + }, [period, limit]) + + return { stats, topSounds, topTracks, topUsers, loading, error } +} \ No newline at end of file diff --git a/src/pages/DashboardPage.tsx b/src/pages/DashboardPage.tsx index 7d3cb19..e3f8108 100644 --- a/src/pages/DashboardPage.tsx +++ b/src/pages/DashboardPage.tsx @@ -1,11 +1,247 @@ +import { useState } from 'react' import { useAuth } from '@/hooks/use-auth' +import { useDashboardStats, type TimePeriod } from '@/hooks/use-dashboard-stats' +import { formatSize } from '@/lib/format-size' + +const PERIOD_OPTIONS = [ + { value: 'today' as TimePeriod, label: 'Today' }, + { value: 'week' as TimePeriod, label: 'This Week' }, + { value: 'month' as TimePeriod, label: 'This Month' }, + { value: 'year' as TimePeriod, label: 'This Year' }, + { value: 'all' as TimePeriod, label: 'All Time' }, +] + +const LIMIT_OPTIONS = [ + { value: 5, label: 'Top 5' }, + { value: 10, label: 'Top 10' }, + { value: 20, label: 'Top 20' }, + { value: 50, label: 'Top 50' }, +] export function DashboardPage() { const { user } = useAuth() + const [selectedPeriod, setSelectedPeriod] = useState('today') + const [selectedLimit, setSelectedLimit] = useState(5) + const { stats, topSounds, topTracks, topUsers, loading, error } = useDashboardStats(selectedPeriod, selectedLimit) if (!user) return null return ( -
+
+ {loading && ( +
+
Loading statistics...
+
+ )} + + {error && ( +
+
Error: {error}
+
+ )} + + {stats && ( + <> + {/* Statistics Cards */} +
+
+
+
+ {stats.soundboard_sounds} +
+
+ Sounds +
+
+
+ +
+
+
+ {stats.tracks} +
+
+ Tracks +
+
+
+ +
+
+
+ {stats.playlists} +
+
+ Playlists +
+
+
+ +
+
+
+ {formatSize(stats.total_size, true)} +
+
+ Total Size +
+
+
+
+ + {/* Period and Limit Selectors */} +
+ + +
+ + {/* Top Content Grid */} +
+ {/* Top Sounds Section */} +
+

Played Sounds

+ + {topSounds && topSounds.sounds.length > 0 ? ( +
+ {topSounds.sounds.map((sound, index) => ( +
+
+
+ {index + 1} +
+
+
{sound.name}
+
+ {sound.filename} +
+
+
+
+
{sound.play_count}
+
+ {sound.play_count === 1 ? 'play' : 'plays'} +
+
+
+ ))} +
+ ) : ( +
+ No sounds played {selectedPeriod === 'all' ? 'yet' : `in the selected period`} +
+ )} +
+ + {/* Top Tracks Section */} +
+

Played Tracks

+ + {topTracks && topTracks.tracks.length > 0 ? ( +
+ {topTracks.tracks.map((track, index) => ( +
+
+
+ {index + 1} +
+
+
{track.name}
+
+ {track.filename} +
+
+
+
+
{track.play_count}
+
+ {track.play_count === 1 ? 'play' : 'plays'} +
+
+
+ ))} +
+ ) : ( +
+ No tracks played {selectedPeriod === 'all' ? 'yet' : `in the selected period`} +
+ )} +
+ + {/* Top Users Section */} +
+

Users

+ + {topUsers && topUsers.users.length > 0 ? ( +
+ {topUsers.users.map((user, index) => ( +
+
+
+ {index + 1} +
+
+ {user.picture && ( + {user.name} + )} +
+
{user.name}
+
+ {user.email} +
+
+
+
+
+
{user.play_count}
+
+ {user.play_count === 1 ? 'play' : 'plays'} +
+
+
+ ))} +
+ ) : ( +
+ No users played sounds {selectedPeriod === 'all' ? 'yet' : `in the selected period`} +
+ )} +
+
+ + )} +
) } \ No newline at end of file