Skip to content

Commit 6b1035e

Browse files
Merge pull request #5 from anedyaio/dev
add DataTable widget
2 parents 4e59897 + a0d22c6 commit 6b1035e

8 files changed

Lines changed: 880 additions & 65 deletions

File tree

src/components/GaugeChart.tsx

Lines changed: 67 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -22,12 +22,12 @@ const GaugeChart: React.FC<GaugeChartProps> = ({
2222
}) => {
2323
// Clamp value to ensure it stays within range
2424
const clampedValue = Math.min(max, Math.max(min, value));
25-
25+
2626
//, ... (existing code)
27-
27+
2828
// Calculate percentage (0 to 1) for needle position
2929
const normalizedValue = (clampedValue - min) / (max - min);
30-
30+
3131
// Angle for needle: -90 (left) to 90 (right)
3232
const angle = normalizedValue * 180 - 90;
3333

@@ -51,6 +51,17 @@ const GaugeChart: React.FC<GaugeChartProps> = ({
5151
};
5252
};
5353

54+
// Helper for dynamic precision based on step size
55+
const getPrecision = (num: number) => {
56+
if (Number.isInteger(num)) return 0;
57+
const str = num.toString();
58+
const decimalPart = str.split('.')[1] || '';
59+
return Math.min(decimalPart.length, 3); // Cap at 3 decimals to avoid floating point issues
60+
};
61+
62+
const step = (max - min) / 4;
63+
const precision = getPrecision(step);
64+
5465
// Generate ticks
5566
const ticks = [0, 0.25, 0.5, 0.75, 1].map((percent) => {
5667
const val = min + (max - min) * percent;
@@ -69,7 +80,7 @@ const GaugeChart: React.FC<GaugeChartProps> = ({
6980
y2: end.y,
7081
tx: textPos.x,
7182
ty: textPos.y,
72-
value: Math.round(val),
83+
value: parseFloat(val.toFixed(precision)),
7384
angle: tickAngle
7485
};
7586
});
@@ -81,7 +92,7 @@ const GaugeChart: React.FC<GaugeChartProps> = ({
8192
if (val <= 0) return "text-blue-500";
8293
if (val < 40) return "text-cyan-600";
8394
if (val < 70) return "text-orange-500";
84-
return "text-red-600";
95+
return "text-red-600";
8596
};
8697

8798
const formatValue = (v: number) => {
@@ -101,7 +112,7 @@ const GaugeChart: React.FC<GaugeChartProps> = ({
101112
<stop offset="100%" stopColor="#ef4444" /> {/* Red */}
102113
</linearGradient>
103114
<filter id="needleShadow" x="-50%" y="-50%" width="200%" height="200%">
104-
<feDropShadow dx="1" dy="1" stdDeviation="2" floodOpacity="0.3"/>
115+
<feDropShadow dx="1" dy="1" stdDeviation="2" floodOpacity="0.3" />
105116
</filter>
106117
</defs>
107118

@@ -117,33 +128,33 @@ const GaugeChart: React.FC<GaugeChartProps> = ({
117128

118129
{/* Colored Gradient Arc */}
119130
<path
120-
d="M 30 100 A 70 70 0 0 1 170 100"
121-
fill="none"
122-
stroke="url(#coldToHotGradient)"
123-
strokeWidth={strokeWidth}
124-
strokeLinecap="round"
131+
d="M 30 100 A 70 70 0 0 1 170 100"
132+
fill="none"
133+
stroke="url(#coldToHotGradient)"
134+
strokeWidth={strokeWidth}
135+
strokeLinecap="round"
125136
/>
126137

127138
{/* Ticks & Labels */}
128139
{ticks.map((tick, i) => (
129-
<g key={i}>
130-
<line
131-
x1={tick.x1} y1={tick.y1}
132-
x2={tick.x2} y2={tick.y2}
133-
stroke="currentColor"
134-
strokeWidth="1.5"
135-
className="text-muted-foreground/50"
136-
/>
137-
<text
138-
x={tick.tx}
139-
y={tick.ty}
140-
textAnchor="middle"
141-
alignmentBaseline="middle"
142-
className="text-[10px] fill-muted-foreground font-medium"
143-
>
144-
{tick.value}
145-
</text>
146-
</g>
140+
<g key={i}>
141+
<line
142+
x1={tick.x1} y1={tick.y1}
143+
x2={tick.x2} y2={tick.y2}
144+
stroke="currentColor"
145+
strokeWidth="1.5"
146+
className="text-muted-foreground/50"
147+
/>
148+
<text
149+
x={tick.tx}
150+
y={tick.ty}
151+
textAnchor="middle"
152+
alignmentBaseline="middle"
153+
className="text-[10px] fill-muted-foreground font-medium"
154+
>
155+
{tick.value}
156+
</text>
157+
</g>
147158
))}
148159

149160
{/* Needle Layer */}
@@ -154,41 +165,41 @@ const GaugeChart: React.FC<GaugeChartProps> = ({
154165
transition: "transform 1.2s cubic-bezier(0.4, 0, 0.2, 1)",
155166
}}
156167
>
157-
{/* Main triangular needle */}
158-
<path
159-
d="M 100 110 L 95 100 L 100 40 L 105 100 Z"
160-
fill="currentColor"
161-
className="text-foreground"
162-
filter="url(#needleShadow)"
163-
/>
164-
{/* Center Pivot */}
168+
{/* Main triangular needle */}
169+
<path
170+
d="M 100 110 L 95 100 L 100 40 L 105 100 Z"
171+
fill="currentColor"
172+
className="text-foreground"
173+
filter="url(#needleShadow)"
174+
/>
175+
{/* Center Pivot */}
165176
<circle cx="100" cy="100" r="5" className="fill-foreground" />
166177
<circle cx="100" cy="100" r="2" className="fill-background" />
167178
</g>
168179
</svg>
169180
</div>
170-
181+
171182
{/* Detail Value Display */}
172183
{showValue && (
173184
<div className="-mt-8 text-center flex flex-col items-center z-10 relative">
174-
<div className="flex items-baseline gap-1">
175-
<span className={`text-4xl font-bold tracking-tight ${getValueColor(value)}`}>
176-
{formatValue(value)}
177-
</span>
178-
<span className="text-sm font-bold text-muted-foreground/80 mb-1">{unit}</span>
179-
</div>
180-
181-
{timestamp && timestamp > 0 && (
182-
<span className="text-xs text-muted-foreground/60 mt-1">
183-
{new Date(timestamp * 1000).toLocaleString(undefined, {
184-
month: "short",
185-
day: "numeric",
186-
hour: "2-digit",
187-
minute: "2-digit",
188-
second: "2-digit",
189-
})}
190-
</span>
191-
)}
185+
<div className="flex items-baseline gap-1">
186+
<span className={`text-4xl font-bold tracking-tight ${getValueColor(value)}`}>
187+
{formatValue(value)}
188+
</span>
189+
<span className="text-sm font-bold text-muted-foreground/80 mb-1">{unit}</span>
190+
</div>
191+
192+
{timestamp && timestamp > 0 && (
193+
<span className="text-xs text-muted-foreground/60 mt-1">
194+
{new Date(timestamp * 1000).toLocaleString(undefined, {
195+
month: "short",
196+
day: "numeric",
197+
hour: "2-digit",
198+
minute: "2-digit",
199+
second: "2-digit",
200+
})}
201+
</span>
202+
)}
192203
</div>
193204
)}
194205
</div>

src/components/dashboard-builder/ComponentSidebar.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import React from 'react';
22
import { Card, CardContent } from '@/components/ui/card';
3-
import { Gauge, LineChart, Hash, Settings, PieChart, ToggleRight, Battery, SlidersHorizontal, Cylinder, Activity, Map as MapIcon, Video, BarChart2, Target } from 'lucide-react';
3+
import { Gauge, LineChart, Hash, Settings, PieChart, ToggleRight, Battery, SlidersHorizontal, Cylinder, Activity, Map as MapIcon, Video, BarChart2, Target, Table } from 'lucide-react';
44
import { useBuilderStore } from '../../store/useBuilderStore';
55
import { WIDGET_SIZE_CONSTRAINTS } from './widgetConfig';
66

@@ -19,6 +19,7 @@ const WIDGET_TYPES = [
1919
{ id: 'SparklineWidget', name: 'Sparkline Trend', icon: <Activity size={18} /> },
2020
{ id: 'MapWidget', name: 'Map', icon: <MapIcon size={18} /> },
2121
{ id: 'CameraViewerWidget', name: 'Camera Viewer', icon: <Video size={18} /> },
22+
{ id: 'DataTableWidget', name: 'Data Table', icon: <Table size={18} /> },
2223
];
2324

2425
export default function ComponentSidebar() {

0 commit comments

Comments
 (0)