"use client"; import { useState, useTransition, useEffect, useCallback } from "react"; import { createPortal } from "react-dom"; import { useRouter } from "next/navigation"; import { FormField, FormLabel, FormInput } from "./ui/Form"; function LockButton({ locked, onToggle, disabled, }: { locked: boolean; onToggle: () => void; disabled?: boolean; }) { return ( ); } const SERIES_STATUSES = [ { value: "", label: "Non défini" }, { value: "ongoing", label: "En cours" }, { value: "ended", label: "Terminée" }, { value: "hiatus", label: "Hiatus" }, { value: "cancelled", label: "Annulée" }, { value: "upcoming", label: "À paraître" }, ] as const; interface EditSeriesFormProps { libraryId: string; seriesName: string; currentAuthors: string[]; currentPublishers: string[]; currentBookAuthor: string | null; currentBookLanguage: string | null; currentDescription: string | null; currentStartYear: number | null; currentTotalVolumes: number | null; currentStatus: string | null; currentLockedFields: Record; } export function EditSeriesForm({ libraryId, seriesName, currentAuthors, currentPublishers, currentBookAuthor, currentBookLanguage, currentDescription, currentStartYear, currentTotalVolumes, currentStatus, currentLockedFields, }: EditSeriesFormProps) { const router = useRouter(); const [isPending, startTransition] = useTransition(); const [isOpen, setIsOpen] = useState(false); const [error, setError] = useState(null); // Champs propres à la série const [newName, setNewName] = useState(seriesName === "unclassified" ? "" : seriesName); const [authors, setAuthors] = useState(currentAuthors); const [authorInput, setAuthorInput] = useState(""); const [authorInputEl, setAuthorInputEl] = useState(null); const [publishers, setPublishers] = useState(currentPublishers); const [publisherInput, setPublisherInput] = useState(""); const [publisherInputEl, setPublisherInputEl] = useState(null); const [description, setDescription] = useState(currentDescription ?? ""); const [startYear, setStartYear] = useState(currentStartYear?.toString() ?? ""); const [totalVolumes, setTotalVolumes] = useState(currentTotalVolumes?.toString() ?? ""); const [status, setStatus] = useState(currentStatus ?? ""); // Lock states const [lockedFields, setLockedFields] = useState>(currentLockedFields); // Propagation aux livres — opt-in via bouton const [bookAuthor, setBookAuthor] = useState(currentBookAuthor ?? ""); const [bookLanguage, setBookLanguage] = useState(currentBookLanguage ?? ""); const [showApplyToBooks, setShowApplyToBooks] = useState(false); const toggleLock = (field: string) => { setLockedFields((prev) => ({ ...prev, [field]: !prev[field] })); }; const addAuthor = () => { const v = authorInput.trim(); if (v && !authors.includes(v)) { setAuthors([...authors, v]); } setAuthorInput(""); authorInputEl?.focus(); }; const removeAuthor = (idx: number) => { setAuthors(authors.filter((_, i) => i !== idx)); }; const handleAuthorKeyDown = (e: React.KeyboardEvent) => { if (e.key === "Enter") { e.preventDefault(); addAuthor(); } }; const addPublisher = () => { const v = publisherInput.trim(); if (v && !publishers.includes(v)) { setPublishers([...publishers, v]); } setPublisherInput(""); publisherInputEl?.focus(); }; const removePublisher = (idx: number) => { setPublishers(publishers.filter((_, i) => i !== idx)); }; const handlePublisherKeyDown = (e: React.KeyboardEvent) => { if (e.key === "Enter") { e.preventDefault(); addPublisher(); } }; const handleClose = useCallback(() => { setNewName(seriesName === "unclassified" ? "" : seriesName); setAuthors(currentAuthors); setAuthorInput(""); setPublishers(currentPublishers); setPublisherInput(""); setDescription(currentDescription ?? ""); setStartYear(currentStartYear?.toString() ?? ""); setTotalVolumes(currentTotalVolumes?.toString() ?? ""); setStatus(currentStatus ?? ""); setLockedFields(currentLockedFields); setShowApplyToBooks(false); setBookAuthor(currentBookAuthor ?? ""); setBookLanguage(currentBookLanguage ?? ""); setError(null); setIsOpen(false); }, [seriesName, currentAuthors, currentPublishers, currentDescription, currentStartYear, currentTotalVolumes, currentBookAuthor, currentBookLanguage, currentLockedFields]); useEffect(() => { if (!isOpen) return; const handleKeyDown = (e: KeyboardEvent) => { if (e.key === "Escape" && !isPending) handleClose(); }; document.addEventListener("keydown", handleKeyDown); return () => document.removeEventListener("keydown", handleKeyDown); }, [isOpen, isPending, handleClose]); const handleSubmit = (e: React.FormEvent) => { e.preventDefault(); if (!newName.trim() && seriesName !== "unclassified") return; setError(null); const finalAuthors = authorInput.trim() ? [...new Set([...authors, authorInput.trim()])] : authors; const finalPublishers = publisherInput.trim() ? [...new Set([...publishers, publisherInput.trim()])] : publishers; startTransition(async () => { try { const effectiveName = newName.trim() || "unclassified"; const body: Record = { new_name: effectiveName, authors: finalAuthors, publishers: finalPublishers, description: description.trim() || null, start_year: startYear.trim() ? parseInt(startYear.trim(), 10) : null, total_volumes: totalVolumes.trim() ? parseInt(totalVolumes.trim(), 10) : null, status: status || null, locked_fields: lockedFields, }; if (showApplyToBooks) { body.author = bookAuthor.trim() || null; body.language = bookLanguage.trim() || null; } const res = await fetch( `/api/libraries/${libraryId}/series/${encodeURIComponent(seriesName)}`, { method: "PATCH", headers: { "Content-Type": "application/json" }, body: JSON.stringify(body), } ); if (!res.ok) { const data = await res.json(); setError(data.error ?? "Erreur lors de la sauvegarde"); return; } setIsOpen(false); if (effectiveName !== seriesName) { router.push(`/libraries/${libraryId}/series/${encodeURIComponent(effectiveName)}` as any); } else { router.refresh(); } } catch { setError("Erreur réseau"); } }); }; const modal = isOpen ? createPortal( <> {/* Backdrop */}
!isPending && handleClose()} /> {/* Modal */}
{/* Header */}

