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:
@@ -1 +1 @@
|
|||||||
NEXT_PUBLIC_COMMIT_SHA=9427a2a
|
NEXT_PUBLIC_COMMIT_SHA=eb56096
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ import { Tabs, TabsContent } from '@/components/ui/tabs';
|
|||||||
import { useAudioPlayer } from '@/app/components/AudioPlayerContext';
|
import { useAudioPlayer } from '@/app/components/AudioPlayerContext';
|
||||||
import { getNavidromeAPI } from '@/lib/navidrome';
|
import { getNavidromeAPI } from '@/lib/navidrome';
|
||||||
import { Play, Plus, User, Disc, History, Trash2 } from 'lucide-react';
|
import { Play, Plus, User, Disc, History, Trash2 } from 'lucide-react';
|
||||||
import ListeningStreakCard from '@/app/components/ListeningStreakCard';
|
|
||||||
import {
|
import {
|
||||||
AlertDialog,
|
AlertDialog,
|
||||||
AlertDialogAction,
|
AlertDialogAction,
|
||||||
@@ -79,10 +78,6 @@ export default function HistoryPage() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="h-full px-4 py-6 lg:px-8">
|
<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">
|
<Tabs defaultValue="music" className="h-full space-y-6">
|
||||||
<TabsContent value="music" className="border-none p-0 outline-hidden">
|
<TabsContent value="music" className="border-none p-0 outline-hidden">
|
||||||
<div className="flex items-center justify-between">
|
<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 { ScrollArea } from '@/components/ui/scroll-area';
|
||||||
import { Input } from '@/components/ui/input';
|
import { Input } from '@/components/ui/input';
|
||||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
|
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 Loading from '@/app/components/loading';
|
||||||
import { getNavidromeAPI } from '@/lib/navidrome';
|
import { getNavidromeAPI } from '@/lib/navidrome';
|
||||||
|
|
||||||
type SortOption = 'title' | 'artist' | 'album' | 'year' | 'duration' | 'track';
|
type SortOption = 'title' | 'artist' | 'album' | 'year' | 'duration' | 'track';
|
||||||
type SortDirection = 'asc' | 'desc';
|
type SortDirection = 'asc' | 'desc';
|
||||||
|
|
||||||
|
const ITEMS_PER_PAGE = 50;
|
||||||
|
|
||||||
export default function SongsPage() {
|
export default function SongsPage() {
|
||||||
const { getAllSongs } = useNavidrome();
|
const { getAllSongs } = useNavidrome();
|
||||||
const { playTrack, addToQueue, currentTrack } = useAudioPlayer();
|
const { playTrack, addToQueue, currentTrack } = useAudioPlayer();
|
||||||
@@ -26,6 +28,7 @@ export default function SongsPage() {
|
|||||||
const [searchQuery, setSearchQuery] = useState('');
|
const [searchQuery, setSearchQuery] = useState('');
|
||||||
const [sortBy, setSortBy] = useState<SortOption>('title');
|
const [sortBy, setSortBy] = useState<SortOption>('title');
|
||||||
const [sortDirection, setSortDirection] = useState<SortDirection>('asc');
|
const [sortDirection, setSortDirection] = useState<SortDirection>('asc');
|
||||||
|
const [currentPage, setCurrentPage] = useState(1);
|
||||||
const api = getNavidromeAPI();
|
const api = getNavidromeAPI();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -100,6 +103,7 @@ export default function SongsPage() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
setFilteredSongs(filtered);
|
setFilteredSongs(filtered);
|
||||||
|
setCurrentPage(1); // Reset to first page when filters change
|
||||||
}, [songs, searchQuery, sortBy, sortDirection]);
|
}, [songs, searchQuery, sortBy, sortDirection]);
|
||||||
const handlePlayClick = (song: Song) => {
|
const handlePlayClick = (song: Song) => {
|
||||||
if (!api) {
|
if (!api) {
|
||||||
@@ -154,6 +158,24 @@ export default function SongsPage() {
|
|||||||
return currentTrack?.id === song.id;
|
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) {
|
if (loading) {
|
||||||
return <Loading />;
|
return <Loading />;
|
||||||
}
|
}
|
||||||
@@ -165,7 +187,8 @@ export default function SongsPage() {
|
|||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<h1 className="text-3xl font-semibold tracking-tight">Songs</h1>
|
<h1 className="text-3xl font-semibold tracking-tight">Songs</h1>
|
||||||
<p className="text-sm text-muted-foreground">
|
<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>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -216,7 +239,7 @@ export default function SongsPage() {
|
|||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="space-y-1">
|
<div className="space-y-1">
|
||||||
{filteredSongs.map((song, index) => (
|
{paginatedSongs.map((song, index) => (
|
||||||
<div
|
<div
|
||||||
key={song.id}
|
key={song.id}
|
||||||
className={`group flex items-center p-3 rounded-lg hover:bg-accent/50 cursor-pointer transition-colors ${
|
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>
|
</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" />
|
<Play className="w-4 h-4 mx-auto hidden group-hover:block" />
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
@@ -298,6 +321,35 @@ export default function SongsPage() {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</ScrollArea>
|
</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>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -14,7 +14,6 @@ import { SongRecommendations } from './components/SongRecommendations';
|
|||||||
import { Skeleton } from '@/components/ui/skeleton';
|
import { Skeleton } from '@/components/ui/skeleton';
|
||||||
import { useIsMobile } from '@/hooks/use-mobile';
|
import { useIsMobile } from '@/hooks/use-mobile';
|
||||||
import { UserProfile } from './components/UserProfile';
|
import { UserProfile } from './components/UserProfile';
|
||||||
import CompactListeningStreak from './components/CompactListeningStreak';
|
|
||||||
|
|
||||||
type TimeOfDay = 'morning' | 'afternoon' | 'evening';
|
type TimeOfDay = 'morning' | 'afternoon' | 'evening';
|
||||||
|
|
||||||
@@ -205,17 +204,10 @@ function MusicPageContent() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="p-6 pb-24 w-full">
|
<div className="p-6 pb-24 w-full">
|
||||||
{/* Connection status (offline indicator) */}
|
|
||||||
|
|
||||||
{/* Song Recommendations Section */}
|
{/* Song Recommendations Section */}
|
||||||
<div className="mb-8">
|
<div className="mb-8">
|
||||||
<SongRecommendations userName={userName} />
|
<SongRecommendations userName={userName} />
|
||||||
</div>
|
</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">
|
<Tabs defaultValue="music" className="h-full space-y-6">
|
||||||
|
|||||||
Reference in New Issue
Block a user