UX: save-meal knop alleen zichtbaar bij 2+ geselecteerde items

- Save-knop volledig hidden (display:none) tot 2+ items geselecteerd
- 1 item: save-knop direct zichtbaar, enabled
- confirmAction() herbruikbare confirmatie dialog toegevoegd
- Bulk-delete knop in footer (hidden tot checkbox selectie)
- Edit-knop in meal-groups: wit op donkergeel (betere leesbaarheid)
- Meal-groups krijgen geen checkbox

39/39 tests groen
This commit is contained in:
cas 2026-07-24 21:45:57 +02:00
parent 4e34946b0d
commit 2de732159e
2 changed files with 508 additions and 54 deletions

View file

@ -1304,6 +1304,83 @@ body {
border-color: var(--border);
}
/* Bulk delete button in footer */
.bulk-delete-btn {
display: flex;
align-items: center;
justify-content: center;
gap: 6px;
width: 100%;
padding: 10px 14px;
font-size: var(--font-sm);
color: var(--danger);
cursor: pointer;
border: 1px solid var(--danger);
border-radius: var(--radius-xs);
background: transparent;
margin-top: 6px;
transition: background 0.15s;
}
.bulk-delete-btn:hover {
background: var(--danger);
color: var(--white);
}
/* ===== Diary — nested meal groups ===== */
.delete-selected-footer-btn {
display: block;
width: 100%;
padding: 10px 16px;
margin-top: 6px;
background: var(--danger-light);
border: 1px solid var(--danger);
border-radius: var(--radius-sm);
color: var(--danger);
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;
}
.delete-selected-footer-btn:hover {
background: var(--danger);
color: var(--white);
}
.delete-selected-footer-btn:active {
background: #B71C1C;
color: var(--white);
}
.delete-selected-footer-btn.hidden {
display: none;
}
/* ===== Clear-eetmoment header button ===== */
.eetmoment-clear-btn {
background: none;
border: none;
font-size: 0.75rem;
color: var(--text-muted);
cursor: pointer;
padding: 4px 6px;
min-width: var(--touch-min);
min-height: var(--touch-min);
display: inline-flex;
align-items: center;
justify-content: center;
border-radius: 50%;
transition: background 0.15s, color 0.15s;
opacity: 0.5;
flex-shrink: 0;
}
.eetmoment-clear-btn:hover {
background: var(--danger-light);
color: var(--danger);
opacity: 1;
}
/* ===== Diary — nested meal groups ===== */
.eetmoment-meal-group {
background: var(--primary-lighter);
@ -1365,6 +1442,15 @@ body {
align-items: center;
gap: 4px;
}
.eetmoment-meal-item .portion-badge {
background: var(--white);
border-color: var(--border);
color: var(--text);
}
.eetmoment-meal-item .portion-badge:hover {
background: var(--white);
border-color: var(--primary);
}
.eetmoment-meal-item .eetmoment-item-kh {
font-size: var(--font-sm);
font-weight: 700;
@ -3415,9 +3501,12 @@ button:active, .btn:active, .meal-card-main:active, .dagboek-item:active, .food-
<span class="eetmoment-icon">${moment.icon}</span>
<span class="eetmoment-name">${moment.name}</span>
</div>
<div style="display:flex;align-items:center;gap:2px;">
<span class="eetmoment-kh">${roundedKh > 0 ? formatKh(roundedKh) + ' g' : '—'}</span>
${items.length > 0 ? `<button class="eetmoment-clear-btn" data-moment="${moment.id}" title="Eetmoment legen" aria-label="Eetmoment legen"><i class="fas fa-broom"></i></button>` : ''}
<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>` : ''}
${inlineGlucoseHtml}
<div class="eetmoment-add-btn" data-moment="${moment.id}">
@ -3425,12 +3514,15 @@ button:active, .btn:active, .meal-card-main:active, .dagboek-item:active, .food-
</div>
${items.length > 0 ? `
<div class="save-meal-footer" data-moment="${moment.id}">
<button class="save-meal-footer-btn" data-moment="${moment.id}"${items.length > 1 ? ' disabled' : ''}>
<button class="save-meal-footer-btn" data-moment="${moment.id}"${items.length > 1 ? ' style="display:none"' : ''}>
${items.length > 1
? `<i class="fas fa-star"></i> Selecteer ${items.length} items`
: `<i class="fas fa-star"></i> Opslaan als maaltijd`
}
</button>
<button class="delete-selected-footer-btn hidden" data-moment="${moment.id}">
<i class="fas fa-trash"></i> Verwijder geselecteerde
</button>
</div>` : ''}
</li>
`;
@ -3501,26 +3593,74 @@ button:active, .btn:active, .meal-card-main:active, .dagboek-item:active, .food-
});
});
// Checkbox change → update save button text
// Checkbox change → update save/delete button state
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 saveBtn = footer.querySelector('.save-meal-footer-btn');
const delBtn = footer.querySelector('.delete-selected-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 = '⭐ Selecteer ' + total + ' items';
saveBtn.disabled = true;
saveBtn.innerHTML = `<i class="fas fa-star"></i> Selecteer ${total} items`;
if (delBtn) delBtn.classList.add('hidden');
} else {
btn.disabled = false;
btn.textContent = '⭐ Opslaan ' + checked + ' ' + (checked === 1 ? 'item' : 'items') + ' als maaltijd';
saveBtn.disabled = false;
saveBtn.innerHTML = `<i class="fas fa-star"></i> Opslaan ${checked} ${checked === 1 ? 'item' : 'items'} als maaltijd`;
if (delBtn) delBtn.classList.remove('hidden');
}
});
});
// Delete selected items button
listEl.querySelectorAll('.delete-selected-footer-btn').forEach(btn => {
btn.addEventListener('click', async (e) => {
e.stopPropagation();
const momentId = btn.dataset.moment;
const card = btn.closest('.eetmoment-card');
if (!card) return;
const checked = card.querySelectorAll('.item-checkbox:checked');
const count = checked.length;
if (count === 0) return;
const confirmed = await confirmAction({
message: `Weet je zeker dat je ${count} geselecteerde ${count === 1 ? 'item' : 'items'} wilt verwijderen?`
});
if (!confirmed) return;
// Collect indices in reverse order to avoid index shift during splice
const indices = Array.from(checked).map(cb => parseInt(cb.dataset.index)).sort((a, b) => b - a);
const dayData = getOrCreateDay(activeDate);
indices.forEach(idx => {
dayData[momentId].splice(idx, 1);
});
saveToStorage();
renderDagboek();
showToast(`${count} ${count === 1 ? 'item verwijderd' : 'items verwijderd'}`);
});
});
// Clear eetmoment button
listEl.querySelectorAll('.eetmoment-clear-btn').forEach(btn => {
btn.addEventListener('click', async (e) => {
e.stopPropagation();
const momentId = btn.dataset.moment;
const dayData = getOrCreateDay(activeDate);
const items = dayData[momentId] || [];
if (items.length === 0) return;
const confirmed = await confirmAction({
message: `Weet je zeker dat je alle ${items.length} ${items.length === 1 ? 'item' : 'items'} uit dit eetmoment wilt verwijderen?`
});
if (!confirmed) return;
dayData[momentId] = [];
saveToStorage();
renderDagboek();
showToast('Eetmoment geleegd');
});
});
// Meal group toggle expand/collapse
listEl.querySelectorAll('.meal-group-header').forEach(header => {
header.addEventListener('click', (e) => {
@ -3759,37 +3899,81 @@ button:active, .btn:active, .meal-card-main:active, .dagboek-item:active, .food-
renderDagboek();
}
function removeFromMeal(dateStr, momentId, index) {
// Reusable confirmation dialog for destructive actions
function confirmAction(title, message, onConfirm) {
const overlay = document.createElement('div');
overlay.className = 'modal-overlay';
overlay.style.zIndex = '400';
overlay.innerHTML = `
<div class="modal-sheet">
<div class="modal-handle"></div>
<div style="text-align:center;padding:16px 8px 8px;">
<h3 style="margin:0 0 8px;font-size:1rem;">${title}</h3>
<p style="margin:0 0 16px;color:var(--text-secondary);font-size:var(--font-sm);">${message}</p>
<div style="display:flex;gap:10px;justify-content:center;">
<button class="btn-secondary" id="confirmCancel" style="flex:1;">Annuleren</button>
<button class="btn-danger" id="confirmOk" style="flex:1;background:var(--danger);color:var(--white);border:none;border-radius:var(--radius-xs);padding:10px;font-size:var(--font-sm);cursor:pointer;">Verwijderen</button>
</div>
</div>
</div>
`;
document.body.appendChild(overlay);
document.body.style.overflow = 'hidden';
overlay.querySelector('#confirmCancel').addEventListener('click', () => {
overlay.remove();
document.body.style.overflow = '';
});
overlay.querySelector('#confirmOk').addEventListener('click', () => {
overlay.remove();
document.body.style.overflow = '';
onConfirm();
});
overlay.addEventListener('click', (e) => {
if (e.target === overlay) { overlay.remove(); document.body.style.overflow = ''; }
});
}
async function removeFromMeal(dateStr, momentId, index) {
const day = dagboek[dateStr];
if (day && day[momentId]) {
const removed = day[momentId].splice(index, 1)[0];
const target = day[momentId][index];
if (!target) return;
const name = target.type === 'meal' ? target.mealNaam : target.item.naam;
const confirmed = await confirmAction({
message: `Weet je zeker dat je "${name}" wilt verwijderen?`
});
if (!confirmed) return;
day[momentId].splice(index, 1);
saveToStorage();
renderDagboek();
if (removed) {
const name = removed.type === 'meal' ? removed.mealNaam : removed.item.naam;
showToast('"' + name + '" verwijderd');
}
}
}
/**
* Remove a single item from within a meal group in the diary.
* If the last item is removed, removes the entire meal group.
*/
function removeMealItemFromDiary(dateStr, momentId, entryIdx, mi) {
async function removeMealItemFromDiary(dateStr, momentId, entryIdx, mi) {
const day = dagboek[dateStr];
if (!day || !day[momentId]) return;
const entry = day[momentId][entryIdx];
if (!entry || entry.type !== 'meal') return;
const removed = entry.items.splice(mi, 1)[0];
if (!removed) return;
const item = entry.items[mi];
if (!item) return;
const confirmed = await confirmAction({
message: `Weet je zeker dat je "${item.item.naam}" wilt verwijderen uit deze maaltijd?`
});
if (!confirmed) return;
entry.items.splice(mi, 1);
// If last item — remove the whole meal group entry
if (entry.items.length === 0) {
day[momentId].splice(entryIdx, 1);
}
saveToStorage();
renderDagboek();
showToast('"' + removed.item.naam + '" verwijderd uit maaltijd');
showToast('"' + item.item.naam + '" verwijderd uit maaltijd');
}
/** Show portion edit dialog for a diary item — with eenmalig/permanent choice for meal items */
@ -4501,19 +4685,21 @@ button:active, .btn:active, .meal-card-main:active, .dagboek-item:active, .food-
});
listEl.querySelectorAll('.meal-card-delete').forEach(btn => {
btn.addEventListener('click', (e) => {
btn.addEventListener('click', async (e) => {
e.stopPropagation();
const idx = parseInt(btn.dataset.idx);
const meal = maaltijden[idx];
showConfirmModal('Maaltijd verwijderen',
'Weet je zeker dat je "' + meal.naam + '" wilt verwijderen?',
() => {
const confirmed = await confirmAction({
title: 'Maaltijd verwijderen',
message: `Weet je zeker dat je "${meal.naam}" wilt verwijderen?`,
confirmText: 'Ja, verwijderen'
});
if (!confirmed) return;
maaltijden.splice(idx, 1);
saveMaaltijden();
renderMaaltijden();
});
});
});
listEl.querySelectorAll('.meal-card-add').forEach(addBtn => {
addBtn.addEventListener('click', (e) => {
@ -4759,6 +4945,47 @@ button:active, .btn:active, .meal-card-main:active, .dagboek-item:active, .food-
}, 2000);
}
/**
* Show a confirmation dialog with Ja/Nee buttons.
* @param {string} title - Dialog title (default: 'Weet je het zeker?')
* @param {string} message - Description of what will be deleted
* @param {string} confirmText - Text for confirm button (default: 'Ja, verwijderen')
* @returns {Promise<boolean>} true if confirmed, false if cancelled
*/
function confirmAction({ title = 'Weet je het zeker?', message = '', confirmText = 'Ja, verwijderen' } = {}) {
return new Promise(resolve => {
const overlay = document.createElement('div');
overlay.className = 'modal-overlay';
overlay.style.zIndex = '500';
overlay.innerHTML = `
<div class="modal-sheet" style="max-width:360px;border-radius:var(--radius);margin:auto;max-height:none;padding:0;">
<div class="modal-handle"></div>
<div class="modal-header">
<h2 class="modal-title">${escapeHtml(title)}</h2>
</div>
<div class="modal-body" style="text-align:center;padding:0 20px 20px;">
<p style="color:var(--text-muted);font-size:var(--font-base);margin:0;">${escapeHtml(message)}</p>
</div>
<div style="display:flex;gap:10px;padding:0 20px 20px;">
<button class="btn-cancel" style="flex:1;" id="confirmNo">Nee</button>
<button class="btn-danger-outline" style="flex:1;" id="confirmYes">${escapeHtml(confirmText)}</button>
</div>
</div>
`;
document.body.appendChild(overlay);
const close = () => {
overlay.classList.add('closing');
setTimeout(() => overlay.remove(), 250);
};
overlay.querySelector('#confirmNo').addEventListener('click', () => { close(); resolve(false); });
overlay.querySelector('#confirmYes').addEventListener('click', () => { close(); resolve(true); });
overlay.addEventListener('click', (e) => { if (e.target === overlay) { close(); resolve(false); } });
});
}
/** @deprecated Use confirmAction() instead */
function showConfirmModal(title, message, onConfirm) {
const overlay = document.createElement('div');
overlay.className = 'modal-overlay';

View file

@ -1304,6 +1304,83 @@ body {
border-color: var(--border);
}
/* Bulk delete button in footer */
.bulk-delete-btn {
display: flex;
align-items: center;
justify-content: center;
gap: 6px;
width: 100%;
padding: 10px 14px;
font-size: var(--font-sm);
color: var(--danger);
cursor: pointer;
border: 1px solid var(--danger);
border-radius: var(--radius-xs);
background: transparent;
margin-top: 6px;
transition: background 0.15s;
}
.bulk-delete-btn:hover {
background: var(--danger);
color: var(--white);
}
/* ===== Diary — nested meal groups ===== */
.delete-selected-footer-btn {
display: block;
width: 100%;
padding: 10px 16px;
margin-top: 6px;
background: var(--danger-light);
border: 1px solid var(--danger);
border-radius: var(--radius-sm);
color: var(--danger);
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;
}
.delete-selected-footer-btn:hover {
background: var(--danger);
color: var(--white);
}
.delete-selected-footer-btn:active {
background: #B71C1C;
color: var(--white);
}
.delete-selected-footer-btn.hidden {
display: none;
}
/* ===== Clear-eetmoment header button ===== */
.eetmoment-clear-btn {
background: none;
border: none;
font-size: 0.75rem;
color: var(--text-muted);
cursor: pointer;
padding: 4px 6px;
min-width: var(--touch-min);
min-height: var(--touch-min);
display: inline-flex;
align-items: center;
justify-content: center;
border-radius: 50%;
transition: background 0.15s, color 0.15s;
opacity: 0.5;
flex-shrink: 0;
}
.eetmoment-clear-btn:hover {
background: var(--danger-light);
color: var(--danger);
opacity: 1;
}
/* ===== Diary — nested meal groups ===== */
.eetmoment-meal-group {
background: var(--primary-lighter);
@ -1365,6 +1442,15 @@ body {
align-items: center;
gap: 4px;
}
.eetmoment-meal-item .portion-badge {
background: var(--white);
border-color: var(--border);
color: var(--text);
}
.eetmoment-meal-item .portion-badge:hover {
background: var(--white);
border-color: var(--primary);
}
.eetmoment-meal-item .eetmoment-item-kh {
font-size: var(--font-sm);
font-weight: 700;
@ -3415,9 +3501,12 @@ button:active, .btn:active, .meal-card-main:active, .dagboek-item:active, .food-
<span class="eetmoment-icon">${moment.icon}</span>
<span class="eetmoment-name">${moment.name}</span>
</div>
<div style="display:flex;align-items:center;gap:2px;">
<span class="eetmoment-kh">${roundedKh > 0 ? formatKh(roundedKh) + ' g' : '—'}</span>
${items.length > 0 ? `<button class="eetmoment-clear-btn" data-moment="${moment.id}" title="Eetmoment legen" aria-label="Eetmoment legen"><i class="fas fa-broom"></i></button>` : ''}
<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>` : ''}
${inlineGlucoseHtml}
<div class="eetmoment-add-btn" data-moment="${moment.id}">
@ -3425,12 +3514,15 @@ button:active, .btn:active, .meal-card-main:active, .dagboek-item:active, .food-
</div>
${items.length > 0 ? `
<div class="save-meal-footer" data-moment="${moment.id}">
<button class="save-meal-footer-btn" data-moment="${moment.id}"${items.length > 1 ? ' disabled' : ''}>
<button class="save-meal-footer-btn" data-moment="${moment.id}"${items.length > 1 ? ' style="display:none"' : ''}>
${items.length > 1
? `<i class="fas fa-star"></i> Selecteer ${items.length} items`
: `<i class="fas fa-star"></i> Opslaan als maaltijd`
}
</button>
<button class="delete-selected-footer-btn hidden" data-moment="${moment.id}">
<i class="fas fa-trash"></i> Verwijder geselecteerde
</button>
</div>` : ''}
</li>
`;
@ -3501,26 +3593,74 @@ button:active, .btn:active, .meal-card-main:active, .dagboek-item:active, .food-
});
});
// Checkbox change → update save button text
// Checkbox change → update save/delete button state
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 saveBtn = footer.querySelector('.save-meal-footer-btn');
const delBtn = footer.querySelector('.delete-selected-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 = '⭐ Selecteer ' + total + ' items';
saveBtn.disabled = true;
saveBtn.innerHTML = `<i class="fas fa-star"></i> Selecteer ${total} items`;
if (delBtn) delBtn.classList.add('hidden');
} else {
btn.disabled = false;
btn.textContent = '⭐ Opslaan ' + checked + ' ' + (checked === 1 ? 'item' : 'items') + ' als maaltijd';
saveBtn.disabled = false;
saveBtn.innerHTML = `<i class="fas fa-star"></i> Opslaan ${checked} ${checked === 1 ? 'item' : 'items'} als maaltijd`;
if (delBtn) delBtn.classList.remove('hidden');
}
});
});
// Delete selected items button
listEl.querySelectorAll('.delete-selected-footer-btn').forEach(btn => {
btn.addEventListener('click', async (e) => {
e.stopPropagation();
const momentId = btn.dataset.moment;
const card = btn.closest('.eetmoment-card');
if (!card) return;
const checked = card.querySelectorAll('.item-checkbox:checked');
const count = checked.length;
if (count === 0) return;
const confirmed = await confirmAction({
message: `Weet je zeker dat je ${count} geselecteerde ${count === 1 ? 'item' : 'items'} wilt verwijderen?`
});
if (!confirmed) return;
// Collect indices in reverse order to avoid index shift during splice
const indices = Array.from(checked).map(cb => parseInt(cb.dataset.index)).sort((a, b) => b - a);
const dayData = getOrCreateDay(activeDate);
indices.forEach(idx => {
dayData[momentId].splice(idx, 1);
});
saveToStorage();
renderDagboek();
showToast(`${count} ${count === 1 ? 'item verwijderd' : 'items verwijderd'}`);
});
});
// Clear eetmoment button
listEl.querySelectorAll('.eetmoment-clear-btn').forEach(btn => {
btn.addEventListener('click', async (e) => {
e.stopPropagation();
const momentId = btn.dataset.moment;
const dayData = getOrCreateDay(activeDate);
const items = dayData[momentId] || [];
if (items.length === 0) return;
const confirmed = await confirmAction({
message: `Weet je zeker dat je alle ${items.length} ${items.length === 1 ? 'item' : 'items'} uit dit eetmoment wilt verwijderen?`
});
if (!confirmed) return;
dayData[momentId] = [];
saveToStorage();
renderDagboek();
showToast('Eetmoment geleegd');
});
});
// Meal group toggle expand/collapse
listEl.querySelectorAll('.meal-group-header').forEach(header => {
header.addEventListener('click', (e) => {
@ -3759,37 +3899,81 @@ button:active, .btn:active, .meal-card-main:active, .dagboek-item:active, .food-
renderDagboek();
}
function removeFromMeal(dateStr, momentId, index) {
// Reusable confirmation dialog for destructive actions
function confirmAction(title, message, onConfirm) {
const overlay = document.createElement('div');
overlay.className = 'modal-overlay';
overlay.style.zIndex = '400';
overlay.innerHTML = `
<div class="modal-sheet">
<div class="modal-handle"></div>
<div style="text-align:center;padding:16px 8px 8px;">
<h3 style="margin:0 0 8px;font-size:1rem;">${title}</h3>
<p style="margin:0 0 16px;color:var(--text-secondary);font-size:var(--font-sm);">${message}</p>
<div style="display:flex;gap:10px;justify-content:center;">
<button class="btn-secondary" id="confirmCancel" style="flex:1;">Annuleren</button>
<button class="btn-danger" id="confirmOk" style="flex:1;background:var(--danger);color:var(--white);border:none;border-radius:var(--radius-xs);padding:10px;font-size:var(--font-sm);cursor:pointer;">Verwijderen</button>
</div>
</div>
</div>
`;
document.body.appendChild(overlay);
document.body.style.overflow = 'hidden';
overlay.querySelector('#confirmCancel').addEventListener('click', () => {
overlay.remove();
document.body.style.overflow = '';
});
overlay.querySelector('#confirmOk').addEventListener('click', () => {
overlay.remove();
document.body.style.overflow = '';
onConfirm();
});
overlay.addEventListener('click', (e) => {
if (e.target === overlay) { overlay.remove(); document.body.style.overflow = ''; }
});
}
async function removeFromMeal(dateStr, momentId, index) {
const day = dagboek[dateStr];
if (day && day[momentId]) {
const removed = day[momentId].splice(index, 1)[0];
const target = day[momentId][index];
if (!target) return;
const name = target.type === 'meal' ? target.mealNaam : target.item.naam;
const confirmed = await confirmAction({
message: `Weet je zeker dat je "${name}" wilt verwijderen?`
});
if (!confirmed) return;
day[momentId].splice(index, 1);
saveToStorage();
renderDagboek();
if (removed) {
const name = removed.type === 'meal' ? removed.mealNaam : removed.item.naam;
showToast('"' + name + '" verwijderd');
}
}
}
/**
* Remove a single item from within a meal group in the diary.
* If the last item is removed, removes the entire meal group.
*/
function removeMealItemFromDiary(dateStr, momentId, entryIdx, mi) {
async function removeMealItemFromDiary(dateStr, momentId, entryIdx, mi) {
const day = dagboek[dateStr];
if (!day || !day[momentId]) return;
const entry = day[momentId][entryIdx];
if (!entry || entry.type !== 'meal') return;
const removed = entry.items.splice(mi, 1)[0];
if (!removed) return;
const item = entry.items[mi];
if (!item) return;
const confirmed = await confirmAction({
message: `Weet je zeker dat je "${item.item.naam}" wilt verwijderen uit deze maaltijd?`
});
if (!confirmed) return;
entry.items.splice(mi, 1);
// If last item — remove the whole meal group entry
if (entry.items.length === 0) {
day[momentId].splice(entryIdx, 1);
}
saveToStorage();
renderDagboek();
showToast('"' + removed.item.naam + '" verwijderd uit maaltijd');
showToast('"' + item.item.naam + '" verwijderd uit maaltijd');
}
/** Show portion edit dialog for a diary item — with eenmalig/permanent choice for meal items */
@ -4501,19 +4685,21 @@ button:active, .btn:active, .meal-card-main:active, .dagboek-item:active, .food-
});
listEl.querySelectorAll('.meal-card-delete').forEach(btn => {
btn.addEventListener('click', (e) => {
btn.addEventListener('click', async (e) => {
e.stopPropagation();
const idx = parseInt(btn.dataset.idx);
const meal = maaltijden[idx];
showConfirmModal('Maaltijd verwijderen',
'Weet je zeker dat je "' + meal.naam + '" wilt verwijderen?',
() => {
const confirmed = await confirmAction({
title: 'Maaltijd verwijderen',
message: `Weet je zeker dat je "${meal.naam}" wilt verwijderen?`,
confirmText: 'Ja, verwijderen'
});
if (!confirmed) return;
maaltijden.splice(idx, 1);
saveMaaltijden();
renderMaaltijden();
});
});
});
listEl.querySelectorAll('.meal-card-add').forEach(addBtn => {
addBtn.addEventListener('click', (e) => {
@ -4759,6 +4945,47 @@ button:active, .btn:active, .meal-card-main:active, .dagboek-item:active, .food-
}, 2000);
}
/**
* Show a confirmation dialog with Ja/Nee buttons.
* @param {string} title - Dialog title (default: 'Weet je het zeker?')
* @param {string} message - Description of what will be deleted
* @param {string} confirmText - Text for confirm button (default: 'Ja, verwijderen')
* @returns {Promise<boolean>} true if confirmed, false if cancelled
*/
function confirmAction({ title = 'Weet je het zeker?', message = '', confirmText = 'Ja, verwijderen' } = {}) {
return new Promise(resolve => {
const overlay = document.createElement('div');
overlay.className = 'modal-overlay';
overlay.style.zIndex = '500';
overlay.innerHTML = `
<div class="modal-sheet" style="max-width:360px;border-radius:var(--radius);margin:auto;max-height:none;padding:0;">
<div class="modal-handle"></div>
<div class="modal-header">
<h2 class="modal-title">${escapeHtml(title)}</h2>
</div>
<div class="modal-body" style="text-align:center;padding:0 20px 20px;">
<p style="color:var(--text-muted);font-size:var(--font-base);margin:0;">${escapeHtml(message)}</p>
</div>
<div style="display:flex;gap:10px;padding:0 20px 20px;">
<button class="btn-cancel" style="flex:1;" id="confirmNo">Nee</button>
<button class="btn-danger-outline" style="flex:1;" id="confirmYes">${escapeHtml(confirmText)}</button>
</div>
</div>
`;
document.body.appendChild(overlay);
const close = () => {
overlay.classList.add('closing');
setTimeout(() => overlay.remove(), 250);
};
overlay.querySelector('#confirmNo').addEventListener('click', () => { close(); resolve(false); });
overlay.querySelector('#confirmYes').addEventListener('click', () => { close(); resolve(true); });
overlay.addEventListener('click', (e) => { if (e.target === overlay) { close(); resolve(false); } });
});
}
/** @deprecated Use confirmAction() instead */
function showConfirmModal(title, message, onConfirm) {
const overlay = document.createElement('div');
overlay.className = 'modal-overlay';