Modifier la série

{/* Body */}
Nom setNewName(e.target.value)} disabled={isPending} placeholder="Nom de la série" />
Année de début toggleLock("start_year")} disabled={isPending} />
setStartYear(e.target.value)} disabled={isPending} placeholder="ex : 1990" />
Nombre de volumes toggleLock("total_volumes")} disabled={isPending} />
setTotalVolumes(e.target.value)} disabled={isPending} placeholder="ex : 12" />
Statut toggleLock("status")} disabled={isPending} />
{/* Auteurs — multi-valeur */}
Auteur(s) toggleLock("authors")} disabled={isPending} />
{authors.length > 0 && (
{authors.map((a, i) => ( {a} ))}
)}
setAuthorInput(e.target.value)} onKeyDown={handleAuthorKeyDown} disabled={isPending} placeholder="Ajouter un auteur (Entrée pour valider)" className="flex h-10 flex-1 rounded-md border border-input bg-background px-3 py-2 text-sm shadow-sm transition-colors placeholder:text-muted-foreground/90 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50" />
{showApplyToBooks && (
Auteur (livres) setBookAuthor(e.target.value)} disabled={isPending} placeholder="Écrase le champ auteur de chaque livre" /> Langue (livres) setBookLanguage(e.target.value)} disabled={isPending} placeholder="ex : fr, en, jp" />
)} {/* Éditeurs — multi-valeur */}
Éditeur(s) toggleLock("publishers")} disabled={isPending} />
{publishers.length > 0 && (
{publishers.map((p, i) => ( {p} ))}
)}
setPublisherInput(e.target.value)} onKeyDown={handlePublisherKeyDown} disabled={isPending} placeholder="Ajouter un éditeur (Entrée pour valider)" className="flex h-10 flex-1 rounded-md border border-input bg-background px-3 py-2 text-sm shadow-sm transition-colors placeholder:text-muted-foreground/90 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50" />
Description toggleLock("description")} disabled={isPending} />