@squadbase/vantage 0.6.2-dev1.3e69e88 → 0.6.2-dev1.5aae04e
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/add-4GHF2TX2.js +6 -0
- package/dist/add-4GHF2TX2.js.map +1 -0
- package/dist/{build-JB4VOMUI.js → build-X5NVCQKT.js} +9 -9
- package/dist/{build-JB4VOMUI.js.map → build-X5NVCQKT.js.map} +1 -1
- package/dist/{check-ZJYK6RLG.js → check-ZBIH422T.js} +7 -7
- package/dist/{check-ZJYK6RLG.js.map → check-ZBIH422T.js.map} +1 -1
- package/dist/chunk-2ICNHUG6.js +1820 -0
- package/dist/chunk-2ICNHUG6.js.map +1 -0
- package/dist/{chunk-5ZOP4SQR.js → chunk-2N72FKLB.js} +12 -7
- package/dist/chunk-2N72FKLB.js.map +1 -0
- package/dist/{chunk-BCE22JSM.js → chunk-7DZGJQ3P.js} +33 -14
- package/dist/chunk-7DZGJQ3P.js.map +1 -0
- package/dist/{chunk-U2M6HGKQ.js → chunk-AABMKHEU.js} +4 -4
- package/dist/{chunk-U2M6HGKQ.js.map → chunk-AABMKHEU.js.map} +1 -1
- package/dist/{chunk-KLEIVDUV.js → chunk-CEXAQQ5W.js} +150 -11
- package/dist/chunk-CEXAQQ5W.js.map +1 -0
- package/dist/{chunk-QI3XQKHS.js → chunk-GNXCX4WE.js} +10 -3
- package/dist/chunk-GNXCX4WE.js.map +1 -0
- package/dist/chunk-HKSVYMYJ.js +7919 -0
- package/dist/chunk-HKSVYMYJ.js.map +1 -0
- package/dist/{chunk-JGRMRXXZ.js → chunk-JKLLFVEU.js} +3 -3
- package/dist/{chunk-JGRMRXXZ.js.map → chunk-JKLLFVEU.js.map} +1 -1
- package/dist/{chunk-PXRX5PEC.js → chunk-PDOBHMUQ.js} +3 -3
- package/dist/chunk-PDOBHMUQ.js.map +1 -0
- package/dist/{chunk-ZVANIULZ.js → chunk-RNTDBX6S.js} +4 -4
- package/dist/{chunk-ZVANIULZ.js.map → chunk-RNTDBX6S.js.map} +1 -1
- package/dist/{chunk-A2UUGASH.js → chunk-U736PVHY.js} +2 -2
- package/dist/chunk-U736PVHY.js.map +1 -0
- package/dist/{chunk-PVTHHV7X.js → chunk-V3LF7BE7.js} +27 -5
- package/dist/chunk-V3LF7BE7.js.map +1 -0
- package/dist/{chunk-EP6JHXSR.js → chunk-WAMZWZJB.js} +54 -9
- package/dist/chunk-WAMZWZJB.js.map +1 -0
- package/dist/{chunk-RPZAALCX.js → chunk-XPQSTZUU.js} +254 -164
- package/dist/chunk-XPQSTZUU.js.map +1 -0
- package/dist/{chunk-DTDVSFRY.js → chunk-XQUSGIDG.js} +17 -3
- package/dist/chunk-XQUSGIDG.js.map +1 -0
- package/dist/cli.js +12 -11
- package/dist/cli.js.map +1 -1
- package/dist/client/index.d.ts +32 -3
- package/dist/client/index.js +205 -12
- package/dist/client/index.js.map +1 -1
- package/dist/components/index.d.ts +1976 -29
- package/dist/components/index.js +4 -4
- package/dist/{dev-FCEC3UKQ.js → dev-4XEDTELV.js} +10 -10
- package/dist/dev-4XEDTELV.js.map +1 -0
- package/dist/doc/index.js +2 -2
- package/dist/doc/index.js.map +1 -1
- package/dist/{docs-EJJ3BECT.js → docs-JVP4RPKT.js} +3 -3
- package/dist/{docs-EJJ3BECT.js.map → docs-JVP4RPKT.js.map} +1 -1
- package/dist/{doctor-WHZXPERA.js → doctor-5E2VEZPK.js} +7 -7
- package/dist/{doctor-WHZXPERA.js.map → doctor-5E2VEZPK.js.map} +1 -1
- package/dist/markdown/index.js +1 -1
- package/dist/markdown/index.js.map +1 -1
- package/dist/{preview-OGMCNQUO.js → preview-T6CLSQVX.js} +3 -3
- package/dist/{preview-OGMCNQUO.js.map → preview-T6CLSQVX.js.map} +1 -1
- package/dist/query/index.d.ts +0 -11
- package/dist/query/index.js +2 -2
- package/dist/{routes-GTFO5PUF.js → routes-3SGAFDMC.js} +6 -6
- package/dist/{routes-GTFO5PUF.js.map → routes-3SGAFDMC.js.map} +1 -1
- package/dist/{search-6Y422SV6.js → search-5LC4XPB5.js} +3 -3
- package/dist/{search-6Y422SV6.js.map → search-5LC4XPB5.js.map} +1 -1
- package/dist/slide/index.js +2 -2
- package/dist/slide/index.js.map +1 -1
- package/dist/table/index.js +11 -10
- package/dist/table/index.js.map +1 -1
- package/dist/ui/index.d.ts +144 -11
- package/dist/ui/index.js +1339 -87
- package/dist/ui/index.js.map +1 -1
- package/dist/{upgrade-AL4KZZR4.js → upgrade-JHXZBGQS.js} +7 -7
- package/dist/{upgrade-AL4KZZR4.js.map → upgrade-JHXZBGQS.js.map} +1 -1
- package/dist/vite/index.d.ts +10 -0
- package/dist/vite/index.js +4 -4
- package/docs/en/components.md +29 -15
- package/docs/en/parts/data-table.md +22 -0
- package/docs/en/parts/delta.md +50 -0
- package/docs/en/parts/format.md +43 -0
- package/docs/en/parts/kpi-card.md +57 -0
- package/docs/en/parts/metric-value.md +3 -2
- package/docs/en/parts/sparkline.md +1 -1
- package/docs/en/parts/trend-indicator.md +17 -1
- package/docs/en/ui-and-theming.md +1 -1
- package/docs/index.json +366 -76
- package/docs/ja/changelog.md +31 -0
- package/docs/ja/components.md +57 -101
- package/docs/ja/page-types/csv.md +83 -0
- package/docs/ja/page-types/doc.md +83 -0
- package/docs/ja/page-types/excel.md +82 -0
- package/docs/ja/page-types/slide.md +132 -0
- package/docs/ja/page-types.md +47 -0
- package/docs/ja/parts/area-chart.md +61 -0
- package/docs/ja/parts/bar-chart.md +69 -0
- package/docs/ja/parts/bar-list.md +35 -0
- package/docs/ja/parts/category-bar.md +36 -0
- package/docs/ja/parts/data-table.md +22 -0
- package/docs/ja/parts/delta.md +50 -0
- package/docs/ja/parts/donut-chart.md +47 -0
- package/docs/ja/parts/echart.md +3 -0
- package/docs/ja/parts/format.md +44 -0
- package/docs/ja/parts/kpi-card.md +55 -0
- package/docs/ja/parts/line-chart.md +58 -0
- package/docs/ja/parts/metric-value.md +3 -2
- package/docs/ja/parts/progress-circle.md +34 -0
- package/docs/ja/parts/sparkline.md +2 -2
- package/docs/ja/parts/tracker.md +32 -0
- package/docs/ja/parts/trend-indicator.md +17 -1
- package/docs/ja/routing.md +8 -86
- package/docs/ja/ui/alert-dialog.md +41 -0
- package/docs/ja/ui/avatar.md +31 -0
- package/docs/ja/ui/combobox.md +74 -0
- package/docs/ja/ui/drawer.md +41 -0
- package/docs/ja/ui/field.md +42 -0
- package/docs/ja/ui/hover-card.md +21 -0
- package/docs/ja/ui/kbd.md +16 -0
- package/docs/ja/ui/pagination.md +36 -0
- package/docs/ja/ui/radio-group.md +21 -0
- package/docs/ja/ui/toast.md +46 -0
- package/docs/ja/ui-and-theming.md +14 -7
- package/package.json +41 -31
- package/registry/ui/accordion.tsx +76 -0
- package/registry/ui/alert-dialog.tsx +189 -0
- package/registry/ui/alert.tsx +80 -0
- package/registry/ui/app-shell.tsx +287 -0
- package/registry/ui/area-chart.tsx +3675 -0
- package/registry/ui/attention-list.tsx +303 -0
- package/registry/ui/avatar.tsx +110 -0
- package/registry/ui/badge.tsx +56 -0
- package/registry/ui/bar-chart.tsx +3672 -0
- package/registry/ui/bar-list.tsx +3716 -0
- package/registry/ui/box-plot-chart.tsx +3880 -0
- package/registry/ui/breadcrumb.tsx +128 -0
- package/registry/ui/button.tsx +62 -0
- package/registry/ui/calendar.tsx +223 -0
- package/registry/ui/card.tsx +107 -0
- package/registry/ui/category-bar.tsx +1263 -0
- package/registry/ui/chart-frame.tsx +1366 -0
- package/registry/ui/checkbox.tsx +30 -0
- package/registry/ui/collapsible.tsx +23 -0
- package/registry/ui/combobox.tsx +310 -0
- package/registry/ui/command.tsx +198 -0
- package/registry/ui/dashboard-card.tsx +217 -0
- package/registry/ui/data-table.tsx +693 -146
- package/registry/ui/date-range-picker.tsx +390 -0
- package/registry/ui/delta.tsx +188 -0
- package/registry/ui/dialog.tsx +161 -0
- package/registry/ui/donut-chart.tsx +3724 -0
- package/registry/ui/drawer.tsx +229 -0
- package/registry/ui/dropdown-menu.tsx +274 -0
- package/registry/ui/echart.tsx +923 -0
- package/registry/ui/field.tsx +240 -0
- package/registry/ui/filter-bar.tsx +392 -0
- package/registry/ui/funnel-steps.tsx +3569 -0
- package/registry/ui/heatmap.tsx +1760 -0
- package/registry/ui/histogram-chart.tsx +3960 -0
- package/registry/ui/hover-card.tsx +52 -0
- package/registry/ui/input-group.tsx +160 -0
- package/registry/ui/input.tsx +24 -0
- package/registry/ui/kbd.tsx +30 -0
- package/registry/ui/kpi-card.tsx +699 -0
- package/registry/ui/label.tsx +22 -0
- package/registry/ui/legend.tsx +3536 -0
- package/registry/ui/line-chart.tsx +3673 -0
- package/registry/ui/metric-value.tsx +62 -0
- package/registry/ui/multi-select.tsx +232 -0
- package/registry/ui/page-shell.tsx +282 -0
- package/registry/ui/pagination.tsx +140 -0
- package/registry/ui/paired-chart.tsx +3801 -0
- package/registry/ui/placeholder.tsx +36 -0
- package/registry/ui/popover.tsx +92 -0
- package/registry/ui/progress-circle.tsx +867 -0
- package/registry/ui/progress.tsx +85 -0
- package/registry/ui/radio-group.tsx +39 -0
- package/registry/ui/sample-data.tsx +86 -0
- package/registry/ui/scatter-chart.tsx +4186 -0
- package/registry/ui/scroll-area.tsx +56 -0
- package/registry/ui/searchable-select.tsx +214 -0
- package/registry/ui/section-header.tsx +43 -0
- package/registry/ui/segmented-control.tsx +76 -0
- package/registry/ui/select.tsx +255 -0
- package/registry/ui/separator.tsx +27 -0
- package/registry/ui/sheet.tsx +139 -0
- package/registry/ui/sidebar.tsx +725 -0
- package/registry/ui/skeleton.tsx +17 -0
- package/registry/ui/slider.tsx +56 -0
- package/registry/ui/sparkline.tsx +230 -0
- package/registry/ui/spinner.tsx +14 -0
- package/registry/ui/states.tsx +90 -0
- package/registry/ui/status-badge.tsx +121 -0
- package/registry/ui/switch.tsx +34 -0
- package/registry/ui/table.tsx +118 -0
- package/registry/ui/tabs.tsx +84 -0
- package/registry/ui/textarea.tsx +22 -0
- package/registry/ui/toast.tsx +250 -0
- package/registry/ui/toggle-group.tsx +91 -0
- package/registry/ui/toggle.tsx +47 -0
- package/registry/ui/tooltip.tsx +68 -0
- package/registry/ui/tracker.tsx +843 -0
- package/registry/ui/trend-indicator.tsx +74 -0
- package/registry/ui/updated-at.tsx +122 -0
- package/registry/ui/waterfall-chart.tsx +4222 -0
- package/skills/vantage-add-feature/SKILL.md +9 -7
- package/skills/vantage-design/SKILL.md +12 -2
- package/skills/vantage-design/references/format.md +1 -1
- package/skills/vantage-pitfalls/SKILL.md +5 -1
- package/skills/vantage-slide/SKILL.md +17 -0
- package/templates/AGENTS.md +22 -1
- package/templates/agents/api.md +29 -0
- package/templates/agents/csv.md +2 -1
- package/templates/agents/doc.md +4 -3
- package/templates/agents/page.md +32 -8
- package/templates/agents/slide.md +13 -1
- package/templates/agents/xlsx.md +2 -1
- package/theme.css +241 -130
- package/dist/add-DY4AXANK.js +0 -6
- package/dist/add-DY4AXANK.js.map +0 -1
- package/dist/chunk-5ZOP4SQR.js.map +0 -1
- package/dist/chunk-A2UUGASH.js.map +0 -1
- package/dist/chunk-BCE22JSM.js.map +0 -1
- package/dist/chunk-DTDVSFRY.js.map +0 -1
- package/dist/chunk-EP6JHXSR.js.map +0 -1
- package/dist/chunk-KLEIVDUV.js.map +0 -1
- package/dist/chunk-MP3QSOXL.js +0 -2232
- package/dist/chunk-MP3QSOXL.js.map +0 -1
- package/dist/chunk-PVTHHV7X.js.map +0 -1
- package/dist/chunk-PXRX5PEC.js.map +0 -1
- package/dist/chunk-QI3XQKHS.js.map +0 -1
- package/dist/chunk-RPZAALCX.js.map +0 -1
- package/dist/chunk-T3FLAGI6.js +0 -316
- package/dist/chunk-T3FLAGI6.js.map +0 -1
- package/dist/dev-FCEC3UKQ.js.map +0 -1
|
@@ -0,0 +1,303 @@
|
|
|
1
|
+
// Copied by `vantage add ui attention-list`. This file is yours to edit.
|
|
2
|
+
// Generated from @squadbase/squadkit registry/components/attention-list.tsx by `pnpm run registry` (in Vantage, edit packages/squadkit, not registry/).
|
|
3
|
+
"use client"
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* AttentionList — 期日超過・期限間近・リスク・滞留に当たる行を上に寄せ、理由と次の一手を添えて並べる要対応一覧。
|
|
7
|
+
* 判定は `judgeAttention`(純関数)が行い、部品は並べ替えと描画だけをする。区分の文言としきい値
|
|
8
|
+
* (`dueSoonDays` / `staleDays`)は props で渡す。業務の意味(請求・商談・契約など)は呼び元が決める。
|
|
9
|
+
*
|
|
10
|
+
* - 区分は 1 行に 1 つ。複数に当たるときは 期日超過 > リスク > 期限間近 > 滞留 の順で強いほうを採る。
|
|
11
|
+
* - 並びは区分の強さ、同じ区分では日数の大きい(遅れている・止まっている)行が先。日数が同じなら元の順。
|
|
12
|
+
* - 区分の色は StatusBadge の意味色(negative / warning / info)。色だけでなく区分名と日数の文で伝える。
|
|
13
|
+
* - 判定に当たらない行は既定で出さない(`includeUnflagged` で末尾に出す)。当たる行がなければ Empty を出す。
|
|
14
|
+
* - 行の項目は関数(`getDueDate` など)か列名(`dueDateKey` など)で渡す。列名は JSON で書けるので、下書きはこちらを使う。
|
|
15
|
+
* 両方あれば関数が先。どちらも無ければ、期日・動き・リスクは「無い」として判定せず、行の ID は行の順番、見出しは空になる。
|
|
16
|
+
* - 行を押すと `onRowClick(row)` を呼ぶ。渡さなければ押せない行(button ではなく li)になる。
|
|
17
|
+
*
|
|
18
|
+
* @example
|
|
19
|
+
* import { AttentionList } from "@/components/attention-list"
|
|
20
|
+
* // Vantage: import { AttentionList } from "@squadbase/vantage/components"
|
|
21
|
+
*
|
|
22
|
+
* <AttentionList
|
|
23
|
+
* rows={receivables}
|
|
24
|
+
* asOf="2026-10-23"
|
|
25
|
+
* dueSoonDays={7}
|
|
26
|
+
* getRowId={(r) => r.id}
|
|
27
|
+
* getTitle={(r) => r.customer}
|
|
28
|
+
* getDueDate={(r) => r.dueDate}
|
|
29
|
+
* getNextAction={(r) => r.nextAction}
|
|
30
|
+
* onRowClick={(r) => openDetail(r.id)}
|
|
31
|
+
* />
|
|
32
|
+
*/
|
|
33
|
+
import { differenceInCalendarDays, parseISO } from "date-fns"
|
|
34
|
+
import { useMemo, type HTMLAttributes, type ReactNode } from "react"
|
|
35
|
+
|
|
36
|
+
import { formatDate } from "@squadbase/vantage/components"
|
|
37
|
+
import { cn } from "@squadbase/vantage/ui"
|
|
38
|
+
import { Empty } from "@squadbase/vantage/ui"
|
|
39
|
+
import { StatusBadge } from "@squadbase/vantage/components"
|
|
40
|
+
|
|
41
|
+
export type AttentionKind = "overdue" | "risk" | "dueSoon" | "stale"
|
|
42
|
+
|
|
43
|
+
/** 強い順。並びと、複数に当たったときの採用に使う。 */
|
|
44
|
+
const KIND_ORDER: readonly AttentionKind[] = ["overdue", "risk", "dueSoon", "stale"]
|
|
45
|
+
|
|
46
|
+
const KIND_TONES: Record<AttentionKind, string> = {
|
|
47
|
+
overdue: "negative",
|
|
48
|
+
risk: "negative",
|
|
49
|
+
dueSoon: "warning",
|
|
50
|
+
stale: "info",
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export const DEFAULT_ATTENTION_LABELS: Record<AttentionKind, string> = {
|
|
54
|
+
overdue: "期日超過",
|
|
55
|
+
risk: "リスク",
|
|
56
|
+
dueSoon: "期限間近",
|
|
57
|
+
stale: "滞留",
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
type DateInput = Date | string | null | undefined
|
|
61
|
+
|
|
62
|
+
export interface AttentionJudgement {
|
|
63
|
+
kind: AttentionKind
|
|
64
|
+
/**
|
|
65
|
+
* overdue: 期日を過ぎた日数(1 以上)/ dueSoon: 期日までの日数(0 = 当日)/
|
|
66
|
+
* stale: 最終の動きからの日数/ risk: 0。
|
|
67
|
+
*/
|
|
68
|
+
days: number
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export interface JudgeAttentionInput {
|
|
72
|
+
/** 期日。無ければ期日の判定(超過・間近)はしない。 */
|
|
73
|
+
dueDate?: DateInput
|
|
74
|
+
/** 最後に動きがあった日。無ければ滞留の判定はしない。 */
|
|
75
|
+
lastActivityAt?: DateInput
|
|
76
|
+
/** 呼び元がリスクと判定した行。 */
|
|
77
|
+
risk?: boolean
|
|
78
|
+
/** 基準日。必ず渡す(部品の内で「今日」を決めない)。 */
|
|
79
|
+
asOf: Date | string
|
|
80
|
+
/** 期日の何日前から「期限間近」にするか。当日を含み、この日数ちょうども含む。既定 7。 */
|
|
81
|
+
dueSoonDays?: number
|
|
82
|
+
/** 何日動きがなければ「滞留」にするか。この日数ちょうどから滞留。未指定なら滞留は判定しない。 */
|
|
83
|
+
staleDays?: number
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function toDate(value: DateInput): Date | null {
|
|
87
|
+
if (value === null || value === undefined || value === "") return null
|
|
88
|
+
const date = typeof value === "string" ? parseISO(value) : value
|
|
89
|
+
return Number.isFinite(date.getTime()) ? date : null
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* 1 行が要対応かを判定する。当たらなければ `null`。日数は暦日(時刻は無視)で数える。
|
|
94
|
+
* 期日の当日は「超過」ではなく「期限間近(0 日)」。
|
|
95
|
+
*/
|
|
96
|
+
export function judgeAttention(input: JudgeAttentionInput): AttentionJudgement | null {
|
|
97
|
+
const asOf = toDate(input.asOf)
|
|
98
|
+
if (!asOf) return null
|
|
99
|
+
const dueSoonDays = input.dueSoonDays ?? 7
|
|
100
|
+
|
|
101
|
+
const due = toDate(input.dueDate)
|
|
102
|
+
if (due) {
|
|
103
|
+
const untilDue = differenceInCalendarDays(due, asOf)
|
|
104
|
+
if (untilDue < 0) return { kind: "overdue", days: -untilDue }
|
|
105
|
+
}
|
|
106
|
+
if (input.risk) return { kind: "risk", days: 0 }
|
|
107
|
+
if (due) {
|
|
108
|
+
const untilDue = differenceInCalendarDays(due, asOf)
|
|
109
|
+
if (untilDue <= dueSoonDays) return { kind: "dueSoon", days: untilDue }
|
|
110
|
+
}
|
|
111
|
+
const last = toDate(input.lastActivityAt)
|
|
112
|
+
if (last && input.staleDays !== undefined) {
|
|
113
|
+
const idle = differenceInCalendarDays(asOf, last)
|
|
114
|
+
if (idle >= input.staleDays) return { kind: "stale", days: idle }
|
|
115
|
+
}
|
|
116
|
+
return null
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/** 区分の強さ → 日数の大きい順(dueSoon は期日が近い順)。同順位は 0 を返し、元の順を保つ。 */
|
|
120
|
+
export function compareAttention(a: AttentionJudgement | null, b: AttentionJudgement | null): number {
|
|
121
|
+
if (!a || !b) return a ? -1 : b ? 1 : 0
|
|
122
|
+
const byKind = KIND_ORDER.indexOf(a.kind) - KIND_ORDER.indexOf(b.kind)
|
|
123
|
+
if (byKind !== 0) return byKind
|
|
124
|
+
return a.kind === "dueSoon" ? a.days - b.days : b.days - a.days
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function defaultReason(j: AttentionJudgement, labels: Record<AttentionKind, string>): string {
|
|
128
|
+
switch (j.kind) {
|
|
129
|
+
case "overdue":
|
|
130
|
+
return `期日を ${j.days} 日過ぎています`
|
|
131
|
+
case "dueSoon":
|
|
132
|
+
return j.days === 0 ? "期日は今日です" : `期日まであと ${j.days} 日です`
|
|
133
|
+
case "stale":
|
|
134
|
+
return `${j.days} 日間動きがありません`
|
|
135
|
+
case "risk":
|
|
136
|
+
return labels.risk
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
export interface AttentionListProps<T> {
|
|
141
|
+
rows: T[]
|
|
142
|
+
/** 基準日(`"2026-10-23"` か Date)。 */
|
|
143
|
+
asOf: Date | string
|
|
144
|
+
/** 期日の何日前から「期限間近」にするか。既定 7。 */
|
|
145
|
+
dueSoonDays?: number
|
|
146
|
+
/** この日数動きがなければ「滞留」。未指定なら滞留は判定しない。 */
|
|
147
|
+
staleDays?: number
|
|
148
|
+
/** 区分名の差し替え(例: `{ overdue: "入金遅れ" }`)。 */
|
|
149
|
+
labels?: Partial<Record<AttentionKind, string>>
|
|
150
|
+
/** 行の ID。省略時は `idKey` の列、それも無ければ行の順番。 */
|
|
151
|
+
getRowId?: (row: T) => string
|
|
152
|
+
/** 行の見出し(顧客名・案件名など)。省略時は `titleKey` の列、それも無ければ空。 */
|
|
153
|
+
getTitle?: (row: T) => ReactNode
|
|
154
|
+
getDueDate?: (row: T) => DateInput
|
|
155
|
+
getLastActivityAt?: (row: T) => DateInput
|
|
156
|
+
/** リスクと判定する行。期日・滞留より先に出す。 */
|
|
157
|
+
isRisk?: (row: T) => boolean
|
|
158
|
+
/** 行の ID の列名(JSON で書ける `getRowId`)。 */
|
|
159
|
+
idKey?: string
|
|
160
|
+
/** 見出しの列名(JSON で書ける `getTitle`)。 */
|
|
161
|
+
titleKey?: string
|
|
162
|
+
/** 期日の列名(JSON で書ける `getDueDate`)。値は `"2026-10-23"` 形式の日付か Date。 */
|
|
163
|
+
dueDateKey?: string
|
|
164
|
+
/** 最後に動きがあった日の列名(JSON で書ける `getLastActivityAt`)。 */
|
|
165
|
+
lastActivityAtKey?: string
|
|
166
|
+
/** リスクの列名(JSON で書ける `isRisk`)。値が真の行をリストにする。 */
|
|
167
|
+
riskKey?: string
|
|
168
|
+
/** 次の一手の列名(JSON で書ける `getNextAction`)。値が空の行には添えない。 */
|
|
169
|
+
nextActionKey?: string
|
|
170
|
+
/** 理由の文。省略すると区分と日数から作る。 */
|
|
171
|
+
getReason?: (row: T, judgement: AttentionJudgement) => ReactNode
|
|
172
|
+
/** 次の一手。判定に当たる行に添える。 */
|
|
173
|
+
getNextAction?: (row: T, judgement: AttentionJudgement) => ReactNode
|
|
174
|
+
/** 見出しの右に出す補足(金額・担当者など)。 */
|
|
175
|
+
getMeta?: (row: T) => ReactNode
|
|
176
|
+
/** 行を押したとき。渡すと行が button になる。 */
|
|
177
|
+
onRowClick?: (row: T) => void
|
|
178
|
+
/** 判定に当たらない行も末尾に出す。既定 false。 */
|
|
179
|
+
includeUnflagged?: boolean
|
|
180
|
+
/** 該当なしのときの見出し・説明。 */
|
|
181
|
+
emptyTitle?: string
|
|
182
|
+
emptyDescription?: string
|
|
183
|
+
className?: string
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
function cell(row: unknown, key: string | undefined): unknown {
|
|
187
|
+
if (!key || row === null || typeof row !== "object") return undefined
|
|
188
|
+
return (row as Record<string, unknown>)[key]
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
function asDateInput(value: unknown): DateInput {
|
|
192
|
+
return typeof value === "string" || value instanceof Date ? value : undefined
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
function asNode(value: unknown): ReactNode {
|
|
196
|
+
return typeof value === "string" || typeof value === "number" ? value : null
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
export function AttentionList<T>({
|
|
200
|
+
rows,
|
|
201
|
+
asOf,
|
|
202
|
+
dueSoonDays,
|
|
203
|
+
staleDays,
|
|
204
|
+
labels,
|
|
205
|
+
getRowId,
|
|
206
|
+
getTitle,
|
|
207
|
+
getDueDate,
|
|
208
|
+
getLastActivityAt,
|
|
209
|
+
isRisk,
|
|
210
|
+
idKey,
|
|
211
|
+
titleKey,
|
|
212
|
+
dueDateKey,
|
|
213
|
+
lastActivityAtKey,
|
|
214
|
+
riskKey,
|
|
215
|
+
nextActionKey,
|
|
216
|
+
getReason,
|
|
217
|
+
getNextAction,
|
|
218
|
+
getMeta,
|
|
219
|
+
onRowClick,
|
|
220
|
+
includeUnflagged = false,
|
|
221
|
+
emptyTitle = "対応が必要な項目はありません",
|
|
222
|
+
emptyDescription,
|
|
223
|
+
className,
|
|
224
|
+
...rest
|
|
225
|
+
}: AttentionListProps<T> & Omit<HTMLAttributes<HTMLUListElement>, keyof AttentionListProps<T>>) {
|
|
226
|
+
const mergedLabels = useMemo(() => ({ ...DEFAULT_ATTENTION_LABELS, ...labels }), [labels])
|
|
227
|
+
const colorMap = KIND_TONES
|
|
228
|
+
const dueOf = (row: T) => (getDueDate ? getDueDate(row) : asDateInput(cell(row, dueDateKey)))
|
|
229
|
+
const nextActionOf = (row: T, j: AttentionJudgement): ReactNode =>
|
|
230
|
+
getNextAction ? getNextAction(row, j) : asNode(cell(row, nextActionKey))
|
|
231
|
+
|
|
232
|
+
// アクセサは呼び元の判定そのものなので、rows が同じ参照でも毎回かけ直す(メモ化すると古い判定が残る)
|
|
233
|
+
const items = (rows ?? [])
|
|
234
|
+
.map((row, index) => ({
|
|
235
|
+
row,
|
|
236
|
+
index,
|
|
237
|
+
judgement: judgeAttention({
|
|
238
|
+
dueDate: dueOf(row),
|
|
239
|
+
lastActivityAt: getLastActivityAt ? getLastActivityAt(row) : asDateInput(cell(row, lastActivityAtKey)),
|
|
240
|
+
risk: isRisk ? isRisk(row) : Boolean(cell(row, riskKey)),
|
|
241
|
+
asOf,
|
|
242
|
+
dueSoonDays,
|
|
243
|
+
staleDays,
|
|
244
|
+
}),
|
|
245
|
+
}))
|
|
246
|
+
.filter((item) => includeUnflagged || item.judgement)
|
|
247
|
+
.sort((a, b) => compareAttention(a.judgement, b.judgement) || a.index - b.index)
|
|
248
|
+
|
|
249
|
+
if (items.length === 0) {
|
|
250
|
+
return <Empty title={emptyTitle} description={emptyDescription} className={className} />
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
return (
|
|
254
|
+
<ul data-slot="attention-list" className={cn("divide-y divide-border", className)} {...rest}>
|
|
255
|
+
{items.map(({ row, index, judgement }) => {
|
|
256
|
+
const due = dueOf(row)
|
|
257
|
+
const body = (
|
|
258
|
+
<>
|
|
259
|
+
<div className="flex min-w-0 flex-wrap items-center gap-x-3 gap-y-1">
|
|
260
|
+
{judgement && (
|
|
261
|
+
<StatusBadge status={judgement.kind} label={mergedLabels[judgement.kind]} colorMap={colorMap} />
|
|
262
|
+
)}
|
|
263
|
+
<span className="min-w-0 font-medium">{getTitle ? getTitle(row) : asNode(cell(row, titleKey))}</span>
|
|
264
|
+
{getMeta && <span className="text-sm tabular-nums text-muted-foreground">{getMeta(row)}</span>}
|
|
265
|
+
{due ? (
|
|
266
|
+
<span className="ml-auto text-sm tabular-nums text-muted-foreground">
|
|
267
|
+
期日 {formatDate(due)}
|
|
268
|
+
</span>
|
|
269
|
+
) : null}
|
|
270
|
+
</div>
|
|
271
|
+
{judgement && (
|
|
272
|
+
<p className="mt-1 text-sm text-muted-foreground">
|
|
273
|
+
{getReason ? getReason(row, judgement) : defaultReason(judgement, mergedLabels)}
|
|
274
|
+
</p>
|
|
275
|
+
)}
|
|
276
|
+
{judgement && (getNextAction || nextActionKey) && nextActionOf(row, judgement) ? (
|
|
277
|
+
<p className="mt-1 text-sm">
|
|
278
|
+
<span className="text-muted-foreground">次の一手: </span>
|
|
279
|
+
{nextActionOf(row, judgement)}
|
|
280
|
+
</p>
|
|
281
|
+
) : null}
|
|
282
|
+
</>
|
|
283
|
+
)
|
|
284
|
+
return (
|
|
285
|
+
<li key={getRowId ? getRowId(row) : String(cell(row, idKey) ?? index)} data-slot="attention-list-row">
|
|
286
|
+
{onRowClick ? (
|
|
287
|
+
<button
|
|
288
|
+
type="button"
|
|
289
|
+
onClick={() => onRowClick(row)}
|
|
290
|
+
className="block w-full px-3 py-3 text-left transition-colors hover:bg-muted/50 focus-visible:bg-muted/50 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
|
|
291
|
+
>
|
|
292
|
+
{body}
|
|
293
|
+
</button>
|
|
294
|
+
) : (
|
|
295
|
+
<div className="px-3 py-3">{body}</div>
|
|
296
|
+
)}
|
|
297
|
+
</li>
|
|
298
|
+
)
|
|
299
|
+
})}
|
|
300
|
+
</ul>
|
|
301
|
+
)
|
|
302
|
+
}
|
|
303
|
+
AttentionList.displayName = "AttentionList"
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
// Copied by `vantage add ui avatar`. This file is yours to edit.
|
|
2
|
+
// Generated from @squadbase/squadkit registry/ui/avatar.tsx by `pnpm run registry` (in Vantage, edit packages/squadkit, not registry/).
|
|
3
|
+
"use client"
|
|
4
|
+
|
|
5
|
+
import * as React from "react"
|
|
6
|
+
import { Avatar as AvatarPrimitive } from "@base-ui/react/avatar"
|
|
7
|
+
import { cn } from "@squadbase/vantage/ui"
|
|
8
|
+
|
|
9
|
+
function Avatar({
|
|
10
|
+
className,
|
|
11
|
+
size = "default",
|
|
12
|
+
...props
|
|
13
|
+
}: AvatarPrimitive.Root.Props & {
|
|
14
|
+
size?: "default" | "sm" | "lg"
|
|
15
|
+
}) {
|
|
16
|
+
return (
|
|
17
|
+
<AvatarPrimitive.Root
|
|
18
|
+
data-slot="avatar"
|
|
19
|
+
data-size={size}
|
|
20
|
+
className={cn(
|
|
21
|
+
"group/avatar relative flex size-8 shrink-0 rounded-full select-none after:absolute after:inset-0 after:rounded-full after:border after:border-border after:mix-blend-darken data-[size=lg]:size-10 data-[size=sm]:size-6 dark:after:mix-blend-lighten",
|
|
22
|
+
className
|
|
23
|
+
)}
|
|
24
|
+
{...props}
|
|
25
|
+
/>
|
|
26
|
+
)
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function AvatarImage({ className, ...props }: AvatarPrimitive.Image.Props) {
|
|
30
|
+
return (
|
|
31
|
+
<AvatarPrimitive.Image
|
|
32
|
+
data-slot="avatar-image"
|
|
33
|
+
className={cn(
|
|
34
|
+
"aspect-square size-full rounded-full object-cover",
|
|
35
|
+
className
|
|
36
|
+
)}
|
|
37
|
+
{...props}
|
|
38
|
+
/>
|
|
39
|
+
)
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function AvatarFallback({
|
|
43
|
+
className,
|
|
44
|
+
...props
|
|
45
|
+
}: AvatarPrimitive.Fallback.Props) {
|
|
46
|
+
return (
|
|
47
|
+
<AvatarPrimitive.Fallback
|
|
48
|
+
data-slot="avatar-fallback"
|
|
49
|
+
className={cn(
|
|
50
|
+
"flex size-full items-center justify-center rounded-full bg-muted text-sm text-muted-foreground group-data-[size=sm]/avatar:text-xs",
|
|
51
|
+
className
|
|
52
|
+
)}
|
|
53
|
+
{...props}
|
|
54
|
+
/>
|
|
55
|
+
)
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function AvatarBadge({ className, ...props }: React.ComponentProps<"span">) {
|
|
59
|
+
return (
|
|
60
|
+
<span
|
|
61
|
+
data-slot="avatar-badge"
|
|
62
|
+
className={cn(
|
|
63
|
+
"absolute right-0 bottom-0 z-10 inline-flex items-center justify-center rounded-full bg-primary text-primary-foreground bg-blend-color ring-2 ring-background select-none",
|
|
64
|
+
"group-data-[size=sm]/avatar:size-2 group-data-[size=sm]/avatar:[&>svg]:hidden",
|
|
65
|
+
"group-data-[size=default]/avatar:size-2.5 group-data-[size=default]/avatar:[&>svg]:size-2",
|
|
66
|
+
"group-data-[size=lg]/avatar:size-3 group-data-[size=lg]/avatar:[&>svg]:size-2",
|
|
67
|
+
className
|
|
68
|
+
)}
|
|
69
|
+
{...props}
|
|
70
|
+
/>
|
|
71
|
+
)
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function AvatarGroup({ className, ...props }: React.ComponentProps<"div">) {
|
|
75
|
+
return (
|
|
76
|
+
<div
|
|
77
|
+
data-slot="avatar-group"
|
|
78
|
+
className={cn(
|
|
79
|
+
"group/avatar-group flex -space-x-2 *:data-[slot=avatar]:ring-2 *:data-[slot=avatar]:ring-background",
|
|
80
|
+
className
|
|
81
|
+
)}
|
|
82
|
+
{...props}
|
|
83
|
+
/>
|
|
84
|
+
)
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function AvatarGroupCount({
|
|
88
|
+
className,
|
|
89
|
+
...props
|
|
90
|
+
}: React.ComponentProps<"div">) {
|
|
91
|
+
return (
|
|
92
|
+
<div
|
|
93
|
+
data-slot="avatar-group-count"
|
|
94
|
+
className={cn(
|
|
95
|
+
"relative flex size-8 shrink-0 items-center justify-center rounded-full bg-muted text-sm text-muted-foreground ring-2 ring-background group-has-data-[size=lg]/avatar-group:size-10 group-has-data-[size=sm]/avatar-group:size-6 [&>svg]:size-4 group-has-data-[size=lg]/avatar-group:[&>svg]:size-5 group-has-data-[size=sm]/avatar-group:[&>svg]:size-3",
|
|
96
|
+
className
|
|
97
|
+
)}
|
|
98
|
+
{...props}
|
|
99
|
+
/>
|
|
100
|
+
)
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export {
|
|
104
|
+
Avatar,
|
|
105
|
+
AvatarImage,
|
|
106
|
+
AvatarFallback,
|
|
107
|
+
AvatarGroup,
|
|
108
|
+
AvatarGroupCount,
|
|
109
|
+
AvatarBadge,
|
|
110
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
// Copied by `vantage add ui badge`. This file is yours to edit.
|
|
2
|
+
// Generated from @squadbase/squadkit registry/ui/badge.tsx by `pnpm run registry` (in Vantage, edit packages/squadkit, not registry/).
|
|
3
|
+
"use client"
|
|
4
|
+
|
|
5
|
+
import { mergeProps } from "@base-ui/react/merge-props"
|
|
6
|
+
import { useRender } from "@base-ui/react/use-render"
|
|
7
|
+
import { cva, type VariantProps } from "class-variance-authority"
|
|
8
|
+
|
|
9
|
+
import { cn } from "@squadbase/vantage/ui"
|
|
10
|
+
|
|
11
|
+
const badgeVariants = cva(
|
|
12
|
+
"group/badge inline-flex h-5 w-fit shrink-0 items-center justify-center gap-1 overflow-hidden rounded-4xl border border-transparent px-2 py-0.5 text-xs font-medium whitespace-nowrap transition-all focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 [&>svg]:pointer-events-none [&>svg]:size-3!",
|
|
13
|
+
{
|
|
14
|
+
variants: {
|
|
15
|
+
variant: {
|
|
16
|
+
default: "bg-primary text-primary-foreground [a]:hover:bg-primary/80",
|
|
17
|
+
secondary:
|
|
18
|
+
"bg-secondary text-secondary-foreground [a]:hover:bg-secondary/80",
|
|
19
|
+
destructive:
|
|
20
|
+
"bg-destructive/10 text-destructive focus-visible:ring-destructive/20 dark:bg-destructive/20 dark:focus-visible:ring-destructive/40 [a]:hover:bg-destructive/20",
|
|
21
|
+
outline:
|
|
22
|
+
"border-border text-foreground [a]:hover:bg-muted [a]:hover:text-muted-foreground",
|
|
23
|
+
ghost:
|
|
24
|
+
"hover:bg-muted hover:text-muted-foreground dark:hover:bg-muted/50",
|
|
25
|
+
link: "text-primary underline-offset-4 hover:underline",
|
|
26
|
+
},
|
|
27
|
+
},
|
|
28
|
+
defaultVariants: {
|
|
29
|
+
variant: "default",
|
|
30
|
+
},
|
|
31
|
+
}
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
function Badge({
|
|
35
|
+
className,
|
|
36
|
+
variant = "default",
|
|
37
|
+
render,
|
|
38
|
+
...props
|
|
39
|
+
}: useRender.ComponentProps<"span"> & VariantProps<typeof badgeVariants>) {
|
|
40
|
+
return useRender({
|
|
41
|
+
defaultTagName: "span",
|
|
42
|
+
props: mergeProps<"span">(
|
|
43
|
+
{
|
|
44
|
+
className: cn(badgeVariants({ variant }), className),
|
|
45
|
+
},
|
|
46
|
+
props
|
|
47
|
+
),
|
|
48
|
+
render,
|
|
49
|
+
state: {
|
|
50
|
+
slot: "badge",
|
|
51
|
+
variant,
|
|
52
|
+
},
|
|
53
|
+
})
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export { Badge, badgeVariants }
|