@squadbase/vantage-template 0.1.1 → 0.2.0-dev1.0c136ab
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/README.md +19 -9
- package/base-template/.squadbase/skills/vantage-add-feature/SKILL.md +14 -7
- package/base-template/.squadbase/skills/vantage-app/SKILL.md +4 -0
- package/base-template/.squadbase/skills/vantage-design/SKILL.md +62 -52
- package/base-template/.squadbase/skills/vantage-design/references/extract.md +26 -27
- package/base-template/.squadbase/skills/vantage-design/references/format.md +12 -172
- package/base-template/.squadbase/skills/vantage-design/references/presets.md +50 -110
- package/base-template/.squadbase/skills/vantage-pitfalls/SKILL.md +50 -1
- package/base-template/.squadbase/skills/vantage-slide/SKILL.md +299 -0
- package/base-template/.squadbase/skills/vantage-slide/references/morph.md +102 -0
- package/base-template/.squadbase/skills/vantage-slide/references/steps.md +57 -0
- package/base-template/.squadbase/skills/vantage-slide/references/tokens.md +87 -0
- package/base-template/.squadbase/skills/vantage-slide/references/transitions.md +196 -0
- package/base-template/AGENTS.md +225 -18
- package/base-template/agents/api.md +36 -0
- package/base-template/agents/csv.md +2 -1
- package/base-template/agents/doc.md +52 -3
- package/base-template/agents/page.md +92 -10
- package/base-template/agents/slide.md +90 -4
- package/base-template/agents/xlsx.md +2 -1
- package/base-template/bun.lock +917 -0
- package/base-template/bunfig.toml +5 -0
- package/base-template/package.json +6 -6
- package/base-template/squadbase.yml +1 -0
- package/base-template/src/_layout.tsx +16 -7
- package/base-template/src/components/build-recovery.tsx +37 -1
- package/base-template/src/components/user-card.tsx +11 -11
- package/base-template/src/index.tsx +8 -3
- package/base-template/src/styles.css +108 -12
- package/dist/index.js +971 -6
- package/package.json +20 -17
- package/ui-templates/CLAUDE.md +14 -11
- 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 +51 -89
- package/ui-templates/kpi-chart-simple-ja/lib/types.ts +4 -4
- package/ui-templates/kpi-chart-simple-ja/pages/index.tsx +51 -87
- 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
- package/base-template/DESIGN.md +0 -108
- package/base-template/package-lock.json +0 -5841
|
@@ -10,7 +10,7 @@ import {
|
|
|
10
10
|
DashboardCardContent,
|
|
11
11
|
DashboardCardPreset,
|
|
12
12
|
DateRangePicker,
|
|
13
|
-
|
|
13
|
+
FunnelSteps,
|
|
14
14
|
PageShell,
|
|
15
15
|
PageShellHeader,
|
|
16
16
|
PageShellHeading,
|
|
@@ -20,12 +20,10 @@ import {
|
|
|
20
20
|
PageShellContent,
|
|
21
21
|
Placeholder,
|
|
22
22
|
} from "@squadbase/vantage/components"
|
|
23
|
-
import type { DateRange
|
|
24
|
-
import { ToggleGroup, ToggleGroupItem } from "@squadbase/vantage/ui"
|
|
23
|
+
import type { DateRange } from "@squadbase/vantage/components"
|
|
25
24
|
|
|
26
25
|
import { StageTable } from "./components/funnel/stage-table"
|
|
27
26
|
import { funnelStages, stageRows } from "./components/funnel/mock-data"
|
|
28
|
-
import type { FunnelStage } from "./components/funnel/types"
|
|
29
27
|
|
|
30
28
|
export const page = definePage({
|
|
31
29
|
title: "Funnel",
|
|
@@ -36,65 +34,7 @@ export const page = definePage({
|
|
|
36
34
|
const today = new Date()
|
|
37
35
|
|
|
38
36
|
const STAGE_NAMES = ["Stage 1", "Stage 2", "Stage 3", "Stage 4", "Stage 5"]
|
|
39
|
-
|
|
40
|
-
type DisplayMode = "count" | "percent"
|
|
41
|
-
|
|
42
|
-
function funnelOption(
|
|
43
|
-
data: FunnelStage[],
|
|
44
|
-
labels: string[],
|
|
45
|
-
display: DisplayMode,
|
|
46
|
-
): EChartsOption {
|
|
47
|
-
const max = data[0]?.value ?? 0
|
|
48
|
-
|
|
49
|
-
return {
|
|
50
|
-
tooltip: { trigger: "item" },
|
|
51
|
-
series: [
|
|
52
|
-
{
|
|
53
|
-
type: "funnel",
|
|
54
|
-
orient: "horizontal",
|
|
55
|
-
funnelAlign: "center",
|
|
56
|
-
left: "2%",
|
|
57
|
-
right: "2%",
|
|
58
|
-
top: 16,
|
|
59
|
-
bottom: 64,
|
|
60
|
-
min: 0,
|
|
61
|
-
max,
|
|
62
|
-
sort: "descending",
|
|
63
|
-
gap: 4,
|
|
64
|
-
label: {
|
|
65
|
-
show: true,
|
|
66
|
-
position: "bottom",
|
|
67
|
-
formatter: (p: unknown) => {
|
|
68
|
-
const params = p as {
|
|
69
|
-
name: string
|
|
70
|
-
value: number
|
|
71
|
-
data: { conversionRate: number; share: number }
|
|
72
|
-
}
|
|
73
|
-
const main =
|
|
74
|
-
display === "percent"
|
|
75
|
-
? `${params.data.share.toFixed(1)}%`
|
|
76
|
-
: params.value.toLocaleString("en-US")
|
|
77
|
-
return `{name|${params.name}}\n{value|${main}}`
|
|
78
|
-
},
|
|
79
|
-
rich: {
|
|
80
|
-
name: { fontSize: 12, fontWeight: 600, lineHeight: 16, align: "center" },
|
|
81
|
-
value: { fontSize: 11, opacity: 0.6, lineHeight: 14, align: "center" },
|
|
82
|
-
},
|
|
83
|
-
},
|
|
84
|
-
labelLine: { show: false },
|
|
85
|
-
// No `itemStyle.color` and no `option.color`: the stages take
|
|
86
|
-
// `--chart-1..5`, and the labels the foreground token, so the chart
|
|
87
|
-
// rebrands with the theme (v0.2.1+).
|
|
88
|
-
data: data.map((d, i) => ({
|
|
89
|
-
name: labels[i] ?? "",
|
|
90
|
-
value: d.value,
|
|
91
|
-
conversionRate: d.conversionRate,
|
|
92
|
-
share: (d.value / Math.max(1, max)) * 100,
|
|
93
|
-
})),
|
|
94
|
-
},
|
|
95
|
-
] as EChartsOption["series"],
|
|
96
|
-
}
|
|
97
|
-
}
|
|
37
|
+
const funnelRows = funnelStages.map((d, i) => ({ stage: STAGE_NAMES[i], value: d.value }))
|
|
98
38
|
|
|
99
39
|
const summaryCards = [
|
|
100
40
|
{
|
|
@@ -128,7 +68,6 @@ export default function HomePage() {
|
|
|
128
68
|
from: subDays(today, 29),
|
|
129
69
|
to: today,
|
|
130
70
|
})
|
|
131
|
-
const [display, setDisplay] = useState<DisplayMode>("count")
|
|
132
71
|
|
|
133
72
|
return (
|
|
134
73
|
<PageShell>
|
|
@@ -140,19 +79,6 @@ export default function HomePage() {
|
|
|
140
79
|
</PageShellDescription>
|
|
141
80
|
</PageShellHeading>
|
|
142
81
|
<PageShellHeaderEnd className="flex-row items-center gap-2">
|
|
143
|
-
{/* Base UI toggle groups are always multi-value — keep the array at length 1. */}
|
|
144
|
-
<ToggleGroup
|
|
145
|
-
value={[display]}
|
|
146
|
-
onValueChange={(value) => {
|
|
147
|
-
const next = value[0]
|
|
148
|
-
if (next) setDisplay(next as DisplayMode)
|
|
149
|
-
}}
|
|
150
|
-
variant="outline"
|
|
151
|
-
size="sm"
|
|
152
|
-
>
|
|
153
|
-
<ToggleGroupItem value="count">Option A</ToggleGroupItem>
|
|
154
|
-
<ToggleGroupItem value="percent">Option B</ToggleGroupItem>
|
|
155
|
-
</ToggleGroup>
|
|
156
82
|
<DateRangePicker
|
|
157
83
|
value={dateRange}
|
|
158
84
|
onChange={setDateRange}
|
|
@@ -180,17 +106,13 @@ export default function HomePage() {
|
|
|
180
106
|
</div>
|
|
181
107
|
|
|
182
108
|
<div className="grid gap-4 lg:grid-cols-3">
|
|
183
|
-
<
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
height="440px"
|
|
191
|
-
/>
|
|
192
|
-
</DashboardCardPreset>
|
|
193
|
-
</div>
|
|
109
|
+
<DashboardCardPreset
|
|
110
|
+
className="lg:col-span-2"
|
|
111
|
+
title="Conversion funnel"
|
|
112
|
+
description="Stage-by-stage conversion"
|
|
113
|
+
>
|
|
114
|
+
<FunnelSteps data={funnelRows} index="stage" categories={["value"]} />
|
|
115
|
+
</DashboardCardPreset>
|
|
194
116
|
<DashboardCardPreset title="Stages" description="Conversion by stage">
|
|
195
117
|
<StagesSidebar />
|
|
196
118
|
</DashboardCardPreset>
|
|
@@ -10,7 +10,7 @@ import {
|
|
|
10
10
|
DashboardCardContent,
|
|
11
11
|
DashboardCardPreset,
|
|
12
12
|
DateRangePicker,
|
|
13
|
-
|
|
13
|
+
FunnelSteps,
|
|
14
14
|
PageShell,
|
|
15
15
|
PageShellHeader,
|
|
16
16
|
PageShellHeading,
|
|
@@ -20,12 +20,10 @@ import {
|
|
|
20
20
|
PageShellContent,
|
|
21
21
|
Placeholder,
|
|
22
22
|
} from "@squadbase/vantage/components"
|
|
23
|
-
import type { DateRange
|
|
24
|
-
import { ToggleGroup, ToggleGroupItem } from "@squadbase/vantage/ui"
|
|
23
|
+
import type { DateRange } from "@squadbase/vantage/components"
|
|
25
24
|
|
|
26
25
|
import { StageTable } from "./components/funnel/stage-table"
|
|
27
26
|
import { funnelStages, stageRows } from "./components/funnel/mock-data"
|
|
28
|
-
import type { FunnelStage } from "./components/funnel/types"
|
|
29
27
|
|
|
30
28
|
export const page = definePage({
|
|
31
29
|
title: "ファネル",
|
|
@@ -36,64 +34,7 @@ export const page = definePage({
|
|
|
36
34
|
const today = new Date()
|
|
37
35
|
|
|
38
36
|
const STAGE_NAMES = ["ステージ1", "ステージ2", "ステージ3", "ステージ4", "ステージ5"]
|
|
39
|
-
|
|
40
|
-
type DisplayMode = "count" | "percent"
|
|
41
|
-
|
|
42
|
-
function funnelOption(
|
|
43
|
-
data: FunnelStage[],
|
|
44
|
-
labels: string[],
|
|
45
|
-
display: DisplayMode,
|
|
46
|
-
): EChartsOption {
|
|
47
|
-
const max = data[0]?.value ?? 0
|
|
48
|
-
|
|
49
|
-
return {
|
|
50
|
-
tooltip: { trigger: "item" },
|
|
51
|
-
series: [
|
|
52
|
-
{
|
|
53
|
-
type: "funnel",
|
|
54
|
-
orient: "horizontal",
|
|
55
|
-
funnelAlign: "center",
|
|
56
|
-
left: "2%",
|
|
57
|
-
right: "2%",
|
|
58
|
-
top: 16,
|
|
59
|
-
bottom: 64,
|
|
60
|
-
min: 0,
|
|
61
|
-
max,
|
|
62
|
-
sort: "descending",
|
|
63
|
-
gap: 4,
|
|
64
|
-
label: {
|
|
65
|
-
show: true,
|
|
66
|
-
position: "bottom",
|
|
67
|
-
formatter: (p: unknown) => {
|
|
68
|
-
const params = p as {
|
|
69
|
-
name: string
|
|
70
|
-
value: number
|
|
71
|
-
data: { conversionRate: number; share: number }
|
|
72
|
-
}
|
|
73
|
-
const main =
|
|
74
|
-
display === "percent"
|
|
75
|
-
? `${params.data.share.toFixed(1)}%`
|
|
76
|
-
: params.value.toLocaleString("ja-JP")
|
|
77
|
-
return `{name|${params.name}}\n{value|${main}}`
|
|
78
|
-
},
|
|
79
|
-
rich: {
|
|
80
|
-
name: { fontSize: 12, fontWeight: 600, lineHeight: 16, align: "center" },
|
|
81
|
-
value: { fontSize: 11, opacity: 0.6, lineHeight: 14, align: "center" },
|
|
82
|
-
},
|
|
83
|
-
},
|
|
84
|
-
labelLine: { show: false },
|
|
85
|
-
// itemStyle.color も option.color も持たせない: ステージは --chart-1..5、
|
|
86
|
-
// ラベルは文字色トークンで描かれ、テーマ差し替えに追従する (v0.2.1〜)。
|
|
87
|
-
data: data.map((d, i) => ({
|
|
88
|
-
name: labels[i] ?? "",
|
|
89
|
-
value: d.value,
|
|
90
|
-
conversionRate: d.conversionRate,
|
|
91
|
-
share: (d.value / Math.max(1, max)) * 100,
|
|
92
|
-
})),
|
|
93
|
-
},
|
|
94
|
-
] as EChartsOption["series"],
|
|
95
|
-
}
|
|
96
|
-
}
|
|
37
|
+
const funnelRows = funnelStages.map((d, i) => ({ stage: STAGE_NAMES[i], value: d.value }))
|
|
97
38
|
|
|
98
39
|
const summaryCards = [
|
|
99
40
|
{
|
|
@@ -127,7 +68,6 @@ export default function HomePage() {
|
|
|
127
68
|
from: subDays(today, 29),
|
|
128
69
|
to: today,
|
|
129
70
|
})
|
|
130
|
-
const [display, setDisplay] = useState<DisplayMode>("count")
|
|
131
71
|
|
|
132
72
|
return (
|
|
133
73
|
<PageShell>
|
|
@@ -139,19 +79,6 @@ export default function HomePage() {
|
|
|
139
79
|
</PageShellDescription>
|
|
140
80
|
</PageShellHeading>
|
|
141
81
|
<PageShellHeaderEnd className="flex-row items-center gap-2">
|
|
142
|
-
{/* Base UI の ToggleGroup は常に配列。単一選択は長さ 1 に保つ。 */}
|
|
143
|
-
<ToggleGroup
|
|
144
|
-
value={[display]}
|
|
145
|
-
onValueChange={(value) => {
|
|
146
|
-
const next = value[0]
|
|
147
|
-
if (next) setDisplay(next as DisplayMode)
|
|
148
|
-
}}
|
|
149
|
-
variant="outline"
|
|
150
|
-
size="sm"
|
|
151
|
-
>
|
|
152
|
-
<ToggleGroupItem value="count">選択肢A</ToggleGroupItem>
|
|
153
|
-
<ToggleGroupItem value="percent">選択肢B</ToggleGroupItem>
|
|
154
|
-
</ToggleGroup>
|
|
155
82
|
<DateRangePicker
|
|
156
83
|
value={dateRange}
|
|
157
84
|
onChange={setDateRange}
|
|
@@ -179,17 +106,13 @@ export default function HomePage() {
|
|
|
179
106
|
</div>
|
|
180
107
|
|
|
181
108
|
<div className="grid gap-4 lg:grid-cols-3">
|
|
182
|
-
<
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
height="440px"
|
|
190
|
-
/>
|
|
191
|
-
</DashboardCardPreset>
|
|
192
|
-
</div>
|
|
109
|
+
<DashboardCardPreset
|
|
110
|
+
className="lg:col-span-2"
|
|
111
|
+
title="コンバージョンファネル"
|
|
112
|
+
description="ステージ別コンバージョン"
|
|
113
|
+
>
|
|
114
|
+
<FunnelSteps data={funnelRows} index="stage" categories={["value"]} />
|
|
115
|
+
</DashboardCardPreset>
|
|
193
116
|
<DashboardCardPreset title="ステージ" description="ステージ別コンバージョン">
|
|
194
117
|
<StagesSidebar />
|
|
195
118
|
</DashboardCardPreset>
|
|
@@ -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
|
"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
|
}
|