Files
Fociz/brainstorm_canvas.php
T
NiekRabelink 9da056b480 Add roadmap management feature with database schema and user management
- Implemented roadmap.php for displaying and managing roadmap items with filters for quarter and status.
- Created schema.sql to define the database structure for users, invites, daily plans, tasks, brainstorm sessions, brainstorm cards, roadmap items, and integrations.
- Developed users.php for admin panel to manage team members and invitations, including functionality to create and revoke invites.
2026-09-08 12:47:11 +02:00

372 lines
19 KiB
PHP

<?php
/**
* Fociz Interactief Brainstorm Canvas & Roadmap Converter
*/
require_once __DIR__ . '/includes/header.php';
$db = get_db_connection();
$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-400'>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
$stmtCards = $db->prepare("
SELECT * FROM brainstorm_cards
WHERE session_id = ?
ORDER BY votes DESC, created_at ASC
");
$stmtCards->execute([$sessionId]);
$cards = $stmtCards->fetchAll();
$categories = [
'Ideeën' => ['title' => 'Ideeën & Concepten', 'icon' => 'lightbulb', 'color' => 'amber', 'border' => 'border-amber-500/40', 'bg' => 'bg-amber-950/20', 'badge' => 'bg-amber-500/20 text-amber-300'],
'Knelpunten' => ['title' => 'Knelpunten & Uitdagingen', 'icon' => 'alert-triangle', 'color' => 'rose', 'border' => 'border-rose-500/40', 'bg' => 'bg-rose-950/20', 'badge' => 'bg-rose-500/20 text-rose-300'],
'Oplossingen' => ['title' => 'Oplossingen & Features', 'icon' => 'rocket', 'color' => 'emerald', 'border' => 'border-emerald-500/40', 'bg' => 'bg-emerald-950/20', 'badge' => 'bg-emerald-500/20 text-emerald-300'],
'Quick Wins' => ['title' => 'Quick Wins (Direct te doen)', 'icon' => 'zap', 'color' => 'blue', 'border' => 'border-blue-500/40', 'bg' => 'bg-blue-950/20', 'badge' => 'bg-blue-500/20 text-blue-300'],
];
?>
<div class="space-y-6">
<!-- Top Bar -->
<div class="flex flex-col lg:flex-row lg:items-center justify-between gap-4 bg-slate-900/90 border border-slate-800 rounded-2xl p-5 shadow-xl">
<div class="flex items-center space-x-4">
<a href="/brainstorm.php" class="p-2 rounded-xl bg-slate-800 text-slate-400 hover:text-white transition">
<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 py-0.5 rounded bg-amber-500/10 text-amber-400 font-semibold border border-amber-500/20">
<?= htmlspecialchars($session['theme']) ?>
</span>
<span class="text-slate-500">Gestart door <?= htmlspecialchars($session['creator_name']) ?></span>
</div>
<h1 class="text-xl sm:text-2xl font-black text-white tracking-tight mt-0.5">
<?= htmlspecialchars($session['title']) ?>
</h1>
</div>
</div>
<div class="flex items-center space-x-3">
<button onclick="openAddCardModal()" class="flex items-center space-x-1.5 px-3.5 py-2 rounded-xl bg-slate-800 hover:bg-slate-700 text-slate-200 text-xs font-semibold border border-slate-700 transition">
<i data-lucide="plus" class="w-4 h-4 text-amber-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-gradient-to-r from-emerald-500 to-teal-400 hover:from-emerald-600 hover:to-teal-500 text-slate-950 text-xs font-bold transition shadow-lg shadow-emerald-500/20">
<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="bg-slate-900/60 border border-slate-800/80 rounded-2xl p-4 flex flex-col min-h-[500px]">
<!-- Column Header -->
<div class="flex items-center justify-between pb-3 mb-3 border-b border-slate-800">
<div class="flex items-center space-x-2">
<i data-lucide="<?= $catConfig['icon'] ?>" class="w-4 h-4 text-<?= $catConfig['color'] ?>-400"></i>
<h3 class="text-xs font-bold text-white uppercase tracking-wider"><?= $catConfig['title'] ?></h3>
</div>
<span class="text-xs font-mono font-semibold px-2 py-0.5 rounded-full bg-slate-800 text-slate-400">
<?= count($catCards) ?>
</span>
</div>
<!-- Cards Container -->
<div class="flex-1 space-y-3">
<?php if (empty($catCards)): ?>
<div class="py-8 text-center text-slate-600 text-xs">
Nog geen kaarten in deze categorie.
</div>
<?php else: ?>
<?php foreach ($catCards as $card): ?>
<div class="group relative rounded-xl p-4 border <?= $catConfig['border'] ?> <?= $catConfig['bg'] ?> backdrop-blur-sm shadow-md transition hover:-translate-y-0.5" id="card-<?= $card['id'] ?>">
<!-- Top: Checkbox voor selectie + Roadmap badge -->
<div class="flex items-center justify-between mb-2">
<label class="flex items-center space-x-2 cursor-pointer">
<input type="checkbox" value="<?= $card['id'] ?>" class="card-checkbox w-4 h-4 rounded bg-slate-900 border-slate-700 text-emerald-500 focus:ring-0">
<span class="text-[10px] text-slate-400">Selecteer</span>
</label>
<?php if (!empty($card['converted_to_roadmap_item_id'])): ?>
<span class="px-2 py-0.5 rounded-full bg-emerald-500/20 text-emerald-300 font-semibold text-[10px] flex items-center space-x-1 border border-emerald-500/30">
<i data-lucide="check" class="w-3 h-3"></i>
<span>Op Roadmap</span>
</span>
<?php endif; ?>
</div>
<!-- Inhoud -->
<p class="text-xs text-slate-200 leading-relaxed whitespace-pre-wrap">
<?= htmlspecialchars($card['content']) ?>
</p>
<!-- Footer van kaart -->
<div class="mt-3 pt-2.5 border-t border-slate-800/60 flex items-center justify-between text-[10px] text-slate-400">
<span><?= htmlspecialchars($card['author_name'] ?? 'Collega') ?></span>
<div class="flex items-center space-x-2">
<!-- Upvote button -->
<button onclick="voteCard(<?= $card['id'] ?>)" class="flex items-center space-x-1 px-2 py-1 rounded-lg bg-slate-800/80 hover:bg-slate-700 text-amber-300 transition">
<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-500 hover:text-red-400 transition opacity-0 group-hover:opacity-100">
<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-800 hover:border-slate-700 text-slate-500 hover:text-slate-300 text-xs transition flex items-center justify-center space-x-1">
<i data-lucide="plus" class="w-3.5 h-3.5"></i>
<span>Kaart toevoegen</span>
</button>
</div>
<?php endforeach; ?>
</div>
</div>
<!-- Modal: Nieuwe Kaart Toevoegen -->
<div id="addCardModal" class="hidden fixed inset-0 z-50 bg-black/70 backdrop-blur-sm flex items-center justify-center p-4">
<div class="bg-slate-900 border border-slate-800 rounded-2xl p-6 shadow-2xl max-w-md w-full">
<div class="flex items-center justify-between mb-4 pb-3 border-b border-slate-800">
<h3 class="text-base font-bold text-white flex items-center space-x-2">
<i data-lucide="sticky-note" class="w-4 h-4 text-amber-400"></i>
<span>Brainstormkaart Toevoegen</span>
</h3>
<button onclick="closeAddCardModal()" class="text-slate-400 hover:text-white">
<i data-lucide="x" class="w-5 h-5"></i>
</button>
</div>
<div class="space-y-4">
<div>
<label class="block text-xs font-medium text-slate-300 mb-1">Categorie</label>
<select id="modalCardCategory" class="w-full bg-slate-950 border border-slate-800 rounded-xl px-3 py-2 text-white text-xs focus:outline-none">
<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-medium 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-950 border border-slate-800 rounded-xl p-3 text-white text-xs placeholder-slate-500 focus:outline-none focus:ring-1 focus:ring-amber-500 resize-none"></textarea>
</div>
<div class="flex space-x-2 pt-2">
<button onclick="submitNewCard()" class="flex-1 bg-amber-500 hover:bg-amber-600 text-slate-950 font-bold py-2.5 px-4 rounded-xl text-xs transition">
Plaats op Canvas
</button>
<button onclick="closeAddCardModal()" class="px-4 py-2.5 bg-slate-800 hover:bg-slate-700 text-slate-300 rounded-xl text-xs transition">
Annuleren
</button>
</div>
</div>
</div>
</div>
<!-- Modal: Zet om naar Roadmap -->
<div id="convertModal" class="hidden fixed inset-0 z-50 bg-black/70 backdrop-blur-sm flex items-center justify-center p-4">
<div class="bg-slate-900 border border-emerald-500/40 rounded-2xl p-6 shadow-2xl max-w-lg w-full">
<div class="flex items-center justify-between mb-4 pb-3 border-b border-slate-800">
<h3 class="text-base font-bold text-white flex items-center space-x-2">
<i data-lucide="map" class="w-4 h-4 text-emerald-400"></i>
<span>Zet Brainstorm om naar Roadmap</span>
</h3>
<button onclick="closeConvertModal()" class="text-slate-400 hover:text-white">
<i data-lucide="x" class="w-5 h-5"></i>
</button>
</div>
<div class="space-y-4">
<div class="bg-emerald-950/30 border border-emerald-500/20 rounded-xl p-3 text-xs text-emerald-300">
<span id="selectedCountText" class="font-bold">0</span> kaarten geselecteerd om om te zetten naar planbare roadmap items.
</div>
<div class="grid grid-cols-2 gap-3">
<div>
<label class="block text-xs font-medium text-slate-300 mb-1">Doel Kwartaal</label>
<select id="roadmapQuarter" class="w-full bg-slate-950 border border-slate-800 rounded-xl px-3 py-2 text-white text-xs focus:outline-none">
<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-medium text-slate-300 mb-1">Item Type</label>
<select id="roadmapType" class="w-full bg-slate-950 border border-slate-800 rounded-xl px-3 py-2 text-white text-xs focus:outline-none">
<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-medium text-slate-300 mb-1">Prioriteit</label>
<select id="roadmapPriority" class="w-full bg-slate-950 border border-slate-800 rounded-xl px-3 py-2 text-white text-xs focus:outline-none">
<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-medium text-slate-300 mb-1">Streefdatum (Deadline)</label>
<input type="date" id="roadmapDueDate" class="w-full bg-slate-950 border border-slate-800 rounded-xl px-3 py-1.5 text-white text-xs focus:outline-none">
</div>
</div>
<div class="flex space-x-2 pt-2">
<button onclick="submitConvertToRoadmap()" class="flex-1 bg-emerald-500 hover:bg-emerald-600 text-slate-950 font-bold py-2.5 px-4 rounded-xl text-xs transition shadow-lg shadow-emerald-500/20 flex items-center justify-center space-x-2">
<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-800 hover:bg-slate-700 text-slate-300 rounded-xl text-xs transition">
Annuleren
</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();
}
// --- 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.');
}
}
</script>
<?php require_once __DIR__ . '/includes/footer.php'; ?>