@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.
Files changed (67) hide show
  1. package/.turbo/turbo-build.log +2 -2
  2. package/dist/ai/AiAssistantLauncher.js +61 -8
  3. package/dist/ai/AiAssistantLauncher.js.map +2 -2
  4. package/dist/ai/AiChat.js +29 -6
  5. package/dist/ai/AiChat.js.map +2 -2
  6. package/dist/ai/AiChatSessions.js +31 -9
  7. package/dist/ai/AiChatSessions.js.map +2 -2
  8. package/dist/ai/AiDock.js +8 -1
  9. package/dist/ai/AiDock.js.map +2 -2
  10. package/dist/ai/ConversationShareButton.js +40 -0
  11. package/dist/ai/ConversationShareButton.js.map +7 -0
  12. package/dist/ai/ConversationShareDialog.js +234 -0
  13. package/dist/ai/ConversationShareDialog.js.map +7 -0
  14. package/dist/ai/conversation-store.js +4 -2
  15. package/dist/ai/conversation-store.js.map +2 -2
  16. package/dist/ai/index.js +4 -0
  17. package/dist/ai/index.js.map +2 -2
  18. package/dist/ai/useAiChat.js +20 -4
  19. package/dist/ai/useAiChat.js.map +2 -2
  20. package/dist/backend/charts/BarChart.js +19 -76
  21. package/dist/backend/charts/BarChart.js.map +2 -2
  22. package/dist/backend/charts/BarChartImpl.js +91 -0
  23. package/dist/backend/charts/BarChartImpl.js.map +7 -0
  24. package/dist/backend/charts/LineChart.js +18 -91
  25. package/dist/backend/charts/LineChart.js.map +2 -2
  26. package/dist/backend/charts/LineChartImpl.js +112 -0
  27. package/dist/backend/charts/LineChartImpl.js.map +7 -0
  28. package/dist/backend/charts/PieChart.js +17 -62
  29. package/dist/backend/charts/PieChart.js.map +2 -2
  30. package/dist/backend/charts/PieChartImpl.js +76 -0
  31. package/dist/backend/charts/PieChartImpl.js.map +7 -0
  32. package/dist/backend/dashboard/DashboardScreen.js +9 -1
  33. package/dist/backend/dashboard/DashboardScreen.js.map +2 -2
  34. package/dist/backend/dashboard/widgetRegistry.js +18 -2
  35. package/dist/backend/dashboard/widgetRegistry.js.map +2 -2
  36. package/dist/backend/icons/lucideRegistry.generated.js +2 -0
  37. package/dist/backend/icons/lucideRegistry.generated.js.map +2 -2
  38. package/dist/backend/injection/useInjectionDataWidgets.js +12 -2
  39. package/dist/backend/injection/useInjectionDataWidgets.js.map +2 -2
  40. package/package.json +4 -5
  41. package/src/ai/AiAssistantLauncher.tsx +81 -5
  42. package/src/ai/AiChat.tsx +42 -2
  43. package/src/ai/AiChatSessions.tsx +55 -9
  44. package/src/ai/AiDock.tsx +13 -0
  45. package/src/ai/ConversationShareButton.tsx +39 -0
  46. package/src/ai/ConversationShareDialog.tsx +299 -0
  47. package/src/ai/__tests__/AiAssistantLauncher.test.tsx +6 -0
  48. package/src/ai/__tests__/AiChat.conversation.test.tsx +97 -0
  49. package/src/ai/__tests__/AiChatSessions.test.tsx +228 -0
  50. package/src/ai/__tests__/AiDock.test.tsx +31 -1
  51. package/src/ai/__tests__/conversation-store.test.ts +74 -1
  52. package/src/ai/conversation-store.ts +22 -2
  53. package/src/ai/index.ts +2 -0
  54. package/src/ai/useAiChat.ts +47 -4
  55. package/src/backend/__tests__/lazy-heavy-libraries.test.ts +53 -0
  56. package/src/backend/charts/BarChart.tsx +21 -72
  57. package/src/backend/charts/BarChartImpl.tsx +99 -0
  58. package/src/backend/charts/LineChart.tsx +23 -85
  59. package/src/backend/charts/LineChartImpl.tsx +120 -0
  60. package/src/backend/charts/PieChart.tsx +19 -60
  61. package/src/backend/charts/PieChartImpl.tsx +82 -0
  62. package/src/backend/dashboard/DashboardScreen.tsx +9 -1
  63. package/src/backend/dashboard/__tests__/DashboardScreen.test.tsx +45 -0
  64. package/src/backend/dashboard/__tests__/widgetRegistry.test.ts +60 -0
  65. package/src/backend/dashboard/widgetRegistry.ts +23 -5
  66. package/src/backend/icons/lucideRegistry.generated.tsx +2 -0
  67. 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
- {chartContent}
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
- <ResponsiveContainer width="100%" height="100%">
112
- <ChartComponent
113
- data={data}
114
- margin={{ top: 5, right: 10, left: 10, bottom: 5 }}
115
- >
116
- {showGridLines && (
117
- <CartesianGrid
118
- strokeDasharray="3 3"
119
- stroke="hsl(var(--border))"
120
- />
121
- )}
122
- <XAxis
123
- dataKey={index}
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
- <ResponsiveContainer width="100%" height="100%">
110
- <RechartsPieChart>
111
- <Pie
112
- data={data}
113
- dataKey="value"
114
- nameKey="name"
115
- cx="50%"
116
- cy="40%"
117
- innerRadius={innerRadius}
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
  )