Add webhook receivers for Jira and Microsoft Graph, and implement Outlook 365 email hub with inbox management features

This commit is contained in:
2026-09-10 14:08:34 +02:00
parent 25c2ffeef8
commit dc0b33a278
5 changed files with 1137 additions and 0 deletions
+152
View File
@@ -0,0 +1,152 @@
<?php
/**
* Fociz Live Sync & Realtime Notificatie Hub
* Controleert continu op nieuwe Outlook e-mails, relevante Jira tickets en agenda updates.
*/
require_once __DIR__ . '/../includes/auth.php';
require_once __DIR__ . '/../includes/integrations/microsoft.php';
require_once __DIR__ . '/../includes/integrations/jira.php';
$user = require_auth();
$userId = (int)$user['id'];
$db = get_db_connection();
header('Content-Type: application/json; charset=utf-8');
try {
$nowTs = time();
$nowIso = date('Y-m-d H:i:s');
$today = date('Y-m-d');
$clientLastMailId = trim($_GET['last_mail_id'] ?? '');
$clientLastJiraKey = trim($_GET['last_jira_key'] ?? '');
$clientCalendarHash = trim($_GET['calendar_hash'] ?? '');
// 1. Outlook E-mail Controle
$mailRes = microsoft_fetch_inbox_messages($userId, 5);
$messages = $mailRes['messages'] ?? [];
$unreadCount = (int)($mailRes['unread_count'] ?? 0);
$latestMail = $messages[0] ?? null;
$hasNewMail = false;
$newMailPayload = null;
if ($latestMail && !empty($latestMail['id'])) {
// Als de client een ander ID heeft dan de allernieuwste mail en het bericht is ongelezen
if (!empty($clientLastMailId) && $latestMail['id'] !== $clientLastMailId && empty($latestMail['is_read'])) {
$hasNewMail = true;
$newMailPayload = [
'id' => $latestMail['id'],
'subject' => $latestMail['subject'],
'from_name' => $latestMail['from_name'],
'from_email' => $latestMail['from_email'],
'received_human' => $latestMail['received_human'],
'web_link' => $latestMail['web_link'] ?? '/mail.php',
];
}
}
// 2. Jira Tickets Controle (rekening houdend met persoonlijke toewijzing & default queue filter zoals DX of SD)
$userFilterCode = $user['default_jira_filter_code'] ?? '';
$jiraData = jira_get_user_issues($userId);
$assignedIssues = $jiraData['assigned_issues'] ?? ($jiraData['issues'] ?? []);
$teamIssues = $jiraData['team_issues'] ?? [];
$dxValues = ['koppelingen', 'sso-koppeling', 'rest-endpoints', 'datadump', 'uwv-koppeling', 'migratie'];
$relevantIssues = [];
// Filter issues die specifiek relevant zijn voor deze medewerker
foreach (array_merge($assignedIssues, $teamIssues) as $iss) {
$isAssignedToMe = !empty($iss['assignee_name']) && (stripos($iss['assignee_name'], $user['name']) !== false || stripos($iss['assignee_name'], 'Niek') !== false);
$ol = strtolower(trim($iss['onderwerp_logging'] ?? ''));
$isQueueMatch = false;
if (!empty($userFilterCode)) {
if ($userFilterCode === 'DX' && in_array($ol, $dxValues)) {
$isQueueMatch = true;
} elseif ($userFilterCode === 'SD' && ($ol === '' || $ol === 'empty' || !in_array($ol, $dxValues))) {
$isQueueMatch = true;
}
}
if ($isAssignedToMe || $isQueueMatch) {
$relevantIssues[$iss['key']] = $iss;
}
}
$hasNewJira = false;
$newJiraPayload = [];
$newestJiraKey = '';
if (!empty($relevantIssues)) {
// Pak het nieuwste ticket
$newest = reset($relevantIssues);
$newestJiraKey = $newest['key'];
if (!empty($clientLastJiraKey) && $newestJiraKey !== $clientLastJiraKey) {
$hasNewJira = true;
$newJiraPayload = [
'key' => $newest['key'],
'summary' => $newest['summary'],
'priority' => $newest['priority'] ?? 'Medium',
'issue_type' => $newest['issue_type'] ?? 'Taak',
'project' => $newest['project'] ?? 'SD',
'url' => $newest['url'] ?? ('/jira.php?highlight=' . $newest['key']),
];
}
}
// 3. Outlook Agenda Controle (Tijdlijn events van vandaag)
$calendarChanged = false;
$todayEvents = [];
$stmtInt = $db->prepare("SELECT * FROM integrations WHERE user_id = ? AND provider = 'OUTLOOK' AND is_enabled = 1");
$stmtInt->execute([$userId]);
$outlookInt = $stmtInt->fetch();
if ($outlookInt && !empty($outlookInt['access_token']) && empty($outlookInt['is_demo_mode'])) {
$todayEvents = microsoft_fetch_today_events($userId, $today);
}
$calHash = md5(json_encode(array_map(function($e) {
return ($e['id'] ?? '') . ($e['start'] ?? '') . ($e['end'] ?? '') . ($e['subject'] ?? '');
}, $todayEvents)));
if (!empty($clientCalendarHash) && $calHash !== $clientCalendarHash) {
$calendarChanged = true;
}
// 4. Update sync timestamps in database
$stmtUpdate = $db->prepare("UPDATE users SET last_mail_check_at = NOW(), last_jira_check_at = NOW() WHERE id = ?");
$stmtUpdate->execute([$userId]);
echo json_encode([
'success' => true,
'server_time' => date('H:i:s'),
'mail' => [
'has_new' => $hasNewMail,
'unread_count' => $unreadCount,
'latest_id' => $latestMail['id'] ?? '',
'new_mail' => $newMailPayload,
],
'jira' => [
'has_new' => $hasNewJira,
'newest_key' => $newestJiraKey,
'new_issue' => $newJiraPayload,
],
'calendar' => [
'has_changes' => $calendarChanged,
'hash' => $calHash,
'count' => count($todayEvents),
],
]);
exit;
} catch (Throwable $e) {
http_response_code(500);
echo json_encode([
'success' => false,
'error' => $e->getMessage(),
]);
}
+188
View File
@@ -0,0 +1,188 @@
<?php
/**
* Fociz Microsoft Outlook 365 E-mail API Endpoint
* Verwerkt ophalen van inbox, detailberichten, beantwoorden, doorsturen en 1-klik inplannen.
*/
require_once __DIR__ . '/../includes/auth.php';
require_once __DIR__ . '/../includes/integrations/microsoft.php';
$user = require_auth();
$userId = (int)$user['id'];
$db = get_db_connection();
header('Content-Type: application/json; charset=utf-8');
$action = $_GET['action'] ?? ($_POST['action'] ?? '');
try {
// 1. Inbox berichten ophalen
if ($action === 'get_inbox') {
$limit = isset($_GET['limit']) ? (int)$_GET['limit'] : 25;
$unreadOnly = !empty($_GET['unread_only']) && $_GET['unread_only'] === '1';
$res = microsoft_fetch_inbox_messages($userId, $limit, $unreadOnly);
echo json_encode($res);
exit;
}
// 2. Enkel bericht detail ophalen
if ($action === 'get_message') {
$messageId = trim($_GET['id'] ?? ($_POST['id'] ?? ''));
if (empty($messageId)) {
echo json_encode(['success' => false, 'error' => 'Geen bericht-ID opgegeven.']);
exit;
}
$detail = microsoft_fetch_message_detail($userId, $messageId);
if (!$detail) {
echo json_encode(['success' => false, 'error' => 'Bericht niet gevonden of geen toegang.']);
exit;
}
echo json_encode(['success' => true, 'message' => $detail]);
exit;
}
// 3. Markeer als gelezen of ongelezen
if ($action === 'mark_read') {
$input = json_decode(file_get_contents('php://input'), true) ?: $_POST;
$messageId = trim($input['id'] ?? '');
$isRead = isset($input['is_read']) ? (bool)$input['is_read'] : true;
if (empty($messageId)) {
echo json_encode(['success' => false, 'error' => 'Geen bericht-ID opgegeven.']);
exit;
}
$ok = microsoft_mark_message_read($userId, $messageId, $isRead);
echo json_encode(['success' => $ok]);
exit;
}
// 4. E-mail beantwoorden
if ($action === 'reply') {
$input = json_decode(file_get_contents('php://input'), true) ?: $_POST;
$messageId = trim($input['id'] ?? '');
$comment = trim($input['comment'] ?? '');
$replyAll = !empty($input['reply_all']);
if (empty($messageId) || empty($comment)) {
echo json_encode(['success' => false, 'error' => 'Bericht-ID en reactietekst zijn verplicht.']);
exit;
}
$res = microsoft_reply_message($userId, $messageId, $comment, $replyAll);
echo json_encode($res);
exit;
}
// 5. E-mail doorsturen
if ($action === 'forward') {
$input = json_decode(file_get_contents('php://input'), true) ?: $_POST;
$messageId = trim($input['id'] ?? '');
$toInput = $input['to'] ?? '';
$comment = trim($input['comment'] ?? '');
if (empty($messageId) || empty($toInput)) {
echo json_encode(['success' => false, 'error' => 'Bericht-ID en minimaal één geadresseerde zijn verplicht.']);
exit;
}
$recipients = [];
if (is_array($toInput)) {
$recipients = $toInput;
} else {
$parts = explode(',', (string)$toInput);
foreach ($parts as $p) {
$p = trim($p);
if (filter_var($p, FILTER_VALIDATE_EMAIL)) {
$recipients[] = ['address' => $p, 'name' => $p];
}
}
}
if (empty($recipients)) {
echo json_encode(['success' => false, 'error' => 'Geen geldige e-mailadressen ingevoerd.']);
exit;
}
$res = microsoft_forward_message($userId, $messageId, $recipients, $comment);
echo json_encode($res);
exit;
}
// 6. E-mail direct inplannen in Fociz dagplanning
if ($action === 'plan_email') {
$input = json_decode(file_get_contents('php://input'), true) ?: $_POST;
$messageId = trim($input['message_id'] ?? '');
$subject = trim($input['subject'] ?? 'E-mail actiepunt');
$planDate = trim($input['plan_date'] ?? date('Y-m-d'));
$timeBlockStart = !empty($input['time_block_start']) ? trim($input['time_block_start']) : null;
$durationMinutes = (int)($input['duration_minutes'] ?? 30);
$priority = in_array($input['priority'] ?? '', ['P1', 'P2', 'P3']) ? $input['priority'] : 'P2';
$webLink = trim($input['web_link'] ?? 'https://outlook.office.com/mail/');
$sender = trim($input['sender'] ?? '');
if (empty($subject)) {
echo json_encode(['success' => false, 'error' => 'Onderwerp is verplicht.']);
exit;
}
if ($durationMinutes <= 0) $durationMinutes = 30;
$timeBlockEnd = null;
if (!empty($timeBlockStart)) {
try {
$dtEnd = new DateTime("{$planDate} {$timeBlockStart}:00", new DateTimeZone('Europe/Amsterdam'));
$dtEnd->modify("+{$durationMinutes} minutes");
$timeBlockEnd = $dtEnd->format('H:i');
} catch (Throwable $t) {}
}
// Zorg dat daily_plan bestaat
$stmtPlan = $db->prepare("
INSERT INTO daily_plans (user_id, plan_date)
VALUES (?, ?)
ON DUPLICATE KEY UPDATE id = LAST_INSERT_ID(id)
");
$stmtPlan->execute([$userId, $planDate]);
$planId = (int)$db->lastInsertId();
$taskTitle = "✉️ " . $subject;
if (!empty($sender)) {
$taskTitle .= " (van " . $sender . ")";
}
$stmtTask = $db->prepare("
INSERT INTO tasks (user_id, daily_plan_id, title, priority, time_block_start, time_block_end, duration_minutes, source, external_id, external_url)
VALUES (?, ?, ?, ?, ?, ?, ?, 'OUTLOOK_MAIL', ?, ?)
");
$stmtTask->execute([
$userId,
$planId,
$taskTitle,
$priority,
$timeBlockStart,
$timeBlockEnd,
$durationMinutes,
$messageId,
$webLink
]);
$taskId = (int)$db->lastInsertId();
echo json_encode([
'success' => true,
'task_id' => $taskId,
'plan_date' => $planDate,
'message' => "E-mail succesvol ingepland voor {$planDate}."
]);
exit;
}
echo json_encode(['success' => false, 'error' => 'Onbekende e-mailactie.']);
} catch (Throwable $e) {
http_response_code(500);
echo json_encode(['success' => false, 'error' => $e->getMessage()]);
}
+40
View File
@@ -0,0 +1,40 @@
<?php
/**
* Jira Webhook Receiver
* Ontvangt webhook events van Atlassian Jira Cloud (issue_created, issue_updated, etc.)
*/
require_once __DIR__ . '/../../config/database.php';
header('Content-Type: application/json');
$rawPayload = file_get_contents('php://input');
$data = json_decode($rawPayload, true);
if (!$data) {
http_response_code(400);
echo json_encode(['error' => 'Invalid JSON payload']);
exit;
}
try {
$db = get_db_connection();
$event = $data['webhookEvent'] ?? 'jira:unknown';
$issueKey = $data['issue']['key'] ?? '';
$summary = $data['issue']['fields']['summary'] ?? '';
// Sla event tijdelijk op of invalidate session cache
error_log("Jira Webhook ontvangen: {$event} voor {$issueKey} - {$summary}");
echo json_encode([
'received' => true,
'event' => $event,
'issue_key' => $issueKey,
'timestamp' => date('Y-m-d H:i:s')
]);
exit;
} catch (Throwable $t) {
http_response_code(500);
echo json_encode(['error' => $t->getMessage()]);
}
+31
View File
@@ -0,0 +1,31 @@
<?php
/**
* Microsoft Graph Webhook Receiver
* Ontvangt subscription notifications voor /me/mailFolders('inbox')/messages en /me/events
*/
require_once __DIR__ . '/../../config/database.php';
// Validatie bij aanmaken van Graph subscription
if (isset($_GET['validationToken'])) {
header('Content-Type: text/plain; charset=utf-8');
http_response_code(200);
echo $_GET['validationToken'];
exit;
}
header('Content-Type: application/json');
$raw = file_get_contents('php://input');
$data = json_decode($raw, true);
if ($data && isset($data['value'])) {
foreach ($data['value'] as $change) {
$resource = $change['resource'] ?? '';
$changeType = $change['changeType'] ?? '';
error_log("Microsoft Graph Webhook: {$changeType} op {$resource}");
}
}
echo json_encode(['received' => true]);
+726
View File
@@ -0,0 +1,726 @@
<?php
/**
* Fociz Microsoft Outlook 365 E-mail Hub
* Volwaardige inbox, berichtdetail, beantwoorden, doorsturen en direct inplannen in Fociz.
*/
$pageTitle = 'Fociz — Microsoft Outlook Mail';
require_once __DIR__ . '/includes/header.php';
require_once __DIR__ . '/includes/integrations/microsoft.php';
$userId = (int)($currentUser['id'] ?? 0);
$db = get_db_connection();
// Haal initiële inbox data op
$inboxData = microsoft_fetch_inbox_messages($userId, 30);
$messages = $inboxData['messages'] ?? [];
$isDemo = !empty($inboxData['is_demo']);
$unreadCount = (int)($inboxData['unread_count'] ?? 0);
// Controleer status
$stmtInt = $db->prepare("SELECT * FROM integrations WHERE user_id = ? AND provider = 'OUTLOOK' AND is_enabled = 1");
$stmtInt->execute([$userId]);
$outlookInt = $stmtInt->fetch();
$isLiveOutlook = !empty($outlookInt['access_token']) && empty($outlookInt['is_demo_mode']);
$jsonMessages = json_encode($messages, JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_HEX_AMP);
?>
<div class="max-w-7xl mx-auto space-y-6">
<!-- Top Banner: Status & Snelle Acties -->
<div class="fociz-card p-6 sm:p-7 relative overflow-hidden">
<div class="flex flex-col lg:flex-row lg:items-center justify-between gap-4">
<div>
<div class="flex items-center space-x-2 text-sky-600 dark:text-sky-400 text-xs font-bold uppercase tracking-wider mb-1">
<i data-lucide="mail" class="w-4 h-4"></i>
<span>Microsoft Outlook 365 &bull; Inbox</span>
<?php if ($isLiveOutlook): ?>
<span class="px-2.5 py-0.5 rounded-full text-[10px] font-extrabold bg-emerald-50 text-emerald-700 dark:bg-emerald-500/15 dark:text-emerald-300 ring-1 ring-emerald-500/30">LIVE VERBONDEN</span>
<?php else: ?>
<span class="px-2.5 py-0.5 rounded-full text-[10px] font-bold bg-amber-50 text-amber-700 dark:bg-amber-500/15 dark:text-amber-300 ring-1 ring-amber-500/30">DEMO INBOX</span>
<?php endif; ?>
</div>
<h1 class="text-2xl sm:text-3xl font-black tracking-tight text-slate-900 dark:text-white">
E-mail & Communicatie
</h1>
<p class="text-xs sm:text-sm text-slate-500 dark:text-slate-400 mt-1">
Bekijk binnenkomende e-mails, beantwoord direct en plan belangrijke berichten in één klik in op jouw tijdlijn.
</p>
</div>
<!-- Knoppen rechts -->
<div class="flex flex-wrap items-center gap-2">
<button onclick="refreshMailbox()" id="refreshMailBtn" class="flex items-center space-x-1.5 px-3.5 py-2 rounded-xl bg-slate-100 hover:bg-slate-200 dark:bg-slate-800/80 dark:hover:bg-slate-700/80 text-slate-700 dark:text-slate-300 text-xs font-semibold transition cursor-pointer shadow-2xs">
<i data-lucide="refresh-cw" id="refreshMailIcon" class="w-3.5 h-3.5"></i>
<span>Vernieuwen</span>
</button>
<?php if (!$isLiveOutlook): ?>
<a href="/profile.php" class="flex items-center space-x-1.5 px-4 py-2 rounded-xl bg-gradient-to-r from-sky-600 to-indigo-600 hover:from-sky-500 hover:to-indigo-500 text-white text-xs font-bold transition shadow-sm shadow-sky-600/20">
<i data-lucide="link-2" class="w-3.5 h-3.5"></i>
<span>Live Outlook Koppelen</span>
</a>
<?php else: ?>
<a href="https://outlook.office.com/mail/" target="_blank" rel="noopener noreferrer" class="flex items-center space-x-1.5 px-3.5 py-2 rounded-xl bg-sky-50 dark:bg-sky-950/40 text-sky-700 dark:text-sky-300 border border-sky-200 dark:border-sky-800/60 text-xs font-bold transition hover:bg-sky-100">
<span>Open in Outlook Web</span>
<i data-lucide="external-link" class="w-3.5 h-3.5"></i>
</a>
<?php endif; ?>
</div>
</div>
<?php if (!$isLiveOutlook): ?>
<div class="mt-5 p-3.5 rounded-2xl bg-amber-50/80 dark:bg-amber-950/30 border border-amber-200 dark:border-amber-900/40 flex items-center justify-between gap-3 text-xs text-amber-900 dark:text-amber-200">
<div class="flex items-center space-x-2.5">
<i data-lucide="info" class="w-4 h-4 text-amber-600 dark:text-amber-400 flex-shrink-0"></i>
<span>Je bekijkt momenteel een interactieve demonstratieset. Koppel je zakelijke Microsoft 365 account via Profiel om al je live zakelijke e-mails in te laden.</span>
</div>
<a href="/auth/oauth_microsoft.php" class="font-bold underline hover:text-amber-700 dark:hover:text-amber-100 flex-shrink-0">Nu koppelen &rarr;</a>
</div>
<?php endif; ?>
</div>
<!-- Filterbalk & Zoeken -->
<div class="fociz-card p-4 flex flex-col sm:flex-row items-center justify-between gap-3">
<!-- Filter tabs: Alle / Ongelezen -->
<div class="flex items-center space-x-1.5 bg-slate-100/90 dark:bg-slate-800/60 p-1 rounded-xl border border-slate-200/70 dark:border-slate-700/60 text-xs font-bold w-full sm:w-auto">
<button onclick="setMailFilter('all')" id="filterBtn_all" class="flex-1 sm:flex-none px-3.5 py-1.5 rounded-lg bg-white dark:bg-slate-900 text-slate-900 dark:text-white shadow-xs transition">
<span>Alle Berichten</span>
<span class="ml-1 px-1.5 py-0.2 rounded-full text-[10px] bg-slate-100 dark:bg-slate-800 text-slate-600 dark:text-slate-400" id="countAll"><?= count($messages) ?></span>
</button>
<button onclick="setMailFilter('unread')" id="filterBtn_unread" class="flex-1 sm:flex-none px-3.5 py-1.5 rounded-lg text-slate-600 dark:text-slate-400 hover:text-slate-900 dark:hover:text-white transition">
<span>Ongelezen</span>
<span class="ml-1 px-1.5 py-0.2 rounded-full text-[10px] bg-sky-100 dark:bg-sky-900/60 text-sky-700 dark:text-sky-300" id="countUnread"><?= $unreadCount ?></span>
</button>
</div>
<!-- Zoekveld -->
<div class="relative w-full sm:w-72">
<i data-lucide="search" class="w-3.5 h-3.5 text-slate-400 absolute left-3 top-3 pointer-events-none"></i>
<input type="text" id="mailSearchInput" oninput="applyMailSearch()" placeholder="Zoek op afzender, onderwerp..." class="w-full bg-slate-50 dark:bg-[#0c0f17] border border-slate-200 dark:border-slate-800 rounded-xl pl-9 pr-3 py-2 text-xs text-slate-900 dark:text-white placeholder-slate-400 focus:outline-none focus:ring-1 focus:ring-sky-500">
</div>
</div>
<!-- Twee-kolommen Mail Interface -->
<div class="grid grid-cols-1 lg:grid-cols-12 gap-6 min-h-[600px]">
<!-- Linker kolom: Berichtenlijst (5 kolommen) -->
<div id="mailListColumn" class="lg:col-span-5 fociz-card p-3 sm:p-4 flex flex-col h-[720px] overflow-hidden">
<div class="text-xs font-bold uppercase tracking-wider text-slate-400 dark:text-slate-500 px-2 py-1 mb-2 flex items-center justify-between">
<span>Inbox Berichten (<span id="visibleMailCount"><?= count($messages) ?></span>)</span>
<span class="text-[10px] text-slate-400 font-normal">Sorteer: Nieuwste eerst</span>
</div>
<div class="flex-1 overflow-y-auto space-y-1.5 pr-1" id="mailListContainer">
<!-- Wordt dynamisch gerenderd via JS -->
</div>
</div>
<!-- Rechter kolom: Bericht Detail & Acties (7 kolommen) -->
<div id="mailDetailColumn" class="lg:col-span-7 fociz-card p-6 sm:p-7 flex flex-col h-[720px] overflow-hidden">
<!-- Empty state wanneer geen mail geselecteerd is -->
<div id="noMailSelectedState" class="flex-1 flex flex-col items-center justify-center text-center p-8 text-slate-400">
<div class="w-14 h-14 rounded-2xl bg-slate-100 dark:bg-slate-800/80 flex items-center justify-center mb-3 text-slate-400 dark:text-slate-500">
<i data-lucide="mail-open" class="w-7 h-7"></i>
</div>
<h4 class="text-sm font-bold text-slate-700 dark:text-slate-300">Geen e-mail geselecteerd</h4>
<p class="text-xs text-slate-400 dark:text-slate-500 max-w-sm mt-1">
Selecteer een bericht aan de linkerkant om de volledige inhoud te lezen, te beantwoorden of in te plannen in je dagagenda.
</p>
</div>
<!-- Actief geselecteerd bericht -->
<div id="mailActiveContainer" class="hidden flex-1 flex flex-col min-h-0">
<!-- Mobiel terug knop -->
<button type="button" onclick="closeMobileDetail()" class="lg:hidden flex items-center space-x-1 text-xs font-bold text-slate-500 mb-3 hover:text-slate-800">
<i data-lucide="arrow-left" class="w-3.5 h-3.5"></i>
<span>Terug naar lijst</span>
</button>
<!-- Detail Header -->
<div class="pb-4 border-b border-slate-100 dark:border-slate-800/80 space-y-3">
<div class="flex items-start justify-between gap-3">
<h2 id="detailSubject" class="text-lg sm:text-xl font-black text-slate-900 dark:text-white leading-snug"></h2>
<div class="flex items-center space-x-1.5 flex-shrink-0">
<!-- Plan in Fociz knop -->
<button type="button" onclick="openPlanMailModal()" class="flex items-center space-x-1.5 px-3 py-1.5 rounded-xl bg-indigo-600 hover:bg-indigo-500 text-white text-xs font-bold transition shadow-sm shadow-indigo-600/20 cursor-pointer">
<i data-lucide="calendar-plus" class="w-3.5 h-3.5"></i>
<span>Plan in Fociz</span>
</button>
<!-- Beantwoorden knop -->
<button type="button" onclick="openReplyModal()" class="flex items-center space-x-1 px-2.5 py-1.5 rounded-xl bg-slate-100 hover:bg-slate-200 dark:bg-slate-800 dark:hover:bg-slate-700 text-slate-700 dark:text-slate-300 text-xs font-semibold transition cursor-pointer">
<i data-lucide="reply" class="w-3.5 h-3.5"></i>
<span class="hidden sm:inline">Antwoord</span>
</button>
<!-- Doorsturen knop -->
<button type="button" onclick="openForwardModal()" class="flex items-center space-x-1 px-2.5 py-1.5 rounded-xl bg-slate-100 hover:bg-slate-200 dark:bg-slate-800 dark:hover:bg-slate-700 text-slate-700 dark:text-slate-300 text-xs font-semibold transition cursor-pointer">
<i data-lucide="corner-up-right" class="w-3.5 h-3.5"></i>
<span class="hidden sm:inline">Doorsturen</span>
</button>
</div>
</div>
<!-- Afzender & Ontvangers info -->
<div class="flex items-center justify-between text-xs pt-1">
<div class="flex items-center space-x-3">
<div id="detailSenderAvatar" class="w-9 h-9 rounded-xl bg-sky-100 dark:bg-sky-950/60 text-sky-700 dark:text-sky-300 font-bold flex items-center justify-center text-xs">
NL
</div>
<div>
<div class="font-bold text-slate-900 dark:text-white" id="detailSenderName"></div>
<div class="text-[11px] text-slate-400 font-mono" id="detailSenderEmail"></div>
</div>
</div>
<div class="text-right">
<div class="text-xs font-semibold text-slate-600 dark:text-slate-300" id="detailReceivedHuman"></div>
<div class="text-[10px] text-slate-400 font-mono" id="detailReceivedExact"></div>
</div>
</div>
</div>
<!-- Detail Body (Scrollable) -->
<div class="flex-1 overflow-y-auto py-5 text-sm text-slate-800 dark:text-slate-200 leading-relaxed font-normal" id="detailBody">
<!-- HTML body content -->
</div>
<!-- Footer met web link -->
<div class="pt-3 border-t border-slate-100 dark:border-slate-800 flex items-center justify-between text-xs text-slate-400">
<span id="detailFooterBadge">Outlook 365 Bericht</span>
<a id="detailWebLink" href="#" target="_blank" rel="noopener noreferrer" class="text-sky-600 dark:text-sky-400 hover:underline flex items-center space-x-1 font-semibold">
<span>Bekijk in Outlook Web App</span>
<i data-lucide="external-link" class="w-3 h-3"></i>
</a>
</div>
</div>
</div>
</div>
</div>
<!-- MODAL 1: E-mail Inplannen in Fociz Dagplanning -->
<div id="planMailModal" class="fixed inset-0 z-50 bg-slate-950/60 backdrop-blur-sm hidden items-center justify-center p-4">
<div class="bg-white dark:bg-[#111624] border border-slate-200 dark:border-slate-800 rounded-3xl max-w-lg w-full p-6 sm:p-7 shadow-2xl space-y-5 animate-in fade-in zoom-in-95 duration-150">
<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.5">
<div class="w-9 h-9 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-5 h-5"></i>
</div>
<div>
<h3 class="text-sm font-bold text-slate-900 dark:text-white">Plan E-mail in als Taak</h3>
<p class="text-[11px] text-slate-400">Plaats dit actiepunt direct op jouw Fociz tijdlijn</p>
</div>
</div>
<button onclick="closePlanMailModal()" class="text-slate-400 hover:text-slate-600 p-1.5 rounded-lg transition cursor-pointer">
<i data-lucide="x" class="w-4 h-4"></i>
</button>
</div>
<form onsubmit="submitPlanEmail(event)" class="space-y-4">
<input type="hidden" id="planMailId" value="">
<input type="hidden" id="planMailWebLink" value="">
<input type="hidden" id="planMailSender" value="">
<div>
<label class="block text-xs font-semibold text-slate-700 dark:text-slate-300 mb-1">Taaktitel</label>
<input type="text" id="planMailSubject" required class="w-full bg-slate-50 dark:bg-[#0c0f17] border border-slate-300 dark:border-slate-700 rounded-xl px-3.5 py-2 text-xs text-slate-900 dark:text-white font-semibold focus:outline-none focus:ring-1 focus:ring-indigo-500">
</div>
<div class="grid grid-cols-1 sm:grid-cols-2 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="planMailDate" value="<?= date('Y-m-d') ?>" required class="w-full bg-slate-50 dark:bg-[#0c0f17] border border-slate-300 dark:border-slate-700 rounded-xl px-3 py-2 text-xs text-slate-900 dark:text-white focus:outline-none 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">Starttijd (optioneel)</label>
<input type="text" id="planMailStartTime" placeholder="bijv. 10:30" class="w-full bg-slate-50 dark:bg-[#0c0f17] border border-slate-300 dark:border-slate-700 rounded-xl px-3 py-2 text-xs font-mono text-slate-900 dark:text-white focus:outline-none focus:ring-1 focus:ring-indigo-500">
</div>
</div>
<div class="grid grid-cols-1 sm:grid-cols-2 gap-3">
<div>
<label class="block text-xs font-semibold text-slate-700 dark:text-slate-300 mb-1">Tijdsduur</label>
<select id="planMailDuration" class="w-full bg-slate-50 dark:bg-[#0c0f17] border border-slate-300 dark:border-slate-700 rounded-xl px-3 py-2 text-xs text-slate-900 dark:text-white focus:outline-none focus:ring-1 focus:ring-indigo-500">
<option value="15">15 minuten</option>
<option value="30" selected>30 minuten</option>
<option value="45">45 minuten</option>
<option value="60">60 minuten (1 uur)</option>
<option value="90">90 minuten</option>
</select>
</div>
<div>
<label class="block text-xs font-semibold text-slate-700 dark:text-slate-300 mb-1">Prioriteit</label>
<select id="planMailPriority" class="w-full bg-slate-50 dark:bg-[#0c0f17] border border-slate-300 dark:border-slate-700 rounded-xl px-3 py-2 text-xs text-slate-900 dark:text-white focus:outline-none focus:ring-1 focus:ring-indigo-500">
<option value="P1">P1 - Urgent / Brandend</option>
<option value="P2" selected>P2 - Belangrijk</option>
<option value="P3">P3 - Regulier</option>
</select>
</div>
</div>
<div class="flex items-center justify-end space-x-2 pt-3 border-t border-slate-100 dark:border-slate-800">
<button type="button" onclick="closePlanMailModal()" class="px-4 py-2 rounded-xl text-xs font-semibold text-slate-600 dark:text-slate-400 hover:bg-slate-100 dark:hover:bg-slate-800 transition cursor-pointer">
Annuleren
</button>
<button type="submit" id="planMailSubmitBtn" class="flex items-center space-x-1.5 px-5 py-2 rounded-xl text-xs font-bold bg-indigo-600 hover:bg-indigo-500 text-white transition shadow-sm shadow-indigo-600/30 cursor-pointer">
<i data-lucide="check" class="w-3.5 h-3.5"></i>
<span>Inplannen</span>
</button>
</div>
</form>
</div>
</div>
<!-- MODAL 2: E-mail Beantwoorden -->
<div id="replyModal" class="fixed inset-0 z-50 bg-slate-950/60 backdrop-blur-sm hidden items-center justify-center p-4">
<div class="bg-white dark:bg-[#111624] border border-slate-200 dark:border-slate-800 rounded-3xl max-w-lg w-full p-6 sm:p-7 shadow-2xl space-y-5 animate-in fade-in zoom-in-95 duration-150">
<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.5">
<div class="w-9 h-9 rounded-xl bg-sky-500/10 text-sky-600 dark:text-sky-400 flex items-center justify-center">
<i data-lucide="reply" class="w-5 h-5"></i>
</div>
<div>
<h3 class="text-sm font-bold text-slate-900 dark:text-white">Beantwoord Bericht</h3>
<p class="text-[11px] text-slate-400" id="replyToLabel"></p>
</div>
</div>
<button onclick="closeReplyModal()" class="text-slate-400 hover:text-slate-600 p-1.5 rounded-lg transition cursor-pointer">
<i data-lucide="x" class="w-4 h-4"></i>
</button>
</div>
<form onsubmit="submitReply(event)" class="space-y-4">
<input type="hidden" id="replyMailId" value="">
<div class="flex items-center space-x-3 text-xs">
<label class="flex items-center space-x-2 cursor-pointer text-slate-700 dark:text-slate-300">
<input type="checkbox" id="replyAllCheckbox" class="w-4 h-4 rounded text-sky-600 focus:ring-sky-500 border-slate-300 dark:border-slate-700">
<span>Allen beantwoorden (Reply All)</span>
</label>
</div>
<div>
<label class="block text-xs font-semibold text-slate-700 dark:text-slate-300 mb-1">Jouw Reactie</label>
<textarea id="replyContent" rows="6" required placeholder="Typ hier jouw bericht..." class="w-full bg-slate-50 dark:bg-[#0c0f17] border border-slate-300 dark:border-slate-700 rounded-xl p-3 text-xs text-slate-900 dark:text-white placeholder-slate-400 focus:outline-none focus:ring-1 focus:ring-sky-500"></textarea>
</div>
<div class="flex items-center justify-end space-x-2 pt-3 border-t border-slate-100 dark:border-slate-800">
<button type="button" onclick="closeReplyModal()" class="px-4 py-2 rounded-xl text-xs font-semibold text-slate-600 dark:text-slate-400 hover:bg-slate-100 dark:hover:bg-slate-800 transition cursor-pointer">
Annuleren
</button>
<button type="submit" id="replySubmitBtn" class="flex items-center space-x-1.5 px-5 py-2 rounded-xl text-xs font-bold bg-sky-600 hover:bg-sky-500 text-white transition shadow-sm shadow-sky-600/30 cursor-pointer">
<i data-lucide="send" class="w-3.5 h-3.5"></i>
<span>Versturen via Outlook</span>
</button>
</div>
</form>
</div>
</div>
<!-- MODAL 3: E-mail Doorsturen -->
<div id="forwardModal" class="fixed inset-0 z-50 bg-slate-950/60 backdrop-blur-sm hidden items-center justify-center p-4">
<div class="bg-white dark:bg-[#111624] border border-slate-200 dark:border-slate-800 rounded-3xl max-w-lg w-full p-6 sm:p-7 shadow-2xl space-y-5 animate-in fade-in zoom-in-95 duration-150">
<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.5">
<div class="w-9 h-9 rounded-xl bg-violet-500/10 text-violet-600 dark:text-violet-400 flex items-center justify-center">
<i data-lucide="corner-up-right" class="w-5 h-5"></i>
</div>
<div>
<h3 class="text-sm font-bold text-slate-900 dark:text-white">E-mail Doorsturen</h3>
<p class="text-[11px] text-slate-400" id="forwardSubjectLabel"></p>
</div>
</div>
<button onclick="closeForwardModal()" class="text-slate-400 hover:text-slate-600 p-1.5 rounded-lg transition cursor-pointer">
<i data-lucide="x" class="w-4 h-4"></i>
</button>
</div>
<form onsubmit="submitForward(event)" class="space-y-4">
<input type="hidden" id="forwardMailId" value="">
<div>
<label class="block text-xs font-semibold text-slate-700 dark:text-slate-300 mb-1">Ontvanger(s) e-mailadres</label>
<input type="text" id="forwardTo" required placeholder="collega@fociz.nl (meerdere scheiden met komma)" class="w-full bg-slate-50 dark:bg-[#0c0f17] border border-slate-300 dark:border-slate-700 rounded-xl px-3.5 py-2 text-xs text-slate-900 dark:text-white placeholder-slate-400 focus:outline-none focus:ring-1 focus:ring-violet-500">
</div>
<div>
<label class="block text-xs font-semibold text-slate-700 dark:text-slate-300 mb-1">Toelichting (optioneel)</label>
<textarea id="forwardComment" rows="4" placeholder="Eventuele toelichting voor de ontvanger..." class="w-full bg-slate-50 dark:bg-[#0c0f17] border border-slate-300 dark:border-slate-700 rounded-xl p-3 text-xs text-slate-900 dark:text-white placeholder-slate-400 focus:outline-none focus:ring-1 focus:ring-violet-500"></textarea>
</div>
<div class="flex items-center justify-end space-x-2 pt-3 border-t border-slate-100 dark:border-slate-800">
<button type="button" onclick="closeForwardModal()" class="px-4 py-2 rounded-xl text-xs font-semibold text-slate-600 dark:text-slate-400 hover:bg-slate-100 dark:hover:bg-slate-800 transition cursor-pointer">
Annuleren
</button>
<button type="submit" id="forwardSubmitBtn" class="flex items-center space-x-1.5 px-5 py-2 rounded-xl text-xs font-bold bg-violet-600 hover:bg-violet-500 text-white transition shadow-sm shadow-violet-600/30 cursor-pointer">
<i data-lucide="send" class="w-3.5 h-3.5"></i>
<span>Doorsturen via Outlook</span>
</button>
</div>
</form>
</div>
</div>
<script>
let allMessages = <?= $jsonMessages ?>;
let activeFilter = 'all'; // 'all' | 'unread'
let selectedMessage = null;
function setMailFilter(filter) {
activeFilter = filter;
const btnAll = document.getElementById('filterBtn_all');
const btnUnread = document.getElementById('filterBtn_unread');
if (filter === 'all') {
btnAll.className = "flex-1 sm:flex-none px-3.5 py-1.5 rounded-lg bg-white dark:bg-slate-900 text-slate-900 dark:text-white shadow-xs transition";
btnUnread.className = "flex-1 sm:flex-none px-3.5 py-1.5 rounded-lg text-slate-600 dark:text-slate-400 hover:text-slate-900 dark:hover:text-white transition";
} else {
btnUnread.className = "flex-1 sm:flex-none px-3.5 py-1.5 rounded-lg bg-white dark:bg-slate-900 text-slate-900 dark:text-white shadow-xs transition";
btnAll.className = "flex-1 sm:flex-none px-3.5 py-1.5 rounded-lg text-slate-600 dark:text-slate-400 hover:text-slate-900 dark:hover:text-white transition";
}
applyMailSearch();
}
function applyMailSearch() {
const q = (document.getElementById('mailSearchInput')?.value || '').toLowerCase().trim();
let filtered = allMessages;
if (activeFilter === 'unread') {
filtered = filtered.filter(m => !m.is_read);
}
if (q) {
filtered = filtered.filter(m => {
const sub = (m.subject || '').toLowerCase();
const from = (m.from_name || '').toLowerCase() + ' ' + (m.from_email || '').toLowerCase();
const prev = (m.body_preview || '').toLowerCase();
return sub.includes(q) || from.includes(q) || prev.includes(q);
});
}
renderMailList(filtered);
}
function renderMailList(list) {
const container = document.getElementById('mailListContainer');
const countEl = document.getElementById('visibleMailCount');
if (countEl) countEl.innerText = list.length;
if (!list || list.length === 0) {
container.innerHTML = `
<div class="text-center py-12 text-slate-400 text-xs">
<i data-lucide="inbox" class="w-8 h-8 text-slate-300 dark:text-slate-700 mx-auto mb-2"></i>
<p>Geen berichten gevonden.</p>
</div>
`;
if (typeof lucide !== 'undefined') lucide.createIcons();
return;
}
let html = '';
list.forEach(m => {
const isSelected = selectedMessage && selectedMessage.id === m.id;
const isUnread = !m.is_read;
const initials = (m.from_name || 'NL').split(' ').map(n => n[0]).join('').substring(0, 2).toUpperCase();
let cardClass = isSelected
? 'bg-sky-50 dark:bg-sky-950/40 border-sky-400 dark:border-sky-700 shadow-xs'
: (isUnread ? 'bg-white dark:bg-[#111624] border-slate-200 dark:border-slate-800 hover:border-slate-300 font-semibold' : 'bg-slate-50/60 dark:bg-[#0c0f17]/60 border-slate-100 dark:border-slate-800/60 text-slate-600 dark:text-slate-400');
html += `
<div onclick="selectMail('${m.id}')" class="group relative p-3 rounded-2xl border transition cursor-pointer ${cardClass}">
<div class="flex items-start justify-between gap-2">
<div class="flex items-center space-x-2 truncate">
<div class="w-6 h-6 rounded-lg ${isUnread ? 'bg-sky-600 text-white' : 'bg-slate-200 dark:bg-slate-800 text-slate-600 dark:text-slate-400'} text-[10px] font-bold flex items-center justify-center flex-shrink-0">
${initials}
</div>
<span class="text-xs ${isUnread ? 'font-black text-slate-900 dark:text-white' : 'font-medium text-slate-700 dark:text-slate-300'} truncate">
${escapeHtml(m.from_name)}
</span>
</div>
<div class="flex items-center space-x-1.5 flex-shrink-0">
${isUnread ? '<span class="w-2 h-2 rounded-full bg-sky-500"></span>' : ''}
${m.has_attachments ? '<i data-lucide="paperclip" class="w-3 h-3 text-slate-400"></i>' : ''}
<span class="text-[10px] text-slate-400 font-medium">${escapeHtml(m.received_human || '')}</span>
</div>
</div>
<div class="mt-1 text-xs ${isUnread ? 'font-bold text-slate-900 dark:text-white' : 'text-slate-800 dark:text-slate-200'} truncate">
${escapeHtml(m.subject)}
</div>
<div class="mt-0.5 text-[11px] text-slate-400 dark:text-slate-500 line-clamp-1 leading-snug">
${escapeHtml(m.body_preview || '')}
</div>
</div>
`;
});
container.innerHTML = html;
if (typeof lucide !== 'undefined') lucide.createIcons();
}
async function selectMail(id) {
const found = allMessages.find(m => m.id === id);
if (!found) return;
selectedMessage = found;
renderMailList(allMessages); // Update actieve styling
// Toon detail container
document.getElementById('noMailSelectedState').classList.add('hidden');
const activeCont = document.getElementById('mailActiveContainer');
activeCont.classList.remove('hidden');
// Velden invullen met preview
document.getElementById('detailSubject').innerText = found.subject;
document.getElementById('detailSenderName').innerText = found.from_name;
document.getElementById('detailSenderEmail').innerText = found.from_email;
document.getElementById('detailReceivedHuman').innerText = found.received_human;
document.getElementById('detailReceivedExact').innerText = found.received_at;
document.getElementById('detailWebLink').href = found.web_link || 'https://outlook.office.com/mail/';
const initials = (found.from_name || 'NL').split(' ').map(n => n[0]).join('').substring(0, 2).toUpperCase();
document.getElementById('detailSenderAvatar').innerText = initials;
// Body laden
const bodyEl = document.getElementById('detailBody');
bodyEl.innerHTML = `<div class="p-8 text-center text-slate-400"><i data-lucide="loader-2" class="w-5 h-5 animate-spin mx-auto mb-2"></i><span>Bericht laden...</span></div>`;
if (typeof lucide !== 'undefined') lucide.createIcons();
try {
const res = await fetch(`/api/mail.php?action=get_message&id=${encodeURIComponent(id)}`);
const data = await res.json();
if (data.success && data.message) {
bodyEl.innerHTML = data.message.body_html || `<pre class="font-sans whitespace-pre-wrap">${escapeHtml(data.message.body_text || found.body_preview)}</pre>`;
found.is_read = true;
updateUnreadCount();
renderMailList(allMessages);
} else {
bodyEl.innerHTML = `<div class="p-4 rounded-xl bg-red-50 text-red-700 text-xs">${data.error || 'Kon bericht niet inladen.'}</div>`;
}
} catch(err) {
bodyEl.innerHTML = `<p class="whitespace-pre-wrap">${escapeHtml(found.body_preview)}</p>`;
}
}
function updateUnreadCount() {
const unread = allMessages.filter(m => !m.is_read).length;
const el = document.getElementById('countUnread');
if (el) el.innerText = unread;
}
function closeMobileDetail() {
document.getElementById('mailActiveContainer').classList.add('hidden');
document.getElementById('noMailSelectedState').classList.remove('hidden');
}
// Modal open/close helpers
function openPlanMailModal() {
if (!selectedMessage) return;
document.getElementById('planMailId').value = selectedMessage.id;
document.getElementById('planMailSubject').value = selectedMessage.subject;
document.getElementById('planMailWebLink').value = selectedMessage.web_link || '';
document.getElementById('planMailSender').value = selectedMessage.from_name || '';
const modal = document.getElementById('planMailModal');
modal.classList.remove('hidden');
modal.classList.add('flex');
}
function closePlanMailModal() {
const modal = document.getElementById('planMailModal');
modal.classList.add('hidden');
modal.classList.remove('flex');
}
async function submitPlanEmail(e) {
e.preventDefault();
const btn = document.getElementById('planMailSubmitBtn');
const origHtml = btn.innerHTML;
btn.innerHTML = `<i data-lucide="loader-2" class="w-3.5 h-3.5 animate-spin"></i><span>Inplannen...</span>`;
btn.disabled = true;
try {
const res = await fetch('/api/mail.php?action=plan_email', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({
message_id: document.getElementById('planMailId').value,
subject: document.getElementById('planMailSubject').value,
plan_date: document.getElementById('planMailDate').value,
time_block_start: document.getElementById('planMailStartTime').value,
duration_minutes: parseInt(document.getElementById('planMailDuration').value, 10),
priority: document.getElementById('planMailPriority').value,
web_link: document.getElementById('planMailWebLink').value,
sender: document.getElementById('planMailSender').value
})
});
const data = await res.json();
if (data.success) {
closePlanMailModal();
alert(`Succes! Het e-mailbericht is als taak ingepland voor ${data.plan_date}.`);
} else {
alert(data.error || 'Kon e-mail niet inplannen');
}
} catch(err) {
alert('Netwerkfout: ' + err.message);
} finally {
btn.innerHTML = origHtml;
btn.disabled = false;
if (typeof lucide !== 'undefined') lucide.createIcons();
}
}
function openReplyModal() {
if (!selectedMessage) return;
document.getElementById('replyMailId').value = selectedMessage.id;
document.getElementById('replyToLabel').innerText = `Aan: ${selectedMessage.from_name} (${selectedMessage.from_email})`;
document.getElementById('replyContent').value = '';
const modal = document.getElementById('replyModal');
modal.classList.remove('hidden');
modal.classList.add('flex');
}
function closeReplyModal() {
const modal = document.getElementById('replyModal');
modal.classList.add('hidden');
modal.classList.remove('flex');
}
async function submitReply(e) {
e.preventDefault();
const btn = document.getElementById('replySubmitBtn');
const origHtml = btn.innerHTML;
btn.innerHTML = `<i data-lucide="loader-2" class="w-3.5 h-3.5 animate-spin"></i><span>Versturen...</span>`;
btn.disabled = true;
try {
const res = await fetch('/api/mail.php?action=reply', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({
id: document.getElementById('replyMailId').value,
comment: document.getElementById('replyContent').value,
reply_all: document.getElementById('replyAllCheckbox').checked
})
});
const data = await res.json();
if (data.success) {
closeReplyModal();
alert('Antwoord is succesvol verstuurd via Outlook!');
} else {
alert(data.error || 'Kon reactie niet versturen.');
}
} catch(err) {
alert('Netwerkfout: ' + err.message);
} finally {
btn.innerHTML = origHtml;
btn.disabled = false;
if (typeof lucide !== 'undefined') lucide.createIcons();
}
}
function openForwardModal() {
if (!selectedMessage) return;
document.getElementById('forwardMailId').value = selectedMessage.id;
document.getElementById('forwardSubjectLabel').innerText = `Fwd: ${selectedMessage.subject}`;
document.getElementById('forwardTo').value = '';
document.getElementById('forwardComment').value = '';
const modal = document.getElementById('forwardModal');
modal.classList.remove('hidden');
modal.classList.add('flex');
}
function closeForwardModal() {
const modal = document.getElementById('forwardModal');
modal.classList.add('hidden');
modal.classList.remove('flex');
}
async function submitForward(e) {
e.preventDefault();
const btn = document.getElementById('forwardSubmitBtn');
const origHtml = btn.innerHTML;
btn.innerHTML = `<i data-lucide="loader-2" class="w-3.5 h-3.5 animate-spin"></i><span>Doorsturen...</span>`;
btn.disabled = true;
try {
const res = await fetch('/api/mail.php?action=forward', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({
id: document.getElementById('forwardMailId').value,
to: document.getElementById('forwardTo').value,
comment: document.getElementById('forwardComment').value
})
});
const data = await res.json();
if (data.success) {
closeForwardModal();
alert('E-mail is succesvol doorgestuurd via Outlook!');
} else {
alert(data.error || 'Kon e-mail niet doorsturen.');
}
} catch(err) {
alert('Netwerkfout: ' + err.message);
} finally {
btn.innerHTML = origHtml;
btn.disabled = false;
if (typeof lucide !== 'undefined') lucide.createIcons();
}
}
async function refreshMailbox() {
const icon = document.getElementById('refreshMailIcon');
if (icon) icon.classList.add('animate-spin');
try {
const res = await fetch('/api/mail.php?action=get_inbox');
const data = await res.json();
if (data.success && data.messages) {
allMessages = data.messages;
applyMailSearch();
updateUnreadCount();
}
} catch(err) {
console.warn('Mail refresh error:', err);
} finally {
if (icon) icon.classList.remove('animate-spin');
}
}
function escapeHtml(str) {
if (!str) return '';
return String(str)
.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&#039;');
}
document.addEventListener('DOMContentLoaded', () => {
applyMailSearch();
if (allMessages && allMessages.length > 0) {
selectMail(allMessages[0].id);
}
// Luister naar live websocket/polling event voor nieuwe inkomende e-mails
window.addEventListener('fociz:mail-updated', () => {
refreshMailbox();
});
});
</script>
<?php require_once __DIR__ . '/includes/footer.php'; ?>