feat: ajoute la pagination et le filtrage des tomes dans la page série

This commit is contained in:
Julien Froidefond
2025-02-11 21:43:09 +01:00
parent f8725857ad
commit e4a663b6d4
6 changed files with 300 additions and 261 deletions

View File

@@ -4,7 +4,7 @@ import { SeriesGrid } from "./SeriesGrid";
import { Pagination } from "@/components/ui/Pagination";
import { useRouter, usePathname, useSearchParams } from "next/navigation";
import { useState, useEffect } from "react";
import { Loader2 } from "lucide-react";
import { Loader2, Filter } from "lucide-react";
import { cn } from "@/lib/utils";
interface PaginatedSeriesGridProps {
@@ -28,6 +28,7 @@ export function PaginatedSeriesGrid({
const pathname = usePathname();
const searchParams = useSearchParams();
const [isChangingPage, setIsChangingPage] = useState(false);
const [showOnlyUnread, setShowOnlyUnread] = useState(searchParams.get("unread") === "true");
// Réinitialiser l'état de chargement quand les séries changent
useEffect(() => {
@@ -39,11 +40,29 @@ export function PaginatedSeriesGrid({
// Créer un nouvel objet URLSearchParams pour manipuler les paramètres
const params = new URLSearchParams(searchParams);
params.set("page", page.toString());
if (showOnlyUnread) {
params.set("unread", "true");
}
// Rediriger vers la nouvelle URL avec les paramètres mis à jour
router.push(`${pathname}?${params.toString()}`);
};
const handleUnreadFilter = () => {
setIsChangingPage(true);
const params = new URLSearchParams(searchParams);
params.set("page", "1"); // Retourner à la première page lors du changement de filtre
if (!showOnlyUnread) {
params.set("unread", "true");
} else {
params.delete("unread");
}
setShowOnlyUnread(!showOnlyUnread);
router.push(`${pathname}?${params.toString()}`);
};
// Calcul des indices de début et de fin pour l'affichage
const startIndex = (currentPage - 1) * pageSize + 1;
const endIndex = Math.min(currentPage * pageSize, totalElements);
@@ -62,6 +81,13 @@ export function PaginatedSeriesGrid({
"Aucune série trouvée"
)}
</p>
<button
onClick={handleUnreadFilter}
className="flex items-center gap-2 px-3 py-2 text-sm font-medium rounded-lg hover:bg-accent hover:text-accent-foreground"
>
<Filter className="h-4 w-4" />
{showOnlyUnread ? "Afficher tout" : "À lire"}
</button>
</div>
<div className="relative">

View File

@@ -0,0 +1,91 @@
"use client";
import Image from "next/image";
import { ImageOff } from "lucide-react";
import { KomgaSeries } from "@/types/komga";
interface SeriesHeaderProps {
series: KomgaSeries;
serverUrl: string;
}
export function SeriesHeader({ series, serverUrl }: SeriesHeaderProps) {
return (
<div className="flex flex-col md:flex-row gap-8">
{/* Couverture */}
<div className="w-48 shrink-0">
<div className="relative aspect-[2/3] rounded-lg overflow-hidden bg-muted">
<Image
src={`/api/komga/images/series/${series.id}/thumbnail`}
alt={`Couverture de ${series.metadata.title}`}
fill
className="object-cover"
/>
</div>
</div>
{/* Informations */}
<div className="flex-1 space-y-4">
<div>
<h1 className="text-3xl font-bold">{series.metadata.title}</h1>
{series.metadata.status && (
<span
className={`mt-2 inline-block px-2 py-1 rounded-full text-xs ${
series.metadata.status === "ENDED"
? "bg-green-500/10 text-green-500"
: series.metadata.status === "ONGOING"
? "bg-blue-500/10 text-blue-500"
: series.metadata.status === "ABANDONED"
? "bg-destructive/10 text-destructive"
: "bg-yellow-500/10 text-yellow-500"
}`}
>
{series.metadata.status === "ENDED"
? "Terminé"
: series.metadata.status === "ONGOING"
? "En cours"
: series.metadata.status === "ABANDONED"
? "Abandonné"
: "En pause"}
</span>
)}
</div>
{series.metadata.summary && (
<p className="text-muted-foreground">{series.metadata.summary}</p>
)}
<div className="space-y-1 text-sm text-muted-foreground">
{series.metadata.publisher && (
<div>
<span className="font-medium">Éditeur :</span> {series.metadata.publisher}
</div>
)}
{series.metadata.genres?.length > 0 && (
<div>
<span className="font-medium">Genres :</span> {series.metadata.genres.join(", ")}
</div>
)}
{series.metadata.tags?.length > 0 && (
<div>
<span className="font-medium">Tags :</span> {series.metadata.tags.join(", ")}
</div>
)}
{series.metadata.language && (
<div>
<span className="font-medium">Langue :</span>{" "}
{new Intl.DisplayNames([navigator.language], { type: "language" }).of(
series.metadata.language
)}
</div>
)}
{series.metadata.ageRating && (
<div>
<span className="font-medium">Âge recommandé :</span> {series.metadata.ageRating}+
</div>
)}
</div>
</div>
</div>
);
}