Add pagination to library/songs and remove listening streaks
Library/Songs improvements: - Added pagination with 50 songs per page - Added Previous/Next navigation buttons - Updated header to show current page range (e.g., 'Showing 1-50 of 247 songs') - Track numbers now reflect global position across all pages - Page resets to 1 when search/sort filters change - Imported ChevronLeft and ChevronRight icons for navigation Listening Streak removal: - Removed CompactListeningStreak component from home page - Removed ListeningStreakCard component from history page - Removed listening streak imports from both pages - Cleaned up empty comment sections The songs page now handles large libraries more efficiently with pagination, and the UI is cleaner without the listening streak cards.
This commit is contained in:
@@ -10,7 +10,6 @@ import { Tabs, TabsContent } from '@/components/ui/tabs';
|
||||
import { useAudioPlayer } from '@/app/components/AudioPlayerContext';
|
||||
import { getNavidromeAPI } from '@/lib/navidrome';
|
||||
import { Play, Plus, User, Disc, History, Trash2 } from 'lucide-react';
|
||||
import ListeningStreakCard from '@/app/components/ListeningStreakCard';
|
||||
import {
|
||||
AlertDialog,
|
||||
AlertDialogAction,
|
||||
@@ -79,10 +78,6 @@ export default function HistoryPage() {
|
||||
|
||||
return (
|
||||
<div className="h-full px-4 py-6 lg:px-8">
|
||||
<div className="mb-6">
|
||||
<ListeningStreakCard />
|
||||
</div>
|
||||
|
||||
<Tabs defaultValue="music" className="h-full space-y-6">
|
||||
<TabsContent value="music" className="border-none p-0 outline-hidden">
|
||||
<div className="flex items-center justify-between">
|
||||
|
||||
@@ -10,13 +10,15 @@ import { Separator } from '@/components/ui/separator';
|
||||
import { ScrollArea } from '@/components/ui/scroll-area';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
|
||||
import { Search, Play, Plus, User, Disc } from 'lucide-react';
|
||||
import { Search, Play, Plus, User, Disc, ChevronLeft, ChevronRight } from 'lucide-react';
|
||||
import Loading from '@/app/components/loading';
|
||||
import { getNavidromeAPI } from '@/lib/navidrome';
|
||||
|
||||
type SortOption = 'title' | 'artist' | 'album' | 'year' | 'duration' | 'track';
|
||||
type SortDirection = 'asc' | 'desc';
|
||||
|
||||
const ITEMS_PER_PAGE = 50;
|
||||
|
||||
export default function SongsPage() {
|
||||
const { getAllSongs } = useNavidrome();
|
||||
const { playTrack, addToQueue, currentTrack } = useAudioPlayer();
|
||||
@@ -26,6 +28,7 @@ export default function SongsPage() {
|
||||
const [searchQuery, setSearchQuery] = useState('');
|
||||
const [sortBy, setSortBy] = useState<SortOption>('title');
|
||||
const [sortDirection, setSortDirection] = useState<SortDirection>('asc');
|
||||
const [currentPage, setCurrentPage] = useState(1);
|
||||
const api = getNavidromeAPI();
|
||||
|
||||
useEffect(() => {
|
||||
@@ -100,6 +103,7 @@ export default function SongsPage() {
|
||||
});
|
||||
|
||||
setFilteredSongs(filtered);
|
||||
setCurrentPage(1); // Reset to first page when filters change
|
||||
}, [songs, searchQuery, sortBy, sortDirection]);
|
||||
const handlePlayClick = (song: Song) => {
|
||||
if (!api) {
|
||||
@@ -154,6 +158,24 @@ export default function SongsPage() {
|
||||
return currentTrack?.id === song.id;
|
||||
};
|
||||
|
||||
// Pagination calculations
|
||||
const totalPages = Math.ceil(filteredSongs.length / ITEMS_PER_PAGE);
|
||||
const startIndex = (currentPage - 1) * ITEMS_PER_PAGE;
|
||||
const endIndex = startIndex + ITEMS_PER_PAGE;
|
||||
const paginatedSongs = filteredSongs.slice(startIndex, endIndex);
|
||||
|
||||
const goToNextPage = () => {
|
||||
if (currentPage < totalPages) {
|
||||
setCurrentPage(currentPage + 1);
|
||||
}
|
||||
};
|
||||
|
||||
const goToPreviousPage = () => {
|
||||
if (currentPage > 1) {
|
||||
setCurrentPage(currentPage - 1);
|
||||
}
|
||||
};
|
||||
|
||||
if (loading) {
|
||||
return <Loading />;
|
||||
}
|
||||
@@ -165,7 +187,8 @@ export default function SongsPage() {
|
||||
<div className="space-y-2">
|
||||
<h1 className="text-3xl font-semibold tracking-tight">Songs</h1>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
{filteredSongs.length} of {songs.length} songs
|
||||
Showing {startIndex + 1}-{Math.min(endIndex, filteredSongs.length)} of {filteredSongs.length} songs
|
||||
{searchQuery && ` (filtered from ${songs.length} total)`}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -216,7 +239,7 @@ export default function SongsPage() {
|
||||
</div>
|
||||
) : (
|
||||
<div className="space-y-1">
|
||||
{filteredSongs.map((song, index) => (
|
||||
{paginatedSongs.map((song, index) => (
|
||||
<div
|
||||
key={song.id}
|
||||
className={`group flex items-center p-3 rounded-lg hover:bg-accent/50 cursor-pointer transition-colors ${
|
||||
@@ -232,7 +255,7 @@ export default function SongsPage() {
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
<span className="group-hover:hidden">{index + 1}</span>
|
||||
<span className="group-hover:hidden">{startIndex + index + 1}</span>
|
||||
<Play className="w-4 h-4 mx-auto hidden group-hover:block" />
|
||||
</>
|
||||
)}
|
||||
@@ -298,6 +321,35 @@ export default function SongsPage() {
|
||||
</div>
|
||||
)}
|
||||
</ScrollArea>
|
||||
|
||||
{/* Pagination Controls */}
|
||||
{filteredSongs.length > ITEMS_PER_PAGE && (
|
||||
<div className="flex items-center justify-between pt-4">
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Page {currentPage} of {totalPages}
|
||||
</p>
|
||||
<div className="flex gap-2">
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={goToPreviousPage}
|
||||
disabled={currentPage === 1}
|
||||
>
|
||||
<ChevronLeft className="w-4 h-4 mr-1" />
|
||||
Previous
|
||||
</Button>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={goToNextPage}
|
||||
disabled={currentPage === totalPages}
|
||||
>
|
||||
Next
|
||||
<ChevronRight className="w-4 h-4 ml-1" />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -14,7 +14,6 @@ import { SongRecommendations } from './components/SongRecommendations';
|
||||
import { Skeleton } from '@/components/ui/skeleton';
|
||||
import { useIsMobile } from '@/hooks/use-mobile';
|
||||
import { UserProfile } from './components/UserProfile';
|
||||
import CompactListeningStreak from './components/CompactListeningStreak';
|
||||
|
||||
type TimeOfDay = 'morning' | 'afternoon' | 'evening';
|
||||
|
||||
@@ -205,17 +204,10 @@ function MusicPageContent() {
|
||||
|
||||
return (
|
||||
<div className="p-6 pb-24 w-full">
|
||||
{/* Connection status (offline indicator) */}
|
||||
|
||||
{/* Song Recommendations Section */}
|
||||
<div className="mb-8">
|
||||
<SongRecommendations userName={userName} />
|
||||
</div>
|
||||
|
||||
{/* Listening Streak Section - Only shown when 3+ days streak */}
|
||||
<div className="mb-6">
|
||||
<CompactListeningStreak />
|
||||
</div>
|
||||
|
||||
<>
|
||||
<Tabs defaultValue="music" className="h-full space-y-6">
|
||||
|
||||
Reference in New Issue
Block a user