feat: AniList reading status integration
- Add full AniList integration: OAuth connect, series linking, push/pull sync - Push: PLANNING/CURRENT/COMPLETED based on books read vs total_volumes (never auto-complete from owned books alone) - Pull: update local reading progress from AniList list (per-user) - Detailed sync/pull reports with per-series status and progress - Local user selector in settings to scope sync to a specific user - Rename "AniList" tab/buttons to generic "État de lecture" / "Reading status" - Make Bédéthèque and AniList badges clickable links on series detail page - Fix ON CONFLICT error on series link (provider column in PK) - Migration 0054: fix series_metadata missing columns (authors, publishers, locked_fields, total_volumes, status) - Align button heights on series detail page; move MarkSeriesReadButton to action row Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -14,6 +14,7 @@ interface LibraryActionsProps {
|
||||
metadataProvider: string | null;
|
||||
fallbackMetadataProvider: string | null;
|
||||
metadataRefreshMode: string;
|
||||
readingStatusProvider: string | null;
|
||||
onUpdate?: () => void;
|
||||
}
|
||||
|
||||
@@ -25,6 +26,7 @@ export function LibraryActions({
|
||||
metadataProvider,
|
||||
fallbackMetadataProvider,
|
||||
metadataRefreshMode,
|
||||
readingStatusProvider,
|
||||
}: LibraryActionsProps) {
|
||||
const { t } = useTranslation();
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
@@ -40,6 +42,7 @@ export function LibraryActions({
|
||||
const newMetadataProvider = (formData.get("metadata_provider") as string) || null;
|
||||
const newFallbackProvider = (formData.get("fallback_metadata_provider") as string) || null;
|
||||
const newMetadataRefreshMode = formData.get("metadata_refresh_mode") as string;
|
||||
const newReadingStatusProvider = (formData.get("reading_status_provider") as string) || null;
|
||||
|
||||
try {
|
||||
const [response] = await Promise.all([
|
||||
@@ -58,6 +61,11 @@ export function LibraryActions({
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ metadata_provider: newMetadataProvider, fallback_metadata_provider: newFallbackProvider }),
|
||||
}),
|
||||
fetch(`/api/libraries/${libraryId}/reading-status-provider`, {
|
||||
method: "PATCH",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ reading_status_provider: newReadingStatusProvider }),
|
||||
}),
|
||||
]);
|
||||
|
||||
if (response.ok) {
|
||||
@@ -255,6 +263,34 @@ export function LibraryActions({
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<hr className="border-border/40" />
|
||||
|
||||
{/* Section: État de lecture */}
|
||||
<div className="space-y-5">
|
||||
<h3 className="flex items-center gap-2 text-sm font-semibold text-foreground uppercase tracking-wide">
|
||||
<svg className="w-4 h-4 text-primary" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" />
|
||||
</svg>
|
||||
{t("libraryActions.sectionReadingStatus")}
|
||||
</h3>
|
||||
<div>
|
||||
<div className="flex items-center justify-between gap-4">
|
||||
<label className="text-sm font-medium text-foreground">
|
||||
{t("libraryActions.readingStatusProvider")}
|
||||
</label>
|
||||
<select
|
||||
name="reading_status_provider"
|
||||
defaultValue={readingStatusProvider || ""}
|
||||
className="text-sm border border-border rounded-lg px-3 py-1.5 bg-background min-w-[160px] shrink-0"
|
||||
>
|
||||
<option value="">{t("libraryActions.none")}</option>
|
||||
<option value="anilist">AniList</option>
|
||||
</select>
|
||||
</div>
|
||||
<p className="text-xs text-muted-foreground mt-1.5">{t("libraryActions.readingStatusProviderDesc")}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{saveError && (
|
||||
<p className="text-sm text-destructive bg-destructive/10 px-3 py-2 rounded-lg break-all">
|
||||
{saveError}
|
||||
|
||||
Reference in New Issue
Block a user