feat: add scan library functionality and related error handling in LibraryHeader and services

This commit is contained in:
Julien Froidefond
2025-10-19 09:55:07 +02:00
parent 3704a8d88b
commit 7d9bac5c51
9 changed files with 195 additions and 9 deletions

View File

@@ -1,9 +1,10 @@
"use client";
import { useMemo } from "react";
import { Library, BookOpen } from "lucide-react";
import { Library } from "lucide-react";
import type { KomgaLibrary, KomgaSeries } from "@/types/komga";
import { RefreshButton } from "./RefreshButton";
import { ScanButton } from "./ScanButton";
import { useTranslate } from "@/hooks/useTranslate";
import { StatusBadge } from "@/components/ui/status-badge";
import { SeriesCover } from "@/components/ui/series-cover";
@@ -84,14 +85,8 @@ export const LibraryHeader = ({ library, seriesCount, series, refreshLibrary }:
}
</StatusBadge>
<StatusBadge status="reading" icon={BookOpen}>
{library.booksCount === 1
? t("library.header.books", { count: library.booksCount })
: t("library.header.books_plural", { count: library.booksCount })
}
</StatusBadge>
<RefreshButton libraryId={library.id} refreshLibrary={refreshLibrary} />
<ScanButton libraryId={library.id} />
</div>
{library.unavailable && (

View File

@@ -0,0 +1,88 @@
"use client";
import { useState } from "react";
import { FolderSearch } from "lucide-react";
import { Button } from "@/components/ui/button";
import { useToast } from "@/components/ui/use-toast";
import { cn } from "@/lib/utils";
import { useTranslation } from "react-i18next";
import { useRouter } from "next/navigation";
interface ScanButtonProps {
libraryId: string;
}
export function ScanButton({ libraryId }: ScanButtonProps) {
const [isScanning, setIsScanning] = useState(false);
const { toast } = useToast();
const { t } = useTranslation();
const router = useRouter();
const handleScan = async () => {
setIsScanning(true);
try {
const response = await fetch(`/api/komga/libraries/${libraryId}/scan`, {
method: "POST",
});
if (!response.ok) {
throw new Error("Failed to scan library");
}
toast({
title: t("library.scan.success.title"),
description: t("library.scan.success.description"),
});
// Attendre 5 secondes pour que le scan se termine, puis invalider le cache et rafraîchir
setTimeout(async () => {
try {
// Invalider le cache
await fetch(`/api/komga/libraries/${libraryId}/series`, {
method: "DELETE",
});
// Rafraîchir la page pour voir les changements
router.refresh();
// Toast pour indiquer que l'analyse est terminée
toast({
title: t("library.scan.complete.title"),
description: t("library.scan.complete.description"),
});
} catch (error) {
console.error("Error invalidating cache after scan:", error);
toast({
variant: "destructive",
title: t("library.scan.error.title"),
description: t("library.scan.error.refresh"),
});
} finally {
setIsScanning(false);
}
}, 5000);
} catch (error) {
setIsScanning(false);
toast({
variant: "destructive",
title: t("library.scan.error.title"),
description:
error instanceof Error ? error.message : t("library.scan.error.description"),
});
}
};
return (
<Button
variant="ghost"
size="icon"
onClick={handleScan}
disabled={isScanning}
className="ml-2"
aria-label={t("library.scan.button")}
>
<FolderSearch className={cn("h-4 w-4", isScanning && "animate-pulse")} />
</Button>
);
}