feat: add batch metadata jobs, series filters, and translate backoffice to French

- Add metadata_batch job type with background processing via tokio::spawn
- Auto-apply metadata only when single result at 100% confidence
- Support primary + fallback provider per library, "none" to opt out
- Add batch report/results API endpoints and job detail UI
- Add series_status and has_missing filters to both series listing pages
- Add GET /series/statuses endpoint for dynamic filter options
- Normalize series_metadata status values (migration 0036)
- Hide ComicVine provider tab when no API key configured
- Translate entire backoffice UI from English to French

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-18 18:26:44 +01:00
parent 9a8c1577af
commit b955c2697c
46 changed files with 2161 additions and 379 deletions

View File

@@ -10,6 +10,7 @@ interface LibraryActionsProps {
scanMode: string;
watcherEnabled: boolean;
metadataProvider: string | null;
fallbackMetadataProvider: string | null;
onUpdate?: () => void;
}
@@ -19,6 +20,7 @@ export function LibraryActions({
scanMode,
watcherEnabled,
metadataProvider,
fallbackMetadataProvider,
onUpdate
}: LibraryActionsProps) {
const [isOpen, setIsOpen] = useState(false);
@@ -43,6 +45,7 @@ export function LibraryActions({
const watcherEnabled = formData.get("watcher_enabled") === "true";
const scanMode = formData.get("scan_mode") as string;
const newMetadataProvider = (formData.get("metadata_provider") as string) || null;
const newFallbackProvider = (formData.get("fallback_metadata_provider") as string) || null;
try {
const [response] = await Promise.all([
@@ -58,7 +61,7 @@ export function LibraryActions({
fetch(`/api/libraries/${libraryId}/metadata-provider`, {
method: "PATCH",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ metadata_provider: newMetadataProvider }),
body: JSON.stringify({ metadata_provider: newMetadataProvider, fallback_metadata_provider: newFallbackProvider }),
}),
]);
@@ -106,7 +109,7 @@ export function LibraryActions({
defaultChecked={monitorEnabled}
className="w-4 h-4 rounded border-border text-primary focus:ring-ring"
/>
Auto Scan
Scan auto
</label>
</div>
@@ -119,35 +122,55 @@ export function LibraryActions({
defaultChecked={watcherEnabled}
className="w-4 h-4 rounded border-border text-primary focus:ring-ring"
/>
File Watcher
Surveillance fichiers
</label>
</div>
<div className="flex items-center justify-between">
<label className="text-sm font-medium text-foreground">📅 Schedule</label>
<label className="text-sm font-medium text-foreground">📅 Planification</label>
<select
name="scan_mode"
defaultValue={scanMode}
className="text-sm border border-border rounded-lg px-2 py-1 bg-background"
>
<option value="manual">Manual</option>
<option value="hourly">Hourly</option>
<option value="daily">Daily</option>
<option value="weekly">Weekly</option>
<option value="manual">Manuel</option>
<option value="hourly">Toutes les heures</option>
<option value="daily">Quotidien</option>
<option value="weekly">Hebdomadaire</option>
</select>
</div>
<div className="flex items-center justify-between">
<label className="text-sm font-medium text-foreground flex items-center gap-1.5">
{metadataProvider && <ProviderIcon provider={metadataProvider} size={16} />}
Metadata Provider
Fournisseur
</label>
<select
name="metadata_provider"
defaultValue={metadataProvider || ""}
className="text-sm border border-border rounded-lg px-2 py-1 bg-background"
>
<option value="">Default</option>
<option value="">Par faut</option>
<option value="none">Aucun</option>
<option value="google_books">Google Books</option>
<option value="comicvine">ComicVine</option>
<option value="open_library">Open Library</option>
<option value="anilist">AniList</option>
<option value="bedetheque">Bédéthèque</option>
</select>
</div>
<div className="flex items-center justify-between">
<label className="text-sm font-medium text-foreground flex items-center gap-1.5">
{fallbackMetadataProvider && fallbackMetadataProvider !== "none" && <ProviderIcon provider={fallbackMetadataProvider} size={16} />}
Secours
</label>
<select
name="fallback_metadata_provider"
defaultValue={fallbackMetadataProvider || ""}
className="text-sm border border-border rounded-lg px-2 py-1 bg-background"
>
<option value="">Aucun</option>
<option value="google_books">Google Books</option>
<option value="comicvine">ComicVine</option>
<option value="open_library">Open Library</option>
@@ -168,7 +191,7 @@ export function LibraryActions({
className="w-full"
disabled={isPending}
>
{isPending ? "Saving..." : "Save Settings"}
{isPending ? "Enregistrement..." : "Enregistrer"}
</Button>
</div>
</form>