feat: amélioration de l'affichage des séries et tomes - Ajout d'un overlay au survol pour les informations - Ajout d'une transparence pour les séries/tomes lus - Amélioration de l'affichage du statut de lecture (X/Y pour les séries en cours)
This commit is contained in:
@@ -119,16 +119,16 @@ function MediaCard({ item, onClick }: MediaCardProps) {
|
|||||||
<ImageOff className="w-12 h-12" />
|
<ImageOff className="w-12 h-12" />
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Contenu */}
|
{/* Overlay avec les informations au survol */}
|
||||||
<div className="flex flex-col p-2">
|
<div className="absolute inset-0 bg-black/60 opacity-0 group-hover:opacity-100 transition-opacity duration-200 flex flex-col justify-end p-3">
|
||||||
<h3 className="font-medium line-clamp-2 text-sm">{title}</h3>
|
<h3 className="font-medium text-sm text-white line-clamp-2">{title}</h3>
|
||||||
{isSeries && (
|
{isSeries && (
|
||||||
<p className="text-xs text-muted-foreground mt-1">
|
<p className="text-xs text-white/80 mt-1">
|
||||||
{item.booksCount} tome{item.booksCount > 1 ? "s" : ""}
|
{item.booksCount} tome{item.booksCount > 1 ? "s" : ""}
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</button>
|
</button>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ interface SeriesGridProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Fonction utilitaire pour obtenir les informations de lecture d'une série
|
// Fonction utilitaire pour obtenir les informations de lecture d'une série
|
||||||
const getReadingStatusInfo = (series: KomgaSeries): { label: string; className: string } => {
|
const getReadingStatusInfo = (series: KomgaSeries) => {
|
||||||
const { booksCount, booksReadCount, booksUnreadCount } = series;
|
const { booksCount, booksReadCount, booksUnreadCount } = series;
|
||||||
const booksInProgressCount = booksCount - (booksReadCount + booksUnreadCount);
|
const booksInProgressCount = booksCount - (booksReadCount + booksUnreadCount);
|
||||||
|
|
||||||
@@ -25,7 +25,7 @@ const getReadingStatusInfo = (series: KomgaSeries): { label: string; className:
|
|||||||
|
|
||||||
if (booksInProgressCount > 0 || (booksReadCount > 0 && booksReadCount < booksCount)) {
|
if (booksInProgressCount > 0 || (booksReadCount > 0 && booksReadCount < booksCount)) {
|
||||||
return {
|
return {
|
||||||
label: "En cours",
|
label: `${booksReadCount}/${booksCount}`,
|
||||||
className: "bg-blue-500/10 text-blue-500",
|
className: "bg-blue-500/10 text-blue-500",
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -74,41 +74,35 @@ function SeriesCard({ series, onClick, serverUrl }: SeriesCardProps) {
|
|||||||
return (
|
return (
|
||||||
<button
|
<button
|
||||||
onClick={onClick}
|
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"
|
className="group relative aspect-[2/3] overflow-hidden rounded-lg bg-muted"
|
||||||
>
|
>
|
||||||
{/* Image de couverture */}
|
{!imageError ? (
|
||||||
<div className="relative aspect-[2/3] bg-muted">
|
<Image
|
||||||
{!imageError ? (
|
src={`/api/komga/images/series/${series.id}/thumbnail`}
|
||||||
<Image
|
alt={`Couverture de ${series.metadata.title}`}
|
||||||
src={`/api/komga/images/series/${series.id}/thumbnail`}
|
fill
|
||||||
alt={`Couverture de ${series.metadata.title}`}
|
className={`object-cover ${
|
||||||
fill
|
series.booksCount === series.booksReadCount ? "opacity-50" : ""
|
||||||
className="object-cover"
|
}`}
|
||||||
sizes="(max-width: 640px) 33vw, (max-width: 1024px) 20vw, 20vw"
|
sizes="(max-width: 640px) 33vw, (max-width: 1024px) 20vw, 20vw"
|
||||||
onError={() => setImageError(true)}
|
onError={() => setImageError(true)}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<div className="w-full h-full flex items-center justify-center">
|
<div className="w-full h-full flex items-center justify-center">
|
||||||
<ImageOff className="w-12 h-12" />
|
<ImageOff className="w-12 h-12" />
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Contenu */}
|
{/* Overlay avec les informations au survol */}
|
||||||
<div className="flex flex-col p-2">
|
<div className="absolute inset-x-0 bottom-0 bg-gradient-to-t from-black/60 to-transparent p-4 space-y-2 translate-y-full group-hover:translate-y-0 transition-transform duration-200">
|
||||||
<h3 className="font-medium line-clamp-2 text-sm">{series.metadata.title}</h3>
|
<h3 className="font-medium text-sm text-white line-clamp-2">{series.metadata.title}</h3>
|
||||||
<div className="mt-1 text-xs text-muted-foreground space-y-1">
|
<div className="flex items-center gap-2">
|
||||||
<div className="flex items-center gap-1">
|
<span className={`px-2 py-0.5 rounded-full text-xs ${statusInfo.className}`}>
|
||||||
<Book className="h-3 w-3" />
|
{statusInfo.label}
|
||||||
<span>
|
</span>
|
||||||
{series.booksCount} tome{series.booksCount > 1 ? "s" : ""}
|
<span className="text-xs text-white/80">
|
||||||
</span>
|
{series.booksCount} tome{series.booksCount > 1 ? "s" : ""}
|
||||||
</div>
|
</span>
|
||||||
<div className="flex items-center">
|
|
||||||
<span className={`px-1.5 py-0.5 rounded-full text-[10px] ${statusInfo.className}`}>
|
|
||||||
{statusInfo.label}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -11,6 +11,38 @@ interface BookGridProps {
|
|||||||
getBookThumbnailUrl: (bookId: string) => string;
|
getBookThumbnailUrl: (bookId: string) => string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Fonction utilitaire pour obtenir les informations de statut de lecture
|
||||||
|
const getReadingStatusInfo = (book: KomgaBook) => {
|
||||||
|
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",
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
export function BookGrid({ books, onBookClick, getBookThumbnailUrl }: BookGridProps) {
|
export function BookGrid({ books, onBookClick, getBookThumbnailUrl }: BookGridProps) {
|
||||||
if (!books.length) {
|
if (!books.length) {
|
||||||
return (
|
return (
|
||||||
@@ -22,26 +54,34 @@ export function BookGrid({ books, onBookClick, getBookThumbnailUrl }: BookGridPr
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="grid grid-cols-2 gap-4 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-6">
|
<div className="grid grid-cols-2 gap-4 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-6">
|
||||||
{books.map((book) => (
|
{books.map((book) => {
|
||||||
<button
|
const statusInfo = getReadingStatusInfo(book);
|
||||||
key={book.id}
|
return (
|
||||||
onClick={() => onBookClick(book)}
|
<button
|
||||||
className="group relative aspect-[2/3] overflow-hidden rounded-lg bg-muted hover:opacity-80 transition-opacity"
|
key={book.id}
|
||||||
>
|
onClick={() => onBookClick(book)}
|
||||||
<Image
|
className="group relative aspect-[2/3] overflow-hidden rounded-lg bg-muted hover:opacity-100 transition-all"
|
||||||
src={getBookThumbnailUrl(book.id)}
|
>
|
||||||
alt={book.metadata.title}
|
<Image
|
||||||
fill
|
src={getBookThumbnailUrl(book.id)}
|
||||||
className="object-cover"
|
alt={book.metadata.title}
|
||||||
sizes="(min-width: 1024px) 16.66vw, (min-width: 768px) 25vw, (min-width: 640px) 33.33vw, 50vw"
|
fill
|
||||||
/>
|
className={`object-cover ${book.readProgress?.completed ? "opacity-50" : ""}`}
|
||||||
<div className="absolute inset-x-0 bottom-0 bg-gradient-to-t from-black/60 to-transparent p-4">
|
sizes="(min-width: 1024px) 16.66vw, (min-width: 768px) 25vw, (min-width: 640px) 33.33vw, 50vw"
|
||||||
<p className="text-sm font-medium text-white text-left line-clamp-2">
|
/>
|
||||||
{book.metadata.title}
|
<div className="absolute inset-x-0 bottom-0 bg-gradient-to-t from-black/60 to-transparent p-4 space-y-2 translate-y-full group-hover:translate-y-0 transition-transform duration-200">
|
||||||
</p>
|
<p className="text-sm font-medium text-white text-left line-clamp-2">
|
||||||
</div>
|
{book.metadata.title || `Tome ${book.metadata.number}`}
|
||||||
</button>
|
</p>
|
||||||
))}
|
<div className="flex items-center gap-2">
|
||||||
|
<span className={`px-2 py-0.5 rounded-full text-xs ${statusInfo.className}`}>
|
||||||
|
{statusInfo.label}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
})}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user