feat: improve sound restoration in PlaylistEditPage by fetching complete sound data
This commit is contained in:
@@ -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)}
|
||||||
|
|||||||
@@ -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,11 +19,48 @@ 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 () => {
|
||||||
@@ -52,6 +90,10 @@ export function DashboardPage() {
|
|||||||
|
|
||||||
fetchStatistics()
|
fetchStatistics()
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
fetchTopSounds()
|
||||||
|
}, [soundType, period, limit])
|
||||||
|
|
||||||
if (loading) {
|
if (loading) {
|
||||||
return (
|
return (
|
||||||
@@ -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>
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user