Improve dashboard readability and stress chart direction

This commit is contained in:
2026-04-11 20:16:09 +02:00
parent 2cfd59871c
commit 8d0762eeae
3 changed files with 187 additions and 12 deletions
+117 -11
View File
@@ -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 `
<a class="calendar-link" href="/track?date=${encodeURIComponent(item.date)}" aria-label="${title}">
<rect class="calendar-cell calendar-cell--active" x="${x}" y="${y}" width="${cellSize}" height="${cellSize}" rx="4" fill="${fill}"></rect>
<a class="calendar-link" data-date="${item.date}" href="/track?date=${encodeURIComponent(item.date)}" aria-label="${title}">
<rect class="calendar-cell calendar-cell--active ${activeDate === item.date ? "calendar-cell--selected" : ""}" x="${x}" y="${y}" width="${cellSize}" height="${cellSize}" rx="4" fill="${fill}"></rect>
<title>${title}</title>
</a>
`;
}).join("");
const detailMarkup = latestVisibleEntry === null
? `
<div class="calendar-detail__meta">
<span class="calendar-detail__eyebrow">${config.label}</span>
<strong>Noch kein getrackter Tag</strong>
<span class="calendar-detail__subtle">Sobald du Einträge speicherst, kannst du sie hier direkt auswählen.</span>
</div>
`
: `
<div class="calendar-detail__meta">
<span class="calendar-detail__eyebrow">${config.label}</span>
<strong data-calendar-date>${formatDateLabel(latestVisibleEntry.date)}</strong>
<span class="calendar-detail__label" data-calendar-label>${latestVisibleEntry.label}</span>
</div>
<div class="calendar-detail__score">
<span data-calendar-score>${formatNumber(Number(latestVisibleEntry.score))}</span>
<small>Punkte</small>
</div>
<a class="ghost-link calendar-detail__link" data-calendar-link href="/track?date=${encodeURIComponent(latestVisibleEntry.date)}">Tag öffnen</a>
`;
container.innerHTML = `
<div class="calendar-detail">
${detailMarkup}
</div>
<svg class="calendar-svg" width="${width}" height="${height}" viewBox="0 0 ${width} ${height}" role="img" aria-label="Kalender">
${monthLabels.map(item => `<text class="calendar-tooltip" x="${item.x}" y="14">${item.label}</text>`).join("")}
<text class="calendar-tooltip" x="0" y="34">Mo</text>
@@ -615,6 +684,43 @@
<span>gut</span>
</div>
`;
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() {