diff --git a/src/components/library/PaginatedSeriesGrid.tsx b/src/components/library/PaginatedSeriesGrid.tsx index e0af2c4..3238f0d 100644 --- a/src/components/library/PaginatedSeriesGrid.tsx +++ b/src/components/library/PaginatedSeriesGrid.tsx @@ -1,6 +1,7 @@ "use client"; import { SeriesGrid } from "./SeriesGrid"; +import { SeriesList } from "./SeriesList"; import { Pagination } from "@/components/ui/Pagination"; import { useRouter, usePathname, useSearchParams } from "next/navigation"; import { useState, useEffect, useCallback } from "react"; @@ -10,6 +11,7 @@ import { useTranslate } from "@/hooks/useTranslate"; import { useDisplayPreferences } from "@/hooks/useDisplayPreferences"; import { PageSizeSelect } from "@/components/common/PageSizeSelect"; import { CompactModeButton } from "@/components/common/CompactModeButton"; +import { ViewModeButton } from "@/components/common/ViewModeButton"; import { UnreadFilterButton } from "@/components/common/UnreadFilterButton"; interface PaginatedSeriesGridProps { @@ -35,7 +37,7 @@ export function PaginatedSeriesGrid({ const pathname = usePathname(); const searchParams = useSearchParams(); const [showOnlyUnread, setShowOnlyUnread] = useState(initialShowOnlyUnread); - const { isCompact, itemsPerPage: displayItemsPerPage } = useDisplayPreferences(); + const { isCompact, itemsPerPage: displayItemsPerPage, viewMode } = useDisplayPreferences(); // Utiliser la taille de page effective (depuis l'URL ou les préférences) const effectivePageSize = pageSize || displayItemsPerPage; @@ -119,13 +121,18 @@ export function PaginatedSeriesGrid({
- + + {viewMode === "grid" && }
- + {viewMode === "grid" ? ( + + ) : ( + + )}

diff --git a/src/components/library/SeriesList.tsx b/src/components/library/SeriesList.tsx new file mode 100644 index 0000000..9d5af38 --- /dev/null +++ b/src/components/library/SeriesList.tsx @@ -0,0 +1,193 @@ +"use client"; + +import type { KomgaSeries } from "@/types/komga"; +import { SeriesCover } from "@/components/ui/series-cover"; +import { useRouter } from "next/navigation"; +import { useTranslate } from "@/hooks/useTranslate"; +import { cn } from "@/lib/utils"; +import { Progress } from "@/components/ui/progress"; +import { BookOpen, Calendar, Tag, User } from "lucide-react"; +import { formatDate } from "@/lib/utils"; + +interface SeriesListProps { + series: KomgaSeries[]; +} + +interface SeriesListItemProps { + series: KomgaSeries; +} + +// Utility function to get reading status info +const getReadingStatusInfo = (series: KomgaSeries, t: (key: string, options?: any) => string) => { + if (series.booksCount === 0) { + return { + label: t("series.status.noBooks"), + className: "bg-yellow-500/10 text-yellow-500", + }; + } + + if (series.booksCount === series.booksReadCount) { + return { + label: t("series.status.read"), + className: "bg-green-500/10 text-green-500", + }; + } + + if (series.booksReadCount > 0) { + return { + label: t("series.status.progress", { + read: series.booksReadCount, + total: series.booksCount, + }), + className: "bg-blue-500/10 text-blue-500", + }; + } + + return { + label: t("series.status.unread"), + className: "bg-yellow-500/10 text-yellow-500", + }; +}; + +function SeriesListItem({ series }: SeriesListItemProps) { + const router = useRouter(); + const { t } = useTranslate(); + + const handleClick = () => { + router.push(`/series/${series.id}`); + }; + + const isCompleted = series.booksCount === series.booksReadCount; + const progressPercentage = series.booksCount > 0 + ? (series.booksReadCount / series.booksCount) * 100 + : 0; + + const statusInfo = getReadingStatusInfo(series, t); + + return ( +

+ {/* Couverture */} +
+ +
+ + {/* Contenu */} +
+ {/* Titre */} +
+
+

+ {series.metadata.title} +

+
+ + {/* Badge de statut */} + + {statusInfo.label} + +
+ + {/* Résumé */} + {series.metadata.summary && ( +

+ {series.metadata.summary} +

+ )} + + {/* Métadonnées */} +
+ {/* Nombre de livres */} +
+ + + {series.booksCount === 1 + ? t("series.book", { count: 1 }) + : t("series.books", { count: series.booksCount })} + +
+ + {/* Auteurs */} + {series.booksMetadata?.authors && series.booksMetadata.authors.length > 0 && ( +
+ + + {series.booksMetadata.authors.map(a => a.name).join(", ")} + +
+ )} + + {/* Date de création */} + {series.created && ( +
+ + {formatDate(series.created)} +
+ )} + + {/* Genres */} + {series.metadata.genres && series.metadata.genres.length > 0 && ( +
+ + + {series.metadata.genres.slice(0, 3).join(", ")} + {series.metadata.genres.length > 3 && ` +${series.metadata.genres.length - 3}`} + +
+ )} + + {/* Tags */} + {series.metadata.tags && series.metadata.tags.length > 0 && ( +
+ + + {series.metadata.tags.slice(0, 3).join(", ")} + {series.metadata.tags.length > 3 && ` +${series.metadata.tags.length - 3}`} + +
+ )} +
+ + {/* Barre de progression */} + {series.booksCount > 0 && !isCompleted && series.booksReadCount > 0 && ( +
+ +

+ {Math.round(progressPercentage)}% {t("series.completed")} +

+
+ )} +
+
+ ); +} + +export function SeriesList({ series }: SeriesListProps) { + const { t } = useTranslate(); + + if (!series.length) { + return ( +
+

{t("series.empty")}

+
+ ); + } + + return ( +
+ {series.map((seriesItem) => ( + + ))} +
+ ); +} + diff --git a/src/i18n/messages/en/common.json b/src/i18n/messages/en/common.json index 1ce634d..96118d0 100644 --- a/src/i18n/messages/en/common.json +++ b/src/i18n/messages/en/common.json @@ -284,7 +284,10 @@ "unread": "Unread", "progress": "{read}/{total}" }, + "book": "book", "books": "{count} books", + "books_plural": "{count} books", + "completed": "completed", "filters": { "title": "Filters", "showAll": "Show all", diff --git a/src/i18n/messages/fr/common.json b/src/i18n/messages/fr/common.json index 8c1931b..1b830f6 100644 --- a/src/i18n/messages/fr/common.json +++ b/src/i18n/messages/fr/common.json @@ -284,7 +284,10 @@ "unread": "Non lu", "progress": "{read}/{total}" }, + "book": "tome", "books": "{count} tomes", + "books_plural": "{count} tomes", + "completed": "complété", "filters": { "title": "Filtres", "showAll": "Afficher tout",