diff --git a/admin_classes.php b/admin_classes.php index 33d65f9..67532af 100644 --- a/admin_classes.php +++ b/admin_classes.php @@ -70,9 +70,11 @@ try {
  • Subjects
  • Teachers
  • Workloads
  • +
  • Timeslots
  • - + + diff --git a/admin_subjects.php b/admin_subjects.php index 331504e..217ffb9 100644 --- a/admin_subjects.php +++ b/admin_subjects.php @@ -104,9 +104,11 @@ try {
  • Subjects
  • Teachers
  • Workloads
  • +
  • Timeslots
  • - + + diff --git a/admin_teachers.php b/admin_teachers.php index 01a3607..5c129c0 100644 --- a/admin_teachers.php +++ b/admin_teachers.php @@ -74,9 +74,11 @@ try {
  • Subjects
  • Teachers
  • Workloads
  • +
  • Timeslots
  • - + + diff --git a/admin_timeslots.php b/admin_timeslots.php new file mode 100644 index 0000000..5e5673c --- /dev/null +++ b/admin_timeslots.php @@ -0,0 +1,219 @@ +prepare("SELECT * FROM timeslots WHERE id = ?"); + $stmt->execute([$edit_id]); + $editing_timeslot = $stmt->fetch(PDO::FETCH_ASSOC); + } catch (PDOException $e) { + $error = "Error fetching timeslot: " . $e->getMessage(); + } +} + +// Handle POST request +if ($_SERVER['REQUEST_METHOD'] === 'POST') { + if (isset($_POST['delete_id'])) { + try { + $pdo = db(); + $stmt = $pdo->prepare("DELETE FROM timeslots WHERE id = ?"); + if ($stmt->execute([$_POST['delete_id']])) { + $message = 'Timeslot deleted successfully!'; + } else { + $error = 'Failed to delete timeslot.'; + } + } catch (PDOException $e) { + $error = 'Database error: ' . $e->getMessage(); + } + } + if (isset($_POST['add_timeslot'])) { + $name = trim($_POST['name']); + $start_time = $_POST['start_time']; + $end_time = $_POST['end_time']; + $is_break = isset($_POST['is_break']) ? 1 : 0; + $timeslot_id = $_POST['timeslot_id'] ?? null; + + if (empty($name) || empty($start_time) || empty($end_time)) { + $error = 'All fields are required.'; + } else { + try { + if ($timeslot_id) { + // Update existing timeslot + $stmt = $pdo->prepare("UPDATE timeslots SET name = ?, start_time = ?, end_time = ?, is_break = ? WHERE id = ?"); + $stmt->execute([$name, $start_time, $end_time, $is_break, $timeslot_id]); + $message = "Timeslot updated successfully!"; + } else { + // Insert new timeslot + $stmt = $pdo->prepare("INSERT INTO timeslots (name, start_time, end_time, is_break) VALUES (?, ?, ?, ?)"); + $stmt->execute([$name, $start_time, $end_time, $is_break]); + $message = "Timeslot created successfully!"; + } + } catch (PDOException $e) { + $error = 'Database error: ' . $e->getMessage(); + } + } + } +} + +// Fetch all timeslots +$timeslots = []; +try { + $pdo = db(); + $stmt = $pdo->query("SELECT * FROM timeslots ORDER BY start_time"); + $timeslots = $stmt->fetchAll(PDO::FETCH_ASSOC); +} catch (PDOException $e) { + $error = 'Database error: ' . $e->getMessage(); +} + +?> + + + + + + Admin: Manage Timeslots - Haki Schedule + + + + + + + +
    +
    +
    +

    Manage Timeslots

    + + +
    + + +
    + + + +
    +
    +
    +
    + +
    +
    + + +
    +
    + + +
    +
    + + +
    +
    +
    + > + +
    + + + Cancel Edit + +
    +
    +
    + + +
    +
    +
    Existing Timeslots
    + +

    No timeslots have been created yet.

    + + + + + + + + + + + + + + + + + + + + + + +
    NameStart TimeEnd TimeTypeActions
    + Edit +
    + + +
    +
    + +
    +
    + +
    +
    +
    + + + + + + diff --git a/admin_workloads.php b/admin_workloads.php index 96721ab..e7ca161 100644 --- a/admin_workloads.php +++ b/admin_workloads.php @@ -126,10 +126,12 @@ try {
  • Classes
  • Subjects
  • Teachers
  • -
  • Workloads
  • +
  • Workloads
  • +
  • Timeslots
  • - + + diff --git a/api/move_lesson.php b/api/move_lesson.php new file mode 100644 index 0000000..dfb73f6 --- /dev/null +++ b/api/move_lesson.php @@ -0,0 +1,52 @@ + false, 'error' => 'Invalid request']; + +if ($_SERVER['REQUEST_METHOD'] === 'POST') { + $data = json_decode(file_get_contents('php://input'), true); + + $lesson_id = $data['lesson_id'] ?? null; + $to_class_id = $data['to_class_id'] ?? null; + $to_day = $data['to_day'] ?? null; + $to_timeslot_id = $data['to_timeslot_id'] ?? null; + + if ($lesson_id && $to_class_id && isset($to_day) && $to_timeslot_id) { + try { + $pdo = db(); + + // TODO: Add validation logic here to check for conflicts + + $stmt = $pdo->prepare( + 'UPDATE schedules SET class_id = :class_id, day_of_week = :day, timeslot_id = :timeslot_id WHERE id = :lesson_id' + ); + + $stmt->execute([ + ':class_id' => $to_class_id, + ':day' => $to_day, + ':timeslot_id' => $to_timeslot_id, + ':lesson_id' => $lesson_id + ]); + + if ($stmt->rowCount() > 0) { + $response = ['success' => true]; + } else { + $response['error'] = 'Failed to move lesson. No rows were updated.'; + } + + } catch (PDOException $e) { + // Check for unique constraint violation + if ($e->getCode() == 23000) { // SQLSTATE for integrity constraint violation + $response['error'] = 'This time slot is already occupied.'; + } else { + $response['error'] = 'Database error: ' . $e->getMessage(); + } + } + } else { + $response['error'] = 'Missing required data.'; + } +} + +echo json_encode($response); diff --git a/db/migrations/007_create_timeslots_table.sql b/db/migrations/007_create_timeslots_table.sql new file mode 100644 index 0000000..e702712 --- /dev/null +++ b/db/migrations/007_create_timeslots_table.sql @@ -0,0 +1,7 @@ +CREATE TABLE IF NOT EXISTS timeslots ( + id INT AUTO_INCREMENT PRIMARY KEY, + name VARCHAR(255) NOT NULL, + start_time TIME NOT NULL, + end_time TIME NOT NULL, + is_break BOOLEAN NOT NULL DEFAULT 0 +); diff --git a/db/migrations/008_create_schedules_table.sql b/db/migrations/008_create_schedules_table.sql new file mode 100644 index 0000000..24a57e5 --- /dev/null +++ b/db/migrations/008_create_schedules_table.sql @@ -0,0 +1,18 @@ +CREATE TABLE IF NOT EXISTS `schedules` ( + `id` INT AUTO_INCREMENT PRIMARY KEY, + `class_id` INT NOT NULL, + `day_of_week` INT NOT NULL, + `timeslot_id` INT NOT NULL, + `subject_id` INT, + `teacher_id` INT, + `lesson_display_name` VARCHAR(255) NOT NULL, + `teacher_display_name` VARCHAR(255) NOT NULL, + `is_double` BOOLEAN DEFAULT FALSE, + `is_elective` BOOLEAN DEFAULT FALSE, + `is_horizontal_elective` BOOLEAN DEFAULT FALSE, + UNIQUE KEY `unique_schedule_entry` (`class_id`, `day_of_week`, `timeslot_id`), + FOREIGN KEY (`class_id`) REFERENCES `classes`(`id`) ON DELETE CASCADE, + FOREIGN KEY (`timeslot_id`) REFERENCES `timeslots`(`id`) ON DELETE CASCADE, + FOREIGN KEY (`subject_id`) REFERENCES `subjects`(`id`) ON DELETE SET NULL, + FOREIGN KEY (`teacher_id`) REFERENCES `teachers`(`id`) ON DELETE SET NULL +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; \ No newline at end of file diff --git a/teacher_timetable.php b/teacher_timetable.php new file mode 100644 index 0000000..1d3847f --- /dev/null +++ b/teacher_timetable.php @@ -0,0 +1,251 @@ +query("SELECT * FROM teachers ORDER BY name")->fetchAll(PDO::FETCH_ASSOC); +} + +function get_timeslots($pdo) { + return $pdo->query("SELECT * FROM timeslots ORDER BY start_time")->fetchAll(PDO::FETCH_ASSOC); +} + +function get_teacher_schedule($pdo, $teacher_id) { + $stmt = $pdo->prepare(" + SELECT + s.day_of_week, + s.timeslot_id, + s.lesson_display_name, + c.name as class_name, + s.is_double, + s.is_elective, + s.is_horizontal_elective + FROM schedules s + JOIN classes c ON s.class_id = c.id + WHERE s.teacher_id = :teacher_id + "); + $stmt->execute([':teacher_id' => $teacher_id]); + return $stmt->fetchAll(PDO::FETCH_ASSOC); +} + +// --- Main Logic --- +$pdoconn = db(); +$teachers = get_teachers($pdoconn); +$timeslots = get_timeslots($pdoconn); +$days_of_week = ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday']; + +$selected_teacher_id = isset($_GET['teacher_id']) ? $_GET['teacher_id'] : null; +$selected_teacher_name = ''; +$teacher_schedule_raw = []; +if ($selected_teacher_id) { + foreach ($teachers as $teacher) { + if ($teacher['id'] == $selected_teacher_id) { + $selected_teacher_name = $teacher['name']; + break; + } + } + $teacher_schedule_raw = get_teacher_schedule($pdoconn, $selected_teacher_id); +} + +// Organize schedule for easy display +$teacher_timetable = array_fill(0, count($days_of_week), []); +foreach ($timeslots as $timeslot) { + if (!$timeslot['is_break']) { + foreach ($days_of_week as $day_idx => $day) { + $teacher_timetable[$day_idx][$timeslot['id']] = null; + } + } +} + +foreach ($teacher_schedule_raw as $lesson) { + $day_idx = $lesson['day_of_week']; + $timeslot_id = $lesson['timeslot_id']; + if (isset($teacher_timetable[$day_idx][$timeslot_id])) { + $teacher_timetable[$day_idx][$timeslot_id] = $lesson; + } +} + +?> + + + + + + Teacher Timetable - Haki Schedule + + + + + + + + + +
    +
    +

    Teacher Timetable

    + +
    + + +
    + +
    + +
    +
    +
    + + +
    +
    +
    + +
    + +

    Timetable for

    +
    +
    + + + + + + + + + + + + + + + + + $day): ?> + + + + + + +
    Time
    +
    + - +
    Break + +
    +
    + +
    + +
    +
    +
    + +
    No lessons scheduled for this teacher.
    + +
    +
    + + + + + \ No newline at end of file diff --git a/timetable.php b/timetable.php index f80b7e9..968b059 100644 --- a/timetable.php +++ b/timetable.php @@ -3,10 +3,6 @@ session_start(); require_once 'includes/auth_check.php'; require_once 'db/config.php'; -// --- Configuration --- -define('DAYS_OF_WEEK', ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday']); -define('PERIODS_PER_DAY', 8); - // --- Database Fetch --- function get_workloads($pdo) { $stmt = $pdo->query(" @@ -34,6 +30,10 @@ function get_classes($pdo) { return $pdo->query("SELECT * FROM classes ORDER BY name")->fetchAll(PDO::FETCH_ASSOC); } +function get_timeslots($pdo) { + return $pdo->query("SELECT * FROM timeslots ORDER BY start_time")->fetchAll(PDO::FETCH_ASSOC); +} + // --- Helper Functions --- function get_grade_from_class_name($class_name) { if (preg_match('/^(Grade\s+\d+)/i', $class_name, $matches)) { @@ -43,7 +43,7 @@ function get_grade_from_class_name($class_name) { } // --- Scoring and Placement Logic --- -function find_best_slot_for_lesson($lesson, $is_double, &$class_timetables, &$teacher_timetables, $all_class_ids) { +function find_best_slot_for_lesson($lesson, $is_double, &$class_timetables, &$teacher_timetables, $all_class_ids, $days_of_week, $periods_per_day) { $best_slot = null; $highest_score = -1; @@ -51,14 +51,14 @@ function find_best_slot_for_lesson($lesson, $is_double, &$class_timetables, &$te $teachers_in_lesson = array_unique(array_column($lesson['component_lessons'], 'teacher_id')); $class_ids_in_lesson = $lesson['type'] === 'horizontal_elective' ? $lesson['participating_class_ids'] : [$class_id]; - for ($day = 0; $day < count(DAYS_OF_WEEK); $day++) { - for ($period = 0; $period < PERIODS_PER_DAY; $period++) { - $current_score = 100; // Base score for a valid slot + for ($day = 0; $day < count($days_of_week); $day++) { + for ($period = 0; $period < $periods_per_day; $period++) { + $current_score = 100; // Base score // 1. Check basic availability $slot_available = true; if ($is_double) { - if ($period + 1 >= PERIODS_PER_DAY) continue; // Not enough space for a double + if ($period + 1 >= $periods_per_day) continue; foreach ($class_ids_in_lesson as $cid) { if (isset($class_timetables[$cid][$day][$period]) || isset($class_timetables[$cid][$day][$period + 1])) { $slot_available = false; break; @@ -85,50 +85,32 @@ function find_best_slot_for_lesson($lesson, $is_double, &$class_timetables, &$te } if (!$slot_available) continue; - // 2. Apply scoring rules for even distribution + // 2. Apply scoring rules foreach ($class_ids_in_lesson as $cid) { - // Penalty for same subject on the same day - for ($p = 0; $p < PERIODS_PER_DAY; $p++) { + for ($p = 0; $p < $periods_per_day; $p++) { if (isset($class_timetables[$cid][$day][$p]) && $class_timetables[$cid][$day][$p]['subject_name'] === $lesson['display_name']) { $current_score -= 50; } } } - // Penalty for teacher back-to-back lessons foreach ($teachers_in_lesson as $teacher_id) { - // Check period before - if ($period > 0 && isset($teacher_timetables[$teacher_id][$day][$period - 1])) { - $current_score -= 15; - } - // Check period after + if ($period > 0 && isset($teacher_timetables[$teacher_id][$day][$period - 1])) $current_score -= 15; $after_period = $is_double ? $period + 2 : $period + 1; - if ($after_period < PERIODS_PER_DAY && isset($teacher_timetables[$teacher_id][$day][$after_period])) { - $current_score -= 15; - } + if ($after_period < $periods_per_day && isset($teacher_timetables[$teacher_id][$day][$after_period])) $current_score -= 15; } - // 3. New Penalty: Avoid scheduling double lessons of the same subject at the same time (resource conflict) if ($is_double) { $subject_name_to_check = $lesson['display_name']; foreach ($all_class_ids as $cid) { - // Skip the classes that are part of the current lesson being placed - if (in_array($cid, $class_ids_in_lesson)) { - continue; - } - - if (isset($class_timetables[$cid][$day][$period])) { - $conflicting_lesson = $class_timetables[$cid][$day][$period]; - // Check if it's a double lesson of the same subject. - if ($conflicting_lesson['is_double'] && $conflicting_lesson['subject_name'] === $subject_name_to_check) { - $current_score -= 500; // High penalty for resource conflict - break; // Found a conflict, no need to check other classes - } + if (in_array($cid, $class_ids_in_lesson)) continue; + if (isset($class_timetables[$cid][$day][$period]) && $class_timetables[$cid][$day][$period]['is_double'] && $class_timetables[$cid][$day][$period]['subject_name'] === $subject_name_to_check) { + $current_score -= 500; + break; } } } - // 4. Compare with the highest score if ($current_score > $highest_score) { $highest_score = $current_score; $best_slot = ['day' => $day, 'period' => $period]; @@ -138,23 +120,15 @@ function find_best_slot_for_lesson($lesson, $is_double, &$class_timetables, &$te return $best_slot; } - // --- Main Scheduling Engine --- -function generate_timetable($workloads, $classes) { +function generate_timetable($workloads, $classes, $days_of_week, $periods_per_day) { $class_timetables = []; foreach ($classes as $class) { - $class_timetables[$class['id']] = array_fill(0, count(DAYS_OF_WEEK), array_fill(0, PERIODS_PER_DAY, null)); + $class_timetables[$class['id']] = array_fill(0, count($days_of_week), array_fill(0, $periods_per_day, null)); } $teacher_timetables = []; // --- Lesson Preparation --- - $classes_by_grade = []; - foreach ($classes as $class) { - $grade_name = get_grade_from_class_name($class['name']); - $classes_by_grade[$grade_name][] = $class; - } - - // Step 1: Identify horizontal electives and separate them from other workloads $horizontal_elective_doubles = []; $horizontal_elective_singles = []; $other_workloads = []; @@ -172,7 +146,6 @@ function generate_timetable($workloads, $classes) { foreach ($workloads_by_grade_and_elective_group as $grade_name => $elective_groups) { foreach ($elective_groups as $elective_group_name => $group_workloads) { $participating_class_ids = array_unique(array_column($group_workloads, 'class_id')); - // A horizontal elective involves more than one class if (count($participating_class_ids) > 1) { $first = $group_workloads[0]; $block = [ @@ -185,27 +158,17 @@ function generate_timetable($workloads, $classes) { ]; if ($block['has_double_lesson'] && $block['lessons_per_week'] >= 2) { - $horizontal_elective_doubles[] = $block; // Create one double lesson - // Create remaining as single lessons - for ($i = 0; $i < $block['lessons_per_week'] - 2; $i++) { - $horizontal_elective_singles[] = $block; - } + $horizontal_elective_doubles[] = $block; + for ($i = 0; $i < $block['lessons_per_week'] - 2; $i++) $horizontal_elective_singles[] = $block; } else { - // Create all as single lessons - for ($i = 0; $i < $block['lessons_per_week']; $i++) { - $horizontal_elective_singles[] = $block; - } + for ($i = 0; $i < $block['lessons_per_week']; $i++) $horizontal_elective_singles[] = $block; } } else { - // Not a horizontal elective, add back to the pool of other workloads - foreach($group_workloads as $workload) { - $other_workloads[] = $workload; - } + foreach($group_workloads as $workload) $other_workloads[] = $workload; } } } - // Step 2: Process remaining workloads (regular subjects and single-class electives) $double_lessons = []; $single_lessons = []; $workloads_by_class = []; @@ -216,7 +179,6 @@ function generate_timetable($workloads, $classes) { foreach ($workloads_by_class as $class_id => $class_workloads) { $elective_groups = []; $individual_lessons = []; - // Separate single-class electives from individual subjects foreach ($class_workloads as $workload) { if (!empty($workload['elective_group'])) { $elective_groups[$workload['elective_group']][] = $workload; @@ -225,75 +187,48 @@ function generate_timetable($workloads, $classes) { } } - // Process single-class elective groups foreach ($elective_groups as $group_name => $group_workloads) { $first = $group_workloads[0]; $block = [ - 'type' => 'elective', - 'class_id' => $class_id, - 'display_name' => $group_name, - 'lessons_per_week' => $first['lessons_per_week'], - 'has_double_lesson' => $first['has_double_lesson'], + 'type' => 'elective', 'class_id' => $class_id, 'display_name' => $group_name, + 'lessons_per_week' => $first['lessons_per_week'], 'has_double_lesson' => $first['has_double_lesson'], 'component_lessons' => $group_workloads ]; - if ($block['has_double_lesson'] && $block['lessons_per_week'] >= 2) { - $double_lessons[] = $block; // One double lesson - // Remaining are single lessons - for ($i = 0; $i < $block['lessons_per_week'] - 2; $i++) { - $single_lessons[] = $block; - } + $double_lessons[] = $block; + for ($i = 0; $i < $block['lessons_per_week'] - 2; $i++) $single_lessons[] = $block; } else { - // All are single lessons - for ($i = 0; $i < $block['lessons_per_week']; $i++) { - $single_lessons[] = $block; - } + for ($i = 0; $i < $block['lessons_per_week']; $i++) $single_lessons[] = $block; } } - // Process individual subjects foreach ($individual_lessons as $workload) { $lesson = [ - 'type' => 'single', - 'class_id' => $workload['class_id'], - 'display_name' => $workload['subject_name'], - 'lessons_per_week' => $workload['lessons_per_week'], - 'has_double_lesson' => $workload['has_double_lesson'], + 'type' => 'single', 'class_id' => $workload['class_id'], 'display_name' => $workload['subject_name'], + 'lessons_per_week' => $workload['lessons_per_week'], 'has_double_lesson' => $workload['has_double_lesson'], 'component_lessons' => [$workload] ]; - if ($lesson['has_double_lesson'] && $lesson['lessons_per_week'] >= 2) { - $double_lessons[] = $lesson; // One double lesson - // Remaining are single lessons - for ($i = 0; $i < $lesson['lessons_per_week'] - 2; $i++) { - $single_lessons[] = $lesson; - } + $double_lessons[] = $lesson; + for ($i = 0; $i < $lesson['lessons_per_week'] - 2; $i++) $single_lessons[] = $lesson; } else { - // All are single lessons - for ($i = 0; $i < $lesson['lessons_per_week']; $i++) { - $single_lessons[] = $lesson; - } + for ($i = 0; $i < $lesson['lessons_per_week']; $i++) $single_lessons[] = $lesson; } } } // --- Placement using Scoring --- - $all_class_ids = array_column($classes, 'id'); // Get all class IDs for the conflict check - - // The order determines priority: most constrained lessons are scheduled first. + $all_class_ids = array_column($classes, 'id'); $all_lessons_in_order = [ - 'horizontal_doubles' => $horizontal_elective_doubles, - 'doubles' => $double_lessons, - 'horizontal_singles' => $horizontal_elective_singles, - 'singles' => $single_lessons + 'horizontal_doubles' => $horizontal_elective_doubles, 'doubles' => $double_lessons, + 'horizontal_singles' => $horizontal_elective_singles, 'singles' => $single_lessons ]; foreach ($all_lessons_in_order as $type => $lessons) { - shuffle($lessons); // Randomize order within the same type to avoid bias + shuffle($lessons); foreach ($lessons as $lesson) { $is_double = ($type === 'doubles' || $type === 'horizontal_doubles'); - - $best_slot = find_best_slot_for_lesson($lesson, $is_double, $class_timetables, $teacher_timetables, $all_class_ids); + $best_slot = find_best_slot_for_lesson($lesson, $is_double, $class_timetables, $teacher_timetables, $all_class_ids, $days_of_week, $periods_per_day); if ($best_slot) { $day = $best_slot['day']; @@ -301,7 +236,16 @@ function generate_timetable($workloads, $classes) { $class_ids_to_place = ($lesson['type'] === 'horizontal_elective') ? $lesson['participating_class_ids'] : [$lesson['class_id']]; $teachers_to_place = array_unique(array_column($lesson['component_lessons'], 'teacher_id')); + $subject_id = null; + $teacher_id = null; + if ($lesson['type'] === 'single' && count($lesson['component_lessons']) === 1) { + $subject_id = $lesson['component_lessons'][0]['subject_id']; + $teacher_id = $lesson['component_lessons'][0]['teacher_id']; + } + $lesson_info = [ + 'subject_id' => $subject_id, + 'teacher_id' => $teacher_id, 'subject_name' => $lesson['display_name'], 'teacher_name' => count($teachers_to_place) > 1 ? 'Multiple' : $lesson['component_lessons'][0]['teacher_name'], 'is_double' => $is_double, @@ -319,29 +263,107 @@ function generate_timetable($workloads, $classes) { $teacher_timetables[$tid][$day][$period + 1] = true; } } else { - foreach ($class_ids_to_place as $cid) { - $class_timetables[$cid][$day][$period] = $lesson_info; - } - foreach ($teachers_to_place as $tid) { - $teacher_timetables[$tid][$day][$period] = true; - } + foreach ($class_ids_to_place as $cid) $class_timetables[$cid][$day][$period] = $lesson_info; + foreach ($teachers_to_place as $tid) $teacher_timetables[$tid][$day][$period] = true; } } } } - return $class_timetables; } +// --- Timetable Persistence --- +function save_timetable($pdo, $class_timetables, $timeslots) { + $pdo->exec('TRUNCATE TABLE schedules'); + $stmt = $pdo->prepare( + 'INSERT INTO schedules (class_id, day_of_week, timeslot_id, subject_id, teacher_id, lesson_display_name, teacher_display_name, is_double, is_elective, is_horizontal_elective) ' . + 'VALUES (:class_id, :day_of_week, :timeslot_id, :subject_id, :teacher_id, :lesson_display_name, :teacher_display_name, :is_double, :is_elective, :is_horizontal_elective)' + ); + + $lesson_periods = array_values(array_filter($timeslots, function($ts) { return !$ts['is_break']; })); + + foreach ($class_timetables as $class_id => $day_schedule) { + foreach ($day_schedule as $day_idx => $period_schedule) { + foreach ($period_schedule as $period_idx => $lesson) { + if ($lesson) { + if (!isset($lesson_periods[$period_idx])) continue; + $timeslot_id = $lesson_periods[$period_idx]['id']; + $stmt->execute([ + ':class_id' => $class_id, + ':day_of_week' => $day_idx, + ':timeslot_id' => $timeslot_id, + ':subject_id' => $lesson['subject_id'], + ':teacher_id' => $lesson['teacher_id'], + ':lesson_display_name' => $lesson['subject_name'], + ':teacher_display_name' => $lesson['teacher_name'], + ':is_double' => $lesson['is_double'], + ':is_elective' => $lesson['is_elective'], + ':is_horizontal_elective' => $lesson['is_horizontal_elective'] + ]); + } + } + } + } +} + +function get_timetable_from_db($pdo, $classes, $timeslots) { + $stmt = $pdo->query('SELECT * FROM schedules ORDER BY id'); + $saved_lessons = $stmt->fetchAll(PDO::FETCH_ASSOC); + if (empty($saved_lessons)) return []; + + $days_of_week = ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday']; + $periods = array_filter($timeslots, function($ts) { return !$ts['is_break']; }); + $periods_per_day = count($periods); + + $class_timetables = []; + foreach ($classes as $class) { + $class_timetables[$class['id']] = array_fill(0, count($days_of_week), array_fill(0, $periods_per_day, null)); + } + + $lesson_periods = array_values($periods); + $timeslot_id_to_period_idx = []; + foreach($lesson_periods as $idx => $period) { + $timeslot_id_to_period_idx[$period['id']] = $idx; + } + + foreach ($saved_lessons as $lesson) { + $class_id = $lesson['class_id']; + $day_idx = $lesson['day_of_week']; + $timeslot_id = $lesson['timeslot_id']; + + if (!isset($timeslot_id_to_period_idx[$timeslot_id]) || !isset($class_timetables[$class_id])) continue; + $period_idx = $timeslot_id_to_period_idx[$timeslot_id]; + + $class_timetables[$class_id][$day_idx][$period_idx] = [ + 'id' => $lesson['id'], + 'subject_id' => $lesson['subject_id'], + 'teacher_id' => $lesson['teacher_id'], + 'subject_name' => $lesson['lesson_display_name'], + 'teacher_name' => $lesson['teacher_display_name'], + 'is_double' => (bool)$lesson['is_double'], + 'is_elective' => (bool)$lesson['is_elective'], + 'is_horizontal_elective' => (bool)$lesson['is_horizontal_elective'] + ]; + } + return $class_timetables; +} + +// --- Main Logic --- $pdoconn = db(); $workloads = get_workloads($pdoconn); $classes = get_classes($pdoconn); -$class_timetables = []; +$timeslots = get_timeslots($pdoconn); + +$days_of_week = ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday']; +$periods = array_filter($timeslots, function($timeslot) { return !$timeslot['is_break']; }); +$periods_per_day = count($periods); if (isset($_POST['generate'])) { - $class_timetables = generate_timetable($workloads, $classes); + $class_timetables = generate_timetable($workloads, $classes, $days_of_week, $periods_per_day); + save_timetable($pdoconn, $class_timetables, $timeslots); +} else { + $class_timetables = get_timetable_from_db($pdoconn, $classes, $timeslots); } - ?> @@ -351,6 +373,29 @@ if (isset($_POST['generate'])) { Timetable - Haki Schedule + + + +