feat: implement favorites functionality across playlists components

This commit is contained in:
JSC
2025-08-16 21:41:57 +02:00
parent 1027a67e37
commit ad466e2f91
6 changed files with 108 additions and 14 deletions

View File

@@ -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"

View File

@@ -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>

View File

@@ -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"

View File

@@ -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>
)