From 797aaa76c717e1064c6eeb2763574827960f321d Mon Sep 17 00:00:00 2001 From: Niek Rabelink Date: Sat, 12 Sep 2026 13:45:01 +0200 Subject: [PATCH] Brainstorm fix --- admin.php | 147 ++++++ api/planner.php | 65 +++ brainstorm.php | 33 +- brainstorm_canvas.php | 8 + includes/integrations/microsoft.php | 6 + includes/settings.php | 21 + planner.php | 734 +++++++++++++++++++++------- 7 files changed, 834 insertions(+), 180 deletions(-) diff --git a/admin.php b/admin.php index 8b91040..37c1cbc 100644 --- a/admin.php +++ b/admin.php @@ -296,6 +296,71 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') { $activeTab = 'calendar'; } + // 3c. Brainstorm Categorieën opslaan + elseif ($action === 'save_brainstorm_categories') { + $rawCategories = $_POST['categories'] ?? []; + $sanitizedCategories = []; + if (is_array($rawCategories)) { + foreach ($rawCategories as $c) { + $name = trim((string)($c['name'] ?? '')); + $desc = trim((string)($c['description'] ?? '')); + $isActive = isset($c['is_active']) && ($c['is_active'] == '1' || $c['is_active'] === 'on') ? 1 : 0; + $isDelete = isset($c['is_delete']) && ($c['is_delete'] == '1' || $c['is_delete'] === 'on'); + + if ($isDelete || empty($name)) { + continue; + } + + $sanitizedCategories[] = [ + 'name' => $name, + 'description' => $desc, + 'is_active' => $isActive, + ]; + } + } + + // Nieuwe categorie toevoegen indien ingevuld + $newName = trim((string)($_POST['new_category_name'] ?? '')); + $newDesc = trim((string)($_POST['new_category_desc'] ?? '')); + if (!empty($newName)) { + $exists = false; + foreach ($sanitizedCategories as &$sc) { + if (strcasecmp($sc['name'], $newName) === 0) { + $sc['name'] = $newName; + if (!empty($newDesc)) $sc['description'] = $newDesc; + $sc['is_active'] = 1; + $exists = true; + break; + } + } + unset($sc); + if (!$exists) { + $sanitizedCategories[] = [ + 'name' => $newName, + 'description' => $newDesc, + 'is_active' => 1, + ]; + } + } + + if (empty($sanitizedCategories)) { + $sanitizedCategories = [ + ['name' => 'Product & Strategie', 'description' => 'Strategische richting, nieuwe producten en visie', 'is_active' => 1], + ['name' => 'Procesverbetering', 'description' => 'Optimalisatie van werkprocessen en efficiëntie', 'is_active' => 1], + ['name' => 'Klant & Service', 'description' => 'Klanttevredenheid, support en klantervaring', 'is_active' => 1], + ['name' => 'Technologie & IT', 'description' => 'Architectuur, tooling, infrastructuur en tech stack','is_active' => 1], + ['name' => 'Marketing & Groei', 'description' => 'Leadgeneratie, branding en marktpositie', 'is_active' => 1], + ]; + } + + set_system_setting('brainstorm_categories', $sanitizedCategories); + fociz_log_audit($userId, 'SETTINGS_UPDATED', 'brainstorm_categories', null, ['count' => count($sanitizedCategories)], 'SUCCESS', $adminUser['email']); + + $message = "Brainstorm categorieën zijn succesvol opgeslagen."; + $messageType = 'success'; + $activeTab = 'system'; + } + // 4. AI Co-Pilot instellingen opslaan (Release v1.5) elseif ($action === 'save_ai_settings') { $activeTab = 'ai'; @@ -375,6 +440,7 @@ $workdaySettings = get_workday_settings(); $aiSettings = get_ai_settings(); $sessionIdleTimeout = get_session_idle_timeout(); $calendarBlockReasons = get_calendar_block_reasons(); +$brainstormCategories = get_brainstorm_categories(); // Redirect URI $redirectUri = rtrim(APP_URL, '/') . '/auth/callback_microsoft.php'; @@ -1925,6 +1991,87 @@ require_once __DIR__ . '/includes/header.php'; + + +
+
+
+
+ +
+
+
+

Brainstorm Categorieën

+ + Beheerbaar + +
+

Beheer de beschikbare categorieën voor nieuwe brainstormsessies

