diff --git a/src/components/home/HeroSection.tsx b/src/components/home/HeroSection.tsx
index 23d7a86..60bc0c7 100644
--- a/src/components/home/HeroSection.tsx
+++ b/src/components/home/HeroSection.tsx
@@ -37,6 +37,7 @@ export function HeroSection({ series }: HeroSectionProps) {
alt={t("home.hero.coverAlt", { title: series.metadata.title })}
quality={25}
sizes="(max-width: 640px) 50vw, (max-width: 1024px) 33vw, 16.666vw"
+ showProgressUi={false}
/>
))}
diff --git a/src/components/home/MediaRow.tsx b/src/components/home/MediaRow.tsx
index bc8f148..122660a 100644
--- a/src/components/home/MediaRow.tsx
+++ b/src/components/home/MediaRow.tsx
@@ -128,19 +128,30 @@ function MediaCard({ item, onClick }: MediaCardProps) {
>
{isSeries ? (
-
+ <>
+
+
+
{title}
+
+ {item.booksCount} tome{item.booksCount > 1 ? "s" : ""}
+
+
+ >
) : (
-
+ <>
+
+ >
)}
- {/* Overlay avec les informations au survol */}
-
-
{title}
- {isSeries && (
-
- {item.booksCount} tome{item.booksCount > 1 ? "s" : ""}
-
- )}
-
);
diff --git a/src/components/series/BookGrid.tsx b/src/components/series/BookGrid.tsx
index 7086fe8..ed51892 100644
--- a/src/components/series/BookGrid.tsx
+++ b/src/components/series/BookGrid.tsx
@@ -1,60 +1,19 @@
"use client";
import { KomgaBook } from "@/types/komga";
-import { formatDate } from "@/lib/utils";
import { BookCover } from "@/components/ui/book-cover";
-import { MarkAsReadButton } from "@/components/ui/mark-as-read-button";
-import { MarkAsUnreadButton } from "@/components/ui/mark-as-unread-button";
-import { BookOfflineButton } from "@/components/ui/book-offline-button";
import { useState, useEffect } from "react";
import { useTranslate } from "@/hooks/useTranslate";
-import { ClientOfflineBookService } from "@/lib/services/client-offlinebook.service";
interface BookGridProps {
books: KomgaBook[];
onBookClick: (book: KomgaBook) => void;
}
-// Fonction utilitaire pour obtenir les informations de statut de lecture
-const getReadingStatusInfo = (book: KomgaBook, t: (key: string, options?: any) => string) => {
- if (!book.readProgress) {
- return {
- label: t("books.status.unread"),
- className: "bg-yellow-500/10 text-yellow-500",
- };
- }
-
- if (book.readProgress.completed) {
- const readDate = book.readProgress.readDate ? formatDate(book.readProgress.readDate) : null;
- return {
- label: readDate ? t("books.status.readDate", { date: readDate }) : t("books.status.read"),
- className: "bg-green-500/10 text-green-500",
- };
- }
-
- const currentPage = ClientOfflineBookService.getCurrentPage(book);
-
- if (currentPage > 0) {
- return {
- label: t("books.status.progress", {
- current: currentPage,
- total: book.media.pagesCount,
- }),
- className: "bg-blue-500/10 text-blue-500",
- };
- }
-
- return {
- label: t("books.status.unread"),
- className: "bg-yellow-500/10 text-yellow-500",
- };
-};
-
export function BookGrid({ books, onBookClick }: BookGridProps) {
const [localBooks, setLocalBooks] = useState(books);
const { t } = useTranslate();
- // Synchroniser localBooks avec les props books
useEffect(() => {
setLocalBooks(books);
}, [books]);
@@ -66,47 +25,41 @@ export function BookGrid({ books, onBookClick }: BookGridProps) {
);
}
-
- const handleMarkAsRead = (bookId: string) => {
- setLocalBooks((prevBooks) =>
- prevBooks.map((book) =>
- book.id === bookId
- ? {
- ...book,
- readProgress: {
- ...(book.readProgress || {}),
- completed: true,
- readDate: new Date().toISOString(),
- page: book.media.pagesCount,
- created: book.readProgress?.created || new Date().toISOString(),
- lastModified: new Date().toISOString(),
- },
- }
- : book
- )
- );
- };
-
- const handleMarkAsUnread = (bookId: string) => {
- setLocalBooks((prevBooks) =>
- prevBooks.map((book) =>
- book.id === bookId
- ? {
- ...book,
- readProgress: null,
- }
- : book
- )
- );
+ const handleOnSuccess = (book: KomgaBook, action: "read" | "unread") => {
+ if (action === "read") {
+ setLocalBooks(
+ localBooks.map((previousBook) =>
+ previousBook.id === book.id
+ ? {
+ ...previousBook,
+ readProgress: {
+ completed: true,
+ page: previousBook.media.pagesCount,
+ readDate: new Date().toISOString(),
+ created: new Date().toISOString(),
+ lastModified: new Date().toISOString(),
+ },
+ }
+ : previousBook
+ )
+ );
+ } else if (action === "unread") {
+ setLocalBooks(
+ localBooks.map((previousBook) =>
+ previousBook.id === book.id
+ ? {
+ ...previousBook,
+ readProgress: null,
+ }
+ : previousBook
+ )
+ );
+ }
};
return (
{localBooks.map((book) => {
- const statusInfo = getReadingStatusInfo(book, t);
- const isRead = book.readProgress?.completed || false;
- const hasReadProgress = book.readProgress !== null;
-
return (
handleOnSuccess(book, action)}
/>
-
- {/* Overlay avec les contrôles */}
-
- {/* Boutons en haut à droite avec un petit décalage */}
-
- {!isRead && (
- handleMarkAsRead(book.id)}
- className="bg-white/90 hover:bg-white text-black shadow-sm"
- />
- )}
- {hasReadProgress && (
- handleMarkAsUnread(book.id)}
- className="bg-white/90 hover:bg-white text-black shadow-sm"
- />
- )}
-
-
-
- {/* Informations en bas - visible au survol uniquement */}
-
-
- {book.metadata.title || `Tome ${book.metadata.number}`}
-
-
-
- {statusInfo.label}
-
-
-
-
);
})}
diff --git a/src/components/series/SeriesHeader.tsx b/src/components/series/SeriesHeader.tsx
index b41a847..1a268c1 100644
--- a/src/components/series/SeriesHeader.tsx
+++ b/src/components/series/SeriesHeader.tsx
@@ -127,6 +127,7 @@ export const SeriesHeader = ({ series, refreshSeries }: SeriesHeaderProps) => {
alt={t("series.header.coverAlt", { title: series.metadata.title })}
className="blur-sm scale-105 brightness-50"
quality={60}
+ showProgressUi={false}
/>
@@ -139,6 +140,7 @@ export const SeriesHeader = ({ series, refreshSeries }: SeriesHeaderProps) => {
series={series as KomgaSeries}
alt={t("series.header.coverAlt", { title: series.metadata.title })}
quality={90}
+ showProgressUi={false}
/>
diff --git a/src/components/ui/book-cover.tsx b/src/components/ui/book-cover.tsx
index e5760e0..e9f4ac8 100644
--- a/src/components/ui/book-cover.tsx
+++ b/src/components/ui/book-cover.tsx
@@ -4,6 +4,47 @@ import { CoverClient } from "./cover-client";
import { ProgressBar } from "./progress-bar";
import { BookCoverProps, getImageUrl } from "./cover-utils";
import { ClientOfflineBookService } from "@/lib/services/client-offlinebook.service";
+import { MarkAsReadButton } from "./mark-as-read-button";
+import { MarkAsUnreadButton } from "./mark-as-unread-button";
+import { BookOfflineButton } from "./book-offline-button";
+import { useTranslate } from "@/hooks/useTranslate";
+import { KomgaBook } from "@/types/komga";
+import { formatDate } from "@/lib/utils";
+
+// Fonction utilitaire pour obtenir les informations de statut de lecture
+const getReadingStatusInfo = (book: KomgaBook, t: (key: string, options?: any) => string) => {
+ if (!book.readProgress) {
+ return {
+ label: t("books.status.unread"),
+ className: "bg-yellow-500/10 text-yellow-500",
+ };
+ }
+
+ if (book.readProgress.completed) {
+ const readDate = book.readProgress.readDate ? formatDate(book.readProgress.readDate) : null;
+ return {
+ label: readDate ? t("books.status.readDate", { date: readDate }) : t("books.status.read"),
+ className: "bg-green-500/10 text-green-500",
+ };
+ }
+
+ const currentPage = ClientOfflineBookService.getCurrentPage(book);
+
+ if (currentPage > 0) {
+ return {
+ label: t("books.status.progress", {
+ current: currentPage,
+ total: book.media.pagesCount,
+ }),
+ className: "bg-blue-500/10 text-blue-500",
+ };
+ }
+
+ return {
+ label: t("books.status.unread"),
+ className: "bg-yellow-500/10 text-yellow-500",
+ };
+};
export function BookCover({
book,
@@ -11,27 +52,99 @@ export function BookCover({
className,
quality = 80,
sizes = "100vw",
+ showProgressUi = true,
+ onSuccess,
+ showControls = true,
+ showOverlay = true,
+ overlayVariant = "default",
}: BookCoverProps) {
- if (!book) return null;
+ const { t } = useTranslate();
const imageUrl = getImageUrl("book", book.id);
const isCompleted = book.readProgress?.completed || false;
const currentPage = ClientOfflineBookService.getCurrentPage(book);
const totalPages = book.media.pagesCount;
- const showProgress = currentPage && totalPages && currentPage > 0 && !isCompleted;
+ const showProgress =
+ showProgressUi && currentPage && totalPages && currentPage > 0 && !isCompleted;
+
+ const statusInfo = getReadingStatusInfo(book, t);
+ const isRead = book.readProgress?.completed || false;
+ const hasReadProgress = book.readProgress !== null;
+
+ const handleMarkAsRead = () => {
+ onSuccess?.(book, "read");
+ };
+
+ const handleMarkAsUnread = () => {
+ onSuccess?.(book, "unread");
+ };
return (
-
+ <>
+
+ {/* Overlay avec les contrôles */}
+ {(showControls || showOverlay) && (
+
+ {showControls && (
+ // Boutons en haut à droite avec un petit décalage
+
+ {!isRead && (
+ handleMarkAsRead()}
+ className="bg-white/90 hover:bg-white text-black shadow-sm"
+ />
+ )}
+ {hasReadProgress && (
+ handleMarkAsUnread()}
+ className="bg-white/90 hover:bg-white text-black shadow-sm"
+ />
+ )}
+
+
+ )}
+ {showOverlay && overlayVariant === "default" && (
+
+
+ {book.metadata.title || `Tome ${book.metadata.number}`}
+
+
+
+ {statusInfo.label}
+
+
+
+ )}
+
+ )}
+ {showOverlay && overlayVariant === "home" && (
+
+
+ {book.metadata.title || `Tome ${book.metadata.number}`}
+
+
+ {currentPage} / {book.media.pagesCount}
+
+
+ )}
+ >
);
}
diff --git a/src/components/ui/cover-utils.tsx b/src/components/ui/cover-utils.tsx
index 7e58bd9..c8615e8 100644
--- a/src/components/ui/cover-utils.tsx
+++ b/src/components/ui/cover-utils.tsx
@@ -5,10 +5,15 @@ export interface BaseCoverProps {
className?: string;
quality?: number;
sizes?: string;
+ showProgressUi?: boolean;
}
export interface BookCoverProps extends BaseCoverProps {
- book?: KomgaBook;
+ book: KomgaBook;
+ onSuccess?: (book: KomgaBook, action: "read" | "unread") => void;
+ showControls?: boolean;
+ showOverlay?: boolean;
+ overlayVariant?: "default" | "home";
}
export interface SeriesCoverProps extends BaseCoverProps {
diff --git a/src/components/ui/mark-as-read-button.tsx b/src/components/ui/mark-as-read-button.tsx
index 574d887..49333c2 100644
--- a/src/components/ui/mark-as-read-button.tsx
+++ b/src/components/ui/mark-as-read-button.tsx
@@ -1,9 +1,10 @@
"use client";
-import { BookCheck } from "lucide-react";
+import { BookCheck, Loader2 } from "lucide-react";
import { Button } from "./button";
import { useToast } from "./use-toast";
import { ClientOfflineBookService } from "@/lib/services/client-offlinebook.service";
+import { useState } from "react";
interface MarkAsReadButtonProps {
bookId: string;
@@ -21,9 +22,11 @@ export function MarkAsReadButton({
className,
}: MarkAsReadButtonProps) {
const { toast } = useToast();
+ const [isLoading, setIsLoading] = useState(false);
const handleMarkAsRead = async (e: React.MouseEvent) => {
e.stopPropagation(); // Empêcher la propagation au parent
+ setIsLoading(true);
try {
ClientOfflineBookService.removeCurrentPageById(bookId);
const response = await fetch(`/api/komga/books/${bookId}/read-progress`, {
@@ -50,6 +53,8 @@ export function MarkAsReadButton({
description: "Impossible de marquer le tome comme lu",
variant: "destructive",
});
+ } finally {
+ setIsLoading(false);
}
};
@@ -59,10 +64,10 @@ export function MarkAsReadButton({
size="icon"
onClick={handleMarkAsRead}
className={`h-8 w-8 p-0 rounded-br-lg rounded-tl-lg ${className}`}
- disabled={isRead}
+ disabled={isRead || isLoading}
aria-label="Marquer comme lu"
>
-
+ {isLoading ? : }
);
}
diff --git a/src/components/ui/mark-as-unread-button.tsx b/src/components/ui/mark-as-unread-button.tsx
index e5664ca..fc577aa 100644
--- a/src/components/ui/mark-as-unread-button.tsx
+++ b/src/components/ui/mark-as-unread-button.tsx
@@ -1,9 +1,11 @@
"use client";
-import { BookX } from "lucide-react";
+import { BookX, Loader2 } from "lucide-react";
import { Button } from "./button";
import { useToast } from "./use-toast";
import { ClientOfflineBookService } from "@/lib/services/client-offlinebook.service";
+import { useState } from "react";
+
interface MarkAsUnreadButtonProps {
bookId: string;
onSuccess?: () => void;
@@ -12,9 +14,11 @@ interface MarkAsUnreadButtonProps {
export function MarkAsUnreadButton({ bookId, onSuccess, className }: MarkAsUnreadButtonProps) {
const { toast } = useToast();
+ const [isLoading, setIsLoading] = useState(false);
const handleMarkAsUnread = async (e: React.MouseEvent) => {
e.stopPropagation(); // Empêcher la propagation au parent
+ setIsLoading(true);
try {
ClientOfflineBookService.removeCurrentPageById(bookId);
const response = await fetch(`/api/komga/books/${bookId}/read-progress`, {
@@ -37,6 +41,8 @@ export function MarkAsUnreadButton({ bookId, onSuccess, className }: MarkAsUnrea
description: "Impossible de marquer le tome comme non lu",
variant: "destructive",
});
+ } finally {
+ setIsLoading(false);
}
};
@@ -46,9 +52,10 @@ export function MarkAsUnreadButton({ bookId, onSuccess, className }: MarkAsUnrea
size="icon"
onClick={handleMarkAsUnread}
className={`h-8 w-8 p-0 rounded-br-lg rounded-tl-lg ${className}`}
+ disabled={isLoading}
aria-label="Marquer comme non lu"
>
-
+ {isLoading ? : }
);
}
diff --git a/src/components/ui/series-cover.tsx b/src/components/ui/series-cover.tsx
index a7fd910..5cb6b3e 100644
--- a/src/components/ui/series-cover.tsx
+++ b/src/components/ui/series-cover.tsx
@@ -10,15 +10,14 @@ export function SeriesCover({
className,
quality = 80,
sizes = "100vw",
+ showProgressUi = true,
}: SeriesCoverProps) {
- if (!series) return null;
-
const imageUrl = getImageUrl("series", series.id);
const isCompleted = series.booksCount === series.booksReadCount;
const readBooks = series.booksReadCount;
const totalBooks = series.booksCount;
- const showProgress = readBooks && totalBooks && readBooks > 0 && !isCompleted;
+ const showProgress = showProgressUi && readBooks && totalBooks && readBooks > 0 && !isCompleted;
return (