Add time range selection for charging sessions

This commit is contained in:
Simon Stürz 2025-12-16 11:26:26 +01:00
parent d6abb023b4
commit 3dc8738159
2 changed files with 136 additions and 8 deletions

View File

@ -24,6 +24,8 @@ class DashboardApp {
fetchSessionsButton: document.getElementById('fetchSessionsButton'),
downloadSessionsButton: document.getElementById('downloadSessionsButton'),
carFilter: document.getElementById('carFilter'),
sessionStartFilter: document.getElementById('sessionStartFilter'),
sessionEndFilter: document.getElementById('sessionEndFilter'),
chargingSessionsTableBody: document.getElementById('chargingSessionsTableBody'),
chargingSessionsEmptyRow: document.getElementById('chargingSessionsEmptyRow'),
chargingSessionsOutput: document.getElementById('chargingSessionsOutput'),
@ -112,6 +114,18 @@ class DashboardApp {
});
}
if (this.elements.sessionStartFilter) {
this.elements.sessionStartFilter.addEventListener('change', () => {
this.renderChargingSessionsTable(this.sessions);
});
}
if (this.elements.sessionEndFilter) {
this.elements.sessionEndFilter.addEventListener('change', () => {
this.renderChargingSessionsTable(this.sessions);
});
}
if (this.elements.brandLogo) {
this.elements.brandLogo.addEventListener('click', () => {
this.handleBrandLogoClick();
@ -1053,17 +1067,28 @@ class DashboardApp {
if (!body)
return;
const normalizedSessions = Array.isArray(sessions) ? sessions : [];
const filteredSessions = this.filterChargingSessionsByTimeRange(normalizedSessions);
const hasTimeRangeFilter = this.hasChargingSessionTimeRangeFilter();
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 (!normalizedSessions.length || !filteredSessions.length) {
if (emptyRow) {
const cell = emptyRow.querySelector('td');
if (cell)
cell.textContent = fallbackMessage || 'No charging sessions fetched yet.';
if (cell) {
if (!normalizedSessions.length) {
cell.textContent = fallbackMessage || 'No charging sessions fetched yet.';
} else if (hasTimeRangeFilter) {
cell.textContent = 'No charging sessions match the selected time range.';
} else {
cell.textContent = fallbackMessage || 'No charging sessions found.';
}
}
emptyRow.classList.remove('hidden');
}
return;
@ -1072,11 +1097,106 @@ class DashboardApp {
if (emptyRow)
emptyRow.classList.add('hidden');
sessions.forEach(session => {
filteredSessions.forEach(session => {
body.appendChild(this.buildChargingSessionRow(session));
});
}
hasChargingSessionTimeRangeFilter() {
const start = this.elements.sessionStartFilter ? this.elements.sessionStartFilter.value : '';
const end = this.elements.sessionEndFilter ? this.elements.sessionEndFilter.value : '';
return !!start || !!end;
}
getChargingSessionTimeRangeMs() {
const startValue = this.elements.sessionStartFilter ? this.elements.sessionStartFilter.value : '';
const endValue = this.elements.sessionEndFilter ? this.elements.sessionEndFilter.value : '';
const startMs = this.parseDateInputToMs(startValue, { endOfDay: false });
const endMs = this.parseDateInputToMs(endValue, { endOfDay: true });
if (this.elements.sessionStartFilter)
this.elements.sessionStartFilter.setCustomValidity('');
if (this.elements.sessionEndFilter)
this.elements.sessionEndFilter.setCustomValidity('');
if (Number.isFinite(startMs) && Number.isFinite(endMs) && startMs > endMs) {
const message = 'Start date must be earlier than end date.';
if (this.elements.sessionStartFilter)
this.elements.sessionStartFilter.setCustomValidity(message);
if (this.elements.sessionEndFilter)
this.elements.sessionEndFilter.setCustomValidity(message);
return { startMs: null, endMs: null };
}
return {
startMs: Number.isFinite(startMs) ? startMs : null,
endMs: Number.isFinite(endMs) ? endMs : null
};
}
parseDateInputToMs(value, options = {}) {
if (!value)
return null;
const match = value.match(/^(\d{4})-(\d{2})-(\d{2})$/);
if (!match)
return null;
const year = Number.parseInt(match[1], 10);
const month = Number.parseInt(match[2], 10);
const day = Number.parseInt(match[3], 10);
if (!Number.isFinite(year) || !Number.isFinite(month) || !Number.isFinite(day))
return null;
const date = new Date(year, month - 1, day);
let ms = date.getTime();
if (!Number.isFinite(ms))
return null;
if (options && options.endOfDay)
ms += 24 * 60 * 60 * 1000 - 1;
return ms;
}
normalizeTimestampToMs(timestamp) {
const numeric = typeof timestamp === 'string' ? Number.parseFloat(timestamp) : timestamp;
if (!Number.isFinite(numeric))
return null;
const ms = numeric > 1e12 ? numeric : numeric * 1000;
return Number.isFinite(ms) ? ms : null;
}
filterChargingSessionsByTimeRange(sessions) {
if (!Array.isArray(sessions) || !sessions.length)
return [];
const { startMs, endMs } = this.getChargingSessionTimeRangeMs();
if (!Number.isFinite(startMs) && !Number.isFinite(endMs))
return sessions;
return sessions.filter(session => {
const sessionStart = this.normalizeTimestampToMs(session ? session.startTimestamp : null);
const sessionEnd = this.normalizeTimestampToMs(session ? session.endTimestamp : null);
const effectiveStart = Number.isFinite(sessionStart) ? sessionStart : null;
const effectiveEnd = Number.isFinite(sessionEnd)
? sessionEnd
: (Number.isFinite(sessionStart) ? sessionStart : null);
if (!Number.isFinite(effectiveStart) && !Number.isFinite(effectiveEnd))
return true;
if (Number.isFinite(startMs) && Number.isFinite(effectiveEnd) && effectiveEnd < startMs)
return false;
if (Number.isFinite(endMs) && Number.isFinite(effectiveStart) && effectiveStart > endMs)
return false;
return true;
});
}
buildChargingSessionRow(session) {
const row = document.createElement('tr');
row.dataset.sessionId = session && session.sessionId ? session.sessionId : '';
@ -1161,7 +1281,7 @@ class DashboardApp {
}
downloadChargingSessionsCsv() {
const sessions = Array.isArray(this.sessions) ? this.sessions : [];
const sessions = this.filterChargingSessionsByTimeRange(this.sessions);
if (!sessions.length) {
console.warn('No charging sessions to download.');
return;

View File

@ -471,7 +471,9 @@
}
input[type="text"],
input[type="password"] {
input[type="password"],
input[type="datetime-local"],
input[type="date"] {
border-radius: 10px;
border: 1px solid #d3dce6;
padding: 0.75rem 1rem;
@ -481,7 +483,9 @@
}
input[type="text"]:focus,
input[type="password"]:focus {
input[type="password"]:focus,
input[type="datetime-local"]:focus,
input[type="date"]:focus {
outline: none;
border-color: var(--secondary-color);
box-shadow: 0 0 0 3px rgba(162, 13, 23, 0.2);
@ -736,11 +740,15 @@
<select id="carFilter" name="carFilter">
<option value="">All cars</option>
</select>
<label class="sr-only" for="sessionStartFilter">Start date</label>
<input type="date" id="sessionStartFilter" name="sessionStartFilter">
<label class="sr-only" for="sessionEndFilter">End date</label>
<input type="date" id="sessionEndFilter" name="sessionEndFilter">
<button type="button" id="fetchSessionsButton" class="primary">Fetch sessions</button>
<button type="button" id="downloadSessionsButton">Download CSV</button>
</div>
</div>
<p class="helper-text">Select a car to filter charging sessions fetched from nymea.</p>
<p class="helper-text">Optionally filter charging sessions by car and time range before downloading.</p>
<div class="table-wrapper">
<table class="data-table sessions-table">
<thead>