+
+
+
+ +
+ + + + +
+
+
Categorienaam
+
Omschrijving / Toelichting
+
Actief
+
Verwijder
+
+ + $bCat): ?> +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+ + +
+
+ + Nieuwe Categorie Toevoegen +
+
+
+ + +
+
+ + +
+
+
+ +
+ +
+
+
diff --git a/api/planner.php b/api/planner.php index c867601..0003fd7 100644 --- a/api/planner.php +++ b/api/planner.php @@ -1047,6 +1047,71 @@ try { exit; } + // 15. Outlook Agenda-afspraak verwijderen + if ($action === 'delete_outlook_event') { + $rawInput = json_decode(file_get_contents('php://input'), true); + $data = is_array($rawInput) ? $rawInput : $_POST; + $eventId = trim($data['event_id'] ?? ''); + + if (empty($eventId)) { + echo json_encode(['success' => false, 'error' => 'Geen event ID opgegeven']); + exit; + } + + if (!function_exists('microsoft_delete_event')) { + echo json_encode(['success' => false, 'error' => 'Microsoft integratie niet beschikbaar']); + exit; + } + + $ok = microsoft_delete_event((int)$user['id'], $eventId); + if ($ok) { + echo json_encode(['success' => true, 'message' => 'Afspraak succesvol verwijderd uit Outlook']); + } else { + echo json_encode(['success' => false, 'error' => 'Kon afspraak niet verwijderen uit Outlook (mogelijk al verwijderd of geen bevoegdheid)']); + } + exit; + } + + // 16. Outlook Agenda-afspraak bijwerken + if ($action === 'update_outlook_event') { + $rawInput = json_decode(file_get_contents('php://input'), true); + $data = is_array($rawInput) ? $rawInput : $_POST; + $eventId = trim($data['event_id'] ?? ''); + $subject = trim($data['title'] ?? ($data['subject'] ?? '')); + $date = trim($data['date'] ?? date('Y-m-d')); + $startTime = trim($data['time_block_start'] ?? ($data['start_time'] ?? '09:00')); + $endTime = trim($data['time_block_end'] ?? ($data['end_time'] ?? '09:30')); + $description = trim($data['description'] ?? ''); + + if (empty($eventId) || empty($subject)) { + echo json_encode(['success' => false, 'error' => 'Titel of event ID ontbreekt']); + exit; + } + + if (!function_exists('microsoft_update_event')) { + echo json_encode(['success' => false, 'error' => 'Microsoft integratie niet beschikbaar']); + exit; + } + + $updateData = [ + 'subject' => $subject, + 'date' => $date, + 'start_time' => $startTime, + 'end_time' => $endTime, + ]; + if ($description !== '') { + $updateData['description'] = $description; + } + + $ok = microsoft_update_event((int)$user['id'], $eventId, $updateData); + if ($ok) { + echo json_encode(['success' => true, 'date' => $date, 'message' => 'Afspraak succesvol bijgewerkt in Outlook']); + } else { + echo json_encode(['success' => false, 'error' => 'Kon afspraak niet bijwerken in Microsoft Outlook']); + } + exit; + } + echo json_encode(['error' => 'Onbekende actie']); } catch (Exception $e) { http_response_code(500); diff --git a/brainstorm.php b/brainstorm.php index d5f5c57..1a3d694 100644 --- a/brainstorm.php +++ b/brainstorm.php @@ -4,16 +4,23 @@ */ require_once __DIR__ . '/includes/auth.php'; +require_once __DIR__ . '/includes/settings.php'; $currentUser = require_auth(); $db = get_db_connection(); +$brainstormCategories = get_brainstorm_categories(); // Verwerk nieuwe sessie aanmaken $error = null; if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action']) && $_POST['action'] === 'create_session') { $title = trim($_POST['title'] ?? ''); - $theme = trim($_POST['theme'] ?? 'Product & Strategie'); + $theme = trim($_POST['category'] ?? ($_POST['theme'] ?? '')); + if (empty($theme) && !empty($brainstormCategories)) { + $firstActive = array_values(array_filter($brainstormCategories, fn($c) => !empty($c['is_active'])))[0] ?? null; + $theme = $firstActive ? $firstActive['name'] : 'Product & Strategie'; + } $description = trim($_POST['description'] ?? ''); + if (!empty($title)) { $stmt = $db->prepare(" INSERT INTO brainstorm_sessions (title, theme, description, created_by_id) @@ -148,13 +155,23 @@ require_once __DIR__ . '/includes/header.php';
- - + + + + + + + + + +
diff --git a/brainstorm_canvas.php b/brainstorm_canvas.php index a174ae3..d3a8eab 100644 --- a/brainstorm_canvas.php +++ b/brainstorm_canvas.php @@ -722,6 +722,14 @@ async function aiSuggestRoadmapMeta(btn) { btn.innerHTML = origHtml; btn.disabled = false; if (typeof lucide !== 'undefined') lucide.createIcons(); + } + } + } catch(err) { + alert('Netwerkfout bij aanroepen van AI'); + if (btn) { + btn.innerHTML = origHtml; + btn.disabled = false; + if (typeof lucide !== 'undefined') lucide.createIcons(); } } } diff --git a/includes/integrations/microsoft.php b/includes/integrations/microsoft.php index d0a737a..679789d 100644 --- a/includes/integrations/microsoft.php +++ b/includes/integrations/microsoft.php @@ -618,6 +618,12 @@ function microsoft_update_event(int $userId, string $eventId, array $eventData): if (!empty($eventData['subject'])) { $payload['subject'] = $eventData['subject']; } + if (isset($eventData['description']) && $eventData['description'] !== '') { + $payload['body'] = [ + 'contentType' => 'HTML', + 'content' => nl2br(htmlspecialchars($eventData['description'])), + ]; + } $date = !empty($eventData['date']) ? $eventData['date'] : date('Y-m-d'); $startTime = !empty($eventData['start_time']) ? $eventData['start_time'] : '09:00'; diff --git a/includes/settings.php b/includes/settings.php index 34679e9..2b325b0 100644 --- a/includes/settings.php +++ b/includes/settings.php @@ -239,3 +239,24 @@ function get_calendar_block_reasons(): array { return $defaultReasons; } +/** + * Haalt de beheerbare brainstorm categorieën op. + * Standaard voorzien van een duidelijke categorienaam, beschrijving en actief-status. + */ +function get_brainstorm_categories(): array { + $defaultCategories = [ + ['name' => 'Product & Strategie', 'description' => 'Strategische richting, nieuwe producten en visie', 'is_active' => 1], + ['name' => 'Procesverbetering', 'description' => 'Optimalisatie van werkprocessen en efficiëntie', 'is_active' => 1], + ['name' => 'Klant & Service', 'description' => 'Klanttevredenheid, support en klantervaring', 'is_active' => 1], + ['name' => 'Technologie & IT', 'description' => 'Architectuur, tooling, infrastructuur en tech stack','is_active' => 1], + ['name' => 'Marketing & Groei', 'description' => 'Leadgeneratie, branding en marktpositie', 'is_active' => 1], + ]; + + $saved = get_system_setting('brainstorm_categories', null); + if (is_array($saved) && !empty($saved)) { + return $saved; + } + return $defaultCategories; +} + + diff --git a/planner.php b/planner.php index 67940d2..3e1a97f 100644 --- a/planner.php +++ b/planner.php @@ -186,6 +186,21 @@ try { error_log("Fout bij ophalen taken: " . $e->getMessage()); } +// 3b. Haal alle reeds ingeplande externe items op (Jira keys & Outlook Mail IDs) +$plannedExternalIds = []; +try { + $stmtPlannedExt = $db->prepare(" + SELECT external_id FROM tasks + WHERE user_id = ? AND external_id IS NOT NULL AND external_id != '' + "); + $stmtPlannedExt->execute([$userId]); + foreach ($stmtPlannedExt->fetchAll() as $row) { + $plannedExternalIds[$row['external_id']] = true; + } +} catch (Throwable $e) { + error_log("Fout bij ophalen planned external IDs: " . $e->getMessage()); +} + // 4. Haal weekstatistieken voor taken op (aantal per dag) $weekTaskStats = []; try { @@ -1025,14 +1040,22 @@ for ($h = $startHour; $h <= $endHour; $h++) { 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-2.5 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" > - -
+ +
+ +
+
@@ -1239,10 +1262,25 @@ for ($h = $startHour; $h <= $endHour; $h++) { - -
+ $evt['id'] ?? '', + 'title' => $evt['subject'] ?? 'Afspraak', + 'subject' => $evt['subject'] ?? 'Afspraak', + 'start' => $evt['start'] ?? '', + 'end' => $evt['end'] ?? '', + 'date' => $evt['date'] ?? $selectedDate, + 'location' => $evt['location'] ?? '', + 'meeting_url' => $evt['meeting_url'] ?? '', + 'web_link' => $evt['web_link'] ?? '', + 'is_all_day' => !empty($evt['is_all_day']), + 'source' => 'OUTLOOK_LIVE', + 'is_outlook_event' => true, + ]; + ?> +
-
- -
-
-
+
-
+
30m @@ -2508,7 +2576,7 @@ if ($isAjaxRequest) { -
+
@@ -2581,7 +2649,7 @@ if ($isAjaxRequest) {
-
+
@@ -2627,7 +2695,7 @@ if ($isAjaxRequest) {
@@ -2636,7 +2704,7 @@ if ($isAjaxRequest) {
@@ -3127,7 +3195,7 @@ function closeDayContextMenu() { activeContextMenuDay = null; } -// --- Rechtermuisklik Context Menu voor Taken (Release v2.2) --- +// --- Rechtermuisklik Context Menu voor Taken & Outlook Events (Release v2.2) --- let activeContextMenuTask = null; function handleTaskContextMenu(e, taskData) { @@ -3141,52 +3209,86 @@ function handleTaskContextMenu(e, taskData) { const titleEl = document.getElementById('taskContextMenuTitle'); const priorityBadge = document.getElementById('taskContextMenuPriorityBadge'); const metaEl = document.getElementById('taskContextMenuMeta'); + const btnOpen = document.getElementById('taskCtxBtnOpen'); + const btnEdit = document.getElementById('taskCtxBtnEdit'); + const btnEditLabel = document.getElementById('taskCtxBtnEditLabel'); + const btnToggle = document.getElementById('taskCtxBtnToggle'); const toggleLabel = document.getElementById('taskCtxBtnToggleLabel'); + const btnSubtasks = document.getElementById('taskCtxBtnSubtasks'); const btnOutlook = document.getElementById('taskCtxBtnOutlook'); + const btnDelete = document.getElementById('taskCtxBtnDelete'); + const btnDeleteLabel = document.getElementById('taskCtxBtnDeleteLabel'); + const divider = document.getElementById('taskCtxDivider'); if (!menu) return; - if (titleEl) titleEl.textContent = taskData.title || 'Taak'; + const isOutlookEvent = Boolean(taskData && (taskData.is_outlook_event || taskData.source === 'OUTLOOK_LIVE')); + + if (titleEl) titleEl.textContent = (isOutlookEvent ? (taskData.subject || taskData.title) : taskData.title) || 'Item'; if (priorityBadge) { - const p = taskData.priority || 'P2'; - priorityBadge.textContent = p; - if (p === 'P1') { - priorityBadge.className = 'text-[9px] font-extrabold px-1.5 py-0.5 rounded-md bg-rose-100 text-rose-700 dark:bg-rose-950 dark:text-rose-400 border border-rose-300 dark:border-rose-800'; - } else if (p === 'P2') { - priorityBadge.className = 'text-[9px] font-extrabold px-1.5 py-0.5 rounded-md bg-amber-100 text-amber-700 dark:bg-amber-950 dark:text-amber-400 border border-amber-300 dark:border-amber-800'; + if (isOutlookEvent) { + priorityBadge.textContent = 'AFSPRAAK'; + priorityBadge.className = 'text-[9px] font-extrabold px-1.5 py-0.5 rounded-md bg-sky-100 text-sky-700 dark:bg-sky-950 dark:text-sky-400 border border-sky-300 dark:border-sky-800'; } else { - priorityBadge.className = 'text-[9px] font-extrabold px-1.5 py-0.5 rounded-md bg-slate-100 text-slate-700 dark:bg-slate-800 dark:text-slate-300 border border-slate-300 dark:border-slate-700'; + const p = taskData.priority || 'P2'; + priorityBadge.textContent = p; + if (p === 'P1') { + priorityBadge.className = 'text-[9px] font-extrabold px-1.5 py-0.5 rounded-md bg-rose-100 text-rose-700 dark:bg-rose-950 dark:text-rose-400 border border-rose-300 dark:border-rose-800'; + } else if (p === 'P2') { + priorityBadge.className = 'text-[9px] font-extrabold px-1.5 py-0.5 rounded-md bg-amber-100 text-amber-700 dark:bg-amber-950 dark:text-amber-400 border border-amber-300 dark:border-amber-800'; + } else { + priorityBadge.className = 'text-[9px] font-extrabold px-1.5 py-0.5 rounded-md bg-slate-100 text-slate-700 dark:bg-slate-800 dark:text-slate-300 border border-slate-300 dark:border-slate-700'; + } } } if (metaEl) { let meta = ''; if (taskData.start) { - meta += `${taskData.start}${taskData.end ? ' - ' + taskData.end : ''} (${taskData.duration || 30}m)`; + meta += `${taskData.start}${taskData.end ? ' - ' + taskData.end : ''}`; + if (taskData.duration) meta += ` (${taskData.duration}m)`; } else if (taskData.duration) { meta += `${taskData.duration}m`; } - if (taskData.source && taskData.source !== 'FOCIZ') { + if (isOutlookEvent) { + meta += (meta ? ' · ' : '') + 'Outlook'; + if (taskData.location && taskData.location !== 'Geen locatie') { + meta += ` · ${taskData.location}`; + } + } else if (taskData.source && taskData.source !== 'FOCIZ') { meta += (meta ? ' · ' : '') + taskData.source; } metaEl.textContent = meta || (taskData.date || 'Vandaag'); } - if (toggleLabel) { - toggleLabel.textContent = taskData.is_completed ? 'Markeren als openstaand' : 'Markeren als voltooid'; - } - - if (btnOutlook) { - if (window.isOutlookConnected && !taskData.external_id && !taskData.is_completed) { - btnOutlook.classList.remove('hidden'); - } else { - btnOutlook.classList.add('hidden'); + // Pas labels en zichtbaarheid aan op basis van type + if (isOutlookEvent) { + if (btnEditLabel) btnEditLabel.textContent = 'Afspraak bewerken'; + if (btnDeleteLabel) btnDeleteLabel.textContent = 'Afspraak verwijderen'; + if (btnToggle) btnToggle.classList.add('hidden'); + if (btnSubtasks) btnSubtasks.classList.add('hidden'); + if (btnOutlook) btnOutlook.classList.add('hidden'); + } else { + if (btnEditLabel) btnEditLabel.textContent = 'Taak bewerken'; + if (btnDeleteLabel) btnDeleteLabel.textContent = 'Taak verwijderen'; + if (btnToggle) { + btnToggle.classList.remove('hidden'); + if (toggleLabel) { + toggleLabel.textContent = taskData.is_completed ? 'Markeren als openstaand' : 'Markeren als voltooid'; + } + } + if (btnSubtasks) btnSubtasks.classList.remove('hidden'); + if (btnOutlook) { + if (window.isOutlookConnected && !taskData.external_id && !taskData.is_completed) { + btnOutlook.classList.remove('hidden'); + } else { + btnOutlook.classList.add('hidden'); + } } } // Koppel actieknoppen - const btnOpen = document.getElementById('taskCtxBtnOpen'); if (btnOpen) { btnOpen.onclick = () => { closeTaskContextMenu(); @@ -3194,7 +3296,6 @@ function handleTaskContextMenu(e, taskData) { }; } - const btnEdit = document.getElementById('taskCtxBtnEdit'); if (btnEdit) { btnEdit.onclick = () => { closeTaskContextMenu(); @@ -3202,7 +3303,6 @@ function handleTaskContextMenu(e, taskData) { }; } - const btnToggle = document.getElementById('taskCtxBtnToggle'); if (btnToggle) { btnToggle.onclick = () => { closeTaskContextMenu(); @@ -3210,7 +3310,6 @@ function handleTaskContextMenu(e, taskData) { }; } - const btnSubtasks = document.getElementById('taskCtxBtnSubtasks'); if (btnSubtasks) { btnSubtasks.onclick = () => { closeTaskContextMenu(); @@ -3231,11 +3330,14 @@ function handleTaskContextMenu(e, taskData) { }; } - const btnDelete = document.getElementById('taskCtxBtnDelete'); if (btnDelete) { btnDelete.onclick = () => { closeTaskContextMenu(); - deleteTask(taskData.id); + if (isOutlookEvent) { + deleteOutlookEvent(taskData.id, taskData.subject || taskData.title); + } else { + deleteTask(taskData.id); + } }; } @@ -3291,30 +3393,34 @@ async function openTaskDetailModal(taskDataOrId) { modal.classList.remove('hidden'); modal.classList.add('flex'); - // Haal live alle volledige details op inclusief beschrijving en subtaken - try { - const resp = await fetch(`/api/planner.php?action=get_task_details&task_id=${taskId}`); - const data = await resp.json(); - if (data.success && data.task) { - currentDetailTaskData = { - id: data.task.id, - title: data.task.title, - description: data.task.description || '', - priority: data.task.priority || 'P2', - start: data.task.time_block_start || '', - end: data.task.time_block_end || '', - duration: parseInt(data.task.duration_minutes, 10) || 30, - is_completed: parseInt(data.task.is_completed, 10) === 1, - source: data.task.source || 'FOCIZ', - external_id: data.task.external_id || '', - external_url: data.task.external_url || '', - date: data.task.plan_date || (task ? task.date : '') - }; - _subtaskCache[taskId] = data.subtasks || []; - populateTaskDetailModal(currentDetailTaskData, data.subtasks || []); + const isOutlookEvent = Boolean(task && (task.is_outlook_event || task.source === 'OUTLOOK_LIVE')); + + // Als het een Outlook event is, hoeven we geen Fociz task ID API aan te roepen + if (!isOutlookEvent && taskId) { + try { + const resp = await fetch(`/api/planner.php?action=get_task_details&task_id=${taskId}`); + const data = await resp.json(); + if (data.success && data.task) { + currentDetailTaskData = { + id: data.task.id, + title: data.task.title, + description: data.task.description || '', + priority: data.task.priority || 'P2', + start: data.task.time_block_start || '', + end: data.task.time_block_end || '', + duration: parseInt(data.task.duration_minutes, 10) || 30, + is_completed: parseInt(data.task.is_completed, 10) === 1, + source: data.task.source || 'FOCIZ', + external_id: data.task.external_id || '', + external_url: data.task.external_url || '', + date: data.task.plan_date || (task ? task.date : '') + }; + _subtaskCache[taskId] = data.subtasks || []; + populateTaskDetailModal(currentDetailTaskData, data.subtasks || []); + } + } catch (err) { + console.error('Fout bij ophalen taakdetails:', err); } - } catch (err) { - console.error('Fout bij ophalen taakdetails:', err); } if (window.lucide && lucide.createIcons) { @@ -3332,56 +3438,85 @@ function populateTaskDetailModal(task, subtasks) { const timeSlotEl = document.getElementById('taskDetailTimeSlot'); const durationEl = document.getElementById('taskDetailDuration'); const descEl = document.getElementById('taskDetailDescription'); + const subtasksSection = document.getElementById('taskDetailSubtasksSection'); const subtasksList = document.getElementById('taskDetailSubtasksList'); const extBlock = document.getElementById('taskDetailExternalBlock'); const extLabel = document.getElementById('taskDetailExternalLabel'); const extLink = document.getElementById('taskDetailExternalLink'); const btnEdit = document.getElementById('taskDetailBtnEdit'); + const btnEditLabel = document.getElementById('taskDetailBtnEditLabel'); const btnDelete = document.getElementById('taskDetailBtnDelete'); + const btnDeleteLabel = document.getElementById('taskDetailBtnDeleteLabel'); - if (titleEl) titleEl.textContent = task.title || 'Geen titel'; + const isOutlookEvent = Boolean(task && (task.is_outlook_event || task.source === 'OUTLOOK_LIVE')); - // Prioriteit - const p = task.priority || 'P2'; - if (priorityBadge) { - priorityBadge.textContent = p === 'P1' ? 'P1 · Hoge Prioriteit' : (p === 'P2' ? 'P2 · Belangrijk' : 'P3 · Regulier'); - if (p === 'P1') { - priorityBadge.className = 'px-2 py-0.5 rounded-full text-[10px] font-extrabold uppercase tracking-wide bg-rose-50 dark:bg-rose-500/15 text-rose-700 dark:text-rose-400 border border-rose-200 dark:border-rose-500/30'; - if (priorityIcon) priorityIcon.className = 'w-10 h-10 rounded-2xl bg-rose-50 dark:bg-rose-500/10 border border-rose-200 dark:border-rose-500/20 text-rose-600 dark:text-rose-400 flex items-center justify-center flex-shrink-0 mt-0.5'; - } else if (p === 'P2') { - priorityBadge.className = 'px-2 py-0.5 rounded-full text-[10px] font-extrabold uppercase tracking-wide bg-amber-50 dark:bg-amber-500/15 text-amber-700 dark:text-amber-400 border border-amber-200 dark:border-amber-500/30'; - if (priorityIcon) priorityIcon.className = 'w-10 h-10 rounded-2xl 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 flex-shrink-0 mt-0.5'; - } else { - priorityBadge.className = 'px-2 py-0.5 rounded-full text-[10px] font-extrabold uppercase tracking-wide bg-slate-100 dark:bg-slate-800 text-slate-700 dark:text-slate-300 border border-slate-200 dark:border-slate-700'; - if (priorityIcon) priorityIcon.className = 'w-10 h-10 rounded-2xl bg-slate-100 dark:bg-slate-800 border border-slate-200 dark:border-slate-700 text-slate-600 dark:text-slate-400 flex items-center justify-center flex-shrink-0 mt-0.5'; + if (titleEl) titleEl.textContent = (isOutlookEvent ? (task.subject || task.title) : task.title) || 'Geen titel'; + + // Prioriteit & icoon + if (isOutlookEvent) { + if (priorityBadge) { + priorityBadge.textContent = 'Outlook Afspraak'; + priorityBadge.className = 'px-2 py-0.5 rounded-full text-[10px] font-extrabold uppercase tracking-wide bg-sky-50 dark:bg-sky-500/15 text-sky-700 dark:text-sky-400 border border-sky-200 dark:border-sky-500/30'; + } + if (priorityIcon) { + priorityIcon.className = 'w-10 h-10 rounded-2xl bg-sky-50 dark:bg-sky-500/10 border border-sky-200 dark:border-sky-500/20 text-sky-600 dark:text-sky-400 flex items-center justify-center flex-shrink-0 mt-0.5'; + priorityIcon.innerHTML = ''; + } + } else { + const p = task.priority || 'P2'; + if (priorityBadge) { + priorityBadge.textContent = p === 'P1' ? 'P1 · Hoge Prioriteit' : (p === 'P2' ? 'P2 · Belangrijk' : 'P3 · Regulier'); + if (p === 'P1') { + priorityBadge.className = 'px-2 py-0.5 rounded-full text-[10px] font-extrabold uppercase tracking-wide bg-rose-50 dark:bg-rose-500/15 text-rose-700 dark:text-rose-400 border border-rose-200 dark:border-rose-500/30'; + if (priorityIcon) priorityIcon.className = 'w-10 h-10 rounded-2xl bg-rose-50 dark:bg-rose-500/10 border border-rose-200 dark:border-rose-500/20 text-rose-600 dark:text-rose-400 flex items-center justify-center flex-shrink-0 mt-0.5'; + } else if (p === 'P2') { + priorityBadge.className = 'px-2 py-0.5 rounded-full text-[10px] font-extrabold uppercase tracking-wide bg-amber-50 dark:bg-amber-500/15 text-amber-700 dark:text-amber-400 border border-amber-200 dark:border-amber-500/30'; + if (priorityIcon) priorityIcon.className = 'w-10 h-10 rounded-2xl 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 flex-shrink-0 mt-0.5'; + } else { + priorityBadge.className = 'px-2 py-0.5 rounded-full text-[10px] font-extrabold uppercase tracking-wide bg-slate-100 dark:bg-slate-800 text-slate-700 dark:text-slate-300 border border-slate-200 dark:border-slate-700'; + if (priorityIcon) priorityIcon.className = 'w-10 h-10 rounded-2xl bg-slate-100 dark:bg-slate-800 border border-slate-200 dark:border-slate-700 text-slate-600 dark:text-slate-400 flex items-center justify-center flex-shrink-0 mt-0.5'; + } + } + if (priorityIcon) { + priorityIcon.innerHTML = ''; } } // Source if (sourceBadge) { - const src = (task.source || 'FOCIZ').toUpperCase(); - sourceBadge.textContent = src; - if (src === 'JIRA') { - sourceBadge.className = 'px-2 py-0.5 rounded-full text-[10px] font-mono font-bold bg-indigo-50 dark:bg-indigo-500/15 text-indigo-700 dark:text-indigo-400 border border-indigo-200 dark:border-indigo-500/30'; - } else if (src === 'OUTLOOK' || src === 'MICROSOFT') { + if (isOutlookEvent) { + sourceBadge.textContent = 'OUTLOOK 365'; sourceBadge.className = 'px-2 py-0.5 rounded-full text-[10px] font-mono font-bold bg-sky-50 dark:bg-sky-500/15 text-sky-700 dark:text-sky-400 border border-sky-200 dark:border-sky-500/30'; - } else if (src === 'OUTLOOK_MAIL' || src === 'MAIL') { - sourceBadge.className = 'px-2 py-0.5 rounded-full text-[10px] font-mono font-bold bg-violet-50 dark:bg-violet-500/15 text-violet-700 dark:text-violet-400 border border-violet-200 dark:border-violet-500/30'; - } else if (src === 'BRAINSTORM') { - sourceBadge.className = 'px-2 py-0.5 rounded-full text-[10px] font-mono font-bold bg-amber-50 dark:bg-amber-500/15 text-amber-700 dark:text-amber-400 border border-amber-200 dark:border-amber-500/30'; } else { - sourceBadge.className = 'px-2 py-0.5 rounded-full text-[10px] font-mono font-bold bg-slate-100 dark:bg-slate-800 text-slate-600 dark:text-slate-400 border border-slate-200 dark:border-slate-700'; + const src = (task.source || 'FOCIZ').toUpperCase(); + sourceBadge.textContent = src; + if (src === 'JIRA') { + sourceBadge.className = 'px-2 py-0.5 rounded-full text-[10px] font-mono font-bold bg-indigo-50 dark:bg-indigo-500/15 text-indigo-700 dark:text-indigo-400 border border-indigo-200 dark:border-indigo-500/30'; + } else if (src === 'OUTLOOK' || src === 'MICROSOFT') { + sourceBadge.className = 'px-2 py-0.5 rounded-full text-[10px] font-mono font-bold bg-sky-50 dark:bg-sky-500/15 text-sky-700 dark:text-sky-400 border border-sky-200 dark:border-sky-500/30'; + } else if (src === 'OUTLOOK_MAIL' || src === 'MAIL') { + sourceBadge.className = 'px-2 py-0.5 rounded-full text-[10px] font-mono font-bold bg-violet-50 dark:bg-violet-500/15 text-violet-700 dark:text-violet-400 border border-violet-200 dark:border-violet-500/30'; + } else if (src === 'BRAINSTORM') { + sourceBadge.className = 'px-2 py-0.5 rounded-full text-[10px] font-mono font-bold bg-amber-50 dark:bg-amber-500/15 text-amber-700 dark:text-amber-400 border border-amber-200 dark:border-amber-500/30'; + } else { + sourceBadge.className = 'px-2 py-0.5 rounded-full text-[10px] font-mono font-bold bg-slate-100 dark:bg-slate-800 text-slate-600 dark:text-slate-400 border border-slate-200 dark:border-slate-700'; + } } } // Status if (statusBadge) { - if (task.is_completed) { - statusBadge.textContent = 'Voltooid'; - statusBadge.className = 'px-2 py-0.5 rounded-full text-[10px] font-bold bg-emerald-50 dark:bg-emerald-500/15 text-emerald-700 dark:text-emerald-400 border border-emerald-200 dark:border-emerald-500/30'; + if (isOutlookEvent) { + statusBadge.textContent = 'Agenda Afspraak'; + statusBadge.className = 'px-2 py-0.5 rounded-full text-[10px] font-bold bg-indigo-50 dark:bg-indigo-500/15 text-indigo-700 dark:text-indigo-400 border border-indigo-200 dark:border-indigo-500/30'; } else { - statusBadge.textContent = 'Openstaand'; - statusBadge.className = 'px-2 py-0.5 rounded-full text-[10px] font-bold bg-sky-50 dark:bg-sky-500/15 text-sky-700 dark:text-sky-400 border border-sky-200 dark:border-sky-500/30'; + if (task.is_completed) { + statusBadge.textContent = 'Voltooid'; + statusBadge.className = 'px-2 py-0.5 rounded-full text-[10px] font-bold bg-emerald-50 dark:bg-emerald-500/15 text-emerald-700 dark:text-emerald-400 border border-emerald-200 dark:border-emerald-500/30'; + } else { + statusBadge.textContent = 'Openstaand'; + statusBadge.className = 'px-2 py-0.5 rounded-full text-[10px] font-bold bg-sky-50 dark:bg-sky-500/15 text-sky-700 dark:text-sky-400 border border-sky-200 dark:border-sky-500/30'; + } } } @@ -3393,38 +3528,81 @@ function populateTaskDetailModal(task, subtasks) { timeSlotEl.textContent = (task.start) ? `${task.start} - ${task.end || '?'}` : 'Geen tijdblok'; } if (durationEl) { - durationEl.textContent = `${task.duration || 30} minuten`; + let dur = task.duration; + if (!dur && task.start && task.end) { + const sm = parseTimeToMinutes(task.start); + const em = parseTimeToMinutes(task.end); + if (sm !== null && em !== null && em > sm) dur = em - sm; + } + durationEl.textContent = `${dur || 30} minuten`; } - // Beschrijving + // Beschrijving / Locatie if (descEl) { - if (task.description && task.description.trim()) { - descEl.textContent = task.description; - descEl.classList.remove('italic', 'text-slate-400'); + if (isOutlookEvent) { + let descParts = []; + if (task.location && task.location !== 'Geen locatie') { + descParts.push(`Locatie: ${task.location}`); + } + if (task.description && task.description.trim()) { + descParts.push(task.description.trim()); + } + if (descParts.length > 0) { + descEl.textContent = descParts.join('\n\n'); + descEl.classList.remove('italic', 'text-slate-400'); + } else { + descEl.textContent = 'Geen extra notities of locatie voor deze Outlook-afspraak.'; + descEl.classList.add('italic', 'text-slate-400'); + } } else { - descEl.textContent = 'Geen beschrijving of notities opgegeven voor deze taak.'; - descEl.classList.add('italic', 'text-slate-400'); + if (task.description && task.description.trim()) { + descEl.textContent = task.description; + descEl.classList.remove('italic', 'text-slate-400'); + } else { + descEl.textContent = 'Geen beschrijving of notities opgegeven voor deze taak.'; + descEl.classList.add('italic', 'text-slate-400'); + } } } - // Subtaken - if (subtasksList) { - renderSubtasksInDetailModal(task.id, subtasks || []); + // Subtaken: verbergen voor Outlook afspraken + if (subtasksSection) { + if (isOutlookEvent) { + subtasksSection.classList.add('hidden'); + } else { + subtasksSection.classList.remove('hidden'); + if (subtasksList) { + renderSubtasksInDetailModal(task.id, subtasks || []); + } + } } // Externe link if (extBlock && extLink && extLabel) { - if (task.external_url) { + const extUrl = isOutlookEvent ? (task.meeting_url || task.web_link || task.external_url) : task.external_url; + if (extUrl) { extBlock.classList.remove('hidden'); - extLink.href = task.external_url; - if (task.source === 'JIRA') { + extLink.href = extUrl; + if (isOutlookEvent) { + if (task.meeting_url && task.meeting_url.includes('teams.microsoft.com')) { + extLabel.textContent = 'Deelnemen via Microsoft Teams'; + extLink.querySelector('span') && (extLink.querySelector('span').textContent = 'Deelnemen'); + } else { + extLabel.textContent = 'Gekoppeld in Microsoft Outlook Agenda'; + extLink.querySelector('span') && (extLink.querySelector('span').textContent = 'In Outlook'); + } + } else if (task.source === 'JIRA') { extLabel.textContent = `Gekoppeld Jira Ticket: ${task.external_id || 'Jira'}`; + extLink.querySelector('span') && (extLink.querySelector('span').textContent = 'Openen'); } else if (task.source === 'OUTLOOK' || task.source === 'MICROSOFT') { extLabel.textContent = 'Gekoppeld in Microsoft Outlook Agenda'; + extLink.querySelector('span') && (extLink.querySelector('span').textContent = 'Openen'); } else if (task.source === 'OUTLOOK_MAIL') { extLabel.textContent = 'Afkomstig uit Outlook E-mail'; + extLink.querySelector('span') && (extLink.querySelector('span').textContent = 'Openen'); } else { extLabel.textContent = 'Externe koppeling'; + extLink.querySelector('span') && (extLink.querySelector('span').textContent = 'Openen'); } } else { extBlock.classList.add('hidden'); @@ -3432,6 +3610,13 @@ function populateTaskDetailModal(task, subtasks) { } // Buttons + if (btnEditLabel) { + btnEditLabel.textContent = isOutlookEvent ? 'Afspraak Bewerken' : 'Taak Bewerken'; + } + if (btnDeleteLabel) { + btnDeleteLabel.textContent = isOutlookEvent ? 'Afspraak Verwijderen' : 'Verwijderen'; + } + if (btnEdit) { btnEdit.onclick = () => { closeTaskDetailModal(); @@ -3441,7 +3626,11 @@ function populateTaskDetailModal(task, subtasks) { if (btnDelete) { btnDelete.onclick = () => { closeTaskDetailModal(); - deleteTask(task.id); + if (isOutlookEvent) { + deleteOutlookEvent(task.id, task.subject || task.title); + } else { + deleteTask(task.id); + } }; } } @@ -4002,19 +4191,19 @@ function toggleWidgetCollapse(wId) { function toggleDaySelectionCollapse() { const strip = document.getElementById('weekDaysStripContainer'); const icon = document.getElementById('iconToggleDaySelection'); - const label = document.getElementById('labelToggleDaySelection'); + const btn = document.getElementById('btnToggleDaySelection'); if (!strip) return; const isCurrentlyHidden = strip.classList.contains('hidden'); if (isCurrentlyHidden) { strip.classList.remove('hidden'); if (icon) icon.classList.remove('rotate-180'); - if (label) label.textContent = 'Dagen inklappen'; + if (btn) btn.title = 'Dagen inklappen'; try { localStorage.setItem('fociz_days_selection_collapsed', '0'); } catch(e) {} } else { strip.classList.add('hidden'); if (icon) icon.classList.add('rotate-180'); - if (label) label.textContent = 'Dagen tonen'; + if (btn) btn.title = 'Dagen tonen'; try { localStorage.setItem('fociz_days_selection_collapsed', '1'); } catch(e) {} } if (typeof lucide !== 'undefined' && lucide.createIcons) { @@ -4027,10 +4216,10 @@ function initPlannerDaySelectionCollapse() { if (localStorage.getItem('fociz_days_selection_collapsed') === '1') { const strip = document.getElementById('weekDaysStripContainer'); const icon = document.getElementById('iconToggleDaySelection'); - const label = document.getElementById('labelToggleDaySelection'); + const btn = document.getElementById('btnToggleDaySelection'); if (strip) strip.classList.add('hidden'); if (icon) icon.classList.add('rotate-180'); - if (label) label.textContent = 'Dagen tonen'; + if (btn) btn.title = 'Dagen tonen'; } } catch(e) {} } @@ -4407,6 +4596,7 @@ plannerJiraAssigned.forEach(iss => { if (iss && iss.key && !plannerTicketStore.has(iss.key)) plannerTicketStore.set(iss.key, iss); }); +const plannedExternalIdsSet = new Set(); let activePlannerJiraQueue = ''; // '': Alle, 'my': Mijn, 'DX': DX Queue, 'SD': SD Queue function isIssueInQueue(iss, qCode) { @@ -4508,6 +4698,27 @@ function filterPlannerJira() { ? 'DX' : 'SD'; + const isPlanned = plannedExternalIdsSet.has(issue.key); + const planBtn = isPlanned + ? `` + : ``; + return `
@@ -4525,14 +4736,7 @@ function filterPlannerJira() {
- + ${planBtn}
`; }).join(''); @@ -4608,6 +4812,18 @@ function openJiraTicketDetail(issueKey) { linkEl.classList.add('hidden'); } + // Knop inplannen / ingepland status + const detailPlanBtn = document.getElementById('btnJiraDetailPlan'); + if (detailPlanBtn) { + if (plannedExternalIdsSet.has(issue.key)) { + detailPlanBtn.className = "flex items-center space-x-1.5 px-3 py-1.5 rounded-xl bg-emerald-600 hover:bg-emerald-500 text-white font-bold text-xs transition cursor-pointer shadow-xs"; + detailPlanBtn.innerHTML = 'Ingepland'; + } else { + detailPlanBtn.className = "flex items-center space-x-1.5 px-3 py-1.5 rounded-xl bg-indigo-600 hover:bg-indigo-500 text-white font-bold text-xs transition cursor-pointer"; + detailPlanBtn.innerHTML = 'Inplannen'; + } + } + const modal = document.getElementById('jiraTicketDetailModal'); modal.classList.remove('hidden'); modal.classList.add('flex'); @@ -4960,6 +5176,23 @@ async function submitJiraPlanModal() { if (data.success) { closeJiraPlanModal(); showWidgetToast(`${escapeHtml(issue.key)} ingepland als dagtaak`, 'success'); + + // Live update: voeg toe aan plannedExternalIdsSet en transformeer knoppen naar groen 'Ingepland' + if (typeof plannedExternalIdsSet !== 'undefined') { + plannedExternalIdsSet.add(issue.key); + } + document.querySelectorAll(`.plan-btn-jira[data-jira-key="${issue.key}"]`).forEach(btn => { + btn.className = "plan-btn-jira flex items-center space-x-1 text-[11px] font-bold text-white bg-emerald-600 hover:bg-emerald-500 border border-emerald-600 shadow-xs px-2.5 py-1 rounded-lg transition flex-shrink-0 cursor-pointer"; + btn.title = "Dit ticket is al ingepland als dagtaak (klik om opnieuw/anders in te plannen)"; + btn.innerHTML = 'Ingepland'; + }); + const detailPlanBtn = document.getElementById('btnJiraDetailPlan'); + if (detailPlanBtn && typeof _jiraPlanCurrentIssue !== 'undefined' && _jiraPlanCurrentIssue && _jiraPlanCurrentIssue.key === issue.key) { + detailPlanBtn.className = "flex items-center space-x-1.5 px-3 py-1.5 rounded-xl bg-emerald-600 hover:bg-emerald-500 text-white font-bold text-xs transition cursor-pointer shadow-xs"; + detailPlanBtn.innerHTML = 'Ingepland'; + } + if (typeof lucide !== 'undefined' && lucide.createIcons) lucide.createIcons(); + if (typeof switchPlannerDate === 'function') { switchPlannerDate(planDate, null, false); } @@ -5263,6 +5496,20 @@ async function submitPlanEmailDirect() { if (data.success) { closePlanEmailDirectModal(); showWidgetToast(`E-mail succesvol ingepland als dagtaak`, 'success'); + + // Live update: voeg toe aan plannedExternalIdsSet en transformeer knoppen naar groen 'Ingepland' + if (mailId) { + if (typeof plannedExternalIdsSet !== 'undefined') { + plannedExternalIdsSet.add(mailId); + } + document.querySelectorAll(`.plan-btn-mail[data-mail-id="${mailId}"]`).forEach(btn => { + btn.className = "plan-btn-mail flex items-center space-x-1 text-[11px] font-bold text-white bg-emerald-600 hover:bg-emerald-500 border border-emerald-600 shadow-xs px-2.5 py-1 rounded-lg transition cursor-pointer"; + btn.title = "Deze e-mail is al ingepland als dagtaak (klik om opnieuw/anders in te plannen)"; + btn.innerHTML = 'Ingepland'; + }); + if (typeof lucide !== 'undefined' && lucide.createIcons) lucide.createIcons(); + } + if (typeof switchPlannerDate === 'function') { switchPlannerDate(planDate, null, false); } @@ -6122,8 +6369,94 @@ function setSelectedEditTaskDuration(mins) { if (durationDisplay) durationDisplay.innerText = `${mins}m`; } +// --- Taak & Tijdblok Bewerken Modal Logica --- +function openEditTaskModal(task) { + if (!task) return; + const isOutlook = Boolean(task.is_outlook_event || task.source === 'OUTLOOK_LIVE'); + + document.getElementById('editTaskId').value = task.id; + const isOutlookInput = document.getElementById('editTaskIsOutlook'); + if (isOutlookInput) isOutlookInput.value = isOutlook ? '1' : '0'; + + const headerTitle = document.getElementById('editTaskModalHeaderTitle'); + const headerSubtitle = document.getElementById('editTaskModalHeaderSubtitle'); + const titleLabel = document.getElementById('editTaskTitleLabel'); + const priorityContainer = document.getElementById('editTaskPriorityContainer'); + const saveBtn = document.getElementById('btnSaveEditTask'); + + if (isOutlook) { + if (headerTitle) headerTitle.textContent = 'Outlook Afspraak Bewerken'; + if (headerSubtitle) headerSubtitle.textContent = 'Pas onderwerp, tijdblok en datum van je agenda-afspraak aan'; + if (titleLabel) titleLabel.textContent = 'Onderwerp'; + if (priorityContainer) priorityContainer.classList.add('hidden'); + if (saveBtn) saveBtn.textContent = 'Afspraak Opslaan'; + } else { + if (headerTitle) headerTitle.textContent = 'Taak & Tijdblok Bewerken'; + if (headerSubtitle) headerSubtitle.textContent = 'Pas titel, start-/eindtijd en tijdsduur vrij aan'; + if (titleLabel) titleLabel.textContent = 'Taaktitel'; + if (priorityContainer) priorityContainer.classList.remove('hidden'); + if (saveBtn) saveBtn.textContent = 'Wijzigingen Opslaan'; + } + + document.getElementById('editTaskTitle').value = (isOutlook ? (task.subject || task.title) : task.title) || ''; + const descEl = document.getElementById('editTaskDescription'); + if (descEl) descEl.value = task.description || ''; + document.getElementById('editTaskPriority').value = task.priority || 'P2'; + document.getElementById('editTaskStartTime').value = task.start || ''; + document.getElementById('editTaskEndTime').value = task.end || ''; + + // Datum + const dateInput = document.getElementById('editTaskDate'); + if (dateInput) { + dateInput.value = task.date || (typeof activePlannerDate !== 'undefined' ? activePlannerDate : new Date().toISOString().slice(0, 10)); + } + + // Reset statusbericht van open blok + const notice = document.getElementById('editTaskOpenBlockNotice'); + if (notice) notice.classList.add('hidden'); + + let dur = task.duration; + if (!dur && task.start && task.end) { + const sm = parseTimeToMinutes(task.start); + const em = parseTimeToMinutes(task.end); + if (sm !== null && em !== null && em > sm) dur = em - sm; + } + dur = dur || 30; + setSelectedEditTaskDuration(dur); + + const outlookNotice = document.getElementById('editTaskOutlookNotice'); + if (outlookNotice) { + if (isOutlook) { + outlookNotice.classList.remove('hidden'); + outlookNotice.innerHTML = 'Wijzigingen worden direct gesynchroniseerd naar Microsoft Outlook 365.'; + } else if (task.source === 'OUTLOOK' || task.external_id) { + outlookNotice.classList.remove('hidden'); + outlookNotice.innerHTML = 'Deze taak is gekoppeld aan Outlook 365. Wijzigingen worden automatisch in uw Outlook agenda gesynchroniseerd.'; + } else { + outlookNotice.classList.add('hidden'); + } + } + + const modal = document.getElementById('editTaskModal'); + if (modal) { + modal.classList.remove('hidden'); + modal.classList.add('flex'); + if (typeof lucide !== 'undefined') lucide.createIcons(); + document.getElementById('editTaskTitle')?.focus(); + } +} + +function closeEditTaskModal() { + const modal = document.getElementById('editTaskModal'); + if (modal) { + modal.classList.add('hidden'); + modal.classList.remove('flex'); + } +} + async function submitEditTask() { - const taskId = parseInt(document.getElementById('editTaskId').value, 10); + const isOutlook = document.getElementById('editTaskIsOutlook')?.value === '1'; + const taskId = document.getElementById('editTaskId').value; const title = document.getElementById('editTaskTitle').value.trim(); const priority = document.getElementById('editTaskPriority').value; const dateInput = document.getElementById('editTaskDate'); @@ -6134,7 +6467,7 @@ async function submitEditTask() { const saveBtn = document.getElementById('btnSaveEditTask'); if (!title) { - alert('Vul een taaktitel in.'); + alert(isOutlook ? 'Vul een onderwerp in voor de afspraak.' : 'Vul een taaktitel in.'); return; } @@ -6149,7 +6482,7 @@ async function submitEditTask() { if (answer === 'NEE') { // Automatisch naar volgende werkdag met eerder gezette voorwaarden try { - const nextSlot = await findSlotOnNextWorkday(duration, taskId); + const nextSlot = await findSlotOnNextWorkday(duration, isOutlook ? 0 : parseInt(taskId, 10)); taskDate = nextSlot.date; startTime = nextSlot.start; endTime = nextSlot.end; @@ -6163,52 +6496,109 @@ async function submitEditTask() { } if (saveBtn) { - saveBtn.innerText = 'Opslaan...'; + saveBtn.innerText = isOutlook ? 'Afspraak Opslaan...' : 'Opslaan...'; saveBtn.disabled = true; } try { const description = document.getElementById('editTaskDescription')?.value.trim() || ''; - const res = await fetch('/api/planner.php?action=update_task', { - method: 'POST', - headers: {'Content-Type': 'application/json'}, - body: JSON.stringify({ - task_id: taskId, - title: title, - description: description, - priority: priority, - time_block_start: startTime, - time_block_end: endTime, - duration_minutes: duration, - date: taskDate - }) - }); - const data = await res.json(); - if (data.success) { - closeEditTaskModal(); - const targetDate = data.date || taskDate; - if (typeof switchPlannerDate === 'function') { - switchPlannerDate(targetDate, null, false); - } - if (typeof showWidgetToast === 'function') { - showWidgetToast(`Taak ${escapeHtml(title)} succesvol bijgewerkt`, 'success'); + + if (isOutlook) { + const res = await fetch('/api/planner.php?action=update_outlook_event', { + method: 'POST', + headers: {'Content-Type': 'application/json'}, + body: JSON.stringify({ + event_id: taskId, + title: title, + description: description, + time_block_start: startTime, + time_block_end: endTime, + date: taskDate + }) + }); + const data = await res.json(); + if (data.success) { + closeEditTaskModal(); + const targetDate = data.date || taskDate; + if (typeof switchPlannerDate === 'function') { + switchPlannerDate(targetDate, null, false); + } + if (typeof showWidgetToast === 'function') { + showWidgetToast(`Outlook afspraak ${escapeHtml(title)} succesvol bijgewerkt`, 'success'); + } + } else { + alert(data.error || 'Kon afspraak niet bijwerken in Microsoft Outlook'); + if (saveBtn) { + saveBtn.innerText = 'Afspraak Opslaan'; + saveBtn.disabled = false; + } } } else { - alert(data.error || 'Kon taak niet bijwerken'); - if (saveBtn) { - saveBtn.innerText = 'Wijzigingen Opslaan'; - saveBtn.disabled = false; + const res = await fetch('/api/planner.php?action=update_task', { + method: 'POST', + headers: {'Content-Type': 'application/json'}, + body: JSON.stringify({ + task_id: parseInt(taskId, 10), + title: title, + description: description, + priority: priority, + time_block_start: startTime, + time_block_end: endTime, + duration_minutes: duration, + date: taskDate + }) + }); + const data = await res.json(); + if (data.success) { + closeEditTaskModal(); + const targetDate = data.date || taskDate; + if (typeof switchPlannerDate === 'function') { + switchPlannerDate(targetDate, null, false); + } + if (typeof showWidgetToast === 'function') { + showWidgetToast(`Taak ${escapeHtml(title)} succesvol bijgewerkt`, 'success'); + } + } else { + alert(data.error || 'Kon taak niet bijwerken'); + if (saveBtn) { + saveBtn.innerText = 'Wijzigingen Opslaan'; + saveBtn.disabled = false; + } } } } catch (e) { - alert('Netwerkfout bij bijwerken van de taak'); + alert('Netwerkfout bij bijwerken'); if (saveBtn) { - saveBtn.innerText = 'Wijzigingen Opslaan'; + saveBtn.innerText = isOutlook ? 'Afspraak Opslaan' : 'Wijzigingen Opslaan'; saveBtn.disabled = false; } } } +async function deleteOutlookEvent(eventId, eventSubject) { + const displayTitle = eventSubject ? ` "${eventSubject}"` : ''; + if (!confirm(`Weet je zeker dat je de Outlook afspraak${displayTitle} wilt verwijderen uit je Microsoft Outlook agenda?`)) return; + + try { + const res = await fetch('/api/planner.php?action=delete_outlook_event', { + method: 'POST', + headers: {'Content-Type': 'application/json'}, + body: JSON.stringify({ event_id: eventId }) + }); + const data = await res.json(); + if (data.success) { + if (typeof showWidgetToast === 'function') { + showWidgetToast(`Afspraak ${escapeHtml(eventSubject || 'Outlook')} verwijderd`, 'info'); + } + switchPlannerDate(activePlannerDate, null, false); + } else { + alert(data.error || 'Kon afspraak niet verwijderen uit Outlook'); + } + } catch (err) { + alert('Netwerkfout bij verwijderen van Outlook afspraak'); + } +} + async function syncTaskToOutlook(taskId, btn) { if (!btn) return; const originalHtml = btn.innerHTML;