Add table for charging sessions
This commit is contained in:
parent
a9f51296b3
commit
0f87148e29
126
dashboard/app.js
126
dashboard/app.js
@ -18,6 +18,8 @@ class DashboardApp {
|
|||||||
chargerEmptyRow: document.getElementById('chargerEmptyRow'),
|
chargerEmptyRow: document.getElementById('chargerEmptyRow'),
|
||||||
fetchSessionsButton: document.getElementById('fetchSessionsButton'),
|
fetchSessionsButton: document.getElementById('fetchSessionsButton'),
|
||||||
chargerFilter: document.getElementById('chargerFilter'),
|
chargerFilter: document.getElementById('chargerFilter'),
|
||||||
|
chargingSessionsTableBody: document.getElementById('chargingSessionsTableBody'),
|
||||||
|
chargingSessionsEmptyRow: document.getElementById('chargingSessionsEmptyRow'),
|
||||||
chargingSessionsOutput: document.getElementById('chargingSessionsOutput'),
|
chargingSessionsOutput: document.getElementById('chargingSessionsOutput'),
|
||||||
panelButtons: Array.from(document.querySelectorAll('[data-panel-target]')),
|
panelButtons: Array.from(document.querySelectorAll('[data-panel-target]')),
|
||||||
contentPanels: Array.from(document.querySelectorAll('[data-panel]'))
|
contentPanels: Array.from(document.querySelectorAll('[data-panel]'))
|
||||||
@ -869,24 +871,140 @@ class DashboardApp {
|
|||||||
}
|
}
|
||||||
|
|
||||||
renderChargingSessions(sessions, fallbackMessage) {
|
renderChargingSessions(sessions, fallbackMessage) {
|
||||||
|
const normalizedSessions = Array.isArray(sessions) ? sessions : [];
|
||||||
|
this.sessions = normalizedSessions;
|
||||||
|
|
||||||
|
this.renderChargingSessionsTable(normalizedSessions, fallbackMessage);
|
||||||
|
|
||||||
if (!this.elements.chargingSessionsOutput)
|
if (!this.elements.chargingSessionsOutput)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (!Array.isArray(sessions) || !sessions.length) {
|
if (!normalizedSessions.length) {
|
||||||
this.sessions = [];
|
|
||||||
this.elements.chargingSessionsOutput.textContent = fallbackMessage || 'No charging sessions found.';
|
this.elements.chargingSessionsOutput.textContent = fallbackMessage || 'No charging sessions found.';
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.sessions = sessions;
|
|
||||||
try {
|
try {
|
||||||
this.elements.chargingSessionsOutput.textContent = JSON.stringify(sessions, null, 2);
|
this.elements.chargingSessionsOutput.textContent = JSON.stringify(normalizedSessions, null, 2);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.warn('Failed to render charging sessions', error);
|
console.warn('Failed to render charging sessions', error);
|
||||||
this.elements.chargingSessionsOutput.textContent = 'Unable to display charging sessions.';
|
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) {
|
updateConnectionStatus(text, state) {
|
||||||
if (this.elements.connectionStatus)
|
if (this.elements.connectionStatus)
|
||||||
this.elements.connectionStatus.textContent = text;
|
this.elements.connectionStatus.textContent = text;
|
||||||
|
|||||||
@ -319,36 +319,41 @@
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
table.chargers-table {
|
table.data-table {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
border-collapse: collapse;
|
border-collapse: collapse;
|
||||||
font-size: 0.95rem;
|
font-size: 0.95rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.chargers-table th,
|
.data-table th,
|
||||||
.chargers-table td {
|
.data-table td {
|
||||||
padding: 0.5rem 0.75rem;
|
padding: 0.5rem 0.75rem;
|
||||||
border-bottom: 1px solid #e4e9f2;
|
border-bottom: 1px solid #e4e9f2;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
}
|
}
|
||||||
|
|
||||||
.chargers-table th {
|
.data-table th {
|
||||||
font-size: 0.75rem;
|
font-size: 0.75rem;
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
letter-spacing: 0.05em;
|
letter-spacing: 0.05em;
|
||||||
color: var(--muted-text-color);
|
color: var(--muted-text-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
.chargers-table tbody tr:nth-child(even) {
|
.data-table tbody tr:nth-child(even) {
|
||||||
background: #f9fbfd;
|
background: #f9fbfd;
|
||||||
}
|
}
|
||||||
|
|
||||||
.chargers-table .empty-row td {
|
.data-table .empty-row td {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
font-style: italic;
|
font-style: italic;
|
||||||
color: var(--muted-text-color);
|
color: var(--muted-text-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.data-table .numeric {
|
||||||
|
text-align: right;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
.value-dot {
|
.value-dot {
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
width: 0.75rem;
|
width: 0.75rem;
|
||||||
@ -648,7 +653,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="table-wrapper">
|
<div class="table-wrapper">
|
||||||
<table class="chargers-table">
|
<table class="data-table chargers-table">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th scope="col">Name</th>
|
<th scope="col">Name</th>
|
||||||
@ -690,6 +695,26 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<p class="helper-text">Select a charger to filter by its assigned car and fetch sessions from nymea.</p>
|
<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>
|
<pre id="chargingSessionsOutput">No charging sessions fetched yet.</pre>
|
||||||
</article>
|
</article>
|
||||||
</section>
|
</section>
|
||||||
|
|||||||
Reference in New Issue
Block a user