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:
2026-01-25 00:46:15 +00:00
committed by GitHub
parent c64e40d56b
commit 43a51b165b
4 changed files with 57 additions and 18 deletions

View File

@@ -1 +1 @@
NEXT_PUBLIC_COMMIT_SHA=9427a2a
NEXT_PUBLIC_COMMIT_SHA=eb56096

View File

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

View File

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

View File

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