Files
Fociz/mail.php
T
2026-09-12 10:25:49 +02:00

1111 lines
58 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?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 (met 45s cache, tenzij ?refresh=1)
$forceRefresh = !empty($_GET['refresh']) && $_GET['refresh'] === '1';
$inboxData = microsoft_fetch_inbox_messages($userId, 30, false, $forceRefresh);
$messages = $inboxData['messages'] ?? [];
$isDemo = !empty($inboxData['is_demo']);
$unreadCount = (int)($inboxData['unread_count'] ?? 0);
$warning = $inboxData['warning'] ?? '';
$needsReconsent = !empty($inboxData['needs_reconsent']);
// Controleer status
$stmtInt = $db->prepare("SELECT * FROM integrations WHERE user_id = ? AND provider = 'OUTLOOK' AND is_enabled = 1");
$stmtInt->execute([$userId]);
$outlookInt = $stmtInt->fetch();
$isOutlookConfigured = !empty($outlookInt['access_token']) && empty($outlookInt['is_demo_mode']);
$isLiveMail = $isOutlookConfigured && !$isDemo;
$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">
<?php if (!empty($_GET['success'])): ?>
<div class="p-4 rounded-2xl bg-emerald-50 dark:bg-emerald-950/40 border border-emerald-200 dark:border-emerald-800/60 flex items-center space-x-2.5 text-xs text-emerald-800 dark:text-emerald-200 shadow-2xs">
<i data-lucide="check-circle-2" class="w-4 h-4 text-emerald-600 dark:text-emerald-400 flex-shrink-0"></i>
<span><?= htmlspecialchars($_GET['success']) ?></span>
</div>
<?php elseif (!empty($_GET['error'])): ?>
<div class="p-4 rounded-2xl bg-rose-50 dark:bg-rose-950/40 border border-rose-200 dark:border-rose-800/60 flex items-center space-x-2.5 text-xs text-rose-800 dark:text-rose-200 shadow-2xs">
<i data-lucide="alert-circle" class="w-4 h-4 text-rose-600 dark:text-rose-400 flex-shrink-0"></i>
<span><?= htmlspecialchars($_GET['error']) ?></span>
</div>
<?php endif; ?>
<!-- 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 ($isLiveMail): ?>
<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 elseif ($isOutlookConfigured && $isDemo): ?>
<span class="px-2.5 py-0.5 rounded-full text-[10px] font-extrabold bg-amber-50 text-amber-700 dark:bg-amber-500/15 dark:text-amber-300 ring-1 ring-amber-500/30">E-MAIL MACHTIGING VEREIST</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 (!$isLiveMail): ?>
<a href="/auth/oauth_microsoft.php?reconsent=1&return_to=/mail.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><?= $isOutlookConfigured ? 'Machtigingen Bijwerken' : '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 ($isOutlookConfigured && $isDemo): ?>
<div class="mt-5 p-4 rounded-2xl bg-amber-50/90 dark:bg-amber-950/40 border border-amber-300 dark:border-amber-700/60 flex flex-col sm:flex-row sm:items-center justify-between gap-4 text-xs text-amber-900 dark:text-amber-200 shadow-2xs">
<div class="flex items-start sm:items-center space-x-3">
<div class="w-8 h-8 rounded-xl bg-amber-500/20 text-amber-600 dark:text-amber-400 flex items-center justify-center flex-shrink-0 font-bold">
<i data-lucide="shield-alert" class="w-4 h-4"></i>
</div>
<div>
<span class="font-bold block text-amber-950 dark:text-amber-100">E-mail permissie ontbreekt (Herautorisatie vereist)</span>
<span class="text-[11px] text-amber-800 dark:text-amber-300">
Je Microsoft account is gekoppeld voor je agenda, maar het huidige token heeft nog geen toestemming voor e-mail (<code class="font-mono bg-amber-100 dark:bg-amber-900/60 px-1 py-0.5 rounded text-[10px]">Mail.ReadWrite</code>). Klik op <strong>Machtigingen Bijwerken</strong> om eenmalig toestemming te verlenen.
</span>
</div>
</div>
<a href="/auth/oauth_microsoft.php?reconsent=1&return_to=/mail.php" class="px-4 py-2 rounded-xl bg-amber-600 hover:bg-amber-500 text-white font-bold text-xs shadow-xs transition whitespace-nowrap flex-shrink-0 text-center cursor-pointer">
Machtigingen Bijwerken &rarr;
</a>
</div>
<?php elseif (!$isOutlookConfigured): ?>
<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 om al je live zakelijke e-mails in te laden.</span>
</div>
<a href="/auth/oauth_microsoft.php?return_to=/mail.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.5 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.5 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" onclick="if(event.target === this) closePlanMailModal()" class="fixed inset-0 z-50 bg-slate-950/60 backdrop-blur-sm hidden items-center justify-center p-4 overflow-y-auto">
<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 dark:hover:text-slate-200 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>
<!-- Datum & Slimme "Plan in open blok" knop -->
<div class="space-y-2 pt-1 border-t border-slate-100 dark:border-slate-800">
<div class="flex items-center justify-between">
<span class="text-xs font-bold text-slate-700 dark:text-slate-300">Datum & Tijdblok</span>
<button type="button" onclick="planInNextOpenBlockMail()" id="btnMailPlanOpenBlock" class="inline-flex items-center space-x-1.5 px-3 py-1.5 rounded-xl bg-gradient-to-r from-indigo-600 to-sky-600 hover:from-indigo-500 hover:to-sky-500 text-white text-xs font-bold transition shadow-xs cursor-pointer active:scale-95" title="Vind automatisch het eerstvolgende vrije kwartierblok binnen werktijden">
<i data-lucide="sparkles" class="w-3.5 h-3.5"></i>
<span>Plan in open blok</span>
</button>
</div>
<!-- Open blok banner -->
<div id="mailPlanOpenBlockNotice" class="hidden p-2.5 rounded-xl bg-emerald-50 dark:bg-emerald-500/10 border border-emerald-200 dark:border-emerald-500/20 text-emerald-800 dark:text-emerald-300 text-[11px] flex items-center justify-between">
<div class="flex items-center space-x-1.5 min-w-0 flex-1 mr-2">
<i data-lucide="check-circle" class="w-3.5 h-3.5 text-emerald-600 dark:text-emerald-400 flex-shrink-0"></i>
<span id="mailPlanOpenBlockNoticeText" class="truncate font-semibold"></span>
</div>
<button type="button" onclick="document.getElementById('mailPlanOpenBlockNotice').classList.add('hidden')" class="p-0.5 text-emerald-600 dark:text-emerald-400 hover:text-emerald-800 rounded cursor-pointer">
<i data-lucide="x" class="w-3 h-3"></i>
</button>
</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">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>
<div class="flex items-center justify-between mb-1">
<label class="block text-xs font-semibold text-slate-700 dark:text-slate-300">Starttijd (optioneel)</label>
<span id="planMailEndTimeBadge" class="text-[10px] text-indigo-600 dark:text-indigo-400 font-mono font-semibold"></span>
</div>
<input type="text" id="planMailStartTime" oninput="updatePlanMailEndTimeBadge()" 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" onchange="updatePlanMailEndTimeBadge()" 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 cursor-pointer">
<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 cursor-pointer">
<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: Waarschuwing Buiten Werktijden (Mail) -->
<div id="outsideWorkhoursModalMail" onclick="if(event.target === this) closeOutsideWorkhoursModalMail('CANCEL')" class="fixed inset-0 z-50 bg-slate-950/70 backdrop-blur-sm hidden items-center justify-center p-4">
<div class="bg-white dark:bg-[#111624] border border-amber-500/30 rounded-2xl w-full max-w-md shadow-2xl p-6 space-y-4">
<div class="flex items-start space-x-3">
<div class="w-10 h-10 rounded-2xl bg-amber-500/10 border border-amber-500/20 text-amber-500 flex items-center justify-center flex-shrink-0">
<i data-lucide="alert-triangle" class="w-5 h-5"></i>
</div>
<div class="min-w-0 flex-1">
<h3 class="text-base font-bold text-slate-900 dark:text-white leading-tight">
Let op! Je plant buiten werktijden
</h3>
<p class="text-xs text-slate-500 dark:text-slate-400 mt-1">
Het ingeplande tijdblok valt buiten de gezette werktijden.
</p>
</div>
<button type="button" onclick="closeOutsideWorkhoursModalMail('CANCEL')" class="p-1 text-slate-400 hover:text-slate-600 dark:hover:text-slate-200 rounded-lg transition cursor-pointer flex-shrink-0">
<i data-lucide="x" class="w-4 h-4"></i>
</button>
</div>
<div class="bg-amber-50 dark:bg-amber-950/20 border border-amber-200 dark:border-amber-800/40 rounded-xl p-3.5 text-xs text-amber-800 dark:text-amber-300 space-y-1.5">
<div class="flex items-center justify-between text-[11px] font-semibold text-amber-900 dark:text-amber-200">
<span>Gekozen tijdblok:</span>
<span id="outsideWorkhoursSelectedSlotMail" class="font-mono font-bold"></span>
</div>
<div class="flex items-center justify-between text-[11px] text-amber-700 dark:text-amber-400">
<span>Gezette werktijden:</span>
<span id="outsideWorkhoursBoundsMail" class="font-mono font-semibold">08:30 – 17:00</span>
</div>
<p class="text-[11px] text-amber-700/80 dark:text-amber-400/80 pt-1 border-t border-amber-200/60 dark:border-amber-800/30">
Kies <strong>Nee</strong> om het item automatisch in te plannen in het eerstvolgende open blok op de volgende werkdag, of kies <strong>Ja</strong> om toch nu buiten werktijd te plannen.
</p>
</div>
<div class="flex items-center space-x-2.5 pt-2">
<button type="button" id="btnOutsideWorkhoursNoMail" onclick="closeOutsideWorkhoursModalMail('NEE')" class="flex-1 px-4 py-2.5 rounded-xl bg-indigo-600 hover:bg-indigo-500 text-white font-bold text-xs transition shadow-xs flex items-center justify-center space-x-1.5 cursor-pointer">
<i data-lucide="calendar-arrow-up" class="w-4 h-4"></i>
<span>Nee (Volgende werkdag)</span>
</button>
<button type="button" id="btnOutsideWorkhoursYesMail" onclick="closeOutsideWorkhoursModalMail('JA')" class="px-4 py-2.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-200 font-semibold text-xs transition cursor-pointer">
<span>Ja, toch inplannen</span>
</button>
</div>
</div>
</div>
<!-- MODAL 2: E-mail Beantwoorden -->
<div id="replyModal" onclick="if(event.target === this) closeReplyModal()" 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" onclick="if(event.target === this) closeForwardModal()" 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) {
renderMailBodyContent(bodyEl, data.message.body_html, data.message.body_text || found.body_preview);
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) {
renderMailBodyContent(bodyEl, null, found.body_preview);
}
}
// Grondige HTML sanitization voor inkomende e-mails (XSS & exploit preventie)
function sanitizeEmailHtml(html) {
if (!html) return '';
// 1. Verwijder alle script tags en inhoud
let cleaned = html.replace(/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi, '');
// 2. Verwijder potentieel gevaarlijke tags
cleaned = cleaned.replace(/<\/?(object|embed|applet|meta|base|form|input|button|textarea|select)\b[^>]*>/gi, '');
// 3. Verwijder inline JavaScript event handlers (onload, onerror, onclick, onmouseover, etc.)
cleaned = cleaned.replace(/\s+on[a-z0-9_-]+\s*=\s*(?:'[^']*'|"[^"]*"|[^\s>]+)/gi, '');
// 4. Neutraliseer javascript: en data:text/html pseudo-protocollen in links en afbeeldingen
cleaned = cleaned.replace(/(href|src)\s*=\s*['"]\s*(?:javascript|data:text\/html):[^'"]*['"]/gi, '$1="#"');
return cleaned;
}
// Veilig renderen van e-mail HTML via een geïsoleerde sandboxed iframe
function renderMailBodyContent(container, htmlContent, plainContent) {
container.innerHTML = '';
if (!htmlContent) {
const pre = document.createElement('pre');
pre.className = 'font-sans whitespace-pre-wrap text-sm text-slate-800 dark:text-slate-200 leading-relaxed p-2';
pre.textContent = plainContent || '(Geen inhoud)';
container.appendChild(pre);
return;
}
const safeHtml = sanitizeEmailHtml(htmlContent);
const isDark = document.documentElement.classList.contains('dark');
// Sandboxed iframe: strikte isolatie (geen allow-scripts)
const iframe = document.createElement('iframe');
iframe.id = 'mailBodyIframe';
iframe.setAttribute('sandbox', 'allow-same-origin allow-popups');
iframe.className = 'w-full border-0 rounded-xl transition-all';
iframe.style.width = '100%';
iframe.style.minHeight = '350px';
iframe.style.border = 'none';
iframe.style.background = isDark ? '#0c0f17' : '#ffffff';
iframe.style.display = 'block';
container.appendChild(iframe);
const docHtml = `<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<base target="_blank">
<style>
*, *::before, *::after {
box-sizing: border-box;
}
html {
color-scheme: ${isDark ? 'dark' : 'light'};
}
body {
margin: 0;
padding: 14px;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
font-size: 14px;
line-height: 1.6;
color: ${isDark ? '#f1f5f9' : '#0f172a'};
background-color: ${isDark ? '#0c0f17' : '#ffffff'};
word-break: break-word;
}
img {
max-width: 100% !important;
height: auto !important;
}
table {
max-width: 100% !important;
}
pre, code {
white-space: pre-wrap;
word-break: break-all;
}
a {
color: #4f46e5;
}
</style>
</head>
<body>
${safeHtml}
</body>
</html>`;
iframe.srcdoc = docHtml;
iframe.onload = () => {
try {
const innerDoc = iframe.contentDocument || iframe.contentWindow.document;
if (innerDoc) {
const updateHeight = () => {
const scrollH = innerDoc.documentElement.scrollHeight || innerDoc.body.scrollHeight;
iframe.style.height = Math.max(scrollH + 35, 280) + 'px';
};
updateHeight();
if (window.ResizeObserver) {
const observer = new ResizeObserver(updateHeight);
observer.observe(innerDoc.body);
}
}
} catch (e) {
iframe.style.height = '600px';
}
};
}
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 & Open blok inplannen
let _outsideWorkhoursPendingMail = null;
function updatePlanMailEndTimeBadge() {
const startInput = document.getElementById('planMailStartTime');
const durationInput = document.getElementById('planMailDuration');
const badge = document.getElementById('planMailEndTimeBadge');
if (!startInput || !durationInput || !badge) return;
const startVal = (startInput.value || '').trim();
const duration = parseInt(durationInput.value || '30', 10);
if (!startVal || !startVal.includes(':') || isNaN(duration)) {
badge.innerText = '';
return;
}
const [hStr, mStr] = startVal.split(':');
const totalMinutes = (parseInt(hStr, 10) * 60 + parseInt(mStr, 10)) + duration;
const endH = Math.floor((totalMinutes / 60) % 24);
const endM = totalMinutes % 60;
const endStr = `${String(endH).padStart(2, '0')}:${String(endM).padStart(2, '0')}`;
badge.innerText = `(tot ${endStr})`;
}
async function planInNextOpenBlockMail(startFromNextWorkday = false) {
const durationInput = document.getElementById('planMailDuration');
const duration = parseInt(durationInput?.value || '30', 10);
const btn = document.getElementById('btnMailPlanOpenBlock');
const origHtml = btn ? btn.innerHTML : '';
if (btn) {
btn.disabled = true;
btn.innerHTML = `<i data-lucide="loader-2" class="w-3.5 h-3.5 animate-spin"></i><span>Zoeken...</span>`;
if (typeof lucide !== 'undefined') lucide.createIcons();
}
try {
let url = `/api/planner.php?action=find_next_free_slot&duration=${duration}`;
if (startFromNextWorkday) {
url += '&start_from_next_workday=1';
}
const resp = await fetch(url);
const data = await resp.json();
if (data.success && data.slot) {
const slot = data.slot;
const dateInput = document.getElementById('planMailDate');
const startInput = document.getElementById('planMailStartTime');
if (dateInput) dateInput.value = slot.date;
if (startInput) startInput.value = slot.start;
updatePlanMailEndTimeBadge();
const notice = document.getElementById('mailPlanOpenBlockNotice');
const noticeText = document.getElementById('mailPlanOpenBlockNoticeText');
if (notice && noticeText) {
const formattedDate = new Date(slot.date + 'T00:00:00').toLocaleDateString('nl-NL', { weekday: 'short', day: 'numeric', month: 'short' });
noticeText.innerText = `Vrij blok gevonden: ${formattedDate} om ${slot.start} – ${slot.end} (${duration} min)`;
notice.classList.remove('hidden');
}
} else {
alert(data.error || 'Geen beschikbaar tijdblok gevonden binnen de komende 14 dagen.');
}
} catch (err) {
alert('Netwerkfout bij zoeken van open blok: ' + err.message);
} finally {
if (btn) {
btn.disabled = false;
btn.innerHTML = origHtml;
if (typeof lucide !== 'undefined') lucide.createIcons();
}
}
}
function closeOutsideWorkhoursModalMail(choice) {
const modal = document.getElementById('outsideWorkhoursModalMail');
if (modal) modal.classList.add('hidden');
if (choice === 'JA') {
if (_outsideWorkhoursPendingMail) {
_outsideWorkhoursPendingMail.bypassWorkhours = true;
submitPlanEmail(null, _outsideWorkhoursPendingMail);
}
} else if (choice === 'NEE') {
_outsideWorkhoursPendingMail = null;
planInNextOpenBlockMail(true);
} else {
_outsideWorkhoursPendingMail = null;
}
}
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 notice = document.getElementById('mailPlanOpenBlockNotice');
if (notice) notice.classList.add('hidden');
updatePlanMailEndTimeBadge();
const modal = document.getElementById('planMailModal');
modal.classList.remove('hidden');
modal.classList.add('flex');
if (typeof lucide !== 'undefined') lucide.createIcons();
}
function closePlanMailModal() {
const modal = document.getElementById('planMailModal');
modal.classList.add('hidden');
modal.classList.remove('flex');
}
async function submitPlanEmail(e, options = {}) {
if (e) e.preventDefault();
const messageId = document.getElementById('planMailId').value;
const subject = document.getElementById('planMailSubject').value;
const planDate = document.getElementById('planMailDate').value;
const timeBlockStart = (document.getElementById('planMailStartTime')?.value || '').trim();
const durationMinutes = parseInt(document.getElementById('planMailDuration').value, 10);
const priority = document.getElementById('planMailPriority').value;
const webLink = document.getElementById('planMailWebLink').value;
const sender = document.getElementById('planMailSender').value;
// Controleer of de geplande tijd buiten de gezette werktijden valt (standaard 08:30 - 17:00)
if (!options.bypassWorkhours && timeBlockStart && timeBlockStart.includes(':')) {
const [shStr, smStr] = timeBlockStart.split(':');
const startMin = parseInt(shStr, 10) * 60 + parseInt(smStr, 10);
const endMin = startMin + durationMinutes;
const workStartMin = 8 * 60 + 30; // 08:30
const workEndMin = 17 * 60; // 17:00
if (startMin < workStartMin || endMin > workEndMin) {
_outsideWorkhoursPendingMail = { messageId, subject, planDate, timeBlockStart, durationMinutes, priority, webLink, sender };
const endH = Math.floor((endMin / 60) % 24);
const endM = endMin % 60;
const endStr = `${String(endH).padStart(2, '0')}:${String(endM).padStart(2, '0')}`;
const slotEl = document.getElementById('outsideWorkhoursSelectedSlotMail');
if (slotEl) slotEl.innerText = `${timeBlockStart} – ${endStr} (${durationMinutes} min)`;
const outsideModal = document.getElementById('outsideWorkhoursModalMail');
if (outsideModal) {
outsideModal.classList.remove('hidden');
outsideModal.classList.add('flex');
setTimeout(() => {
const btnNo = document.getElementById('btnOutsideWorkhoursNoMail');
if (btnNo) btnNo.focus();
}, 50);
if (typeof lucide !== 'undefined') lucide.createIcons();
}
return;
}
}
const btn = document.getElementById('planMailSubmitBtn');
const origHtml = btn ? btn.innerHTML : '';
if (btn) {
btn.innerHTML = `<i data-lucide="loader-2" class="w-3.5 h-3.5 animate-spin"></i><span>Inplannen...</span>`;
btn.disabled = true;
if (typeof lucide !== 'undefined') lucide.createIcons();
}
try {
const res = await fetch('/api/mail.php?action=plan_email', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({
message_id: messageId,
subject: subject,
plan_date: planDate,
time_block_start: timeBlockStart,
duration_minutes: durationMinutes,
priority: priority,
web_link: webLink,
sender: sender
})
});
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 {
if (btn) {
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&refresh=1');
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;');
}
// Toetsenbordnavigatie voor alle modals (Escape & Enter)
document.addEventListener('keydown', function(e) {
if (e.key === 'Escape') {
const outsideModal = document.getElementById('outsideWorkhoursModalMail');
if (outsideModal && !outsideModal.classList.contains('hidden')) {
closeOutsideWorkhoursModalMail('CANCEL');
return;
}
closePlanMailModal();
closeReplyModal();
closeForwardModal();
} else if (e.key === 'Enter') {
const outsideModal = document.getElementById('outsideWorkhoursModalMail');
if (outsideModal && !outsideModal.classList.contains('hidden')) {
e.preventDefault();
closeOutsideWorkhoursModalMail('NEE');
return;
}
}
});
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'; ?>