/** * Showcase · table-states — 状態 (V16) * * The five states a 勤怠 (attendance) DataTable must render — each shown statically, * side by side, so a reader learns every state at rest without clicking: * * 1. loading … SkeletonTable inside the real Card+toolbar chrome, preserving * the column layout (no spinner overlay). * 2. empty … two distinct wordings — filter-empty (絞り込み結果なし, offers * 「条件をクリア」) vs truly-empty (データなし, offers a create CTA). * 3. error … Alert tone="destructive" carrying an error code + request-id * (copyable, tabular-nums) + a 再試行 retry action. * 4. partial … rows that DID load, with a warning Alert that one segment * failed / is stale, plus a 部分再取得 retry for the missing part. * 5. ideal … the fully-populated DataTable (sorted, status Badges). * * Built ENTIRELY from real @godxjp/ui primitives — DataTable / Table chrome via * Card + DataTable.Toolbar, Skeleton family, Alert family, EmptyState, Badge, * Button. No hand-rolled , no raw controls, tokens only. * * DNA: compact density · small headings · tabular-nums on 時刻/件数 · fixed color * signaling (success 若竹 / warning 山吹 / attention 朱 / danger 茜 / info 群青 via * tone) · quiet factual JP copy · no emoji. */ import * as React from "react"; import { Clock, Inbox, Plus, RefreshCw, SearchX } from "lucide-react"; import { Button, Heading, Text } from "@godxjp/ui/general"; import { Badge, Card, CardAction, CardContent, CardHeader, CardTitle, DataTable, EmptyState, type ColumnDef, } from "@godxjp/ui/data-display"; import { Alert, AlertActions, AlertDescription, AlertTitle, SkeletonTable, } from "@godxjp/ui/feedback"; import { Flex, PageContainer } from "@godxjp/ui/layout"; // ── Domain: 勤怠 (打刻) records ──────────────────────────────────────────────── type Punch = { id: string; employee: string; dept: string; clockIn: string; clockOut: string; workH: string; status: "present" | "late" | "early" | "pending"; }; /** 勤怠ステータス → Badge tone (fixed color signaling). */ const STATUS_META: Record< Punch["status"], { label: string; tone: "success" | "warning" | "info" | "neutral" } > = { // 出勤 = 若竹(success) · 遅刻 = 山吹(warning, non-destructive lateness) · // 早退 = 群青(info) · 承認待ち = neutral(quiet, not a warning yet). present: { label: "出勤", tone: "success" }, late: { label: "遅刻", tone: "warning" }, early: { label: "早退", tone: "info" }, pending: { label: "承認待ち", tone: "neutral" }, }; const COLUMNS: ColumnDef[] = [ { key: "employee", header: "従業員", width: "w-40" }, { key: "dept", header: "部署", hiddenOnMobile: true }, { key: "clockIn", header: "出勤", align: "right", render: (r) => {r.clockIn}, }, { key: "clockOut", header: "退勤", align: "right", render: (r) => {r.clockOut}, }, { key: "workH", header: "実働", align: "right", hiddenOnMobile: true, render: (r) => {r.workH}, }, { key: "status", header: "状態", align: "center", render: (r) => ( {STATUS_META[r.status].label} ), }, ]; const ROWS: Punch[] = [ { id: "EMP-1042", employee: "佐藤 美咲", dept: "受付", clockIn: "08:58", clockOut: "18:02", workH: "8.0h", status: "present", }, { id: "EMP-1043", employee: "鈴木 一郎", dept: "倉庫", clockIn: "09:14", clockOut: "18:05", workH: "7.8h", status: "late", }, { id: "EMP-1044", employee: "高橋 結衣", dept: "経理", clockIn: "08:55", clockOut: "16:30", workH: "6.6h", status: "early", }, { id: "EMP-1045", employee: "田中 健太", dept: "配送", clockIn: "08:49", clockOut: "—", workH: "—", status: "pending", }, { id: "EMP-1046", employee: "伊藤 さくら", dept: "受付", clockIn: "08:52", clockOut: "18:00", workH: "8.1h", status: "present", }, ]; /** Only the segment that succeeded in the `partial` case (倉庫 fetch failed). */ const PARTIAL_ROWS = ROWS.filter((r) => r.dept !== "倉庫"); // ── Local chrome: a titled section that frames each state demo ───────────────── function StateSection({ step, title, note, children, }: { step: string; title: string; note: string; children: React.ReactNode; }) { return ( {step} {title} {note} {children} ); } /** Shared table-card shell: the toolbar chrome stays identical across states so * the loading skeleton visibly preserves the table's column layout. */ function TableShell({ caption, banner, children, }: { caption: React.ReactNode; /** Padded band between the header and the flush table body (an Alert). */ banner?: React.ReactNode; children?: React.ReactNode; }) { return ( 本日の打刻 {caption} {banner ? {banner} : null} {children ? {children} : null} ); } export default function Demo() { return ( {/* 1 ─ loading: skeleton that preserves the column layout */} {/* 2 ─ empty: filter-empty vs truly-empty wording */} {/* filter-empty: data exists, the FILTER matched nothing → clear filters */} r.id} empty={ 条件をクリア } /> } /> {/* truly-empty: no data at all for the day → create / 打刻登録 CTA */} r.id} empty={ {/* 3 ─ error: code + request-id + retry */} 勤怠データを取得できませんでした サーバーが応答しませんでした。時間をおいて再試行してください。
コード ATTEND_FETCH_504 ・ リクエストID{" "} req_7f3a91c0d2
} />
{/* 4 ─ partial: some rows loaded, one segment failed / stale */} 一部のデータを取得できませんでした 「倉庫」部署の打刻が取得できなかったため、最新ではない可能性があります。
コード ATTEND_PARTIAL_倉庫 ・ リクエストID{" "} req_7f3a91c0e8
} > r.id} />
{/* 5 ─ ideal: fully populated */}
} > r.id} />
); }