- {zones.map(zone => (
-
- ))}
+
+
+
Zone Groups
+ {Object.keys(grouping).length === 0 ? (
+
No active groups. Click π to create a group.
+ ) : (
+
+ {Object.entries(grouping).map(([groupId, members]) => {
+ const groupZones = [groupId, ...members].map(id => zones.find(z => z.id === id))
+ return (
+
+
+ {groupZones.map(z => z?.name).join(' + ')}
+
+
+
+ )
+ })}
+
+ )}
+
+
+
+ {zones.map(zone => (
+ handleSourceChange(id, source)}
+ onGroup={handleGroupToggle}
+ groupedWith={getGroupedWith(zone.id)}
+ />
+ ))}
+
)
}
const styles = {
+ container: {
+ display: 'grid',
+ gridTemplateColumns: '240px 1fr',
+ gap: 16,
+ height: '100%',
+ },
+ groupingPanel: {
+ background: 'var(--surface)',
+ borderRadius: 'var(--radius)',
+ border: '1px solid var(--border)',
+ padding: 12,
+ display: 'flex',
+ flexDirection: 'column',
+ gap: 10,
+ maxHeight: '100vh',
+ overflowY: 'auto',
+ },
+ panelTitle: {
+ fontSize: 12,
+ fontWeight: 700,
+ color: 'var(--muted)',
+ margin: '0 0 8px 0',
+ textTransform: 'uppercase',
+ letterSpacing: 0.5,
+ },
+ emptyText: {
+ fontSize: 11,
+ color: 'var(--muted)',
+ textAlign: 'center',
+ padding: '12px 0',
+ },
+ groupList: {
+ display: 'flex',
+ flexDirection: 'column',
+ gap: 6,
+ },
+ groupListItem: {
+ background: '#38bdf811',
+ border: '1px solid var(--accent)',
+ borderRadius: 4,
+ padding: '8px 10px',
+ display: 'flex',
+ justifyContent: 'space-between',
+ alignItems: 'center',
+ },
+ groupName: {
+ fontSize: 11,
+ fontWeight: 600,
+ color: 'var(--accent)',
+ flex: 1,
+ },
+ ungroupBtn: {
+ background: 'none',
+ border: 'none',
+ color: 'var(--accent)',
+ cursor: 'pointer',
+ fontSize: 14,
+ padding: '0 4px',
+ minWidth: 24,
+ minHeight: 24,
+ },
grid: {
display: 'grid',
gridTemplateColumns: 'repeat(auto-fill, minmax(220px, 1fr))',
gap: 12,
+ overflowY: 'auto',
}
}
diff --git a/dashboard/src/components/nav/ChartPlaceholder.jsx b/dashboard/src/components/nav/ChartPlaceholder.jsx
index 7f63aa8..27a7c51 100644
--- a/dashboard/src/components/nav/ChartPlaceholder.jsx
+++ b/dashboard/src/components/nav/ChartPlaceholder.jsx
@@ -11,6 +11,17 @@ export default function ChartPlaceholder() {
const chartUrl = `http://${signalkHost}:3000/@signalk/freeboard-sk/`
const isMock = import.meta.env.VITE_USE_MOCK === 'true'
+ // Colors from CSS (hardcoded for canvas compatibility)
+ const colors = {
+ bg: '#07111f',
+ surface: '#0a1928',
+ border: '#1e2a3a',
+ text: '#e2eaf2',
+ muted: '#4a6080',
+ accent: '#38bdf8',
+ warning: '#f59e0b',
+ }
+
// Draw track and waypoints
useEffect(() => {
const canvas = canvasRef.current
@@ -68,9 +79,9 @@ export default function ChartPlaceholder() {
})
// Clear and draw background
- ctx.fillStyle = 'var(--surface)'
+ ctx.fillStyle = colors.surface
ctx.fillRect(0, 0, width, height)
- ctx.strokeStyle = 'var(--border)'
+ ctx.strokeStyle = colors.border
ctx.lineWidth = 1
ctx.strokeRect(0, 0, width, height)
@@ -79,14 +90,14 @@ export default function ChartPlaceholder() {
const p = project(wp.lat, wp.lon)
// Waypoint circle
- ctx.fillStyle = idx === snapshot?.currentWaypoint ? 'var(--accent)' : 'var(--warning)'
+ ctx.fillStyle = idx === snapshot?.currentWaypoint ? colors.accent : colors.warning
ctx.beginPath()
ctx.arc(p.x, p.y, 6, 0, Math.PI * 2)
ctx.fill()
// Waypoint number
- ctx.fillStyle = 'var(--bg)'
- ctx.font = 'bold 10px var(--font-mono)'
+ ctx.fillStyle = colors.bg
+ ctx.font = 'bold 10px monospace'
ctx.textAlign = 'center'
ctx.textBaseline = 'middle'
ctx.fillText(idx + 1, p.x, p.y)
@@ -94,7 +105,7 @@ export default function ChartPlaceholder() {
// Draw track line
if (trackRef.current.length > 1) {
- ctx.strokeStyle = 'var(--accent)'
+ ctx.strokeStyle = colors.accent
ctx.lineWidth = 2
ctx.beginPath()
trackRef.current.forEach((p, idx) => {
@@ -116,7 +127,7 @@ export default function ChartPlaceholder() {
ctx.save()
ctx.translate(p.x, p.y)
ctx.rotate(headRad)
- ctx.fillStyle = 'var(--accent)'
+ ctx.fillStyle = colors.accent
ctx.beginPath()
ctx.moveTo(0, -size)
ctx.lineTo(-size / 2, size / 2)
@@ -126,7 +137,7 @@ export default function ChartPlaceholder() {
ctx.restore()
// Ship circle
- ctx.strokeStyle = 'var(--accent)'
+ ctx.strokeStyle = colors.accent
ctx.lineWidth = 2
ctx.beginPath()
ctx.arc(p.x, p.y, 8, 0, Math.PI * 2)