"use client"; import { useEffect, useState, useCallback } from "react"; import { KomgaBook } from "@/types/komga"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; import { Card } from "@/components/ui/card"; import { Button } from "@/components/ui/button"; import { Download, Loader2, Check, Trash2, AlertCircle } from "lucide-react"; import { useToast } from "@/components/ui/use-toast"; import { Progress } from "@/components/ui/progress"; import Image from "next/image"; import Link from "next/link"; import { BookOfflineButton } from "@/components/ui/book-offline-button"; import { useTranslate } from "@/hooks/useTranslate"; type BookStatus = "idle" | "downloading" | "available" | "error"; interface BookDownloadStatus { status: BookStatus; progress: number; timestamp: number; lastDownloadedPage?: number; } interface DownloadedBook { book: KomgaBook; status: BookDownloadStatus; } export function DownloadManager() { const [downloadedBooks, setDownloadedBooks] = useState([]); const [isLoading, setIsLoading] = useState(true); const { toast } = useToast(); const { t } = useTranslate(); const getStorageKey = useCallback((bookId: string) => `book-status-${bookId}`, []); const loadDownloadedBooks = useCallback(async () => { setIsLoading(true); try { const books: DownloadedBook[] = []; for (let i = 0; i < localStorage.length; i++) { const key = localStorage.key(i); if (key?.startsWith("book-status-")) { const bookId = key.replace("book-status-", ""); const status = JSON.parse(localStorage.getItem(key) || ""); if (status.status !== "idle") { try { const response = await fetch(`/api/komga/books/${bookId}`); if (!response.ok) throw new Error("Livre non trouvé"); const bookData = await response.json(); books.push({ book: bookData.book, status, }); } catch (error) { console.error(`Erreur lors de la récupération du livre ${bookId}:`, error); localStorage.removeItem(key); } } } } setDownloadedBooks(books); } catch (error) { console.error("Erreur lors du chargement des livres:", error); toast({ title: "Erreur", description: "Impossible de charger les livres téléchargés", variant: "destructive", }); } finally { setIsLoading(false); } }, [toast]); const updateBookStatuses = useCallback(() => { setDownloadedBooks((prevBooks) => { return prevBooks.map((downloadedBook) => { const status = JSON.parse( localStorage.getItem(getStorageKey(downloadedBook.book.id)) || "{}" ); if (!status || status.status === "idle") { return downloadedBook; } return { ...downloadedBook, status, }; }); }); }, [getStorageKey]); useEffect(() => { loadDownloadedBooks(); const handleStorageChange = (e: StorageEvent) => { if (e.key?.startsWith("book-status-")) { updateBookStatuses(); } }; window.addEventListener("storage", handleStorageChange); const interval = setInterval(updateBookStatuses, 1000); return () => { window.removeEventListener("storage", handleStorageChange); clearInterval(interval); }; }, [loadDownloadedBooks, updateBookStatuses]); const handleDeleteBook = async (book: KomgaBook) => { try { const cache = await caches.open("stripstream-books"); await cache.delete(`/api/komga/images/books/${book.id}/pages`); for (let i = 1; i <= book.media.pagesCount; i++) { await cache.delete(`/api/komga/images/books/${book.id}/pages/${i}`); } localStorage.removeItem(getStorageKey(book.id)); setDownloadedBooks((prev) => prev.filter((b) => b.book.id !== book.id)); toast({ title: t("downloads.toast.deleted"), description: t("downloads.toast.deletedDesc"), }); } catch (error) { console.error("Erreur lors de la suppression du livre:", error); toast({ title: t("downloads.toast.error"), description: t("downloads.toast.errorDesc"), variant: "destructive", }); } }; const handleRetryDownload = async (book: KomgaBook) => { localStorage.removeItem(getStorageKey(book.id)); setDownloadedBooks((prev) => prev.filter((b) => b.book.id !== book.id)); toast({ title: t("downloads.toast.retry"), description: t("downloads.toast.retryDesc"), }); }; if (isLoading) { return (
); } return ( <>

{t("downloads.page.title")}

{t("downloads.page.description") && (

{t("downloads.page.description")}

)}
{t("downloads.tabs.all", { count: downloadedBooks.length })} {t("downloads.tabs.downloading", { count: downloadedBooks.filter((b) => b.status.status === "downloading").length, })} {t("downloads.tabs.available", { count: downloadedBooks.filter((b) => b.status.status === "available").length, })} {t("downloads.tabs.error", { count: downloadedBooks.filter((b) => b.status.status === "error").length, })} {downloadedBooks.some((b) => b.status.status === "error") && ( )}
{downloadedBooks.map(({ book, status }) => ( handleDeleteBook(book)} onRetry={() => handleRetryDownload(book)} /> ))} {downloadedBooks.length === 0 && (

{t("downloads.empty.all")}

)}
{downloadedBooks .filter((b) => b.status.status === "downloading") .map(({ book, status }) => ( handleDeleteBook(book)} onRetry={() => handleRetryDownload(book)} /> ))} {downloadedBooks.filter((b) => b.status.status === "downloading").length === 0 && (

{t("downloads.empty.downloading")}

)}
{downloadedBooks .filter((b) => b.status.status === "available") .map(({ book, status }) => ( handleDeleteBook(book)} onRetry={() => handleRetryDownload(book)} /> ))} {downloadedBooks.filter((b) => b.status.status === "available").length === 0 && (

{t("downloads.empty.available")}

)}
{downloadedBooks .filter((b) => b.status.status === "error") .map(({ book, status }) => ( handleDeleteBook(book)} onRetry={() => handleRetryDownload(book)} /> ))} {downloadedBooks.filter((b) => b.status.status === "error").length === 0 && (

{t("downloads.empty.error")}

)}
); } interface BookDownloadCardProps { book: KomgaBook; status: BookDownloadStatus; onDelete: () => void; onRetry: () => void; } function BookDownloadCard({ book, status, onDelete, onRetry }: BookDownloadCardProps) { const { t } = useTranslate(); const formatSize = (bytes: number) => { const mb = bytes / (1024 * 1024); return t("downloads.info.size", { size: mb.toFixed(1) }); }; const getStatusIcon = (status: BookStatus) => { switch (status) { case "downloading": return ; case "available": return ; case "error": return ; default: return ; } }; const getStatusText = (status: BookStatus) => { return t(`downloads.status.${status}`); }; return (
{t("books.coverAlt",

{book.metadata?.title || t("books.title", { number: book.metadata?.number })}

{formatSize(book.sizeBytes)} {status.status === "downloading" ? t("downloads.info.pages", { current: Math.floor((status.progress * book.media.pagesCount) / 100), total: book.media.pagesCount, }) : t("downloads.info.totalPages", { count: book.media.pagesCount })}
{getStatusIcon(status.status)} {getStatusText(status.status)}
{status.status === "downloading" && (
{Math.round(status.progress)}%
)}
{status.status === "error" && ( )} {status.status !== "downloading" && ( )}
); }