diff --git a/api/live_sync.php b/api/live_sync.php new file mode 100644 index 0000000..0fe34b5 --- /dev/null +++ b/api/live_sync.php @@ -0,0 +1,152 @@ + $latestMail['id'], + 'subject' => $latestMail['subject'], + 'from_name' => $latestMail['from_name'], + 'from_email' => $latestMail['from_email'], + 'received_human' => $latestMail['received_human'], + 'web_link' => $latestMail['web_link'] ?? '/mail.php', + ]; + } + } + + // 2. Jira Tickets Controle (rekening houdend met persoonlijke toewijzing & default queue filter zoals DX of SD) + $userFilterCode = $user['default_jira_filter_code'] ?? ''; + $jiraData = jira_get_user_issues($userId); + $assignedIssues = $jiraData['assigned_issues'] ?? ($jiraData['issues'] ?? []); + $teamIssues = $jiraData['team_issues'] ?? []; + + $dxValues = ['koppelingen', 'sso-koppeling', 'rest-endpoints', 'datadump', 'uwv-koppeling', 'migratie']; + $relevantIssues = []; + + // Filter issues die specifiek relevant zijn voor deze medewerker + 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); + $ol = strtolower(trim($iss['onderwerp_logging'] ?? '')); + + $isQueueMatch = false; + if (!empty($userFilterCode)) { + if ($userFilterCode === 'DX' && in_array($ol, $dxValues)) { + $isQueueMatch = true; + } elseif ($userFilterCode === 'SD' && ($ol === '' || $ol === 'empty' || !in_array($ol, $dxValues))) { + $isQueueMatch = true; + } + } + + if ($isAssignedToMe || $isQueueMatch) { + $relevantIssues[$iss['key']] = $iss; + } + } + + $hasNewJira = false; + $newJiraPayload = []; + $newestJiraKey = ''; + + if (!empty($relevantIssues)) { + // Pak het nieuwste ticket + $newest = reset($relevantIssues); + $newestJiraKey = $newest['key']; + + if (!empty($clientLastJiraKey) && $newestJiraKey !== $clientLastJiraKey) { + $hasNewJira = true; + $newJiraPayload = [ + 'key' => $newest['key'], + 'summary' => $newest['summary'], + 'priority' => $newest['priority'] ?? 'Medium', + 'issue_type' => $newest['issue_type'] ?? 'Taak', + 'project' => $newest['project'] ?? 'SD', + 'url' => $newest['url'] ?? ('/jira.php?highlight=' . $newest['key']), + ]; + } + } + + // 3. Outlook Agenda Controle (Tijdlijn events van vandaag) + $calendarChanged = false; + $todayEvents = []; + $stmtInt = $db->prepare("SELECT * FROM integrations WHERE user_id = ? AND provider = 'OUTLOOK' AND is_enabled = 1"); + $stmtInt->execute([$userId]); + $outlookInt = $stmtInt->fetch(); + + if ($outlookInt && !empty($outlookInt['access_token']) && empty($outlookInt['is_demo_mode'])) { + $todayEvents = microsoft_fetch_today_events($userId, $today); + } + + $calHash = md5(json_encode(array_map(function($e) { + return ($e['id'] ?? '') . ($e['start'] ?? '') . ($e['end'] ?? '') . ($e['subject'] ?? ''); + }, $todayEvents))); + + if (!empty($clientCalendarHash) && $calHash !== $clientCalendarHash) { + $calendarChanged = true; + } + + // 4. Update sync timestamps in database + $stmtUpdate = $db->prepare("UPDATE users SET last_mail_check_at = NOW(), last_jira_check_at = NOW() WHERE id = ?"); + $stmtUpdate->execute([$userId]); + + echo json_encode([ + 'success' => true, + 'server_time' => date('H:i:s'), + 'mail' => [ + 'has_new' => $hasNewMail, + 'unread_count' => $unreadCount, + 'latest_id' => $latestMail['id'] ?? '', + 'new_mail' => $newMailPayload, + ], + 'jira' => [ + 'has_new' => $hasNewJira, + 'newest_key' => $newestJiraKey, + 'new_issue' => $newJiraPayload, + ], + 'calendar' => [ + 'has_changes' => $calendarChanged, + 'hash' => $calHash, + 'count' => count($todayEvents), + ], + ]); + exit; + +} catch (Throwable $e) { + http_response_code(500); + echo json_encode([ + 'success' => false, + 'error' => $e->getMessage(), + ]); +} + diff --git a/api/mail.php b/api/mail.php new file mode 100644 index 0000000..52c8571 --- /dev/null +++ b/api/mail.php @@ -0,0 +1,188 @@ + false, 'error' => 'Geen bericht-ID opgegeven.']); + exit; + } + + $detail = microsoft_fetch_message_detail($userId, $messageId); + if (!$detail) { + echo json_encode(['success' => false, 'error' => 'Bericht niet gevonden of geen toegang.']); + exit; + } + + echo json_encode(['success' => true, 'message' => $detail]); + exit; + } + + // 3. Markeer als gelezen of ongelezen + if ($action === 'mark_read') { + $input = json_decode(file_get_contents('php://input'), true) ?: $_POST; + $messageId = trim($input['id'] ?? ''); + $isRead = isset($input['is_read']) ? (bool)$input['is_read'] : true; + + if (empty($messageId)) { + echo json_encode(['success' => false, 'error' => 'Geen bericht-ID opgegeven.']); + exit; + } + + $ok = microsoft_mark_message_read($userId, $messageId, $isRead); + echo json_encode(['success' => $ok]); + exit; + } + + // 4. E-mail beantwoorden + if ($action === 'reply') { + $input = json_decode(file_get_contents('php://input'), true) ?: $_POST; + $messageId = trim($input['id'] ?? ''); + $comment = trim($input['comment'] ?? ''); + $replyAll = !empty($input['reply_all']); + + if (empty($messageId) || empty($comment)) { + echo json_encode(['success' => false, 'error' => 'Bericht-ID en reactietekst zijn verplicht.']); + exit; + } + + $res = microsoft_reply_message($userId, $messageId, $comment, $replyAll); + echo json_encode($res); + exit; + } + + // 5. E-mail doorsturen + if ($action === 'forward') { + $input = json_decode(file_get_contents('php://input'), true) ?: $_POST; + $messageId = trim($input['id'] ?? ''); + $toInput = $input['to'] ?? ''; + $comment = trim($input['comment'] ?? ''); + + if (empty($messageId) || empty($toInput)) { + echo json_encode(['success' => false, 'error' => 'Bericht-ID en minimaal één geadresseerde zijn verplicht.']); + exit; + } + + $recipients = []; + if (is_array($toInput)) { + $recipients = $toInput; + } else { + $parts = explode(',', (string)$toInput); + foreach ($parts as $p) { + $p = trim($p); + if (filter_var($p, FILTER_VALIDATE_EMAIL)) { + $recipients[] = ['address' => $p, 'name' => $p]; + } + } + } + + if (empty($recipients)) { + echo json_encode(['success' => false, 'error' => 'Geen geldige e-mailadressen ingevoerd.']); + exit; + } + + $res = microsoft_forward_message($userId, $messageId, $recipients, $comment); + echo json_encode($res); + exit; + } + + // 6. E-mail direct inplannen in Fociz dagplanning + if ($action === 'plan_email') { + $input = json_decode(file_get_contents('php://input'), true) ?: $_POST; + $messageId = trim($input['message_id'] ?? ''); + $subject = trim($input['subject'] ?? 'E-mail actiepunt'); + $planDate = trim($input['plan_date'] ?? date('Y-m-d')); + $timeBlockStart = !empty($input['time_block_start']) ? trim($input['time_block_start']) : null; + $durationMinutes = (int)($input['duration_minutes'] ?? 30); + $priority = in_array($input['priority'] ?? '', ['P1', 'P2', 'P3']) ? $input['priority'] : 'P2'; + $webLink = trim($input['web_link'] ?? 'https://outlook.office.com/mail/'); + $sender = trim($input['sender'] ?? ''); + + if (empty($subject)) { + echo json_encode(['success' => false, 'error' => 'Onderwerp is verplicht.']); + exit; + } + + if ($durationMinutes <= 0) $durationMinutes = 30; + + $timeBlockEnd = null; + if (!empty($timeBlockStart)) { + try { + $dtEnd = new DateTime("{$planDate} {$timeBlockStart}:00", new DateTimeZone('Europe/Amsterdam')); + $dtEnd->modify("+{$durationMinutes} minutes"); + $timeBlockEnd = $dtEnd->format('H:i'); + } catch (Throwable $t) {} + } + + // Zorg dat daily_plan bestaat + $stmtPlan = $db->prepare(" + INSERT INTO daily_plans (user_id, plan_date) + VALUES (?, ?) + ON DUPLICATE KEY UPDATE id = LAST_INSERT_ID(id) + "); + $stmtPlan->execute([$userId, $planDate]); + $planId = (int)$db->lastInsertId(); + + $taskTitle = "✉️ " . $subject; + if (!empty($sender)) { + $taskTitle .= " (van " . $sender . ")"; + } + + $stmtTask = $db->prepare(" + INSERT INTO tasks (user_id, daily_plan_id, title, priority, time_block_start, time_block_end, duration_minutes, source, external_id, external_url) + VALUES (?, ?, ?, ?, ?, ?, ?, 'OUTLOOK_MAIL', ?, ?) + "); + $stmtTask->execute([ + $userId, + $planId, + $taskTitle, + $priority, + $timeBlockStart, + $timeBlockEnd, + $durationMinutes, + $messageId, + $webLink + ]); + $taskId = (int)$db->lastInsertId(); + + echo json_encode([ + 'success' => true, + 'task_id' => $taskId, + 'plan_date' => $planDate, + 'message' => "E-mail succesvol ingepland voor {$planDate}." + ]); + exit; + } + + echo json_encode(['success' => false, 'error' => 'Onbekende e-mailactie.']); +} catch (Throwable $e) { + http_response_code(500); + echo json_encode(['success' => false, 'error' => $e->getMessage()]); +} + diff --git a/api/webhooks/jira.php b/api/webhooks/jira.php new file mode 100644 index 0000000..ad3a800 --- /dev/null +++ b/api/webhooks/jira.php @@ -0,0 +1,40 @@ + 'Invalid JSON payload']); + exit; +} + +try { + $db = get_db_connection(); + $event = $data['webhookEvent'] ?? 'jira:unknown'; + $issueKey = $data['issue']['key'] ?? ''; + $summary = $data['issue']['fields']['summary'] ?? ''; + + // Sla event tijdelijk op of invalidate session cache + error_log("Jira Webhook ontvangen: {$event} voor {$issueKey} - {$summary}"); + + echo json_encode([ + 'received' => true, + 'event' => $event, + 'issue_key' => $issueKey, + 'timestamp' => date('Y-m-d H:i:s') + ]); + exit; +} catch (Throwable $t) { + http_response_code(500); + echo json_encode(['error' => $t->getMessage()]); +} + diff --git a/api/webhooks/microsoft.php b/api/webhooks/microsoft.php new file mode 100644 index 0000000..96b6d8b --- /dev/null +++ b/api/webhooks/microsoft.php @@ -0,0 +1,31 @@ + true]); + diff --git a/mail.php b/mail.php new file mode 100644 index 0000000..ddaa330 --- /dev/null +++ b/mail.php @@ -0,0 +1,726 @@ +prepare("SELECT * FROM integrations WHERE user_id = ? AND provider = 'OUTLOOK' AND is_enabled = 1"); +$stmtInt->execute([$userId]); +$outlookInt = $stmtInt->fetch(); +$isLiveOutlook = !empty($outlookInt['access_token']) && empty($outlookInt['is_demo_mode']); + +$jsonMessages = json_encode($messages, JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_HEX_AMP); +?> + +
+ Bekijk binnenkomende e-mails, beantwoord direct en plan belangrijke berichten in één klik in op jouw tijdlijn. +
++ Selecteer een bericht aan de linkerkant om de volledige inhoud te lezen, te beantwoorden of in te plannen in je dagagenda. +
+