- Affichage du statut de lecture des livres dans la grille (Non lu, Lu, En cours)\n- Utilisation des données readProgress de l'API Komga\n- Ajout des badges colorés pour chaque statut (jaune, vert, bleu)\n- Affichage de la date de lecture pour les livres terminés\n- Affichage de la progression (Page X/Y) pour les livres en cours
122 lines
3.5 KiB
TypeScript
122 lines
3.5 KiB
TypeScript
"use client";
|
|
|
|
import { KomgaBook } from "@/types/komga";
|
|
import { ImageOff } from "lucide-react";
|
|
import Image from "next/image";
|
|
import { useState } from "react";
|
|
|
|
interface BookGridProps {
|
|
books: KomgaBook[];
|
|
onBookClick?: (book: KomgaBook) => void;
|
|
getBookThumbnailUrl: (bookId: string) => string;
|
|
}
|
|
|
|
export function BookGrid({ books, onBookClick, getBookThumbnailUrl }: BookGridProps) {
|
|
if (!books.length) {
|
|
return (
|
|
<div className="text-center p-8">
|
|
<p className="text-muted-foreground">Aucun tome disponible</p>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<div className="grid gap-4 sm:grid-cols-3 lg:grid-cols-6">
|
|
{books.map((book) => (
|
|
<BookCard
|
|
key={book.id}
|
|
book={book}
|
|
onClick={() => onBookClick?.(book)}
|
|
getBookThumbnailUrl={getBookThumbnailUrl}
|
|
/>
|
|
))}
|
|
</div>
|
|
);
|
|
}
|
|
|
|
interface BookCardProps {
|
|
book: KomgaBook;
|
|
onClick?: () => void;
|
|
getBookThumbnailUrl: (bookId: string) => string;
|
|
}
|
|
|
|
function BookCard({ book, onClick, getBookThumbnailUrl }: BookCardProps) {
|
|
const [imageError, setImageError] = useState(false);
|
|
|
|
const getReadingStatusInfo = () => {
|
|
if (!book.readProgress) {
|
|
return {
|
|
label: "Non lu",
|
|
className: "bg-yellow-500/10 text-yellow-500",
|
|
};
|
|
}
|
|
|
|
if (book.readProgress.completed) {
|
|
const readDate = book.readProgress.readDate
|
|
? new Date(book.readProgress.readDate).toLocaleDateString()
|
|
: null;
|
|
return {
|
|
label: readDate ? `Lu le ${readDate}` : "Lu",
|
|
className: "bg-green-500/10 text-green-500",
|
|
};
|
|
}
|
|
|
|
if (book.readProgress.page > 0) {
|
|
return {
|
|
label: `Page ${book.readProgress.page}/${book.media.pagesCount}`,
|
|
className: "bg-blue-500/10 text-blue-500",
|
|
};
|
|
}
|
|
|
|
return {
|
|
label: "Non lu",
|
|
className: "bg-yellow-500/10 text-yellow-500",
|
|
};
|
|
};
|
|
|
|
const statusInfo = getReadingStatusInfo();
|
|
|
|
return (
|
|
<button
|
|
onClick={onClick}
|
|
className="group relative flex flex-col rounded-lg border bg-card text-card-foreground shadow-sm hover:bg-accent hover:text-accent-foreground transition-colors overflow-hidden"
|
|
>
|
|
{/* Image de couverture */}
|
|
<div className="relative aspect-[2/3] bg-muted">
|
|
{!imageError ? (
|
|
<Image
|
|
src={getBookThumbnailUrl(book.id)}
|
|
alt={`Couverture de ${book.metadata.title}`}
|
|
fill
|
|
className="object-cover"
|
|
sizes="(max-width: 640px) 33vw, (max-width: 1024px) 16.666vw, 16.666vw"
|
|
onError={() => setImageError(true)}
|
|
/>
|
|
) : (
|
|
<div className="w-full h-full flex items-center justify-center">
|
|
<ImageOff className="w-12 w-12" />
|
|
</div>
|
|
)}
|
|
</div>
|
|
|
|
{/* Contenu */}
|
|
<div className="flex flex-col p-2">
|
|
<h3 className="font-medium line-clamp-2 text-sm">
|
|
{book.metadata.title || `Tome ${book.metadata.number}`}
|
|
</h3>
|
|
<div className="mt-1 text-xs text-muted-foreground space-y-1">
|
|
{book.metadata.releaseDate && (
|
|
<div>{new Date(book.metadata.releaseDate).toLocaleDateString()}</div>
|
|
)}
|
|
<div className="flex items-center">
|
|
<span className={`px-1.5 py-0.5 rounded-full text-[10px] ${statusInfo.className}`}>
|
|
{statusInfo.label}
|
|
</span>
|
|
</div>
|
|
{book.size && <div className="text-[10px]">{book.size}</div>}
|
|
</div>
|
|
</div>
|
|
</button>
|
|
);
|
|
}
|