diff --git a/src/components/dashboard/TopSoundsSection.tsx b/src/components/dashboard/TopSoundsSection.tsx
index 21085d5..0dcdadc 100644
--- a/src/components/dashboard/TopSoundsSection.tsx
+++ b/src/components/dashboard/TopSoundsSection.tsx
@@ -130,7 +130,7 @@ export function TopSoundsSection({
)}
diff --git a/src/components/playlists/CreatePlaylistDialog.tsx b/src/components/playlists/CreatePlaylistDialog.tsx
new file mode 100644
index 0000000..9564997
--- /dev/null
+++ b/src/components/playlists/CreatePlaylistDialog.tsx
@@ -0,0 +1,105 @@
+import { Button } from '@/components/ui/button'
+import {
+ Dialog,
+ DialogContent,
+ DialogDescription,
+ DialogFooter,
+ DialogHeader,
+ DialogTitle,
+} from '@/components/ui/dialog'
+import { Input } from '@/components/ui/input'
+import { Label } from '@/components/ui/label'
+import { Textarea } from '@/components/ui/textarea'
+
+interface CreatePlaylistDialogProps {
+ open: boolean
+ onOpenChange: (open: boolean) => void
+ loading: boolean
+ name: string
+ description: string
+ genre: string
+ onNameChange: (name: string) => void
+ onDescriptionChange: (description: string) => void
+ onGenreChange: (genre: string) => void
+ onSubmit: () => void
+ onCancel: () => void
+}
+
+export function CreatePlaylistDialog({
+ open,
+ onOpenChange,
+ loading,
+ name,
+ description,
+ genre,
+ onNameChange,
+ onDescriptionChange,
+ onGenreChange,
+ onSubmit,
+ onCancel,
+}: CreatePlaylistDialogProps) {
+ return (
+
+ )
+}
\ No newline at end of file
diff --git a/src/components/playlists/PlaylistRow.tsx b/src/components/playlists/PlaylistRow.tsx
new file mode 100644
index 0000000..5898d2a
--- /dev/null
+++ b/src/components/playlists/PlaylistRow.tsx
@@ -0,0 +1,104 @@
+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 { formatDuration } from '@/utils/format-duration'
+import { Calendar, Clock, Edit, Music, Play, User } from 'lucide-react'
+
+interface PlaylistRowProps {
+ playlist: Playlist
+ onEdit: (playlist: Playlist) => void
+ onSetCurrent: (playlist: Playlist) => void
+}
+
+export function PlaylistRow({ playlist, onEdit, onSetCurrent }: PlaylistRowProps) {
+ const formatDate = (dateString: string) => {
+ return new Date(dateString).toLocaleDateString()
+ }
+
+ return (
+
+
+
+
+
+
{playlist.name}
+ {playlist.description && (
+
+ {playlist.description}
+
+ )}
+
+
+
+
+ {playlist.genre ? (
+ {playlist.genre}
+ ) : (
+ -
+ )}
+
+
+ {playlist.user_name ? (
+
+
+ {playlist.user_name}
+
+ ) : (
+ System
+ )}
+
+
+
+
+ {playlist.sound_count}
+
+
+
+
+
+ {formatDuration(playlist.total_duration || 0)}
+
+
+
+
+
+ {formatDate(playlist.created_at)}
+
+
+
+
+ {playlist.is_current && Current}
+ {playlist.is_main && Main}
+ {!playlist.is_current && !playlist.is_main && (
+ -
+ )}
+
+
+
+
+
+ {!playlist.is_current && (
+
+ )}
+
+
+
+ )
+}
\ No newline at end of file
diff --git a/src/components/playlists/PlaylistTable.tsx b/src/components/playlists/PlaylistTable.tsx
new file mode 100644
index 0000000..3c3267a
--- /dev/null
+++ b/src/components/playlists/PlaylistTable.tsx
@@ -0,0 +1,46 @@
+import { PlaylistRow } from '@/components/playlists/PlaylistRow'
+import {
+ Table,
+ TableBody,
+ TableHead,
+ TableHeader,
+ TableRow,
+} from '@/components/ui/table'
+import type { Playlist } from '@/lib/api/services/playlists'
+
+interface PlaylistTableProps {
+ playlists: Playlist[]
+ onEdit: (playlist: Playlist) => void
+ onSetCurrent: (playlist: Playlist) => void
+}
+
+export function PlaylistTable({ playlists, onEdit, onSetCurrent }: PlaylistTableProps) {
+ return (
+
+
+
+
+ Name
+ Genre
+ User
+ Tracks
+ Duration
+ Created
+ Status
+ Actions
+
+
+
+ {playlists.map(playlist => (
+
+ ))}
+
+
+
+ )
+}
\ No newline at end of file
diff --git a/src/components/playlists/PlaylistsHeader.tsx b/src/components/playlists/PlaylistsHeader.tsx
new file mode 100644
index 0000000..4e2a3bd
--- /dev/null
+++ b/src/components/playlists/PlaylistsHeader.tsx
@@ -0,0 +1,134 @@
+import { Button } from '@/components/ui/button'
+import { Input } from '@/components/ui/input'
+import {
+ Select,
+ SelectContent,
+ SelectItem,
+ SelectTrigger,
+ SelectValue,
+} from '@/components/ui/select'
+import type { PlaylistSortField, SortOrder } from '@/lib/api/services/playlists'
+import { Plus, RefreshCw, Search, SortAsc, SortDesc, X } from 'lucide-react'
+
+interface PlaylistsHeaderProps {
+ searchQuery: string
+ onSearchChange: (query: string) => void
+ sortBy: PlaylistSortField
+ onSortByChange: (sortBy: PlaylistSortField) => void
+ sortOrder: SortOrder
+ onSortOrderChange: (order: SortOrder) => void
+ onRefresh: () => void
+ onCreateClick: () => void
+ loading: boolean
+ error: string | null
+ playlistCount: number
+}
+
+export function PlaylistsHeader({
+ searchQuery,
+ onSearchChange,
+ sortBy,
+ onSortByChange,
+ sortOrder,
+ onSortOrderChange,
+ onRefresh,
+ onCreateClick,
+ loading,
+ error,
+ playlistCount,
+}: PlaylistsHeaderProps) {
+ return (
+ <>
+ {/* Header */}
+
+
+
Playlists
+
+ Manage and browse your soundboard playlists
+
+
+
+
+ {!loading && !error && (
+
+ {playlistCount} playlist{playlistCount !== 1 ? 's' : ''}
+
+ )}
+
+
+
+ {/* Search and Sort Controls */}
+
+
+
+
+ onSearchChange(e.target.value)}
+ className="pl-9 pr-9"
+ />
+ {searchQuery && (
+
+ )}
+
+
+
+
+
+
+
+
+
+
+
+ >
+ )
+}
\ No newline at end of file
diff --git a/src/components/playlists/PlaylistsLoadingStates.tsx b/src/components/playlists/PlaylistsLoadingStates.tsx
new file mode 100644
index 0000000..5851362
--- /dev/null
+++ b/src/components/playlists/PlaylistsLoadingStates.tsx
@@ -0,0 +1,58 @@
+import { Skeleton } from '@/components/ui/skeleton'
+import { AlertCircle, Music } from 'lucide-react'
+
+interface PlaylistsLoadingProps {
+ count?: number
+}
+
+export function PlaylistsLoading({ count = 5 }: PlaylistsLoadingProps) {
+ return (
+
+
+ {Array.from({ length: count }).map((_, i) => (
+
+ ))}
+
+ )
+}
+
+interface PlaylistsErrorProps {
+ error: string
+ onRetry: () => void
+}
+
+export function PlaylistsError({ error, onRetry }: PlaylistsErrorProps) {
+ return (
+
+
+
Failed to load playlists
+
{error}
+
+
+ )
+}
+
+interface PlaylistsEmptyProps {
+ searchQuery: string
+}
+
+export function PlaylistsEmpty({ searchQuery }: PlaylistsEmptyProps) {
+ return (
+
+
+
+
+
No playlists found
+
+ {searchQuery
+ ? 'No playlists match your search criteria.'
+ : 'No playlists are available.'}
+
+
+ )
+}
\ No newline at end of file
diff --git a/src/pages/PlaylistsPage.tsx b/src/pages/PlaylistsPage.tsx
index a11fde5..1de4057 100644
--- a/src/pages/PlaylistsPage.tsx
+++ b/src/pages/PlaylistsPage.tsx
@@ -1,56 +1,18 @@
import { AppLayout } from '@/components/AppLayout'
-import { Badge } from '@/components/ui/badge'
-import { Button } from '@/components/ui/button'
+import { CreatePlaylistDialog } from '@/components/playlists/CreatePlaylistDialog'
+import { PlaylistsHeader } from '@/components/playlists/PlaylistsHeader'
import {
- Dialog,
- DialogContent,
- DialogDescription,
- DialogFooter,
- DialogHeader,
- DialogTitle,
- DialogTrigger,
-} from '@/components/ui/dialog'
-import { Input } from '@/components/ui/input'
-import { Label } from '@/components/ui/label'
-import {
- Select,
- SelectContent,
- SelectItem,
- SelectTrigger,
- SelectValue,
-} from '@/components/ui/select'
-import { Skeleton } from '@/components/ui/skeleton'
-import {
- Table,
- TableBody,
- TableCell,
- TableHead,
- TableHeader,
- TableRow,
-} from '@/components/ui/table'
-import { Textarea } from '@/components/ui/textarea'
+ PlaylistsEmpty,
+ PlaylistsError,
+ PlaylistsLoading,
+} from '@/components/playlists/PlaylistsLoadingStates'
+import { PlaylistTable } from '@/components/playlists/PlaylistTable'
import {
type Playlist,
type PlaylistSortField,
type SortOrder,
playlistsService,
} from '@/lib/api/services/playlists'
-import { formatDuration } from '@/utils/format-duration'
-import {
- AlertCircle,
- Calendar,
- Clock,
- Edit,
- Music,
- Play,
- Plus,
- RefreshCw,
- Search,
- SortAsc,
- SortDesc,
- User,
- X,
-} from 'lucide-react'
import { useEffect, useState } from 'react'
import { useNavigate } from 'react-router'
import { toast } from 'sonner'
@@ -160,164 +122,29 @@ export function PlaylistsPage() {
}
}
- const formatDate = (dateString: string) => {
- return new Date(dateString).toLocaleDateString()
+ const handleEditPlaylist = (playlist: Playlist) => {
+ navigate(`/playlists/${playlist.id}/edit`)
}
const renderContent = () => {
if (loading) {
- return (
-
-
- {Array.from({ length: 5 }).map((_, i) => (
-
- ))}
-
- )
+ return
}
if (error) {
- return (
-
-
-
- Failed to load playlists
-
-
{error}
-
-
- )
+ return
}
if (playlists.length === 0) {
- return (
-
-
-
-
-
No playlists found
-
- {searchQuery
- ? 'No playlists match your search criteria.'
- : 'No playlists are available.'}
-
-
- )
+ return
}
return (
-
-
-
-
- Name
- Genre
- User
- Tracks
- Duration
- Created
- Status
- Actions
-
-
-
- {playlists.map(playlist => (
-
-
-
-
-
-
- {playlist.name}
-
- {playlist.description && (
-
- {playlist.description}
-
- )}
-
-
-
-
- {playlist.genre ? (
- {playlist.genre}
- ) : (
- -
- )}
-
-
- {playlist.user_name ? (
-
-
- {playlist.user_name}
-
- ) : (
- System
- )}
-
-
-
-
- {playlist.sound_count}
-
-
-
-
-
- {formatDuration(playlist.total_duration || 0)}
-
-
-
-
-
- {formatDate(playlist.created_at)}
-
-
-
-
- {playlist.is_current && (
- Current
- )}
- {playlist.is_main && Main}
- {!playlist.is_current && !playlist.is_main && (
- -
- )}
-
-
-
-
-
- {!playlist.is_current && (
-
- )}
-
-
-
- ))}
-
-
-
+
)
}
@@ -328,174 +155,33 @@ export function PlaylistsPage() {
}}
>
-
-
-
Playlists
-
- Manage and browse your soundboard playlists
-
-
-
-
- {!loading && !error && (
-
- {playlists.length} playlist{playlists.length !== 1 ? 's' : ''}
-
- )}
-
-
+
setShowCreateDialog(true)}
+ loading={loading}
+ error={error}
+ playlistCount={playlists.length}
+ />
- {/* Search and Sort Controls */}
-
-
-
-
- setSearchQuery(e.target.value)}
- className="pl-9 pr-9"
- />
- {searchQuery && (
-
- )}
-
-
-
-
-
-
-
-
-
-
-
+ setNewPlaylist(prev => ({ ...prev, name }))}
+ onDescriptionChange={description => setNewPlaylist(prev => ({ ...prev, description }))}
+ onGenreChange={genre => setNewPlaylist(prev => ({ ...prev, genre }))}
+ onSubmit={handleCreatePlaylist}
+ onCancel={handleCancelCreate}
+ />
{renderContent()}