状態ボードStatusBoardExperimental

車両、設備、作業台などの状態を、エリア別のタイル盤として問題優先で一覧します。

プレビュー

渋谷エリア

配車・点検2件 要対応

品川エリア

運行中2台

501号車

空車

渋谷エリア / 渋谷駅

渋谷駅西口で待機中。次の配車指示を受けられます。

担当
配車担当: 青井
次の対応
予約便 ORD-2406 を割り当て
更新
2分前

状態とバリエーション

エリア別

問題件数をグループ見出しに集約します。

渋谷エリア

配車・点検2件 要対応

品川エリア

運行中2台

501号車

空車

渋谷エリア / 渋谷駅

渋谷駅西口で待機中。次の配車指示を受けられます。

担当
配車担当: 青井
次の対応
予約便 ORD-2406 を割り当て
更新
2分前

フラット表示

分類が不要な監視盤では items を直接渡します。

501号車

空車

渋谷エリア / 渋谷駅

渋谷駅西口で待機中。次の配車指示を受けられます。

担当
配車担当: 青井
次の対応
予約便 ORD-2406 を割り当て
更新
2分前

選択中

selectedId で詳細対象をリング表示します。

渋谷エリア

配車・点検2件 要対応

品川エリア

運行中2台

508号車

故障

渋谷エリア / 営業所

エンジン警告灯が点灯。営業所で整備確認中です。

担当
整備担当: 田中
次の対応
代替車 604号車へ切り替え
更新
8分前

プロパティ

表は横にスクロールできます
プロパティ初期値説明
groupsStatusBoardGroup[]-見出し付きのグループです。各グループに要対応件数を表示します。
itemsStatusBoardItem[]-グループなしで並べるフラットなタイルです。
selectedIdstring | number-詳細対象として強調するタイルIDです。
minTileWidthnumber150auto-fill grid の最小タイル幅です。
problemTonesStatusBoardTone[]["destructive","warning"]要対応件数とソートで問題扱いするtoneです。
sortbooleantruerank と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>
  );
}

使用コンポーネント