Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 7461231879 | |||
| 6ebaf027bf |
+202
-4
@@ -4,6 +4,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
require_once __DIR__ . '/../includes/auth.php';
|
require_once __DIR__ . '/../includes/auth.php';
|
||||||
|
require_once __DIR__ . '/../includes/settings.php';
|
||||||
require_once __DIR__ . '/../includes/integrations/microsoft.php';
|
require_once __DIR__ . '/../includes/integrations/microsoft.php';
|
||||||
$user = require_auth(true);
|
$user = require_auth(true);
|
||||||
$db = get_db_connection();
|
$db = get_db_connection();
|
||||||
@@ -237,13 +238,28 @@ try {
|
|||||||
} catch (Throwable $e) {}
|
} catch (Throwable $e) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Indien gekoppeld aan Outlook, werk afspraak bij via Graph API
|
$targetDate = !empty($data['date']) ? trim($data['date']) : null;
|
||||||
if (!empty($existingTask['external_id']) && $existingTask['source'] === 'OUTLOOK') {
|
$planDate = null;
|
||||||
try {
|
$newPlanId = null;
|
||||||
|
|
||||||
|
if ($targetDate && preg_match('/^\d{4}-\d{2}-\d{2}$/', $targetDate)) {
|
||||||
|
$stmtPlan = $db->prepare("
|
||||||
|
INSERT INTO daily_plans (user_id, plan_date)
|
||||||
|
VALUES (?, ?)
|
||||||
|
ON DUPLICATE KEY UPDATE id=LAST_INSERT_ID(id)
|
||||||
|
");
|
||||||
|
$stmtPlan->execute([$user['id'], $targetDate]);
|
||||||
|
$newPlanId = (int)$db->lastInsertId();
|
||||||
|
$planDate = $targetDate;
|
||||||
|
} else {
|
||||||
$stmtPlan = $db->prepare("SELECT plan_date FROM daily_plans WHERE id = ?");
|
$stmtPlan = $db->prepare("SELECT plan_date FROM daily_plans WHERE id = ?");
|
||||||
$stmtPlan->execute([$existingTask['daily_plan_id']]);
|
$stmtPlan->execute([$existingTask['daily_plan_id']]);
|
||||||
$planDate = $stmtPlan->fetchColumn() ?: date('Y-m-d');
|
$planDate = $stmtPlan->fetchColumn() ?: date('Y-m-d');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Indien gekoppeld aan Outlook, werk afspraak bij via Graph API
|
||||||
|
if (!empty($existingTask['external_id']) && $existingTask['source'] === 'OUTLOOK') {
|
||||||
|
try {
|
||||||
microsoft_update_event($user['id'], $existingTask['external_id'], [
|
microsoft_update_event($user['id'], $existingTask['external_id'], [
|
||||||
'subject' => $title,
|
'subject' => $title,
|
||||||
'date' => $planDate,
|
'date' => $planDate,
|
||||||
@@ -255,18 +271,28 @@ try {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ($newPlanId) {
|
||||||
|
$stmtUpdate = $db->prepare("
|
||||||
|
UPDATE tasks
|
||||||
|
SET title = ?, priority = ?, time_block_start = ?, time_block_end = ?, duration_minutes = ?, daily_plan_id = ?
|
||||||
|
WHERE id = ? AND user_id = ?
|
||||||
|
");
|
||||||
|
$stmtUpdate->execute([$title, $priority, $timeBlockStart, $timeBlockEnd, $durationMinutes, $newPlanId, $taskId, $user['id']]);
|
||||||
|
} else {
|
||||||
$stmtUpdate = $db->prepare("
|
$stmtUpdate = $db->prepare("
|
||||||
UPDATE tasks
|
UPDATE tasks
|
||||||
SET title = ?, priority = ?, time_block_start = ?, time_block_end = ?, duration_minutes = ?
|
SET title = ?, priority = ?, time_block_start = ?, time_block_end = ?, duration_minutes = ?
|
||||||
WHERE id = ? AND user_id = ?
|
WHERE id = ? AND user_id = ?
|
||||||
");
|
");
|
||||||
$stmtUpdate->execute([$title, $priority, $timeBlockStart, $timeBlockEnd, $durationMinutes, $taskId, $user['id']]);
|
$stmtUpdate->execute([$title, $priority, $timeBlockStart, $timeBlockEnd, $durationMinutes, $taskId, $user['id']]);
|
||||||
|
}
|
||||||
|
|
||||||
echo json_encode([
|
echo json_encode([
|
||||||
'success' => true,
|
'success' => true,
|
||||||
'time_block_start' => $timeBlockStart,
|
'time_block_start' => $timeBlockStart,
|
||||||
'time_block_end' => $timeBlockEnd,
|
'time_block_end' => $timeBlockEnd,
|
||||||
'duration_minutes' => $durationMinutes
|
'duration_minutes' => $durationMinutes,
|
||||||
|
'date' => $planDate
|
||||||
]);
|
]);
|
||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
@@ -619,6 +645,178 @@ try {
|
|||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 11. Zoek eerstvolgende vrije open blok binnen werktijden (Release v2.1)
|
||||||
|
if ($action === 'find_next_free_slot') {
|
||||||
|
$rawInput = json_decode(file_get_contents('php://input'), true);
|
||||||
|
$data = is_array($rawInput) ? $rawInput : $_GET;
|
||||||
|
$duration = max(5, (int)($data['duration_minutes'] ?? 30));
|
||||||
|
$excludeTaskId = (int)($data['task_id'] ?? 0);
|
||||||
|
|
||||||
|
$workdaySettings = get_workday_settings();
|
||||||
|
$workdayStartStr = $workdaySettings['workday_start'] ?? '08:30';
|
||||||
|
$workdayEndStr = $workdaySettings['workday_end'] ?? '17:30';
|
||||||
|
$lunchStartStr = $workdaySettings['lunch_start'] ?? '12:30';
|
||||||
|
$lunchEndStr = $workdaySettings['lunch_end'] ?? '13:00';
|
||||||
|
|
||||||
|
$toMins = function(string $timeStr): int {
|
||||||
|
$parts = explode(':', trim($timeStr));
|
||||||
|
return ((int)($parts[0] ?? 0) * 60) + (int)($parts[1] ?? 0);
|
||||||
|
};
|
||||||
|
|
||||||
|
$toTimeStr = function(int $mins): string {
|
||||||
|
$h = (int)floor($mins / 60);
|
||||||
|
$m = $mins % 60;
|
||||||
|
return sprintf('%02d:%02d', $h, $m);
|
||||||
|
};
|
||||||
|
|
||||||
|
$workdayStartMin = $toMins($workdayStartStr);
|
||||||
|
$workdayEndMin = $toMins($workdayEndStr);
|
||||||
|
$lunchStartMin = $toMins($lunchStartStr);
|
||||||
|
$lunchEndMin = $toMins($lunchEndStr);
|
||||||
|
|
||||||
|
$tz = new DateTimeZone('Europe/Amsterdam');
|
||||||
|
$now = new DateTime('now', $tz);
|
||||||
|
|
||||||
|
// Huidige tijd afronden naar hele kwartieren
|
||||||
|
$nowH = (int)$now->format('G');
|
||||||
|
$nowM = (int)$now->format('i');
|
||||||
|
if ($nowM % 15 !== 0) {
|
||||||
|
$nowM = (int)(ceil($nowM / 15) * 15);
|
||||||
|
if ($nowM >= 60) {
|
||||||
|
$nowH += (int)floor($nowM / 60);
|
||||||
|
$nowM = $nowM % 60;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$currentQuarterMin = $nowH * 60 + $nowM;
|
||||||
|
|
||||||
|
$dutchDays = [1 => 'Maandag', 2 => 'Dinsdag', 3 => 'Woensdag', 4 => 'Donderdag', 5 => 'Vrijdag', 6 => 'Zaterdag', 7 => 'Zondag'];
|
||||||
|
$dutchMonths = ['', 'jan', 'feb', 'mrt', 'apr', 'mei', 'jun', 'jul', 'aug', 'sep', 'okt', 'nov', 'dec'];
|
||||||
|
|
||||||
|
$checkDate = clone $now;
|
||||||
|
$foundSlot = null;
|
||||||
|
$maxDays = 14;
|
||||||
|
$checkedDays = 0;
|
||||||
|
|
||||||
|
while ($checkedDays < $maxDays) {
|
||||||
|
$dayOfWeek = (int)$checkDate->format('N'); // 1 = Ma, 7 = Zo
|
||||||
|
|
||||||
|
// Weekend overslaan
|
||||||
|
if ($dayOfWeek === 6 || $dayOfWeek === 7) {
|
||||||
|
$checkDate->modify('+1 day');
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$dateStr = $checkDate->format('Y-m-d');
|
||||||
|
$isToday = ($dateStr === $now->format('Y-m-d'));
|
||||||
|
|
||||||
|
// Bepaal vroegste startminuut voor deze dag (op hele kwartieren)
|
||||||
|
if ($isToday) {
|
||||||
|
$earliestMin = max($workdayStartMin, $currentQuarterMin);
|
||||||
|
if ($earliestMin % 15 !== 0) {
|
||||||
|
$earliestMin = (int)(ceil($earliestMin / 15) * 15);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$earliestMin = $workdayStartMin;
|
||||||
|
if ($earliestMin % 15 !== 0) {
|
||||||
|
$earliestMin = (int)(ceil($earliestMin / 15) * 15);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Alleen zoeken als er binnen de werkdag nog voldoende tijd is voor duration
|
||||||
|
if ($earliestMin + $duration <= $workdayEndMin) {
|
||||||
|
$busyIntervals = [];
|
||||||
|
|
||||||
|
// Lunchpauze
|
||||||
|
if ($lunchEndMin > $lunchStartMin) {
|
||||||
|
$busyIntervals[] = [$lunchStartMin, $lunchEndMin];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bestaande taken van de gebruiker op deze dag
|
||||||
|
$stmtTasks = $db->prepare("
|
||||||
|
SELECT t.id, t.time_block_start, t.time_block_end, t.duration_minutes
|
||||||
|
FROM tasks t
|
||||||
|
INNER JOIN daily_plans dp ON t.daily_plan_id = dp.id
|
||||||
|
WHERE t.user_id = ?
|
||||||
|
AND dp.plan_date = ?
|
||||||
|
AND t.time_block_start IS NOT NULL
|
||||||
|
AND t.time_block_start != ''
|
||||||
|
AND t.status != 'CANCELLED'
|
||||||
|
");
|
||||||
|
$stmtTasks->execute([$user['id'], $dateStr]);
|
||||||
|
while ($t = $stmtTasks->fetch()) {
|
||||||
|
if ($excludeTaskId > 0 && (int)$t['id'] === $excludeTaskId) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$tStart = $toMins($t['time_block_start']);
|
||||||
|
$tDur = (int)($t['duration_minutes'] ?: 30);
|
||||||
|
$tEnd = !empty($t['time_block_end']) ? $toMins($t['time_block_end']) : ($tStart + $tDur);
|
||||||
|
if ($tEnd > $tStart) {
|
||||||
|
$busyIntervals[] = [$tStart, $tEnd];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Outlook afspraken
|
||||||
|
if (function_exists('microsoft_fetch_events_range')) {
|
||||||
|
try {
|
||||||
|
$outlookEvts = microsoft_fetch_events_range($user['id'], $dateStr, $dateStr, false);
|
||||||
|
foreach ($outlookEvts as $oe) {
|
||||||
|
if (!empty($oe['is_all_day'])) continue;
|
||||||
|
if (empty($oe['start']) || empty($oe['end'])) continue;
|
||||||
|
$oStart = $toMins($oe['start']);
|
||||||
|
$oEnd = $toMins($oe['end']);
|
||||||
|
if ($oEnd > $oStart) {
|
||||||
|
$busyIntervals[] = [$oStart, $oEnd];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Throwable $e) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Doorzoek kwartierslots
|
||||||
|
for ($slotStart = $earliestMin; ($slotStart + $duration) <= $workdayEndMin; $slotStart += 15) {
|
||||||
|
$slotEnd = $slotStart + $duration;
|
||||||
|
$hasCollision = false;
|
||||||
|
|
||||||
|
foreach ($busyIntervals as [$bStart, $bEnd]) {
|
||||||
|
if ($slotStart < $bEnd && $slotEnd > $bStart) {
|
||||||
|
$hasCollision = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$hasCollision) {
|
||||||
|
$foundSlot = [
|
||||||
|
'date' => $dateStr,
|
||||||
|
'start' => $toTimeStr($slotStart),
|
||||||
|
'end' => $toTimeStr($slotEnd),
|
||||||
|
'duration_minutes' => $duration,
|
||||||
|
'is_today' => $isToday,
|
||||||
|
'formatted_date' => $isToday
|
||||||
|
? 'Vandaag (' . date('j', strtotime($dateStr)) . ' ' . $dutchMonths[(int)date('n', strtotime($dateStr))] . ')'
|
||||||
|
: $dutchDays[(int)$checkDate->format('N')] . ' ' . date('j', strtotime($dateStr)) . ' ' . $dutchMonths[(int)date('n', strtotime($dateStr))],
|
||||||
|
];
|
||||||
|
break 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$checkDate->modify('+1 day');
|
||||||
|
$checkedDays++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($foundSlot) {
|
||||||
|
echo json_encode([
|
||||||
|
'success' => true,
|
||||||
|
'slot' => $foundSlot,
|
||||||
|
]);
|
||||||
|
} else {
|
||||||
|
echo json_encode([
|
||||||
|
'success' => false,
|
||||||
|
'error' => "Geen vrij blok van {$duration} minuten gevonden binnen de gezette werktijden ({$workdayStartStr} - {$workdayEndStr}) voor de komende {$maxDays} werkdagen.",
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
echo json_encode(['error' => 'Onbekende actie']);
|
echo json_encode(['error' => 'Onbekende actie']);
|
||||||
} catch (Exception $e) {
|
} catch (Exception $e) {
|
||||||
http_response_code(500);
|
http_response_code(500);
|
||||||
|
|||||||
+139
-7
@@ -860,7 +860,8 @@ for ($h = $startHour; $h <= $endHour; $h++) {
|
|||||||
'end' => $task['time_block_end'] ?? '',
|
'end' => $task['time_block_end'] ?? '',
|
||||||
'duration' => (int)($task['duration_minutes'] ?: 30),
|
'duration' => (int)($task['duration_minutes'] ?: 30),
|
||||||
'source' => $task['source'],
|
'source' => $task['source'],
|
||||||
'external_id' => $task['external_id'] ?? ''
|
'external_id' => $task['external_id'] ?? '',
|
||||||
|
'date' => $selectedDate
|
||||||
]), ENT_QUOTES, 'UTF-8') ?>)" title="Taak & Tijd aanpassen" class="p-1.5 rounded-lg text-slate-400 hover:text-indigo-600 dark:hover:text-indigo-400 hover:bg-indigo-500/10 opacity-0 group-hover:opacity-100 transition cursor-pointer">
|
]), ENT_QUOTES, 'UTF-8') ?>)" title="Taak & Tijd aanpassen" class="p-1.5 rounded-lg text-slate-400 hover:text-indigo-600 dark:hover:text-indigo-400 hover:bg-indigo-500/10 opacity-0 group-hover:opacity-100 transition cursor-pointer">
|
||||||
<i data-lucide="edit-3" class="w-3.5 h-3.5"></i>
|
<i data-lucide="edit-3" class="w-3.5 h-3.5"></i>
|
||||||
</button>
|
</button>
|
||||||
@@ -1178,6 +1179,19 @@ for ($h = $startHour; $h <= $endHour; $h++) {
|
|||||||
<span class="text-[10px] font-bold px-2 py-0.5 rounded-full <?= $t['priority'] === 'P1' ? 'bg-rose-50 dark:bg-rose-500/10 text-rose-700 dark:text-rose-400 border border-rose-200 dark:border-rose-500/20' : ($t['priority'] === 'P2' ? 'bg-amber-50 dark:bg-amber-500/10 text-amber-700 dark:text-amber-400 border border-amber-200 dark:border-amber-500/20' : 'bg-slate-100 dark:bg-slate-800 text-slate-600 dark:text-slate-400 border border-slate-200 dark:border-slate-700') ?>">
|
<span class="text-[10px] font-bold px-2 py-0.5 rounded-full <?= $t['priority'] === 'P1' ? 'bg-rose-50 dark:bg-rose-500/10 text-rose-700 dark:text-rose-400 border border-rose-200 dark:border-rose-500/20' : ($t['priority'] === 'P2' ? 'bg-amber-50 dark:bg-amber-500/10 text-amber-700 dark:text-amber-400 border border-amber-200 dark:border-amber-500/20' : 'bg-slate-100 dark:bg-slate-800 text-slate-600 dark:text-slate-400 border border-slate-200 dark:border-slate-700') ?>">
|
||||||
<?= $t['priority'] ?>
|
<?= $t['priority'] ?>
|
||||||
</span>
|
</span>
|
||||||
|
<button onclick="openEditTaskModal(<?= htmlspecialchars(json_encode([
|
||||||
|
'id' => (int)$t['id'],
|
||||||
|
'title' => $t['title'],
|
||||||
|
'priority' => $t['priority'],
|
||||||
|
'start' => $t['time_block_start'] ?? '',
|
||||||
|
'end' => $t['time_block_end'] ?? '',
|
||||||
|
'duration' => (int)($t['duration_minutes'] ?: 30),
|
||||||
|
'source' => $t['source'],
|
||||||
|
'external_id' => $t['external_id'] ?? '',
|
||||||
|
'date' => $selectedDate
|
||||||
|
]), ENT_QUOTES, 'UTF-8') ?>)" title="Taak & Tijdblok bewerken" class="p-1 rounded-lg text-slate-400 hover:text-indigo-600 dark:hover:text-indigo-400 hover:bg-slate-100 dark:hover:bg-slate-800 transition cursor-pointer">
|
||||||
|
<i data-lucide="edit-3" class="w-3 h-3"></i>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -2005,9 +2019,38 @@ if ($isAjaxRequest) {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="grid grid-cols-1 sm:grid-cols-2 gap-3">
|
<!-- 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-xs font-semibold text-slate-700 dark:text-slate-300">Datum & Tijdblok</span>
|
||||||
|
<button type="button" onclick="planInNextOpenBlock()" id="btnPlanOpenBlock" 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="editTaskOpenBlockNotice" 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="editTaskOpenBlockNoticeText" class="truncate font-semibold"></span>
|
||||||
|
</div>
|
||||||
|
<button type="button" onclick="document.getElementById('editTaskOpenBlockNotice').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>
|
||||||
|
|
||||||
|
<div class="grid grid-cols-1 sm:grid-cols-3 gap-3">
|
||||||
<div>
|
<div>
|
||||||
<label class="block text-xs font-semibold text-slate-700 dark:text-slate-300 mb-1">Starttijd</label>
|
<label for="editTaskDate" class="block text-xs font-semibold text-slate-700 dark:text-slate-300 mb-1">Datum</label>
|
||||||
|
<input type="date" id="editTaskDate" class="w-full bg-slate-50/70 dark:bg-[#0c0f17]/60 border border-slate-300 dark:border-slate-800 rounded-xl px-2.5 py-2 text-slate-900 dark:text-white text-xs focus:outline-none focus:ring-1 focus:ring-indigo-500 shadow-2xs">
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div class="flex items-center justify-between mb-1">
|
||||||
|
<label class="block text-xs font-semibold text-slate-700 dark:text-slate-300">Starttijd</label>
|
||||||
|
<button type="button" onclick="clearEditTaskTimes()" title="Tijd wissen" class="text-[10px] text-slate-400 hover:text-rose-500 cursor-pointer">Wissen</button>
|
||||||
|
</div>
|
||||||
<div class="relative">
|
<div class="relative">
|
||||||
<input type="text" id="editTaskStartTime" placeholder="Bijv. 09:30" class="w-full bg-slate-50/70 dark:bg-[#0c0f17]/60 border border-slate-300 dark:border-slate-800 rounded-xl pl-3 pr-8 py-2 text-slate-900 dark:text-white text-xs font-mono focus:outline-none focus:ring-1 focus:ring-indigo-500 shadow-2xs" oninput="onEditTaskTimeOrDurationChange('start')">
|
<input type="text" id="editTaskStartTime" placeholder="Bijv. 09:30" class="w-full bg-slate-50/70 dark:bg-[#0c0f17]/60 border border-slate-300 dark:border-slate-800 rounded-xl pl-3 pr-8 py-2 text-slate-900 dark:text-white text-xs font-mono focus:outline-none focus:ring-1 focus:ring-indigo-500 shadow-2xs" oninput="onEditTaskTimeOrDurationChange('start')">
|
||||||
<i data-lucide="clock" class="w-4 h-4 text-slate-400 absolute right-2.5 top-2.5 pointer-events-none"></i>
|
<i data-lucide="clock" class="w-4 h-4 text-slate-400 absolute right-2.5 top-2.5 pointer-events-none"></i>
|
||||||
@@ -2016,10 +2059,10 @@ if ($isAjaxRequest) {
|
|||||||
<div>
|
<div>
|
||||||
<div class="flex items-center justify-between mb-1">
|
<div class="flex items-center justify-between mb-1">
|
||||||
<label class="block text-xs font-semibold text-slate-700 dark:text-slate-300">Eindtijd</label>
|
<label class="block text-xs font-semibold text-slate-700 dark:text-slate-300">Eindtijd</label>
|
||||||
<span class="text-[9px] text-indigo-600 dark:text-indigo-400 font-medium">Vrij invulbaar</span>
|
<span class="text-[9px] text-indigo-600 dark:text-indigo-400 font-medium">Auto/Vrij</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="relative">
|
<div class="relative">
|
||||||
<input type="text" id="editTaskEndTime" placeholder="Bijv. 09:40" class="w-full bg-slate-50/70 dark:bg-[#0c0f17]/60 border border-slate-300 dark:border-slate-800 rounded-xl pl-3 pr-8 py-2 text-slate-900 dark:text-white text-xs font-mono focus:outline-none focus:ring-1 focus:ring-indigo-500 shadow-2xs" oninput="onEditTaskTimeOrDurationChange('end')">
|
<input type="text" id="editTaskEndTime" placeholder="Bijv. 10:00" class="w-full bg-slate-50/70 dark:bg-[#0c0f17]/60 border border-slate-300 dark:border-slate-800 rounded-xl pl-3 pr-8 py-2 text-slate-900 dark:text-white text-xs font-mono focus:outline-none focus:ring-1 focus:ring-indigo-500 shadow-2xs" oninput="onEditTaskTimeOrDurationChange('end')">
|
||||||
<i data-lucide="clock-arrow-down" class="w-4 h-4 text-slate-400 absolute right-2.5 top-2.5 pointer-events-none"></i>
|
<i data-lucide="clock-arrow-down" class="w-4 h-4 text-slate-400 absolute right-2.5 top-2.5 pointer-events-none"></i>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -3961,6 +4004,16 @@ function openEditTaskModal(task) {
|
|||||||
document.getElementById('editTaskStartTime').value = task.start || '';
|
document.getElementById('editTaskStartTime').value = task.start || '';
|
||||||
document.getElementById('editTaskEndTime').value = task.end || '';
|
document.getElementById('editTaskEndTime').value = task.end || '';
|
||||||
|
|
||||||
|
// Datum
|
||||||
|
const dateInput = document.getElementById('editTaskDate');
|
||||||
|
if (dateInput) {
|
||||||
|
dateInput.value = task.date || (typeof activePlannerDate !== 'undefined' ? activePlannerDate : new Date().toISOString().slice(0, 10));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset statusbericht van open blok
|
||||||
|
const notice = document.getElementById('editTaskOpenBlockNotice');
|
||||||
|
if (notice) notice.classList.add('hidden');
|
||||||
|
|
||||||
const dur = task.duration || 30;
|
const dur = task.duration || 30;
|
||||||
setSelectedEditTaskDuration(dur);
|
setSelectedEditTaskDuration(dur);
|
||||||
|
|
||||||
@@ -3990,6 +4043,76 @@ function closeEditTaskModal() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function clearEditTaskTimes() {
|
||||||
|
const s = document.getElementById('editTaskStartTime');
|
||||||
|
const e = document.getElementById('editTaskEndTime');
|
||||||
|
if (s) s.value = '';
|
||||||
|
if (e) e.value = '';
|
||||||
|
const notice = document.getElementById('editTaskOpenBlockNotice');
|
||||||
|
if (notice) notice.classList.add('hidden');
|
||||||
|
}
|
||||||
|
|
||||||
|
async function planInNextOpenBlock() {
|
||||||
|
const duration = getSelectedEditTaskDurationMinutes();
|
||||||
|
const taskId = parseInt(document.getElementById('editTaskId')?.value, 10) || 0;
|
||||||
|
const btn = document.getElementById('btnPlanOpenBlock');
|
||||||
|
const notice = document.getElementById('editTaskOpenBlockNotice');
|
||||||
|
const noticeText = document.getElementById('editTaskOpenBlockNoticeText');
|
||||||
|
|
||||||
|
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,
|
||||||
|
task_id: taskId
|
||||||
|
})
|
||||||
|
});
|
||||||
|
const data = await res.json();
|
||||||
|
|
||||||
|
if (data.success && data.slot) {
|
||||||
|
const slot = data.slot;
|
||||||
|
|
||||||
|
// Stel datum en tijden in
|
||||||
|
const dateInput = document.getElementById('editTaskDate');
|
||||||
|
if (dateInput) {
|
||||||
|
dateInput.value = slot.date;
|
||||||
|
}
|
||||||
|
const startInput = document.getElementById('editTaskStartTime');
|
||||||
|
if (startInput) {
|
||||||
|
startInput.value = slot.start;
|
||||||
|
}
|
||||||
|
const endInput = document.getElementById('editTaskEndTime');
|
||||||
|
if (endInput) {
|
||||||
|
endInput.value = slot.end;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Toon statusmelding in dialoog
|
||||||
|
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 onEditTaskTimeOrDurationChange(source) {
|
function onEditTaskTimeOrDurationChange(source) {
|
||||||
const startInput = document.getElementById('editTaskStartTime');
|
const startInput = document.getElementById('editTaskStartTime');
|
||||||
const endInput = document.getElementById('editTaskEndTime');
|
const endInput = document.getElementById('editTaskEndTime');
|
||||||
@@ -4093,6 +4216,8 @@ async function submitEditTask() {
|
|||||||
const taskId = parseInt(document.getElementById('editTaskId').value, 10);
|
const taskId = parseInt(document.getElementById('editTaskId').value, 10);
|
||||||
const title = document.getElementById('editTaskTitle').value.trim();
|
const title = document.getElementById('editTaskTitle').value.trim();
|
||||||
const priority = document.getElementById('editTaskPriority').value;
|
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 startTime = document.getElementById('editTaskStartTime').value.trim();
|
||||||
const endTime = document.getElementById('editTaskEndTime').value.trim();
|
const endTime = document.getElementById('editTaskEndTime').value.trim();
|
||||||
const duration = getSelectedEditTaskDurationMinutes();
|
const duration = getSelectedEditTaskDurationMinutes();
|
||||||
@@ -4118,13 +4243,20 @@ async function submitEditTask() {
|
|||||||
priority: priority,
|
priority: priority,
|
||||||
time_block_start: startTime,
|
time_block_start: startTime,
|
||||||
time_block_end: endTime,
|
time_block_end: endTime,
|
||||||
duration_minutes: duration
|
duration_minutes: duration,
|
||||||
|
date: taskDate
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
const data = await res.json();
|
const data = await res.json();
|
||||||
if (data.success) {
|
if (data.success) {
|
||||||
closeEditTaskModal();
|
closeEditTaskModal();
|
||||||
switchPlannerDate(activePlannerDate, null, false);
|
const targetDate = data.date || taskDate;
|
||||||
|
if (typeof switchPlannerDate === 'function') {
|
||||||
|
switchPlannerDate(targetDate, null, false);
|
||||||
|
}
|
||||||
|
if (typeof showWidgetToast === 'function') {
|
||||||
|
showWidgetToast(`Taak <strong>${escapeHtml(title)}</strong> succesvol bijgewerkt`, 'success');
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
alert(data.error || 'Kon taak niet bijwerken');
|
alert(data.error || 'Kon taak niet bijwerken');
|
||||||
if (saveBtn) {
|
if (saveBtn) {
|
||||||
|
|||||||
Reference in New Issue
Block a user