Aanpassing planner

This commit is contained in:
2026-09-12 11:04:02 +02:00
parent 5a5456ffe0
commit 09ab527a93
7 changed files with 666 additions and 26 deletions
+460 -12
View File
@@ -213,6 +213,32 @@ foreach ($weekOutlookEvents as $evt) {
$outlookEventsByDate[$evtDate][] = $evt;
}
// 4b. Haal actieve kalenderblokkades (vakanties / verlof / afwezigheid) op
$allCalendarBlocks = [];
$calendarBlocksByDate = [];
try {
$stmtBlocks = $db->prepare("
SELECT * FROM calendar_blocks
WHERE user_id = ? AND start_date <= ? AND end_date >= ?
ORDER BY start_date ASC
");
$stmtBlocks->execute([$userId, date('Y-m-d', strtotime('+35 days', $sundayTs)), date('Y-m-d', strtotime('-14 days', $mondayTs))]);
$allCalendarBlocks = $stmtBlocks->fetchAll() ?: [];
foreach ($allCalendarBlocks as $cb) {
$cbStart = strtotime($cb['start_date']);
$cbEnd = strtotime($cb['end_date']);
for ($d = $cbStart; $d <= $cbEnd; $d += 86400) {
$dStr = date('Y-m-d', $d);
$calendarBlocksByDate[$dStr][] = $cb;
}
}
} catch (Throwable $e) {
error_log("Fout bij ophalen calendar_blocks in planner: " . $e->getMessage());
}
$selectedDayBlock = $calendarBlocksByDate[$selectedDate][0] ?? null;
$isSelectedDateBlocked = !empty($selectedDayBlock);
// 7 Dagen van de week samenstellen voor de weekselector
$weekDays = [];
for ($i = 0; $i < 7; $i++) {
@@ -223,6 +249,8 @@ for ($i = 0; $i < 7; $i++) {
$tStat = $weekTaskStats[$dayDate] ?? ['total' => 0, 'completed' => 0];
$mCount = count($outlookEventsByDate[$dayDate] ?? []);
$isWorkday = in_array($dayN, $activeWorkdays, true);
$dayBlock = $calendarBlocksByDate[$dayDate][0] ?? null;
$isBlocked = !empty($dayBlock);
$weekDays[] = [
'date' => $dayDate,
@@ -234,6 +262,8 @@ for ($i = 0; $i < 7; $i++) {
'is_selected' => ($dayDate === $selectedDate),
'is_workday' => $isWorkday,
'is_weekend' => !$isWorkday,
'is_blocked' => $isBlocked,
'block_info' => $dayBlock,
'task_count' => $tStat['total'],
'completed_count' => $tStat['completed'],
'meeting_count' => $mCount,
@@ -456,6 +486,12 @@ for ($h = $startHour; $h <= $endHour; $h++) {
<span>Weekend / Vrij</span>
</span>
<?php endif; ?>
<?php if ($isSelectedDateBlocked): ?>
<span class="px-2.5 py-0.5 rounded-full text-[10px] font-extrabold bg-amber-100 text-amber-900 dark:bg-amber-950/80 dark:text-amber-300 ring-1 ring-amber-500/30 flex items-center gap-1 cursor-pointer" onclick="openCalendarBlockModal(<?= htmlspecialchars(json_encode($selectedDayBlock), ENT_QUOTES) ?>)" title="<?= htmlspecialchars($selectedDayBlock['title']) ?> (klik om te bewerken)">
<i data-lucide="palmtree" class="w-3 h-3 text-amber-600 dark:text-amber-400"></i>
<span><?= htmlspecialchars($selectedDayBlock['title']) ?></span>
</span>
<?php endif; ?>
</div>
<h1 class="text-2xl sm:text-3xl font-black tracking-tight text-slate-900 dark:text-white">
Fociz
@@ -467,6 +503,11 @@ for ($h = $startHour; $h <= $endHour; $h++) {
<!-- Integratie Status Badges & Widget Beheer Knop -->
<div class="flex flex-wrap items-center gap-2">
<button onclick="openCalendarBlockModal()" class="flex items-center space-x-1.5 px-3.5 py-1.5 rounded-xl bg-amber-50 hover:bg-amber-100 dark:bg-amber-500/10 dark:hover:bg-amber-500/20 text-amber-800 dark:text-amber-300 border border-amber-200/80 dark:border-amber-500/30 text-xs font-bold transition shadow-2xs cursor-pointer" title="Hele of meerdere dagen blokkeren voor vakantie of verlof">
<i data-lucide="palmtree" class="w-3.5 h-3.5 text-amber-600 dark:text-amber-400"></i>
<span>Vakantie / Dagen Blokkeren</span>
</button>
<button onclick="openWidgetManagerModal()" class="flex items-center space-x-1.5 px-3.5 py-1.5 rounded-xl bg-indigo-50 hover:bg-indigo-100 dark:bg-indigo-500/10 dark:hover:bg-indigo-500/20 text-indigo-700 dark:text-indigo-300 border border-indigo-200/80 dark:border-indigo-500/30 text-xs font-bold transition shadow-2xs cursor-pointer">
<i data-lucide="layout-grid" class="w-3.5 h-3.5"></i>
<span>Widgets & Indeling</span>
@@ -953,8 +994,13 @@ for ($h = $startHour; $h <= $endHour; $h++) {
</div>
</div>
<!-- Rechterkant: AI Slim Indelen & Directe datumselector -->
<div class="flex flex-wrap items-center space-x-2 justify-end">
<!-- Rechterkant: Dagen Blokkeren, 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>
@@ -984,16 +1030,23 @@ for ($h = $startHour; $h <= $endHour; $h++) {
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']
? '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_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') ?>"
: ($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')) ?>"
>
<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') ?>">
<?= $wd['short_name'] ?>
</span>
<div class="flex items-center gap-1">
<?php if ($wd['is_weekend']): ?>
<?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>
<?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">
Weekend
</span>
@@ -1041,27 +1094,68 @@ 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">
🏖️
</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>
</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">
<div class="flex items-center space-x-2">
<div class="w-7 h-7 rounded-lg <?= $isSelectedDateWeekend ? 'bg-amber-50 dark:bg-amber-950/50 text-amber-600 dark:text-amber-400' : 'bg-indigo-50 dark:bg-indigo-950/50 text-indigo-600 dark:text-indigo-400' ?> flex items-center justify-center font-bold">
<i data-lucide="<?= $isSelectedDateWeekend ? 'coffee' : 'clock' ?>" class="w-3.5 h-3.5"></i>
<div class="w-7 h-7 rounded-lg <?= ($isSelectedDateBlocked || $isSelectedDateWeekend) ? 'bg-amber-50 dark:bg-amber-950/50 text-amber-600 dark:text-amber-400' : 'bg-indigo-50 dark:bg-indigo-950/50 text-indigo-600 dark:text-indigo-400' ?> flex items-center justify-center font-bold">
<i data-lucide="<?= $isSelectedDateBlocked ? 'palmtree' : ($isSelectedDateWeekend ? 'coffee' : 'clock') ?>" class="w-3.5 h-3.5"></i>
</div>
<div>
<div class="flex items-center space-x-2">
<h4 class="text-xs font-bold text-slate-900 dark:text-white uppercase tracking-wider">
Tijdlijn & Time-blocking voor <?= htmlspecialchars($formattedSelectedDate) ?>
</h4>
<?php if ($isSelectedDateWeekend): ?>
<?php if ($isSelectedDateBlocked): ?>
<span class="px-2 py-0.5 rounded-full text-[9px] font-extrabold uppercase bg-amber-100 text-amber-900 dark:bg-amber-950/80 dark:text-amber-200 border border-amber-300 dark:border-amber-700/80">
<?= htmlspecialchars($selectedDayBlock['title']) ?> (Geblokkeerd)
</span>
<?php elseif ($isSelectedDateWeekend): ?>
<span class="px-2 py-0.5 rounded-full text-[9px] font-extrabold uppercase bg-amber-50 text-amber-700 dark:bg-amber-950/60 dark:text-amber-300 border border-amber-200/80 dark:border-amber-800/60">
Weekend / Vrije Dag
</span>
<?php endif; ?>
</div>
<p class="text-[10px] text-slate-400">
<?= $isSelectedDateWeekend
? 'Geen reguliere kantooruren gepland conform beheerinstellingen &bull; Vrije dag'
: 'Urenindeling & synchronisatie met Outlook' ?>
<?= $isSelectedDateBlocked
? 'Deze dag is geblokkeerd in je agenda &bull; Geen kantooruren ingeroosterd'
: ($isSelectedDateWeekend
? 'Geen reguliere kantooruren gepland conform beheerinstellingen &bull; Vrije dag'
: 'Urenindeling & synchronisatie met Outlook') ?>
</p>
</div>
</div>
@@ -2316,6 +2410,123 @@ if ($isAjaxRequest) {
</div>
</div>
<!-- Modal: Agenda Dagen Blokkeren (Vakantie / Verlof / Focus) -->
<div id="calendarBlockModal" class="fixed inset-0 z-50 bg-slate-950/70 backdrop-blur-sm hidden items-center justify-center p-4">
<div class="fociz-card p-6 w-full max-w-lg shadow-2xl space-y-4 max-h-[90vh] flex flex-col">
<!-- Header -->
<div class="flex items-center justify-between pb-3 border-b border-slate-100 dark:border-slate-800/80 flex-shrink-0">
<div class="flex items-center space-x-2.5">
<div class="w-8 h-8 rounded-xl bg-amber-50 dark:bg-amber-500/10 border border-amber-200 dark:border-amber-500/20 text-amber-600 dark:text-amber-400 flex items-center justify-center">
<i data-lucide="palmtree" class="w-4 h-4"></i>
</div>
<div>
<h3 id="calendarBlockModalTitle" class="text-sm font-bold text-slate-900 dark:text-white">Dagen Blokkeren / Vakantie</h3>
<p class="text-[10px] text-slate-500 dark:text-slate-400">Blokkeer een hele dag of periode in je agenda & planner</p>
</div>
</div>
<button type="button" onclick="closeCalendarBlockModal()" class="p-1.5 text-slate-400 hover:text-slate-600 dark:hover:text-slate-200 rounded-lg hover:bg-slate-100 dark:hover:bg-slate-800 transition cursor-pointer">
<i data-lucide="x" class="w-4 h-4"></i>
</button>
</div>
<!-- Formulier / Content -->
<form id="calendarBlockForm" onsubmit="submitCalendarBlock(event)" class="flex-1 overflow-y-auto pr-1 space-y-3.5">
<input type="hidden" id="calBlockId" value="0">
<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">
</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">
</div>
<div class="grid grid-cols-2 gap-3">
<div>
<label class="block text-xs font-bold text-slate-700 dark:text-slate-300 mb-1">Begindatum <span class="text-rose-500">*</span></label>
<input type="date" id="calBlockStartDate" required 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">
</div>
<div>
<label class="block text-xs font-bold text-slate-700 dark:text-slate-300 mb-1">Einddatum (t/m) <span class="text-rose-500">*</span></label>
<input type="date" id="calBlockEndDate" required 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">
</div>
</div>
<div>
<label class="block text-xs font-bold text-slate-700 dark:text-slate-300 mb-1">Notities (optioneel)</label>
<textarea id="calBlockNotes" rows="2" placeholder="Eventuele overdracht of bijzonderheden..." 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"></textarea>
</div>
<?php if ($isOutlookConnected): ?>
<div class="p-3 rounded-xl bg-sky-50/60 dark:bg-sky-950/20 border border-sky-200/60 dark:border-sky-800/40 flex items-center justify-between">
<div class="flex items-center space-x-2.5">
<i data-lucide="refresh-cw" class="w-4 h-4 text-sky-600 dark:text-sky-400 flex-shrink-0"></i>
<div>
<p class="text-xs font-bold text-sky-900 dark:text-sky-200">Synchroniseren naar Outlook 365</p>
<p class="text-[10px] text-sky-700/80 dark:text-sky-400/80">Maakt een 'Afwezig' (OOF) dagblokkade aan in je Microsoft agenda</p>
</div>
</div>
<label class="relative inline-flex items-center cursor-pointer flex-shrink-0 ml-2">
<input type="checkbox" id="calBlockSyncOutlook" checked class="sr-only peer">
<div class="w-9 h-5 bg-slate-200 peer-focus:outline-none rounded-full peer dark:bg-slate-700 peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:border-slate-300 after:border after:rounded-full after:h-4 after:w-4 after:transition-all dark:border-slate-600 peer-checked:bg-sky-600"></div>
</label>
</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">
<i data-lucide="trash-2" class="w-3.5 h-3.5"></i>
<span>Opheffen</span>
</button>
<div class="flex items-center space-x-2 ml-auto">
<button type="button" onclick="closeCalendarBlockModal()" class="px-4 py-2 rounded-xl bg-slate-100 hover:bg-slate-200 dark:bg-slate-800 dark:hover:bg-slate-700 text-slate-700 dark:text-slate-300 text-xs font-semibold transition cursor-pointer">
Annuleren
</button>
<button type="submit" id="btnSaveCalBlock" class="px-4 py-2 rounded-xl bg-amber-600 hover:bg-amber-500 text-white text-xs font-bold transition shadow-xs cursor-pointer flex items-center space-x-1.5">
<i data-lucide="check" class="w-3.5 h-3.5"></i>
<span>Opslaan</span>
</button>
</div>
</div>
</form>
</div>
</div>
<script>
// --- Fociz Widget Beheer & Configuratielogica (v2.0) ---
let focizWidgets = <?= json_encode(array_values($userWidgetLayout), JSON_UNESCAPED_UNICODE) ?>;
@@ -2590,6 +2801,243 @@ function showWidgetToast(htmlMessage, type = 'success', duration = 3500) {
}, duration);
}
// --- Agenda Dagen Blokkeren (Vakantie / Verlof / Focus) ---
const CAL_BLOCK_DEFAULT_TITLES = {
'VACATION': 'Vakantie',
'LEAVE': 'Verlof',
'SICK': 'Ziekte / Afwezig',
'FOCUS': 'Focusdag'
};
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] || ''))) {
titleInput.value = CAL_BLOCK_DEFAULT_TITLES[type] || '';
}
}
function openCalendarBlockModal(existingBlock = null, defaultDate = null) {
const modal = document.getElementById('calendarBlockModal');
if (!modal) return;
const idInput = document.getElementById('calBlockId');
const titleInput = document.getElementById('calBlockTitle');
const startInput = document.getElementById('calBlockStartDate');
const endInput = document.getElementById('calBlockEndDate');
const notesInput = document.getElementById('calBlockNotes');
const deleteBtn = document.getElementById('btnDeleteCurrentCalBlock');
const modalTitle = document.getElementById('calendarBlockModalTitle');
const selDate = defaultDate || '<?= $selectedDate ?>';
if (existingBlock && existingBlock.id) {
if (idInput) idInput.value = existingBlock.id;
if (titleInput) titleInput.value = existingBlock.title || '';
if (startInput) startInput.value = existingBlock.start_date || selDate;
if (endInput) endInput.value = existingBlock.end_date || existingBlock.start_date || selDate;
if (notesInput) notesInput.value = existingBlock.notes || '';
selectCalBlockType(existingBlock.block_type || 'VACATION');
if (deleteBtn) {
deleteBtn.classList.remove('hidden');
deleteBtn.classList.add('flex');
}
if (modalTitle) modalTitle.textContent = 'Blokkade Bewerken';
} else {
if (idInput) idInput.value = 0;
selectCalBlockType('VACATION');
if (titleInput) titleInput.value = 'Vakantie';
if (startInput) startInput.value = selDate;
if (endInput) endInput.value = selDate;
if (notesInput) notesInput.value = '';
if (deleteBtn) {
deleteBtn.classList.add('hidden');
deleteBtn.classList.remove('flex');
}
if (modalTitle) modalTitle.textContent = 'Dagen Blokkeren / Vakantie';
}
loadCalendarBlocksList();
modal.classList.remove('hidden');
modal.classList.add('flex');
if (typeof lucide !== 'undefined' && lucide.createIcons) {
lucide.createIcons();
}
}
function closeCalendarBlockModal() {
const modal = document.getElementById('calendarBlockModal');
if (modal) {
modal.classList.add('hidden');
modal.classList.remove('flex');
}
}
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');
const origHtml = btn ? btn.innerHTML : '';
if (btn) {
btn.innerText = 'Opslaan...';
btn.disabled = true;
}
const id = parseInt(document.getElementById('calBlockId')?.value, 10) || 0;
const blockType = document.getElementById('calBlockType')?.value || 'VACATION';
const title = document.getElementById('calBlockTitle')?.value.trim();
const startDate = document.getElementById('calBlockStartDate')?.value;
const endDate = document.getElementById('calBlockEndDate')?.value;
const notes = document.getElementById('calBlockNotes')?.value.trim();
const syncOutlook = document.getElementById('calBlockSyncOutlook')?.checked || false;
if (!title) {
alert('Vul een titel in voor de blokkade.');
if (btn) { btn.innerHTML = origHtml; btn.disabled = false; }
return;
}
try {
const res = await fetch('/api/planner.php?action=save_calendar_block', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
id: id,
title: title,
block_type: blockType,
start_date: startDate,
end_date: endDate,
notes: notes,
sync_to_outlook: syncOutlook
})
});
const data = await res.json();
if (data.success) {
showWidgetToast(id > 0 ? 'Blokkade succesvol bijgewerkt' : 'Dagen succesvol geblokkeerd', 'success');
setTimeout(() => {
window.location.reload();
}, 600);
} else {
alert(data.error || 'Er is een fout opgetreden bij het opslaan.');
if (btn) { btn.innerHTML = origHtml; btn.disabled = false; }
}
} catch (err) {
alert('Netwerkfout bij opslaan van blokkade.');
if (btn) { btn.innerHTML = origHtml; btn.disabled = false; }
}
}
async function deleteCalendarBlock(blockId) {
if (!blockId) return;
if (!confirm('Weet je zeker dat je deze agenda-blokkade wilt opheffen?')) 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.success) {
showWidgetToast('Blokkade opgeheven', 'success');
setTimeout(() => {
window.location.reload();
}, 600);
} else {
alert(data.error || 'Kon blokkade niet opheffen.');
}
} catch (err) {
alert('Netwerkfout bij opheffen van blokkade.');
}
}
function deleteCurrentCalendarBlock() {
const id = parseInt(document.getElementById('calBlockId')?.value, 10) || 0;
if (id > 0) {
deleteCalendarBlock(id);
}
}
// --- Bepaal de actuele widget layout en exacte volgorde uit het scherm (DOM) ---
function getActiveWidgetLayoutPayload() {
const grid = document.getElementById('focizWidgetGrid');