@open-mercato/ui 0.6.3-develop.3901.1.ddad60693a → 0.6.3
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/.turbo/turbo-build.log +2 -2
- package/dist/ai/AiAssistantLauncher.js +61 -8
- package/dist/ai/AiAssistantLauncher.js.map +2 -2
- package/dist/ai/AiChat.js +29 -6
- package/dist/ai/AiChat.js.map +2 -2
- package/dist/ai/AiChatSessions.js +31 -9
- package/dist/ai/AiChatSessions.js.map +2 -2
- package/dist/ai/AiDock.js +8 -1
- package/dist/ai/AiDock.js.map +2 -2
- package/dist/ai/ConversationShareButton.js +40 -0
- package/dist/ai/ConversationShareButton.js.map +7 -0
- package/dist/ai/ConversationShareDialog.js +234 -0
- package/dist/ai/ConversationShareDialog.js.map +7 -0
- package/dist/ai/conversation-store.js +4 -2
- package/dist/ai/conversation-store.js.map +2 -2
- package/dist/ai/index.js +4 -0
- package/dist/ai/index.js.map +2 -2
- package/dist/ai/useAiChat.js +20 -4
- package/dist/ai/useAiChat.js.map +2 -2
- package/dist/backend/charts/BarChart.js +19 -76
- package/dist/backend/charts/BarChart.js.map +2 -2
- package/dist/backend/charts/BarChartImpl.js +91 -0
- package/dist/backend/charts/BarChartImpl.js.map +7 -0
- package/dist/backend/charts/LineChart.js +18 -91
- package/dist/backend/charts/LineChart.js.map +2 -2
- package/dist/backend/charts/LineChartImpl.js +112 -0
- package/dist/backend/charts/LineChartImpl.js.map +7 -0
- package/dist/backend/charts/PieChart.js +17 -62
- package/dist/backend/charts/PieChart.js.map +2 -2
- package/dist/backend/charts/PieChartImpl.js +76 -0
- package/dist/backend/charts/PieChartImpl.js.map +7 -0
- package/dist/backend/dashboard/DashboardScreen.js +9 -1
- package/dist/backend/dashboard/DashboardScreen.js.map +2 -2
- package/dist/backend/dashboard/widgetRegistry.js +18 -2
- package/dist/backend/dashboard/widgetRegistry.js.map +2 -2
- package/dist/backend/icons/lucideRegistry.generated.js +2 -0
- package/dist/backend/icons/lucideRegistry.generated.js.map +2 -2
- package/dist/backend/injection/useInjectionDataWidgets.js +12 -2
- package/dist/backend/injection/useInjectionDataWidgets.js.map +2 -2
- package/package.json +4 -5
- package/src/ai/AiAssistantLauncher.tsx +81 -5
- package/src/ai/AiChat.tsx +42 -2
- package/src/ai/AiChatSessions.tsx +55 -9
- package/src/ai/AiDock.tsx +13 -0
- package/src/ai/ConversationShareButton.tsx +39 -0
- package/src/ai/ConversationShareDialog.tsx +299 -0
- package/src/ai/__tests__/AiAssistantLauncher.test.tsx +6 -0
- package/src/ai/__tests__/AiChat.conversation.test.tsx +97 -0
- package/src/ai/__tests__/AiChatSessions.test.tsx +228 -0
- package/src/ai/__tests__/AiDock.test.tsx +31 -1
- package/src/ai/__tests__/conversation-store.test.ts +74 -1
- package/src/ai/conversation-store.ts +22 -2
- package/src/ai/index.ts +2 -0
- package/src/ai/useAiChat.ts +47 -4
- package/src/backend/__tests__/lazy-heavy-libraries.test.ts +53 -0
- package/src/backend/charts/BarChart.tsx +21 -72
- package/src/backend/charts/BarChartImpl.tsx +99 -0
- package/src/backend/charts/LineChart.tsx +23 -85
- package/src/backend/charts/LineChartImpl.tsx +120 -0
- package/src/backend/charts/PieChart.tsx +19 -60
- package/src/backend/charts/PieChartImpl.tsx +82 -0
- package/src/backend/dashboard/DashboardScreen.tsx +9 -1
- package/src/backend/dashboard/__tests__/DashboardScreen.test.tsx +45 -0
- package/src/backend/dashboard/__tests__/widgetRegistry.test.ts +60 -0
- package/src/backend/dashboard/widgetRegistry.ts +23 -5
- package/src/backend/icons/lucideRegistry.generated.tsx +2 -0
- package/src/backend/injection/useInjectionDataWidgets.ts +18 -2
|
@@ -47,4 +47,57 @@ describe('heavy libraries are lazy-loaded', () => {
|
|
|
47
47
|
expect(source).not.toMatch(/from\s+['"]remark-gfm['"]/)
|
|
48
48
|
expect(source).toMatch(/markdownToPlainText/)
|
|
49
49
|
})
|
|
50
|
+
|
|
51
|
+
it('BarChart wrapper does not statically import recharts', () => {
|
|
52
|
+
const source = read('packages/ui/src/backend/charts/BarChart.tsx')
|
|
53
|
+
expect(source).not.toMatch(/^[^\n/]*import\s+[^'\n]+from\s+['"]recharts['"]/m)
|
|
54
|
+
expect(source).toMatch(/next\/dynamic/)
|
|
55
|
+
expect(source).toMatch(/import\(['"]\.\/BarChartImpl['"]\)/)
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
it('LineChart wrapper does not statically import recharts', () => {
|
|
59
|
+
const source = read('packages/ui/src/backend/charts/LineChart.tsx')
|
|
60
|
+
expect(source).not.toMatch(/^[^\n/]*import\s+[^'\n]+from\s+['"]recharts['"]/m)
|
|
61
|
+
expect(source).toMatch(/next\/dynamic/)
|
|
62
|
+
expect(source).toMatch(/import\(['"]\.\/LineChartImpl['"]\)/)
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
it('PieChart wrapper does not statically import recharts', () => {
|
|
66
|
+
const source = read('packages/ui/src/backend/charts/PieChart.tsx')
|
|
67
|
+
expect(source).not.toMatch(/^[^\n/]*import\s+[^'\n]+from\s+['"]recharts['"]/m)
|
|
68
|
+
expect(source).toMatch(/next\/dynamic/)
|
|
69
|
+
expect(source).toMatch(/import\(['"]\.\/PieChartImpl['"]\)/)
|
|
70
|
+
})
|
|
71
|
+
|
|
72
|
+
it('recharts is only imported by the chart *Impl modules', () => {
|
|
73
|
+
const impls = ['BarChartImpl', 'LineChartImpl', 'PieChartImpl']
|
|
74
|
+
for (const name of impls) {
|
|
75
|
+
const source = read(`packages/ui/src/backend/charts/${name}.tsx`)
|
|
76
|
+
expect(source).toMatch(/from\s+['"]recharts['"]/)
|
|
77
|
+
}
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
it('WorkflowGraph wrapper has only type-only @xyflow/react imports', () => {
|
|
81
|
+
const source = read(
|
|
82
|
+
'packages/core/src/modules/workflows/components/WorkflowGraph.tsx',
|
|
83
|
+
)
|
|
84
|
+
expect(source).not.toMatch(
|
|
85
|
+
/^[^\n/]*import\s+(?!type\b)[^'\n]+from\s+['"]@xyflow\/react['"]/m,
|
|
86
|
+
)
|
|
87
|
+
expect(source).toMatch(/next\/dynamic/)
|
|
88
|
+
expect(source).toMatch(/import\(['"]\.\/WorkflowGraphImpl['"]\)/)
|
|
89
|
+
})
|
|
90
|
+
|
|
91
|
+
it('WorkflowGraphImpl owns the @xyflow/react runtime import and CSS', () => {
|
|
92
|
+
const source = read(
|
|
93
|
+
'packages/core/src/modules/workflows/components/WorkflowGraphImpl.tsx',
|
|
94
|
+
)
|
|
95
|
+
expect(source).toMatch(/from\s+['"]@xyflow\/react['"]/)
|
|
96
|
+
expect(source).toMatch(/['"]@xyflow\/react\/dist\/style\.css['"]/)
|
|
97
|
+
})
|
|
98
|
+
|
|
99
|
+
it('globals.css no longer eagerly imports @xyflow/react styles', () => {
|
|
100
|
+
const source = read('apps/mercato/src/app/globals.css')
|
|
101
|
+
expect(source).not.toMatch(/@xyflow\/react\/dist\/style\.css/)
|
|
102
|
+
})
|
|
50
103
|
})
|
|
@@ -1,18 +1,8 @@
|
|
|
1
1
|
"use client"
|
|
2
2
|
|
|
3
3
|
import * as React from 'react'
|
|
4
|
-
import
|
|
5
|
-
BarChart as RechartsBarChart,
|
|
6
|
-
Bar,
|
|
7
|
-
XAxis,
|
|
8
|
-
YAxis,
|
|
9
|
-
CartesianGrid,
|
|
10
|
-
Tooltip,
|
|
11
|
-
Legend,
|
|
12
|
-
ResponsiveContainer,
|
|
13
|
-
} from 'recharts'
|
|
4
|
+
import dynamic from 'next/dynamic'
|
|
14
5
|
import { Spinner } from '@open-mercato/ui/primitives/spinner'
|
|
15
|
-
import { ChartTooltipContent, resolveChartColor } from './ChartUtils'
|
|
16
6
|
|
|
17
7
|
export type BarChartDataItem = Record<string, string | number | null | undefined>
|
|
18
8
|
|
|
@@ -43,6 +33,15 @@ function defaultValueFormatter(value: number): string {
|
|
|
43
33
|
return value.toLocaleString(undefined, { maximumFractionDigits: 2 })
|
|
44
34
|
}
|
|
45
35
|
|
|
36
|
+
const BarChartImpl = dynamic(() => import('./BarChartImpl'), {
|
|
37
|
+
ssr: false,
|
|
38
|
+
loading: () => (
|
|
39
|
+
<div className="flex h-40 sm:h-48 items-center justify-center">
|
|
40
|
+
<Spinner className="h-6 w-6 text-muted-foreground" />
|
|
41
|
+
</div>
|
|
42
|
+
),
|
|
43
|
+
})
|
|
44
|
+
|
|
46
45
|
export function BarChart({
|
|
47
46
|
title,
|
|
48
47
|
data,
|
|
@@ -59,10 +58,6 @@ export function BarChart({
|
|
|
59
58
|
emptyMessage = 'No data available',
|
|
60
59
|
categoryLabels,
|
|
61
60
|
}: BarChartProps) {
|
|
62
|
-
const getBarColor = (idx: number): string => {
|
|
63
|
-
return resolveChartColor(colors?.[idx], idx)
|
|
64
|
-
}
|
|
65
|
-
|
|
66
61
|
const hasWrapper = !!title
|
|
67
62
|
const wrapperClass = hasWrapper ? `rounded-lg border bg-card p-4 ${className}` : className
|
|
68
63
|
|
|
@@ -99,66 +94,20 @@ export function BarChart({
|
|
|
99
94
|
)
|
|
100
95
|
}
|
|
101
96
|
|
|
102
|
-
const isHorizontal = layout === 'horizontal'
|
|
103
|
-
const chartHeight = isHorizontal ? Math.max(200, data.length * 28) : 200
|
|
104
|
-
|
|
105
|
-
const chartContent = (
|
|
106
|
-
<ResponsiveContainer width="100%" height={chartHeight}>
|
|
107
|
-
<RechartsBarChart
|
|
108
|
-
data={data}
|
|
109
|
-
layout={isHorizontal ? 'vertical' : 'horizontal'}
|
|
110
|
-
margin={{ top: 5, right: 30, left: 20, bottom: 5 }}
|
|
111
|
-
>
|
|
112
|
-
{showGridLines && (
|
|
113
|
-
<CartesianGrid strokeDasharray="3 3" stroke="hsl(var(--border))" />
|
|
114
|
-
)}
|
|
115
|
-
<XAxis
|
|
116
|
-
type={isHorizontal ? 'number' : 'category'}
|
|
117
|
-
dataKey={isHorizontal ? undefined : index}
|
|
118
|
-
tickFormatter={isHorizontal ? valueFormatter : undefined}
|
|
119
|
-
tick={{ fontSize: 11 }}
|
|
120
|
-
/>
|
|
121
|
-
<YAxis
|
|
122
|
-
type={isHorizontal ? 'category' : 'number'}
|
|
123
|
-
dataKey={isHorizontal ? index : undefined}
|
|
124
|
-
tickFormatter={isHorizontal ? undefined : valueFormatter}
|
|
125
|
-
width={isHorizontal ? 90 : 50}
|
|
126
|
-
interval={0}
|
|
127
|
-
tick={{ fontSize: 10 }}
|
|
128
|
-
/>
|
|
129
|
-
<Tooltip
|
|
130
|
-
content={
|
|
131
|
-
<ChartTooltipContent
|
|
132
|
-
valueFormatter={valueFormatter}
|
|
133
|
-
categoryLabels={categoryLabels}
|
|
134
|
-
labelFormatter={(label, payload) => {
|
|
135
|
-
const entry = payload?.[0] as { payload?: BarChartDataItem } | undefined
|
|
136
|
-
const item = entry?.payload
|
|
137
|
-
return item?.[index] ? String(item[index]) : label
|
|
138
|
-
}}
|
|
139
|
-
/>
|
|
140
|
-
}
|
|
141
|
-
cursor={{ fill: 'hsl(var(--muted))', opacity: 0.2 }}
|
|
142
|
-
/>
|
|
143
|
-
{showLegend && categories.length > 1 && (
|
|
144
|
-
<Legend verticalAlign="top" height={36} />
|
|
145
|
-
)}
|
|
146
|
-
{categories.map((category, idx) => (
|
|
147
|
-
<Bar
|
|
148
|
-
key={category}
|
|
149
|
-
dataKey={category}
|
|
150
|
-
fill={getBarColor(idx)}
|
|
151
|
-
radius={isHorizontal ? [0, 4, 4, 0] : [4, 4, 0, 0]}
|
|
152
|
-
/>
|
|
153
|
-
))}
|
|
154
|
-
</RechartsBarChart>
|
|
155
|
-
</ResponsiveContainer>
|
|
156
|
-
)
|
|
157
|
-
|
|
158
97
|
return (
|
|
159
98
|
<div className={wrapperClass}>
|
|
160
99
|
{title && <h3 className="mb-4 text-base font-medium text-card-foreground">{title}</h3>}
|
|
161
|
-
|
|
100
|
+
<BarChartImpl
|
|
101
|
+
data={data}
|
|
102
|
+
index={index}
|
|
103
|
+
categories={categories}
|
|
104
|
+
colors={colors}
|
|
105
|
+
layout={layout}
|
|
106
|
+
valueFormatter={valueFormatter}
|
|
107
|
+
showLegend={showLegend}
|
|
108
|
+
showGridLines={showGridLines}
|
|
109
|
+
categoryLabels={categoryLabels}
|
|
110
|
+
/>
|
|
162
111
|
</div>
|
|
163
112
|
)
|
|
164
113
|
}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import * as React from 'react'
|
|
4
|
+
import {
|
|
5
|
+
BarChart as RechartsBarChart,
|
|
6
|
+
Bar,
|
|
7
|
+
XAxis,
|
|
8
|
+
YAxis,
|
|
9
|
+
CartesianGrid,
|
|
10
|
+
Tooltip,
|
|
11
|
+
Legend,
|
|
12
|
+
ResponsiveContainer,
|
|
13
|
+
} from 'recharts'
|
|
14
|
+
import { ChartTooltipContent, resolveChartColor } from './ChartUtils'
|
|
15
|
+
import type { BarChartDataItem, BarChartProps } from './BarChart'
|
|
16
|
+
|
|
17
|
+
type ImplProps = Pick<
|
|
18
|
+
BarChartProps,
|
|
19
|
+
| 'data'
|
|
20
|
+
| 'index'
|
|
21
|
+
| 'categories'
|
|
22
|
+
| 'colors'
|
|
23
|
+
| 'layout'
|
|
24
|
+
| 'valueFormatter'
|
|
25
|
+
| 'showLegend'
|
|
26
|
+
| 'showGridLines'
|
|
27
|
+
| 'categoryLabels'
|
|
28
|
+
> & {
|
|
29
|
+
valueFormatter: (value: number) => string
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export default function BarChartImpl({
|
|
33
|
+
data,
|
|
34
|
+
index,
|
|
35
|
+
categories,
|
|
36
|
+
colors,
|
|
37
|
+
layout = 'vertical',
|
|
38
|
+
valueFormatter,
|
|
39
|
+
showLegend = true,
|
|
40
|
+
showGridLines = true,
|
|
41
|
+
categoryLabels,
|
|
42
|
+
}: ImplProps) {
|
|
43
|
+
const isHorizontal = layout === 'horizontal'
|
|
44
|
+
const chartHeight = isHorizontal ? Math.max(200, data.length * 28) : 200
|
|
45
|
+
const getBarColor = (idx: number): string => resolveChartColor(colors?.[idx], idx)
|
|
46
|
+
|
|
47
|
+
return (
|
|
48
|
+
<ResponsiveContainer width="100%" height={chartHeight}>
|
|
49
|
+
<RechartsBarChart
|
|
50
|
+
data={data}
|
|
51
|
+
layout={isHorizontal ? 'vertical' : 'horizontal'}
|
|
52
|
+
margin={{ top: 5, right: 30, left: 20, bottom: 5 }}
|
|
53
|
+
>
|
|
54
|
+
{showGridLines && (
|
|
55
|
+
<CartesianGrid strokeDasharray="3 3" stroke="hsl(var(--border))" />
|
|
56
|
+
)}
|
|
57
|
+
<XAxis
|
|
58
|
+
type={isHorizontal ? 'number' : 'category'}
|
|
59
|
+
dataKey={isHorizontal ? undefined : index}
|
|
60
|
+
tickFormatter={isHorizontal ? valueFormatter : undefined}
|
|
61
|
+
tick={{ fontSize: 11 }}
|
|
62
|
+
/>
|
|
63
|
+
<YAxis
|
|
64
|
+
type={isHorizontal ? 'category' : 'number'}
|
|
65
|
+
dataKey={isHorizontal ? index : undefined}
|
|
66
|
+
tickFormatter={isHorizontal ? undefined : valueFormatter}
|
|
67
|
+
width={isHorizontal ? 90 : 50}
|
|
68
|
+
interval={0}
|
|
69
|
+
tick={{ fontSize: 10 }}
|
|
70
|
+
/>
|
|
71
|
+
<Tooltip
|
|
72
|
+
content={
|
|
73
|
+
<ChartTooltipContent
|
|
74
|
+
valueFormatter={valueFormatter}
|
|
75
|
+
categoryLabels={categoryLabels}
|
|
76
|
+
labelFormatter={(label, payload) => {
|
|
77
|
+
const entry = payload?.[0] as { payload?: BarChartDataItem } | undefined
|
|
78
|
+
const item = entry?.payload
|
|
79
|
+
return item?.[index] ? String(item[index]) : label
|
|
80
|
+
}}
|
|
81
|
+
/>
|
|
82
|
+
}
|
|
83
|
+
cursor={{ fill: 'hsl(var(--muted))', opacity: 0.2 }}
|
|
84
|
+
/>
|
|
85
|
+
{showLegend && categories.length > 1 && (
|
|
86
|
+
<Legend verticalAlign="top" height={36} />
|
|
87
|
+
)}
|
|
88
|
+
{categories.map((category, idx) => (
|
|
89
|
+
<Bar
|
|
90
|
+
key={category}
|
|
91
|
+
dataKey={category}
|
|
92
|
+
fill={getBarColor(idx)}
|
|
93
|
+
radius={isHorizontal ? [0, 4, 4, 0] : [4, 4, 0, 0]}
|
|
94
|
+
/>
|
|
95
|
+
))}
|
|
96
|
+
</RechartsBarChart>
|
|
97
|
+
</ResponsiveContainer>
|
|
98
|
+
)
|
|
99
|
+
}
|
|
@@ -1,20 +1,8 @@
|
|
|
1
1
|
"use client"
|
|
2
2
|
|
|
3
3
|
import * as React from 'react'
|
|
4
|
-
import
|
|
5
|
-
LineChart as RechartsLineChart,
|
|
6
|
-
AreaChart as RechartsAreaChart,
|
|
7
|
-
Line,
|
|
8
|
-
Area,
|
|
9
|
-
XAxis,
|
|
10
|
-
YAxis,
|
|
11
|
-
CartesianGrid,
|
|
12
|
-
Tooltip,
|
|
13
|
-
ResponsiveContainer,
|
|
14
|
-
Legend,
|
|
15
|
-
} from 'recharts'
|
|
4
|
+
import dynamic from 'next/dynamic'
|
|
16
5
|
import { Spinner } from '@open-mercato/ui/primitives/spinner'
|
|
17
|
-
import { ChartTooltipContent, resolveChartColor } from './ChartUtils'
|
|
18
6
|
|
|
19
7
|
export type LineChartDataItem = Record<string, string | number | null | undefined>
|
|
20
8
|
|
|
@@ -47,6 +35,15 @@ function defaultValueFormatter(value: number): string {
|
|
|
47
35
|
return value.toLocaleString(undefined, { maximumFractionDigits: 2 })
|
|
48
36
|
}
|
|
49
37
|
|
|
38
|
+
const LineChartImpl = dynamic(() => import('./LineChartImpl'), {
|
|
39
|
+
ssr: false,
|
|
40
|
+
loading: () => (
|
|
41
|
+
<div className="flex h-48 items-center justify-center">
|
|
42
|
+
<Spinner className="h-6 w-6 text-muted-foreground" />
|
|
43
|
+
</div>
|
|
44
|
+
),
|
|
45
|
+
})
|
|
46
|
+
|
|
50
47
|
export function LineChart({
|
|
51
48
|
title,
|
|
52
49
|
data,
|
|
@@ -65,10 +62,6 @@ export function LineChart({
|
|
|
65
62
|
emptyMessage = 'No data available',
|
|
66
63
|
categoryLabels,
|
|
67
64
|
}: LineChartProps) {
|
|
68
|
-
const getLineColor = (idx: number): string => {
|
|
69
|
-
return resolveChartColor(colors?.[idx], idx)
|
|
70
|
-
}
|
|
71
|
-
|
|
72
65
|
if (error) {
|
|
73
66
|
return (
|
|
74
67
|
<div className={`rounded-lg border bg-card p-4 ${className}`}>
|
|
@@ -102,78 +95,23 @@ export function LineChart({
|
|
|
102
95
|
)
|
|
103
96
|
}
|
|
104
97
|
|
|
105
|
-
const ChartComponent = showArea ? RechartsAreaChart : RechartsLineChart
|
|
106
|
-
|
|
107
98
|
return (
|
|
108
99
|
<div className={`rounded-lg border bg-card p-4 ${className}`}>
|
|
109
100
|
{title && <h3 className="mb-4 text-base font-medium text-card-foreground">{title}</h3>}
|
|
110
101
|
<div className="h-52 w-full">
|
|
111
|
-
<
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
tick={{ fontSize: 12 }}
|
|
125
|
-
tickLine={false}
|
|
126
|
-
axisLine={false}
|
|
127
|
-
/>
|
|
128
|
-
<YAxis
|
|
129
|
-
tickFormatter={valueFormatter}
|
|
130
|
-
tick={{ fontSize: 12 }}
|
|
131
|
-
tickLine={false}
|
|
132
|
-
axisLine={false}
|
|
133
|
-
width={56}
|
|
134
|
-
/>
|
|
135
|
-
<Tooltip
|
|
136
|
-
content={<ChartTooltipContent valueFormatter={valueFormatter} categoryLabels={categoryLabels} />}
|
|
137
|
-
cursor={{ stroke: 'hsl(var(--muted-foreground))', strokeDasharray: '3 3' }}
|
|
138
|
-
/>
|
|
139
|
-
{showLegend && categories.length > 1 && (
|
|
140
|
-
<Legend
|
|
141
|
-
verticalAlign="top"
|
|
142
|
-
height={36}
|
|
143
|
-
formatter={(value) => (
|
|
144
|
-
<span style={{ color: 'hsl(var(--muted-foreground))', fontSize: '12px' }}>{value}</span>
|
|
145
|
-
)}
|
|
146
|
-
/>
|
|
147
|
-
)}
|
|
148
|
-
{showArea
|
|
149
|
-
? categories.map((category, idx) => (
|
|
150
|
-
<Area
|
|
151
|
-
key={category}
|
|
152
|
-
type={curveType}
|
|
153
|
-
dataKey={category}
|
|
154
|
-
stroke={getLineColor(idx)}
|
|
155
|
-
fill={getLineColor(idx)}
|
|
156
|
-
fillOpacity={0.2}
|
|
157
|
-
strokeWidth={2}
|
|
158
|
-
connectNulls={connectNulls}
|
|
159
|
-
dot={false}
|
|
160
|
-
activeDot={{ r: 4, strokeWidth: 0 }}
|
|
161
|
-
/>
|
|
162
|
-
))
|
|
163
|
-
: categories.map((category, idx) => (
|
|
164
|
-
<Line
|
|
165
|
-
key={category}
|
|
166
|
-
type={curveType}
|
|
167
|
-
dataKey={category}
|
|
168
|
-
stroke={getLineColor(idx)}
|
|
169
|
-
strokeWidth={2}
|
|
170
|
-
connectNulls={connectNulls}
|
|
171
|
-
dot={false}
|
|
172
|
-
activeDot={{ r: 4, strokeWidth: 0 }}
|
|
173
|
-
/>
|
|
174
|
-
))}
|
|
175
|
-
</ChartComponent>
|
|
176
|
-
</ResponsiveContainer>
|
|
102
|
+
<LineChartImpl
|
|
103
|
+
data={data}
|
|
104
|
+
index={index}
|
|
105
|
+
categories={categories}
|
|
106
|
+
colors={colors}
|
|
107
|
+
showArea={showArea}
|
|
108
|
+
valueFormatter={valueFormatter}
|
|
109
|
+
showLegend={showLegend}
|
|
110
|
+
showGridLines={showGridLines}
|
|
111
|
+
curveType={curveType}
|
|
112
|
+
connectNulls={connectNulls}
|
|
113
|
+
categoryLabels={categoryLabels}
|
|
114
|
+
/>
|
|
177
115
|
</div>
|
|
178
116
|
</div>
|
|
179
117
|
)
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import * as React from 'react'
|
|
4
|
+
import {
|
|
5
|
+
LineChart as RechartsLineChart,
|
|
6
|
+
AreaChart as RechartsAreaChart,
|
|
7
|
+
Line,
|
|
8
|
+
Area,
|
|
9
|
+
XAxis,
|
|
10
|
+
YAxis,
|
|
11
|
+
CartesianGrid,
|
|
12
|
+
Tooltip,
|
|
13
|
+
ResponsiveContainer,
|
|
14
|
+
Legend,
|
|
15
|
+
} from 'recharts'
|
|
16
|
+
import { ChartTooltipContent, resolveChartColor } from './ChartUtils'
|
|
17
|
+
import type { LineChartProps } from './LineChart'
|
|
18
|
+
|
|
19
|
+
type ImplProps = Pick<
|
|
20
|
+
LineChartProps,
|
|
21
|
+
| 'data'
|
|
22
|
+
| 'index'
|
|
23
|
+
| 'categories'
|
|
24
|
+
| 'colors'
|
|
25
|
+
| 'showArea'
|
|
26
|
+
| 'valueFormatter'
|
|
27
|
+
| 'showLegend'
|
|
28
|
+
| 'showGridLines'
|
|
29
|
+
| 'curveType'
|
|
30
|
+
| 'connectNulls'
|
|
31
|
+
| 'categoryLabels'
|
|
32
|
+
> & {
|
|
33
|
+
valueFormatter: (value: number) => string
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export default function LineChartImpl({
|
|
37
|
+
data,
|
|
38
|
+
index,
|
|
39
|
+
categories,
|
|
40
|
+
colors,
|
|
41
|
+
showArea = false,
|
|
42
|
+
valueFormatter,
|
|
43
|
+
showLegend = true,
|
|
44
|
+
showGridLines = true,
|
|
45
|
+
curveType = 'monotone',
|
|
46
|
+
connectNulls = true,
|
|
47
|
+
categoryLabels,
|
|
48
|
+
}: ImplProps) {
|
|
49
|
+
const getLineColor = (idx: number): string => resolveChartColor(colors?.[idx], idx)
|
|
50
|
+
const ChartComponent = showArea ? RechartsAreaChart : RechartsLineChart
|
|
51
|
+
|
|
52
|
+
return (
|
|
53
|
+
<ResponsiveContainer width="100%" height="100%">
|
|
54
|
+
<ChartComponent
|
|
55
|
+
data={data}
|
|
56
|
+
margin={{ top: 5, right: 10, left: 10, bottom: 5 }}
|
|
57
|
+
>
|
|
58
|
+
{showGridLines && (
|
|
59
|
+
<CartesianGrid
|
|
60
|
+
strokeDasharray="3 3"
|
|
61
|
+
stroke="hsl(var(--border))"
|
|
62
|
+
/>
|
|
63
|
+
)}
|
|
64
|
+
<XAxis
|
|
65
|
+
dataKey={index}
|
|
66
|
+
tick={{ fontSize: 12 }}
|
|
67
|
+
tickLine={false}
|
|
68
|
+
axisLine={false}
|
|
69
|
+
/>
|
|
70
|
+
<YAxis
|
|
71
|
+
tickFormatter={valueFormatter}
|
|
72
|
+
tick={{ fontSize: 12 }}
|
|
73
|
+
tickLine={false}
|
|
74
|
+
axisLine={false}
|
|
75
|
+
width={56}
|
|
76
|
+
/>
|
|
77
|
+
<Tooltip
|
|
78
|
+
content={<ChartTooltipContent valueFormatter={valueFormatter} categoryLabels={categoryLabels} />}
|
|
79
|
+
cursor={{ stroke: 'hsl(var(--muted-foreground))', strokeDasharray: '3 3' }}
|
|
80
|
+
/>
|
|
81
|
+
{showLegend && categories.length > 1 && (
|
|
82
|
+
<Legend
|
|
83
|
+
verticalAlign="top"
|
|
84
|
+
height={36}
|
|
85
|
+
formatter={(value) => (
|
|
86
|
+
<span style={{ color: 'hsl(var(--muted-foreground))', fontSize: '12px' }}>{value}</span>
|
|
87
|
+
)}
|
|
88
|
+
/>
|
|
89
|
+
)}
|
|
90
|
+
{showArea
|
|
91
|
+
? categories.map((category, idx) => (
|
|
92
|
+
<Area
|
|
93
|
+
key={category}
|
|
94
|
+
type={curveType}
|
|
95
|
+
dataKey={category}
|
|
96
|
+
stroke={getLineColor(idx)}
|
|
97
|
+
fill={getLineColor(idx)}
|
|
98
|
+
fillOpacity={0.2}
|
|
99
|
+
strokeWidth={2}
|
|
100
|
+
connectNulls={connectNulls}
|
|
101
|
+
dot={false}
|
|
102
|
+
activeDot={{ r: 4, strokeWidth: 0 }}
|
|
103
|
+
/>
|
|
104
|
+
))
|
|
105
|
+
: categories.map((category, idx) => (
|
|
106
|
+
<Line
|
|
107
|
+
key={category}
|
|
108
|
+
type={curveType}
|
|
109
|
+
dataKey={category}
|
|
110
|
+
stroke={getLineColor(idx)}
|
|
111
|
+
strokeWidth={2}
|
|
112
|
+
connectNulls={connectNulls}
|
|
113
|
+
dot={false}
|
|
114
|
+
activeDot={{ r: 4, strokeWidth: 0 }}
|
|
115
|
+
/>
|
|
116
|
+
))}
|
|
117
|
+
</ChartComponent>
|
|
118
|
+
</ResponsiveContainer>
|
|
119
|
+
)
|
|
120
|
+
}
|
|
@@ -1,17 +1,8 @@
|
|
|
1
1
|
"use client"
|
|
2
2
|
|
|
3
3
|
import * as React from 'react'
|
|
4
|
-
import
|
|
5
|
-
PieChart as RechartsPieChart,
|
|
6
|
-
Pie,
|
|
7
|
-
Cell,
|
|
8
|
-
Tooltip,
|
|
9
|
-
ResponsiveContainer,
|
|
10
|
-
Legend,
|
|
11
|
-
Label,
|
|
12
|
-
} from 'recharts'
|
|
4
|
+
import dynamic from 'next/dynamic'
|
|
13
5
|
import { Spinner } from '@open-mercato/ui/primitives/spinner'
|
|
14
|
-
import { ChartTooltipContent, resolveChartColor } from './ChartUtils'
|
|
15
6
|
|
|
16
7
|
export type PieChartDataItem = {
|
|
17
8
|
name: string
|
|
@@ -42,6 +33,15 @@ function defaultValueFormatter(value: number): string {
|
|
|
42
33
|
return value.toLocaleString(undefined, { maximumFractionDigits: 2 })
|
|
43
34
|
}
|
|
44
35
|
|
|
36
|
+
const PieChartImpl = dynamic(() => import('./PieChartImpl'), {
|
|
37
|
+
ssr: false,
|
|
38
|
+
loading: () => (
|
|
39
|
+
<div className="flex h-48 items-center justify-center">
|
|
40
|
+
<Spinner className="h-6 w-6 text-muted-foreground" />
|
|
41
|
+
</div>
|
|
42
|
+
),
|
|
43
|
+
})
|
|
44
|
+
|
|
45
45
|
export function PieChart({
|
|
46
46
|
title,
|
|
47
47
|
data,
|
|
@@ -55,10 +55,6 @@ export function PieChart({
|
|
|
55
55
|
className = '',
|
|
56
56
|
emptyMessage = 'No data available',
|
|
57
57
|
}: PieChartProps) {
|
|
58
|
-
const getSliceColor = (idx: number): string => {
|
|
59
|
-
return resolveChartColor(colors?.[idx], idx)
|
|
60
|
-
}
|
|
61
|
-
|
|
62
58
|
const total = React.useMemo(() => {
|
|
63
59
|
return data.reduce((sum, item) => sum + item.value, 0)
|
|
64
60
|
}, [data])
|
|
@@ -99,56 +95,19 @@ export function PieChart({
|
|
|
99
95
|
)
|
|
100
96
|
}
|
|
101
97
|
|
|
102
|
-
const innerRadius = variant === 'donut' ? '60%' : 0
|
|
103
|
-
const outerRadius = '80%'
|
|
104
|
-
|
|
105
98
|
return (
|
|
106
99
|
<div className={wrapperClass}>
|
|
107
100
|
{title && <h3 className="mb-4 text-base font-medium text-card-foreground">{title}</h3>}
|
|
108
101
|
<div className="h-52 w-full">
|
|
109
|
-
<
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
outerRadius={outerRadius}
|
|
119
|
-
paddingAngle={2}
|
|
120
|
-
strokeWidth={0}
|
|
121
|
-
>
|
|
122
|
-
{data.map((_, idx) => (
|
|
123
|
-
<Cell key={`cell-${idx}`} fill={getSliceColor(idx)} />
|
|
124
|
-
))}
|
|
125
|
-
{showLabel && variant === 'donut' && (
|
|
126
|
-
<Label
|
|
127
|
-
value={valueFormatter(total)}
|
|
128
|
-
position="center"
|
|
129
|
-
className="fill-foreground text-2xl font-bold"
|
|
130
|
-
/>
|
|
131
|
-
)}
|
|
132
|
-
</Pie>
|
|
133
|
-
{showTooltip && (
|
|
134
|
-
<Tooltip
|
|
135
|
-
content={
|
|
136
|
-
<ChartTooltipContent
|
|
137
|
-
valueFormatter={valueFormatter}
|
|
138
|
-
hideLabel
|
|
139
|
-
/>
|
|
140
|
-
}
|
|
141
|
-
/>
|
|
142
|
-
)}
|
|
143
|
-
<Legend
|
|
144
|
-
verticalAlign="bottom"
|
|
145
|
-
height={36}
|
|
146
|
-
formatter={(value) => (
|
|
147
|
-
<span style={{ color: 'hsl(var(--muted-foreground))', fontSize: '12px' }}>{value}</span>
|
|
148
|
-
)}
|
|
149
|
-
/>
|
|
150
|
-
</RechartsPieChart>
|
|
151
|
-
</ResponsiveContainer>
|
|
102
|
+
<PieChartImpl
|
|
103
|
+
data={data}
|
|
104
|
+
colors={colors}
|
|
105
|
+
variant={variant}
|
|
106
|
+
valueFormatter={valueFormatter}
|
|
107
|
+
showLabel={showLabel}
|
|
108
|
+
showTooltip={showTooltip}
|
|
109
|
+
total={total}
|
|
110
|
+
/>
|
|
152
111
|
</div>
|
|
153
112
|
</div>
|
|
154
113
|
)
|