Add table for charging sessions
parent
a9f51296b3
commit
0f87148e29
126
dashboard/app.js
126
dashboard/app.js
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
Loading…
Reference in New Issue