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:
parent
4e34946b0d
commit
2de732159e
2 changed files with 508 additions and 54 deletions
|
|
@ -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';
|
||||
|
|
|
|||
269
index.html
269
index.html
|
|
@ -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';
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue