Skip to content

Commit 00d3b88

Browse files
feat: add MapWidget with theme-aware Leaflet styling and configuration support
1 parent ed92d47 commit 00d3b88

8 files changed

Lines changed: 780 additions & 153 deletions

File tree

package-lock.json

Lines changed: 161 additions & 149 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,19 +48,22 @@
4848
"@radix-ui/react-tooltip": "^1.2.7",
4949
"@supabase/supabase-js": "^2.90.1",
5050
"@tanstack/react-query": "^5.83.0",
51+
"@types/leaflet": "^1.9.21",
5152
"class-variance-authority": "^0.7.1",
5253
"clsx": "^2.1.1",
5354
"cmdk": "^1.1.1",
5455
"date-fns": "^3.6.0",
5556
"embla-carousel-react": "^8.6.0",
5657
"input-otp": "^1.4.2",
58+
"leaflet": "^1.9.4",
5759
"lucide-react": "^0.462.0",
5860
"next-themes": "^0.3.0",
5961
"react": "^18.3.1",
6062
"react-day-picker": "^8.10.1",
6163
"react-dom": "^18.3.1",
6264
"react-grid-layout": "^2.2.3",
6365
"react-hook-form": "^7.61.1",
66+
"react-leaflet": "^4.2.1",
6467
"react-resizable-panels": "^2.1.9",
6568
"react-router-dom": "^6.30.1",
6669
"recharts": "^2.15.4",

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 } from 'lucide-react';
3+
import { Gauge, LineChart, Hash, Settings, PieChart, ToggleRight, Battery, SlidersHorizontal, Cylinder, Activity, Map as MapIcon } from 'lucide-react';
44
import { useBuilderStore } from '../../store/useBuilderStore';
55
import { WIDGET_SIZE_CONSTRAINTS } from './widgetConfig';
66

@@ -15,6 +15,7 @@ const WIDGET_TYPES = [
1515
{ id: 'SliderWidget', name: 'Slider', icon: <SlidersHorizontal size={18} /> },
1616
{ id: 'TankWidget', name: 'Tank Level', icon: <Cylinder size={18} /> },
1717
{ id: 'SparklineWidget', name: 'Sparkline Trend', icon: <Activity size={18} /> },
18+
{ id: 'MapWidget', name: 'Map', icon: <MapIcon size={18} /> },
1819
];
1920

