812 lines
50 KiB
PHP
812 lines
50 KiB
PHP
<?php
|
||
/**
|
||
* Fociz Dagplanning, Focus Modus & Time-blocking
|
||
* Gekoppeld aan Microsoft Outlook 365 (Agenda) en Atlassian Jira (Tickets)
|
||
*/
|
||
|
||
require_once __DIR__ . '/includes/header.php';
|
||
require_once __DIR__ . '/includes/integrations/microsoft.php';
|
||
require_once __DIR__ . '/includes/integrations/jira.php';
|
||
|
||
$db = get_db_connection();
|
||
$userId = $currentUser['id'];
|
||
$today = date('Y-m-d');
|
||
|
||
// 1. Haal dagplanning op of maak aan
|
||
$stmtPlan = $db->prepare("SELECT * FROM daily_plans WHERE user_id = ? AND plan_date = ?");
|
||
$stmtPlan->execute([$userId, $today]);
|
||
$dailyPlan = $stmtPlan->fetch();
|
||
|
||
if (!$dailyPlan) {
|
||
$stmtCreatePlan = $db->prepare("
|
||
INSERT INTO daily_plans (user_id, plan_date, focus_goal)
|
||
VALUES (?, ?, 'Mijn belangrijkste doel van vandaag behalen')
|
||
");
|
||
$stmtCreatePlan->execute([$userId, $today]);
|
||
$planId = (int)$db->lastInsertId();
|
||
$dailyPlan = ['id' => $planId, 'plan_date' => $today, 'focus_goal' => 'Mijn belangrijkste doel van vandaag behalen', 'notes' => ''];
|
||
}
|
||
|
||
// 2. Haal taken op voor vandaag
|
||
$stmtTasks = $db->prepare("
|
||
SELECT * FROM tasks
|
||
WHERE user_id = ? AND (daily_plan_id = ? OR daily_plan_id IS NULL)
|
||
ORDER BY is_completed ASC, priority ASC, time_block_start ASC, created_at DESC
|
||
");
|
||
$stmtTasks->execute([$userId, $dailyPlan['id']]);
|
||
$tasks = $stmtTasks->fetchAll();
|
||
|
||
// 3. Haal integratiestatussen op
|
||
$stmtInt = $db->prepare("SELECT * FROM integrations WHERE user_id = ?");
|
||
$stmtInt->execute([$userId]);
|
||
$userIntegrations = [];
|
||
foreach ($stmtInt->fetchAll() as $row) {
|
||
$userIntegrations[$row['provider']] = $row;
|
||
}
|
||
|
||
$isOutlookConnected = !empty($userIntegrations['OUTLOOK']['access_token']) && empty($userIntegrations['OUTLOOK']['is_demo_mode']);
|
||
$outlookEvents = [];
|
||
$isLiveOutlook = false;
|
||
|
||
if ($isOutlookConnected) {
|
||
$outlookEvents = microsoft_fetch_today_events($userId, $today);
|
||
$isLiveOutlook = true;
|
||
} else {
|
||
// Alleen tonen indien de gebruiker Microsoft Outlook nog niet gekoppeld heeft
|
||
$outlookEvents = [
|
||
[
|
||
'id' => 'evt-1',
|
||
'subject' => 'Voorbeeld: Team Standup & Sync',
|
||
'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',
|
||
'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',
|
||
'start' => '14:30',
|
||
'end' => '15:30',
|
||
'location' => 'Online Teams Call',
|
||
'meeting_url' => 'https://teams.microsoft.com',
|
||
'source' => 'SANDBOX',
|
||
]
|
||
];
|
||
}
|
||
|
||
// 4. Haal Jira issues op voor de snelle import widget
|
||
$jiraData = jira_get_user_issues($userId);
|
||
$jiraIssues = $jiraData['issues'] ?? [];
|
||
$isJiraLive = !empty($jiraData['success']) && empty($jiraData['is_demo']);
|
||
|
||
// Filter Outlook events die afkomstig zijn van Fociz taken om duplicaten op de tijdlijn te voorkomen
|
||
$syncedTaskExternalIds = [];
|
||
foreach ($tasks as $t) {
|
||
if (!empty($t['external_id'])) {
|
||
$syncedTaskExternalIds[$t['external_id']] = true;
|
||
}
|
||
}
|
||
$displayedOutlookEvents = [];
|
||
foreach ($outlookEvents as $evt) {
|
||
if (!empty($evt['id']) && isset($syncedTaskExternalIds[$evt['id']])) {
|
||
continue;
|
||
}
|
||
$displayedOutlookEvents[] = $evt;
|
||
}
|
||
|
||
// 5. Tijdlijn uren bepalen
|
||
$startHour = 8;
|
||
$endHour = 18;
|
||
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 <= 22) $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 <= 22) $endHour = $h + 1;
|
||
}
|
||
}
|
||
$hours = [];
|
||
for ($h = $startHour; $h <= $endHour; $h++) {
|
||
$hours[] = sprintf('%02d:00', $h);
|
||
}
|
||
|
||
// Nederlandse datumformattering
|
||
$dutchDays = ['Zondag', 'Maandag', 'Dinsdag', 'Woensdag', 'Donderdag', 'Vrijdag', 'Zaterdag'];
|
||
$dutchMonths = ['', 'januari', 'februari', 'maart', 'april', 'mei', 'juni', 'juli', 'augustus', 'september', 'oktober', 'november', 'december'];
|
||
$formattedDate = $dutchDays[date('w')] . ' ' . date('j') . ' ' . $dutchMonths[(int)date('n')] . ' ' . date('Y');
|
||
?>
|
||
<style>
|
||
.line-clamp-1 { display: -webkit-box; -webkit-line-clamp: 1; -webkit-box-orient: vertical; overflow: hidden; }
|
||
.line-clamp-2 { display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; }
|
||
</style>
|
||
|
||
<div class="space-y-6 max-w-7xl mx-auto">
|
||
<!-- Top Banner: Datum, Hoofddoel & Integraties Status -->
|
||
<div class="bg-white dark:bg-gradient-to-r dark:from-slate-900 dark:via-[#0e1626] dark:to-slate-900 border border-slate-200 dark:border-slate-800 rounded-3xl p-6 sm:p-7 shadow-sm dark:shadow-2xl transition">
|
||
<div class="flex flex-col lg:flex-row lg:items-center justify-between gap-4">
|
||
<div>
|
||
<div class="flex items-center space-x-2 text-emerald-600 dark:text-emerald-400 text-xs font-bold uppercase tracking-wider mb-1">
|
||
<i data-lucide="calendar" class="w-4 h-4"></i>
|
||
<span><?= htmlspecialchars($formattedDate) ?></span>
|
||
</div>
|
||
<h1 class="text-2xl sm:text-3xl font-black text-slate-900 dark:text-white tracking-tight">
|
||
Dagplanning & Structuur
|
||
</h1>
|
||
</div>
|
||
|
||
<!-- Integratie Status Badges -->
|
||
<div class="flex flex-wrap items-center gap-2">
|
||
<?php if ($isOutlookConnected): ?>
|
||
<div class="flex items-center space-x-2 px-3 py-1.5 rounded-xl bg-blue-50 dark:bg-blue-950/60 border border-blue-200 dark:border-blue-800/60 text-blue-700 dark:text-blue-300 text-xs font-medium shadow-sm">
|
||
<span class="w-2 h-2 rounded-full bg-emerald-500 animate-pulse"></span>
|
||
<span>Outlook 365 Live</span>
|
||
<button onclick="location.reload()" title="Agenda Verversen" class="p-1 text-blue-500 hover:text-blue-800 dark:text-blue-400 dark:hover:text-white rounded transition">
|
||
<i data-lucide="refresh-cw" class="w-3 h-3"></i>
|
||
</button>
|
||
</div>
|
||
<?php else: ?>
|
||
<a href="/profile.php" class="flex items-center space-x-1.5 px-3 py-1.5 rounded-xl bg-slate-100 hover:bg-slate-200 dark:bg-slate-800/80 dark:hover:bg-slate-700 border border-slate-200 dark:border-slate-700 text-slate-700 dark:text-slate-300 hover:text-slate-900 dark:hover:text-white text-xs font-medium transition shadow-sm">
|
||
<i data-lucide="link-2" class="w-3.5 h-3.5 text-blue-500"></i>
|
||
<span>Koppel Outlook</span>
|
||
</a>
|
||
<?php endif; ?>
|
||
|
||
<?php if ($isJiraLive): ?>
|
||
<a href="#jiraPanel" class="flex items-center space-x-1.5 px-3 py-1.5 rounded-xl bg-sky-50 dark:bg-sky-950/60 border border-sky-200 dark:border-sky-800/60 text-sky-700 dark:text-sky-300 hover:text-sky-900 dark:hover:text-white text-xs font-medium transition shadow-sm">
|
||
<span class="w-2 h-2 rounded-full bg-sky-500 animate-pulse"></span>
|
||
<span>Jira: <?= count($jiraIssues) ?> tickets</span>
|
||
</a>
|
||
<?php else: ?>
|
||
<a href="/profile.php" class="flex items-center space-x-1.5 px-3 py-1.5 rounded-xl bg-slate-100 hover:bg-slate-200 dark:bg-slate-800/80 dark:hover:bg-slate-700 border border-slate-200 dark:border-slate-700 text-slate-700 dark:text-slate-300 hover:text-slate-900 dark:hover:text-white text-xs font-medium transition shadow-sm">
|
||
<i data-lucide="trello" class="w-3.5 h-3.5 text-sky-500"></i>
|
||
<span>Koppel Jira</span>
|
||
</a>
|
||
<?php endif; ?>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Dagdoel invoerveld -->
|
||
<div class="mt-5 pt-4 border-t border-slate-100 dark:border-slate-800/80">
|
||
<label class="block text-xs font-bold text-emerald-700 dark:text-emerald-400 uppercase tracking-wider mb-1.5 flex items-center space-x-1.5">
|
||
<i data-lucide="target" class="w-4 h-4"></i>
|
||
<span>Mijn Belangrijkste Dagdoel (Nummer 1 Focus)</span>
|
||
</label>
|
||
<div class="flex items-center space-x-2">
|
||
<input
|
||
type="text"
|
||
id="focusGoalInput"
|
||
value="<?= htmlspecialchars($dailyPlan['focus_goal'] ?? '') ?>"
|
||
placeholder="Wat moet er vandaag absoluut gebeuren voor succes?"
|
||
class="flex-1 bg-slate-50 dark:bg-slate-950/70 border border-slate-300 dark:border-slate-700/80 rounded-xl px-4 py-2.5 text-slate-900 dark:text-white placeholder-slate-400 dark:placeholder-slate-500 text-sm focus:outline-none focus:ring-2 focus:ring-emerald-500/50 focus:border-emerald-500 font-medium transition"
|
||
>
|
||
<button
|
||
onclick="saveGoal()"
|
||
id="saveGoalBtn"
|
||
class="px-4 py-2.5 bg-emerald-500 hover:bg-emerald-600 text-slate-950 font-bold text-xs rounded-xl transition flex items-center space-x-1 shadow-md shadow-emerald-500/20 flex-shrink-0 cursor-pointer"
|
||
>
|
||
<i data-lucide="check-circle-2" class="w-4 h-4"></i>
|
||
<span>Opslaan</span>
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Main Grid: Timer + Takenlijst + Jira (Links) & Timeblocking (Rechts) -->
|
||
<div class="grid grid-cols-1 lg:grid-cols-12 gap-6">
|
||
|
||
<!-- Linker kolom: Focus Timer & Taken -->
|
||
<div class="lg:col-span-7 space-y-6 min-w-0">
|
||
|
||
<!-- Focus Modus Timer -->
|
||
<div class="bg-white dark:bg-slate-900/80 border border-slate-200 dark:border-slate-800 rounded-3xl p-6 shadow-sm dark:shadow-xl relative overflow-hidden backdrop-blur-sm transition">
|
||
<div class="flex items-center justify-between mb-4">
|
||
<div class="flex items-center space-x-2">
|
||
<div class="w-8 h-8 rounded-xl bg-emerald-500/10 text-emerald-600 dark:text-emerald-400 flex items-center justify-center font-bold">
|
||
<i data-lucide="flame" class="w-4 h-4"></i>
|
||
</div>
|
||
<div>
|
||
<h3 class="text-sm font-bold text-slate-900 dark:text-white uppercase tracking-wider">Focus Modus</h3>
|
||
<p class="text-[11px] text-slate-500 dark:text-slate-400">Pomodoro & diepe concentratie</p>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="flex bg-slate-100 dark:bg-slate-950/80 p-1 rounded-xl border border-slate-200 dark:border-slate-800 text-xs">
|
||
<button onclick="setTimerPreset(25)" id="btnPreset25" class="px-2.5 py-1 rounded-lg font-bold bg-emerald-500 text-slate-950 transition">25m Focus</button>
|
||
<button onclick="setTimerPreset(50)" id="btnPreset50" class="px-2.5 py-1 rounded-lg text-slate-600 dark:text-slate-400 hover:text-slate-900 dark:hover:text-slate-200 transition">50m Deep</button>
|
||
<button onclick="setTimerPreset(5)" id="btnPreset5" class="px-2.5 py-1 rounded-lg text-slate-600 dark:text-slate-400 hover:text-slate-900 dark:hover:text-slate-200 transition">5m Pauze</button>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="text-center my-6">
|
||
<div id="timerDisplay" class="text-5xl sm:text-6xl font-black font-mono tracking-tight text-slate-900 dark:text-white mb-2">25:00</div>
|
||
<div class="min-h-[22px] text-xs text-slate-500 dark:text-slate-400 truncate max-w-sm mx-auto flex items-center justify-center space-x-1">
|
||
<span class="text-emerald-600 dark:text-emerald-400 font-medium">Actieve focus:</span>
|
||
<span id="activeTaskDisplay" class="text-slate-800 dark:text-slate-200 font-semibold">Kies een taak hieronder</span>
|
||
</div>
|
||
|
||
<div class="w-full bg-slate-100 dark:bg-slate-950/80 h-2 rounded-full overflow-hidden mt-4 border border-slate-200 dark:border-slate-800/80">
|
||
<div id="timerProgress" class="h-full bg-gradient-to-r from-emerald-500 to-teal-400 transition-all duration-300 rounded-full" style="width: 0%"></div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="flex items-center justify-center space-x-3">
|
||
<button id="timerToggleBtn" onclick="toggleTimer()" class="flex items-center space-x-2 px-6 py-2.5 rounded-xl font-bold text-sm bg-emerald-500 hover:bg-emerald-600 text-slate-950 transition shadow-lg shadow-emerald-500/20 cursor-pointer">
|
||
<i data-lucide="play" class="w-4 h-4 fill-current"></i>
|
||
<span id="timerToggleText">Start Focus</span>
|
||
</button>
|
||
<button onclick="resetTimer()" class="p-2.5 rounded-xl bg-slate-100 hover:bg-slate-200 dark:bg-slate-800/80 dark:hover:bg-slate-700 text-slate-600 dark:text-slate-400 hover:text-slate-900 dark:hover:text-slate-200 transition border border-slate-200 dark:border-slate-700/60 cursor-pointer" title="Reset">
|
||
<i data-lucide="rotate-ccw" class="w-4 h-4"></i>
|
||
</button>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Takenlijst -->
|
||
<div class="bg-white dark:bg-slate-900/80 border border-slate-200 dark:border-slate-800 rounded-3xl p-6 shadow-sm dark:shadow-xl backdrop-blur-sm transition">
|
||
<div class="flex items-center justify-between mb-4 pb-3 border-b border-slate-100 dark:border-slate-800">
|
||
<div>
|
||
<h3 class="text-sm font-bold text-slate-900 dark:text-white uppercase tracking-wider flex items-center space-x-2">
|
||
<span>Dagtaken & Prioriteiten</span>
|
||
<span class="text-xs font-normal text-slate-500 dark:text-slate-400">(<?= count($tasks) ?>)</span>
|
||
</h3>
|
||
</div>
|
||
|
||
<button onclick="toggleAddTaskForm()" class="flex items-center space-x-1.5 px-3 py-1.5 rounded-xl bg-emerald-500 hover:bg-emerald-600 text-slate-950 text-xs font-bold transition shadow-md shadow-emerald-500/20 cursor-pointer">
|
||
<i data-lucide="plus" class="w-4 h-4"></i>
|
||
<span>Nieuwe Taak</span>
|
||
</button>
|
||
</div>
|
||
|
||
<!-- Nieuwe taak formulier (verborgen by default) -->
|
||
<div id="addTaskForm" class="hidden mb-4 p-4 rounded-2xl bg-slate-50 dark:bg-slate-950/90 border border-emerald-500/30 space-y-3 transition">
|
||
<div>
|
||
<label class="block text-[11px] font-medium text-slate-600 dark:text-slate-400 mb-1">Taaktitel</label>
|
||
<input type="text" id="newTaskTitle" placeholder="Wat moet er vandaag gedaan worden?" class="w-full bg-white dark:bg-slate-900 border border-slate-300 dark:border-slate-800 rounded-xl px-3 py-2 text-slate-900 dark:text-white text-xs placeholder-slate-400 dark:placeholder-slate-500 focus:outline-none focus:ring-1 focus:ring-emerald-500">
|
||
</div>
|
||
<div class="grid grid-cols-1 sm:grid-cols-3 gap-3">
|
||
<div>
|
||
<label class="block text-[11px] font-medium text-slate-600 dark:text-slate-400 mb-1">Prioriteit</label>
|
||
<select id="newTaskPriority" class="w-full bg-white dark:bg-slate-900 border border-slate-300 dark:border-slate-800 rounded-xl px-2.5 py-1.5 text-slate-900 dark:text-white text-xs focus:outline-none">
|
||
<option value="P1">P1 - Brandend / Focus</option>
|
||
<option value="P2" selected>P2 - Belangrijk (Standaard)</option>
|
||
<option value="P3">P3 - Regulier / Lage druk</option>
|
||
</select>
|
||
</div>
|
||
<div>
|
||
<label class="block text-[11px] font-medium text-slate-600 dark:text-slate-400 mb-1">Tijdblok (Starttijd)</label>
|
||
<input type="text" id="newTaskTime" placeholder="Bijv. 09:30" class="w-full bg-white dark:bg-slate-900 border border-slate-300 dark:border-slate-800 rounded-xl px-2.5 py-1.5 text-slate-900 dark:text-white text-xs font-mono focus:outline-none">
|
||
</div>
|
||
<div>
|
||
<label class="block text-[11px] font-medium text-slate-600 dark:text-slate-400 mb-1">Tijdsduur</label>
|
||
<select id="newTaskDuration" class="w-full bg-white dark:bg-slate-900 border border-slate-300 dark:border-slate-800 rounded-xl px-2.5 py-1.5 text-slate-900 dark:text-white text-xs focus:outline-none">
|
||
<option value="15">15 minuten</option>
|
||
<option value="30" selected>30 minuten</option>
|
||
<option value="45">45 minuten</option>
|
||
<option value="60">60 minuten (1 uur)</option>
|
||
<option value="90">90 minuten (1,5 uur)</option>
|
||
<option value="120">120 minuten (2 uur)</option>
|
||
</select>
|
||
</div>
|
||
</div>
|
||
|
||
<?php if ($isOutlookConnected): ?>
|
||
<div class="p-2.5 rounded-xl bg-blue-50/70 dark:bg-blue-950/40 border border-blue-200/70 dark:border-blue-800/40">
|
||
<label class="flex items-center space-x-2 text-xs text-blue-900 dark:text-blue-200 cursor-pointer select-none">
|
||
<input type="checkbox" id="newTaskSyncOutlook" checked class="w-4 h-4 rounded text-blue-600 focus:ring-blue-500 border-slate-300 dark:border-slate-700">
|
||
<span class="flex items-center space-x-1.5 font-medium">
|
||
<i data-lucide="calendar" class="w-3.5 h-3.5 text-blue-500"></i>
|
||
<span>Direct inplannen in Microsoft Outlook agenda</span>
|
||
</span>
|
||
</label>
|
||
</div>
|
||
<?php endif; ?>
|
||
|
||
<div class="flex space-x-2 pt-1">
|
||
<button onclick="submitNewTask()" class="px-4 py-1.5 bg-emerald-500 hover:bg-emerald-600 text-slate-950 font-bold rounded-lg text-xs transition cursor-pointer">Opslaan</button>
|
||
<button onclick="toggleAddTaskForm()" class="px-3 py-1.5 bg-slate-200 hover:bg-slate-300 dark:bg-slate-800 dark:hover:bg-slate-700 text-slate-700 dark:text-slate-400 rounded-lg text-xs transition cursor-pointer">Annuleren</button>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Taken weergave -->
|
||
<div class="space-y-2" id="tasksContainer">
|
||
<?php if (empty($tasks)): ?>
|
||
<div class="text-center py-8 text-slate-400 dark:text-slate-500 text-xs">
|
||
Nog geen taken gepland voor vandaag. Klik op 'Nieuwe Taak' of importeer een Jira ticket hieronder.
|
||
</div>
|
||
<?php else: ?>
|
||
<?php foreach ($tasks as $task): ?>
|
||
<div class="group flex items-center justify-between p-3 rounded-2xl border <?= $task['is_completed'] ? 'bg-slate-50/50 dark:bg-slate-950/30 border-slate-200 dark:border-slate-900 opacity-60' : 'bg-slate-50 dark:bg-slate-950/60 border-slate-200 dark:border-slate-800 hover:border-slate-300 dark:hover:border-slate-700' ?> transition shadow-sm" id="task-row-<?= $task['id'] ?>">
|
||
<div class="flex items-center space-x-3 truncate mr-2">
|
||
<button onclick="toggleTask(<?= $task['id'] ?>, <?= $task['is_completed'] ? 0 : 1 ?>)" class="w-5 h-5 rounded-lg border flex items-center justify-center transition <?= $task['is_completed'] ? 'bg-emerald-500 border-emerald-500 text-slate-950 font-bold' : 'border-slate-300 dark:border-slate-700 hover:border-emerald-500 bg-white dark:bg-slate-900' ?>">
|
||
<?php if ($task['is_completed']): ?>
|
||
<i data-lucide="check" class="w-3.5 h-3.5 stroke-[3]"></i>
|
||
<?php endif; ?>
|
||
</button>
|
||
|
||
<div class="truncate">
|
||
<div class="flex items-center space-x-1.5 truncate">
|
||
<?php if ($task['source'] === 'JIRA'): ?>
|
||
<span class="px-1.5 py-0.2 rounded bg-sky-500/10 border border-sky-500/20 text-sky-600 dark:text-sky-400 font-mono text-[9px] font-bold">
|
||
JIRA
|
||
</span>
|
||
<?php elseif ($task['source'] === 'OUTLOOK' || !empty($task['external_id'])): ?>
|
||
<span class="px-1.5 py-0.2 rounded bg-blue-500/10 border border-blue-500/20 text-blue-600 dark:text-blue-400 font-mono text-[9px] font-bold flex items-center space-x-0.5">
|
||
<i data-lucide="calendar" class="w-2.5 h-2.5"></i>
|
||
<span>OUTLOOK</span>
|
||
</span>
|
||
<?php endif; ?>
|
||
<span class="text-xs font-medium truncate <?= $task['is_completed'] ? 'line-through text-slate-400 dark:text-slate-500' : 'text-slate-800 dark:text-slate-200' ?>">
|
||
<?= htmlspecialchars($task['title']) ?>
|
||
</span>
|
||
</div>
|
||
|
||
<div class="flex items-center space-x-2 mt-0.5">
|
||
<?php if (!empty($task['time_block_start'])): ?>
|
||
<span class="text-[10px] text-slate-500 dark:text-slate-400 font-mono flex items-center space-x-1">
|
||
<i data-lucide="clock" class="w-2.5 h-2.5"></i>
|
||
<span><?= htmlspecialchars($task['time_block_start']) ?><?= !empty($task['time_block_end']) ? ' - ' . htmlspecialchars($task['time_block_end']) : '' ?></span>
|
||
</span>
|
||
<?php endif; ?>
|
||
<?php if (!empty($task['external_url'])): ?>
|
||
<a href="<?= htmlspecialchars($task['external_url']) ?>" target="_blank" class="text-[10px] <?= ($task['source'] === 'OUTLOOK' || !empty($task['external_id'])) ? 'text-blue-600 dark:text-blue-400' : 'text-sky-600 dark:text-sky-400' ?> hover:underline flex items-center space-x-0.5">
|
||
<span><?= ($task['source'] === 'OUTLOOK' || !empty($task['external_id'])) ? 'Open in Outlook' : 'Open in Jira' ?></span>
|
||
<i data-lucide="external-link" class="w-2.5 h-2.5"></i>
|
||
</a>
|
||
<?php endif; ?>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="flex items-center space-x-2 flex-shrink-0">
|
||
<span class="text-[10px] font-bold px-2 py-0.5 rounded-full <?= $task['priority'] === 'P1' ? 'bg-red-500/15 text-red-700 dark:text-red-400 border border-red-500/30' : ($task['priority'] === 'P2' ? 'bg-amber-500/15 text-amber-700 dark:text-amber-400 border border-amber-500/30' : 'bg-slate-200 dark:bg-slate-800 text-slate-600 dark:text-slate-400') ?>">
|
||
<?= htmlspecialchars($task['priority']) ?>
|
||
</span>
|
||
|
||
<?php if ($isOutlookConnected && empty($task['external_id']) && !$task['is_completed']): ?>
|
||
<button onclick="syncTaskToOutlook(<?= $task['id'] ?>, this)" title="Direct synchroniseren naar Outlook Agenda" class="p-1.5 rounded-lg text-slate-400 hover:text-blue-600 dark:hover:text-blue-400 hover:bg-blue-500/10 transition">
|
||
<i data-lucide="calendar-plus" class="w-3.5 h-3.5"></i>
|
||
</button>
|
||
<?php endif; ?>
|
||
|
||
<?php if (!$task['is_completed']): ?>
|
||
<button onclick="selectFocusTask('<?= htmlspecialchars(addslashes($task['title'])) ?>')" title="Koppel aan Focus Timer" class="p-1.5 rounded-lg text-slate-400 hover:text-emerald-600 dark:hover:text-emerald-400 hover:bg-emerald-500/10 transition">
|
||
<i data-lucide="flame" class="w-3.5 h-3.5"></i>
|
||
</button>
|
||
<?php endif; ?>
|
||
|
||
<button onclick="deleteTask(<?= $task['id'] ?>)" title="Verwijderen" class="p-1.5 rounded-lg text-slate-400 hover:text-red-600 dark:hover:text-red-400 hover:bg-red-500/10 opacity-0 group-hover:opacity-100 transition">
|
||
<i data-lucide="trash-2" class="w-3.5 h-3.5"></i>
|
||
</button>
|
||
</div>
|
||
</div>
|
||
<?php endforeach; ?>
|
||
<?php endif; ?>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Jira Snelle Import Widget -->
|
||
<div id="jiraPanel" class="bg-white dark:bg-slate-900/80 border border-slate-200 dark:border-slate-800 rounded-3xl p-6 shadow-sm dark:shadow-xl backdrop-blur-sm transition">
|
||
<div class="flex items-center justify-between pb-3 border-b border-slate-100 dark:border-slate-800 mb-3">
|
||
<div class="flex items-center space-x-2">
|
||
<i data-lucide="trello" class="w-4 h-4 text-sky-500"></i>
|
||
<h3 class="text-sm font-bold text-slate-900 dark:text-white uppercase tracking-wider">
|
||
Mijn Jira Tickets
|
||
</h3>
|
||
<span class="px-2 py-0.5 rounded-full text-[10px] font-bold <?= $isJiraLive ? 'bg-sky-500/15 text-sky-700 dark:text-sky-400 border border-sky-500/30' : 'bg-slate-100 dark:bg-slate-800 text-slate-600 dark:text-slate-400' ?>">
|
||
<?= count($jiraIssues) ?>
|
||
</span>
|
||
</div>
|
||
|
||
<a href="/profile.php" class="text-xs text-slate-500 dark:text-slate-400 hover:text-sky-600 dark:hover:text-sky-300 transition flex items-center space-x-1">
|
||
<span>Beheren</span>
|
||
<i data-lucide="chevron-right" class="w-3.5 h-3.5"></i>
|
||
</a>
|
||
</div>
|
||
|
||
<?php if (empty($jiraIssues)): ?>
|
||
<div class="text-center py-6 text-slate-400 dark:text-slate-500 text-xs">
|
||
Geen openstaande tickets gevonden in Jira.
|
||
</div>
|
||
<?php else: ?>
|
||
<div class="space-y-2 max-h-64 overflow-y-auto pr-1">
|
||
<?php foreach (array_slice($jiraIssues, 0, 6) as $issue): ?>
|
||
<div class="flex items-center justify-between p-2.5 rounded-2xl bg-slate-50 dark:bg-slate-950/60 border border-slate-200 dark:border-slate-800/80 hover:border-slate-300 dark:hover:border-slate-700 transition shadow-sm">
|
||
<div class="truncate mr-2">
|
||
<div class="flex items-center space-x-2">
|
||
<span class="font-mono text-[10px] font-bold text-sky-600 dark:text-sky-400 bg-sky-500/10 px-1.5 py-0.5 rounded">
|
||
<?= htmlspecialchars($issue['key']) ?>
|
||
</span>
|
||
<span class="text-xs font-semibold text-slate-900 dark:text-white truncate">
|
||
<?= htmlspecialchars($issue['summary']) ?>
|
||
</span>
|
||
</div>
|
||
<span class="text-[10px] text-slate-500 mt-0.5 block">
|
||
<?= htmlspecialchars($issue['status']) ?> • <?= htmlspecialchars($issue['priority']) ?>
|
||
</span>
|
||
</div>
|
||
|
||
<button
|
||
onclick="quickImportJira(this, '<?= htmlspecialchars(addslashes($issue['summary'])) ?>', '<?= htmlspecialchars($issue['key']) ?>', '<?= htmlspecialchars(addslashes($issue['url'] ?? '')) ?>')"
|
||
class="flex items-center space-x-1 text-[11px] font-bold text-emerald-700 dark:text-emerald-400 hover:text-emerald-800 dark:hover:text-emerald-300 bg-emerald-500/10 hover:bg-emerald-500/20 border border-emerald-500/20 px-2 py-1 rounded-lg transition flex-shrink-0 cursor-pointer"
|
||
title="Plan in als dagtaak"
|
||
>
|
||
<i data-lucide="plus" class="w-3 h-3"></i>
|
||
<span>Plan</span>
|
||
</button>
|
||
</div>
|
||
<?php endforeach; ?>
|
||
</div>
|
||
<?php endif; ?>
|
||
</div>
|
||
|
||
<!-- Dagnotities & Reflectie -->
|
||
<div class="bg-white dark:bg-slate-900/80 border border-slate-200 dark:border-slate-800 rounded-3xl p-6 shadow-sm dark:shadow-xl backdrop-blur-sm transition">
|
||
<div class="flex items-center space-x-2 mb-3">
|
||
<i data-lucide="book-open" class="w-4 h-4 text-emerald-600 dark:text-emerald-400"></i>
|
||
<h3 class="text-sm font-bold text-slate-900 dark:text-white uppercase tracking-wider">Dagnotities & Kladblok</h3>
|
||
</div>
|
||
<textarea
|
||
id="dailyNotesInput"
|
||
rows="3"
|
||
placeholder="Belangrijke aantekeningen, reflecties of snelle memo's voor vandaag..."
|
||
onblur="saveGoal()"
|
||
class="w-full bg-slate-50 dark:bg-slate-950/70 border border-slate-300 dark:border-slate-800 rounded-2xl p-3 text-slate-900 dark:text-white text-xs placeholder-slate-400 dark:placeholder-slate-500 focus:outline-none focus:ring-1 focus:ring-emerald-500 resize-none transition"
|
||
><?= htmlspecialchars($dailyPlan['notes'] ?? '') ?></textarea>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Rechter kolom: Timeblocking & Outlook Tijdlijn -->
|
||
<div class="lg:col-span-5 space-y-6 min-w-0">
|
||
<div class="bg-white dark:bg-slate-900/80 border border-slate-200 dark:border-slate-800 rounded-3xl p-5 sm:p-6 shadow-sm dark:shadow-xl backdrop-blur-sm transition overflow-hidden">
|
||
<div class="flex items-center justify-between mb-4 pb-3 border-b border-slate-100 dark:border-slate-800">
|
||
<div class="flex items-center space-x-2">
|
||
<i data-lucide="clock" class="w-5 h-5 text-emerald-600 dark:text-emerald-400"></i>
|
||
<h3 class="text-sm font-bold text-slate-900 dark:text-white uppercase tracking-wider">Tijdlijn & Time-blocking</h3>
|
||
</div>
|
||
<div class="flex items-center space-x-2">
|
||
<?php if ($isOutlookConnected): ?>
|
||
<span class="px-2 py-0.5 rounded bg-blue-500/10 border border-blue-500/20 text-blue-700 dark:text-blue-400 text-[10px] font-bold">Outlook 365</span>
|
||
<?php endif; ?>
|
||
<span class="text-xs text-slate-500 dark:text-slate-400 font-mono"><?= $hours[0] ?> – <?= end($hours) ?></span>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="space-y-2.5">
|
||
<?php foreach ($hours as $hour):
|
||
$hourInt = (int)explode(':', $hour)[0];
|
||
|
||
// Zoek Outlook meetings die binnen dit uur vallen (gefilterd op Fociz duplicaten)
|
||
$slotEvents = array_filter($displayedOutlookEvents, function($e) use ($hourInt) {
|
||
if (empty($e['start'])) return false;
|
||
return (int)explode(':', $e['start'])[0] === $hourInt;
|
||
});
|
||
|
||
// Zoek taken gepland in dit uur
|
||
$slotTasks = array_filter($tasks, function($t) use ($hourInt) {
|
||
return !empty($t['time_block_start']) && (int)explode(':', $t['time_block_start'])[0] === $hourInt;
|
||
});
|
||
?>
|
||
<div class="flex items-start group min-h-[50px] border-b border-slate-100 dark:border-slate-800/40 pb-2.5 last:border-0 min-w-0">
|
||
<div class="w-12 sm:w-14 text-xs font-mono font-medium text-slate-500 dark:text-slate-400 pt-1 flex-shrink-0">
|
||
<?= $hour ?>
|
||
</div>
|
||
|
||
<div class="flex-1 space-y-1.5 min-w-0">
|
||
<!-- Outlook meetings -->
|
||
<?php foreach ($slotEvents as $evt): ?>
|
||
<div class="p-2.5 rounded-2xl bg-blue-50/80 dark:bg-blue-950/50 border border-blue-200/80 dark:border-blue-800/60 text-blue-900 dark:text-blue-100 text-xs shadow-sm min-w-0 overflow-hidden">
|
||
<div class="flex items-start justify-between gap-2 min-w-0">
|
||
<div class="flex items-start space-x-2 min-w-0 flex-1">
|
||
<span class="px-1.5 py-0.5 rounded-lg bg-blue-500/15 text-blue-700 dark:text-blue-300 font-mono text-[10px] font-bold flex-shrink-0 whitespace-nowrap mt-0.5">
|
||
<?= htmlspecialchars($evt['start']) ?> - <?= htmlspecialchars($evt['end']) ?>
|
||
</span>
|
||
<span class="font-semibold text-xs leading-snug break-words line-clamp-2 min-w-0 flex-1" title="<?= htmlspecialchars($evt['subject']) ?>">
|
||
<?= htmlspecialchars($evt['subject']) ?>
|
||
</span>
|
||
</div>
|
||
<div class="flex items-center space-x-1 flex-shrink-0">
|
||
<?php if (!empty($evt['meeting_url'])): ?>
|
||
<a href="<?= htmlspecialchars($evt['meeting_url']) ?>" target="_blank" class="flex items-center space-x-1 text-[10px] text-white bg-blue-600 hover:bg-blue-700 px-2 py-0.5 rounded-lg flex-shrink-0 transition font-medium">
|
||
<i data-lucide="video" class="w-3 h-3"></i>
|
||
<span class="hidden sm:inline">Deelnemen</span>
|
||
</a>
|
||
<?php elseif (!empty($evt['web_link'])): ?>
|
||
<a href="<?= htmlspecialchars($evt['web_link']) ?>" target="_blank" class="flex items-center space-x-1 text-[10px] text-blue-600 dark:text-blue-300 hover:underline px-1 py-0.5 transition font-medium">
|
||
<i data-lucide="external-link" class="w-3 h-3"></i>
|
||
<span class="hidden sm:inline">Outlook</span>
|
||
</a>
|
||
<?php endif; ?>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<?php endforeach; ?>
|
||
|
||
<!-- Geplande taken -->
|
||
<?php foreach ($slotTasks as $t): ?>
|
||
<div class="p-2.5 rounded-2xl border text-xs min-w-0 overflow-hidden <?= $t['is_completed'] ? 'bg-slate-100/70 dark:bg-slate-900/40 border-slate-200 dark:border-slate-800 text-slate-400 dark:text-slate-500 line-through' : 'bg-emerald-50/80 dark:bg-emerald-950/30 border-emerald-200/80 dark:border-emerald-800/40 text-emerald-900 dark:text-emerald-100 shadow-sm' ?>">
|
||
<div class="flex items-start justify-between gap-2 min-w-0">
|
||
<div class="flex items-start space-x-2 min-w-0 flex-1">
|
||
<span class="px-1.5 py-0.5 rounded-lg bg-emerald-500/15 text-emerald-700 dark:text-emerald-300 font-mono text-[10px] font-bold flex-shrink-0 whitespace-nowrap mt-0.5">
|
||
<?= htmlspecialchars($t['time_block_start']) ?><?= !empty($t['time_block_end']) ? ' - ' . htmlspecialchars($t['time_block_end']) : '' ?>
|
||
</span>
|
||
<span class="font-semibold text-xs leading-snug break-words line-clamp-2 min-w-0 flex-1" title="<?= htmlspecialchars($t['title']) ?>">
|
||
<?= htmlspecialchars($t['title']) ?>
|
||
</span>
|
||
</div>
|
||
<div class="flex items-center space-x-1.5 flex-shrink-0">
|
||
<?php if (!empty($t['external_id']) && $t['source'] === 'OUTLOOK'): ?>
|
||
<span class="text-[9px] font-bold px-1.5 py-0.5 rounded bg-blue-500/15 text-blue-700 dark:text-blue-300 border border-blue-500/25 flex items-center space-x-1 whitespace-nowrap" title="In Outlook agenda">
|
||
<i data-lucide="calendar" class="w-2.5 h-2.5"></i>
|
||
<span class="hidden sm:inline">In Outlook</span>
|
||
</span>
|
||
<?php elseif ($isOutlookConnected && !$t['is_completed']): ?>
|
||
<button onclick="syncTaskToOutlook(<?= $t['id'] ?>, this)" title="Synchroniseer naar Outlook Agenda" class="text-[9px] font-bold px-1.5 py-0.5 rounded bg-blue-500/10 hover:bg-blue-500/20 text-blue-700 dark:text-blue-300 border border-blue-500/20 flex items-center space-x-1 transition cursor-pointer whitespace-nowrap">
|
||
<i data-lucide="calendar-plus" class="w-2.5 h-2.5"></i>
|
||
<span class="hidden sm:inline">Naar Outlook</span>
|
||
</button>
|
||
<?php endif; ?>
|
||
<span class="text-[10px] font-bold px-1.5 py-0.5 rounded bg-emerald-500/20 text-emerald-700 dark:text-emerald-400 flex-shrink-0">
|
||
<?= $t['priority'] ?>
|
||
</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<?php endforeach; ?>
|
||
|
||
<?php if (empty($slotEvents) && empty($slotTasks)): ?>
|
||
<button onclick="quickSchedule('<?= $hour ?>')" class="w-full text-left py-1 px-2 rounded-lg border border-dashed border-transparent hover:border-slate-300 dark:hover:border-slate-800 hover:bg-slate-50 dark:hover:bg-slate-950/40 text-slate-400 dark:text-slate-600 hover:text-slate-700 dark:hover:text-slate-400 text-xs transition cursor-pointer">
|
||
+ Plan taak om <?= $hour ?>
|
||
</button>
|
||
<?php endif; ?>
|
||
</div>
|
||
</div>
|
||
<?php endforeach; ?>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<script>
|
||
// --- Focus Timer Logica ---
|
||
let timerInterval = null;
|
||
let timerSeconds = 25 * 60;
|
||
let totalDuration = 25 * 60;
|
||
let isRunning = false;
|
||
|
||
function updateTimerDisplay() {
|
||
const min = Math.floor(timerSeconds / 60);
|
||
const sec = timerSeconds % 60;
|
||
document.getElementById('timerDisplay').innerText =
|
||
`${String(min).padStart(2, '0')}:${String(sec).padStart(2, '0')}`;
|
||
|
||
const progress = ((totalDuration - timerSeconds) / totalDuration) * 100;
|
||
document.getElementById('timerProgress').style.width = `${progress}%`;
|
||
}
|
||
|
||
function setTimerPreset(minutes) {
|
||
clearInterval(timerInterval);
|
||
isRunning = false;
|
||
timerSeconds = minutes * 60;
|
||
totalDuration = minutes * 60;
|
||
updateTimerDisplay();
|
||
|
||
document.getElementById('timerToggleText').innerText = 'Start Focus';
|
||
const inactiveClass = 'px-2.5 py-1 rounded-lg text-slate-600 dark:text-slate-400 hover:text-slate-900 dark:hover:text-slate-200 transition';
|
||
const activeClass = 'px-2.5 py-1 rounded-lg font-bold bg-emerald-500 text-slate-950 transition';
|
||
document.getElementById('btnPreset25').className = minutes === 25 ? activeClass : inactiveClass;
|
||
document.getElementById('btnPreset50').className = minutes === 50 ? activeClass : inactiveClass;
|
||
document.getElementById('btnPreset5').className = minutes === 5 ? activeClass : inactiveClass;
|
||
}
|
||
|
||
function toggleTimer() {
|
||
if (isRunning) {
|
||
clearInterval(timerInterval);
|
||
isRunning = false;
|
||
document.getElementById('timerToggleText').innerText = 'Hervatten';
|
||
} else {
|
||
isRunning = true;
|
||
document.getElementById('timerToggleText').innerText = 'Pauzeren';
|
||
timerInterval = setInterval(() => {
|
||
if (timerSeconds > 0) {
|
||
timerSeconds--;
|
||
updateTimerDisplay();
|
||
} else {
|
||
clearInterval(timerInterval);
|
||
isRunning = false;
|
||
document.getElementById('timerToggleText').innerText = 'Start Focus';
|
||
alert('Focus sessie voltooid! Neem even een korte pauze.');
|
||
}
|
||
}, 1000);
|
||
}
|
||
}
|
||
|
||
function resetTimer() {
|
||
clearInterval(timerInterval);
|
||
isRunning = false;
|
||
timerSeconds = totalDuration;
|
||
document.getElementById('timerToggleText').innerText = 'Start Focus';
|
||
updateTimerDisplay();
|
||
}
|
||
|
||
function selectFocusTask(title) {
|
||
document.getElementById('activeTaskDisplay').innerText = title;
|
||
}
|
||
|
||
// --- Dagdoel & Taken AJAX ---
|
||
async function saveGoal() {
|
||
const focusGoal = document.getElementById('focusGoalInput').value;
|
||
const notes = document.getElementById('dailyNotesInput').value;
|
||
const btn = document.getElementById('saveGoalBtn');
|
||
|
||
btn.innerText = 'Opslaan...';
|
||
try {
|
||
await fetch('/api/planner.php?action=save_goal', {
|
||
method: 'POST',
|
||
headers: {'Content-Type': 'application/json'},
|
||
body: JSON.stringify({ focus_goal: focusGoal, notes: notes, date: '<?= $today ?>' })
|
||
});
|
||
btn.innerHTML = '<i data-lucide="check" class="w-4 h-4"></i><span>Opgeslagen</span>';
|
||
if (typeof lucide !== 'undefined') lucide.createIcons();
|
||
setTimeout(() => {
|
||
btn.innerHTML = '<i data-lucide="check-circle-2" class="w-4 h-4"></i><span>Opslaan</span>';
|
||
if (typeof lucide !== 'undefined') lucide.createIcons();
|
||
}, 1500);
|
||
} catch(err) {
|
||
btn.innerText = 'Fout';
|
||
}
|
||
}
|
||
|
||
function toggleAddTaskForm() {
|
||
const form = document.getElementById('addTaskForm');
|
||
form.classList.toggle('hidden');
|
||
if (!form.classList.contains('hidden')) {
|
||
document.getElementById('newTaskTitle').focus();
|
||
}
|
||
}
|
||
|
||
function quickSchedule(hour) {
|
||
toggleAddTaskForm();
|
||
const timeInput = document.getElementById('newTaskTime');
|
||
if (timeInput) timeInput.value = hour;
|
||
const outlookCheckbox = document.getElementById('newTaskSyncOutlook');
|
||
if (outlookCheckbox) outlookCheckbox.checked = true;
|
||
}
|
||
|
||
async function submitNewTask() {
|
||
const title = document.getElementById('newTaskTitle').value.trim();
|
||
const priority = document.getElementById('newTaskPriority').value;
|
||
const time = document.getElementById('newTaskTime').value.trim();
|
||
const duration = document.getElementById('newTaskDuration') ? parseInt(document.getElementById('newTaskDuration').value) : 30;
|
||
const syncOutlook = document.getElementById('newTaskSyncOutlook') ? document.getElementById('newTaskSyncOutlook').checked : false;
|
||
|
||
if (!title) return;
|
||
|
||
try {
|
||
const res = await fetch('/api/planner.php?action=add_task', {
|
||
method: 'POST',
|
||
headers: {'Content-Type': 'application/json'},
|
||
body: JSON.stringify({
|
||
title,
|
||
priority,
|
||
time_block_start: time,
|
||
duration_minutes: duration,
|
||
sync_to_outlook: syncOutlook,
|
||
date: '<?= $today ?>'
|
||
})
|
||
});
|
||
const data = await res.json();
|
||
if (data.success) {
|
||
location.reload();
|
||
} else {
|
||
alert(data.error || 'Fout bij opslaan van de taak');
|
||
}
|
||
} catch(err) {
|
||
alert('Netwerkfout bij opslaan van de taak');
|
||
}
|
||
}
|
||
|
||
async function syncTaskToOutlook(taskId, btn) {
|
||
if (!btn) return;
|
||
const originalHtml = btn.innerHTML;
|
||
btn.innerHTML = '<span class="text-[9px] animate-pulse font-medium">Syncen...</span>';
|
||
btn.disabled = true;
|
||
|
||
try {
|
||
const res = await fetch('/api/planner.php?action=sync_task_to_outlook', {
|
||
method: 'POST',
|
||
headers: {'Content-Type': 'application/json'},
|
||
body: JSON.stringify({ task_id: taskId })
|
||
});
|
||
const data = await res.json();
|
||
if (data.success) {
|
||
btn.innerHTML = '<i data-lucide="check" class="w-3.5 h-3.5 text-emerald-500"></i>';
|
||
if (typeof lucide !== 'undefined') lucide.createIcons();
|
||
setTimeout(() => {
|
||
location.reload();
|
||
}, 600);
|
||
} else {
|
||
alert(data.error || 'Kon taak niet synchroniseren naar Outlook');
|
||
btn.innerHTML = originalHtml;
|
||
btn.disabled = false;
|
||
if (typeof lucide !== 'undefined') lucide.createIcons();
|
||
}
|
||
} catch(err) {
|
||
alert('Netwerkfout bij synchroniseren naar Outlook');
|
||
btn.innerHTML = originalHtml;
|
||
btn.disabled = false;
|
||
if (typeof lucide !== 'undefined') lucide.createIcons();
|
||
}
|
||
}
|
||
|
||
async function quickImportJira(btn, title, jiraKey, url) {
|
||
const originalHtml = btn.innerHTML;
|
||
btn.innerText = '...';
|
||
btn.disabled = true;
|
||
|
||
try {
|
||
const res = await fetch('/api/planner.php?action=add_task', {
|
||
method: 'POST',
|
||
headers: {'Content-Type': 'application/json'},
|
||
body: JSON.stringify({
|
||
title: `[${jiraKey}] ${title}`,
|
||
priority: 'P1',
|
||
source: 'JIRA',
|
||
external_id: jiraKey,
|
||
external_url: url,
|
||
date: '<?= $today ?>'
|
||
})
|
||
});
|
||
const data = await res.json();
|
||
if (data.success) {
|
||
btn.innerHTML = '<i data-lucide="check" class="w-3 h-3"></i>';
|
||
location.reload();
|
||
} else {
|
||
btn.innerHTML = originalHtml;
|
||
btn.disabled = false;
|
||
alert(data.error || 'Kon taak niet toevoegen');
|
||
}
|
||
} catch(err) {
|
||
btn.innerHTML = originalHtml;
|
||
btn.disabled = false;
|
||
alert('Netwerkfout bij toevoegen');
|
||
}
|
||
}
|
||
|
||
async function toggleTask(taskId, isCompleted) {
|
||
await fetch('/api/planner.php?action=toggle_task', {
|
||
method: 'POST',
|
||
headers: {'Content-Type': 'application/json'},
|
||
body: JSON.stringify({ task_id: taskId, is_completed: isCompleted })
|
||
});
|
||
location.reload();
|
||
}
|
||
|
||
async function deleteTask(taskId) {
|
||
if (!confirm('Taak verwijderen? (Indien gekoppeld aan Outlook wordt ook de agenda-afspraak verwijderd)')) return;
|
||
await fetch('/api/planner.php?action=delete_task', {
|
||
method: 'POST',
|
||
headers: {'Content-Type': 'application/json'},
|
||
body: JSON.stringify({ task_id: taskId })
|
||
});
|
||
document.getElementById(`task-row-${taskId}`)?.remove();
|
||
}
|
||
</script>
|
||
|
||
<?php require_once __DIR__ . '/includes/footer.php'; ?>
|