import { useState } from "react"; import { Cascader, DatePicker, Field, Form, FormField, Input, Select, Slider, Switch, TagInput, Textarea, } from "@godxjp/ui/data-entry"; import type { DateRange } from "react-day-picker"; import { Card, CardContent, CardDescription, CardHeader, CardTitle, Avatar, AvatarFallback, } from "@godxjp/ui/data-display"; import { Alert, AlertDescription, AlertTitle } from "@godxjp/ui/feedback"; import { Button } from "@godxjp/ui/general"; import { AppShell, Flex, PageContainer, ResponsiveGrid, Sidebar, type SidebarSectionProp, Topbar, } from "@godxjp/ui/layout"; import { BookOpen, FileText, LayoutDashboard, ReceiptText, Users } from "lucide-react"; const sections: SidebarSectionProp[] = [ { label: "会計", items: [ { id: "dashboard", label: "ダッシュボード", icon: LayoutDashboard }, { id: "journal", label: "仕訳", icon: FileText }, { id: "invoices", label: "請求書", icon: ReceiptText }, { id: "partners", label: "取引先", icon: BookOpen }, ], }, { label: "管理", items: [{ id: "users", label: "ユーザー", icon: Users }] }, ]; const EXPENSE_CATEGORIES = [ { value: "operating", label: "営業費用", children: [ { value: "travel", label: "旅費交通費" }, { value: "supplies", label: "消耗品費" }, ], }, { value: "labor", label: "人件費", children: [ { value: "salary", label: "給与手当" }, { value: "bonus", label: "賞与" }, ], }, ]; /** The form's controlled value shape. */ interface InvoiceForm { partner: string; code: string; account: string; category: string[]; issueDate: Date | undefined; period: DateRange | undefined; amount: string; taxRate: number[]; labels: string[]; memo: string; draft: boolean; } const EMPTY: InvoiceForm = { partner: "", code: "", account: "", category: [], issueDate: undefined, period: undefined, amount: "", taxRate: [10], labels: [], memo: "", draft: false, }; type Errors = Partial>; /** Required-field validation — returns a message per empty required field. */ function validate(v: InvoiceForm): Errors { const e: Errors = {}; if (!v.partner.trim()) e.partner = "取引先名は必須です"; if (!v.code.trim()) e.code = "取引先コードは必須です"; if (!v.account) e.account = "勘定科目を選択してください"; if (!v.issueDate) e.issueDate = "発行日を選択してください"; if (!v.amount.trim()) e.amount = "金額を入力してください"; else if (!/^\d+$/.test(v.amount)) e.amount = "金額は半角数字で入力してください"; return e; } /** * Invoice form — a complete, realistic multi-section form with the full controlled validation flow. * Sections are Card blocks; the action bar (Cancel + Submit, primary rightmost — rule 41) lives in * the PageContainer footer. Submitting with empty required fields renders an inline error under each * field AND a summary Alert; fixing a field clears its error live; a valid submit shows a pending * spinner (button disabled) then a success Alert. Composed only from real @godxjp/ui primitives. */ export default function Demo() { const [v, setV] = useState(EMPTY); const [errors, setErrors] = useState({}); const [status, setStatus] = useState<"idle" | "pending" | "success">("idle"); // Update one field and clear its error live (validation-on-fix). function update(key: K, value: InvoiceForm[K]) { setV((s) => ({ ...s, [key]: value })); setErrors((e) => (e[key] ? { ...e, [key]: undefined } : e)); if (status === "success") setStatus("idle"); } function handleSubmit(event: React.FormEvent) { event.preventDefault(); const next = validate(v); setErrors(next); if (Object.keys(next).length > 0) { setStatus("idle"); return; } // Valid: simulate an async submit — spinner + disabled button, then success. setStatus("pending"); window.setTimeout(() => setStatus("success"), 1200); } function handleReset() { setV(EMPTY); setErrors({}); setStatus("idle"); } const errorCount = Object.values(errors).filter(Boolean).length; const pending = status === "pending"; return ( {}} product={{ name: "CoreBooks", role: "管理コンソール", color: "hsl(var(--primary))" }} /> } topbar={ C } /> } > } >
{/* Summary feedback — success after a valid submit, or an error summary on failure. */} {status === "success" ? ( 請求書を作成しました {v.partner} 宛の請求書({v.code})を保存しました。 ) : errorCount > 0 ? ( 入力内容を確認してください {errorCount} 件の必須項目が未入力です。各項目のエラーメッセージをご確認ください。 ) : null} {/* ── Section: 取引先 ── */} 取引先 請求先の取引先名とコードを入力します。 update("partner", e.target.value)} placeholder="株式会社ベトヤ" disabled={pending} /> update("code", e.target.value)} placeholder="BTY-0012" disabled={pending} /> {/* ── Section: 仕訳 ── */} 仕訳 勘定科目・経費カテゴリ・金額・税率・日付を入力します。 update("amount", e.target.value)} placeholder="100000" disabled={pending} /> update("taxRate", val)} min={0} max={10} step={2} name="tax_rate" aria-label="消費税率" /> {/* ── Section: 補足 ── */} 補足 ラベル・摘要と下書き保存の設定。 update("labels", val)} placeholder="ラベルを追加…" name="labels" />