Postits en editable items.
This commit is contained in:
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user