From bcb7b41976bede434274da38cea0a02bfd2b85b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20St=C3=BCrz?= Date: Thu, 11 Dec 2025 13:54:25 +0100 Subject: [PATCH] First csv download --- dashboard/app.js | 86 ++++++++++++++++++++++++++++++++++++++++++++ dashboard/index.html | 1 + 2 files changed, 87 insertions(+) diff --git a/dashboard/app.js b/dashboard/app.js index 721d3e4..a7301f4 100644 --- a/dashboard/app.js +++ b/dashboard/app.js @@ -17,6 +17,7 @@ class DashboardApp { chargerTableBody: document.getElementById('chargerTableBody'), chargerEmptyRow: document.getElementById('chargerEmptyRow'), fetchSessionsButton: document.getElementById('fetchSessionsButton'), + downloadSessionsButton: document.getElementById('downloadSessionsButton'), chargerFilter: document.getElementById('chargerFilter'), chargingSessionsTableBody: document.getElementById('chargingSessionsTableBody'), chargingSessionsEmptyRow: document.getElementById('chargingSessionsEmptyRow'), @@ -79,6 +80,12 @@ class DashboardApp { this.fetchChargingSessions(); }); } + + if (this.elements.downloadSessionsButton) { + this.elements.downloadSessionsButton.addEventListener('click', () => { + this.downloadChargingSessionsCsv(); + }); + } } initializePanelNavigation() { @@ -1005,6 +1012,85 @@ class DashboardApp { return end - start; } + downloadChargingSessionsCsv() { + const sessions = Array.isArray(this.sessions) ? this.sessions : []; + if (!sessions.length) { + console.warn('No charging sessions to download.'); + return; + } + + const csvContent = this.buildSessionsCsv(sessions); + if (!csvContent) { + console.warn('Failed to build CSV for charging sessions.'); + return; + } + + const blob = new Blob([csvContent], { type: 'text/csv;charset=utf-8;' }); + const url = URL.createObjectURL(blob); + const link = document.createElement('a'); + link.href = url; + link.download = `charging-sessions-${new Date().toISOString().slice(0, 10)}.csv`; + document.body.appendChild(link); + link.click(); + setTimeout(() => { + document.body.removeChild(link); + URL.revokeObjectURL(url); + }, 0); + } + + buildSessionsCsv(sessions) { + if (!Array.isArray(sessions) || !sessions.length) + return ''; + + const headers = this.collectSessionColumns(sessions); + if (!headers.length) + return ''; + + const lines = []; + lines.push(headers.join(';')); + sessions.forEach(session => { + const row = headers.map(header => this.escapeCsvValue(session ? session[header] : '')); + lines.push(row.join(';')); + }); + + return lines.join('\n'); + } + + collectSessionColumns(sessions) { + const headers = []; + sessions.forEach(session => { + if (!session) + return; + Object.keys(session).forEach(key => { + if (!headers.includes(key)) + headers.push(key); + }); + }); + return headers; + } + + escapeCsvValue(value) { + if (value === null || value === undefined) + return ''; + + if (typeof value === 'object') { + try { + value = JSON.stringify(value); + } catch (error) { + value = String(value); + } + } + + let stringValue = String(value); + if (stringValue.includes('"')) + stringValue = stringValue.replace(/"/g, '""'); + + if (stringValue.search(/[;\n"]/g) !== -1) + stringValue = `"${stringValue}"`; + + return stringValue; + } + updateConnectionStatus(text, state) { if (this.elements.connectionStatus) this.elements.connectionStatus.textContent = text; diff --git a/dashboard/index.html b/dashboard/index.html index 5e75774..627b6bb 100644 --- a/dashboard/index.html +++ b/dashboard/index.html @@ -692,6 +692,7 @@ +

Select a charger to filter by its assigned car and fetch sessions from nymea.