Aanpassing planner
This commit is contained in:
@@ -1096,8 +1096,9 @@ require_once __DIR__ . '/includes/header.php';
|
|||||||
<i data-lucide="shield-check" class="w-3.5 h-3.5"></i>
|
<i data-lucide="shield-check" class="w-3.5 h-3.5"></i>
|
||||||
<span>Zero-Data Leakage</span>
|
<span>Zero-Data Leakage</span>
|
||||||
</span>
|
</span>
|
||||||
<span class="px-2.5 py-1 rounded-full <?= $aiSettings['has_key'] ? 'bg-teal-50 dark:bg-teal-950/40 text-teal-700 dark:text-teal-400 border border-teal-200 dark:border-teal-800/60' : 'bg-amber-50 dark:bg-amber-950/40 text-amber-700 dark:text-amber-400 border border-amber-200 dark:border-amber-800/60' ?> text-xs font-bold">
|
<span class="px-2.5 py-1 rounded-full <?= $aiSettings['has_key'] ? 'bg-teal-50 dark:bg-teal-950/40 text-teal-700 dark:text-teal-400 border border-teal-200 dark:border-teal-800/60' : 'bg-amber-50 dark:bg-amber-950/40 text-amber-700 dark:text-amber-400 border border-amber-200 dark:border-amber-800/60' ?> text-xs font-bold inline-flex items-center space-x-1.5">
|
||||||
<?= $aiSettings['has_key'] ? 'AI Actief (' . htmlspecialchars($aiSettings['ai_model']) . ')' : 'Sleutel Vereist' ?>
|
<i data-lucide="<?= $aiSettings['has_key'] ? 'check-circle-2' : 'key' ?>" class="w-3.5 h-3.5 flex-shrink-0"></i>
|
||||||
|
<span><?= $aiSettings['has_key'] ? 'AI Actief (' . htmlspecialchars($aiSettings['ai_model']) . ')' : 'Sleutel Vereist' ?></span>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
+7
-1
@@ -62,8 +62,14 @@ try {
|
|||||||
$relevantIssues = [];
|
$relevantIssues = [];
|
||||||
|
|
||||||
// Filter issues die specifiek relevant zijn voor deze medewerker
|
// Filter issues die specifiek relevant zijn voor deze medewerker
|
||||||
|
$userFirstName = explode(' ', trim($user['name'] ?? ''))[0] ?? '';
|
||||||
foreach (array_merge($assignedIssues, $teamIssues) as $iss) {
|
foreach (array_merge($assignedIssues, $teamIssues) as $iss) {
|
||||||
$isAssignedToMe = !empty($iss['assignee_name']) && (stripos($iss['assignee_name'], $user['name']) !== false || stripos($iss['assignee_name'], 'Niek') !== false);
|
$assignee = $iss['assignee_name'] ?? '';
|
||||||
|
$isAssignedToMe = !empty($assignee) && (
|
||||||
|
stripos($assignee, $user['name'] ?? '') !== false ||
|
||||||
|
(!empty($userFirstName) && mb_strlen($userFirstName) >= 2 && stripos($assignee, $userFirstName) !== false) ||
|
||||||
|
(!empty($user['email']) && stripos($assignee, $user['email']) !== false)
|
||||||
|
);
|
||||||
$ol = strtolower(trim($iss['onderwerp_logging'] ?? ''));
|
$ol = strtolower(trim($iss['onderwerp_logging'] ?? ''));
|
||||||
|
|
||||||
$isQueueMatch = false;
|
$isQueueMatch = false;
|
||||||
|
|||||||
+151
-1
@@ -747,8 +747,23 @@ try {
|
|||||||
} catch (Throwable $e) {}
|
} catch (Throwable $e) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 3. Batch Calendar Blocks: haal verlof en geblokkeerde vakantiedagen op voor dit venster
|
||||||
|
$blockedDates = [];
|
||||||
|
try {
|
||||||
|
$stmtBlk = $db->prepare("SELECT start_date, end_date FROM calendar_blocks WHERE user_id = ? AND start_date <= ? AND end_date >= ?");
|
||||||
|
$stmtBlk->execute([$user['id'], $rangeEndDate, $rangeStartDate]);
|
||||||
|
while ($b = $stmtBlk->fetch()) {
|
||||||
|
$bStart = strtotime($b['start_date']);
|
||||||
|
$bEnd = strtotime($b['end_date']);
|
||||||
|
for ($d = $bStart; $d <= $bEnd; $d += 86400) {
|
||||||
|
$blockedDates[date('Y-m-d', $d)] = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Throwable $e) {}
|
||||||
|
|
||||||
while ($checkedDays < $maxDays) {
|
while ($checkedDays < $maxDays) {
|
||||||
$dayOfWeek = (int)$checkDate->format('N'); // 1 = Ma, 7 = Zo
|
$dayOfWeek = (int)$checkDate->format('N'); // 1 = Ma, 7 = Zo
|
||||||
|
$dateStr = $checkDate->format('Y-m-d');
|
||||||
|
|
||||||
// Niet-werkdag / weekend overslaan op basis van organisatie-instellingen
|
// Niet-werkdag / weekend overslaan op basis van organisatie-instellingen
|
||||||
if (!in_array($dayOfWeek, $activeWorkdays, true)) {
|
if (!in_array($dayOfWeek, $activeWorkdays, true)) {
|
||||||
@@ -756,7 +771,11 @@ try {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
$dateStr = $checkDate->format('Y-m-d');
|
// Geblokkeerde dagen (bijv. vakantie, verlof) overslaan
|
||||||
|
if (!empty($blockedDates[$dateStr])) {
|
||||||
|
$checkDate->modify('+1 day');
|
||||||
|
continue;
|
||||||
|
}
|
||||||
$isToday = ($dateStr === $now->format('Y-m-d')) && !$startFromNextWorkday;
|
$isToday = ($dateStr === $now->format('Y-m-d')) && !$startFromNextWorkday;
|
||||||
|
|
||||||
// Bepaal vroegste startminuut voor deze dag (op hele kwartieren)
|
// Bepaal vroegste startminuut voor deze dag (op hele kwartieren)
|
||||||
@@ -854,6 +873,137 @@ try {
|
|||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 12. Kalenderblokkades ophalen (Vakanties / Verlof / Afwezigheid)
|
||||||
|
if ($action === 'get_calendar_blocks') {
|
||||||
|
$stmt = $db->prepare("
|
||||||
|
SELECT * FROM calendar_blocks
|
||||||
|
WHERE user_id = ?
|
||||||
|
ORDER BY start_date DESC
|
||||||
|
LIMIT 100
|
||||||
|
");
|
||||||
|
$stmt->execute([$user['id']]);
|
||||||
|
$blocks = $stmt->fetchAll() ?: [];
|
||||||
|
echo json_encode(['success' => true, 'blocks' => $blocks]);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 13. Kalenderblokkade opslaan (Vakantie / Verlof / Dag(en) blokkeren)
|
||||||
|
if ($action === 'save_calendar_block') {
|
||||||
|
$rawInput = json_decode(file_get_contents('php://input'), true);
|
||||||
|
$data = is_array($rawInput) ? $rawInput : $_POST;
|
||||||
|
|
||||||
|
$blockId = (int)($data['id'] ?? 0);
|
||||||
|
$title = trim($data['title'] ?? '');
|
||||||
|
$blockType = trim($data['block_type'] ?? 'VACATION');
|
||||||
|
$startDate = trim($data['start_date'] ?? $today);
|
||||||
|
$endDate = trim($data['end_date'] ?? $startDate);
|
||||||
|
$notes = trim($data['notes'] ?? '');
|
||||||
|
$syncToOutlook = !empty($data['sync_to_outlook']);
|
||||||
|
|
||||||
|
if (empty($title)) {
|
||||||
|
echo json_encode(['success' => false, 'error' => 'Geef een titel op voor de blokkade (bijv. Vakantie of Vrije dag)']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!preg_match('/^\d{4}-\d{2}-\d{2}$/', $startDate) || !preg_match('/^\d{4}-\d{2}-\d{2}$/', $endDate)) {
|
||||||
|
echo json_encode(['success' => false, 'error' => 'Ongeldige datumnotatie']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($endDate < $startDate) {
|
||||||
|
$temp = $startDate;
|
||||||
|
$startDate = $endDate;
|
||||||
|
$endDate = $temp;
|
||||||
|
}
|
||||||
|
|
||||||
|
$outlookEventId = null;
|
||||||
|
|
||||||
|
// Indien bewerken en er bestond al een Outlook event, haal het event ID op
|
||||||
|
if ($blockId > 0) {
|
||||||
|
$stmtEx = $db->prepare("SELECT outlook_event_id FROM calendar_blocks WHERE id = ? AND user_id = ?");
|
||||||
|
$stmtEx->execute([$blockId, $user['id']]);
|
||||||
|
$outlookEventId = $stmtEx->fetchColumn() ?: null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Optioneel synchroniseren naar Outlook 365
|
||||||
|
if ($syncToOutlook && function_exists('microsoft_create_event')) {
|
||||||
|
try {
|
||||||
|
if (!empty($outlookEventId) && function_exists('microsoft_delete_event')) {
|
||||||
|
microsoft_delete_event($user['id'], $outlookEventId);
|
||||||
|
}
|
||||||
|
|
||||||
|
$eventDesc = !empty($notes) ? $notes : "Geblokkeerd via Fociz Agenda ({$blockType})";
|
||||||
|
$msRes = microsoft_create_event($user['id'], [
|
||||||
|
'subject' => $title,
|
||||||
|
'description' => $eventDesc,
|
||||||
|
'date' => $startDate,
|
||||||
|
'end_date' => $endDate,
|
||||||
|
'is_all_day' => true,
|
||||||
|
'show_as' => ($blockType === 'FOCUS') ? 'busy' : 'oof',
|
||||||
|
]);
|
||||||
|
if (!empty($msRes['success']) && !empty($msRes['event_id'])) {
|
||||||
|
$outlookEventId = $msRes['event_id'];
|
||||||
|
}
|
||||||
|
} catch (Throwable $e) {
|
||||||
|
error_log("Fout bij sync calendar_block naar Outlook: " . $e->getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($blockId > 0) {
|
||||||
|
$stmt = $db->prepare("
|
||||||
|
UPDATE calendar_blocks
|
||||||
|
SET title = ?, block_type = ?, start_date = ?, end_date = ?, notes = ?, outlook_event_id = ?, updated_at = NOW()
|
||||||
|
WHERE id = ? AND user_id = ?
|
||||||
|
");
|
||||||
|
$stmt->execute([$title, $blockType, $startDate, $endDate, $notes, $outlookEventId, $blockId, $user['id']]);
|
||||||
|
} else {
|
||||||
|
$stmt = $db->prepare("
|
||||||
|
INSERT INTO calendar_blocks (user_id, start_date, end_date, title, block_type, notes, outlook_event_id, created_at, updated_at)
|
||||||
|
VALUES (?, ?, ?, ?, ?, ?, ?, NOW(), NOW())
|
||||||
|
");
|
||||||
|
$stmt->execute([$user['id'], $startDate, $endDate, $title, $blockType, $notes, $outlookEventId]);
|
||||||
|
$blockId = (int)$db->lastInsertId();
|
||||||
|
}
|
||||||
|
|
||||||
|
echo json_encode([
|
||||||
|
'success' => true,
|
||||||
|
'message' => 'Agenda-blokkade succesvol opgeslagen',
|
||||||
|
'block_id' => $blockId,
|
||||||
|
'start_date'=> $startDate,
|
||||||
|
'end_date' => $endDate,
|
||||||
|
'title' => $title
|
||||||
|
]);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 14. Kalenderblokkade verwijderen / opheffen
|
||||||
|
if ($action === 'delete_calendar_block') {
|
||||||
|
$rawInput = json_decode(file_get_contents('php://input'), true);
|
||||||
|
$data = is_array($rawInput) ? $rawInput : $_POST;
|
||||||
|
$blockId = (int)($data['id'] ?? 0);
|
||||||
|
|
||||||
|
if ($blockId <= 0) {
|
||||||
|
echo json_encode(['success' => false, 'error' => 'Ongeldig blok ID']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$stmtEx = $db->prepare("SELECT outlook_event_id FROM calendar_blocks WHERE id = ? AND user_id = ?");
|
||||||
|
$stmtEx->execute([$blockId, $user['id']]);
|
||||||
|
$outlookEventId = $stmtEx->fetchColumn();
|
||||||
|
|
||||||
|
if (!empty($outlookEventId) && function_exists('microsoft_delete_event')) {
|
||||||
|
try {
|
||||||
|
microsoft_delete_event($user['id'], $outlookEventId);
|
||||||
|
} catch (Throwable $e) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
$stmtDel = $db->prepare("DELETE FROM calendar_blocks WHERE id = ? AND user_id = ?");
|
||||||
|
$stmtDel->execute([$blockId, $user['id']]);
|
||||||
|
|
||||||
|
echo json_encode(['success' => true, 'message' => 'Blokkade succesvol opgeheven']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
echo json_encode(['error' => 'Onbekende actie']);
|
echo json_encode(['error' => 'Onbekende actie']);
|
||||||
} catch (Exception $e) {
|
} catch (Exception $e) {
|
||||||
http_response_code(500);
|
http_response_code(500);
|
||||||
|
|||||||
@@ -170,6 +170,8 @@ function ensure_tables_exist(PDO $pdo): void {
|
|||||||
"CREATE TABLE IF NOT EXISTS `user_known_ips` (`id` INT AUTO_INCREMENT PRIMARY KEY, `user_id` INT NOT NULL, `ip_address` VARCHAR(45) NOT NULL, `user_agent` VARCHAR(255) NULL, `is_verified` TINYINT(1) NOT NULL DEFAULT 1, `verify_token` VARCHAR(64) NULL, `verify_token_expires_at` DATETIME NULL, `first_seen_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, `last_seen_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, UNIQUE KEY `uk_user_ip` (`user_id`, `ip_address`), INDEX (`verify_token`)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci",
|
"CREATE TABLE IF NOT EXISTS `user_known_ips` (`id` INT AUTO_INCREMENT PRIMARY KEY, `user_id` INT NOT NULL, `ip_address` VARCHAR(45) NOT NULL, `user_agent` VARCHAR(255) NULL, `is_verified` TINYINT(1) NOT NULL DEFAULT 1, `verify_token` VARCHAR(64) NULL, `verify_token_expires_at` DATETIME NULL, `first_seen_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, `last_seen_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, UNIQUE KEY `uk_user_ip` (`user_id`, `ip_address`), INDEX (`verify_token`)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci",
|
||||||
// SOC 2 & ISO 27001: Onwijzigbare Audit Trail
|
// SOC 2 & ISO 27001: Onwijzigbare Audit Trail
|
||||||
"CREATE TABLE IF NOT EXISTS `audit_logs` (`id` INT AUTO_INCREMENT PRIMARY KEY, `user_id` INT NULL, `user_email` VARCHAR(191) NULL, `action` VARCHAR(64) NOT NULL, `resource_type` VARCHAR(64) NULL, `resource_id` INT NULL, `details` TEXT NULL, `ip_address` VARCHAR(45) NOT NULL, `user_agent` VARCHAR(255) NULL, `status` ENUM('SUCCESS', 'FAILED', 'WARNING', 'PENDING') NOT NULL DEFAULT 'SUCCESS', `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, INDEX (`user_id`), INDEX (`action`), INDEX (`created_at`)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci",
|
"CREATE TABLE IF NOT EXISTS `audit_logs` (`id` INT AUTO_INCREMENT PRIMARY KEY, `user_id` INT NULL, `user_email` VARCHAR(191) NULL, `action` VARCHAR(64) NOT NULL, `resource_type` VARCHAR(64) NULL, `resource_id` INT NULL, `details` TEXT NULL, `ip_address` VARCHAR(45) NOT NULL, `user_agent` VARCHAR(255) NULL, `status` ENUM('SUCCESS', 'FAILED', 'WARNING', 'PENDING') NOT NULL DEFAULT 'SUCCESS', `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, INDEX (`user_id`), INDEX (`action`), INDEX (`created_at`)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci",
|
||||||
|
// Agenda & Verlof: Hele of meerdere dagen blokkeren (Vakantie, Vrije dag, Ziekte)
|
||||||
|
"CREATE TABLE IF NOT EXISTS `calendar_blocks` (`id` INT AUTO_INCREMENT PRIMARY KEY, `user_id` INT NOT NULL, `start_date` DATE NOT NULL, `end_date` DATE NOT NULL, `title` VARCHAR(255) NOT NULL, `block_type` VARCHAR(50) NOT NULL DEFAULT 'VACATION', `notes` TEXT NULL, `outlook_event_id` VARCHAR(255) NULL, `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, `updated_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, INDEX (`user_id`, `start_date`, `end_date`)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci",
|
||||||
];
|
];
|
||||||
foreach ($colUpgrades as $sql) {
|
foreach ($colUpgrades as $sql) {
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -313,3 +313,19 @@ CREATE TABLE IF NOT EXISTS `user_sessions` (
|
|||||||
CONSTRAINT `fk_user_sessions_user` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE CASCADE
|
CONSTRAINT `fk_user_sessions_user` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE CASCADE
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||||
|
|
||||||
|
-- Fociz Agenda & Verlof: Hele of meerdere dagen blokkeren (Vakantie, Vrije dag, Ziekte)
|
||||||
|
CREATE TABLE IF NOT EXISTS `calendar_blocks` (
|
||||||
|
`id` INT AUTO_INCREMENT PRIMARY KEY,
|
||||||
|
`user_id` INT NOT NULL,
|
||||||
|
`start_date` DATE NOT NULL,
|
||||||
|
`end_date` DATE NOT NULL,
|
||||||
|
`title` VARCHAR(255) NOT NULL,
|
||||||
|
`block_type` VARCHAR(50) NOT NULL DEFAULT 'VACATION',
|
||||||
|
`notes` TEXT NULL,
|
||||||
|
`outlook_event_id` VARCHAR(255) NULL,
|
||||||
|
`created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
`updated_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||||
|
INDEX (`user_id`, `start_date`, `end_date`),
|
||||||
|
CONSTRAINT `fk_calblocks_user` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE CASCADE
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||||
|
|
||||||
|
|||||||
@@ -485,8 +485,24 @@ function microsoft_create_event(int $userId, array $eventData): array {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$isAllDay = !empty($eventData['is_all_day']);
|
||||||
|
if ($isAllDay) {
|
||||||
|
$startDate = $date;
|
||||||
|
$endDate = !empty($eventData['end_date']) ? trim($eventData['end_date']) : $date;
|
||||||
|
// In MS Graph API is de einddatum van een all-day event exclusief (dus +1 dag om hele periode te beslaan)
|
||||||
|
$endPlusOne = date('Y-m-d', strtotime('+1 day', strtotime($endDate)));
|
||||||
|
$startDateTimeStr = "{$startDate}T00:00:00";
|
||||||
|
$endDateTimeStr = "{$endPlusOne}T00:00:00";
|
||||||
|
$showAs = !empty($eventData['show_as']) ? $eventData['show_as'] : 'oof'; // Out of Office / Afwezig
|
||||||
|
$isReminderOn = false;
|
||||||
|
$startTime = '00:00';
|
||||||
|
$endTime = '23:59';
|
||||||
|
} else {
|
||||||
$startDateTimeStr = "{$date}T{$startTime}:00";
|
$startDateTimeStr = "{$date}T{$startTime}:00";
|
||||||
$endDateTimeStr = "{$date}T{$endTime}:00";
|
$endDateTimeStr = "{$date}T{$endTime}:00";
|
||||||
|
$showAs = 'busy';
|
||||||
|
$isReminderOn = true;
|
||||||
|
}
|
||||||
|
|
||||||
$payload = [
|
$payload = [
|
||||||
'subject' => $subject,
|
'subject' => $subject,
|
||||||
@@ -502,8 +518,9 @@ function microsoft_create_event(int $userId, array $eventData): array {
|
|||||||
'dateTime' => $endDateTimeStr,
|
'dateTime' => $endDateTimeStr,
|
||||||
'timeZone' => 'Europe/Amsterdam',
|
'timeZone' => 'Europe/Amsterdam',
|
||||||
],
|
],
|
||||||
'showAs' => 'busy',
|
'isAllDay' => $isAllDay,
|
||||||
'isReminderOn' => true,
|
'showAs' => $showAs,
|
||||||
|
'isReminderOn' => $isReminderOn,
|
||||||
'reminderMinutesBeforeStart' => 15,
|
'reminderMinutesBeforeStart' => 15,
|
||||||
];
|
];
|
||||||
|
|
||||||
@@ -923,11 +940,11 @@ function microsoft_get_mail_demo_data(): array {
|
|||||||
[
|
[
|
||||||
'id' => 'demo-mail-1',
|
'id' => 'demo-mail-1',
|
||||||
'subject' => '⚡ Urgent: UWV Koppeling REST-endpoints en Datamigratie timing',
|
'subject' => '⚡ Urgent: UWV Koppeling REST-endpoints en Datamigratie timing',
|
||||||
'body_preview' => 'Beste Niek, we hebben vanuit de overheidsspecificaties aanvullende eisen ontvangen omtrent de UWV REST-endpoints en de validatie van werknemersdossiers...',
|
'body_preview' => 'Beste collega, we hebben vanuit de overheidsspecificaties aanvullende eisen ontvangen omtrent de UWV REST-endpoints en de validatie van werknemersdossiers...',
|
||||||
'body_html' => '<p>Beste Niek,</p><p>We hebben vanuit de overheidsspecificaties aanvullende eisen ontvangen omtrent de <strong>UWV REST-endpoints</strong> en de validatie van werknemersdossiers.</p><p>Zou je kunnen verifiëren of onze SSO-koppeling en de datadump export hier al op aansluiten? Het zou fijn zijn als we dit vanmiddag kort kunnen afstemmen in de planning.</p><p>Met vriendelijke groet,<br><strong>Directie UWV Koppelingen & Integraties</strong></p>',
|
'body_html' => '<p>Beste collega,</p><p>We hebben vanuit de overheidsspecificaties aanvullende eisen ontvangen omtrent de <strong>UWV REST-endpoints</strong> en de validatie van werknemersdossiers.</p><p>Zou je kunnen verifiëren of onze SSO-koppeling en de datadump export hier al op aansluiten? Het zou fijn zijn als we dit vanmiddag kort kunnen afstemmen in de planning.</p><p>Met vriendelijke groet,<br><strong>Directie UWV Koppelingen & Integraties</strong></p>',
|
||||||
'from_name' => 'Directie UWV Integraties',
|
'from_name' => 'Directie UWV Integraties',
|
||||||
'from_email' => 'koppelingen@uwv-overheid.nl',
|
'from_email' => 'koppelingen@uwv-overheid.nl',
|
||||||
'to_recipients' => [['name' => 'Niek Rabelink', 'address' => 'niek@fociz.nl']],
|
'to_recipients' => [['name' => 'Collega', 'address' => 'collega@fociz.nl']],
|
||||||
'cc_recipients' => [],
|
'cc_recipients' => [],
|
||||||
'received_at' => date('Y-m-d H:i:s', $now - (12 * 60)), // 12 minuten geleden
|
'received_at' => date('Y-m-d H:i:s', $now - (12 * 60)), // 12 minuten geleden
|
||||||
'received_human' => '12 min geleden',
|
'received_human' => '12 min geleden',
|
||||||
@@ -944,7 +961,7 @@ function microsoft_get_mail_demo_data(): array {
|
|||||||
'body_html' => '<p>Hallo team,</p><p>Bijgaand tref je de voorlopige urenstaat en factuurspecificatie aan voor de lopende sprint. Graag voor het einde van de werkweek accorderen in het systeem.</p><p>Laat het weten indien er correcties nodig zijn op de dagregistraties.</p><p>Hartelijke groet,<br><strong>Financiële Administratie Fociz</strong></p>',
|
'body_html' => '<p>Hallo team,</p><p>Bijgaand tref je de voorlopige urenstaat en factuurspecificatie aan voor de lopende sprint. Graag voor het einde van de werkweek accorderen in het systeem.</p><p>Laat het weten indien er correcties nodig zijn op de dagregistraties.</p><p>Hartelijke groet,<br><strong>Financiële Administratie Fociz</strong></p>',
|
||||||
'from_name' => 'Financiële Administratie',
|
'from_name' => 'Financiële Administratie',
|
||||||
'from_email' => 'facturatie@fociz.nl',
|
'from_email' => 'facturatie@fociz.nl',
|
||||||
'to_recipients' => [['name' => 'Niek Rabelink', 'address' => 'niek@fociz.nl']],
|
'to_recipients' => [['name' => 'Collega', 'address' => 'collega@fociz.nl']],
|
||||||
'cc_recipients' => [],
|
'cc_recipients' => [],
|
||||||
'received_at' => date('Y-m-d H:i:s', $now - (65 * 60)), // 1 uur geleden
|
'received_at' => date('Y-m-d H:i:s', $now - (65 * 60)), // 1 uur geleden
|
||||||
'received_human' => '1 uur geleden',
|
'received_human' => '1 uur geleden',
|
||||||
@@ -961,7 +978,7 @@ function microsoft_get_mail_demo_data(): array {
|
|||||||
'body_html' => '<p>Goedemiddag,</p><p>De testomgeving voor de nieuwe Atlassian API migratie (CHANGE-2046) is succesvol klaargezet. We kunnen starten met de verificatietesten op de Servicedesk wachtrijen en DX filters.</p><p>Alle endpoint logs zijn beschikbaar via het beheerpaneel.</p><p>Groeten,<br><strong>Team DevOps & Infrastructuur</strong></p>',
|
'body_html' => '<p>Goedemiddag,</p><p>De testomgeving voor de nieuwe Atlassian API migratie (CHANGE-2046) is succesvol klaargezet. We kunnen starten met de verificatietesten op de Servicedesk wachtrijen en DX filters.</p><p>Alle endpoint logs zijn beschikbaar via het beheerpaneel.</p><p>Groeten,<br><strong>Team DevOps & Infrastructuur</strong></p>',
|
||||||
'from_name' => 'DevOps Systeembeheer',
|
'from_name' => 'DevOps Systeembeheer',
|
||||||
'from_email' => 'infra@fociz.nl',
|
'from_email' => 'infra@fociz.nl',
|
||||||
'to_recipients' => [['name' => 'Niek Rabelink', 'address' => 'niek@fociz.nl']],
|
'to_recipients' => [['name' => 'Collega', 'address' => 'collega@fociz.nl']],
|
||||||
'cc_recipients' => [],
|
'cc_recipients' => [],
|
||||||
'received_at' => date('Y-m-d H:i:s', $now - (3 * 3600)), // 3 uur geleden
|
'received_at' => date('Y-m-d H:i:s', $now - (3 * 3600)), // 3 uur geleden
|
||||||
'received_human' => '3 uur geleden',
|
'received_human' => '3 uur geleden',
|
||||||
@@ -995,7 +1012,7 @@ function microsoft_get_mail_demo_data(): array {
|
|||||||
'body_html' => '<p>Welkom bij Fociz!</p><p>Wist je dat je binnengekomen Outlook e-mails en Jira tickets met één klik direct kunt inplannen in jouw persoonlijke dagplanning?</p><p>Hierdoor houd je maximale grip op je werkdag en behoud je de ultieme focus.</p><p>Veel productiviteit gewenst,<br><strong>Het Fociz Team</strong></p>',
|
'body_html' => '<p>Welkom bij Fociz!</p><p>Wist je dat je binnengekomen Outlook e-mails en Jira tickets met één klik direct kunt inplannen in jouw persoonlijke dagplanning?</p><p>Hierdoor houd je maximale grip op je werkdag en behoud je de ultieme focus.</p><p>Veel productiviteit gewenst,<br><strong>Het Fociz Team</strong></p>',
|
||||||
'from_name' => 'Fociz Support Team',
|
'from_name' => 'Fociz Support Team',
|
||||||
'from_email' => 'no-reply@fociz.nl',
|
'from_email' => 'no-reply@fociz.nl',
|
||||||
'to_recipients' => [['name' => 'Niek Rabelink', 'address' => 'niek@fociz.nl']],
|
'to_recipients' => [['name' => 'Collega', 'address' => 'collega@fociz.nl']],
|
||||||
'cc_recipients' => [],
|
'cc_recipients' => [],
|
||||||
'received_at' => date('Y-m-d H:i:s', $now - (48 * 3600)), // 2 dagen geleden
|
'received_at' => date('Y-m-d H:i:s', $now - (48 * 3600)), // 2 dagen geleden
|
||||||
'received_human' => '2 dagen geleden',
|
'received_human' => '2 dagen geleden',
|
||||||
|
|||||||
+457
-9
@@ -213,6 +213,32 @@ foreach ($weekOutlookEvents as $evt) {
|
|||||||
$outlookEventsByDate[$evtDate][] = $evt;
|
$outlookEventsByDate[$evtDate][] = $evt;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 4b. Haal actieve kalenderblokkades (vakanties / verlof / afwezigheid) op
|
||||||
|
$allCalendarBlocks = [];
|
||||||
|
$calendarBlocksByDate = [];
|
||||||
|
try {
|
||||||
|
$stmtBlocks = $db->prepare("
|
||||||
|
SELECT * FROM calendar_blocks
|
||||||
|
WHERE user_id = ? AND start_date <= ? AND end_date >= ?
|
||||||
|
ORDER BY start_date ASC
|
||||||
|
");
|
||||||
|
$stmtBlocks->execute([$userId, date('Y-m-d', strtotime('+35 days', $sundayTs)), date('Y-m-d', strtotime('-14 days', $mondayTs))]);
|
||||||
|
$allCalendarBlocks = $stmtBlocks->fetchAll() ?: [];
|
||||||
|
foreach ($allCalendarBlocks as $cb) {
|
||||||
|
$cbStart = strtotime($cb['start_date']);
|
||||||
|
$cbEnd = strtotime($cb['end_date']);
|
||||||
|
for ($d = $cbStart; $d <= $cbEnd; $d += 86400) {
|
||||||
|
$dStr = date('Y-m-d', $d);
|
||||||
|
$calendarBlocksByDate[$dStr][] = $cb;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Throwable $e) {
|
||||||
|
error_log("Fout bij ophalen calendar_blocks in planner: " . $e->getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
$selectedDayBlock = $calendarBlocksByDate[$selectedDate][0] ?? null;
|
||||||
|
$isSelectedDateBlocked = !empty($selectedDayBlock);
|
||||||
|
|
||||||
// 7 Dagen van de week samenstellen voor de weekselector
|
// 7 Dagen van de week samenstellen voor de weekselector
|
||||||
$weekDays = [];
|
$weekDays = [];
|
||||||
for ($i = 0; $i < 7; $i++) {
|
for ($i = 0; $i < 7; $i++) {
|
||||||
@@ -223,6 +249,8 @@ for ($i = 0; $i < 7; $i++) {
|
|||||||
$tStat = $weekTaskStats[$dayDate] ?? ['total' => 0, 'completed' => 0];
|
$tStat = $weekTaskStats[$dayDate] ?? ['total' => 0, 'completed' => 0];
|
||||||
$mCount = count($outlookEventsByDate[$dayDate] ?? []);
|
$mCount = count($outlookEventsByDate[$dayDate] ?? []);
|
||||||
$isWorkday = in_array($dayN, $activeWorkdays, true);
|
$isWorkday = in_array($dayN, $activeWorkdays, true);
|
||||||
|
$dayBlock = $calendarBlocksByDate[$dayDate][0] ?? null;
|
||||||
|
$isBlocked = !empty($dayBlock);
|
||||||
|
|
||||||
$weekDays[] = [
|
$weekDays[] = [
|
||||||
'date' => $dayDate,
|
'date' => $dayDate,
|
||||||
@@ -234,6 +262,8 @@ for ($i = 0; $i < 7; $i++) {
|
|||||||
'is_selected' => ($dayDate === $selectedDate),
|
'is_selected' => ($dayDate === $selectedDate),
|
||||||
'is_workday' => $isWorkday,
|
'is_workday' => $isWorkday,
|
||||||
'is_weekend' => !$isWorkday,
|
'is_weekend' => !$isWorkday,
|
||||||
|
'is_blocked' => $isBlocked,
|
||||||
|
'block_info' => $dayBlock,
|
||||||
'task_count' => $tStat['total'],
|
'task_count' => $tStat['total'],
|
||||||
'completed_count' => $tStat['completed'],
|
'completed_count' => $tStat['completed'],
|
||||||
'meeting_count' => $mCount,
|
'meeting_count' => $mCount,
|
||||||
@@ -456,6 +486,12 @@ for ($h = $startHour; $h <= $endHour; $h++) {
|
|||||||
<span>Weekend / Vrij</span>
|
<span>Weekend / Vrij</span>
|
||||||
</span>
|
</span>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
|
<?php if ($isSelectedDateBlocked): ?>
|
||||||
|
<span class="px-2.5 py-0.5 rounded-full text-[10px] font-extrabold bg-amber-100 text-amber-900 dark:bg-amber-950/80 dark:text-amber-300 ring-1 ring-amber-500/30 flex items-center gap-1 cursor-pointer" onclick="openCalendarBlockModal(<?= htmlspecialchars(json_encode($selectedDayBlock), ENT_QUOTES) ?>)" title="<?= htmlspecialchars($selectedDayBlock['title']) ?> (klik om te bewerken)">
|
||||||
|
<i data-lucide="palmtree" class="w-3 h-3 text-amber-600 dark:text-amber-400"></i>
|
||||||
|
<span><?= htmlspecialchars($selectedDayBlock['title']) ?></span>
|
||||||
|
</span>
|
||||||
|
<?php endif; ?>
|
||||||
</div>
|
</div>
|
||||||
<h1 class="text-2xl sm:text-3xl font-black tracking-tight text-slate-900 dark:text-white">
|
<h1 class="text-2xl sm:text-3xl font-black tracking-tight text-slate-900 dark:text-white">
|
||||||
Fociz
|
Fociz
|
||||||
@@ -467,6 +503,11 @@ for ($h = $startHour; $h <= $endHour; $h++) {
|
|||||||
|
|
||||||
<!-- Integratie Status Badges & Widget Beheer Knop -->
|
<!-- Integratie Status Badges & Widget Beheer Knop -->
|
||||||
<div class="flex flex-wrap items-center gap-2">
|
<div class="flex flex-wrap items-center gap-2">
|
||||||
|
<button onclick="openCalendarBlockModal()" class="flex items-center space-x-1.5 px-3.5 py-1.5 rounded-xl bg-amber-50 hover:bg-amber-100 dark:bg-amber-500/10 dark:hover:bg-amber-500/20 text-amber-800 dark:text-amber-300 border border-amber-200/80 dark:border-amber-500/30 text-xs font-bold transition shadow-2xs cursor-pointer" title="Hele of meerdere dagen blokkeren voor vakantie of verlof">
|
||||||
|
<i data-lucide="palmtree" class="w-3.5 h-3.5 text-amber-600 dark:text-amber-400"></i>
|
||||||
|
<span>Vakantie / Dagen Blokkeren</span>
|
||||||
|
</button>
|
||||||
|
|
||||||
<button onclick="openWidgetManagerModal()" class="flex items-center space-x-1.5 px-3.5 py-1.5 rounded-xl bg-indigo-50 hover:bg-indigo-100 dark:bg-indigo-500/10 dark:hover:bg-indigo-500/20 text-indigo-700 dark:text-indigo-300 border border-indigo-200/80 dark:border-indigo-500/30 text-xs font-bold transition shadow-2xs cursor-pointer">
|
<button onclick="openWidgetManagerModal()" class="flex items-center space-x-1.5 px-3.5 py-1.5 rounded-xl bg-indigo-50 hover:bg-indigo-100 dark:bg-indigo-500/10 dark:hover:bg-indigo-500/20 text-indigo-700 dark:text-indigo-300 border border-indigo-200/80 dark:border-indigo-500/30 text-xs font-bold transition shadow-2xs cursor-pointer">
|
||||||
<i data-lucide="layout-grid" class="w-3.5 h-3.5"></i>
|
<i data-lucide="layout-grid" class="w-3.5 h-3.5"></i>
|
||||||
<span>Widgets & Indeling</span>
|
<span>Widgets & Indeling</span>
|
||||||
@@ -953,8 +994,13 @@ for ($h = $startHour; $h <= $endHour; $h++) {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Rechterkant: AI Slim Indelen & Directe datumselector -->
|
<!-- Rechterkant: Dagen Blokkeren, AI Slim Indelen & Directe datumselector -->
|
||||||
<div class="flex flex-wrap items-center space-x-2 justify-end">
|
<div class="flex flex-wrap items-center gap-2 justify-end">
|
||||||
|
<button type="button" onclick="openCalendarBlockModal()" title="Hele of meerdere dagen blokkeren voor vakantie, verlof of afwezigheid" class="flex items-center space-x-1.5 px-3 py-1.5 rounded-xl bg-amber-50 hover:bg-amber-100 dark:bg-amber-950/40 dark:hover:bg-amber-900/50 text-amber-800 dark:text-amber-300 border border-amber-200 dark:border-amber-800/60 text-xs font-semibold shadow-2xs transition cursor-pointer">
|
||||||
|
<i data-lucide="palmtree" class="w-3.5 h-3.5 text-amber-600 dark:text-amber-400"></i>
|
||||||
|
<span class="hidden sm:inline">Dagen Blokkeren</span>
|
||||||
|
</button>
|
||||||
|
|
||||||
<?php if ($aiSettings['ai_enabled'] && $aiSettings['has_key']): ?>
|
<?php if ($aiSettings['ai_enabled'] && $aiSettings['has_key']): ?>
|
||||||
<button onclick="aiSmartSchedule(this)" id="aiScheduleBtn" title="Laat AI Co-Pilot openstaande taken optimaal inplannen om meetings en lunch heen" class="flex items-center space-x-1.5 px-3 py-1.5 rounded-xl bg-gradient-to-r from-indigo-600 to-sky-600 hover:from-indigo-500 hover:to-sky-500 text-white text-xs font-semibold shadow-xs transition cursor-pointer">
|
<button onclick="aiSmartSchedule(this)" id="aiScheduleBtn" title="Laat AI Co-Pilot openstaande taken optimaal inplannen om meetings en lunch heen" class="flex items-center space-x-1.5 px-3 py-1.5 rounded-xl bg-gradient-to-r from-indigo-600 to-sky-600 hover:from-indigo-500 hover:to-sky-500 text-white text-xs font-semibold shadow-xs transition cursor-pointer">
|
||||||
<i data-lucide="sparkles" class="w-3.5 h-3.5"></i>
|
<i data-lucide="sparkles" class="w-3.5 h-3.5"></i>
|
||||||
@@ -984,16 +1030,23 @@ for ($h = $startHour; $h <= $endHour; $h++) {
|
|||||||
onclick="switchPlannerDate('<?= $wd['date'] ?>', event); return false;"
|
onclick="switchPlannerDate('<?= $wd['date'] ?>', event); return false;"
|
||||||
class="fociz-card-interactive group relative flex flex-col p-3 rounded-xl border transition-all text-left <?= $wd['is_selected']
|
class="fociz-card-interactive group relative flex flex-col p-3 rounded-xl border transition-all text-left <?= $wd['is_selected']
|
||||||
? 'bg-gradient-to-b from-indigo-500/15 via-indigo-500/5 to-transparent dark:from-indigo-500/25 dark:via-indigo-500/10 dark:to-transparent border-indigo-500 ring-2 ring-indigo-500/40 shadow-sm'
|
? 'bg-gradient-to-b from-indigo-500/15 via-indigo-500/5 to-transparent dark:from-indigo-500/25 dark:via-indigo-500/10 dark:to-transparent border-indigo-500 ring-2 ring-indigo-500/40 shadow-sm'
|
||||||
|
: ($wd['is_blocked']
|
||||||
|
? 'bg-amber-50/70 dark:bg-amber-950/20 border-amber-300/80 dark:border-amber-800/60 hover:border-amber-400 shadow-2xs'
|
||||||
: ($wd['is_workday']
|
: ($wd['is_workday']
|
||||||
? 'bg-slate-50/70 dark:bg-[#0c0f17]/50 border-slate-200/80 dark:border-slate-800/80 hover:border-indigo-300 dark:hover:border-indigo-600/50 hover:bg-white dark:hover:bg-[#111624] shadow-2xs'
|
? 'bg-slate-50/70 dark:bg-[#0c0f17]/50 border-slate-200/80 dark:border-slate-800/80 hover:border-indigo-300 dark:hover:border-indigo-600/50 hover:bg-white dark:hover:bg-[#111624] shadow-2xs'
|
||||||
: 'bg-slate-100/60 dark:bg-[#080b11]/60 border-dashed border-amber-300/60 dark:border-amber-900/40 hover:border-amber-400 dark:hover:border-amber-700 hover:bg-white dark:hover:bg-[#0f141f] shadow-2xs') ?>"
|
: 'bg-slate-100/60 dark:bg-[#080b11]/60 border-dashed border-amber-300/60 dark:border-amber-900/40 hover:border-amber-400 dark:hover:border-amber-700 hover:bg-white dark:hover:bg-[#0f141f] shadow-2xs')) ?>"
|
||||||
>
|
>
|
||||||
<div class="flex items-center justify-between mb-1">
|
<div class="flex items-center justify-between mb-1">
|
||||||
<span class="text-[10px] font-bold uppercase tracking-wider <?= $wd['is_selected'] ? 'text-indigo-600 dark:text-indigo-400' : ($wd['is_workday'] ? 'text-slate-500 dark:text-slate-400' : 'text-slate-400 dark:text-slate-500') ?>">
|
<span class="text-[10px] font-bold uppercase tracking-wider <?= $wd['is_selected'] ? 'text-indigo-600 dark:text-indigo-400' : ($wd['is_workday'] ? 'text-slate-500 dark:text-slate-400' : 'text-slate-400 dark:text-slate-500') ?>">
|
||||||
<?= $wd['short_name'] ?>
|
<?= $wd['short_name'] ?>
|
||||||
</span>
|
</span>
|
||||||
<div class="flex items-center gap-1">
|
<div class="flex items-center gap-1">
|
||||||
<?php if ($wd['is_weekend']): ?>
|
<?php if ($wd['is_blocked']): ?>
|
||||||
|
<span class="px-1.5 py-0.5 rounded text-[8px] font-extrabold uppercase bg-amber-200/90 dark:bg-amber-900/80 text-amber-950 dark:text-amber-200 border border-amber-300 dark:border-amber-700/80 flex items-center gap-0.5" title="<?= htmlspecialchars($wd['block_info']['title']) ?>">
|
||||||
|
<i data-lucide="palmtree" class="w-2.5 h-2.5"></i>
|
||||||
|
<span><?= htmlspecialchars(mb_strimwidth($wd['block_info']['title'], 0, 7, '..')) ?></span>
|
||||||
|
</span>
|
||||||
|
<?php elseif ($wd['is_weekend']): ?>
|
||||||
<span class="px-1.5 py-0.5 rounded text-[8px] font-extrabold uppercase bg-amber-50 dark:bg-amber-950/60 text-amber-700 dark:text-amber-400 border border-amber-200/80 dark:border-amber-800/60" title="Weekend / Niet-werkdag conform beheerinstellingen">
|
<span class="px-1.5 py-0.5 rounded text-[8px] font-extrabold uppercase bg-amber-50 dark:bg-amber-950/60 text-amber-700 dark:text-amber-400 border border-amber-200/80 dark:border-amber-800/60" title="Weekend / Niet-werkdag conform beheerinstellingen">
|
||||||
Weekend
|
Weekend
|
||||||
</span>
|
</span>
|
||||||
@@ -1041,27 +1094,68 @@ for ($h = $startHour; $h <= $endHour; $h++) {
|
|||||||
<?php endforeach; ?>
|
<?php endforeach; ?>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<?php if ($isSelectedDateBlocked): ?>
|
||||||
|
<!-- Prominente Vakantie / Blokkade Banner -->
|
||||||
|
<div class="p-4 sm:p-5 rounded-2xl bg-gradient-to-r from-amber-50 via-amber-100/60 to-orange-50 dark:from-amber-950/40 dark:via-[#1a1408] dark:to-orange-950/30 border-2 border-amber-300/90 dark:border-amber-700/70 flex flex-col sm:flex-row sm:items-center justify-between gap-4 shadow-sm">
|
||||||
|
<div class="flex items-start sm:items-center space-x-3.5">
|
||||||
|
<div class="w-11 h-11 rounded-2xl bg-amber-200 dark:bg-amber-900/80 text-amber-900 dark:text-amber-200 flex items-center justify-center font-bold text-xl flex-shrink-0 shadow-xs ring-2 ring-amber-300 dark:ring-amber-700/80">
|
||||||
|
🏖️
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div class="flex items-center space-x-2 flex-wrap gap-y-1">
|
||||||
|
<h4 class="text-sm sm:text-base font-black text-amber-950 dark:text-amber-200">
|
||||||
|
<?= htmlspecialchars($selectedDayBlock['title']) ?>
|
||||||
|
</h4>
|
||||||
|
<span class="px-2.5 py-0.5 rounded-full text-[10px] font-extrabold bg-amber-200/90 text-amber-950 dark:bg-amber-800/80 dark:text-amber-100 uppercase tracking-wider border border-amber-300 dark:border-amber-700">
|
||||||
|
<?= htmlspecialchars($selectedDayBlock['block_type']) ?>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<p class="text-xs text-amber-900/80 dark:text-amber-300/80 mt-1">
|
||||||
|
Geblokkeerd van <strong><?= date('j M Y', strtotime($selectedDayBlock['start_date'])) ?></strong> t/m <strong><?= date('j M Y', strtotime($selectedDayBlock['end_date'])) ?></strong>
|
||||||
|
<?php if (!empty($selectedDayBlock['notes'])): ?>
|
||||||
|
• <?= htmlspecialchars($selectedDayBlock['notes']) ?>
|
||||||
|
<?php endif; ?>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center space-x-2 self-end sm:self-auto flex-shrink-0">
|
||||||
|
<button onclick="openCalendarBlockModal(<?= htmlspecialchars(json_encode($selectedDayBlock), ENT_QUOTES) ?>)" class="px-3.5 py-1.5 rounded-xl bg-white dark:bg-slate-900 text-slate-800 dark:text-slate-200 text-xs font-bold hover:bg-amber-50 dark:hover:bg-slate-800 border border-amber-300/80 dark:border-amber-700 transition shadow-2xs cursor-pointer">
|
||||||
|
Bewerken
|
||||||
|
</button>
|
||||||
|
<button onclick="deleteCalendarBlock(<?= (int)$selectedDayBlock['id'] ?>)" class="px-3.5 py-1.5 rounded-xl bg-rose-50 hover:bg-rose-100 dark:bg-rose-950/50 dark:hover:bg-rose-900/60 text-rose-700 dark:text-rose-300 text-xs font-bold border border-rose-200 dark:border-rose-800/60 transition shadow-2xs cursor-pointer">
|
||||||
|
Opheffen
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
<!-- 3. Sub-header van de Tijdlijn voor de geselecteerde dag -->
|
<!-- 3. Sub-header van de Tijdlijn voor de geselecteerde dag -->
|
||||||
<div class="flex items-center justify-between pt-3 border-t border-slate-100 dark:border-slate-800/80">
|
<div class="flex items-center justify-between pt-3 border-t border-slate-100 dark:border-slate-800/80">
|
||||||
<div class="flex items-center space-x-2">
|
<div class="flex items-center space-x-2">
|
||||||
<div class="w-7 h-7 rounded-lg <?= $isSelectedDateWeekend ? 'bg-amber-50 dark:bg-amber-950/50 text-amber-600 dark:text-amber-400' : 'bg-indigo-50 dark:bg-indigo-950/50 text-indigo-600 dark:text-indigo-400' ?> flex items-center justify-center font-bold">
|
<div class="w-7 h-7 rounded-lg <?= ($isSelectedDateBlocked || $isSelectedDateWeekend) ? 'bg-amber-50 dark:bg-amber-950/50 text-amber-600 dark:text-amber-400' : 'bg-indigo-50 dark:bg-indigo-950/50 text-indigo-600 dark:text-indigo-400' ?> flex items-center justify-center font-bold">
|
||||||
<i data-lucide="<?= $isSelectedDateWeekend ? 'coffee' : 'clock' ?>" class="w-3.5 h-3.5"></i>
|
<i data-lucide="<?= $isSelectedDateBlocked ? 'palmtree' : ($isSelectedDateWeekend ? 'coffee' : 'clock') ?>" class="w-3.5 h-3.5"></i>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<div class="flex items-center space-x-2">
|
<div class="flex items-center space-x-2">
|
||||||
<h4 class="text-xs font-bold text-slate-900 dark:text-white uppercase tracking-wider">
|
<h4 class="text-xs font-bold text-slate-900 dark:text-white uppercase tracking-wider">
|
||||||
Tijdlijn & Time-blocking voor <?= htmlspecialchars($formattedSelectedDate) ?>
|
Tijdlijn & Time-blocking voor <?= htmlspecialchars($formattedSelectedDate) ?>
|
||||||
</h4>
|
</h4>
|
||||||
<?php if ($isSelectedDateWeekend): ?>
|
<?php if ($isSelectedDateBlocked): ?>
|
||||||
|
<span class="px-2 py-0.5 rounded-full text-[9px] font-extrabold uppercase bg-amber-100 text-amber-900 dark:bg-amber-950/80 dark:text-amber-200 border border-amber-300 dark:border-amber-700/80">
|
||||||
|
<?= htmlspecialchars($selectedDayBlock['title']) ?> (Geblokkeerd)
|
||||||
|
</span>
|
||||||
|
<?php elseif ($isSelectedDateWeekend): ?>
|
||||||
<span class="px-2 py-0.5 rounded-full text-[9px] font-extrabold uppercase bg-amber-50 text-amber-700 dark:bg-amber-950/60 dark:text-amber-300 border border-amber-200/80 dark:border-amber-800/60">
|
<span class="px-2 py-0.5 rounded-full text-[9px] font-extrabold uppercase bg-amber-50 text-amber-700 dark:bg-amber-950/60 dark:text-amber-300 border border-amber-200/80 dark:border-amber-800/60">
|
||||||
Weekend / Vrije Dag
|
Weekend / Vrije Dag
|
||||||
</span>
|
</span>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
</div>
|
</div>
|
||||||
<p class="text-[10px] text-slate-400">
|
<p class="text-[10px] text-slate-400">
|
||||||
<?= $isSelectedDateWeekend
|
<?= $isSelectedDateBlocked
|
||||||
|
? 'Deze dag is geblokkeerd in je agenda • Geen kantooruren ingeroosterd'
|
||||||
|
: ($isSelectedDateWeekend
|
||||||
? 'Geen reguliere kantooruren gepland conform beheerinstellingen • Vrije dag'
|
? 'Geen reguliere kantooruren gepland conform beheerinstellingen • Vrije dag'
|
||||||
: 'Urenindeling & synchronisatie met Outlook' ?>
|
: 'Urenindeling & synchronisatie met Outlook') ?>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -2316,6 +2410,123 @@ if ($isAjaxRequest) {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Modal: Agenda Dagen Blokkeren (Vakantie / Verlof / Focus) -->
|
||||||
|
<div id="calendarBlockModal" class="fixed inset-0 z-50 bg-slate-950/70 backdrop-blur-sm hidden items-center justify-center p-4">
|
||||||
|
<div class="fociz-card p-6 w-full max-w-lg shadow-2xl space-y-4 max-h-[90vh] flex flex-col">
|
||||||
|
<!-- Header -->
|
||||||
|
<div class="flex items-center justify-between pb-3 border-b border-slate-100 dark:border-slate-800/80 flex-shrink-0">
|
||||||
|
<div class="flex items-center space-x-2.5">
|
||||||
|
<div class="w-8 h-8 rounded-xl bg-amber-50 dark:bg-amber-500/10 border border-amber-200 dark:border-amber-500/20 text-amber-600 dark:text-amber-400 flex items-center justify-center">
|
||||||
|
<i data-lucide="palmtree" class="w-4 h-4"></i>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h3 id="calendarBlockModalTitle" class="text-sm font-bold text-slate-900 dark:text-white">Dagen Blokkeren / Vakantie</h3>
|
||||||
|
<p class="text-[10px] text-slate-500 dark:text-slate-400">Blokkeer een hele dag of periode in je agenda & planner</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<button type="button" onclick="closeCalendarBlockModal()" 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>
|
||||||
|
|
||||||
|
<!-- Formulier / Content -->
|
||||||
|
<form id="calendarBlockForm" onsubmit="submitCalendarBlock(event)" class="flex-1 overflow-y-auto pr-1 space-y-3.5">
|
||||||
|
<input type="hidden" id="calBlockId" value="0">
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label class="block text-xs font-bold text-slate-700 dark:text-slate-300 mb-1">Type Blokkade</label>
|
||||||
|
<div class="grid grid-cols-4 gap-2">
|
||||||
|
<button type="button" onclick="updateCalBlockDefaultTitle('VACATION')" id="calType_VACATION" class="cal-block-type-btn p-2 rounded-xl border text-center transition cursor-pointer flex flex-col items-center">
|
||||||
|
<span class="text-base mb-0.5">🏖️</span>
|
||||||
|
<span class="text-[10px] font-bold">Vakantie</span>
|
||||||
|
</button>
|
||||||
|
<button type="button" onclick="updateCalBlockDefaultTitle('LEAVE')" id="calType_LEAVE" class="cal-block-type-btn p-2 rounded-xl border text-center transition cursor-pointer flex flex-col items-center">
|
||||||
|
<span class="text-base mb-0.5">✈️</span>
|
||||||
|
<span class="text-[10px] font-bold">Verlof / ADV</span>
|
||||||
|
</button>
|
||||||
|
<button type="button" onclick="updateCalBlockDefaultTitle('SICK')" id="calType_SICK" class="cal-block-type-btn p-2 rounded-xl border text-center transition cursor-pointer flex flex-col items-center">
|
||||||
|
<span class="text-base mb-0.5">🤒</span>
|
||||||
|
<span class="text-[10px] font-bold">Ziekte</span>
|
||||||
|
</button>
|
||||||
|
<button type="button" onclick="updateCalBlockDefaultTitle('FOCUS')" id="calType_FOCUS" class="cal-block-type-btn p-2 rounded-xl border text-center transition cursor-pointer flex flex-col items-center">
|
||||||
|
<span class="text-base mb-0.5">🎯</span>
|
||||||
|
<span class="text-[10px] font-bold">Focusdag</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<input type="hidden" id="calBlockType" value="VACATION">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label class="block text-xs font-bold text-slate-700 dark:text-slate-300 mb-1">Titel / Omschrijving <span class="text-rose-500">*</span></label>
|
||||||
|
<input type="text" id="calBlockTitle" required placeholder="Bijv. Zomervakantie, ADV dag, Verlof..." class="w-full px-3 py-2 rounded-xl bg-slate-50 dark:bg-slate-800/80 border border-slate-200 dark:border-slate-700 text-xs font-medium text-slate-900 dark:text-white placeholder-slate-400 focus:outline-hidden focus:ring-2 focus:ring-amber-500">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="grid grid-cols-2 gap-3">
|
||||||
|
<div>
|
||||||
|
<label class="block text-xs font-bold text-slate-700 dark:text-slate-300 mb-1">Begindatum <span class="text-rose-500">*</span></label>
|
||||||
|
<input type="date" id="calBlockStartDate" required class="w-full px-3 py-2 rounded-xl bg-slate-50 dark:bg-slate-800/80 border border-slate-200 dark:border-slate-700 text-xs font-medium text-slate-900 dark:text-white focus:outline-hidden focus:ring-2 focus:ring-amber-500">
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label class="block text-xs font-bold text-slate-700 dark:text-slate-300 mb-1">Einddatum (t/m) <span class="text-rose-500">*</span></label>
|
||||||
|
<input type="date" id="calBlockEndDate" required class="w-full px-3 py-2 rounded-xl bg-slate-50 dark:bg-slate-800/80 border border-slate-200 dark:border-slate-700 text-xs font-medium text-slate-900 dark:text-white focus:outline-hidden focus:ring-2 focus:ring-amber-500">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label class="block text-xs font-bold text-slate-700 dark:text-slate-300 mb-1">Notities (optioneel)</label>
|
||||||
|
<textarea id="calBlockNotes" rows="2" placeholder="Eventuele overdracht of bijzonderheden..." class="w-full px-3 py-2 rounded-xl bg-slate-50 dark:bg-slate-800/80 border border-slate-200 dark:border-slate-700 text-xs font-medium text-slate-900 dark:text-white placeholder-slate-400 focus:outline-hidden focus:ring-2 focus:ring-amber-500"></textarea>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<?php if ($isOutlookConnected): ?>
|
||||||
|
<div class="p-3 rounded-xl bg-sky-50/60 dark:bg-sky-950/20 border border-sky-200/60 dark:border-sky-800/40 flex items-center justify-between">
|
||||||
|
<div class="flex items-center space-x-2.5">
|
||||||
|
<i data-lucide="refresh-cw" class="w-4 h-4 text-sky-600 dark:text-sky-400 flex-shrink-0"></i>
|
||||||
|
<div>
|
||||||
|
<p class="text-xs font-bold text-sky-900 dark:text-sky-200">Synchroniseren naar Outlook 365</p>
|
||||||
|
<p class="text-[10px] text-sky-700/80 dark:text-sky-400/80">Maakt een 'Afwezig' (OOF) dagblokkade aan in je Microsoft agenda</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<label class="relative inline-flex items-center cursor-pointer flex-shrink-0 ml-2">
|
||||||
|
<input type="checkbox" id="calBlockSyncOutlook" checked class="sr-only peer">
|
||||||
|
<div class="w-9 h-5 bg-slate-200 peer-focus:outline-none rounded-full peer dark:bg-slate-700 peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:border-slate-300 after:border after:rounded-full after:h-4 after:w-4 after:transition-all dark:border-slate-600 peer-checked:bg-sky-600"></div>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<!-- Overzicht actieve / geplande blokkades -->
|
||||||
|
<div class="pt-2 border-t border-slate-100 dark:border-slate-800">
|
||||||
|
<div class="flex items-center justify-between mb-2">
|
||||||
|
<span class="text-[11px] font-bold text-slate-600 dark:text-slate-400 uppercase tracking-wider">Actieve & Toekomstige Blokkades</span>
|
||||||
|
<button type="button" onclick="loadCalendarBlocksList()" class="text-[10px] text-indigo-600 hover:text-indigo-700 dark:text-indigo-400 font-semibold cursor-pointer flex items-center space-x-1">
|
||||||
|
<i data-lucide="refresh-cw" class="w-2.5 h-2.5"></i>
|
||||||
|
<span>Vernieuwen</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div id="calendarBlocksListContainer" class="space-y-1.5 max-h-36 overflow-y-auto pr-1">
|
||||||
|
<div class="text-[11px] text-slate-400 text-center py-2">Laden...</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Knoppen balk -->
|
||||||
|
<div class="flex items-center justify-between pt-3 border-t border-slate-100 dark:border-slate-800/80 flex-shrink-0">
|
||||||
|
<button type="button" id="btnDeleteCurrentCalBlock" onclick="deleteCurrentCalendarBlock()" class="hidden px-3 py-2 rounded-xl bg-rose-50 hover:bg-rose-100 dark:bg-rose-950/50 dark:hover:bg-rose-900/60 text-rose-700 dark:text-rose-300 text-xs font-bold border border-rose-200 dark:border-rose-800/60 transition cursor-pointer items-center space-x-1">
|
||||||
|
<i data-lucide="trash-2" class="w-3.5 h-3.5"></i>
|
||||||
|
<span>Opheffen</span>
|
||||||
|
</button>
|
||||||
|
<div class="flex items-center space-x-2 ml-auto">
|
||||||
|
<button type="button" onclick="closeCalendarBlockModal()" 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="submit" id="btnSaveCalBlock" class="px-4 py-2 rounded-xl bg-amber-600 hover:bg-amber-500 text-white text-xs font-bold transition shadow-xs cursor-pointer flex items-center space-x-1.5">
|
||||||
|
<i data-lucide="check" class="w-3.5 h-3.5"></i>
|
||||||
|
<span>Opslaan</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
// --- Fociz Widget Beheer & Configuratielogica (v2.0) ---
|
// --- Fociz Widget Beheer & Configuratielogica (v2.0) ---
|
||||||
let focizWidgets = <?= json_encode(array_values($userWidgetLayout), JSON_UNESCAPED_UNICODE) ?>;
|
let focizWidgets = <?= json_encode(array_values($userWidgetLayout), JSON_UNESCAPED_UNICODE) ?>;
|
||||||
@@ -2590,6 +2801,243 @@ function showWidgetToast(htmlMessage, type = 'success', duration = 3500) {
|
|||||||
}, duration);
|
}, duration);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// --- Agenda Dagen Blokkeren (Vakantie / Verlof / Focus) ---
|
||||||
|
const CAL_BLOCK_DEFAULT_TITLES = {
|
||||||
|
'VACATION': 'Vakantie',
|
||||||
|
'LEAVE': 'Verlof',
|
||||||
|
'SICK': 'Ziekte / Afwezig',
|
||||||
|
'FOCUS': 'Focusdag'
|
||||||
|
};
|
||||||
|
|
||||||
|
function selectCalBlockType(type) {
|
||||||
|
const input = document.getElementById('calBlockType');
|
||||||
|
if (input) input.value = type;
|
||||||
|
|
||||||
|
const types = ['VACATION', 'LEAVE', 'SICK', 'FOCUS'];
|
||||||
|
types.forEach(t => {
|
||||||
|
const btn = document.getElementById(`calType_${t}`);
|
||||||
|
if (!btn) return;
|
||||||
|
if (t === type) {
|
||||||
|
btn.className = 'cal-block-type-btn p-2 rounded-xl border border-amber-500 bg-amber-100 dark:bg-amber-950/80 text-amber-950 dark:text-amber-200 font-bold shadow-xs text-center transition cursor-pointer flex flex-col items-center ring-2 ring-amber-500/20';
|
||||||
|
} else {
|
||||||
|
btn.className = 'cal-block-type-btn p-2 rounded-xl border border-slate-200 dark:border-slate-700/80 bg-slate-50 dark:bg-slate-800/60 text-slate-600 dark:text-slate-400 hover:bg-slate-100 dark:hover:bg-slate-800 text-center transition cursor-pointer flex flex-col items-center';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateCalBlockDefaultTitle(type) {
|
||||||
|
const titleInput = document.getElementById('calBlockTitle');
|
||||||
|
const currentVal = titleInput ? titleInput.value.trim() : '';
|
||||||
|
const oldType = document.getElementById('calBlockType')?.value;
|
||||||
|
|
||||||
|
selectCalBlockType(type);
|
||||||
|
|
||||||
|
if (titleInput && (!currentVal || currentVal === (CAL_BLOCK_DEFAULT_TITLES[oldType] || ''))) {
|
||||||
|
titleInput.value = CAL_BLOCK_DEFAULT_TITLES[type] || '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function openCalendarBlockModal(existingBlock = null, defaultDate = null) {
|
||||||
|
const modal = document.getElementById('calendarBlockModal');
|
||||||
|
if (!modal) return;
|
||||||
|
|
||||||
|
const idInput = document.getElementById('calBlockId');
|
||||||
|
const titleInput = document.getElementById('calBlockTitle');
|
||||||
|
const startInput = document.getElementById('calBlockStartDate');
|
||||||
|
const endInput = document.getElementById('calBlockEndDate');
|
||||||
|
const notesInput = document.getElementById('calBlockNotes');
|
||||||
|
const deleteBtn = document.getElementById('btnDeleteCurrentCalBlock');
|
||||||
|
const modalTitle = document.getElementById('calendarBlockModalTitle');
|
||||||
|
|
||||||
|
const selDate = defaultDate || '<?= $selectedDate ?>';
|
||||||
|
|
||||||
|
if (existingBlock && existingBlock.id) {
|
||||||
|
if (idInput) idInput.value = existingBlock.id;
|
||||||
|
if (titleInput) titleInput.value = existingBlock.title || '';
|
||||||
|
if (startInput) startInput.value = existingBlock.start_date || selDate;
|
||||||
|
if (endInput) endInput.value = existingBlock.end_date || existingBlock.start_date || selDate;
|
||||||
|
if (notesInput) notesInput.value = existingBlock.notes || '';
|
||||||
|
selectCalBlockType(existingBlock.block_type || 'VACATION');
|
||||||
|
if (deleteBtn) {
|
||||||
|
deleteBtn.classList.remove('hidden');
|
||||||
|
deleteBtn.classList.add('flex');
|
||||||
|
}
|
||||||
|
if (modalTitle) modalTitle.textContent = 'Blokkade Bewerken';
|
||||||
|
} else {
|
||||||
|
if (idInput) idInput.value = 0;
|
||||||
|
selectCalBlockType('VACATION');
|
||||||
|
if (titleInput) titleInput.value = 'Vakantie';
|
||||||
|
if (startInput) startInput.value = selDate;
|
||||||
|
if (endInput) endInput.value = selDate;
|
||||||
|
if (notesInput) notesInput.value = '';
|
||||||
|
if (deleteBtn) {
|
||||||
|
deleteBtn.classList.add('hidden');
|
||||||
|
deleteBtn.classList.remove('flex');
|
||||||
|
}
|
||||||
|
if (modalTitle) modalTitle.textContent = 'Dagen Blokkeren / Vakantie';
|
||||||
|
}
|
||||||
|
|
||||||
|
loadCalendarBlocksList();
|
||||||
|
|
||||||
|
modal.classList.remove('hidden');
|
||||||
|
modal.classList.add('flex');
|
||||||
|
|
||||||
|
if (typeof lucide !== 'undefined' && lucide.createIcons) {
|
||||||
|
lucide.createIcons();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function closeCalendarBlockModal() {
|
||||||
|
const modal = document.getElementById('calendarBlockModal');
|
||||||
|
if (modal) {
|
||||||
|
modal.classList.add('hidden');
|
||||||
|
modal.classList.remove('flex');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function loadCalendarBlocksList() {
|
||||||
|
const container = document.getElementById('calendarBlocksListContainer');
|
||||||
|
if (!container) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const res = await fetch('/api/planner.php?action=get_calendar_blocks');
|
||||||
|
const data = await res.json();
|
||||||
|
|
||||||
|
if (!data.success || !Array.isArray(data.blocks) || data.blocks.length === 0) {
|
||||||
|
container.innerHTML = '<p class="text-[11px] text-slate-400 text-center py-2 italic">Geen actieve of geplande blokkades gevonden.</p>';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const icons = {
|
||||||
|
'VACATION': '🏖️',
|
||||||
|
'LEAVE': '✈️',
|
||||||
|
'SICK': '🤒',
|
||||||
|
'FOCUS': '🎯'
|
||||||
|
};
|
||||||
|
|
||||||
|
container.innerHTML = data.blocks.map(b => {
|
||||||
|
const icon = icons[b.block_type] || '📅';
|
||||||
|
const safeTitle = escapeHtml(b.title);
|
||||||
|
const dateStr = b.start_date === b.end_date
|
||||||
|
? b.start_date
|
||||||
|
: `${b.start_date} t/m ${b.end_date}`;
|
||||||
|
const blockJson = JSON.stringify(b).replace(/"/g, '"');
|
||||||
|
|
||||||
|
return `
|
||||||
|
<div class="flex items-center justify-between p-2 rounded-xl bg-slate-50 dark:bg-slate-800/70 border border-slate-200/70 dark:border-slate-700/60 text-xs">
|
||||||
|
<div class="flex items-center space-x-2 min-w-0 flex-1 cursor-pointer" onclick="openCalendarBlockModal(${blockJson})">
|
||||||
|
<span class="text-sm flex-shrink-0">${icon}</span>
|
||||||
|
<div class="min-w-0 flex-1 truncate">
|
||||||
|
<div class="font-bold text-slate-800 dark:text-slate-200 truncate">${safeTitle}</div>
|
||||||
|
<div class="text-[10px] text-slate-500 dark:text-slate-400">${dateStr}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center space-x-1 flex-shrink-0 ml-2">
|
||||||
|
<button type="button" onclick="openCalendarBlockModal(${blockJson})" class="p-1 text-slate-400 hover:text-indigo-600 dark:hover:text-indigo-400 transition cursor-pointer" title="Bewerken">
|
||||||
|
<i data-lucide="edit-2" class="w-3.5 h-3.5"></i>
|
||||||
|
</button>
|
||||||
|
<button type="button" onclick="deleteCalendarBlock(${b.id})" class="p-1 text-slate-400 hover:text-rose-600 dark:hover:text-rose-400 transition cursor-pointer" title="Opheffen">
|
||||||
|
<i data-lucide="trash-2" class="w-3.5 h-3.5"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}).join('');
|
||||||
|
|
||||||
|
if (typeof lucide !== 'undefined' && lucide.createIcons) {
|
||||||
|
lucide.createIcons();
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
container.innerHTML = '<p class="text-[11px] text-rose-500 text-center py-2">Kon blokkades niet laden.</p>';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function submitCalendarBlock(event) {
|
||||||
|
event.preventDefault();
|
||||||
|
const btn = document.getElementById('btnSaveCalBlock');
|
||||||
|
const origHtml = btn ? btn.innerHTML : '';
|
||||||
|
if (btn) {
|
||||||
|
btn.innerText = 'Opslaan...';
|
||||||
|
btn.disabled = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const id = parseInt(document.getElementById('calBlockId')?.value, 10) || 0;
|
||||||
|
const blockType = document.getElementById('calBlockType')?.value || 'VACATION';
|
||||||
|
const title = document.getElementById('calBlockTitle')?.value.trim();
|
||||||
|
const startDate = document.getElementById('calBlockStartDate')?.value;
|
||||||
|
const endDate = document.getElementById('calBlockEndDate')?.value;
|
||||||
|
const notes = document.getElementById('calBlockNotes')?.value.trim();
|
||||||
|
const syncOutlook = document.getElementById('calBlockSyncOutlook')?.checked || false;
|
||||||
|
|
||||||
|
if (!title) {
|
||||||
|
alert('Vul een titel in voor de blokkade.');
|
||||||
|
if (btn) { btn.innerHTML = origHtml; btn.disabled = false; }
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const res = await fetch('/api/planner.php?action=save_calendar_block', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify({
|
||||||
|
id: id,
|
||||||
|
title: title,
|
||||||
|
block_type: blockType,
|
||||||
|
start_date: startDate,
|
||||||
|
end_date: endDate,
|
||||||
|
notes: notes,
|
||||||
|
sync_to_outlook: syncOutlook
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
const data = await res.json();
|
||||||
|
if (data.success) {
|
||||||
|
showWidgetToast(id > 0 ? 'Blokkade succesvol bijgewerkt' : 'Dagen succesvol geblokkeerd', 'success');
|
||||||
|
setTimeout(() => {
|
||||||
|
window.location.reload();
|
||||||
|
}, 600);
|
||||||
|
} else {
|
||||||
|
alert(data.error || 'Er is een fout opgetreden bij het opslaan.');
|
||||||
|
if (btn) { btn.innerHTML = origHtml; btn.disabled = false; }
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
alert('Netwerkfout bij opslaan van blokkade.');
|
||||||
|
if (btn) { btn.innerHTML = origHtml; btn.disabled = false; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function deleteCalendarBlock(blockId) {
|
||||||
|
if (!blockId) return;
|
||||||
|
if (!confirm('Weet je zeker dat je deze agenda-blokkade wilt opheffen?')) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const res = await fetch('/api/planner.php?action=delete_calendar_block', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify({ id: blockId })
|
||||||
|
});
|
||||||
|
|
||||||
|
const data = await res.json();
|
||||||
|
if (data.success) {
|
||||||
|
showWidgetToast('Blokkade opgeheven', 'success');
|
||||||
|
setTimeout(() => {
|
||||||
|
window.location.reload();
|
||||||
|
}, 600);
|
||||||
|
} else {
|
||||||
|
alert(data.error || 'Kon blokkade niet opheffen.');
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
alert('Netwerkfout bij opheffen van blokkade.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function deleteCurrentCalendarBlock() {
|
||||||
|
const id = parseInt(document.getElementById('calBlockId')?.value, 10) || 0;
|
||||||
|
if (id > 0) {
|
||||||
|
deleteCalendarBlock(id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// --- Bepaal de actuele widget layout en exacte volgorde uit het scherm (DOM) ---
|
// --- Bepaal de actuele widget layout en exacte volgorde uit het scherm (DOM) ---
|
||||||
function getActiveWidgetLayoutPayload() {
|
function getActiveWidgetLayoutPayload() {
|
||||||
const grid = document.getElementById('focizWidgetGrid');
|
const grid = document.getElementById('focizWidgetGrid');
|
||||||
|
|||||||
Reference in New Issue
Block a user