feat: Enhance task management with start and end time handling
- Added functionality to calculate task duration based on provided start and end times. - Implemented logic to derive end time from start time and duration if only start time is provided. - Updated task creation and update APIs to handle new time fields. - Introduced a modal for editing tasks with flexible time and duration inputs. - Improved UI for task creation and editing, including better handling of time inputs and duration selection. - Added two-way synchronization with Outlook for task updates.
This commit is contained in:
@@ -542,7 +542,9 @@ require_once __DIR__ . '/includes/header.php';
|
||||
<div>
|
||||
<label class="block text-[11px] font-medium text-slate-600 dark:text-slate-400 mb-1">Standaard tijdslot</label>
|
||||
<select name="default_task_duration" class="w-full bg-white dark:bg-[#111622] border border-slate-300 dark:border-slate-700 rounded-xl px-3 py-1.5 text-xs text-slate-900 dark:text-white focus:outline-none focus:ring-1 focus:ring-indigo-500">
|
||||
<option value="10" <?= $workdaySettings['default_task_duration'] == 10 ? 'selected' : '' ?>>10 minuten</option>
|
||||
<option value="15" <?= $workdaySettings['default_task_duration'] == 15 ? 'selected' : '' ?>>15 minuten</option>
|
||||
<option value="20" <?= $workdaySettings['default_task_duration'] == 20 ? 'selected' : '' ?>>20 minuten</option>
|
||||
<option value="30" <?= $workdaySettings['default_task_duration'] == 30 ? 'selected' : '' ?>>30 minuten (Standaard)</option>
|
||||
<option value="45" <?= $workdaySettings['default_task_duration'] == 45 ? 'selected' : '' ?>>45 minuten</option>
|
||||
<option value="60" <?= $workdaySettings['default_task_duration'] == 60 ? 'selected' : '' ?>>60 minuten (1 uur)</option>
|
||||
|
||||
+111
-19
@@ -38,8 +38,8 @@ try {
|
||||
$title = trim($data['title'] ?? '');
|
||||
$priority = in_array($data['priority'] ?? '', ['P1', 'P2', 'P3']) ? $data['priority'] : 'P2';
|
||||
$timeBlockStart = !empty($data['time_block_start']) ? trim($data['time_block_start']) : null;
|
||||
$durationMinutes = (int)($data['duration_minutes'] ?? 30);
|
||||
if ($durationMinutes <= 0) $durationMinutes = 30;
|
||||
$timeBlockEnd = !empty($data['time_block_end']) ? trim($data['time_block_end']) : null;
|
||||
$durationMinutes = (int)($data['duration_minutes'] ?? 0);
|
||||
$date = !empty($data['date']) ? trim($data['date']) : $today;
|
||||
$syncOutlook = !empty($data['sync_to_outlook']);
|
||||
|
||||
@@ -48,6 +48,30 @@ try {
|
||||
exit;
|
||||
}
|
||||
|
||||
// Als zowel start- als eindtijd zijn opgegeven, bereken exact de minuten
|
||||
if (!empty($timeBlockStart) && !empty($timeBlockEnd)) {
|
||||
$t1 = strtotime("2000-01-01 {$timeBlockStart}");
|
||||
$t2 = strtotime("2000-01-01 {$timeBlockEnd}");
|
||||
if ($t1 !== false && $t2 !== false && $t2 > $t1) {
|
||||
$durationMinutes = (int)(($t2 - $t1) / 60);
|
||||
}
|
||||
}
|
||||
|
||||
if ($durationMinutes <= 0) {
|
||||
$durationMinutes = 30;
|
||||
}
|
||||
|
||||
// Als starttijd bekend is maar eindtijd nog niet, bereken eindtijd
|
||||
if (!empty($timeBlockStart) && empty($timeBlockEnd)) {
|
||||
try {
|
||||
$dtEnd = new DateTime("{$date} {$timeBlockStart}:00", new DateTimeZone('Europe/Amsterdam'));
|
||||
$dtEnd->modify("+{$durationMinutes} minutes");
|
||||
$timeBlockEnd = $dtEnd->format('H:i');
|
||||
} catch (Throwable $e) {
|
||||
// negeer
|
||||
}
|
||||
}
|
||||
|
||||
// Haal of maak daily plan op
|
||||
$stmtPlan = $db->prepare("
|
||||
INSERT INTO daily_plans (user_id, plan_date)
|
||||
@@ -60,7 +84,6 @@ try {
|
||||
$source = !empty($data['source']) ? substr(trim($data['source']), 0, 50) : 'FOCIZ';
|
||||
$externalId = !empty($data['external_id']) ? substr(trim($data['external_id']), 0, 255) : null;
|
||||
$externalUrl = !empty($data['external_url']) ? trim($data['external_url']) : null;
|
||||
$timeBlockEnd = null;
|
||||
$outlookSyncError = null;
|
||||
|
||||
// Als synchronisatie naar Outlook gewenst is (of source is OUTLOOK) en nog geen extern ID heeft
|
||||
@@ -75,6 +98,7 @@ try {
|
||||
'description' => "Gepland via Fociz Dagplanning ({$priority})",
|
||||
'date' => $date,
|
||||
'start_time' => $timeBlockStart,
|
||||
'end_time' => $timeBlockEnd,
|
||||
'duration_minutes' => $durationMinutes,
|
||||
]);
|
||||
|
||||
@@ -83,23 +107,12 @@ try {
|
||||
$externalId = $msResult['event_id'];
|
||||
$externalUrl = $msResult['web_link'] ?? 'https://outlook.office.com';
|
||||
$timeBlockStart = $msResult['start_time'] ?? $timeBlockStart;
|
||||
$timeBlockEnd = $msResult['end_time'] ?? null;
|
||||
$timeBlockEnd = $msResult['end_time'] ?? $timeBlockEnd;
|
||||
} else {
|
||||
$outlookSyncError = $msResult['error'] ?? 'Kon afspraak niet aanmaken in Microsoft Outlook';
|
||||
}
|
||||
}
|
||||
|
||||
// Bereken timeBlockEnd indien nog niet gezet maar wel starttijd bekend
|
||||
if (!empty($timeBlockStart) && empty($timeBlockEnd)) {
|
||||
try {
|
||||
$dtEnd = new DateTime("{$date} {$timeBlockStart}:00", new DateTimeZone('Europe/Amsterdam'));
|
||||
$dtEnd->modify("+{$durationMinutes} minutes");
|
||||
$timeBlockEnd = $dtEnd->format('H:i');
|
||||
} catch (Throwable $e) {
|
||||
// negeer
|
||||
}
|
||||
}
|
||||
|
||||
$stmt = $db->prepare("
|
||||
INSERT INTO tasks (user_id, daily_plan_id, title, priority, time_block_start, time_block_end, duration_minutes, status, source, external_id, external_url)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, 'TODO', ?, ?, ?)
|
||||
@@ -116,14 +129,91 @@ try {
|
||||
'time_block_start' => $timeBlockStart,
|
||||
'time_block_end' => $timeBlockEnd,
|
||||
'duration_minutes' => $durationMinutes,
|
||||
'is_completed' => 0,
|
||||
'status' => 'TODO',
|
||||
'source' => $source,
|
||||
'external_id' => $externalId,
|
||||
'external_url' => $externalUrl,
|
||||
'external_url' => $externalUrl
|
||||
],
|
||||
'outlook_synced' => ($source === 'OUTLOOK' && !empty($externalId)),
|
||||
'outlook_error' => $outlookSyncError,
|
||||
'outlook_error' => $outlookSyncError
|
||||
]);
|
||||
exit;
|
||||
}
|
||||
|
||||
// 2b. Taak bewerken (titel, prioriteit, starttijd, eindtijd, duur)
|
||||
if ($action === 'update_task') {
|
||||
$data = json_decode(file_get_contents('php://input'), true);
|
||||
$taskId = (int)($data['task_id'] ?? 0);
|
||||
$title = trim($data['title'] ?? '');
|
||||
$priority = in_array($data['priority'] ?? '', ['P1', 'P2', 'P3']) ? $data['priority'] : 'P2';
|
||||
$timeBlockStart = !empty($data['time_block_start']) ? trim($data['time_block_start']) : null;
|
||||
$timeBlockEnd = !empty($data['time_block_end']) ? trim($data['time_block_end']) : null;
|
||||
$durationMinutes = (int)($data['duration_minutes'] ?? 0);
|
||||
|
||||
if (empty($title)) {
|
||||
echo json_encode(['error' => 'Titel mag niet leeg zijn']);
|
||||
exit;
|
||||
}
|
||||
|
||||
// Haal bestaande taak op
|
||||
$stmtTask = $db->prepare("SELECT * FROM tasks WHERE id = ? AND user_id = ?");
|
||||
$stmtTask->execute([$taskId, $user['id']]);
|
||||
$existingTask = $stmtTask->fetch();
|
||||
|
||||
if (!$existingTask) {
|
||||
echo json_encode(['error' => 'Taak niet gevonden']);
|
||||
exit;
|
||||
}
|
||||
|
||||
if (!empty($timeBlockStart) && !empty($timeBlockEnd)) {
|
||||
$t1 = strtotime("2000-01-01 {$timeBlockStart}");
|
||||
$t2 = strtotime("2000-01-01 {$timeBlockEnd}");
|
||||
if ($t1 !== false && $t2 !== false && $t2 > $t1) {
|
||||
$durationMinutes = (int)(($t2 - $t1) / 60);
|
||||
}
|
||||
}
|
||||
|
||||
if ($durationMinutes <= 0) {
|
||||
$durationMinutes = (int)($existingTask['duration_minutes'] ?: 30);
|
||||
}
|
||||
|
||||
if (!empty($timeBlockStart) && empty($timeBlockEnd)) {
|
||||
try {
|
||||
$dtEnd = new DateTime("2000-01-01 {$timeBlockStart}:00", new DateTimeZone('Europe/Amsterdam'));
|
||||
$dtEnd->modify("+{$durationMinutes} minutes");
|
||||
$timeBlockEnd = $dtEnd->format('H:i');
|
||||
} catch (Throwable $e) {}
|
||||
}
|
||||
|
||||
// Indien gekoppeld aan Outlook, werk afspraak bij via Graph API
|
||||
if (!empty($existingTask['external_id']) && $existingTask['source'] === 'OUTLOOK') {
|
||||
try {
|
||||
$stmtPlan = $db->prepare("SELECT plan_date FROM daily_plans WHERE id = ?");
|
||||
$stmtPlan->execute([$existingTask['daily_plan_id']]);
|
||||
$planDate = $stmtPlan->fetchColumn() ?: date('Y-m-d');
|
||||
|
||||
microsoft_update_event($user['id'], $existingTask['external_id'], [
|
||||
'subject' => $title,
|
||||
'date' => $planDate,
|
||||
'start_time' => $timeBlockStart ?: '09:00',
|
||||
'end_time' => $timeBlockEnd ?: '09:30',
|
||||
]);
|
||||
} catch (Throwable $t) {
|
||||
error_log("Fout bij bijwerken Outlook event: " . $t->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
$stmtUpdate = $db->prepare("
|
||||
UPDATE tasks
|
||||
SET title = ?, priority = ?, time_block_start = ?, time_block_end = ?, duration_minutes = ?
|
||||
WHERE id = ? AND user_id = ?
|
||||
");
|
||||
$stmtUpdate->execute([$title, $priority, $timeBlockStart, $timeBlockEnd, $durationMinutes, $taskId, $user['id']]);
|
||||
|
||||
echo json_encode([
|
||||
'success' => true,
|
||||
'time_block_start' => $timeBlockStart,
|
||||
'time_block_end' => $timeBlockEnd,
|
||||
'duration_minutes' => $durationMinutes
|
||||
]);
|
||||
exit;
|
||||
}
|
||||
@@ -166,6 +256,7 @@ try {
|
||||
|
||||
$planDate = $task['plan_date'] ?: $today;
|
||||
$startTime = !empty($task['time_block_start']) ? $task['time_block_start'] : ($data['time_block_start'] ?? '09:00');
|
||||
$endTime = !empty($task['time_block_end']) ? $task['time_block_end'] : null;
|
||||
$duration = (int)($task['duration_minutes'] ?: 30);
|
||||
|
||||
$msResult = microsoft_create_event($user['id'], [
|
||||
@@ -173,6 +264,7 @@ try {
|
||||
'description' => "Gepland via Fociz Dagplanning (" . $task['priority'] . ")",
|
||||
'date' => $planDate,
|
||||
'start_time' => $startTime,
|
||||
'end_time' => $endTime,
|
||||
'duration_minutes' => $duration,
|
||||
]);
|
||||
|
||||
|
||||
@@ -562,6 +562,75 @@ function microsoft_delete_event(int $userId, string $eventId): bool {
|
||||
return in_array($response['status'], [200, 204, 404]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Werkt een bestaande afspraak bij in Microsoft Outlook via Graph API (PATCH)
|
||||
*/
|
||||
function microsoft_update_event(int $userId, string $eventId, array $eventData): bool {
|
||||
if (empty($eventId)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$token = microsoft_get_valid_token($userId);
|
||||
if (!$token) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$payload = [];
|
||||
if (!empty($eventData['subject'])) {
|
||||
$payload['subject'] = $eventData['subject'];
|
||||
}
|
||||
|
||||
$date = !empty($eventData['date']) ? $eventData['date'] : date('Y-m-d');
|
||||
$startTime = !empty($eventData['start_time']) ? $eventData['start_time'] : '09:00';
|
||||
$endTime = !empty($eventData['end_time']) ? $eventData['end_time'] : '09:30';
|
||||
|
||||
if (preg_match('/^(\d{1,2})[:.](\d{2})$/', $startTime, $m)) {
|
||||
$startTime = sprintf('%02d:%02d', (int)$m[1], (int)$m[2]);
|
||||
}
|
||||
if (preg_match('/^(\d{1,2})[:.](\d{2})$/', $endTime, $m)) {
|
||||
$endTime = sprintf('%02d:%02d', (int)$m[1], (int)$m[2]);
|
||||
}
|
||||
|
||||
$payload['start'] = [
|
||||
'dateTime' => "{$date}T{$startTime}:00",
|
||||
'timeZone' => 'Europe/Amsterdam',
|
||||
];
|
||||
$payload['end'] = [
|
||||
'dateTime' => "{$date}T{$endTime}:00",
|
||||
'timeZone' => 'Europe/Amsterdam',
|
||||
];
|
||||
|
||||
$url = MS_GRAPH_BASE_URL . '/me/events/' . urlencode($eventId);
|
||||
$response = fociz_http_request($url, [
|
||||
'method' => 'PATCH',
|
||||
'headers' => [
|
||||
'Authorization' => "Bearer {$token}",
|
||||
'Content-Type' => 'application/json',
|
||||
'Accept' => 'application/json',
|
||||
],
|
||||
'body' => $payload,
|
||||
'timeout' => 12,
|
||||
]);
|
||||
|
||||
if ($response['status'] === 401) {
|
||||
$freshToken = microsoft_get_valid_token($userId, true);
|
||||
if ($freshToken && $freshToken !== $token) {
|
||||
$response = fociz_http_request($url, [
|
||||
'method' => 'PATCH',
|
||||
'headers' => [
|
||||
'Authorization' => "Bearer {$freshToken}",
|
||||
'Content-Type' => 'application/json',
|
||||
'Accept' => 'application/json',
|
||||
],
|
||||
'body' => $payload,
|
||||
'timeout' => 12,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
return in_array($response['status'], [200, 204]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Ontkoppelt Microsoft integratie voor een gebruiker
|
||||
* Wist uitsluitend de persoonlijke tokens; behoudt de centrale Azure App gegevens indien aanwezig.
|
||||
|
||||
+486
-20
@@ -505,30 +505,56 @@ for ($h = $startHour; $h <= $endHour; $h++) {
|
||||
<label class="block text-[11px] font-medium text-slate-600 dark:text-slate-400 mb-1">Taaktitel</label>
|
||||
<input type="text" id="newTaskTitle" placeholder="Wat moet er vandaag gedaan worden?" class="w-full bg-white dark:bg-slate-900 border border-slate-300 dark:border-slate-800 rounded-xl px-3 py-2 text-slate-900 dark:text-white text-xs placeholder-slate-400 dark:placeholder-slate-500 focus:outline-none focus:ring-1 focus:ring-indigo-500">
|
||||
</div>
|
||||
<div class="grid grid-cols-1 sm:grid-cols-3 gap-3">
|
||||
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-3">
|
||||
<div>
|
||||
<label class="block text-[11px] font-medium text-slate-600 dark:text-slate-400 mb-1">Prioriteit</label>
|
||||
<select id="newTaskPriority" class="w-full bg-white dark:bg-slate-900 border border-slate-300 dark:border-slate-800 rounded-xl px-2.5 py-1.5 text-slate-900 dark:text-white text-xs focus:outline-none">
|
||||
<label class="block text-[11px] font-semibold text-slate-700 dark:text-slate-300 mb-1">Prioriteit</label>
|
||||
<select id="newTaskPriority" class="w-full bg-white dark:bg-slate-900 border border-slate-300 dark:border-slate-800 rounded-xl px-2.5 py-1.5 text-slate-900 dark:text-white text-xs focus:outline-none focus:ring-1 focus:ring-indigo-500">
|
||||
<option value="P1">P1 - Brandend / Focus</option>
|
||||
<option value="P2" selected>P2 - Belangrijk (Standaard)</option>
|
||||
<option value="P3">P3 - Regulier / Lage druk</option>
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-[11px] font-medium text-slate-600 dark:text-slate-400 mb-1">Tijdblok (Starttijd)</label>
|
||||
<input type="text" id="newTaskTime" placeholder="Bijv. 09:30" class="w-full bg-white dark:bg-slate-900 border border-slate-300 dark:border-slate-800 rounded-xl px-2.5 py-1.5 text-slate-900 dark:text-white text-xs font-mono focus:outline-none">
|
||||
<label class="block text-[11px] font-semibold text-slate-700 dark:text-slate-300 mb-1">Starttijd</label>
|
||||
<div class="relative">
|
||||
<input type="text" id="newTaskTime" placeholder="Bijv. 09:30" class="w-full bg-white dark:bg-slate-900 border border-slate-300 dark:border-slate-800 rounded-xl pl-2.5 pr-7 py-1.5 text-slate-900 dark:text-white text-xs font-mono focus:outline-none focus:ring-1 focus:ring-indigo-500" oninput="onNewTaskTimeOrDurationChange('start')">
|
||||
<i data-lucide="clock" class="w-3.5 h-3.5 text-slate-400 absolute right-2.5 top-2 pointer-events-none"></i>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-[11px] font-medium text-slate-600 dark:text-slate-400 mb-1">Tijdsduur</label>
|
||||
<select id="newTaskDuration" class="w-full bg-white dark:bg-slate-900 border border-slate-300 dark:border-slate-800 rounded-xl px-2.5 py-1.5 text-slate-900 dark:text-white text-xs focus:outline-none">
|
||||
<div class="flex items-center justify-between mb-1">
|
||||
<label class="block text-[11px] 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>
|
||||
</div>
|
||||
<div class="relative">
|
||||
<input type="text" id="newTaskEndTime" placeholder="Bijv. 09:40" class="w-full bg-white dark:bg-slate-900 border border-slate-300 dark:border-slate-800 rounded-xl pl-2.5 pr-7 py-1.5 text-slate-900 dark:text-white text-xs font-mono focus:outline-none focus:ring-1 focus:ring-indigo-500" oninput="onNewTaskTimeOrDurationChange('end')">
|
||||
<i data-lucide="clock-arrow-down" class="w-3.5 h-3.5 text-slate-400 absolute right-2.5 top-2 pointer-events-none"></i>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="flex items-center justify-between mb-1">
|
||||
<label class="block text-[11px] font-semibold text-slate-700 dark:text-slate-300">Tijdsduur</label>
|
||||
<span id="newTaskDurationDisplay" class="text-[10px] font-mono font-bold text-indigo-600 dark:text-indigo-400">30m</span>
|
||||
</div>
|
||||
<div class="flex items-center space-x-1.5">
|
||||
<?php
|
||||
$defDuration = (int)($workdaySettings['default_task_duration'] ?? 30);
|
||||
$durations = [15 => '15 minuten', 30 => '30 minuten', 45 => '45 minuten', 60 => '60 minuten (1 uur)', 90 => '90 minuten (1,5 uur)', 120 => '120 minuten (2 uur)'];
|
||||
foreach ($durations as $mins => $label):
|
||||
?>
|
||||
<option value="<?= $mins ?>" <?= $defDuration === $mins ? 'selected' : '' ?>><?= $label ?></option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
<select id="newTaskDuration" class="w-full bg-white dark:bg-slate-900 border border-slate-300 dark:border-slate-800 rounded-xl px-2.5 py-1.5 text-slate-900 dark:text-white text-xs focus:outline-none focus:ring-1 focus:ring-indigo-500" onchange="onNewTaskTimeOrDurationChange('duration')">
|
||||
<option value="5" <?= $defDuration === 5 ? 'selected' : '' ?>>5 minuten</option>
|
||||
<option value="10" <?= $defDuration === 10 ? 'selected' : '' ?>>10 minuten</option>
|
||||
<option value="15" <?= $defDuration === 15 ? 'selected' : '' ?>>15 minuten</option>
|
||||
<option value="20" <?= $defDuration === 20 ? 'selected' : '' ?>>20 minuten</option>
|
||||
<option value="25" <?= $defDuration === 25 ? 'selected' : '' ?>>25 minuten</option>
|
||||
<option value="30" <?= $defDuration === 30 ? 'selected' : '' ?>>30 minuten</option>
|
||||
<option value="45" <?= $defDuration === 45 ? 'selected' : '' ?>>45 minuten</option>
|
||||
<option value="60" <?= $defDuration === 60 ? 'selected' : '' ?>>60 minuten (1 uur)</option>
|
||||
<option value="90" <?= $defDuration === 90 ? 'selected' : '' ?>>90 minuten (1,5 uur)</option>
|
||||
<option value="120" <?= $defDuration === 120 ? 'selected' : '' ?>>120 minuten (2 uur)</option>
|
||||
<option value="custom">Aangepast...</option>
|
||||
</select>
|
||||
<input type="number" id="newTaskCustomDuration" min="1" max="720" placeholder="Min" class="hidden w-20 bg-white dark:bg-slate-900 border border-slate-300 dark:border-slate-800 rounded-xl px-2 py-1.5 text-slate-900 dark:text-white text-xs font-mono focus:outline-none focus:ring-1 focus:ring-indigo-500" oninput="onNewTaskTimeOrDurationChange('custom_duration')">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -587,7 +613,7 @@ for ($h = $startHour; $h <= $endHour; $h++) {
|
||||
<?php if (!empty($task['time_block_start'])): ?>
|
||||
<span class="text-[10px] text-slate-500 dark:text-slate-400 font-mono flex items-center space-x-1">
|
||||
<i data-lucide="clock" class="w-2.5 h-2.5"></i>
|
||||
<span><?= htmlspecialchars($task['time_block_start']) ?><?= !empty($task['time_block_end']) ? ' - ' . htmlspecialchars($task['time_block_end']) : '' ?></span>
|
||||
<span><?= htmlspecialchars($task['time_block_start']) ?><?= !empty($task['time_block_end']) ? ' - ' . htmlspecialchars($task['time_block_end']) : '' ?><?= !empty($task['duration_minutes']) ? ' (' . (int)$task['duration_minutes'] . 'm)' : '' ?></span>
|
||||
</span>
|
||||
<?php endif; ?>
|
||||
<?php if (!empty($task['external_url'])): ?>
|
||||
@@ -613,12 +639,25 @@ for ($h = $startHour; $h <= $endHour; $h++) {
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if (!$task['is_completed']): ?>
|
||||
<button onclick="selectFocusTask('<?= htmlspecialchars(addslashes($task['title'])) ?>')" title="Koppel aan Focus Timer" class="p-1.5 rounded-lg text-slate-400 hover:text-amber-500 hover:bg-amber-500/10 transition">
|
||||
<button onclick="selectFocusTask('<?= htmlspecialchars(addslashes($task['title'])) ?>')" title="Koppel aan Focus Timer" class="p-1.5 rounded-lg text-slate-400 hover:text-amber-500 hover:bg-amber-500/10 transition cursor-pointer">
|
||||
<i data-lucide="flame" class="w-3.5 h-3.5"></i>
|
||||
</button>
|
||||
<?php endif; ?>
|
||||
|
||||
<button onclick="deleteTask(<?= $task['id'] ?>)" title="Verwijderen" class="p-1.5 rounded-lg text-slate-400 hover:text-red-600 dark:hover:text-red-400 hover:bg-red-500/10 opacity-0 group-hover:opacity-100 transition">
|
||||
<button onclick="openEditTaskModal(<?= htmlspecialchars(json_encode([
|
||||
'id' => (int)$task['id'],
|
||||
'title' => $task['title'],
|
||||
'priority' => $task['priority'],
|
||||
'start' => $task['time_block_start'] ?? '',
|
||||
'end' => $task['time_block_end'] ?? '',
|
||||
'duration' => (int)($task['duration_minutes'] ?: 30),
|
||||
'source' => $task['source'],
|
||||
'external_id' => $task['external_id'] ?? ''
|
||||
]), 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>
|
||||
</button>
|
||||
|
||||
<button onclick="deleteTask(<?= $task['id'] ?>)" title="Verwijderen" class="p-1.5 rounded-lg text-slate-400 hover:text-red-600 dark:hover:text-red-400 hover:bg-red-500/10 opacity-0 group-hover:opacity-100 transition cursor-pointer">
|
||||
<i data-lucide="trash-2" class="w-3.5 h-3.5"></i>
|
||||
</button>
|
||||
</div>
|
||||
@@ -834,6 +873,100 @@ for ($h = $startHour; $h <= $endHour; $h++) {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Modal: Taak & Tijdblok Bewerken (Vrij invullen van tijden & duur) -->
|
||||
<div id="editTaskModal" class="fixed inset-0 z-50 bg-slate-900/60 backdrop-blur-xs hidden items-center justify-center p-4">
|
||||
<div class="bg-white dark:bg-[#111622] border border-slate-200/90 dark:border-slate-800 rounded-2xl 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">
|
||||
<div class="w-8 h-8 rounded-xl bg-indigo-50 dark:bg-indigo-500/10 border border-indigo-200 dark:border-indigo-500/20 text-indigo-600 dark:text-indigo-400 flex items-center justify-center">
|
||||
<i data-lucide="edit-3" class="w-4 h-4"></i>
|
||||
</div>
|
||||
<div>
|
||||
<h3 class="text-sm font-bold text-slate-900 dark:text-white">Taak & Tijdblok Bewerken</h3>
|
||||
<p class="text-[10px] text-slate-500 dark:text-slate-400">Pas titel, start-/eindtijd en tijdsduur vrij aan</p>
|
||||
</div>
|
||||
</div>
|
||||
<button onclick="closeEditTaskModal()" class="p-1.5 text-slate-400 hover:text-slate-600 dark:hover:text-slate-200 rounded-lg hover:bg-slate-100 dark:hover:bg-slate-800 transition cursor-pointer">
|
||||
<i data-lucide="x" class="w-4 h-4"></i>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<input type="hidden" id="editTaskId">
|
||||
|
||||
<div>
|
||||
<label class="block text-xs font-semibold text-slate-700 dark:text-slate-300 mb-1">Taaktitel</label>
|
||||
<input type="text" id="editTaskTitle" class="w-full bg-white dark:bg-slate-950 border border-slate-300 dark:border-slate-800 rounded-xl px-3 py-2 text-slate-900 dark:text-white text-xs focus:outline-none focus:ring-1 focus:ring-indigo-500">
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-1 sm:grid-cols-2 gap-3">
|
||||
<div>
|
||||
<label class="block text-xs font-semibold text-slate-700 dark:text-slate-300 mb-1">Prioriteit</label>
|
||||
<select id="editTaskPriority" class="w-full bg-white dark:bg-slate-950 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">
|
||||
<option value="P1">P1 - Brandend / Focus</option>
|
||||
<option value="P2">P2 - Belangrijk (Standaard)</option>
|
||||
<option value="P3">P3 - Regulier / Lage druk</option>
|
||||
</select>
|
||||
</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">Tijdsduur</label>
|
||||
<span id="editTaskDurationDisplay" class="text-[11px] font-mono font-bold text-indigo-600 dark:text-indigo-400">30m</span>
|
||||
</div>
|
||||
<div class="flex items-center space-x-1.5">
|
||||
<select id="editTaskDuration" class="w-full bg-white dark:bg-slate-950 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" onchange="onEditTaskTimeOrDurationChange('duration')">
|
||||
<option value="5">5 minuten</option>
|
||||
<option value="10">10 minuten</option>
|
||||
<option value="15">15 minuten</option>
|
||||
<option value="20">20 minuten</option>
|
||||
<option value="25">25 minuten</option>
|
||||
<option value="30">30 minuten</option>
|
||||
<option value="45">45 minuten</option>
|
||||
<option value="60">60 minuten (1 uur)</option>
|
||||
<option value="90">90 minuten (1,5 uur)</option>
|
||||
<option value="120">120 minuten (2 uur)</option>
|
||||
<option value="custom">Aangepast...</option>
|
||||
</select>
|
||||
<input type="number" id="editTaskCustomDuration" min="1" max="720" placeholder="Min" class="hidden w-20 bg-white dark:bg-slate-950 border border-slate-300 dark:border-slate-800 rounded-xl px-2 py-2 text-slate-900 dark:text-white text-xs font-mono focus:outline-none focus:ring-1 focus:ring-indigo-500" oninput="onEditTaskTimeOrDurationChange('custom_duration')">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-1 sm:grid-cols-2 gap-3">
|
||||
<div>
|
||||
<label class="block text-xs font-semibold text-slate-700 dark:text-slate-300 mb-1">Starttijd</label>
|
||||
<div class="relative">
|
||||
<input type="text" id="editTaskStartTime" placeholder="Bijv. 09:30" class="w-full bg-white dark:bg-slate-950 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" 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>
|
||||
</div>
|
||||
</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">Eindtijd</label>
|
||||
<span class="text-[9px] text-indigo-600 dark:text-indigo-400 font-medium">Vrij invulbaar</span>
|
||||
</div>
|
||||
<div class="relative">
|
||||
<input type="text" id="editTaskEndTime" placeholder="Bijv. 09:40" class="w-full bg-white dark:bg-slate-950 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" 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>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="editTaskOutlookNotice" class="hidden p-2.5 rounded-xl bg-sky-50 dark:bg-sky-500/10 border border-sky-200 dark:border-sky-500/20 text-sky-800 dark:text-sky-300 text-[11px] flex items-center space-x-2">
|
||||
<i data-lucide="refresh-cw" class="w-3.5 h-3.5 flex-shrink-0"></i>
|
||||
<span>Deze taak is gekoppeld aan Outlook 365. Wijzigingen worden automatisch in uw Outlook agenda gesynchroniseerd.</span>
|
||||
</div>
|
||||
|
||||
<div class="flex justify-end space-x-2 pt-3 border-t border-slate-100 dark:border-slate-800/80">
|
||||
<button type="button" onclick="closeEditTaskModal()" class="px-4 py-2 rounded-xl bg-slate-100 hover:bg-slate-200 dark:bg-slate-800 dark:hover:bg-slate-700 text-slate-700 dark:text-slate-300 text-xs font-semibold transition cursor-pointer">
|
||||
Annuleren
|
||||
</button>
|
||||
<button type="button" onclick="submitEditTask()" id="btnSaveEditTask" class="px-4 py-2 rounded-xl bg-indigo-600 hover:bg-indigo-500 text-white text-xs font-bold transition shadow-xs cursor-pointer">
|
||||
Wijzigingen Opslaan
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// --- Focus Timer Logica (Release v1.4) ---
|
||||
const pomodoroFocusTime = <?= (int)$workdaySettings['pomodoro_focus'] ?>;
|
||||
@@ -981,6 +1114,139 @@ async function saveGoal() {
|
||||
}
|
||||
}
|
||||
|
||||
// --- Tijdblok Hulpfuncties & Vrije Duur Berekening ---
|
||||
function parseTimeToMinutes(str) {
|
||||
if (!str || typeof str !== 'string') return null;
|
||||
str = str.trim();
|
||||
if (!str) return null;
|
||||
|
||||
let m = str.match(/^(\d{1,2})[:.](\d{2})$/);
|
||||
if (m) {
|
||||
const hours = parseInt(m[1], 10);
|
||||
const mins = parseInt(m[2], 10);
|
||||
if (hours >= 0 && hours < 24 && mins >= 0 && mins < 60) {
|
||||
return (hours * 60) + mins;
|
||||
}
|
||||
}
|
||||
|
||||
m = str.match(/^(\d{1,2})$/);
|
||||
if (m) {
|
||||
const hours = parseInt(m[1], 10);
|
||||
if (hours >= 0 && hours < 24) {
|
||||
return hours * 60;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
function formatMinutesToTime(totalMins) {
|
||||
totalMins = (totalMins + (24 * 60)) % (24 * 60);
|
||||
const h = Math.floor(totalMins / 60);
|
||||
const m = totalMins % 60;
|
||||
return `${String(h).padStart(2, '0')}:${String(m).padStart(2, '0')}`;
|
||||
}
|
||||
|
||||
// Tweeweg synchronisatie bij Nieuwe Taak (Starttijd, Eindtijd & Tijdsduur)
|
||||
function onNewTaskTimeOrDurationChange(source) {
|
||||
const startInput = document.getElementById('newTaskTime');
|
||||
const endInput = document.getElementById('newTaskEndTime');
|
||||
const durationSelect = document.getElementById('newTaskDuration');
|
||||
const customDurationInput = document.getElementById('newTaskCustomDuration');
|
||||
const durationDisplay = document.getElementById('newTaskDurationDisplay');
|
||||
|
||||
if (!startInput || !endInput || !durationSelect) return;
|
||||
const startMins = parseTimeToMinutes(startInput.value);
|
||||
|
||||
if (source === 'end') {
|
||||
const endMins = parseTimeToMinutes(endInput.value);
|
||||
if (startMins !== null && endMins !== null && endMins > startMins) {
|
||||
const diffMins = endMins - startMins;
|
||||
setSelectedNewTaskDuration(diffMins);
|
||||
}
|
||||
} else if (source === 'duration') {
|
||||
const val = durationSelect.value;
|
||||
if (val === 'custom') {
|
||||
customDurationInput.classList.remove('hidden');
|
||||
customDurationInput.focus();
|
||||
const customVal = parseInt(customDurationInput.value, 10);
|
||||
if (!isNaN(customVal) && customVal > 0 && startMins !== null) {
|
||||
endInput.value = formatMinutesToTime(startMins + customVal);
|
||||
if (durationDisplay) durationDisplay.innerText = `${customVal}m`;
|
||||
}
|
||||
} else {
|
||||
customDurationInput.classList.add('hidden');
|
||||
const dur = parseInt(val, 10);
|
||||
if (!isNaN(dur) && dur > 0) {
|
||||
if (durationDisplay) durationDisplay.innerText = `${dur}m`;
|
||||
if (startMins !== null) {
|
||||
endInput.value = formatMinutesToTime(startMins + dur);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (source === 'custom_duration') {
|
||||
const customVal = parseInt(customDurationInput.value, 10);
|
||||
if (!isNaN(customVal) && customVal > 0) {
|
||||
if (durationDisplay) durationDisplay.innerText = `${customVal}m`;
|
||||
if (startMins !== null) {
|
||||
endInput.value = formatMinutesToTime(startMins + customVal);
|
||||
}
|
||||
}
|
||||
} else if (source === 'start') {
|
||||
let dur = getSelectedNewTaskDurationMinutes();
|
||||
if (startMins !== null) {
|
||||
if (endInput.value && parseTimeToMinutes(endInput.value) !== null) {
|
||||
const currentEndMins = parseTimeToMinutes(endInput.value);
|
||||
if (currentEndMins > startMins) {
|
||||
setSelectedNewTaskDuration(currentEndMins - startMins);
|
||||
} else if (dur > 0) {
|
||||
endInput.value = formatMinutesToTime(startMins + dur);
|
||||
}
|
||||
} else if (dur > 0) {
|
||||
endInput.value = formatMinutesToTime(startMins + dur);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function getSelectedNewTaskDurationMinutes() {
|
||||
const durationSelect = document.getElementById('newTaskDuration');
|
||||
const customDurationInput = document.getElementById('newTaskCustomDuration');
|
||||
if (!durationSelect) return 30;
|
||||
if (durationSelect.value === 'custom') {
|
||||
const val = parseInt(customDurationInput?.value, 10);
|
||||
return (!isNaN(val) && val > 0) ? val : 30;
|
||||
}
|
||||
const val = parseInt(durationSelect.value, 10);
|
||||
return (!isNaN(val) && val > 0) ? val : 30;
|
||||
}
|
||||
|
||||
function setSelectedNewTaskDuration(mins) {
|
||||
const durationSelect = document.getElementById('newTaskDuration');
|
||||
const customDurationInput = document.getElementById('newTaskCustomDuration');
|
||||
const durationDisplay = document.getElementById('newTaskDurationDisplay');
|
||||
if (!durationSelect) return;
|
||||
|
||||
let found = false;
|
||||
for (let i = 0; i < durationSelect.options.length; i++) {
|
||||
if (durationSelect.options[i].value === String(mins)) {
|
||||
durationSelect.selectedIndex = i;
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found) {
|
||||
durationSelect.value = 'custom';
|
||||
if (customDurationInput) {
|
||||
customDurationInput.value = mins;
|
||||
customDurationInput.classList.remove('hidden');
|
||||
}
|
||||
} else {
|
||||
if (customDurationInput) customDurationInput.classList.add('hidden');
|
||||
}
|
||||
if (durationDisplay) durationDisplay.innerText = `${mins}m`;
|
||||
}
|
||||
|
||||
function toggleAddTaskForm() {
|
||||
const form = document.getElementById('addTaskForm');
|
||||
form.classList.toggle('hidden');
|
||||
@@ -990,21 +1256,32 @@ function toggleAddTaskForm() {
|
||||
}
|
||||
|
||||
function quickSchedule(hour) {
|
||||
toggleAddTaskForm();
|
||||
const form = document.getElementById('addTaskForm');
|
||||
if (form && form.classList.contains('hidden')) {
|
||||
form.classList.remove('hidden');
|
||||
}
|
||||
const timeInput = document.getElementById('newTaskTime');
|
||||
if (timeInput) timeInput.value = hour;
|
||||
if (timeInput) {
|
||||
timeInput.value = hour;
|
||||
onNewTaskTimeOrDurationChange('start');
|
||||
}
|
||||
const outlookCheckbox = document.getElementById('newTaskSyncOutlook');
|
||||
if (outlookCheckbox) outlookCheckbox.checked = true;
|
||||
document.getElementById('newTaskTitle')?.focus();
|
||||
}
|
||||
|
||||
async function submitNewTask() {
|
||||
const title = document.getElementById('newTaskTitle').value.trim();
|
||||
const priority = document.getElementById('newTaskPriority').value;
|
||||
const time = document.getElementById('newTaskTime').value.trim();
|
||||
const duration = document.getElementById('newTaskDuration') ? parseInt(document.getElementById('newTaskDuration').value) : 30;
|
||||
const endTime = document.getElementById('newTaskEndTime') ? document.getElementById('newTaskEndTime').value.trim() : '';
|
||||
const duration = getSelectedNewTaskDurationMinutes();
|
||||
const syncOutlook = document.getElementById('newTaskSyncOutlook') ? document.getElementById('newTaskSyncOutlook').checked : false;
|
||||
|
||||
if (!title) return;
|
||||
if (!title) {
|
||||
alert('Vul een taaktitel in.');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const res = await fetch('/api/planner.php?action=add_task', {
|
||||
@@ -1013,7 +1290,8 @@ async function submitNewTask() {
|
||||
body: JSON.stringify({
|
||||
title,
|
||||
priority,
|
||||
time_block_start: time,
|
||||
time_block_start: time,
|
||||
time_block_end: endTime,
|
||||
duration_minutes: duration,
|
||||
sync_to_outlook: syncOutlook,
|
||||
date: activePlannerDate
|
||||
@@ -1033,6 +1311,194 @@ async function submitNewTask() {
|
||||
}
|
||||
}
|
||||
|
||||
// --- Taak & Tijdblok Bewerken Modal Logica ---
|
||||
function openEditTaskModal(task) {
|
||||
if (!task) return;
|
||||
document.getElementById('editTaskId').value = task.id;
|
||||
document.getElementById('editTaskTitle').value = task.title || '';
|
||||
document.getElementById('editTaskPriority').value = task.priority || 'P2';
|
||||
document.getElementById('editTaskStartTime').value = task.start || '';
|
||||
document.getElementById('editTaskEndTime').value = task.end || '';
|
||||
|
||||
const dur = task.duration || 30;
|
||||
setSelectedEditTaskDuration(dur);
|
||||
|
||||
const outlookNotice = document.getElementById('editTaskOutlookNotice');
|
||||
if (outlookNotice) {
|
||||
if (task.source === 'OUTLOOK' || task.external_id) {
|
||||
outlookNotice.classList.remove('hidden');
|
||||
} else {
|
||||
outlookNotice.classList.add('hidden');
|
||||
}
|
||||
}
|
||||
|
||||
const modal = document.getElementById('editTaskModal');
|
||||
if (modal) {
|
||||
modal.classList.remove('hidden');
|
||||
modal.classList.add('flex');
|
||||
if (typeof lucide !== 'undefined') lucide.createIcons();
|
||||
document.getElementById('editTaskTitle')?.focus();
|
||||
}
|
||||
}
|
||||
|
||||
function closeEditTaskModal() {
|
||||
const modal = document.getElementById('editTaskModal');
|
||||
if (modal) {
|
||||
modal.classList.add('hidden');
|
||||
modal.classList.remove('flex');
|
||||
}
|
||||
}
|
||||
|
||||
function onEditTaskTimeOrDurationChange(source) {
|
||||
const startInput = document.getElementById('editTaskStartTime');
|
||||
const endInput = document.getElementById('editTaskEndTime');
|
||||
const durationSelect = document.getElementById('editTaskDuration');
|
||||
const customDurationInput = document.getElementById('editTaskCustomDuration');
|
||||
const durationDisplay = document.getElementById('editTaskDurationDisplay');
|
||||
|
||||
if (!startInput || !endInput || !durationSelect) return;
|
||||
const startMins = parseTimeToMinutes(startInput.value);
|
||||
|
||||
if (source === 'end') {
|
||||
const endMins = parseTimeToMinutes(endInput.value);
|
||||
if (startMins !== null && endMins !== null && endMins > startMins) {
|
||||
const diffMins = endMins - startMins;
|
||||
setSelectedEditTaskDuration(diffMins);
|
||||
}
|
||||
} else if (source === 'duration') {
|
||||
const val = durationSelect.value;
|
||||
if (val === 'custom') {
|
||||
customDurationInput.classList.remove('hidden');
|
||||
customDurationInput.focus();
|
||||
const customVal = parseInt(customDurationInput.value, 10);
|
||||
if (!isNaN(customVal) && customVal > 0 && startMins !== null) {
|
||||
endInput.value = formatMinutesToTime(startMins + customVal);
|
||||
if (durationDisplay) durationDisplay.innerText = `${customVal}m`;
|
||||
}
|
||||
} else {
|
||||
customDurationInput.classList.add('hidden');
|
||||
const dur = parseInt(val, 10);
|
||||
if (!isNaN(dur) && dur > 0) {
|
||||
if (durationDisplay) durationDisplay.innerText = `${dur}m`;
|
||||
if (startMins !== null) {
|
||||
endInput.value = formatMinutesToTime(startMins + dur);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (source === 'custom_duration') {
|
||||
const customVal = parseInt(customDurationInput.value, 10);
|
||||
if (!isNaN(customVal) && customVal > 0) {
|
||||
if (durationDisplay) durationDisplay.innerText = `${customVal}m`;
|
||||
if (startMins !== null) {
|
||||
endInput.value = formatMinutesToTime(startMins + customVal);
|
||||
}
|
||||
}
|
||||
} else if (source === 'start') {
|
||||
let dur = getSelectedEditTaskDurationMinutes();
|
||||
if (startMins !== null) {
|
||||
if (endInput.value && parseTimeToMinutes(endInput.value) !== null) {
|
||||
const currentEndMins = parseTimeToMinutes(endInput.value);
|
||||
if (currentEndMins > startMins) {
|
||||
setSelectedEditTaskDuration(currentEndMins - startMins);
|
||||
} else if (dur > 0) {
|
||||
endInput.value = formatMinutesToTime(startMins + dur);
|
||||
}
|
||||
} else if (dur > 0) {
|
||||
endInput.value = formatMinutesToTime(startMins + dur);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function getSelectedEditTaskDurationMinutes() {
|
||||
const durationSelect = document.getElementById('editTaskDuration');
|
||||
const customDurationInput = document.getElementById('editTaskCustomDuration');
|
||||
if (!durationSelect) return 30;
|
||||
if (durationSelect.value === 'custom') {
|
||||
const val = parseInt(customDurationInput?.value, 10);
|
||||
return (!isNaN(val) && val > 0) ? val : 30;
|
||||
}
|
||||
const val = parseInt(durationSelect.value, 10);
|
||||
return (!isNaN(val) && val > 0) ? val : 30;
|
||||
}
|
||||
|
||||
function setSelectedEditTaskDuration(mins) {
|
||||
const durationSelect = document.getElementById('editTaskDuration');
|
||||
const customDurationInput = document.getElementById('editTaskCustomDuration');
|
||||
const durationDisplay = document.getElementById('editTaskDurationDisplay');
|
||||
if (!durationSelect) return;
|
||||
|
||||
let found = false;
|
||||
for (let i = 0; i < durationSelect.options.length; i++) {
|
||||
if (durationSelect.options[i].value === String(mins)) {
|
||||
durationSelect.selectedIndex = i;
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found) {
|
||||
durationSelect.value = 'custom';
|
||||
if (customDurationInput) {
|
||||
customDurationInput.value = mins;
|
||||
customDurationInput.classList.remove('hidden');
|
||||
}
|
||||
} else {
|
||||
if (customDurationInput) customDurationInput.classList.add('hidden');
|
||||
}
|
||||
if (durationDisplay) durationDisplay.innerText = `${mins}m`;
|
||||
}
|
||||
|
||||
async function submitEditTask() {
|
||||
const taskId = parseInt(document.getElementById('editTaskId').value, 10);
|
||||
const title = document.getElementById('editTaskTitle').value.trim();
|
||||
const priority = document.getElementById('editTaskPriority').value;
|
||||
const startTime = document.getElementById('editTaskStartTime').value.trim();
|
||||
const endTime = document.getElementById('editTaskEndTime').value.trim();
|
||||
const duration = getSelectedEditTaskDurationMinutes();
|
||||
const saveBtn = document.getElementById('btnSaveEditTask');
|
||||
|
||||
if (!title) {
|
||||
alert('Vul een taaktitel in.');
|
||||
return;
|
||||
}
|
||||
|
||||
if (saveBtn) {
|
||||
saveBtn.innerText = 'Opslaan...';
|
||||
saveBtn.disabled = true;
|
||||
}
|
||||
|
||||
try {
|
||||
const res = await fetch('/api/planner.php?action=update_task', {
|
||||
method: 'POST',
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
body: JSON.stringify({
|
||||
task_id: taskId,
|
||||
title: title,
|
||||
priority: priority,
|
||||
time_block_start: startTime,
|
||||
time_block_end: endTime,
|
||||
duration_minutes: duration
|
||||
})
|
||||
});
|
||||
const data = await res.json();
|
||||
if (data.success) {
|
||||
location.reload();
|
||||
} else {
|
||||
alert(data.error || 'Kon taak niet bijwerken');
|
||||
if (saveBtn) {
|
||||
saveBtn.innerText = 'Wijzigingen Opslaan';
|
||||
saveBtn.disabled = false;
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
alert('Netwerkfout bij bijwerken van de taak');
|
||||
if (saveBtn) {
|
||||
saveBtn.innerText = 'Wijzigingen Opslaan';
|
||||
saveBtn.disabled = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function syncTaskToOutlook(taskId, btn) {
|
||||
if (!btn) return;
|
||||
const originalHtml = btn.innerHTML;
|
||||
|
||||
Reference in New Issue
Block a user