From a1793d09982f410c525fa7f5a98e1e478bfe3e20 Mon Sep 17 00:00:00 2001 From: sebseb7 Date: Thu, 25 Dec 2025 02:25:25 +0100 Subject: [PATCH] u --- uiserver/src/components/Chart.js | 25 +++++++++++++- uiserver/src/components/ViewManager.js | 46 +++++++++++++++++++++----- uiserver/webpack.config.js | 12 +++++-- 3 files changed, 70 insertions(+), 13 deletions(-) diff --git a/uiserver/src/components/Chart.js b/uiserver/src/components/Chart.js index fb29074..692622c 100644 --- a/uiserver/src/components/Chart.js +++ b/uiserver/src/components/Chart.js @@ -99,7 +99,9 @@ export default class Chart extends Component { for (let i = 0; i < points.length; i++) { const [tsStr, rawVal, untilStr] = points[i]; - const val = Number(rawVal); + const numVal = Number(rawVal); + // MUI-X charts only accepts numbers and null - NaN causes errors + const val = Number.isNaN(numVal) ? null : numVal; let start = new Date(tsStr).getTime(); let explicitEnd = untilStr ? new Date(untilStr).getTime() : null; @@ -230,12 +232,14 @@ export default class Chart extends Component { let label = id; let yAxisKey = 'left'; let color = undefined; + let fillColor = undefined; if (channelConfig) { const item = channelConfig.find(c => c.id === id); if (item) { if (item.alias) label = item.alias; if (item.yAxis) yAxisKey = item.yAxis; if (item.color) color = item.color; + if (item.fillColor) fillColor = item.fillColor; } } @@ -247,6 +251,16 @@ export default class Chart extends Component { yAxisKey: yAxisKey, }; if (color) sObj.color = color; + // Enable area fill if fillColor is set (with 50% transparency) + if (fillColor) { + sObj.area = true; + // Convert hex to rgba with 50% opacity + const hex = fillColor.replace('#', ''); + const r = parseInt(hex.substring(0, 2), 16); + const g = parseInt(hex.substring(2, 4), 16); + const b = parseInt(hex.substring(4, 6), 16); + sObj.areaColor = `rgba(${r}, ${g}, ${b}, 0.5)`; + } return sObj; }); @@ -289,6 +303,15 @@ export default class Chart extends Component { position: { vertical: 'top', horizontal: 'middle' }, padding: 0, }, + lineHighlight: { strokeWidth: 3 }, + }} + sx={{ + '& .MuiLineElement-root': { + strokeWidth: 3, + }, + '& .MuiAreaElement-root': { + fillOpacity: 0.5, + }, }} /> diff --git a/uiserver/src/components/ViewManager.js b/uiserver/src/components/ViewManager.js index fe89796..c072b5b 100644 --- a/uiserver/src/components/ViewManager.js +++ b/uiserver/src/components/ViewManager.js @@ -44,6 +44,7 @@ class ViewManager extends Component { views: [], open: false, colorPickerOpen: false, + colorPickerMode: 'line', editingId: null, viewName: '', availableDevices: [], @@ -183,7 +184,7 @@ class ViewManager extends Component { const { user } = this.props; try { - await fetch('/api/views/reorder', { + const res = await fetch('/api/views/reorder', { method: 'POST', headers: { 'Content-Type': 'application/json', @@ -191,6 +192,10 @@ class ViewManager extends Component { }, body: JSON.stringify({ order }) }); + if (!res.ok) { + const err = await res.json(); + console.error("Failed to save order:", err); + } } catch (err) { console.error("Failed to save order", err); } @@ -276,19 +281,33 @@ class ViewManager extends Component { })); }; - openColorPicker = (idx) => { - this.setState({ colorPickerOpen: true, pickerTargetIndex: idx }); + openColorPicker = (idx, mode = 'line') => { + this.setState({ colorPickerOpen: true, pickerTargetIndex: idx, colorPickerMode: mode }); }; selectColor = (color) => { - const { pickerTargetIndex, viewConfig } = this.state; + const { pickerTargetIndex, viewConfig, colorPickerMode } = this.state; if (pickerTargetIndex !== null) { - const newConfig = [...viewConfig]; - newConfig[pickerTargetIndex].color = color; + const newConfig = viewConfig.map((ch, i) => { + if (i === pickerTargetIndex) { + if (colorPickerMode === 'fill') { + return { ...ch, fillColor: color }; + } else { + return { ...ch, color: color }; + } + } + return ch; + }); this.setState({ viewConfig: newConfig, colorPickerOpen: false, pickerTargetIndex: null }); } }; + clearFillColor = (idx) => { + const newConfig = [...this.state.viewConfig]; + delete newConfig[idx].fillColor; + this.setState({ viewConfig: newConfig }); + }; + handleRangeChange = (e, newVal) => { if (newVal) this.setState({ rangeLabel: newVal }); }; @@ -371,7 +390,8 @@ class ViewManager extends Component { id: `${c.device}:${c.channel}`, alias: c.alias, yAxis: c.yAxis || 'left', - color: c.color + color: c.color, + fillColor: c.fillColor }))} axisConfig={axes} windowEnd={windowEnd} @@ -413,9 +433,17 @@ class ViewManager extends Component { {viewConfig.map((ch, idx) => ( - this.openColorPicker(idx)}> - + this.openColorPicker(idx, 'line')} title="Line color"> + + this.openColorPicker(idx, 'fill')} title="Fill color (area)"> + + + {ch.fillColor && ( + this.clearFillColor(idx)} title="Remove fill" sx={{ ml: -0.5 }}> + + + )} { try { - const stmt = db.prepare('SELECT * FROM views ORDER BY name'); + const stmt = db.prepare('SELECT * FROM views ORDER BY position ASC, id ASC'); const rows = stmt.all(); const views = rows.map(row => { try { @@ -148,7 +148,7 @@ module.exports = { app.get('/api/views/:id', (req, res) => { try { - const stmt = db.prepare('SELECT * FROM views ORDER BY position ASC, id ASC'); + const stmt = db.prepare('SELECT * FROM views WHERE id = ?'); const view = stmt.get(req.params.id); if (view) { view.config = JSON.parse(view.config); @@ -195,17 +195,23 @@ module.exports = { // Reorder Views app.post('/api/views/reorder', requireAdmin, (req, res) => { const { order } = req.body; + console.log('[API] Reorder request:', order); if (!Array.isArray(order)) return res.status(400).json({ error: 'Invalid format' }); const updateStmt = db.prepare('UPDATE views SET position = ? WHERE id = ?'); const updateMany = db.transaction((items) => { - for (const item of items) updateStmt.run(item.position, item.id); + for (const item of items) { + console.log('[API] Updating view', item.id, 'to position', item.position); + updateStmt.run(item.position, item.id); + } }); try { updateMany(order); + console.log('[API] Reorder successful'); res.json({ success: true }); } catch (err) { + console.error('[API] Reorder error:', err); res.status(500).json({ error: err.message }); } });