spb/wp-spboucher-ai
Public
1import { Check, Csrf, Field, Select } from "@/components/admin-ui";2import type { taxonomy } from "@/lib/admin/queries";3type Tax = Awaited<ReturnType<typeof taxonomy>>;4const dateStr = (d: string | null | undefined, p: string | undefined) => (!d ? "" : p === "year" ? String(d).slice(0, 4) : p === "month" ? String(d).slice(0, 7) : String(d).slice(0, 10));5export function PaperForm({ p, tax, csrf, action }: { p: Record<string, any> | null; tax: Tax; csrf: string; action: (f: FormData) => Promise<void> }) {6 const sel = (k: string) => new Set<number>(((p?.[k] as { id: number }[]) ?? []).map((x) => x.id));7 const j = (v: unknown) => (v ? JSON.stringify(v, null, 1) : "");8 return (9 <form action={action} className="space-y-6">10 <Csrf token={csrf} />{p && <input type="hidden" name="id" value={p.id} />}11 <section className="card p-4 grid gap-4 sm:grid-cols-4">12 <Select label="Series" name="series_code" value={p?.series_code ?? "WP"} options={tax.series.map((s) => ({ v: s.code, label: `${s.code} — ${s.name_en}` }))} />13 <Field label="Number" name="number" type="number" value={p?.number} required hint="Permanent. Never renumber; gaps are fine." />14 <Field label="Internal code" name="internal_code" value={p?.internal_code} hint="e.g. repository name; not proof of release" />15 <Field label="Language" name="language" value={p?.language ?? "en"} />16 <Field label="Title" name="title" value={p?.title} required className="sm:col-span-4" />17 <Field label="Subtitle" name="subtitle" value={p?.subtitle} className="sm:col-span-4" />18 <Field label="Title (FR)" name="title_fr" value={p?.title_fr} className="sm:col-span-2" /><Field label="Subtitle (FR)" name="subtitle_fr" value={p?.subtitle_fr} className="sm:col-span-2" />19 <Field label="Abstract (paragraphs separated by a blank line)" name="abstract" value={(p?.abstract as string[] | undefined)?.join("\n\n")} rows={10} className="sm:col-span-4" />20 <Field label="Abstract (FR)" name="abstract_fr" value={(p?.abstract_fr as string[] | undefined)?.join("\n\n")} rows={6} className="sm:col-span-4" />21 <Field label="Research question" name="research_question" value={p?.research_question} rows={2} className="sm:col-span-2" /><Field label="Research question (FR)" name="research_question_fr" value={p?.research_question_fr} rows={2} className="sm:col-span-2" />22 <Field label="Headline (one-sentence result)" name="hero_headline" value={p?.hero_headline} className="sm:col-span-2" /><Field label="Sub-headline" name="hero_subheadline" value={p?.hero_subheadline} rows={2} className="sm:col-span-2" />23 <Field label="Notice (e.g. Working draft)" name="notice" value={p?.notice} className="sm:col-span-2" /><Field label="Notice (FR)" name="notice_fr" value={p?.notice_fr} className="sm:col-span-2" />24 </section>25 <section className="card p-4 grid gap-4 sm:grid-cols-4">26 <Field label="Year" name="year" type="number" value={p?.year} />27 <Field label="First release (YYYY, YYYY-MM or YYYY-MM-DD)" name="first_released_on" value={dateStr(p?.first_released_on, p?.first_released_precision)} hint="Precision is kept as entered; never guess a day." />28 <Field label="Latest revision" name="last_revised_on" value={dateStr(p?.last_revised_on, p?.last_revised_precision)} />29 <Select label="Publication status" name="publication_status" value={p?.publication_status ?? "working_paper"} options={[{ v: "working_paper", label: "Working paper" }, { v: "under_review", label: "Under review" }, { v: "forthcoming", label: "Forthcoming" }, { v: "published", label: "Published in a journal" }]} />30 <Field label="Journal reference" name="journal_ref" value={p?.journal_ref} className="sm:col-span-2" /><Field label="DOI" name="doi" value={p?.doi} /><Field label="SSRN abstract id" name="ssrn_id" value={p?.ssrn_id} />31 <Field label="Keywords (one per line)" name="keywords" value={(p?.keywords as string[] | undefined)?.join("\n")} rows={5} className="sm:col-span-2" /><Field label="JEL codes (comma-separated)" name="jel" value={(p?.jel as string[] | undefined)?.join(", ")} className="sm:col-span-2" />32 <Select label="Primary domain" name="primary_domain_id" value={p?.primary_domain_id ?? ""} options={[{ v: "", label: "—" }, ...tax.topics.filter((t) => t.kind === "domain").map((t) => ({ v: t.id, label: t.name_en }))]} />33 <div className="flex items-end gap-4"><Check label="Featured on home" name="featured" checked={p?.featured} /><Field label="Rank" name="featured_rank" type="number" value={p?.featured_rank} /></div>34 </section>35 <section className="card p-4 grid gap-4 md:grid-cols-2">36 <div><p className="label mb-2">Authors (order = position)</p><div className="grid gap-1 max-h-48 overflow-auto">{tax.authors.map((a) => <Check key={a.id} label={a.display_name} name="author_ids" checked={sel("authors").has(a.id)} />)}</div><input type="hidden" name="author_ids_hint" value="" /></div>37 <div><p className="label mb-2">Topics and domains</p><div className="grid gap-1 max-h-48 overflow-auto">{tax.topics.map((t) => <Check key={t.id} label={`${t.kind === "domain" ? "◆ " : ""}${t.name_en}`} name="topic_ids" checked={sel("topics").has(t.id)} />)}</div></div>38 <div><p className="label mb-2">Methods</p><div className="grid gap-1 max-h-48 overflow-auto">{tax.methods.map((m) => <Check key={m.id} label={m.name_en} name="method_ids" checked={sel("methods").has(m.id)} />)}</div></div>39 <div><p className="label mb-2">Datasets</p><div className="grid gap-1 max-h-48 overflow-auto">{tax.datasets.map((d) => <Check key={d.id} label={d.name_en} name="dataset_ids" checked={sel("datasets").has(d.id)} />)}</div></div>40 </section>41 <section className="card p-4 grid gap-4 md:grid-cols-2">42 <Field label='Findings — JSON [{"value","label"}]' name="findings" value={j(p?.findings)} rows={8} />43 <Field label='Contributions — JSON [{"kind?","title","description"}]' name="contributions" value={j(p?.contributions)} rows={8} />44 <Field label='Methodology — JSON [{"title","description"}] (verified fields only)' name="methodology" value={j(p?.methodology)} rows={8} />45 <Field label='Data sections — JSON [{"title","description"}]' name="data_sections" value={j(p?.data_sections)} rows={8} />46 <Field label="Reproducibility notes (one per line)" name="reproducibility" value={(p?.reproducibility as string[] | undefined)?.join("\n")} rows={5} />47 <Field label='Artifacts — JSON [{"kind","label","url"}] (code, appendix, slides…)' name="artifacts" value={j(p?.artifacts)} rows={5} />48 <Field label='External links — JSON [{"kind","label","url"}]' name="external_links" value={j(p?.external_links)} rows={4} />49 </section>50 <div className="flex gap-2"><button className="btn btn-primary">{p ? "Save changes" : "Create draft"}</button></div>51 </form>52 );53}54