@squadbase/vantage-template 0.2.0-dev1.af2f601 → 0.2.0-dev1.c68cce4
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/base-template/AGENTS.md +3 -3
- package/base-template/agents/page.md +5 -4
- package/base-template/bun.lock +7 -7
- package/base-template/package.json +2 -2
- package/package.json +2 -2
- package/ui-templates/CLAUDE.md +13 -12
- package/ui-templates/chart-grid/lib/mock-data.ts +0 -3
- package/ui-templates/chart-grid/lib/types.ts +6 -6
- package/ui-templates/chart-grid/pages/index.tsx +11 -161
- package/ui-templates/chart-grid-ja/lib/mock-data.ts +0 -3
- package/ui-templates/chart-grid-ja/lib/types.ts +6 -6
- package/ui-templates/chart-grid-ja/pages/index.tsx +11 -161
- package/ui-templates/funnel/lib/types.ts +2 -2
- package/ui-templates/funnel/pages/index.tsx +10 -88
- package/ui-templates/funnel-ja/lib/types.ts +2 -2
- package/ui-templates/funnel-ja/pages/index.tsx +10 -87
- package/ui-templates/kpi-chart-advanced/lib/mock-data.ts +6 -6
- package/ui-templates/kpi-chart-advanced/lib/types.ts +6 -7
- package/ui-templates/kpi-chart-advanced/pages/index.tsx +6 -117
- package/ui-templates/kpi-chart-advanced-ja/lib/mock-data.ts +6 -6
- package/ui-templates/kpi-chart-advanced-ja/lib/types.ts +6 -7
- package/ui-templates/kpi-chart-advanced-ja/pages/index.tsx +6 -117
- package/ui-templates/kpi-chart-simple/lib/types.ts +4 -4
- package/ui-templates/kpi-chart-simple/pages/index.tsx +4 -42
- package/ui-templates/kpi-chart-simple-ja/lib/types.ts +4 -4
- package/ui-templates/kpi-chart-simple-ja/pages/index.tsx +4 -40
- package/ui-templates/table-focus/lib/types.ts +3 -3
- package/ui-templates/table-focus/pages/index.tsx +3 -45
- package/ui-templates/table-focus-ja/lib/types.ts +3 -3
- package/ui-templates/table-focus-ja/pages/index.tsx +3 -45
|
@@ -20,7 +20,9 @@ import {
|
|
|
20
20
|
DashboardCardPreset,
|
|
21
21
|
DashboardCardTitle,
|
|
22
22
|
DateRangePicker,
|
|
23
|
-
|
|
23
|
+
AreaChart,
|
|
24
|
+
BarChart,
|
|
25
|
+
DonutChart,
|
|
24
26
|
PageShell,
|
|
25
27
|
PageShellContent,
|
|
26
28
|
PageShellDescription,
|
|
@@ -32,7 +34,6 @@ import {
|
|
|
32
34
|
PageShellTitle,
|
|
33
35
|
Placeholder,
|
|
34
36
|
Sparkline,
|
|
35
|
-
type EChartsOption,
|
|
36
37
|
} from "@squadbase/vantage/components"
|
|
37
38
|
|
|
38
39
|
import { CampaignTable } from "./components/kpi-chart-advanced/campaign-table"
|
|
@@ -44,10 +45,7 @@ import {
|
|
|
44
45
|
trendSeries,
|
|
45
46
|
} from "./components/kpi-chart-advanced/mock-data"
|
|
46
47
|
import type {
|
|
47
|
-
BreakdownSlice,
|
|
48
|
-
ComparisonPoint,
|
|
49
48
|
DashboardFilters,
|
|
50
|
-
TrendPoint,
|
|
51
49
|
} from "./components/kpi-chart-advanced/types"
|
|
52
50
|
|
|
53
51
|
export const page = definePage({
|
|
@@ -56,115 +54,6 @@ export const page = definePage({
|
|
|
56
54
|
"Hero KPI (1 large + 4 small in 2×2 grid), bar+line comparison, donut breakdown, daily trend, and a detail table. KPI-led analytics dashboard layout.",
|
|
57
55
|
})
|
|
58
56
|
|
|
59
|
-
// ── chart helpers ───────────────────────────────────────────────────────────
|
|
60
|
-
|
|
61
|
-
function getBaseGrid() {
|
|
62
|
-
return { left: "3%", right: "4%", bottom: "12%", containLabel: true }
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
function getDualAxisGrid() {
|
|
66
|
-
return { left: "3%", right: "6%", bottom: "12%", containLabel: true }
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
function formatNumber(n: number): string {
|
|
70
|
-
const sign = n < 0 ? "-" : ""
|
|
71
|
-
const abs = Math.abs(n)
|
|
72
|
-
if (abs >= 1_000_000) return `${sign}${(abs / 1_000_000).toFixed(1)}M`
|
|
73
|
-
if (abs >= 1_000) return `${sign}${(abs / 1_000).toFixed(1)}K`
|
|
74
|
-
return n.toLocaleString("en-US")
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
// ── chart options ────────────────────────────────────────────────────────────
|
|
78
|
-
|
|
79
|
-
function comparisonOption(data: ComparisonPoint[]): EChartsOption {
|
|
80
|
-
return {
|
|
81
|
-
tooltip: { trigger: "axis" },
|
|
82
|
-
legend: { bottom: 0 },
|
|
83
|
-
grid: getDualAxisGrid(),
|
|
84
|
-
xAxis: {
|
|
85
|
-
type: "category",
|
|
86
|
-
data: data.map((d) => d.period),
|
|
87
|
-
},
|
|
88
|
-
yAxis: [
|
|
89
|
-
{
|
|
90
|
-
type: "value",
|
|
91
|
-
axisLabel: { formatter: (v: number) => formatNumber(v) },
|
|
92
|
-
},
|
|
93
|
-
{
|
|
94
|
-
type: "value",
|
|
95
|
-
axisLabel: { formatter: (v: number) => `${v.toFixed(0)}%` },
|
|
96
|
-
splitLine: { show: false },
|
|
97
|
-
},
|
|
98
|
-
],
|
|
99
|
-
series: [
|
|
100
|
-
{
|
|
101
|
-
name: "Series A",
|
|
102
|
-
type: "bar",
|
|
103
|
-
data: data.map((d) => d.current),
|
|
104
|
-
barMaxWidth: 28,
|
|
105
|
-
},
|
|
106
|
-
{
|
|
107
|
-
name: "Series B",
|
|
108
|
-
type: "bar",
|
|
109
|
-
data: data.map((d) => d.previous),
|
|
110
|
-
barMaxWidth: 28,
|
|
111
|
-
},
|
|
112
|
-
{
|
|
113
|
-
name: "Series C",
|
|
114
|
-
type: "line",
|
|
115
|
-
yAxisIndex: 1,
|
|
116
|
-
smooth: true,
|
|
117
|
-
data: data.map((d) => d.growth),
|
|
118
|
-
},
|
|
119
|
-
],
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
function breakdownOption(data: BreakdownSlice[]): EChartsOption {
|
|
124
|
-
return {
|
|
125
|
-
tooltip: { trigger: "item" },
|
|
126
|
-
legend: { bottom: 0 },
|
|
127
|
-
series: [
|
|
128
|
-
{
|
|
129
|
-
type: "pie",
|
|
130
|
-
radius: ["52%", "78%"],
|
|
131
|
-
avoidLabelOverlap: true,
|
|
132
|
-
itemStyle: { borderRadius: 6, borderColor: "transparent", borderWidth: 2 },
|
|
133
|
-
label: { show: false },
|
|
134
|
-
data: data.map((d) => ({ name: d.segment, value: d.value })),
|
|
135
|
-
},
|
|
136
|
-
],
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
function trendOption(data: TrendPoint[]): EChartsOption {
|
|
141
|
-
return {
|
|
142
|
-
tooltip: { trigger: "axis" },
|
|
143
|
-
legend: { bottom: 0 },
|
|
144
|
-
grid: getBaseGrid(),
|
|
145
|
-
xAxis: {
|
|
146
|
-
type: "category",
|
|
147
|
-
data: data.map((d) => d.date),
|
|
148
|
-
boundaryGap: false,
|
|
149
|
-
axisLabel: { formatter: (v: string) => v.slice(5) },
|
|
150
|
-
},
|
|
151
|
-
yAxis: {
|
|
152
|
-
type: "value",
|
|
153
|
-
axisLabel: { formatter: (v: number) => formatNumber(v) },
|
|
154
|
-
},
|
|
155
|
-
series: [
|
|
156
|
-
{
|
|
157
|
-
name: "Series A",
|
|
158
|
-
type: "line",
|
|
159
|
-
smooth: true,
|
|
160
|
-
showSymbol: false,
|
|
161
|
-
data: data.map((d) => d.value),
|
|
162
|
-
areaStyle: { opacity: 0.18 },
|
|
163
|
-
},
|
|
164
|
-
],
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
|
|
168
57
|
// ── page ─────────────────────────────────────────────────────────────────────
|
|
169
58
|
|
|
170
59
|
const today = new Date()
|
|
@@ -315,11 +204,11 @@ export default function HomePage() {
|
|
|
315
204
|
title="Comparison"
|
|
316
205
|
description="Current vs previous period"
|
|
317
206
|
>
|
|
318
|
-
<
|
|
207
|
+
<BarChart data={comparisonSeries} index="period" categories={["current", "previous"]} labels={{ current: "Series A", previous: "Series B" }} height="320px" />
|
|
319
208
|
</DashboardCardPreset>
|
|
320
209
|
</div>
|
|
321
210
|
<DashboardCardPreset title="Breakdown" description="Share by segment">
|
|
322
|
-
<
|
|
211
|
+
<DonutChart data={breakdownSlices} index="segment" categories={["value"]} height="320px" />
|
|
323
212
|
</DashboardCardPreset>
|
|
324
213
|
</div>
|
|
325
214
|
|
|
@@ -327,7 +216,7 @@ export default function HomePage() {
|
|
|
327
216
|
title="Trend"
|
|
328
217
|
description="Values over the selected period"
|
|
329
218
|
>
|
|
330
|
-
<
|
|
219
|
+
<AreaChart data={trendSeries} index="date" categories={["value"]} labels={{ value: "Series A" }} height="280px" />
|
|
331
220
|
</DashboardCardPreset>
|
|
332
221
|
|
|
333
222
|
<DashboardCardPreset title="Campaigns" description="Performance by campaign">
|
|
@@ -40,12 +40,12 @@ export const trendSeries: TrendPoint[] = [
|
|
|
40
40
|
]
|
|
41
41
|
|
|
42
42
|
export const comparisonSeries: ComparisonPoint[] = [
|
|
43
|
-
{ period: "2023-10", current: 308820, previous: 285196
|
|
44
|
-
{ period: "2023-11", current: 360871, previous: 300245
|
|
45
|
-
{ period: "2023-12", current: 369038, previous: 344274
|
|
46
|
-
{ period: "2024-01", current: 402455, previous: 338646
|
|
47
|
-
{ period: "2024-02", current: 443741, previous: 396096
|
|
48
|
-
{ period: "2024-03", current: 445883, previous: 387812
|
|
43
|
+
{ period: "2023-10", current: 308820, previous: 285196 },
|
|
44
|
+
{ period: "2023-11", current: 360871, previous: 300245 },
|
|
45
|
+
{ period: "2023-12", current: 369038, previous: 344274 },
|
|
46
|
+
{ period: "2024-01", current: 402455, previous: 338646 },
|
|
47
|
+
{ period: "2024-02", current: 443741, previous: 396096 },
|
|
48
|
+
{ period: "2024-03", current: 445883, previous: 387812 },
|
|
49
49
|
]
|
|
50
50
|
|
|
51
51
|
export const breakdownSlices: BreakdownSlice[] = [
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export
|
|
1
|
+
export type KpiItem = {
|
|
2
2
|
id: "revenue" | "users" | "conversion" | "aov" | "sessions"
|
|
3
3
|
value: string
|
|
4
4
|
change: number
|
|
@@ -7,24 +7,23 @@ export interface KpiItem {
|
|
|
7
7
|
sparklineData: number[]
|
|
8
8
|
}
|
|
9
9
|
|
|
10
|
-
export
|
|
10
|
+
export type ComparisonPoint = {
|
|
11
11
|
period: string
|
|
12
12
|
current: number
|
|
13
13
|
previous: number
|
|
14
|
-
growth: number
|
|
15
14
|
}
|
|
16
15
|
|
|
17
|
-
export
|
|
16
|
+
export type BreakdownSlice = {
|
|
18
17
|
segment: string
|
|
19
18
|
value: number
|
|
20
19
|
}
|
|
21
20
|
|
|
22
|
-
export
|
|
21
|
+
export type TrendPoint = {
|
|
23
22
|
date: string
|
|
24
23
|
value: number
|
|
25
24
|
}
|
|
26
25
|
|
|
27
|
-
export
|
|
26
|
+
export type CampaignRow = {
|
|
28
27
|
id: string
|
|
29
28
|
name: string
|
|
30
29
|
channel: string
|
|
@@ -33,6 +32,6 @@ export interface CampaignRow {
|
|
|
33
32
|
roi: number
|
|
34
33
|
}
|
|
35
34
|
|
|
36
|
-
export
|
|
35
|
+
export type DashboardFilters = {
|
|
37
36
|
dateRange: { from: Date | undefined; to: Date | undefined }
|
|
38
37
|
}
|
|
@@ -20,7 +20,9 @@ import {
|
|
|
20
20
|
DashboardCardPreset,
|
|
21
21
|
DashboardCardTitle,
|
|
22
22
|
DateRangePicker,
|
|
23
|
-
|
|
23
|
+
AreaChart,
|
|
24
|
+
BarChart,
|
|
25
|
+
DonutChart,
|
|
24
26
|
PageShell,
|
|
25
27
|
PageShellContent,
|
|
26
28
|
PageShellDescription,
|
|
@@ -32,7 +34,6 @@ import {
|
|
|
32
34
|
PageShellTitle,
|
|
33
35
|
Placeholder,
|
|
34
36
|
Sparkline,
|
|
35
|
-
type EChartsOption,
|
|
36
37
|
} from "@squadbase/vantage/components"
|
|
37
38
|
|
|
38
39
|
import { CampaignTable } from "./components/kpi-chart-advanced/campaign-table"
|
|
@@ -44,10 +45,7 @@ import {
|
|
|
44
45
|
trendSeries,
|
|
45
46
|
} from "./components/kpi-chart-advanced/mock-data"
|
|
46
47
|
import type {
|
|
47
|
-
BreakdownSlice,
|
|
48
|
-
ComparisonPoint,
|
|
49
48
|
DashboardFilters,
|
|
50
|
-
TrendPoint,
|
|
51
49
|
} from "./components/kpi-chart-advanced/types"
|
|
52
50
|
|
|
53
51
|
export const page = definePage({
|
|
@@ -56,115 +54,6 @@ export const page = definePage({
|
|
|
56
54
|
"ヒーローKPI (大1+小4の2×2) ・棒+折れ線比較チャート・ドーナツ・日次トレンド・明細テーブルを搭載したKPI主役のダッシュボード構成",
|
|
57
55
|
})
|
|
58
56
|
|
|
59
|
-
// ── chart helpers ───────────────────────────────────────────────────────────
|
|
60
|
-
|
|
61
|
-
function getBaseGrid() {
|
|
62
|
-
return { left: "3%", right: "4%", bottom: "12%", containLabel: true }
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
function getDualAxisGrid() {
|
|
66
|
-
return { left: "3%", right: "6%", bottom: "12%", containLabel: true }
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
function formatNumber(n: number): string {
|
|
70
|
-
const sign = n < 0 ? "-" : ""
|
|
71
|
-
const abs = Math.abs(n)
|
|
72
|
-
if (abs >= 100_000_000) return `${sign}${(abs / 100_000_000).toFixed(1)}億`
|
|
73
|
-
if (abs >= 10_000) return `${sign}${(abs / 10_000).toFixed(0)}万`
|
|
74
|
-
return n.toLocaleString("ja-JP")
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
// ── chart options ────────────────────────────────────────────────────────────
|
|
78
|
-
|
|
79
|
-
function comparisonOption(data: ComparisonPoint[]): EChartsOption {
|
|
80
|
-
return {
|
|
81
|
-
tooltip: { trigger: "axis" },
|
|
82
|
-
legend: { bottom: 0 },
|
|
83
|
-
grid: getDualAxisGrid(),
|
|
84
|
-
xAxis: {
|
|
85
|
-
type: "category",
|
|
86
|
-
data: data.map((d) => d.period),
|
|
87
|
-
},
|
|
88
|
-
yAxis: [
|
|
89
|
-
{
|
|
90
|
-
type: "value",
|
|
91
|
-
axisLabel: { formatter: (v: number) => formatNumber(v) },
|
|
92
|
-
},
|
|
93
|
-
{
|
|
94
|
-
type: "value",
|
|
95
|
-
axisLabel: { formatter: (v: number) => `${v.toFixed(0)}%` },
|
|
96
|
-
splitLine: { show: false },
|
|
97
|
-
},
|
|
98
|
-
],
|
|
99
|
-
series: [
|
|
100
|
-
{
|
|
101
|
-
name: "系列A",
|
|
102
|
-
type: "bar",
|
|
103
|
-
data: data.map((d) => d.current),
|
|
104
|
-
barMaxWidth: 28,
|
|
105
|
-
},
|
|
106
|
-
{
|
|
107
|
-
name: "系列B",
|
|
108
|
-
type: "bar",
|
|
109
|
-
data: data.map((d) => d.previous),
|
|
110
|
-
barMaxWidth: 28,
|
|
111
|
-
},
|
|
112
|
-
{
|
|
113
|
-
name: "系列C",
|
|
114
|
-
type: "line",
|
|
115
|
-
yAxisIndex: 1,
|
|
116
|
-
smooth: true,
|
|
117
|
-
data: data.map((d) => d.growth),
|
|
118
|
-
},
|
|
119
|
-
],
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
function breakdownOption(data: BreakdownSlice[]): EChartsOption {
|
|
124
|
-
return {
|
|
125
|
-
tooltip: { trigger: "item" },
|
|
126
|
-
legend: { bottom: 0 },
|
|
127
|
-
series: [
|
|
128
|
-
{
|
|
129
|
-
type: "pie",
|
|
130
|
-
radius: ["52%", "78%"],
|
|
131
|
-
avoidLabelOverlap: true,
|
|
132
|
-
itemStyle: { borderRadius: 6, borderColor: "transparent", borderWidth: 2 },
|
|
133
|
-
label: { show: false },
|
|
134
|
-
data: data.map((d) => ({ name: d.segment, value: d.value })),
|
|
135
|
-
},
|
|
136
|
-
],
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
function trendOption(data: TrendPoint[]): EChartsOption {
|
|
141
|
-
return {
|
|
142
|
-
tooltip: { trigger: "axis" },
|
|
143
|
-
legend: { bottom: 0 },
|
|
144
|
-
grid: getBaseGrid(),
|
|
145
|
-
xAxis: {
|
|
146
|
-
type: "category",
|
|
147
|
-
data: data.map((d) => d.date),
|
|
148
|
-
boundaryGap: false,
|
|
149
|
-
axisLabel: { formatter: (v: string) => v.slice(5) },
|
|
150
|
-
},
|
|
151
|
-
yAxis: {
|
|
152
|
-
type: "value",
|
|
153
|
-
axisLabel: { formatter: (v: number) => formatNumber(v) },
|
|
154
|
-
},
|
|
155
|
-
series: [
|
|
156
|
-
{
|
|
157
|
-
name: "系列A",
|
|
158
|
-
type: "line",
|
|
159
|
-
smooth: true,
|
|
160
|
-
showSymbol: false,
|
|
161
|
-
data: data.map((d) => d.value),
|
|
162
|
-
areaStyle: { opacity: 0.18 },
|
|
163
|
-
},
|
|
164
|
-
],
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
|
|
168
57
|
// ── page ─────────────────────────────────────────────────────────────────────
|
|
169
58
|
|
|
170
59
|
const today = new Date()
|
|
@@ -312,16 +201,16 @@ export default function HomePage() {
|
|
|
312
201
|
<div className="grid gap-4 lg:grid-cols-3">
|
|
313
202
|
<div className="lg:col-span-2">
|
|
314
203
|
<DashboardCardPreset title="比較" description="今期 vs 前期">
|
|
315
|
-
<
|
|
204
|
+
<BarChart data={comparisonSeries} index="period" categories={["current", "previous"]} labels={{ current: "系列A", previous: "系列B" }} height="320px" />
|
|
316
205
|
</DashboardCardPreset>
|
|
317
206
|
</div>
|
|
318
207
|
<DashboardCardPreset title="内訳" description="セグメント別シェア">
|
|
319
|
-
<
|
|
208
|
+
<DonutChart data={breakdownSlices} index="segment" categories={["value"]} height="320px" />
|
|
320
209
|
</DashboardCardPreset>
|
|
321
210
|
</div>
|
|
322
211
|
|
|
323
212
|
<DashboardCardPreset title="トレンド" description="選択期間の値">
|
|
324
|
-
<
|
|
213
|
+
<AreaChart data={trendSeries} index="date" categories={["value"]} labels={{ value: "系列A" }} height="280px" />
|
|
325
214
|
</DashboardCardPreset>
|
|
326
215
|
|
|
327
216
|
<DashboardCardPreset title="キャンペーン" description="キャンペーン別パフォーマンス">
|
|
@@ -11,7 +11,7 @@ export type RangeKey = "7d" | "30d" | "90d"
|
|
|
11
11
|
export type KpiId = "total-revenue" | "active-users" | "conversion-rate" | "aov"
|
|
12
12
|
|
|
13
13
|
/** Raw numbers only — labels and formatting belong to the page. */
|
|
14
|
-
export
|
|
14
|
+
export type KpiItem = {
|
|
15
15
|
id: KpiId
|
|
16
16
|
value: number
|
|
17
17
|
/** Percent change against the preceding window of the same length. */
|
|
@@ -19,14 +19,14 @@ export interface KpiItem {
|
|
|
19
19
|
sparklineData: number[]
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
-
export
|
|
22
|
+
export type TrendPoint = {
|
|
23
23
|
date: string
|
|
24
24
|
revenue: number
|
|
25
25
|
orders: number
|
|
26
26
|
visitors: number
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
-
export
|
|
29
|
+
export type TopItemRow = {
|
|
30
30
|
id: string
|
|
31
31
|
name: string
|
|
32
32
|
category: string
|
|
@@ -36,7 +36,7 @@ export interface TopItemRow {
|
|
|
36
36
|
}
|
|
37
37
|
|
|
38
38
|
/** The payload `GET /api/kpi-summary` returns. */
|
|
39
|
-
export
|
|
39
|
+
export type KpiSummary = {
|
|
40
40
|
range: RangeKey
|
|
41
41
|
kpis: KpiItem[]
|
|
42
42
|
trend: TrendPoint[]
|
|
@@ -7,7 +7,7 @@ import {
|
|
|
7
7
|
DashboardCardHeader,
|
|
8
8
|
DashboardCardPreset,
|
|
9
9
|
DashboardCardTitle,
|
|
10
|
-
|
|
10
|
+
AreaChart,
|
|
11
11
|
PageShell,
|
|
12
12
|
PageShellContent,
|
|
13
13
|
PageShellDescription,
|
|
@@ -19,13 +19,13 @@ import {
|
|
|
19
19
|
SegmentedControl,
|
|
20
20
|
Sparkline,
|
|
21
21
|
} from "@squadbase/vantage/components"
|
|
22
|
-
import type {
|
|
22
|
+
import type { SegmentOption } from "@squadbase/vantage/components"
|
|
23
23
|
import { Button, ErrorState, Loading } from "@squadbase/vantage/ui"
|
|
24
24
|
import { useApiQuery } from "@squadbase/vantage/query"
|
|
25
25
|
import { useSearchParam } from "@squadbase/vantage/router"
|
|
26
26
|
|
|
27
27
|
import { TopItemsTable } from "./components/kpi-chart-simple/top-items-table"
|
|
28
|
-
import type { KpiId, KpiSummary
|
|
28
|
+
import type { KpiId, KpiSummary } from "./lib/kpi-chart-simple/types"
|
|
29
29
|
|
|
30
30
|
export const page = definePage({
|
|
31
31
|
title: "KPI + Chart (Simple)",
|
|
@@ -35,14 +35,6 @@ export const page = definePage({
|
|
|
35
35
|
|
|
36
36
|
// ── formatting ───────────────────────────────────────────────────────────────
|
|
37
37
|
|
|
38
|
-
function formatNumber(n: number): string {
|
|
39
|
-
const sign = n < 0 ? "-" : ""
|
|
40
|
-
const abs = Math.abs(n)
|
|
41
|
-
if (abs >= 1_000_000) return `${sign}${(abs / 1_000_000).toFixed(1)}M`
|
|
42
|
-
if (abs >= 1_000) return `${sign}${(abs / 1_000).toFixed(1)}K`
|
|
43
|
-
return n.toLocaleString("en-US")
|
|
44
|
-
}
|
|
45
|
-
|
|
46
38
|
function formatCurrency(n: number): string {
|
|
47
39
|
const sign = n < 0 ? "-" : ""
|
|
48
40
|
const abs = Math.abs(n)
|
|
@@ -51,36 +43,6 @@ function formatCurrency(n: number): string {
|
|
|
51
43
|
return `${sign}$${abs.toFixed(0)}`
|
|
52
44
|
}
|
|
53
45
|
|
|
54
|
-
// ── chart options ────────────────────────────────────────────────────────────
|
|
55
|
-
|
|
56
|
-
function trendOption(data: TrendPoint[]): EChartsOption {
|
|
57
|
-
return {
|
|
58
|
-
tooltip: { trigger: "axis" },
|
|
59
|
-
legend: { bottom: 0 },
|
|
60
|
-
grid: { left: "3%", right: "4%", bottom: "10%", containLabel: true },
|
|
61
|
-
xAxis: {
|
|
62
|
-
type: "category",
|
|
63
|
-
data: data.map((d) => d.date),
|
|
64
|
-
boundaryGap: false,
|
|
65
|
-
axisLabel: { formatter: (value: string) => value.slice(5) },
|
|
66
|
-
},
|
|
67
|
-
yAxis: {
|
|
68
|
-
type: "value",
|
|
69
|
-
axisLabel: { formatter: (v: number) => formatNumber(v) },
|
|
70
|
-
},
|
|
71
|
-
series: [
|
|
72
|
-
{
|
|
73
|
-
name: "Series A",
|
|
74
|
-
type: "line",
|
|
75
|
-
smooth: true,
|
|
76
|
-
showSymbol: false,
|
|
77
|
-
data: data.map((d) => d.revenue),
|
|
78
|
-
areaStyle: { opacity: 0.18 },
|
|
79
|
-
},
|
|
80
|
-
],
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
|
|
84
46
|
// ── page ─────────────────────────────────────────────────────────────────────
|
|
85
47
|
|
|
86
48
|
/** Display labels live here, not in the payload the API returns. */
|
|
@@ -189,7 +151,7 @@ function DashboardBody({ data }: { data: KpiSummary }) {
|
|
|
189
151
|
</div>
|
|
190
152
|
|
|
191
153
|
<DashboardCardPreset data-sq-ref="trend" title="Trend" description="Values over the selected period">
|
|
192
|
-
<
|
|
154
|
+
<AreaChart data={data.trend} index="date" categories={["revenue"]} labels={{ revenue: "Series A" }} height="320px" />
|
|
193
155
|
</DashboardCardPreset>
|
|
194
156
|
|
|
195
157
|
<DashboardCardPreset data-sq-ref="top-items" title="Top items" description="Ranked by value">
|
|
@@ -11,7 +11,7 @@ export type RangeKey = "7d" | "30d" | "90d"
|
|
|
11
11
|
export type KpiId = "total-revenue" | "active-users" | "conversion-rate" | "aov"
|
|
12
12
|
|
|
13
13
|
/** Raw numbers only — labels and formatting belong to the page. */
|
|
14
|
-
export
|
|
14
|
+
export type KpiItem = {
|
|
15
15
|
id: KpiId
|
|
16
16
|
value: number
|
|
17
17
|
/** Percent change against the preceding window of the same length. */
|
|
@@ -19,14 +19,14 @@ export interface KpiItem {
|
|
|
19
19
|
sparklineData: number[]
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
-
export
|
|
22
|
+
export type TrendPoint = {
|
|
23
23
|
date: string
|
|
24
24
|
revenue: number
|
|
25
25
|
orders: number
|
|
26
26
|
visitors: number
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
-
export
|
|
29
|
+
export type TopItemRow = {
|
|
30
30
|
id: string
|
|
31
31
|
name: string
|
|
32
32
|
category: string
|
|
@@ -36,7 +36,7 @@ export interface TopItemRow {
|
|
|
36
36
|
}
|
|
37
37
|
|
|
38
38
|
/** The payload `GET /api/kpi-summary` returns. */
|
|
39
|
-
export
|
|
39
|
+
export type KpiSummary = {
|
|
40
40
|
range: RangeKey
|
|
41
41
|
kpis: KpiItem[]
|
|
42
42
|
trend: TrendPoint[]
|
|
@@ -7,7 +7,7 @@ import {
|
|
|
7
7
|
DashboardCardHeader,
|
|
8
8
|
DashboardCardPreset,
|
|
9
9
|
DashboardCardTitle,
|
|
10
|
-
|
|
10
|
+
AreaChart,
|
|
11
11
|
PageShell,
|
|
12
12
|
PageShellContent,
|
|
13
13
|
PageShellDescription,
|
|
@@ -19,13 +19,13 @@ import {
|
|
|
19
19
|
SegmentedControl,
|
|
20
20
|
Sparkline,
|
|
21
21
|
} from "@squadbase/vantage/components"
|
|
22
|
-
import type {
|
|
22
|
+
import type { SegmentOption } from "@squadbase/vantage/components"
|
|
23
23
|
import { Button, ErrorState, Loading } from "@squadbase/vantage/ui"
|
|
24
24
|
import { useApiQuery } from "@squadbase/vantage/query"
|
|
25
25
|
import { useSearchParam } from "@squadbase/vantage/router"
|
|
26
26
|
|
|
27
27
|
import { TopItemsTable } from "./components/kpi-chart-simple/top-items-table"
|
|
28
|
-
import type { KpiId, KpiSummary
|
|
28
|
+
import type { KpiId, KpiSummary } from "./lib/kpi-chart-simple/types"
|
|
29
29
|
|
|
30
30
|
export const page = definePage({
|
|
31
31
|
title: "KPI + チャート (シンプル)",
|
|
@@ -35,14 +35,6 @@ export const page = definePage({
|
|
|
35
35
|
|
|
36
36
|
// ── 表示フォーマット ─────────────────────────────────────────────────────────
|
|
37
37
|
|
|
38
|
-
function formatNumber(n: number): string {
|
|
39
|
-
const sign = n < 0 ? "-" : ""
|
|
40
|
-
const abs = Math.abs(n)
|
|
41
|
-
if (abs >= 1_000_000) return `${sign}${(abs / 1_000_000).toFixed(1)}M`
|
|
42
|
-
if (abs >= 1_000) return `${sign}${(abs / 1_000).toFixed(1)}K`
|
|
43
|
-
return n.toLocaleString("ja-JP")
|
|
44
|
-
}
|
|
45
|
-
|
|
46
38
|
function formatCurrency(n: number): string {
|
|
47
39
|
const sign = n < 0 ? "-" : ""
|
|
48
40
|
const abs = Math.abs(n)
|
|
@@ -53,34 +45,6 @@ function formatCurrency(n: number): string {
|
|
|
53
45
|
|
|
54
46
|
// ── チャートオプション ───────────────────────────────────────────────────────
|
|
55
47
|
|
|
56
|
-
function trendOption(data: TrendPoint[]): EChartsOption {
|
|
57
|
-
return {
|
|
58
|
-
tooltip: { trigger: "axis" },
|
|
59
|
-
legend: { bottom: 0 },
|
|
60
|
-
grid: { left: "3%", right: "4%", bottom: "10%", containLabel: true },
|
|
61
|
-
xAxis: {
|
|
62
|
-
type: "category",
|
|
63
|
-
data: data.map((d) => d.date),
|
|
64
|
-
boundaryGap: false,
|
|
65
|
-
axisLabel: { formatter: (value: string) => value.slice(5) },
|
|
66
|
-
},
|
|
67
|
-
yAxis: {
|
|
68
|
-
type: "value",
|
|
69
|
-
axisLabel: { formatter: (v: number) => formatNumber(v) },
|
|
70
|
-
},
|
|
71
|
-
series: [
|
|
72
|
-
{
|
|
73
|
-
name: "系列A",
|
|
74
|
-
type: "line",
|
|
75
|
-
smooth: true,
|
|
76
|
-
showSymbol: false,
|
|
77
|
-
data: data.map((d) => d.revenue),
|
|
78
|
-
areaStyle: { opacity: 0.18 },
|
|
79
|
-
},
|
|
80
|
-
],
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
|
|
84
48
|
// ── ページ ───────────────────────────────────────────────────────────────────
|
|
85
49
|
|
|
86
50
|
/** 表示ラベルは API のレスポンスではなく描画側に置く。 */
|
|
@@ -189,7 +153,7 @@ function DashboardBody({ data }: { data: KpiSummary }) {
|
|
|
189
153
|
</div>
|
|
190
154
|
|
|
191
155
|
<DashboardCardPreset data-sq-ref="trend" title="トレンド" description="選択期間の値">
|
|
192
|
-
<
|
|
156
|
+
<AreaChart data={data.trend} index="date" categories={["revenue"]} labels={{ revenue: "系列A" }} height="320px" />
|
|
193
157
|
</DashboardCardPreset>
|
|
194
158
|
|
|
195
159
|
<DashboardCardPreset data-sq-ref="top-items" title="上位項目" description="値で並べ替え">
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export
|
|
1
|
+
export type DetailRow = {
|
|
2
2
|
id: string
|
|
3
3
|
name: string
|
|
4
4
|
owner: string
|
|
@@ -10,7 +10,7 @@ export interface DetailRow {
|
|
|
10
10
|
updated: string
|
|
11
11
|
}
|
|
12
12
|
|
|
13
|
-
export
|
|
13
|
+
export type SummaryRow = {
|
|
14
14
|
segment: string
|
|
15
15
|
count: number
|
|
16
16
|
revenue: number
|
|
@@ -18,7 +18,7 @@ export interface SummaryRow {
|
|
|
18
18
|
share: number
|
|
19
19
|
}
|
|
20
20
|
|
|
21
|
-
export
|
|
21
|
+
export type TrendPoint = {
|
|
22
22
|
date: string
|
|
23
23
|
value: number
|
|
24
24
|
}
|