{
  "code": "import { Head, router } from \"@inertiajs/react\"\nimport { useMemo, useState } from \"react\"\nimport { PageContainer, Flex } from \"@godxjp/ui/layout\"\nimport { Card, CardContent, DataTable, EmptyState, Badge, type ColumnDef } from \"@godxjp/ui/data-display\"\nimport { SearchInput, Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from \"@godxjp/ui/data-entry\"\nimport { Toolbar, ToolbarGroup, Pagination } from \"@godxjp/ui/navigation\"\nimport { formatDate } from \"@godxjp/ui/datetime\"\nimport { withCrmLayout } from \"@/layouts/crm-layout\" // see \"inertia-persistent-layout\"\n\ntype Coupon = { id: string; name: string; status: string; scope: string; validFrom: string; validTo: string; usage: number }\nconst PAGE_SIZE = 10\n\nfunction Coupons({ coupons }: { coupons: Coupon[] }) {\n  const [q, setQ] = useState(\"\")\n  const [status, setStatus] = useState(\"all\")\n  const [page, setPage] = useState(1)\n\n  const filtered = useMemo(() => coupons.filter((c) => {\n    if (q && !c.name.toLowerCase().includes(q.toLowerCase())) return false\n    if (status !== \"all\" && c.status !== status) return false\n    return true\n  }), [coupons, q, status])\n  const paged = filtered.slice((page - 1) * PAGE_SIZE, page * PAGE_SIZE)\n\n  // ColumnDef = { key, header, render?, align?: \"left\"|\"center\"|\"right\", sortable?, width? }\n  const columns: ColumnDef<Coupon>[] = [\n    { key: \"name\", header: \"クーポン名\", render: (c) => <span className=\"font-medium\">{c.name}</span> },\n    { key: \"scope\", header: \"スコープ\" }, // a category is plain text; Badge is for the status column only\n    { key: \"status\", header: \"ステータス\", render: (c) => <Badge status={c.status} /> },\n    { key: \"valid\", header: \"有効期間\", render: (c) => `${formatDate(c.validFrom)} 〜 ${formatDate(c.validTo)}` },\n    { key: \"usage\", header: \"利用数\", align: \"right\", render: (c) => c.usage.toLocaleString() },\n  ]\n\n  return (\n    <>\n      <Head title=\"クーポン管理\" />\n      {/* RULE: every page wraps in PageContainer; vertical rhythm via Flex direction=\"col\"/ResponsiveGrid, never p-* or gap-* */}\n      {/* No subtitle that restates the title, no StatCard row: the total is Pagination's showTotal. */}\n      <PageContainer title=\"クーポン管理\">\n        <Flex direction=\"col\" gap=\"lg\">\n          {/* Toolbar is the filter-bar primitive (standalone, above the table surface). It WRAPS and\n              never scrolls sideways (rule #51); past 2–4 filters, see the \"list-filters\" pattern. Never a\n              hand-built Flex/Card of FormFields (ui-audit hand-rolled-filter-bar). */}\n          <Toolbar hasActiveFilters={q !== \"\" || status !== \"all\"} onClear={() => { setQ(\"\"); setStatus(\"all\"); setPage(1) }}>\n            {/* SearchInput is value + onSearch(v) — NOT onChange */}\n            <SearchInput placeholder=\"クーポン名で検索\" value={q} onSearch={(v) => { setQ(v); setPage(1) }} />\n            {/* Status view of the SAME table = a filter: Segmented with count for 2-4 statuses, this\n                Select past 4. Never Tabs: Tabs switch panels with their own columns (gh#1324). */}\n            <ToolbarGroup label=\"ステータス\">\n              <Select value={status} onValueChange={(v) => { setStatus(v); setPage(1) }}>\n                <SelectTrigger><SelectValue /></SelectTrigger>\n                <SelectContent>\n                  <SelectItem value=\"all\">全ステータス</SelectItem>\n                  <SelectItem value=\"公開中\">公開中</SelectItem>\n                  <SelectItem value=\"下書き\">下書き</SelectItem>\n                </SelectContent>\n              </Select>\n            </ToolbarGroup>\n          </Toolbar>\n\n          <Card>\n            <CardContent flush>\n              {filtered.length === 0\n                ? <EmptyState title=\"該当するクーポンがありません\" description=\"検索条件を変更してください。\" /> /* one sentence, no icon */\n                : <DataTable data={paged} columns={columns} getRowId={(c) => c.id} onRowClick={(c) => router.visit(`/coupons/${c.id}`)} />}\n            </CardContent>\n          </Card>\n\n          {/* Pagination ONLY for real multi-page data — hide it while empty or single-page. */}\n          {filtered.length > PAGE_SIZE && (\n            <Pagination value={page} total={filtered.length} pageSize={PAGE_SIZE} showTotal onValueChange={(p) => setPage(p)} />\n          )}\n        </Flex>\n      </PageContainer>\n    </>\n  )\n}\n\nCoupons.layout = withCrmLayout\nexport default Coupons",
  "name": "inertia-list-page",
  "tagline": "Inertia + @godxjp/ui list page — PageContainer + Toolbar (above the table) + DataTable + Badge + Pagination. No KPI row, no restating subtitle.",
  "tags": [
    "inertia",
    "list",
    "table",
    "page",
    "filter",
    "pagination",
    "datatable",
    "crm"
  ]
}
