Files
2026-09-14 09:21:19 +02:00

612 lines
26 KiB
PHP

<?php
/**
* Fociz Kanban / Trello Bord API
* Beheert borden, kolommen/lanes, kaarten, toewijzingen, tijdspaden & Outlook 365 integratie
*/
require_once __DIR__ . '/../includes/auth.php';
require_once __DIR__ . '/../includes/integrations/microsoft.php';
header('Content-Type: application/json; charset=utf-8');
$currentUser = require_auth(true);
$db = get_db_connection();
ensure_tables_exist($db);
$userId = (int)($currentUser['id'] ?? 0);
$userRole = $currentUser['role'] ?? 'MEMBER';
$isAdmin = ($userRole === 'ADMIN');
$action = $_GET['action'] ?? ($_POST['action'] ?? '');
$input = json_decode(file_get_contents('php://input'), true) ?: $_POST;
/**
* Controleert of de gebruiker toegang heeft tot het bord
*/
function check_board_access(PDO $db, int $boardId, int $userId, bool $isAdmin): bool {
if ($isAdmin) return true;
$stmt = $db->prepare("SELECT COUNT(*) FROM board_members WHERE board_id = ? AND user_id = ?");
$stmt->execute([$boardId, $userId]);
return ((int)$stmt->fetchColumn()) > 0;
}
/**
* Controleert of de gebruiker beheerder is van het bord
*/
function check_board_admin(PDO $db, int $boardId, int $userId, bool $isAdmin): bool {
if ($isAdmin) return true;
$stmt = $db->prepare("SELECT role FROM board_members WHERE board_id = ? AND user_id = ?");
$stmt->execute([$boardId, $userId]);
$role = $stmt->fetchColumn();
return ($role === 'ADMIN');
}
try {
// 1. Alle beschikbare borden ophalen voor deze gebruiker
if ($action === 'get_boards') {
if ($isAdmin) {
$stmt = $db->query("
SELECT b.*, u.name as creator_name,
(SELECT COUNT(*) FROM board_cards WHERE board_id = b.id) as card_count
FROM boards b
LEFT JOIN users u ON b.created_by_id = u.id
WHERE b.is_archived = 0
ORDER BY b.id ASC
");
$boards = $stmt->fetchAll();
} else {
$stmt = $db->prepare("
SELECT b.*, u.name as creator_name,
(SELECT COUNT(*) FROM board_cards WHERE board_id = b.id) as card_count
FROM boards b
JOIN board_members bm ON b.id = bm.board_id
LEFT JOIN users u ON b.created_by_id = u.id
WHERE bm.user_id = ? AND b.is_archived = 0
ORDER BY b.id ASC
");
$stmt->execute([$userId]);
$boards = $stmt->fetchAll();
}
echo json_encode([
'success' => true,
'boards' => $boards,
'is_admin' => $isAdmin
]);
exit;
}
// 2. Volledige bord-data ophalen (lanes, kaarten, toewijzingen, autorisaties)
if ($action === 'get_board_data') {
$boardId = (int)($input['board_id'] ?? ($_GET['board_id'] ?? 0));
// Als geen board_id is meegegeven, pak het eerste beschikbare bord
if ($boardId <= 0) {
if ($isAdmin) {
$boardId = (int)$db->query("SELECT id FROM boards WHERE is_archived = 0 ORDER BY id ASC LIMIT 1")->fetchColumn();
} else {
$stmtFirst = $db->prepare("SELECT b.id FROM boards b JOIN board_members bm ON b.id = bm.board_id WHERE bm.user_id = ? AND b.is_archived = 0 ORDER BY b.id ASC LIMIT 1");
$stmtFirst->execute([$userId]);
$boardId = (int)$stmtFirst->fetchColumn();
}
}
if ($boardId <= 0) {
echo json_encode([
'success' => true,
'has_board' => false,
'message' => 'Geen actieve borden gevonden.'
]);
exit;
}
if (!check_board_access($db, $boardId, $userId, $isAdmin)) {
http_response_code(403);
echo json_encode(['success' => false, 'error' => 'Geen toegang tot dit bord. Vraag de beheerder om autorisatie.']);
exit;
}
// Haal bord-informatie op
$stmtB = $db->prepare("SELECT b.*, u.name as creator_name FROM boards b LEFT JOIN users u ON b.created_by_id = u.id WHERE b.id = ?");
$stmtB->execute([$boardId]);
$board = $stmtB->fetch();
// Haal lanes op
$stmtL = $db->prepare("SELECT * FROM board_lanes WHERE board_id = ? ORDER BY position ASC, id ASC");
$stmtL->execute([$boardId]);
$lanes = $stmtL->fetchAll();
// Haal kaarten op met assignee en schedule
$stmtC = $db->prepare("
SELECT c.*,
u.name as assignee_name, u.email as assignee_email, u.avatar_url as assignee_avatar,
s.id as schedule_id, s.schedule_type, s.start_date, s.end_date, s.start_time, s.end_time, s.days_of_week, s.outlook_event_id, s.outlook_synced_at
FROM board_cards c
LEFT JOIN users u ON c.assignee_id = u.id
LEFT JOIN board_card_schedules s ON c.id = s.card_id
WHERE c.board_id = ?
ORDER BY c.position ASC, c.id DESC
");
$stmtC->execute([$boardId]);
$cards = $stmtC->fetchAll();
// Groepeer kaarten per lane
$cardsByLane = [];
foreach ($lanes as $lane) {
$cardsByLane[$lane['id']] = [];
}
foreach ($cards as $card) {
$lId = $card['lane_id'];
if (!isset($cardsByLane[$lId])) {
$cardsByLane[$lId] = [];
}
$cardsByLane[$lId][] = $card;
}
// Haal teamleden en Outlook integratie status op (voor toewijzing)
$usersStmt = $db->query("SELECT id, name, email, role, avatar_url FROM users ORDER BY name ASC");
$teamMembers = $usersStmt->fetchAll();
// Check welke collega's een actieve Outlook koppeling hebben
$outlookActiveUsers = [];
$intStmt = $db->query("SELECT user_id FROM integrations WHERE provider = 'OUTLOOK' AND is_enabled = 1 AND access_token IS NOT NULL");
foreach ($intStmt->fetchAll() as $row) {
$outlookActiveUsers[(int)$row['user_id']] = true;
}
foreach ($teamMembers as &$tm) {
$tm['has_outlook'] = !empty($outlookActiveUsers[(int)$tm['id']]);
}
unset($tm);
// Haal geautoriseerde leden van dit bord op
$membersStmt = $db->prepare("SELECT user_id, role FROM board_members WHERE board_id = ?");
$membersStmt->execute([$boardId]);
$authorizedMembers = [];
foreach ($membersStmt->fetchAll() as $m) {
$authorizedMembers[(int)$m['user_id']] = $m['role'];
}
echo json_encode([
'success' => true,
'has_board' => true,
'board' => $board,
'lanes' => $lanes,
'cards_by_lane' => $cardsByLane,
'team_members' => $teamMembers,
'authorized_members' => $authorizedMembers,
'is_board_admin' => check_board_admin($db, $boardId, $userId, $isAdmin),
'current_user_id' => $userId,
]);
exit;
}
// 3. Nieuw bord aanmaken (Beheerder)
if ($action === 'create_board') {
if (!$isAdmin) {
http_response_code(403);
echo json_encode(['success' => false, 'error' => 'Alleen beheerders kunnen nieuwe borden aanmaken.']);
exit;
}
$title = trim($input['title'] ?? '');
$description = trim($input['description'] ?? '');
if (empty($title)) {
echo json_encode(['success' => false, 'error' => 'Vul een bordtitel in.']);
exit;
}
$stmt = $db->prepare("INSERT INTO boards (title, description, created_by_id) VALUES (?, ?, ?)");
$stmt->execute([$title, $description, $userId]);
$boardId = (int)$db->lastInsertId();
// Voeg maker toe als beheerder van het bord
$db->prepare("INSERT INTO board_members (board_id, user_id, role) VALUES (?, ?, 'ADMIN')")
->execute([$boardId, $userId]);
// Standaard lanes aanmaken
$defaultLanes = [
['title' => 'Open', 'color' => 'slate', 'pos' => 0],
['title' => 'In uitvoering', 'color' => 'amber', 'pos' => 1],
['title' => 'Testen', 'color' => 'purple', 'pos' => 2],
['title' => 'Klaar', 'color' => 'emerald', 'pos' => 3],
];
$laneStmt = $db->prepare("INSERT INTO board_lanes (board_id, title, color, position) VALUES (?, ?, ?, ?)");
foreach ($defaultLanes as $l) {
$laneStmt->execute([$boardId, $l['title'], $l['color'], $l['pos']]);
}
echo json_encode([
'success' => true,
'board_id' => $boardId,
'message' => 'Bord succesvol aangemaakt!'
]);
exit;
}
// 4. Bord autorisaties / leden beheren (Beheerder)
if ($action === 'manage_members') {
$boardId = (int)($input['board_id'] ?? 0);
if (!check_board_admin($db, $boardId, $userId, $isAdmin)) {
http_response_code(403);
echo json_encode(['success' => false, 'error' => 'Geen beheerrechten voor dit bord.']);
exit;
}
$memberIds = $input['member_ids'] ?? [];
if (!is_array($memberIds)) $memberIds = [];
// Huidige leden bijwerken
$db->prepare("DELETE FROM board_members WHERE board_id = ?")->execute([$boardId]);
$insertStmt = $db->prepare("INSERT INTO board_members (board_id, user_id, role) VALUES (?, ?, 'MEMBER')");
foreach ($memberIds as $mId) {
$mId = (int)$mId;
if ($mId > 0) {
$insertStmt->execute([$boardId, $mId]);
}
}
// Zorg dat beheerder altijd lid blijft
$db->prepare("INSERT IGNORE INTO board_members (board_id, user_id, role) VALUES (?, ?, 'ADMIN')")->execute([$boardId, $userId]);
echo json_encode(['success' => true, 'message' => 'Toegangsrechten voor het bord succesvol bijgewerkt.']);
exit;
}
// 5. Kolom / Lane toevoegen of bewerken
if ($action === 'save_lane') {
$boardId = (int)($input['board_id'] ?? 0);
$laneId = (int)($input['lane_id'] ?? 0);
$title = trim($input['title'] ?? '');
$color = trim($input['color'] ?? 'slate');
if (!check_board_admin($db, $boardId, $userId, $isAdmin)) {
http_response_code(403);
echo json_encode(['success' => false, 'error' => 'Alleen bordbeheerders kunnen kolommen beheren.']);
exit;
}
if (empty($title)) {
echo json_encode(['success' => false, 'error' => 'Vul een kolomnaam in.']);
exit;
}
if ($laneId > 0) {
$stmt = $db->prepare("UPDATE board_lanes SET title = ?, color = ? WHERE id = ? AND board_id = ?");
$stmt->execute([$title, $color, $laneId, $boardId]);
} else {
$stmtMax = $db->prepare("SELECT COALESCE(MAX(position), -1) FROM board_lanes WHERE board_id = ?");
$stmtMax->execute([$boardId]);
$maxPos = (int)$stmtMax->fetchColumn();
$stmt = $db->prepare("INSERT INTO board_lanes (board_id, title, color, position) VALUES (?, ?, ?, ?)");
$stmt->execute([$boardId, $title, $color, $maxPos + 1]);
$laneId = (int)$db->lastInsertId();
}
echo json_encode(['success' => true, 'lane_id' => $laneId, 'message' => 'Kolom opgeslagen.']);
exit;
}
// 6. Kolom / Lane verwijderen
if ($action === 'delete_lane') {
$boardId = (int)($input['board_id'] ?? 0);
$laneId = (int)($input['lane_id'] ?? 0);
if (!check_board_admin($db, $boardId, $userId, $isAdmin)) {
http_response_code(403);
echo json_encode(['success' => false, 'error' => 'Geen beheerrechten.']);
exit;
}
// Controleer of er kaarten in staan
$stmtCardCnt = $db->prepare("SELECT COUNT(*) FROM board_cards WHERE lane_id = ?");
$stmtCardCnt->execute([$laneId]);
$cardCount = (int)$stmtCardCnt->fetchColumn();
if ($cardCount > 0) {
echo json_encode(['success' => false, 'error' => 'Verplaats of verwijder eerst de taken in deze kolom alvorens de kolom te verwijderen.']);
exit;
}
$db->prepare("DELETE FROM board_lanes WHERE id = ? AND board_id = ?")->execute([$laneId, $boardId]);
echo json_encode(['success' => true, 'message' => 'Kolom verwijderd.']);
exit;
}
// 7. Kaart (Post-it / Opdracht) aanmaken of bewerken
if ($action === 'save_card') {
$boardId = (int)($input['board_id'] ?? 0);
$cardId = (int)($input['card_id'] ?? 0);
$laneId = (int)($input['lane_id'] ?? 0);
$title = trim($input['title'] ?? '');
$description = trim($input['description'] ?? '');
$assigneeId = !empty($input['assignee_id']) ? (int)$input['assignee_id'] : null;
$priority = in_array($input['priority'] ?? '', ['P1', 'P2', 'P3']) ? $input['priority'] : 'P2';
$color = in_array($input['color'] ?? '', ['yellow', 'blue', 'green', 'purple', 'pink', 'orange', 'slate']) ? $input['color'] : 'yellow';
if (!check_board_access($db, $boardId, $userId, $isAdmin)) {
http_response_code(403);
echo json_encode(['success' => false, 'error' => 'Geen toegang tot dit bord.']);
exit;
}
if (empty($title)) {
echo json_encode(['success' => false, 'error' => 'Vul een titel in voor de taak.']);
exit;
}
if ($cardId > 0) {
$stmt = $db->prepare("
UPDATE board_cards
SET title = ?, description = ?, assignee_id = ?, priority = ?, color = ?, lane_id = ?
WHERE id = ? AND board_id = ?
");
$stmt->execute([$title, $description, $assigneeId, $priority, $color, $laneId, $cardId, $boardId]);
} else {
$stmtMax = $db->prepare("SELECT COALESCE(MAX(position), -1) FROM board_cards WHERE lane_id = ?");
$stmtMax->execute([$laneId]);
$maxPos = (int)$stmtMax->fetchColumn();
$stmt = $db->prepare("
INSERT INTO board_cards (board_id, lane_id, title, description, assignee_id, priority, color, position, created_by_id)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
");
$stmt->execute([$boardId, $laneId, $title, $description, $assigneeId, $priority, $color, $maxPos + 1, $userId]);
$cardId = (int)$db->lastInsertId();
}
// Tijdspad opslaan en optioneel Outlook agenda synchroniseren indien meegegeven
$scheduleData = $input['schedule'] ?? null;
$outlookSyncMsg = null;
if (!empty($scheduleData) && is_array($scheduleData) && !empty($scheduleData['enabled'])) {
$scheduleType = in_array($scheduleData['type'] ?? '', ['SINGLE', 'DAILY_RANGE']) ? $scheduleData['type'] : 'SINGLE';
$startDate = trim($scheduleData['start_date'] ?? date('Y-m-d'));
$endDate = ($scheduleType === 'DAILY_RANGE' && !empty($scheduleData['end_date'])) ? trim($scheduleData['end_date']) : $startDate;
$startTime = trim($scheduleData['start_time'] ?? '14:00');
$endTime = trim($scheduleData['end_time'] ?? '15:00');
$daysOfWeek = trim($scheduleData['days_of_week'] ?? '1,2,3,4,5');
$syncOutlook = !empty($scheduleData['sync_outlook']);
$outlookEventId = null;
$outlookSyncedAt = null;
// Als Outlook sync gevraagd is én er een assignee is:
if ($syncOutlook && $assigneeId > 0) {
// Controleer of de assignee Outlook gekoppeld heeft
$stmtCheckInt = $db->prepare("SELECT * FROM integrations WHERE user_id = ? AND provider = 'OUTLOOK' AND is_enabled = 1");
$stmtCheckInt->execute([$assigneeId]);
$outlookInt = $stmtCheckInt->fetch();
if (!empty($outlookInt['access_token'])) {
$startTs = strtotime("{$startDate} {$startTime}");
$endTs = strtotime("{$startDate} {$endTime}");
$durationMins = max(15, (int)(($endTs - $startTs) / 60));
// Maak agenda-afspraak aan in Outlook
$eventPayload = [
'subject' => "[Fociz Bord] {$title}",
'description' => "Opdracht toegewezen via Fociz Bord:\n\n{$description}\n\nTijdspad: {$startTime} - {$endTime}",
'date' => $startDate,
'start_time' => $startTime,
'end_time' => $endTime,
'duration_minutes' => $durationMins,
];
$resCreate = microsoft_create_event($assigneeId, $eventPayload);
if (!empty($resCreate['success'])) {
$outlookEventId = $resCreate['event_id'] ?? ($resCreate['id'] ?? null);
$outlookSyncedAt = date('Y-m-d H:i:s');
$outlookSyncMsg = 'Taak succesvol ingepland in de Outlook agenda van de collega!';
} else {
$outlookSyncMsg = 'Taak opgeslagen, maar Outlook-sync mislukt: ' . ($resCreate['error'] ?? 'onbekende fout');
}
} else {
$outlookSyncMsg = 'Let op: De toegewezen collega heeft Microsoft Outlook 365 nog niet gekoppeld.';
}
}
// Opslaan in board_card_schedules
$stmtSched = $db->prepare("
INSERT INTO board_card_schedules (card_id, schedule_type, start_date, end_date, start_time, end_time, days_of_week, outlook_event_id, outlook_synced_at)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
ON DUPLICATE KEY UPDATE
schedule_type = VALUES(schedule_type),
start_date = VALUES(start_date),
end_date = VALUES(end_date),
start_time = VALUES(start_time),
end_time = VALUES(end_time),
days_of_week = VALUES(days_of_week),
outlook_event_id = COALESCE(VALUES(outlook_event_id), outlook_event_id),
outlook_synced_at = COALESCE(VALUES(outlook_synced_at), outlook_synced_at)
");
$stmtSched->execute([$cardId, $scheduleType, $startDate, $endDate, $startTime, $endTime, $daysOfWeek, $outlookEventId, $outlookSyncedAt]);
} elseif (isset($scheduleData['enabled']) && !$scheduleData['enabled']) {
// Tijdspad uitschakelen indien expliciet uitgezet
$db->prepare("DELETE FROM board_card_schedules WHERE card_id = ?")->execute([$cardId]);
}
echo json_encode([
'success' => true,
'card_id' => $cardId,
'outlook_message' => $outlookSyncMsg,
'message' => 'Taak succesvol opgeslagen!'
]);
exit;
}
// 8. Kaart verplaatsen naar een andere lane of positie (Drag & Drop / Quick move)
if ($action === 'move_card') {
$cardId = (int)($input['card_id'] ?? 0);
$targetLaneId = (int)($input['target_lane_id'] ?? 0);
$targetPosition = (int)($input['target_position'] ?? 0);
$stmtCard = $db->prepare("SELECT * FROM board_cards WHERE id = ?");
$stmtCard->execute([$cardId]);
$card = $stmtCard->fetch();
if (!$card) {
echo json_encode(['success' => false, 'error' => 'Taak niet gevonden.']);
exit;
}
if (!check_board_access($db, (int)$card['board_id'], $userId, $isAdmin)) {
http_response_code(403);
echo json_encode(['success' => false, 'error' => 'Geen toegang.']);
exit;
}
// Valideer of de doellane daadwerkelijk bij hetzelfde bord hoort (voorkom IDOR/lane hijacking)
$stmtLane = $db->prepare("SELECT id FROM board_lanes WHERE id = ? AND board_id = ?");
$stmtLane->execute([$targetLaneId, (int)$card['board_id']]);
if (!$stmtLane->fetch()) {
http_response_code(400);
echo json_encode(['success' => false, 'error' => 'Ongeldige doellane voor dit bord.']);
exit;
}
$stmt = $db->prepare("UPDATE board_cards SET lane_id = ?, position = ? WHERE id = ?");
$stmt->execute([$targetLaneId, $targetPosition, $cardId]);
echo json_encode(['success' => true]);
exit;
}
// 9. Kaart verwijderen
if ($action === 'delete_card') {
$cardId = (int)($input['card_id'] ?? 0);
$stmtCard = $db->prepare("
SELECT c.*, s.outlook_event_id
FROM board_cards c
LEFT JOIN board_card_schedules s ON c.id = s.card_id
WHERE c.id = ?
");
$stmtCard->execute([$cardId]);
$card = $stmtCard->fetch();
if (!$card) {
echo json_encode(['success' => false, 'error' => 'Taak niet gevonden.']);
exit;
}
if (!check_board_access($db, (int)$card['board_id'], $userId, $isAdmin)) {
http_response_code(403);
echo json_encode(['success' => false, 'error' => 'Geen rechten.']);
exit;
}
// Indien gekoppeld aan Outlook van de assignee, verwijder ook daar de afspraak
if (!empty($card['outlook_event_id']) && !empty($card['assignee_id'])) {
try {
microsoft_delete_event((int)$card['assignee_id'], $card['outlook_event_id']);
} catch (Throwable $t) {}
}
$db->prepare("DELETE FROM board_cards WHERE id = ?")->execute([$cardId]);
echo json_encode(['success' => true, 'message' => 'Taak verwijderd.']);
exit;
}
// 10. Tijdlijn Datums Snel Bijwerken (Startdatum & Einddatum via Gantt drag)
if ($action === 'update_card_dates') {
$cardId = (int)($input['card_id'] ?? 0);
$startDate = trim($input['start_date'] ?? '');
$endDate = trim($input['end_date'] ?? $startDate);
if (!$cardId || !$startDate) {
echo json_encode(['success' => false, 'error' => 'Ongeldige parameters.']);
exit;
}
$stmtCard = $db->prepare("SELECT * FROM board_cards WHERE id = ?");
$stmtCard->execute([$cardId]);
$card = $stmtCard->fetch();
if (!$card) {
echo json_encode(['success' => false, 'error' => 'Taak niet gevonden.']);
exit;
}
if (!check_board_access($db, (int)$card['board_id'], $userId, $isAdmin)) {
http_response_code(403);
echo json_encode(['success' => false, 'error' => 'Geen toegang tot dit bord.']);
exit;
}
$stmtSched = $db->prepare("
INSERT INTO board_card_schedules (card_id, schedule_type, start_date, end_date, start_time, end_time, days_of_week)
VALUES (?, 'DAILY_RANGE', ?, ?, '09:00', '17:00', '1,2,3,4,5')
ON DUPLICATE KEY UPDATE
start_date = VALUES(start_date),
end_date = VALUES(end_date),
schedule_type = 'DAILY_RANGE'
");
$stmtSched->execute([$cardId, $startDate, $endDate]);
echo json_encode([
'success' => true,
'card_id' => $cardId,
'start_date' => $startDate,
'end_date' => $endDate,
'message' => 'Tijdlijn bijgewerkt!'
]);
exit;
}
// 11. Snelle Inline Taak Aanmaken (⚡ Wat moet er gedaan worden?)
if ($action === 'quick_create_card') {
$boardId = (int)($input['board_id'] ?? 0);
$title = trim($input['title'] ?? '');
$laneId = (int)($input['lane_id'] ?? 0);
$startDate = !empty($input['start_date']) ? trim($input['start_date']) : null;
$endDate = !empty($input['end_date']) ? trim($input['end_date']) : $startDate;
if (!$boardId || empty($title)) {
echo json_encode(['success' => false, 'error' => 'Vul een titel in voor de taak.']);
exit;
}
if (!check_board_access($db, $boardId, $userId, $isAdmin)) {
http_response_code(403);
echo json_encode(['success' => false, 'error' => 'Geen toegang tot dit bord.']);
exit;
}
// Als geen lane_id is meegegeven, pak de 1e lane van het bord
if ($laneId <= 0) {
$stmtLane = $db->prepare("SELECT id FROM board_lanes WHERE board_id = ? ORDER BY position ASC, id ASC LIMIT 1");
$stmtLane->execute([$boardId]);
$laneId = (int)$stmtLane->fetchColumn();
}
$stmtMax = $db->prepare("SELECT COALESCE(MAX(position), -1) FROM board_cards WHERE lane_id = ?");
$stmtMax->execute([$laneId]);
$maxPos = (int)$stmtMax->fetchColumn();
$stmt = $db->prepare("
INSERT INTO board_cards (board_id, lane_id, title, description, assignee_id, priority, color, position, created_by_id)
VALUES (?, ?, ?, '', NULL, 'P2', 'yellow', ?, ?)
");
$stmt->execute([$boardId, $laneId, $title, $maxPos + 1, $userId]);
$newCardId = (int)$db->lastInsertId();
// Optioneel datums toekennen
if ($startDate) {
$stmtSched = $db->prepare("
INSERT INTO board_card_schedules (card_id, schedule_type, start_date, end_date, start_time, end_time, days_of_week)
VALUES (?, 'DAILY_RANGE', ?, ?, '09:00', '17:00', '1,2,3,4,5')
");
$stmtSched->execute([$newCardId, $startDate, $endDate]);
}
echo json_encode([
'success' => true,
'card_id' => $newCardId,
'lane_id' => $laneId,
'message' => 'Taak direct toegevoegd!'
]);
exit;
}
echo json_encode(['success' => false, 'error' => 'Onbekende actie.']);
} catch (Throwable $e) {
error_log("Fociz Board API Fout: " . $e->getMessage());
http_response_code(500);
echo json_encode(['success' => false, 'error' => 'Serverfout: ' . $e->getMessage()]);
}