From eaee4d5f1c4ba455185aae16e623a384cd801c7c Mon Sep 17 00:00:00 2001 From: Niek Rabelink Date: Wed, 9 Sep 2026 20:54:42 +0200 Subject: [PATCH 1/2] Refactor code structure for improved readability and maintainability --- api/board.php | 495 ++++++++++++++++++ board.php | 1181 +++++++++++++++++++++++++++++++++++++++++++ config/database.php | 32 ++ config/schema.sql | 74 +++ includes/header.php | 68 ++- planner.php | 5 +- 6 files changed, 1843 insertions(+), 12 deletions(-) create mode 100644 api/board.php create mode 100644 board.php diff --git a/api/board.php b/api/board.php new file mode 100644 index 0000000..303e115 --- /dev/null +++ b/api/board.php @@ -0,0 +1,495 @@ +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 { + $maxPos = (int)$db->query("SELECT COALESCE(MAX(position), -1) FROM board_lanes WHERE board_id = {$boardId}")->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 + $cardCount = (int)$db->query("SELECT COUNT(*) FROM board_cards WHERE lane_id = {$laneId}")->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 { + $maxPos = (int)$db->query("SELECT COALESCE(MAX(position), -1) FROM board_cards WHERE lane_id = {$laneId}")->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['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); + + $card = $db->query("SELECT * FROM board_cards WHERE id = {$cardId}")->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; + } + + $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; + } + + 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()]); +} + diff --git a/board.php b/board.php new file mode 100644 index 0000000..5a1fa78 --- /dev/null +++ b/board.php @@ -0,0 +1,1181 @@ + + +
+ + +
+
+ + +
+
+ +
+
+
+ + +
+

+ Teamtaken, opdrachten & Outlook agendablokkering +

+
+
+ + +
+ + +
+ +
+ + +
+ +
+ + +
+ + +
+ + + + + + + + + + + +
+
+
+ + +
+
+ + Bord laden... +
+
+ +
+ + + + + + + + + + + + + diff --git a/config/database.php b/config/database.php index 63c9d35..74462a2 100644 --- a/config/database.php +++ b/config/database.php @@ -147,6 +147,7 @@ function ensure_tables_exist(PDO $pdo): void { "ALTER TABLE users ADD COLUMN theme VARCHAR(20) NOT NULL DEFAULT 'dark' AFTER notification_settings", "ALTER TABLE users ADD COLUMN avatar_url VARCHAR(255) NULL AFTER theme", "ALTER TABLE users ADD COLUMN backup_codes TEXT NULL AFTER two_factor_enabled", + "ALTER TABLE board_card_schedules ADD UNIQUE KEY uk_sched_card (card_id)", ]; foreach ($colUpgrades as $sql) { try { @@ -155,6 +156,37 @@ function ensure_tables_exist(PDO $pdo): void { // Stil negeren indien kolom al bestaat } } + + // Zorg dat er ten minste één standaard bord met standaard lanes aanwezig is + try { + $boardCount = (int)$pdo->query("SELECT COUNT(*) FROM boards")->fetchColumn(); + if ($boardCount === 0) { + $firstAdminId = (int)$pdo->query("SELECT id FROM users ORDER BY (role = 'ADMIN') DESC, id ASC LIMIT 1")->fetchColumn(); + if ($firstAdminId > 0) { + $pdo->prepare("INSERT INTO boards (title, description, created_by_id) VALUES (?, ?, ?)") + ->execute(['Hoofdbord', 'Standaard team- en takenbord voor opdrachten', $firstAdminId]); + $boardId = (int)$pdo->lastInsertId(); + + // Maak beheerder lid van het bord + $pdo->prepare("INSERT INTO board_members (board_id, user_id, role) VALUES (?, ?, 'ADMIN')") + ->execute([$boardId, $firstAdminId]); + + // Maak de 4 standaard lanes aan + $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 = $pdo->prepare("INSERT INTO board_lanes (board_id, title, color, position) VALUES (?, ?, ?, ?)"); + foreach ($defaultLanes as $l) { + $laneStmt->execute([$boardId, $l['title'], $l['color'], $l['pos']]); + } + } + } + } catch (Throwable $e) { + // Stil negeren indien tabellen nog in opbouw zijn + } } catch (Throwable $e) { error_log("Fout bij controleren tabellen: " . $e->getMessage()); } diff --git a/config/schema.sql b/config/schema.sql index aed8b4b..992abbe 100644 --- a/config/schema.sql +++ b/config/schema.sql @@ -133,3 +133,77 @@ CREATE TABLE IF NOT EXISTS `system_settings` ( `setting_value` TEXT NULL, `updated_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +-- Fociz Kanban / Trello Borden, Lanes, Kaarten & Tijdspadkoppelingen +CREATE TABLE IF NOT EXISTS `boards` ( + `id` INT AUTO_INCREMENT PRIMARY KEY, + `title` VARCHAR(255) NOT NULL, + `description` TEXT NULL, + `created_by_id` INT NOT NULL, + `is_archived` TINYINT(1) NOT NULL DEFAULT 0, + `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + `updated_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + INDEX (`created_by_id`), + CONSTRAINT `fk_boards_creator` FOREIGN KEY (`created_by_id`) REFERENCES `users` (`id`) ON DELETE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +CREATE TABLE IF NOT EXISTS `board_members` ( + `id` INT AUTO_INCREMENT PRIMARY KEY, + `board_id` INT NOT NULL, + `user_id` INT NOT NULL, + `role` ENUM('ADMIN', 'MEMBER', 'VIEWER') NOT NULL DEFAULT 'MEMBER', + `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + UNIQUE KEY `uk_board_user` (`board_id`, `user_id`), + CONSTRAINT `fk_bmembers_board` FOREIGN KEY (`board_id`) REFERENCES `boards` (`id`) ON DELETE CASCADE, + CONSTRAINT `fk_bmembers_user` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +CREATE TABLE IF NOT EXISTS `board_lanes` ( + `id` INT AUTO_INCREMENT PRIMARY KEY, + `board_id` INT NOT NULL, + `title` VARCHAR(100) NOT NULL, + `color` VARCHAR(30) NOT NULL DEFAULT 'slate', + `position` INT NOT NULL DEFAULT 0, + `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + `updated_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + INDEX (`board_id`, `position`), + CONSTRAINT `fk_blanes_board` FOREIGN KEY (`board_id`) REFERENCES `boards` (`id`) ON DELETE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +CREATE TABLE IF NOT EXISTS `board_cards` ( + `id` INT AUTO_INCREMENT PRIMARY KEY, + `board_id` INT NOT NULL, + `lane_id` INT NOT NULL, + `title` VARCHAR(255) NOT NULL, + `description` TEXT NULL, + `assignee_id` INT NULL, + `priority` ENUM('P1', 'P2', 'P3') NOT NULL DEFAULT 'P2', + `color` VARCHAR(30) NOT NULL DEFAULT 'yellow', + `position` INT NOT NULL DEFAULT 0, + `created_by_id` INT NOT NULL, + `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + `updated_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + INDEX (`board_id`, `lane_id`, `position`), + INDEX (`assignee_id`), + CONSTRAINT `fk_bcards_board` FOREIGN KEY (`board_id`) REFERENCES `boards` (`id`) ON DELETE CASCADE, + CONSTRAINT `fk_bcards_lane` FOREIGN KEY (`lane_id`) REFERENCES `board_lanes` (`id`) ON DELETE CASCADE, + CONSTRAINT `fk_bcards_assignee` FOREIGN KEY (`assignee_id`) REFERENCES `users` (`id`) ON DELETE SET NULL, + CONSTRAINT `fk_bcards_creator` FOREIGN KEY (`created_by_id`) REFERENCES `users` (`id`) ON DELETE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +CREATE TABLE IF NOT EXISTS `board_card_schedules` ( + `id` INT AUTO_INCREMENT PRIMARY KEY, + `card_id` INT NOT NULL, + `schedule_type` ENUM('SINGLE', 'DAILY_RANGE') NOT NULL DEFAULT 'SINGLE', + `start_date` DATE NOT NULL, + `end_date` DATE NOT NULL, + `start_time` VARCHAR(10) NOT NULL DEFAULT '14:00', + `end_time` VARCHAR(10) NOT NULL DEFAULT '15:00', + `days_of_week` VARCHAR(50) NULL DEFAULT '1,2,3,4,5', + `outlook_event_id` VARCHAR(255) NULL, + `outlook_synced_at` DATETIME NULL, + `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + `updated_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + UNIQUE KEY `uk_sched_card` (`card_id`), + CONSTRAINT `fk_bsched_card` FOREIGN KEY (`card_id`) REFERENCES `board_cards` (`id`) ON DELETE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; diff --git a/includes/header.php b/includes/header.php index 0ecaa22..29784a1 100644 --- a/includes/header.php +++ b/includes/header.php @@ -13,7 +13,7 @@ $currentUserTheme = !empty($currentUser['id']) ? get_user_theme((int)$currentUse - <?= htmlspecialchars($pageTitle ?? 'Fociz — Focus, Dagplanning & Roadmap') ?> + <?= htmlspecialchars($pageTitle ?? 'Fociz — Focus, Planning & Roadmap') ?>