"use client"; import { useState, useEffect } from "react"; import { useRouter, useSearchParams } from "next/navigation"; import { useAudio } from "@/lib/audio-context"; import { ConfirmDialog } from "@/components/ui/ConfirmDialog"; import { Tab, DeleteDialogState } from "@/features/library/types"; import { useLibraryData, LibraryFilter, SortOption, } from "@/features/library/hooks/useLibraryData"; import { api } from "@/lib/api"; import { useLibraryActions } from "@/features/library/hooks/useLibraryActions"; import { LibraryHeader } from "@/features/library/components/LibraryHeader"; import { LibraryTabs } from "@/features/library/components/LibraryTabs"; import { ArtistsGrid } from "@/features/library/components/ArtistsGrid"; import { AlbumsGrid } from "@/features/library/components/AlbumsGrid"; import { TracksList } from "@/features/library/components/TracksList"; import { Shuffle, ListFilter } from "lucide-react"; export default function LibraryPage() { const router = useRouter(); const searchParams = useSearchParams(); const { currentTrack, playTracks } = useAudio(); // Get active tab from URL params, default to "artists" const activeTab = (searchParams.get("tab") as Tab) || "artists"; // Filter state (owned = your library, discovery = discovery weekly artists) const [filter, setFilter] = useState("owned"); // Sort and pagination state const [sortBy, setSortBy] = useState("name"); const [itemsPerPage, setItemsPerPage] = useState(50); const [currentPage, setCurrentPage] = useState(1); const [showFilters, setShowFilters] = useState(false); // Use custom hooks with server-side pagination const { artists, albums, tracks, isLoading, reloadData, pagination } = useLibraryData({ activeTab, filter, sortBy, itemsPerPage, currentPage, }); const { playArtist, playAlbum, addTrackToQueue, addTrackToPlaylist, deleteArtist, deleteAlbum, deleteTrack, } = useLibraryActions(); // Reset page and filter when tab changes useEffect(() => { setCurrentPage(1); // Reset filter to 'owned' when switching to tracks tab (which doesn't support filter) if (activeTab === "tracks") { setFilter("owned"); } }, [activeTab]); // Reset page when filter or sort changes useEffect(() => { setCurrentPage(1); }, [filter, sortBy, itemsPerPage]); // Get total items and pages from pagination const totalItems = pagination.total; const totalPages = pagination.totalPages; // Delete confirmation dialog state const [deleteConfirm, setDeleteConfirm] = useState({ isOpen: false, type: "track", id: "", title: "", }); // Change tab function const changeTab = (tab: Tab) => { router.push(`/library?tab=${tab}`, { scroll: false }); }; // Helper to convert library Track to audio context Track format const formatTracksForAudio = (libraryTracks: typeof tracks) => { return libraryTracks.map((track) => ({ id: track.id, title: track.title, duration: track.duration, artist: { id: track.album?.artist?.id, name: track.album?.artist?.name || "Unknown Artist", }, album: { id: track.album?.id, title: track.album?.title || "Unknown Album", coverArt: track.album?.coverArt, }, })); }; // Wrapper for playTracks that converts track format const handlePlayTracks = ( libraryTracks: typeof tracks, startIndex?: number ) => { const formattedTracks = formatTracksForAudio(libraryTracks); playTracks(formattedTracks, startIndex); }; // Shuffle entire library - fetches all tracks for true shuffle const handleShuffleLibrary = async () => { try { // Fetch a large batch of tracks for shuffling const { tracks: allTracks } = await api.getTracks({ limit: 10000, }); if (allTracks.length === 0) { return; } // Shuffle the tracks const shuffled = [...allTracks].sort(() => Math.random() - 0.5); const formattedTracks = formatTracksForAudio(shuffled); playTracks(formattedTracks, 0); } catch (error) { console.error("Failed to shuffle library:", error); } }; // Handle delete confirmation const handleDelete = async () => { try { switch (deleteConfirm.type) { case "artist": await deleteArtist(deleteConfirm.id); break; case "album": await deleteAlbum(deleteConfirm.id); break; case "track": await deleteTrack(deleteConfirm.id); break; } // Reload data and close dialog - the item disappearing is feedback enough await reloadData(); setDeleteConfirm({ isOpen: false, type: "track", id: "", title: "", }); } catch (error) { console.error(`Failed to delete ${deleteConfirm.type}:`, error); // Keep dialog open on error so user can retry } }; return (
{/* Tabs and Controls Row */}
{/* Shuffle Button */} {/* Filter Toggle */} {/* Item Count */} {totalItems.toLocaleString()}{" "} {activeTab === "artists" ? "artists" : activeTab === "albums" ? "albums" : "songs"}
{/* Expandable Filters Row */} {showFilters && (
{/* Filter Toggle (Owned / Discovery / All) - Only show for artists and albums */} {(activeTab === "artists" || activeTab === "albums") && (
)} {/* Sort Dropdown */} {/* Items per page */}
)} {activeTab === "artists" && ( setDeleteConfirm({ isOpen: true, type: "artist", id, title: name, }) } /> )} {activeTab === "albums" && ( setDeleteConfirm({ isOpen: true, type: "album", id, title, }) } /> )} {activeTab === "tracks" && ( setDeleteConfirm({ isOpen: true, type: "track", id, title, }) } /> )} {/* Pagination */} {totalPages > 1 && (
{currentPage} / {totalPages}
)} setDeleteConfirm({ isOpen: false, type: "track", id: "", title: "", }) } onConfirm={handleDelete} title={`Delete ${ deleteConfirm.type === "artist" ? "Artist" : deleteConfirm.type === "album" ? "Album" : "Track" }?`} message={ deleteConfirm.type === "track" ? `Are you sure you want to delete "${deleteConfirm.title}"? This will permanently delete the file from your system.` : deleteConfirm.type === "album" ? `Are you sure you want to delete "${deleteConfirm.title}"? This will permanently delete all tracks and files from your system.` : `Are you sure you want to delete "${deleteConfirm.title}"? This will permanently delete all albums, tracks, and files from your system.` } confirmText="Delete" cancelText="Cancel" variant="danger" />
); }