状態ボードStatusBoardExperimental
車両、設備、作業台などの状態を、エリア別のタイル盤として問題優先で一覧します。
プレビュー
渋谷エリア
配車・点検2件 要対応品川エリア
運行中2台501号車
空車渋谷エリア / 渋谷駅
渋谷駅西口で待機中。次の配車指示を受けられます。
- 担当
- 配車担当: 青井
- 次の対応
- 予約便 ORD-2406 を割り当て
- 更新
- 2分前
状態とバリエーション
エリア別
問題件数をグループ見出しに集約します。
渋谷エリア
配車・点検2件 要対応品川エリア
運行中2台501号車
空車渋谷エリア / 渋谷駅
渋谷駅西口で待機中。次の配車指示を受けられます。
- 担当
- 配車担当: 青井
- 次の対応
- 予約便 ORD-2406 を割り当て
- 更新
- 2分前
フラット表示
分類が不要な監視盤では items を直接渡します。
501号車
空車渋谷エリア / 渋谷駅
渋谷駅西口で待機中。次の配車指示を受けられます。
- 担当
- 配車担当: 青井
- 次の対応
- 予約便 ORD-2406 を割り当て
- 更新
- 2分前
選択中
selectedId で詳細対象をリング表示します。
渋谷エリア
配車・点検2件 要対応品川エリア
運行中2台508号車
故障渋谷エリア / 営業所
エンジン警告灯が点灯。営業所で整備確認中です。
- 担当
- 整備担当: 田中
- 次の対応
- 代替車 604号車へ切り替え
- 更新
- 8分前
プロパティ
表は横にスクロールできます
| プロパティ | 型 | 初期値 | 説明 |
|---|---|---|---|
| groups | StatusBoardGroup[] | - | 見出し付きのグループです。各グループに要対応件数を表示します。 |
| items | StatusBoardItem[] | - | グループなしで並べるフラットなタイルです。 |
| selectedId | string | number | - | 詳細対象として強調するタイルIDです。 |
| minTileWidth | number | 150 | auto-fill grid の最小タイル幅です。 |
| problemTones | StatusBoardTone[] | ["destructive","warning"] | 要対応件数とソートで問題扱いするtoneです。 |
| sort | boolean | true | rank とtone重大度で問題優先に並べます。 |
| formatProblemCount / formatItemCount | (count: number) => ReactNode | - | グループ見出し右側の件数表示をローカライズします。 |
使い方
import * as React from "react";
import { Badge, StatusBoard, type StatusBoardGroup } from "@gunjo/ui";
import { IconAlertTriangle, IconCar, IconCircleCheck, IconTool } from "@tabler/icons-react";
const groups: StatusBoardGroup[] = [
{
label: "渋谷エリア",
sublabel: "配車・点検",
items: [
{ id: "501", label: "501号車", icon: <IconCar className="h-4 w-4" />, status: "空車", statusIcon: <IconCircleCheck className="h-3 w-3" />, tone: "success", location: "渋谷駅", note: "最終更新 2分前", trailing: <Badge variant="secondary">待機</Badge> },
{ id: "508", label: "508号車", icon: <IconCar className="h-4 w-4" />, status: "故障", statusIcon: <IconAlertTriangle className="h-3 w-3" />, tone: "destructive", location: "営業所", note: "エンジン警告灯" },
{ id: "512", label: "512号車", icon: <IconCar className="h-4 w-4" />, status: "点検中", statusIcon: <IconTool className="h-3 w-3" />, tone: "warning", location: "整備レーン", note: "ブレーキ確認中" },
],
},
{
label: "品川エリア",
sublabel: "運行中",
items: [
{ id: "601", label: "601号車", icon: <IconCar className="h-4 w-4" />, status: "乗車中", tone: "primary", location: "品川駅", note: "羽田方面" },
{ id: "604", label: "604号車", icon: <IconCar className="h-4 w-4" />, status: "空車", tone: "success", location: "港南口", note: "最終更新 5分前" },
],
},
];
const details = {
"501": { area: "渋谷エリア", summary: "渋谷駅西口で待機中。次の配車指示を受けられます。", owner: "配車担当: 青井", nextAction: "予約便 ORD-2406 を割り当て", updated: "2分前" },
"508": { area: "渋谷エリア", summary: "エンジン警告灯が点灯。営業所で整備確認中です。", owner: "整備担当: 田中", nextAction: "代替車 604号車へ切り替え", updated: "8分前" },
"512": { area: "渋谷エリア", summary: "整備レーンでブレーキ確認中。完了後に運行へ戻します。", owner: "整備担当: 佐藤", nextAction: "点検記録を承認", updated: "12分前" },
"601": { area: "品川エリア", summary: "品川駅から羽田方面へ乗車中です。", owner: "乗務員: 山田", nextAction: "到着後に空車へ戻す", updated: "1分前" },
"604": { area: "品川エリア", summary: "港南口で待機中。渋谷エリアの代替車候補です。", owner: "配車担当: 青井", nextAction: "508号車の代替可否を確認", updated: "5分前" },
};
export function DispatchBoard() {
const [selectedId, setSelectedId] = React.useState("501");
const selectableGroups = groups.map((group) => ({
...group,
items: group.items.map((item) => ({ ...item, onSelect: () => setSelectedId(String(item.id)) })),
}));
const selectedItem = selectableGroups.flatMap((group) => group.items).find((item) => item.id === selectedId)!;
const detail = details[selectedId as keyof typeof details];
return (
<div className="flex w-full max-w-4xl flex-col gap-3 rounded-lg border bg-card p-4">
<StatusBoard groups={selectableGroups} selectedId={selectedId} formatProblemCount={(count) => count + "件 要対応"} formatItemCount={(count) => count + "台"} />
<section className="grid min-w-0 gap-3 rounded-md border bg-background p-3" aria-live="polite" aria-label="選択中車両の詳細">
<div className="min-w-0 space-y-1.5">
<div className="flex flex-wrap items-center gap-2">
<h3 className="text-sm font-semibold text-foreground">{selectedItem.label}</h3>
<Badge variant={selectedItem.tone === "destructive" ? "destructive" : "secondary"}>{selectedItem.status}</Badge>
</div>
<p className="text-xs text-muted-foreground">{detail.area} / {selectedItem.location}</p>
<p className="text-sm text-foreground">{detail.summary}</p>
</div>
<dl className="grid min-w-0 gap-2 rounded-md bg-muted/40 p-3 text-xs">
<div><dt className="text-muted-foreground">担当</dt><dd className="font-medium text-foreground">{detail.owner}</dd></div>
<div><dt className="text-muted-foreground">次の対応</dt><dd className="font-medium text-foreground">{detail.nextAction}</dd></div>
<div><dt className="text-muted-foreground">更新</dt><dd className="font-medium text-foreground">{detail.updated}</dd></div>
</dl>
</section>
</div>
);
}