feat: add progressbar on lists

This commit is contained in:
Julien Froidefond
2025-02-26 08:07:40 +01:00
parent 15a27005a0
commit 7c8fa6bf72
6 changed files with 83 additions and 18 deletions

View File

@@ -63,6 +63,8 @@ function LibraryCard({ library, onClick }: LibraryCardProps) {
alt={`Couverture de ${library.name}`} alt={`Couverture de ${library.name}`}
sizes="(max-width: 640px) 100vw, (max-width: 1024px) 50vw, 33vw" sizes="(max-width: 640px) 100vw, (max-width: 1024px) 50vw, 33vw"
quality={25} quality={25}
readBooks={library.booksReadCount}
totalBooks={library.booksCount}
/> />
</div> </div>
</div> </div>

View File

@@ -65,6 +65,8 @@ export function SeriesGrid({ series }: SeriesGridProps) {
id={series.id} id={series.id}
alt={`Couverture de ${series.metadata.title}`} alt={`Couverture de ${series.metadata.title}`}
isCompleted={series.booksCount === series.booksReadCount} isCompleted={series.booksCount === series.booksReadCount}
readBooks={series.booksReadCount}
totalBooks={series.booksCount}
/> />
<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"> <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 text-sm text-white line-clamp-2">{series.metadata.title}</h3> <h3 className="font-medium text-sm text-white line-clamp-2">{series.metadata.title}</h3>

View File

@@ -6,6 +6,7 @@ import { Cover } from "@/components/ui/cover";
import { MarkAsReadButton } from "@/components/ui/mark-as-read-button"; import { MarkAsReadButton } from "@/components/ui/mark-as-read-button";
import { MarkAsUnreadButton } from "@/components/ui/mark-as-unread-button"; import { MarkAsUnreadButton } from "@/components/ui/mark-as-unread-button";
import { BookOfflineButton } from "@/components/ui/book-offline-button"; import { BookOfflineButton } from "@/components/ui/book-offline-button";
import { ProgressBar } from "@/components/ui/progress-bar";
import { useState, useEffect } from "react"; import { useState, useEffect } from "react";
interface BookGridProps { interface BookGridProps {
@@ -97,6 +98,7 @@ export function BookGrid({ books, onBookClick }: BookGridProps) {
{localBooks.map((book) => { {localBooks.map((book) => {
const statusInfo = getReadingStatusInfo(book); const statusInfo = getReadingStatusInfo(book);
const isRead = book.readProgress?.completed || false; const isRead = book.readProgress?.completed || false;
const currentPage = book.readProgress?.page || 0;
return ( return (
<div <div
@@ -112,6 +114,8 @@ export function BookGrid({ books, onBookClick }: BookGridProps) {
id={book.id} id={book.id}
alt={`Couverture de ${book.metadata.title || `Tome ${book.metadata.number}`}`} alt={`Couverture de ${book.metadata.title || `Tome ${book.metadata.number}`}`}
isCompleted={isRead} isCompleted={isRead}
currentPage={currentPage}
totalPages={book.media.pagesCount}
/> />
</button> </button>

View File

@@ -1,6 +1,7 @@
import { CoverClient } from "./cover-client"; import { CoverClient } from "./cover-client";
import { ProgressBar } from "./progress-bar";
interface CoverProps { interface BaseCoverProps {
type: "series" | "book"; type: "series" | "book";
id: string; id: string;
alt?: string; alt?: string;
@@ -10,6 +11,20 @@ interface CoverProps {
isCompleted?: boolean; isCompleted?: boolean;
} }
interface BookCoverProps extends BaseCoverProps {
type: "book";
currentPage?: number;
totalPages?: number;
}
interface SeriesCoverProps extends BaseCoverProps {
type: "series";
readBooks?: number;
totalBooks?: number;
}
type CoverProps = BookCoverProps | SeriesCoverProps;
function getImageUrl(type: "series" | "book", id: string) { function getImageUrl(type: "series" | "book", id: string) {
if (type === "series") { if (type === "series") {
return `/api/komga/images/series/${id}/thumbnail`; return `/api/komga/images/series/${id}/thumbnail`;
@@ -17,25 +32,46 @@ function getImageUrl(type: "series" | "book", id: string) {
return `/api/komga/images/books/${id}/thumbnail`; return `/api/komga/images/books/${id}/thumbnail`;
} }
export function Cover({ export function Cover(props: CoverProps) {
type, const {
id, type,
alt = "Image de couverture", id,
className, alt = "Image de couverture",
quality = 80, className,
sizes = "100vw", quality = 80,
isCompleted = false, sizes = "100vw",
}: CoverProps) { isCompleted = false,
} = props;
const imageUrl = getImageUrl(type, id); const imageUrl = getImageUrl(type, id);
const showProgress = () => {
if (type === "book") {
const { currentPage, totalPages } = props;
return currentPage && totalPages && currentPage > 0 && !isCompleted ? (
<ProgressBar progress={currentPage} total={totalPages} />
) : null;
}
if (type === "series") {
const { readBooks, totalBooks } = props;
return readBooks && totalBooks && readBooks > 0 && !isCompleted ? (
<ProgressBar progress={readBooks} total={totalBooks} />
) : null;
}
};
return ( return (
<CoverClient <div className="relative w-full h-full">
imageUrl={imageUrl} <CoverClient
alt={alt} imageUrl={imageUrl}
className={className} alt={alt}
quality={quality} className={className}
sizes={sizes} quality={quality}
isCompleted={isCompleted} sizes={sizes}
/> isCompleted={isCompleted}
/>
{showProgress()}
</div>
); );
} }

View File

@@ -0,0 +1,19 @@
interface ProgressBarProps {
progress: number;
total: number;
}
export function ProgressBar({ progress, total }: ProgressBarProps) {
const percentage = Math.round((progress / total) * 100);
return (
<div className="absolute bottom-0 left-0 right-0 px-3 py-2 bg-black/50 backdrop-blur-sm border-t border-white/10">
<div className="h-2 bg-white/30 rounded-full overflow-hidden">
<div
className="h-full bg-white rounded-full transition-all duration-300"
style={{ width: `${percentage}%` }}
/>
</div>
</div>
);
}

View File

@@ -17,6 +17,8 @@ export interface KomgaLibrary {
importLastModified: string; importLastModified: string;
lastModified: string; lastModified: string;
unavailable: boolean; unavailable: boolean;
booksCount: number;
booksReadCount: number;
} }
export interface KomgaSeries { export interface KomgaSeries {