"use client"; import { useState, type ReactNode } from "react"; import { Plus } from "lucide-react"; import { Button } from "@/components/ui/Button"; import { TextInput } from "@/components/ui/TextInput"; import type { RegisterInput } from "@/lib/foreshadow/board"; interface RegisterFormProps { projectId: string; busy: boolean; onRegister: (input: RegisterInput) => Promise; open?: boolean; onOpenChange?: (open: boolean) => void; } const EMPTY = { code: "", title: "", plantedAt: "", content: "", expectedCloseFrom: "", expectedCloseTo: "", importance: "", }; // 登记新伏笔(UX §6.8)。受控表单 → 组装 RegisterInput;成功后清空。 export function RegisterForm({ projectId, busy, onRegister, open: controlledOpen, onOpenChange, }: RegisterFormProps) { const [localOpen, setLocalOpen] = useState(false); const [form, setForm] = useState({ ...EMPTY }); const open = controlledOpen ?? localOpen; const setOpen = (next: boolean): void => { if (controlledOpen === undefined) setLocalOpen(next); onOpenChange?.(next); }; const set = (key: keyof typeof EMPTY, value: string): void => setForm((prev) => ({ ...prev, [key]: value })); const submit = async (): Promise => { const ok = await onRegister({ projectId, code: form.code, title: form.title, plantedAt: toInt(form.plantedAt), content: form.content || null, expectedCloseFrom: toInt(form.expectedCloseFrom), expectedCloseTo: toInt(form.expectedCloseTo), importance: form.importance || null, }); if (ok) { setForm({ ...EMPTY }); setOpen(false); } }; if (!open) { return ( ); } const canSubmit = form.code.trim().length > 0 && form.title.trim().length > 0 && !busy; return (
{ e.preventDefault(); void submit(); }} className="rounded border border-line bg-panel p-4" >
set("code", e.target.value)} /> set("title", e.target.value)} /> set("plantedAt", e.target.value)} /> set("expectedCloseFrom", e.target.value)} /> set("expectedCloseTo", e.target.value)} /> set("importance", e.target.value)} placeholder="主线/支线" /> set("content", e.target.value)} />
); } interface FormSlotProps { label: string; id: string; span2?: boolean; span3?: boolean; children: ReactNode; } function FormSlot({ label, id, span2, span3, children }: FormSlotProps) { const span = span3 ? "col-span-2 sm:col-span-3" : span2 ? "col-span-2" : ""; return (
{children}
); } function toInt(raw: string): number | null { const trimmed = raw.trim(); if (trimmed.length === 0) return null; const n = Number.parseInt(trimmed, 10); return Number.isInteger(n) ? n : null; }