create-mcp-use-app 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +88 -0
- package/dist/templates/api/README.md +224 -0
- package/dist/templates/api/package.json +24 -0
- package/dist/templates/api/src/server.ts +229 -0
- package/dist/templates/api/tsconfig.json +20 -0
- package/dist/templates/basic/README.md +64 -0
- package/dist/templates/basic/package.json +23 -0
- package/dist/templates/basic/src/server.ts +67 -0
- package/dist/templates/basic/tsconfig.json +20 -0
- package/dist/templates/filesystem/README.md +139 -0
- package/dist/templates/filesystem/package.json +23 -0
- package/dist/templates/filesystem/src/server.ts +155 -0
- package/dist/templates/filesystem/tsconfig.json +20 -0
- package/dist/templates/ui/README.md +454 -0
- package/dist/templates/ui/package-lock.json +2870 -0
- package/dist/templates/ui/package.json +45 -0
- package/dist/templates/ui/resources/data-visualization.tsx +475 -0
- package/dist/templates/ui/resources/kanban-board.tsx +306 -0
- package/dist/templates/ui/resources/todo-list.tsx +408 -0
- package/dist/templates/ui/src/server.ts +153 -0
- package/dist/templates/ui/tsconfig.json +20 -0
- package/package.json +63 -0
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "test-ui",
|
|
3
|
+
"type": "module",
|
|
4
|
+
"version": "1.0.0",
|
|
5
|
+
"description": "MCP server: test-ui",
|
|
6
|
+
"author": "",
|
|
7
|
+
"license": "MIT",
|
|
8
|
+
"keywords": [
|
|
9
|
+
"mcp",
|
|
10
|
+
"server",
|
|
11
|
+
"ui",
|
|
12
|
+
"react",
|
|
13
|
+
"widgets",
|
|
14
|
+
"ai",
|
|
15
|
+
"tools"
|
|
16
|
+
],
|
|
17
|
+
"main": "dist/server.js",
|
|
18
|
+
"scripts": {
|
|
19
|
+
"build": "mcp-use build",
|
|
20
|
+
"dev": "mcp-use dev",
|
|
21
|
+
"start": "mcp-use start"
|
|
22
|
+
},
|
|
23
|
+
"dependencies": {
|
|
24
|
+
"@mcp-ui/server": "^5.11.0",
|
|
25
|
+
"@mcp-use/inspector": "workspace:*",
|
|
26
|
+
"cors": "^2.8.5",
|
|
27
|
+
"express": "^4.18.0",
|
|
28
|
+
"mcp-use": "workspace:*"
|
|
29
|
+
},
|
|
30
|
+
"devDependencies": {
|
|
31
|
+
"@mcp-use/cli": "workspace:*",
|
|
32
|
+
"@types/cors": "^2.8.0",
|
|
33
|
+
"@types/express": "^4.17.0",
|
|
34
|
+
"@types/node": "^20.0.0",
|
|
35
|
+
"@types/react": "^18.0.0",
|
|
36
|
+
"@types/react-dom": "^18.0.0",
|
|
37
|
+
"concurrently": "^8.0.0",
|
|
38
|
+
"esbuild": "^0.23.0",
|
|
39
|
+
"globby": "^14.0.2",
|
|
40
|
+
"react": "^18.0.0",
|
|
41
|
+
"react-dom": "^18.0.0",
|
|
42
|
+
"tsx": "^4.0.0",
|
|
43
|
+
"typescript": "^5.0.0"
|
|
44
|
+
}
|
|
45
|
+
}
|
|
@@ -0,0 +1,475 @@
|
|
|
1
|
+
import React, { useEffect, useState } from 'react'
|
|
2
|
+
import { createRoot } from 'react-dom/client'
|
|
3
|
+
|
|
4
|
+
interface DataPoint {
|
|
5
|
+
label: string
|
|
6
|
+
value: number
|
|
7
|
+
color?: string
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
interface DataVisualizationProps {
|
|
11
|
+
initialData?: DataPoint[]
|
|
12
|
+
chartType?: 'bar' | 'line' | 'pie'
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const DataVisualization: React.FC<DataVisualizationProps> = ({
|
|
16
|
+
initialData = [],
|
|
17
|
+
chartType = 'bar',
|
|
18
|
+
}) => {
|
|
19
|
+
const [data, setData] = useState<DataPoint[]>(initialData)
|
|
20
|
+
const [currentChartType, setCurrentChartType] = useState<'bar' | 'line' | 'pie'>(chartType)
|
|
21
|
+
const [newDataPoint, setNewDataPoint] = useState({ label: '', value: 0 })
|
|
22
|
+
|
|
23
|
+
// Load data from URL parameters or use defaults
|
|
24
|
+
useEffect(() => {
|
|
25
|
+
const urlParams = new URLSearchParams(window.location.search)
|
|
26
|
+
const dataParam = urlParams.get('data')
|
|
27
|
+
const typeParam = urlParams.get('chartType')
|
|
28
|
+
|
|
29
|
+
if (dataParam) {
|
|
30
|
+
try {
|
|
31
|
+
const parsedData = JSON.parse(decodeURIComponent(dataParam))
|
|
32
|
+
setData(parsedData)
|
|
33
|
+
}
|
|
34
|
+
catch (error) {
|
|
35
|
+
console.error('Error parsing data from URL:', error)
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
else {
|
|
39
|
+
// Default data for demo
|
|
40
|
+
setData([
|
|
41
|
+
{ label: 'January', value: 65, color: '#3498db' },
|
|
42
|
+
{ label: 'February', value: 59, color: '#e74c3c' },
|
|
43
|
+
{ label: 'March', value: 80, color: '#2ecc71' },
|
|
44
|
+
{ label: 'April', value: 81, color: '#f39c12' },
|
|
45
|
+
{ label: 'May', value: 56, color: '#9b59b6' },
|
|
46
|
+
{ label: 'June', value: 55, color: '#1abc9c' },
|
|
47
|
+
])
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (typeParam) {
|
|
51
|
+
setCurrentChartType(typeParam as 'bar' | 'line' | 'pie')
|
|
52
|
+
}
|
|
53
|
+
}, [])
|
|
54
|
+
|
|
55
|
+
const addDataPoint = () => {
|
|
56
|
+
if (newDataPoint.label.trim() && newDataPoint.value > 0) {
|
|
57
|
+
const colors = ['#3498db', '#e74c3c', '#2ecc71', '#f39c12', '#9b59b6', '#1abc9c', '#34495e', '#e67e22']
|
|
58
|
+
const newPoint: DataPoint = {
|
|
59
|
+
...newDataPoint,
|
|
60
|
+
color: colors[data.length % colors.length],
|
|
61
|
+
}
|
|
62
|
+
setData([...data, newPoint])
|
|
63
|
+
setNewDataPoint({ label: '', value: 0 })
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const removeDataPoint = (index: number) => {
|
|
68
|
+
setData(data.filter((_, i) => i !== index))
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const getMaxValue = () => {
|
|
72
|
+
return Math.max(...data.map(d => d.value), 0)
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const getTotalValue = () => {
|
|
76
|
+
return data.reduce((sum, d) => sum + d.value, 0)
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const renderBarChart = () => {
|
|
80
|
+
const maxValue = getMaxValue()
|
|
81
|
+
|
|
82
|
+
return (
|
|
83
|
+
<div style={{ padding: '20px' }}>
|
|
84
|
+
<h3 style={{ marginBottom: '20px', color: '#2c3e50' }}>Bar Chart</h3>
|
|
85
|
+
<div style={{ display: 'flex', alignItems: 'end', gap: '10px', height: '300px' }}>
|
|
86
|
+
{data.map((point, index) => (
|
|
87
|
+
<div key={index} style={{ flex: '1', display: 'flex', flexDirection: 'column', alignItems: 'center' }}>
|
|
88
|
+
<div
|
|
89
|
+
style={{
|
|
90
|
+
background: point.color || '#3498db',
|
|
91
|
+
height: `${(point.value / maxValue) * 250}px`,
|
|
92
|
+
width: '100%',
|
|
93
|
+
borderRadius: '4px 4px 0 0',
|
|
94
|
+
transition: 'all 0.3s ease',
|
|
95
|
+
cursor: 'pointer',
|
|
96
|
+
position: 'relative',
|
|
97
|
+
}}
|
|
98
|
+
title={`${point.label}: ${point.value}`}
|
|
99
|
+
>
|
|
100
|
+
<div style={{
|
|
101
|
+
position: 'absolute',
|
|
102
|
+
top: '-25px',
|
|
103
|
+
left: '50%',
|
|
104
|
+
transform: 'translateX(-50%)',
|
|
105
|
+
background: 'rgba(0,0,0,0.8)',
|
|
106
|
+
color: 'white',
|
|
107
|
+
padding: '2px 6px',
|
|
108
|
+
borderRadius: '4px',
|
|
109
|
+
fontSize: '12px',
|
|
110
|
+
whiteSpace: 'nowrap',
|
|
111
|
+
}}
|
|
112
|
+
>
|
|
113
|
+
{point.value}
|
|
114
|
+
</div>
|
|
115
|
+
</div>
|
|
116
|
+
<div style={{
|
|
117
|
+
marginTop: '10px',
|
|
118
|
+
fontSize: '12px',
|
|
119
|
+
textAlign: 'center',
|
|
120
|
+
color: '#7f8c8d',
|
|
121
|
+
wordBreak: 'break-word',
|
|
122
|
+
}}
|
|
123
|
+
>
|
|
124
|
+
{point.label}
|
|
125
|
+
</div>
|
|
126
|
+
</div>
|
|
127
|
+
))}
|
|
128
|
+
</div>
|
|
129
|
+
</div>
|
|
130
|
+
)
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const renderLineChart = () => {
|
|
134
|
+
const maxValue = getMaxValue()
|
|
135
|
+
const width = 600
|
|
136
|
+
const height = 300
|
|
137
|
+
const padding = 40
|
|
138
|
+
|
|
139
|
+
const points = data.map((point, index) => ({
|
|
140
|
+
x: padding + (index * (width - 2 * padding)) / (data.length - 1),
|
|
141
|
+
y: padding + ((maxValue - point.value) / maxValue) * (height - 2 * padding),
|
|
142
|
+
}))
|
|
143
|
+
|
|
144
|
+
const pathData = points.map((point, index) =>
|
|
145
|
+
`${index === 0 ? 'M' : 'L'} ${point.x} ${point.y}`,
|
|
146
|
+
).join(' ')
|
|
147
|
+
|
|
148
|
+
return (
|
|
149
|
+
<div style={{ padding: '20px' }}>
|
|
150
|
+
<h3 style={{ marginBottom: '20px', color: '#2c3e50' }}>Line Chart</h3>
|
|
151
|
+
<svg width={width} height={height} style={{ border: '1px solid #ecf0f1', borderRadius: '8px' }}>
|
|
152
|
+
{/* Grid lines */}
|
|
153
|
+
{[0, 0.25, 0.5, 0.75, 1].map(ratio => (
|
|
154
|
+
<line
|
|
155
|
+
key={ratio}
|
|
156
|
+
x1={padding}
|
|
157
|
+
y1={padding + ratio * (height - 2 * padding)}
|
|
158
|
+
x2={width - padding}
|
|
159
|
+
y2={padding + ratio * (height - 2 * padding)}
|
|
160
|
+
stroke="#ecf0f1"
|
|
161
|
+
strokeWidth="1"
|
|
162
|
+
/>
|
|
163
|
+
))}
|
|
164
|
+
|
|
165
|
+
{/* Line */}
|
|
166
|
+
<path
|
|
167
|
+
d={pathData}
|
|
168
|
+
fill="none"
|
|
169
|
+
stroke="#3498db"
|
|
170
|
+
strokeWidth="3"
|
|
171
|
+
/>
|
|
172
|
+
|
|
173
|
+
{/* Data points */}
|
|
174
|
+
{points.map((point, index) => (
|
|
175
|
+
<circle
|
|
176
|
+
key={index}
|
|
177
|
+
cx={point.x}
|
|
178
|
+
cy={point.y}
|
|
179
|
+
r="6"
|
|
180
|
+
fill={data[index].color || '#3498db'}
|
|
181
|
+
stroke="white"
|
|
182
|
+
strokeWidth="2"
|
|
183
|
+
style={{ cursor: 'pointer' }}
|
|
184
|
+
title={`${data[index].label}: ${data[index].value}`}
|
|
185
|
+
/>
|
|
186
|
+
))}
|
|
187
|
+
|
|
188
|
+
{/* Labels */}
|
|
189
|
+
{data.map((point, index) => (
|
|
190
|
+
<text
|
|
191
|
+
key={index}
|
|
192
|
+
x={padding + (index * (width - 2 * padding)) / (data.length - 1)}
|
|
193
|
+
y={height - 10}
|
|
194
|
+
textAnchor="middle"
|
|
195
|
+
fontSize="12"
|
|
196
|
+
fill="#7f8c8d"
|
|
197
|
+
>
|
|
198
|
+
{point.label}
|
|
199
|
+
</text>
|
|
200
|
+
))}
|
|
201
|
+
</svg>
|
|
202
|
+
</div>
|
|
203
|
+
)
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
const renderPieChart = () => {
|
|
207
|
+
const total = getTotalValue()
|
|
208
|
+
let currentAngle = 0
|
|
209
|
+
|
|
210
|
+
return (
|
|
211
|
+
<div style={{ padding: '20px' }}>
|
|
212
|
+
<h3 style={{ marginBottom: '20px', color: '#2c3e50' }}>Pie Chart</h3>
|
|
213
|
+
<div style={{ display: 'flex', gap: '40px', alignItems: 'center' }}>
|
|
214
|
+
<svg width="300" height="300" style={{ border: '1px solid #ecf0f1', borderRadius: '8px' }}>
|
|
215
|
+
{data.map((point, index) => {
|
|
216
|
+
const percentage = point.value / total
|
|
217
|
+
const angle = percentage * 360
|
|
218
|
+
const startAngle = currentAngle
|
|
219
|
+
const endAngle = currentAngle + angle
|
|
220
|
+
currentAngle += angle
|
|
221
|
+
|
|
222
|
+
const centerX = 150
|
|
223
|
+
const centerY = 150
|
|
224
|
+
const radius = 120
|
|
225
|
+
|
|
226
|
+
const startAngleRad = (startAngle - 90) * (Math.PI / 180)
|
|
227
|
+
const endAngleRad = (endAngle - 90) * (Math.PI / 180)
|
|
228
|
+
|
|
229
|
+
const x1 = centerX + radius * Math.cos(startAngleRad)
|
|
230
|
+
const y1 = centerY + radius * Math.sin(startAngleRad)
|
|
231
|
+
const x2 = centerX + radius * Math.cos(endAngleRad)
|
|
232
|
+
const y2 = centerY + radius * Math.sin(endAngleRad)
|
|
233
|
+
|
|
234
|
+
const largeArcFlag = angle > 180 ? 1 : 0
|
|
235
|
+
|
|
236
|
+
const pathData = [
|
|
237
|
+
`M ${centerX} ${centerY}`,
|
|
238
|
+
`L ${x1} ${y1}`,
|
|
239
|
+
`A ${radius} ${radius} 0 ${largeArcFlag} 1 ${x2} ${y2}`,
|
|
240
|
+
'Z',
|
|
241
|
+
].join(' ')
|
|
242
|
+
|
|
243
|
+
return (
|
|
244
|
+
<path
|
|
245
|
+
key={index}
|
|
246
|
+
d={pathData}
|
|
247
|
+
fill={point.color || '#3498db'}
|
|
248
|
+
stroke="white"
|
|
249
|
+
strokeWidth="2"
|
|
250
|
+
style={{ cursor: 'pointer' }}
|
|
251
|
+
title={`${point.label}: ${point.value} (${(percentage * 100).toFixed(1)}%)`}
|
|
252
|
+
/>
|
|
253
|
+
)
|
|
254
|
+
})}
|
|
255
|
+
</svg>
|
|
256
|
+
|
|
257
|
+
<div style={{ flex: '1' }}>
|
|
258
|
+
<h4 style={{ marginBottom: '15px', color: '#2c3e50' }}>Legend</h4>
|
|
259
|
+
{data.map((point, index) => (
|
|
260
|
+
<div
|
|
261
|
+
key={index}
|
|
262
|
+
style={{
|
|
263
|
+
display: 'flex',
|
|
264
|
+
alignItems: 'center',
|
|
265
|
+
marginBottom: '8px',
|
|
266
|
+
padding: '8px',
|
|
267
|
+
background: '#f8f9fa',
|
|
268
|
+
borderRadius: '4px',
|
|
269
|
+
}}
|
|
270
|
+
>
|
|
271
|
+
<div style={{
|
|
272
|
+
width: '16px',
|
|
273
|
+
height: '16px',
|
|
274
|
+
background: point.color || '#3498db',
|
|
275
|
+
borderRadius: '2px',
|
|
276
|
+
marginRight: '10px',
|
|
277
|
+
}}
|
|
278
|
+
/>
|
|
279
|
+
<span style={{ flex: '1', fontSize: '14px' }}>{point.label}</span>
|
|
280
|
+
<span style={{
|
|
281
|
+
fontSize: '14px',
|
|
282
|
+
fontWeight: 'bold',
|
|
283
|
+
color: '#2c3e50',
|
|
284
|
+
}}
|
|
285
|
+
>
|
|
286
|
+
{point.value}
|
|
287
|
+
</span>
|
|
288
|
+
</div>
|
|
289
|
+
))}
|
|
290
|
+
</div>
|
|
291
|
+
</div>
|
|
292
|
+
</div>
|
|
293
|
+
)
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
return (
|
|
297
|
+
<div style={{ padding: '20px' }}>
|
|
298
|
+
<div style={{ marginBottom: '30px' }}>
|
|
299
|
+
<h1 style={{ margin: '0 0 20px 0', color: '#2c3e50' }}>Data Visualization</h1>
|
|
300
|
+
|
|
301
|
+
{/* Controls */}
|
|
302
|
+
<div style={{
|
|
303
|
+
background: 'white',
|
|
304
|
+
padding: '20px',
|
|
305
|
+
borderRadius: '8px',
|
|
306
|
+
boxShadow: '0 2px 4px rgba(0,0,0,0.1)',
|
|
307
|
+
marginBottom: '20px',
|
|
308
|
+
}}
|
|
309
|
+
>
|
|
310
|
+
<div style={{ display: 'flex', gap: '20px', flexWrap: 'wrap', alignItems: 'center', marginBottom: '20px' }}>
|
|
311
|
+
<div>
|
|
312
|
+
<label style={{ marginRight: '10px', fontWeight: 'bold', color: '#2c3e50' }}>Chart Type:</label>
|
|
313
|
+
<select
|
|
314
|
+
value={currentChartType}
|
|
315
|
+
onChange={e => setCurrentChartType(e.target.value as typeof currentChartType)}
|
|
316
|
+
style={{
|
|
317
|
+
padding: '8px 12px',
|
|
318
|
+
border: '1px solid #ddd',
|
|
319
|
+
borderRadius: '4px',
|
|
320
|
+
}}
|
|
321
|
+
>
|
|
322
|
+
<option value="bar">Bar Chart</option>
|
|
323
|
+
<option value="line">Line Chart</option>
|
|
324
|
+
<option value="pie">Pie Chart</option>
|
|
325
|
+
</select>
|
|
326
|
+
</div>
|
|
327
|
+
</div>
|
|
328
|
+
|
|
329
|
+
<div style={{ display: 'flex', gap: '10px', flexWrap: 'wrap', alignItems: 'center' }}>
|
|
330
|
+
<input
|
|
331
|
+
type="text"
|
|
332
|
+
placeholder="Label"
|
|
333
|
+
value={newDataPoint.label}
|
|
334
|
+
onChange={e => setNewDataPoint({ ...newDataPoint, label: e.target.value })}
|
|
335
|
+
style={{
|
|
336
|
+
padding: '8px 12px',
|
|
337
|
+
border: '1px solid #ddd',
|
|
338
|
+
borderRadius: '4px',
|
|
339
|
+
minWidth: '120px',
|
|
340
|
+
}}
|
|
341
|
+
/>
|
|
342
|
+
<input
|
|
343
|
+
type="number"
|
|
344
|
+
placeholder="Value"
|
|
345
|
+
value={newDataPoint.value}
|
|
346
|
+
onChange={e => setNewDataPoint({ ...newDataPoint, value: Number(e.target.value) })}
|
|
347
|
+
style={{
|
|
348
|
+
padding: '8px 12px',
|
|
349
|
+
border: '1px solid #ddd',
|
|
350
|
+
borderRadius: '4px',
|
|
351
|
+
minWidth: '100px',
|
|
352
|
+
}}
|
|
353
|
+
/>
|
|
354
|
+
<button
|
|
355
|
+
onClick={addDataPoint}
|
|
356
|
+
style={{
|
|
357
|
+
padding: '8px 16px',
|
|
358
|
+
background: '#3498db',
|
|
359
|
+
color: 'white',
|
|
360
|
+
border: 'none',
|
|
361
|
+
borderRadius: '4px',
|
|
362
|
+
cursor: 'pointer',
|
|
363
|
+
}}
|
|
364
|
+
>
|
|
365
|
+
Add Data Point
|
|
366
|
+
</button>
|
|
367
|
+
</div>
|
|
368
|
+
</div>
|
|
369
|
+
|
|
370
|
+
{/* Chart */}
|
|
371
|
+
<div style={{
|
|
372
|
+
background: 'white',
|
|
373
|
+
borderRadius: '8px',
|
|
374
|
+
boxShadow: '0 2px 4px rgba(0,0,0,0.1)',
|
|
375
|
+
overflow: 'hidden',
|
|
376
|
+
}}
|
|
377
|
+
>
|
|
378
|
+
{data.length === 0
|
|
379
|
+
? (
|
|
380
|
+
<div style={{
|
|
381
|
+
textAlign: 'center',
|
|
382
|
+
padding: '40px 20px',
|
|
383
|
+
color: '#7f8c8d',
|
|
384
|
+
fontStyle: 'italic',
|
|
385
|
+
}}
|
|
386
|
+
>
|
|
387
|
+
No data to visualize. Add some data points above!
|
|
388
|
+
</div>
|
|
389
|
+
)
|
|
390
|
+
: (
|
|
391
|
+
<>
|
|
392
|
+
{currentChartType === 'bar' && renderBarChart()}
|
|
393
|
+
{currentChartType === 'line' && renderLineChart()}
|
|
394
|
+
{currentChartType === 'pie' && renderPieChart()}
|
|
395
|
+
</>
|
|
396
|
+
)}
|
|
397
|
+
</div>
|
|
398
|
+
|
|
399
|
+
{/* Data table */}
|
|
400
|
+
{data.length > 0 && (
|
|
401
|
+
<div style={{
|
|
402
|
+
background: 'white',
|
|
403
|
+
borderRadius: '8px',
|
|
404
|
+
boxShadow: '0 2px 4px rgba(0,0,0,0.1)',
|
|
405
|
+
marginTop: '20px',
|
|
406
|
+
overflow: 'hidden',
|
|
407
|
+
}}
|
|
408
|
+
>
|
|
409
|
+
<div style={{ padding: '20px', borderBottom: '1px solid #ecf0f1' }}>
|
|
410
|
+
<h3 style={{ margin: '0', color: '#2c3e50' }}>Data Table</h3>
|
|
411
|
+
</div>
|
|
412
|
+
<div style={{ overflowX: 'auto' }}>
|
|
413
|
+
<table style={{ width: '100%', borderCollapse: 'collapse' }}>
|
|
414
|
+
<thead>
|
|
415
|
+
<tr style={{ background: '#f8f9fa' }}>
|
|
416
|
+
<th style={{ padding: '12px', textAlign: 'left', borderBottom: '1px solid #ecf0f1' }}>Label</th>
|
|
417
|
+
<th style={{ padding: '12px', textAlign: 'left', borderBottom: '1px solid #ecf0f1' }}>Value</th>
|
|
418
|
+
<th style={{ padding: '12px', textAlign: 'left', borderBottom: '1px solid #ecf0f1' }}>Percentage</th>
|
|
419
|
+
<th style={{ padding: '12px', textAlign: 'left', borderBottom: '1px solid #ecf0f1' }}>Color</th>
|
|
420
|
+
<th style={{ padding: '12px', textAlign: 'left', borderBottom: '1px solid #ecf0f1' }}>Actions</th>
|
|
421
|
+
</tr>
|
|
422
|
+
</thead>
|
|
423
|
+
<tbody>
|
|
424
|
+
{data.map((point, index) => (
|
|
425
|
+
<tr key={index} style={{ borderBottom: '1px solid #ecf0f1' }}>
|
|
426
|
+
<td style={{ padding: '12px' }}>{point.label}</td>
|
|
427
|
+
<td style={{ padding: '12px', fontWeight: 'bold' }}>{point.value}</td>
|
|
428
|
+
<td style={{ padding: '12px' }}>
|
|
429
|
+
{((point.value / getTotalValue()) * 100).toFixed(1)}
|
|
430
|
+
%
|
|
431
|
+
</td>
|
|
432
|
+
<td style={{ padding: '12px' }}>
|
|
433
|
+
<div style={{
|
|
434
|
+
width: '20px',
|
|
435
|
+
height: '20px',
|
|
436
|
+
background: point.color || '#3498db',
|
|
437
|
+
borderRadius: '2px',
|
|
438
|
+
display: 'inline-block',
|
|
439
|
+
}}
|
|
440
|
+
/>
|
|
441
|
+
</td>
|
|
442
|
+
<td style={{ padding: '12px' }}>
|
|
443
|
+
<button
|
|
444
|
+
onClick={() => removeDataPoint(index)}
|
|
445
|
+
style={{
|
|
446
|
+
background: '#e74c3c',
|
|
447
|
+
color: 'white',
|
|
448
|
+
border: 'none',
|
|
449
|
+
borderRadius: '4px',
|
|
450
|
+
padding: '4px 8px',
|
|
451
|
+
cursor: 'pointer',
|
|
452
|
+
fontSize: '12px',
|
|
453
|
+
}}
|
|
454
|
+
>
|
|
455
|
+
Remove
|
|
456
|
+
</button>
|
|
457
|
+
</td>
|
|
458
|
+
</tr>
|
|
459
|
+
))}
|
|
460
|
+
</tbody>
|
|
461
|
+
</table>
|
|
462
|
+
</div>
|
|
463
|
+
</div>
|
|
464
|
+
)}
|
|
465
|
+
</div>
|
|
466
|
+
</div>
|
|
467
|
+
)
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
// Mount the component
|
|
471
|
+
const container = document.getElementById('widget-root')
|
|
472
|
+
if (container) {
|
|
473
|
+
const root = createRoot(container)
|
|
474
|
+
root.render(<DataVisualization />)
|
|
475
|
+
}
|