From 8d0762eeaeac895634c36ac2239ba9b3fc089788 Mon Sep 17 00:00:00 2001 From: Florian Heinz Date: Sat, 11 Apr 2026 20:16:09 +0200 Subject: [PATCH] Improve dashboard readability and stress chart direction --- assets/css/app.css | 69 ++++++++++++++++++ assets/js/app.js | 128 +++++++++++++++++++++++++++++++--- templates/pages/dashboard.php | 2 +- 3 files changed, 187 insertions(+), 12 deletions(-) diff --git a/assets/css/app.css b/assets/css/app.css index 9ec37fc..f555f29 100644 --- a/assets/css/app.css +++ b/assets/css/app.css @@ -404,6 +404,60 @@ button { overflow-x: auto; } +.calendar-detail { + display: flex; + justify-content: space-between; + align-items: center; + gap: 1rem; + margin-bottom: 1rem; + padding: 0.9rem 1rem; + border-radius: 18px; + background: rgba(255, 255, 255, 0.08); + border: 1px solid rgba(255, 255, 255, 0.1); +} + +.calendar-detail__meta { + display: grid; + gap: 0.18rem; +} + +.calendar-detail__eyebrow { + color: rgba(239, 247, 255, 0.58); + font-size: 0.78rem; + letter-spacing: 0.08em; + text-transform: uppercase; +} + +.calendar-detail__meta strong { + font-size: 1.05rem; + line-height: 1.2; +} + +.calendar-detail__label, +.calendar-detail__subtle { + color: var(--muted); +} + +.calendar-detail__score { + display: grid; + justify-items: end; + line-height: 1; +} + +.calendar-detail__score span { + font-size: 1.5rem; + font-weight: 800; +} + +.calendar-detail__score small { + color: var(--muted); + margin-top: 0.2rem; +} + +.calendar-detail__link { + white-space: nowrap; +} + .calendar-legend { display: flex; justify-content: flex-end; @@ -479,6 +533,12 @@ button { stroke-width: 1.1; } +.calendar-cell--selected { + stroke: rgba(255, 255, 255, 0.9); + stroke-width: 1.2; + filter: brightness(1.08); +} + .line-chart { min-height: 10.25rem; } @@ -1182,6 +1242,15 @@ input[type="range"] { align-items: flex-start; } + .calendar-detail { + flex-direction: column; + align-items: flex-start; + } + + .calendar-detail__score { + justify-items: start; + } + .line-chart { min-height: 9.5rem; } diff --git a/assets/js/app.js b/assets/js/app.js index da571eb..61bc90a 100644 --- a/assets/js/app.js +++ b/assets/js/app.js @@ -347,6 +347,7 @@ } const seriesName = container.dataset.series || ""; + const invertScale = seriesName === "stress"; const values = items.map(item => Number(item.value)); const width = 760; const height = 196; @@ -377,7 +378,8 @@ const only = items[0]; const chartTop = padding.top; const chartBottom = height - padding.bottom; - const ratio = (Number(only.value) - minValue) / Math.max(maxValue - minValue, 1); + const baseRatio = (Number(only.value) - minValue) / Math.max(maxValue - minValue, 1); + const ratio = invertScale ? (1 - baseRatio) : baseRatio; const cx = width / 2; const cy = chartTop + ((1 - ratio) * (chartBottom - chartTop)); @@ -398,7 +400,8 @@ const step = items.length > 1 ? (width - padding.left - padding.right) / (items.length - 1) : 0; const points = items.map((item, index) => { - const ratio = (Number(item.value) - minValue) / Math.max(maxValue - minValue, 1); + const baseRatio = (Number(item.value) - minValue) / Math.max(maxValue - minValue, 1); + const ratio = invertScale ? (1 - baseRatio) : baseRatio; return { x: padding.left + (index * step), y: padding.top + ((1 - ratio) * (height - padding.top - padding.bottom)), @@ -516,10 +519,49 @@ return `rgba(112, 240, 182, ${0.38 + (ratio * 0.52)})`; } + function calendarRangeConfig() { + const viewport = window.innerWidth || container.clientWidth || 1200; + + if (viewport <= 640) { + return { + rangeDays: 119, + label: "letzte 4 Monate", + cellSize: 14, + columnGap: 6, + rowGap: 6, + xOffset: 36, + yOffset: 24, + }; + } + + if (viewport <= 980) { + return { + rangeDays: 181, + label: "letzte 6 Monate", + cellSize: 13, + columnGap: 5, + rowGap: 5, + xOffset: 36, + yOffset: 24, + }; + } + + return { + rangeDays: 364, + label: "letzte 12 Monate", + cellSize: 12, + columnGap: 5, + rowGap: 5, + xOffset: 34, + yOffset: 22, + }; + } + const map = new Map(items.map(item => [item.date, item])); + const config = calendarRangeConfig(); const end = new Date(); const start = new Date(end); - start.setDate(end.getDate() - 364); + start.setDate(end.getDate() - config.rangeDays); const gridStart = new Date(start); const startWeekday = gridStart.getDay(); @@ -547,11 +589,11 @@ } const totalWeeks = Math.floor((days.length - 1) / 7) + 1; - const cellSize = 12; - const baseCellGap = 5; - const verticalGap = 5; - const xOffset = 34; - const yOffset = 22; + const cellSize = config.cellSize; + const baseCellGap = config.columnGap; + const verticalGap = config.rowGap; + const xOffset = config.xOffset; + const yOffset = config.yOffset; const gridHeight = (7 * cellSize) + (6 * verticalGap); const height = yOffset + gridHeight + 8; const rightPadding = 4; @@ -563,6 +605,9 @@ : 0; const monthLabels = []; let lastMonth = -1; + const visibleEntries = days.filter(item => item.entry !== null); + const latestVisibleEntry = visibleEntries.length ? visibleEntries[visibleEntries.length - 1].entry : null; + let activeDate = latestVisibleEntry ? latestVisibleEntry.date : null; const cells = days.map((item, index) => { const week = Math.floor(index / 7); @@ -571,7 +616,7 @@ const y = yOffset + (row * (cellSize + verticalGap)); const fill = calendarColor(item.entry); - if (item.day <= 7 && item.month !== lastMonth && item.entry !== null) { + if (item.day <= 7 && item.month !== lastMonth) { monthLabels.push({ x, label: new Intl.DateTimeFormat("de-DE", { month: "short" }).format(new Date(`${item.date}T12:00:00`)), @@ -588,14 +633,38 @@ } return ` - - + + ${title} `; }).join(""); + const detailMarkup = latestVisibleEntry === null + ? ` +
+ ${config.label} + Noch kein getrackter Tag + Sobald du Einträge speicherst, kannst du sie hier direkt auswählen. +
+ ` + : ` +
+ ${config.label} + ${formatDateLabel(latestVisibleEntry.date)} + ${latestVisibleEntry.label} +
+
+ ${formatNumber(Number(latestVisibleEntry.score))} + Punkte +
+ Tag öffnen + `; + container.innerHTML = ` +
+ ${detailMarkup} +
${monthLabels.map(item => `${item.label}`).join("")} Mo @@ -615,6 +684,43 @@ gut `; + + const detailDate = container.querySelector("[data-calendar-date]"); + const detailLabel = container.querySelector("[data-calendar-label]"); + const detailScore = container.querySelector("[data-calendar-score]"); + const detailLink = container.querySelector("[data-calendar-link]"); + + const setActive = date => { + const entry = map.get(date); + if (!entry || !detailDate || !detailLabel || !detailScore || !detailLink) { + return; + } + + activeDate = date; + detailDate.textContent = formatDateLabel(entry.date); + detailLabel.textContent = entry.label; + detailScore.textContent = formatNumber(Number(entry.score)); + detailLink.href = `/track?date=${encodeURIComponent(entry.date)}`; + + container.querySelectorAll(".calendar-cell--selected").forEach(cell => { + cell.classList.remove("calendar-cell--selected"); + }); + + const activeLink = container.querySelector(`.calendar-link[data-date="${date}"] .calendar-cell`); + if (activeLink) { + activeLink.classList.add("calendar-cell--selected"); + } + }; + + container.querySelectorAll(".calendar-link[data-date]").forEach(link => { + const date = link.dataset.date; + if (!date) { + return; + } + + link.addEventListener("mouseenter", () => setActive(date)); + link.addEventListener("focus", () => setActive(date)); + }); } function initDashboardCharts() { diff --git a/templates/pages/dashboard.php b/templates/pages/dashboard.php index 1304882..a913738 100644 --- a/templates/pages/dashboard.php +++ b/templates/pages/dashboard.php @@ -68,7 +68,7 @@

Belastung

Stressverlauf

- letzte 30 Einträge + weniger ist besser