SPB Git
6.8 KB · 54 lines tsx
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
Files · main
Menu

Theme