From 2062fd065a380827825da33d817adb32a14a5b13 Mon Sep 17 00:00:00 2001 From: Niek Rabelink Date: Wed, 9 Sep 2026 19:02:40 +0200 Subject: [PATCH] Implement seamless AJAX date switching and in-place updates in planner to preserve scroll position and eliminate full-page reload flickers --- planner.php | 169 +++++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 148 insertions(+), 21 deletions(-) diff --git a/planner.php b/planner.php index 83e24d1..407dba2 100644 --- a/planner.php +++ b/planner.php @@ -4,13 +4,22 @@ * Gekoppeld aan Microsoft Outlook 365 (Agenda) en Atlassian Jira (Tickets) */ -require_once __DIR__ . '/includes/header.php'; +// Controleer of dit een AJAX verzoek is voor een geruisloze datumwisseling +$isAjaxRequest = (!empty($_GET['ajax']) && $_GET['ajax'] === '1'); + +if ($isAjaxRequest) { + require_once __DIR__ . '/includes/auth.php'; + $currentUser = require_auth(true); +} else { + require_once __DIR__ . '/includes/header.php'; +} + require_once __DIR__ . '/includes/settings.php'; require_once __DIR__ . '/includes/integrations/microsoft.php'; require_once __DIR__ . '/includes/integrations/jira.php'; $db = get_db_connection(); -$userId = $currentUser['id']; +$userId = (int)($currentUser['id'] ?? 0); $today = date('Y-m-d'); $selectedDate = (!empty($_GET['date']) && preg_match('/^\d{4}-\d{2}-\d{2}$/', $_GET['date'])) ? $_GET['date'] : $today; $isTodaySelected = ($selectedDate === $today); @@ -294,7 +303,8 @@ for ($h = $startHour; $h <= $endHour; $h++) { .line-clamp-2 { display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; } -
+
+
@@ -382,16 +392,16 @@ for ($h = $startHour; $h <= $endHour; $h++) {
- + - + Vandaag - + @@ -414,7 +424,7 @@ for ($h = $startHour; $h <= $endHour; $h++) { type="date" id="jumpDateInput" value="" - onchange="if(this.value) window.location.href='planner.php?date=' + this.value;" + onchange="if(this.value) switchPlannerDate(this.value, event);" class="bg-slate-50 dark:bg-slate-950 border border-slate-200 dark:border-slate-800 rounded-xl px-3 py-1.5 text-xs text-slate-800 dark:text-slate-200 focus:outline-none focus:ring-2 focus:ring-indigo-500/30 font-medium cursor-pointer shadow-2xs" >
@@ -425,6 +435,7 @@ for ($h = $startHour; $h <= $endHour; $h++) {
+ true, + 'selectedDate' => $selectedDate, + 'formattedDate' => $formattedSelectedDate, + 'weekStart' => $weekStart, + 'weekEnd' => $weekEnd, + 'weekNumber' => $weekNumber, + 'yearNumber' => $yearNumber, + 'formattedWeek' => $formattedWeekRange, + 'syncHash' => $initialSyncHash, + 'focusGoal' => $dailyPlan['focus_goal'] ?? '', + 'dailyNotes' => $dailyPlan['notes'] ?? '', + 'title' => 'Fociz — Dagplanning (' . $formattedSelectedDate . ')', + 'html' => $contentHtml, + ]); + exit; +} +?>
@@ -1134,8 +1167,103 @@ function selectFocusTask(title) { document.getElementById('activeTaskDisplay').innerText = title; } +// --- Globale Planner Status & Naadloos AJAX Datumbeheer --- +let activePlannerDate = ''; +let weekStartDate = ''; +let weekEndDate = ''; +let currentSyncHash = ''; +let isDateSwitching = false; + +/** + * Wisselt de actieve plannerdatum geruisloos via AJAX + * Behoudt de huidige schermhoogte (scrollpositie) en voorkomt flitsen/herladen + */ +async function switchPlannerDate(newDate, event = null, pushState = true) { + if (event && event.preventDefault) { + event.preventDefault(); + } + if (!newDate || isDateSwitching) return; + + const container = document.getElementById('plannerContentArea'); + if (!container) { + window.location.href = 'planner.php?date=' + encodeURIComponent(newDate); + return; + } + + // Onthoud exact de huidige scrollpositie + const currentScrollY = window.scrollY; + isDateSwitching = true; + + // Subtiele visuele feedback zonder layout-shift + container.classList.add('opacity-60', 'pointer-events-none'); + + try { + const response = await fetch(`planner.php?date=${encodeURIComponent(newDate)}&ajax=1`, { + headers: { + 'X-Requested-With': 'XMLHttpRequest' + } + }); + + if (!response.ok) { + throw new Error(`HTTP fout ${response.status}`); + } + + const data = await response.json(); + if (!data || !data.success || !data.html) { + throw new Error(data?.error || 'Ongeldige serverrespons'); + } + + // Vervang de dynamische plannerinhoud geruisloos + container.innerHTML = data.html; + + // Werk globale variabelen bij + activePlannerDate = data.selectedDate; + if (data.weekStart) weekStartDate = data.weekStart; + if (data.weekEnd) weekEndDate = data.weekEnd; + if (data.syncHash) currentSyncHash = data.syncHash; + + // Werk paginatitel bij + if (data.title) { + document.title = data.title; + } + + // Werk URL bij in adresbalk zonder paginaherlaad + if (pushState && window.history && window.history.pushState) { + const newUrl = `planner.php?date=${encodeURIComponent(newDate)}`; + window.history.pushState({ date: newDate }, data.title || '', newUrl); + } + + // Render Lucide SVG-iconen in de nieuwe inhoud + if (typeof lucide !== 'undefined' && lucide.createIcons) { + lucide.createIcons(); + } + + // Garandeer dat het scherm exact op dezelfde scrollhoogte blijft + window.scrollTo({ + top: currentScrollY, + behavior: 'instant' + }); + + } catch (err) { + console.error('Fout bij wisselen van datum via AJAX:', err); + // Veilige fallback bij netwerkonderbreking + window.location.href = 'planner.php?date=' + encodeURIComponent(newDate); + } finally { + isDateSwitching = false; + container.classList.remove('opacity-60', 'pointer-events-none'); + } +} + +// Browser Terug/Vooruit knoppen opvangen (popstate) +window.addEventListener('popstate', (event) => { + const urlParams = new URLSearchParams(window.location.search); + const dateFromUrl = urlParams.get('date'); + if (dateFromUrl && dateFromUrl !== activePlannerDate) { + switchPlannerDate(dateFromUrl, null, false); + } +}); + // --- Dagdoel & Taken AJAX --- -const activePlannerDate = ''; async function saveGoal() { const focusGoal = document.getElementById('focusGoalInput').value; @@ -1348,7 +1476,10 @@ async function submitNewTask() { if (syncOutlook && !data.outlook_synced) { alert('Let op: De taak is opgeslagen in Fociz, maar kon niet direct naar Microsoft Outlook worden verzonden:\n\n' + (data.outlook_error || 'Controleer uw verbinding in Mijn Profiel.')); } - location.reload(); + toggleAddTaskForm(); + const titleInput = document.getElementById('newTaskTitle'); + if (titleInput) titleInput.value = ''; + switchPlannerDate(activePlannerDate, null, false); } else { alert(data.error || 'Fout bij opslaan van de taak'); } @@ -1528,7 +1659,8 @@ async function submitEditTask() { }); const data = await res.json(); if (data.success) { - location.reload(); + closeEditTaskModal(); + switchPlannerDate(activePlannerDate, null, false); } else { alert(data.error || 'Kon taak niet bijwerken'); if (saveBtn) { @@ -1599,7 +1731,7 @@ async function quickImportJira(btn, title, jiraKey, url) { const data = await res.json(); if (data.success) { btn.innerHTML = ''; - location.reload(); + switchPlannerDate(activePlannerDate, null, false); } else { btn.innerHTML = originalHtml; btn.disabled = false; @@ -1618,7 +1750,7 @@ async function toggleTask(taskId, isCompleted) { headers: {'Content-Type': 'application/json'}, body: JSON.stringify({ task_id: taskId, is_completed: isCompleted }) }); - location.reload(); + switchPlannerDate(activePlannerDate, null, false); } async function deleteTask(taskId) { @@ -1628,7 +1760,7 @@ async function deleteTask(taskId) { headers: {'Content-Type': 'application/json'}, body: JSON.stringify({ task_id: taskId }) }); - document.getElementById(`task-row-${taskId}`)?.remove(); + switchPlannerDate(activePlannerDate, null, false); } // --- AI Co-Pilot Slimme Dagplanning (Release v1.5) --- @@ -1652,7 +1784,7 @@ async function aiSmartSchedule(btn) { if (data.success) { alert(`Succes! AI Co-Pilot heeft ${data.scheduled_count} ta(a)k(en) optimaal ingepland op je tijdlijn.`); - location.reload(); + switchPlannerDate(activePlannerDate, null, false); } else { alert(data.error || 'Kon dagplanning niet genereren via AI'); if (btn) { @@ -1672,10 +1804,7 @@ async function aiSmartSchedule(btn) { } // --- Outlook 365 Auto-Sync & Achtergrond Controle --- const isOutlookConnected = ; -let currentSyncHash = ''; const syncIntervalSeconds = ; -const weekStartDate = ''; -const weekEndDate = ''; let isSyncCheckActive = false; async function checkOutlookChanges(isManual = false) { @@ -1708,10 +1837,8 @@ async function checkOutlookChanges(isManual = false) { if (data.has_changes) { if (statusText) statusText.innerText = 'Wijzigingen bijwerken...'; - saveTimerStateBeforeReload(); - setTimeout(() => { - location.reload(); - }, 400); + await switchPlannerDate(activePlannerDate, null, false); + if (statusText) statusText.innerText = 'Outlook 365 Live'; return; } else { currentSyncHash = data.hash;