feat: update loading skeleton and playlist handling for main playlists
Some checks failed
Frontend CI / lint (push) Failing after 17s
Frontend CI / build (push) Has been skipped

This commit is contained in:
JSC
2025-08-16 01:21:34 +02:00
parent 7b01ace746
commit f6117ededd
13 changed files with 141 additions and 55 deletions

View File

@@ -2,12 +2,7 @@ import { AppLayout } from '@/components/AppLayout'
import { DashboardHeader } from '@/components/dashboard/DashboardHeader'
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
interface LoadingSkeletonProps {
title: string
description: string
}
export function LoadingSkeleton({ title, description }: LoadingSkeletonProps) {
export function LoadingSkeleton() {
return (
<AppLayout
breadcrumb={{

View File

@@ -1,6 +1,6 @@
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
import { LucideIcon } from 'lucide-react'
import { ReactNode } from 'react'
import type { LucideIcon } from 'lucide-react'
import type { ReactNode } from 'react'
interface StatisticCardProps {
title: string

View File

@@ -13,7 +13,8 @@ export function CreditsNav({ user }: CreditsNavProps) {
const [credits, setCredits] = useState(user.credits)
useEffect(() => {
const handleCreditsChanged = (data: { credits_after: number }) => {
const handleCreditsChanged = (...args: unknown[]) => {
const data = args[0] as { credits_after: number }
setCredits(data.credits_after)
}

View File

@@ -51,7 +51,8 @@ export function CompactPlayer({ className }: CompactPlayerProps) {
// Listen for player state updates
useEffect(() => {
const handlePlayerState = (newState: PlayerState) => {
const handlePlayerState = (...args: unknown[]) => {
const newState = args[0] as PlayerState
setState(newState)
}

View File

@@ -99,7 +99,8 @@ export function Player({ className, onPlayerModeChange }: PlayerProps) {
// Listen for player state updates
useEffect(() => {
const handlePlayerState = (newState: PlayerState) => {
const handlePlayerState = (...args: unknown[]) => {
const newState = args[0] as PlayerState
setState(newState)
}

View File

@@ -2,7 +2,7 @@ import { Badge } from '@/components/ui/badge'
import { Button } from '@/components/ui/button'
import { TableCell, TableRow } from '@/components/ui/table'
import type { Playlist } from '@/lib/api/services/playlists'
import { formatDate, formatDateDistanceToNow } from '@/utils/format-date'
import { formatDateDistanceToNow } from '@/utils/format-date'
import { formatDuration } from '@/utils/format-duration'
import { Calendar, Clock, Edit, Music, Play, User } from 'lucide-react'

View File

@@ -117,7 +117,13 @@ export function PlaylistDetailsCard({
{/* Edit/Save/Cancel buttons */}
<div className="pt-4 border-t">
{isEditMode ? (
{playlist.is_main ? (
<div className="text-center">
<p className="text-sm text-muted-foreground">
Main playlist details cannot be edited
</p>
</div>
) : isEditMode ? (
<div className="flex justify-end gap-2">
<Button variant="outline" onClick={onCancelEdit}>
<X className="h-4 w-4 mr-2" />

View File

@@ -7,13 +7,15 @@ import { Music, X } from 'lucide-react'
interface SimpleSortableRowProps {
sound: PlaylistSound
index: number
onRemoveSound: (soundId: number) => void
onRemoveSound?: (soundId: number) => void
isMainPlaylist?: boolean
}
export function SimpleSortableRow({
sound,
index,
onRemoveSound,
isMainPlaylist = false,
}: SimpleSortableRowProps) {
const {
attributes,
@@ -48,6 +50,7 @@ export function SimpleSortableRow({
<div className="font-medium truncate">{sound.name}</div>
</div>
{onRemoveSound && !isMainPlaylist && (
<Button
size="sm"
variant="ghost"
@@ -60,6 +63,7 @@ export function SimpleSortableRow({
>
<X className="h-3 w-3" />
</Button>
)}
</div>
)
}

View File

@@ -12,8 +12,9 @@ interface SortableTableRowProps {
index: number
onMoveSoundUp: (index: number) => void
onMoveSoundDown: (index: number) => void
onRemoveSound: (soundId: number) => void
onRemoveSound?: (soundId: number) => void
totalSounds: number
isMainPlaylist?: boolean
}
export function SortableTableRow({
@@ -23,6 +24,7 @@ export function SortableTableRow({
onMoveSoundDown,
onRemoveSound,
totalSounds,
isMainPlaylist = false,
}: SortableTableRowProps) {
const {
attributes,
@@ -97,6 +99,7 @@ export function SortableTableRow({
>
<ChevronDown className="h-4 w-4" />
</Button>
{onRemoveSound && !isMainPlaylist && (
<Button
size="sm"
variant="ghost"
@@ -106,6 +109,7 @@ export function SortableTableRow({
>
<Trash2 className="h-4 w-4" />
</Button>
)}
</div>
</TableCell>
</TableRow>

View File

@@ -2,7 +2,7 @@
* Simple event emitter for cross-component communication
*/
type EventHandler = (...args: unknown[]) => void
export type EventHandler = (...args: unknown[]) => void
class EventEmitter {
private events: Map<string, EventHandler[]> = new Map()

View File

@@ -158,7 +158,7 @@ export function DashboardPage() {
}, [fetchTopSounds])
if (loading) {
return <LoadingSkeleton title="Dashboard" description="Loading dashboard statistics..." />
return <LoadingSkeleton />
}
if (error || !soundboardStatistics || !trackStatistics) {

View File

@@ -282,6 +282,11 @@ export function PlaylistEditPage() {
}
const handleAddSoundToPlaylist = async (soundId: number) => {
if (playlist?.is_main) {
toast.error('Cannot add sounds to the main playlist')
return
}
try {
// Add at the end - backend should handle position gaps automatically
const position = sounds.length
@@ -328,6 +333,11 @@ export function PlaylistEditPage() {
}
const handleRemoveSound = async (soundId: number) => {
if (playlist?.is_main) {
toast.error('Cannot remove sounds from the main playlist')
return
}
try {
// Find the sound being removed
const removedSound = sounds.find(s => s.id === soundId)
@@ -417,6 +427,12 @@ export function PlaylistEditPage() {
const activeId = active.id as string
const overId = over.id as string
// Prevent adding sounds to main playlist
if (playlist?.is_main && activeId.startsWith('available-sound-')) {
toast.error('Cannot add sounds to the main playlist')
return
}
// Handle adding sound from available list to playlist
if (
activeId.startsWith('available-sound-') &&
@@ -585,6 +601,7 @@ export function PlaylistEditPage() {
Playlist Sounds ({sounds.length})
</CardTitle>
<div className="flex items-center gap-2">
{!playlist.is_main && (
<Button
variant={isAddMode ? 'default' : 'outline'}
size="sm"
@@ -601,6 +618,7 @@ export function PlaylistEditPage() {
<Plus className="h-4 w-4" />
)}
</Button>
)}
<Button
variant="outline"
size="sm"
@@ -621,6 +639,60 @@ export function PlaylistEditPage() {
<Skeleton key={i} className="h-12 w-full" />
))}
</div>
) : playlist.is_main ? (
// Main playlist: Only show sounds in read-only mode with reordering
sounds.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 in main playlist</p>
<p className="text-xs mt-1">
Main playlist sounds are managed automatically
</p>
</div>
) : (
<div className="rounded-md border">
<SortableContext
items={sounds.map(sound => `table-sound-${sound.id}`)}
strategy={verticalListSortingStrategy}
>
<Table>
<TableHeader>
<TableRow>
<TableHead className="w-16 text-center">
<div className="flex items-center justify-center gap-1">
<div className="flex flex-col">
<div className="w-1 h-1 bg-muted-foreground/40 rounded-full"></div>
<div className="w-1 h-1 bg-muted-foreground/40 rounded-full mt-0.5"></div>
<div className="w-1 h-1 bg-muted-foreground/40 rounded-full mt-0.5"></div>
</div>
<span className="text-xs">#</span>
</div>
</TableHead>
<TableHead>Name</TableHead>
<TableHead>Duration</TableHead>
<TableHead>Type</TableHead>
<TableHead>Plays</TableHead>
<TableHead className="w-32">Actions</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{sounds.map((sound, index) => (
<SortableTableRow
key={sound.id}
sound={sound}
index={index}
onMoveSoundUp={handleMoveSoundUp}
onMoveSoundDown={handleMoveSoundDown}
onRemoveSound={undefined} // Disable remove for main playlist
totalSounds={sounds.length}
isMainPlaylist={true}
/>
))}
</TableBody>
</Table>
</SortableContext>
</div>
)
) : isAddMode ? (
// Add Mode: Split layout with simplified playlist and available sounds
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
@@ -785,6 +857,7 @@ export function PlaylistEditPage() {
onMoveSoundDown={handleMoveSoundDown}
onRemoveSound={handleRemoveSound}
totalSounds={sounds.length}
isMainPlaylist={false}
/>
))}
</TableBody>

View File

@@ -141,7 +141,8 @@ export function SoundsPage() {
// Listen for sound_played events and update play_count
useEffect(() => {
const handleSoundPlayed = (eventData: SoundPlayedEventData) => {
const handleSoundPlayed = (...args: unknown[]) => {
const eventData = args[0] as SoundPlayedEventData
setSounds(prevSounds =>
prevSounds.map(sound =>
sound.id === eventData.sound_id