Compare commits
5 Commits
dd738e670b
...
434df2754c
| Author | SHA1 | Date | |
|---|---|---|---|
| 434df2754c | |||
| 890cbe7446 | |||
| e879e8c134 | |||
| ca6ed7d175 | |||
| 9262b09c4d |
+13
@@ -8,3 +8,16 @@ Thumbs.db
|
|||||||
*.log
|
*.log
|
||||||
.vscode/
|
.vscode/
|
||||||
.idea/
|
.idea/
|
||||||
|
|
||||||
|
# Environment
|
||||||
|
.env
|
||||||
|
.env*.local
|
||||||
|
|
||||||
|
# Systeem & Editor
|
||||||
|
.DS_Store
|
||||||
|
Thumbs.db
|
||||||
|
*.log
|
||||||
|
.vscode/
|
||||||
|
.idea/
|
||||||
|
config/.env.example
|
||||||
|
config/config.php
|
||||||
@@ -542,7 +542,13 @@ require_once __DIR__ . '/includes/header.php';
|
|||||||
<div>
|
<div>
|
||||||
<label class="block text-[11px] font-medium text-slate-600 dark:text-slate-400 mb-1">Standaard tijdslot</label>
|
<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">
|
<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">
|
||||||
|
<<<<<<< HEAD
|
||||||
|
<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="15" <?= $workdaySettings['default_task_duration'] == 15 ? 'selected' : '' ?>>15 minuten</option>
|
||||||
|
<option value="20" <?= $workdaySettings['default_task_duration'] == 20 ? 'selected' : '' ?>>20 minuten</option>
|
||||||
|
=======
|
||||||
|
<option value="15" <?= $workdaySettings['default_task_duration'] == 15 ? 'selected' : '' ?>>15 minuten</option>
|
||||||
|
>>>>>>> dd738e670b78819c11e329da93db1b472808f195
|
||||||
<option value="30" <?= $workdaySettings['default_task_duration'] == 30 ? 'selected' : '' ?>>30 minuten (Standaard)</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="45" <?= $workdaySettings['default_task_duration'] == 45 ? 'selected' : '' ?>>45 minuten</option>
|
||||||
<option value="60" <?= $workdaySettings['default_task_duration'] == 60 ? 'selected' : '' ?>>60 minuten (1 uur)</option>
|
<option value="60" <?= $workdaySettings['default_task_duration'] == 60 ? 'selected' : '' ?>>60 minuten (1 uur)</option>
|
||||||
|
|||||||
+253
@@ -38,8 +38,13 @@ try {
|
|||||||
$title = trim($data['title'] ?? '');
|
$title = trim($data['title'] ?? '');
|
||||||
$priority = in_array($data['priority'] ?? '', ['P1', 'P2', 'P3']) ? $data['priority'] : 'P2';
|
$priority = in_array($data['priority'] ?? '', ['P1', 'P2', 'P3']) ? $data['priority'] : 'P2';
|
||||||
$timeBlockStart = !empty($data['time_block_start']) ? trim($data['time_block_start']) : null;
|
$timeBlockStart = !empty($data['time_block_start']) ? trim($data['time_block_start']) : null;
|
||||||
|
<<<<<<< HEAD
|
||||||
|
$timeBlockEnd = !empty($data['time_block_end']) ? trim($data['time_block_end']) : null;
|
||||||
|
$durationMinutes = (int)($data['duration_minutes'] ?? 0);
|
||||||
|
=======
|
||||||
$durationMinutes = (int)($data['duration_minutes'] ?? 30);
|
$durationMinutes = (int)($data['duration_minutes'] ?? 30);
|
||||||
if ($durationMinutes <= 0) $durationMinutes = 30;
|
if ($durationMinutes <= 0) $durationMinutes = 30;
|
||||||
|
>>>>>>> dd738e670b78819c11e329da93db1b472808f195
|
||||||
$date = !empty($data['date']) ? trim($data['date']) : $today;
|
$date = !empty($data['date']) ? trim($data['date']) : $today;
|
||||||
$syncOutlook = !empty($data['sync_to_outlook']);
|
$syncOutlook = !empty($data['sync_to_outlook']);
|
||||||
|
|
||||||
@@ -48,6 +53,33 @@ try {
|
|||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
<<<<<<< HEAD
|
||||||
|
// 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
=======
|
||||||
|
>>>>>>> dd738e670b78819c11e329da93db1b472808f195
|
||||||
// Haal of maak daily plan op
|
// Haal of maak daily plan op
|
||||||
$stmtPlan = $db->prepare("
|
$stmtPlan = $db->prepare("
|
||||||
INSERT INTO daily_plans (user_id, plan_date)
|
INSERT INTO daily_plans (user_id, plan_date)
|
||||||
@@ -60,7 +92,10 @@ try {
|
|||||||
$source = !empty($data['source']) ? substr(trim($data['source']), 0, 50) : 'FOCIZ';
|
$source = !empty($data['source']) ? substr(trim($data['source']), 0, 50) : 'FOCIZ';
|
||||||
$externalId = !empty($data['external_id']) ? substr(trim($data['external_id']), 0, 255) : null;
|
$externalId = !empty($data['external_id']) ? substr(trim($data['external_id']), 0, 255) : null;
|
||||||
$externalUrl = !empty($data['external_url']) ? trim($data['external_url']) : null;
|
$externalUrl = !empty($data['external_url']) ? trim($data['external_url']) : null;
|
||||||
|
<<<<<<< HEAD
|
||||||
|
=======
|
||||||
$timeBlockEnd = null;
|
$timeBlockEnd = null;
|
||||||
|
>>>>>>> dd738e670b78819c11e329da93db1b472808f195
|
||||||
$outlookSyncError = null;
|
$outlookSyncError = null;
|
||||||
|
|
||||||
// Als synchronisatie naar Outlook gewenst is (of source is OUTLOOK) en nog geen extern ID heeft
|
// Als synchronisatie naar Outlook gewenst is (of source is OUTLOOK) en nog geen extern ID heeft
|
||||||
@@ -75,6 +110,10 @@ try {
|
|||||||
'description' => "Gepland via Fociz Dagplanning ({$priority})",
|
'description' => "Gepland via Fociz Dagplanning ({$priority})",
|
||||||
'date' => $date,
|
'date' => $date,
|
||||||
'start_time' => $timeBlockStart,
|
'start_time' => $timeBlockStart,
|
||||||
|
<<<<<<< HEAD
|
||||||
|
'end_time' => $timeBlockEnd,
|
||||||
|
=======
|
||||||
|
>>>>>>> dd738e670b78819c11e329da93db1b472808f195
|
||||||
'duration_minutes' => $durationMinutes,
|
'duration_minutes' => $durationMinutes,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
@@ -83,12 +122,18 @@ try {
|
|||||||
$externalId = $msResult['event_id'];
|
$externalId = $msResult['event_id'];
|
||||||
$externalUrl = $msResult['web_link'] ?? 'https://outlook.office.com';
|
$externalUrl = $msResult['web_link'] ?? 'https://outlook.office.com';
|
||||||
$timeBlockStart = $msResult['start_time'] ?? $timeBlockStart;
|
$timeBlockStart = $msResult['start_time'] ?? $timeBlockStart;
|
||||||
|
<<<<<<< HEAD
|
||||||
|
$timeBlockEnd = $msResult['end_time'] ?? $timeBlockEnd;
|
||||||
|
=======
|
||||||
$timeBlockEnd = $msResult['end_time'] ?? null;
|
$timeBlockEnd = $msResult['end_time'] ?? null;
|
||||||
|
>>>>>>> dd738e670b78819c11e329da93db1b472808f195
|
||||||
} else {
|
} else {
|
||||||
$outlookSyncError = $msResult['error'] ?? 'Kon afspraak niet aanmaken in Microsoft Outlook';
|
$outlookSyncError = $msResult['error'] ?? 'Kon afspraak niet aanmaken in Microsoft Outlook';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
<<<<<<< HEAD
|
||||||
|
=======
|
||||||
// Bereken timeBlockEnd indien nog niet gezet maar wel starttijd bekend
|
// Bereken timeBlockEnd indien nog niet gezet maar wel starttijd bekend
|
||||||
if (!empty($timeBlockStart) && empty($timeBlockEnd)) {
|
if (!empty($timeBlockStart) && empty($timeBlockEnd)) {
|
||||||
try {
|
try {
|
||||||
@@ -100,6 +145,7 @@ try {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
>>>>>>> dd738e670b78819c11e329da93db1b472808f195
|
||||||
$stmt = $db->prepare("
|
$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)
|
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', ?, ?, ?)
|
VALUES (?, ?, ?, ?, ?, ?, ?, 'TODO', ?, ?, ?)
|
||||||
@@ -116,6 +162,93 @@ try {
|
|||||||
'time_block_start' => $timeBlockStart,
|
'time_block_start' => $timeBlockStart,
|
||||||
'time_block_end' => $timeBlockEnd,
|
'time_block_end' => $timeBlockEnd,
|
||||||
'duration_minutes' => $durationMinutes,
|
'duration_minutes' => $durationMinutes,
|
||||||
|
<<<<<<< HEAD
|
||||||
|
'source' => $source,
|
||||||
|
'external_id' => $externalId,
|
||||||
|
'external_url' => $externalUrl
|
||||||
|
],
|
||||||
|
'outlook_synced' => ($source === 'OUTLOOK' && !empty($externalId)),
|
||||||
|
'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
|
||||||
|
=======
|
||||||
'is_completed' => 0,
|
'is_completed' => 0,
|
||||||
'status' => 'TODO',
|
'status' => 'TODO',
|
||||||
'source' => $source,
|
'source' => $source,
|
||||||
@@ -124,6 +257,7 @@ try {
|
|||||||
],
|
],
|
||||||
'outlook_synced' => ($source === 'OUTLOOK' && !empty($externalId)),
|
'outlook_synced' => ($source === 'OUTLOOK' && !empty($externalId)),
|
||||||
'outlook_error' => $outlookSyncError,
|
'outlook_error' => $outlookSyncError,
|
||||||
|
>>>>>>> dd738e670b78819c11e329da93db1b472808f195
|
||||||
]);
|
]);
|
||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
@@ -166,6 +300,10 @@ try {
|
|||||||
|
|
||||||
$planDate = $task['plan_date'] ?: $today;
|
$planDate = $task['plan_date'] ?: $today;
|
||||||
$startTime = !empty($task['time_block_start']) ? $task['time_block_start'] : ($data['time_block_start'] ?? '09:00');
|
$startTime = !empty($task['time_block_start']) ? $task['time_block_start'] : ($data['time_block_start'] ?? '09:00');
|
||||||
|
<<<<<<< HEAD
|
||||||
|
$endTime = !empty($task['time_block_end']) ? $task['time_block_end'] : null;
|
||||||
|
=======
|
||||||
|
>>>>>>> dd738e670b78819c11e329da93db1b472808f195
|
||||||
$duration = (int)($task['duration_minutes'] ?: 30);
|
$duration = (int)($task['duration_minutes'] ?: 30);
|
||||||
|
|
||||||
$msResult = microsoft_create_event($user['id'], [
|
$msResult = microsoft_create_event($user['id'], [
|
||||||
@@ -173,6 +311,10 @@ try {
|
|||||||
'description' => "Gepland via Fociz Dagplanning (" . $task['priority'] . ")",
|
'description' => "Gepland via Fociz Dagplanning (" . $task['priority'] . ")",
|
||||||
'date' => $planDate,
|
'date' => $planDate,
|
||||||
'start_time' => $startTime,
|
'start_time' => $startTime,
|
||||||
|
<<<<<<< HEAD
|
||||||
|
'end_time' => $endTime,
|
||||||
|
=======
|
||||||
|
>>>>>>> dd738e670b78819c11e329da93db1b472808f195
|
||||||
'duration_minutes' => $duration,
|
'duration_minutes' => $duration,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
@@ -252,6 +394,117 @@ try {
|
|||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
<<<<<<< HEAD
|
||||||
|
// 6. Realtime Outlook synchronisatie & controle op gewijzigde/gewiste items
|
||||||
|
if ($action === 'check_outlook_sync') {
|
||||||
|
$rawInput = file_get_contents('php://input');
|
||||||
|
$data = !empty($rawInput) ? (json_decode($rawInput, true) ?: []) : [];
|
||||||
|
if (empty($data)) {
|
||||||
|
$data = $_GET;
|
||||||
|
}
|
||||||
|
|
||||||
|
$planDate = !empty($data['plan_date']) ? trim($data['plan_date']) : $today;
|
||||||
|
$weekStart = !empty($data['week_start']) ? trim($data['week_start']) : date('Y-m-d', strtotime('monday this week', strtotime($planDate)));
|
||||||
|
$weekEnd = !empty($data['week_end']) ? trim($data['week_end']) : date('Y-m-d', strtotime('sunday this week', strtotime($planDate)));
|
||||||
|
$lastHash = !empty($data['last_hash']) ? trim($data['last_hash']) : '';
|
||||||
|
|
||||||
|
// Controleer of Outlook gekoppeld is
|
||||||
|
$stmtInt = $db->prepare("SELECT * FROM integrations WHERE user_id = ? AND provider = 'OUTLOOK' AND is_enabled = 1");
|
||||||
|
$stmtInt->execute([$user['id']]);
|
||||||
|
$integration = $stmtInt->fetch();
|
||||||
|
|
||||||
|
if (!$integration || empty($integration['access_token']) || !empty($integration['is_demo_mode'])) {
|
||||||
|
echo json_encode([
|
||||||
|
'success' => true,
|
||||||
|
'connected' => false,
|
||||||
|
'has_changes' => false,
|
||||||
|
'last_sync' => date('H:i:s'),
|
||||||
|
]);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Haal live afspraken op voor de hele week
|
||||||
|
$weekLiveEvents = microsoft_fetch_events_range($user['id'], $weekStart, $weekEnd);
|
||||||
|
|
||||||
|
// Voer reconciliatie uit met de Fociz tasks tabel
|
||||||
|
$reconcileRes = microsoft_reconcile_tasks_with_outlook($user['id'], $weekStart, $weekEnd, $weekLiveEvents);
|
||||||
|
|
||||||
|
// Haal de geactualiseerde taken voor deze dag op
|
||||||
|
$stmtPlan = $db->prepare("SELECT id FROM daily_plans WHERE user_id = ? AND plan_date = ?");
|
||||||
|
$stmtPlan->execute([$user['id'], $planDate]);
|
||||||
|
$planId = $stmtPlan->fetchColumn();
|
||||||
|
|
||||||
|
$tasks = [];
|
||||||
|
if ($planId) {
|
||||||
|
$stmtTasks = $db->prepare("
|
||||||
|
SELECT id, title, priority, time_block_start, time_block_end, duration_minutes, is_completed, status, source, external_id
|
||||||
|
FROM tasks
|
||||||
|
WHERE user_id = ? AND (daily_plan_id = ? " . ($planDate === $today ? "OR daily_plan_id IS NULL" : "") . ")
|
||||||
|
ORDER BY is_completed ASC, priority ASC, time_block_start ASC, created_at DESC
|
||||||
|
");
|
||||||
|
$stmtTasks->execute([$user['id'], $planId]);
|
||||||
|
$tasks = $stmtTasks->fetchAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Filter events voor de geselecteerde dag die geen gesynchroniseerde Fociz taak zijn
|
||||||
|
$syncedIds = [];
|
||||||
|
foreach ($tasks as $t) {
|
||||||
|
if (!empty($t['external_id'])) {
|
||||||
|
$syncedIds[$t['external_id']] = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$dayEvents = [];
|
||||||
|
foreach ($weekLiveEvents as $e) {
|
||||||
|
if (($e['date'] ?? '') === $planDate) {
|
||||||
|
if (!empty($e['id']) && isset($syncedIds[$e['id']])) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$dayEvents[] = [
|
||||||
|
'id' => $e['id'],
|
||||||
|
'start' => $e['start'],
|
||||||
|
'end' => $e['end'],
|
||||||
|
'subject' => $e['subject'],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bereken status hash voor de dag
|
||||||
|
$hashData = [
|
||||||
|
'events' => $dayEvents,
|
||||||
|
'tasks' => array_map(function($t) {
|
||||||
|
return [
|
||||||
|
'id' => $t['id'],
|
||||||
|
'title' => $t['title'],
|
||||||
|
'time_block_start' => $t['time_block_start'],
|
||||||
|
'time_block_end' => $t['time_block_end'],
|
||||||
|
'is_completed' => $t['is_completed'],
|
||||||
|
];
|
||||||
|
}, $tasks)
|
||||||
|
];
|
||||||
|
$currentHash = md5(json_encode($hashData));
|
||||||
|
|
||||||
|
$hasChanges = ($reconcileRes['deleted'] > 0 || $reconcileRes['updated'] > 0);
|
||||||
|
if (!empty($lastHash) && $lastHash !== $currentHash) {
|
||||||
|
$hasChanges = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
echo json_encode([
|
||||||
|
'success' => true,
|
||||||
|
'connected' => true,
|
||||||
|
'has_changes' => $hasChanges,
|
||||||
|
'hash' => $currentHash,
|
||||||
|
'deleted_count' => $reconcileRes['deleted'],
|
||||||
|
'updated_count' => $reconcileRes['updated'],
|
||||||
|
'events_count' => count($dayEvents),
|
||||||
|
'tasks_count' => count($tasks),
|
||||||
|
'last_sync' => date('H:i:s'),
|
||||||
|
]);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
=======
|
||||||
|
>>>>>>> dd738e670b78819c11e329da93db1b472808f195
|
||||||
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);
|
||||||
|
|||||||
@@ -1,6 +1,17 @@
|
|||||||
# MariaDB Verbinding
|
# MariaDB Verbinding
|
||||||
DB_HOST="192.168.2.33"
|
DB_HOST="192.168.2.33"
|
||||||
DB_PORT="3306"
|
DB_PORT="3306"
|
||||||
|
<<<<<<< HEAD
|
||||||
|
DB_NAME="focizdev"
|
||||||
|
DB_USER="focizdev_user"
|
||||||
|
DB_PASS="F0c1z_Dev_Secure_2026!"
|
||||||
|
|
||||||
|
# Omgeving: 'development' (knippert rood) of 'live' (knippert groen)
|
||||||
|
APP_ENV="development"
|
||||||
|
|
||||||
|
# Applicatie URL (voor uitnodigingslinks en OAuth redirects: https://dev.fociz.nl of https://fociz.nl)
|
||||||
|
APP_URL="https://dev.fociz.nl"
|
||||||
|
=======
|
||||||
DB_NAME="fociz"
|
DB_NAME="fociz"
|
||||||
DB_USER="focizlive_user"
|
DB_USER="focizlive_user"
|
||||||
DB_PASS="F0c1z_Live_Secure_2026!"
|
DB_PASS="F0c1z_Live_Secure_2026!"
|
||||||
@@ -10,6 +21,7 @@ APP_ENV="live"
|
|||||||
|
|
||||||
# Applicatie URL (voor uitnodigingslinks en OAuth redirects: https://dev.fociz.nl of https://fociz.nl)
|
# Applicatie URL (voor uitnodigingslinks en OAuth redirects: https://dev.fociz.nl of https://fociz.nl)
|
||||||
APP_URL="https://www.fociz.nl"
|
APP_URL="https://www.fociz.nl"
|
||||||
|
>>>>>>> dd738e670b78819c11e329da93db1b472808f195
|
||||||
|
|
||||||
# Applicatie Geheime Sleutel (willekeurige tekenreeks)
|
# Applicatie Geheime Sleutel (willekeurige tekenreeks)
|
||||||
APP_SECRET="fociz-maria-db-super-secret-key-998877"
|
APP_SECRET="fociz-maria-db-super-secret-key-998877"
|
||||||
|
|||||||
@@ -68,16 +68,26 @@ if (!function_exists('fociz_env')) {
|
|||||||
// MariaDB Verbindingsgegevens (uit .env of .env.example)
|
// MariaDB Verbindingsgegevens (uit .env of .env.example)
|
||||||
define('DB_HOST', fociz_env('DB_HOST', '192.168.2.33'));
|
define('DB_HOST', fociz_env('DB_HOST', '192.168.2.33'));
|
||||||
define('DB_PORT', fociz_env('DB_PORT', '3306'));
|
define('DB_PORT', fociz_env('DB_PORT', '3306'));
|
||||||
|
<<<<<<< HEAD
|
||||||
|
define('DB_NAME', fociz_env('DB_NAME', 'focizdev'));
|
||||||
|
define('DB_USER', fociz_env('DB_USER', 'focizdev_user'));
|
||||||
|
define('DB_PASS', fociz_env('DB_PASS', 'F0c1z_Dev_Secure_2026!'));
|
||||||
|
=======
|
||||||
define('DB_NAME', fociz_env('DB_NAME', 'fociz'));
|
define('DB_NAME', fociz_env('DB_NAME', 'fociz'));
|
||||||
define('DB_USER', fociz_env('DB_USER', 'focizlive_user'));
|
define('DB_USER', fociz_env('DB_USER', 'focizlive_user'));
|
||||||
define('DB_PASS', fociz_env('DB_PASS', 'F0c1z_Live_Secure_2026!'));
|
define('DB_PASS', fociz_env('DB_PASS', 'F0c1z_Live_Secure_2026!'));
|
||||||
|
>>>>>>> dd738e670b78819c11e329da93db1b472808f195
|
||||||
define('DB_CHARSET', 'utf8mb4');
|
define('DB_CHARSET', 'utf8mb4');
|
||||||
|
|
||||||
// Bepaal de dynamische of geconfigureerde applicatie URL
|
// Bepaal de dynamische of geconfigureerde applicatie URL
|
||||||
if (!function_exists('fociz_detect_app_url')) {
|
if (!function_exists('fociz_detect_app_url')) {
|
||||||
function fociz_detect_app_url(): string {
|
function fociz_detect_app_url(): string {
|
||||||
$envUrl = fociz_env('APP_URL');
|
$envUrl = fociz_env('APP_URL');
|
||||||
|
<<<<<<< HEAD
|
||||||
|
if (!empty($envUrl) && !str_contains($envUrl, 'uw-domein.nl')) {
|
||||||
|
=======
|
||||||
if (!empty($envUrl) && !str_contains($envUrl, 'www.fociz.nl')) {
|
if (!empty($envUrl) && !str_contains($envUrl, 'www.fociz.nl')) {
|
||||||
|
>>>>>>> dd738e670b78819c11e329da93db1b472808f195
|
||||||
return rtrim($envUrl, '/');
|
return rtrim($envUrl, '/');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -563,6 +563,78 @@ function microsoft_delete_event(int $userId, string $eventId): bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
<<<<<<< HEAD
|
||||||
|
* 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]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
=======
|
||||||
|
>>>>>>> dd738e670b78819c11e329da93db1b472808f195
|
||||||
* Ontkoppelt Microsoft integratie voor een gebruiker
|
* Ontkoppelt Microsoft integratie voor een gebruiker
|
||||||
* Wist uitsluitend de persoonlijke tokens; behoudt de centrale Azure App gegevens indien aanwezig.
|
* Wist uitsluitend de persoonlijke tokens; behoudt de centrale Azure App gegevens indien aanwezig.
|
||||||
*/
|
*/
|
||||||
@@ -594,3 +666,226 @@ function microsoft_disconnect(int $userId): bool {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
<<<<<<< HEAD
|
||||||
|
/**
|
||||||
|
* Haalt één specifieke afspraak op uit Microsoft Outlook via Graph API.
|
||||||
|
* Retourneert:
|
||||||
|
* - array met event data indien gevonden (HTTP 200)
|
||||||
|
* - false indien definitief verwijderd / geannuleerd (HTTP 404 of isCancelled)
|
||||||
|
* - null indien er een netwerk/server error optreedt (beschermt taken tegen onterecht wissen)
|
||||||
|
*/
|
||||||
|
function microsoft_get_single_event(int $userId, string $eventId): false|array|null {
|
||||||
|
if (empty($eventId)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$token = microsoft_get_valid_token($userId);
|
||||||
|
if (!$token) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$url = MS_GRAPH_BASE_URL . '/me/events/' . urlencode($eventId) . '?' . http_build_query([
|
||||||
|
'$select' => 'id,subject,start,end,isCancelled',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$response = fociz_http_request($url, [
|
||||||
|
'method' => 'GET',
|
||||||
|
'headers' => [
|
||||||
|
'Authorization' => "Bearer {$token}",
|
||||||
|
'Accept' => 'application/json',
|
||||||
|
'Prefer' => 'outlook.timezone="Europe/Amsterdam"',
|
||||||
|
],
|
||||||
|
'timeout' => 8,
|
||||||
|
]);
|
||||||
|
|
||||||
|
if ($response['status'] === 401) {
|
||||||
|
$freshToken = microsoft_get_valid_token($userId, true);
|
||||||
|
if ($freshToken && $freshToken !== $token) {
|
||||||
|
$token = $freshToken;
|
||||||
|
$response = fociz_http_request($url, [
|
||||||
|
'method' => 'GET',
|
||||||
|
'headers' => [
|
||||||
|
'Authorization' => "Bearer {$token}",
|
||||||
|
'Accept' => 'application/json',
|
||||||
|
'Prefer' => 'outlook.timezone="Europe/Amsterdam"',
|
||||||
|
],
|
||||||
|
'timeout' => 8,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($response['status'] === 404) {
|
||||||
|
return false; // Definitief niet meer gevonden in Outlook
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($response['status'] === 200 && is_array($response['json'])) {
|
||||||
|
$e = $response['json'];
|
||||||
|
if (!empty($e['isCancelled'])) {
|
||||||
|
return false; // Geannuleerd
|
||||||
|
}
|
||||||
|
|
||||||
|
$rawStart = $e['start']['dateTime'] ?? '';
|
||||||
|
$rawEnd = $e['end']['dateTime'] ?? '';
|
||||||
|
|
||||||
|
$eventDate = !empty($rawStart) ? substr($rawStart, 0, 10) : '';
|
||||||
|
$startTime = '';
|
||||||
|
$endTime = '';
|
||||||
|
|
||||||
|
if (!empty($rawStart) && preg_match('/T(\d{2}:\d{2})/', $rawStart, $m)) {
|
||||||
|
$startTime = $m[1];
|
||||||
|
}
|
||||||
|
if (!empty($rawEnd) && preg_match('/T(\d{2}:\d{2})/', $rawEnd, $m)) {
|
||||||
|
$endTime = $m[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
return [
|
||||||
|
'id' => $e['id'],
|
||||||
|
'subject' => !empty($e['subject']) ? $e['subject'] : '(Geen onderwerp)',
|
||||||
|
'date' => $eventDate,
|
||||||
|
'start' => $startTime ?: '09:00',
|
||||||
|
'end' => $endTime ?: '10:00',
|
||||||
|
'start_raw' => $rawStart,
|
||||||
|
'end_raw' => $rawEnd,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Synchroniseert en reconciled Fociz taken met de live Microsoft Outlook agenda.
|
||||||
|
*
|
||||||
|
* - Als een gekoppelde afspraak in Outlook is verwijderd of geannuleerd, wordt de taak in Fociz gewist.
|
||||||
|
* - Als een afspraak in Outlook van tijd, datum of titel is gewijzigd, wordt de taak in Fociz bijgewerkt.
|
||||||
|
*
|
||||||
|
* @param int $userId
|
||||||
|
* @param string $startDate Datumbereik begin (Y-m-d)
|
||||||
|
* @param string $endDate Datumbereik einde (Y-m-d)
|
||||||
|
* @param array $liveEvents Reeds opgehaalde Outlook events via microsoft_fetch_events_range
|
||||||
|
* @return array ['deleted' => int, 'updated' => int]
|
||||||
|
*/
|
||||||
|
function microsoft_reconcile_tasks_with_outlook(int $userId, string $startDate, string $endDate, array $liveEvents): array {
|
||||||
|
$db = get_db_connection();
|
||||||
|
$deletedCount = 0;
|
||||||
|
$updatedCount = 0;
|
||||||
|
|
||||||
|
// 1. Indexeer live events op id
|
||||||
|
$liveEventMap = [];
|
||||||
|
foreach ($liveEvents as $evt) {
|
||||||
|
if (!empty($evt['id'])) {
|
||||||
|
$liveEventMap[$evt['id']] = $evt;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Haal alle taken op voor deze gebruiker in dit datumbereik met een external_id (gekoppeld aan Outlook)
|
||||||
|
$stmt = $db->prepare("
|
||||||
|
SELECT t.id, t.title, t.time_block_start, t.time_block_end, t.duration_minutes, t.external_id, dp.plan_date, dp.id as daily_plan_id
|
||||||
|
FROM tasks t
|
||||||
|
JOIN daily_plans dp ON t.daily_plan_id = dp.id
|
||||||
|
WHERE t.user_id = ?
|
||||||
|
AND t.source = 'OUTLOOK'
|
||||||
|
AND t.external_id IS NOT NULL
|
||||||
|
AND t.external_id != ''
|
||||||
|
AND dp.plan_date BETWEEN ? AND ?
|
||||||
|
");
|
||||||
|
$stmt->execute([$userId, $startDate, $endDate]);
|
||||||
|
$syncedTasks = $stmt->fetchAll();
|
||||||
|
|
||||||
|
foreach ($syncedTasks as $task) {
|
||||||
|
$extId = $task['external_id'];
|
||||||
|
|
||||||
|
if (isset($liveEventMap[$extId])) {
|
||||||
|
// Het event bestaat in het huidige week/datumbereik!
|
||||||
|
$evt = $liveEventMap[$extId];
|
||||||
|
|
||||||
|
$dateChanged = (!empty($evt['date']) && $evt['date'] !== $task['plan_date']);
|
||||||
|
$titleChanged = (!empty($evt['subject']) && $evt['subject'] !== $task['title']);
|
||||||
|
$startChanged = (!empty($evt['start']) && $evt['start'] !== $task['time_block_start']);
|
||||||
|
$endChanged = (!empty($evt['end']) && $evt['end'] !== $task['time_block_end']);
|
||||||
|
|
||||||
|
if ($dateChanged || $titleChanged || $startChanged || $endChanged) {
|
||||||
|
$targetPlanId = (int)$task['daily_plan_id'];
|
||||||
|
|
||||||
|
// Als datum gewijzigd is, verplaats naar de daily_plan van de nieuwe datum
|
||||||
|
if ($dateChanged) {
|
||||||
|
$stmtPlan = $db->prepare("
|
||||||
|
INSERT INTO daily_plans (user_id, plan_date)
|
||||||
|
VALUES (?, ?)
|
||||||
|
ON DUPLICATE KEY UPDATE id=LAST_INSERT_ID(id)
|
||||||
|
");
|
||||||
|
$stmtPlan->execute([$userId, $evt['date']]);
|
||||||
|
$targetPlanId = (int)$db->lastInsertId();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bereken duur
|
||||||
|
$newStart = !empty($evt['start']) ? $evt['start'] : $task['time_block_start'];
|
||||||
|
$newEnd = !empty($evt['end']) ? $evt['end'] : $task['time_block_end'];
|
||||||
|
$newDuration = (int)$task['duration_minutes'];
|
||||||
|
if (!empty($newStart) && !empty($newEnd)) {
|
||||||
|
$durCalc = (strtotime("2000-01-01 {$newEnd}") - strtotime("2000-01-01 {$newStart}")) / 60;
|
||||||
|
if ($durCalc > 0) {
|
||||||
|
$newDuration = (int)$durCalc;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$newTitle = !empty($evt['subject']) ? $evt['subject'] : $task['title'];
|
||||||
|
|
||||||
|
$updateStmt = $db->prepare("
|
||||||
|
UPDATE tasks
|
||||||
|
SET daily_plan_id = ?, title = ?, time_block_start = ?, time_block_end = ?, duration_minutes = ?
|
||||||
|
WHERE id = ? AND user_id = ?
|
||||||
|
");
|
||||||
|
$updateStmt->execute([$targetPlanId, $newTitle, $newStart, $newEnd, $newDuration, $task['id'], $userId]);
|
||||||
|
$updatedCount++;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Event niet gevonden in het weekbereik!
|
||||||
|
// Controleer specifiek of het event in Outlook gewist is of naar een andere week verplaatst
|
||||||
|
$singleEvent = microsoft_get_single_event($userId, $extId);
|
||||||
|
|
||||||
|
if ($singleEvent === false) {
|
||||||
|
// Event is definitief verwijderd of geannuleerd in Outlook!
|
||||||
|
$delStmt = $db->prepare("DELETE FROM tasks WHERE id = ? AND user_id = ?");
|
||||||
|
$delStmt->execute([$task['id'], $userId]);
|
||||||
|
$deletedCount++;
|
||||||
|
} elseif (is_array($singleEvent)) {
|
||||||
|
// Event bestaat nog, maar staat op een andere datum (buiten huidig bereik)
|
||||||
|
$newDate = $singleEvent['date'];
|
||||||
|
if (!empty($newDate)) {
|
||||||
|
$stmtPlan = $db->prepare("
|
||||||
|
INSERT INTO daily_plans (user_id, plan_date)
|
||||||
|
VALUES (?, ?)
|
||||||
|
ON DUPLICATE KEY UPDATE id=LAST_INSERT_ID(id)
|
||||||
|
");
|
||||||
|
$stmtPlan->execute([$userId, $newDate]);
|
||||||
|
$newPlanId = (int)$db->lastInsertId();
|
||||||
|
|
||||||
|
$newStart = $singleEvent['start'];
|
||||||
|
$newEnd = $singleEvent['end'];
|
||||||
|
$newDuration = (int)$task['duration_minutes'];
|
||||||
|
if (!empty($newStart) && !empty($newEnd)) {
|
||||||
|
$durCalc = (strtotime("2000-01-01 {$newEnd}") - strtotime("2000-01-01 {$newStart}")) / 60;
|
||||||
|
if ($durCalc > 0) $newDuration = (int)$durCalc;
|
||||||
|
}
|
||||||
|
|
||||||
|
$updateStmt = $db->prepare("
|
||||||
|
UPDATE tasks
|
||||||
|
SET daily_plan_id = ?, title = ?, time_block_start = ?, time_block_end = ?, duration_minutes = ?
|
||||||
|
WHERE id = ? AND user_id = ?
|
||||||
|
");
|
||||||
|
$updateStmt->execute([$newPlanId, $singleEvent['subject'], $newStart, $newEnd, $newDuration, $task['id'], $userId]);
|
||||||
|
$updatedCount++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return [
|
||||||
|
'deleted' => $deletedCount,
|
||||||
|
'updated' => $updatedCount,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
=======
|
||||||
|
>>>>>>> dd738e670b78819c11e329da93db1b472808f195
|
||||||
|
|||||||
+781
@@ -42,6 +42,9 @@ $dutchShortMonths = ['', 'jan', 'feb', 'mrt', 'apr', 'mei', 'jun', 'jul', 'aug',
|
|||||||
$formattedSelectedDate = $dutchFullDays[(int)date('N', $selectedTs)] . ' ' . date('j', $selectedTs) . ' ' . $dutchMonths[(int)date('n', $selectedTs)] . ' ' . date('Y', $selectedTs);
|
$formattedSelectedDate = $dutchFullDays[(int)date('N', $selectedTs)] . ' ' . date('j', $selectedTs) . ' ' . $dutchMonths[(int)date('n', $selectedTs)] . ' ' . date('Y', $selectedTs);
|
||||||
$formattedWeekRange = date('j', $mondayTs) . ' ' . $dutchShortMonths[(int)date('n', $mondayTs)] . ' – ' . date('j', $sundayTs) . ' ' . $dutchShortMonths[(int)date('n', $sundayTs)] . ' ' . date('Y', $sundayTs);
|
$formattedWeekRange = date('j', $mondayTs) . ' ' . $dutchShortMonths[(int)date('n', $mondayTs)] . ' – ' . date('j', $sundayTs) . ' ' . $dutchShortMonths[(int)date('n', $sundayTs)] . ' ' . date('Y', $sundayTs);
|
||||||
|
|
||||||
|
<<<<<<< HEAD
|
||||||
|
// 1. Haal integratiestatussen op
|
||||||
|
=======
|
||||||
// 1. Haal dagplanning op of maak aan voor $selectedDate
|
// 1. Haal dagplanning op of maak aan voor $selectedDate
|
||||||
$stmtPlan = $db->prepare("SELECT * FROM daily_plans WHERE user_id = ? AND plan_date = ?");
|
$stmtPlan = $db->prepare("SELECT * FROM daily_plans WHERE user_id = ? AND plan_date = ?");
|
||||||
$stmtPlan->execute([$userId, $selectedDate]);
|
$stmtPlan->execute([$userId, $selectedDate]);
|
||||||
@@ -85,6 +88,7 @@ foreach ($stmtWeekTasks->fetchAll() as $row) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 4. Haal integratiestatussen op
|
// 4. Haal integratiestatussen op
|
||||||
|
>>>>>>> dd738e670b78819c11e329da93db1b472808f195
|
||||||
$stmtInt = $db->prepare("SELECT * FROM integrations WHERE user_id = ?");
|
$stmtInt = $db->prepare("SELECT * FROM integrations WHERE user_id = ?");
|
||||||
$stmtInt->execute([$userId]);
|
$stmtInt->execute([$userId]);
|
||||||
$userIntegrations = [];
|
$userIntegrations = [];
|
||||||
@@ -93,6 +97,12 @@ foreach ($stmtInt->fetchAll() as $row) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
$isOutlookConnected = !empty($userIntegrations['OUTLOOK']['access_token']) && empty($userIntegrations['OUTLOOK']['is_demo_mode']);
|
$isOutlookConnected = !empty($userIntegrations['OUTLOOK']['access_token']) && empty($userIntegrations['OUTLOOK']['is_demo_mode']);
|
||||||
|
<<<<<<< HEAD
|
||||||
|
$outlookIntegrationSettings = json_decode($userIntegrations['OUTLOOK']['settings'] ?? '{}', true) ?: [];
|
||||||
|
$syncIntervalSeconds = isset($outlookIntegrationSettings['sync_interval_seconds']) ? (int)$outlookIntegrationSettings['sync_interval_seconds'] : 30;
|
||||||
|
|
||||||
|
=======
|
||||||
|
>>>>>>> dd738e670b78819c11e329da93db1b472808f195
|
||||||
$weekOutlookEvents = [];
|
$weekOutlookEvents = [];
|
||||||
$isLiveOutlook = false;
|
$isLiveOutlook = false;
|
||||||
|
|
||||||
@@ -100,6 +110,12 @@ if ($isOutlookConnected) {
|
|||||||
// Haal afspraken op voor het volledige datumbereik van de week
|
// Haal afspraken op voor het volledige datumbereik van de week
|
||||||
$weekOutlookEvents = microsoft_fetch_events_range($userId, $weekStart, $weekEnd);
|
$weekOutlookEvents = microsoft_fetch_events_range($userId, $weekStart, $weekEnd);
|
||||||
$isLiveOutlook = true;
|
$isLiveOutlook = true;
|
||||||
|
<<<<<<< HEAD
|
||||||
|
|
||||||
|
// Synchroniseer / reconcilieer Fociz taken direct met actuele Outlook data
|
||||||
|
microsoft_reconcile_tasks_with_outlook($userId, $weekStart, $weekEnd, $weekOutlookEvents);
|
||||||
|
=======
|
||||||
|
>>>>>>> dd738e670b78819c11e329da93db1b472808f195
|
||||||
} else {
|
} else {
|
||||||
// Demo data indien Microsoft Outlook nog niet gekoppeld is
|
// Demo data indien Microsoft Outlook nog niet gekoppeld is
|
||||||
$weekOutlookEvents = [
|
$weekOutlookEvents = [
|
||||||
@@ -136,6 +152,51 @@ if ($isOutlookConnected) {
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
<<<<<<< HEAD
|
||||||
|
// 2. Haal dagplanning op of maak aan voor $selectedDate
|
||||||
|
$stmtPlan = $db->prepare("SELECT * FROM daily_plans WHERE user_id = ? AND plan_date = ?");
|
||||||
|
$stmtPlan->execute([$userId, $selectedDate]);
|
||||||
|
$dailyPlan = $stmtPlan->fetch();
|
||||||
|
|
||||||
|
if (!$dailyPlan) {
|
||||||
|
$defaultGoal = ($selectedDate === $today) ? 'Mijn belangrijkste doel van vandaag behalen' : 'Focusdoel voor deze dag bepalen';
|
||||||
|
$stmtCreatePlan = $db->prepare("
|
||||||
|
INSERT INTO daily_plans (user_id, plan_date, focus_goal)
|
||||||
|
VALUES (?, ?, ?)
|
||||||
|
");
|
||||||
|
$stmtCreatePlan->execute([$userId, $selectedDate, $defaultGoal]);
|
||||||
|
$planId = (int)$db->lastInsertId();
|
||||||
|
$dailyPlan = ['id' => $planId, 'plan_date' => $selectedDate, 'focus_goal' => $defaultGoal, 'notes' => ''];
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Haal taken op voor de geselecteerde dag (vers bijgewerkt na eventuele reconciliatie)
|
||||||
|
$stmtTasks = $db->prepare("
|
||||||
|
SELECT * FROM tasks
|
||||||
|
WHERE user_id = ? AND (daily_plan_id = ? " . ($selectedDate === $today ? "OR daily_plan_id IS NULL" : "") . ")
|
||||||
|
ORDER BY is_completed ASC, priority ASC, time_block_start ASC, created_at DESC
|
||||||
|
");
|
||||||
|
$stmtTasks->execute([$userId, $dailyPlan['id']]);
|
||||||
|
$tasks = $stmtTasks->fetchAll();
|
||||||
|
|
||||||
|
// 4. Haal weekstatistieken voor taken op (aantal per dag)
|
||||||
|
$stmtWeekTasks = $db->prepare("
|
||||||
|
SELECT dp.plan_date, COUNT(t.id) as total_tasks, SUM(CASE WHEN t.is_completed = 1 THEN 1 ELSE 0 END) as completed_tasks
|
||||||
|
FROM daily_plans dp
|
||||||
|
LEFT JOIN tasks t ON t.daily_plan_id = dp.id AND t.user_id = ?
|
||||||
|
WHERE dp.user_id = ? AND dp.plan_date BETWEEN ? AND ?
|
||||||
|
GROUP BY dp.plan_date
|
||||||
|
");
|
||||||
|
$stmtWeekTasks->execute([$userId, $userId, $weekStart, $weekEnd]);
|
||||||
|
$weekTaskStats = [];
|
||||||
|
foreach ($stmtWeekTasks->fetchAll() as $row) {
|
||||||
|
$weekTaskStats[$row['plan_date']] = [
|
||||||
|
'total' => (int)$row['total_tasks'],
|
||||||
|
'completed' => (int)$row['completed_tasks'],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
=======
|
||||||
|
>>>>>>> dd738e670b78819c11e329da93db1b472808f195
|
||||||
// Groepeer Outlook afspraken per datum
|
// Groepeer Outlook afspraken per datum
|
||||||
$outlookEventsByDate = [];
|
$outlookEventsByDate = [];
|
||||||
foreach ($weekOutlookEvents as $evt) {
|
foreach ($weekOutlookEvents as $evt) {
|
||||||
@@ -188,6 +249,33 @@ foreach ($dayOutlookEvents as $evt) {
|
|||||||
$displayedOutlookEvents[] = $evt;
|
$displayedOutlookEvents[] = $evt;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
<<<<<<< HEAD
|
||||||
|
// Bereken initiële status-hash voor achtergrondsynchronisatie
|
||||||
|
$dayEventsForHash = [];
|
||||||
|
foreach ($displayedOutlookEvents as $e) {
|
||||||
|
$dayEventsForHash[] = [
|
||||||
|
'id' => $e['id'] ?? '',
|
||||||
|
'start' => $e['start'] ?? '',
|
||||||
|
'end' => $e['end'] ?? '',
|
||||||
|
'subject' => $e['subject'] ?? '',
|
||||||
|
];
|
||||||
|
}
|
||||||
|
$tasksForHash = array_map(function($t) {
|
||||||
|
return [
|
||||||
|
'id' => $t['id'],
|
||||||
|
'title' => $t['title'],
|
||||||
|
'time_block_start' => $t['time_block_start'],
|
||||||
|
'time_block_end' => $t['time_block_end'],
|
||||||
|
'is_completed' => $t['is_completed'],
|
||||||
|
];
|
||||||
|
}, $tasks);
|
||||||
|
$initialSyncHash = md5(json_encode([
|
||||||
|
'events' => $dayEventsForHash,
|
||||||
|
'tasks' => $tasksForHash
|
||||||
|
]));
|
||||||
|
|
||||||
|
=======
|
||||||
|
>>>>>>> dd738e670b78819c11e329da93db1b472808f195
|
||||||
// 6. Tijdlijn uren & Werkdag Standaarden (Release v1.4)
|
// 6. Tijdlijn uren & Werkdag Standaarden (Release v1.4)
|
||||||
$workdayStartStr = $workdaySettings['workday_start'] ?? '08:30';
|
$workdayStartStr = $workdaySettings['workday_start'] ?? '08:30';
|
||||||
$workdayEndStr = $workdaySettings['workday_end'] ?? '17:30';
|
$workdayEndStr = $workdaySettings['workday_end'] ?? '17:30';
|
||||||
@@ -250,11 +338,20 @@ for ($h = $startHour; $h <= $endHour; $h++) {
|
|||||||
<!-- Integratie Status Badges -->
|
<!-- Integratie Status Badges -->
|
||||||
<div class="flex flex-wrap items-center gap-2">
|
<div class="flex flex-wrap items-center gap-2">
|
||||||
<?php if ($isOutlookConnected): ?>
|
<?php if ($isOutlookConnected): ?>
|
||||||
|
<<<<<<< HEAD
|
||||||
|
<div id="outlookSyncBadge" class="flex items-center space-x-2 px-3 py-1.5 rounded-xl bg-sky-50 dark:bg-sky-500/10 border border-sky-200/80 dark:border-sky-500/20 text-sky-800 dark:text-sky-300 text-xs font-medium shadow-2xs transition">
|
||||||
|
<span id="outlookSyncDot" class="w-2 h-2 rounded-full bg-sky-500"></span>
|
||||||
|
<span id="outlookSyncStatusText">Outlook 365 Live</span>
|
||||||
|
<span id="outlookLastSyncText" class="text-[10px] text-sky-600/70 dark:text-sky-400/60 font-mono hidden sm:inline">(synced <span id="syncTimeDisplay"><?= date('H:i') ?></span>)</span>
|
||||||
|
<button id="manualSyncBtn" onclick="triggerManualSync()" title="Nu handmatig synchroniseren met Outlook" class="p-0.5 hover:bg-sky-100 dark:hover:bg-sky-500/20 text-sky-600 hover:text-sky-900 dark:text-sky-400 dark:hover:text-white rounded transition cursor-pointer">
|
||||||
|
<i id="manualSyncIcon" data-lucide="refresh-cw" class="w-3 h-3"></i>
|
||||||
|
=======
|
||||||
<div class="flex items-center space-x-2 px-3 py-1.5 rounded-xl bg-sky-50 dark:bg-sky-500/10 border border-sky-200/80 dark:border-sky-500/20 text-sky-800 dark:text-sky-300 text-xs font-medium shadow-2xs">
|
<div class="flex items-center space-x-2 px-3 py-1.5 rounded-xl bg-sky-50 dark:bg-sky-500/10 border border-sky-200/80 dark:border-sky-500/20 text-sky-800 dark:text-sky-300 text-xs font-medium shadow-2xs">
|
||||||
<span class="w-2 h-2 rounded-full bg-sky-500 animate-pulse"></span>
|
<span class="w-2 h-2 rounded-full bg-sky-500 animate-pulse"></span>
|
||||||
<span>Outlook 365 Live</span>
|
<span>Outlook 365 Live</span>
|
||||||
<button onclick="location.reload()" title="Agenda Verversen" class="p-0.5 text-sky-600 hover:text-sky-900 dark:text-sky-400 dark:hover:text-white rounded transition">
|
<button onclick="location.reload()" title="Agenda Verversen" class="p-0.5 text-sky-600 hover:text-sky-900 dark:text-sky-400 dark:hover:text-white rounded transition">
|
||||||
<i data-lucide="refresh-cw" class="w-3 h-3"></i>
|
<i data-lucide="refresh-cw" class="w-3 h-3"></i>
|
||||||
|
>>>>>>> dd738e670b78819c11e329da93db1b472808f195
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<?php else: ?>
|
<?php else: ?>
|
||||||
@@ -474,16 +571,65 @@ for ($h = $startHour; $h <= $endHour; $h++) {
|
|||||||
<label class="block text-[11px] font-medium text-slate-600 dark:text-slate-400 mb-1">Taaktitel</label>
|
<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">
|
<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>
|
||||||
|
<<<<<<< HEAD
|
||||||
|
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-3">
|
||||||
|
<div>
|
||||||
|
<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">
|
||||||
|
=======
|
||||||
<div class="grid grid-cols-1 sm:grid-cols-3 gap-3">
|
<div class="grid grid-cols-1 sm:grid-cols-3 gap-3">
|
||||||
<div>
|
<div>
|
||||||
<label class="block text-[11px] font-medium text-slate-600 dark:text-slate-400 mb-1">Prioriteit</label>
|
<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">
|
<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">
|
||||||
|
>>>>>>> dd738e670b78819c11e329da93db1b472808f195
|
||||||
<option value="P1">P1 - Brandend / Focus</option>
|
<option value="P1">P1 - Brandend / Focus</option>
|
||||||
<option value="P2" selected>P2 - Belangrijk (Standaard)</option>
|
<option value="P2" selected>P2 - Belangrijk (Standaard)</option>
|
||||||
<option value="P3">P3 - Regulier / Lage druk</option>
|
<option value="P3">P3 - Regulier / Lage druk</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
|
<<<<<<< HEAD
|
||||||
|
<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>
|
||||||
|
<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);
|
||||||
|
?>
|
||||||
|
<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>
|
||||||
|
=======
|
||||||
<label class="block text-[11px] font-medium text-slate-600 dark:text-slate-400 mb-1">Tijdblok (Starttijd)</label>
|
<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">
|
<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">
|
||||||
</div>
|
</div>
|
||||||
@@ -498,6 +644,7 @@ for ($h = $startHour; $h <= $endHour; $h++) {
|
|||||||
<option value="<?= $mins ?>" <?= $defDuration === $mins ? 'selected' : '' ?>><?= $label ?></option>
|
<option value="<?= $mins ?>" <?= $defDuration === $mins ? 'selected' : '' ?>><?= $label ?></option>
|
||||||
<?php endforeach; ?>
|
<?php endforeach; ?>
|
||||||
</select>
|
</select>
|
||||||
|
>>>>>>> dd738e670b78819c11e329da93db1b472808f195
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -556,7 +703,11 @@ for ($h = $startHour; $h <= $endHour; $h++) {
|
|||||||
<?php if (!empty($task['time_block_start'])): ?>
|
<?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">
|
<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>
|
<i data-lucide="clock" class="w-2.5 h-2.5"></i>
|
||||||
|
<<<<<<< HEAD
|
||||||
|
<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><?= 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']) : '' ?></span>
|
||||||
|
>>>>>>> dd738e670b78819c11e329da93db1b472808f195
|
||||||
</span>
|
</span>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
<?php if (!empty($task['external_url'])): ?>
|
<?php if (!empty($task['external_url'])): ?>
|
||||||
@@ -582,12 +733,33 @@ for ($h = $startHour; $h <= $endHour; $h++) {
|
|||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
|
|
||||||
<?php if (!$task['is_completed']): ?>
|
<?php if (!$task['is_completed']): ?>
|
||||||
|
<<<<<<< HEAD
|
||||||
|
<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">
|
||||||
|
=======
|
||||||
<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">
|
||||||
|
>>>>>>> dd738e670b78819c11e329da93db1b472808f195
|
||||||
<i data-lucide="flame" class="w-3.5 h-3.5"></i>
|
<i data-lucide="flame" class="w-3.5 h-3.5"></i>
|
||||||
</button>
|
</button>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<<<<<<< HEAD
|
||||||
|
<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">
|
||||||
|
=======
|
||||||
<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="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">
|
||||||
|
>>>>>>> dd738e670b78819c11e329da93db1b472808f195
|
||||||
<i data-lucide="trash-2" class="w-3.5 h-3.5"></i>
|
<i data-lucide="trash-2" class="w-3.5 h-3.5"></i>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -803,6 +975,103 @@ for ($h = $startHour; $h <= $endHour; $h++) {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<<<<<<< HEAD
|
||||||
|
<!-- 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>
|
||||||
|
|
||||||
|
=======
|
||||||
|
>>>>>>> dd738e670b78819c11e329da93db1b472808f195
|
||||||
<script>
|
<script>
|
||||||
// --- Focus Timer Logica (Release v1.4) ---
|
// --- Focus Timer Logica (Release v1.4) ---
|
||||||
const pomodoroFocusTime = <?= (int)$workdaySettings['pomodoro_focus'] ?>;
|
const pomodoroFocusTime = <?= (int)$workdaySettings['pomodoro_focus'] ?>;
|
||||||
@@ -844,6 +1113,10 @@ function toggleTimer() {
|
|||||||
clearInterval(timerInterval);
|
clearInterval(timerInterval);
|
||||||
isRunning = false;
|
isRunning = false;
|
||||||
document.getElementById('timerToggleText').innerText = 'Hervatten';
|
document.getElementById('timerToggleText').innerText = 'Hervatten';
|
||||||
|
<<<<<<< HEAD
|
||||||
|
saveTimerStateBeforeReload();
|
||||||
|
=======
|
||||||
|
>>>>>>> dd738e670b78819c11e329da93db1b472808f195
|
||||||
} else {
|
} else {
|
||||||
isRunning = true;
|
isRunning = true;
|
||||||
document.getElementById('timerToggleText').innerText = 'Pauzeren';
|
document.getElementById('timerToggleText').innerText = 'Pauzeren';
|
||||||
@@ -854,6 +1127,10 @@ function toggleTimer() {
|
|||||||
} else {
|
} else {
|
||||||
clearInterval(timerInterval);
|
clearInterval(timerInterval);
|
||||||
isRunning = false;
|
isRunning = false;
|
||||||
|
<<<<<<< HEAD
|
||||||
|
sessionStorage.removeItem('fociz_timer_state');
|
||||||
|
=======
|
||||||
|
>>>>>>> dd738e670b78819c11e329da93db1b472808f195
|
||||||
document.getElementById('timerToggleText').innerText = 'Start Focus';
|
document.getElementById('timerToggleText').innerText = 'Start Focus';
|
||||||
alert('Focus sessie voltooid! Neem even een korte pauze.');
|
alert('Focus sessie voltooid! Neem even een korte pauze.');
|
||||||
}
|
}
|
||||||
@@ -865,10 +1142,65 @@ function resetTimer() {
|
|||||||
clearInterval(timerInterval);
|
clearInterval(timerInterval);
|
||||||
isRunning = false;
|
isRunning = false;
|
||||||
timerSeconds = totalDuration;
|
timerSeconds = totalDuration;
|
||||||
|
<<<<<<< HEAD
|
||||||
|
sessionStorage.removeItem('fociz_timer_state');
|
||||||
|
=======
|
||||||
|
>>>>>>> dd738e670b78819c11e329da93db1b472808f195
|
||||||
document.getElementById('timerToggleText').innerText = 'Start Focus';
|
document.getElementById('timerToggleText').innerText = 'Start Focus';
|
||||||
updateTimerDisplay();
|
updateTimerDisplay();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
<<<<<<< HEAD
|
||||||
|
function saveTimerStateBeforeReload() {
|
||||||
|
try {
|
||||||
|
if (isRunning || timerSeconds < totalDuration) {
|
||||||
|
const state = {
|
||||||
|
isRunning: isRunning,
|
||||||
|
timerSeconds: timerSeconds,
|
||||||
|
totalDuration: totalDuration,
|
||||||
|
timestamp: Date.now()
|
||||||
|
};
|
||||||
|
sessionStorage.setItem('fociz_timer_state', JSON.stringify(state));
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Kon timer status niet opslaan:', e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function restoreTimerStateIfAny() {
|
||||||
|
try {
|
||||||
|
const raw = sessionStorage.getItem('fociz_timer_state');
|
||||||
|
if (!raw) return;
|
||||||
|
sessionStorage.removeItem('fociz_timer_state');
|
||||||
|
const state = JSON.parse(raw);
|
||||||
|
if (state && typeof state.timerSeconds === 'number') {
|
||||||
|
totalDuration = state.totalDuration || (pomodoroFocusTime * 60);
|
||||||
|
let remaining = state.timerSeconds;
|
||||||
|
if (state.isRunning && state.timestamp) {
|
||||||
|
const elapsed = Math.floor((Date.now() - state.timestamp) / 1000);
|
||||||
|
remaining = Math.max(0, remaining - elapsed);
|
||||||
|
}
|
||||||
|
timerSeconds = remaining;
|
||||||
|
updateTimerDisplay();
|
||||||
|
|
||||||
|
if (state.isRunning && timerSeconds > 0) {
|
||||||
|
toggleTimer(); // Hervat timer
|
||||||
|
} else if (!state.isRunning && timerSeconds < totalDuration) {
|
||||||
|
const toggleBtnText = document.getElementById('timerToggleText');
|
||||||
|
if (toggleBtnText) toggleBtnText.innerText = 'Hervatten';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Kon timer status niet herstellen:', e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
window.addEventListener('beforeunload', () => {
|
||||||
|
saveTimerStateBeforeReload();
|
||||||
|
});
|
||||||
|
|
||||||
|
=======
|
||||||
|
>>>>>>> dd738e670b78819c11e329da93db1b472808f195
|
||||||
function selectFocusTask(title) {
|
function selectFocusTask(title) {
|
||||||
document.getElementById('activeTaskDisplay').innerText = title;
|
document.getElementById('activeTaskDisplay').innerText = title;
|
||||||
}
|
}
|
||||||
@@ -899,6 +1231,142 @@ async function saveGoal() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
<<<<<<< HEAD
|
||||||
|
// --- 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`;
|
||||||
|
}
|
||||||
|
|
||||||
|
=======
|
||||||
|
>>>>>>> dd738e670b78819c11e329da93db1b472808f195
|
||||||
function toggleAddTaskForm() {
|
function toggleAddTaskForm() {
|
||||||
const form = document.getElementById('addTaskForm');
|
const form = document.getElementById('addTaskForm');
|
||||||
form.classList.toggle('hidden');
|
form.classList.toggle('hidden');
|
||||||
@@ -908,21 +1376,47 @@ function toggleAddTaskForm() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function quickSchedule(hour) {
|
function quickSchedule(hour) {
|
||||||
|
<<<<<<< HEAD
|
||||||
|
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;
|
||||||
|
onNewTaskTimeOrDurationChange('start');
|
||||||
|
}
|
||||||
|
const outlookCheckbox = document.getElementById('newTaskSyncOutlook');
|
||||||
|
if (outlookCheckbox) outlookCheckbox.checked = true;
|
||||||
|
document.getElementById('newTaskTitle')?.focus();
|
||||||
|
=======
|
||||||
toggleAddTaskForm();
|
toggleAddTaskForm();
|
||||||
const timeInput = document.getElementById('newTaskTime');
|
const timeInput = document.getElementById('newTaskTime');
|
||||||
if (timeInput) timeInput.value = hour;
|
if (timeInput) timeInput.value = hour;
|
||||||
const outlookCheckbox = document.getElementById('newTaskSyncOutlook');
|
const outlookCheckbox = document.getElementById('newTaskSyncOutlook');
|
||||||
if (outlookCheckbox) outlookCheckbox.checked = true;
|
if (outlookCheckbox) outlookCheckbox.checked = true;
|
||||||
|
>>>>>>> dd738e670b78819c11e329da93db1b472808f195
|
||||||
}
|
}
|
||||||
|
|
||||||
async function submitNewTask() {
|
async function submitNewTask() {
|
||||||
const title = document.getElementById('newTaskTitle').value.trim();
|
const title = document.getElementById('newTaskTitle').value.trim();
|
||||||
const priority = document.getElementById('newTaskPriority').value;
|
const priority = document.getElementById('newTaskPriority').value;
|
||||||
const time = document.getElementById('newTaskTime').value.trim();
|
const time = document.getElementById('newTaskTime').value.trim();
|
||||||
|
<<<<<<< HEAD
|
||||||
|
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) {
|
||||||
|
alert('Vul een taaktitel in.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
=======
|
||||||
const duration = document.getElementById('newTaskDuration') ? parseInt(document.getElementById('newTaskDuration').value) : 30;
|
const duration = document.getElementById('newTaskDuration') ? parseInt(document.getElementById('newTaskDuration').value) : 30;
|
||||||
const syncOutlook = document.getElementById('newTaskSyncOutlook') ? document.getElementById('newTaskSyncOutlook').checked : false;
|
const syncOutlook = document.getElementById('newTaskSyncOutlook') ? document.getElementById('newTaskSyncOutlook').checked : false;
|
||||||
|
|
||||||
if (!title) return;
|
if (!title) return;
|
||||||
|
>>>>>>> dd738e670b78819c11e329da93db1b472808f195
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const res = await fetch('/api/planner.php?action=add_task', {
|
const res = await fetch('/api/planner.php?action=add_task', {
|
||||||
@@ -931,7 +1425,12 @@ async function submitNewTask() {
|
|||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
title,
|
title,
|
||||||
priority,
|
priority,
|
||||||
|
<<<<<<< HEAD
|
||||||
|
time_block_start: time,
|
||||||
|
time_block_end: endTime,
|
||||||
|
=======
|
||||||
time_block_start: time,
|
time_block_start: time,
|
||||||
|
>>>>>>> dd738e670b78819c11e329da93db1b472808f195
|
||||||
duration_minutes: duration,
|
duration_minutes: duration,
|
||||||
sync_to_outlook: syncOutlook,
|
sync_to_outlook: syncOutlook,
|
||||||
date: activePlannerDate
|
date: activePlannerDate
|
||||||
@@ -951,6 +1450,197 @@ async function submitNewTask() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
<<<<<<< HEAD
|
||||||
|
// --- 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
=======
|
||||||
|
>>>>>>> dd738e670b78819c11e329da93db1b472808f195
|
||||||
async function syncTaskToOutlook(taskId, btn) {
|
async function syncTaskToOutlook(taskId, btn) {
|
||||||
if (!btn) return;
|
if (!btn) return;
|
||||||
const originalHtml = btn.innerHTML;
|
const originalHtml = btn.innerHTML;
|
||||||
@@ -1076,6 +1766,97 @@ async function aiSmartSchedule(btn) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
<<<<<<< HEAD
|
||||||
|
// --- Outlook 365 Auto-Sync & Achtergrond Controle ---
|
||||||
|
const isOutlookConnected = <?= $isOutlookConnected ? 'true' : 'false' ?>;
|
||||||
|
let currentSyncHash = '<?= $initialSyncHash ?? '' ?>';
|
||||||
|
const syncIntervalSeconds = <?= (int)($syncIntervalSeconds ?? 30) ?>;
|
||||||
|
const weekStartDate = '<?= $weekStart ?>';
|
||||||
|
const weekEndDate = '<?= $weekEnd ?>';
|
||||||
|
let isSyncCheckActive = false;
|
||||||
|
|
||||||
|
async function checkOutlookChanges(isManual = false) {
|
||||||
|
if (!isOutlookConnected || isSyncCheckActive) return;
|
||||||
|
isSyncCheckActive = true;
|
||||||
|
|
||||||
|
const icon = document.getElementById('manualSyncIcon');
|
||||||
|
const dot = document.getElementById('outlookSyncDot');
|
||||||
|
const statusText = document.getElementById('outlookSyncStatusText');
|
||||||
|
|
||||||
|
if (icon) icon.classList.add('animate-spin');
|
||||||
|
if (dot) {
|
||||||
|
dot.classList.remove('bg-sky-500');
|
||||||
|
dot.classList.add('bg-amber-500', 'animate-ping');
|
||||||
|
}
|
||||||
|
if (isManual && statusText) {
|
||||||
|
statusText.innerText = 'Synchroniseren...';
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const res = await fetch(`/api/planner.php?action=check_outlook_sync&plan_date=${encodeURIComponent(activePlannerDate)}&week_start=${encodeURIComponent(weekStartDate)}&week_end=${encodeURIComponent(weekEndDate)}&last_hash=${encodeURIComponent(currentSyncHash)}`);
|
||||||
|
if (!res.ok) throw new Error('Netwerkfout bij synchronisatie controle');
|
||||||
|
const data = await res.json();
|
||||||
|
|
||||||
|
if (data.success && data.connected) {
|
||||||
|
if (data.last_sync) {
|
||||||
|
const timeEl = document.getElementById('syncTimeDisplay');
|
||||||
|
if (timeEl) timeEl.innerText = data.last_sync.substring(0, 5);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data.has_changes) {
|
||||||
|
if (statusText) statusText.innerText = 'Wijzigingen bijwerken...';
|
||||||
|
saveTimerStateBeforeReload();
|
||||||
|
setTimeout(() => {
|
||||||
|
location.reload();
|
||||||
|
}, 400);
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
currentSyncHash = data.hash;
|
||||||
|
if (statusText) statusText.innerText = 'Outlook 365 Live';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.warn('Outlook achtergrond controle:', err);
|
||||||
|
if (statusText) statusText.innerText = 'Outlook 365 Live';
|
||||||
|
} finally {
|
||||||
|
isSyncCheckActive = false;
|
||||||
|
if (icon) icon.classList.remove('animate-spin');
|
||||||
|
if (dot) {
|
||||||
|
dot.classList.remove('bg-amber-500', 'animate-ping');
|
||||||
|
dot.classList.add('bg-sky-500');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function triggerManualSync() {
|
||||||
|
checkOutlookChanges(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialisatie bij pagina-inladen
|
||||||
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
|
restoreTimerStateIfAny();
|
||||||
|
|
||||||
|
if (isOutlookConnected) {
|
||||||
|
if (syncIntervalSeconds > 0) {
|
||||||
|
setInterval(() => {
|
||||||
|
checkOutlookChanges(false);
|
||||||
|
}, syncIntervalSeconds * 1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Direct controleren wanneer gebruiker terugkeert naar het tabblad / venster
|
||||||
|
window.addEventListener('focus', () => {
|
||||||
|
checkOutlookChanges(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
document.addEventListener('visibilitychange', () => {
|
||||||
|
if (document.visibilityState === 'visible') {
|
||||||
|
checkOutlookChanges(false);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
=======
|
||||||
|
>>>>>>> dd738e670b78819c11e329da93db1b472808f195
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<?php require_once __DIR__ . '/includes/footer.php'; ?>
|
<?php require_once __DIR__ . '/includes/footer.php'; ?>
|
||||||
|
|||||||
+49
@@ -48,6 +48,26 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
|||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
<<<<<<< HEAD
|
||||||
|
// 2b. Outlook synchronisatievoorkeuren opslaan
|
||||||
|
elseif ($action === 'save_outlook_settings') {
|
||||||
|
$syncInterval = (int)($_POST['sync_interval_seconds'] ?? 30);
|
||||||
|
|
||||||
|
$stmtExisting = $db->prepare("SELECT settings FROM integrations WHERE user_id = ? AND provider = 'OUTLOOK'");
|
||||||
|
$stmtExisting->execute([$userId]);
|
||||||
|
$existing = $stmtExisting->fetch();
|
||||||
|
$settings = json_decode($existing['settings'] ?? '{}', true) ?: [];
|
||||||
|
$settings['sync_interval_seconds'] = $syncInterval;
|
||||||
|
|
||||||
|
$stmtUp = $db->prepare("UPDATE integrations SET settings = ? WHERE user_id = ? AND provider = 'OUTLOOK'");
|
||||||
|
$stmtUp->execute([json_encode($settings), $userId]);
|
||||||
|
|
||||||
|
$message = "Uw Microsoft Outlook synchronisatievoorkeur is succesvol opgeslagen.";
|
||||||
|
$messageType = 'success';
|
||||||
|
}
|
||||||
|
|
||||||
|
=======
|
||||||
|
>>>>>>> dd738e670b78819c11e329da93db1b472808f195
|
||||||
// 3. Jira Opslaan & Testen
|
// 3. Jira Opslaan & Testen
|
||||||
elseif ($action === 'save_jira') {
|
elseif ($action === 'save_jira') {
|
||||||
$companyDomain = jira_get_company_domain();
|
$companyDomain = jira_get_company_domain();
|
||||||
@@ -462,6 +482,35 @@ require_once __DIR__ . '/includes/header.php';
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<<<<<<< HEAD
|
||||||
|
<form method="POST" class="mt-4 p-4 rounded-xl bg-slate-50/70 dark:bg-slate-900/60 border border-slate-200/80 dark:border-slate-800 flex flex-col sm:flex-row sm:items-center justify-between gap-4">
|
||||||
|
<input type="hidden" name="action" value="save_outlook_settings">
|
||||||
|
<div class="space-y-1">
|
||||||
|
<label for="sync_interval_seconds" class="text-xs font-bold text-slate-900 dark:text-white flex items-center space-x-1.5">
|
||||||
|
<i data-lucide="refresh-cw" class="w-3.5 h-3.5 text-sky-500"></i>
|
||||||
|
<span>Automatische Agenda Synchronisatie</span>
|
||||||
|
</label>
|
||||||
|
<p class="text-[11px] text-slate-500 dark:text-slate-400 leading-relaxed">
|
||||||
|
Bepaalt hoe vaak het dagplanningscherm in de achtergrond controleert op gewijzigde of verwijderde Outlook afspraken.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center space-x-2">
|
||||||
|
<?php $currentInterval = (int)($outlookSettings['sync_interval_seconds'] ?? 30); ?>
|
||||||
|
<select id="sync_interval_seconds" name="sync_interval_seconds" class="px-3 py-2 rounded-xl border border-slate-300 dark:border-slate-700 bg-white dark:bg-slate-800 text-slate-900 dark:text-white text-xs focus:ring-2 focus:ring-sky-500 focus:outline-hidden">
|
||||||
|
<option value="15" <?= $currentInterval === 15 ? 'selected' : '' ?>>Elke 15 seconden (Real-time)</option>
|
||||||
|
<option value="30" <?= $currentInterval === 30 ? 'selected' : '' ?>>Elke 30 seconden (Aanbevolen)</option>
|
||||||
|
<option value="60" <?= $currentInterval === 60 ? 'selected' : '' ?>>Elke 60 seconden (1 minuut)</option>
|
||||||
|
<option value="120" <?= $currentInterval === 120 ? 'selected' : '' ?>>Elke 2 minuten</option>
|
||||||
|
<option value="0" <?= $currentInterval === 0 ? 'selected' : '' ?>>Handmatig (en bij vensterfocus)</option>
|
||||||
|
</select>
|
||||||
|
<button type="submit" class="px-4 py-2 rounded-xl bg-sky-600 hover:bg-sky-500 text-white font-semibold text-xs transition shadow-2xs whitespace-nowrap cursor-pointer">
|
||||||
|
Opslaan
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
=======
|
||||||
|
>>>>>>> dd738e670b78819c11e329da93db1b472808f195
|
||||||
<div class="mt-4 p-3.5 rounded-xl bg-teal-50 dark:bg-teal-500/10 border border-teal-200 dark:border-teal-500/20 text-teal-800 dark:text-teal-300 text-xs flex items-center justify-between">
|
<div class="mt-4 p-3.5 rounded-xl bg-teal-50 dark:bg-teal-500/10 border border-teal-200 dark:border-teal-500/20 text-teal-800 dark:text-teal-300 text-xs flex items-center justify-between">
|
||||||
<div class="flex items-center space-x-2">
|
<div class="flex items-center space-x-2">
|
||||||
<i data-lucide="check-circle" class="w-4 h-4 text-teal-600 dark:text-teal-400 flex-shrink-0"></i>
|
<i data-lucide="check-circle" class="w-4 h-4 text-teal-600 dark:text-teal-400 flex-shrink-0"></i>
|
||||||
|
|||||||
Reference in New Issue
Block a user