'Ma', 2 => 'Di', 3 => 'Wo', 4 => 'Do', 5 => 'Vr', 6 => 'Za', 7 => 'Zo']; $dutchFullDays = [1 => 'Maandag', 2 => 'Dinsdag', 3 => 'Woensdag', 4 => 'Donderdag', 5 => 'Vrijdag', 6 => 'Zaterdag', 7 => 'Zondag']; $dutchMonths = ['', 'januari', 'februari', 'maart', 'april', 'mei', 'juni', 'juli', 'augustus', 'september', 'oktober', 'november', 'december']; $dutchShortMonths = ['', 'jan', 'feb', 'mrt', 'apr', 'mei', 'jun', 'jul', 'aug', 'sep', 'okt', 'nov', 'dec']; $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); // 1. Haal integratiestatussen op $userIntegrations = []; try { $stmtInt = $db->prepare("SELECT * FROM integrations WHERE user_id = ?"); $stmtInt->execute([$userId]); foreach ($stmtInt->fetchAll() as $row) { $userIntegrations[$row['provider']] = $row; } } catch (Throwable $e) { error_log("Fout bij ophalen integraties: " . $e->getMessage()); ensure_tables_exist($db); } $isOutlookConnected = !empty($userIntegrations['OUTLOOK']['access_token']) && empty($userIntegrations['OUTLOOK']['is_demo_mode']); $outlookIntegrationSettings = json_decode($userIntegrations['OUTLOOK']['settings'] ?? '{}', true) ?: []; $syncIntervalSeconds = isset($outlookIntegrationSettings['sync_interval_seconds']) ? (int)$outlookIntegrationSettings['sync_interval_seconds'] : 30; $weekOutlookEvents = []; $isLiveOutlook = false; if ($isOutlookConnected) { try { // Haal afspraken op voor het volledige datumbereik van de week $weekOutlookEvents = microsoft_fetch_events_range($userId, $weekStart, $weekEnd); $isLiveOutlook = true; // Synchroniseer / reconcilieer Fociz taken direct met actuele Outlook data microsoft_reconcile_tasks_with_outlook($userId, $weekStart, $weekEnd, $weekOutlookEvents); } catch (Throwable $e) { error_log("Outlook synchronisatiefout in planner: " . $e->getMessage()); $isLiveOutlook = false; $weekOutlookEvents = []; } } else { // Demo data indien Microsoft Outlook nog niet gekoppeld is $weekOutlookEvents = [ [ 'id' => 'evt-1', 'subject' => 'Voorbeeld: Team Standup & Sync', 'date' => $today, 'start' => '09:00', 'end' => '09:30', 'location' => 'Microsoft Teams', 'meeting_url' => 'https://teams.microsoft.com', 'source' => 'SANDBOX', ], [ 'id' => 'evt-2', 'subject' => 'Voorbeeld: Roadmap Review met Stakeholders', 'date' => $today, 'start' => '11:00', 'end' => '12:00', 'location' => 'Vergaderruimte A & Teams', 'meeting_url' => 'https://teams.microsoft.com', 'source' => 'SANDBOX', ], [ 'id' => 'evt-3', 'subject' => 'Voorbeeld: Architectuur & DB Evaluatie', 'date' => $today, 'start' => '14:30', 'end' => '15:30', 'location' => 'Online Teams Call', 'meeting_url' => 'https://teams.microsoft.com', 'source' => 'SANDBOX', ] ]; } // 2. Haal dagplanning op of maak aan voor $selectedDate $dailyPlan = null; try { $stmtPlan = $db->prepare("SELECT * FROM daily_plans WHERE user_id = ? AND plan_date = ?"); $stmtPlan->execute([$userId, $selectedDate]); $dailyPlan = $stmtPlan->fetch(); if (!$dailyPlan) { $defaultGoal = ($selectedDate === $today) ? 'Mijn belangrijkste doel van vandaag behalen' : 'Focusdoel voor deze dag bepalen'; $stmtCreatePlan = $db->prepare(" INSERT INTO daily_plans (user_id, plan_date, focus_goal) VALUES (?, ?, ?) ON DUPLICATE KEY UPDATE id=LAST_INSERT_ID(id) "); $stmtCreatePlan->execute([$userId, $selectedDate, $defaultGoal]); $planId = (int)$db->lastInsertId(); $dailyPlan = ['id' => $planId, 'plan_date' => $selectedDate, 'focus_goal' => $defaultGoal, 'notes' => '']; } } catch (Throwable $e) { error_log("Fout bij dagplanning ophalen/aanmaken: " . $e->getMessage()); ensure_tables_exist($db); $dailyPlan = ['id' => 0, 'plan_date' => $selectedDate, 'focus_goal' => 'Mijn belangrijkste doel van vandaag behalen', 'notes' => '']; } $planId = !empty($dailyPlan['id']) ? (int)$dailyPlan['id'] : 0; // 3. Haal taken op voor de geselecteerde dag (vers bijgewerkt na eventuele reconciliatie) $tasks = []; try { $stmtTasks = $db->prepare(" SELECT * FROM tasks WHERE user_id = ? AND (daily_plan_id = ? " . ($selectedDate === $today ? "OR daily_plan_id IS NULL" : "") . ") ORDER BY is_completed ASC, priority ASC, time_block_start ASC, created_at DESC "); $stmtTasks->execute([$userId, $planId]); $tasks = $stmtTasks->fetchAll() ?: []; } catch (Throwable $e) { error_log("Fout bij ophalen taken: " . $e->getMessage()); } // 4. Haal weekstatistieken voor taken op (aantal per dag) $weekTaskStats = []; try { $stmtWeekTasks = $db->prepare(" SELECT dp.plan_date, COUNT(t.id) as total_tasks, SUM(CASE WHEN t.is_completed = 1 THEN 1 ELSE 0 END) as completed_tasks FROM daily_plans dp LEFT JOIN tasks t ON t.daily_plan_id = dp.id AND t.user_id = ? WHERE dp.user_id = ? AND dp.plan_date BETWEEN ? AND ? GROUP BY dp.plan_date "); $stmtWeekTasks->execute([$userId, $userId, $weekStart, $weekEnd]); foreach ($stmtWeekTasks->fetchAll() as $row) { $weekTaskStats[$row['plan_date']] = [ 'total' => (int)$row['total_tasks'], 'completed' => (int)$row['completed_tasks'], ]; } } catch (Throwable $e) { error_log("Fout bij weekstatistieken ophalen: " . $e->getMessage()); } // Groepeer Outlook afspraken per datum $outlookEventsByDate = []; foreach ($weekOutlookEvents as $evt) { $evtDate = $evt['date'] ?? $selectedDate; $outlookEventsByDate[$evtDate][] = $evt; } // 7 Dagen van de week samenstellen voor de weekselector $weekDays = []; for ($i = 0; $i < 7; $i++) { $dayTs = strtotime("+{$i} days", $mondayTs); $dayDate = date('Y-m-d', $dayTs); $dayN = (int)date('N', $dayTs); $dayM = (int)date('n', $dayTs); $tStat = $weekTaskStats[$dayDate] ?? ['total' => 0, 'completed' => 0]; $mCount = count($outlookEventsByDate[$dayDate] ?? []); $weekDays[] = [ 'date' => $dayDate, 'day_num' => date('j', $dayTs), 'short_name' => $dutchShortDays[$dayN], 'full_name' => $dutchFullDays[$dayN], 'month_short' => $dutchShortMonths[$dayM], 'is_today' => ($dayDate === $today), 'is_selected' => ($dayDate === $selectedDate), 'task_count' => $tStat['total'], 'completed_count' => $tStat['completed'], 'meeting_count' => $mCount, ]; } // 5. Haal Jira issues op voor de snelle import widget $jiraIssues = []; $isJiraLive = false; try { $jiraData = jira_get_user_issues($userId); $jiraIssues = $jiraData['issues'] ?? []; $isJiraLive = !empty($jiraData['success']) && empty($jiraData['is_demo']); } catch (Throwable $e) { error_log("Fout bij ophalen Jira issues: " . $e->getMessage()); } // 5b. Haal recente e-mails op voor de Outlook E-mail Widget $recentEmails = []; $unreadEmailCount = 0; try { $mailData = microsoft_fetch_inbox_messages($userId, 5); $recentEmails = $mailData['messages'] ?? []; $unreadEmailCount = (int)($mailData['unread_count'] ?? 0); } catch (Throwable $e) { error_log("Fout bij ophalen recente e-mails in planner: " . $e->getMessage()); } // 5c. Widget Catalogus & Raster Indeling (Fociz v2.0) $availableWidgets = [ 'daily_goal' => ['id' => 'daily_goal', 'title' => 'Dagfocus & Hoofddoel', 'category' => 'Planning', 'icon' => 'target', 'enabled' => true, 'col_span' => 12, 'order' => 1, 'desc' => 'Centraal focusdoel voor de dag met snelle tellers'], 'tasks' => ['id' => 'tasks', 'title' => 'Dagtaken & Prioriteiten', 'category' => 'Planning', 'icon' => 'check-square', 'enabled' => true, 'col_span' => 7, 'order' => 2, 'desc' => 'Volledige takenlijst met P1/P2/P3, tijdvakken en Outlook synchronisatie'], 'timeline' => ['id' => 'timeline', 'title' => 'Tijdlijn & Time-blocking', 'category' => 'Agenda', 'icon' => 'clock', 'enabled' => true, 'col_span' => 5, 'order' => 3, 'desc' => 'Visueel urenoverzicht met lunchpauze, afspraken en AI Slim Indelen'], 'outlook_mail' => ['id' => 'outlook_mail', 'title' => 'Outlook E-mail Widget', 'category' => 'Communicatie', 'icon' => 'mail', 'enabled' => true, 'col_span' => 6, 'order' => 4, 'desc' => 'Inkomende e-mails direct inzien en met 1 klik inplannen als taak'], 'jira_queue' => ['id' => 'jira_queue', 'title' => 'Jira Tickets & Wachtrijen', 'category' => 'Ontwikkeling', 'icon' => 'layers', 'enabled' => true, 'col_span' => 6, 'order' => 5, 'desc' => 'Openstaande Jira tickets (DX/SD wachtrijen) met directe importknop'], 'calendar_meetings' => ['id' => 'calendar_meetings', 'title' => 'Agenda & Teams Meetings', 'category' => 'Agenda', 'icon' => 'video', 'enabled' => true, 'col_span' => 6, 'order' => 6, 'desc' => 'Vandaag geplande afspraken met 1-klik Teams deelnemen knop'], 'scratchpad' => ['id' => 'scratchpad', 'title' => 'Dagnotities & Kladblok', 'category' => 'Productiviteit', 'icon' => 'edit-3', 'enabled' => true, 'col_span' => 6, 'order' => 7, 'desc' => 'Vrij notitieblok met automatische achtergrond-opslag'], 'stats' => ['id' => 'stats', 'title' => 'Productiviteit & KPI\'s', 'category' => 'Inzicht', 'icon' => 'bar-chart-2', 'enabled' => true, 'col_span' => 12, 'order' => 8, 'desc' => 'Voortgangsbalk, voltooide taken, focus uren en meeting totalen'], 'quick_links' => ['id' => 'quick_links', 'title' => 'Snelle Koppelingen & Tools', 'category' => 'Productiviteit', 'icon' => 'external-link', 'enabled' => false, 'col_span' => 6, 'order' => 9, 'desc' => '1-klik snelkoppelingen naar Jira, Teams, Outlook, Azure en documentatie'], 'habit_tracker' => ['id' => 'habit_tracker', 'title' => 'Dagelijkse Focus Gewoontes', 'category' => 'Productiviteit', 'icon' => 'sparkles', 'enabled' => false, 'col_span' => 6, 'order' => 10, 'desc' => 'Houd gezonde werkgewoontes bij (water drinken, 15m bewegen, inbox zero)'], 'focus_sound' => ['id' => 'focus_sound', 'title' => 'Focus Sounds & Omgeving', 'category' => 'Productiviteit', 'icon' => 'headphones', 'enabled' => false, 'col_span' => 6, 'order' => 11, 'desc' => 'Rustgevende focus-geluiden voor concentratie en diep werk'], 'team_status' => ['id' => 'team_status', 'title' => 'Team & Beschikbaarheid', 'category' => 'Team', 'icon' => 'users', 'enabled' => false, 'col_span' => 6, 'order' => 12, 'desc' => 'Overzicht van collega\'s en wie vandaag bereikbaar of in overleg is'] ]; $userWidgetLayout = $availableWidgets; $savedLayoutJson = $currentUser['fociz_widget_layout'] ?? ''; if (!empty($savedLayoutJson)) { $decoded = json_decode($savedLayoutJson, true); if (is_array($decoded)) { foreach ($decoded as $item) { $wId = $item['id'] ?? ''; if (isset($userWidgetLayout[$wId])) { $userWidgetLayout[$wId]['enabled'] = !empty($item['enabled']); if (!empty($item['col_span'])) $userWidgetLayout[$wId]['col_span'] = (int)$item['col_span']; if (isset($item['order'])) $userWidgetLayout[$wId]['order'] = (int)$item['order']; } } } } uasort($userWidgetLayout, function($a, $b) { return ($a['order'] ?? 0) <=> ($b['order'] ?? 0); }); // Bereken dagstatistieken $completedTasksCount = 0; $pendingTasksCount = 0; $highPriorityCount = 0; $totalPlannedMinutes = 0; foreach ($tasks as $t) { if (!empty($t['is_completed'])) { $completedTasksCount++; } else { $pendingTasksCount++; if (($t['priority'] ?? '') === 'P1') $highPriorityCount++; } $totalPlannedMinutes += (int)($t['duration_minutes'] ?: 30); } $totalTaskCount = count($tasks); $taskProgressPercent = $totalTaskCount > 0 ? round(($completedTasksCount / $totalTaskCount) * 100) : 0; // Filter Outlook events die afkomstig zijn van Fociz taken voor de geselecteerde dag om duplicaten op de tijdlijn te voorkomen $dayOutlookEvents = $outlookEventsByDate[$selectedDate] ?? []; $syncedTaskExternalIds = []; foreach ($tasks as $t) { if (!empty($t['external_id'])) { $syncedTaskExternalIds[$t['external_id']] = true; } } $displayedOutlookEvents = []; foreach ($dayOutlookEvents as $evt) { if (!empty($evt['id']) && isset($syncedTaskExternalIds[$evt['id']])) { continue; } $displayedOutlookEvents[] = $evt; } // Bereken initiële status-hash voor achtergrondsynchronisatie $dayEventsForHash = []; foreach ($displayedOutlookEvents as $e) { $dayEventsForHash[] = [ 'id' => $e['id'] ?? '', 'start' => $e['start'] ?? '', 'end' => $e['end'] ?? '', 'subject' => $e['subject'] ?? '', ]; } $tasksForHash = array_map(function($t) { return [ 'id' => $t['id'], 'title' => $t['title'], 'time_block_start' => $t['time_block_start'], 'time_block_end' => $t['time_block_end'], 'is_completed' => $t['is_completed'], ]; }, $tasks); $initialSyncHash = md5(json_encode([ 'events' => $dayEventsForHash, 'tasks' => $tasksForHash ])); // 6. Tijdlijn uren & Werkdag Standaarden (Release v1.4) $workdayStartStr = $workdaySettings['workday_start'] ?? '08:30'; $workdayEndStr = $workdaySettings['workday_end'] ?? '17:30'; $lunchStart = $workdaySettings['lunch_start'] ?? '12:30'; $lunchEnd = $workdaySettings['lunch_end'] ?? '13:00'; $lunchHour = (int)explode(':', $lunchStart)[0]; $startHour = (int)explode(':', $workdayStartStr)[0]; $endHourParts = explode(':', $workdayEndStr); $endHour = (int)$endHourParts[0]; if (!empty($endHourParts[1]) && (int)$endHourParts[1] > 0) { $endHour++; } if ($startHour < 6) $startHour = 6; if ($endHour > 23) $endHour = 23; foreach ($displayedOutlookEvents as $e) { if (!empty($e['start'])) { $h = (int)explode(':', $e['start'])[0]; if ($h < $startHour && $h >= 6) $startHour = $h; if ($h > $endHour && $h <= 23) $endHour = $h + 1; } } foreach ($tasks as $t) { if (!empty($t['time_block_start'])) { $h = (int)explode(':', $t['time_block_start'])[0]; if ($h < $startHour && $h >= 6) $startHour = $h; if ($h > $endHour && $h <= 23) $endHour = $h + 1; } } $hours = []; for ($h = $startHour; $h <= $endHour; $h++) { $hours[] = sprintf('%02d:00', $h); } ?>
VANDAAG GEKOZEN DAG

