import { useEffect, useState } from "react"; import { api, ApiError } from "../api"; import type { SubmissionDetail, SubmissionRow } from "../types"; import { FIELD_LABELS } from "../types"; import { ArrowLeft, Check, X } from "lucide-react"; const STATUS_TABS = [ { key: "pending", label: "待审核" }, { key: "approved", label: "已通过" }, { key: "rejected", label: "已驳回" }, ]; const STATUS_BADGE: Record = { pending: "bg-amber-50 text-amber-700", approved: "bg-emerald-50 text-emerald-700", rejected: "bg-red-50 text-red-700", }; const STATUS_TEXT: Record = { pending: "待审核", approved: "已通过", rejected: "已驳回", }; export default function SubmissionsPage({ onPending }: { onPending?: (n: number) => void }) { const [tab, setTab] = useState("pending"); const [rows, setRows] = useState([]); const [openId, setOpenId] = useState(null); const [error, setError] = useState(""); async function load() { setError(""); try { const res = await api.listSubmissions(tab, 1, 50); setRows(res.items); onPending?.(res.pending); } catch (e) { setError(e instanceof ApiError ? e.message : "加载失败"); } } useEffect(() => { load(); // eslint-disable-next-line react-hooks/exhaustive-deps }, [tab]); if (openId) { return ( { setOpenId(null); load(); }} /> ); } return (
{STATUS_TABS.map((t) => ( ))}
{error && (
{error}
)}
{rows.length === 0 ? ( ) : ( rows.map((r) => ( setOpenId(r.id)} className="cursor-pointer hover:bg-gray-50" > )) )}
商品名称 条码 投稿人 类型 提交时间 状态
暂无投稿
{r.name} {r.gtin || "—"} {r.submitter_name || "匿名"} {r.matched ? "补全已有商品" : "新建商品"} {new Date(r.created_at).toLocaleString()} {STATUS_TEXT[r.status]}
); } function Field({ label, value }: { label: string; value: React.ReactNode }) { if (value === null || value === undefined || value === "") return null; return (
{label}
{value}
); } function SubmissionView({ id, onBack }: { id: string; onBack: () => void }) { const [d, setD] = useState(null); const [error, setError] = useState(""); const [busy, setBusy] = useState(false); const [rejecting, setRejecting] = useState(false); const [reason, setReason] = useState(""); useEffect(() => { api.getSubmission(id).then(setD).catch((e) => setError(e.message)); }, [id]); async function approve() { if (!confirm("确认通过该投稿?将写入正式商品库并记录来源 community。")) return; setBusy(true); setError(""); try { await api.approveSubmission(id); onBack(); } catch (e) { setError(e instanceof ApiError ? e.message : "操作失败"); } finally { setBusy(false); } } async function reject() { setBusy(true); setError(""); try { await api.rejectSubmission(id, reason.trim()); onBack(); } catch (e) { setError(e instanceof ApiError ? e.message : "操作失败"); } finally { setBusy(false); } } if (error && !d) { return (
{error}
); } if (!d) return
加载中…
; const p = d.payload; const nutri = Object.entries(p.nutriments || {}); return (

{p.name}

{STATUS_TEXT[d.status]}
{error &&
{error}
} {d.target_product_id && (
该条码已存在商品,通过后将补全已有商品(仅覆盖本次提供的字段)。
)}

投稿内容

{nutri.length > 0 && ( {p.nutrition_basis ? `(${p.nutrition_basis}) ` : ""} {nutri.map(([k, v]) => `${FIELD_LABELS[k] || k}:${v}`).join(",")} } /> )} {p.images && p.images.length > 0 && ( {p.images.map((im, i) => ( ))}
} /> )}

投稿人 / 备注

{d.reviewed_by && } {d.review_note && } {d.result_product_id && }
{d.status === "pending" && (
{rejecting ? (
setReason(e.target.value)} placeholder="例如:资料无法核实 / 重复投稿" />
) : (
)}
)} ); }