Postits en editable items.
This commit is contained in:
+559
-46
@@ -25,51 +25,112 @@ if (!$session) {
|
||||
exit;
|
||||
}
|
||||
|
||||
// Haal alle kaarten op van deze sessie
|
||||
// Haal alle kaarten op van deze sessie inclusief toewijzing
|
||||
$stmtCards = $db->prepare("
|
||||
SELECT * FROM brainstorm_cards
|
||||
WHERE session_id = ?
|
||||
ORDER BY votes DESC, created_at ASC
|
||||
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-200/80 dark:border-amber-800/40',
|
||||
'bg' => 'bg-amber-50/60 dark:bg-amber-950/20',
|
||||
'badge' => 'bg-amber-500/15 text-amber-800 dark:text-amber-300'
|
||||
'border' => 'border-amber-300 dark:border-amber-600/40',
|
||||
'bg' => 'bg-[#fef9c3] dark:bg-[#713f12]/30 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-200/80 dark:border-rose-800/40',
|
||||
'bg' => 'bg-rose-50/60 dark:bg-rose-950/20',
|
||||
'badge' => 'bg-rose-500/15 text-rose-800 dark:text-rose-300'
|
||||
'border' => 'border-rose-300 dark:border-rose-600/40',
|
||||
'bg' => 'bg-[#ffe4e6] dark:bg-[#881337]/30 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-200/80 dark:border-teal-800/40',
|
||||
'bg' => 'bg-teal-50/60 dark:bg-teal-950/20',
|
||||
'badge' => 'bg-teal-500/15 text-teal-800 dark:text-teal-300'
|
||||
'border' => 'border-teal-300 dark:border-teal-600/40',
|
||||
'bg' => 'bg-[#ccfbf1] dark:bg-[#134e4a]/30 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-200/80 dark:border-indigo-800/40',
|
||||
'bg' => 'bg-indigo-50/60 dark:bg-indigo-950/20',
|
||||
'badge' => 'bg-indigo-500/15 text-indigo-800 dark:text-indigo-300'
|
||||
'border' => 'border-indigo-300 dark:border-indigo-600/40',
|
||||
'bg' => 'bg-[#e0e7ff] dark:bg-[#312e81]/30 text-indigo-950 dark:text-indigo-100',
|
||||
'badge' => 'bg-indigo-500/20 text-indigo-900 dark:text-indigo-200'
|
||||
],
|
||||
];
|
||||
?>
|
||||
|
||||
<style>
|
||||
/* --- Realistische Post-it Styling met Opkrullende Rand Links Onder --- */
|
||||
.postit-note {
|
||||
position: relative;
|
||||
border-radius: 4px 18px 4px 18px;
|
||||
box-shadow: 0 4px 12px -2px rgba(0, 0, 0, 0.12), 0 2px 6px -1px rgba(0, 0, 0, 0.08);
|
||||
transition: transform 0.2s cubic-bezier(0.16, 1, 0.3, 1), box-shadow 0.2s ease;
|
||||
filter: drop-shadow(0 2px 4px rgba(0, 0, 0, 0.04));
|
||||
}
|
||||
.postit-note:hover {
|
||||
transform: translateY(-3px) scale(1.01);
|
||||
box-shadow: 0 12px 20px -4px rgba(0, 0, 0, 0.16), 0 6px 10px -2px rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
/* Opkrullend hoekje links onder */
|
||||
.postit-note::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
bottom: -1px;
|
||||
left: -1px;
|
||||
width: 22px;
|
||||
height: 22px;
|
||||
background: linear-gradient(135deg, rgba(0,0,0,0.18) 0%, rgba(0,0,0,0.06) 45%, transparent 50%);
|
||||
border-bottom-left-radius: 4px;
|
||||
box-shadow: -2px 2px 4px rgba(0,0,0,0.12);
|
||||
pointer-events: none;
|
||||
z-index: 5;
|
||||
}
|
||||
/* Subtiele tape / plakrand bovenaan */
|
||||
.postit-note::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 15%;
|
||||
right: 15%;
|
||||
height: 4px;
|
||||
background: rgba(255, 255, 255, 0.4);
|
||||
border-bottom-left-radius: 2px;
|
||||
border-bottom-right-radius: 2px;
|
||||
pointer-events: none;
|
||||
}
|
||||
.dark .postit-note::before {
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
</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">
|
||||
@@ -146,44 +207,89 @@ $categories = [
|
||||
Nog geen kaarten in deze categorie.
|
||||
</div>
|
||||
<?php else: ?>
|
||||
<?php foreach ($catCards as $card): ?>
|
||||
<?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)"
|
||||
class="brainstorm-card group relative rounded-2xl p-4 border <?= $catConfig['border'] ?> <?= $catConfig['bg'] ?> backdrop-blur-sm shadow-sm hover:shadow-md transition-all duration-150 cursor-grab active:cursor-grabbing hover:-translate-y-0.5 flex flex-col justify-between"
|
||||
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.5">
|
||||
<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 dark:bg-slate-900 border-slate-300 dark:border-slate-700 text-indigo-600 focus:ring-0">
|
||||
<span class="text-[11px] text-slate-500 dark:text-slate-400 font-medium">Selecteer</span>
|
||||
<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>
|
||||
|
||||
<?php if (!empty($card['converted_to_roadmap_item_id'])): ?>
|
||||
<span class="px-2 py-0.5 rounded-md bg-teal-50 dark:bg-teal-500/15 text-teal-700 dark:text-teal-400 font-bold text-[10px] flex items-center space-x-1 border border-teal-200 dark:border-teal-500/20">
|
||||
<i data-lucide="check" class="w-3 h-3"></i>
|
||||
<span>Op Roadmap</span>
|
||||
</span>
|
||||
<?php endif; ?>
|
||||
<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="text-left py-1">
|
||||
<p class="text-xs text-slate-800 dark:text-slate-100 font-medium leading-relaxed whitespace-pre-wrap break-words text-left">
|
||||
<div class="text-left py-1" onclick="openEditCardModal(<?= $cardJson ?>)">
|
||||
<p class="text-xs font-semibold leading-relaxed whitespace-pre-wrap break-words text-left cursor-pointer" title="Klik om te bewerken">
|
||||
<?= htmlspecialchars($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-3 pt-2.5 border-t border-slate-200/60 dark:border-slate-800/60 flex items-center justify-between text-[10px] text-slate-500 dark:text-slate-400">
|
||||
<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-semibold text-slate-700 dark:text-slate-300"><?= htmlspecialchars($card['author_name'] ?? 'Collega') ?></span>
|
||||
<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-2 py-0.5 rounded-lg bg-violet-100/90 hover:bg-violet-200 dark:bg-violet-950/60 dark:hover:bg-violet-900/60 text-violet-700 dark:text-violet-300 font-bold text-[10px] transition cursor-pointer border border-violet-200/80 dark:border-violet-800/60 shadow-2xs flex-shrink-0" title="Splits dit idee uit naar behapbare taken via Google Gemini Pro">
|
||||
<i data-lucide="sparkles" class="w-3 h-3 text-violet-500"></i>
|
||||
<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; ?>
|
||||
@@ -191,13 +297,13 @@ $categories = [
|
||||
|
||||
<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-1 rounded-lg bg-white/80 hover:bg-white dark:bg-slate-800/80 dark:hover:bg-slate-700 text-slate-600 dark:text-slate-300 hover:text-amber-500 dark:hover:text-amber-400 border border-slate-200/80 dark:border-transparent transition shadow-xs cursor-pointer">
|
||||
<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-slate-400 hover:text-rose-600 dark:text-slate-500 dark:hover:text-rose-400 transition opacity-0 group-hover:opacity-100 cursor-pointer">
|
||||
<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>
|
||||
@@ -217,23 +323,66 @@ $categories = [
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Modal: Nieuwe Kaart Toevoegen -->
|
||||
<div id="addCardModal" onclick="if(event.target === this) closeAddCardModal()" class="hidden fixed inset-0 z-50 bg-slate-950/70 backdrop-blur-sm flex items-center justify-center p-4">
|
||||
<!-- 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="sticky-note" class="w-4 h-4 text-indigo-600 dark:text-indigo-400"></i>
|
||||
<span>Brainstormkaart Toevoegen</span>
|
||||
<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="closeAddCardModal()" 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">
|
||||
<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="modalCardCategory" 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">
|
||||
<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>
|
||||
@@ -242,15 +391,125 @@ $categories = [
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block text-xs font-semibold text-slate-700 dark:text-slate-300 mb-1">Idee / Beschrijving</label>
|
||||
<textarea id="modalCardContent" rows="4" placeholder="Schrijf hier uw inzicht, idee of knelpunt..." class="w-full bg-slate-50/70 dark:bg-[#0c0f17]/60 border border-slate-300 dark:border-slate-800 rounded-xl p-3 text-slate-900 dark:text-white text-xs placeholder-slate-400 dark:placeholder-slate-500 focus:outline-none focus:ring-1 focus:ring-indigo-500 resize-none shadow-2xs"></textarea>
|
||||
<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="submitNewCard()" 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">
|
||||
Plaats op Canvas
|
||||
<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="closeAddCardModal()" 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">
|
||||
<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>
|
||||
@@ -564,6 +823,260 @@ async function deleteCard(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;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user