Postits en editable items.
This commit is contained in:
+531
-2
@@ -38,6 +38,21 @@ try {
|
||||
}
|
||||
|
||||
$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">
|
||||
@@ -121,8 +136,17 @@ $quarters = ['Q1', 'Q2', 'Q3', 'Q4'];
|
||||
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) bovenaan"
|
||||
title="Sleep naar een ander kwartaal (Q1, Q2, Q3, Q4) of klik met rechtermuisknop"
|
||||
>
|
||||
|
||||
<!-- Top header van item -->
|
||||
@@ -293,10 +317,241 @@ $quarters = ['Q1', 'Q2', 'Q3', 'Q4'];
|
||||
<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', {
|
||||
@@ -433,10 +688,284 @@ async function handleRoadmapQuarterDrop(e, targetQuarter) {
|
||||
}
|
||||
}
|
||||
|
||||
// --- 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.add('hidden');
|
||||
if (modal && !modal.classList.contains('hidden')) modal.classList.add('hidden');
|
||||
closeEditRoadmapModal();
|
||||
closeRoadmapScheduleModal();
|
||||
closeRoadmapContextMenu();
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
Reference in New Issue
Block a user