Large refactor

This commit is contained in:
2026-09-11 21:43:36 +02:00
parent 7fe7c0f51a
commit deba8463b8
11 changed files with 871 additions and 113 deletions
+335 -31
View File
@@ -467,8 +467,8 @@ $boardsJson = json_encode($boards, JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_QUOT
</div>
<!-- MODAL 1: Inplannen in Dagplanner (planner.php) -->
<div id="planTaskModal" class="fixed inset-0 z-50 bg-slate-950/70 backdrop-blur-sm hidden items-center justify-center p-4 overflow-y-auto">
<div class="bg-white dark:bg-slate-900 border border-slate-200 dark:border-slate-800 rounded-2xl w-full max-w-md shadow-2xl p-6 transition transform">
<div id="planTaskModal" onclick="if(event.target === this) closePlanModal()" class="fixed inset-0 z-50 bg-slate-950/70 backdrop-blur-sm hidden items-center justify-center p-4 overflow-y-auto">
<div class="bg-white dark:bg-slate-900 border border-slate-200 dark:border-slate-800 rounded-2xl w-full max-w-lg shadow-2xl p-6 transition transform">
<div class="flex items-center justify-between pb-4 border-b border-slate-100 dark:border-slate-800">
<div class="flex items-center space-x-2.5">
<div class="w-8 h-8 rounded-lg bg-indigo-50 dark:bg-indigo-950/50 text-indigo-600 dark:text-indigo-400 flex items-center justify-center">
@@ -476,7 +476,7 @@ $boardsJson = json_encode($boards, JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_QUOT
</div>
<div>
<h3 class="text-sm font-bold text-slate-900 dark:text-white">Inplannen in Dagplanner</h3>
<p class="text-[11px] text-slate-400">Voeg toe aan je dagfocus</p>
<p class="text-[11px] text-slate-400">Voeg toe aan je dagfocus op de tijdlijn</p>
</div>
</div>
<button onclick="closePlanModal()" class="text-slate-400 hover:text-slate-600 dark:hover:text-slate-200 p-1.5 rounded-lg transition cursor-pointer">
@@ -489,25 +489,81 @@ $boardsJson = json_encode($boards, JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_QUOT
<input type="hidden" id="planIssueSummary" value="">
<input type="hidden" id="planIssueUrl" value="">
<!-- Ticket Info Preview -->
<div class="p-3 rounded-xl bg-slate-50 dark:bg-slate-950 border border-slate-200/80 dark:border-slate-800/80">
<!-- Ticket Info Preview met uitklapbare inhoud -->
<div class="p-3.5 rounded-xl bg-slate-50 dark:bg-slate-950 border border-slate-200/80 dark:border-slate-800/80 space-y-2">
<div class="flex items-center space-x-2">
<span id="planTicketKeyBadge" class="font-mono text-xs font-bold text-indigo-600 dark:text-indigo-400">FOC-101</span>
<span id="planTicketProject" class="text-[11px] text-slate-400">• Fociz Platform</span>
<span id="planSpBadge" class="hidden ml-auto text-[10px] font-bold px-2 py-0.5 rounded-full bg-violet-50 dark:bg-violet-500/10 text-violet-700 dark:text-violet-300 border border-violet-200 dark:border-violet-500/20"></span>
</div>
<div id="planTicketSummaryText" class="text-xs font-semibold text-slate-800 dark:text-slate-200 mt-1 line-clamp-2">
<div id="planTicketSummaryText" class="text-xs font-semibold text-slate-800 dark:text-slate-200 line-clamp-2">
Tweestapsverificatie implementeren
</div>
<div id="planSpHint" class="hidden mt-1.5 text-[11px] text-violet-600 dark:text-violet-400 flex items-center space-x-1">
<div id="planSpHint" class="hidden mt-1 text-[11px] text-violet-600 dark:text-violet-400 flex items-center space-x-1">
<i data-lucide="zap" class="w-3 h-3"></i>
<span id="planSpHintText"></span>
</div>
<!-- Uitklapbare beschrijving (10 regels verticaal scrollen) -->
<div class="pt-1.5 border-t border-slate-200/60 dark:border-slate-800/60">
<button type="button" onclick="togglePlanTicketDesc()" class="text-[11px] text-indigo-600 dark:text-indigo-400 hover:underline flex items-center space-x-1 font-medium cursor-pointer">
<i data-lucide="file-text" class="w-3 h-3"></i>
<span id="planTicketDescToggleText">Toon ticket inhoud</span>
<i data-lucide="chevron-down" id="planTicketDescChevron" class="w-3 h-3 transition-transform duration-200"></i>
</button>
<div id="planTicketDescContainer" class="hidden mt-2 p-3 rounded-lg bg-white dark:bg-slate-900 border border-slate-200 dark:border-slate-800 text-[11px] text-slate-700 dark:text-slate-300 max-h-40 overflow-y-auto leading-relaxed whitespace-pre-wrap select-text font-normal">
</div>
</div>
</div>
<!-- Aanpasbare Taaktitel in Agenda -->
<div>
<div class="flex items-center justify-between mb-1">
<label for="planTaskTitleInput" class="block text-xs font-bold text-slate-700 dark:text-slate-300">Naam op de tijdlijn</label>
<button type="button" onclick="clearPlanTaskTitle()" class="text-[10px] text-slate-400 hover:text-rose-500 cursor-pointer flex items-center space-x-0.5" title="Titel wissen">
<i data-lucide="x-circle" class="w-3 h-3"></i>
<span>Wissen</span>
</button>
</div>
<div class="relative">
<input
type="text"
id="planTaskTitleInput"
required
placeholder="Bijv. Oppakken Jira item FOC-101"
class="w-full bg-slate-50 dark:bg-slate-950 border border-slate-200 dark:border-slate-800 rounded-xl pl-3 pr-8 py-2 text-xs text-slate-900 dark:text-white font-medium focus:outline-none focus:ring-2 focus:ring-indigo-500/40"
>
<button type="button" onclick="clearPlanTaskTitle()" class="absolute right-2.5 top-2.5 text-slate-400 hover:text-slate-600 dark:hover:text-slate-200 cursor-pointer" title="Wissen">
<i data-lucide="x" class="w-3.5 h-3.5"></i>
</button>
</div>
</div>
<!-- Datum & Slimme "Plan in open blok" knop -->
<div class="space-y-2 pt-1 border-t border-slate-100 dark:border-slate-800">
<div class="flex items-center justify-between">
<span class="text-xs font-bold text-slate-700 dark:text-slate-300">Datum & Tijdblok</span>
<button type="button" onclick="planInNextOpenBlockJira()" id="btnJiraPlanOpenBlock" class="inline-flex items-center space-x-1.5 px-3 py-1.5 rounded-xl bg-gradient-to-r from-indigo-600 to-sky-600 hover:from-indigo-500 hover:to-sky-500 text-white text-xs font-bold transition shadow-xs cursor-pointer active:scale-95" title="Vind automatisch het eerstvolgende vrije kwartierblok binnen werktijden">
<i data-lucide="sparkles" class="w-3.5 h-3.5"></i>
<span>Plan in open blok</span>
</button>
</div>
<!-- Open blok banner -->
<div id="jiraPlanOpenBlockNotice" class="hidden p-2.5 rounded-xl bg-emerald-50 dark:bg-emerald-500/10 border border-emerald-200 dark:border-emerald-500/20 text-emerald-800 dark:text-emerald-300 text-[11px] flex items-center justify-between">
<div class="flex items-center space-x-1.5 min-w-0 flex-1 mr-2">
<i data-lucide="check-circle" class="w-3.5 h-3.5 text-emerald-600 dark:text-emerald-400 flex-shrink-0"></i>
<span id="jiraPlanOpenBlockNoticeText" class="truncate font-semibold"></span>
</div>
<button type="button" onclick="document.getElementById('jiraPlanOpenBlockNotice').classList.add('hidden')" class="p-0.5 text-emerald-600 dark:text-emerald-400 hover:text-emerald-800 rounded cursor-pointer">
<i data-lucide="x" class="w-3 h-3"></i>
</button>
</div>
</div>
<!-- Datum Kiezer -->
<div>
<label class="block text-xs font-bold text-slate-700 dark:text-slate-300 mb-1">Datum</label>
<label class="block text-xs font-semibold text-slate-700 dark:text-slate-300 mb-1">Datum</label>
<input
type="date"
id="planDateInput"
@@ -518,19 +574,26 @@ $boardsJson = json_encode($boards, JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_QUOT
</div>
<!-- Tijdsblok Starttijd & Duur -->
<div class="grid grid-cols-2 gap-3">
<div class="grid grid-cols-1 sm:grid-cols-2 gap-3">
<div>
<label class="block text-xs font-bold text-slate-700 dark:text-slate-300 mb-1">Starttijd (optioneel)</label>
<input
type="time"
id="planStartTimeInput"
value="09:00"
class="w-full bg-slate-50 dark:bg-slate-950 border border-slate-200 dark:border-slate-800 rounded-xl px-3 py-2 text-xs text-slate-900 dark:text-white focus:outline-none focus:ring-2 focus:ring-indigo-500/40"
>
<div class="flex items-center justify-between mb-1">
<label class="block text-xs font-semibold text-slate-700 dark:text-slate-300">Starttijd (optioneel)</label>
<span id="planEndTimeBadge" class="text-[10px] text-indigo-600 dark:text-indigo-400 font-mono font-semibold"></span>
</div>
<div class="relative">
<input
type="time"
id="planStartTimeInput"
value="09:00"
oninput="updatePlanEndTimeBadge()"
class="w-full bg-slate-50 dark:bg-slate-950 border border-slate-200 dark:border-slate-800 rounded-xl px-3 py-2 text-xs font-mono text-slate-900 dark:text-white focus:outline-none focus:ring-2 focus:ring-indigo-500/40"
>
</div>
</div>
<div>
<label class="block text-xs font-bold text-slate-700 dark:text-slate-300 mb-1">Verwachte Duur</label>
<select id="planDurationInput" class="w-full bg-slate-50 dark:bg-slate-950 border border-slate-200 dark:border-slate-800 rounded-xl px-3 py-2 text-xs text-slate-900 dark:text-white focus:outline-none focus:ring-2 focus:ring-indigo-500/40 cursor-pointer">
<label class="block text-xs font-semibold text-slate-700 dark:text-slate-300 mb-1">Verwachte Duur</label>
<select id="planDurationInput" onchange="updatePlanEndTimeBadge()" class="w-full bg-slate-50 dark:bg-slate-950 border border-slate-200 dark:border-slate-800 rounded-xl px-3 py-2 text-xs text-slate-900 dark:text-white focus:outline-none focus:ring-2 focus:ring-indigo-500/40 cursor-pointer">
<option value="15">15 minuten</option>
<option value="30">30 minuten</option>
<option value="45" selected>45 minuten</option>
<option value="60">1 uur (60 min)</option>
@@ -573,8 +636,54 @@ $boardsJson = json_encode($boards, JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_QUOT
</div>
</div>
<!-- Modal: Waarschuwing Buiten Werktijden (Jira) -->
<div id="outsideWorkhoursModalJira" onclick="if(event.target === this) closeOutsideWorkhoursModalJira('CANCEL')" class="fixed inset-0 z-50 bg-slate-950/70 backdrop-blur-sm hidden items-center justify-center p-4">
<div class="bg-white dark:bg-slate-900 border border-amber-500/30 rounded-2xl w-full max-w-md shadow-2xl p-6 space-y-4">
<div class="flex items-start space-x-3">
<div class="w-10 h-10 rounded-2xl bg-amber-500/10 border border-amber-500/20 text-amber-500 flex items-center justify-center flex-shrink-0">
<i data-lucide="alert-triangle" class="w-5 h-5"></i>
</div>
<div class="min-w-0 flex-1">
<h3 class="text-base font-bold text-slate-900 dark:text-white leading-tight">
Let op! Je plant buiten werktijden
</h3>
<p class="text-xs text-slate-500 dark:text-slate-400 mt-1">
Het ingeplande tijdblok valt buiten de gezette werktijden.
</p>
</div>
<button type="button" onclick="closeOutsideWorkhoursModalJira('CANCEL')" class="p-1 text-slate-400 hover:text-slate-600 dark:hover:text-slate-200 rounded-lg transition cursor-pointer flex-shrink-0">
<i data-lucide="x" class="w-4 h-4"></i>
</button>
</div>
<div class="bg-amber-50 dark:bg-amber-950/20 border border-amber-200 dark:border-amber-800/40 rounded-xl p-3.5 text-xs text-amber-800 dark:text-amber-300 space-y-1.5">
<div class="flex items-center justify-between text-[11px] font-semibold text-amber-900 dark:text-amber-200">
<span>Gekozen tijdblok:</span>
<span id="outsideWorkhoursSelectedSlotJira" class="font-mono font-bold"></span>
</div>
<div class="flex items-center justify-between text-[11px] text-amber-700 dark:text-amber-400">
<span>Gezette werktijden:</span>
<span id="outsideWorkhoursBoundsJira" class="font-mono font-semibold">08:30 – 17:00</span>
</div>
<p class="text-[11px] text-amber-700/80 dark:text-amber-400/80 pt-1 border-t border-amber-200/60 dark:border-amber-800/30">
Kies <strong>Nee</strong> om het item automatisch in te plannen in het eerstvolgende open blok op de volgende werkdag, of kies <strong>Ja</strong> om toch nu buiten werktijd te plannen.
</p>
</div>
<div class="flex items-center space-x-2.5 pt-2">
<button type="button" id="btnOutsideWorkhoursNoJira" onclick="closeOutsideWorkhoursModalJira('NEE')" class="flex-1 px-4 py-2.5 rounded-xl bg-indigo-600 hover:bg-indigo-500 text-white font-bold text-xs transition shadow-xs flex items-center justify-center space-x-1.5 cursor-pointer">
<i data-lucide="calendar-arrow-up" class="w-4 h-4"></i>
<span>Nee (Volgende werkdag)</span>
</button>
<button type="button" id="btnOutsideWorkhoursYesJira" onclick="closeOutsideWorkhoursModalJira('JA')" class="px-4 py-2.5 rounded-xl bg-slate-100 hover:bg-slate-200 dark:bg-slate-800 dark:hover:bg-slate-700 text-slate-700 dark:text-slate-200 font-semibold text-xs transition cursor-pointer">
<span>Ja, toch inplannen</span>
</button>
</div>
</div>
</div>
<!-- MODAL 2: Toevoegen aan Kanban Bord (board.php) -->
<div id="boardTaskModal" class="fixed inset-0 z-50 bg-slate-950/70 backdrop-blur-sm hidden items-center justify-center p-4 overflow-y-auto">
<div id="boardTaskModal" onclick="if(event.target === this) closeBoardModal()" class="fixed inset-0 z-50 bg-slate-950/70 backdrop-blur-sm hidden items-center justify-center p-4 overflow-y-auto">
<div class="bg-white dark:bg-slate-900 border border-slate-200 dark:border-slate-800 rounded-2xl w-full max-w-md shadow-2xl p-6 transition transform">
<div class="flex items-center justify-between pb-4 border-b border-slate-100 dark:border-slate-800">
<div class="flex items-center space-x-2.5">
@@ -1399,12 +1508,131 @@ function renderTickets(tickets) {
}
// Modal 1: Plan in Dagplanner
let _activeJiraTicket = null;
let _outsideWorkhoursPendingJira = null;
function togglePlanTicketDesc() {
const container = document.getElementById('planTicketDescContainer');
const chevron = document.getElementById('planTicketDescChevron');
const text = document.getElementById('planTicketDescToggleText');
if (!container) return;
if (container.classList.contains('hidden')) {
container.classList.remove('hidden');
if (chevron) chevron.classList.add('rotate-180');
if (text) text.textContent = 'Verberg ticket inhoud';
} else {
container.classList.add('hidden');
if (chevron) chevron.classList.remove('rotate-180');
if (text) text.textContent = 'Toon ticket inhoud';
}
if (typeof lucide !== 'undefined') lucide.createIcons();
}
function clearPlanTaskTitle() {
const input = document.getElementById('planTaskTitleInput');
if (input) {
input.value = '';
input.focus();
}
}
function updatePlanEndTimeBadge() {
const startInput = document.getElementById('planStartTimeInput');
const durationInput = document.getElementById('planDurationInput');
const badge = document.getElementById('planEndTimeBadge');
if (!startInput || !durationInput || !badge) return;
const startVal = (startInput.value || '').trim();
const duration = parseInt(durationInput.value || '30', 10);
if (!startVal || !startVal.includes(':') || isNaN(duration)) {
badge.innerText = '';
return;
}
const [hStr, mStr] = startVal.split(':');
const totalMinutes = (parseInt(hStr, 10) * 60 + parseInt(mStr, 10)) + duration;
const endH = Math.floor((totalMinutes / 60) % 24);
const endM = totalMinutes % 60;
const endStr = `${String(endH).padStart(2, '0')}:${String(endM).padStart(2, '0')}`;
badge.innerText = `(tot ${endStr})`;
}
async function planInNextOpenBlockJira(startFromNextWorkday = false) {
const durationInput = document.getElementById('planDurationInput');
const duration = parseInt(durationInput?.value || '45', 10);
const btn = document.getElementById('btnJiraPlanOpenBlock');
const origHtml = btn ? btn.innerHTML : '';
if (btn) {
btn.disabled = true;
btn.innerHTML = `<i data-lucide="loader-2" class="w-3.5 h-3.5 animate-spin"></i><span>Zoeken...</span>`;
if (typeof lucide !== 'undefined') lucide.createIcons();
}
try {
let url = `/api/planner.php?action=find_next_free_slot&duration=${duration}`;
if (startFromNextWorkday) {
url += '&start_from_next_workday=1';
}
const resp = await fetch(url);
const data = await resp.json();
if (data.success && data.slot) {
const slot = data.slot;
const dateInput = document.getElementById('planDateInput');
const startInput = document.getElementById('planStartTimeInput');
if (dateInput) dateInput.value = slot.date;
if (startInput) startInput.value = slot.start;
updatePlanEndTimeBadge();
const notice = document.getElementById('jiraPlanOpenBlockNotice');
const noticeText = document.getElementById('jiraPlanOpenBlockNoticeText');
if (notice && noticeText) {
const formattedDate = new Date(slot.date + 'T00:00:00').toLocaleDateString('nl-NL', { weekday: 'short', day: 'numeric', month: 'short' });
noticeText.innerText = `Vrij blok gevonden: ${formattedDate} om ${slot.start} – ${slot.end} (${duration} min)`;
notice.classList.remove('hidden');
}
} else {
alert(data.error || 'Geen beschikbaar tijdblok gevonden binnen de komende 14 dagen.');
}
} catch (err) {
alert('Netwerkfout bij zoeken van open blok: ' + err.message);
} finally {
if (btn) {
btn.disabled = false;
btn.innerHTML = origHtml;
if (typeof lucide !== 'undefined') lucide.createIcons();
}
}
}
function closeOutsideWorkhoursModalJira(choice) {
const modal = document.getElementById('outsideWorkhoursModalJira');
if (modal) modal.classList.add('hidden');
if (choice === 'JA') {
if (_outsideWorkhoursPendingJira) {
_outsideWorkhoursPendingJira.bypassWorkhours = true;
submitPlanTask(null, _outsideWorkhoursPendingJira);
}
} else if (choice === 'NEE') {
_outsideWorkhoursPendingJira = null;
planInNextOpenBlockJira(true);
} else {
_outsideWorkhoursPendingJira = null;
}
}
function openPlanModal(item) {
const ticket = (typeof item === 'string')
? (ticketStore.get(item) || (jiraData.assigned_issues || []).concat(jiraData.team_issues || []).find(t => t.key === item))
: item;
if (!ticket) return;
_activeJiraTicket = ticket;
document.getElementById('planIssueKey').value = ticket.key;
document.getElementById('planIssueSummary').value = ticket.summary;
document.getElementById('planIssueUrl').value = ticket.url;
@@ -1413,6 +1641,27 @@ function openPlanModal(item) {
document.getElementById('planTicketProject').innerText = '• ' + (ticket.project || 'Project');
document.getElementById('planTicketSummaryText').innerText = ticket.summary;
// Vul aanpasbare taaktitel in
const titleInput = document.getElementById('planTaskTitleInput');
if (titleInput) {
titleInput.value = `Oppakken Jira item ${ticket.key} - ${ticket.summary}`;
}
// Vul ticket omschrijving preview
const descContainer = document.getElementById('planTicketDescContainer');
if (descContainer) {
descContainer.textContent = ticket.description || 'Geen gedetailleerde omschrijving beschikbaar in Jira.';
descContainer.classList.add('hidden');
}
const chevron = document.getElementById('planTicketDescChevron');
if (chevron) chevron.classList.remove('rotate-180');
const descText = document.getElementById('planTicketDescToggleText');
if (descText) descText.textContent = 'Toon ticket inhoud';
// Verberg open blok notice
const notice = document.getElementById('jiraPlanOpenBlockNotice');
if (notice) notice.classList.add('hidden');
// Story Points → Duur conversie (1 SP = 30 minuten, configurabel)
const spBadge = document.getElementById('planSpBadge');
const spHint = document.getElementById('planSpHint');
@@ -1420,7 +1669,7 @@ function openPlanModal(item) {
const sp = parseFloat(ticket.story_points);
if (!isNaN(sp) && sp > 0) {
const spMinutes = Math.round(sp * 30); // 1 SP = 30 minuten
const closestDuration = [30, 45, 60, 90, 120].reduce((prev, curr) =>
const closestDuration = [15, 30, 45, 60, 90, 120].reduce((prev, curr) =>
Math.abs(curr - spMinutes) < Math.abs(prev - spMinutes) ? curr : prev
);
const durationSelect = document.getElementById('planDurationInput');
@@ -1449,6 +1698,8 @@ function openPlanModal(item) {
if (r.value === focizPrio) r.checked = true;
}
updatePlanEndTimeBadge();
const modal = document.getElementById('planTaskModal');
modal.classList.remove('hidden');
modal.classList.add('flex');
@@ -1461,19 +1712,18 @@ function closePlanModal() {
modal.classList.remove('flex');
}
async function submitPlanTask(e) {
e.preventDefault();
async function submitPlanTask(e, options = {}) {
if (e) e.preventDefault();
const btn = document.getElementById('planSubmitBtn');
const oldBtnText = btn.innerHTML;
btn.disabled = true;
btn.innerHTML = `<i data-lucide="loader-2" class="w-3.5 h-3.5 animate-spin"></i><span>Inplannen...</span>`;
if (typeof lucide !== 'undefined') lucide.createIcons();
const oldBtnText = btn ? btn.innerHTML : '';
const key = document.getElementById('planIssueKey').value;
const summary = document.getElementById('planIssueSummary').value;
const customTitle = (document.getElementById('planTaskTitleInput')?.value || '').trim();
const url = document.getElementById('planIssueUrl').value;
const planDate = document.getElementById('planDateInput').value;
const timeBlockStart = document.getElementById('planStartTimeInput').value;
const timeBlockStart = (document.getElementById('planStartTimeInput')?.value || '').trim();
const durationMinutes = parseInt(document.getElementById('planDurationInput').value, 10);
let priority = 'P2';
@@ -1482,6 +1732,45 @@ async function submitPlanTask(e) {
if (r.checked) priority = r.value;
}
// Controleer of de geplande tijd buiten de gezette werktijden valt (standaard 08:30 - 17:00)
if (!options.bypassWorkhours && timeBlockStart && timeBlockStart.includes(':')) {
const [shStr, smStr] = timeBlockStart.split(':');
const startMin = parseInt(shStr, 10) * 60 + parseInt(smStr, 10);
const endMin = startMin + durationMinutes;
const workStartMin = 8 * 60 + 30; // 08:30
const workEndMin = 17 * 60; // 17:00
if (startMin < workStartMin || endMin > workEndMin) {
_outsideWorkhoursPendingJira = { key, summary, title: customTitle, url, planDate, timeBlockStart, durationMinutes, priority };
const endH = Math.floor((endMin / 60) % 24);
const endM = endMin % 60;
const endStr = `${String(endH).padStart(2, '0')}:${String(endM).padStart(2, '0')}`;
const slotEl = document.getElementById('outsideWorkhoursSelectedSlotJira');
if (slotEl) slotEl.innerText = `${timeBlockStart} – ${endStr} (${durationMinutes} min)`;
const outsideModal = document.getElementById('outsideWorkhoursModalJira');
if (outsideModal) {
outsideModal.classList.remove('hidden');
outsideModal.classList.add('flex');
setTimeout(() => {
const btnNo = document.getElementById('btnOutsideWorkhoursNoJira');
if (btnNo) btnNo.focus();
}, 50);
if (typeof lucide !== 'undefined') lucide.createIcons();
}
return;
}
}
if (btn) {
btn.disabled = true;
btn.innerHTML = `<i data-lucide="loader-2" class="w-3.5 h-3.5 animate-spin"></i><span>Inplannen...</span>`;
if (typeof lucide !== 'undefined') lucide.createIcons();
}
try {
const response = await fetch('/api/integrations.php?action=plan_jira_task', {
method: 'POST',
@@ -1489,6 +1778,7 @@ async function submitPlanTask(e) {
body: JSON.stringify({
key,
summary,
title: customTitle || `[${key}] ${summary}`,
url,
plan_date: planDate,
time_block_start: timeBlockStart,
@@ -1512,9 +1802,11 @@ async function submitPlanTask(e) {
} catch (err) {
alert('Netwerkfout: ' + err.message);
} finally {
btn.disabled = false;
btn.innerHTML = oldBtnText;
if (typeof lucide !== 'undefined') lucide.createIcons();
if (btn) {
btn.disabled = false;
btn.innerHTML = oldBtnText;
if (typeof lucide !== 'undefined') lucide.createIcons();
}
}
}
@@ -1681,11 +1973,23 @@ function escapeHtml(str) {
.replace(/'/g, '&#039;');
}
// Sluit modals bij Escape toets
// Toetsenbordnavigatie voor alle modals (Escape & Enter)
document.addEventListener('keydown', function(e) {
if (e.key === 'Escape') {
const outsideModal = document.getElementById('outsideWorkhoursModalJira');
if (outsideModal && !outsideModal.classList.contains('hidden')) {
closeOutsideWorkhoursModalJira('CANCEL');
return;
}
closePlanModal();
closeBoardModal();
} else if (e.key === 'Enter') {
const outsideModal = document.getElementById('outsideWorkhoursModalJira');
if (outsideModal && !outsideModal.classList.contains('hidden')) {
e.preventDefault();
closeOutsideWorkhoursModalJira('NEE');
return;
}
}
});