Fociz

Jouw centrale werkstation • Taken, Agenda, Mail & Jira

Outlook 365 Live
Koppel Outlook Jira: tickets Koppel Jira
$widget): if (empty($widget['enabled'])) continue; $span = (int)($widget['col_span'] ?? 6); $colClass = match($span) { 12 => 'lg:col-span-12', 8 => 'lg:col-span-8', 7 => 'lg:col-span-7', 6 => 'lg:col-span-6', 5 => 'lg:col-span-5', 4 => 'lg:col-span-4', default => 'lg:col-span-6' }; ?>

Dagfocus & Hoofddoel

Taken /
P1 Focus
Meetings

Dagtaken & Prioriteiten

Nog geen taken gepland voor .

Klik op 'Nieuwe Taak' of importeer uit Jira / Outlook Mail hieronder.

JIRA MAIL OUTLOOK

Tijdlijn & Time-blocking

Outlook 365 –
- Lunchpauze (Gereserveerd)
Pauze

Outlook E-mail

0): ?> ongelezen
Naar Mailbox

Geen recente e-mails gevonden in Postvak IN.

Jira Wachtrij & Tickets

Alle Tickets

Geen openstaande tickets gevonden in Jira.

•

Agenda & Teams Meetings

afspraken

Geen vergaderingen of afspraken gepland voor .

