+ Het ingeplande tijdblok valt buiten de gezette werktijden.
+
+
+
+
+
+
+
+ Gekozen tijdblok:
+
+
+
+ Gezette werktijden:
+
+
+
+ Kies Nee om het item automatisch in te plannen in het eerstvolgende open blok op de volgende werkdag, of kies Ja om toch nu buiten werktijd te plannen.
+
+
+
+
+
+
+
+
+
+
@@ -3412,16 +3458,27 @@ function setJiraPlanDuration(minutes) {
}
function suggestNextFreeSlot() {
- // Zoek het volgende ronde kwartier na het huidige moment
const now = new Date();
let h = now.getHours();
let m = now.getMinutes();
- // Rond op naar boven naar het volgende kwartier
m = Math.ceil((m + 1) / 15) * 15;
if (m >= 60) { h += 1; m = 0; }
- // Begrenzen op werktijd 08:00-18:00
- if (h < 8) { h = 8; m = 0; }
- if (h >= 18) { h = 8; m = 0; } // Morgen → val terug op 08:00
+
+ const wStart = (typeof FOCIZ_WORKDAY_START !== 'undefined' && FOCIZ_WORKDAY_START) ? FOCIZ_WORKDAY_START : '08:30';
+ const wEnd = (typeof FOCIZ_WORKDAY_END !== 'undefined' && FOCIZ_WORKDAY_END) ? FOCIZ_WORKDAY_END : '17:00';
+ const [startH, startM] = wStart.split(':').map(Number);
+ const [endH, endM] = wEnd.split(':').map(Number);
+
+ const curMin = h * 60 + m;
+ const startMin = startH * 60 + startM;
+ const endMin = endH * 60 + endM;
+
+ if (curMin < startMin) {
+ return `${String(startH).padStart(2,'0')}:${String(startM).padStart(2,'0')}`;
+ }
+ if (curMin >= endMin) {
+ return `${String(startH).padStart(2,'0')}:${String(startM).padStart(2,'0')}`;
+ }
return `${String(h).padStart(2,'0')}:${String(m).padStart(2,'0')}`;
}
@@ -3434,16 +3491,44 @@ async function submitJiraPlanModal() {
const finalTitle = customTitle || `Oppakken Jira item ${issue.key}`;
const dateInput = document.getElementById('jiraPlanDate');
- const planDate = (dateInput && dateInput.value) ? dateInput.value : ((typeof activePlannerDate !== 'undefined' && activePlannerDate) ? activePlannerDate : new Date().toISOString().slice(0, 10));
+ let planDate = (dateInput && dateInput.value) ? dateInput.value : ((typeof activePlannerDate !== 'undefined' && activePlannerDate) ? activePlannerDate : new Date().toISOString().slice(0, 10));
const duration = parseInt(document.getElementById('jiraPlanDuration').value, 10) || 15;
- const startTime = document.getElementById('jiraPlanStartTime').value.trim();
+ let startTime = document.getElementById('jiraPlanStartTime').value.trim();
const priority = document.getElementById('jiraPlanPriority').value;
+ // Controleer of de ingeplande tijd buiten de gezette werktijden valt
+ if (startTime) {
+ const [hh, mm] = startTime.split(':').map(Number);
+ const endMin = hh * 60 + mm + duration;
+ const endTime = `${String(Math.floor(endMin / 60)).padStart(2,'0')}:${String(endMin % 60).padStart(2,'0')}`;
+
+ const check = checkWorkdayBounds(planDate, startTime, endTime, duration);
+ if (check.isOutside) {
+ const answer = await promptOutsideWorkhoursModal(check);
+ if (answer === 'CANCEL') {
+ return;
+ }
+ if (answer === 'NEE') {
+ // Automatisch naar volgende werkdag met eerder gezette voorwaarden
+ try {
+ const nextSlot = await findSlotOnNextWorkday(duration, 0);
+ planDate = nextSlot.date;
+ startTime = nextSlot.start;
+ } catch (err) {
+ alert(err.message || 'Kon geen open blok vinden op de volgende werkdag.');
+ return;
+ }
+ }
+ }
+ }
+
const btn = document.getElementById('btnSubmitJiraPlan');
- const origHtml = btn.innerHTML;
- btn.innerHTML = 'Inplannen...';
- btn.disabled = true;
+ const origHtml = btn ? btn.innerHTML : '';
+ if (btn) {
+ btn.innerHTML = 'Inplannen...';
+ btn.disabled = true;
+ }
try {
const payload = {
@@ -3478,13 +3563,17 @@ async function submitJiraPlanModal() {
}
} else {
alert(data.error || 'Kon ticket niet inplannen');
- btn.innerHTML = origHtml;
- btn.disabled = false;
+ if (btn) {
+ btn.innerHTML = origHtml;
+ btn.disabled = false;
+ }
}
} catch(e) {
alert('Netwerkfout bij inplannen');
- btn.innerHTML = origHtml;
- btn.disabled = false;
+ if (btn) {
+ btn.innerHTML = origHtml;
+ btn.disabled = false;
+ }
}
}
@@ -3530,6 +3619,31 @@ async function submitPlanEmailDirect() {
return;
}
+ let planDate = activePlannerDate;
+ if (startTime) {
+ const [hh, mm] = startTime.split(':').map(Number);
+ const endMin = hh * 60 + mm + duration;
+ const endTime = `${String(Math.floor(endMin / 60)).padStart(2,'0')}:${String(endMin % 60).padStart(2,'0')}`;
+
+ const check = checkWorkdayBounds(planDate, startTime, endTime, duration);
+ if (check.isOutside) {
+ const answer = await promptOutsideWorkhoursModal(check);
+ if (answer === 'CANCEL') {
+ return;
+ }
+ if (answer === 'NEE') {
+ try {
+ const nextSlot = await findSlotOnNextWorkday(duration, 0);
+ planDate = nextSlot.date;
+ startTime = nextSlot.start;
+ } catch (err) {
+ alert(err.message || 'Kon geen open blok vinden op de volgende werkdag.');
+ return;
+ }
+ }
+ }
+ }
+
const btn = document.getElementById('btnSubmitPlanEmailDirect');
const origHtml = btn.innerHTML;
btn.innerText = 'Inplannen...';
@@ -3546,13 +3660,13 @@ async function submitPlanEmailDirect() {
duration_minutes: duration,
time_block_start: startTime,
sync_outlook: syncOutlook,
- date: activePlannerDate
+ date: planDate
})
});
const data = await res.json();
if (data.success) {
closePlanEmailDirectModal();
- switchPlannerDate(activePlannerDate, null, false);
+ switchPlannerDate(planDate, null, false);
} else {
alert(data.error || 'Kon e-mail niet inplannen');
}
@@ -3669,6 +3783,135 @@ let weekStartDate = '= $weekStart ?>';
let weekEndDate = '= $weekEnd ?>';
let currentSyncHash = '= $initialSyncHash ?? '' ?>';
let isDateSwitching = false;
+const FOCIZ_WORKDAY_START = '= $workdayStartStr ?>';
+const FOCIZ_WORKDAY_END = '= $workdayEndStr ?>';
+
+/**
+ * Controleert of een gepland tijdstip buiten de gezette werktijden of in het weekend valt
+ */
+function checkWorkdayBounds(planDate, startTime, endTime, duration) {
+ if (!startTime || !startTime.trim()) {
+ return { isOutside: false };
+ }
+
+ const start = startTime.trim();
+ let [sH, sM] = start.split(':').map(Number);
+ if (isNaN(sH) || isNaN(sM)) return { isOutside: false };
+
+ let dur = parseInt(duration, 10);
+ if (isNaN(dur) || dur <= 0) dur = 30;
+
+ let end = (endTime && endTime.trim()) ? endTime.trim() : '';
+ let eH, eM;
+ if (end) {
+ [eH, eM] = end.split(':').map(Number);
+ }
+ if (isNaN(eH) || isNaN(eM)) {
+ const totalEndMin = sH * 60 + sM + dur;
+ eH = Math.floor(totalEndMin / 60);
+ eM = totalEndMin % 60;
+ end = `${String(eH).padStart(2,'0')}:${String(eM).padStart(2,'0')}`;
+ }
+
+ const startMin = sH * 60 + sM;
+ const endMin = eH * 60 + eM;
+
+ const wStart = (typeof FOCIZ_WORKDAY_START !== 'undefined' && FOCIZ_WORKDAY_START) ? FOCIZ_WORKDAY_START : '08:30';
+ const wEnd = (typeof FOCIZ_WORKDAY_END !== 'undefined' && FOCIZ_WORKDAY_END) ? FOCIZ_WORKDAY_END : '17:00';
+
+ const [wsH, wsM] = wStart.split(':').map(Number);
+ const [weH, weM] = wEnd.split(':').map(Number);
+ const wStartMin = wsH * 60 + wsM;
+ const wEndMin = weH * 60 + weM;
+
+ let isOutside = false;
+ let reason = '';
+
+ if (planDate) {
+ const d = new Date(planDate + 'T12:00:00');
+ const dayOfWeek = d.getDay(); // 0 = Zo, 6 = Za
+ if (dayOfWeek === 0 || dayOfWeek === 6) {
+ isOutside = true;
+ reason = 'Het gekozen tijdstip valt in het weekend.';
+ }
+ }
+
+ if (startMin < wStartMin || endMin > wEndMin || startMin >= wEndMin) {
+ isOutside = true;
+ reason = `Het gekozen tijdstip (${start} - ${end}) valt buiten de gezette werktijden (${wStart} - ${wEnd}).`;
+ }
+
+ return {
+ isOutside,
+ reason,
+ start,
+ end,
+ duration: dur,
+ workdayStart: wStart,
+ workdayEnd: wEnd,
+ date: planDate
+ };
+}
+
+let _outsideWorkhoursResolver = null;
+
+function promptOutsideWorkhoursModal(checkResult) {
+ return new Promise((resolve) => {
+ _outsideWorkhoursResolver = resolve;
+ const modal = document.getElementById('outsideWorkhoursModal');
+ const selectedEl = document.getElementById('outsideWorkhoursSelectedSlot');
+ const boundsEl = document.getElementById('outsideWorkhoursBounds');
+
+ if (selectedEl) {
+ selectedEl.textContent = `${checkResult.start} - ${checkResult.end} (${checkResult.duration}m)`;
+ }
+ if (boundsEl) {
+ boundsEl.textContent = `${checkResult.workdayStart} tot ${checkResult.workdayEnd}`;
+ }
+
+ if (modal) {
+ modal.classList.remove('hidden');
+ modal.classList.add('flex');
+ if (typeof lucide !== 'undefined') lucide.createIcons();
+ } 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');
+ }
+ });
+}
+
+function closeOutsideWorkhoursModal(choice) {
+ const modal = document.getElementById('outsideWorkhoursModal');
+ if (modal) {
+ modal.classList.add('hidden');
+ modal.classList.remove('flex');
+ }
+ if (typeof _outsideWorkhoursResolver === 'function') {
+ const res = _outsideWorkhoursResolver;
+ _outsideWorkhoursResolver = null;
+ res(choice);
+ }
+}
+
+/**
+ * Zoekt automatisch het eerstvolgende open kwartierblok op de volgende werkdag
+ */
+async function findSlotOnNextWorkday(duration, taskId = 0) {
+ 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,
+ task_id: taskId,
+ start_from_next_workday: true
+ })
+ });
+ const data = await res.json();
+ if (data.success && data.slot) {
+ return data.slot;
+ }
+ throw new Error(data.error || 'Geen vrij open blok gevonden op de volgende werkdag.');
+}
/**
* Wisselt de actieve plannerdatum geruisloos via AJAX
@@ -3964,6 +4207,28 @@ async function submitNewTask() {
return;
}
+ let planDate = activePlannerDate;
+ if (time) {
+ const check = checkWorkdayBounds(planDate, time, endTime, duration);
+ if (check.isOutside) {
+ const answer = await promptOutsideWorkhoursModal(check);
+ if (answer === 'CANCEL') {
+ return;
+ }
+ if (answer === 'NEE') {
+ try {
+ const nextSlot = await findSlotOnNextWorkday(duration, 0);
+ planDate = nextSlot.date;
+ time = nextSlot.start;
+ endTime = nextSlot.end;
+ } catch (err) {
+ alert(err.message || 'Kon geen open blok vinden op de volgende werkdag.');
+ return;
+ }
+ }
+ }
+ }
+
try {
const res = await fetch('/api/planner.php?action=add_task', {
method: 'POST',
@@ -3975,7 +4240,7 @@ async function submitNewTask() {
time_block_end: endTime,
duration_minutes: duration,
sync_to_outlook: syncOutlook,
- date: activePlannerDate
+ date: planDate
})
});
const data = await res.json();
@@ -3986,7 +4251,7 @@ async function submitNewTask() {
toggleAddTaskForm();
const titleInput = document.getElementById('newTaskTitle');
if (titleInput) titleInput.value = '';
- switchPlannerDate(activePlannerDate, null, false);
+ switchPlannerDate(planDate, null, false);
} else {
alert(data.error || 'Fout bij opslaan van de taak');
}
@@ -4217,9 +4482,9 @@ async function submitEditTask() {
const title = document.getElementById('editTaskTitle').value.trim();
const priority = document.getElementById('editTaskPriority').value;
const dateInput = document.getElementById('editTaskDate');
- const taskDate = (dateInput && dateInput.value) ? dateInput.value.trim() : (typeof activePlannerDate !== 'undefined' ? activePlannerDate : new Date().toISOString().slice(0, 10));
- const startTime = document.getElementById('editTaskStartTime').value.trim();
- const endTime = document.getElementById('editTaskEndTime').value.trim();
+ let taskDate = (dateInput && dateInput.value) ? dateInput.value.trim() : (typeof activePlannerDate !== 'undefined' ? activePlannerDate : new Date().toISOString().slice(0, 10));
+ let startTime = document.getElementById('editTaskStartTime').value.trim();
+ let endTime = document.getElementById('editTaskEndTime').value.trim();
const duration = getSelectedEditTaskDurationMinutes();
const saveBtn = document.getElementById('btnSaveEditTask');
@@ -4228,6 +4493,30 @@ async function submitEditTask() {
return;
}
+ // Controleer of de geplande tijd buiten de gezette werktijden valt
+ if (startTime) {
+ const check = checkWorkdayBounds(taskDate, startTime, endTime, duration);
+ if (check.isOutside) {
+ const answer = await promptOutsideWorkhoursModal(check);
+ if (answer === 'CANCEL') {
+ return;
+ }
+ if (answer === 'NEE') {
+ // Automatisch naar volgende werkdag met eerder gezette voorwaarden
+ try {
+ const nextSlot = await findSlotOnNextWorkday(duration, taskId);
+ taskDate = nextSlot.date;
+ startTime = nextSlot.start;
+ endTime = nextSlot.end;
+ } catch (err) {
+ alert(err.message || 'Kon geen open blok vinden op de volgende werkdag.');
+ return;
+ }
+ }
+ // Bij 'JA': ga gewoon door met het buiten werktijd ingevulde tijdstip
+ }
+ }
+
if (saveBtn) {
saveBtn.innerText = 'Opslaan...';
saveBtn.disabled = true;