import { Button } from "@/components/ui/button"; import { Checkbox } from "@/components/ui/checkbox"; import { Download, CheckCircle, XCircle, FileCheck, FileText, Globe, ImageDown, Play, Pause } from "lucide-react"; import { Spinner } from "@/components/ui/spinner"; import { Tooltip, TooltipContent, TooltipTrigger, } from "@/components/ui/tooltip"; import { Pagination, PaginationContent, PaginationEllipsis, PaginationItem, PaginationLink, PaginationNext, PaginationPrevious, } from "@/components/ui/pagination"; import type { TrackMetadata, TrackAvailability } from "@/types/api"; import { TidalIcon, QobuzIcon, AmazonIcon, DeezerIcon } from "./PlatformIcons"; import { usePreview } from "@/hooks/usePreview"; interface TrackListProps { tracks: TrackMetadata[]; searchQuery: string; sortBy: string; selectedTracks: string[]; downloadedTracks: Set; failedTracks: Set; skippedTracks: Set; downloadingTrack: string | null; isDownloading: boolean; currentPage: number; itemsPerPage: number; showCheckboxes?: boolean; hideAlbumColumn?: boolean; folderName?: string; isArtistDiscography?: boolean; downloadedLyrics?: Set; failedLyrics?: Set; skippedLyrics?: Set; downloadingLyricsTrack?: string | null; checkingAvailabilityTrack?: string | null; availabilityMap?: Map; downloadedCovers?: Set; failedCovers?: Set; skippedCovers?: Set; downloadingCoverTrack?: string | null; onToggleTrack: (id: string) => void; onToggleSelectAll: (tracks: TrackMetadata[]) => void; onDownloadTrack: (id: string, name: string, artists: string, albumName: string, spotifyId?: string, folderName?: string, durationMs?: number, position?: number, albumArtist?: string, releaseDate?: string, coverUrl?: string, spotifyTrackNumber?: number, spotifyDiscNumber?: number, spotifyTotalTracks?: number, spotifyTotalDiscs?: number, copyright?: string, publisher?: string) => void; onDownloadLyrics?: (spotifyId: string, name: string, artists: string, albumName: string, folderName?: string, isArtistDiscography?: boolean, position?: number, albumArtist?: string, releaseDate?: string, discNumber?: number) => void; onCheckAvailability?: (spotifyId: string) => void; onDownloadCover?: (coverUrl: string, trackName: string, artistName: string, albumName: string, folderName?: string, isArtistDiscography?: boolean, position?: number, trackId?: string, albumArtist?: string, releaseDate?: string, discNumber?: number) => void; onPageChange: (page: number) => void; onAlbumClick?: (album: { id: string; name: string; external_urls: string; }) => void; onArtistClick?: (artist: { id: string; name: string; external_urls: string; }) => void; onTrackClick?: (track: TrackMetadata) => void; } export function TrackList({ tracks, searchQuery, sortBy, selectedTracks, downloadedTracks, failedTracks, skippedTracks, downloadingTrack, isDownloading, currentPage, itemsPerPage, showCheckboxes = false, hideAlbumColumn = false, folderName, isArtistDiscography = false, downloadedLyrics, failedLyrics, skippedLyrics, downloadingLyricsTrack, checkingAvailabilityTrack, availabilityMap, downloadedCovers, failedCovers, skippedCovers, downloadingCoverTrack, onToggleTrack, onToggleSelectAll, onDownloadTrack, onDownloadLyrics, onCheckAvailability, onDownloadCover, onPageChange, onAlbumClick, onArtistClick, onTrackClick, }: TrackListProps) { const { playPreview, loadingPreview, playingTrack } = usePreview(); let filteredTracks = tracks.filter((track) => { if (!searchQuery) return true; const query = searchQuery.toLowerCase(); return (track.name.toLowerCase().includes(query) || track.artists.toLowerCase().includes(query) || track.album_name.toLowerCase().includes(query)); }); if (sortBy === "title-asc") { filteredTracks = [...filteredTracks].sort((a, b) => a.name.localeCompare(b.name)); } else if (sortBy === "title-desc") { filteredTracks = [...filteredTracks].sort((a, b) => b.name.localeCompare(a.name)); } else if (sortBy === "artist-asc") { filteredTracks = [...filteredTracks].sort((a, b) => a.artists.localeCompare(b.artists)); } else if (sortBy === "artist-desc") { filteredTracks = [...filteredTracks].sort((a, b) => b.artists.localeCompare(a.artists)); } else if (sortBy === "duration-asc") { filteredTracks = [...filteredTracks].sort((a, b) => a.duration_ms - b.duration_ms); } else if (sortBy === "duration-desc") { filteredTracks = [...filteredTracks].sort((a, b) => b.duration_ms - a.duration_ms); } else if (sortBy === "plays-asc") { filteredTracks = [...filteredTracks].sort((a, b) => { const aPlays = a.plays ? parseInt(a.plays, 10) : 0; const bPlays = b.plays ? parseInt(b.plays, 10) : 0; if (isNaN(aPlays)) return 1; if (isNaN(bPlays)) return -1; return aPlays - bPlays; }); } else if (sortBy === "plays-desc") { filteredTracks = [...filteredTracks].sort((a, b) => { const aPlays = a.plays ? parseInt(a.plays, 10) : 0; const bPlays = b.plays ? parseInt(b.plays, 10) : 0; if (isNaN(aPlays)) return 1; if (isNaN(bPlays)) return -1; return bPlays - aPlays; }); } else if (sortBy === "downloaded") { filteredTracks = [...filteredTracks].sort((a, b) => { const aDownloaded = a.spotify_id ? downloadedTracks.has(a.spotify_id) : false; const bDownloaded = b.spotify_id ? downloadedTracks.has(b.spotify_id) : false; return (bDownloaded ? 1 : 0) - (aDownloaded ? 1 : 0); }); } else if (sortBy === "not-downloaded") { filteredTracks = [...filteredTracks].sort((a, b) => { const aDownloaded = a.spotify_id ? downloadedTracks.has(a.spotify_id) : false; const bDownloaded = b.spotify_id ? downloadedTracks.has(b.spotify_id) : false; return (aDownloaded ? 1 : 0) - (bDownloaded ? 1 : 0); }); } else if (sortBy === "failed") { filteredTracks = [...filteredTracks].sort((a, b) => { const aFailed = a.spotify_id ? failedTracks.has(a.spotify_id) : false; const bFailed = b.spotify_id ? failedTracks.has(b.spotify_id) : false; return (bFailed ? 1 : 0) - (aFailed ? 1 : 0); }); } const totalPages = Math.ceil(filteredTracks.length / itemsPerPage); const startIndex = (currentPage - 1) * itemsPerPage; const endIndex = startIndex + itemsPerPage; const paginatedTracks = filteredTracks.slice(startIndex, endIndex); const getPaginationPages = (current: number, total: number): (number | 'ellipsis')[] => { if (total <= 10) { return Array.from({ length: total }, (_, i) => i + 1); } const pages: (number | 'ellipsis')[] = []; pages.push(1); if (current <= 7) { for (let i = 2; i <= 10; i++) { pages.push(i); } pages.push('ellipsis'); pages.push(total); } else if (current >= total - 7) { pages.push('ellipsis'); for (let i = total - 9; i <= total; i++) { pages.push(i); } } else { pages.push('ellipsis'); pages.push(current - 1); pages.push(current); pages.push(current + 1); pages.push('ellipsis'); pages.push(total); } return pages; }; const tracksWithId = filteredTracks.filter((track) => track.spotify_id); const allSelected = tracksWithId.length > 0 && tracksWithId.every((track) => selectedTracks.includes(track.spotify_id!)); const formatDuration = (ms: number) => { const minutes = Math.floor(ms / 60000); const seconds = Math.floor((ms % 60000) / 1000); return `${minutes}:${seconds.toString().padStart(2, "0")}`; }; const formatPlays = (plays: string | undefined) => { if (!plays) return ""; const num = parseInt(plays, 10); if (isNaN(num)) return plays; return num.toLocaleString(); }; return (
{showCheckboxes && ()} {!hideAlbumColumn && ()} {paginatedTracks.map((track, index) => ( {showCheckboxes && ()} {!hideAlbumColumn && ()} ))}
onToggleSelectAll(filteredTracks)}/> # Title Album Duration Plays Actions
{track.spotify_id && ( onToggleTrack(track.spotify_id!)}/>)}
{startIndex + index + 1} {track.status && (track.status === "UP" || track.status === "DOWN" || track.status === "NEW") && ( {track.status === "NEW" ? "●" : track.status === "UP" ? "▲" : "▼"} )}
{track.images && ({track.name})}
{onTrackClick ? ( onTrackClick(track)}> {track.name} ) : ({track.name})} {track.is_explicit && (E)} {track.spotify_id && skippedTracks.has(track.spotify_id) ? () : track.spotify_id && downloadedTracks.has(track.spotify_id) ? () : track.spotify_id && failedTracks.has(track.spotify_id) ? () : null}
{track.artists_data && track.artists_data.length > 0 ? ((() => { const artistNames = track.artists.split(", ").map(name => name.trim()); return artistNames.map((name, i) => { const artistData = track.artists_data![i]; const hasArtistData = artistData && artistData.id && artistData.external_urls; return ( {onArtistClick && hasArtistData ? ( onArtistClick({ id: artistData.id, name: name, external_urls: artistData.external_urls, })}> {name} ) : (name)} {i < artistNames.length - 1 && ", "} ); }); })()) : onArtistClick && track.artist_id && track.artist_url ? ( onArtistClick({ id: track.artist_id!, name: track.artists, external_urls: track.artist_url!, })}> {track.artists} ) : (track.artists)}
{onAlbumClick && track.album_id && track.album_url ? ( onAlbumClick({ id: track.album_id!, name: track.album_name, external_urls: track.album_url!, })}> {track.album_name} ) : (track.album_name)} {formatDuration(track.duration_ms)} {track.plays ? formatPlays(track.plays) : ""}
{track.spotify_id && ( {downloadingTrack === track.spotify_id ? (

Downloading...

) : skippedTracks.has(track.spotify_id) ? (

Already exists

) : downloadedTracks.has(track.spotify_id) ? (

Downloaded

) : failedTracks.has(track.spotify_id) ? (

Failed

) : (

Download Track

)}
)} {track.spotify_id && (

{playingTrack === track.spotify_id ? "Stop Preview" : "Play Preview"}

)} {track.spotify_id && onDownloadLyrics && (

Download Lyric

)} {track.images && onDownloadCover && (

Download Cover

)} {track.spotify_id && onCheckAvailability && ( {availabilityMap?.has(track.spotify_id) ? (
) : (

Check Availability

)}
)}
{totalPages > 1 && ( { e.preventDefault(); if (currentPage > 1) onPageChange(currentPage - 1); }} className={currentPage === 1 ? "pointer-events-none opacity-50" : "cursor-pointer"}/> {getPaginationPages(currentPage, totalPages).map((page, index) => (page === 'ellipsis' ? ( ) : ( { e.preventDefault(); onPageChange(page); }} isActive={currentPage === page} className="cursor-pointer"> {page} )))} { e.preventDefault(); if (currentPage < totalPages) onPageChange(currentPage + 1); }} className={currentPage === totalPages ? "pointer-events-none opacity-50" : "cursor-pointer"}/> )}
); }