Dagnotities & Kladblok

Automatisch opgeslagen

Dagstatistieken & Voortgang

% voltooid
Voltooide Taken
/
P1 Focus Taken
openstaand
Hoogste urgentie
Geplande Tijd
u m
In timeblocks
Agenda Meetings
afspraken
In Outlook 365

Dagelijkse Focus Gewoontes

0/5 gedaan
'water', 'label' => '💧 Voldoende water gedronken (2L)'], ['id' => 'walk', 'label' => '🚶‍♂️ Minimaal 15 minuten bewogen / gewandeld'], ['id' => 'inbox', 'label' => '📭 Postvak IN opgeruimd & acties ingepland'], ['id' => 'standup', 'label' => '👥 Standup & overleg voorbereid'], ['id' => 'reflection', 'label' => '🧘 Dag afgesloten met reflectie & planning morgen'], ]; foreach ($habits as $h): ?>

Focus Sounds & Rust

Gepauzeerd

Team & Beschikbaarheid

Vandaag
(Jij) Actief in Fociz
Beschikbaar
SD
Servicedesk Team DX & SD Wachtrij
Tickets opvolgen
0) { ob_end_clean(); } header('Content-Type: application/json; charset=utf-8'); echo json_encode([ 'success' => 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 (' . $formattedSelectedDate . ')', 'html' => $contentHtml, ]); exit; } ?>