Files
Fociz/roadmap.php

974 lines
54 KiB
PHP
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
/**
* Fociz Roadmap & Tijdlijn
*/
require_once __DIR__ . '/includes/header.php';
$db = get_db_connection();
$selectedQuarter = $_GET['quarter'] ?? 'ALL';
$selectedStatus = $_GET['status'] ?? 'ALL';
$sql = "
SELECT r.*, u.name as creator_name, c.content as brainstorm_snippet
FROM roadmap_items r
JOIN users u ON r.created_by_id = u.id
LEFT JOIN brainstorm_cards c ON r.origin_brainstorm_card_id = c.id
WHERE 1=1
";
$params = [];
if ($selectedQuarter !== 'ALL') {
$sql .= " AND r.quarter = ?";
$params[] = $selectedQuarter;
}
if ($selectedStatus !== 'ALL') {
$sql .= " AND r.status = ?";
$params[] = $selectedStatus;
}
$roadmapItems = [];
try {
$stmt = $db->prepare($sql);
$stmt->execute($params);
$roadmapItems = $stmt->fetchAll() ?: [];
} catch (Throwable $e) {
error_log("Fout bij ophalen roadmap items: " . $e->getMessage());
ensure_tables_exist($db);
}
$quarters = ['Q1', 'Q2', 'Q3', 'Q4'];
$currentUserId = (int)($currentUser['id'] ?? 0);
// Haal alle actieve teamleden op voor toewijzing & agendablokkades
$teamMembersStmt = $db->query("SELECT id, name, email, role, avatar_url FROM users ORDER BY name ASC");
$teamMembers = $teamMembersStmt->fetchAll();
$outlookUsers = [];
$intStmt = $db->query("SELECT user_id FROM integrations WHERE provider = 'OUTLOOK' AND is_enabled = 1 AND access_token IS NOT NULL");
foreach ($intStmt->fetchAll() as $row) {
$outlookUsers[(int)$row['user_id']] = true;
}
foreach ($teamMembers as &$tm) {
$tm['has_outlook'] = !empty($outlookUsers[(int)$tm['id']]);
}
unset($tm);
?>
<div class="space-y-6 max-w-7xl mx-auto">
<!-- Header -->
<div class="fociz-card p-6 sm:p-7 flex flex-col sm:flex-row sm:items-center justify-between gap-4">
<div>
<div class="flex items-center space-x-2 text-indigo-600 dark:text-indigo-400 text-xs font-bold uppercase tracking-wider mb-1">
<i data-lucide="map" class="w-4 h-4"></i>
<span>Planning & Strategie</span>
</div>
<h1 class="text-2xl sm:text-3xl font-black text-slate-900 dark:text-white tracking-tight">
Roadmap & Tijdlijn
</h1>
<p class="text-slate-500 dark:text-slate-400 text-xs sm:text-sm mt-1 max-w-2xl">
Centraal overzicht van alle mijlpalen, features en doelen, direct voortgekomen uit brainstorms en gesynchroniseerd met Jira.
</p>
</div>
<button onclick="document.getElementById('newItemModal').classList.remove('hidden')" class="flex items-center space-x-2 px-4 py-2.5 rounded-xl bg-indigo-600 hover:bg-indigo-500 text-white text-xs font-bold transition shadow-xs flex-shrink-0 cursor-pointer">
<i data-lucide="plus" class="w-4 h-4"></i>
<span>Nieuw Item</span>
</button>
</div>
<!-- Filters: Quarters & Status -->
<div class="fociz-card p-4 flex flex-wrap items-center justify-between gap-3">
<!-- Quarter Tabs (tevens drop targets) -->
<div class="flex items-center space-x-1 text-xs">
<a href="/roadmap.php?quarter=ALL&status=<?= htmlspecialchars($selectedStatus) ?>" class="px-3 py-1.5 rounded-lg transition font-semibold <?= $selectedQuarter === 'ALL' ? 'bg-indigo-600 text-white shadow-xs' : 'text-slate-600 hover:text-slate-900 dark:text-slate-400 dark:hover:text-slate-200 hover:bg-slate-100 dark:hover:bg-slate-800/80' ?>">
Alle Quarters
</a>
<?php foreach ($quarters as $q): ?>
<a
href="/roadmap.php?quarter=<?= $q ?>&status=<?= htmlspecialchars($selectedStatus) ?>"
class="roadmap-quarter-tab px-3.5 py-1.5 rounded-lg transition-all font-semibold <?= $selectedQuarter === $q ? 'bg-indigo-600 text-white shadow-xs' : 'text-slate-600 hover:text-slate-900 dark:text-slate-400 dark:hover:text-slate-200 hover:bg-slate-100 dark:hover:bg-slate-800/80' ?>"
data-quarter="<?= $q ?>"
ondragover="handleRoadmapQuarterDragOver(event)"
ondragleave="handleRoadmapQuarterDragLeave(event)"
ondrop="handleRoadmapQuarterDrop(event, '<?= $q ?>')"
title="Sleep een roadmap item hierheen om het kwartaal direct te wijzigen"
>
<?= $q ?>
</a>
<?php endforeach; ?>
</div>
<!-- Status Filter -->
<div class="flex items-center space-x-2 text-xs">
<span class="text-slate-500 dark:text-slate-400 font-semibold">Status:</span>
<select onchange="window.location.href='/roadmap.php?quarter=<?= htmlspecialchars($selectedQuarter) ?>&status='+this.value" class="bg-slate-50/70 dark:bg-[#0c0f17]/60 border border-slate-200 dark:border-slate-800 rounded-xl px-3 py-1.5 text-slate-900 dark:text-white text-xs font-semibold focus:outline-none focus:ring-1 focus:ring-indigo-500 shadow-2xs">
<option value="ALL" <?= $selectedStatus === 'ALL' ? 'selected' : '' ?>>Alle Statussen</option>
<option value="PLANNED" <?= $selectedStatus === 'PLANNED' ? 'selected' : '' ?>>Gepland</option>
<option value="IN_PROGRESS" <?= $selectedStatus === 'IN_PROGRESS' ? 'selected' : '' ?>>In Uitvoering</option>
<option value="REVIEW" <?= $selectedStatus === 'REVIEW' ? 'selected' : '' ?>>In Review</option>
<option value="COMPLETED" <?= $selectedStatus === 'COMPLETED' ? 'selected' : '' ?>>Voltooid</option>
</select>
</div>
</div>
<!-- Roadmap Items Lijst -->
<div class="space-y-4">
<?php if (empty($roadmapItems)): ?>
<div class="fociz-card p-12 text-center">
<i data-lucide="map-pin" class="w-12 h-12 text-slate-300 dark:text-slate-700 mx-auto mb-3"></i>
<h3 class="text-base font-bold text-slate-900 dark:text-white">Geen items gevonden op de roadmap</h3>
<p class="text-xs text-slate-500 dark:text-slate-400 mt-1 max-w-sm mx-auto">
Ga naar een <a href="/brainstorm.php" class="text-indigo-600 dark:text-indigo-400 underline font-medium">Brainstorm sessie</a> en zet ideeën met één klik om naar de roadmap, of voeg handmatig een item toe.
</p>
</div>
<?php else: ?>
<?php foreach ($roadmapItems as $item):
$statusBorder = match($item['status']) {
'COMPLETED' => 'border-l-4 border-l-emerald-500',
'IN_PROGRESS' => 'border-l-4 border-l-indigo-500',
'REVIEW' => 'border-l-4 border-l-amber-500',
default => 'border-l-4 border-l-slate-300 dark:border-l-slate-700',
};
?>
<div
id="roadmap-item-<?= $item['id'] ?>"
draggable="true"
ondragstart="handleRoadmapItemDragStart(event, <?= $item['id'] ?>)"
ondragend="handleRoadmapItemDragEnd(event)"
oncontextmenu="openRoadmapContextMenu(event, <?= $item['id'] ?>); return false;"
data-title="<?= htmlspecialchars($item['title'], ENT_QUOTES) ?>"
data-description="<?= htmlspecialchars($item['description'] ?? '', ENT_QUOTES) ?>"
data-quarter="<?= htmlspecialchars($item['quarter']) ?>"
data-type="<?= htmlspecialchars($item['type']) ?>"
data-priority="<?= htmlspecialchars($item['priority']) ?>"
data-status="<?= htmlspecialchars($item['status']) ?>"
data-due-date="<?= htmlspecialchars($item['due_date'] ?? '') ?>"
data-jira-key="<?= htmlspecialchars($item['jira_issue_key'] ?? '') ?>"
class="fociz-card-interactive p-5 space-y-3.5 <?= $statusBorder ?> cursor-grab active:cursor-grabbing transition-all duration-150"
title="Sleep naar een ander kwartaal (Q1, Q2, Q3, Q4) of klik met rechtermuisknop"
>
<!-- Top header van item -->
<div class="flex flex-col sm:flex-row sm:items-center justify-between gap-2">
<div class="flex items-center space-x-2.5 flex-wrap">
<!-- Quarter badge -->
<span class="px-2.5 py-0.5 rounded-lg bg-indigo-50 dark:bg-indigo-500/10 text-indigo-700 dark:text-indigo-300 font-mono text-xs font-bold border border-indigo-200 dark:border-indigo-500/20">
<?= htmlspecialchars($item['quarter']) ?>
</span>
<!-- Type badge -->
<span class="px-2 py-0.5 rounded-lg bg-slate-100 dark:bg-slate-800 text-slate-700 dark:text-slate-300 text-[10px] font-semibold border border-slate-200 dark:border-slate-700">
<?= htmlspecialchars($item['type']) ?>
</span>
<!-- Priority badge -->
<span class="text-[10px] font-bold px-2 py-0.5 rounded-full <?= $item['priority'] === 'CRITICAL' ? 'bg-rose-50 dark:bg-rose-500/10 text-rose-700 dark:text-rose-400 border border-rose-200 dark:border-rose-500/20' : ($item['priority'] === 'HIGH' ? 'bg-amber-50 dark:bg-amber-500/10 text-amber-700 dark:text-amber-400 border border-amber-200 dark:border-amber-500/20' : 'bg-slate-100 dark:bg-slate-800 text-slate-600 dark:text-slate-400 border border-slate-200 dark:border-slate-700') ?>">
<?= htmlspecialchars($item['priority']) ?>
</span>
<!-- Jira Issue Key link -->
<?php if (!empty($item['jira_issue_key'])): ?>
<a href="/profile.php" class="flex items-center space-x-1 px-2.5 py-0.5 rounded-lg bg-sky-50 dark:bg-sky-500/10 border border-sky-200 dark:border-sky-500/20 text-sky-700 dark:text-sky-400 text-[10px] font-mono hover:underline">
<i data-lucide="layers" class="w-3 h-3"></i>
<span><?= htmlspecialchars($item['jira_issue_key']) ?></span>
</a>
<?php endif; ?>
</div>
<!-- Status Selector -->
<div class="flex items-center space-x-2">
<select onchange="updateRoadmapStatus(<?= $item['id'] ?>, this.value)" class="bg-slate-50/70 dark:bg-[#0c0f17]/60 border border-slate-200 dark:border-slate-800 rounded-xl px-2.5 py-1 text-xs text-slate-800 dark:text-slate-200 font-bold focus:outline-none focus:ring-1 focus:ring-indigo-500 shadow-2xs">
<option value="PLANNED" <?= $item['status'] === 'PLANNED' ? 'selected' : '' ?>>⏳ Gepland</option>
<option value="IN_PROGRESS" <?= $item['status'] === 'IN_PROGRESS' ? 'selected' : '' ?>>⚡ In Uitvoering</option>
<option value="REVIEW" <?= $item['status'] === 'REVIEW' ? 'selected' : '' ?>>🔍 In Review</option>
<option value="COMPLETED" <?= $item['status'] === 'COMPLETED' ? 'selected' : '' ?>>✅ Voltooid</option>
</select>
<button onclick="deleteRoadmapItem(<?= $item['id'] ?>)" title="Verwijderen" class="p-1.5 rounded-lg text-slate-400 hover:text-rose-600 dark:text-slate-500 dark:hover:text-rose-400 hover:bg-rose-500/10 transition cursor-pointer">
<i data-lucide="trash-2" class="w-4 h-4"></i>
</button>
</div>
</div>
<!-- Titel & Beschrijving -->
<div>
<h3 class="text-base font-bold text-slate-900 dark:text-white leading-snug">
<?= htmlspecialchars($item['title']) ?>
</h3>
<?php if (!empty($item['description'])): ?>
<p class="text-slate-600 dark:text-slate-400 text-xs mt-1 whitespace-pre-wrap leading-relaxed">
<?= htmlspecialchars($item['description']) ?>
</p>
<?php endif; ?>
</div>
<!-- Origin Brainstorm snippet -->
<?php if (!empty($item['origin_brainstorm_card_id'])): ?>
<div class="bg-slate-50/70 dark:bg-[#0c0f17]/60 rounded-xl p-2.5 text-[11px] text-slate-600 dark:text-slate-400 flex items-center space-x-2 border border-slate-200/80 dark:border-slate-800">
<i data-lucide="lightbulb" class="w-3.5 h-3.5 text-amber-500 flex-shrink-0"></i>
<span class="truncate">Ontstaan uit brainstormsessie</span>
</div>
<?php endif; ?>
<!-- Voortgangsbalk & Deadline -->
<div class="pt-2.5 border-t border-slate-100 dark:border-slate-800/60 flex flex-col sm:flex-row sm:items-center justify-between gap-3 text-xs">
<!-- Progress Slider -->
<div class="flex items-center space-x-3 flex-1 max-w-md">
<span class="text-slate-500 dark:text-slate-400 text-[11px] font-semibold w-16">Voortgang:</span>
<div class="flex-1 bg-slate-100 dark:bg-slate-900 h-2.5 rounded-full overflow-hidden border border-slate-200 dark:border-slate-800">
<div class="bg-gradient-to-r from-indigo-600 to-sky-500 h-full transition-all" style="width: <?= $item['progress'] ?>%"></div>
</div>
<span class="font-mono text-slate-800 dark:text-slate-200 font-bold w-10 text-right"><?= $item['progress'] ?>%</span>
<div class="flex space-x-1">
<button onclick="updateRoadmapProgress(<?= $item['id'] ?>, 25)" class="px-2 py-0.5 rounded-lg bg-slate-100 dark:bg-slate-800 text-[10px] text-slate-600 dark:text-slate-400 hover:text-slate-900 dark:hover:text-white cursor-pointer font-semibold transition">25%</button>
<button onclick="updateRoadmapProgress(<?= $item['id'] ?>, 50)" class="px-2 py-0.5 rounded-lg bg-slate-100 dark:bg-slate-800 text-[10px] text-slate-600 dark:text-slate-400 hover:text-slate-900 dark:hover:text-white cursor-pointer font-semibold transition">50%</button>
<button onclick="updateRoadmapProgress(<?= $item['id'] ?>, 100)" class="px-2 py-0.5 rounded-lg bg-indigo-50 dark:bg-indigo-500/15 text-[10px] text-indigo-700 dark:text-indigo-400 hover:text-indigo-800 dark:hover:text-indigo-300 font-bold cursor-pointer transition">100%</button>
</div>
</div>
<!-- Deadline & Creator -->
<div class="flex items-center space-x-3 text-slate-500 text-[11px]">
<?php if (!empty($item['due_date'])): ?>
<span class="flex items-center space-x-1 text-slate-700 dark:text-slate-300 font-semibold">
<i data-lucide="calendar" class="w-3.5 h-3.5 text-indigo-600 dark:text-indigo-400"></i>
<span>Deadline: <?= date('d-m-Y', strtotime($item['due_date'])) ?></span>
</span>
<?php endif; ?>
<span>Toegevoegd door <?= htmlspecialchars($item['creator_name']) ?></span>
</div>
</div>
</div>
<?php endforeach; ?>
<?php endif; ?>
</div>
</div>
<!-- Modal: Nieuw Roadmap Item Handmatig Toevoegen -->
<div id="newItemModal" onclick="if(event.target === this) this.classList.add('hidden')" class="hidden fixed inset-0 z-50 bg-slate-950/70 backdrop-blur-sm flex items-center justify-center p-4">
<div class="fociz-card p-6 sm:p-7 shadow-2xl max-w-lg w-full space-y-4">
<div class="flex items-center justify-between pb-3 border-b border-slate-100 dark:border-slate-800">
<h3 class="text-sm font-bold text-slate-900 dark:text-white flex items-center space-x-2">
<i data-lucide="map" class="w-4 h-4 text-indigo-600 dark:text-indigo-400"></i>
<span>Nieuw Roadmap Item Toevoegen</span>
</h3>
<button onclick="document.getElementById('newItemModal').classList.add('hidden')" class="p-1.5 text-slate-400 hover:text-slate-600 dark:hover:text-white rounded-lg hover:bg-slate-100 dark:hover:bg-slate-800 transition cursor-pointer">
<i data-lucide="x" class="w-4 h-4"></i>
</button>
</div>
<div class="space-y-4">
<div>
<label class="block text-xs font-semibold text-slate-700 dark:text-slate-300 mb-1">Titel / Doel</label>
<input type="text" id="manualTitle" placeholder="Bijv. 2FA Authenticatie afronden" class="w-full bg-slate-50/70 dark:bg-[#0c0f17]/60 border border-slate-300 dark:border-slate-800 rounded-xl px-3.5 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-indigo-500 shadow-2xs">
</div>
<div>
<label class="block text-xs font-semibold text-slate-700 dark:text-slate-300 mb-1">Beschrijving / Specificatie</label>
<textarea id="manualDescription" rows="3" placeholder="Wat houdt deze milestone of feature in?" class="w-full bg-slate-50/70 dark:bg-[#0c0f17]/60 border border-slate-300 dark:border-slate-800 rounded-xl 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-indigo-500 resize-none shadow-2xs"></textarea>
</div>
<div class="grid grid-cols-2 gap-3">
<div>
<label class="block text-xs font-semibold text-slate-700 dark:text-slate-300 mb-1">Kwartaal</label>
<select id="manualQuarter" class="w-full bg-slate-50/70 dark:bg-[#0c0f17]/60 border border-slate-300 dark:border-slate-800 rounded-xl px-3 py-2 text-slate-900 dark:text-white text-xs focus:outline-none focus:ring-1 focus:ring-indigo-500 shadow-2xs">
<option value="Q1">Q1</option>
<option value="Q2">Q2</option>
<option value="Q3" selected>Q3</option>
<option value="Q4">Q4</option>
</select>
</div>
<div>
<label class="block text-xs font-semibold text-slate-700 dark:text-slate-300 mb-1">Type</label>
<select id="manualType" class="w-full bg-slate-50/70 dark:bg-[#0c0f17]/60 border border-slate-300 dark:border-slate-800 rounded-xl px-3 py-2 text-slate-900 dark:text-white text-xs focus:outline-none focus:ring-1 focus:ring-indigo-500 shadow-2xs">
<option value="FEATURE" selected>Feature</option>
<option value="EPIC">Epic</option>
<option value="MILESTONE">Mijlpaal</option>
<option value="TASK">Taak</option>
</select>
</div>
</div>
<div class="grid grid-cols-2 gap-3">
<div>
<label class="block text-xs font-semibold text-slate-700 dark:text-slate-300 mb-1">Prioriteit</label>
<select id="manualPriority" class="w-full bg-slate-50/70 dark:bg-[#0c0f17]/60 border border-slate-300 dark:border-slate-800 rounded-xl px-3 py-2 text-slate-900 dark:text-white text-xs focus:outline-none focus:ring-1 focus:ring-indigo-500 shadow-2xs">
<option value="CRITICAL">Kritiek</option>
<option value="HIGH" selected>Hoog</option>
<option value="MEDIUM">Normaal</option>
<option value="LOW">Laag</option>
</select>
</div>
<div>
<label class="block text-xs font-semibold text-slate-700 dark:text-slate-300 mb-1">Jira Issue Key (optioneel)</label>
<input type="text" id="manualJiraKey" placeholder="Bijv. FOC-101" class="w-full bg-slate-50/70 dark:bg-[#0c0f17]/60 border border-slate-300 dark:border-slate-800 rounded-xl px-3 py-1.5 text-slate-900 dark:text-white text-xs font-mono focus:outline-none focus:ring-1 focus:ring-indigo-500 shadow-2xs">
</div>
</div>
<div>
<label class="block text-xs font-semibold text-slate-700 dark:text-slate-300 mb-1">Streefdatum</label>
<input type="date" id="manualDueDate" class="w-full bg-slate-50/70 dark:bg-[#0c0f17]/60 border border-slate-300 dark:border-slate-800 rounded-xl px-3 py-1.5 text-slate-900 dark:text-white text-xs focus:outline-none focus:ring-1 focus:ring-indigo-500 shadow-2xs">
</div>
<div class="flex space-x-2 pt-2 border-t border-slate-100 dark:border-slate-800/80">
<button onclick="submitManualItem()" class="flex-1 bg-indigo-600 hover:bg-indigo-500 text-white font-bold py-2.5 px-4 rounded-xl text-xs transition cursor-pointer shadow-xs">
Toevoegen aan Roadmap
</button>
<button onclick="document.getElementById('newItemModal').classList.add('hidden')" class="px-4 py-2.5 bg-slate-100 hover:bg-slate-200 dark:bg-slate-800 dark:hover:bg-slate-700 text-slate-700 dark:text-slate-300 rounded-xl text-xs font-semibold transition cursor-pointer">
Annuleren
</button>
</div>
</div>
</div>
</div>
<!-- Modal: Roadmap Item Bewerken -->
<div id="editRoadmapModal" onclick="if(event.target === this) closeEditRoadmapModal()" class="hidden fixed inset-0 z-50 bg-slate-950/70 backdrop-blur-sm flex items-center justify-center p-4">
<div class="fociz-card p-6 sm:p-7 shadow-2xl max-w-lg w-full space-y-4">
<div class="flex items-center justify-between pb-3 border-b border-slate-100 dark:border-slate-800">
<h3 class="text-sm font-bold text-slate-900 dark:text-white flex items-center space-x-2">
<i data-lucide="edit-3" class="w-4 h-4 text-indigo-600 dark:text-indigo-400"></i>
<span>Roadmap Item Bewerken</span>
</h3>
<button onclick="closeEditRoadmapModal()" class="p-1.5 text-slate-400 hover:text-slate-600 dark:hover:text-white rounded-lg hover:bg-slate-100 dark:hover:bg-slate-800 transition cursor-pointer">
<i data-lucide="x" class="w-4 h-4"></i>
</button>
</div>
<input type="hidden" id="editRoadmapId">
<div class="space-y-4">
<div>
<label class="block text-xs font-semibold text-slate-700 dark:text-slate-300 mb-1">Titel / Doel</label>
<input type="text" id="editRoadmapTitle" class="w-full bg-slate-50/70 dark:bg-[#0c0f17]/60 border border-slate-300 dark:border-slate-800 rounded-xl px-3.5 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-indigo-500 shadow-2xs">
</div>
<div>
<label class="block text-xs font-semibold text-slate-700 dark:text-slate-300 mb-1">Beschrijving</label>
<textarea id="editRoadmapDescription" rows="3" class="w-full bg-slate-50/70 dark:bg-[#0c0f17]/60 border border-slate-300 dark:border-slate-800 rounded-xl 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-indigo-500 resize-none shadow-2xs"></textarea>
</div>
<div class="grid grid-cols-2 gap-3">
<div>
<label class="block text-xs font-semibold text-slate-700 dark:text-slate-300 mb-1">Kwartaal</label>
<select id="editRoadmapQuarter" class="w-full bg-slate-50/70 dark:bg-[#0c0f17]/60 border border-slate-300 dark:border-slate-800 rounded-xl px-3 py-2 text-slate-900 dark:text-white text-xs focus:outline-none focus:ring-1 focus:ring-indigo-500 shadow-2xs">
<option value="Q1">Q1</option>
<option value="Q2">Q2</option>
<option value="Q3">Q3</option>
<option value="Q4">Q4</option>
</select>
</div>
<div>
<label class="block text-xs font-semibold text-slate-700 dark:text-slate-300 mb-1">Type</label>
<select id="editRoadmapType" class="w-full bg-slate-50/70 dark:bg-[#0c0f17]/60 border border-slate-300 dark:border-slate-800 rounded-xl px-3 py-2 text-slate-900 dark:text-white text-xs focus:outline-none focus:ring-1 focus:ring-indigo-500 shadow-2xs">
<option value="FEATURE">Feature</option>
<option value="EPIC">Epic</option>
<option value="MILESTONE">Mijlpaal</option>
<option value="TASK">Taak</option>
</select>
</div>
</div>
<div class="grid grid-cols-2 gap-3">
<div>
<label class="block text-xs font-semibold text-slate-700 dark:text-slate-300 mb-1">Prioriteit</label>
<select id="editRoadmapPriority" class="w-full bg-slate-50/70 dark:bg-[#0c0f17]/60 border border-slate-300 dark:border-slate-800 rounded-xl px-3 py-2 text-slate-900 dark:text-white text-xs focus:outline-none focus:ring-1 focus:ring-indigo-500 shadow-2xs">
<option value="CRITICAL">Kritiek</option>
<option value="HIGH">Hoog</option>
<option value="MEDIUM">Normaal</option>
<option value="LOW">Laag</option>
</select>
</div>
<div>
<label class="block text-xs font-semibold text-slate-700 dark:text-slate-300 mb-1">Status</label>
<select id="editRoadmapStatus" class="w-full bg-slate-50/70 dark:bg-[#0c0f17]/60 border border-slate-300 dark:border-slate-800 rounded-xl px-3 py-2 text-slate-900 dark:text-white text-xs focus:outline-none focus:ring-1 focus:ring-indigo-500 shadow-2xs">
<option value="PLANNED">Gepland</option>
<option value="IN_PROGRESS">In Uitvoering</option>
<option value="REVIEW">In Review</option>
<option value="COMPLETED">Voltooid</option>
</select>
</div>
</div>
<div class="grid grid-cols-2 gap-3">
<div>
<label class="block text-xs font-semibold text-slate-700 dark:text-slate-300 mb-1">Jira Issue Key</label>
<input type="text" id="editRoadmapJiraKey" class="w-full bg-slate-50/70 dark:bg-[#0c0f17]/60 border border-slate-300 dark:border-slate-800 rounded-xl px-3 py-1.5 text-slate-900 dark:text-white text-xs font-mono focus:outline-none focus:ring-1 focus:ring-indigo-500 shadow-2xs">
</div>
<div>
<label class="block text-xs font-semibold text-slate-700 dark:text-slate-300 mb-1">Streefdatum</label>
<input type="date" id="editRoadmapDueDate" class="w-full bg-slate-50/70 dark:bg-[#0c0f17]/60 border border-slate-300 dark:border-slate-800 rounded-xl px-3 py-1.5 text-slate-900 dark:text-white text-xs focus:outline-none focus:ring-1 focus:ring-indigo-500 shadow-2xs">
</div>
</div>
<div class="flex space-x-2 pt-2 border-t border-slate-100 dark:border-slate-800/80">
<button id="btnSaveEditRoadmap" onclick="submitEditRoadmapItem()" class="flex-1 bg-indigo-600 hover:bg-indigo-500 text-white font-bold py-2.5 px-4 rounded-xl text-xs transition cursor-pointer shadow-xs">
Opslaan
</button>
<button onclick="closeEditRoadmapModal()" class="px-4 py-2.5 bg-slate-100 hover:bg-slate-200 dark:bg-slate-800 dark:hover:bg-slate-700 text-slate-700 dark:text-slate-300 rounded-xl text-xs font-semibold transition cursor-pointer">
Annuleren
</button>
</div>
</div>
</div>
</div>
<!-- Modal: Roadmap Item Toewijzen & Inplannen in Vrij Blok -->
<div id="roadmapScheduleModal" onclick="if(event.target === this) closeRoadmapScheduleModal()" class="hidden fixed inset-0 z-50 bg-slate-950/70 backdrop-blur-sm flex items-center justify-center p-4">
<div class="fociz-card p-6 sm:p-7 shadow-2xl max-w-lg w-full space-y-4">
<div class="flex items-center justify-between pb-3 border-b border-slate-100 dark:border-slate-800">
<div class="flex items-center space-x-2">
<div class="w-8 h-8 rounded-xl bg-amber-500/20 text-amber-600 dark:text-amber-400 flex items-center justify-center">
<i data-lucide="calendar-plus" class="w-4 h-4"></i>
</div>
<div>
<h3 class="text-sm font-bold text-slate-900 dark:text-white">Inplannen in Agenda van Collega</h3>
<p class="text-[11px] text-slate-500 dark:text-slate-400">Plant dit item direct in een vrij tijdblok van de collega</p>
</div>
</div>
<button onclick="closeRoadmapScheduleModal()" class="p-1.5 text-slate-400 hover:text-slate-600 dark:hover:text-white rounded-lg hover:bg-slate-100 dark:hover:bg-slate-800 transition cursor-pointer">
<i data-lucide="x" class="w-4 h-4"></i>
</button>
</div>
<input type="hidden" id="roadmapScheduleItemId">
<div class="space-y-4">
<!-- Item titel preview -->
<div class="p-2.5 rounded-xl bg-slate-50 dark:bg-[#0c0f17] border border-slate-200 dark:border-slate-800 text-xs">
<span class="text-[10px] text-slate-400 font-bold uppercase tracking-wider block mb-0.5">Roadmap Item</span>
<span id="roadmapScheduleTitlePreview" class="font-bold text-slate-800 dark:text-slate-200"></span>
</div>
<!-- Collega Selector -->
<div>
<label class="block text-xs font-semibold text-slate-700 dark:text-slate-300 mb-1">Toewijzen aan collega</label>
<select id="roadmapAssigneeSelect" onchange="onRoadmapAssigneeChange()" class="w-full bg-slate-50/70 dark:bg-[#0c0f17]/60 border border-slate-300 dark:border-slate-800 rounded-xl px-3 py-2 text-slate-900 dark:text-white text-xs focus:outline-none focus:ring-1 focus:ring-indigo-500 shadow-2xs">
<?php foreach ($teamMembers as $tm): ?>
<option value="<?= $tm['id'] ?>" data-has-outlook="<?= $tm['has_outlook'] ? '1' : '0' ?>" <?= $tm['id'] === $currentUserId ? 'selected' : '' ?>>
<?= htmlspecialchars($tm['name']) ?> (<?= $tm['role'] === 'ADMIN' ? 'Beheerder' : 'Collega' ?>) <?= $tm['has_outlook'] ? '— 📅 Outlook Actief' : '' ?>
</option>
<?php endforeach; ?>
</select>
</div>
<!-- Outlook melding -->
<div id="roadmapAssignOutlookNotice" class="p-2 rounded-xl bg-sky-50 dark:bg-sky-950/40 border border-sky-200 dark:border-sky-800/60 flex items-center space-x-2 text-[11px] text-sky-700 dark:text-sky-300">
<i data-lucide="calendar-check" class="w-4 h-4 text-sky-500 flex-shrink-0"></i>
<span>Deze collega heeft Microsoft Outlook gekoppeld. Er wordt gekeken naar echte vrije blokken.</span>
</div>
<!-- Vrij Blok Zoeker -->
<div class="p-3 rounded-xl bg-amber-500/10 border border-amber-500/20 space-y-2">
<div class="flex items-center justify-between">
<div class="text-xs">
<span class="font-bold text-amber-900 dark:text-amber-200 flex items-center space-x-1">
<i data-lucide="sparkles" class="w-3.5 h-3.5 text-amber-500"></i>
<span>Automatisch vrij blok vinden</span>
</span>
<span class="text-[10px] text-amber-700 dark:text-amber-400">Zoekt binnen werktijden (09:00 - 17:00)</span>
</div>
<button type="button" id="btnRoadmapFindSlot" onclick="findFreeSlotForRoadmapAssignee()" class="px-3 py-1.5 rounded-lg bg-amber-500 hover:bg-amber-600 text-white font-bold text-xs shadow-xs transition flex items-center space-x-1.5 flex-shrink-0 cursor-pointer">
<i data-lucide="search" class="w-3.5 h-3.5"></i>
<span>Zoek vrij blok</span>
</button>
</div>
<div id="roadmapFoundSlotNotice" class="hidden p-2 rounded-lg bg-emerald-50 dark:bg-emerald-950/40 border border-emerald-300 dark:border-emerald-800 text-xs text-emerald-800 dark:text-emerald-300 font-medium flex items-center space-x-1.5">
<i data-lucide="check-circle" class="w-4 h-4 text-emerald-500 flex-shrink-0"></i>
<span id="roadmapFoundSlotText"></span>
</div>
</div>
<div class="grid grid-cols-2 gap-3">
<div>
<label class="block text-xs font-semibold text-slate-700 dark:text-slate-300 mb-1">Datum</label>
<input type="date" id="roadmapPlanDate" value="<?= date('Y-m-d') ?>" class="w-full bg-slate-50/70 dark:bg-[#0c0f17]/60 border border-slate-300 dark:border-slate-800 rounded-xl px-3 py-1.5 text-slate-900 dark:text-white text-xs focus:outline-none focus:ring-1 focus:ring-indigo-500 shadow-2xs">
</div>
<div>
<label class="block text-xs font-semibold text-slate-700 dark:text-slate-300 mb-1">Starttijd</label>
<input type="time" id="roadmapStartTime" value="10:00" class="w-full bg-slate-50/70 dark:bg-[#0c0f17]/60 border border-slate-300 dark:border-slate-800 rounded-xl px-3 py-1.5 text-slate-900 dark:text-white text-xs focus:outline-none focus:ring-1 focus:ring-indigo-500 shadow-2xs">
</div>
</div>
<div class="grid grid-cols-2 gap-3">
<div>
<label class="block text-xs font-semibold text-slate-700 dark:text-slate-300 mb-1">Duur</label>
<select id="roadmapDurationMinutes" class="w-full bg-slate-50/70 dark:bg-[#0c0f17]/60 border border-slate-300 dark:border-slate-800 rounded-xl px-3 py-2 text-slate-900 dark:text-white text-xs focus:outline-none">
<option value="30">30 minuten</option>
<option value="45">45 minuten</option>
<option value="60" selected>1 uur</option>
<option value="90">1.5 uur</option>
<option value="120">2 uur</option>
</select>
</div>
<div class="flex items-center pt-5">
<label class="flex items-center space-x-2 cursor-pointer">
<input type="checkbox" id="roadmapSyncOutlook" checked class="rounded text-indigo-600 focus:ring-indigo-500">
<span class="text-xs font-semibold text-slate-800 dark:text-slate-200">Sync met Outlook 365</span>
</label>
</div>
</div>
<div class="flex space-x-2 pt-2 border-t border-slate-100 dark:border-slate-800/80">
<button id="btnSubmitRoadmapSchedule" onclick="submitRoadmapSchedule()" class="flex-1 bg-gradient-to-r from-indigo-600 to-indigo-500 hover:from-indigo-500 hover:to-indigo-400 text-white font-bold py-2.5 px-4 rounded-xl text-xs transition cursor-pointer shadow-xs flex items-center justify-center space-x-1.5">
<i data-lucide="check-circle" class="w-4 h-4"></i>
<span>Inplannen in Agenda</span>
</button>
<button onclick="closeRoadmapScheduleModal()" class="px-4 py-2.5 bg-slate-100 hover:bg-slate-200 dark:bg-slate-800 dark:hover:bg-slate-700 text-slate-700 dark:text-slate-300 rounded-xl text-xs font-semibold transition cursor-pointer">
Annuleren
</button>
</div>
</div>
</div>
</div>
<!-- Rechtermuisknop Contextmenu voor Roadmap Items -->
<div id="roadmapContextMenu" class="hidden fixed z-50 w-56 bg-white dark:bg-slate-900 rounded-xl shadow-2xl border border-slate-200 dark:border-slate-800 py-1.5 text-xs text-slate-700 dark:text-slate-200">
<button onclick="triggerRoadmapCtxEdit()" class="w-full px-3.5 py-2 text-left flex items-center space-x-2.5 hover:bg-indigo-50 dark:hover:bg-indigo-950/50 hover:text-indigo-600 dark:hover:text-indigo-400 transition cursor-pointer">
<i data-lucide="edit-3" class="w-4 h-4 text-indigo-500"></i>
<span>Item bewerken</span>
</button>
<button onclick="triggerRoadmapCtxSchedule()" class="w-full px-3.5 py-2 text-left flex items-center space-x-2.5 hover:bg-amber-50 dark:hover:bg-amber-950/40 hover:text-amber-600 dark:hover:text-amber-400 transition cursor-pointer">
<i data-lucide="calendar-plus" class="w-4 h-4 text-amber-500"></i>
<span>👤 Toewijzen & vrij blok inplannen</span>
</button>
<div class="my-1 border-t border-slate-100 dark:border-slate-800/80"></div>
<div class="px-3.5 py-1 text-[10px] font-bold text-slate-400 uppercase tracking-wider">Verplaats naar Kwartaal</div>
<div class="grid grid-cols-4 gap-1 px-3 py-1">
<button onclick="triggerRoadmapCtxMoveQuarter('Q1')" class="py-1 text-center font-mono font-bold rounded hover:bg-indigo-50 dark:hover:bg-indigo-950/50 text-indigo-600 cursor-pointer">Q1</button>
<button onclick="triggerRoadmapCtxMoveQuarter('Q2')" class="py-1 text-center font-mono font-bold rounded hover:bg-indigo-50 dark:hover:bg-indigo-950/50 text-indigo-600 cursor-pointer">Q2</button>
<button onclick="triggerRoadmapCtxMoveQuarter('Q3')" class="py-1 text-center font-mono font-bold rounded hover:bg-indigo-50 dark:hover:bg-indigo-950/50 text-indigo-600 cursor-pointer">Q3</button>
<button onclick="triggerRoadmapCtxMoveQuarter('Q4')" class="py-1 text-center font-mono font-bold rounded hover:bg-indigo-50 dark:hover:bg-indigo-950/50 text-indigo-600 cursor-pointer">Q4</button>
</div>
<div class="my-1 border-t border-slate-100 dark:border-slate-800/80"></div>
<button onclick="triggerRoadmapCtxDelete()" class="w-full px-3.5 py-2 text-left flex items-center space-x-2.5 text-rose-600 hover:bg-rose-50 dark:hover:bg-rose-950/40 transition cursor-pointer">
<i data-lucide="trash-2" class="w-4 h-4"></i>
<span>Item verwijderen</span>
</button>
</div>
<script>
async function updateRoadmapStatus(id, status) {
await fetch('/api/roadmap.php?action=update_item', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({ id, status })
});
}
async function updateRoadmapProgress(id, progress) {
await fetch('/api/roadmap.php?action=update_item', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({ id, progress })
});
location.reload();
}
async function deleteRoadmapItem(id) {
if (!confirm('Dit roadmap item verwijderen?')) return;
await fetch('/api/roadmap.php?action=delete_item', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({ id })
});
document.getElementById(`roadmap-item-${id}`)?.remove();
}
async function submitManualItem() {
const title = document.getElementById('manualTitle').value.trim();
const description = document.getElementById('manualDescription').value.trim();
const quarter = document.getElementById('manualQuarter').value;
const type = document.getElementById('manualType').value;
const priority = document.getElementById('manualPriority').value;
const jira_key = document.getElementById('manualJiraKey').value.trim();
const due_date = document.getElementById('manualDueDate').value;
if (!title) {
alert('Titel is verplicht');
return;
}
const res = await fetch('/api/roadmap.php?action=add_item', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({ title, description, quarter, type, priority, jira_key, due_date })
});
const data = await res.json();
if (data.success) {
location.reload();
}
}
// --- Drag & Drop van Roadmap Items naar ander Kwartaal (Q1, Q2, Q3, Q4) ---
let draggedRoadmapItemId = null;
function handleRoadmapItemDragStart(e, itemId) {
draggedRoadmapItemId = itemId;
try {
e.dataTransfer.effectAllowed = 'move';
e.dataTransfer.setData('text/plain', String(itemId));
} catch (err) {}
setTimeout(() => {
const el = document.getElementById(`roadmap-item-${itemId}`);
if (el) el.classList.add('opacity-40', 'scale-[0.99]');
}, 0);
}
function handleRoadmapItemDragEnd(e) {
if (draggedRoadmapItemId) {
const el = document.getElementById(`roadmap-item-${draggedRoadmapItemId}`);
if (el) el.classList.remove('opacity-40', 'scale-[0.99]');
draggedRoadmapItemId = null;
}
document.querySelectorAll('.roadmap-quarter-tab').forEach(tab => {
tab.classList.remove('ring-2', 'ring-indigo-400', 'scale-105', 'bg-indigo-100', 'dark:bg-indigo-900/60');
});
}
function handleRoadmapQuarterDragOver(e) {
if (!draggedRoadmapItemId) return;
e.preventDefault();
e.dataTransfer.dropEffect = 'move';
const tab = e.currentTarget;
if (tab && !tab.classList.contains('ring-2')) {
tab.classList.add('ring-2', 'ring-indigo-400', 'scale-105', 'bg-indigo-100', 'dark:bg-indigo-900/60');
}
}
function handleRoadmapQuarterDragLeave(e) {
const tab = e.currentTarget;
if (tab && !tab.contains(e.relatedTarget)) {
tab.classList.remove('ring-2', 'ring-indigo-400', 'scale-105', 'bg-indigo-100', 'dark:bg-indigo-900/60');
}
}
async function handleRoadmapQuarterDrop(e, targetQuarter) {
e.preventDefault();
const tab = e.currentTarget;
if (tab) {
tab.classList.remove('ring-2', 'ring-indigo-400', 'scale-105', 'bg-indigo-100', 'dark:bg-indigo-900/60');
}
const itemId = draggedRoadmapItemId || parseInt(e.dataTransfer.getData('text/plain'), 10);
draggedRoadmapItemId = null;
if (itemId) {
const el = document.getElementById(`roadmap-item-${itemId}`);
if (el) el.classList.remove('opacity-40', 'scale-[0.99]', 'pointer-events-none');
}
document.querySelectorAll('[id^="roadmap-item-"].opacity-40').forEach(el => {
el.classList.remove('opacity-40', 'scale-[0.99]', 'pointer-events-none');
});
if (!itemId || !targetQuarter) return;
try {
const res = await fetch('/api/roadmap.php?action=update_item', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({ id: itemId, quarter: targetQuarter })
});
const data = await res.json();
if (data.success) {
location.reload();
} else {
alert(data.error || 'Kon item niet verplaatsen');
}
} catch (err) {
console.error('Fout bij verplaatsen roadmap item:', err);
alert('Netwerkfout bij verplaatsen');
}
}
// --- Rechtermuisknop Contextmenu & Acties voor Roadmap Items ---
let activeRoadmapCtxItemId = null;
function openRoadmapContextMenu(e, itemId) {
e.preventDefault();
e.stopPropagation();
activeRoadmapCtxItemId = itemId;
const menu = document.getElementById('roadmapContextMenu');
if (!menu) return;
menu.classList.remove('hidden');
const x = Math.min(e.clientX, window.innerWidth - 240);
const y = Math.min(e.clientY, window.innerHeight - 240);
menu.style.left = `${x}px`;
menu.style.top = `${y}px`;
if (typeof lucide !== 'undefined') lucide.createIcons();
}
function closeRoadmapContextMenu() {
const menu = document.getElementById('roadmapContextMenu');
if (menu) menu.classList.add('hidden');
activeRoadmapCtxItemId = null;
}
document.addEventListener('click', (e) => {
if (!e.target.closest('#roadmapContextMenu')) {
closeRoadmapContextMenu();
}
});
function triggerRoadmapCtxEdit() {
if (!activeRoadmapCtxItemId) return;
const itemId = activeRoadmapCtxItemId;
closeRoadmapContextMenu();
const el = document.getElementById(`roadmap-item-${itemId}`);
if (!el) return;
document.getElementById('editRoadmapId').value = itemId;
document.getElementById('editRoadmapTitle').value = el.getAttribute('data-title') || '';
document.getElementById('editRoadmapDescription').value = el.getAttribute('data-description') || '';
document.getElementById('editRoadmapQuarter').value = el.getAttribute('data-quarter') || 'Q3';
document.getElementById('editRoadmapType').value = el.getAttribute('data-type') || 'FEATURE';
document.getElementById('editRoadmapPriority').value = el.getAttribute('data-priority') || 'HIGH';
document.getElementById('editRoadmapStatus').value = el.getAttribute('data-status') || 'PLANNED';
document.getElementById('editRoadmapDueDate').value = el.getAttribute('data-due-date') || '';
document.getElementById('editRoadmapJiraKey').value = el.getAttribute('data-jira-key') || '';
const modal = document.getElementById('editRoadmapModal');
modal.classList.remove('hidden');
if (typeof lucide !== 'undefined') lucide.createIcons();
document.getElementById('editRoadmapTitle')?.focus();
}
function closeEditRoadmapModal() {
document.getElementById('editRoadmapModal')?.classList.add('hidden');
}
async function submitEditRoadmapItem() {
const id = parseInt(document.getElementById('editRoadmapId').value, 10);
const title = document.getElementById('editRoadmapTitle').value.trim();
const description = document.getElementById('editRoadmapDescription').value.trim();
const quarter = document.getElementById('editRoadmapQuarter').value;
const type = document.getElementById('editRoadmapType').value;
const priority = document.getElementById('editRoadmapPriority').value;
const status = document.getElementById('editRoadmapStatus').value;
const due_date = document.getElementById('editRoadmapDueDate').value;
const jira_key = document.getElementById('editRoadmapJiraKey').value.trim();
if (!title) {
alert('Titel is verplicht');
return;
}
const btn = document.getElementById('btnSaveEditRoadmap');
btn.disabled = true;
btn.innerText = 'Opslaan...';
try {
const res = await fetch('/api/roadmap.php?action=update_item', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({
id,
title,
description,
quarter,
type,
priority,
status,
due_date,
jira_key
})
});
const data = await res.json();
if (data.success) {
closeEditRoadmapModal();
location.reload();
} else {
alert(data.error || 'Kon item niet opslaan');
}
} catch(e) {
alert('Netwerkfout bij opslaan');
} finally {
btn.disabled = false;
btn.innerText = 'Opslaan';
}
}
function triggerRoadmapCtxSchedule() {
if (!activeRoadmapCtxItemId) return;
const itemId = activeRoadmapCtxItemId;
closeRoadmapContextMenu();
const el = document.getElementById(`roadmap-item-${itemId}`);
if (!el) return;
document.getElementById('roadmapScheduleItemId').value = itemId;
document.getElementById('roadmapScheduleTitlePreview').innerText = el.getAttribute('data-title') || 'Roadmap item';
document.getElementById('roadmapFoundSlotNotice').classList.add('hidden');
onRoadmapAssigneeChange();
const modal = document.getElementById('roadmapScheduleModal');
modal.classList.remove('hidden');
if (typeof lucide !== 'undefined') lucide.createIcons();
setTimeout(() => {
findFreeSlotForRoadmapAssignee();
}, 150);
}
function closeRoadmapScheduleModal() {
document.getElementById('roadmapScheduleModal')?.classList.add('hidden');
}
function onRoadmapAssigneeChange() {
const sel = document.getElementById('roadmapAssigneeSelect');
const opt = sel.options[sel.selectedIndex];
const notice = document.getElementById('roadmapAssignOutlookNotice');
if (opt && opt.getAttribute('data-has-outlook') === '1') {
notice.classList.remove('hidden');
} else {
notice.classList.add('hidden');
}
}
async function findFreeSlotForRoadmapAssignee() {
const sel = document.getElementById('roadmapAssigneeSelect');
const targetUserId = sel.value ? parseInt(sel.value, 10) : <?= $currentUserId ?>;
const duration = parseInt(document.getElementById('roadmapDurationMinutes').value, 10) || 60;
const btn = document.getElementById('btnRoadmapFindSlot');
const origHtml = btn.innerHTML;
btn.disabled = true;
btn.innerHTML = '<i data-lucide="loader-2" class="w-3.5 h-3.5 animate-spin"></i><span>Zoeken...</span>';
if (typeof lucide !== 'undefined') lucide.createIcons();
try {
const res = await fetch('/api/planner.php?action=find_next_free_slot', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({
duration_minutes: duration,
target_user_id: targetUserId
})
});
const data = await res.json();
if (data.success && data.slot) {
const slot = data.slot;
document.getElementById('roadmapPlanDate').value = slot.date;
document.getElementById('roadmapStartTime').value = slot.start;
const notice = document.getElementById('roadmapFoundSlotNotice');
const noticeText = document.getElementById('roadmapFoundSlotText');
noticeText.innerHTML = `Vrij agendablok gevonden: <strong>${slot.formatted_date}</strong> om <strong>${slot.start} – ${slot.end}</strong>`;
notice.classList.remove('hidden');
} else {
alert(data.error || 'Geen beschikbaar tijdblok gevonden binnen werktijden.');
}
} catch(err) {
alert('Netwerkfout bij zoeken van vrij agendablok: ' + err.message);
} finally {
btn.disabled = false;
btn.innerHTML = origHtml;
if (typeof lucide !== 'undefined') lucide.createIcons();
}
}
async function submitRoadmapSchedule() {
const itemId = parseInt(document.getElementById('roadmapScheduleItemId').value, 10);
const assigneeId = document.getElementById('roadmapAssigneeSelect').value ? parseInt(document.getElementById('roadmapAssigneeSelect').value, 10) : null;
const planDate = document.getElementById('roadmapPlanDate').value;
const startTime = document.getElementById('roadmapStartTime').value;
const durationMinutes = parseInt(document.getElementById('roadmapDurationMinutes').value, 10) || 60;
const syncOutlook = document.getElementById('roadmapSyncOutlook').checked;
const el = document.getElementById(`roadmap-item-${itemId}`);
const title = el ? el.getAttribute('data-title') : 'Roadmap Item';
const btn = document.getElementById('btnSubmitRoadmapSchedule');
const origHtml = btn.innerHTML;
btn.disabled = true;
btn.innerHTML = '<i data-lucide="loader-2" class="w-4 h-4 animate-spin"></i><span>Inplannen...</span>';
if (typeof lucide !== 'undefined') lucide.createIcons();
try {
// Maak agenda taak aan via api/planner.php
const res = await fetch('/api/planner.php?action=add_task', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({
date: planDate,
title: title,
start_time: startTime,
duration: durationMinutes,
type: 'PROJECT',
priority: 'MEDIUM',
sync_to_microsoft: syncOutlook,
target_user_id: assigneeId
})
});
const data = await res.json();
if (data.success) {
closeRoadmapScheduleModal();
let msg = `Roadmap item is succesvol ingepland op ${planDate} van ${startTime} tot ${data.task?.end_time || ''}!`;
if (data.outlook_message) {
msg += `\n${data.outlook_message}`;
}
alert(msg);
} else {
alert(data.error || 'Kon afspraak niet inplannen');
}
} catch(err) {
alert('Netwerkfout bij inplannen: ' + err.message);
} finally {
btn.disabled = false;
btn.innerHTML = origHtml;
if (typeof lucide !== 'undefined') lucide.createIcons();
}
}
async function triggerRoadmapCtxMoveQuarter(targetQuarter) {
if (!activeRoadmapCtxItemId) return;
const itemId = activeRoadmapCtxItemId;
closeRoadmapContextMenu();
try {
const res = await fetch('/api/roadmap.php?action=update_item', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({ id: itemId, quarter: targetQuarter })
});
const data = await res.json();
if (data.success) {
location.reload();
} else {
alert(data.error || 'Kon item niet verplaatsen');
}
} catch (err) {
alert('Netwerkfout bij verplaatsen item');
}
}
function triggerRoadmapCtxDelete() {
if (!activeRoadmapCtxItemId) return;
const itemId = activeRoadmapCtxItemId;
closeRoadmapContextMenu();
deleteRoadmapItem(itemId);
}
document.addEventListener('keydown', (e) => {
if (e.key === 'Escape') {
const modal = document.getElementById('newItemModal');
if (modal && !modal.classList.contains('hidden')) modal.classList.add('hidden');
closeEditRoadmapModal();
closeRoadmapScheduleModal();
closeRoadmapContextMenu();
}
});
</script>
<?php require_once __DIR__ . '/includes/footer.php'; ?>