UX: Save-meal checkbox-selectie — kies welke items in maaltijd gaan
- Checkboxes per item (2+ items) met counter in knoptekst - Save-meal knop terug naar full-width footer stijl (.save-meal-footer-btn) - suggestMealName vult nu meteen slimme naam in - Worker hernoemde .save-meal-btn → .save-meal-footer-btn - Tests geüpdatet: pre-filled name ipv empty, clear-input voor valideertest - Coder profiel verwijderd (overbodig naast frontend/backend-eng) 39/39 tests groen
This commit is contained in:
parent
45a32ca964
commit
0bceb721ca
3 changed files with 211 additions and 61 deletions
|
|
@ -1246,28 +1246,62 @@ body {
|
||||||
opacity: 0.6;
|
opacity: 0.6;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ===== Save Meal Icon in Header ===== */
|
/* ===== Save Meal — checkbox + footer ===== */
|
||||||
.save-meal-btn {
|
.item-checkbox-wrapper {
|
||||||
display: inline-flex;
|
display: none;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
flex-shrink: 0;
|
||||||
width: 24px;
|
}
|
||||||
height: 24px;
|
.eetmoment-card.has-multi-items .item-checkbox-wrapper {
|
||||||
font-size: 14px;
|
display: inline-flex;
|
||||||
color: var(--primary-dark);
|
}
|
||||||
|
.item-checkbox {
|
||||||
|
flex-shrink: 0;
|
||||||
|
width: 18px;
|
||||||
|
height: 18px;
|
||||||
|
accent-color: var(--primary);
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
border-radius: 50%;
|
margin: 0 6px 0 0;
|
||||||
transition: background 0.15s, transform 0.15s;
|
|
||||||
user-select: none;
|
|
||||||
-webkit-user-select: none;
|
|
||||||
}
|
}
|
||||||
.save-meal-btn:hover {
|
/* Meal-group checkbox: on the header line */
|
||||||
|
.eetmoment-meal-group .item-checkbox {
|
||||||
|
margin: 0 6px 0 -2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.save-meal-footer {
|
||||||
|
padding: 4px 14px 10px;
|
||||||
|
}
|
||||||
|
.save-meal-footer-btn {
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
padding: 10px 16px;
|
||||||
background: var(--primary-light);
|
background: var(--primary-light);
|
||||||
transform: scale(1.1);
|
border: 1px solid var(--primary);
|
||||||
|
border-radius: var(--radius-sm);
|
||||||
|
color: var(--primary-dark);
|
||||||
|
font-size: var(--font-sm);
|
||||||
|
font-weight: 600;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background 0.15s, opacity 0.15s;
|
||||||
|
text-align: center;
|
||||||
|
font-family: var(--body-font);
|
||||||
|
min-height: var(--touch-min);
|
||||||
|
line-height: 1.3;
|
||||||
}
|
}
|
||||||
.save-meal-btn:active {
|
.save-meal-footer-btn:hover {
|
||||||
background: var(--accent);
|
background: var(--primary);
|
||||||
transform: scale(0.95);
|
color: var(--white);
|
||||||
|
}
|
||||||
|
.save-meal-footer-btn:active {
|
||||||
|
background: var(--primary-dark);
|
||||||
|
color: var(--white);
|
||||||
|
}
|
||||||
|
.save-meal-footer-btn:disabled {
|
||||||
|
opacity: 0.4;
|
||||||
|
cursor: default;
|
||||||
|
background: var(--primary-lighter);
|
||||||
|
color: var(--primary-dark);
|
||||||
|
border-color: var(--border);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ===== Diary — nested meal groups ===== */
|
/* ===== Diary — nested meal groups ===== */
|
||||||
|
|
@ -3322,6 +3356,7 @@ button:active, .btn:active, .meal-card-main:active, .dagboek-item:active, .food-
|
||||||
return `
|
return `
|
||||||
<li class="eetmoment-meal-group">
|
<li class="eetmoment-meal-group">
|
||||||
<div class="eetmoment-meal-header" data-action="toggle-meal" data-entry="${i}">
|
<div class="eetmoment-meal-header" data-action="toggle-meal" data-entry="${i}">
|
||||||
|
<span class="item-checkbox-wrapper"><input type="checkbox" class="item-checkbox" data-index="${i}" checked></span>
|
||||||
<span class="eetmoment-meal-name">🍽 ${escapeHtml(entry.mealNaam)}</span>
|
<span class="eetmoment-meal-name">🍽 ${escapeHtml(entry.mealNaam)}</span>
|
||||||
<span class="eetmoment-meal-kh">${formatKh(mealKh)} g</span>
|
<span class="eetmoment-meal-kh">${formatKh(mealKh)} g</span>
|
||||||
<span style="font-size:0.7rem;color:var(--text-muted);"><i class="fas fa-chevron-up"></i></span>
|
<span style="font-size:0.7rem;color:var(--text-muted);"><i class="fas fa-chevron-up"></i></span>
|
||||||
|
|
@ -3349,6 +3384,9 @@ button:active, .btn:active, .meal-card-main:active, .dagboek-item:active, .food-
|
||||||
const itemOverLimit = overLimit && itemKh > 0;
|
const itemOverLimit = overLimit && itemKh > 0;
|
||||||
return `
|
return `
|
||||||
<li class="eetmoment-item${itemOverLimit ? ' over-limit' : ''}">
|
<li class="eetmoment-item${itemOverLimit ? ' over-limit' : ''}">
|
||||||
|
<span class="item-checkbox-wrapper">
|
||||||
|
<input type="checkbox" class="item-checkbox" data-index="${i}" checked>
|
||||||
|
</span>
|
||||||
<div class="eetmoment-item-info">
|
<div class="eetmoment-item-info">
|
||||||
<div class="eetmoment-item-name">${escapeHtml(displayNaam(entry.item.naam))}</div>
|
<div class="eetmoment-item-name">${escapeHtml(displayNaam(entry.item.naam))}</div>
|
||||||
<div class="eetmoment-item-portion">
|
<div class="eetmoment-item-portion">
|
||||||
|
|
@ -3372,21 +3410,26 @@ button:active, .btn:active, .meal-card-main:active, .dagboek-item:active, .food-
|
||||||
const inlineGlucoseHtml = glucoseMoment ? renderInlineGlucoseItem(glucoseMoment) : '';
|
const inlineGlucoseHtml = glucoseMoment ? renderInlineGlucoseItem(glucoseMoment) : '';
|
||||||
|
|
||||||
return `
|
return `
|
||||||
<li class="eetmoment-card${cardOverLimit ? ' over-limit' : ''}" style="--i:${mi}">
|
<li class="eetmoment-card ${cardOverLimit ? 'over-limit' : ''}${items.length > 1 ? ' has-multi-items' : ''}" style="--i:${mi}">
|
||||||
<div class="eetmoment-header" data-moment="${moment.id}">
|
<div class="eetmoment-header" data-moment="${moment.id}">
|
||||||
<div class="eetmoment-header-left">
|
<div class="eetmoment-header-left">
|
||||||
<span class="eetmoment-icon">${moment.icon}</span>
|
<span class="eetmoment-icon">${moment.icon}</span>
|
||||||
<span class="eetmoment-name">${moment.name}</span>
|
<span class="eetmoment-name">${moment.name}</span>
|
||||||
</div>
|
</div>
|
||||||
<span class="eetmoment-kh">${roundedKh > 0 ? formatKh(roundedKh) + ' g' : '—'}</span>
|
<span class="eetmoment-kh">${roundedKh > 0 ? formatKh(roundedKh) + ' g' : '—'}</span>
|
||||||
${items.length > 0 ? '<span class=\"save-meal-btn\" data-moment=\"' + moment.id + '\"><i class=\"fas fa-star\"></i></span>' : ''}
|
<span style="font-size:0.8rem;color:var(--text-muted)">${items.length > 0 ? '<i class="fas fa-chevron-down"></i>' : ''}</span>
|
||||||
<span style="font-size:0.8rem;color:var(--text-muted)">${items.length > 0 ? '<i class=\"fas fa-chevron-down\"></i>' : ''}</span>
|
|
||||||
</div>
|
</div>
|
||||||
${items.length > 0 ? `<ul class="eetmoment-items">${itemsHtml}</ul>` : ''}
|
${items.length > 0 ? `<ul class="eetmoment-items">${itemsHtml}</ul>` : ''}
|
||||||
${inlineGlucoseHtml}
|
${inlineGlucoseHtml}
|
||||||
<div class="eetmoment-add-btn" data-moment="${moment.id}">
|
<div class="eetmoment-add-btn" data-moment="${moment.id}">
|
||||||
<span>+</span> Voedingsmiddel toevoegen
|
<span>+</span> Voedingsmiddel toevoegen
|
||||||
</div>
|
</div>
|
||||||
|
${items.length > 0 ? `
|
||||||
|
<div class="save-meal-footer" data-moment="${moment.id}">
|
||||||
|
<button class="save-meal-footer-btn" data-moment="${moment.id}">
|
||||||
|
<i class="fas fa-star"></i> Opslaan ${items.length > 1 ? items.length + ' geselecteerde items' : '1 item'} als maaltijd
|
||||||
|
</button>
|
||||||
|
</div>` : ''}
|
||||||
</li>
|
</li>
|
||||||
`;
|
`;
|
||||||
}).join('');
|
}).join('');
|
||||||
|
|
@ -3443,18 +3486,43 @@ button:active, .btn:active, .meal-card-main:active, .dagboek-item:active, .food-
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// Save meal buttons
|
// Save meal footer button
|
||||||
listEl.querySelectorAll('.save-meal-btn').forEach(btn => {
|
listEl.querySelectorAll('.save-meal-footer-btn').forEach(btn => {
|
||||||
btn.addEventListener('click', (e) => {
|
btn.addEventListener('click', (e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
saveCurrentMomentAsMeal(btn.dataset.moment);
|
const momentId = btn.dataset.moment;
|
||||||
|
const card = btn.closest('.eetmoment-card');
|
||||||
|
// Collect selected indices from checked checkboxes
|
||||||
|
const checked = card.querySelectorAll('.item-checkbox:checked');
|
||||||
|
const selectedIndices = Array.from(checked).map(cb => parseInt(cb.dataset.index));
|
||||||
|
saveCurrentMomentAsMeal(momentId, selectedIndices);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Checkbox change → update save button text
|
||||||
|
listEl.querySelectorAll('.item-checkbox').forEach(cb => {
|
||||||
|
cb.addEventListener('change', () => {
|
||||||
|
const card = cb.closest('.eetmoment-card');
|
||||||
|
if (!card) return;
|
||||||
|
const footer = card.querySelector('.save-meal-footer');
|
||||||
|
if (!footer) return;
|
||||||
|
const btn = footer.querySelector('.save-meal-footer-btn');
|
||||||
|
const checked = card.querySelectorAll('.item-checkbox:checked').length;
|
||||||
|
const total = card.querySelectorAll('.item-checkbox').length;
|
||||||
|
if (checked === 0) {
|
||||||
|
btn.disabled = true;
|
||||||
|
btn.textContent = '⭐ 0 geselecteerde items als maaltijd';
|
||||||
|
} else {
|
||||||
|
btn.disabled = false;
|
||||||
|
btn.textContent = `⭐ Opslaan ${checked} geselecteerde ${checked === 1 ? 'item' : 'items'} als maaltijd`;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// Meal group toggle expand/collapse
|
// Meal group toggle expand/collapse
|
||||||
listEl.querySelectorAll('.meal-group-header').forEach(header => {
|
listEl.querySelectorAll('.meal-group-header').forEach(header => {
|
||||||
header.addEventListener('click', (e) => {
|
header.addEventListener('click', (e) => {
|
||||||
if (e.target.closest('.meal-group-edit, .eetmoment-remove')) return;
|
if (e.target.closest('.meal-group-edit, .eetmoment-remove, .item-checkbox')) return;
|
||||||
header.classList.toggle('expanded');
|
header.classList.toggle('expanded');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
@ -4286,13 +4354,19 @@ button:active, .btn:active, .meal-card-main:active, .dagboek-item:active, .food-
|
||||||
return r;
|
return r;
|
||||||
}
|
}
|
||||||
|
|
||||||
function saveCurrentMomentAsMeal(momentId) {
|
function saveCurrentMomentAsMeal(momentId, selectedIndices) {
|
||||||
const dayData = getOrCreateDay(activeDate);
|
const dayData = getOrCreateDay(activeDate);
|
||||||
const rawItems = dayData[momentId] || [];
|
const rawItems = dayData[momentId] || [];
|
||||||
if (rawItems.length === 0) return;
|
if (rawItems.length === 0) return;
|
||||||
|
|
||||||
|
// Filter to only selected items (by index in rawItems)
|
||||||
|
const filteredRaw = selectedIndices
|
||||||
|
? rawItems.filter((_, i) => selectedIndices.includes(i))
|
||||||
|
: rawItems;
|
||||||
|
if (filteredRaw.length === 0) return;
|
||||||
|
|
||||||
// Flatten: unwrap meal-group entries into uniform {item, portie} items
|
// Flatten: unwrap meal-group entries into uniform {item, portie} items
|
||||||
const items = flattenEntries(rawItems);
|
const items = flattenEntries(filteredRaw);
|
||||||
|
|
||||||
// Calculate total kh
|
// Calculate total kh
|
||||||
let khTotaal = 0;
|
let khTotaal = 0;
|
||||||
|
|
@ -4325,7 +4399,7 @@ button:active, .btn:active, .meal-card-main:active, .dagboek-item:active, .food-
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-body">
|
<div class="modal-body">
|
||||||
<label class="meal-name-label">Maaltijdnaam</label>
|
<label class="meal-name-label">Maaltijdnaam</label>
|
||||||
<input type="text" class="meal-name-input" id="mealNameInput" value="" placeholder="Geef een naam aan deze maaltijd..." autofocus>
|
<input type="text" class="meal-name-input" id="mealNameInput" value="${escapeHtml(defaultName)}" placeholder="Geef een naam aan deze maaltijd..." autofocus>
|
||||||
<div class="meal-name-items-preview">${previewText}</div>
|
<div class="meal-name-items-preview">${previewText}</div>
|
||||||
<div class="meal-name-actions">
|
<div class="meal-name-actions">
|
||||||
<button class="meal-name-cancel" id="mealNameCancel">Annuleren</button>
|
<button class="meal-name-cancel" id="mealNameCancel">Annuleren</button>
|
||||||
|
|
|
||||||
128
index.html
128
index.html
|
|
@ -1246,28 +1246,62 @@ body {
|
||||||
opacity: 0.6;
|
opacity: 0.6;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ===== Save Meal Icon in Header ===== */
|
/* ===== Save Meal — checkbox + footer ===== */
|
||||||
.save-meal-btn {
|
.item-checkbox-wrapper {
|
||||||
display: inline-flex;
|
display: none;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
flex-shrink: 0;
|
||||||
width: 24px;
|
}
|
||||||
height: 24px;
|
.eetmoment-card.has-multi-items .item-checkbox-wrapper {
|
||||||
font-size: 14px;
|
display: inline-flex;
|
||||||
color: var(--primary-dark);
|
}
|
||||||
|
.item-checkbox {
|
||||||
|
flex-shrink: 0;
|
||||||
|
width: 18px;
|
||||||
|
height: 18px;
|
||||||
|
accent-color: var(--primary);
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
border-radius: 50%;
|
margin: 0 6px 0 0;
|
||||||
transition: background 0.15s, transform 0.15s;
|
|
||||||
user-select: none;
|
|
||||||
-webkit-user-select: none;
|
|
||||||
}
|
}
|
||||||
.save-meal-btn:hover {
|
/* Meal-group checkbox: on the header line */
|
||||||
|
.eetmoment-meal-group .item-checkbox {
|
||||||
|
margin: 0 6px 0 -2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.save-meal-footer {
|
||||||
|
padding: 4px 14px 10px;
|
||||||
|
}
|
||||||
|
.save-meal-footer-btn {
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
padding: 10px 16px;
|
||||||
background: var(--primary-light);
|
background: var(--primary-light);
|
||||||
transform: scale(1.1);
|
border: 1px solid var(--primary);
|
||||||
|
border-radius: var(--radius-sm);
|
||||||
|
color: var(--primary-dark);
|
||||||
|
font-size: var(--font-sm);
|
||||||
|
font-weight: 600;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background 0.15s, opacity 0.15s;
|
||||||
|
text-align: center;
|
||||||
|
font-family: var(--body-font);
|
||||||
|
min-height: var(--touch-min);
|
||||||
|
line-height: 1.3;
|
||||||
}
|
}
|
||||||
.save-meal-btn:active {
|
.save-meal-footer-btn:hover {
|
||||||
background: var(--accent);
|
background: var(--primary);
|
||||||
transform: scale(0.95);
|
color: var(--white);
|
||||||
|
}
|
||||||
|
.save-meal-footer-btn:active {
|
||||||
|
background: var(--primary-dark);
|
||||||
|
color: var(--white);
|
||||||
|
}
|
||||||
|
.save-meal-footer-btn:disabled {
|
||||||
|
opacity: 0.4;
|
||||||
|
cursor: default;
|
||||||
|
background: var(--primary-lighter);
|
||||||
|
color: var(--primary-dark);
|
||||||
|
border-color: var(--border);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ===== Diary — nested meal groups ===== */
|
/* ===== Diary — nested meal groups ===== */
|
||||||
|
|
@ -3322,6 +3356,7 @@ button:active, .btn:active, .meal-card-main:active, .dagboek-item:active, .food-
|
||||||
return `
|
return `
|
||||||
<li class="eetmoment-meal-group">
|
<li class="eetmoment-meal-group">
|
||||||
<div class="eetmoment-meal-header" data-action="toggle-meal" data-entry="${i}">
|
<div class="eetmoment-meal-header" data-action="toggle-meal" data-entry="${i}">
|
||||||
|
<span class="item-checkbox-wrapper"><input type="checkbox" class="item-checkbox" data-index="${i}" checked></span>
|
||||||
<span class="eetmoment-meal-name">🍽 ${escapeHtml(entry.mealNaam)}</span>
|
<span class="eetmoment-meal-name">🍽 ${escapeHtml(entry.mealNaam)}</span>
|
||||||
<span class="eetmoment-meal-kh">${formatKh(mealKh)} g</span>
|
<span class="eetmoment-meal-kh">${formatKh(mealKh)} g</span>
|
||||||
<span style="font-size:0.7rem;color:var(--text-muted);"><i class="fas fa-chevron-up"></i></span>
|
<span style="font-size:0.7rem;color:var(--text-muted);"><i class="fas fa-chevron-up"></i></span>
|
||||||
|
|
@ -3349,6 +3384,9 @@ button:active, .btn:active, .meal-card-main:active, .dagboek-item:active, .food-
|
||||||
const itemOverLimit = overLimit && itemKh > 0;
|
const itemOverLimit = overLimit && itemKh > 0;
|
||||||
return `
|
return `
|
||||||
<li class="eetmoment-item${itemOverLimit ? ' over-limit' : ''}">
|
<li class="eetmoment-item${itemOverLimit ? ' over-limit' : ''}">
|
||||||
|
<span class="item-checkbox-wrapper">
|
||||||
|
<input type="checkbox" class="item-checkbox" data-index="${i}" checked>
|
||||||
|
</span>
|
||||||
<div class="eetmoment-item-info">
|
<div class="eetmoment-item-info">
|
||||||
<div class="eetmoment-item-name">${escapeHtml(displayNaam(entry.item.naam))}</div>
|
<div class="eetmoment-item-name">${escapeHtml(displayNaam(entry.item.naam))}</div>
|
||||||
<div class="eetmoment-item-portion">
|
<div class="eetmoment-item-portion">
|
||||||
|
|
@ -3372,21 +3410,26 @@ button:active, .btn:active, .meal-card-main:active, .dagboek-item:active, .food-
|
||||||
const inlineGlucoseHtml = glucoseMoment ? renderInlineGlucoseItem(glucoseMoment) : '';
|
const inlineGlucoseHtml = glucoseMoment ? renderInlineGlucoseItem(glucoseMoment) : '';
|
||||||
|
|
||||||
return `
|
return `
|
||||||
<li class="eetmoment-card${cardOverLimit ? ' over-limit' : ''}" style="--i:${mi}">
|
<li class="eetmoment-card ${cardOverLimit ? 'over-limit' : ''}${items.length > 1 ? ' has-multi-items' : ''}" style="--i:${mi}">
|
||||||
<div class="eetmoment-header" data-moment="${moment.id}">
|
<div class="eetmoment-header" data-moment="${moment.id}">
|
||||||
<div class="eetmoment-header-left">
|
<div class="eetmoment-header-left">
|
||||||
<span class="eetmoment-icon">${moment.icon}</span>
|
<span class="eetmoment-icon">${moment.icon}</span>
|
||||||
<span class="eetmoment-name">${moment.name}</span>
|
<span class="eetmoment-name">${moment.name}</span>
|
||||||
</div>
|
</div>
|
||||||
<span class="eetmoment-kh">${roundedKh > 0 ? formatKh(roundedKh) + ' g' : '—'}</span>
|
<span class="eetmoment-kh">${roundedKh > 0 ? formatKh(roundedKh) + ' g' : '—'}</span>
|
||||||
${items.length > 0 ? '<span class=\"save-meal-btn\" data-moment=\"' + moment.id + '\"><i class=\"fas fa-star\"></i></span>' : ''}
|
<span style="font-size:0.8rem;color:var(--text-muted)">${items.length > 0 ? '<i class="fas fa-chevron-down"></i>' : ''}</span>
|
||||||
<span style="font-size:0.8rem;color:var(--text-muted)">${items.length > 0 ? '<i class=\"fas fa-chevron-down\"></i>' : ''}</span>
|
|
||||||
</div>
|
</div>
|
||||||
${items.length > 0 ? `<ul class="eetmoment-items">${itemsHtml}</ul>` : ''}
|
${items.length > 0 ? `<ul class="eetmoment-items">${itemsHtml}</ul>` : ''}
|
||||||
${inlineGlucoseHtml}
|
${inlineGlucoseHtml}
|
||||||
<div class="eetmoment-add-btn" data-moment="${moment.id}">
|
<div class="eetmoment-add-btn" data-moment="${moment.id}">
|
||||||
<span>+</span> Voedingsmiddel toevoegen
|
<span>+</span> Voedingsmiddel toevoegen
|
||||||
</div>
|
</div>
|
||||||
|
${items.length > 0 ? `
|
||||||
|
<div class="save-meal-footer" data-moment="${moment.id}">
|
||||||
|
<button class="save-meal-footer-btn" data-moment="${moment.id}">
|
||||||
|
<i class="fas fa-star"></i> Opslaan ${items.length > 1 ? items.length + ' geselecteerde items' : '1 item'} als maaltijd
|
||||||
|
</button>
|
||||||
|
</div>` : ''}
|
||||||
</li>
|
</li>
|
||||||
`;
|
`;
|
||||||
}).join('');
|
}).join('');
|
||||||
|
|
@ -3443,18 +3486,43 @@ button:active, .btn:active, .meal-card-main:active, .dagboek-item:active, .food-
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// Save meal buttons
|
// Save meal footer button
|
||||||
listEl.querySelectorAll('.save-meal-btn').forEach(btn => {
|
listEl.querySelectorAll('.save-meal-footer-btn').forEach(btn => {
|
||||||
btn.addEventListener('click', (e) => {
|
btn.addEventListener('click', (e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
saveCurrentMomentAsMeal(btn.dataset.moment);
|
const momentId = btn.dataset.moment;
|
||||||
|
const card = btn.closest('.eetmoment-card');
|
||||||
|
// Collect selected indices from checked checkboxes
|
||||||
|
const checked = card.querySelectorAll('.item-checkbox:checked');
|
||||||
|
const selectedIndices = Array.from(checked).map(cb => parseInt(cb.dataset.index));
|
||||||
|
saveCurrentMomentAsMeal(momentId, selectedIndices);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Checkbox change → update save button text
|
||||||
|
listEl.querySelectorAll('.item-checkbox').forEach(cb => {
|
||||||
|
cb.addEventListener('change', () => {
|
||||||
|
const card = cb.closest('.eetmoment-card');
|
||||||
|
if (!card) return;
|
||||||
|
const footer = card.querySelector('.save-meal-footer');
|
||||||
|
if (!footer) return;
|
||||||
|
const btn = footer.querySelector('.save-meal-footer-btn');
|
||||||
|
const checked = card.querySelectorAll('.item-checkbox:checked').length;
|
||||||
|
const total = card.querySelectorAll('.item-checkbox').length;
|
||||||
|
if (checked === 0) {
|
||||||
|
btn.disabled = true;
|
||||||
|
btn.textContent = '⭐ 0 geselecteerde items als maaltijd';
|
||||||
|
} else {
|
||||||
|
btn.disabled = false;
|
||||||
|
btn.textContent = `⭐ Opslaan ${checked} geselecteerde ${checked === 1 ? 'item' : 'items'} als maaltijd`;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// Meal group toggle expand/collapse
|
// Meal group toggle expand/collapse
|
||||||
listEl.querySelectorAll('.meal-group-header').forEach(header => {
|
listEl.querySelectorAll('.meal-group-header').forEach(header => {
|
||||||
header.addEventListener('click', (e) => {
|
header.addEventListener('click', (e) => {
|
||||||
if (e.target.closest('.meal-group-edit, .eetmoment-remove')) return;
|
if (e.target.closest('.meal-group-edit, .eetmoment-remove, .item-checkbox')) return;
|
||||||
header.classList.toggle('expanded');
|
header.classList.toggle('expanded');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
@ -4286,13 +4354,19 @@ button:active, .btn:active, .meal-card-main:active, .dagboek-item:active, .food-
|
||||||
return r;
|
return r;
|
||||||
}
|
}
|
||||||
|
|
||||||
function saveCurrentMomentAsMeal(momentId) {
|
function saveCurrentMomentAsMeal(momentId, selectedIndices) {
|
||||||
const dayData = getOrCreateDay(activeDate);
|
const dayData = getOrCreateDay(activeDate);
|
||||||
const rawItems = dayData[momentId] || [];
|
const rawItems = dayData[momentId] || [];
|
||||||
if (rawItems.length === 0) return;
|
if (rawItems.length === 0) return;
|
||||||
|
|
||||||
|
// Filter to only selected items (by index in rawItems)
|
||||||
|
const filteredRaw = selectedIndices
|
||||||
|
? rawItems.filter((_, i) => selectedIndices.includes(i))
|
||||||
|
: rawItems;
|
||||||
|
if (filteredRaw.length === 0) return;
|
||||||
|
|
||||||
// Flatten: unwrap meal-group entries into uniform {item, portie} items
|
// Flatten: unwrap meal-group entries into uniform {item, portie} items
|
||||||
const items = flattenEntries(rawItems);
|
const items = flattenEntries(filteredRaw);
|
||||||
|
|
||||||
// Calculate total kh
|
// Calculate total kh
|
||||||
let khTotaal = 0;
|
let khTotaal = 0;
|
||||||
|
|
@ -4325,7 +4399,7 @@ button:active, .btn:active, .meal-card-main:active, .dagboek-item:active, .food-
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-body">
|
<div class="modal-body">
|
||||||
<label class="meal-name-label">Maaltijdnaam</label>
|
<label class="meal-name-label">Maaltijdnaam</label>
|
||||||
<input type="text" class="meal-name-input" id="mealNameInput" value="" placeholder="Geef een naam aan deze maaltijd..." autofocus>
|
<input type="text" class="meal-name-input" id="mealNameInput" value="${escapeHtml(defaultName)}" placeholder="Geef een naam aan deze maaltijd..." autofocus>
|
||||||
<div class="meal-name-items-preview">${previewText}</div>
|
<div class="meal-name-items-preview">${previewText}</div>
|
||||||
<div class="meal-name-actions">
|
<div class="meal-name-actions">
|
||||||
<button class="meal-name-cancel" id="mealNameCancel">Annuleren</button>
|
<button class="meal-name-cancel" id="mealNameCancel">Annuleren</button>
|
||||||
|
|
|
||||||
|
|
@ -192,7 +192,7 @@ test.describe('Karby Eetdagboek', () => {
|
||||||
|
|
||||||
// ---------- Maaltijden ----------
|
// ---------- Maaltijden ----------
|
||||||
|
|
||||||
test('meal save: name input is empty by default', async ({ page }) => {
|
test('meal save: name input pre-filled with smart suggestion', async ({ page }) => {
|
||||||
await page.goto(BASE);
|
await page.goto(BASE);
|
||||||
await page.locator('.search-input').fill('brood');
|
await page.locator('.search-input').fill('brood');
|
||||||
await page.waitForTimeout(800);
|
await page.waitForTimeout(800);
|
||||||
|
|
@ -203,13 +203,14 @@ test.describe('Karby Eetdagboek', () => {
|
||||||
// Item toevoegen via addMealSubmit → sluit modal + gaat naar dagboek
|
// Item toevoegen via addMealSubmit → sluit modal + gaat naar dagboek
|
||||||
await page.locator('#addMealSubmit').click();
|
await page.locator('#addMealSubmit').click();
|
||||||
await page.waitForTimeout(800);
|
await page.waitForTimeout(800);
|
||||||
const saveBtn = page.locator('.save-meal-btn');
|
const saveBtn = page.locator('.save-meal-footer-btn');
|
||||||
await expect(saveBtn.first()).toBeVisible({ timeout: 5000 });
|
await expect(saveBtn.first()).toBeVisible({ timeout: 5000 });
|
||||||
await saveBtn.first().click();
|
await saveBtn.first().click();
|
||||||
await page.waitForTimeout(500);
|
await page.waitForTimeout(500);
|
||||||
const nameInput = page.locator('#mealNameInput');
|
const nameInput = page.locator('#mealNameInput');
|
||||||
await expect(nameInput).toBeVisible({ timeout: 5000 });
|
await expect(nameInput).toBeVisible({ timeout: 5000 });
|
||||||
await expect(nameInput).toHaveValue('');
|
// Smart suggestion should pre-fill with a non-empty name
|
||||||
|
await expect(nameInput).not.toHaveValue('');
|
||||||
await page.locator('#mealNameCancel').click();
|
await page.locator('#mealNameCancel').click();
|
||||||
await page.waitForTimeout(300);
|
await page.waitForTimeout(300);
|
||||||
});
|
});
|
||||||
|
|
@ -224,13 +225,14 @@ test.describe('Karby Eetdagboek', () => {
|
||||||
await page.waitForTimeout(500);
|
await page.waitForTimeout(500);
|
||||||
await page.locator('#addMealSubmit').click();
|
await page.locator('#addMealSubmit').click();
|
||||||
await page.waitForTimeout(800);
|
await page.waitForTimeout(800);
|
||||||
const saveBtn = page.locator('.save-meal-btn');
|
const saveBtn = page.locator('.save-meal-footer-btn');
|
||||||
await expect(saveBtn.first()).toBeVisible({ timeout: 5000 });
|
await expect(saveBtn.first()).toBeVisible({ timeout: 5000 });
|
||||||
await saveBtn.first().click();
|
await saveBtn.first().click();
|
||||||
await page.waitForTimeout(500);
|
await page.waitForTimeout(500);
|
||||||
const nameInput = page.locator('#mealNameInput');
|
const nameInput = page.locator('#mealNameInput');
|
||||||
await expect(nameInput).toBeVisible({ timeout: 5000 });
|
await expect(nameInput).toBeVisible({ timeout: 5000 });
|
||||||
await expect(nameInput).toHaveValue('');
|
// Clear the smart-suggested name to test empty validation
|
||||||
|
await nameInput.fill('');
|
||||||
// Opslaan zonder naam → validatiefout
|
// Opslaan zonder naam → validatiefout
|
||||||
await page.locator('#mealNameSave').click();
|
await page.locator('#mealNameSave').click();
|
||||||
await page.waitForTimeout(300);
|
await page.waitForTimeout(300);
|
||||||
|
|
@ -301,7 +303,7 @@ test.describe('Karby Eetdagboek', () => {
|
||||||
await page.locator('#addMealSubmit').click();
|
await page.locator('#addMealSubmit').click();
|
||||||
await page.waitForTimeout(800);
|
await page.waitForTimeout(800);
|
||||||
// Stap 2: Sla op als maaltijd
|
// Stap 2: Sla op als maaltijd
|
||||||
const saveBtn = page.locator('.save-meal-btn');
|
const saveBtn = page.locator('.save-meal-footer-btn');
|
||||||
await expect(saveBtn.first()).toBeVisible({ timeout: 5000 });
|
await expect(saveBtn.first()).toBeVisible({ timeout: 5000 });
|
||||||
await saveBtn.first().click();
|
await saveBtn.first().click();
|
||||||
await page.waitForTimeout(500);
|
await page.waitForTimeout(500);
|
||||||
|
|
@ -370,7 +372,7 @@ test.describe('Karby Eetdagboek', () => {
|
||||||
// Navigate to diary, save as meal
|
// Navigate to diary, save as meal
|
||||||
await page.locator('#navDagboek').click();
|
await page.locator('#navDagboek').click();
|
||||||
await page.waitForTimeout(600);
|
await page.waitForTimeout(600);
|
||||||
const saveBtn = page.locator('.save-meal-btn');
|
const saveBtn = page.locator('.save-meal-footer-btn');
|
||||||
if (await saveBtn.count() === 0) { test.skip(); return; }
|
if (await saveBtn.count() === 0) { test.skip(); return; }
|
||||||
await saveBtn.first().click();
|
await saveBtn.first().click();
|
||||||
await page.waitForTimeout(500);
|
await page.waitForTimeout(500);
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue