Compare commits

...

2 Commits

Author SHA1 Message Date
NiekRabelink 7e7f071f04 Merge pull request 'Dashboard' (#79) from dev into main
Reviewed-on: #79
2026-09-14 09:52:31 +00:00
NiekRabelink 3595d58763 Dashboard 2026-09-14 11:52:18 +02:00
2 changed files with 129 additions and 94 deletions
+58
View File
@@ -252,6 +252,64 @@ require_once __DIR__ . '/env_badge.php';
<!-- Right side: Clean User Profile & Logout --> <!-- Right side: Clean User Profile & Logout -->
<div class="flex items-center space-x-2 sm:space-x-3"> <div class="flex items-center space-x-2 sm:space-x-3">
<?php if ($currentPage === 'planner.php' && !empty($formattedSelectedDate)): ?>
<!-- Header Planner Datum & Status -->
<div class="flex items-center gap-1.5 sm:gap-2 mr-1 sm:mr-2">
<div class="flex items-center space-x-1.5 text-xs font-bold text-slate-700 dark:text-slate-200 bg-slate-100 dark:bg-slate-800/80 px-2.5 py-1.5 rounded-xl border border-slate-200/80 dark:border-slate-700/60 shadow-2xs">
<i data-lucide="calendar" class="w-3.5 h-3.5 text-indigo-500"></i>
<span id="headerCurrentDateDisplay"><?= htmlspecialchars($formattedSelectedDate) ?></span>
</div>
<div id="headerDateBadgesContainer" class="hidden sm:flex items-center gap-1.5">
<?php if (!empty($isTodaySelected)): ?>
<span class="px-2 py-0.5 rounded-full text-[10px] font-extrabold bg-indigo-50 text-indigo-700 dark:bg-indigo-500/15 dark:text-indigo-300 ring-1 ring-indigo-500/30">VANDAAG</span>
<?php endif; ?>
<?php if (!empty($isSelectedDateWorkday)): ?>
<span class="px-2 py-0.5 rounded-full text-[10px] font-bold bg-emerald-50 text-emerald-700 dark:bg-emerald-500/15 dark:text-emerald-300 ring-1 ring-emerald-500/30 flex items-center gap-1" title="Reguliere werkdag">
<i data-lucide="briefcase" class="w-3 h-3"></i>
<span>Werkdag</span>
</span>
<?php else: ?>
<span class="px-2 py-0.5 rounded-full text-[10px] font-extrabold bg-amber-50 text-amber-700 dark:bg-amber-500/15 dark:text-amber-400 ring-1 ring-amber-500/30 flex items-center gap-1" title="Weekend / Vrije dag">
<i data-lucide="coffee" class="w-3 h-3"></i>
<span>Weekend / Vrij</span>
</span>
<?php endif; ?>
<?php if (!empty($isSelectedDateBlocked)): ?>
<span class="px-2 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>
</div>
<!-- Instellingen Dropdown (Widgets, Indeling & Dagen Blokkeren) - alleen op planner.php -->
<div class="relative mr-1 sm:mr-2">
<button id="plannerSettingsDropdownBtn" onclick="togglePlannerSettingsMenu(event)" class="p-1.5 sm:p-2 rounded-xl text-slate-400 hover:text-slate-700 dark:hover:text-slate-200 hover:bg-slate-100 dark:hover:bg-slate-800/80 transition cursor-pointer border border-transparent hover:border-slate-200 dark:hover:border-slate-700" title="Dashboard Opties & Instellingen">
<i data-lucide="settings" class="w-4 sm:w-5 h-4 sm:h-5"></i>
</button>
<div id="plannerSettingsDropdownMenu" class="hidden absolute right-0 mt-2 w-56 rounded-2xl bg-white dark:bg-[#111624] border border-slate-200 dark:border-slate-800 shadow-xl py-1.5 z-50 text-xs text-slate-700 dark:text-slate-300">
<button onclick="closePlannerSettingsMenu(); openWidgetManagerModal();" class="w-full text-left px-3.5 py-2 hover:bg-slate-50 dark:hover:bg-slate-800/70 flex items-center space-x-2.5 transition cursor-pointer">
<i data-lucide="layout-grid" class="w-4 h-4 text-indigo-500"></i>
<span class="font-medium">Widgets & Indeling</span>
</button>
<button onclick="closePlannerSettingsMenu(); openCalendarBlockModal();" class="w-full text-left px-3.5 py-2 hover:bg-slate-50 dark:hover:bg-slate-800/70 flex items-center space-x-2.5 transition cursor-pointer">
<i data-lucide="palmtree" class="w-4 h-4 text-amber-500"></i>
<span class="font-medium">Vakantie / Dagen Blokkeren</span>
</button>
<?php if (!empty($isOutlookConnected)): ?>
<button onclick="closePlannerSettingsMenu(); triggerManualSync();" class="w-full text-left px-3.5 py-2 hover:bg-slate-50 dark:hover:bg-slate-800/70 flex items-center space-x-2.5 transition cursor-pointer border-t border-slate-100 dark:border-slate-800/80">
<i data-lucide="refresh-cw" class="w-4 h-4 text-sky-500"></i>
<span class="font-medium">Outlook Synchroniseren</span>
</button>
<?php endif; ?>
</div>
</div>
<?php endif; ?>
<!-- Profiel link rechtsboven --> <!-- Profiel link rechtsboven -->
<a href="/profile.php" class="flex items-center space-x-2.5 group hover:opacity-90 transition px-2 py-1.5 rounded-xl hover:bg-slate-100 dark:hover:bg-slate-800/60" title="Mijn Profiel & Instellingen"> <a href="/profile.php" class="flex items-center space-x-2.5 group hover:opacity-90 transition px-2 py-1.5 rounded-xl hover:bg-slate-100 dark:hover:bg-slate-800/60" title="Mijn Profiel & Instellingen">
<div class="w-7 h-7 rounded-lg bg-gradient-to-tr from-indigo-600 to-sky-500 p-0.5 flex-shrink-0 shadow-xs overflow-hidden"> <div class="w-7 h-7 rounded-lg bg-gradient-to-tr from-indigo-600 to-sky-500 p-0.5 flex-shrink-0 shadow-xs overflow-hidden">
+70 -93
View File
@@ -7,13 +7,8 @@
// Controleer of dit een AJAX verzoek is voor een geruisloze datumwisseling // Controleer of dit een AJAX verzoek is voor een geruisloze datumwisseling
$isAjaxRequest = (!empty($_GET['ajax']) && $_GET['ajax'] === '1'); $isAjaxRequest = (!empty($_GET['ajax']) && $_GET['ajax'] === '1');
if ($isAjaxRequest) {
require_once __DIR__ . '/includes/auth.php'; require_once __DIR__ . '/includes/auth.php';
$currentUser = require_auth(true); $currentUser = require_auth($isAjaxRequest);
} else {
$pageTitle = 'Fociz';
require_once __DIR__ . '/includes/header.php';
}
require_once __DIR__ . '/includes/settings.php'; require_once __DIR__ . '/includes/settings.php';
require_once __DIR__ . '/includes/integrations/microsoft.php'; require_once __DIR__ . '/includes/integrations/microsoft.php';
@@ -70,7 +65,33 @@ $dutchShortMonths = ['', 'jan', 'feb', 'mrt', 'apr', 'mei', 'jun', 'jul', 'aug',
$formattedSelectedDate = $dutchFullDays[(int)date('N', $selectedTs)] . ' ' . date('j', $selectedTs) . ' ' . $dutchMonths[(int)date('n', $selectedTs)] . ' ' . date('Y', $selectedTs); $formattedSelectedDate = $dutchFullDays[(int)date('N', $selectedTs)] . ' ' . date('j', $selectedTs) . ' ' . $dutchMonths[(int)date('n', $selectedTs)] . ' ' . date('Y', $selectedTs);
$formattedWeekRange = date('j', $mondayTs) . ' ' . $dutchShortMonths[(int)date('n', $mondayTs)] . ' – ' . date('j', $sundayTs) . ' ' . $dutchShortMonths[(int)date('n', $sundayTs)] . ' ' . date('Y', $sundayTs); $formattedWeekRange = date('j', $mondayTs) . ' ' . $dutchShortMonths[(int)date('n', $mondayTs)] . ' – ' . date('j', $sundayTs) . ' ' . $dutchShortMonths[(int)date('n', $sundayTs)] . ' ' . date('Y', $sundayTs);
// 1. Haal integratiestatussen op // Haal actieve kalenderblokkades (vakanties / verlof / afwezigheid) op vóór header render
$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);
// 1. Haal integratiestatussen op vóór header render
$userIntegrations = []; $userIntegrations = [];
try { try {
$stmtInt = $db->prepare("SELECT * FROM integrations WHERE user_id = ?"); $stmtInt = $db->prepare("SELECT * FROM integrations WHERE user_id = ?");
@@ -87,6 +108,11 @@ $isOutlookConnected = !empty($userIntegrations['OUTLOOK']['access_token']) && em
$outlookIntegrationSettings = json_decode($userIntegrations['OUTLOOK']['settings'] ?? '{}', true) ?: []; $outlookIntegrationSettings = json_decode($userIntegrations['OUTLOOK']['settings'] ?? '{}', true) ?: [];
$syncIntervalSeconds = isset($outlookIntegrationSettings['sync_interval_seconds']) ? (int)$outlookIntegrationSettings['sync_interval_seconds'] : 30; $syncIntervalSeconds = isset($outlookIntegrationSettings['sync_interval_seconds']) ? (int)$outlookIntegrationSettings['sync_interval_seconds'] : 30;
if (!$isAjaxRequest) {
$pageTitle = 'Fociz';
require_once __DIR__ . '/includes/header.php';
}
$weekOutlookEvents = []; $weekOutlookEvents = [];
$isLiveOutlook = false; $isLiveOutlook = false;
@@ -247,32 +273,6 @@ foreach ($weekOutlookEvents as $evt) {
$outlookEventsByDate[$evtDate][] = $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 // 7 Dagen van de week samenstellen voor de weekselector
$weekDays = []; $weekDays = [];
for ($i = 0; $i < 7; $i++) { for ($i = 0; $i < 7; $i++) {
@@ -495,67 +495,7 @@ for ($h = $startHour; $h <= $endHour; $h++) {
<?php else: ?> <?php else: ?>
<?php ob_start(); ?> <?php ob_start(); ?>
<?php endif; ?> <?php endif; ?>
<!-- Top Banner: Datum, Hoofddoel & Integraties Status -->
<div class="fociz-card p-6 sm:p-7 relative overflow-hidden">
<div class="flex flex-col lg:flex-row lg:items-center justify-between gap-4">
<div>
<div class="flex flex-wrap items-center gap-2 text-indigo-600 dark:text-indigo-400 text-xs font-bold uppercase tracking-wider mb-1">
<div class="flex items-center space-x-1.5">
<i data-lucide="calendar" class="w-4 h-4"></i>
<span><?= htmlspecialchars($formattedSelectedDate) ?></span>
</div>
<?php if ($isTodaySelected): ?>
<span class="px-2.5 py-0.5 rounded-full text-[10px] font-extrabold bg-indigo-50 text-indigo-700 dark:bg-indigo-500/15 dark:text-indigo-300 ring-1 ring-indigo-500/30">VANDAAG</span>
<?php else: ?>
<span class="px-2.5 py-0.5 rounded-full text-[10px] font-bold bg-slate-100 dark:bg-slate-800/80 text-slate-600 dark:text-slate-400">GEKOZEN DAG</span>
<?php endif; ?>
<?php if ($isSelectedDateWorkday): ?>
<span class="px-2 py-0.5 rounded-full text-[10px] font-bold bg-emerald-50 text-emerald-700 dark:bg-emerald-500/15 dark:text-emerald-300 ring-1 ring-emerald-500/30 flex items-center gap-1" title="Reguliere werkdag conform beheerinstellingen">
<i data-lucide="briefcase" class="w-3 h-3"></i>
<span>Werkdag</span>
</span>
<?php else: ?>
<span class="px-2 py-0.5 rounded-full text-[10px] font-extrabold bg-amber-50 text-amber-700 dark:bg-amber-500/15 dark:text-amber-400 ring-1 ring-amber-500/30 flex items-center gap-1" title="Weekend / Vrije dag conform beheerinstellingen">
<i data-lucide="coffee" class="w-3 h-3"></i>
<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
</h1>
</div>
<!-- Instellingen Dropdown (Widgets, Indeling & Dagen Blokkeren) -->
<div class="relative">
<button id="plannerSettingsDropdownBtn" onclick="togglePlannerSettingsMenu(event)" class="p-2 rounded-xl text-slate-400 hover:text-slate-700 dark:hover:text-slate-200 hover:bg-slate-100 dark:hover:bg-slate-800/80 transition cursor-pointer border border-transparent hover:border-slate-200 dark:hover:border-slate-700" title="Dashboard Opties & Instellingen">
<i data-lucide="settings" class="w-5 h-5"></i>
</button>
<div id="plannerSettingsDropdownMenu" class="hidden absolute right-0 mt-2 w-56 rounded-2xl bg-white dark:bg-[#111624] border border-slate-200 dark:border-slate-800 shadow-xl py-1.5 z-50 text-xs text-slate-700 dark:text-slate-300">
<button onclick="closePlannerSettingsMenu(); openWidgetManagerModal();" class="w-full text-left px-3.5 py-2 hover:bg-slate-50 dark:hover:bg-slate-800/70 flex items-center space-x-2.5 transition cursor-pointer">
<i data-lucide="layout-grid" class="w-4 h-4 text-indigo-500"></i>
<span class="font-medium">Widgets & Indeling</span>
</button>
<button onclick="closePlannerSettingsMenu(); openCalendarBlockModal();" class="w-full text-left px-3.5 py-2 hover:bg-slate-50 dark:hover:bg-slate-800/70 flex items-center space-x-2.5 transition cursor-pointer">
<i data-lucide="palmtree" class="w-4 h-4 text-amber-500"></i>
<span class="font-medium">Vakantie / Dagen Blokkeren</span>
</button>
<?php if ($isOutlookConnected): ?>
<button onclick="closePlannerSettingsMenu(); triggerManualSync();" class="w-full text-left px-3.5 py-2 hover:bg-slate-50 dark:hover:bg-slate-800/70 flex items-center space-x-2.5 transition cursor-pointer border-t border-slate-100 dark:border-slate-800/80">
<i data-lucide="refresh-cw" class="w-4 h-4 text-sky-500"></i>
<span class="font-medium">Outlook Synchroniseren</span>
</button>
<?php endif; ?>
</div>
</div>
</div>
</div>
<!-- AI Dagbriefing Banner (async geladen, alleen vandaag) --> <!-- AI Dagbriefing Banner (async geladen, alleen vandaag) -->
<?php if ($isTodaySelected): ?> <?php if ($isTodaySelected): ?>
@@ -1965,6 +1905,10 @@ if ($isAjaxRequest) {
'success' => true, 'success' => true,
'selectedDate' => $selectedDate, 'selectedDate' => $selectedDate,
'formattedDate' => $formattedSelectedDate, 'formattedDate' => $formattedSelectedDate,
'isToday' => (bool)$isTodaySelected,
'isWorkday' => (bool)$isSelectedDateWorkday,
'isBlocked' => (bool)$isSelectedDateBlocked,
'selectedDayBlock'=> $selectedDayBlock ?? null,
'weekStart' => $weekStart, 'weekStart' => $weekStart,
'weekEnd' => $weekEnd, 'weekEnd' => $weekEnd,
'weekNumber' => $weekNumber, 'weekNumber' => $weekNumber,
@@ -6230,6 +6174,39 @@ async function switchPlannerDate(newDate, event = null, pushState = true) {
document.title = data.title; document.title = data.title;
} }
// Werk datum & badges in top header bij
const headerDateEl = document.getElementById('headerCurrentDateDisplay');
if (headerDateEl && data.formattedDate) {
headerDateEl.textContent = data.formattedDate;
}
const headerBadgesEl = document.getElementById('headerDateBadgesContainer');
if (headerBadgesEl) {
let badgesHtml = '';
if (data.isToday) {
badgesHtml += `<span class="px-2 py-0.5 rounded-full text-[10px] font-extrabold bg-indigo-50 text-indigo-700 dark:bg-indigo-500/15 dark:text-indigo-300 ring-1 ring-indigo-500/30">VANDAAG</span>`;
}
if (data.isWorkday) {
badgesHtml += `<span class="px-2 py-0.5 rounded-full text-[10px] font-bold bg-emerald-50 text-emerald-700 dark:bg-emerald-500/15 dark:text-emerald-300 ring-1 ring-emerald-500/30 flex items-center gap-1" title="Reguliere werkdag">
<i data-lucide="briefcase" class="w-3 h-3"></i>
<span>Werkdag</span>
</span>`;
} else {
badgesHtml += `<span class="px-2 py-0.5 rounded-full text-[10px] font-extrabold bg-amber-50 text-amber-700 dark:bg-amber-500/15 dark:text-amber-400 ring-1 ring-amber-500/30 flex items-center gap-1" title="Weekend / Vrije dag">
<i data-lucide="coffee" class="w-3 h-3"></i>
<span>Weekend / Vrij</span>
</span>`;
}
if (data.isBlocked && data.selectedDayBlock) {
const blockJson = JSON.stringify(data.selectedDayBlock).replace(/"/g, '&quot;');
const blockTitle = (data.selectedDayBlock.title || '').replace(/</g, '&lt;').replace(/>/g, '&gt;');
badgesHtml += `<span class="px-2 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(${blockJson})' title="${blockTitle} (klik om te bewerken)">
<i data-lucide="palmtree" class="w-3 h-3 text-amber-600 dark:text-amber-400"></i>
<span>${blockTitle}</span>
</span>`;
}
headerBadgesEl.innerHTML = badgesHtml;
}
// Werk URL bij in adresbalk zonder paginaherlaad // Werk URL bij in adresbalk zonder paginaherlaad
if (pushState && window.history && window.history.pushState) { if (pushState && window.history && window.history.pushState) {
const todayStr = '<?= $today ?>'; const todayStr = '<?= $today ?>';