Schone start na index corruptie

This commit is contained in:
2026-09-09 14:31:23 +02:00
commit a5324548ec
41 changed files with 9351 additions and 0 deletions
+212
View File
@@ -0,0 +1,212 @@
<?php
/**
* AJAX API voor AI Co-Pilot (Release v1.5)
*/
require_once __DIR__ . '/../includes/auth.php';
require_once __DIR__ . '/../includes/ai.php';
require_once __DIR__ . '/../includes/settings.php';
$user = require_auth();
$db = get_db_connection();
header('Content-Type: application/json');
$action = $_GET['action'] ?? ($_POST['action'] ?? '');
try {
// 1. Auto-categoriseer kaartjes in een brainstormsessie
if ($action === 'categorize_cards') {
$data = json_decode(file_get_contents('php://input'), true);
$sessionId = (int)($data['session_id'] ?? 0);
if (!$sessionId) {
echo json_encode(['error' => 'Sessie ID ontbreekt']);
exit;
}
$stmtSession = $db->prepare("SELECT * FROM brainstorm_sessions WHERE id = ?");
$stmtSession->execute([$sessionId]);
$session = $stmtSession->fetch();
if (!$session) {
echo json_encode(['error' => 'Sessie niet gevonden']);
exit;
}
$stmtCards = $db->prepare("SELECT id, content, category FROM brainstorm_cards WHERE session_id = ?");
$stmtCards->execute([$sessionId]);
$cards = $stmtCards->fetchAll();
if (empty($cards)) {
echo json_encode(['error' => 'Er zijn nog geen kaartjes op het canvas om te categoriseren']);
exit;
}
$aiRes = fociz_ai_categorize_cards($session['title'], $session['theme'] ?? 'Algemeen', $cards);
if (!$aiRes['success']) {
echo json_encode(['error' => $aiRes['error']]);
exit;
}
$validCategories = ['Ideeën', 'Knelpunten', 'Oplossingen', 'Quick Wins'];
$updateStmt = $db->prepare("UPDATE brainstorm_cards SET category = ? WHERE id = ? AND session_id = ?");
$updatedCount = 0;
foreach ($aiRes['categorized'] as $item) {
$cardId = (int)($item['id'] ?? 0);
$newCat = trim($item['category'] ?? '');
if ($cardId > 0 && in_array($newCat, $validCategories)) {
$updateStmt->execute([$newCat, $cardId, $sessionId]);
$updatedCount++;
}
}
echo json_encode(['success' => true, 'updated' => $updatedCount]);
exit;
}
// 2. Automatisch ideeën genereren voor een sessie
if ($action === 'generate_ideas') {
$data = json_decode(file_get_contents('php://input'), true);
$sessionId = (int)($data['session_id'] ?? 0);
$count = min(6, max(2, (int)($data['count'] ?? 4)));
if (!$sessionId) {
echo json_encode(['error' => 'Sessie ID ontbreekt']);
exit;
}
$stmtSession = $db->prepare("SELECT * FROM brainstorm_sessions WHERE id = ?");
$stmtSession->execute([$sessionId]);
$session = $stmtSession->fetch();
if (!$session) {
echo json_encode(['error' => 'Sessie niet gevonden']);
exit;
}
$aiRes = fociz_ai_generate_ideas($session['title'], $session['theme'] ?? 'Algemeen', $count);
if (!$aiRes['success']) {
echo json_encode(['error' => $aiRes['error']]);
exit;
}
$insertStmt = $db->prepare("
INSERT INTO brainstorm_cards (session_id, content, category, color, author_name, votes)
VALUES (?, ?, 'Ideeën', 'amber', ?, 1)
");
$createdCards = [];
$authorName = 'AI Co-Pilot';
foreach ($aiRes['ideas'] as $ideaText) {
$ideaText = trim($ideaText);
if (!empty($ideaText)) {
$insertStmt->execute([$sessionId, $ideaText, $authorName]);
$createdCards[] = [
'id' => (int)$db->lastInsertId(),
'content' => $ideaText,
'category' => 'Ideeën',
'color' => 'amber',
'author_name' => $authorName,
'votes' => 1,
];
}
}
echo json_encode(['success' => true, 'cards' => $createdCards]);
exit;
}
// 3. Brainstorm notitie omzetten naar Roadmap Item
if ($action === 'summarize_to_roadmap') {
$data = json_decode(file_get_contents('php://input'), true);
$content = trim($data['content'] ?? '');
$sessionTitle = trim($data['session_title'] ?? '');
if (empty($content)) {
echo json_encode(['error' => 'Geen inhoud opgegeven om samen te vatten']);
exit;
}
$aiRes = fociz_ai_summarize_to_roadmap($content, $sessionTitle);
if (!$aiRes['success']) {
echo json_encode(['error' => $aiRes['error']]);
exit;
}
echo json_encode(['success' => true, 'item' => $aiRes['item']]);
exit;
}
// 4. Slimme dagindeling berekenen voor taken rondom Outlook afspraken
if ($action === 'smart_schedule') {
$data = json_decode(file_get_contents('php://input'), true);
$date = !empty($data['date']) ? trim($data['date']) : date('Y-m-d');
// Haal daily plan op
$stmtPlan = $db->prepare("SELECT id FROM daily_plans WHERE user_id = ? AND plan_date = ?");
$stmtPlan->execute([$user['id'], $date]);
$plan = $stmtPlan->fetch();
$planId = $plan['id'] ?? 0;
// Haal openstaande taken op voor de dag (inclusief fallback voor ongebonden taken op de huidige dag)
$isToday = ($date === date('Y-m-d'));
$stmtTasks = $db->prepare("
SELECT t.id, t.title, t.priority, t.duration_minutes
FROM tasks t
WHERE t.user_id = ? AND (t.daily_plan_id = ? " . ($isToday ? "OR t.daily_plan_id IS NULL" : "") . ") AND t.is_completed = 0
ORDER BY t.priority ASC, t.created_at ASC
");
$stmtTasks->execute([$user['id'], $planId]);
$tasks = $stmtTasks->fetchAll();
if (empty($tasks)) {
echo json_encode(['error' => 'Geen openstaande taken gevonden om in te delen']);
exit;
}
require_once __DIR__ . '/../includes/integrations/microsoft.php';
$events = microsoft_fetch_today_events($user['id'], $date);
$workdaySettings = get_workday_settings();
$aiRes = fociz_ai_smart_schedule($tasks, $events, $workdaySettings);
if (!$aiRes['success']) {
echo json_encode(['error' => $aiRes['error']]);
exit;
}
// Werk de tijdsblokken bij in MariaDB
$updateStmt = $db->prepare("
UPDATE tasks
SET time_block_start = ?,
duration_minutes = COALESCE(?, duration_minutes)
WHERE id = ? AND user_id = ?
");
$scheduledCount = 0;
foreach ($aiRes['scheduled'] as $s) {
$taskId = (int)($s['id'] ?? 0);
$startTime = trim($s['time_block_start'] ?? '');
$duration = !empty($s['duration_minutes']) ? (int)$s['duration_minutes'] : null;
if ($taskId > 0 && preg_match('/^\d{1,2}:\d{2}$/', $startTime)) {
$updateStmt->execute([$startTime, $duration, $taskId, $user['id']]);
$scheduledCount++;
}
}
echo json_encode(['success' => true, 'scheduled_count' => $scheduledCount]);
exit;
}
echo json_encode(['error' => 'Onbekende actie']);
} catch (Exception $e) {
http_response_code(500);
echo json_encode(['error' => $e->getMessage()]);
}
+146
View File
@@ -0,0 +1,146 @@
<?php
/**
* AJAX API voor Brainstorm Sessies, Kaarten & Omzetten naar Roadmap
*/
require_once __DIR__ . '/../includes/auth.php';
$user = require_auth();
$db = get_db_connection();
header('Content-Type: application/json');
$action = $_GET['action'] ?? ($_POST['action'] ?? '');
try {
// 1. Brainstormkaart toevoegen
if ($action === 'add_card') {
$data = json_decode(file_get_contents('php://input'), true);
$sessionId = (int)($data['session_id'] ?? 0);
$content = trim($data['content'] ?? '');
$category = $data['category'] ?? 'Ideeën';
$color = $data['color'] ?? 'amber';
if (empty($content)) {
echo json_encode(['error' => 'Inhoud is verplicht']);
exit;
}
$stmt = $db->prepare("
INSERT INTO brainstorm_cards (session_id, content, category, color, author_name, votes)
VALUES (?, ?, ?, ?, ?, 0)
");
$stmt->execute([$sessionId, $content, $category, $color, $user['name']]);
$cardId = (int)$db->lastInsertId();
echo json_encode([
'success' => true,
'card' => [
'id' => $cardId,
'content' => $content,
'category' => $category,
'color' => $color,
'author_name' => $user['name'],
'votes' => 0,
'converted_to_roadmap_item_id' => null
]
]);
exit;
}
// 2. Stemmen / Upvote op kaart
if ($action === 'vote_card') {
$data = json_decode(file_get_contents('php://input'), true);
$cardId = (int)($data['card_id'] ?? 0);
$stmt = $db->prepare("UPDATE brainstorm_cards SET votes = votes + 1 WHERE id = ?");
$stmt->execute([$cardId]);
$stmtVotes = $db->prepare("SELECT votes FROM brainstorm_cards WHERE id = ?");
$stmtVotes->execute([$cardId]);
$votes = (int)$stmtVotes->fetchColumn();
echo json_encode(['success' => true, 'votes' => $votes]);
exit;
}
// 3. Kaart verwijderen
if ($action === 'delete_card') {
$data = json_decode(file_get_contents('php://input'), true);
$cardId = (int)($data['card_id'] ?? 0);
$stmt = $db->prepare("DELETE FROM brainstorm_cards WHERE id = ?");
$stmt->execute([$cardId]);
echo json_encode(['success' => true]);
exit;
}
// 4. Omzetten naar Roadmap!
if ($action === 'convert_to_roadmap') {
$data = json_decode(file_get_contents('php://input'), true);
$cardIds = $data['card_ids'] ?? [];
$quarter = $data['quarter'] ?? 'Q3';
$type = $data['type'] ?? 'FEATURE';
$priority = $data['priority'] ?? 'MEDIUM';
$dueDate = !empty($data['due_date']) ? $data['due_date'] : null;
if (empty($cardIds) || !is_array($cardIds)) {
echo json_encode(['error' => 'Selecteer ten minste één kaart om om te zetten.']);
exit;
}
$db->beginTransaction();
$convertedCount = 0;
foreach ($cardIds as $cardId) {
$cardId = (int)$cardId;
$stmtCard = $db->prepare("SELECT * FROM brainstorm_cards WHERE id = ?");
$stmtCard->execute([$cardId]);
$card = $stmtCard->fetch();
if ($card) {
// Maak titel (eerste zin of max 80 tekens)
$firstLine = explode("\n", trim($card['content']))[0];
$title = mb_substr($firstLine, 0, 80);
$stmtRoadmap = $db->prepare("
INSERT INTO roadmap_items (title, description, type, status, quarter, priority, progress, due_date, origin_brainstorm_card_id, created_by_id)
VALUES (?, ?, ?, 'PLANNED', ?, ?, 0, ?, ?, ?)
");
$stmtRoadmap->execute([
$title,
$card['content'],
$type,
$quarter,
$priority,
$dueDate,
$cardId,
$user['id']
]);
$roadmapId = (int)$db->lastInsertId();
// Koppel terug aan brainstorm kaart
$stmtUpdateCard = $db->prepare("UPDATE brainstorm_cards SET converted_to_roadmap_item_id = ? WHERE id = ?");
$stmtUpdateCard->execute([$roadmapId, $cardId]);
$convertedCount++;
}
}
$db->commit();
echo json_encode([
'success' => true,
'message' => "{$convertedCount} brainstorm items succesvol omgezet naar de Roadmap!",
'converted_count' => $convertedCount
]);
exit;
}
echo json_encode(['error' => 'Onbekende actie']);
} catch (Exception $e) {
if ($db->inTransaction()) $db->rollBack();
http_response_code(500);
echo json_encode(['error' => $e->getMessage()]);
}
+114
View File
@@ -0,0 +1,114 @@
<?php
/**
* AJAX API voor Integraties (Outlook, Teams, Jira)
*/
require_once __DIR__ . '/../includes/auth.php';
require_once __DIR__ . '/../includes/integrations/microsoft.php';
require_once __DIR__ . '/../includes/integrations/jira.php';
$user = require_auth();
$db = get_db_connection();
header('Content-Type: application/json');
$action = $_GET['action'] ?? ($_POST['action'] ?? '');
try {
// 1. Jira Verbinding Testen & Optioneel Opslaan
if ($action === 'test_jira') {
$input = json_decode(file_get_contents('php://input'), true) ?: $_POST;
$domain = trim($input['jira_domain'] ?? '');
$email = trim($input['jira_email'] ?? '');
$apiToken = trim($input['jira_api_token'] ?? '');
$save = !empty($input['save']);
// Als er geen nieuw token is meegegeven, kijk of er een opgeslagen token is
if (empty($apiToken)) {
$stmt = $db->prepare("SELECT settings FROM integrations WHERE user_id = ? AND provider = 'JIRA'");
$stmt->execute([$user['id']]);
$saved = json_decode($stmt->fetchColumn() ?: '{}', true);
$apiToken = $saved['jira_api_token'] ?? '';
if (empty($domain)) $domain = $saved['jira_domain'] ?? '';
if (empty($email)) $email = $saved['jira_email'] ?? '';
}
$res = jira_test_connection($domain, $email, $apiToken);
if ($res['success'] && $save) {
$jiraUser = $res['user'];
$settings = json_encode([
'jira_domain' => jira_normalize_domain($domain),
'jira_email' => $email,
'jira_api_token' => $apiToken,
'account_name' => $jiraUser['displayName'] ?? '',
'account_id' => $jiraUser['accountId'] ?? '',
]);
$stmt = $db->prepare("
INSERT INTO integrations (user_id, provider, is_enabled, is_demo_mode, settings, last_sync_at)
VALUES (?, 'JIRA', 1, 0, ?, NOW())
ON DUPLICATE KEY UPDATE
is_enabled = 1,
is_demo_mode = 0,
settings = VALUES(settings),
last_sync_at = NOW()
");
$stmt->execute([$user['id'], $settings]);
// Wis eventuele oude sessie cache
unset($_SESSION["jira_issues_user_{$user['id']}"]);
}
echo json_encode($res);
exit;
}
// 2. Jira Issues Ophalen (Toegewezen aan de gebruiker)
if ($action === 'get_jira_issues') {
$force = !empty($_GET['force']) || !empty($_POST['force']);
$res = jira_get_user_issues($user['id'], $force);
echo json_encode($res);
exit;
}
// 3. Outlook Agenda Afspraken Ophalen
if ($action === 'get_outlook_events') {
$date = $_GET['date'] ?? date('Y-m-d');
$events = microsoft_fetch_today_events($user['id'], $date);
echo json_encode([
'success' => true,
'date' => $date,
'events' => $events,
'count' => count($events),
]);
exit;
}
// 4. Integratie Ontkoppelen
if ($action === 'disconnect') {
$input = json_decode(file_get_contents('php://input'), true) ?: $_POST;
$provider = strtoupper(trim($input['provider'] ?? ''));
if ($provider === 'OUTLOOK') {
microsoft_disconnect($user['id']);
echo json_encode(['success' => true, 'message' => 'Microsoft Outlook ontkoppeld']);
exit;
}
if ($provider === 'JIRA') {
jira_disconnect($user['id']);
echo json_encode(['success' => true, 'message' => 'Atlassian Jira ontkoppeld']);
exit;
}
echo json_encode(['error' => 'Onbekende provider']);
exit;
}
echo json_encode(['error' => 'Onbekende actie']);
} catch (Exception $e) {
http_response_code(500);
echo json_encode(['error' => $e->getMessage()]);
}
+128
View File
@@ -0,0 +1,128 @@
<?php
/**
* AJAX API voor Persoonlijke Agenda Notificaties
*/
require_once __DIR__ . '/../includes/auth.php';
require_once __DIR__ . '/../includes/integrations/microsoft.php';
$user = require_auth();
$db = get_db_connection();
header('Content-Type: application/json; charset=utf-8');
$action = $_GET['action'] ?? ($_POST['action'] ?? '');
$today = date('Y-m-d');
try {
// 1. Instellingen ophalen
if ($action === 'get_settings') {
$settings = get_user_notification_settings((int)$user['id']);
echo json_encode([
'success' => true,
'settings' => $settings,
]);
exit;
}
// 2. Instellingen opslaan
if ($action === 'save_settings') {
$input = json_decode(file_get_contents('php://input'), true) ?: $_POST;
$saved = update_user_notification_settings((int)$user['id'], $input);
$settings = get_user_notification_settings((int)$user['id']);
echo json_encode([
'success' => $saved,
'settings' => $settings,
'message' => $saved ? 'Notificatievoorkeuren succesvol opgeslagen.' : 'Fout bij opslaan.',
]);
exit;
}
// 3. Actuele agenda-afspraken van vandaag ophalen voor notificatie-evaluatie
if ($action === 'get_events') {
$userId = (int)$user['id'];
$settings = get_user_notification_settings($userId);
// Controleer of Outlook gekoppeld is
$stmtInt = $db->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']);
$events = [];
if ($isLiveOutlook) {
$events = microsoft_fetch_today_events($userId, $today);
}
// Als er geen live Outlook events zijn, haal ook taken met tijdblokken op uit Fociz dagplanning
$stmtTasks = $db->prepare("
SELECT t.id, t.title, t.time_block_start, t.time_block_end, t.source, t.external_url
FROM tasks t
LEFT JOIN daily_plans dp ON t.daily_plan_id = dp.id
WHERE t.user_id = ? AND (dp.plan_date = ? OR t.time_block_start IS NOT NULL) AND t.is_completed = 0
ORDER BY t.time_block_start ASC
");
$stmtTasks->execute([$userId, $today]);
$tasks = $stmtTasks->fetchAll();
foreach ($tasks as $t) {
if (!empty($t['time_block_start'])) {
$events[] = [
'id' => 'task_' . $t['id'],
'subject' => $t['title'],
'start' => $t['time_block_start'],
'end' => $t['time_block_end'] ?: date('H:i', strtotime($t['time_block_start'] . ' +30 minutes')),
'start_raw' => "{$today}T{$t['time_block_start']}:00",
'location' => 'Fociz Dagplanning',
'meeting_url' => $t['external_url'] ?? '/planner.php',
'is_all_day' => false,
'source' => 'FOCIZ_TASK',
];
}
}
// Als er nog steeds geen afspraken zijn (en demo mode actief), lever sandbox items zodat er altijd getest kan worden
if (empty($events) && (!empty($outlookInt['is_demo_mode']) || !$isLiveOutlook)) {
$currentHour = (int)date('H');
$nextHour = str_pad((string)(($currentHour + 1) % 24), 2, '0', STR_PAD_LEFT);
$events = [
[
'id' => 'sandbox_meeting_1',
'subject' => 'Focus Sync & Dagstart',
'start' => "{$nextHour}:00",
'end' => "{$nextHour}:30",
'start_raw' => "{$today}T{$nextHour}:00:00",
'location' => 'Microsoft Teams',
'meeting_url' => 'https://teams.microsoft.com',
'is_all_day' => false,
'source' => 'SANDBOX',
]
];
}
echo json_encode([
'success' => true,
'server_time' => date('H:i:s'),
'server_datetime' => date('Y-m-d H:i:s'),
'server_timestamp'=> time(),
'settings' => $settings,
'events' => $events,
'count' => count($events),
]);
exit;
}
echo json_encode(['error' => 'Ongeldige actie']);
exit;
} catch (Throwable $e) {
http_response_code(500);
echo json_encode([
'success' => false,
'error' => $e->getMessage(),
]);
exit;
}
+260
View File
@@ -0,0 +1,260 @@
<?php
/**
* AJAX API voor Dagplanning & Taken
*/
require_once __DIR__ . '/../includes/auth.php';
require_once __DIR__ . '/../includes/integrations/microsoft.php';
$user = require_auth();
$db = get_db_connection();
header('Content-Type: application/json');
$action = $_GET['action'] ?? ($_POST['action'] ?? '');
$today = date('Y-m-d');
try {
// 1. Dagdoel of notities opslaan
if ($action === 'save_goal') {
$data = json_decode(file_get_contents('php://input'), true);
$date = $data['date'] ?? $today;
$focusGoal = $data['focus_goal'] ?? '';
$notes = $data['notes'] ?? '';
$stmt = $db->prepare("
INSERT INTO daily_plans (user_id, plan_date, focus_goal, notes)
VALUES (?, ?, ?, ?)
ON DUPLICATE KEY UPDATE focus_goal = VALUES(focus_goal), notes = VALUES(notes)
");
$stmt->execute([$user['id'], $date, $focusGoal, $notes]);
echo json_encode(['success' => true]);
exit;
}
// 2. Taak toevoegen
if ($action === 'add_task') {
$data = json_decode(file_get_contents('php://input'), true);
$title = trim($data['title'] ?? '');
$priority = in_array($data['priority'] ?? '', ['P1', 'P2', 'P3']) ? $data['priority'] : 'P2';
$timeBlockStart = !empty($data['time_block_start']) ? trim($data['time_block_start']) : null;
$durationMinutes = (int)($data['duration_minutes'] ?? 30);
if ($durationMinutes <= 0) $durationMinutes = 30;
$date = !empty($data['date']) ? trim($data['date']) : $today;
$syncOutlook = !empty($data['sync_to_outlook']);
if (empty($title)) {
echo json_encode(['error' => 'Titel is verplicht']);
exit;
}
// Haal of maak daily plan op
$stmtPlan = $db->prepare("
INSERT INTO daily_plans (user_id, plan_date)
VALUES (?, ?)
ON DUPLICATE KEY UPDATE id=LAST_INSERT_ID(id)
");
$stmtPlan->execute([$user['id'], $date]);
$planId = $db->lastInsertId();
$source = !empty($data['source']) ? substr(trim($data['source']), 0, 50) : 'FOCIZ';
$externalId = !empty($data['external_id']) ? substr(trim($data['external_id']), 0, 255) : null;
$externalUrl = !empty($data['external_url']) ? trim($data['external_url']) : null;
$timeBlockEnd = null;
$outlookSyncError = null;
// Als synchronisatie naar Outlook gewenst is (of source is OUTLOOK) en nog geen extern ID heeft
if (($syncOutlook || $source === 'OUTLOOK') && empty($externalId)) {
// Als er geen starttijd is ingevuld maar wel sync gewenst is, gebruik 09:00 als veilige standaard
if (empty($timeBlockStart)) {
$timeBlockStart = '09:00';
}
$msResult = microsoft_create_event($user['id'], [
'subject' => $title,
'description' => "Gepland via Fociz Dagplanning ({$priority})",
'date' => $date,
'start_time' => $timeBlockStart,
'duration_minutes' => $durationMinutes,
]);
if (!empty($msResult['success']) && !empty($msResult['event_id'])) {
$source = 'OUTLOOK';
$externalId = $msResult['event_id'];
$externalUrl = $msResult['web_link'] ?? 'https://outlook.office.com';
$timeBlockStart = $msResult['start_time'] ?? $timeBlockStart;
$timeBlockEnd = $msResult['end_time'] ?? null;
} else {
$outlookSyncError = $msResult['error'] ?? 'Kon afspraak niet aanmaken in Microsoft Outlook';
}
}
// Bereken timeBlockEnd indien nog niet gezet maar wel starttijd bekend
if (!empty($timeBlockStart) && empty($timeBlockEnd)) {
try {
$dtEnd = new DateTime("{$date} {$timeBlockStart}:00", new DateTimeZone('Europe/Amsterdam'));
$dtEnd->modify("+{$durationMinutes} minutes");
$timeBlockEnd = $dtEnd->format('H:i');
} catch (Throwable $e) {
// negeer
}
}
$stmt = $db->prepare("
INSERT INTO tasks (user_id, daily_plan_id, title, priority, time_block_start, time_block_end, duration_minutes, status, source, external_id, external_url)
VALUES (?, ?, ?, ?, ?, ?, ?, 'TODO', ?, ?, ?)
");
$stmt->execute([$user['id'], $planId, $title, $priority, $timeBlockStart, $timeBlockEnd, $durationMinutes, $source, $externalId, $externalUrl]);
$taskId = (int)$db->lastInsertId();
echo json_encode([
'success' => true,
'task' => [
'id' => $taskId,
'title' => $title,
'priority' => $priority,
'time_block_start' => $timeBlockStart,
'time_block_end' => $timeBlockEnd,
'duration_minutes' => $durationMinutes,
'is_completed' => 0,
'status' => 'TODO',
'source' => $source,
'external_id' => $externalId,
'external_url' => $externalUrl,
],
'outlook_synced' => ($source === 'OUTLOOK' && !empty($externalId)),
'outlook_error' => $outlookSyncError,
]);
exit;
}
// 3. Bestaande taak handmatig naar Microsoft Outlook synchroniseren
if ($action === 'sync_task_to_outlook') {
$data = json_decode(file_get_contents('php://input'), true);
$taskId = (int)($data['task_id'] ?? 0);
if (!$taskId) {
echo json_encode(['error' => 'Geen taak-ID opgegeven']);
exit;
}
// Haal taak inclusief plan datum op
$stmtTask = $db->prepare("
SELECT t.*, dp.plan_date
FROM tasks t
LEFT JOIN daily_plans dp ON t.daily_plan_id = dp.id
WHERE t.id = ? AND t.user_id = ?
");
$stmtTask->execute([$taskId, $user['id']]);
$task = $stmtTask->fetch();
if (!$task) {
echo json_encode(['error' => 'Taak niet gevonden']);
exit;
}
// Als al gesynchroniseerd
if (!empty($task['external_id']) && $task['source'] === 'OUTLOOK') {
echo json_encode([
'success' => true,
'already_synced' => true,
'external_id' => $task['external_id'],
'external_url' => $task['external_url']
]);
exit;
}
$planDate = $task['plan_date'] ?: $today;
$startTime = !empty($task['time_block_start']) ? $task['time_block_start'] : ($data['time_block_start'] ?? '09:00');
$duration = (int)($task['duration_minutes'] ?: 30);
$msResult = microsoft_create_event($user['id'], [
'subject' => $task['title'],
'description' => "Gepland via Fociz Dagplanning (" . $task['priority'] . ")",
'date' => $planDate,
'start_time' => $startTime,
'duration_minutes' => $duration,
]);
if (empty($msResult['success']) || empty($msResult['event_id'])) {
echo json_encode(['error' => $msResult['error'] ?? 'Kon afspraak niet aanmaken in Microsoft Outlook']);
exit;
}
// Sla Outlook ID en bron op in de taak
$stmtUp = $db->prepare("
UPDATE tasks
SET source = 'OUTLOOK',
external_id = ?,
external_url = ?,
time_block_start = COALESCE(time_block_start, ?),
time_block_end = ?
WHERE id = ? AND user_id = ?
");
$stmtUp->execute([
$msResult['event_id'],
$msResult['web_link'] ?? 'https://outlook.office.com',
$msResult['start_time'],
$msResult['end_time'],
$taskId,
$user['id']
]);
echo json_encode([
'success' => true,
'external_id' => $msResult['event_id'],
'external_url' => $msResult['web_link'] ?? 'https://outlook.office.com',
'start_time' => $msResult['start_time'],
'end_time' => $msResult['end_time'],
]);
exit;
}
// 4. Taak status toggelen (voltooid / open)
if ($action === 'toggle_task') {
$data = json_decode(file_get_contents('php://input'), true);
$taskId = (int)($data['task_id'] ?? 0);
$isCompleted = !empty($data['is_completed']) ? 1 : 0;
$status = $isCompleted ? 'DONE' : 'TODO';
$stmt = $db->prepare("
UPDATE tasks SET is_completed = ?, status = ?
WHERE id = ? AND user_id = ?
");
$stmt->execute([$isCompleted, $status, $taskId, $user['id']]);
echo json_encode(['success' => true]);
exit;
}
// 5. Taak verwijderen (verwijder ook eventueel gekoppelde Outlook afspraak)
if ($action === 'delete_task') {
$data = json_decode(file_get_contents('php://input'), true);
$taskId = (int)($data['task_id'] ?? 0);
// Haal eerst op of deze taak aan Outlook was gekoppeld
$stmtCheck = $db->prepare("SELECT external_id, source FROM tasks WHERE id = ? AND user_id = ?");
$stmtCheck->execute([$taskId, $user['id']]);
$taskToDelete = $stmtCheck->fetch();
if ($taskToDelete && !empty($taskToDelete['external_id']) && $taskToDelete['source'] === 'OUTLOOK') {
try {
microsoft_delete_event($user['id'], $taskToDelete['external_id']);
} catch (Throwable $t) {
error_log("Fout bij verwijderen Outlook afspraak voor taak {$taskId}: " . $t->getMessage());
}
}
$stmt = $db->prepare("DELETE FROM tasks WHERE id = ? AND user_id = ?");
$stmt->execute([$taskId, $user['id']]);
echo json_encode(['success' => true]);
exit;
}
echo json_encode(['error' => 'Onbekende actie']);
} catch (Exception $e) {
http_response_code(500);
echo json_encode(['error' => $e->getMessage()]);
}
+93
View File
@@ -0,0 +1,93 @@
<?php
/**
* AJAX API voor Roadmap Beheer
*/
require_once __DIR__ . '/../includes/auth.php';
$user = require_auth();
$db = get_db_connection();
header('Content-Type: application/json');
$action = $_GET['action'] ?? ($_POST['action'] ?? '');
try {
// 1. Roadmap item toevoegen
if ($action === 'add_item') {
$data = json_decode(file_get_contents('php://input'), true);
$title = trim($data['title'] ?? '');
$description = trim($data['description'] ?? '');
$quarter = $data['quarter'] ?? 'Q3';
$type = $data['type'] ?? 'FEATURE';
$priority = $data['priority'] ?? 'MEDIUM';
$dueDate = !empty($data['due_date']) ? $data['due_date'] : null;
$jiraKey = !empty($data['jira_key']) ? trim($data['jira_key']) : null;
if (empty($title)) {
echo json_encode(['error' => 'Titel is verplicht']);
exit;
}
$stmt = $db->prepare("
INSERT INTO roadmap_items (title, description, quarter, type, priority, status, progress, due_date, jira_issue_key, created_by_id)
VALUES (?, ?, ?, ?, ?, 'PLANNED', 0, ?, ?, ?)
");
$stmt->execute([$title, $description, $quarter, $type, $priority, $dueDate, $jiraKey, $user['id']]);
echo json_encode(['success' => true]);
exit;
}
// 2. Status of voortgang bijwerken
if ($action === 'update_item') {
$data = json_decode(file_get_contents('php://input'), true);
$id = (int)($data['id'] ?? 0);
$status = $data['status'] ?? null;
$progress = isset($data['progress']) ? (int)$data['progress'] : null;
$jiraKey = isset($data['jira_key']) ? trim($data['jira_key']) : null;
$fields = [];
$params = [];
if ($status !== null) {
$fields[] = "status = ?";
$params[] = $status;
}
if ($progress !== null) {
$fields[] = "progress = ?";
$params[] = max(0, min(100, $progress));
}
if ($jiraKey !== null) {
$fields[] = "jira_issue_key = ?";
$params[] = $jiraKey;
}
if (!empty($fields)) {
$params[] = $id;
$sql = "UPDATE roadmap_items SET " . implode(", ", $fields) . " WHERE id = ?";
$stmt = $db->prepare($sql);
$stmt->execute($params);
}
echo json_encode(['success' => true]);
exit;
}
// 3. Item verwijderen
if ($action === 'delete_item') {
$data = json_decode(file_get_contents('php://input'), true);
$id = (int)($data['id'] ?? 0);
$stmt = $db->prepare("DELETE FROM roadmap_items WHERE id = ?");
$stmt->execute([$id]);
echo json_encode(['success' => true]);
exit;
}
echo json_encode(['error' => 'Onbekende actie']);
} catch (Exception $e) {
http_response_code(500);
echo json_encode(['error' => $e->getMessage()]);
}
+48
View File
@@ -0,0 +1,48 @@
<?php
/**
* Fociz Thema API Endpoint (Dark / Light / System)
*/
header('Content-Type: application/json; charset=utf-8');
require_once __DIR__ . '/../includes/auth.php';
$user = require_auth(true);
$userId = (int)$user['id'];
if ($_SERVER['REQUEST_METHOD'] === 'GET') {
$currentTheme = get_user_theme($userId);
echo json_encode([
'success' => true,
'theme' => $currentTheme,
]);
exit;
}
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$raw = file_get_contents('php://input');
$data = json_decode($raw, true);
$theme = $data['theme'] ?? ($_POST['theme'] ?? 'dark');
if (!in_array($theme, ['dark', 'light', 'system'])) {
http_response_code(400);
echo json_encode(['success' => false, 'error' => 'Ongeldig thema. Kies dark, light of system.']);
exit;
}
$ok = update_user_theme($userId, $theme);
if ($ok) {
echo json_encode([
'success' => true,
'theme' => $theme,
'message' => 'Themavoorkeur succesvol opgeslagen.',
]);
} else {
http_response_code(500);
echo json_encode(['success' => false, 'error' => 'Kon thema niet opslaan in database.']);
}
exit;
}
http_response_code(405);
echo json_encode(['success' => false, 'error' => 'Method not allowed']);