diff --git a/docker/index.html b/docker/index.html
index 33b64d6..617473d 100644
--- a/docker/index.html
+++ b/docker/index.html
@@ -465,96 +465,111 @@ body {
background: var(--progress-red);
}
-/* ===== Glucose Section ===== */
-.glucose-section {
+/* ===== Nuchter card — compact glucose card vóór Ontbijt ===== */
+.nuchter-card {
background: var(--white);
border-radius: var(--radius);
box-shadow: var(--shadow-sm);
- padding: 14px 16px;
+ overflow: hidden;
margin-bottom: 10px;
-}
-
-.glucose-header {
- display: flex;
- align-items: center;
- gap: 8px;
- margin-bottom: 12px;
-}
-
-.glucose-icon {
- font-size: 1.1rem;
- color: var(--progress-red);
-}
-
-.glucose-title {
- font-weight: 700;
- font-size: var(--font-md);
- color: var(--text);
-}
-
-.glucose-unit-label {
- font-size: var(--font-xs);
- color: var(--text-muted);
- margin-left: 2px;
-}
-
-/* Grid: 4 columns on wide, 2×2 on narrow */
-.glucose-grid {
- display: grid;
- grid-template-columns: repeat(4, 1fr);
- gap: 8px;
-}
-
-@media (max-width: 480px) {
- .glucose-grid {
- grid-template-columns: repeat(2, 1fr);
- }
-}
-
-.glucose-item {
- background: var(--bg);
- border-radius: var(--radius-sm);
- padding: 10px 8px;
- text-align: center;
border: 1px solid var(--border);
transition: border-color 0.2s, background 0.2s;
+ list-style: none;
}
-
-.glucose-item.glucose-good {
+.nuchter-card.glucose-good {
border-color: #28a745;
background: #f0fdf4;
}
-
-.glucose-item.glucose-bad {
+.nuchter-card.glucose-bad {
border-color: var(--progress-red);
background: #fef2f2;
}
-
-.glucose-item.glucose-empty {
+.nuchter-card.glucose-empty {
border-color: var(--border);
- background: var(--bg);
+ background: var(--white);
+}
+.nuchter-card-header {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ padding: 10px 14px;
+ background: var(--primary-lighter);
+}
+.nuchter-card-icon {
+ font-size: 1.1rem;
+}
+.nuchter-card-label {
+ font-weight: 600;
+ font-size: var(--font-sm);
+ color: var(--text);
+}
+.nuchter-card-range {
+ font-size: var(--font-xs);
+ color: var(--text-muted);
+ margin-left: auto;
+}
+.nuchter-card-body {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ padding: 10px 14px;
+ gap: 12px;
+}
+.nuchter-card-body .glucose-input-wrapper {
+ margin-bottom: 0;
}
-.glucose-item-label {
+/* ===== Inline glucose in eetmoment card ===== */
+.glucose-inline {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ padding: 8px 14px;
+ border-top: 1px solid #F0F0F0;
+ gap: 8px;
+ transition: background 0.15s;
+}
+.glucose-inline.glucose-good {
+ background: #f0fdf4;
+}
+.glucose-inline.glucose-bad {
+ background: #fef2f2;
+}
+.glucose-inline.glucose-empty {
+ background: transparent;
+}
+.glucose-inline-left {
+ display: flex;
+ align-items: center;
+ gap: 6px;
+ min-width: 0;
+}
+.glucose-inline-icon {
+ font-size: 0.9rem;
+ color: var(--progress-red);
+}
+.glucose-inline-label {
font-size: var(--font-xs);
font-weight: 600;
color: var(--text-secondary);
- margin-bottom: 2px;
- line-height: 1.3;
}
-
-.glucose-item-range {
+.glucose-inline-range {
font-size: 0.65rem;
color: var(--text-muted);
- margin-bottom: 8px;
}
+.glucose-inline-right {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+}
+
+/* ===== Shared glucose input styles ===== */
.glucose-input-wrapper {
display: flex;
align-items: center;
justify-content: center;
gap: 2px;
- margin-bottom: 6px;
}
.glucose-input {
@@ -2258,15 +2273,7 @@ button:active, .btn:active, .meal-card-main:active, .dagboek-item:active, .food-
-
-
+
@@ -2455,10 +2462,10 @@ button:active, .btn:active, .meal-card-main:active, .dagboek-item:active, .food-
// ===== Prikmomenten voor glucose =====
const PRICKMOMENTEN = [
- { key: 'nuchter', label: 'Nuchter (voor ontbijt)', targetMin: 0, targetMax: 5.4 },
- { key: 'naOntbijt', label: '2 uur na ontbijt', targetMin: 5.4, targetMax: 6.7 },
- { key: 'naLunch', label: '2 uur na lunch', targetMin: 5.4, targetMax: 6.7 },
- { key: 'naAvondeten', label: '2 uur na avondeten', targetMin: 5.4, targetMax: 6.7 }
+ { key: 'nuchter', label: 'Nuchter (voor ontbijt)', targetMin: 0, targetMax: 5.4, momentId: null },
+ { key: 'naOntbijt', label: '2 uur na ontbijt', targetMin: 5.4, targetMax: 6.7, momentId: 'Ontbijt' },
+ { key: 'naLunch', label: '2 uur na lunch', targetMin: 5.4, targetMax: 6.7, momentId: 'Lunch' },
+ { key: 'naAvondeten', label: '2 uur na avondeten', targetMin: 5.4, targetMax: 6.7, momentId: 'Avondeten' }
];
// ===== Standaard portiegroottes per categorie =====
@@ -2704,48 +2711,102 @@ button:active, .btn:active, .meal-card-main:active, .dagboek-item:active, .food-
}
}
- function renderGlucoseSection() {
- const grid = document.getElementById('glucoseGrid');
- if (!grid) return;
+ // ===== Glucose — Inline per eetmoment =====
+ function renderNuchterCard() {
+ const m = PRICKMOMENTEN.find(p => p.key === 'nuchter');
+ if (!m) return '';
ensureGlucoseDay(activeDate);
+ const value = glucose[activeDate]?.['nuchter'] ?? null;
+ const status = isGlucoseInRange(value, 'nuchter');
- grid.innerHTML = PRICKMOMENTEN.map(m => {
- const value = glucose[activeDate][m.key];
- const status = isGlucoseInRange(value, m.key);
+ let statusIcon, statusClass, statusText;
+ if (value === null || value === undefined || value === '') {
+ statusIcon = '⏹️';
+ statusClass = 'glucose-empty';
+ statusText = 'Nog meten';
+ } else if (status === 'good') {
+ statusIcon = '✅';
+ statusClass = 'glucose-good';
+ statusText = 'Binnen range';
+ } else if (status === 'high') {
+ statusIcon = '🔴';
+ statusClass = 'glucose-bad';
+ statusText = 'Te hoog';
+ } else {
+ statusIcon = '🔴';
+ statusClass = 'glucose-bad';
+ statusText = 'Te laag';
+ }
- let statusIcon, statusClass, statusText;
- if (value === null || value === undefined || value === '') {
- statusIcon = '⏹️';
- statusClass = 'glucose-empty';
- statusText = 'Nog meten';
- } else if (status === 'good') {
- statusIcon = '✅';
- statusClass = 'glucose-good';
- statusText = 'Binnen range';
- } else if (status === 'high') {
- statusIcon = '🔴';
- statusClass = 'glucose-bad';
- statusText = 'Te hoog';
- } else {
- statusIcon = '🔴';
- statusClass = 'glucose-bad';
- statusText = 'Te laag';
- }
+ const displayValue = (value !== null && value !== undefined) ? value : '';
- const displayValue = (value !== null && value !== undefined) ? value : '';
+ return `
+
+
+
+
+
+ mmol/L
+
+
+ ${statusIcon}
+ ${statusText}
+
+
+
+ `;
+ }
- // Short label: remove parenthetical and "2 uur na" prefix
- let shortLabel = m.label;
- shortLabel = shortLabel.replace(/\([^)]*\)/g, '').trim();
- shortLabel = shortLabel.replace(/^2 uur na /i, '').trim();
- // Capitalize first letter
- shortLabel = shortLabel.charAt(0).toUpperCase() + shortLabel.slice(1);
+ function renderInlineGlucoseItem(m) {
+ ensureGlucoseDay(activeDate);
+ const value = glucose[activeDate]?.[m.key] ?? null;
+ const status = isGlucoseInRange(value, m.key);
- return `
-
-
${shortLabel}
-
${m.key === 'nuchter' ? '< ' + m.targetMax : m.targetMin + '–' + m.targetMax}
+ let statusIcon, statusClass, statusText;
+ if (value === null || value === undefined || value === '') {
+ statusIcon = '⏹️';
+ statusClass = 'glucose-empty';
+ statusText = 'Nog meten';
+ } else if (status === 'good') {
+ statusIcon = '✅';
+ statusClass = 'glucose-good';
+ statusText = 'Binnen range';
+ } else if (status === 'high') {
+ statusIcon = '🔴';
+ statusClass = 'glucose-bad';
+ statusText = 'Te hoog';
+ } else {
+ statusIcon = '🔴';
+ statusClass = 'glucose-bad';
+ statusText = 'Te laag';
+ }
+
+ const displayValue = (value !== null && value !== undefined) ? value : '';
+
+ // Short label: remove parenthetical and "2 uur na" prefix
+ let shortLabel = m.label;
+ shortLabel = shortLabel.replace(/\([^)]*\)/g, '').trim();
+ shortLabel = shortLabel.replace(/^2 uur na /i, '').trim();
+ shortLabel = shortLabel.charAt(0).toUpperCase() + shortLabel.slice(1);
+
+ return `
+
+
+ 🩸
+ ${shortLabel}
+ ${m.targetMin}–${m.targetMax}
+
+
- `;
- }).join('');
+
+ `;
+ }
+
+ function attachGlucoseInputHandlers() {
+ document.querySelectorAll('.glucose-input').forEach(input => {
+ // Avoid double-binding
+ if (input.dataset.glucoseBound) return;
+ input.dataset.glucoseBound = 'true';
- // Attach save-on-blur handlers
- grid.querySelectorAll('.glucose-input').forEach(input => {
input.addEventListener('blur', () => {
const moment = input.dataset.moment;
const val = input.value.trim() === '' ? null : parseFloat(input.value);
@@ -2778,11 +2844,10 @@ button:active, .btn:active, .meal-card-main:active, .dagboek-item:active, .food-
if (glucose[activeDate][moment] !== val) {
glucose[activeDate][moment] = val;
saveGlucoseToStorage();
- renderGlucoseSection();
+ updateGlucoseUI(moment);
}
});
- // Enter key also saves
input.addEventListener('keydown', (e) => {
if (e.key === 'Enter') {
input.blur();
@@ -2791,6 +2856,28 @@ button:active, .btn:active, .meal-card-main:active, .dagboek-item:active, .food-
});
}
+ function updateGlucoseUI(momentKey) {
+ const m = PRICKMOMENTEN.find(p => p.key === momentKey);
+ if (!m) return;
+
+ if (m.momentId === null) {
+ // Update nuchter card
+ const card = document.querySelector('.nuchter-card');
+ if (card) {
+ card.outerHTML = renderNuchterCard();
+ }
+ } else {
+ // Update inline glucose in eetmoment card
+ const el = document.querySelector(`.glucose-inline[data-moment-key="${momentKey}"]`);
+ if (el) {
+ el.outerHTML = renderInlineGlucoseItem(m);
+ }
+ }
+
+ // Re-attach handlers on replaced elements
+ attachGlucoseInputHandlers();
+ }
+
// ===== Daily Carb Limit =====
const DEFAULT_CARB_LIMIT = 175; // grams per day
@@ -3184,12 +3271,10 @@ button:active, .btn:active, .meal-card-main:active, .dagboek-item:active, .food-
// Progress bar
renderProgressBar(totaal);
- // Glucose section
- renderGlucoseSection();
-
- // Eating moments
+ // Eating moments — prepend nuchter card, then eetmoment cards with inline glucose
const listEl = document.getElementById('eetmomentList');
- listEl.innerHTML = EETMOMENTEN.map((moment, mi) => {
+ const nuchterHtml = renderNuchterCard();
+ listEl.innerHTML = nuchterHtml + EETMOMENTEN.map((moment, mi) => {
const items = dayData[moment.id] || [];
const momentKh = berekenEetmomentKh(activeDate, moment.id);
const roundedKh = Math.round(momentKh);
@@ -3275,6 +3360,9 @@ button:active, .btn:active, .meal-card-main:active, .dagboek-item:active, .food-
}).join('');
const cardOverLimit = overLimit && momentKh > 0;
+ // Check if this eetmoment has a matching glucose moment
+ const glucoseMoment = PRICKMOMENTEN.find(p => p.momentId === moment.id);
+ const inlineGlucoseHtml = glucoseMoment ? renderInlineGlucoseItem(glucoseMoment) : '';
return `
@@ -3284,9 +3372,10 @@ button:active, .btn:active, .meal-card-main:active, .dagboek-item:active, .food-
${moment.name}
${roundedKh > 0 ? formatKh(roundedKh) + ' g' : '—'}
- ${items.length > 0 ? '' : ''}
+ ${items.length > 0 ? '' : ''}
${items.length > 0 ? `` : ''}
+ ${inlineGlucoseHtml}
+ Voedingsmiddel toevoegen
@@ -3379,6 +3468,9 @@ button:active, .btn:active, .meal-card-main:active, .dagboek-item:active, .food-
// Empty state
const hasItems = EETMOMENTEN.some(m => (dayData[m.id] || []).length > 0);
document.getElementById('diaryEmpty').classList.toggle('hidden', hasItems);
+
+ // Attach glucose input handlers (nuchter card + inline glucose)
+ attachGlucoseInputHandlers();
}
function showDetail(item) {
diff --git a/index.html b/index.html
index 33b64d6..617473d 100644
--- a/index.html
+++ b/index.html
@@ -465,96 +465,111 @@ body {
background: var(--progress-red);
}
-/* ===== Glucose Section ===== */
-.glucose-section {
+/* ===== Nuchter card — compact glucose card vóór Ontbijt ===== */
+.nuchter-card {
background: var(--white);
border-radius: var(--radius);
box-shadow: var(--shadow-sm);
- padding: 14px 16px;
+ overflow: hidden;
margin-bottom: 10px;
-}
-
-.glucose-header {
- display: flex;
- align-items: center;
- gap: 8px;
- margin-bottom: 12px;
-}
-
-.glucose-icon {
- font-size: 1.1rem;
- color: var(--progress-red);
-}
-
-.glucose-title {
- font-weight: 700;
- font-size: var(--font-md);
- color: var(--text);
-}
-
-.glucose-unit-label {
- font-size: var(--font-xs);
- color: var(--text-muted);
- margin-left: 2px;
-}
-
-/* Grid: 4 columns on wide, 2×2 on narrow */
-.glucose-grid {
- display: grid;
- grid-template-columns: repeat(4, 1fr);
- gap: 8px;
-}
-
-@media (max-width: 480px) {
- .glucose-grid {
- grid-template-columns: repeat(2, 1fr);
- }
-}
-
-.glucose-item {
- background: var(--bg);
- border-radius: var(--radius-sm);
- padding: 10px 8px;
- text-align: center;
border: 1px solid var(--border);
transition: border-color 0.2s, background 0.2s;
+ list-style: none;
}
-
-.glucose-item.glucose-good {
+.nuchter-card.glucose-good {
border-color: #28a745;
background: #f0fdf4;
}
-
-.glucose-item.glucose-bad {
+.nuchter-card.glucose-bad {
border-color: var(--progress-red);
background: #fef2f2;
}
-
-.glucose-item.glucose-empty {
+.nuchter-card.glucose-empty {
border-color: var(--border);
- background: var(--bg);
+ background: var(--white);
+}
+.nuchter-card-header {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ padding: 10px 14px;
+ background: var(--primary-lighter);
+}
+.nuchter-card-icon {
+ font-size: 1.1rem;
+}
+.nuchter-card-label {
+ font-weight: 600;
+ font-size: var(--font-sm);
+ color: var(--text);
+}
+.nuchter-card-range {
+ font-size: var(--font-xs);
+ color: var(--text-muted);
+ margin-left: auto;
+}
+.nuchter-card-body {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ padding: 10px 14px;
+ gap: 12px;
+}
+.nuchter-card-body .glucose-input-wrapper {
+ margin-bottom: 0;
}
-.glucose-item-label {
+/* ===== Inline glucose in eetmoment card ===== */
+.glucose-inline {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ padding: 8px 14px;
+ border-top: 1px solid #F0F0F0;
+ gap: 8px;
+ transition: background 0.15s;
+}
+.glucose-inline.glucose-good {
+ background: #f0fdf4;
+}
+.glucose-inline.glucose-bad {
+ background: #fef2f2;
+}
+.glucose-inline.glucose-empty {
+ background: transparent;
+}
+.glucose-inline-left {
+ display: flex;
+ align-items: center;
+ gap: 6px;
+ min-width: 0;
+}
+.glucose-inline-icon {
+ font-size: 0.9rem;
+ color: var(--progress-red);
+}
+.glucose-inline-label {
font-size: var(--font-xs);
font-weight: 600;
color: var(--text-secondary);
- margin-bottom: 2px;
- line-height: 1.3;
}
-
-.glucose-item-range {
+.glucose-inline-range {
font-size: 0.65rem;
color: var(--text-muted);
- margin-bottom: 8px;
}
+.glucose-inline-right {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+}
+
+/* ===== Shared glucose input styles ===== */
.glucose-input-wrapper {
display: flex;
align-items: center;
justify-content: center;
gap: 2px;
- margin-bottom: 6px;
}
.glucose-input {
@@ -2258,15 +2273,7 @@ button:active, .btn:active, .meal-card-main:active, .dagboek-item:active, .food-
-
-
+
@@ -2455,10 +2462,10 @@ button:active, .btn:active, .meal-card-main:active, .dagboek-item:active, .food-
// ===== Prikmomenten voor glucose =====
const PRICKMOMENTEN = [
- { key: 'nuchter', label: 'Nuchter (voor ontbijt)', targetMin: 0, targetMax: 5.4 },
- { key: 'naOntbijt', label: '2 uur na ontbijt', targetMin: 5.4, targetMax: 6.7 },
- { key: 'naLunch', label: '2 uur na lunch', targetMin: 5.4, targetMax: 6.7 },
- { key: 'naAvondeten', label: '2 uur na avondeten', targetMin: 5.4, targetMax: 6.7 }
+ { key: 'nuchter', label: 'Nuchter (voor ontbijt)', targetMin: 0, targetMax: 5.4, momentId: null },
+ { key: 'naOntbijt', label: '2 uur na ontbijt', targetMin: 5.4, targetMax: 6.7, momentId: 'Ontbijt' },
+ { key: 'naLunch', label: '2 uur na lunch', targetMin: 5.4, targetMax: 6.7, momentId: 'Lunch' },
+ { key: 'naAvondeten', label: '2 uur na avondeten', targetMin: 5.4, targetMax: 6.7, momentId: 'Avondeten' }
];
// ===== Standaard portiegroottes per categorie =====
@@ -2704,48 +2711,102 @@ button:active, .btn:active, .meal-card-main:active, .dagboek-item:active, .food-
}
}
- function renderGlucoseSection() {
- const grid = document.getElementById('glucoseGrid');
- if (!grid) return;
+ // ===== Glucose — Inline per eetmoment =====
+ function renderNuchterCard() {
+ const m = PRICKMOMENTEN.find(p => p.key === 'nuchter');
+ if (!m) return '';
ensureGlucoseDay(activeDate);
+ const value = glucose[activeDate]?.['nuchter'] ?? null;
+ const status = isGlucoseInRange(value, 'nuchter');
- grid.innerHTML = PRICKMOMENTEN.map(m => {
- const value = glucose[activeDate][m.key];
- const status = isGlucoseInRange(value, m.key);
+ let statusIcon, statusClass, statusText;
+ if (value === null || value === undefined || value === '') {
+ statusIcon = '⏹️';
+ statusClass = 'glucose-empty';
+ statusText = 'Nog meten';
+ } else if (status === 'good') {
+ statusIcon = '✅';
+ statusClass = 'glucose-good';
+ statusText = 'Binnen range';
+ } else if (status === 'high') {
+ statusIcon = '🔴';
+ statusClass = 'glucose-bad';
+ statusText = 'Te hoog';
+ } else {
+ statusIcon = '🔴';
+ statusClass = 'glucose-bad';
+ statusText = 'Te laag';
+ }
- let statusIcon, statusClass, statusText;
- if (value === null || value === undefined || value === '') {
- statusIcon = '⏹️';
- statusClass = 'glucose-empty';
- statusText = 'Nog meten';
- } else if (status === 'good') {
- statusIcon = '✅';
- statusClass = 'glucose-good';
- statusText = 'Binnen range';
- } else if (status === 'high') {
- statusIcon = '🔴';
- statusClass = 'glucose-bad';
- statusText = 'Te hoog';
- } else {
- statusIcon = '🔴';
- statusClass = 'glucose-bad';
- statusText = 'Te laag';
- }
+ const displayValue = (value !== null && value !== undefined) ? value : '';
- const displayValue = (value !== null && value !== undefined) ? value : '';
+ return `
+
+
+
+
+
+ mmol/L
+
+
+ ${statusIcon}
+ ${statusText}
+
+
+
+ `;
+ }
- // Short label: remove parenthetical and "2 uur na" prefix
- let shortLabel = m.label;
- shortLabel = shortLabel.replace(/\([^)]*\)/g, '').trim();
- shortLabel = shortLabel.replace(/^2 uur na /i, '').trim();
- // Capitalize first letter
- shortLabel = shortLabel.charAt(0).toUpperCase() + shortLabel.slice(1);
+ function renderInlineGlucoseItem(m) {
+ ensureGlucoseDay(activeDate);
+ const value = glucose[activeDate]?.[m.key] ?? null;
+ const status = isGlucoseInRange(value, m.key);
- return `
-
-
${shortLabel}
-
${m.key === 'nuchter' ? '< ' + m.targetMax : m.targetMin + '–' + m.targetMax}
+ let statusIcon, statusClass, statusText;
+ if (value === null || value === undefined || value === '') {
+ statusIcon = '⏹️';
+ statusClass = 'glucose-empty';
+ statusText = 'Nog meten';
+ } else if (status === 'good') {
+ statusIcon = '✅';
+ statusClass = 'glucose-good';
+ statusText = 'Binnen range';
+ } else if (status === 'high') {
+ statusIcon = '🔴';
+ statusClass = 'glucose-bad';
+ statusText = 'Te hoog';
+ } else {
+ statusIcon = '🔴';
+ statusClass = 'glucose-bad';
+ statusText = 'Te laag';
+ }
+
+ const displayValue = (value !== null && value !== undefined) ? value : '';
+
+ // Short label: remove parenthetical and "2 uur na" prefix
+ let shortLabel = m.label;
+ shortLabel = shortLabel.replace(/\([^)]*\)/g, '').trim();
+ shortLabel = shortLabel.replace(/^2 uur na /i, '').trim();
+ shortLabel = shortLabel.charAt(0).toUpperCase() + shortLabel.slice(1);
+
+ return `
+
+
+ 🩸
+ ${shortLabel}
+ ${m.targetMin}–${m.targetMax}
+
+
- `;
- }).join('');
+
+ `;
+ }
+
+ function attachGlucoseInputHandlers() {
+ document.querySelectorAll('.glucose-input').forEach(input => {
+ // Avoid double-binding
+ if (input.dataset.glucoseBound) return;
+ input.dataset.glucoseBound = 'true';
- // Attach save-on-blur handlers
- grid.querySelectorAll('.glucose-input').forEach(input => {
input.addEventListener('blur', () => {
const moment = input.dataset.moment;
const val = input.value.trim() === '' ? null : parseFloat(input.value);
@@ -2778,11 +2844,10 @@ button:active, .btn:active, .meal-card-main:active, .dagboek-item:active, .food-
if (glucose[activeDate][moment] !== val) {
glucose[activeDate][moment] = val;
saveGlucoseToStorage();
- renderGlucoseSection();
+ updateGlucoseUI(moment);
}
});
- // Enter key also saves
input.addEventListener('keydown', (e) => {
if (e.key === 'Enter') {
input.blur();
@@ -2791,6 +2856,28 @@ button:active, .btn:active, .meal-card-main:active, .dagboek-item:active, .food-
});
}
+ function updateGlucoseUI(momentKey) {
+ const m = PRICKMOMENTEN.find(p => p.key === momentKey);
+ if (!m) return;
+
+ if (m.momentId === null) {
+ // Update nuchter card
+ const card = document.querySelector('.nuchter-card');
+ if (card) {
+ card.outerHTML = renderNuchterCard();
+ }
+ } else {
+ // Update inline glucose in eetmoment card
+ const el = document.querySelector(`.glucose-inline[data-moment-key="${momentKey}"]`);
+ if (el) {
+ el.outerHTML = renderInlineGlucoseItem(m);
+ }
+ }
+
+ // Re-attach handlers on replaced elements
+ attachGlucoseInputHandlers();
+ }
+
// ===== Daily Carb Limit =====
const DEFAULT_CARB_LIMIT = 175; // grams per day
@@ -3184,12 +3271,10 @@ button:active, .btn:active, .meal-card-main:active, .dagboek-item:active, .food-
// Progress bar
renderProgressBar(totaal);
- // Glucose section
- renderGlucoseSection();
-
- // Eating moments
+ // Eating moments — prepend nuchter card, then eetmoment cards with inline glucose
const listEl = document.getElementById('eetmomentList');
- listEl.innerHTML = EETMOMENTEN.map((moment, mi) => {
+ const nuchterHtml = renderNuchterCard();
+ listEl.innerHTML = nuchterHtml + EETMOMENTEN.map((moment, mi) => {
const items = dayData[moment.id] || [];
const momentKh = berekenEetmomentKh(activeDate, moment.id);
const roundedKh = Math.round(momentKh);
@@ -3275,6 +3360,9 @@ button:active, .btn:active, .meal-card-main:active, .dagboek-item:active, .food-
}).join('');
const cardOverLimit = overLimit && momentKh > 0;
+ // Check if this eetmoment has a matching glucose moment
+ const glucoseMoment = PRICKMOMENTEN.find(p => p.momentId === moment.id);
+ const inlineGlucoseHtml = glucoseMoment ? renderInlineGlucoseItem(glucoseMoment) : '';
return `
@@ -3284,9 +3372,10 @@ button:active, .btn:active, .meal-card-main:active, .dagboek-item:active, .food-
${moment.name}
${roundedKh > 0 ? formatKh(roundedKh) + ' g' : '—'}
- ${items.length > 0 ? '' : ''}
+ ${items.length > 0 ? '' : ''}
${items.length > 0 ? `` : ''}
+ ${inlineGlucoseHtml}
+ Voedingsmiddel toevoegen
@@ -3379,6 +3468,9 @@ button:active, .btn:active, .meal-card-main:active, .dagboek-item:active, .food-
// Empty state
const hasItems = EETMOMENTEN.some(m => (dayData[m.id] || []).length > 0);
document.getElementById('diaryEmpty').classList.toggle('hidden', hasItems);
+
+ // Attach glucose input handlers (nuchter card + inline glucose)
+ attachGlucoseInputHandlers();
}
function showDetail(item) {
diff --git a/tests/glucose-e2e.spec.js b/tests/glucose-e2e.spec.js
index b8b02d9..07ff498 100644
--- a/tests/glucose-e2e.spec.js
+++ b/tests/glucose-e2e.spec.js
@@ -3,38 +3,41 @@ const { test, expect } = require('@playwright/test');
const BASE = 'https://eetdagboek.vantwout.dev';
/**
- * Helpers for glucose tests
+ * Helpers for glucose tests — inline per eetmoment
*/
/** Navigate to diary tab and wait for render */
async function openDiary(page) {
await page.locator('#navDagboek').click();
await page.waitForTimeout(600);
- // Wait for glucose section to render
- await expect(page.locator('.glucose-section')).toBeVisible({ timeout: 5000 });
+ // Wait for nuchter card to render
+ await expect(page.locator('.nuchter-card')).toBeVisible({ timeout: 5000 });
}
/** Fill a glucose input and trigger save-on-blur */
async function setGlucoseValue(page, momentKey, value) {
const input = page.locator(`input.glucose-input[data-moment="${momentKey}"]`);
await input.fill(String(value));
- // Trigger blur — saves to localStorage and re-renders the grid
+ // Trigger blur — saves to localStorage and re-renders just the glucose UI
await input.evaluate(el => el.blur());
// Wait for re-render to complete
await page.waitForTimeout(400);
}
-/** Get the parent .glucose-item for a given moment */
-function glucoseItem(page, momentKey) {
- return page.locator(`input.glucose-input[data-moment="${momentKey}"]`).locator('..').locator('..');
+/** Get the parent glucose container for a given moment */
+function glucoseContainer(page, momentKey) {
+ if (momentKey === 'nuchter') {
+ return page.locator('.nuchter-card');
+ }
+ return page.locator(`.glucose-inline[data-moment-key="${momentKey}"]`);
}
/** Get status text element for a given moment */
function statusText(page, momentKey) {
- return page.locator(`input.glucose-input[data-moment="${momentKey}"]`)
- .locator('..') // .glucose-input-wrapper
- .locator('..') // .glucose-item
- .locator('.glucose-status-text');
+ if (momentKey === 'nuchter') {
+ return page.locator('.nuchter-card .glucose-status-text');
+ }
+ return page.locator(`.glucose-inline[data-moment-key="${momentKey}"] .glucose-status-text`);
}
// =============================================================================
@@ -47,33 +50,33 @@ test.describe('Glucose-metingen — invoer, validatie, kleurcodering, persistent
await page.goto(BASE);
await openDiary(page);
- // Section structure
- await expect(page.locator('.glucose-section')).toBeVisible();
- await expect(page.locator('.glucose-title')).toHaveText('Glucose');
- await expect(page.locator('.glucose-unit-label')).toHaveText('mmol/L');
- await expect(page.locator('.glucose-header .glucose-icon i')).toBeVisible();
+ // Nuchter card structure
+ await expect(page.locator('.nuchter-card')).toBeVisible();
+ await expect(page.locator('.nuchter-card-label')).toHaveText('Nuchter (voor ontbijt)');
+ await expect(page.locator('.nuchter-card-range')).toHaveText('< 5.4');
- // 4 grid items
- const items = page.locator('.glucose-item');
- await expect(items).toHaveCount(4);
+ // Inline glucose in eetmoment cards (Ontbijt, Lunch, Avondeten)
+ const moments = [
+ { key: 'naOntbijt', label: 'Ontbijt', range: '5.4\u20136.7' },
+ { key: 'naLunch', label: 'Lunch', range: '5.4\u20136.7' },
+ { key: 'naAvondeten', label: 'Avondeten', range: '5.4\u20136.7' },
+ ];
- // Labels
- await expect(items.nth(0).locator('.glucose-item-label')).toHaveText('Nuchter');
- await expect(items.nth(1).locator('.glucose-item-label')).toHaveText('Ontbijt');
- await expect(items.nth(2).locator('.glucose-item-label')).toHaveText('Lunch');
- await expect(items.nth(3).locator('.glucose-item-label')).toHaveText('Avondeten');
+ // 3 inline glucose items should exist
+ const inlineItems = page.locator('.glucose-inline');
+ await expect(inlineItems).toHaveCount(3);
- // Target ranges
- await expect(items.nth(0).locator('.glucose-item-range')).toHaveText('< 5.4');
- await expect(items.nth(1).locator('.glucose-item-range')).toHaveText('5.4\u20136.7');
- await expect(items.nth(2).locator('.glucose-item-range')).toHaveText('5.4\u20136.7');
- await expect(items.nth(3).locator('.glucose-item-range')).toHaveText('5.4\u20136.7');
+ for (let i = 0; i < moments.length; i++) {
+ const m = moments[i];
+ const container = page.locator(`.glucose-inline[data-moment-key="${m.key}"]`);
+ await expect(container.locator('.glucose-inline-label')).toHaveText(m.label);
+ await expect(container.locator('.glucose-inline-range')).toHaveText(m.range);
+ }
- // Input attributes
- const moments = ['nuchter', 'naOntbijt', 'naLunch', 'naAvondeten'];
- for (let i = 0; i < 4; i++) {
- const input = items.nth(i).locator('.glucose-input');
- await expect(input).toHaveAttribute('data-moment', moments[i]);
+ // Input attributes — check 4 inputs
+ const inputMoments = ['nuchter', 'naOntbijt', 'naLunch', 'naAvondeten'];
+ for (const moment of inputMoments) {
+ const input = page.locator(`input.glucose-input[data-moment="${moment}"]`);
await expect(input).toHaveAttribute('step', '0.1');
await expect(input).toHaveAttribute('min', '0');
await expect(input).toHaveAttribute('max', '30');
@@ -90,11 +93,10 @@ test.describe('Glucose-metingen — invoer, validatie, kleurcodering, persistent
await setGlucoseValue(page, 'nuchter', 5.1);
- // Check the item class and status
- const item = glucoseItem(page, 'nuchter');
- await expect(item).toHaveClass(/glucose-good/);
- await expect(item.locator('.glucose-status-icon')).toContainText('\u2705'); // ✅
- await expect(item.locator('.glucose-status-text')).toHaveText('Binnen range');
+ const container = glucoseContainer(page, 'nuchter');
+ await expect(container).toHaveClass(/glucose-good/);
+ await expect(container.locator('.glucose-status-icon')).toContainText('\u2705'); // ✅
+ await expect(container.locator('.glucose-status-text')).toHaveText('Binnen range');
});
// ---- 3. Nuchter ≥ 5.4 (rood) ----
@@ -105,9 +107,9 @@ test.describe('Glucose-metingen — invoer, validatie, kleurcodering, persistent
await setGlucoseValue(page, 'nuchter', 5.4);
- const item = glucoseItem(page, 'nuchter');
- await expect(item).toHaveClass(/glucose-bad/);
- await expect(item.locator('.glucose-status-text')).toHaveText('Te hoog');
+ const container = glucoseContainer(page, 'nuchter');
+ await expect(container).toHaveClass(/glucose-bad/);
+ await expect(container.locator('.glucose-status-text')).toHaveText('Te hoog');
});
// ---- 4. Postprandiaal < 5.4 (rood/te laag) ----
@@ -118,9 +120,9 @@ test.describe('Glucose-metingen — invoer, validatie, kleurcodering, persistent
await setGlucoseValue(page, 'naOntbijt', 5.3);
- const item = glucoseItem(page, 'naOntbijt');
- await expect(item).toHaveClass(/glucose-bad/);
- await expect(item.locator('.glucose-status-text')).toHaveText('Te laag');
+ const container = glucoseContainer(page, 'naOntbijt');
+ await expect(container).toHaveClass(/glucose-bad/);
+ await expect(container.locator('.glucose-status-text')).toHaveText('Te laag');
});
// ---- 5. Postprandiaal binnen range (groen) ----
@@ -131,9 +133,9 @@ test.describe('Glucose-metingen — invoer, validatie, kleurcodering, persistent
await setGlucoseValue(page, 'naOntbijt', 6.0);
- const item = glucoseItem(page, 'naOntbijt');
- await expect(item).toHaveClass(/glucose-good/);
- await expect(item.locator('.glucose-status-text')).toHaveText('Binnen range');
+ const container = glucoseContainer(page, 'naOntbijt');
+ await expect(container).toHaveClass(/glucose-good/);
+ await expect(container.locator('.glucose-status-text')).toHaveText('Binnen range');
});
// ---- 6. Postprandiaal > 6.7 (rood/te hoog) ----
@@ -144,9 +146,9 @@ test.describe('Glucose-metingen — invoer, validatie, kleurcodering, persistent
await setGlucoseValue(page, 'naLunch', 6.8);
- const item = glucoseItem(page, 'naLunch');
- await expect(item).toHaveClass(/glucose-bad/);
- await expect(item.locator('.glucose-status-text')).toHaveText('Te hoog');
+ const container = glucoseContainer(page, 'naLunch');
+ await expect(container).toHaveClass(/glucose-bad/);
+ await expect(container.locator('.glucose-status-text')).toHaveText('Te hoog');
});
// ---- 7. Leeg veld — grijze 'nog meten' indicator ----
@@ -155,31 +157,24 @@ test.describe('Glucose-metingen — invoer, validatie, kleurcodering, persistent
await page.goto(BASE);
await openDiary(page);
- // All 4 items should start empty
- const items = page.locator('.glucose-item');
- const count = await items.count();
- expect(count).toBe(4);
+ // Nuchter card should start empty
+ const nuchter = glucoseContainer(page, 'nuchter');
+ await expect(nuchter).toHaveClass(/glucose-empty/);
+ await expect(nuchter.locator('.glucose-status-text')).toHaveText('Nog meten');
+ await expect(nuchter.locator('.glucose-status-icon')).toContainText('\u23F9');
- for (let i = 0; i < count; i++) {
- await expect(items.nth(i)).toHaveClass(/glucose-empty/);
- }
-
- const statusTexts = page.locator('.glucose-status-text');
- const texts = await statusTexts.allTextContents();
- for (const t of texts) {
- expect(t).toBe('Nog meten');
- }
-
- // Status icons should be ⏹️
- const icons = page.locator('.glucose-status-icon');
- const iconTexts = await icons.allTextContents();
- for (const icon of iconTexts) {
- expect(icon.includes('\u23F9\uFE0F') || icon.includes('\u23F9')).toBeTruthy();
+ // Inline items should start empty
+ const inlineMoments = ['naOntbijt', 'naLunch', 'naAvondeten'];
+ for (const moment of inlineMoments) {
+ const container = glucoseContainer(page, moment);
+ await expect(container).toHaveClass(/glucose-empty/);
+ await expect(container.locator('.glucose-status-text')).toHaveText('Nog meten');
}
// All inputs should have empty value
const inputs = page.locator('.glucose-input');
const inputCount = await inputs.count();
+ expect(inputCount).toBe(4);
for (let i = 0; i < inputCount; i++) {
await expect(inputs.nth(i)).toHaveValue('');
}
@@ -195,20 +190,20 @@ test.describe('Glucose-metingen — invoer, validatie, kleurcodering, persistent
await setGlucoseValue(page, 'nuchter', 5.1);
// Verify it's shown correctly
- let item = glucoseItem(page, 'nuchter');
- await expect(item).toHaveClass(/glucose-good/);
- await expect(item.locator('.glucose-status-text')).toHaveText('Binnen range');
- await expect(item.locator('.glucose-input')).toHaveValue('5.1');
+ let container = glucoseContainer(page, 'nuchter');
+ await expect(container).toHaveClass(/glucose-good/);
+ await expect(container.locator('.glucose-status-text')).toHaveText('Binnen range');
+ await expect(container.locator('.glucose-input')).toHaveValue('5.1');
// Reload the page
await page.goto(BASE);
await openDiary(page);
// Value should still be there
- item = glucoseItem(page, 'nuchter');
- await expect(item).toHaveClass(/glucose-good/);
- await expect(item.locator('.glucose-status-text')).toHaveText('Binnen range');
- await expect(item.locator('.glucose-input')).toHaveValue('5.1');
+ container = glucoseContainer(page, 'nuchter');
+ await expect(container).toHaveClass(/glucose-good/);
+ await expect(container.locator('.glucose-status-text')).toHaveText('Binnen range');
+ await expect(container.locator('.glucose-input')).toHaveValue('5.1');
});
// ---- 9. Datum-navigatie — andere datum andere waarden ----
@@ -221,23 +216,23 @@ test.describe('Glucose-metingen — invoer, validatie, kleurcodering, persistent
await setGlucoseValue(page, 'nuchter', 5.1);
// Verify today shows the value
- let item = glucoseItem(page, 'nuchter');
- await expect(item).toHaveClass(/glucose-good/);
+ let container = glucoseContainer(page, 'nuchter');
+ await expect(container).toHaveClass(/glucose-good/);
// Navigate to tomorrow
await page.locator('#dayNext').click();
await page.waitForTimeout(600);
// Tomorrow should have all empty glucose fields
- item = glucoseItem(page, 'nuchter');
- await expect(item).toHaveClass(/glucose-empty/);
- await expect(item.locator('.glucose-status-text')).toHaveText('Nog meten');
- await expect(item.locator('.glucose-input')).toHaveValue('');
+ container = glucoseContainer(page, 'nuchter');
+ await expect(container).toHaveClass(/glucose-empty/);
+ await expect(container.locator('.glucose-status-text')).toHaveText('Nog meten');
+ await expect(container.locator('.glucose-input')).toHaveValue('');
// Also check the other moments are empty too
for (const moment of ['naOntbijt', 'naLunch', 'naAvondeten']) {
- const otherItem = glucoseItem(page, moment);
- await expect(otherItem).toHaveClass(/glucose-empty/);
+ const otherContainer = glucoseContainer(page, moment);
+ await expect(otherContainer).toHaveClass(/glucose-empty/);
}
// Navigate back to today
@@ -245,10 +240,10 @@ test.describe('Glucose-metingen — invoer, validatie, kleurcodering, persistent
await page.waitForTimeout(600);
// Today's value should still be there
- item = glucoseItem(page, 'nuchter');
- await expect(item).toHaveClass(/glucose-good/);
- await expect(item.locator('.glucose-status-text')).toHaveText('Binnen range');
- await expect(item.locator('.glucose-input')).toHaveValue('5.1');
+ container = glucoseContainer(page, 'nuchter');
+ await expect(container).toHaveClass(/glucose-good/);
+ await expect(container.locator('.glucose-status-text')).toHaveText('Binnen range');
+ await expect(container.locator('.glucose-input')).toHaveValue('5.1');
});
// ---- 10. Decimale invoer — step 0.1 ----
@@ -263,18 +258,18 @@ test.describe('Glucose-metingen — invoer, validatie, kleurcodering, persistent
// Enter 5.5 (decimal)
await setGlucoseValue(page, 'nuchter', 5.5);
- let item = glucoseItem(page, 'nuchter');
- await expect(item.locator('.glucose-input')).toHaveValue('5.5');
+ let container = glucoseContainer(page, 'nuchter');
+ await expect(container.locator('.glucose-input')).toHaveValue('5.5');
// 5.5 ≥ 5.4 for nuchter → should be bad/Te hoog
- await expect(item).toHaveClass(/glucose-bad/);
- await expect(item.locator('.glucose-status-text')).toHaveText('Te hoog');
+ await expect(container).toHaveClass(/glucose-bad/);
+ await expect(container.locator('.glucose-status-text')).toHaveText('Te hoog');
// Now test a postprandial decimal value
await setGlucoseValue(page, 'naOntbijt', 6.2);
- item = glucoseItem(page, 'naOntbijt');
- await expect(item.locator('.glucose-input')).toHaveValue('6.2');
- await expect(item).toHaveClass(/glucose-good/);
- await expect(item.locator('.glucose-status-text')).toHaveText('Binnen range');
+ container = glucoseContainer(page, 'naOntbijt');
+ await expect(container.locator('.glucose-input')).toHaveValue('6.2');
+ await expect(container).toHaveClass(/glucose-good/);
+ await expect(container.locator('.glucose-status-text')).toHaveText('Binnen range');
});
});