feat: add progressbar on lists
This commit is contained in:
@@ -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>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|
||||||
|
|||||||
@@ -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>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
19
src/components/ui/progress-bar.tsx
Normal file
19
src/components/ui/progress-bar.tsx
Normal 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>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -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 {
|
||||||
|
|||||||
Reference in New Issue
Block a user