import { AppLayout } from '@/components/AppLayout' import { AppPagination } from '@/components/AppPagination' import { CreatePlaylistDialog } from '@/components/playlists/CreatePlaylistDialog' import { PlaylistsHeader } from '@/components/playlists/PlaylistsHeader' import { 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 { favoritesService } from '@/lib/api/services/favorites' import { useEffect, useState } from 'react' import { useNavigate } from 'react-router' import { toast } from 'sonner' export function PlaylistsPage() { const navigate = useNavigate() const [playlists, setPlaylists] = useState([]) const [loading, setLoading] = useState(true) const [error, setError] = useState(null) // Search and sorting state const [searchQuery, setSearchQuery] = useState('') const [sortBy, setSortBy] = useState('name') const [sortOrder, setSortOrder] = useState('asc') const [showFavoritesOnly, setShowFavoritesOnly] = useState(false) // Pagination state const [currentPage, setCurrentPage] = useState(1) const [totalPages, setTotalPages] = useState(1) const [totalCount, setTotalCount] = useState(0) const [pageSize, setPageSize] = useState(10) // Create playlist dialog state const [showCreateDialog, setShowCreateDialog] = useState(false) const [createLoading, setCreateLoading] = useState(false) const [newPlaylist, setNewPlaylist] = useState({ name: '', description: '', genre: '', }) // Debounce search query const [debouncedSearchQuery, setDebouncedSearchQuery] = useState(searchQuery) useEffect(() => { const handler = setTimeout(() => { setDebouncedSearchQuery(searchQuery) }, 300) return () => clearTimeout(handler) }, [searchQuery]) const fetchPlaylists = async () => { try { setLoading(true) setError(null) const response = await playlistsService.getPlaylists({ search: debouncedSearchQuery.trim() || undefined, sort_by: sortBy, sort_order: sortOrder, favorites_only: showFavoritesOnly, page: currentPage, limit: pageSize, }) setPlaylists(response.playlists) setTotalPages(response.total_pages) setTotalCount(response.total) } catch (err) { const errorMessage = err instanceof Error ? err.message : 'Failed to fetch playlists' setError(errorMessage) toast.error(errorMessage) } finally { setLoading(false) } } useEffect(() => { fetchPlaylists() }, [debouncedSearchQuery, sortBy, sortOrder, showFavoritesOnly, currentPage, pageSize]) // Reset to page 1 when filters change useEffect(() => { if (currentPage !== 1) { setCurrentPage(1) } }, [debouncedSearchQuery, sortBy, sortOrder, showFavoritesOnly, pageSize]) const handlePageChange = (page: number) => { setCurrentPage(page) } const handlePageSizeChange = (size: number) => { setPageSize(size) setCurrentPage(1) // Reset to first page when changing page size } const handleCreatePlaylist = async () => { if (!newPlaylist.name.trim()) { toast.error('Playlist name is required') return } try { setCreateLoading(true) await playlistsService.createPlaylist({ name: newPlaylist.name.trim(), description: newPlaylist.description.trim() || undefined, genre: newPlaylist.genre.trim() || undefined, }) toast.success(`Playlist "${newPlaylist.name}" created successfully`) // Reset form and close dialog setNewPlaylist({ name: '', description: '', genre: '' }) setShowCreateDialog(false) // Refresh the playlists list fetchPlaylists() } catch (err) { const errorMessage = err instanceof Error ? err.message : 'Failed to create playlist' toast.error(errorMessage) } finally { setCreateLoading(false) } } const handleCancelCreate = () => { setNewPlaylist({ name: '', description: '', genre: '' }) setShowCreateDialog(false) } const handleSetCurrent = async (playlist: Playlist) => { try { await playlistsService.setCurrentPlaylist(playlist.id) toast.success(`"${playlist.name}" is now the current playlist`) // Refresh the playlists list to update the current status fetchPlaylists() } catch (err) { const errorMessage = err instanceof Error ? err.message : 'Failed to set current playlist' toast.error(errorMessage) } } const handleEditPlaylist = (playlist: Playlist) => { navigate(`/playlists/${playlist.id}/edit`) } const handleFavoriteToggle = async (playlistId: number, shouldFavorite: boolean) => { try { if (shouldFavorite) { await favoritesService.addPlaylistFavorite(playlistId) toast.success('Added to favorites') } else { await favoritesService.removePlaylistFavorite(playlistId) toast.success('Removed from favorites') } // Update the playlist in the local state setPlaylists(prevPlaylists => prevPlaylists.map(playlist => playlist.id === playlistId ? { ...playlist, is_favorited: shouldFavorite, favorite_count: shouldFavorite ? playlist.favorite_count + 1 : Math.max(0, playlist.favorite_count - 1), } : playlist, ), ) } catch (error) { toast.error( `Failed to ${shouldFavorite ? 'add to' : 'remove from'} favorites: ${ error instanceof Error ? error.message : 'Unknown error' }`, ) } } const renderContent = () => { if (loading) { return } if (error) { return } if (playlists.length === 0) { return } return (
) } return (
setShowCreateDialog(true)} loading={loading} error={error} playlistCount={totalCount} showFavoritesOnly={showFavoritesOnly} onFavoritesToggle={setShowFavoritesOnly} /> setNewPlaylist(prev => ({ ...prev, name }))} onDescriptionChange={description => setNewPlaylist(prev => ({ ...prev, description }))} onGenreChange={genre => setNewPlaylist(prev => ({ ...prev, genre }))} onSubmit={handleCreatePlaylist} onCancel={handleCancelCreate} /> {renderContent()}
) }