import { useMutation, useQueries, useQuery, useQueryClient } from "@tanstack/react-query"; import { useState, type ReactNode } from "react"; import { useLocation } from "wouter"; import { api, type EntryView, type Work } from "../api.ts"; import { friendlyError, mediaTypeLabel } from "../format.ts"; import { useDebounced } from "../hooks.ts"; import { Cover } from "./Cover.tsx"; import { Icon } from "./Icon.tsx"; /** Drafts only: suggested matches and a catalogue search. Linking keeps the user's rating, reads and review. */ export function LinkToCatalogue({ entry }: { entry: EntryView }) { const qc = useQueryClient(); const [, navigate] = useLocation(); const [q, setQ] = useState(""); const term = useDebounced(q.trim()); const suggestions = useQuery({ queryKey: ["suggestions", entry.workId], queryFn: () => api.suggestions(entry.workId) }); const candidates = useQueries({ queries: (suggestions.data ?? []).slice(0, 5).map((s) => ({ queryKey: ["work", s.candidateWorkId], queryFn: () => api.work(s.candidateWorkId), staleTime: 300_000 })), }); const search = useQuery({ queryKey: ["works-search", term], queryFn: () => api.searchWorks(term), enabled: term.length >= 2, }); const done = (newId?: string) => { void qc.invalidateQueries({ queryKey: ["entries"] }); void qc.invalidateQueries({ queryKey: ["entry", entry.id] }); void qc.invalidateQueries({ queryKey: ["suggestions", entry.workId] }); if (newId && newId !== entry.id) navigate(`/entries/${newId}`, { replace: true }); }; const move = useMutation({ mutationFn: (workId: string) => api.moveEntry(entry.id, workId), onSuccess: (e) => done(e.id) }); const accept = useMutation({ mutationFn: api.acceptSuggestion, onSuccess: () => done() }); const reject = useMutation({ mutationFn: api.rejectSuggestion, onSuccess: () => done() }); const busy = move.isPending || accept.isPending || reject.isPending; const error = move.error ?? accept.error ?? reject.error; const row = (w: Work, actions: ReactNode) => (
  • {w.title}
    {[mediaTypeLabel(w.media_type), w.year, w.creators?.[0]?.name].filter(Boolean).join(" · ")}
    {actions}
  • ); const results = (search.data ?? []).filter((w) => w.id !== entry.workId); return (

    This is a draft. Link it to a catalogue entry to fill in the cover and details. Your rating, reads and review stay as they are.

    {candidates.length > 0 && ( <>

    Suggested

    )}
    setQ(e.target.value)} />
    {term.length >= 2 && !search.isPending && ( results.length === 0 ? (

    Nothing in the catalogue matches “{term}”. Connect a source to bring in more.

    ) : ( ) )} {error &&

    {friendlyError(error)}

    }
    ); }