Large refactor
This commit is contained in:
+166
-10
@@ -1771,7 +1771,7 @@ if ($isAjaxRequest) {
|
||||
<?php endif; ?>
|
||||
|
||||
<!-- Modal: Jira Ticket Inplannen (met datum, tijdsuggestie & aanpasbare titel) -->
|
||||
<div id="jiraPlanModal" class="fixed inset-0 z-50 bg-slate-950/70 backdrop-blur-sm hidden items-center justify-center p-4">
|
||||
<div id="jiraPlanModal" onclick="if(event.target === this) closeJiraPlanModal()" class="fixed inset-0 z-50 bg-slate-950/70 backdrop-blur-sm hidden items-center justify-center p-4">
|
||||
<div class="fociz-card p-6 w-full max-w-lg max-h-[90vh] overflow-y-auto shadow-2xl space-y-4">
|
||||
<div class="flex items-center justify-between pb-3 border-b border-slate-100 dark:border-slate-800/80">
|
||||
<div class="flex items-center space-x-2.5">
|
||||
@@ -1825,21 +1825,46 @@ if ($isAjaxRequest) {
|
||||
<input type="text" id="jiraPlanCustomTitle" class="w-full bg-white dark:bg-[#0c0f17] border border-slate-200 dark:border-slate-800 rounded-xl px-3 py-2 text-sm text-slate-900 dark:text-white focus:outline-none focus:ring-1 focus:ring-indigo-500 placeholder-slate-400" placeholder="Oppakken Jira item ...">
|
||||
</div>
|
||||
|
||||
<!-- Datum & Tijdblok met slimme "Plan in open blok" actieknop -->
|
||||
<div class="space-y-2 pt-1 border-t border-slate-100 dark:border-slate-800/80">
|
||||
<div class="flex items-center justify-between">
|
||||
<span class="text-[11px] font-bold text-slate-700 dark:text-slate-300">Datum & Tijdblok</span>
|
||||
<button type="button" onclick="planJiraInNextOpenBlock()" 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>
|
||||
|
||||
<!-- Succes / statusmelding wanneer een open blok gevonden is -->
|
||||
<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">
|
||||
<i data-lucide="x" class="w-3 h-3"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Datum & Starttijd -->
|
||||
<div class="grid grid-cols-1 sm:grid-cols-2 gap-3">
|
||||
<div>
|
||||
<label for="jiraPlanDate" class="block text-[11px] font-bold text-slate-700 dark:text-slate-300 mb-1.5">
|
||||
Datum
|
||||
</label>
|
||||
<input type="date" id="jiraPlanDate" class="w-full bg-white dark:bg-[#0c0f17] border border-slate-200 dark:border-slate-800 rounded-xl px-3 py-2 text-sm text-slate-900 dark:text-white focus:outline-none focus:ring-1 focus:ring-indigo-500">
|
||||
<input type="date" id="jiraPlanDate" class="w-full bg-white dark:bg-[#0c0f17] border border-slate-200 dark:border-slate-800 rounded-xl px-3 py-2 text-sm text-slate-900 dark:text-white focus:outline-none focus:ring-1 focus:ring-indigo-500" onchange="updateJiraPlanEndTimeDisplay()">
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label for="jiraPlanStartTime" class="block text-[11px] font-bold text-slate-700 dark:text-slate-300 mb-1.5">
|
||||
Starttijd
|
||||
</label>
|
||||
<div class="flex items-center justify-between mb-1.5">
|
||||
<label for="jiraPlanStartTime" class="block text-[11px] font-bold text-slate-700 dark:text-slate-300">
|
||||
Starttijd
|
||||
</label>
|
||||
<span id="jiraPlanEndTimeBadge" class="text-[10px] font-mono text-indigo-600 dark:text-indigo-400 font-semibold"></span>
|
||||
</div>
|
||||
<div class="relative flex items-center">
|
||||
<input type="time" id="jiraPlanStartTime" class="w-full bg-white dark:bg-[#0c0f17] border border-slate-200 dark:border-slate-800 rounded-xl pl-3 pr-8 py-2 text-sm text-slate-900 dark:text-white focus:outline-none focus:ring-1 focus:ring-indigo-500">
|
||||
<input type="time" id="jiraPlanStartTime" class="w-full bg-white dark:bg-[#0c0f17] border border-slate-200 dark:border-slate-800 rounded-xl pl-3 pr-8 py-2 text-sm text-slate-900 dark:text-white focus:outline-none focus:ring-1 focus:ring-indigo-500" oninput="updateJiraPlanEndTimeDisplay()">
|
||||
<button type="button" onclick="clearJiraPlanStartTime()" title="Starttijd wissen" class="absolute right-2 p-1 text-slate-400 hover:text-rose-500 dark:hover:text-rose-400 rounded transition cursor-pointer">
|
||||
<i data-lucide="x" class="w-3.5 h-3.5"></i>
|
||||
</button>
|
||||
@@ -1962,7 +1987,7 @@ if ($isAjaxRequest) {
|
||||
</div>
|
||||
|
||||
<!-- Modal: Taak & Tijdblok Bewerken (Vrij invullen van tijden & duur) -->
|
||||
<div id="editTaskModal" class="fixed inset-0 z-50 bg-slate-950/70 backdrop-blur-sm hidden items-center justify-center p-4">
|
||||
<div id="editTaskModal" onclick="if(event.target === this) closeEditTaskModal()" class="fixed inset-0 z-50 bg-slate-950/70 backdrop-blur-sm hidden items-center justify-center p-4">
|
||||
<div class="fociz-card p-6 w-full max-w-lg shadow-2xl space-y-4">
|
||||
<div class="flex items-center justify-between pb-3 border-b border-slate-100 dark:border-slate-800/80">
|
||||
<div class="flex items-center space-x-2.5">
|
||||
@@ -2085,7 +2110,7 @@ if ($isAjaxRequest) {
|
||||
</div>
|
||||
|
||||
<!-- Modal: Waarschuwing Buiten Werktijden -->
|
||||
<div id="outsideWorkhoursModal" class="fixed inset-0 z-50 bg-slate-950/70 backdrop-blur-sm hidden items-center justify-center p-4">
|
||||
<div id="outsideWorkhoursModal" onclick="if(event.target === this) closeOutsideWorkhoursModal('CANCEL')" class="fixed inset-0 z-50 bg-slate-950/70 backdrop-blur-sm hidden items-center justify-center p-4">
|
||||
<div class="fociz-card p-6 w-full max-w-md shadow-2xl space-y-4 border border-amber-500/30">
|
||||
<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">
|
||||
@@ -2131,7 +2156,7 @@ if ($isAjaxRequest) {
|
||||
</div>
|
||||
|
||||
<!-- Modal: Direct E-mail Inplannen als Dagtaak -->
|
||||
<div id="planEmailDirectModal" class="fixed inset-0 z-50 bg-slate-950/70 backdrop-blur-sm hidden items-center justify-center p-4">
|
||||
<div id="planEmailDirectModal" onclick="if(event.target === this) closePlanEmailDirectModal()" class="fixed inset-0 z-50 bg-slate-950/70 backdrop-blur-sm hidden items-center justify-center p-4">
|
||||
<div class="fociz-card p-6 w-full max-w-lg shadow-2xl space-y-4">
|
||||
<div class="flex items-center justify-between pb-3 border-b border-slate-100 dark:border-slate-800/80">
|
||||
<div class="flex items-center space-x-2.5">
|
||||
@@ -3343,6 +3368,11 @@ function clearJiraPlanStartTime() {
|
||||
input.value = '';
|
||||
input.focus();
|
||||
}
|
||||
const notice = document.getElementById('jiraPlanOpenBlockNotice');
|
||||
if (notice) notice.classList.add('hidden');
|
||||
if (typeof updateJiraPlanEndTimeDisplay === 'function') {
|
||||
updateJiraPlanEndTimeDisplay();
|
||||
}
|
||||
}
|
||||
|
||||
function toggleJiraPlanDescription() {
|
||||
@@ -3432,6 +3462,10 @@ function openJiraPlanModal(issueKey) {
|
||||
const suggestTime = targetDate === todayStr ? suggestNextFreeSlot() : '09:00';
|
||||
document.getElementById('jiraPlanStartTime').value = suggestTime;
|
||||
|
||||
const notice = document.getElementById('jiraPlanOpenBlockNotice');
|
||||
if (notice) notice.classList.add('hidden');
|
||||
updateJiraPlanEndTimeDisplay();
|
||||
|
||||
const modal = document.getElementById('jiraPlanModal');
|
||||
modal.classList.remove('hidden');
|
||||
modal.classList.add('flex');
|
||||
@@ -3445,16 +3479,96 @@ function closeJiraPlanModal() {
|
||||
_jiraPlanCurrentIssue = null;
|
||||
}
|
||||
|
||||
function updateJiraPlanEndTimeDisplay() {
|
||||
const startTimeInput = document.getElementById('jiraPlanStartTime');
|
||||
const badge = document.getElementById('jiraPlanEndTimeBadge');
|
||||
const durationInput = document.getElementById('jiraPlanDuration');
|
||||
if (!badge) return;
|
||||
|
||||
const startTime = startTimeInput ? startTimeInput.value.trim() : '';
|
||||
const duration = durationInput ? (parseInt(durationInput.value, 10) || 15) : 15;
|
||||
|
||||
if (!startTime) {
|
||||
badge.textContent = '';
|
||||
return;
|
||||
}
|
||||
|
||||
const parts = startTime.split(':').map(Number);
|
||||
if (parts.length < 2 || isNaN(parts[0]) || isNaN(parts[1])) {
|
||||
badge.textContent = '';
|
||||
return;
|
||||
}
|
||||
|
||||
const endTotalMin = parts[0] * 60 + parts[1] + duration;
|
||||
const endH = Math.floor(endTotalMin / 60);
|
||||
const endM = endTotalMin % 60;
|
||||
const endTimeStr = `${String(endH).padStart(2,'0')}:${String(endM).padStart(2,'0')}`;
|
||||
badge.textContent = `(tot ${endTimeStr})`;
|
||||
}
|
||||
|
||||
async function planJiraInNextOpenBlock() {
|
||||
const durationInput = document.getElementById('jiraPlanDuration');
|
||||
const duration = durationInput ? (parseInt(durationInput.value, 10) || 15) : 15;
|
||||
const btn = document.getElementById('btnJiraPlanOpenBlock');
|
||||
const notice = document.getElementById('jiraPlanOpenBlockNotice');
|
||||
const noticeText = document.getElementById('jiraPlanOpenBlockNoticeText');
|
||||
|
||||
let origBtnHtml = '';
|
||||
if (btn) {
|
||||
origBtnHtml = btn.innerHTML;
|
||||
btn.innerHTML = '<i data-lucide="loader-2" class="w-3.5 h-3.5 animate-spin text-white"></i><span>Zoeken...</span>';
|
||||
btn.disabled = true;
|
||||
}
|
||||
|
||||
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: duration
|
||||
})
|
||||
});
|
||||
const data = await res.json();
|
||||
|
||||
if (data.success && data.slot) {
|
||||
const slot = data.slot;
|
||||
const dateInput = document.getElementById('jiraPlanDate');
|
||||
if (dateInput) dateInput.value = slot.date;
|
||||
|
||||
const startInput = document.getElementById('jiraPlanStartTime');
|
||||
if (startInput) startInput.value = slot.start;
|
||||
|
||||
updateJiraPlanEndTimeDisplay();
|
||||
|
||||
if (notice && noticeText) {
|
||||
noticeText.textContent = `Open blok gevonden: ${slot.formatted_date} van ${slot.start} tot ${slot.end} (${slot.duration_minutes}m)`;
|
||||
notice.classList.remove('hidden');
|
||||
}
|
||||
} else {
|
||||
alert(data.error || 'Geen vrij blok gevonden binnen de gezette werktijden.');
|
||||
}
|
||||
} catch (err) {
|
||||
alert('Netwerkfout bij zoeken naar een open blok.');
|
||||
} finally {
|
||||
if (btn) {
|
||||
btn.innerHTML = origBtnHtml;
|
||||
btn.disabled = false;
|
||||
}
|
||||
if (typeof lucide !== 'undefined') lucide.createIcons();
|
||||
}
|
||||
}
|
||||
|
||||
function setJiraPlanDuration(minutes) {
|
||||
document.getElementById('jiraPlanDuration').value = minutes;
|
||||
document.querySelectorAll('.jira-plan-dur-btn').forEach(btn => {
|
||||
const dur = parseInt(btn.getAttribute('data-dur'), 10);
|
||||
if (dur === minutes) {
|
||||
btn.className = 'jira-plan-dur-btn px-2 py-1.5 rounded-xl border text-[11px] font-bold transition cursor-pointer bg-indigo-600 border-indigo-600 text-white';
|
||||
btn.className = 'jira-plan-dur-btn px-2 py-1.5 rounded-xl border text-[11px] font-bold transition cursor-pointer bg-indigo-600 border-indigo-600 text-white shadow-xs';
|
||||
} else {
|
||||
btn.className = 'jira-plan-dur-btn px-2 py-1.5 rounded-xl border text-[11px] font-bold transition cursor-pointer bg-white dark:bg-slate-900 border-slate-200 dark:border-slate-800 text-slate-700 dark:text-slate-300 hover:border-indigo-400';
|
||||
}
|
||||
});
|
||||
updateJiraPlanEndTimeDisplay();
|
||||
}
|
||||
|
||||
function suggestNextFreeSlot() {
|
||||
@@ -3873,6 +3987,10 @@ function promptOutsideWorkhoursModal(checkResult) {
|
||||
modal.classList.remove('hidden');
|
||||
modal.classList.add('flex');
|
||||
if (typeof lucide !== 'undefined') lucide.createIcons();
|
||||
setTimeout(() => {
|
||||
const noBtn = document.getElementById('btnOutsideWorkhoursNo');
|
||||
if (noBtn) noBtn.focus();
|
||||
}, 50);
|
||||
} else {
|
||||
const ok = confirm(`Let op! Je plant buiten werktijden (${checkResult.workdayStart} - ${checkResult.workdayEnd}).\n\nKlik OK (Ja) om toch zo in te plannen, of Annuleren (Nee) om automatisch naar de volgende werkdag te gaan.`);
|
||||
resolve(ok ? 'JA' : 'NEE');
|
||||
@@ -3913,6 +4031,44 @@ async function findSlotOnNextWorkday(duration, taskId = 0) {
|
||||
throw new Error(data.error || 'Geen vrij open blok gevonden op de volgende werkdag.');
|
||||
}
|
||||
|
||||
// Globale sneltoetsafhandeling voor modals (Escape om te sluiten, Enter voor actie)
|
||||
window.addEventListener('keydown', (e) => {
|
||||
if (e.key === 'Escape') {
|
||||
const outsideModal = document.getElementById('outsideWorkhoursModal');
|
||||
if (outsideModal && !outsideModal.classList.contains('hidden')) {
|
||||
closeOutsideWorkhoursModal('CANCEL');
|
||||
return;
|
||||
}
|
||||
const jiraModal = document.getElementById('jiraPlanModal');
|
||||
if (jiraModal && !jiraModal.classList.contains('hidden')) {
|
||||
closeJiraPlanModal();
|
||||
return;
|
||||
}
|
||||
const editModal = document.getElementById('editTaskModal');
|
||||
if (editModal && !editModal.classList.contains('hidden')) {
|
||||
closeEditTaskModal();
|
||||
return;
|
||||
}
|
||||
const emailModal = document.getElementById('planEmailDirectModal');
|
||||
if (emailModal && !emailModal.classList.contains('hidden')) {
|
||||
closePlanEmailDirectModal();
|
||||
return;
|
||||
}
|
||||
const detailModal = document.getElementById('jiraTicketDetailModal');
|
||||
if (detailModal && !detailModal.classList.contains('hidden')) {
|
||||
closeJiraTicketDetailModal();
|
||||
return;
|
||||
}
|
||||
} else if (e.key === 'Enter') {
|
||||
const outsideModal = document.getElementById('outsideWorkhoursModal');
|
||||
if (outsideModal && !outsideModal.classList.contains('hidden')) {
|
||||
e.preventDefault();
|
||||
closeOutsideWorkhoursModal('NEE');
|
||||
return;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Wisselt de actieve plannerdatum geruisloos via AJAX
|
||||
* Behoudt de huidige schermhoogte (scrollpositie) en voorkomt flitsen/herladen
|
||||
|
||||
Reference in New Issue
Block a user