Add table for charging sessions

initial-version
Simon Stürz 2025-12-11 13:27:19 +01:00
parent a9f51296b3
commit 0f87148e29
2 changed files with 154 additions and 11 deletions

View File

@ -18,6 +18,8 @@ class DashboardApp {
chargerEmptyRow: document.getElementById('chargerEmptyRow'),
fetchSessionsButton: document.getElementById('fetchSessionsButton'),
chargerFilter: document.getElementById('chargerFilter'),
chargingSessionsTableBody: document.getElementById('chargingSessionsTableBody'),
chargingSessionsEmptyRow: document.getElementById('chargingSessionsEmptyRow'),
chargingSessionsOutput: document.getElementById('chargingSessionsOutput'),
panelButtons: Array.from(document.querySelectorAll('[data-panel-target]')),
contentPanels: Array.from(document.querySelectorAll('[data-panel]'))
@ -869,24 +871,140 @@ class DashboardApp {
}
renderChargingSessions(sessions, fallbackMessage) {
const normalizedSessions = Array.isArray(sessions) ? sessions : [];
this.sessions = normalizedSessions;
this.renderChargingSessionsTable(normalizedSessions, fallbackMessage);
if (!this.elements.chargingSessionsOutput)
return;
if (!Array.isArray(sessions) || !sessions.length) {
this.sessions = [];
if (!normalizedSessions.length) {
this.elements.chargingSessionsOutput.textContent = fallbackMessage || 'No charging sessions found.';
return;
}
this.sessions = sessions;
try {
this.elements.chargingSessionsOutput.textContent = JSON.stringify(sessions, null, 2);
this.elements.chargingSessionsOutput.textContent = JSON.stringify(normalizedSessions, null, 2);
} catch (error) {
console.warn('Failed to render charging sessions', error);
this.elements.chargingSessionsOutput.textContent = 'Unable to display charging sessions.';
}
}
renderChargingSessionsTable(sessions, fallbackMessage) {
const body = this.elements.chargingSessionsTableBody;
const emptyRow = this.elements.chargingSessionsEmptyRow;
if (!body)
return;
const rows = body.querySelectorAll('tr[data-session-id]');
rows.forEach(row => {
if (row.parentElement)
row.parentElement.removeChild(row);
});
if (!Array.isArray(sessions) || !sessions.length) {
if (emptyRow) {
const cell = emptyRow.querySelector('td');
if (cell)
cell.textContent = fallbackMessage || 'No charging sessions fetched yet.';
emptyRow.classList.remove('hidden');
}
return;
}
if (emptyRow)
emptyRow.classList.add('hidden');
sessions.forEach(session => {
body.appendChild(this.buildChargingSessionRow(session));
});
}
buildChargingSessionRow(session) {
const row = document.createElement('tr');
row.dataset.sessionId = session && session.sessionId ? session.sessionId : '';
const cells = [
this.deriveSessionName(session),
session && session.chargerName ? session.chargerName : '—',
session && session.carName ? session.carName : '—',
this.formatTimestamp(session ? session.startTimestamp : null),
this.formatTimestamp(session ? session.endTimestamp : null),
this.formatSessionEnergy(session)
];
cells.forEach((value, index) => {
const cell = document.createElement('td');
if (index === 5)
cell.classList.add('numeric');
cell.textContent = value;
row.appendChild(cell);
});
return row;
}
deriveSessionName(session) {
if (!session)
return '—';
if (session.name)
return session.name;
if (session.property)
return session.property;
if (session.sessionId)
return `Session ${session.sessionId}`;
return '—';
}
formatTimestamp(timestamp) {
const numeric = typeof timestamp === 'string' ? Number.parseFloat(timestamp) : timestamp;
if (!Number.isFinite(numeric))
return '—';
const ms = numeric > 1e12 ? numeric : numeric * 1000;
const date = new Date(ms);
if (Number.isNaN(date.getTime()))
return '—';
const pad = value => String(value).padStart(2, '0');
const day = pad(date.getDate());
const month = pad(date.getMonth() + 1);
const year = date.getFullYear();
const hours = pad(date.getHours());
const minutes = pad(date.getMinutes());
return `${day}.${month}.${year} ${hours}:${minutes}`;
}
formatSessionEnergy(session) {
const value = session && Number.isFinite(session.sessionEnergy)
? session.sessionEnergy
: this.calculateSessionEnergyFromRange(session);
if (!Number.isFinite(value))
return '—';
return `${value.toFixed(2)} kWh`;
}
calculateSessionEnergyFromRange(session) {
if (!session)
return null;
const end = typeof session.energyEnd === 'string' ? Number.parseFloat(session.energyEnd) : session.energyEnd;
const start = typeof session.energyStart === 'string' ? Number.parseFloat(session.energyStart) : session.energyStart;
if (!Number.isFinite(end) || !Number.isFinite(start))
return null;
return end - start;
}
updateConnectionStatus(text, state) {
if (this.elements.connectionStatus)
this.elements.connectionStatus.textContent = text;

View File

@ -319,36 +319,41 @@
width: 100%;
}
table.chargers-table {
table.data-table {
width: 100%;
border-collapse: collapse;
font-size: 0.95rem;
}
.chargers-table th,
.chargers-table td {
.data-table th,
.data-table td {
padding: 0.5rem 0.75rem;
border-bottom: 1px solid #e4e9f2;
text-align: left;
}
.chargers-table th {
.data-table th {
font-size: 0.75rem;
text-transform: uppercase;
letter-spacing: 0.05em;
color: var(--muted-text-color);
}
.chargers-table tbody tr:nth-child(even) {
.data-table tbody tr:nth-child(even) {
background: #f9fbfd;
}
.chargers-table .empty-row td {
.data-table .empty-row td {
text-align: center;
font-style: italic;
color: var(--muted-text-color);
}
.data-table .numeric {
text-align: right;
white-space: nowrap;
}
.value-dot {
display: inline-flex;
width: 0.75rem;
@ -648,7 +653,7 @@
</div>
</div>
<div class="table-wrapper">
<table class="chargers-table">
<table class="data-table chargers-table">
<thead>
<tr>
<th scope="col">Name</th>
@ -690,6 +695,26 @@
</div>
</div>
<p class="helper-text">Select a charger to filter by its assigned car and fetch sessions from nymea.</p>
<div class="table-wrapper">
<table class="data-table sessions-table">
<thead>
<tr>
<th scope="col">Name</th>
<th scope="col">Charger</th>
<th scope="col">Car</th>
<th scope="col">Start</th>
<th scope="col">End</th>
<th scope="col">Energy (kWh)</th>
</tr>
</thead>
<tbody id="chargingSessionsTableBody">
<tr id="chargingSessionsEmptyRow" class="empty-row">
<td colspan="6">No charging sessions fetched yet.</td>
</tr>
</tbody>
</table>
</div>
<p class="helper-text">Raw session payload (for debugging).</p>
<pre id="chargingSessionsOutput">No charging sessions fetched yet.</pre>
</article>
</section>