Files
sbd2-frontend/src/pages/PlaylistsPage.tsx

268 lines
8.1 KiB
TypeScript

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<Playlist[]>([])
const [loading, setLoading] = useState(true)
const [error, setError] = useState<string | null>(null)
// Search and sorting state
const [searchQuery, setSearchQuery] = useState('')
const [sortBy, setSortBy] = useState<PlaylistSortField>('name')
const [sortOrder, setSortOrder] = useState<SortOrder>('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 <PlaylistsLoading />
}
if (error) {
return <PlaylistsError error={error} onRetry={fetchPlaylists} />
}
if (playlists.length === 0) {
return <PlaylistsEmpty searchQuery={searchQuery} showFavoritesOnly={showFavoritesOnly} />
}
return (
<div className="space-y-4">
<PlaylistTable
playlists={playlists}
onEdit={handleEditPlaylist}
onSetCurrent={handleSetCurrent}
onFavoriteToggle={handleFavoriteToggle}
/>
<AppPagination
currentPage={currentPage}
totalPages={totalPages}
totalCount={totalCount}
pageSize={pageSize}
onPageChange={handlePageChange}
onPageSizeChange={handlePageSizeChange}
itemName="playlists"
/>
</div>
)
}
return (
<AppLayout
breadcrumb={{
items: [{ label: 'Dashboard', href: '/' }, { label: 'Playlists' }],
}}
>
<div className="flex-1 rounded-xl bg-muted/50 p-4">
<PlaylistsHeader
searchQuery={searchQuery}
onSearchChange={setSearchQuery}
sortBy={sortBy}
onSortByChange={setSortBy}
sortOrder={sortOrder}
onSortOrderChange={setSortOrder}
onRefresh={fetchPlaylists}
onCreateClick={() => setShowCreateDialog(true)}
loading={loading}
error={error}
playlistCount={totalCount}
showFavoritesOnly={showFavoritesOnly}
onFavoritesToggle={setShowFavoritesOnly}
/>
<CreatePlaylistDialog
open={showCreateDialog}
onOpenChange={setShowCreateDialog}
loading={createLoading}
name={newPlaylist.name}
description={newPlaylist.description}
genre={newPlaylist.genre}
onNameChange={name => setNewPlaylist(prev => ({ ...prev, name }))}
onDescriptionChange={description => setNewPlaylist(prev => ({ ...prev, description }))}
onGenreChange={genre => setNewPlaylist(prev => ({ ...prev, genre }))}
onSubmit={handleCreatePlaylist}
onCancel={handleCancelCreate}
/>
{renderContent()}
</div>
</AppLayout>
)
}