対応キューActionQueueBeta
重大度つきの要対応項目を、種別・期日・操作と合わせて並べる対応キューです。
プレビュー
- 重大:本日
- 警告:あと7日
- 警告:あと14日
- 情報:明日
対応対象失効リスク
田所 慎一さま - 終身保険が今月末で失効
保険料未入金。失効防止コールを本日中に。
期限: 本日
状態とバリエーション
重大度順
critical / warning / info をアイコンとトーンで示し、既定で重大度順に並びます。
- 重大:本日
- 警告:あと7日
- 警告:あと14日
- 情報:明日
対応対象失効リスク
田所 慎一さま - 終身保険が今月末で失効
保険料未入金。失効防止コールを本日中に。
期限: 本日
絞り込み済み
上位のフィルタやタブで items を絞り込んでも、ActionQueue は同じ行構造を保ちます。
- 警告:あと7日
- 警告:あと14日
対応対象更新
三宅 加奈さま - 収入保障保険の更新期限
更新手続き書類の送付がまだです。
期限: あと7日
空状態
対応項目がない場合は、破線のプレースホルダーだけを表示します。
対応が必要な項目はありません
プロパティ
表は横にスクロールできます
| プロパティ | 型 | 初期値 | 説明 |
|---|---|---|---|
| items | ActionItem[] | - | 表示する対応項目です。重大度、種別、見出し、補足、期日、アクションを行ごとに渡します。 |
| ActionItem.severity | "critical" | "warning" | "info" | "neutral" | "neutral" | 重大度です。アイコン、トーン、重大度ソートに使います。色だけに依存しません。 |
| ActionItem.onSelect | () => void | - | 行本文を選択可能にします。省略すると表示専用の行になります。 |
| ActionItem.actions | ReactNode | - | 行末に配置するボタンなどです。行ボタンの内側ではなく兄弟として描画します。 |
| sortBySeverity | boolean | true | critical から neutral の順に並び替えます。 |
| emptyLabel | ReactNode | "対応が必要な項目はありません" | items が空の時に表示するプレースホルダーです。 |
| severityLabels | Partial<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>
);
}