1747 lines
90 KiB
PHP
1747 lines
90 KiB
PHP
<?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>
|
||
/* --- Realistische Echte Post-it Sticky Note Styling --- */
|
||
.postit-note {
|
||
position: relative;
|
||
border-top-left-radius: 2px;
|
||
border-top-right-radius: 3px;
|
||
border-bottom-right-radius: 4px;
|
||
border-bottom-left-radius: 14px;
|
||
box-shadow:
|
||
0 1px 1px rgba(0, 0, 0, 0.08),
|
||
0 4px 6px -1px rgba(0, 0, 0, 0.08),
|
||
0 10px 18px -4px rgba(0, 0, 0, 0.12),
|
||
-2px 6px 8px -2px rgba(0, 0, 0, 0.08);
|
||
transition: transform 0.2s cubic-bezier(0.16, 1, 0.3, 1), box-shadow 0.2s ease;
|
||
}
|
||
.postit-note:hover {
|
||
transform: translateY(-3px) rotate(-0.5deg);
|
||
box-shadow:
|
||
0 2px 4px rgba(0, 0, 0, 0.06),
|
||
0 8px 12px -2px rgba(0, 0, 0, 0.12),
|
||
0 16px 24px -4px rgba(0, 0, 0, 0.16),
|
||
-4px 8px 12px -2px rgba(0, 0, 0, 0.12);
|
||
}
|
||
/* Echte sticky note curl & schaduw links onder */
|
||
.postit-note::after {
|
||
content: '';
|
||
position: absolute;
|
||
bottom: -1px;
|
||
left: -1px;
|
||
width: 24px;
|
||
height: 24px;
|
||
background:
|
||
linear-gradient(135deg, rgba(0, 0, 0, 0.22) 0%, rgba(0, 0, 0, 0.08) 35%, transparent 45%),
|
||
linear-gradient(315deg, rgba(255, 255, 255, 0.8) 0%, rgba(255, 255, 255, 0.2) 40%, transparent 48%);
|
||
border-bottom-left-radius: 4px;
|
||
pointer-events: none;
|
||
z-index: 5;
|
||
}
|
||
/* Subtiele klevende plakrand aan de bovenkant */
|
||
.postit-note::before {
|
||
content: '';
|
||
position: absolute;
|
||
top: 0;
|
||
left: 0;
|
||
right: 0;
|
||
height: 6px;
|
||
background: linear-gradient(to bottom, rgba(0, 0, 0, 0.04), transparent);
|
||
border-top-left-radius: 2px;
|
||
border-top-right-radius: 3px;
|
||
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="aiGenerateIdeas(this)" class="flex items-center space-x-1.5 px-3 py-2 rounded-xl bg-violet-50 hover:bg-violet-100 dark:bg-violet-950/50 dark:hover:bg-violet-900/50 text-violet-700 dark:text-violet-300 border border-violet-200 dark:border-violet-800/60 text-xs font-bold transition cursor-pointer shadow-xs" title="Genereer 4 innovatieve ideeën via AI Co-Pilot">
|
||
<i data-lucide="sparkles" class="w-4 h-4 text-violet-500"></i>
|
||
<span>💡 AI Ideeën</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>✨ Auto-Categoriseer</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>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 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">
|
||
<div class="flex items-center space-x-2 font-bold mb-1">
|
||
<i data-lucide="alert-circle" class="w-4 h-4 text-rose-600 dark:text-rose-400"></i>
|
||
<span>Kon taken niet genereren</span>
|
||
</div>
|
||
<p id="breakdownErrorMsg" class="text-[11px] text-rose-700 dark:text-rose-300"></p>
|
||
</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>
|
||
|
||
<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 aiGenerateIdeas(btn) {
|
||
if (!confirm('Wil je AI Co-Pilot 4 innovatieve ideeën laten genereren en direct op het canvas plaatsen?')) 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>Genereren...</span></span>';
|
||
btn.disabled = true;
|
||
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 })
|
||
});
|
||
const data = await res.json();
|
||
if (data.success) {
|
||
location.reload();
|
||
} else {
|
||
alert(data.error || 'Kon geen ideeën genereren via AI');
|
||
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 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 : '';
|
||
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>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>Ingevuld!</span>';
|
||
if (typeof lucide !== 'undefined') lucide.createIcons();
|
||
setTimeout(() => {
|
||
btn.innerHTML = origHtml;
|
||
btn.disabled = false;
|
||
if (typeof lucide !== 'undefined') lucide.createIcons();
|
||
}, 1800);
|
||
}
|
||
} else {
|
||
alert(data.error || 'Kon geen AI advies ophalen');
|
||
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();
|
||
}
|
||
}
|
||
}
|
||
|
||
// ==========================================
|
||
// 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) {
|
||
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) {
|
||
document.getElementById('breakdownLoading').classList.add('hidden');
|
||
document.getElementById('breakdownError').classList.remove('hidden');
|
||
document.getElementById('breakdownErrorMsg').innerText = data.error || 'Geen taken gegenereerd door AI.';
|
||
return;
|
||
}
|
||
|
||
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) {
|
||
document.getElementById('breakdownLoading').classList.add('hidden');
|
||
document.getElementById('breakdownError').classList.remove('hidden');
|
||
document.getElementById('breakdownErrorMsg').innerText = 'Netwerkfout bij communicatie met AI: ' + err.message;
|
||
}
|
||
}
|
||
|
||
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, "&")
|
||
.replace(/</g, "<")
|
||
.replace(/>/g, ">")
|
||
.replace(/"/g, """)
|
||
.replace(/'/g, "'");
|
||
}
|
||
|
||
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();
|
||
}
|
||
}
|
||
|
||
document.addEventListener('keydown', (e) => {
|
||
if (e.key === 'Escape') {
|
||
closeAddCardModal();
|
||
closeConvertModal();
|
||
closeAiBreakdownModal();
|
||
}
|
||
});
|
||
</script>
|
||
|
||
<?php require_once __DIR__ . '/includes/footer.php'; ?>
|