Merge pull request 'Planner wijzigingen' (#63) from dev into main

Reviewed-on: #63
This commit was merged in pull request #63.
This commit is contained in:
2026-09-12 10:50:24 +00:00
+199 -157
View File
@@ -994,13 +994,8 @@ for ($h = $startHour; $h <= $endHour; $h++) {
</div>
</div>
<!-- Rechterkant: Dagen Blokkeren, AI Slim Indelen & Directe datumselector -->
<!-- Rechterkant: AI Slim Indelen & Directe datumselector -->
<div class="flex flex-wrap items-center gap-2 justify-end">
<button type="button" onclick="openCalendarBlockModal()" title="Hele of meerdere dagen blokkeren voor vakantie, verlof of afwezigheid" class="flex items-center space-x-1.5 px-3 py-1.5 rounded-xl bg-amber-50 hover:bg-amber-100 dark:bg-amber-950/40 dark:hover:bg-amber-900/50 text-amber-800 dark:text-amber-300 border border-amber-200 dark:border-amber-800/60 text-xs font-semibold shadow-2xs transition cursor-pointer">
<i data-lucide="palmtree" class="w-3.5 h-3.5 text-amber-600 dark:text-amber-400"></i>
<span class="hidden sm:inline">Dagen Blokkeren</span>
</button>
<?php if ($aiSettings['ai_enabled'] && $aiSettings['has_key']): ?>
<button onclick="aiSmartSchedule(this)" id="aiScheduleBtn" title="Laat AI Co-Pilot openstaande taken optimaal inplannen om meetings en lunch heen" class="flex items-center space-x-1.5 px-3 py-1.5 rounded-xl bg-gradient-to-r from-indigo-600 to-sky-600 hover:from-indigo-500 hover:to-sky-500 text-white text-xs font-semibold shadow-xs transition cursor-pointer">
<i data-lucide="sparkles" class="w-3.5 h-3.5"></i>
@@ -1022,32 +1017,38 @@ for ($h = $startHour; $h <= $endHour; $h++) {
</div>
</div>
<!-- 2. 7 Dagen Weekstrook (Dagselector) -->
<!-- 2. 7 Dagen Weekstrook (Dagselector met Rechtermuisknop Blokkade Opties) -->
<div class="grid grid-cols-2 sm:grid-cols-4 md:grid-cols-7 gap-2.5">
<?php foreach ($weekDays as $wd): ?>
<a
href="<?= ($wd['date'] === $today) ? 'planner.php' : ('planner.php?date=' . $wd['date']) ?>"
onclick="switchPlannerDate('<?= $wd['date'] ?>', event); return false;"
class="fociz-card-interactive group relative flex flex-col p-3 rounded-xl border transition-all text-left <?= $wd['is_selected']
oncontextmenu="openDayContextMenu(event, '<?= $wd['date'] ?>', <?= $wd['is_blocked'] ? (int)$wd['block_info']['id'] : 0 ?>, '<?= $wd['is_blocked'] ? addslashes(htmlspecialchars($wd['block_info']['title'], ENT_QUOTES)) : '' ?>'); return false;"
data-date="<?= $wd['date'] ?>"
data-blocked="<?= $wd['is_blocked'] ? '1' : '0' ?>"
class="fociz-card-interactive group relative flex flex-col p-3 rounded-xl border transition-all text-left <?= $wd['is_blocked']
? ($wd['is_selected']
? 'bg-amber-100/90 dark:bg-amber-950/60 border-amber-400 dark:border-amber-600 ring-2 ring-amber-400/50 shadow-sm'
: 'bg-amber-50/80 dark:bg-amber-950/30 border-amber-300/80 dark:border-amber-800/70 hover:border-amber-400 dark:hover:border-amber-600 shadow-2xs')
: ($wd['is_selected']
? 'bg-gradient-to-b from-indigo-500/15 via-indigo-500/5 to-transparent dark:from-indigo-500/25 dark:via-indigo-500/10 dark:to-transparent border-indigo-500 ring-2 ring-indigo-500/40 shadow-sm'
: ($wd['is_blocked']
? 'bg-amber-50/70 dark:bg-amber-950/20 border-amber-300/80 dark:border-amber-800/60 hover:border-amber-400 shadow-2xs'
: ($wd['is_workday']
? 'bg-slate-50/70 dark:bg-[#0c0f17]/50 border-slate-200/80 dark:border-slate-800/80 hover:border-indigo-300 dark:hover:border-indigo-600/50 hover:bg-white dark:hover:bg-[#111624] shadow-2xs'
: 'bg-slate-100/60 dark:bg-[#080b11]/60 border-dashed border-amber-300/60 dark:border-amber-900/40 hover:border-amber-400 dark:hover:border-amber-700 hover:bg-white dark:hover:bg-[#0f141f] shadow-2xs')) ?>"
: 'bg-slate-100/60 dark:bg-[#080b11]/60 border-dashed border-slate-200/80 dark:border-slate-800/80 hover:border-slate-300 dark:hover:border-slate-700 hover:bg-white dark:hover:bg-[#0f141f] shadow-2xs')) ?>"
title="<?= $wd['is_blocked'] ? ('Geblokkeerd: ' . htmlspecialchars($wd['block_info']['title']) . ' • Rechtermuisknop voor opties') : 'Klik om te selecteren • Rechtermuisknop om dag te blokkeren' ?>"
>
<div class="flex items-center justify-between mb-1">
<span class="text-[10px] font-bold uppercase tracking-wider <?= $wd['is_selected'] ? 'text-indigo-600 dark:text-indigo-400' : ($wd['is_workday'] ? 'text-slate-500 dark:text-slate-400' : 'text-slate-400 dark:text-slate-500') ?>">
<span class="text-[10px] font-bold uppercase tracking-wider <?= $wd['is_blocked'] ? 'text-amber-800 dark:text-amber-300' : ($wd['is_selected'] ? 'text-indigo-600 dark:text-indigo-400' : ($wd['is_workday'] ? 'text-slate-500 dark:text-slate-400' : 'text-slate-400 dark:text-slate-500')) ?>">
<?= $wd['short_name'] ?>
</span>
<div class="flex items-center gap-1">
<?php if ($wd['is_blocked']): ?>
<span class="px-1.5 py-0.5 rounded text-[8px] font-extrabold uppercase bg-amber-200/90 dark:bg-amber-900/80 text-amber-950 dark:text-amber-200 border border-amber-300 dark:border-amber-700/80 flex items-center gap-0.5" title="<?= htmlspecialchars($wd['block_info']['title']) ?>">
<i data-lucide="palmtree" class="w-2.5 h-2.5"></i>
<span><?= htmlspecialchars(mb_strimwidth($wd['block_info']['title'], 0, 7, '..')) ?></span>
<span class="px-2 py-0.5 rounded-md text-[9px] font-bold uppercase bg-amber-200/90 dark:bg-amber-900/80 text-amber-950 dark:text-amber-200 border border-amber-300 dark:border-amber-700/80 inline-flex items-center gap-1 leading-none shadow-2xs" title="<?= htmlspecialchars($wd['block_info']['title']) ?>">
<i data-lucide="palmtree" class="w-2.5 h-2.5 flex-shrink-0"></i>
<span class="max-w-[80px] truncate"><?= htmlspecialchars($wd['block_info']['title']) ?></span>
</span>
<?php elseif ($wd['is_weekend']): ?>
<span class="px-1.5 py-0.5 rounded text-[8px] font-extrabold uppercase bg-amber-50 dark:bg-amber-950/60 text-amber-700 dark:text-amber-400 border border-amber-200/80 dark:border-amber-800/60" title="Weekend / Niet-werkdag conform beheerinstellingen">
<span class="px-1.5 py-0.5 rounded text-[8px] font-extrabold uppercase bg-slate-200/60 dark:bg-slate-800 text-slate-500 dark:text-slate-400 border border-slate-300/60 dark:border-slate-700/60">
Weekend
</span>
<?php endif; ?>
@@ -1094,40 +1095,16 @@ for ($h = $startHour; $h <= $endHour; $h++) {
<?php endforeach; ?>
</div>
<?php if ($isSelectedDateBlocked): ?>
<!-- Prominente Vakantie / Blokkade Banner -->
<div class="p-4 sm:p-5 rounded-2xl bg-gradient-to-r from-amber-50 via-amber-100/60 to-orange-50 dark:from-amber-950/40 dark:via-[#1a1408] dark:to-orange-950/30 border-2 border-amber-300/90 dark:border-amber-700/70 flex flex-col sm:flex-row sm:items-center justify-between gap-4 shadow-sm">
<div class="flex items-start sm:items-center space-x-3.5">
<div class="w-11 h-11 rounded-2xl bg-amber-200 dark:bg-amber-900/80 text-amber-900 dark:text-amber-200 flex items-center justify-center font-bold text-xl flex-shrink-0 shadow-xs ring-2 ring-amber-300 dark:ring-amber-700/80">
🏖️
<!-- Contextmenu voor Rechtermuisklik op een dag -->
<div id="dayContextMenu" class="fixed z-[9999] hidden min-w-[220px] bg-white dark:bg-[#111624] border border-slate-200 dark:border-slate-800 rounded-2xl shadow-xl py-1 text-xs text-slate-800 dark:text-slate-200 select-none animate-in fade-in zoom-in-95 duration-75">
<div class="px-3 py-1.5 text-[10px] font-bold text-slate-400 dark:text-slate-500 uppercase tracking-wider border-b border-slate-100 dark:border-slate-800/80 flex items-center justify-between">
<span id="dayContextMenuDateLabel">Dag</span>
<span id="dayContextMenuStatusBadge"></span>
</div>
<div>
<div class="flex items-center space-x-2 flex-wrap gap-y-1">
<h4 class="text-sm sm:text-base font-black text-amber-950 dark:text-amber-200">
<?= htmlspecialchars($selectedDayBlock['title']) ?>
</h4>
<span class="px-2.5 py-0.5 rounded-full text-[10px] font-extrabold bg-amber-200/90 text-amber-950 dark:bg-amber-800/80 dark:text-amber-100 uppercase tracking-wider border border-amber-300 dark:border-amber-700">
<?= htmlspecialchars($selectedDayBlock['block_type']) ?>
</span>
</div>
<p class="text-xs text-amber-900/80 dark:text-amber-300/80 mt-1">
Geblokkeerd van <strong><?= date('j M Y', strtotime($selectedDayBlock['start_date'])) ?></strong> t/m <strong><?= date('j M Y', strtotime($selectedDayBlock['end_date'])) ?></strong>
<?php if (!empty($selectedDayBlock['notes'])): ?>
&bull; <?= htmlspecialchars($selectedDayBlock['notes']) ?>
<?php endif; ?>
</p>
<div id="dayContextMenuActions" class="p-1 space-y-0.5">
<!-- Acties worden via JS ingevuld -->
</div>
</div>
<div class="flex items-center space-x-2 self-end sm:self-auto flex-shrink-0">
<button onclick="openCalendarBlockModal(<?= htmlspecialchars(json_encode($selectedDayBlock), ENT_QUOTES) ?>)" class="px-3.5 py-1.5 rounded-xl bg-white dark:bg-slate-900 text-slate-800 dark:text-slate-200 text-xs font-bold hover:bg-amber-50 dark:hover:bg-slate-800 border border-amber-300/80 dark:border-amber-700 transition shadow-2xs cursor-pointer">
Bewerken
</button>
<button onclick="deleteCalendarBlock(<?= (int)$selectedDayBlock['id'] ?>)" class="px-3.5 py-1.5 rounded-xl bg-rose-50 hover:bg-rose-100 dark:bg-rose-950/50 dark:hover:bg-rose-900/60 text-rose-700 dark:text-rose-300 text-xs font-bold border border-rose-200 dark:border-rose-800/60 transition shadow-2xs cursor-pointer">
Opheffen
</button>
</div>
</div>
<?php endif; ?>
<!-- 3. Sub-header van de Tijdlijn voor de geselecteerde dag -->
<div class="flex items-center justify-between pt-3 border-t border-slate-100 dark:border-slate-800/80">
@@ -2435,30 +2412,17 @@ if ($isAjaxRequest) {
<div>
<label class="block text-xs font-bold text-slate-700 dark:text-slate-300 mb-1">Type Blokkade</label>
<div class="grid grid-cols-4 gap-2">
<button type="button" onclick="updateCalBlockDefaultTitle('VACATION')" id="calType_VACATION" class="cal-block-type-btn p-2 rounded-xl border text-center transition cursor-pointer flex flex-col items-center">
<span class="text-base mb-0.5">🏖️</span>
<span class="text-[10px] font-bold">Vakantie</span>
</button>
<button type="button" onclick="updateCalBlockDefaultTitle('LEAVE')" id="calType_LEAVE" class="cal-block-type-btn p-2 rounded-xl border text-center transition cursor-pointer flex flex-col items-center">
<span class="text-base mb-0.5">✈️</span>
<span class="text-[10px] font-bold">Verlof / ADV</span>
</button>
<button type="button" onclick="updateCalBlockDefaultTitle('SICK')" id="calType_SICK" class="cal-block-type-btn p-2 rounded-xl border text-center transition cursor-pointer flex flex-col items-center">
<span class="text-base mb-0.5">🤒</span>
<span class="text-[10px] font-bold">Ziekte</span>
</button>
<button type="button" onclick="updateCalBlockDefaultTitle('FOCUS')" id="calType_FOCUS" class="cal-block-type-btn p-2 rounded-xl border text-center transition cursor-pointer flex flex-col items-center">
<span class="text-base mb-0.5">🎯</span>
<span class="text-[10px] font-bold">Focusdag</span>
</button>
</div>
<input type="hidden" id="calBlockType" value="VACATION">
<select id="calBlockType" onchange="updateCalBlockDefaultTitle(this.value)" class="w-full px-3 py-2 rounded-xl bg-slate-50 dark:bg-slate-800/80 border border-slate-200 dark:border-slate-700 text-xs font-medium text-slate-900 dark:text-white focus:outline-hidden focus:ring-2 focus:ring-amber-500">
<option value="VACATION">Vakantie</option>
<option value="LEAVE">Verlof / ADV / Vrije dag</option>
<option value="SICK">Ziekte / Afwezig</option>
<option value="FOCUS">Focusdag</option>
</select>
</div>
<div>
<label class="block text-xs font-bold text-slate-700 dark:text-slate-300 mb-1">Titel / Omschrijving <span class="text-rose-500">*</span></label>
<input type="text" id="calBlockTitle" required placeholder="Bijv. Zomervakantie, ADV dag, Verlof..." class="w-full px-3 py-2 rounded-xl bg-slate-50 dark:bg-slate-800/80 border border-slate-200 dark:border-slate-700 text-xs font-medium text-slate-900 dark:text-white placeholder-slate-400 focus:outline-hidden focus:ring-2 focus:ring-amber-500">
<input type="text" id="calBlockTitle" required placeholder="Bijv. Vakantie, Vrije dag, Verlof..." class="w-full px-3 py-2 rounded-xl bg-slate-50 dark:bg-slate-800/80 border border-slate-200 dark:border-slate-700 text-xs font-medium text-slate-900 dark:text-white placeholder-slate-400 focus:outline-hidden focus:ring-2 focus:ring-amber-500">
</div>
<div class="grid grid-cols-2 gap-3">
@@ -2493,20 +2457,6 @@ if ($isAjaxRequest) {
</div>
<?php endif; ?>
<!-- Overzicht actieve / geplande blokkades -->
<div class="pt-2 border-t border-slate-100 dark:border-slate-800">
<div class="flex items-center justify-between mb-2">
<span class="text-[11px] font-bold text-slate-600 dark:text-slate-400 uppercase tracking-wider">Actieve & Toekomstige Blokkades</span>
<button type="button" onclick="loadCalendarBlocksList()" class="text-[10px] text-indigo-600 hover:text-indigo-700 dark:text-indigo-400 font-semibold cursor-pointer flex items-center space-x-1">
<i data-lucide="refresh-cw" class="w-2.5 h-2.5"></i>
<span>Vernieuwen</span>
</button>
</div>
<div id="calendarBlocksListContainer" class="space-y-1.5 max-h-36 overflow-y-auto pr-1">
<div class="text-[11px] text-slate-400 text-center py-2">Laden...</div>
</div>
</div>
<!-- Knoppen balk -->
<div class="flex items-center justify-between pt-3 border-t border-slate-100 dark:border-slate-800/80 flex-shrink-0">
<button type="button" id="btnDeleteCurrentCalBlock" onclick="deleteCurrentCalendarBlock()" class="hidden px-3 py-2 rounded-xl bg-rose-50 hover:bg-rose-100 dark:bg-rose-950/50 dark:hover:bg-rose-900/60 text-rose-700 dark:text-rose-300 text-xs font-bold border border-rose-200 dark:border-rose-800/60 transition cursor-pointer items-center space-x-1">
@@ -2801,7 +2751,7 @@ function showWidgetToast(htmlMessage, type = 'success', duration = 3500) {
}, duration);
}
// --- Agenda Dagen Blokkeren (Vakantie / Verlof / Focus) ---
// --- Agenda Dagen Blokkeren & Rechtermuisklik Context Menu ---
const CAL_BLOCK_DEFAULT_TITLES = {
'VACATION': 'Vakantie',
'LEAVE': 'Verlof',
@@ -2812,31 +2762,182 @@ const CAL_BLOCK_DEFAULT_TITLES = {
function selectCalBlockType(type) {
const input = document.getElementById('calBlockType');
if (input) input.value = type;
const types = ['VACATION', 'LEAVE', 'SICK', 'FOCUS'];
types.forEach(t => {
const btn = document.getElementById(`calType_${t}`);
if (!btn) return;
if (t === type) {
btn.className = 'cal-block-type-btn p-2 rounded-xl border border-amber-500 bg-amber-100 dark:bg-amber-950/80 text-amber-950 dark:text-amber-200 font-bold shadow-xs text-center transition cursor-pointer flex flex-col items-center ring-2 ring-amber-500/20';
} else {
btn.className = 'cal-block-type-btn p-2 rounded-xl border border-slate-200 dark:border-slate-700/80 bg-slate-50 dark:bg-slate-800/60 text-slate-600 dark:text-slate-400 hover:bg-slate-100 dark:hover:bg-slate-800 text-center transition cursor-pointer flex flex-col items-center';
}
});
}
function updateCalBlockDefaultTitle(type) {
const titleInput = document.getElementById('calBlockTitle');
const currentVal = titleInput ? titleInput.value.trim() : '';
const oldType = document.getElementById('calBlockType')?.value;
selectCalBlockType(type);
if (titleInput && (!currentVal || currentVal === (CAL_BLOCK_DEFAULT_TITLES[oldType] || ''))) {
if (titleInput && (!currentVal || Object.values(CAL_BLOCK_DEFAULT_TITLES).includes(currentVal))) {
titleInput.value = CAL_BLOCK_DEFAULT_TITLES[type] || '';
}
}
// --- Rechtermuisklik Context Menu voor Dagen ---
let activeContextMenuDay = null;
function openDayContextMenu(e, dateStr, blockId = 0, blockTitle = '') {
e.preventDefault();
e.stopPropagation();
activeContextMenuDay = {
date: dateStr,
blockId: blockId,
title: blockTitle
};
const menu = document.getElementById('dayContextMenu');
const label = document.getElementById('dayContextMenuDateLabel');
const statusBadge = document.getElementById('dayContextMenuStatusBadge');
const actions = document.getElementById('dayContextMenuActions');
if (!menu || !label || !actions) return;
// Nederlandse dag en maand formatteren
const dParts = dateStr.split('-');
const dObj = new Date(parseInt(dParts[0], 10), parseInt(dParts[1], 10) - 1, parseInt(dParts[2], 10));
const dayNames = ['Zondag', 'Maandag', 'Dinsdag', 'Woensdag', 'Donderdag', 'Vrijdag', 'Zaterdag'];
const monthNames = ['jan', 'feb', 'mrt', 'apr', 'mei', 'jun', 'jul', 'aug', 'sep', 'okt', 'nov', 'dec'];
const formatted = `${dayNames[dObj.getDay()]} ${dObj.getDate()} ${monthNames[dObj.getMonth()]}`;
label.textContent = formatted;
if (blockId > 0) {
statusBadge.innerHTML = `<span class="px-1.5 py-0.5 rounded text-[9px] font-bold bg-amber-100 dark:bg-amber-950 text-amber-800 dark:text-amber-300 border border-amber-300/80 dark:border-amber-700/80">${escapeHtml(blockTitle || 'Geblokkeerd')}</span>`;
actions.innerHTML = `
<button type="button" onclick="quickUnblockDay(${blockId})" class="w-full flex items-center space-x-2 px-3 py-2 rounded-xl text-left text-rose-600 dark:text-rose-400 hover:bg-rose-50 dark:hover:bg-rose-950/40 transition cursor-pointer font-medium">
<i data-lucide="trash-2" class="w-3.5 h-3.5 flex-shrink-0 text-rose-500"></i>
<span>Blokkade opheffen</span>
</button>
<button type="button" onclick="closeDayContextMenu(); openCalendarBlockModal({id: ${blockId}, start_date: '${dateStr}', end_date: '${dateStr}', title: '${escapeHtml(blockTitle)}'}, '${dateStr}')" class="w-full flex items-center space-x-2 px-3 py-2 rounded-xl text-left hover:bg-slate-100 dark:hover:bg-slate-800/80 text-slate-700 dark:text-slate-300 transition cursor-pointer font-medium">
<i data-lucide="calendar" class="w-3.5 h-3.5 flex-shrink-0 text-slate-400"></i>
<span>Periode aanpassen...</span>
</button>
`;
} else {
statusBadge.innerHTML = `<span class="text-[9px] font-normal text-slate-400">Beschikbaar</span>`;
actions.innerHTML = `
<button type="button" onclick="quickBlockDay('${dateStr}', 'Vakantie', 'VACATION')" class="w-full flex items-center space-x-2 px-3 py-2 rounded-xl text-left hover:bg-amber-50 dark:hover:bg-amber-950/50 text-slate-800 dark:text-slate-200 transition cursor-pointer font-medium">
<span class="text-sm leading-none">🏖️</span>
<span>Hele dag: Vakantie</span>
</button>
<button type="button" onclick="quickBlockDay('${dateStr}', 'Verlof', 'LEAVE')" class="w-full flex items-center space-x-2 px-3 py-2 rounded-xl text-left hover:bg-amber-50 dark:hover:bg-amber-950/50 text-slate-800 dark:text-slate-200 transition cursor-pointer font-medium">
<span class="text-sm leading-none">✈️</span>
<span>Hele dag: Verlof / Vrij</span>
</button>
<button type="button" onclick="quickBlockDay('${dateStr}', 'Ziek', 'SICK')" class="w-full flex items-center space-x-2 px-3 py-2 rounded-xl text-left hover:bg-amber-50 dark:hover:bg-amber-950/50 text-slate-800 dark:text-slate-200 transition cursor-pointer font-medium">
<span class="text-sm leading-none">🤒</span>
<span>Hele dag: Ziek / Afwezig</span>
</button>
<div class="my-1 border-t border-slate-100 dark:border-slate-800/80"></div>
<button type="button" onclick="closeDayContextMenu(); openCalendarBlockModal(null, '${dateStr}')" class="w-full flex items-center space-x-2 px-3 py-2 rounded-xl text-left hover:bg-slate-100 dark:hover:bg-slate-800/80 text-slate-600 dark:text-slate-400 transition cursor-pointer font-medium">
<i data-lucide="calendar-plus" class="w-3.5 h-3.5 flex-shrink-0 text-slate-400"></i>
<span>Meerdere dagen blokkeren...</span>
</button>
`;
}
if (window.lucide && lucide.createIcons) {
lucide.createIcons();
}
menu.classList.remove('hidden');
const menuWidth = 220;
const menuHeight = menu.offsetHeight || 160;
let posX = e.clientX;
let posY = e.clientY;
if (posX + menuWidth > window.innerWidth - 10) {
posX = window.innerWidth - menuWidth - 15;
}
if (posY + menuHeight > window.innerHeight - 10) {
posY = window.innerHeight - menuHeight - 15;
}
menu.style.left = `${posX}px`;
menu.style.top = `${posY}px`;
}
function closeDayContextMenu() {
const menu = document.getElementById('dayContextMenu');
if (menu) {
menu.classList.add('hidden');
}
activeContextMenuDay = null;
}
document.addEventListener('click', function(e) {
const menu = document.getElementById('dayContextMenu');
if (menu && !menu.contains(e.target)) {
closeDayContextMenu();
}
});
document.addEventListener('keydown', function(e) {
if (e.key === 'Escape') {
closeDayContextMenu();
}
});
async function quickBlockDay(dateStr, title, blockType) {
closeDayContextMenu();
try {
const res = await fetch('/api/planner.php?action=save_calendar_block', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
title: title,
block_type: blockType,
start_date: dateStr,
end_date: dateStr,
sync_to_outlook: true
})
});
const data = await res.json();
if (data && data.success) {
showWidgetToast(`${title} ingepland voor ${dateStr}`, 'success');
setTimeout(() => {
window.location.reload();
}, 300);
} else {
alert(data.error || 'Fout bij inplannen van blokkade');
}
} catch (err) {
alert('Netwerkfout bij inplannen van blokkade');
}
}
async function quickUnblockDay(blockId) {
closeDayContextMenu();
if (!blockId) return;
try {
const res = await fetch('/api/planner.php?action=delete_calendar_block', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ id: blockId })
});
const data = await res.json();
if (data && data.success) {
showWidgetToast('Blokkade opgeheven', 'success');
setTimeout(() => {
window.location.reload();
}, 300);
} else {
alert(data.error || 'Fout bij opheffen van blokkade');
}
} catch (err) {
alert('Netwerkfout bij opheffen van blokkade');
}
}
function openCalendarBlockModal(existingBlock = null, defaultDate = null) {
const modal = document.getElementById('calendarBlockModal');
if (!modal) return;
@@ -2862,7 +2963,7 @@ function openCalendarBlockModal(existingBlock = null, defaultDate = null) {
deleteBtn.classList.remove('hidden');
deleteBtn.classList.add('flex');
}
if (modalTitle) modalTitle.textContent = 'Blokkade Bewerken';
if (modalTitle) modalTitle.textContent = 'Blokkade Aanpassen';
} else {
if (idInput) idInput.value = 0;
selectCalBlockType('VACATION');
@@ -2874,11 +2975,9 @@ function openCalendarBlockModal(existingBlock = null, defaultDate = null) {
deleteBtn.classList.add('hidden');
deleteBtn.classList.remove('flex');
}
if (modalTitle) modalTitle.textContent = 'Dagen Blokkeren / Vakantie';
if (modalTitle) modalTitle.textContent = 'Meerdere Dagen Blokkeren';
}
loadCalendarBlocksList();
modal.classList.remove('hidden');
modal.classList.add('flex');
@@ -2895,63 +2994,6 @@ function closeCalendarBlockModal() {
}
}
async function loadCalendarBlocksList() {
const container = document.getElementById('calendarBlocksListContainer');
if (!container) return;
try {
const res = await fetch('/api/planner.php?action=get_calendar_blocks');
const data = await res.json();
if (!data.success || !Array.isArray(data.blocks) || data.blocks.length === 0) {
container.innerHTML = '<p class="text-[11px] text-slate-400 text-center py-2 italic">Geen actieve of geplande blokkades gevonden.</p>';
return;
}
const icons = {
'VACATION': '🏖️',
'LEAVE': '✈️',
'SICK': '🤒',
'FOCUS': '🎯'
};
container.innerHTML = data.blocks.map(b => {
const icon = icons[b.block_type] || '📅';
const safeTitle = escapeHtml(b.title);
const dateStr = b.start_date === b.end_date
? b.start_date
: `${b.start_date} t/m ${b.end_date}`;
const blockJson = JSON.stringify(b).replace(/"/g, '&quot;');
return `
<div class="flex items-center justify-between p-2 rounded-xl bg-slate-50 dark:bg-slate-800/70 border border-slate-200/70 dark:border-slate-700/60 text-xs">
<div class="flex items-center space-x-2 min-w-0 flex-1 cursor-pointer" onclick="openCalendarBlockModal(${blockJson})">
<span class="text-sm flex-shrink-0">${icon}</span>
<div class="min-w-0 flex-1 truncate">
<div class="font-bold text-slate-800 dark:text-slate-200 truncate">${safeTitle}</div>
<div class="text-[10px] text-slate-500 dark:text-slate-400">${dateStr}</div>
</div>
</div>
<div class="flex items-center space-x-1 flex-shrink-0 ml-2">
<button type="button" onclick="openCalendarBlockModal(${blockJson})" class="p-1 text-slate-400 hover:text-indigo-600 dark:hover:text-indigo-400 transition cursor-pointer" title="Bewerken">
<i data-lucide="edit-2" class="w-3.5 h-3.5"></i>
</button>
<button type="button" onclick="deleteCalendarBlock(${b.id})" class="p-1 text-slate-400 hover:text-rose-600 dark:hover:text-rose-400 transition cursor-pointer" title="Opheffen">
<i data-lucide="trash-2" class="w-3.5 h-3.5"></i>
</button>
</div>
</div>
`;
}).join('');
if (typeof lucide !== 'undefined' && lucide.createIcons) {
lucide.createIcons();
}
} catch (e) {
container.innerHTML = '<p class="text-[11px] text-rose-500 text-center py-2">Kon blokkades niet laden.</p>';
}
}
async function submitCalendarBlock(event) {
event.preventDefault();
const btn = document.getElementById('btnSaveCalBlock');