2021
export default function ComponentSidebar() {

src/components/dashboard-builder/PropertiesPanel.tsx

Lines changed: 57 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { Input } from '@/components/ui/input';
44
import { Label } from '@/components/ui/label';
55
import { Button } from '@/components/ui/button';
66
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
7+
import { Switch } from '@/components/ui/switch';
78
import { Plus, X, Palette, ChevronDown, ChevronUp } from 'lucide-react';
89
import type { ValueMapping } from './widgets/ValueDisplayWidget';
910
import type { DonutSeries } from './widgets/DonutChartWidget';
@@ -70,14 +71,14 @@ export default function PropertiesPanel() {
7071
<div className="space-y-2">
7172
<Label>Data Source</Label>
7273
<Select
73-
value={draftConfig.dataSource || (['HistoricalTrendWidget', 'SparklineWidget', 'GaugeWidget'].includes(widget.type) ? 'variable' : 'valuestore')}
74+
value={draftConfig.dataSource || (['HistoricalTrendWidget', 'SparklineWidget', 'GaugeWidget', 'MapWidget'].includes(widget.type) ? 'variable' : 'valuestore')}
7475
onValueChange={(val) => handleConfigChange({ dataSource: val })}
7576
>
7677
<SelectTrigger>
7778
<SelectValue placeholder="Select data source" />
7879
</SelectTrigger>
7980
<SelectContent>
80-
{!['HistoricalTrendWidget', 'SparklineWidget', 'GaugeWidget'].includes(widget.type) && (
81+
{!['HistoricalTrendWidget', 'SparklineWidget', 'GaugeWidget', 'MapWidget'].includes(widget.type) && (
8182
<SelectItem value="valuestore">Valuestore</SelectItem>
8283
)}
8384
{!['ToggleSwitchWidget', 'SliderWidget'].includes(widget.type) && (
@@ -88,7 +89,7 @@ export default function PropertiesPanel() {
8889
</div>
8990
<div className="space-y-2">
9091
<Label>
91-
{ (draftConfig.dataSource === 'variable' || (['HistoricalTrendWidget', 'SparklineWidget', 'GaugeWidget'].includes(widget.type) && !draftConfig.dataSource))
92+
{ (draftConfig.dataSource === 'variable' || (['HistoricalTrendWidget', 'SparklineWidget', 'GaugeWidget', 'MapWidget'].includes(widget.type) && !draftConfig.dataSource))
9293
? 'Variable Identifier' : 'Key' }
9394
</Label>
9495
<Input
@@ -288,6 +289,59 @@ export default function PropertiesPanel() {
288289
</div>
289290
)}
290291

292+
{widget.type === 'MapWidget' && (
293+
<div className="space-y-4">
294+
<div className="space-y-2">
295+
<Label title="Maximum number of points to fetch">Data Limit</Label>
296+
<Input
297+
type="number"
298+
placeholder="1000"
299+
className="h-8"
300+
min={1}
301+
max={10000}
302+
value={draftConfig.limit || ''}
303+
onChange={(e) => {
304+
const val = e.target.value ? Number(e.target.value) : undefined;
305+
handleConfigChange({ limit: val !== undefined ? Math.min(val, 10000) : undefined });
306+
}}
307+
/>
308+
<p className="text-[10px] text-muted-foreground italic">
309+
Max: 10,000 points.
310+
</p>
311+
</div>
312+
<div className="space-y-2">
313+
<Label>Path Color</Label>
314+
<div className="flex gap-2 items-center">
315+
<Input
316+
type="color"
317+
value={draftConfig.pathColor || '#2563eb'}
318+
onChange={(e) => handleConfigChange({ pathColor: e.target.value })}
319+
className="w-12 h-8 p-1 cursor-pointer"
320+
/>
321+
<Input
322+
type="text"
323+
placeholder="#2563eb"
324+
value={draftConfig.pathColor || '#2563eb'}
325+
onChange={(e) => handleConfigChange({ pathColor: e.target.value })}
326+
className="flex-1 h-8"
327+
/>
328+
</div>
329+
</div>
330+
<div className="flex items-center justify-between py-2 border-t mt-2">
331+
<div className="space-y-0.5">
332+
<Label className="text-xs">Show History Table</Label>
333+
<p className="text-[10px] text-muted-foreground">
334+
Display location history table next to map.
335+
</p>
336+
</div>
337+
<Switch
338+
checked={draftConfig.showTable || false}
339+
onCheckedChange={(checked) => handleConfigChange({ showTable: checked })}
340+
/>
341+
</div>
342+
</div>
343+
)}
344+
291345
{widget.type === 'ValueDisplayWidget' && (
292346
<div className="space-y-4">
293347
<div className="space-y-2">

src/components/dashboard-builder/widgetConfig.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ export const WIDGET_SIZE_CONSTRAINTS: Record<string, WidgetSizeConfig> = {
2222
SliderWidget: { minW: 3, minH: 1, maxW: 12, maxH: 4, defaultW: 4, defaultH: 1 },
2323
TankWidget: { minW: 2, minH: 2, maxW: 8, maxH: 12, defaultW: 2.5, defaultH: 2 },
2424
SparklineWidget: { minW: 1, minH: 1, maxW: 12, maxH: 12, defaultW: 2.5, defaultH: 1.5 },
25+
MapWidget: { minW: 4, minH: 3, maxW: 24, maxH: 24, defaultW: 6, defaultH: 4 },
2526
};
2627

2728
/** Returns constraints for a widget type, with safe fallback */

0 commit comments

Comments
 (0)