Compare commits

...

2 Commits

Author SHA1 Message Date
NiekRabelink 65f81d620c Merge pull request 'Change mail options' (#95) from dev into main
Reviewed-on: #95
2026-09-16 10:39:19 +00:00
NiekRabelink 0b5ca54db9 Change mail options 2026-09-16 12:39:06 +02:00
3 changed files with 292 additions and 18 deletions
+16
View File
@@ -30,6 +30,22 @@ try {
exit;
}
// 0b. Uitgeschakelde mappen opslaan
if ($action === 'save_disabled_folders') {
$input = file_get_contents('php://input');
$data = json_decode($input, true);
$folders = $data['disabled_folders'] ?? ($_POST['disabled_folders'] ?? []);
if (is_string($folders)) {
$folders = json_decode($folders, true) ?: [];
}
if (!is_array($folders)) {
$folders = [];
}
$ok = microsoft_save_disabled_folders($userId, $folders);
echo json_encode(['success' => $ok, 'disabled_folders' => $folders]);
exit;
}
// 1. Inbox of specifieke map berichten ophalen
if ($action === 'get_folder_messages' || $action === 'get_inbox') {
$folderId = trim($_GET['folder_id'] ?? ($_POST['folder_id'] ?? 'inbox'));
+72 -10
View File
@@ -1180,6 +1180,51 @@ function microsoft_get_mail_demo_data(?string $folderId = 'inbox'): array {
];
}
/**
* Haalt de door de gebruiker uitgeschakelde mailmappen op
*/
function microsoft_get_disabled_folders(int $userId): array {
try {
$db = get_db_connection();
$stmt = $db->prepare("SELECT settings FROM integrations WHERE user_id = ? AND provider = 'OUTLOOK'");
$stmt->execute([$userId]);
$row = $stmt->fetch();
if (!$row || empty($row['settings'])) return [];
$settings = json_decode($row['settings'], true);
if (is_array($settings) && !empty($settings['disabled_mail_folders']) && is_array($settings['disabled_mail_folders'])) {
return array_values(array_unique(array_filter(array_map('strval', $settings['disabled_mail_folders']))));
}
return [];
} catch (Throwable $e) {
return [];
}
}
/**
* Slaat de uitgeschakelde mailmappen op voor een gebruiker
*/
function microsoft_save_disabled_folders(int $userId, array $disabledFolders): bool {
try {
$db = get_db_connection();
$stmt = $db->prepare("SELECT settings FROM integrations WHERE user_id = ? AND provider = 'OUTLOOK'");
$stmt->execute([$userId]);
$row = $stmt->fetch();
$settings = ($row && !empty($row['settings'])) ? (json_decode($row['settings'], true) ?: []) : [];
$clean = array_values(array_unique(array_filter(array_map('strval', $disabledFolders))));
$settings['disabled_mail_folders'] = $clean;
$update = $db->prepare("UPDATE integrations SET settings = ?, updated_at = NOW() WHERE user_id = ? AND provider = 'OUTLOOK'");
$ok = $update->execute([json_encode($settings, JSON_UNESCAPED_UNICODE), $userId]);
fociz_cache_delete("ms_folders_{$userId}");
return $ok;
} catch (Throwable $e) {
error_log("microsoft_save_disabled_folders error: " . $e->getMessage());
return false;
}
}
/**
* Haalt alle beschikbare mailmappen op van Microsoft Graph API (exclusief SPAM/Junk, Prullenbak/Deleted, Outbox en Concepten)
*/
@@ -1237,12 +1282,20 @@ function microsoft_fetch_mail_folders(int $userId, bool $forceRefresh = false):
'is_child' => false,
],
];
$totalUnread = array_sum(array_column($demoFolders, 'unread_count'));
$disabledFolders = microsoft_get_disabled_folders($userId);
$disabledSet = array_flip($disabledFolders);
$totalUnread = 0;
foreach ($demoFolders as $df) {
if (!isset($disabledSet[$df['id']])) {
$totalUnread += (int)($df['unread_count'] ?? 0);
}
}
return [
'success' => true,
'is_demo' => true,
'folders' => $demoFolders,
'total_unread' => $totalUnread,
'success' => true,
'is_demo' => true,
'folders' => $demoFolders,
'total_unread' => $totalUnread,
'disabled_folders' => $disabledFolders,
];
}
@@ -1427,13 +1480,22 @@ function microsoft_fetch_mail_folders(int $userId, bool $forceRefresh = false):
return strcasecmp($a['name'], $b['name']);
});
$totalUnread = array_sum(array_column($folders, 'unread_count'));
$disabledFolders = microsoft_get_disabled_folders($userId);
$disabledSet = array_flip($disabledFolders);
$totalUnread = 0;
foreach ($folders as $f) {
if (!isset($disabledSet[$f['id']])) {
$totalUnread += (int)($f['unread_count'] ?? 0);
}
}
$result = [
'success' => true,
'is_demo' => false,
'folders' => $folders,
'total_unread' => $totalUnread,
'success' => true,
'is_demo' => false,
'folders' => $folders,
'total_unread' => $totalUnread,
'disabled_folders' => $disabledFolders,
];
fociz_cache_set($cacheKey, $result, 300); // 5 minuten cache
+204 -8
View File
@@ -42,6 +42,9 @@ $outlookInt = $stmtInt->fetch();
$isOutlookConfigured = !empty($outlookInt['access_token']) && empty($outlookInt['is_demo_mode']);
$isLiveMail = $isOutlookConfigured && !$isDemo;
$disabledFolderIds = $foldersData['disabled_folders'] ?? microsoft_get_disabled_folders($userId);
$jsonDisabledFolders = json_encode(array_values($disabledFolderIds), JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_HEX_AMP);
$jsonMessages = json_encode($messages, JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_HEX_AMP);
$jsonFolders = json_encode($mailFolders, JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_HEX_AMP);
?>
@@ -170,9 +173,17 @@ $jsonFolders = json_encode($mailFolders, JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX
<!-- Linker kolom: Berichtenlijst & Mappen (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 &amp; Mappen (<span id="visibleMailCount"><?= count($messages) ?></span>)</span>
<span class="text-[10px] text-slate-400 font-normal">Sorteer: Nieuwste eerst</span>
<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 gap-2">
<div class="flex items-center space-x-2 truncate">
<span>Inbox &amp; Mappen (<span id="visibleMailCount"><?= count($messages) ?></span>)</span>
<span id="disabledFoldersIndicator" class="hidden px-2 py-0.5 rounded-full text-[10px] font-bold bg-amber-100 dark:bg-amber-950/60 text-amber-700 dark:text-amber-300 border border-amber-200 dark:border-amber-800/60"></span>
</div>
<div class="flex items-center space-x-1">
<button type="button" onclick="openManageFoldersModal()" title="Mailmappen beheren / in- of uitschakelen" class="flex items-center space-x-1 text-[11px] font-semibold text-slate-500 hover:text-sky-600 dark:text-slate-400 dark:hover:text-sky-400 px-2 py-1 rounded-lg hover:bg-slate-100 dark:hover:bg-slate-800 transition cursor-pointer">
<i data-lucide="sliders-horizontal" class="w-3.5 h-3.5"></i>
<span>Beheren</span>
</button>
</div>
</div>
<div class="flex-1 overflow-y-auto space-y-2.5 pr-1 custom-scrollbar" id="mailFoldersContainer">
@@ -551,8 +562,43 @@ $jsonFolders = json_encode($mailFolders, JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX
</div>
</div>
<!-- MODAL 4: Mailmappen Beheren (In- en uitschakelen / herstellen) -->
<div id="manageFoldersModal" onclick="if(event.target === this) closeManageFoldersModal()" 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 flex flex-col max-h-[90vh]">
<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="sliders-horizontal" class="w-5 h-5"></i>
</div>
<div>
<h3 class="text-sm font-bold text-slate-900 dark:text-white">Mailmappen Beheren</h3>
<p class="text-[11px] text-slate-400">Schakel mappen uit om ze te verbergen uit het overzicht. Dit is op ieder moment weer te herstellen.</p>
</div>
</div>
<button onclick="closeManageFoldersModal()" 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>
<div class="flex-1 overflow-y-auto space-y-2 pr-1 custom-scrollbar max-h-[50vh]" id="manageFoldersList">
<!-- Dynamisch gevuld via renderManageFoldersList() -->
</div>
<div class="flex items-center justify-between pt-3 border-t border-slate-100 dark:border-slate-800">
<button type="button" onclick="resetAllFoldersVisibility()" class="text-xs font-semibold text-sky-600 hover:text-sky-700 dark:text-sky-400 hover:underline cursor-pointer flex items-center space-x-1">
<i data-lucide="rotate-ccw" class="w-3.5 h-3.5"></i>
<span>Alles herstellen (alle mappen aan)</span>
</button>
<button type="button" onclick="closeManageFoldersModal()" class="px-4 py-2 rounded-xl text-xs font-bold bg-slate-900 dark:bg-slate-100 text-white dark:text-slate-900 hover:bg-slate-800 dark:hover:bg-white transition cursor-pointer">
Sluiten
</button>
</div>
</div>
</div>
<script>
let mailFolders = <?= $jsonFolders ?>;
let disabledFolderIds = new Set(<?= $jsonDisabledFolders ?>);
let folderMessagesMap = {
'inbox': <?= $jsonMessages ?>
};
@@ -573,8 +619,9 @@ function setMailFilter(filter) {
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 cursor-pointer";
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 cursor-pointer";
// Bij ongelezen filter: klap automatisch alle mappen met ongelezen mails open!
// Bij ongelezen filter: klap automatisch alle actieve mappen met ongelezen mails open!
mailFolders.forEach(f => {
if (disabledFolderIds.has(f.id)) return;
const msgs = folderMessagesMap[f.id];
const hasUnread = msgs ? msgs.some(m => !m.is_read) : ((f.unread_count || 0) > 0);
if (hasUnread) {
@@ -660,9 +707,15 @@ function renderFolders() {
const q = (document.getElementById('mailSearchInput')?.value || '').toLowerCase().trim();
let totalVisible = 0;
let visibleFoldersCount = 0;
let html = '';
mailFolders.forEach(folder => {
if (disabledFolderIds.has(folder.id)) {
return; // Deze map is uitgeschakeld/verborgen door de gebruiker
}
visibleFoldersCount++;
const isInbox = (folder.id === 'inbox' || folder.is_inbox);
const isExpanded = expandedFolders.has(folder.id);
const isLoading = loadingFolders.has(folder.id);
@@ -697,7 +750,7 @@ function renderFolders() {
html += `
<div class="rounded-2xl border border-slate-200/80 dark:border-slate-800/80 bg-white/70 dark:bg-slate-900/40 overflow-hidden shadow-2xs">
<!-- Folder Header Accordion Knop -->
<button type="button" onclick="toggleFolder('${folder.id}')" class="w-full flex items-center justify-between p-3 select-none transition cursor-pointer ${isInbox ? 'bg-slate-100/90 dark:bg-slate-800/80' : 'bg-slate-50/80 dark:bg-slate-800/40 hover:bg-slate-100 dark:hover:bg-slate-800/70'}">
<div onclick="toggleFolder('${folder.id}')" class="w-full flex items-center justify-between p-3 select-none transition cursor-pointer ${isInbox ? 'bg-slate-100/90 dark:bg-slate-800/80' : 'bg-slate-50/80 dark:bg-slate-800/40 hover:bg-slate-100 dark:hover:bg-slate-800/70'}">
<div class="flex items-center space-x-2.5 min-w-0 ${folder.is_child ? 'pl-3' : ''}">
<div class="w-7 h-7 rounded-xl ${isInbox ? 'bg-sky-600 text-white shadow-xs' : (folder.is_child ? 'bg-slate-100 dark:bg-slate-800 text-slate-500' : 'bg-slate-200 dark:bg-slate-700 text-slate-700 dark:text-slate-300')} flex items-center justify-center flex-shrink-0">
<i data-lucide="${isInbox ? 'inbox' : (folder.is_child ? 'corner-down-right' : 'folder')}" class="w-3.5 h-3.5"></i>
@@ -707,11 +760,18 @@ function renderFolders() {
<span class="text-[10px] text-slate-400 font-semibold">(${folderTotal})</span>
</div>
</div>
<div class="flex items-center space-x-2 flex-shrink-0 ml-2">
<div class="flex items-center space-x-1.5 flex-shrink-0 ml-2" onclick="event.stopPropagation()">
${unreadCount > 0 ? `<span class="px-2 py-0.5 rounded-full text-[10px] font-black bg-sky-100 dark:bg-sky-950/70 text-sky-700 dark:text-sky-300 ring-1 ring-sky-500/30">${unreadCount} ongelezen</span>` : ''}
<i data-lucide="chevron-down" class="w-4 h-4 text-slate-400 transition-transform duration-200 ${isExpanded ? 'rotate-180' : ''}"></i>
${!isInbox ? `
<button type="button" onclick="disableFolder('${folder.id}', event)" title="Map uitschakelen (verbergen uit overzicht)" class="p-1 rounded-lg text-slate-400 hover:text-rose-600 hover:bg-rose-50 dark:hover:bg-rose-950/40 dark:hover:text-rose-400 transition cursor-pointer">
<i data-lucide="eye-off" class="w-3.5 h-3.5"></i>
</button>
` : ''}
<button type="button" onclick="toggleFolder('${folder.id}')" title="In- of uitklappen" class="p-1 text-slate-400 hover:text-slate-600 dark:hover:text-slate-200 transition cursor-pointer">
<i data-lucide="chevron-down" class="w-4 h-4 transition-transform duration-200 ${isExpanded ? 'rotate-180' : ''}"></i>
</button>
</div>
</button>
</div>
<!-- Folder Berichten Inhoud -->
<div class="${isExpanded ? '' : 'hidden'} p-2 space-y-1.5 bg-slate-50/40 dark:bg-[#0c0f17]/30 border-t border-slate-100 dark:border-slate-800/80">
@@ -730,6 +790,18 @@ function renderFolders() {
`;
});
if (visibleFoldersCount === 0) {
html = `
<div class="p-6 text-center text-slate-400 space-y-2">
<i data-lucide="folder-x" class="w-8 h-8 mx-auto text-slate-300 dark:text-slate-600"></i>
<p class="text-xs font-semibold">Alle mappen zijn momenteel uitgeschakeld of verborgen.</p>
<button type="button" onclick="openManageFoldersModal()" class="text-xs text-sky-600 dark:text-sky-400 font-bold hover:underline cursor-pointer">
Mappen beheren &amp; herstellen &rarr;
</button>
</div>
`;
}
container.innerHTML = html;
const countEl = document.getElementById('visibleMailCount');
@@ -959,6 +1031,9 @@ function updateTotalCounters() {
let totalAll = 0;
mailFolders.forEach(f => {
if (disabledFolderIds.has(f.id)) {
return;
}
const msgs = folderMessagesMap[f.id];
if (msgs) {
totalUnread += msgs.filter(m => !m.is_read).length;
@@ -974,6 +1049,17 @@ function updateTotalCounters() {
const elAll = document.getElementById('countAll');
if (elAll) elAll.innerText = totalAll;
const indicator = document.getElementById('disabledFoldersIndicator');
if (indicator) {
const disabledCount = disabledFolderIds.size;
if (disabledCount > 0) {
indicator.innerText = `${disabledCount} verborgen`;
indicator.classList.remove('hidden');
} else {
indicator.classList.add('hidden');
}
}
}
function closeMobileDetail() {
@@ -1406,6 +1492,115 @@ function escapeHtml(str) {
.replace(/'/g, '&#039;');
}
// Mailmappen beheren & in/uitschakelen
function disableFolder(folderId, e) {
if (e) e.stopPropagation();
toggleFolderDisabledState(folderId, true);
}
function openManageFoldersModal() {
renderManageFoldersList();
const modal = document.getElementById('manageFoldersModal');
if (modal) {
modal.classList.remove('hidden');
modal.classList.add('flex');
if (typeof lucide !== 'undefined') lucide.createIcons();
}
}
function closeManageFoldersModal() {
const modal = document.getElementById('manageFoldersModal');
if (modal) {
modal.classList.add('hidden');
modal.classList.remove('flex');
}
}
function renderManageFoldersList() {
const container = document.getElementById('manageFoldersList');
if (!container) return;
let html = '';
mailFolders.forEach(f => {
const isInbox = (f.id === 'inbox' || f.is_inbox);
const isDisabled = disabledFolderIds.has(f.id);
html += `
<div class="flex items-center justify-between p-3 rounded-2xl border ${isDisabled ? 'bg-slate-50 dark:bg-slate-900/30 border-dashed border-slate-200 dark:border-slate-800 opacity-70' : 'bg-white dark:bg-slate-900/60 border-slate-200/80 dark:border-slate-800'} transition">
<div class="flex items-center space-x-3 min-w-0 ${f.is_child ? 'pl-3' : ''}">
<div class="w-8 h-8 rounded-xl ${isInbox ? 'bg-sky-600 text-white' : (isDisabled ? 'bg-slate-100 dark:bg-slate-800 text-slate-400' : 'bg-slate-100 dark:bg-slate-800 text-slate-700 dark:text-slate-300')} flex items-center justify-center flex-shrink-0">
<i data-lucide="${isInbox ? 'inbox' : (isDisabled ? 'folder-x' : (f.is_child ? 'corner-down-right' : 'folder'))}" class="w-4 h-4"></i>
</div>
<div class="truncate">
<div class="flex items-center space-x-1.5">
<span class="text-xs font-bold ${isDisabled ? 'line-through text-slate-400 dark:text-slate-500' : 'text-slate-900 dark:text-white'} truncate">${escapeHtml(f.name)}</span>
${isInbox ? '<span class="text-[10px] font-semibold text-sky-600 dark:text-sky-400 bg-sky-50 dark:bg-sky-950/60 px-1.5 py-0.5 rounded-md">Standaard Inbox</span>' : ''}
</div>
<span class="text-[10px] text-slate-400">${f.total_count || 0} berichten ${f.unread_count ? `&bull; ${f.unread_count} ongelezen` : ''}</span>
</div>
</div>
<div class="flex-shrink-0 ml-3">
${isInbox ? `
<span class="text-[11px] font-medium text-slate-400 italic pr-2">Altijd actief</span>
` : `
<button type="button" onclick="toggleFolderDisabledState('${f.id}', ${!isDisabled})" class="flex items-center space-x-1.5 px-3 py-1.5 rounded-xl text-xs font-bold transition cursor-pointer ${isDisabled ? 'bg-sky-50 hover:bg-sky-100 text-sky-700 dark:bg-sky-950/60 dark:hover:bg-sky-900/80 dark:text-sky-300' : 'bg-rose-50 hover:bg-rose-100 text-rose-700 dark:bg-rose-950/40 dark:hover:bg-rose-900/60 dark:text-rose-300'}">
<i data-lucide="${isDisabled ? 'eye' : 'eye-off'}" class="w-3.5 h-3.5"></i>
<span>${isDisabled ? 'Weer Inschakelen' : 'Uitschakelen'}</span>
</button>
`}
</div>
</div>
`;
});
container.innerHTML = html;
if (typeof lucide !== 'undefined') lucide.createIcons();
}
async function toggleFolderDisabledState(folderId, willDisable) {
if (folderId === 'inbox') return;
if (willDisable) {
disabledFolderIds.add(folderId);
expandedFolders.delete(folderId);
} else {
disabledFolderIds.delete(folderId);
}
renderFolders();
updateTotalCounters();
renderManageFoldersList();
await saveDisabledFoldersToServer();
}
async function resetAllFoldersVisibility() {
disabledFolderIds.clear();
renderFolders();
updateTotalCounters();
renderManageFoldersList();
await saveDisabledFoldersToServer();
}
async function saveDisabledFoldersToServer() {
const arr = Array.from(disabledFolderIds);
try {
const res = await fetch('/api/mail.php?action=save_disabled_folders', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ disabled_folders: arr })
});
const data = await res.json();
if (!data.success) {
console.warn('Opslaan uitgeschakelde mappen mislukt:', data.error);
}
} catch (err) {
console.error('Fout bij opslaan mappen status:', err);
}
}
// Toetsenbordnavigatie voor alle modals (Escape & Enter)
document.addEventListener('keydown', function(e) {
if (e.key === 'Escape') {
@@ -1417,6 +1612,7 @@ document.addEventListener('keydown', function(e) {
closePlanMailModal();
closeReplyModal();
closeForwardModal();
closeManageFoldersModal();
} else if (e.key === 'Enter') {
const outsideModal = document.getElementById('outsideWorkhoursModalMail');
if (outsideModal && !outsideModal.classList.contains('hidden')) {