Files
Fociz/brainstorm_canvas.php
T

2001 lines
104 KiB
PHP
Raw 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 Interactief Brainstorm Canvas & Roadmap Converter
*/
require_once __DIR__ . '/includes/header.php';
require_once __DIR__ . '/includes/settings.php';
$db = get_db_connection();
$userId = (int)($currentUser['id'] ?? 0);
$aiSettings = get_ai_settings($userId);
$sessionId = (int)($_GET['id'] ?? 0);
$stmtSession = $db->prepare("
SELECT s.*, u.name as creator_name
FROM brainstorm_sessions s
JOIN users u ON s.created_by_id = u.id
WHERE s.id = ?
");
$stmtSession->execute([$sessionId]);
$session = $stmtSession->fetch();
if (!$session) {
echo "<div class='p-8 text-center text-red-500'>Brainstormsessie niet gevonden. <a href='/brainstorm.php' class='underline'>Terug</a></div>";
require_once __DIR__ . '/includes/footer.php';
exit;
}
// Haal alle kaarten op van deze sessie inclusief toewijzing
$stmtCards = $db->prepare("
SELECT c.*, u.name as assignee_name, u.avatar_url as assignee_avatar
FROM brainstorm_cards c
LEFT JOIN users u ON c.assignee_id = u.id
WHERE c.session_id = ?
ORDER BY c.votes DESC, c.created_at ASC
");
$stmtCards->execute([$sessionId]);
$cards = $stmtCards->fetchAll();
// 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);
$categories = [
'Ideeën' => [
'title' => 'Ideeën & Concepten',
'icon' => 'lightbulb',
'color' => 'amber',
'border' => 'border-amber-300/80 dark:border-amber-500/40',
'bg' => 'bg-[#fffbeb] dark:bg-[#713f12]/35 text-amber-950 dark:text-amber-100',
'badge' => 'bg-amber-500/20 text-amber-900 dark:text-amber-200'
],
'Knelpunten' => [
'title' => 'Knelpunten & Uitdagingen',
'icon' => 'alert-triangle',
'color' => 'rose',
'border' => 'border-rose-300/80 dark:border-rose-500/40',
'bg' => 'bg-[#fff1f2] dark:bg-[#881337]/35 text-rose-950 dark:text-rose-100',
'badge' => 'bg-rose-500/20 text-rose-900 dark:text-rose-200'
],
'Oplossingen' => [
'title' => 'Oplossingen & Features',
'icon' => 'rocket',
'color' => 'teal',
'border' => 'border-teal-300/80 dark:border-teal-500/40',
'bg' => 'bg-[#f0fdfa] dark:bg-[#134e4a]/35 text-teal-950 dark:text-teal-100',
'badge' => 'bg-teal-500/20 text-teal-900 dark:text-teal-200'
],
'Quick Wins' => [
'title' => 'Quick Wins (Direct)',
'icon' => 'zap',
'color' => 'indigo',
'border' => 'border-indigo-300/80 dark:border-indigo-500/40',
'bg' => 'bg-[#eef2ff] dark:bg-[#312e81]/35 text-indigo-950 dark:text-indigo-100',
'badge' => 'bg-indigo-500/20 text-indigo-900 dark:text-indigo-200'
],
];
?>
<style>
/* --- Strakke, Moderne & Professionele Post-it Sticky Note Styling --- */
.postit-note {
position: relative;
border-radius: 6px;
/* Natuurlijke, strakke papieren diepteschaduw */
box-shadow:
0 1px 2px rgba(0, 0, 0, 0.04),
0 4px 10px -2px rgba(0, 0, 0, 0.08),
0 12px 20px -4px rgba(0, 0, 0, 0.06);
transition: transform 0.2s cubic-bezier(0.16, 1, 0.3, 1), box-shadow 0.2s ease, border-color 0.2s ease;
}
.postit-note:hover {
transform: translateY(-3px);
box-shadow:
0 3px 6px rgba(0, 0, 0, 0.06),
0 8px 18px -2px rgba(0, 0, 0, 0.12),
0 16px 28px -4px rgba(0, 0, 0, 0.10);
}
/* Strakke, semi-transparante plakstrip (tape) bovenaan het briefje */
.postit-note::before {
content: '';
position: absolute;
top: -6px;
left: 50%;
transform: translateX(-50%);
width: 60px;
height: 14px;
background: rgba(255, 255, 255, 0.65);
border: 1px solid rgba(0, 0, 0, 0.06);
backdrop-filter: blur(2px);
border-radius: 2px;
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
pointer-events: none;
z-index: 10;
}
.dark .postit-note::before {
background: rgba(255, 255, 255, 0.15);
border-color: rgba(255, 255, 255, 0.1);
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3);
}
/* Subtiele plakrand glans bovenaan de post-it zelf */
.postit-note::after {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
height: 4px;
background: linear-gradient(to bottom, rgba(0, 0, 0, 0.04), transparent);
border-top-left-radius: 6px;
border-top-right-radius: 6px;
pointer-events: none;
}
</style>
<div class="space-y-6 max-w-7xl mx-auto">
<!-- Top Bar -->
<div class="fociz-card p-5 flex flex-col lg:flex-row lg:items-center justify-between gap-4">
<div class="flex items-center space-x-4">
<a href="/brainstorm.php" class="p-2.5 rounded-xl bg-slate-100 hover:bg-slate-200 dark:bg-slate-800 dark:hover:bg-slate-700 text-slate-600 dark:text-slate-400 hover:text-slate-900 dark:hover:text-white transition cursor-pointer">
<i data-lucide="arrow-left" class="w-5 h-5"></i>
</a>
<div>
<div class="flex items-center space-x-2 text-xs">
<span class="px-2.5 py-0.5 rounded-lg bg-indigo-50 dark:bg-indigo-500/10 text-indigo-700 dark:text-indigo-400 font-bold border border-indigo-200 dark:border-indigo-500/20">
<?= htmlspecialchars($session['theme']) ?>
</span>
<span class="text-slate-400 dark:text-slate-500">Gestart door <?= htmlspecialchars($session['creator_name']) ?></span>
</div>
<h1 class="text-xl sm:text-2xl font-black text-slate-900 dark:text-white tracking-tight mt-0.5">
<?= htmlspecialchars($session['title']) ?>
</h1>
</div>
</div>
<div class="flex flex-wrap items-center gap-2.5">
<?php if ($aiSettings['ai_enabled'] && $aiSettings['has_key']): ?>
<button onclick="aiGenerateSessionBoard(this)" class="flex items-center space-x-1.5 px-3.5 py-2 rounded-xl bg-violet-600 hover:bg-violet-500 text-white text-xs font-bold transition cursor-pointer shadow-xs" title="Genereer een compleet whiteboard (Ideeën, Knelpunten, Oplossingen en Quick Wins) op basis van de sessie briefing">
<i data-lucide="sparkles" class="w-4 h-4 text-violet-200"></i>
<span>AI Post-its Genereren</span>
</button>
<button onclick="aiCategorizeCards(this)" class="flex items-center space-x-1.5 px-3 py-2 rounded-xl bg-indigo-50 hover:bg-indigo-100 dark:bg-indigo-950/50 dark:hover:bg-indigo-900/50 text-indigo-700 dark:text-indigo-300 border border-indigo-200 dark:border-indigo-800/60 text-xs font-bold transition cursor-pointer shadow-xs" title="Sorteer alle kaartjes automatisch in Ideeën, Knelpunten, Oplossingen of Quick Wins">
<i data-lucide="wand-2" class="w-4 h-4 text-indigo-500"></i>
<span>Categoriseren</span>
</button>
<?php endif; ?>
<button onclick="openAddCardModal()" class="flex items-center space-x-1.5 px-3.5 py-2 rounded-xl bg-slate-100 hover:bg-slate-200 dark:bg-slate-800 dark:hover:bg-slate-700 text-slate-700 dark:text-slate-200 text-xs font-bold border border-slate-200 dark:border-slate-700 transition cursor-pointer shadow-xs">
<i data-lucide="plus" class="w-4 h-4 text-indigo-600 dark:text-indigo-400"></i>
<span>Nieuwe Kaart</span>
</button>
<!-- De Kernfeature: Zet om naar Roadmap! -->
<button onclick="openConvertModal()" class="flex items-center space-x-2 px-4 py-2 rounded-xl bg-indigo-600 hover:bg-indigo-500 text-white text-xs font-bold transition shadow-xs cursor-pointer">
<i data-lucide="map" class="w-4 h-4"></i>
<span>Zet om naar Roadmap</span>
</button>
<?php if (($currentUser['role'] ?? '') === 'ADMIN' || (int)$session['created_by_id'] === (int)$userId): ?>
<button onclick="promptDeleteCurrentCanvasSession()" class="p-2 rounded-xl text-slate-400 hover:text-rose-600 dark:hover:text-rose-400 hover:bg-rose-50 dark:hover:bg-rose-500/10 border border-transparent hover:border-rose-200 dark:hover:border-rose-900/40 transition cursor-pointer" title="Brainstormsessie verwijderen">
<i data-lucide="trash-2" class="w-4 h-4"></i>
</button>
<?php endif; ?>
</div>
</div>
<!-- Sessie Context & Briefing Banner -->
<?php if (!empty($session['description'])): ?>
<div class="fociz-card p-4 sm:p-5 border-l-4 border-l-indigo-500 relative overflow-hidden transition">
<div class="flex items-start justify-between gap-4">
<div class="space-y-1.5 flex-1">
<div class="flex items-center space-x-2">
<span class="text-xs font-bold uppercase tracking-wider text-indigo-600 dark:text-indigo-400 flex items-center space-x-1.5">
<i data-lucide="file-text" class="w-3.5 h-3.5"></i>
<span>Sessie Briefing & Doelstellingen</span>
</span>
<span class="text-[11px] text-slate-400 dark:text-slate-500">• Context voor AI & Team</span>
</div>
<div class="text-xs text-slate-700 dark:text-slate-300 whitespace-pre-line leading-relaxed font-normal">
<?= htmlspecialchars($session['description']) ?>
</div>
</div>
<?php if ($aiSettings['ai_enabled'] && $aiSettings['has_key']): ?>
<button onclick="aiGenerateSessionBoard(this)" class="flex-shrink-0 px-3 py-2 rounded-xl bg-violet-50 hover:bg-violet-100 dark:bg-violet-950/40 dark:hover:bg-violet-900/40 text-violet-700 dark:text-violet-300 border border-violet-200 dark:border-violet-800/60 text-xs font-bold transition cursor-pointer flex items-center space-x-1.5 shadow-2xs">
<i data-lucide="sparkles" class="w-3.5 h-3.5 text-violet-500"></i>
<span>AI Post-its Maken</span>
</button>
<?php endif; ?>
</div>
</div>
<?php endif; ?>
<!-- Whiteboard Categorie Kolommen -->
<div class="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-4 gap-5">
<?php foreach ($categories as $catKey => $catConfig):
$catCards = array_filter($cards, fn($c) => $c['category'] === $catKey);
?>
<div class="fociz-card p-4 flex flex-col min-h-[500px] transition-all duration-200">
<!-- Column Header -->
<div class="flex items-center justify-between pb-3 mb-3 border-b border-slate-100 dark:border-slate-800">
<div class="flex items-center space-x-2">
<i data-lucide="<?= $catConfig['icon'] ?>" class="w-4 h-4 text-<?= $catConfig['color'] ?>-500"></i>
<h3 class="text-xs font-bold text-slate-900 dark:text-white uppercase tracking-wider"><?= $catConfig['title'] ?></h3>
</div>
<span id="badge-count-<?= htmlspecialchars(md5($catKey)) ?>" class="text-xs font-mono font-bold px-2 py-0.5 rounded-full bg-slate-100 dark:bg-slate-800 text-slate-600 dark:text-slate-400 border border-slate-200 dark:border-slate-700">
<?= count($catCards) ?>
</span>
</div>
<!-- Cards Container as Drop Zone -->
<div
class="brainstorm-lane flex-1 space-y-3 min-h-[140px] rounded-xl p-1.5 transition-all duration-150"
data-category="<?= htmlspecialchars($catKey, ENT_QUOTES) ?>"
ondragover="handleCanvasLaneDragOver(event)"
ondragleave="handleCanvasLaneDragLeave(event)"
ondrop="handleCanvasLaneDrop(event, '<?= htmlspecialchars($catKey, ENT_QUOTES) ?>')"
>
<?php if (empty($catCards)): ?>
<div class="lane-empty-placeholder py-8 text-center text-slate-400 dark:text-slate-600 text-xs font-medium pointer-events-none">
Nog geen kaarten in deze categorie.
</div>
<?php else: ?>
<?php foreach ($catCards as $card):
$hasAssignee = !empty($card['assignee_id']) && !empty($card['assignee_name']);
$hasSchedule = !empty($card['scheduled_date']) && !empty($card['scheduled_start_time']);
$cardJson = htmlspecialchars(json_encode([
'id' => (int)$card['id'],
'content' => $card['content'],
'category' => $card['category'],
'color' => $card['color'],
'assignee_id' => $card['assignee_id'] ? (int)$card['assignee_id'] : null,
'assignee_name' => $card['assignee_name'] ?? null,
'scheduled_date' => $card['scheduled_date'] ?? null,
'scheduled_start_time' => $card['scheduled_start_time'] ?? null,
'scheduled_end_time' => $card['scheduled_end_time'] ?? null,
]), ENT_QUOTES, 'UTF-8');
?>
<div
id="card-<?= $card['id'] ?>"
draggable="true"
ondragstart="handleCanvasCardDragStart(event, <?= $card['id'] ?>)"
ondragend="handleCanvasCardDragEnd(event)"
oncontextmenu="openCardContextMenu(event, <?= $cardJson ?>); return false;"
class="brainstorm-card postit-note group relative p-4 border <?= $catConfig['border'] ?> <?= $catConfig['bg'] ?> transition-all duration-150 cursor-grab active:cursor-grabbing hover:-translate-y-1 flex flex-col justify-between"
>
<!-- Top: Checkbox voor selectie + Roadmap badge -->
<div class="flex items-center justify-between gap-2 mb-2">
<label class="inline-flex items-center space-x-2 cursor-pointer select-none">
<input type="checkbox" value="<?= $card['id'] ?>" data-content="<?= htmlspecialchars($card['content'], ENT_QUOTES) ?>" class="card-checkbox w-4 h-4 rounded bg-white/80 dark:bg-slate-900/80 border-slate-400/50 text-indigo-600 focus:ring-0">
<span class="text-[11px] opacity-70 font-medium">Selecteer</span>
</label>
<div class="flex items-center space-x-1">
<?php if (!empty($card['converted_to_roadmap_item_id'])): ?>
<span class="px-2 py-0.5 rounded-md bg-teal-600/15 text-teal-800 dark:text-teal-300 font-bold text-[10px] flex items-center space-x-1 border border-teal-500/30">
<i data-lucide="check" class="w-3 h-3"></i>
<span>Roadmap</span>
</span>
<?php endif; ?>
<button type="button" onclick="openCardContextMenu(event, <?= $cardJson ?>)" class="p-1 rounded text-slate-500 hover:text-slate-800 dark:hover:text-white transition cursor-pointer" title="Opties & Inplannen (Rechtermuisknop)">
<i data-lucide="more-horizontal" class="w-3.5 h-3.5"></i>
</button>
</div>
</div>
<!-- Inhoud Post-it met verfijnde typografie en uitlijning -->
<div class="w-full text-left py-1" onclick="openEditCardModal(<?= $cardJson ?>)">
<p class="text-xs font-semibold leading-relaxed whitespace-pre-line break-words text-left text-slate-900 dark:text-slate-100 cursor-pointer m-0 p-0 block" title="Klik om te bewerken"><?= htmlspecialchars(trim($card['content'])) ?></p>
</div>
<!-- Assignee & Agenda Tijdblok Badge (indien gekoppeld) -->
<?php if ($hasAssignee || $hasSchedule): ?>
<div class="mt-2.5 pt-2 border-t border-black/10 dark:border-white/10 flex items-center justify-between gap-1 text-[10px]">
<?php if ($hasAssignee): ?>
<div class="flex items-center space-x-1.5 truncate">
<?php if (!empty($card['assignee_avatar'])): ?>
<img src="<?= htmlspecialchars($card['assignee_avatar']) ?>" class="w-4 h-4 rounded-full object-cover border border-black/10">
<?php else: ?>
<div class="w-4 h-4 rounded-full bg-indigo-600 text-white font-bold text-[8px] flex items-center justify-center">
<?= strtoupper(substr($card['assignee_name'], 0, 1)) ?>
</div>
<?php endif; ?>
<span class="font-bold truncate max-w-[100px]"><?= htmlspecialchars($card['assignee_name']) ?></span>
</div>
<?php endif; ?>
<?php if ($hasSchedule): ?>
<span class="inline-flex items-center space-x-1 font-bold px-1.5 py-0.5 rounded bg-black/10 dark:bg-white/10 text-[9px]" title="Ingepland op <?= htmlspecialchars($card['scheduled_date']) ?>">
<i data-lucide="clock" class="w-2.5 h-2.5"></i>
<span><?= htmlspecialchars(substr($card['scheduled_start_time'], 0, 5)) ?></span>
</span>
<?php endif; ?>
</div>
<?php endif; ?>
<!-- Footer van kaart -->
<div class="mt-2.5 pt-2 border-t border-black/10 dark:border-white/10 flex items-center justify-between text-[10px] opacity-80">
<div class="flex items-center space-x-1.5 min-w-0">
<span class="truncate font-medium"><?= htmlspecialchars($card['author_name'] ?? 'Collega') ?></span>
<?php if ($aiSettings['ai_enabled'] && $aiSettings['has_key']): ?>
<button onclick="openAiBreakdownModal(<?= $card['id'] ?>, <?= htmlspecialchars(json_encode($card['content']), ENT_QUOTES) ?>)" class="inline-flex items-center space-x-1 px-1.5 py-0.5 rounded bg-black/10 hover:bg-black/15 dark:bg-white/10 dark:hover:bg-white/15 font-bold text-[9px] transition cursor-pointer" title="Splits dit idee uit naar taken via AI">
<i data-lucide="sparkles" class="w-2.5 h-2.5 text-violet-600 dark:text-violet-400"></i>
<span>AI</span>
</button>
<?php endif; ?>
</div>
<div class="flex items-center space-x-1.5 flex-shrink-0">
<!-- Upvote button -->
<button onclick="voteCard(<?= $card['id'] ?>)" class="flex items-center space-x-1 px-2 py-0.5 rounded bg-black/10 hover:bg-black/15 dark:bg-white/10 dark:hover:bg-white/15 transition shadow-2xs cursor-pointer">
<i data-lucide="thumbs-up" class="w-3 h-3"></i>
<span id="votes-<?= $card['id'] ?>" class="font-bold"><?= $card['votes'] ?></span>
</button>
<!-- Delete button -->
<button onclick="deleteCard(<?= $card['id'] ?>)" class="p-1 rounded text-rose-600 hover:text-rose-700 opacity-0 group-hover:opacity-100 transition cursor-pointer" title="Verwijderen">
<i data-lucide="trash-2" class="w-3.5 h-3.5"></i>
</button>
</div>
</div>
</div>
<?php endforeach; ?>
<?php endif; ?>
</div>
<!-- Quick add button at column bottom -->
<button onclick="openAddCardModal('<?= $catKey ?>')" class="mt-3 w-full py-2 rounded-xl border border-dashed border-slate-200 dark:border-slate-800 hover:border-indigo-300 dark:hover:border-indigo-700/60 text-slate-400 hover:text-indigo-600 dark:hover:text-indigo-400 text-xs font-semibold transition flex items-center justify-center space-x-1 cursor-pointer">
<i data-lucide="plus" class="w-3.5 h-3.5"></i>
<span>Kaart toevoegen</span>
</button>
</div>
<?php endforeach; ?>
</div>
</div>
<!-- Floating Rechtermuisknop Contextmenu -->
<div id="cardContextMenu" class="hidden fixed z-[999] bg-white dark:bg-[#111624] border border-slate-200 dark:border-slate-800 rounded-2xl shadow-2xl py-1.5 w-64 text-xs font-medium text-slate-700 dark:text-slate-200 select-none animate-in fade-in zoom-in-95 duration-100">
<div class="px-3.5 py-1.5 border-b border-slate-100 dark:border-slate-800/80 mb-1">
<span id="ctxMenuCardTitle" class="text-[11px] font-bold text-slate-900 dark:text-white truncate block"></span>
<span id="ctxMenuCardMeta" class="text-[10px] text-slate-400 truncate block"></span>
</div>
<button onclick="triggerCtxEdit()" 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>Kaart bewerken</span>
</button>
<button onclick="triggerCtxAssign()" 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="user-check" class="w-4 h-4 text-emerald-500"></i>
<span>Toewijzen & Agenda inplannen</span>
</button>
<button onclick="triggerCtxQuickSlot()" 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="zap" class="w-4 h-4 text-amber-500"></i>
<span>⚡ Zoek direct vrij blok</span>
</button>
<button onclick="triggerCtxRoadmap()" 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="map" class="w-4 h-4 text-sky-500"></i>
<span>Zet om naar Roadmap</span>
</button>
<?php if ($aiSettings['ai_enabled'] && $aiSettings['has_key']): ?>
<button onclick="triggerCtxBreakdown()" class="w-full px-3.5 py-2 text-left flex items-center space-x-2.5 hover:bg-violet-50 dark:hover:bg-violet-950/50 hover:text-violet-600 dark:hover:text-violet-400 transition cursor-pointer">
<i data-lucide="sparkles" class="w-4 h-4 text-violet-500"></i>
<span>AI Splits uit in taken</span>
</button>
<?php endif; ?>
<div class="my-1 border-t border-slate-100 dark:border-slate-800/80"></div>
<button onclick="triggerCtxDelete()" 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>Kaart verwijderen</span>
</button>
</div>
<!-- Modal: Kaart Bewerken -->
<div id="editCardModal" onclick="if(event.target === this) closeEditCardModal()" 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 shadow-2xl max-w-md 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>Brainstormkaart Bewerken</span>
</h3>
<button onclick="closeEditCardModal()" 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">
<input type="hidden" id="editCardId" value="">
<div>
<label class="block text-xs font-semibold text-slate-700 dark:text-slate-300 mb-1">Categorie</label>
<select id="editCardCategory" 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="Ideeën">💡 Ideeën & Concepten</option>
<option value="Knelpunten">⚠️ Knelpunten & Uitdagingen</option>
<option value="Oplossingen">🚀 Oplossingen & Features</option>
<option value="Quick Wins">⚡ Quick Wins</option>
</select>
</div>
<div>
<label class="block text-xs font-semibold text-slate-700 dark:text-slate-300 mb-1">Inhoud Post-it</label>
<textarea id="editCardContent" rows="4" 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 focus:outline-none focus:ring-1 focus:ring-indigo-500 resize-none shadow-2xs"></textarea>
</div>
<div class="flex space-x-2 pt-2 border-t border-slate-100 dark:border-slate-800/80">
<button onclick="submitEditCard()" 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">
Wijzigingen Opslaan
</button>
<button onclick="closeEditCardModal()" 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: Toewijzen aan Collega & Inplannen in Agenda (Vrij Blok) -->
<div id="assignScheduleModal" onclick="if(event.target === this) closeAssignScheduleModal()" 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 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-indigo-500/10 text-indigo-600 dark:text-indigo-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">Toewijzen & Agenda Inplanning</h3>
<p class="text-[11px] text-slate-400">Plan deze brainstormkaart in als taak in het vrije blok van een collega</p>
</div>
</div>
<button onclick="closeAssignScheduleModal()" 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">
<input type="hidden" id="assignCardId" value="">
<div class="p-3 bg-slate-50 dark:bg-slate-900/60 rounded-xl border border-slate-200 dark:border-slate-800 text-xs">
<span class="text-slate-400 text-[10px] uppercase font-bold tracking-wider block mb-1">Kaart</span>
<p id="assignCardContentPreview" class="text-slate-800 dark:text-slate-200 font-medium line-clamp-2"></p>
</div>
<!-- Collega selecteren -->
<div>
<label class="block text-xs font-bold text-slate-700 dark:text-slate-300 uppercase tracking-wider mb-1.5">
Toewijzen aan collega *
</label>
<select id="assignColleagueSelect" onchange="onAssignColleagueChange()" class="w-full bg-slate-50 dark:bg-slate-950 border border-slate-200 dark:border-slate-800 rounded-xl px-3.5 py-2.5 text-xs text-slate-900 dark:text-white focus:outline-none focus:ring-2 focus:ring-indigo-500 cursor-pointer">
<option value="">-- Kies een collega --</option>
<?php foreach ($teamMembers as $tm): ?>
<option value="<?= $tm['id'] ?>" data-has-outlook="<?= $tm['has_outlook'] ? '1' : '0' ?>" data-name="<?= htmlspecialchars($tm['name']) ?>">
<?= htmlspecialchars($tm['name']) ?> <?= $tm['has_outlook'] ? '📅 (Outlook 365)' : '' ?>
</option>
<?php endforeach; ?>
</select>
<p id="assignOutlookNotice" class="hidden mt-1 text-[11px] text-sky-600 dark:text-sky-400 font-medium flex items-center space-x-1">
<i data-lucide="calendar-check" class="w-3 h-3"></i>
<span>Deze collega heeft Microsoft Outlook 365 actief. De afspraak kan direct in Outlook gezet worden!</span>
</p>
</div>
<!-- Slim Zoeken: Knop Eerstvolgend Vrij Blok -->
<div class="p-3 rounded-xl bg-amber-500/10 border border-amber-500/20 flex items-center justify-between gap-3">
<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-[11px] text-amber-700 dark:text-amber-400">Zoekt binnen de gezette werktijden en Outlook agenda</span>
</div>
<button type="button" id="btnFindSlot" onclick="findFreeSlotForColleague()" 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="foundSlotNotice" class="hidden p-2.5 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="foundSlotText"></span>
</div>
<!-- Datum & Tijdblok -->
<div class="grid grid-cols-1 sm:grid-cols-3 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="assignPlanDate" value="<?= date('Y-m-d') ?>" class="w-full bg-slate-50 dark:bg-slate-950 border border-slate-200 dark:border-slate-800 rounded-xl px-3 py-2 text-xs text-slate-900 dark:text-white focus:outline-none">
</div>
<div>
<label class="block text-xs font-semibold text-slate-700 dark:text-slate-300 mb-1">Starttijd</label>
<input type="time" id="assignStartTime" value="09:00" class="w-full bg-slate-50 dark:bg-slate-950 border border-slate-200 dark:border-slate-800 rounded-xl px-3 py-2 text-xs text-slate-900 dark:text-white focus:outline-none">
</div>
<div>
<label class="block text-xs font-semibold text-slate-700 dark:text-slate-300 mb-1">Duur</label>
<select id="assignDurationMinutes" class="w-full bg-slate-50 dark:bg-slate-950 border border-slate-200 dark:border-slate-800 rounded-xl px-3 py-2 text-xs text-slate-900 dark:text-white 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">1 uur (60m)</option>
<option value="90">1,5 uur (90m)</option>
<option value="120">2 uur (120m)</option>
</select>
</div>
</div>
<!-- Vinkje Outlook -->
<label class="flex items-center space-x-2 cursor-pointer pt-1">
<input type="checkbox" id="assignSyncOutlook" checked class="rounded text-indigo-600 focus:ring-indigo-500">
<span class="text-xs font-semibold text-slate-800 dark:text-slate-200">
Direct als agenda-afspraak plaatsen in Outlook 365 van deze collega
</span>
</label>
<!-- Buttons -->
<div class="flex space-x-2 pt-2 border-t border-slate-100 dark:border-slate-800/80">
<button type="button" onclick="submitAssignAndSchedule()" id="btnSubmitAssign" 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 flex items-center justify-center space-x-1.5">
<i data-lucide="check" class="w-4 h-4"></i>
<span>Inplannen & Toewijzen</span>
</button>
<button type="button" onclick="closeAssignScheduleModal()" 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: Zet om naar Roadmap -->
<div id="convertModal" onclick="if(event.target === this) closeConvertModal()" 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 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>Zet Brainstorm om naar Roadmap</span>
</h3>
<button onclick="closeConvertModal()" 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 class="bg-indigo-50 dark:bg-indigo-500/10 border border-indigo-200 dark:border-indigo-500/20 rounded-xl p-3 text-xs text-indigo-800 dark:text-indigo-300 font-semibold">
<span id="selectedCountText" class="font-bold">0</span> kaarten geselecteerd om om te zetten naar planbare roadmap items.
</div>
<?php if ($aiSettings['ai_enabled'] && $aiSettings['has_key']): ?>
<div class="p-3 rounded-xl bg-violet-50 dark:bg-violet-950/40 border border-violet-200 dark:border-violet-800/60 flex items-center justify-between">
<div class="flex items-center space-x-2 text-xs text-violet-900 dark:text-violet-200">
<i data-lucide="sparkles" class="w-4 h-4 text-violet-500 flex-shrink-0"></i>
<span class="text-[11px] font-medium leading-tight">Laat AI Co-Pilot kwartaal, type & prioriteit adviseren</span>
</div>
<button type="button" onclick="aiSuggestRoadmapMeta(this)" id="aiSuggestBtn" class="px-2.5 py-1 bg-violet-600 hover:bg-violet-700 text-white rounded-lg text-xs font-bold transition flex items-center space-x-1 cursor-pointer flex-shrink-0 shadow-xs">
<span>✨ AI Advies</span>
</button>
</div>
<?php endif; ?>
<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">Doel Kwartaal</label>
<select id="roadmapQuarter" 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 (Jan - Mrt)</option>
<option value="Q2">Q2 (Apr - Jun)</option>
<option value="Q3" selected>Q3 (Jul - Sep)</option>
<option value="Q4">Q4 (Okt - Dec)</option>
</select>
</div>
<div>
<label class="block text-xs font-semibold text-slate-700 dark:text-slate-300 mb-1">Item Type</label>
<select id="roadmapType" 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 / Functionaliteit</option>
<option value="EPIC">Epic (Groot project)</option>
<option value="MILESTONE">Mijlpaal</option>
<option value="TASK">Oplos-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="roadmapPriority" 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="HIGH" selected>Hoog (Belangrijke milestone)</option>
<option value="CRITICAL">Kritiek (Must-have)</option>
<option value="MEDIUM">Normaal</option>
<option value="LOW">Laag / Backlog</option>
</select>
</div>
<div>
<label class="block text-xs font-semibold text-slate-700 dark:text-slate-300 mb-1">Streefdatum (Deadline)</label>
<input type="date" id="roadmapDueDate" 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 onclick="submitConvertToRoadmap()" class="flex-1 bg-indigo-600 hover:bg-indigo-500 text-white font-bold py-2.5 px-4 rounded-xl text-xs transition shadow-xs flex items-center justify-center space-x-2 cursor-pointer">
<i data-lucide="check-circle" class="w-4 h-4"></i>
<span>Definitief Toevoegen aan Roadmap</span>
</button>
<button onclick="closeConvertModal()" 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: AI Idee Uitwerken & Exporteren (Release Google Gemini Pro) -->
<div id="aiBreakdownModal" onclick="if(event.target === this) closeAiBreakdownModal()" class="hidden fixed inset-0 z-50 bg-slate-950/75 backdrop-blur-sm flex items-center justify-center p-3 sm:p-4 overflow-y-auto">
<div class="fociz-card p-5 sm:p-6 shadow-2xl max-w-2xl w-full my-8 space-y-4 max-h-[92vh] flex flex-col">
<!-- Header -->
<div class="flex items-center justify-between pb-3 border-b border-slate-100 dark:border-slate-800 flex-shrink-0">
<div class="flex items-center space-x-2.5">
<div class="w-9 h-9 rounded-xl bg-violet-100 dark:bg-violet-900/40 text-violet-600 dark:text-violet-300 flex items-center justify-center flex-shrink-0">
<i data-lucide="sparkles" class="w-5 h-5"></i>
</div>
<div>
<h3 class="text-sm sm:text-base font-bold text-slate-900 dark:text-white flex items-center space-x-2">
<span>Idee Uitwerken naar Taken via AI</span>
</h3>
<p class="text-[11px] text-slate-500 dark:text-slate-400">Google Gemini Pro ontleedt het idee in behapbare items</p>
</div>
</div>
<div class="flex items-center space-x-2">
<span class="hidden sm:inline-flex px-2 py-0.5 rounded-full bg-emerald-50 dark:bg-emerald-950/40 text-emerald-700 dark:text-emerald-400 border border-emerald-200 dark:border-emerald-800/60 text-[10px] font-bold items-center space-x-1">
<i data-lucide="shield-check" class="w-3 h-3"></i>
<span>Zero-Data Leakage</span>
</span>
<button onclick="closeAiBreakdownModal()" 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>
<!-- Scrollable content area -->
<div class="space-y-4 overflow-y-auto pr-1 flex-1">
<!-- Geselecteerd Idee Preview -->
<div class="p-3 rounded-xl bg-violet-50/70 dark:bg-violet-950/20 border border-violet-200/70 dark:border-violet-800/40">
<div class="flex items-center justify-between text-[10px] text-violet-700 dark:text-violet-300 font-bold uppercase tracking-wider mb-1">
<span>Oorspronkelijk Brainstorm Idee</span>
<span id="breakdownSessionLabel" class="font-normal normal-case text-slate-500 dark:text-slate-400"></span>
</div>
<p id="breakdownIdeaText" class="text-xs text-slate-800 dark:text-slate-200 italic font-medium leading-relaxed"></p>
</div>
<!-- Loading State met Spinner -->
<div id="breakdownLoading" class="py-12 flex flex-col items-center justify-center space-y-3 text-center">
<div class="w-10 h-10 rounded-full border-3 border-violet-500/20 border-t-violet-600 animate-spin"></div>
<div class="space-y-1">
<p class="text-xs font-bold text-slate-900 dark:text-white">Google Gemini Pro aan het werk...</p>
<p class="text-[11px] text-slate-500 dark:text-slate-400">Idee wordt ontleed in 3-5 concrete user stories en acceptatiecriteria</p>
</div>
</div>
<!-- Error State -->
<div id="breakdownError" class="hidden p-4 rounded-xl bg-rose-50 dark:bg-rose-950/30 border border-rose-200 dark:border-rose-800 text-rose-800 dark:text-rose-200 text-xs space-y-2">
<div class="flex items-center space-x-2 font-bold">
<i data-lucide="alert-circle" class="w-4 h-4 text-rose-600 dark:text-rose-400 flex-shrink-0"></i>
<span>Kon taken niet genereren</span>
</div>
<p id="breakdownErrorMsg" class="text-[11px] text-rose-700 dark:text-rose-300 leading-relaxed"></p>
<div class="pt-1">
<button type="button" onclick="regenerateBreakdown()" class="inline-flex items-center space-x-1.5 px-3 py-1.5 rounded-lg bg-rose-600 hover:bg-rose-700 text-white text-xs font-semibold transition cursor-pointer">
<i data-lucide="refresh-cw" class="w-3 h-3"></i>
<span>Opnieuw proberen</span>
</button>
</div>
</div>
<!-- Resultaat Container -->
<div id="breakdownResult" class="hidden space-y-4">
<!-- AI Aanpak Samenvatting -->
<div id="breakdownSummaryBox" class="p-3 rounded-xl bg-slate-50 dark:bg-[#0c0f17] border border-slate-200 dark:border-slate-800 text-xs flex items-start space-x-2.5">
<i data-lucide="sparkles" class="w-4 h-4 text-violet-500 flex-shrink-0 mt-0.5"></i>
<div>
<strong class="text-slate-900 dark:text-white font-bold block text-xs">Geadviseerde Aanpak:</strong>
<p id="breakdownSummaryText" class="text-slate-600 dark:text-slate-300 text-[11px] mt-0.5 leading-relaxed"></p>
</div>
</div>
<!-- Bestemming Kiezer Tabs -->
<div class="space-y-2">
<label class="block text-xs font-bold text-slate-900 dark:text-white">Kies Export Bestemming:</label>
<div class="grid grid-cols-3 gap-2">
<button type="button" onclick="setBreakdownDestination('kanban')" id="destBtn-kanban" class="dest-btn p-2.5 rounded-xl border text-xs font-bold transition flex flex-col items-center justify-center space-y-1 cursor-pointer bg-indigo-50 dark:bg-indigo-950/40 border-indigo-500 text-indigo-700 dark:text-indigo-300">
<i data-lucide="trello" class="w-4 h-4"></i>
<span>Kanban Bord</span>
</button>
<button type="button" onclick="setBreakdownDestination('timeline')" id="destBtn-timeline" class="dest-btn p-2.5 rounded-xl border text-xs font-semibold transition flex flex-col items-center justify-center space-y-1 cursor-pointer bg-white dark:bg-slate-900 border-slate-200 dark:border-slate-800 text-slate-600 dark:text-slate-400 hover:border-slate-300">
<i data-lucide="clock" class="w-4 h-4"></i>
<span>Tijdlijn / Gantt</span>
</button>
<button type="button" onclick="setBreakdownDestination('planner')" id="destBtn-planner" class="dest-btn p-2.5 rounded-xl border text-xs font-semibold transition flex flex-col items-center justify-center space-y-1 cursor-pointer bg-white dark:bg-slate-900 border-slate-200 dark:border-slate-800 text-slate-600 dark:text-slate-400 hover:border-slate-300">
<i data-lucide="calendar" class="w-4 h-4"></i>
<span>Dagplanner</span>
</button>
</div>
</div>
<!-- Bestemmings-specifieke opties -->
<!-- Opties voor Kanban Bord -->
<div id="destOptions-kanban" class="grid grid-cols-1 sm:grid-cols-2 gap-3 p-3 rounded-xl bg-slate-50 dark:bg-[#0c0f17] border border-slate-200 dark:border-slate-800 text-xs">
<div>
<label class="block font-semibold text-slate-700 dark:text-slate-300 mb-1">Kanban Bord</label>
<select id="breakdownBoardSelect" onchange="onBreakdownBoardChange(this.value)" class="w-full bg-white dark:bg-slate-900 border border-slate-200 dark:border-slate-800 rounded-xl px-3 py-2 text-xs text-slate-900 dark:text-white focus:outline-none focus:ring-1 focus:ring-indigo-500">
<option value="">Borden laden...</option>
</select>
</div>
<div>
<label class="block font-semibold text-slate-700 dark:text-slate-300 mb-1">Kolom / Status</label>
<select id="breakdownLaneSelect" class="w-full bg-white dark:bg-slate-900 border border-slate-200 dark:border-slate-800 rounded-xl px-3 py-2 text-xs text-slate-900 dark:text-white focus:outline-none focus:ring-1 focus:ring-indigo-500">
<option value="0">Standaard (eerste kolom)</option>
</select>
</div>
</div>
<!-- Opties voor Tijdlijn -->
<div id="destOptions-timeline" class="hidden grid grid-cols-1 sm:grid-cols-3 gap-3 p-3 rounded-xl bg-slate-50 dark:bg-[#0c0f17] border border-slate-200 dark:border-slate-800 text-xs">
<div class="sm:col-span-1">
<label class="block font-semibold text-slate-700 dark:text-slate-300 mb-1">Project Bord</label>
<select id="breakdownTimelineBoardSelect" class="w-full bg-white dark:bg-slate-900 border border-slate-200 dark:border-slate-800 rounded-xl px-3 py-2 text-xs text-slate-900 dark:text-white focus:outline-none focus:ring-1 focus:ring-indigo-500">
<option value="">Borden laden...</option>
</select>
</div>
<div>
<label class="block font-semibold text-slate-700 dark:text-slate-300 mb-1">Startdatum</label>
<input type="date" id="breakdownStartDate" value="<?= date('Y-m-d') ?>" class="w-full bg-white dark:bg-slate-900 border border-slate-200 dark:border-slate-800 rounded-xl px-3 py-1.5 text-xs text-slate-900 dark:text-white focus:outline-none focus:ring-1 focus:ring-indigo-500">
</div>
<div>
<label class="block font-semibold text-slate-700 dark:text-slate-300 mb-1">Einddatum</label>
<input type="date" id="breakdownEndDate" value="<?= date('Y-m-d', strtotime('+3 days')) ?>" class="w-full bg-white dark:bg-slate-900 border border-slate-200 dark:border-slate-800 rounded-xl px-3 py-1.5 text-xs text-slate-900 dark:text-white focus:outline-none focus:ring-1 focus:ring-indigo-500">
</div>
</div>
<!-- Opties voor Dagplanner -->
<div id="destOptions-planner" class="hidden p-3 rounded-xl bg-slate-50 dark:bg-[#0c0f17] border border-slate-200 dark:border-slate-800 text-xs">
<div class="max-w-xs">
<label class="block font-semibold text-slate-700 dark:text-slate-300 mb-1">Plandatum</label>
<input type="date" id="breakdownPlanDate" value="<?= date('Y-m-d') ?>" class="w-full bg-white dark:bg-slate-900 border border-slate-200 dark:border-slate-800 rounded-xl px-3 py-1.5 text-xs text-slate-900 dark:text-white focus:outline-none focus:ring-1 focus:ring-indigo-500">
</div>
</div>
<!-- Taken Lijst -->
<div class="space-y-2">
<div class="flex items-center justify-between">
<span class="text-xs font-bold text-slate-900 dark:text-white flex items-center space-x-1.5">
<span>Gegenereerde Taken</span>
<span id="breakdownTasksBadge" class="px-2 py-0.5 rounded-full bg-indigo-50 dark:bg-indigo-950 text-indigo-700 dark:text-indigo-400 font-mono text-[10px]">0</span>
</span>
<button type="button" onclick="addManualBreakdownItem()" class="text-xs text-indigo-600 dark:text-indigo-400 font-bold hover:underline flex items-center space-x-1 cursor-pointer">
<i data-lucide="plus" class="w-3.5 h-3.5"></i>
<span>Taak toevoegen</span>
</button>
</div>
<div id="breakdownTasksContainer" class="space-y-2.5">
<!-- Wordt dynamisch gevuld met renderBreakdownTasks() -->
</div>
</div>
</div>
</div>
<!-- Footer -->
<div class="pt-3 border-t border-slate-100 dark:border-slate-800 flex items-center justify-between flex-shrink-0">
<button type="button" onclick="regenerateBreakdown()" id="breakdownRegenBtn" class="px-3.5 py-2 rounded-xl bg-slate-100 hover:bg-slate-200 dark:bg-slate-800 dark:hover:bg-slate-700 text-slate-700 dark:text-slate-200 text-xs font-semibold transition cursor-pointer flex items-center space-x-1.5">
<i data-lucide="refresh-cw" class="w-3.5 h-3.5"></i>
<span>Opnieuw Genereren</span>
</button>
<div class="flex items-center space-x-2">
<button type="button" onclick="closeAiBreakdownModal()" class="px-4 py-2 rounded-xl bg-slate-100 hover:bg-slate-200 dark:bg-slate-800 dark:hover:bg-slate-700 text-slate-700 dark:text-slate-300 text-xs font-semibold transition cursor-pointer">
Annuleren
</button>
<button type="button" onclick="submitBreakdownExport()" id="breakdownExportBtn" class="px-5 py-2 rounded-xl bg-indigo-600 hover:bg-indigo-500 text-white text-xs font-bold transition shadow-xs cursor-pointer flex items-center space-x-2">
<i data-lucide="arrow-right" class="w-4 h-4"></i>
<span id="breakdownExportBtnText">Exporteer Taken</span>
</button>
</div>
</div>
</div>
</div>
<!-- Modal: Sessie Verwijderen Bevestiging -->
<div id="deleteCanvasSessionModal" onclick="if(event.target === this) closeDeleteCanvasSessionModal()" 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-md w-full space-y-4">
<div class="flex items-start space-x-3.5 pb-2">
<div class="w-10 h-10 rounded-xl bg-rose-50 dark:bg-rose-500/10 border border-rose-200 dark:border-rose-500/20 text-rose-600 dark:text-rose-400 flex items-center justify-center flex-shrink-0">
<i data-lucide="alert-triangle" class="w-5 h-5"></i>
</div>
<div>
<h3 class="text-base font-bold text-slate-900 dark:text-white">Brainstormsessie verwijderen</h3>
<p class="text-xs text-slate-500 dark:text-slate-400 mt-1">
Weet u zeker dat u deze sessie wilt verwijderen? Deze actie kan niet ongedaan worden gemaakt.
</p>
</div>
</div>
<div class="p-3.5 rounded-xl bg-rose-50/60 dark:bg-rose-950/20 border border-rose-200/80 dark:border-rose-900/40 text-xs text-rose-800 dark:text-rose-300 space-y-1">
<p class="font-bold text-slate-900 dark:text-white"><?= htmlspecialchars($session['title']) ?></p>
<p class="text-[11px] text-rose-700 dark:text-rose-400">
Let op: Alle <?= count($cards) ?> brainstormkaart(en) en bijbehorende inhoud worden definitief verwijderd.
</p>
</div>
<div class="flex space-x-2 pt-2 border-t border-slate-100 dark:border-slate-800/80">
<button type="button" onclick="closeDeleteCanvasSessionModal()" class="flex-1 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>
<button type="button" id="btnConfirmDeleteCanvasSession" onclick="executeDeleteCanvasSession()" class="flex-1 px-4 py-2.5 bg-rose-600 hover:bg-rose-500 text-white font-bold rounded-xl text-xs transition cursor-pointer shadow-xs flex items-center justify-center space-x-1.5">
<i data-lucide="trash-2" class="w-4 h-4"></i>
<span>Verwijderen</span>
</button>
</div>
</div>
</div>
<script>
const sessionId = <?= $sessionId ?>;
function openAddCardModal(defaultCategory = null) {
if (defaultCategory) {
document.getElementById('modalCardCategory').value = defaultCategory;
}
document.getElementById('addCardModal').classList.remove('hidden');
document.getElementById('modalCardContent').focus();
}
function closeAddCardModal() {
document.getElementById('addCardModal').classList.add('hidden');
document.getElementById('modalCardContent').value = '';
}
async function submitNewCard() {
const category = document.getElementById('modalCardCategory').value;
const content = document.getElementById('modalCardContent').value.trim();
if (!content) return;
const res = await fetch('/api/brainstorm.php?action=add_card', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({ session_id: sessionId, category, content })
});
const data = await res.json();
if (data.success) {
location.reload();
}
}
async function voteCard(cardId) {
const res = await fetch('/api/brainstorm.php?action=vote_card', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({ card_id: cardId })
});
const data = await res.json();
if (data.success) {
document.getElementById(`votes-${cardId}`).innerText = data.votes;
}
}
async function deleteCard(cardId) {
if (!confirm('Kaart verwijderen?')) return;
await fetch('/api/brainstorm.php?action=delete_card', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({ card_id: cardId })
});
document.getElementById(`card-${cardId}`)?.remove();
}
// --- Rechtermuisknop Contextmenu Logica ---
let _activeCtxCard = null;
function openCardContextMenu(e, card) {
if (e) {
e.preventDefault();
e.stopPropagation();
}
_activeCtxCard = card;
const menu = document.getElementById('cardContextMenu');
const titleEl = document.getElementById('ctxMenuCardTitle');
const metaEl = document.getElementById('ctxMenuCardMeta');
const firstLine = card.content.split('\n')[0] || 'Brainstormkaart';
titleEl.innerText = firstLine;
metaEl.innerText = `${card.category} ${card.assignee_name ? '• ' + card.assignee_name : ''}`;
// Positioneer het menu op de muiscursor
const mouseX = e ? e.clientX : window.innerWidth / 2;
const mouseY = e ? e.clientY : window.innerHeight / 2;
menu.classList.remove('hidden');
menu.style.left = `${Math.min(mouseX, window.innerWidth - 270)}px`;
menu.style.top = `${Math.min(mouseY, window.innerHeight - 300)}px`;
if (window.lucide) lucide.createIcons();
}
function closeCardContextMenu() {
const menu = document.getElementById('cardContextMenu');
if (menu) menu.classList.add('hidden');
}
// Contextmenu acties
function triggerCtxEdit() {
const card = _activeCtxCard;
closeCardContextMenu();
if (card) openEditCardModal(card);
}
function triggerCtxAssign() {
const card = _activeCtxCard;
closeCardContextMenu();
if (card) openAssignScheduleModal(card);
}
function triggerCtxQuickSlot() {
const card = _activeCtxCard;
closeCardContextMenu();
if (card) {
openAssignScheduleModal(card);
setTimeout(() => findFreeSlotForColleague(), 100);
}
}
function triggerCtxRoadmap() {
const card = _activeCtxCard;
closeCardContextMenu();
if (card) {
const chk = document.querySelector(`.card-checkbox[value="${card.id}"]`);
if (chk) chk.checked = true;
openConvertModal();
}
}
function triggerCtxBreakdown() {
const card = _activeCtxCard;
closeCardContextMenu();
if (card) openAiBreakdownModal(card.id, card.content);
}
function triggerCtxDelete() {
const card = _activeCtxCard;
closeCardContextMenu();
if (card) deleteCard(card.id);
}
// Sluit contextmenu bij klik erbuiten
document.addEventListener('click', (e) => {
if (!e.target.closest('#cardContextMenu')) {
closeCardContextMenu();
}
});
// --- Kaart Bewerken Modal Logica ---
function openEditCardModal(card) {
document.getElementById('editCardId').value = card.id;
document.getElementById('editCardCategory').value = card.category || 'Ideeën';
document.getElementById('editCardContent').value = card.content || '';
document.getElementById('editCardModal').classList.remove('hidden');
document.getElementById('editCardContent').focus();
}
function closeEditCardModal() {
document.getElementById('editCardModal').classList.add('hidden');
}
async function submitEditCard() {
const cardId = parseInt(document.getElementById('editCardId').value, 10);
const category = document.getElementById('editCardCategory').value;
const content = document.getElementById('editCardContent').value.trim();
if (!cardId || !content) return;
try {
const res = await fetch('/api/brainstorm.php?action=update_card', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({ card_id: cardId, category, content })
});
const data = await res.json();
if (data.success) {
closeEditCardModal();
location.reload();
} else {
alert(data.error || 'Kon kaart niet bijwerken');
}
} catch(err) {
alert('Netwerkfout bij bijwerken: ' + err.message);
}
}
// --- Toewijzen & Agenda Inplannen Modal Logica ---
function openAssignScheduleModal(card) {
document.getElementById('assignCardId').value = card.id;
document.getElementById('assignCardContentPreview').innerText = card.content;
const sel = document.getElementById('assignColleagueSelect');
if (card.assignee_id) {
sel.value = card.assignee_id;
} else {
sel.value = '';
}
onAssignColleagueChange();
if (card.scheduled_date) {
document.getElementById('assignPlanDate').value = card.scheduled_date;
} else {
document.getElementById('assignPlanDate').value = new Date().toISOString().slice(0, 10);
}
if (card.scheduled_start_time) {
document.getElementById('assignStartTime').value = card.scheduled_start_time.slice(0, 5);
} else {
document.getElementById('assignStartTime').value = '09:00';
}
document.getElementById('foundSlotNotice').classList.add('hidden');
document.getElementById('assignScheduleModal').classList.remove('hidden');
if (window.lucide) lucide.createIcons();
}
function closeAssignScheduleModal() {
document.getElementById('assignScheduleModal').classList.add('hidden');
}
function onAssignColleagueChange() {
const sel = document.getElementById('assignColleagueSelect');
const opt = sel.options[sel.selectedIndex];
const notice = document.getElementById('assignOutlookNotice');
if (opt && opt.getAttribute('data-has-outlook') === '1') {
notice.classList.remove('hidden');
} else {
notice.classList.add('hidden');
}
}
// Zoek eerstvolgend vrij blok van de geselecteerde collega
async function findFreeSlotForColleague() {
const sel = document.getElementById('assignColleagueSelect');
const targetUserId = sel.value ? parseInt(sel.value, 10) : <?= $userId ?>;
const duration = parseInt(document.getElementById('assignDurationMinutes').value, 10) || 30;
const btn = document.getElementById('btnFindSlot');
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 (window.lucide) 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('assignPlanDate').value = slot.date;
document.getElementById('assignStartTime').value = slot.start;
const notice = document.getElementById('foundSlotNotice');
const noticeText = document.getElementById('foundSlotText');
noticeText.innerHTML = `Vrij blok gevonden: <strong>${slot.formatted_date}</strong> om <strong>${slot.start} – ${slot.end}</strong> (${duration} min)`;
notice.classList.remove('hidden');
} else {
alert(data.error || 'Geen beschikbaar tijdblok gevonden binnen werktijden.');
}
} catch(err) {
alert('Netwerkfout bij zoeken van vrij blok: ' + err.message);
} finally {
btn.disabled = false;
btn.innerHTML = origHtml;
if (window.lucide) lucide.createIcons();
}
}
async function submitAssignAndSchedule() {
const cardId = parseInt(document.getElementById('assignCardId').value, 10);
const assigneeId = document.getElementById('assignColleagueSelect').value ? parseInt(document.getElementById('assignColleagueSelect').value, 10) : null;
const planDate = document.getElementById('assignPlanDate').value;
const startTime = document.getElementById('assignStartTime').value;
const durationMinutes = parseInt(document.getElementById('assignDurationMinutes').value, 10) || 30;
const syncOutlook = document.getElementById('assignSyncOutlook').checked;
const btn = document.getElementById('btnSubmitAssign');
const origHtml = btn.innerHTML;
btn.disabled = true;
btn.innerHTML = '<i data-lucide="loader-2" class="w-4 h-4 animate-spin"></i><span>Opslaan...</span>';
if (window.lucide) lucide.createIcons();
try {
const res = await fetch('/api/brainstorm.php?action=assign_and_schedule', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({
card_id: cardId,
assignee_id: assigneeId,
plan_date: planDate,
start_time: startTime,
duration_minutes: durationMinutes,
sync_outlook: syncOutlook
})
});
const data = await res.json();
if (data.success) {
closeAssignScheduleModal();
location.reload();
} else {
alert(data.error || 'Kon toewijzing niet opslaan');
}
} catch(err) {
alert('Netwerkfout bij opslaan: ' + err.message);
} finally {
btn.disabled = false;
btn.innerHTML = origHtml;
if (window.lucide) lucide.createIcons();
}
}
// --- Drag & Drop tussen Brainstorm Categorie Lanes ---
let draggedCanvasCardId = null;
function handleCanvasCardDragStart(e, cardId) {
draggedCanvasCardId = cardId;
try {
e.dataTransfer.effectAllowed = 'move';
e.dataTransfer.setData('text/plain', String(cardId));
} catch (err) {}
setTimeout(() => {
const el = document.getElementById(`card-${cardId}`);
if (el) {
el.classList.add('opacity-40', 'scale-[0.98]');
}
}, 0);
}
function handleCanvasCardDragEnd(e) {
if (draggedCanvasCardId) {
const el = document.getElementById(`card-${draggedCanvasCardId}`);
if (el) {
el.classList.remove('opacity-40', 'scale-[0.98]');
}
draggedCanvasCardId = null;
}
document.querySelectorAll('.brainstorm-lane').forEach(lane => {
lane.classList.remove('bg-indigo-50/80', 'dark:bg-indigo-950/40', 'ring-2', 'ring-indigo-400', 'dark:ring-indigo-500', 'ring-inset');
});
}
function handleCanvasLaneDragOver(e) {
if (!draggedCanvasCardId) return;
e.preventDefault();
e.dataTransfer.dropEffect = 'move';
const lane = e.currentTarget;
if (lane && !lane.classList.contains('ring-2')) {
lane.classList.add('bg-indigo-50/80', 'dark:bg-indigo-950/40', 'ring-2', 'ring-indigo-400', 'dark:ring-indigo-500', 'ring-inset');
}
}
function handleCanvasLaneDragLeave(e) {
const lane = e.currentTarget;
if (lane && !lane.contains(e.relatedTarget)) {
lane.classList.remove('bg-indigo-50/80', 'dark:bg-indigo-950/40', 'ring-2', 'ring-indigo-400', 'dark:ring-indigo-500', 'ring-inset');
}
}
async function handleCanvasLaneDrop(e, targetCategory) {
e.preventDefault();
const lane = e.currentTarget;
if (lane) {
lane.classList.remove('bg-indigo-50/80', 'dark:bg-indigo-950/40', 'ring-2', 'ring-indigo-400', 'dark:ring-indigo-500', 'ring-inset');
}
const cardId = draggedCanvasCardId || parseInt(e.dataTransfer.getData('text/plain'), 10);
draggedCanvasCardId = null;
if (!cardId || !targetCategory) return;
const cardEl = document.getElementById(`card-${cardId}`);
if (!cardEl) return;
// Directe reactivatie van de kaart
cardEl.classList.remove('opacity-40', 'scale-[0.98]', 'pointer-events-none');
document.querySelectorAll('.opacity-40.scale-\\[0\\.98\\]').forEach(el => el.classList.remove('opacity-40', 'scale-[0.98]', 'pointer-events-none'));
const sourceLane = cardEl.closest('.brainstorm-lane');
const sourceCategory = sourceLane ? sourceLane.getAttribute('data-category') : null;
// Als de kaart al in deze categorie zit, doe niets
if (sourceCategory === targetCategory) return;
// Verplaats DOM element direct voor een directe, vloeiende UX
lane.appendChild(cardEl);
const placeholder = lane.querySelector('.lane-empty-placeholder');
if (placeholder) placeholder.remove();
// Kleurstijlen per categorie bijwerken (1-op-1 identiek aan PHP styling)
const categoryStyles = {
'Ideeën': { border: 'border-amber-300/80', darkBorder: 'dark:border-amber-500/40', bg: 'bg-[#fffbeb]', darkBg: 'dark:bg-[#713f12]/35', text: 'text-amber-950', darkText: 'dark:text-amber-100' },
'Knelpunten': { border: 'border-rose-300/80', darkBorder: 'dark:border-rose-500/40', bg: 'bg-[#fff1f2]', darkBg: 'dark:bg-[#881337]/35', text: 'text-rose-950', darkText: 'dark:text-rose-100' },
'Oplossingen': { border: 'border-teal-300/80', darkBorder: 'dark:border-teal-500/40', bg: 'bg-[#f0fdfa]', darkBg: 'dark:bg-[#134e4a]/35', text: 'text-teal-950', darkText: 'dark:text-teal-100' },
'Quick Wins': { border: 'border-indigo-300/80', darkBorder: 'dark:border-indigo-500/40', bg: 'bg-[#eef2ff]', darkBg: 'dark:bg-[#312e81]/35', text: 'text-indigo-950', darkText: 'dark:text-indigo-100' }
};
// Verwijder oude kleurklassen en voeg nieuwe toe
Object.values(categoryStyles).forEach(st => {
cardEl.classList.remove(st.border, st.darkBorder, st.bg, st.darkBg, st.text, st.darkText, 'bg-[#fef9c3]', 'bg-[#ffe4e6]', 'bg-[#ccfbf1]', 'bg-[#e0e7ff]', 'bg-amber-50/60', 'bg-rose-50/60', 'bg-teal-50/60', 'bg-indigo-50/60');
});
const targetStyle = categoryStyles[targetCategory] || categoryStyles['Ideeën'];
cardEl.classList.add(targetStyle.border, targetStyle.darkBorder, targetStyle.bg, targetStyle.darkBg, targetStyle.text, targetStyle.darkText);
// Stuur POST naar backend om categorie en kleur te bewaren
try {
const res = await fetch('/api/brainstorm.php?action=move_card', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({
card_id: cardId,
category: targetCategory
})
});
const data = await res.json();
if (!data.success) {
alert(data.error || 'Kon kaart niet verplaatsen');
location.reload();
}
} catch (err) {
console.error('Fout bij verplaatsen brainstormkaart:', err);
alert('Netwerkfout bij verplaatsen');
location.reload();
}
}
// --- Roadmap Conversie Logica ---
function getSelectedCardIds() {
const checkboxes = document.querySelectorAll('.card-checkbox:checked');
return Array.from(checkboxes).map(cb => cb.value);
}
function openConvertModal() {
const selectedIds = getSelectedCardIds();
if (selectedIds.length === 0) {
alert('Vink eerst minimaal één kaart aan om om te zetten naar de roadmap.');
return;
}
document.getElementById('selectedCountText').innerText = selectedIds.length;
document.getElementById('convertModal').classList.remove('hidden');
}
function closeConvertModal() {
document.getElementById('convertModal').classList.add('hidden');
}
async function submitConvertToRoadmap() {
const cardIds = getSelectedCardIds();
const quarter = document.getElementById('roadmapQuarter').value;
const type = document.getElementById('roadmapType').value;
const priority = document.getElementById('roadmapPriority').value;
const dueDate = document.getElementById('roadmapDueDate').value;
const res = await fetch('/api/brainstorm.php?action=convert_to_roadmap', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({
card_ids: cardIds,
quarter,
type,
priority,
due_date: dueDate
})
});
const data = await res.json();
if (data.success) {
alert(data.message);
window.location.href = '/roadmap.php';
} else {
alert(data.error || 'Er is een fout opgetreden.');
}
}
// --- AI Co-Pilot Canvas Assistentie (Release v1.5) ---
async function aiGenerateSessionBoard(btn, silent = false, attempt = 1, maxAttempts = 3) {
if (!silent && attempt === 1) {
const hasBriefing = <?= !empty($session['description']) ? 'true' : 'false' ?>;
const confirmMsg = hasBriefing
? 'Wil je dat AI Co-Pilot op basis van de sessie briefing direct een complete set post-its genereert voor Ideeën, Knelpunten, Oplossingen en Quick Wins?'
: 'Wil je dat AI Co-Pilot innovatieve post-its genereert voor het whiteboard?';
if (!confirm(confirmMsg)) return;
}
const origHtml = btn ? (btn.dataset.origHtml || btn.innerHTML) : '';
if (btn && !btn.dataset.origHtml) {
btn.dataset.origHtml = origHtml;
}
if (btn) {
btn.disabled = true;
if (attempt > 1) {
btn.innerHTML = `<span class="animate-pulse flex items-center space-x-1.5"><i data-lucide="loader-2" class="w-4 h-4 animate-spin"></i><span>Piekdrukte, wachtrij (${attempt}/${maxAttempts})...</span></span>`;
} else {
btn.innerHTML = '<span class="animate-pulse flex items-center space-x-1.5"><i data-lucide="loader-2" class="w-4 h-4 animate-spin"></i><span>AI genereert post-its...</span></span>';
}
if (typeof lucide !== 'undefined') lucide.createIcons();
}
try {
const res = await fetch('/api/ai.php?action=generate_ideas', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({ session_id: sessionId, count: 4, full_board: 1 })
});
const data = await res.json();
if (data.success) {
// Verwijder auto_ai parameter uit URL bij reload
const cleanUrl = window.location.pathname + '?id=' + sessionId;
window.history.replaceState({}, document.title, cleanUrl);
location.reload();
} else {
const err = (data.error || '').toLowerCase();
const isOverloaded = err.includes('demand') || err.includes('druk') || err.includes('spikes') || err.includes('busy') || err.includes('temporarily') || err.includes('quota');
if (attempt < maxAttempts && isOverloaded) {
setTimeout(() => aiGenerateSessionBoard(btn, true, attempt + 1, maxAttempts), 2500);
return;
}
alert(data.error || 'Kon geen post-its genereren via AI');
if (btn) {
btn.innerHTML = btn.dataset.origHtml || origHtml;
btn.disabled = false;
if (typeof lucide !== 'undefined') lucide.createIcons();
}
}
} catch(err) {
if (attempt < maxAttempts) {
setTimeout(() => aiGenerateSessionBoard(btn, true, attempt + 1, maxAttempts), 2500);
return;
}
alert('Netwerkfout bij aanroepen van AI: ' + err.message);
if (btn) {
btn.innerHTML = btn.dataset.origHtml || origHtml;
btn.disabled = false;
if (typeof lucide !== 'undefined') lucide.createIcons();
}
}
}
async function aiGenerateIdeas(btn) {
return aiGenerateSessionBoard(btn);
}
async function aiCategorizeCards(btn) {
if (!confirm('Wil je dat AI Co-Pilot alle kaartjes automatisch classificeert in de juiste kolom (Ideeën, Knelpunten, Oplossingen of Quick Wins)?')) return;
const origHtml = btn ? btn.innerHTML : '';
if (btn) {
btn.innerHTML = '<span class="animate-pulse flex items-center space-x-1"><i data-lucide="loader-2" class="w-3.5 h-3.5 animate-spin"></i><span>Indelen...</span></span>';
btn.disabled = true;
if (typeof lucide !== 'undefined') lucide.createIcons();
}
try {
const res = await fetch('/api/ai.php?action=categorize_cards', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({ session_id: sessionId })
});
const data = await res.json();
if (data.success) {
alert(`Succes! ${data.updated} kaartjes zijn door AI Co-Pilot opnieuw gecategoriseerd.`);
location.reload();
} else {
alert(data.error || 'Kon kaartjes niet categoriseren');
if (btn) {
btn.innerHTML = origHtml;
btn.disabled = false;
if (typeof lucide !== 'undefined') lucide.createIcons();
}
}
} catch(err) {
alert('Netwerkfout bij aanroepen van AI');
if (btn) {
btn.innerHTML = origHtml;
btn.disabled = false;
if (typeof lucide !== 'undefined') lucide.createIcons();
}
}
}
async function aiSuggestRoadmapMeta(btn) {
const checkboxes = document.querySelectorAll('.card-checkbox:checked');
if (checkboxes.length === 0) return;
const firstContent = checkboxes[0].dataset.content || '';
if (!firstContent) return;
const origHtml = btn ? btn.innerHTML : '';
let attempt = 0;
const maxAttempts = 3;
const executeRequest = async () => {
attempt++;
if (btn) {
btn.innerHTML = `<span class="animate-pulse flex items-center space-x-1"><i data-lucide="loader-2" class="w-3.5 h-3.5 animate-spin"></i><span>${attempt > 1 ? `Wachtrij (${attempt}/${maxAttempts})...` : 'Analyseren...'}</span></span>`;
btn.disabled = true;
if (typeof lucide !== 'undefined') lucide.createIcons();
}
try {
const res = await fetch('/api/ai.php?action=summarize_to_roadmap', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({
content: firstContent,
session_title: <?= json_encode($session['title']) ?>
})
});
const data = await res.json();
if (data.success && data.item) {
if (data.item.quarter && document.getElementById('roadmapQuarter')) {
document.getElementById('roadmapQuarter').value = data.item.quarter;
}
if (data.item.type && document.getElementById('roadmapType')) {
document.getElementById('roadmapType').value = data.item.type;
}
if (data.item.priority && document.getElementById('roadmapPriority')) {
document.getElementById('roadmapPriority').value = data.item.priority;
}
if (btn) {
btn.innerHTML = '<i data-lucide="check" class="w-3.5 h-3.5"></i><span>' + (data.is_fallback ? 'Ingevuld (Standaard)' : 'Ingevuld!') + '</span>';
if (typeof lucide !== 'undefined') lucide.createIcons();
setTimeout(() => {
btn.innerHTML = origHtml;
btn.disabled = false;
if (typeof lucide !== 'undefined') lucide.createIcons();
}, 1800);
}
} else {
const err = (data.error || '').toLowerCase();
const isOverloaded = err.includes('demand') || err.includes('druk') || err.includes('spikes') || err.includes('busy') || err.includes('temporarily') || err.includes('quota');
if (attempt < maxAttempts && isOverloaded) {
if (btn) {
btn.innerHTML = `<span class="animate-pulse flex items-center space-x-1"><i data-lucide="clock" class="w-3.5 h-3.5"></i><span>Piekdrukte, wachtrij...</span></span>`;
if (typeof lucide !== 'undefined') lucide.createIcons();
}
setTimeout(executeRequest, 2000);
return;
}
// Fallback: vul standaardwaarden in zodat het omzetten altijd kan doorgaan
if (document.getElementById('roadmapQuarter') && !document.getElementById('roadmapQuarter').value) {
document.getElementById('roadmapQuarter').value = 'Q3';
}
if (btn) {
btn.innerHTML = '<i data-lucide="check" class="w-3.5 h-3.5"></i><span>Ingevuld (Standaard)</span>';
if (typeof lucide !== 'undefined') lucide.createIcons();
setTimeout(() => {
btn.innerHTML = origHtml;
btn.disabled = false;
if (typeof lucide !== 'undefined') lucide.createIcons();
}, 1800);
}
}
} catch(err) {
if (attempt < maxAttempts) {
setTimeout(executeRequest, 2000);
} else {
if (btn) {
btn.innerHTML = origHtml;
btn.disabled = false;
if (typeof lucide !== 'undefined') lucide.createIcons();
}
}
}
};
await executeRequest();
}
// ==========================================
// AI IDEE UITWERKEN & EXPORTEREN LOGICA
// ==========================================
let currentBreakdownIdea = '';
let currentBreakdownCardId = 0;
let currentBreakdownItems = [];
let currentDestination = 'kanban';
let cachedBoards = [];
async function openAiBreakdownModal(cardId, content) {
currentBreakdownCardId = cardId;
currentBreakdownIdea = content;
currentBreakdownItems = [];
document.getElementById('breakdownIdeaText').innerText = `"${content}"`;
document.getElementById('breakdownSessionLabel').innerText = "Sessie: " + <?= json_encode($session['title']) ?> + " (" + <?= json_encode($session['theme']) ?> + ")";
const modal = document.getElementById('aiBreakdownModal');
modal.classList.remove('hidden');
// Reset weergave
document.getElementById('breakdownLoading').classList.remove('hidden');
document.getElementById('breakdownResult').classList.add('hidden');
document.getElementById('breakdownError').classList.add('hidden');
if (window.lucide) lucide.createIcons();
// Haal borden op
await loadBoardsForBreakdown();
// Haal AI taken op
await fetchAiBreakdown(content);
}
function closeAiBreakdownModal() {
const modal = document.getElementById('aiBreakdownModal');
if (modal) modal.classList.add('hidden');
}
async function regenerateBreakdown() {
if (!currentBreakdownIdea) return;
const regenBtn = document.getElementById('breakdownRegenBtn');
if (regenBtn) {
regenBtn.disabled = true;
regenBtn.innerHTML = '<i data-lucide="loader-2" class="w-3.5 h-3.5 animate-spin"></i><span>Bezig...</span>';
if (window.lucide) lucide.createIcons();
}
document.getElementById('breakdownLoading').classList.remove('hidden');
document.getElementById('breakdownResult').classList.add('hidden');
document.getElementById('breakdownError').classList.add('hidden');
await fetchAiBreakdown(currentBreakdownIdea);
if (regenBtn) {
regenBtn.disabled = false;
regenBtn.innerHTML = '<i data-lucide="refresh-cw" class="w-3.5 h-3.5"></i><span>Opnieuw Genereren</span>';
if (window.lucide) lucide.createIcons();
}
}
async function fetchAiBreakdown(content, attempt = 1, maxAttempts = 3) {
const loadingEl = document.getElementById('breakdownLoading');
const loadingText = loadingEl ? loadingEl.querySelector('p') : null;
if (attempt > 1 && loadingText) {
loadingText.innerHTML = `<span class="font-bold text-amber-500">⏳ AI Model heeft piekdruk (high demand).</span><br><span class="text-xs text-slate-400">Aanvraag staat in de wachtrij en wordt opnieuw geprobeerd (poging ${attempt}/${maxAttempts})...</span>`;
} else if (loadingText) {
loadingText.innerHTML = 'AI ontleedt het idee in behapbare taken...';
}
try {
const res = await fetch('/api/ai.php?action=breakdown_idea', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
session_id: sessionId,
content: content,
session_title: <?= json_encode($session['title']) ?>,
theme: <?= json_encode($session['theme']) ?>,
})
});
const data = await res.json();
if (!data.success || !data.items || data.items.length === 0) {
const err = (data.error || '').toLowerCase();
const isOverloaded = err.includes('demand') || err.includes('druk') || err.includes('spikes') || err.includes('busy') || err.includes('temporarily') || err.includes('quota');
if (attempt < maxAttempts && isOverloaded) {
setTimeout(() => fetchAiBreakdown(content, attempt + 1, maxAttempts), 2500);
return;
}
// Graceful Fallback: zorg dat het omzetten van het idee altijd vlekkeloos doorgaat
const cleanTitle = (content || '').split('\n')[0].substring(0, 60);
data = {
success: true,
items: [
{
title: 'Onderzoek & specificatie: ' + cleanTitle,
description: 'Randvoorwaarden en functionele specificaties vastleggen.',
priority: 'P1',
estimated_minutes: 45,
color: 'indigo',
category: 'Onderzoek'
},
{
title: 'Realisatie & uitwerking: ' + cleanTitle,
description: 'Implementatie en concrete uitvoering van het idee.',
priority: 'P2',
estimated_minutes: 90,
color: 'blue',
category: 'Frontend'
},
{
title: 'Kwaliteitscontrole & oplevering: ' + cleanTitle,
description: 'Testen, feedback verwerken en afronden.',
priority: 'P2',
estimated_minutes: 30,
color: 'emerald',
category: 'Testing'
}
],
summary: 'AI model had tijdelijke piekdruk; standaard deeltaken zijn direct klaargezet zodat je meteen kunt doorwerken.'
};
}
currentBreakdownItems = data.items.map((it, idx) => ({
id: idx + 1,
selected: true,
title: it.title || 'Nieuwe taak',
description: it.description || '',
priority: it.priority || 'P2',
estimated_minutes: parseInt(it.estimated_minutes) || 45,
color: it.color || 'indigo',
category: it.category || 'Feature',
}));
document.getElementById('breakdownSummaryText').innerText = data.summary || 'Dit idee is opgesplitst in behapbare deeltaken voor snelle executie.';
renderBreakdownTasks();
document.getElementById('breakdownLoading').classList.add('hidden');
document.getElementById('breakdownResult').classList.remove('hidden');
if (window.lucide) lucide.createIcons();
} catch (err) {
if (attempt < maxAttempts) {
setTimeout(() => fetchAiBreakdown(content, attempt + 1, maxAttempts), 2500);
return;
}
const cleanTitle = (content || '').split('\n')[0].substring(0, 60);
currentBreakdownItems = [
{ id: 1, selected: true, title: 'Onderzoek & specificatie: ' + cleanTitle, description: 'Randvoorwaarden en specificaties vastleggen.', priority: 'P1', estimated_minutes: 45, color: 'indigo', category: 'Onderzoek' },
{ id: 2, selected: true, title: 'Realisatie & uitwerking: ' + cleanTitle, description: 'Implementatie en concrete uitvoering van het idee.', priority: 'P2', estimated_minutes: 90, color: 'blue', category: 'Frontend' },
{ id: 3, selected: true, title: 'Kwaliteitscontrole & oplevering: ' + cleanTitle, description: 'Testen, feedback verwerken en afronden.', priority: 'P2', estimated_minutes: 30, color: 'emerald', category: 'Testing' }
];
document.getElementById('breakdownSummaryText').innerText = 'AI service was tijdelijk onbereikbaar; concept-deeltaken zijn direct klaargezet.';
renderBreakdownTasks();
document.getElementById('breakdownLoading').classList.add('hidden');
document.getElementById('breakdownResult').classList.remove('hidden');
if (window.lucide) lucide.createIcons();
}
}
function renderBreakdownTasks() {
const container = document.getElementById('breakdownTasksContainer');
const badge = document.getElementById('breakdownTasksBadge');
if (!container) return;
badge.innerText = currentBreakdownItems.length;
container.innerHTML = '';
currentBreakdownItems.forEach((item, idx) => {
const div = document.createElement('div');
div.className = 'p-3 rounded-xl border border-slate-200 dark:border-slate-800 bg-white dark:bg-[#0c0f17] space-y-2 text-xs transition hover:border-indigo-300 dark:hover:border-indigo-700/60 shadow-2xs';
div.innerHTML = `
<div class="flex items-start justify-between gap-2">
<div class="flex items-center space-x-2 flex-1">
<input type="checkbox" onchange="toggleBreakdownItemSelect(${idx}, this.checked)" ${item.selected ? 'checked' : ''} class="w-4 h-4 rounded text-indigo-600 focus:ring-0 border-slate-300 dark:border-slate-700 cursor-pointer">
<input type="text" value="${escapeHtml(item.title)}" onchange="updateBreakdownItemField(${idx}, 'title', this.value)" placeholder="Taaktitel" class="w-full font-bold text-slate-900 dark:text-white bg-transparent border-b border-transparent hover:border-slate-300 focus:border-indigo-500 focus:outline-none px-1 py-0.5 text-xs">
</div>
<div class="flex items-center space-x-1.5 flex-shrink-0">
<select onchange="updateBreakdownItemField(${idx}, 'priority', this.value)" class="bg-slate-100 dark:bg-slate-800 text-[10px] font-bold rounded-lg px-2 py-1 text-slate-700 dark:text-slate-300 border-none focus:ring-0 cursor-pointer">
<option value="P1" ${item.priority === 'P1' ? 'selected' : ''}>P1 (Brandend)</option>
<option value="P2" ${item.priority === 'P2' ? 'selected' : ''}>P2 (Normaal)</option>
<option value="P3" ${item.priority === 'P3' ? 'selected' : ''}>P3 (Laag)</option>
</select>
<select onchange="updateBreakdownItemField(${idx}, 'estimated_minutes', parseInt(this.value))" class="bg-slate-100 dark:bg-slate-800 text-[10px] font-semibold rounded-lg px-2 py-1 text-slate-700 dark:text-slate-300 border-none focus:ring-0 cursor-pointer">
<option value="15" ${item.estimated_minutes === 15 ? 'selected' : ''}>15m</option>
<option value="30" ${item.estimated_minutes === 30 ? 'selected' : ''}>30m</option>
<option value="45" ${item.estimated_minutes === 45 ? 'selected' : ''}>45m</option>
<option value="60" ${item.estimated_minutes === 60 ? 'selected' : ''}>60m</option>
<option value="90" ${item.estimated_minutes === 90 ? 'selected' : ''}>90m</option>
<option value="120" ${item.estimated_minutes === 120 ? 'selected' : ''}>120m</option>
</select>
<button type="button" onclick="removeBreakdownItem(${idx})" class="p-1 text-slate-400 hover:text-rose-500 rounded transition cursor-pointer" title="Verwijder taak">
<i data-lucide="trash-2" class="w-3.5 h-3.5"></i>
</button>
</div>
</div>
<textarea onchange="updateBreakdownItemField(${idx}, 'description', this.value)" placeholder="Toelichting & acceptatiecriteria..." rows="2" class="w-full bg-slate-50 dark:bg-slate-900/50 border border-slate-200 dark:border-slate-800 rounded-lg p-2 text-[11px] text-slate-700 dark:text-slate-300 focus:outline-none focus:border-indigo-500 leading-relaxed resize-none">${escapeHtml(item.description)}</textarea>
`;
container.appendChild(div);
});
updateExportButtonText();
if (window.lucide) lucide.createIcons();
}
function escapeHtml(text) {
if (!text) return '';
return text.toString()
.replace(/&/g, "&amp;")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;")
.replace(/"/g, "&quot;")
.replace(/'/g, "&#039;");
}
function toggleBreakdownItemSelect(idx, isChecked) {
if (currentBreakdownItems[idx]) {
currentBreakdownItems[idx].selected = isChecked;
updateExportButtonText();
}
}
function updateBreakdownItemField(idx, field, value) {
if (currentBreakdownItems[idx]) {
currentBreakdownItems[idx][field] = value;
}
}
function addManualBreakdownItem() {
currentBreakdownItems.push({
id: Date.now(),
selected: true,
title: 'Nieuwe deel-taak',
description: 'Context en criteria toevoegen...',
priority: 'P2',
estimated_minutes: 30,
color: 'indigo',
category: 'Feature'
});
renderBreakdownTasks();
}
function removeBreakdownItem(idx) {
currentBreakdownItems.splice(idx, 1);
renderBreakdownTasks();
}
function setBreakdownDestination(dest) {
currentDestination = dest;
const dests = ['kanban', 'timeline', 'planner'];
dests.forEach(d => {
const btn = document.getElementById('destBtn-' + d);
const opt = document.getElementById('destOptions-' + d);
if (d === dest) {
btn.className = 'dest-btn p-2.5 rounded-xl border text-xs font-bold transition flex flex-col items-center justify-center space-y-1 cursor-pointer bg-indigo-50 dark:bg-indigo-950/40 border-indigo-500 text-indigo-700 dark:text-indigo-300';
if (opt) opt.classList.remove('hidden');
} else {
btn.className = 'dest-btn p-2.5 rounded-xl border text-xs font-semibold transition flex flex-col items-center justify-center space-y-1 cursor-pointer bg-white dark:bg-slate-900 border-slate-200 dark:border-slate-800 text-slate-600 dark:text-slate-400 hover:border-slate-300';
if (opt) opt.classList.add('hidden');
}
});
updateExportButtonText();
}
function updateExportButtonText() {
const selectedCount = currentBreakdownItems.filter(i => i.selected).length;
const btnText = document.getElementById('breakdownExportBtnText');
if (!btnText) return;
let targetLabel = 'Kanban Bord';
if (currentDestination === 'timeline') targetLabel = 'Tijdlijn';
if (currentDestination === 'planner') targetLabel = 'Dagplanner';
btnText.innerText = `Exporteer ${selectedCount} ${selectedCount === 1 ? 'Taak' : 'Taken'} naar ${targetLabel}`;
}
async function loadBoardsForBreakdown() {
try {
const res = await fetch('/api/board.php?action=get_boards');
const data = await res.json();
if (data.success && data.boards) {
cachedBoards = data.boards;
const selectKanban = document.getElementById('breakdownBoardSelect');
const selectTimeline = document.getElementById('breakdownTimelineBoardSelect');
selectKanban.innerHTML = '';
selectTimeline.innerHTML = '';
cachedBoards.forEach(b => {
const opt1 = document.createElement('option');
opt1.value = b.id;
opt1.textContent = b.title;
selectKanban.appendChild(opt1);
const opt2 = document.createElement('option');
opt2.value = b.id;
opt2.textContent = b.title;
selectTimeline.appendChild(opt2);
});
if (cachedBoards.length > 0) {
await onBreakdownBoardChange(cachedBoards[0].id);
}
}
} catch (e) {
console.error('Fout bij ophalen van borden:', e);
}
}
async function onBreakdownBoardChange(boardId) {
if (!boardId) return;
try {
const res = await fetch(`/api/board.php?action=get_board_data&board_id=${boardId}`);
const data = await res.json();
const laneSelect = document.getElementById('breakdownLaneSelect');
laneSelect.innerHTML = '';
if (data.success && data.lanes && data.lanes.length > 0) {
data.lanes.forEach(lane => {
const opt = document.createElement('option');
opt.value = lane.id;
opt.textContent = lane.name;
laneSelect.appendChild(opt);
});
} else {
laneSelect.innerHTML = '<option value="0">Standaard kolom</option>';
}
} catch (e) {
console.error('Fout bij ophalen van lanes:', e);
}
}
async function submitBreakdownExport() {
const selectedItems = currentBreakdownItems.filter(i => i.selected);
if (selectedItems.length === 0) {
alert('Selecteer minimaal één taak om te exporteren.');
return;
}
const btn = document.getElementById('breakdownExportBtn');
const btnText = document.getElementById('breakdownExportBtnText');
const origHtml = btn.innerHTML;
btn.disabled = true;
btn.innerHTML = '<span class="flex items-center space-x-2"><i data-lucide="loader-2" class="w-4 h-4 animate-spin"></i><span>Exporteren...</span></span>';
if (window.lucide) lucide.createIcons();
const payload = {
destination: currentDestination,
items: selectedItems,
};
if (currentDestination === 'kanban') {
payload.board_id = document.getElementById('breakdownBoardSelect').value;
payload.lane_id = document.getElementById('breakdownLaneSelect').value;
} else if (currentDestination === 'timeline') {
payload.board_id = document.getElementById('breakdownTimelineBoardSelect').value;
payload.start_date = document.getElementById('breakdownStartDate').value;
payload.end_date = document.getElementById('breakdownEndDate').value;
} else if (currentDestination === 'planner') {
payload.plan_date = document.getElementById('breakdownPlanDate').value;
}
try {
const res = await fetch('/api/ai.php?action=save_breakdown_items', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload)
});
const data = await res.json();
if (data.success) {
closeAiBreakdownModal();
let destMsg = '';
let targetUrl = '';
if (currentDestination === 'kanban') {
destMsg = `Succesvol ${data.created_count} taken toegevoegd aan het Kanban Bord!`;
targetUrl = `/board.php?id=${payload.board_id}`;
} else if (currentDestination === 'timeline') {
destMsg = `Succesvol ${data.created_count} taken gepland op de Project Tijdlijn!`;
targetUrl = `/timeline.php?board_id=${payload.board_id}`;
} else {
destMsg = `Succesvol ${data.created_count} taken toegevoegd aan uw Dagplanner!`;
targetUrl = `/planner.php?date=${payload.plan_date}`;
}
if (confirm(`${destMsg}\n\nWilt u nu direct naar deze pagina gaan om ze te bekijken?`)) {
window.location.href = targetUrl;
}
} else {
alert('Fout bij exporteren van taken: ' + (data.error || 'Onbekende fout'));
}
} catch (err) {
alert('Netwerkfout bij exporteren: ' + err.message);
} finally {
btn.disabled = false;
btn.innerHTML = origHtml;
if (window.lucide) lucide.createIcons();
}
}
function promptDeleteCurrentCanvasSession() {
const modal = document.getElementById('deleteCanvasSessionModal');
if (modal) {
modal.classList.remove('hidden');
modal.classList.add('flex');
}
if (window.lucide) lucide.createIcons();
}
function closeDeleteCanvasSessionModal() {
const modal = document.getElementById('deleteCanvasSessionModal');
if (modal) {
modal.classList.add('hidden');
modal.classList.remove('flex');
}
}
async function executeDeleteCanvasSession() {
const btn = document.getElementById('btnConfirmDeleteCanvasSession');
const origHtml = btn.innerHTML;
btn.disabled = true;
btn.innerHTML = '<i data-lucide="loader-2" class="w-4 h-4 animate-spin"></i><span>Verwijderen...</span>';
if (window.lucide) lucide.createIcons();
try {
const res = await fetch('/api/brainstorm.php?action=delete_session', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ session_id: <?= (int)$sessionId ?> })
});
const data = await res.json();
if (data.success) {
window.location.href = '/brainstorm.php';
} else {
alert('Fout bij verwijderen: ' + (data.error || 'Onbekende fout'));
btn.disabled = false;
btn.innerHTML = origHtml;
if (window.lucide) lucide.createIcons();
}
} catch (err) {
alert('Netwerkfout bij verwijderen: ' + err.message);
btn.disabled = false;
btn.innerHTML = origHtml;
if (window.lucide) lucide.createIcons();
}
}
document.addEventListener('keydown', (e) => {
if (e.key === 'Escape') {
closeAddCardModal();
closeConvertModal();
closeAiBreakdownModal();
closeDeleteCanvasSessionModal();
}
});
// Automatische AI-generatie bij doorgestuurde auto_ai=1 parameter (bijv. direct na creatie)
document.addEventListener('DOMContentLoaded', () => {
const urlParams = new URLSearchParams(window.location.search);
if (urlParams.get('auto_ai') === '1') {
// Toon subtiele toast of indicator en start generatie
const aiBtn = document.querySelector('button[onclick*="aiGenerateSessionBoard"]');
aiGenerateSessionBoard(aiBtn, true);
}
});
</script>
<?php require_once __DIR__ . '/includes/footer.php'; ?>