Postits en editable items.
This commit is contained in:
+170
@@ -353,6 +353,25 @@ html.dark #timelineGridWrapper::-webkit-scrollbar-thumb {
|
||||
</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="btnTimelineFindSlot" onclick="findFreeSlotForTimelineAssignee()" 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="timelineFoundSlotNotice" 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="timelineFoundSlotText"></span>
|
||||
</div>
|
||||
|
||||
<!-- Outlook Agenda Synchronisatie -->
|
||||
<div class="flex items-center justify-between pt-2 border-t border-slate-200/50 dark:border-slate-800/50">
|
||||
<div class="flex items-center space-x-2">
|
||||
@@ -393,6 +412,26 @@ html.dark #timelineGridWrapper::-webkit-scrollbar-thumb {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Rechtermuisknop Contextmenu voor Tijdlijn Items -->
|
||||
<div id="timelineContextMenu" class="hidden fixed z-50 w-56 bg-white dark:bg-slate-900 rounded-xl shadow-2xl border border-slate-200 dark:border-slate-800 py-1.5 text-xs text-slate-700 dark:text-slate-200">
|
||||
<button onclick="triggerTimelineCtxEdit()" 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="triggerTimelineCtxQuickSlot()" 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>
|
||||
|
||||
<div class="my-1 border-t border-slate-100 dark:border-slate-800/80"></div>
|
||||
|
||||
<button onclick="triggerTimelineCtxDelete()" 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>
|
||||
// --- Globale Status voor Tijdlijn ---
|
||||
let currentBoardId = null;
|
||||
@@ -733,6 +772,11 @@ function renderTimeline() {
|
||||
leftItem.setAttribute('draggable', 'true');
|
||||
leftItem.setAttribute('data-card-id', card.id);
|
||||
|
||||
leftItem.oncontextmenu = (e) => {
|
||||
openTimelineContextMenu(e, card.id);
|
||||
return false;
|
||||
};
|
||||
|
||||
leftItem.ondragstart = (e) => {
|
||||
e.dataTransfer.setData('text/plain', String(card.id));
|
||||
e.dataTransfer.effectAllowed = 'move';
|
||||
@@ -779,6 +823,11 @@ function renderTimeline() {
|
||||
rightRow.className = 'timeline-task-row timeline-row h-12 relative flex items-center';
|
||||
rightRow.setAttribute('data-card-id', card.id);
|
||||
|
||||
rightRow.oncontextmenu = (e) => {
|
||||
openTimelineContextMenu(e, card.id);
|
||||
return false;
|
||||
};
|
||||
|
||||
// Klikken op een lege plek in de rij om direct in te plannen
|
||||
rightRow.onclick = (e) => {
|
||||
if (e.target.closest('.timeline-bar')) return;
|
||||
@@ -803,6 +852,10 @@ function renderTimeline() {
|
||||
barEl.style.left = `${leftPx}px`;
|
||||
barEl.style.width = `${widthPx}px`;
|
||||
barEl.title = `${escapeHtml(card.title)} (${card.start_date} t/m ${card.end_date})`;
|
||||
barEl.oncontextmenu = (ev) => {
|
||||
openTimelineContextMenu(ev, card.id);
|
||||
return false;
|
||||
};
|
||||
barEl.onclick = (ev) => {
|
||||
ev.stopPropagation();
|
||||
openCardModal(card.id);
|
||||
@@ -1172,9 +1225,126 @@ function escapeHtml(str) {
|
||||
.replace(/'/g, ''');
|
||||
}
|
||||
|
||||
// --- Rechtermuisknop Context Menu voor Tijdlijn ---
|
||||
let activeTimelineCtxCardId = null;
|
||||
|
||||
function openTimelineContextMenu(e, cardId) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
activeTimelineCtxCardId = cardId;
|
||||
const menu = document.getElementById('timelineContextMenu');
|
||||
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 closeTimelineContextMenu() {
|
||||
const menu = document.getElementById('timelineContextMenu');
|
||||
if (menu) menu.classList.add('hidden');
|
||||
activeTimelineCtxCardId = null;
|
||||
}
|
||||
|
||||
document.addEventListener('click', (e) => {
|
||||
if (!e.target.closest('#timelineContextMenu')) {
|
||||
closeTimelineContextMenu();
|
||||
}
|
||||
});
|
||||
|
||||
function triggerTimelineCtxEdit() {
|
||||
if (!activeTimelineCtxCardId) return;
|
||||
const cid = activeTimelineCtxCardId;
|
||||
closeTimelineContextMenu();
|
||||
openCardModal(cid);
|
||||
}
|
||||
|
||||
function triggerTimelineCtxQuickSlot() {
|
||||
if (!activeTimelineCtxCardId) return;
|
||||
const cid = activeTimelineCtxCardId;
|
||||
closeTimelineContextMenu();
|
||||
openCardModal(cid);
|
||||
setTimeout(() => {
|
||||
findFreeSlotForTimelineAssignee();
|
||||
}, 150);
|
||||
}
|
||||
|
||||
async function triggerTimelineCtxDelete() {
|
||||
if (!activeTimelineCtxCardId) return;
|
||||
const cid = activeTimelineCtxCardId;
|
||||
closeTimelineContextMenu();
|
||||
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');
|
||||
}
|
||||
}
|
||||
|
||||
// Zoek vrij agendablok voor de in de tijdlijn-modal geselecteerde assignee
|
||||
async function findFreeSlotForTimelineAssignee() {
|
||||
const sel = document.getElementById('cardAssigneeSelect');
|
||||
const targetUserId = sel.value ? parseInt(sel.value, 10) : currentUserId;
|
||||
const schedToggle = document.getElementById('cardScheduleToggle');
|
||||
if (!schedToggle.checked) {
|
||||
schedToggle.checked = true;
|
||||
toggleScheduleFields(true);
|
||||
}
|
||||
|
||||
const btn = document.getElementById('btnTimelineFindSlot');
|
||||
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('cardStartDate').value = slot.date;
|
||||
document.getElementById('cardEndDate').value = slot.date;
|
||||
|
||||
const notice = document.getElementById('timelineFoundSlotNotice');
|
||||
const noticeText = document.getElementById('timelineFoundSlotText');
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
document.addEventListener('keydown', (e) => {
|
||||
if (e.key === 'Escape') {
|
||||
closeCardModal();
|
||||
closeTimelineContextMenu();
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
Reference in New Issue
Block a user