Postits en editable items.

This commit is contained in:
2026-09-12 17:14:47 +02:00
parent 7c27949cc2
commit 94d6325693
9 changed files with 1680 additions and 56 deletions
+219 -1
View File
@@ -21,6 +21,47 @@ $isAdmin = ($userRole === 'ADMIN');
max-width: 100% !important;
width: 100% !important;
}
/* --- Realistische Post-it Styling met Opkrullende Rand Links Onder --- */
.postit-note {
position: relative;
border-radius: 4px 18px 4px 18px;
box-shadow: 0 4px 12px -2px rgba(0, 0, 0, 0.12), 0 2px 6px -1px rgba(0, 0, 0, 0.08);
transition: transform 0.2s cubic-bezier(0.16, 1, 0.3, 1), box-shadow 0.2s ease;
filter: drop-shadow(0 2px 4px rgba(0, 0, 0, 0.04));
}
.postit-note:hover {
transform: translateY(-3px) scale(1.01);
box-shadow: 0 12px 20px -4px rgba(0, 0, 0, 0.16), 0 6px 10px -2px rgba(0, 0, 0, 0.08);
}
.postit-note::after {
content: '';
position: absolute;
bottom: -1px;
left: -1px;
width: 20px;
height: 20px;
background: linear-gradient(135deg, rgba(0,0,0,0.18) 0%, rgba(0,0,0,0.06) 45%, transparent 50%);
border-bottom-left-radius: 4px;
box-shadow: -2px 2px 4px rgba(0,0,0,0.12);
pointer-events: none;
z-index: 5;
}
.postit-note::before {
content: '';
position: absolute;
top: 0;
left: 15%;
right: 15%;
height: 4px;
background: rgba(255, 255, 255, 0.4);
border-bottom-left-radius: 2px;
border-bottom-right-radius: 2px;
pointer-events: none;
}
.dark .postit-note::before {
background: rgba(255, 255, 255, 0.1);
}
</style>
<div id="boardPageContainer" class="space-y-4 w-full">
@@ -277,6 +318,25 @@ $isAdmin = ($userRole === 'ADMIN');
</div>
</div>
<!-- Knop Vrij Blok Zoeken voor Assignee -->
<div class="p-2.5 rounded-xl bg-amber-500/10 border border-amber-500/20 flex items-center justify-between gap-2">
<div class="text-[11px]">
<span class="font-bold text-amber-900 dark:text-amber-200 flex items-center space-x-1">
<i data-lucide="sparkles" class="w-3 h-3 text-amber-500"></i>
<span>Vrij agendablok zoeken</span>
</span>
<span class="text-[10px] text-amber-700 dark:text-amber-400">Zoekt binnen werktijden van de toegewezen collega</span>
</div>
<button type="button" id="btnBoardFindSlot" onclick="findFreeSlotForBoardAssignee()" class="px-2.5 py-1 rounded-lg bg-amber-500 hover:bg-amber-600 text-white font-bold text-[10px] shadow-xs transition flex items-center space-x-1 flex-shrink-0 cursor-pointer">
<i data-lucide="search" class="w-3 h-3"></i>
<span>Zoek vrij blok</span>
</button>
</div>
<div id="boardFoundSlotNotice" class="hidden p-2 rounded-lg bg-emerald-50 dark:bg-emerald-950/40 border border-emerald-300 dark:border-emerald-800 text-[11px] text-emerald-800 dark:text-emerald-300 font-medium flex items-center space-x-1.5">
<i data-lucide="check-circle" class="w-3.5 h-3.5 text-emerald-500 flex-shrink-0"></i>
<span id="boardFoundSlotText"></span>
</div>
<div id="scheduleEndDateRow" class="hidden">
<label class="block text-[11px] font-semibold text-slate-600 dark:text-slate-400 mb-1">Einddatum herhaling</label>
<input type="date" id="cardModalEndDate" value="<?= date('Y-m-d', strtotime('+7 days')) ?>" class="w-full bg-white dark:bg-slate-900 border border-slate-200 dark:border-slate-800 rounded-xl px-3 py-1.5 text-xs text-slate-800 dark:text-slate-200 focus:outline-none">
@@ -425,7 +485,39 @@ $isAdmin = ($userRole === 'ADMIN');
<button onclick="submitCreateBoard()" class="px-5 py-2 bg-indigo-600 hover:bg-indigo-500 text-white font-bold text-xs rounded-xl transition">Bord Aanmaken</button>
</div>
</div>
<!-- Floating Rechtermuisknop Contextmenu voor Bord Kaarten -->
<div id="boardCardContextMenu" class="hidden fixed z-[999] bg-white dark:bg-[#111624] border border-slate-200 dark:border-slate-800 rounded-2xl shadow-2xl py-1.5 w-64 text-xs font-medium text-slate-700 dark:text-slate-200 select-none animate-in fade-in zoom-in-95 duration-100">
<div class="px-3.5 py-1.5 border-b border-slate-100 dark:border-slate-800/80 mb-1">
<span id="boardCtxTitle" class="text-[11px] font-bold text-slate-900 dark:text-white truncate block"></span>
<span id="boardCtxMeta" class="text-[10px] text-slate-400 truncate block"></span>
</div>
<button onclick="triggerBoardCtxEdit()" class="w-full px-3.5 py-2 text-left flex items-center space-x-2.5 hover:bg-indigo-50 dark:hover:bg-indigo-950/50 hover:text-indigo-600 dark:hover:text-indigo-400 transition cursor-pointer">
<i data-lucide="edit-3" class="w-4 h-4 text-indigo-500"></i>
<span>Taak bewerken</span>
</button>
<button onclick="triggerBoardCtxQuickSlot()" class="w-full px-3.5 py-2 text-left flex items-center space-x-2.5 hover:bg-amber-50 dark:hover:bg-amber-950/40 hover:text-amber-600 dark:hover:text-amber-400 transition cursor-pointer">
<i data-lucide="zap" class="w-4 h-4 text-amber-500"></i>
<span>⚡ Zoek direct vrij blok</span>
</button>
<button onclick="triggerBoardCtxQuickMove(-1)" class="w-full px-3.5 py-2 text-left flex items-center space-x-2.5 hover:bg-slate-100 dark:hover:bg-slate-800 transition cursor-pointer">
<i data-lucide="arrow-left" class="w-4 h-4 text-slate-500"></i>
<span>Verplaats naar vorige kolom</span>
</button>
<button onclick="triggerBoardCtxQuickMove(1)" class="w-full px-3.5 py-2 text-left flex items-center space-x-2.5 hover:bg-slate-100 dark:hover:bg-slate-800 transition cursor-pointer">
<i data-lucide="arrow-right" class="w-4 h-4 text-slate-500"></i>
<span>Verplaats naar volgende kolom</span>
</button>
<div class="my-1 border-t border-slate-100 dark:border-slate-800/80"></div>
<button onclick="triggerBoardCtxDelete()" class="w-full px-3.5 py-2 text-left flex items-center space-x-2.5 text-rose-600 hover:bg-rose-50 dark:hover:bg-rose-950/40 transition cursor-pointer">
<i data-lucide="trash-2" class="w-4 h-4"></i>
<span>Taak verwijderen</span>
</button>
</div>
<script>
@@ -738,7 +830,8 @@ function renderCards() {
ondragstart="onCardDragStart(event, ${card.id})"
ondragend="onCardDragEnd(event)"
onclick="onCardClick(event, ${card.id})"
class="group/card relative p-3 rounded-xl border ${colorClass} shadow-2xs hover:shadow-md hover:-translate-y-0.5 transition cursor-grab active:cursor-grabbing mb-2.5"
oncontextmenu="openBoardCardContextMenu(event, ${card.id}); return false;"
class="group/card relative p-3 rounded-xl border ${colorClass} shadow-2xs hover:shadow-md hover:-translate-y-0.5 transition cursor-grab active:cursor-grabbing mb-2.5 postit-note"
>
<div class="flex items-start justify-between gap-1.5 mb-1.5">
<h4 class="text-xs font-bold leading-snug line-clamp-2 break-words">
@@ -1053,6 +1146,131 @@ async function quickMoveCard(cardId, direction) {
}
}
// Rechtermuisknop Context Menu Logica voor Bord
let activeBoardCtxCardId = null;
function openBoardCardContextMenu(e, cardId) {
e.preventDefault();
e.stopPropagation();
activeBoardCtxCardId = cardId;
const menu = document.getElementById('boardCardContextMenu');
if (!menu) return;
menu.classList.remove('hidden');
const x = Math.min(e.clientX, window.innerWidth - 240);
const y = Math.min(e.clientY, window.innerHeight - 240);
menu.style.left = `${x}px`;
menu.style.top = `${y}px`;
if (typeof lucide !== 'undefined') lucide.createIcons();
}
function closeBoardCardContextMenu() {
const menu = document.getElementById('boardCardContextMenu');
if (menu) menu.classList.add('hidden');
activeBoardCtxCardId = null;
}
document.addEventListener('click', (e) => {
if (!e.target.closest('#boardCardContextMenu')) {
closeBoardCardContextMenu();
}
});
function triggerBoardCtxEdit() {
if (!activeBoardCtxCardId) return;
const cid = activeBoardCtxCardId;
closeBoardCardContextMenu();
openCardModal(cid);
}
async function triggerBoardCtxQuickSlot() {
if (!activeBoardCtxCardId) return;
const cid = activeBoardCtxCardId;
closeBoardCardContextMenu();
openCardModal(cid);
setTimeout(() => {
findFreeSlotForBoardAssignee();
}, 150);
}
function triggerBoardCtxQuickMove(dir) {
if (!activeBoardCtxCardId) return;
const cid = activeBoardCtxCardId;
closeBoardCardContextMenu();
quickMoveCard(cid, dir);
}
async function triggerBoardCtxDelete() {
if (!activeBoardCtxCardId) return;
const cid = activeBoardCtxCardId;
closeBoardCardContextMenu();
if (!confirm('Weet je zeker dat je deze taak wilt verwijderen?')) return;
try {
const res = await fetch('/api/board.php?action=delete_card', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({ card_id: cid })
});
const data = await res.json();
if (data.success) {
await loadBoardData(currentBoardId);
} else {
alert(data.error || 'Kon taak niet verwijderen');
}
} catch (e) {
alert('Netwerkfout bij verwijderen taak');
}
}
// Zoek vrij agendablok voor de in de modal geselecteerde assignee
async function findFreeSlotForBoardAssignee() {
const sel = document.getElementById('cardModalAssignee');
const targetUserId = sel.value ? parseInt(sel.value, 10) : currentUserId;
const schedCheckbox = document.getElementById('cardModalScheduleEnabled');
if (!schedCheckbox.checked) {
schedCheckbox.checked = true;
toggleScheduleFields();
}
const btn = document.getElementById('btnBoardFindSlot');
const origHtml = btn.innerHTML;
btn.disabled = true;
btn.innerHTML = '<i data-lucide="loader-2" class="w-3 h-3 animate-spin"></i><span>Zoeken...</span>';
if (typeof lucide !== 'undefined') lucide.createIcons();
try {
const res = await fetch('/api/planner.php?action=find_next_free_slot', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({
duration_minutes: 60,
target_user_id: targetUserId
})
});
const data = await res.json();
if (data.success && data.slot) {
const slot = data.slot;
document.getElementById('cardModalStartDate').value = slot.date;
document.getElementById('cardModalEndDate').value = slot.date;
document.getElementById('cardModalStartTime').value = slot.start;
document.getElementById('cardModalEndTime').value = slot.end;
const notice = document.getElementById('boardFoundSlotNotice');
const noticeText = document.getElementById('boardFoundSlotText');
noticeText.innerHTML = `Vrij agendablok gevonden: <strong>${slot.formatted_date}</strong> om <strong>${slot.start} – ${slot.end}</strong>`;
notice.classList.remove('hidden');
} else {
alert(data.error || 'Geen beschikbaar tijdblok gevonden binnen werktijden voor deze collega.');
}
} catch (err) {
alert('Netwerkfout bij zoeken van vrij agendablok: ' + err.message);
} finally {
btn.disabled = false;
btn.innerHTML = origHtml;
if (typeof lucide !== 'undefined') lucide.createIcons();
}
}
// 8. Beheer Modal: Lanes & Leden Autorisatie
function openManageBoardModal() {
if (!currentBoardData) return;