780 lines
53 KiB
PHP
780 lines
53 KiB
PHP
<?php
|
|
/**
|
|
* Fociz Systeembeheer & Centrale Integraties Portaal (Uitsluitend voor Beheerders)
|
|
*/
|
|
|
|
require_once __DIR__ . '/includes/auth.php';
|
|
require_once __DIR__ . '/includes/integrations/microsoft.php';
|
|
require_once __DIR__ . '/includes/integrations/jira.php';
|
|
require_once __DIR__ . '/includes/settings.php';
|
|
|
|
// Strikte autorisatie: alleen gebruikers met de rol 'ADMIN' hebben toegang
|
|
$adminUser = require_admin();
|
|
$currentUser = $adminUser;
|
|
$userId = (int)$adminUser['id'];
|
|
$db = get_db_connection();
|
|
|
|
$message = null;
|
|
$messageType = 'success';
|
|
|
|
if (!empty($_GET['success'])) {
|
|
$message = $_GET['success'];
|
|
$messageType = 'success';
|
|
} elseif (!empty($_GET['error'])) {
|
|
$message = $_GET['error'];
|
|
$messageType = 'error';
|
|
}
|
|
|
|
// POST Verwerking VÓÓR header output zodat redirects en opslaan vlekkeloos werken
|
|
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
|
$action = $_POST['action'] ?? '';
|
|
|
|
// 1. Centrale Azure App Registratie opslaan
|
|
if ($action === 'save_azure') {
|
|
$clientId = trim($_POST['client_id'] ?? '');
|
|
$clientSecret = trim($_POST['client_secret'] ?? '');
|
|
$tenantId = trim($_POST['tenant_id'] ?? '');
|
|
if (empty($tenantId) || $tenantId === 'common') {
|
|
$tenantId = 'planningsagenda.nl';
|
|
}
|
|
$isDemo = isset($_POST['is_demo_mode']) ? 1 : 0;
|
|
|
|
$stmtExisting = $db->prepare("SELECT * FROM integrations WHERE user_id = ? AND provider = 'OUTLOOK'");
|
|
$stmtExisting->execute([$userId]);
|
|
$existing = $stmtExisting->fetch();
|
|
$existingSettings = json_decode($existing['settings'] ?? '{}', true) ?: [];
|
|
|
|
if (($clientSecret === '' || $clientSecret === '••••••••••••••••') && !empty($existingSettings['client_secret']) && $existingSettings['client_secret'] !== '••••••••••••••••') {
|
|
$clientSecret = $existingSettings['client_secret'];
|
|
}
|
|
|
|
$newSettings = array_merge($existingSettings, [
|
|
'client_id' => $clientId,
|
|
'client_secret' => $clientSecret,
|
|
'tenant_id' => $tenantId,
|
|
]);
|
|
|
|
$stmt = $db->prepare("
|
|
INSERT INTO integrations (user_id, provider, is_enabled, is_demo_mode, settings, last_sync_at)
|
|
VALUES (?, 'OUTLOOK', 1, ?, ?, NOW())
|
|
ON DUPLICATE KEY UPDATE
|
|
is_demo_mode = VALUES(is_demo_mode),
|
|
settings = VALUES(settings),
|
|
last_sync_at = NOW()
|
|
");
|
|
$stmt->execute([$userId, $isDemo, json_encode($newSettings)]);
|
|
|
|
$message = "Centrale Microsoft 365 Azure App instellingen zijn succesvol opgeslagen.";
|
|
$messageType = 'success';
|
|
}
|
|
|
|
// 2. Centraal Jira Bedrijfsdomein opslaan
|
|
elseif ($action === 'save_jira_domain') {
|
|
$rawDomain = trim($_POST['jira_domain'] ?? '');
|
|
$companyDomain = !empty($rawDomain) ? jira_normalize_domain($rawDomain) : '';
|
|
|
|
$stmtExisting = $db->prepare("SELECT * FROM integrations WHERE user_id = ? AND provider = 'JIRA'");
|
|
$stmtExisting->execute([$userId]);
|
|
$existing = $stmtExisting->fetch();
|
|
$existingSettings = json_decode($existing['settings'] ?? '{}', true) ?: [];
|
|
|
|
$existingSettings['jira_domain'] = $companyDomain;
|
|
|
|
$stmt = $db->prepare("
|
|
INSERT INTO integrations (user_id, provider, is_enabled, is_demo_mode, settings, last_sync_at)
|
|
VALUES (?, 'JIRA', 1, 0, ?, NOW())
|
|
ON DUPLICATE KEY UPDATE
|
|
settings = VALUES(settings),
|
|
last_sync_at = NOW()
|
|
");
|
|
$stmt->execute([$userId, json_encode($existingSettings)]);
|
|
|
|
$message = "Standaard Atlassian Jira bedrijfsdomein is succesvol opgeslagen.";
|
|
$messageType = 'success';
|
|
}
|
|
|
|
// 3. Organisatie Werkdag Standaarden opslaan (Release v1.4)
|
|
elseif ($action === 'save_workday_settings') {
|
|
$workdayStart = trim($_POST['workday_start'] ?? '08:30');
|
|
$workdayEnd = trim($_POST['workday_end'] ?? '17:30');
|
|
$lunchStart = trim($_POST['lunch_start'] ?? '12:30');
|
|
$lunchEnd = trim($_POST['lunch_end'] ?? '13:00');
|
|
$pomoFocus = max(10, min(120, (int)($_POST['pomodoro_focus'] ?? 25)));
|
|
$pomoDeep = max(20, min(180, (int)($_POST['pomodoro_deep'] ?? 50)));
|
|
$pomoBreak = max(2, min(30, (int)($_POST['pomodoro_break'] ?? 5)));
|
|
$taskDuration = in_array((int)($_POST['default_task_duration'] ?? 30), [15, 30, 45, 60, 90, 120]) ? (int)$_POST['default_task_duration'] : 30;
|
|
|
|
set_system_setting('workday_start', $workdayStart);
|
|
set_system_setting('workday_end', $workdayEnd);
|
|
set_system_setting('lunch_start', $lunchStart);
|
|
set_system_setting('lunch_end', $lunchEnd);
|
|
set_system_setting('pomodoro_focus', $pomoFocus);
|
|
set_system_setting('pomodoro_deep', $pomoDeep);
|
|
set_system_setting('pomodoro_break', $pomoBreak);
|
|
set_system_setting('default_task_duration', $taskDuration);
|
|
|
|
$message = "Organisatie werkdagstandaarden zijn succesvol bijgewerkt.";
|
|
$messageType = 'success';
|
|
}
|
|
|
|
// 4. AI Co-Pilot instellingen opslaan (Release v1.5)
|
|
elseif ($action === 'save_ai_settings') {
|
|
$aiEnabled = isset($_POST['ai_enabled']) ? 1 : 0;
|
|
$aiProvider = trim($_POST['ai_provider'] ?? 'openai');
|
|
$aiModel = trim($_POST['ai_model'] ?? 'gpt-4o-mini');
|
|
$apiKeyIn = trim($_POST['ai_api_key'] ?? '');
|
|
|
|
$currentAi = get_ai_settings();
|
|
if ($apiKeyIn !== '' && $apiKeyIn !== '••••••••••••••••') {
|
|
set_system_setting('ai_api_key', $apiKeyIn);
|
|
}
|
|
|
|
set_system_setting('ai_enabled', $aiEnabled);
|
|
set_system_setting('ai_provider', $aiProvider);
|
|
set_system_setting('ai_model', $aiModel);
|
|
|
|
$message = "AI Co-Pilot instellingen zijn succesvol opgeslagen.";
|
|
$messageType = 'success';
|
|
}
|
|
}
|
|
|
|
// Haal actuele configuratie op
|
|
$appConfig = microsoft_get_app_config();
|
|
$companyJiraDomain = jira_get_company_domain();
|
|
$workdaySettings = get_workday_settings();
|
|
$aiSettings = get_ai_settings();
|
|
|
|
// Redirect URI
|
|
$redirectUri = rtrim(APP_URL, '/') . '/auth/callback_microsoft.php';
|
|
|
|
// Admin Consent URL
|
|
$tenantForConsent = !empty($appConfig['tenant_id']) ? $appConfig['tenant_id'] : 'common';
|
|
$adminConsentUrl = "https://login.microsoftonline.com/" . urlencode($tenantForConsent) . "/adminconsent?client_id=" . urlencode($appConfig['client_id']) . "&redirect_uri=" . urlencode($redirectUri);
|
|
|
|
// Statistieken ophalen voor Systeem & Team Monitor
|
|
$totalUsers = 0;
|
|
$totalAdmins = 0;
|
|
$totalMembers = 0;
|
|
$pendingInvites = 0;
|
|
$outlookConnectedCount = 0;
|
|
$jiraConnectedCount = 0;
|
|
$recentUsers = [];
|
|
|
|
try {
|
|
$totalUsers = (int)$db->query("SELECT COUNT(*) FROM users")->fetchColumn();
|
|
$totalAdmins = (int)$db->query("SELECT COUNT(*) FROM users WHERE role = 'ADMIN'")->fetchColumn();
|
|
$totalMembers = $totalUsers - $totalAdmins;
|
|
} catch (Throwable $e) {
|
|
error_log("Fout bij ophalen user stats: " . $e->getMessage());
|
|
}
|
|
|
|
try {
|
|
$pendingInvites = (int)$db->query("SELECT COUNT(*) FROM invites WHERE used_at IS NULL AND expires_at > NOW()")->fetchColumn();
|
|
} catch (Throwable $e) {
|
|
error_log("Fout bij ophalen invites stats: " . $e->getMessage());
|
|
ensure_tables_exist($db);
|
|
}
|
|
|
|
try {
|
|
$outlookConnectedCount = (int)$db->query("SELECT COUNT(*) FROM integrations WHERE provider = 'OUTLOOK' AND access_token IS NOT NULL")->fetchColumn();
|
|
$jiraConnectedCount = (int)$db->query("SELECT COUNT(*) FROM integrations WHERE provider = 'JIRA' AND settings LIKE '%jira_api_token%' AND settings NOT LIKE '%\"jira_api_token\":\"\"%'")->fetchColumn();
|
|
} catch (Throwable $e) {
|
|
error_log("Fout bij ophalen integrations stats: " . $e->getMessage());
|
|
ensure_tables_exist($db);
|
|
}
|
|
|
|
try {
|
|
$recentUsers = $db->query("
|
|
SELECT u.id, u.name, u.email, u.role, u.created_at,
|
|
(SELECT COUNT(*) FROM integrations i WHERE i.user_id = u.id AND i.provider = 'OUTLOOK' AND i.access_token IS NOT NULL) as has_outlook,
|
|
(SELECT COUNT(*) FROM integrations i WHERE i.user_id = u.id AND i.provider = 'JIRA' AND i.settings LIKE '%jira_api_token%' AND i.settings NOT LIKE '%\"jira_api_token\":\"\"%') as has_jira
|
|
FROM users u
|
|
ORDER BY u.created_at DESC
|
|
LIMIT 6
|
|
")->fetchAll() ?: [];
|
|
} catch (Throwable $e) {
|
|
error_log("Fout bij ophalen recente gebruikers: " . $e->getMessage());
|
|
}
|
|
|
|
$pageTitle = 'Systeembeheer & Integraties — Fociz';
|
|
require_once __DIR__ . '/includes/header.php';
|
|
?>
|
|
|
|
<div class="space-y-8 max-w-7xl mx-auto pb-12">
|
|
<!-- Top Header Banner -->
|
|
<div class="relative overflow-hidden fociz-card p-6 sm:p-8">
|
|
<div class="absolute -top-24 -right-24 w-80 h-80 bg-indigo-500/10 rounded-full blur-3xl pointer-events-none"></div>
|
|
<div class="absolute -bottom-24 -left-24 w-80 h-80 bg-sky-500/10 rounded-full blur-3xl pointer-events-none"></div>
|
|
|
|
<div class="relative z-10 flex flex-col md:flex-row md:items-center md:justify-between gap-6">
|
|
<div class="space-y-2">
|
|
<div class="inline-flex items-center space-x-2 px-3 py-1 rounded-full bg-indigo-50 dark:bg-indigo-950/40 border border-indigo-200 dark:border-indigo-800/60 text-indigo-700 dark:text-indigo-300 text-xs font-semibold tracking-wide">
|
|
<i data-lucide="shield-alert" class="w-3.5 h-3.5"></i>
|
|
<span>Beheerderspaneel • Alleen toegankelijk voor Beheer</span>
|
|
</div>
|
|
<h1 class="text-3xl sm:text-4xl font-black text-slate-900 dark:text-white tracking-tight">
|
|
Systeembeheer & Integraties
|
|
</h1>
|
|
<p class="text-sm text-slate-600 dark:text-slate-400 max-w-2xl leading-relaxed">
|
|
Beheer hier de centrale organisatiekoppelingen zoals Microsoft 365 Azure App Registratie en Jira, inspecteer de teamstatus en configureer toekomstige modules.
|
|
</p>
|
|
</div>
|
|
|
|
<div class="flex flex-wrap items-center gap-3">
|
|
<a href="/users.php" class="inline-flex items-center space-x-2 px-4 py-2.5 rounded-xl bg-slate-100 hover:bg-slate-200 dark:bg-slate-800/90 dark:hover:bg-slate-700 text-slate-800 dark:text-slate-200 border border-slate-200 dark:border-slate-700 text-xs font-semibold transition shadow-xs">
|
|
<i data-lucide="users" class="w-4 h-4 text-indigo-600 dark:text-indigo-400"></i>
|
|
<span>Team & Invites (<?= $totalUsers ?>)</span>
|
|
</a>
|
|
<a href="/profile.php" class="inline-flex items-center space-x-2 px-4 py-2.5 rounded-xl bg-indigo-600 hover:bg-indigo-500 text-white text-xs font-bold transition shadow-xs">
|
|
<i data-lucide="user" class="w-4 h-4"></i>
|
|
<span>Mijn Persoonlijk Profiel</span>
|
|
</a>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Meldingen Banner -->
|
|
<?php if ($message): ?>
|
|
<div class="p-4 rounded-2xl flex items-start space-x-3 text-sm animate-in fade-in duration-200 <?= $messageType === 'success' ? 'bg-teal-50/80 dark:bg-teal-950/30 border border-teal-200 dark:border-teal-800/60 text-teal-800 dark:text-teal-200' : 'bg-red-500/10 border border-red-500/30 text-red-800 dark:text-red-200' ?>">
|
|
<i data-lucide="<?= $messageType === 'success' ? 'check-circle-2' : 'alert-triangle' ?>" class="w-5 h-5 flex-shrink-0 mt-0.5 <?= $messageType === 'success' ? 'text-teal-600 dark:text-teal-400' : 'text-red-600 dark:text-red-400' ?>"></i>
|
|
<div class="flex-1 font-medium"><?= htmlspecialchars($message) ?></div>
|
|
</div>
|
|
<?php endif; ?>
|
|
|
|
<!-- Stat kaarten rij -->
|
|
<div class="grid grid-cols-2 lg:grid-cols-4 gap-4">
|
|
<div class="fociz-card p-5 flex items-center space-x-4">
|
|
<div class="w-12 h-12 rounded-xl bg-indigo-50 dark:bg-indigo-950/40 border border-indigo-200 dark:border-indigo-800/60 flex items-center justify-center text-indigo-600 dark:text-indigo-400 flex-shrink-0">
|
|
<i data-lucide="users" class="w-6 h-6"></i>
|
|
</div>
|
|
<div>
|
|
<span class="text-xs text-slate-500 dark:text-slate-400 font-medium block">Totaal Teamleden</span>
|
|
<span class="text-2xl font-black text-slate-900 dark:text-white"><?= $totalUsers ?></span>
|
|
<span class="text-[10px] text-slate-500 dark:text-slate-500 block"><?= $totalAdmins ?> Beheerders • <?= $totalMembers ?> Collega's</span>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="fociz-card p-5 flex items-center space-x-4">
|
|
<div class="w-12 h-12 rounded-xl bg-sky-50 dark:bg-sky-950/40 border border-sky-200 dark:border-sky-800/60 flex items-center justify-center text-sky-600 dark:text-sky-400 flex-shrink-0">
|
|
<i data-lucide="calendar" class="w-6 h-6"></i>
|
|
</div>
|
|
<div>
|
|
<span class="text-xs text-slate-500 dark:text-slate-400 font-medium block">Outlook Live Gekoppeld</span>
|
|
<span class="text-2xl font-black text-slate-900 dark:text-white"><?= $outlookConnectedCount ?></span>
|
|
<span class="text-[10px] text-teal-600 dark:text-teal-400 font-medium block"><?= $appConfig['is_configured'] ? 'Azure App Actief' : 'Configuratie nodig' ?></span>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="fociz-card p-5 flex items-center space-x-4">
|
|
<div class="w-12 h-12 rounded-xl bg-indigo-50 dark:bg-indigo-950/40 border border-indigo-200 dark:border-indigo-800/60 flex items-center justify-center text-indigo-600 dark:text-indigo-400 flex-shrink-0">
|
|
<i data-lucide="check-square" class="w-6 h-6"></i>
|
|
</div>
|
|
<div>
|
|
<span class="text-xs text-slate-500 dark:text-slate-400 font-medium block">Jira Gekoppeld</span>
|
|
<span class="text-2xl font-black text-slate-900 dark:text-white"><?= $jiraConnectedCount ?></span>
|
|
<span class="text-[10px] text-slate-500 dark:text-slate-500 block"><?= !empty($companyJiraDomain) ? 'Domein ingesteld' : 'Geen domein' ?></span>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="fociz-card p-5 flex items-center space-x-4">
|
|
<div class="w-12 h-12 rounded-xl bg-amber-50 dark:bg-amber-950/40 border border-amber-200 dark:border-amber-800/60 flex items-center justify-center text-amber-600 dark:text-amber-400 flex-shrink-0">
|
|
<i data-lucide="mail" class="w-6 h-6"></i>
|
|
</div>
|
|
<div>
|
|
<span class="text-xs text-slate-500 dark:text-slate-400 font-medium block">Openstaande Invites</span>
|
|
<span class="text-2xl font-black text-slate-900 dark:text-white"><?= $pendingInvites ?></span>
|
|
<a href="/users.php" class="text-[10px] text-amber-600 dark:text-amber-400 hover:underline block font-medium">Beheer uitnodigingen →</a>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Hoofdgrid met Beheer Secties -->
|
|
<div class="grid grid-cols-1 lg:grid-cols-12 gap-8">
|
|
|
|
<!-- Linkerkolom: Integraties Beheer (8 cols) -->
|
|
<div class="lg:col-span-8 space-y-8">
|
|
|
|
<!-- 1. Microsoft 365 Azure App Registratie -->
|
|
<div class="fociz-card p-6 sm:p-7">
|
|
<div class="flex flex-col sm:flex-row sm:items-center justify-between gap-4 pb-6 border-b border-slate-100 dark:border-slate-800/80">
|
|
<div class="flex items-center space-x-3.5">
|
|
<div class="w-12 h-12 rounded-xl bg-sky-50 dark:bg-sky-950/40 border border-sky-200 dark:border-sky-800/60 flex items-center justify-center text-sky-600 dark:text-sky-400 font-bold shadow-xs">
|
|
<i data-lucide="cloud" class="w-6 h-6"></i>
|
|
</div>
|
|
<div>
|
|
<div class="flex items-center space-x-2">
|
|
<h2 class="text-lg font-bold text-slate-900 dark:text-white tracking-tight">Microsoft 365 & Azure App Registratie</h2>
|
|
<?php if ($appConfig['is_configured']): ?>
|
|
<span class="px-2.5 py-0.5 rounded-full bg-teal-50 dark:bg-teal-950/40 border border-teal-200 dark:border-teal-800/60 text-teal-700 dark:text-teal-400 text-[10px] font-bold">
|
|
Geconfigureerd
|
|
</span>
|
|
<?php else: ?>
|
|
<span class="px-2.5 py-0.5 rounded-full bg-amber-50 dark:bg-amber-950/40 border border-amber-200 dark:border-amber-800/60 text-amber-700 dark:text-amber-400 text-[10px] font-bold">
|
|
Configuratie Vereist
|
|
</span>
|
|
<?php endif; ?>
|
|
</div>
|
|
<p class="text-xs text-slate-500 dark:text-slate-400">Centrale OAuth 2.0 instellingen voor de hele organisatie</p>
|
|
</div>
|
|
</div>
|
|
|
|
<?php if ($appConfig['is_configured']): ?>
|
|
<a href="<?= htmlspecialchars($adminConsentUrl) ?>" target="_blank" rel="noopener noreferrer" class="inline-flex items-center space-x-2 px-3.5 py-2 rounded-xl bg-sky-50 hover:bg-sky-100 dark:bg-sky-950/40 dark:hover:bg-sky-900/40 text-sky-700 dark:text-sky-300 border border-sky-200 dark:border-sky-800/60 text-xs font-semibold transition" title="Verleen namens alle gebruikers beheerdersgoedkeuring in Azure">
|
|
<i data-lucide="shield-check" class="w-4 h-4 text-sky-600 dark:text-sky-400"></i>
|
|
<span>Admin Consent Verlenen</span>
|
|
<i data-lucide="external-link" class="w-3.5 h-3.5 text-sky-600 dark:text-sky-400"></i>
|
|
</a>
|
|
<?php endif; ?>
|
|
</div>
|
|
|
|
<form method="POST" class="mt-6 space-y-5">
|
|
<input type="hidden" name="action" value="save_azure">
|
|
|
|
<!-- Redirect URI Kopieerbox -->
|
|
<div class="p-4 rounded-2xl bg-slate-50 dark:bg-[#0c0f17] border border-slate-200 dark:border-slate-800 space-y-2">
|
|
<div class="flex items-center justify-between">
|
|
<label class="text-xs font-semibold text-slate-700 dark:text-slate-300 flex items-center space-x-1.5">
|
|
<i data-lucide="link" class="w-3.5 h-3.5 text-sky-500"></i>
|
|
<span>Redirect URI (Terugbel-URL voor Azure Portal)</span>
|
|
</label>
|
|
<span class="text-[10px] text-slate-500">Kopieer dit exact naar de Azure Web Redirect URIs</span>
|
|
</div>
|
|
<div class="flex items-center space-x-2">
|
|
<input type="text" readonly id="adminRedirectUri" value="<?= htmlspecialchars($redirectUri) ?>" class="flex-1 bg-white dark:bg-[#111622] border border-slate-300 dark:border-slate-800 rounded-xl px-3 py-2 text-xs font-mono text-indigo-600 dark:text-indigo-400 select-all focus:outline-none">
|
|
<button type="button" onclick="copyAdminRedirectUri()" class="px-3 py-2 rounded-xl bg-slate-100 hover:bg-slate-200 dark:bg-slate-800 dark:hover:bg-slate-700 text-slate-800 dark:text-white text-xs font-semibold flex items-center space-x-1.5 transition border border-slate-200 dark:border-transparent cursor-pointer">
|
|
<i data-lucide="copy" class="w-3.5 h-3.5" id="copyIcon"></i>
|
|
<span id="copyText">Kopiëren</span>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Client ID & Tenant ID Grid -->
|
|
<div class="grid grid-cols-1 sm:grid-cols-2 gap-4">
|
|
<div>
|
|
<label class="block text-xs font-semibold text-slate-700 dark:text-slate-300 mb-1.5">
|
|
Application (Client) ID <span class="text-red-500">*</span>
|
|
</label>
|
|
<input type="text" name="client_id" required value="<?= htmlspecialchars($appConfig['client_id']) ?>" placeholder="bijv. 38314451-1d05-4205-88ec-..." class="w-full bg-white dark:bg-[#0c0f17] border border-slate-300 dark:border-slate-800 rounded-xl px-3.5 py-2.5 text-xs text-slate-900 dark:text-white placeholder-slate-400 dark:placeholder-slate-600 focus:outline-none focus:border-indigo-500 focus:ring-1 focus:ring-indigo-500">
|
|
</div>
|
|
|
|
<div>
|
|
<label class="block text-xs font-semibold text-slate-700 dark:text-slate-300 mb-1.5">
|
|
Directory (Tenant) ID / Domeinnaam
|
|
</label>
|
|
<input type="text" name="tenant_id" value="<?= htmlspecialchars((!empty($appConfig['tenant_id']) && $appConfig['tenant_id'] !== 'common') ? $appConfig['tenant_id'] : 'planningsagenda.nl') ?>" placeholder="bijv. planningsagenda.nl of Azure Tenant GUID" class="w-full bg-white dark:bg-[#0c0f17] border border-slate-300 dark:border-slate-800 rounded-xl px-3.5 py-2.5 text-xs text-slate-900 dark:text-white placeholder-slate-400 dark:placeholder-slate-600 focus:outline-none focus:border-indigo-500 focus:ring-1 focus:ring-indigo-500 font-mono">
|
|
<span class="text-[10px] text-slate-500 dark:text-slate-400 mt-1 block">Vul uw domeinnaam in (<code class="text-indigo-600 dark:text-indigo-400 font-mono">planningsagenda.nl</code>) of de Directory (tenant) ID uit Azure. <em>Let op: Gebruik géén 'common' voor single-tenant apps.</em></span>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Client Secret met toggle -->
|
|
<div>
|
|
<div class="flex items-center justify-between mb-1.5">
|
|
<label class="text-xs font-semibold text-slate-700 dark:text-slate-300">
|
|
Client Secret (Value) <span class="text-red-500">*</span>
|
|
</label>
|
|
<span class="text-[10px] text-slate-500">Wordt veilig en versleuteld opgeslagen</span>
|
|
</div>
|
|
<div class="relative">
|
|
<input type="password" id="adminClientSecret" name="client_secret" value="<?= !empty($appConfig['client_secret']) ? '••••••••••••••••' : '' ?>" placeholder="Voer de Secret Value in uit Azure..." class="w-full bg-white dark:bg-[#0c0f17] border border-slate-300 dark:border-slate-800 rounded-xl pl-3.5 pr-10 py-2.5 text-xs text-slate-900 dark:text-white placeholder-slate-400 dark:placeholder-slate-600 focus:outline-none focus:border-indigo-500 focus:ring-1 focus:ring-indigo-500">
|
|
<button type="button" onclick="toggleAdminSecret()" class="absolute inset-y-0 right-0 pr-3 flex items-center text-slate-400 hover:text-slate-600 dark:hover:text-slate-300">
|
|
<i data-lucide="eye" class="w-4 h-4" id="eyeSecretIcon"></i>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="pt-2 flex items-center justify-between">
|
|
<div class="text-[11px] text-slate-500 dark:text-slate-400">
|
|
Zodra opgeslagen kunnen al uw collega's met 1 klik hun Outlook agenda koppelen.
|
|
</div>
|
|
<button type="submit" class="px-5 py-2.5 rounded-xl bg-indigo-600 hover:bg-indigo-500 text-white text-xs font-bold transition shadow-xs cursor-pointer">
|
|
Azure Gegevens Opslaan
|
|
</button>
|
|
</div>
|
|
</form>
|
|
|
|
<!-- Azure Setup Documentatie Accordion -->
|
|
<div class="mt-6 pt-6 border-t border-slate-100 dark:border-slate-800/80">
|
|
<button type="button" onclick="toggleAdminGuide()" class="w-full flex items-center justify-between text-xs font-semibold text-slate-700 dark:text-slate-300 hover:text-slate-900 dark:hover:text-white transition cursor-pointer">
|
|
<span class="flex items-center space-x-2">
|
|
<i data-lucide="help-circle" class="w-4 h-4 text-sky-500"></i>
|
|
<span>Hoe configureer ik de Azure App Registratie in 4 stappen?</span>
|
|
</span>
|
|
<i data-lucide="chevron-down" class="w-4 h-4 transition-transform duration-200" id="guideArrow"></i>
|
|
</button>
|
|
|
|
<div id="adminGuideContent" class="hidden mt-4 p-4 rounded-2xl bg-slate-50 dark:bg-[#0c0f17] border border-slate-200 dark:border-slate-800 text-xs text-slate-600 dark:text-slate-300 space-y-3 leading-relaxed">
|
|
<div class="flex items-start space-x-2.5">
|
|
<span class="w-5 h-5 rounded-full bg-sky-500/20 text-sky-600 dark:text-sky-400 flex items-center justify-center font-bold text-[10px] flex-shrink-0 mt-0.5">1</span>
|
|
<div>
|
|
<strong class="text-slate-900 dark:text-white">Azure Portal:</strong> Ga naar <a href="https://portal.azure.com/#view/Microsoft_AAD_IAM/ActiveDirectoryMenuBlade/~/RegisteredApps" target="_blank" class="text-sky-600 dark:text-sky-400 underline">portal.azure.com</a> → <em>Microsoft Entra ID</em> → <em>App registrations</em> → <em>New registration</em>. Kies "Accounts in this organizational directory only" (single-tenant).
|
|
</div>
|
|
</div>
|
|
|
|
<div class="flex items-start space-x-2.5">
|
|
<span class="w-5 h-5 rounded-full bg-sky-500/20 text-sky-600 dark:text-sky-400 flex items-center justify-center font-bold text-[10px] flex-shrink-0 mt-0.5">2</span>
|
|
<div>
|
|
<strong class="text-slate-900 dark:text-white">Redirect URI:</strong> Kies platform <strong>Web</strong> en vul exact in: <code class="text-indigo-600 dark:text-indigo-400 font-mono"><?= htmlspecialchars($redirectUri) ?></code>.
|
|
</div>
|
|
</div>
|
|
|
|
<div class="flex items-start space-x-2.5">
|
|
<span class="w-5 h-5 rounded-full bg-sky-500/20 text-sky-600 dark:text-sky-400 flex items-center justify-center font-bold text-[10px] flex-shrink-0 mt-0.5">3</span>
|
|
<div>
|
|
<strong class="text-slate-900 dark:text-white">API Permissions:</strong> Ga naar <em>API permissions</em> → <em>Add a permission</em> → <em>Microsoft Graph</em> → <strong>Delegated permissions</strong> → Vink aan: <code class="text-sky-600 dark:text-sky-400">User.Read</code> en <code class="text-sky-600 dark:text-sky-400">Calendars.ReadWrite</code>.
|
|
</div>
|
|
</div>
|
|
|
|
<div class="flex items-start space-x-2.5">
|
|
<span class="w-5 h-5 rounded-full bg-sky-500/20 text-sky-600 dark:text-sky-400 flex items-center justify-center font-bold text-[10px] flex-shrink-0 mt-0.5">4</span>
|
|
<div>
|
|
<strong class="text-slate-900 dark:text-white">Beheerdersgoedkeuring (Admin Consent):</strong> Klik op <em>"Grant admin consent for [Uw Organisatie]"</em>. Er verschijnt direct een groen vinkje bij alle regels. Hiermee hoeven individuele collega's geen beheerder meer te vragen!
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 2. Atlassian Jira Bedrijfsinstellingen -->
|
|
<div class="fociz-card p-6 sm:p-7">
|
|
<div class="flex items-center space-x-3.5 pb-6 border-b border-slate-100 dark:border-slate-800/80">
|
|
<div class="w-12 h-12 rounded-xl bg-indigo-50 dark:bg-indigo-950/40 border border-indigo-200 dark:border-indigo-800/60 flex items-center justify-center text-indigo-600 dark:text-indigo-400 font-bold shadow-xs">
|
|
<i data-lucide="trello" class="w-6 h-6"></i>
|
|
</div>
|
|
<div>
|
|
<div class="flex items-center space-x-2">
|
|
<h2 class="text-lg font-bold text-slate-900 dark:text-white tracking-tight">Atlassian Jira Organisatie-instellingen</h2>
|
|
<?php if (!empty($companyJiraDomain)): ?>
|
|
<span class="px-2.5 py-0.5 rounded-full bg-teal-50 dark:bg-teal-950/40 border border-teal-200 dark:border-teal-800/60 text-teal-700 dark:text-teal-400 text-[10px] font-bold">
|
|
Actief
|
|
</span>
|
|
<?php else: ?>
|
|
<span class="px-2.5 py-0.5 rounded-full bg-slate-100 dark:bg-slate-800 text-slate-600 dark:text-slate-400 text-[10px] font-bold">
|
|
Optioneel
|
|
</span>
|
|
<?php endif; ?>
|
|
</div>
|
|
<p class="text-xs text-slate-500 dark:text-slate-400">Standaard Jira Cloud domein voor het hele team</p>
|
|
</div>
|
|
</div>
|
|
|
|
<form method="POST" class="mt-6 space-y-4">
|
|
<input type="hidden" name="action" value="save_jira_domain">
|
|
|
|
<div>
|
|
<label class="block text-xs font-semibold text-slate-700 dark:text-slate-300 mb-1.5">
|
|
Standaard Jira Bedrijfsdomein
|
|
</label>
|
|
<input type="text" name="jira_domain" value="<?= htmlspecialchars($companyJiraDomain) ?>" placeholder="https://uwbedrijf.atlassian.net" class="w-full bg-white dark:bg-[#0c0f17] border border-slate-300 dark:border-slate-800 rounded-xl px-3.5 py-2.5 text-xs text-slate-900 dark:text-white placeholder-slate-400 dark:placeholder-slate-600 focus:outline-none focus:border-indigo-500 focus:ring-1 focus:ring-indigo-500 font-mono">
|
|
<p class="text-[11px] text-slate-500 dark:text-slate-400 mt-1.5 leading-relaxed">
|
|
Wanneer u dit instelt, wordt dit domein automatisch vooraf ingevuld op de profielpagina van alle collega's. Zij hoeven dan enkel nog hun eigen persoonlijke e-mailadres en Atlassian API-token in te voeren.
|
|
</p>
|
|
</div>
|
|
|
|
<div class="flex justify-end pt-2">
|
|
<button type="submit" class="px-5 py-2.5 rounded-xl bg-indigo-600 hover:bg-indigo-500 text-white text-xs font-bold transition shadow-xs cursor-pointer">
|
|
Jira Domein Opslaan
|
|
</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
|
|
<!-- 3. Organisatie Werkdag Standaarden (Release v1.4) -->
|
|
<div class="fociz-card p-6 sm:p-7 space-y-6">
|
|
<div class="flex items-center justify-between pb-4 border-b border-slate-100 dark:border-slate-800/80">
|
|
<div class="flex items-center space-x-3.5">
|
|
<div class="w-12 h-12 rounded-xl bg-indigo-50 dark:bg-indigo-950/40 border border-indigo-200 dark:border-indigo-800/60 flex items-center justify-center text-indigo-600 dark:text-indigo-400 font-bold shadow-xs">
|
|
<i data-lucide="clock" class="w-6 h-6"></i>
|
|
</div>
|
|
<div>
|
|
<h2 class="text-lg font-bold text-slate-900 dark:text-white tracking-tight">Werkdag & Dagplanning Standaarden</h2>
|
|
<p class="text-xs text-slate-500 dark:text-slate-400">Centrale kantoortijden, lunchpauzes en Pomodoro presets voor het team</p>
|
|
</div>
|
|
</div>
|
|
<span class="px-2.5 py-1 rounded-full bg-teal-50 dark:bg-teal-950/40 text-teal-700 dark:text-teal-400 text-xs font-bold border border-teal-200 dark:border-teal-800/60">
|
|
Release v1.4 Actief
|
|
</span>
|
|
</div>
|
|
|
|
<form method="POST" action="/admin.php" class="space-y-4">
|
|
<input type="hidden" name="action" value="save_workday_settings">
|
|
|
|
<div class="grid grid-cols-1 sm:grid-cols-2 gap-4">
|
|
<!-- Werkdag uren -->
|
|
<div class="p-4 rounded-2xl bg-slate-50 dark:bg-[#0c0f17] border border-slate-200 dark:border-slate-800 space-y-3">
|
|
<div class="text-xs font-bold text-slate-900 dark:text-white flex items-center space-x-1.5">
|
|
<i data-lucide="sun" class="w-3.5 h-3.5 text-amber-500"></i>
|
|
<span>Kantooruren / Tijdlijn Bereik</span>
|
|
</div>
|
|
<div class="grid grid-cols-2 gap-3">
|
|
<div>
|
|
<label class="block text-[11px] font-medium text-slate-600 dark:text-slate-400 mb-1">Starttijd</label>
|
|
<input type="time" name="workday_start" value="<?= htmlspecialchars($workdaySettings['workday_start']) ?>" class="w-full bg-white dark:bg-[#111622] border border-slate-300 dark:border-slate-700 rounded-xl px-2.5 py-1.5 text-xs text-slate-900 dark:text-white font-mono focus:outline-none focus:ring-1 focus:ring-indigo-500">
|
|
</div>
|
|
<div>
|
|
<label class="block text-[11px] font-medium text-slate-600 dark:text-slate-400 mb-1">Eindtijd</label>
|
|
<input type="time" name="workday_end" value="<?= htmlspecialchars($workdaySettings['workday_end']) ?>" class="w-full bg-white dark:bg-[#111622] border border-slate-300 dark:border-slate-700 rounded-xl px-2.5 py-1.5 text-xs text-slate-900 dark:text-white font-mono focus:outline-none focus:ring-1 focus:ring-indigo-500">
|
|
</div>
|
|
</div>
|
|
<p class="text-[10px] text-slate-400 dark:text-slate-500">Bepaalt het standaard start- en einduur op de dagelijkse tijdlijn.</p>
|
|
</div>
|
|
|
|
<!-- Lunchpauze -->
|
|
<div class="p-4 rounded-2xl bg-slate-50 dark:bg-[#0c0f17] border border-slate-200 dark:border-slate-800 space-y-3">
|
|
<div class="text-xs font-bold text-slate-900 dark:text-white flex items-center space-x-1.5">
|
|
<i data-lucide="coffee" class="w-3.5 h-3.5 text-amber-500"></i>
|
|
<span>Automatisch Lunchpauze Blok</span>
|
|
</div>
|
|
<div class="grid grid-cols-2 gap-3">
|
|
<div>
|
|
<label class="block text-[11px] font-medium text-slate-600 dark:text-slate-400 mb-1">Start lunch</label>
|
|
<input type="time" name="lunch_start" value="<?= htmlspecialchars($workdaySettings['lunch_start']) ?>" class="w-full bg-white dark:bg-[#111622] border border-slate-300 dark:border-slate-700 rounded-xl px-2.5 py-1.5 text-xs text-slate-900 dark:text-white font-mono focus:outline-none focus:ring-1 focus:ring-indigo-500">
|
|
</div>
|
|
<div>
|
|
<label class="block text-[11px] font-medium text-slate-600 dark:text-slate-400 mb-1">Einde lunch</label>
|
|
<input type="time" name="lunch_end" value="<?= htmlspecialchars($workdaySettings['lunch_end']) ?>" class="w-full bg-white dark:bg-[#111622] border border-slate-300 dark:border-slate-700 rounded-xl px-2.5 py-1.5 text-xs text-slate-900 dark:text-white font-mono focus:outline-none focus:ring-1 focus:ring-indigo-500">
|
|
</div>
|
|
</div>
|
|
<p class="text-[10px] text-slate-400 dark:text-slate-500">Wordt visueel gemarkeerd als pauzeblok op de tijdlijn.</p>
|
|
</div>
|
|
|
|
<!-- Pomodoro presets -->
|
|
<div class="p-4 rounded-2xl bg-slate-50 dark:bg-[#0c0f17] border border-slate-200 dark:border-slate-800 space-y-3">
|
|
<div class="text-xs font-bold text-slate-900 dark:text-white flex items-center space-x-1.5">
|
|
<i data-lucide="flame" class="w-3.5 h-3.5 text-rose-500"></i>
|
|
<span>Focus & Pomodoro Presets (minuten)</span>
|
|
</div>
|
|
<div class="grid grid-cols-3 gap-2">
|
|
<div>
|
|
<label class="block text-[10px] font-medium text-slate-600 dark:text-slate-400 mb-1">Focus</label>
|
|
<input type="number" name="pomodoro_focus" min="10" max="120" value="<?= (int)$workdaySettings['pomodoro_focus'] ?>" class="w-full bg-white dark:bg-[#111622] border border-slate-300 dark:border-slate-700 rounded-xl px-2.5 py-1.5 text-xs text-slate-900 dark:text-white font-mono focus:outline-none focus:ring-1 focus:ring-indigo-500">
|
|
</div>
|
|
<div>
|
|
<label class="block text-[10px] font-medium text-slate-600 dark:text-slate-400 mb-1">Deep</label>
|
|
<input type="number" name="pomodoro_deep" min="20" max="180" value="<?= (int)$workdaySettings['pomodoro_deep'] ?>" class="w-full bg-white dark:bg-[#111622] border border-slate-300 dark:border-slate-700 rounded-xl px-2.5 py-1.5 text-xs text-slate-900 dark:text-white font-mono focus:outline-none focus:ring-1 focus:ring-indigo-500">
|
|
</div>
|
|
<div>
|
|
<label class="block text-[10px] font-medium text-slate-600 dark:text-slate-400 mb-1">Pauze</label>
|
|
<input type="number" name="pomodoro_break" min="2" max="30" value="<?= (int)$workdaySettings['pomodoro_break'] ?>" class="w-full bg-white dark:bg-[#111622] border border-slate-300 dark:border-slate-700 rounded-xl px-2.5 py-1.5 text-xs text-slate-900 dark:text-white font-mono focus:outline-none focus:ring-1 focus:ring-indigo-500">
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Standaard taakduur -->
|
|
<div class="p-4 rounded-2xl bg-slate-50 dark:bg-[#0c0f17] border border-slate-200 dark:border-slate-800 space-y-3">
|
|
<div class="text-xs font-bold text-slate-900 dark:text-white flex items-center space-x-1.5">
|
|
<i data-lucide="check-square" class="w-3.5 h-3.5 text-indigo-500"></i>
|
|
<span>Standaard Taakduur bij Aanmaken</span>
|
|
</div>
|
|
<div>
|
|
<label class="block text-[11px] font-medium text-slate-600 dark:text-slate-400 mb-1">Standaard tijdslot</label>
|
|
<select name="default_task_duration" class="w-full bg-white dark:bg-[#111622] border border-slate-300 dark:border-slate-700 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">
|
|
<option value="10" <?= $workdaySettings['default_task_duration'] == 10 ? 'selected' : '' ?>>10 minuten</option>
|
|
<option value="15" <?= $workdaySettings['default_task_duration'] == 15 ? 'selected' : '' ?>>15 minuten</option>
|
|
<option value="20" <?= $workdaySettings['default_task_duration'] == 20 ? 'selected' : '' ?>>20 minuten</option>
|
|
<option value="30" <?= $workdaySettings['default_task_duration'] == 30 ? 'selected' : '' ?>>30 minuten (Standaard)</option>
|
|
<option value="45" <?= $workdaySettings['default_task_duration'] == 45 ? 'selected' : '' ?>>45 minuten</option>
|
|
<option value="60" <?= $workdaySettings['default_task_duration'] == 60 ? 'selected' : '' ?>>60 minuten (1 uur)</option>
|
|
<option value="90" <?= $workdaySettings['default_task_duration'] == 90 ? 'selected' : '' ?>>90 minuten (1,5 uur)</option>
|
|
<option value="120" <?= $workdaySettings['default_task_duration'] == 120 ? 'selected' : '' ?>>120 minuten (2 uur)</option>
|
|
</select>
|
|
</div>
|
|
<p class="text-[10px] text-slate-400 dark:text-slate-500">Selectie bij het toevoegen van nieuwe dagtaken.</p>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="flex justify-end pt-2">
|
|
<button type="submit" class="px-5 py-2.5 rounded-xl bg-indigo-600 hover:bg-indigo-500 text-white text-xs font-bold transition shadow-xs cursor-pointer">
|
|
Werkdag Standaarden Opslaan
|
|
</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
|
|
<!-- 4. AI & Brainstorm Co-Pilot (Release v1.5) -->
|
|
<div class="fociz-card p-6 sm:p-7 space-y-6">
|
|
<div class="flex items-center justify-between pb-4 border-b border-slate-100 dark:border-slate-800/80">
|
|
<div class="flex items-center space-x-3.5">
|
|
<div class="w-12 h-12 rounded-xl bg-amber-50 dark:bg-amber-950/40 border border-amber-200 dark:border-amber-800/60 flex items-center justify-center text-amber-600 dark:text-amber-400 font-bold shadow-xs">
|
|
<i data-lucide="sparkles" class="w-6 h-6"></i>
|
|
</div>
|
|
<div>
|
|
<h2 class="text-lg font-bold text-slate-900 dark:text-white tracking-tight">AI & Brainstorm Co-Pilot</h2>
|
|
<p class="text-xs text-slate-500 dark:text-slate-400">Intelligente assistentie voor post-it categorisatie, ideegeneratie en slimme planning</p>
|
|
</div>
|
|
</div>
|
|
<span class="px-2.5 py-1 rounded-full <?= $aiSettings['has_key'] ? 'bg-teal-50 dark:bg-teal-950/40 text-teal-700 dark:text-teal-400 border border-teal-200 dark:border-teal-800/60' : 'bg-amber-50 dark:bg-amber-950/40 text-amber-700 dark:text-amber-400 border border-amber-200 dark:border-amber-800/60' ?> text-xs font-bold">
|
|
<?= $aiSettings['has_key'] ? 'AI Actief (v1.5)' : 'Release v1.5 Sleutel Vereist' ?>
|
|
</span>
|
|
</div>
|
|
|
|
<form method="POST" action="/admin.php" class="space-y-4">
|
|
<input type="hidden" name="action" value="save_ai_settings">
|
|
|
|
<div class="p-3.5 rounded-2xl bg-amber-50/70 dark:bg-amber-950/20 border border-amber-200/70 dark:border-amber-800/40">
|
|
<label class="flex items-center space-x-2 text-xs text-amber-900 dark:text-amber-200 cursor-pointer select-none">
|
|
<input type="checkbox" name="ai_enabled" value="1" <?= $aiSettings['ai_enabled'] ? 'checked' : '' ?> class="w-4 h-4 rounded text-indigo-600 focus:ring-indigo-500 border-slate-300 dark:border-slate-700">
|
|
<span class="font-bold">AI Co-Pilot inschakelen voor alle teamleden</span>
|
|
</label>
|
|
</div>
|
|
|
|
<div class="grid grid-cols-1 sm:grid-cols-2 gap-4">
|
|
<div>
|
|
<label class="block text-xs font-semibold text-slate-700 dark:text-slate-300 mb-1.5">AI Provider & Model</label>
|
|
<select name="ai_model" class="w-full bg-slate-50 dark:bg-[#0c0f17] 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">
|
|
<option value="gpt-4o-mini" <?= $aiSettings['ai_model'] === 'gpt-4o-mini' ? 'selected' : '' ?>>OpenAI GPT-4o-mini (Aanbevolen & Snel)</option>
|
|
<option value="gpt-4o" <?= $aiSettings['ai_model'] === 'gpt-4o' ? 'selected' : '' ?>>OpenAI GPT-4o (Krachtig & Uitgebreid)</option>
|
|
<option value="claude-3-5-sonnet-20241022" <?= $aiSettings['ai_model'] === 'claude-3-5-sonnet-20241022' ? 'selected' : '' ?>>Anthropic Claude 3.5 Sonnet</option>
|
|
</select>
|
|
</div>
|
|
|
|
<div>
|
|
<label class="block text-xs font-semibold text-slate-700 dark:text-slate-300 mb-1.5">OpenAI / Provider API Sleutel</label>
|
|
<input
|
|
type="password"
|
|
name="ai_api_key"
|
|
value="<?= $aiSettings['has_key'] ? '••••••••••••••••' : '' ?>"
|
|
placeholder="sk-proj-..."
|
|
class="w-full bg-slate-50 dark:bg-[#0c0f17] border border-slate-300 dark:border-slate-800 rounded-xl px-3 py-2 text-slate-900 dark:text-white text-xs font-mono placeholder-slate-400 focus:outline-none focus:ring-1 focus:ring-indigo-500"
|
|
>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="flex justify-end pt-2">
|
|
<button type="submit" class="px-5 py-2.5 rounded-xl bg-indigo-600 hover:bg-indigo-500 text-white text-xs font-bold transition shadow-xs cursor-pointer">
|
|
AI Instellingen Opslaan
|
|
</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Rechterkolom: Systeem & Team Monitor (4 cols) -->
|
|
<div class="lg:col-span-4 space-y-6">
|
|
|
|
<!-- Team Status Overzicht -->
|
|
<div class="fociz-card p-6 space-y-5">
|
|
<div class="flex items-center justify-between pb-4 border-b border-slate-100 dark:border-slate-800/80">
|
|
<div class="flex items-center space-x-2.5">
|
|
<i data-lucide="activity" class="w-4 h-4 text-indigo-600 dark:text-indigo-400"></i>
|
|
<h3 class="text-sm font-bold text-slate-900 dark:text-white">Team & Integratiestatus</h3>
|
|
</div>
|
|
<a href="/users.php" class="text-[11px] text-indigo-600 dark:text-indigo-400 hover:underline font-semibold">
|
|
Alle gebruikers →
|
|
</a>
|
|
</div>
|
|
|
|
<div class="space-y-3">
|
|
<?php foreach ($recentUsers as $u): ?>
|
|
<div class="p-3 rounded-2xl bg-slate-50 dark:bg-[#0c0f17] border border-slate-200 dark:border-slate-800/70 flex items-center justify-between">
|
|
<div class="flex items-center space-x-3 overflow-hidden">
|
|
<div class="w-8 h-8 rounded-full bg-indigo-50 dark:bg-indigo-950/40 border border-indigo-200 dark:border-indigo-800/60 text-indigo-700 dark:text-indigo-300 font-bold flex items-center justify-center text-xs flex-shrink-0">
|
|
<?= strtoupper(substr($u['name'], 0, 1)) ?>
|
|
</div>
|
|
<div class="truncate">
|
|
<span class="text-xs font-semibold text-slate-900 dark:text-white block truncate"><?= htmlspecialchars($u['name']) ?></span>
|
|
<span class="text-[10px] text-slate-500 dark:text-slate-400 block truncate"><?= htmlspecialchars($u['email']) ?></span>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="flex items-center space-x-1.5 flex-shrink-0">
|
|
<span title="Outlook: <?= $u['has_outlook'] ? 'Live Gekoppeld' : 'Niet gekoppeld' ?>" class="w-5 h-5 rounded-md flex items-center justify-center text-[10px] <?= $u['has_outlook'] ? 'bg-sky-50 dark:bg-sky-950/50 text-sky-700 dark:text-sky-300 border border-sky-200 dark:border-sky-800/60' : 'bg-slate-200 dark:bg-slate-800 text-slate-400 dark:text-slate-600' ?>">
|
|
<i data-lucide="calendar" class="w-3 h-3"></i>
|
|
</span>
|
|
<span title="Jira: <?= $u['has_jira'] ? 'Gekoppeld' : 'Niet gekoppeld' ?>" class="w-5 h-5 rounded-md flex items-center justify-center text-[10px] <?= $u['has_jira'] ? 'bg-indigo-50 dark:bg-indigo-950/50 text-indigo-700 dark:text-indigo-300 border border-indigo-200 dark:border-indigo-800/60' : 'bg-slate-200 dark:bg-slate-800 text-slate-400 dark:text-slate-600' ?>">
|
|
<i data-lucide="check-square" class="w-3 h-3"></i>
|
|
</span>
|
|
</div>
|
|
</div>
|
|
<?php endforeach; ?>
|
|
</div>
|
|
|
|
<div class="pt-2">
|
|
<a href="/users.php" class="w-full flex items-center justify-center space-x-2 py-2.5 rounded-xl bg-slate-100 hover:bg-slate-200 dark:bg-slate-800 dark:hover:bg-slate-700 text-slate-800 dark:text-slate-200 text-xs font-semibold transition border border-slate-200 dark:border-slate-700 shadow-xs">
|
|
<i data-lucide="user-plus" class="w-3.5 h-3.5 text-indigo-600 dark:text-indigo-400"></i>
|
|
<span>Nieuwe Collega Uitnodigen</span>
|
|
</a>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Systeem Gezondheid & Beveiliging -->
|
|
<div class="fociz-card p-6 space-y-4">
|
|
<div class="flex items-center space-x-2 text-teal-600 dark:text-teal-400 text-xs font-semibold">
|
|
<i data-lucide="shield-check" class="w-4 h-4"></i>
|
|
<span>Systeem & Beveiliging</span>
|
|
</div>
|
|
<div class="space-y-2.5 text-xs text-slate-700 dark:text-slate-300">
|
|
<div class="flex items-center justify-between p-2.5 rounded-xl bg-slate-50 dark:bg-[#0c0f17] border border-slate-200 dark:border-slate-800">
|
|
<span>Tweestapsverificatie (2FA)</span>
|
|
<span class="text-teal-600 dark:text-teal-400 font-bold flex items-center space-x-1">
|
|
<i data-lucide="check" class="w-3 h-3"></i>
|
|
<span>Verplicht</span>
|
|
</span>
|
|
</div>
|
|
|
|
<div class="flex items-center justify-between p-2.5 rounded-xl bg-slate-50 dark:bg-[#0c0f17] border border-slate-200 dark:border-slate-800">
|
|
<span>Database Versie</span>
|
|
<span class="text-slate-500 dark:text-slate-400 font-mono text-[11px]">MariaDB 10.x</span>
|
|
</div>
|
|
|
|
<div class="flex items-center justify-between p-2.5 rounded-xl bg-slate-50 dark:bg-[#0c0f17] border border-slate-200 dark:border-slate-800">
|
|
<span>Push Notificaties</span>
|
|
<span class="text-teal-600 dark:text-teal-400 font-bold">Actief</span>
|
|
</div>
|
|
|
|
<div class="flex items-center justify-between p-2.5 rounded-xl bg-slate-50 dark:bg-[#0c0f17] border border-slate-200 dark:border-slate-800">
|
|
<span>Graph API Sync</span>
|
|
<span class="text-sky-600 dark:text-sky-400 font-bold">Live v1.0</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
function copyAdminRedirectUri() {
|
|
const input = document.getElementById('adminRedirectUri');
|
|
if (!input) return;
|
|
input.select();
|
|
navigator.clipboard.writeText(input.value).then(() => {
|
|
const copyText = document.getElementById('copyText');
|
|
const copyIcon = document.getElementById('copyIcon');
|
|
if (copyText) copyText.innerText = 'Gekopieerd!';
|
|
if (copyIcon) copyIcon.setAttribute('data-lucide', 'check');
|
|
if (window.lucide) lucide.createIcons();
|
|
setTimeout(() => {
|
|
if (copyText) copyText.innerText = 'Kopiëren';
|
|
if (copyIcon) copyIcon.setAttribute('data-lucide', 'copy');
|
|
if (window.lucide) lucide.createIcons();
|
|
}, 2000);
|
|
});
|
|
}
|
|
|
|
function toggleAdminSecret() {
|
|
const input = document.getElementById('adminClientSecret');
|
|
const icon = document.getElementById('eyeSecretIcon');
|
|
if (!input || !icon) return;
|
|
if (input.type === 'password') {
|
|
input.type = 'text';
|
|
icon.setAttribute('data-lucide', 'eye-off');
|
|
} else {
|
|
input.type = 'password';
|
|
icon.setAttribute('data-lucide', 'eye');
|
|
}
|
|
if (window.lucide) lucide.createIcons();
|
|
}
|
|
|
|
function toggleAdminGuide() {
|
|
const content = document.getElementById('adminGuideContent');
|
|
const arrow = document.getElementById('guideArrow');
|
|
if (!content) return;
|
|
content.classList.toggle('hidden');
|
|
if (arrow) {
|
|
arrow.classList.toggle('rotate-180');
|
|
}
|
|
}
|
|
</script>
|
|
|
|
<?php require_once __DIR__ . '/includes/footer.php'; ?>
|