This commit is contained in:
2026-09-12 13:14:38 +02:00
parent 32632dab91
commit bc33c0bd1a
+214 -12
View File
@@ -894,23 +894,26 @@ require_once __DIR__ . '/includes/header.php';
<div class="space-y-2"> <div class="space-y-2">
<div class="grid grid-cols-12 gap-3 px-3 py-1.5 text-[10px] font-bold text-slate-400 dark:text-slate-500 uppercase tracking-wider border-b border-slate-100 dark:border-slate-800"> <div class="grid grid-cols-12 gap-3 px-3 py-1.5 text-[10px] font-bold text-slate-400 dark:text-slate-500 uppercase tracking-wider border-b border-slate-100 dark:border-slate-800">
<div class="col-span-2 sm:col-span-1 text-center">Icoon</div> <div class="col-span-3 sm:col-span-2 text-center">Icoon</div>
<div class="col-span-3 sm:col-span-2">Systeemcode</div> <div class="col-span-3 sm:col-span-2">Systeemcode</div>
<div class="col-span-5 sm:col-span-6">Zichtbare Titel / Omschrijving</div> <div class="col-span-4 sm:col-span-6">Zichtbare Titel / Omschrijving</div>
<div class="col-span-1 text-center">Actief</div> <div class="col-span-1 text-center">Actief</div>
<div class="col-span-1 text-right">Verwijder</div> <div class="col-span-1 text-right">Verwijder</div>
</div> </div>
<?php foreach ($calendarBlockReasons as $idx => $reason): ?> <?php foreach ($calendarBlockReasons as $idx => $reason): ?>
<div class="grid grid-cols-12 gap-3 items-center p-3 rounded-2xl bg-slate-50/70 dark:bg-[#0c0f17] border border-slate-200/70 dark:border-slate-800/70 hover:border-slate-300 dark:hover:border-slate-700 transition"> <div class="grid grid-cols-12 gap-3 items-center p-3 rounded-2xl bg-slate-50/70 dark:bg-[#0c0f17] border border-slate-200/70 dark:border-slate-800/70 hover:border-slate-300 dark:hover:border-slate-700 transition">
<div class="col-span-2 sm:col-span-1"> <div class="col-span-3 sm:col-span-2 flex items-center space-x-1.5">
<input type="text" name="reasons[<?= $idx ?>][icon]" value="<?= htmlspecialchars($reason['icon'] ?? '📅') ?>" class="w-full text-center text-lg py-1 px-1 bg-white dark:bg-[#111624] border border-slate-300 dark:border-slate-700 rounded-xl focus:outline-none focus:ring-1 focus:ring-amber-500"> <button type="button" onclick="openIconPickerFor('reason_icon_<?= $idx ?>', 'reason_icon_preview_<?= $idx ?>')" class="w-10 h-10 flex-shrink-0 text-xl flex items-center justify-center bg-white dark:bg-[#111624] border border-slate-300 dark:border-slate-700 hover:border-amber-400 dark:hover:border-amber-500 rounded-xl transition cursor-pointer shadow-2xs hover:scale-105" title="Klik om een icoon te kiezen uit de lijst">
<span id="reason_icon_preview_<?= $idx ?>"><?= htmlspecialchars($reason['icon'] ?? '📅') ?></span>
</button>
<input type="text" id="reason_icon_<?= $idx ?>" name="reasons[<?= $idx ?>][icon]" value="<?= htmlspecialchars($reason['icon'] ?? '📅') ?>" oninput="document.getElementById('reason_icon_preview_<?= $idx ?>').innerText = this.value || '📅'" class="w-12 text-center text-xs py-2 px-1 bg-white dark:bg-[#111624] border border-slate-300 dark:border-slate-700 rounded-xl focus:outline-none focus:ring-1 focus:ring-amber-500">
</div> </div>
<div class="col-span-3 sm:col-span-2"> <div class="col-span-3 sm:col-span-2">
<input type="hidden" name="reasons[<?= $idx ?>][code]" value="<?= htmlspecialchars($reason['code']) ?>"> <input type="hidden" name="reasons[<?= $idx ?>][code]" value="<?= htmlspecialchars($reason['code']) ?>">
<span class="font-mono text-xs font-bold text-slate-700 dark:text-slate-300 px-2.5 py-1.5 rounded-xl bg-white dark:bg-[#111624] border border-slate-200 dark:border-slate-800 inline-block truncate w-full shadow-2xs"><?= htmlspecialchars($reason['code']) ?></span> <span class="font-mono text-xs font-bold text-slate-700 dark:text-slate-300 px-2.5 py-1.5 rounded-xl bg-white dark:bg-[#111624] border border-slate-200 dark:border-slate-800 inline-block truncate w-full shadow-2xs"><?= htmlspecialchars($reason['code']) ?></span>
</div> </div>
<div class="col-span-5 sm:col-span-6"> <div class="col-span-4 sm:col-span-6">
<input type="text" name="reasons[<?= $idx ?>][title]" value="<?= htmlspecialchars($reason['title']) ?>" class="w-full px-3 py-1.5 rounded-xl bg-white dark:bg-[#111624] border border-slate-300 dark:border-slate-700 text-xs font-medium text-slate-900 dark:text-white focus:outline-none focus:ring-1 focus:ring-amber-500" required> <input type="text" name="reasons[<?= $idx ?>][title]" value="<?= htmlspecialchars($reason['title']) ?>" class="w-full px-3 py-1.5 rounded-xl bg-white dark:bg-[#111624] border border-slate-300 dark:border-slate-700 text-xs font-medium text-slate-900 dark:text-white focus:outline-none focus:ring-1 focus:ring-amber-500" required>
</div> </div>
<div class="col-span-1 text-center"> <div class="col-span-1 text-center">
@@ -929,20 +932,28 @@ require_once __DIR__ . '/includes/header.php';
<!-- Nieuwe reden toevoegen regel --> <!-- Nieuwe reden toevoegen regel -->
<div class="p-4 rounded-2xl bg-amber-50/40 dark:bg-amber-950/20 border border-dashed border-amber-300/80 dark:border-amber-800/60 space-y-3"> <div class="p-4 rounded-2xl bg-amber-50/40 dark:bg-amber-950/20 border border-dashed border-amber-300/80 dark:border-amber-800/60 space-y-3">
<div class="flex items-center space-x-2 text-xs font-bold text-amber-900 dark:text-amber-200"> <div class="flex items-center justify-between">
<i data-lucide="plus-circle" class="w-4 h-4 text-amber-600 dark:text-amber-400"></i> <div class="flex items-center space-x-2 text-xs font-bold text-amber-900 dark:text-amber-200">
<span>Nieuwe Blokkadereden Toevoegen</span> <i data-lucide="plus-circle" class="w-4 h-4 text-amber-600 dark:text-amber-400"></i>
<span>Nieuwe Blokkadereden Toevoegen</span>
</div>
<span class="text-[10px] text-amber-700 dark:text-amber-400">Klik op de icoonknop om uit de standaard iconenset te kiezen</span>
</div> </div>
<div class="grid grid-cols-1 sm:grid-cols-12 gap-3 items-center"> <div class="grid grid-cols-1 sm:grid-cols-12 gap-3 items-center">
<div class="sm:col-span-2"> <div class="sm:col-span-3 flex items-center space-x-1.5">
<label class="block text-[10px] font-bold text-slate-500 dark:text-slate-400 mb-1">Icoon (Emoji)</label> <button type="button" onclick="openIconPickerFor('new_icon_input', 'new_icon_preview')" class="w-10 h-10 flex-shrink-0 text-xl flex items-center justify-center bg-white dark:bg-[#111624] border border-amber-300 dark:border-amber-700 hover:border-amber-500 rounded-xl transition cursor-pointer shadow-2xs hover:scale-105" title="Kies een icoon uit de lijst">
<input type="text" name="new_icon" placeholder="🎯" class="w-full text-center text-lg py-1 px-1 bg-white dark:bg-[#111624] border border-slate-300 dark:border-slate-700 rounded-xl focus:outline-none focus:ring-1 focus:ring-amber-500"> <span id="new_icon_preview">🎯</span>
</button>
<div class="flex-1">
<label class="block text-[10px] font-bold text-slate-500 dark:text-slate-400 mb-1">Icoon (Kies of typ)</label>
<input type="text" id="new_icon_input" name="new_icon" value="🎯" placeholder="🎯" oninput="document.getElementById('new_icon_preview').innerText = this.value || '📅'" class="w-full text-center text-xs py-1.5 px-2 bg-white dark:bg-[#111624] border border-slate-300 dark:border-slate-700 rounded-xl focus:outline-none focus:ring-1 focus:ring-amber-500">
</div>
</div> </div>
<div class="sm:col-span-3"> <div class="sm:col-span-3">
<label class="block text-[10px] font-bold text-slate-500 dark:text-slate-400 mb-1">Code (bv. STUDIEDAG)</label> <label class="block text-[10px] font-bold text-slate-500 dark:text-slate-400 mb-1">Code (bv. STUDIEDAG)</label>
<input type="text" name="new_code" placeholder="BIJV_CURSUS" class="w-full px-3 py-1.5 rounded-xl bg-white dark:bg-[#111624] border border-slate-300 dark:border-slate-700 text-xs font-mono text-slate-900 dark:text-white uppercase focus:outline-none focus:ring-1 focus:ring-amber-500"> <input type="text" name="new_code" placeholder="BIJV_CURSUS" class="w-full px-3 py-1.5 rounded-xl bg-white dark:bg-[#111624] border border-slate-300 dark:border-slate-700 text-xs font-mono text-slate-900 dark:text-white uppercase focus:outline-none focus:ring-1 focus:ring-amber-500">
</div> </div>
<div class="sm:col-span-7"> <div class="sm:col-span-6">
<label class="block text-[10px] font-bold text-slate-500 dark:text-slate-400 mb-1">Zichtbare Titel</label> <label class="block text-[10px] font-bold text-slate-500 dark:text-slate-400 mb-1">Zichtbare Titel</label>
<input type="text" name="new_title" placeholder="Bijv. Studiedag / Opleiding" class="w-full px-3 py-1.5 rounded-xl bg-white dark:bg-[#111624] border border-slate-300 dark:border-slate-700 text-xs font-medium text-slate-900 dark:text-white focus:outline-none focus:ring-1 focus:ring-amber-500"> <input type="text" name="new_title" placeholder="Bijv. Studiedag / Opleiding" class="w-full px-3 py-1.5 rounded-xl bg-white dark:bg-[#111624] border border-slate-300 dark:border-slate-700 text-xs font-medium text-slate-900 dark:text-white focus:outline-none focus:ring-1 focus:ring-amber-500">
</div> </div>
@@ -1920,6 +1931,52 @@ require_once __DIR__ . '/includes/header.php';
</div> </div>
<!-- Modal: Icoon Kiezer voor Agenda Blokkades -->
<div id="iconPickerModal" class="fixed inset-0 z-[99999] bg-slate-950/70 backdrop-blur-xs hidden items-center justify-center p-4">
<div class="fociz-card p-6 w-full max-w-lg shadow-2xl space-y-4 max-h-[85vh] flex flex-col animate-in fade-in zoom-in-95 duration-100">
<!-- Header -->
<div class="flex items-center justify-between pb-3 border-b border-slate-100 dark:border-slate-800/80 flex-shrink-0">
<div class="flex items-center space-x-2.5">
<div class="w-8 h-8 rounded-xl bg-amber-50 dark:bg-amber-500/10 border border-amber-200 dark:border-amber-500/20 text-amber-600 dark:text-amber-400 flex items-center justify-center">
<i data-lucide="smile" class="w-4 h-4"></i>
</div>
<div>
<h3 class="text-sm font-bold text-slate-900 dark:text-white">Kies een Icoon</h3>
<p class="text-[10px] text-slate-500 dark:text-slate-400">Selecteer een standaard icoon voor de dagblokkadereden</p>
</div>
</div>
<button type="button" onclick="closeIconPicker()" class="p-1.5 text-slate-400 hover:text-slate-600 dark:hover:text-slate-200 rounded-lg hover:bg-slate-100 dark:hover:bg-slate-800 transition cursor-pointer">
<i data-lucide="x" class="w-4 h-4"></i>
</button>
</div>
<!-- Zoekbalk & Categorie Tabs -->
<div class="space-y-2.5 flex-shrink-0">
<div class="relative">
<i data-lucide="search" class="w-3.5 h-3.5 absolute left-3 top-3 text-slate-400"></i>
<input type="text" id="iconPickerSearch" placeholder="Zoek op categorie of naam (bijv. vakantie, ziek, focus, kantoor, studie)..." oninput="filterIconPicker(this.value)" class="w-full pl-8 pr-3 py-2 rounded-xl bg-slate-50 dark:bg-slate-800/80 border border-slate-200 dark:border-slate-700 text-xs text-slate-900 dark:text-white placeholder-slate-400 focus:outline-none focus:ring-2 focus:ring-amber-500">
</div>
<div class="flex items-center space-x-1.5 overflow-x-auto no-scrollbar py-0.5" id="iconPickerCategoryTabs">
<!-- Dynamische categorieknoppen -->
</div>
</div>
<!-- Grid met Iconen -->
<div id="iconPickerGrid" class="flex-1 overflow-y-auto p-1.5 grid grid-cols-6 sm:grid-cols-8 gap-2 min-h-[220px] max-h-[320px] rounded-xl bg-slate-50/50 dark:bg-[#0c0f17]/50 border border-slate-200/50 dark:border-slate-800/50">
<!-- Wordt via JS gevuld met grote, klikbare iconen -->
</div>
<!-- Footer -->
<div class="flex items-center justify-between pt-3 border-t border-slate-100 dark:border-slate-800/80 flex-shrink-0 text-xs">
<span class="text-[11px] text-slate-400">Tip: Je kunt ook zelf een aangepaste emoji typen.</span>
<button type="button" onclick="closeIconPicker()" class="px-4 py-2 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 font-semibold transition cursor-pointer">
Sluiten
</button>
</div>
</div>
</div>
<script> <script>
const ADMIN_TABS = ['users', 'calendar', 'microsoft', 'jira', 'ai', 'security', 'system']; const ADMIN_TABS = ['users', 'calendar', 'microsoft', 'jira', 'ai', 'security', 'system'];
@@ -2128,9 +2185,154 @@ async function testAdminAiConnection() {
} }
} }
// --- Agenda Blokkades Icoon Kiezer ---
let currentIconTargetInputId = null;
let currentIconTargetPreviewId = null;
let currentIconCategory = 'all';
const ICON_PICKER_DATA = {
'Vakantie & Vrij': ['🏖️', '✈️', '🌴', '⛺', '☀️', '⛵', '🎒', '🗺️', '🍹', '🏔️', '🚗', '🛳️', '🏕️', '🕶️', '🏄', '🏊', '⛷️', '🚴', '🎣', '🌅', '🌌', '🌍'],
'Werk & Focus': ['🎯', '📋', '💻', '📊', '📈', '🏢', '💼', '📁', '✍️', '💡', '⚡', '🔍', '📅', '🗓️', '⏰', '⏳', '🔒', '🛠️', '📞', '🤝', '📌', '📑', '🖊️', '🖥️', '⌨️', '📂'],
'Ziekte & Zorg': ['🤒', '🏥', '💊', '🩺', '🩹', '🛌', '🧑‍⚕️', '🦷', '🧘', '🍵', '💉', '😷', '🤧', '🚑', '🌡️', '🧑‍🦽', '🧴', '🍎', '❤️‍🩹'],
'Studie & Opleiding': ['🎓', '📚', '📖', '🏫', '🧑‍🏫', '📝', '🧠', '🔬', '📐', '📜', '🏆', '🎨', '🧪', '🔭', '💡', '🎒'],
'Persoonlijk & Verlof': ['👶', '💍', '🏠', '🚚', '🎂', '🎉', '🕊️', '⚖️', '👥', '☕', '🍴', '👨‍👩‍👧', '🎈', '🎁', '🐶', '🐱', '🧹', '🪴', '🛒', '🔑', '💤']
};
function escapeHtml(text) {
if (!text) return '';
return text.toString().replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#039;");
}
function openIconPickerFor(inputId, previewId) {
currentIconTargetInputId = inputId;
currentIconTargetPreviewId = previewId;
const modal = document.getElementById('iconPickerModal');
if (!modal) return;
currentIconCategory = 'all';
renderIconCategoryTabs();
renderIconGrid();
modal.classList.remove('hidden');
modal.classList.add('flex');
if (window.lucide && typeof lucide.createIcons === 'function') {
lucide.createIcons();
}
setTimeout(() => {
const searchInput = document.getElementById('iconPickerSearch');
if (searchInput) {
searchInput.value = '';
searchInput.focus();
}
}, 50);
}
function closeIconPicker() {
const modal = document.getElementById('iconPickerModal');
if (modal) {
modal.classList.add('hidden');
modal.classList.remove('flex');
}
currentIconTargetInputId = null;
currentIconTargetPreviewId = null;
}
function selectIcon(icon) {
if (currentIconTargetInputId) {
const input = document.getElementById(currentIconTargetInputId);
if (input) {
input.value = icon;
input.dispatchEvent(new Event('input', { bubbles: true }));
}
}
if (currentIconTargetPreviewId) {
const preview = document.getElementById(currentIconTargetPreviewId);
if (preview) preview.innerText = icon;
}
closeIconPicker();
}
function renderIconCategoryTabs() {
const container = document.getElementById('iconPickerCategoryTabs');
if (!container) return;
let html = `
<button type="button" onclick="switchIconCategory('all')" class="px-2.5 py-1 rounded-lg text-xs font-semibold whitespace-nowrap transition cursor-pointer ${currentIconCategory === 'all' ? 'bg-amber-600 text-white shadow-xs' : 'bg-slate-100 dark:bg-slate-800 text-slate-600 dark:text-slate-300 hover:bg-slate-200 dark:hover:bg-slate-700'}">
Alle
</button>
`;
Object.keys(ICON_PICKER_DATA).forEach(cat => {
const isActive = currentIconCategory === cat;
html += `
<button type="button" onclick="switchIconCategory('${escapeHtml(cat)}')" class="px-2.5 py-1 rounded-lg text-xs font-semibold whitespace-nowrap transition cursor-pointer ${isActive ? 'bg-amber-600 text-white shadow-xs' : 'bg-slate-100 dark:bg-slate-800 text-slate-600 dark:text-slate-300 hover:bg-slate-200 dark:hover:bg-slate-700'}">
${escapeHtml(cat)}
</button>
`;
});
container.innerHTML = html;
}
function switchIconCategory(category) {
currentIconCategory = category;
renderIconCategoryTabs();
const searchInput = document.getElementById('iconPickerSearch');
const query = searchInput ? searchInput.value : '';
renderIconGrid(query);
}
function renderIconGrid(filterText = '') {
const grid = document.getElementById('iconPickerGrid');
if (!grid) return;
let iconsToDisplay = [];
const query = filterText.toLowerCase().trim();
if (currentIconCategory === 'all') {
Object.entries(ICON_PICKER_DATA).forEach(([cat, icons]) => {
if (!query || cat.toLowerCase().includes(query)) {
iconsToDisplay.push(...icons);
} else {
icons.forEach(ic => {
if (ic.includes(query)) iconsToDisplay.push(ic);
});
}
});
} else {
const catIcons = ICON_PICKER_DATA[currentIconCategory] || [];
if (!query) {
iconsToDisplay = catIcons;
} else {
iconsToDisplay = catIcons.filter(ic => ic.includes(query) || currentIconCategory.toLowerCase().includes(query));
}
}
iconsToDisplay = Array.from(new Set(iconsToDisplay));
if (iconsToDisplay.length === 0) {
grid.innerHTML = '<div class="col-span-full py-8 text-center text-xs text-slate-400">Geen iconen gevonden voor deze zoekopdracht.</div>';
return;
}
grid.innerHTML = iconsToDisplay.map(ic => `
<button type="button" onclick="selectIcon('${ic}')" class="h-11 flex items-center justify-center text-2xl rounded-xl bg-white hover:bg-amber-50 dark:bg-slate-900 dark:hover:bg-amber-950/40 border border-slate-200/80 hover:border-amber-400 dark:border-slate-800 dark:hover:border-amber-500 hover:scale-110 transition duration-150 cursor-pointer shadow-2xs group" title="${ic}">
<span class="group-hover:scale-110 transition-transform duration-150">${ic}</span>
</button>
`).join('');
}
function filterIconPicker(query) {
renderIconGrid(query);
}
document.addEventListener('keydown', function(e) { document.addEventListener('keydown', function(e) {
if (e.key === 'Escape') { if (e.key === 'Escape') {
closeJiraFilterModal(); closeJiraFilterModal();
closeIconPicker();
} }
}); });
</script> </script>