From 4f9ecfb43d32d6d96623069cdb59fb6165c27a8f Mon Sep 17 00:00:00 2001 From: Reimar Date: Wed, 2 Apr 2025 12:24:27 +0200 Subject: [PATCH] Add period selector --- frontend/home/index.html | 28 +++- frontend/scripts/home.js | 156 ++++++++++--------- frontend/scripts/services/devices.service.js | 5 +- frontend/styles/home.css | 49 +++++- 4 files changed, 153 insertions(+), 85 deletions(-) diff --git a/frontend/home/index.html b/frontend/home/index.html index 82a6269..fb428da 100644 --- a/frontend/home/index.html +++ b/frontend/home/index.html @@ -26,20 +26,38 @@
- +
- +
-
+
+
+ +
+
Today
+
Last 3 days
+
Last week
+
All time
+
+
+ +
+ + +
+ +
+ + +
+
diff --git a/frontend/scripts/home.js b/frontend/scripts/home.js index aba076b..592470d 100644 --- a/frontend/scripts/home.js +++ b/frontend/scripts/home.js @@ -2,40 +2,7 @@ import { logout } from "../shared/utils.js"; import { getUser } from "../shared/utils.js"; import { getDevices, getLogsOnDeviceId } from "./services/devices.service.js"; -async function buildChart(data) { - const xValues = data.map((log) => - new Date(log.date).toLocaleString() - ); // Full Date labels - const yValues = data.map((log) => log.temperature); // Temperature values - new Chart("myChart", { - type: "line", - data: { - labels: xValues, - datasets: [ - { - label: "Temperature", - fill: false, - lineTension: 0.4, - backgroundColor: "rgba(0,0,255,1.0)", - borderColor: "rgba(0,0,255,0.1)", - data: yValues, - }, - ], - }, - options: { - tooltips: { - callbacks: { - title: function (tooltipItem) { - return `Date: ${tooltipItem[0].label}`; - }, - label: function (tooltipItem) { - return `Temperature: ${tooltipItem.value}°C`; - }, - }, - }, - }, - }); -} +let chart; const TABLE_PAGINATION_SIZE = 30; @@ -108,7 +75,12 @@ function randomColorChannelValue() { return Math.floor(Math.random() * 256); } -async function fetchData(startDate = null, endDate = null) { +async function fetchData() { + document.body.classList.add("loading"); + + const startDate = document.getElementById("period-start").valueAsDate?.toISOString(); + const endDate = document.getElementById("period-end").valueAsDate?.toISOString(); + const devices = await getDevices() .catch(handleError); @@ -117,7 +89,7 @@ async function fetchData(startDate = null, endDate = null) { for (const device of devices) { addDeviceToDropdown(device); - const data = await getLogsOnDeviceId(device.id) + const data = await getLogsOnDeviceId(device.id, startDate, endDate) .catch(handleError); deviceData.push(data); @@ -129,52 +101,86 @@ async function fetchData(startDate = null, endDate = null) { buildTable(deviceData[0]); - new Chart("myChart", { - type: "line", - data: { - datasets: deviceData.map((dataset, i) => { - const color = new Array(3) - .fill(null) - .map(randomColorChannelValue) - .join(","); - - return { - label: devices[i].name, - fill: false, - lineTension: 0.4, - backgroundColor: `rgba(${color}, 1.0)`, - borderColor: `rgba(${color}, 0.1)`, - data: dataset.map(log => ({ - x: new Date(log.date).getTime(), - y: log.temperature, - })), - }; - }), - }, - options: { - parsing: false, - plugins: { - tooltip: { - callbacks: { - label: item => `Temperature: ${item.formattedValue}°C`, + if (!chart) { + chart = new Chart("chart", { + type: "line", + data: { + datasets: [], + }, + options: { + responsive: false, + parsing: false, + plugins: { + tooltip: { + callbacks: { + label: item => `Temperature: ${item.formattedValue}°C`, + }, + }, + decimation: { + enabled: true, + algorithm: "lttb", + samples: window.innerWidth / 2, }, }, - decimation: { - enabled: true, - algorithm: "lttb", - samples: window.innerWidth / 2, + scales: { + x: { + type: "time", + }, }, }, - scales: { - x: { - type: "time", - }, - }, - }, + }); + } + + chart.data.datasets = deviceData.map((dataset, i) => { + const color = new Array(3) + .fill(null) + .map(randomColorChannelValue) + .join(","); + + return { + label: devices[i].name, + fill: false, + lineTension: 0.4, + backgroundColor: `rgba(${color}, 1.0)`, + borderColor: `rgba(${color}, 0.1)`, + data: dataset.map(log => ({ + x: new Date(log.date).getTime(), + y: log.temperature, + })), + }; }); + + chart.update(); + + document.body.classList.remove("loading"); } -fetchData(); +function setPeriod(start, end) { + document.getElementById("period-start").valueAsDate = start && new Date(start); + document.getElementById("period-end").valueAsDate = start && new Date(end); + + fetchData(); +} + +function setPeriodLastDays(days) { + const start = new Date() + start.setDate(new Date().getDate() - days); + start.setHours(0, 0, 0, 0); + setPeriod(start, new Date().setHours(23, 59, 0, 0)); +} + +for (const elem of document.getElementsByClassName("last-x-days")) { + elem.onclick = event => setPeriodLastDays(event.target.dataset.days); +} + +for (const elem of document.querySelectorAll("#period-start, #period-end")) { + elem.onchange = fetchData; +} + +document.getElementById("all-time").onclick = () => setPeriod(null, null); document.querySelector(".logout-container").addEventListener("click", logout); +setPeriodLastDays(3); +fetchData(); + diff --git a/frontend/scripts/services/devices.service.js b/frontend/scripts/services/devices.service.js index d767050..4dd1daf 100644 --- a/frontend/scripts/services/devices.service.js +++ b/frontend/scripts/services/devices.service.js @@ -16,6 +16,7 @@ export function update(name, temphigh, tempLow, referenceId) { return request("PUT", "/device/edit", {name: name, temphigh: temphigh, tempLow: tempLow, referenceId: referenceId}); } -export function getLogsOnDeviceId(id) { - return request("GET", `/device/logs/${id}`); +export function getLogsOnDeviceId(id, startDate = null, endDate = null) { + const query = startDate && endDate ? `?dateTimeStart=${startDate}&dateTimeEnd=${endDate}` : ""; + return request("GET", `/device/logs/${id}${query}`); } diff --git a/frontend/styles/home.css b/frontend/styles/home.css index 4330c8e..58a8d89 100644 --- a/frontend/styles/home.css +++ b/frontend/styles/home.css @@ -4,6 +4,18 @@ body { background-color: #F9F9F9; } +.loading * { + pointer-events: none; +} + +.loading #chart, .loading table { + opacity: 50%; +} + +.loading select, .loading input { + color: rgba(0, 0, 0, 0.3); +} + #container { margin: 0 2rem; } @@ -13,6 +25,8 @@ body { border-radius: 8px; box-shadow: 2px 2px 2px rgba(0, 0, 0, 0.1); border: 1px solid #DDD; + margin-bottom: 2rem; + background-color: white; } #filters { @@ -21,14 +35,20 @@ body { justify-content: space-between; } +#filters > * { + display: flex; + gap: 1rem; + align-items: center; +} + table { font-family: arial, sans-serif; border-collapse: collapse; width: 100%; - background-color: white; color: #616161; font-family: arial, sans-serif; border-collapse: collapse; + transition: opacity ease-in 100ms; } td, @@ -88,6 +108,12 @@ table tr:not(:last-child) .temperature { box-shadow: 2px 2px 2px rgba(0, 0, 0, 0.1); } +#chart { + width: 100%; + height: 400px; + transition: opacity ease-in 100ms; +} + #error { margin: 2rem; } @@ -121,15 +147,32 @@ label { color: #616161; } -select { +select, input { background-color: white; color: #424242; border: 1px solid #DDD; border-radius: 8px; padding: 0.5rem 1rem; + transition: color ease-in 100ms; } -select:focus { +select:focus, input:focus { border-color: #1E88E5; } +#period-templates { + display: flex; + gap: 1rem; +} + +.period-template { + font-size: 0.85rem; + color: #616161; + cursor: pointer; + transition-duration: 100ms; +} + +.period-template:hover { + color: #212121; +} +