feat: improve sound restoration in PlaylistEditPage by fetching complete sound data

This commit is contained in:
JSC
2025-08-11 22:04:54 +02:00
parent 53e5ec74d8
commit ee05bc8a64
3 changed files with 156 additions and 20 deletions

View File

@@ -504,7 +504,7 @@ export function Player({ className, onPlayerModeChange }: PlayerProps) {
{/* Main Player Area */} {/* Main Player Area */}
<div className="flex-1 flex flex-col items-center justify-center p-8"> <div className="flex-1 flex flex-col items-center justify-center p-8">
{/* Large Album Art */} {/* Large Album Art */}
<div className="w-80 aspect-square bg-muted rounded-lg flex items-center justify-center overflow-hidden mb-8"> <div className="max-w-300 max-h-200 aspect-auto bg-muted rounded-lg flex items-center justify-center overflow-hidden mb-8">
{state.current_sound?.thumbnail ? ( {state.current_sound?.thumbnail ? (
<img <img
src={filesService.getThumbnailUrl(state.current_sound.id)} src={filesService.getThumbnailUrl(state.current_sound.id)}

View File

@@ -1,7 +1,8 @@
import { useEffect, useState } from 'react' import { useEffect, useState } from 'react'
import { AppLayout } from '@/components/AppLayout' import { AppLayout } from '@/components/AppLayout'
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card' import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
import { Volume2, Play, Clock, HardDrive, Music } from 'lucide-react' import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'
import { Volume2, Play, Clock, HardDrive, Music, Trophy, Loader2 } from 'lucide-react'
import { formatDuration, formatFileSize } from '@/lib/format' import { formatDuration, formatFileSize } from '@/lib/format'
interface SoundboardStatistics { interface SoundboardStatistics {
@@ -18,12 +19,49 @@ interface TrackStatistics {
total_size: number total_size: number
} }
interface TopSound {
id: number
name: string
type: string
play_count: number
duration: number | null
created_at: string | null
}
export function DashboardPage() { export function DashboardPage() {
const [soundboardStatistics, setSoundboardStatistics] = useState<SoundboardStatistics | null>(null) const [soundboardStatistics, setSoundboardStatistics] = useState<SoundboardStatistics | null>(null)
const [trackStatistics, setTrackStatistics] = useState<TrackStatistics | null>(null) const [trackStatistics, setTrackStatistics] = useState<TrackStatistics | null>(null)
const [loading, setLoading] = useState(true) const [loading, setLoading] = useState(true)
const [error, setError] = useState<string | null>(null) const [error, setError] = useState<string | null>(null)
// Top sounds state
const [topSounds, setTopSounds] = useState<TopSound[]>([])
const [topSoundsLoading, setTopSoundsLoading] = useState(false)
const [soundType, setSoundType] = useState('SDB')
const [period, setPeriod] = useState('all_time')
const [limit, setLimit] = useState(5)
const fetchTopSounds = async () => {
try {
setTopSoundsLoading(true)
const response = await fetch(
`/api/v1/dashboard/top-sounds?sound_type=${soundType}&period=${period}&limit=${limit}`,
{ credentials: 'include' }
)
if (!response.ok) {
throw new Error('Failed to fetch top sounds')
}
const data = await response.json()
setTopSounds(data)
} catch (err) {
console.error('Failed to fetch top sounds:', err)
} finally {
setTopSoundsLoading(false)
}
}
useEffect(() => { useEffect(() => {
const fetchStatistics = async () => { const fetchStatistics = async () => {
try { try {
@@ -53,6 +91,10 @@ export function DashboardPage() {
fetchStatistics() fetchStatistics()
}, []) }, [])
useEffect(() => {
fetchTopSounds()
}, [soundType, period, limit])
if (loading) { if (loading) {
return ( return (
<AppLayout <AppLayout
@@ -268,6 +310,108 @@ export function DashboardPage() {
</Card> </Card>
</div> </div>
</div> </div>
{/* Top Sounds Section */}
<div className="mt-8">
<Card>
<CardHeader>
<div className="flex items-center justify-between">
<div className="flex items-center gap-2">
<Trophy className="h-5 w-5" />
<CardTitle>Top Sounds</CardTitle>
</div>
<div className="flex items-center gap-4">
<div className="flex items-center gap-2">
<span className="text-sm font-medium">Type:</span>
<Select value={soundType} onValueChange={setSoundType}>
<SelectTrigger className="w-32">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="all">All</SelectItem>
<SelectItem value="SDB">Soundboard</SelectItem>
<SelectItem value="EXT">Tracks</SelectItem>
<SelectItem value="TTS">TTS</SelectItem>
</SelectContent>
</Select>
</div>
<div className="flex items-center gap-2">
<span className="text-sm font-medium">Period:</span>
<Select value={period} onValueChange={setPeriod}>
<SelectTrigger className="w-32">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="today">Today</SelectItem>
<SelectItem value="1_day">1 Day</SelectItem>
<SelectItem value="1_week">1 Week</SelectItem>
<SelectItem value="1_month">1 Month</SelectItem>
<SelectItem value="1_year">1 Year</SelectItem>
<SelectItem value="all_time">All Time</SelectItem>
</SelectContent>
</Select>
</div>
<div className="flex items-center gap-2">
<span className="text-sm font-medium">Count:</span>
<Select value={limit.toString()} onValueChange={(value) => setLimit(parseInt(value))}>
<SelectTrigger className="w-20">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="5">5</SelectItem>
<SelectItem value="10">10</SelectItem>
<SelectItem value="25">25</SelectItem>
<SelectItem value="50">50</SelectItem>
</SelectContent>
</Select>
</div>
</div>
</div>
</CardHeader>
<CardContent>
{topSoundsLoading ? (
<div className="flex items-center justify-center py-8">
<Loader2 className="h-6 w-6 animate-spin mr-2" />
Loading top sounds...
</div>
) : topSounds.length === 0 ? (
<div className="text-center py-8 text-muted-foreground">
<Music className="h-8 w-8 mx-auto mb-2 opacity-50" />
<p>No sounds found for the selected criteria</p>
</div>
) : (
<div className="space-y-3">
{topSounds.map((sound, index) => (
<div key={sound.id} className="flex items-center gap-4 p-3 bg-muted/30 rounded-lg">
<div className="flex items-center justify-center w-8 h-8 bg-primary text-primary-foreground rounded-full font-bold text-sm">
{index + 1}
</div>
<Music className="h-4 w-4 text-muted-foreground flex-shrink-0" />
<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" />
{formatDuration(sound.duration)}
</span>
)}
<span className="px-1.5 py-0.5 bg-secondary rounded text-xs">
{sound.type}
</span>
</div>
</div>
</div>
))}
</div>
)}
</CardContent>
</Card>
</div>
</div> </div>
</div> </div>
</AppLayout> </AppLayout>

View File

@@ -606,25 +606,17 @@ export function PlaylistEditPage() {
// If it's an EXT sound and we're in add mode, add it back to available sounds // If it's an EXT sound and we're in add mode, add it back to available sounds
if (isAddMode && removedSound.type === 'EXT') { if (isAddMode && removedSound.type === 'EXT') {
// Convert PlaylistSound back to Sound format for available list // Fetch the complete sound data to add back to available sounds
const soundToAddBack = { try {
id: removedSound.id, const allExtSounds = await soundsService.getSoundsByType('EXT')
name: removedSound.name, const soundToAddBack = allExtSounds.find(s => s.id === soundId)
filename: removedSound.filename, if (soundToAddBack) {
duration: removedSound.duration, setAvailableSounds(prev => [...prev, soundToAddBack].sort((a, b) => a.name.localeCompare(b.name)))
size: removedSound.size, }
hash: removedSound.hash, } catch {
type: removedSound.type, // If we can't fetch the sound data, just refresh the available sounds
play_count: removedSound.play_count, await fetchAvailableSounds()
is_normalized: removedSound.is_normalized,
normalized_filename: removedSound.normalized_filename,
normalized_duration: removedSound.normalized_duration,
normalized_size: removedSound.normalized_size,
normalized_hash: removedSound.normalized_hash,
created_at: removedSound.created_at,
updated_at: removedSound.updated_at
} }
setAvailableSounds(prev => [...prev, soundToAddBack].sort((a, b) => a.name.localeCompare(b.name)))
} }
// Optimistically update playlist stats // Optimistically update playlist stats