対応キューActionQueueBeta

重大度つきの要対応項目を、種別・期日・操作と合わせて並べる対応キューです。

プレビュー

  • 重大
    本日
  • 警告
    あと7日
  • 警告
    あと14日
  • 情報
    明日
対応対象失効リスク

田所 慎一さま - 終身保険が今月末で失効

保険料未入金。失効防止コールを本日中に。

期限: 本日

状態とバリエーション

重大度順

critical / warning / info をアイコンとトーンで示し、既定で重大度順に並びます。

  • 重大
    本日
  • 警告
    あと7日
  • 警告
    あと14日
  • 情報
    明日
対応対象失効リスク

田所 慎一さま - 終身保険が今月末で失効

保険料未入金。失効防止コールを本日中に。

期限: 本日

絞り込み済み

上位のフィルタやタブで items を絞り込んでも、ActionQueue は同じ行構造を保ちます。

  • 警告
    あと7日
  • 警告
    あと14日
対応対象更新

三宅 加奈さま - 収入保障保険の更新期限

更新手続き書類の送付がまだです。

期限: あと7日

空状態

対応項目がない場合は、破線のプレースホルダーだけを表示します。

対応が必要な項目はありません

プロパティ

表は横にスクロールできます
プロパティ初期値説明
itemsActionItem[]-表示する対応項目です。重大度、種別、見出し、補足、期日、アクションを行ごとに渡します。
ActionItem.severity"critical" | "warning" | "info" | "neutral""neutral"重大度です。アイコン、トーン、重大度ソートに使います。色だけに依存しません。
ActionItem.onSelect() => void-行本文を選択可能にします。省略すると表示専用の行になります。
ActionItem.actionsReactNode-行末に配置するボタンなどです。行ボタンの内側ではなく兄弟として描画します。
sortBySeveritybooleantruecritical から neutral の順に並び替えます。
emptyLabelReactNode"対応が必要な項目はありません"items が空の時に表示するプレースホルダーです。
severityLabelsPartial<Record<ActionSeverity, string>>重大 / 警告 / 情報 / 通常読み上げ用の重大度ラベル(重大 / 警告 / 情報 / 通常)を上書きします。インスタンス単位のローカライズ用で、既定は日本語のままです。

使い方

import * as React from "react";
import { ActionQueue, Button, ToggleGroup, ToggleGroupItem, type ActionItem } from "@gunjo/ui";

const items = [
  {
    surface: "expiry",
    severity: "critical",
    kind: "失効リスク",
    title: "田所 慎一さま - 終身保険が今月末で失効",
    detail: "保険料未入金。失効防止コールを本日中に。",
    meta: "本日",
  },
  {
    surface: "renewal",
    severity: "warning",
    kind: "更新",
    title: "三宅 加奈さま - 収入保障保険の更新期限",
    detail: "更新手続き書類の送付がまだです。",
    meta: "あと7日",
  },
  {
    surface: "renewal",
    severity: "warning",
    kind: "満期",
    title: "黒川 大悟さま - 学資保険が満期",
    detail: "満期金の案内と次プランの提案を準備します。",
    meta: "あと14日",
  },
  {
    surface: "birthday",
    severity: "info",
    kind: "誕生日",
    title: "宇佐美 結菜さま - お誕生日",
    detail: "ご連絡のきっかけ。医療特約の見直し提案も。",
    meta: "明日",
  },
];

function actionButtonVariant(severity: string): React.ComponentProps<typeof Button>["variant"] {
  switch (severity) {
    case "critical":
      return "destructive";
    case "warning":
      return "warning";
    case "info":
      return "info";
    default:
      return "secondary";
  }
}

export function MorningActionQueue() {
  const [surface, setSurface] = React.useState("all");
  const [selected, setSelected] = React.useState<string | null>(null);
  const visibleItems = surface === "all" ? items : items.filter((item) => item.surface === surface);
  const selectedItem = visibleItems.find((item) => item.title === selected) ?? visibleItems[0];
  const actionItems: ActionItem[] = visibleItems.map((item) => ({
    ...item,
    severity: item.severity as ActionItem["severity"],
    onSelect: () => setSelected(item.title),
    actions: (
      <Button
        size="sm"
        variant={actionButtonVariant(item.severity)}
        onClick={() => setSelected(item.title)}
      >
        対応
      </Button>
    ),
  }));

  return (
    <div className="flex w-full max-w-2xl flex-col gap-4">
      <ToggleGroup
        type="single"
        value={surface}
        onValueChange={(value) => value && setSurface(value)}
        variant="outline"
        size="sm"
        disallowEmpty
        aria-label="対応種別"
      >
        <ToggleGroupItem value="all">すべて</ToggleGroupItem>
        <ToggleGroupItem value="expiry">失効</ToggleGroupItem>
        <ToggleGroupItem value="renewal">更新</ToggleGroupItem>
        <ToggleGroupItem value="birthday">誕生日</ToggleGroupItem>
      </ToggleGroup>

      <ActionQueue items={actionItems} emptyLabel="対応が必要な項目はありません" />

      {selectedItem ? (
        <div className="rounded-md border bg-muted/30 px-3 py-3 text-sm" aria-live="polite">
          <div className="flex flex-wrap items-center gap-2">
            <span className="text-xs font-medium uppercase tracking-wide text-muted-foreground">対応対象</span>
            <span className="rounded-full border bg-background px-2 py-0.5 text-xs font-medium text-foreground">{selectedItem.kind}</span>
          </div>
          <p className="mt-2 font-medium text-foreground">{selectedItem.title}</p>
          <p className="mt-1 text-muted-foreground">{selectedItem.detail}</p>
          <p className="mt-2 text-xs text-muted-foreground">期限: {selectedItem.meta}</p>
        </div>
      ) : null}
    </div>
  );
}

使用コンポーネント