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.
This commit is contained in:
2026-09-08 12:47:11 +02:00
commit 9da056b480
25 changed files with 3737 additions and 0 deletions
+304
View File
@@ -0,0 +1,304 @@
<?php
/**
* Fociz Integraties Hub (Outlook, Teams, Jira)
*/
require_once __DIR__ . '/includes/header.php';
$db = get_db_connection();
$userId = $currentUser['id'];
// Formulierverwerking voor integratie instellingen
$message = null;
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$provider = $_POST['provider'] ?? '';
$isDemo = isset($_POST['is_demo_mode']) ? 1 : 0;
$settings = json_encode([
'client_id' => $_POST['client_id'] ?? '',
'client_secret' => $_POST['client_secret'] ?? '',
'tenant_id' => $_POST['tenant_id'] ?? '',
'jira_domain' => $_POST['jira_domain'] ?? '',
'jira_email' => $_POST['jira_email'] ?? '',
'jira_api_token' => $_POST['jira_api_token'] ?? '',
]);
if (in_array($provider, ['OUTLOOK', 'TEAMS', 'JIRA'])) {
$stmt = $db->prepare("
INSERT INTO integrations (user_id, provider, is_enabled, is_demo_mode, settings, last_sync_at)
VALUES (?, ?, 1, ?, ?, NOW())
ON DUPLICATE KEY UPDATE is_demo_mode = VALUES(is_demo_mode), settings = VALUES(settings), last_sync_at = NOW()
");
$stmt->execute([$userId, $provider, $isDemo, $settings]);
$message = "Instellingen voor {$provider} succesvol opgeslagen.";
}
}
// Haal huidige configuraties op
$stmtConfigs = $db->prepare("SELECT * FROM integrations WHERE user_id = ?");
$stmtConfigs->execute([$userId]);
$configsRaw = $stmtConfigs->fetchAll();
$configs = [];
foreach ($configsRaw as $c) {
$configs[$c['provider']] = $c;
$configs[$c['provider']]['settings_parsed'] = json_decode($c['settings'] ?? '{}', true) ?: [];
}
// Jira demo issues
$jiraIssues = [
[
'key' => 'FOC-101',
'summary' => 'Tweestapsverificatie (2FA TOTP) flow implementeren',
'status' => 'In Progress',
'priority' => 'High',
'epic' => 'Security & Access',
],
[
'key' => 'FOC-102',
'summary' => 'Interactief whiteboard canvas voor brainstormsessies',
'status' => 'To Do',
'priority' => 'High',
'epic' => 'Brainstorming Module',
],
[
'key' => 'FOC-103',
'summary' => 'Koppeling met Microsoft Outlook Calendar & Teams Graph API',
'status' => 'In Review',
'priority' => 'Medium',
'epic' => 'Integrations Hub',
],
[
'key' => 'FOC-104',
'summary' => 'Roadmap kwartaalweergave en voortgangsindicatoren',
'status' => 'To Do',
'priority' => 'Medium',
'epic' => 'Roadmap Module',
]
];
?>
<div class="space-y-6">
<!-- Header -->
<div class="bg-slate-900/80 border border-slate-800 rounded-2xl p-6 shadow-xl">
<div class="flex items-center space-x-2 text-blue-400 text-xs font-semibold uppercase tracking-wider mb-1">
<i data-lucide="layers" class="w-4 h-4"></i>
<span>Connectiviteit & Synchronisatie</span>
</div>
<h1 class="text-2xl sm:text-3xl font-black text-white tracking-tight">
Integraties Hub
</h1>
<p class="text-slate-400 text-xs sm:text-sm mt-1">
Beheer uw koppelingen met Microsoft Outlook (agenda & taken), Microsoft Teams (meetings) en Atlassian Jira (issues & roadmap).
</p>
</div>
<?php if ($message): ?>
<div class="p-4 rounded-xl bg-emerald-500/10 border border-emerald-500/30 text-emerald-400 text-xs font-semibold flex items-center space-x-2">
<i data-lucide="check-circle" class="w-4 h-4"></i>
<span><?= htmlspecialchars($message) ?></span>
</div>
<?php endif; ?>
<!-- Integratie Cards Grid -->
<div class="grid grid-cols-1 lg:grid-cols-3 gap-6">
<!-- 1. OUTLOOK -->
<div class="bg-slate-900/80 border border-slate-800 rounded-2xl p-5 shadow-xl flex flex-col justify-between">
<div>
<div class="flex items-center justify-between mb-4">
<div class="w-10 h-10 rounded-xl bg-blue-500/10 border border-blue-500/30 text-blue-400 flex items-center justify-center font-bold">
<i data-lucide="calendar" class="w-5 h-5"></i>
</div>
<span class="px-2.5 py-0.5 rounded-full bg-emerald-500/10 border border-emerald-500/20 text-emerald-400 text-xs font-medium flex items-center space-x-1">
<i data-lucide="check-circle" class="w-3 h-3"></i>
<span>Actief</span>
</span>
</div>
<h3 class="text-base font-bold text-white mb-1">Microsoft Outlook</h3>
<p class="text-xs text-slate-400 mb-4">
Synchroniseert uw agenda-afspraken en e-mails automatisch naar uw dagplanning en time-blocks.
</p>
<div class="bg-slate-950/60 rounded-xl p-3 border border-slate-800/80 text-xs space-y-2 mb-4">
<div class="flex items-center justify-between text-slate-400">
<span>Status:</span>
<span class="text-emerald-400 font-semibold">Gekoppeld</span>
</div>
<div class="flex items-center justify-between text-slate-400">
<span>Sync frequentie:</span>
<span class="text-slate-200">Elke 15 min</span>
</div>
</div>
</div>
<!-- Instellingen formulier -->
<form method="POST" action="/integrations.php" class="pt-3 border-t border-slate-800 space-y-3">
<input type="hidden" name="provider" value="OUTLOOK">
<label class="flex items-center space-x-2 text-xs text-slate-300">
<input type="checkbox" name="is_demo_mode" value="1" <?= empty($configs['OUTLOOK']) || !empty($configs['OUTLOOK']['is_demo_mode']) ? 'checked' : '' ?> class="rounded bg-slate-900 border-slate-700 text-emerald-500">
<span>Gebruik interactieve Sandbox data</span>
</label>
<button type="submit" class="w-full py-2 rounded-xl bg-slate-800 hover:bg-slate-700 text-white text-xs font-semibold transition">
Instellingen Bijwerken
</button>
</form>
</div>
<!-- 2. MICROSOFT TEAMS -->
<div class="bg-slate-900/80 border border-slate-800 rounded-2xl p-5 shadow-xl flex flex-col justify-between">
<div>
<div class="flex items-center justify-between mb-4">
<div class="w-10 h-10 rounded-xl bg-indigo-500/10 border border-indigo-500/30 text-indigo-400 flex items-center justify-center font-bold">
<i data-lucide="video" class="w-5 h-5"></i>
</div>
<span class="px-2.5 py-0.5 rounded-full bg-emerald-500/10 border border-emerald-500/20 text-emerald-400 text-xs font-medium flex items-center space-x-1">
<i data-lucide="check-circle" class="w-3 h-3"></i>
<span>Actief</span>
</span>
</div>
<h3 class="text-base font-bold text-white mb-1">Microsoft Teams</h3>
<p class="text-xs text-slate-400 mb-4">
Toont vandaag geplande Teams meetings met directe 'Deelnemen' links en kanaalupdates.
</p>
<div class="bg-slate-950/60 rounded-xl p-3 border border-slate-800/80 text-xs space-y-2 mb-4">
<div class="flex items-center justify-between text-slate-400">
<span>Direct Deelnemen:</span>
<span class="text-emerald-400 font-semibold">Ondersteund</span>
</div>
<div class="flex items-center justify-between text-slate-400">
<span>Meldingen:</span>
<span class="text-slate-200">Ingeschakeld</span>
</div>
</div>
</div>
<form method="POST" action="/integrations.php" class="pt-3 border-t border-slate-800 space-y-3">
<input type="hidden" name="provider" value="TEAMS">
<label class="flex items-center space-x-2 text-xs text-slate-300">
<input type="checkbox" name="is_demo_mode" value="1" <?= empty($configs['TEAMS']) || !empty($configs['TEAMS']['is_demo_mode']) ? 'checked' : '' ?> class="rounded bg-slate-900 border-slate-700 text-emerald-500">
<span>Gebruik interactieve Sandbox data</span>
</label>
<button type="submit" class="w-full py-2 rounded-xl bg-slate-800 hover:bg-slate-700 text-white text-xs font-semibold transition">
Instellingen Bijwerken
</button>
</form>
</div>
<!-- 3. ATLASSIAN JIRA -->
<div class="bg-slate-900/80 border border-slate-800 rounded-2xl p-5 shadow-xl flex flex-col justify-between">
<div>
<div class="flex items-center justify-between mb-4">
<div class="w-10 h-10 rounded-xl bg-sky-500/10 border border-sky-500/30 text-sky-400 flex items-center justify-center font-bold">
<i data-lucide="trello" class="w-5 h-5"></i>
</div>
<span class="px-2.5 py-0.5 rounded-full bg-emerald-500/10 border border-emerald-500/20 text-emerald-400 text-xs font-medium flex items-center space-x-1">
<i data-lucide="check-circle" class="w-3 h-3"></i>
<span>Actief</span>
</span>
</div>
<h3 class="text-base font-bold text-white mb-1">Atlassian Jira</h3>
<p class="text-xs text-slate-400 mb-4">
Koppelt Jira Epics en Issues direct aan uw roadmap items en takenlijst.
</p>
<div class="bg-slate-950/60 rounded-xl p-3 border border-slate-800/80 text-xs space-y-2 mb-4">
<div class="flex items-center justify-between text-slate-400">
<span>Two-way Status Sync:</span>
<span class="text-emerald-400 font-semibold">Ondersteund</span>
</div>
<div class="flex items-center justify-between text-slate-400">
<span>Roadmap Linkage:</span>
<span class="text-slate-200">Actief</span>
</div>
</div>
</div>
<form method="POST" action="/integrations.php" class="pt-3 border-t border-slate-800 space-y-2">
<input type="hidden" name="provider" value="JIRA">
<div>
<label class="block text-[10px] text-slate-400 mb-0.5">Jira Cloud Domein</label>
<input type="text" name="jira_domain" placeholder="uwbedrijf.atlassian.net" value="<?= htmlspecialchars($configs['JIRA']['settings_parsed']['jira_domain'] ?? '') ?>" class="w-full bg-slate-950 border border-slate-800 rounded-lg px-2.5 py-1 text-xs text-white placeholder-slate-600 focus:outline-none">
</div>
<label class="flex items-center space-x-2 text-xs text-slate-300 pt-1">
<input type="checkbox" name="is_demo_mode" value="1" <?= empty($configs['JIRA']) || !empty($configs['JIRA']['is_demo_mode']) ? 'checked' : '' ?> class="rounded bg-slate-900 border-slate-700 text-emerald-500">
<span>Gebruik interactieve Sandbox data</span>
</label>
<button type="submit" class="w-full py-2 rounded-xl bg-slate-800 hover:bg-slate-700 text-white text-xs font-semibold transition">
Jira Opslaan
</button>
</form>
</div>
</div>
<!-- Gesynchroniseerde Jira Issues & 1-Click Import naar Dagplanning -->
<div class="bg-slate-900/80 border border-slate-800 rounded-2xl p-6 shadow-xl space-y-4">
<div class="flex items-center justify-between pb-3 border-b border-slate-800">
<div class="flex items-center space-x-2">
<i data-lucide="check-square" class="w-5 h-5 text-sky-400"></i>
<h3 class="text-sm font-bold text-white uppercase tracking-wider">
Jira Issues Toegewezen aan U
</h3>
</div>
<span class="text-xs text-slate-400 font-mono"><?= count($jiraIssues) ?> issues</span>
</div>
<div class="grid grid-cols-1 md:grid-cols-2 gap-3">
<?php foreach ($jiraIssues as $issue): ?>
<div class="bg-slate-950/60 border border-slate-800 hover:border-slate-700 rounded-xl p-4 flex flex-col justify-between transition">
<div>
<div class="flex items-center justify-between mb-2">
<span class="font-mono text-xs font-bold text-sky-400 bg-sky-500/10 border border-sky-500/20 px-2 py-0.5 rounded">
<?= htmlspecialchars($issue['key']) ?>
</span>
<span class="text-[10px] font-semibold px-2 py-0.5 rounded bg-slate-800 text-slate-300">
<?= htmlspecialchars($issue['status']) ?>
</span>
</div>
<h4 class="text-xs font-bold text-white leading-snug">
<?= htmlspecialchars($issue['summary']) ?>
</h4>
<span class="text-[10px] text-slate-500 mt-1 block">Epic: <?= htmlspecialchars($issue['epic']) ?></span>
</div>
<div class="mt-4 pt-3 border-t border-slate-800/80 flex items-center justify-between">
<span class="text-[10px] font-bold text-amber-400"><?= htmlspecialchars($issue['priority']) ?> Priority</span>
<button onclick="importJiraToTasks('<?= htmlspecialchars(addslashes($issue['summary'])) ?>', '<?= htmlspecialchars($issue['key']) ?>')" class="flex items-center space-x-1 text-[11px] font-semibold text-emerald-400 hover:text-emerald-300 bg-emerald-500/10 hover:bg-emerald-500/20 border border-emerald-500/20 px-2.5 py-1 rounded-lg transition">
<i data-lucide="plus" class="w-3 h-3"></i>
<span>Plan als Dagtaak</span>
</button>
</div>
</div>
<?php endforeach; ?>
</div>
</div>
</div>
<script>
async function importJiraToTasks(title, jiraKey) {
const res = await fetch('/api/planner.php?action=add_task', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({
title: `[${jiraKey}] ${title}`,
priority: 'P1',
date: '<?= date('Y-m-d') ?>'
})
});
const data = await res.json();
if (data.success) {
alert(`Jira issue ${jiraKey} is toegevoegd aan uw dagplanning!`);
}
}
</script>
<?php require_once __DIR__ . '/includes/footer.php'; ?>