feat: implement favorites functionality across playlists components
This commit is contained in:
@@ -4,15 +4,22 @@ import { TableCell, TableRow } from '@/components/ui/table'
|
||||
import type { Playlist } from '@/lib/api/services/playlists'
|
||||
import { formatDateDistanceToNow } from '@/utils/format-date'
|
||||
import { formatDuration } from '@/utils/format-duration'
|
||||
import { Calendar, Clock, Edit, Music, Play, User } from 'lucide-react'
|
||||
import { cn } from '@/lib/utils'
|
||||
import { Calendar, Clock, Edit, Heart, Music, Play, User } from 'lucide-react'
|
||||
|
||||
interface PlaylistRowProps {
|
||||
playlist: Playlist
|
||||
onEdit: (playlist: Playlist) => void
|
||||
onSetCurrent: (playlist: Playlist) => void
|
||||
onFavoriteToggle?: (playlistId: number, isFavorited: boolean) => void
|
||||
}
|
||||
|
||||
export function PlaylistRow({ playlist, onEdit, onSetCurrent }: PlaylistRowProps) {
|
||||
export function PlaylistRow({ playlist, onEdit, onSetCurrent, onFavoriteToggle }: PlaylistRowProps) {
|
||||
const handleFavoriteToggle = () => {
|
||||
if (onFavoriteToggle) {
|
||||
onFavoriteToggle(playlist.id, !playlist.is_favorited)
|
||||
}
|
||||
}
|
||||
return (
|
||||
<TableRow className="hover:bg-muted/50">
|
||||
<TableCell>
|
||||
@@ -76,6 +83,24 @@ export function PlaylistRow({ playlist, onEdit, onSetCurrent }: PlaylistRowProps
|
||||
</TableCell>
|
||||
<TableCell className="text-center">
|
||||
<div className="flex items-center justify-center gap-1">
|
||||
{onFavoriteToggle && (
|
||||
<Button
|
||||
size="sm"
|
||||
variant="ghost"
|
||||
onClick={handleFavoriteToggle}
|
||||
className="h-8 w-8 p-0"
|
||||
title={playlist.is_favorited ? "Remove from favorites" : "Add to favorites"}
|
||||
>
|
||||
<Heart
|
||||
className={cn(
|
||||
'h-4 w-4 transition-all duration-200',
|
||||
playlist.is_favorited
|
||||
? 'fill-current text-red-500 hover:text-red-600'
|
||||
: 'text-muted-foreground hover:text-foreground'
|
||||
)}
|
||||
/>
|
||||
</Button>
|
||||
)}
|
||||
<Button
|
||||
size="sm"
|
||||
variant="ghost"
|
||||
|
||||
@@ -12,9 +12,10 @@ interface PlaylistTableProps {
|
||||
playlists: Playlist[]
|
||||
onEdit: (playlist: Playlist) => void
|
||||
onSetCurrent: (playlist: Playlist) => void
|
||||
onFavoriteToggle?: (playlistId: number, isFavorited: boolean) => void
|
||||
}
|
||||
|
||||
export function PlaylistTable({ playlists, onEdit, onSetCurrent }: PlaylistTableProps) {
|
||||
export function PlaylistTable({ playlists, onEdit, onSetCurrent, onFavoriteToggle }: PlaylistTableProps) {
|
||||
return (
|
||||
<div className="rounded-md border">
|
||||
<Table>
|
||||
@@ -37,6 +38,7 @@ export function PlaylistTable({ playlists, onEdit, onSetCurrent }: PlaylistTable
|
||||
playlist={playlist}
|
||||
onEdit={onEdit}
|
||||
onSetCurrent={onSetCurrent}
|
||||
onFavoriteToggle={onFavoriteToggle}
|
||||
/>
|
||||
))}
|
||||
</TableBody>
|
||||
|
||||
@@ -8,7 +8,7 @@ import {
|
||||
SelectValue,
|
||||
} from '@/components/ui/select'
|
||||
import type { PlaylistSortField, SortOrder } from '@/lib/api/services/playlists'
|
||||
import { Plus, RefreshCw, Search, SortAsc, SortDesc, X } from 'lucide-react'
|
||||
import { Heart, Plus, RefreshCw, Search, SortAsc, SortDesc, X } from 'lucide-react'
|
||||
|
||||
interface PlaylistsHeaderProps {
|
||||
searchQuery: string
|
||||
@@ -22,6 +22,8 @@ interface PlaylistsHeaderProps {
|
||||
loading: boolean
|
||||
error: string | null
|
||||
playlistCount: number
|
||||
showFavoritesOnly: boolean
|
||||
onFavoritesToggle: (show: boolean) => void
|
||||
}
|
||||
|
||||
export function PlaylistsHeader({
|
||||
@@ -36,6 +38,8 @@ export function PlaylistsHeader({
|
||||
loading,
|
||||
error,
|
||||
playlistCount,
|
||||
showFavoritesOnly,
|
||||
onFavoritesToggle,
|
||||
}: PlaylistsHeaderProps) {
|
||||
return (
|
||||
<>
|
||||
@@ -50,7 +54,10 @@ export function PlaylistsHeader({
|
||||
<div className="flex items-center gap-4">
|
||||
{!loading && !error && (
|
||||
<div className="text-sm text-muted-foreground">
|
||||
{playlistCount} playlist{playlistCount !== 1 ? 's' : ''}
|
||||
{showFavoritesOnly
|
||||
? `${playlistCount} favorite playlist${playlistCount !== 1 ? 's' : ''}`
|
||||
: `${playlistCount} playlist${playlistCount !== 1 ? 's' : ''}`
|
||||
}
|
||||
</div>
|
||||
)}
|
||||
<Button onClick={onCreateClick}>
|
||||
@@ -116,6 +123,15 @@ export function PlaylistsHeader({
|
||||
)}
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
variant={showFavoritesOnly ? "default" : "outline"}
|
||||
size="icon"
|
||||
onClick={() => onFavoritesToggle(!showFavoritesOnly)}
|
||||
title={showFavoritesOnly ? "Show all playlists" : "Show only favorites"}
|
||||
>
|
||||
<Heart className={`h-4 w-4 ${showFavoritesOnly ? 'fill-current' : ''}`} />
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
variant="outline"
|
||||
size="icon"
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Skeleton } from '@/components/ui/skeleton'
|
||||
import { AlertCircle, Music } from 'lucide-react'
|
||||
import { AlertCircle } from 'lucide-react'
|
||||
|
||||
interface PlaylistsLoadingProps {
|
||||
count?: number
|
||||
@@ -39,19 +39,25 @@ export function PlaylistsError({ error, onRetry }: PlaylistsErrorProps) {
|
||||
|
||||
interface PlaylistsEmptyProps {
|
||||
searchQuery: string
|
||||
showFavoritesOnly?: boolean
|
||||
}
|
||||
|
||||
export function PlaylistsEmpty({ searchQuery }: PlaylistsEmptyProps) {
|
||||
export function PlaylistsEmpty({ searchQuery, showFavoritesOnly = false }: PlaylistsEmptyProps) {
|
||||
return (
|
||||
<div className="flex flex-col items-center justify-center py-12 text-center">
|
||||
<div className="h-12 w-12 rounded-full bg-muted flex items-center justify-center mb-4">
|
||||
<Music className="h-6 w-6 text-muted-foreground" />
|
||||
<span className="text-2xl">{showFavoritesOnly ? '💝' : '🎵'}</span>
|
||||
</div>
|
||||
<h3 className="text-lg font-semibold mb-2">No playlists found</h3>
|
||||
<h3 className="text-lg font-semibold mb-2">
|
||||
{showFavoritesOnly ? 'No favorite playlists found' : 'No playlists found'}
|
||||
</h3>
|
||||
<p className="text-muted-foreground">
|
||||
{searchQuery
|
||||
? 'No playlists match your search criteria.'
|
||||
: 'No playlists are available.'}
|
||||
{showFavoritesOnly
|
||||
? 'You haven\'t favorited any playlists yet. Click the heart icon on playlists to add them to your favorites.'
|
||||
: searchQuery
|
||||
? 'No playlists match your search criteria.'
|
||||
: 'No playlists are available.'
|
||||
}
|
||||
</p>
|
||||
</div>
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user