Now fixing teacher timetable

This commit is contained in:
Flatlogic Bot 2025-12-02 19:19:52 +00:00
parent debb478acf
commit ef2d9513a5
10 changed files with 326 additions and 504 deletions

View File

@ -17,7 +17,11 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['delete_id'])) {
$stmt->execute([$delete_id, $school_id]); $stmt->execute([$delete_id, $school_id]);
$message = "Class deleted successfully."; $message = "Class deleted successfully.";
} catch (PDOException $e) { } catch (PDOException $e) {
$error = "Error deleting class: " . $e->getMessage(); if ($e->getCode() == '23000') { // Integrity constraint violation
$error = "Cannot delete this class because it has subjects, workloads, or schedules associated with it. Please remove those associations before deleting.";
} else {
$error = "Error deleting class: " . $e->getMessage();
}
} }
} }

View File

@ -17,7 +17,11 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['delete_id'])) {
$stmt->execute([$delete_id, $school_id]); $stmt->execute([$delete_id, $school_id]);
$message = "Elective group deleted successfully."; $message = "Elective group deleted successfully.";
} catch (PDOException $e) { } catch (PDOException $e) {
$error = "Error deleting group: " . $e->getMessage(); if ($e->getCode() == '23000') { // Integrity constraint violation
$error = "Cannot delete this elective group because it has subjects associated with it. Please remove those associations before deleting.";
} else {
$error = "Error deleting group: " . $e->getMessage();
}
} }
} }

View File

@ -17,7 +17,11 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['delete_id'])) {
$stmt->execute([$delete_id, $school_id]); $stmt->execute([$delete_id, $school_id]);
$message = "Subject deleted successfully."; $message = "Subject deleted successfully.";
} catch (PDOException $e) { } catch (PDOException $e) {
$error = "Error deleting subject: " . $e->getMessage(); if ($e->getCode() == '23000') { // Integrity constraint violation
$error = "Cannot delete this subject because it is currently used in workloads or schedules. Please remove those associations before deleting.";
} else {
$error = "Error deleting subject: " . $e->getMessage();
}
} }
} }

View File

@ -29,7 +29,11 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['delete_id'])) {
$message = "Teacher deleted successfully."; $message = "Teacher deleted successfully.";
} catch (PDOException $e) { } catch (PDOException $e) {
$pdo->rollBack(); $pdo->rollBack();
$error = "Error deleting teacher: " . $e->getMessage(); if ($e->getCode() == '23000') { // Integrity constraint violation
$error = "Cannot delete this teacher because they are assigned to workloads or schedules. Please remove those associations before deleting.";
} else {
$error = "Error deleting teacher: " . $e->getMessage();
}
} }
} }

View File

@ -32,7 +32,11 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$error = 'Failed to delete timeslot.'; $error = 'Failed to delete timeslot.';
} }
} catch (PDOException $e) { } catch (PDOException $e) {
$error = 'Database error: ' . $e->getMessage(); if ($e->getCode() == '23000') { // Integrity constraint violation
$error = "Cannot delete this timeslot because it is currently used in schedules. Please remove those associations before deleting.";
} else {
$error = "Error deleting timeslot: " . $e->getMessage();
}
} }
} }
if (isset($_POST['add_timeslot'])) { if (isset($_POST['add_timeslot'])) {

View File

@ -0,0 +1,7 @@
CREATE TABLE IF NOT EXISTS `elective_group_subjects` (
`elective_group_id` INT NOT NULL,
`subject_id` INT NOT NULL,
PRIMARY KEY (`elective_group_id`, `subject_id`),
FOREIGN KEY (`elective_group_id`) REFERENCES `elective_groups`(`id`) ON DELETE CASCADE,
FOREIGN KEY (`subject_id`) REFERENCES `subjects`(`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

View File

@ -0,0 +1,8 @@
CREATE TABLE IF NOT EXISTS elective_group_subjects (
id INT AUTO_INCREMENT PRIMARY KEY,
elective_group_id INT NOT NULL,
subject_id INT NOT NULL,
FOREIGN KEY (elective_group_id) REFERENCES elective_groups(id) ON DELETE CASCADE,
FOREIGN KEY (subject_id) REFERENCES subjects(id) ON DELETE CASCADE,
UNIQUE KEY (elective_group_id, subject_id)
);

42
includes/navbar.php Normal file
View File

@ -0,0 +1,42 @@
<?php
// Note: This file assumes session_start() has been called by the including file.
$current_page = basename($_SERVER['SCRIPT_NAME']);
$role = $_SESSION['role'] ?? '';
?>
<nav class="navbar navbar-expand-lg navbar-light bg-white sticky-top shadow-sm">
<div class="container">
<a class="navbar-brand fw-bold" href="/">Haki Schedule</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarNav">
<ul class="navbar-nav ms-auto">
<li class="nav-item"><a class="nav-link <?php echo ($current_page == 'index.php') ? 'active' : ''; ?>" href="/">Home</a></li>
<?php if (isset($_SESSION['user_id'])) : ?>
<?php if ($role === 'admin'): ?>
<li class="nav-item dropdown">
<a class="nav-link dropdown-toggle <?php echo (strpos($current_page, 'admin_') === 0) ? 'active' : ''; ?>" href="#" id="manageDropdown" role="button" data-bs-toggle="dropdown" aria-expanded="false">
Manage
</a>
<ul class="dropdown-menu" aria-labelledby="manageDropdown">
<li><a class="dropdown-item" href="/admin_classes.php">Classes</a></li>
<li><a class="dropdown-item" href="/admin_subjects.php">Subjects</a></li>
<li><a class="dropdown-item" href="/admin_teachers.php">Teachers</a></li>
<li><a class="dropdown-item" href="/admin_workloads.php">Workloads</a></li>
<li><a class="dropdown-item" href="/admin_timeslots.php">Timeslots</a></li>
<li><a class="dropdown-item" href="/admin_elective_groups.php">Elective Groups</a></li>
</ul>
</li>
<li class="nav-item"><a class="nav-link <?php echo ($current_page == 'timetable.php') ? 'active' : ''; ?>" href="/timetable.php">Class Timetable</a></li>
<?php endif; ?>
<li class="nav-item"><a class="nav-link <?php echo ($current_page == 'teacher_timetable.php') ? 'active' : ''; ?>" href="/teacher_timetable.php">Teacher Timetable</a></li>
<li class="nav-item"><a class="nav-link" href="/logout.php">Logout</a></li>
<?php else : ?>
<li class="nav-item"><a class="nav-link <?php echo ($current_page == 'demo.php') ? 'active' : ''; ?>" href="/demo.php">Demo</a></li>
<li class="nav-item"><a class="nav-link <?php echo ($current_page == 'login.php') ? 'active' : ''; ?>" href="/login.php">Login</a></li>
<li class="nav-item"><a class="nav-link <?php echo ($current_page == 'register.php') ? 'active' : ''; ?>" href="/register.php">Register</a></li>
<?php endif; ?>
</ul>
</div>
</div>
</nav>

View File

@ -85,9 +85,9 @@ foreach ($timeslots as $timeslot) {
} }
foreach ($teacher_schedule_raw as $lesson) { foreach ($teacher_schedule_raw as $lesson) {
$day_idx = $lesson['day_of_week']; $day_idx = $lesson['day_of_week'] - 1; // Adjust for 0-based array index
$timeslot_id = $lesson['timeslot_id']; $timeslot_id = $lesson['timeslot_id'];
if (isset($teacher_timetable[$day_idx][$timeslot_id])) { if (isset($teacher_timetable[$day_idx]) && isset($teacher_timetable[$day_idx][$timeslot_id])) {
$teacher_timetable[$day_idx][$timeslot_id] = $lesson; $teacher_timetable[$day_idx][$timeslot_id] = $lesson;
} }
} }
@ -181,7 +181,7 @@ foreach ($teacher_schedule_raw as $lesson) {
<select name="teacher_id" id="teacher_id" class="form-select" onchange="this.form.submit()"> <select name="teacher_id" id="teacher_id" class="form-select" onchange="this.form.submit()">
<option value="">-- Select a Teacher --</option> <option value="">-- Select a Teacher --</option>
<?php foreach ($teachers as $teacher): ?> <?php foreach ($teachers as $teacher): ?>
<option value="<?php echo $teacher['id']; ?>" <?php echo ($selected_teacher_id == $teacher['id']) ? 'selected' : ''; ?>> <option value="<?php echo $teacher['id']; ?>" <?php echo ($selected_teacher_id == $teacher['id']) ? 'selected' : ''; ?> >
<?php echo htmlspecialchars($teacher['name']); ?> <?php echo htmlspecialchars($teacher['name']); ?>
</option> </option>
<?php endforeach; ?> <?php endforeach; ?>

View File

@ -4,370 +4,258 @@ require_once 'includes/auth_check.php';
require_once 'db/config.php'; require_once 'db/config.php';
// --- Database Fetch --- // --- Database Fetch ---
function get_workloads($pdo) { function get_all_data($pdo) {
$stmt = $pdo->query(" $data = [];
$data['classes'] = $pdo->query("SELECT * FROM classes ORDER BY name")->fetchAll(PDO::FETCH_ASSOC);
$data['subjects'] = $pdo->query("SELECT id, name FROM subjects")->fetchAll(PDO::FETCH_KEY_PAIR);
$data['teachers'] = $pdo->query("SELECT id, name FROM teachers")->fetchAll(PDO::FETCH_KEY_PAIR);
$data['timeslots'] = $pdo->query("SELECT * FROM timeslots ORDER BY start_time")->fetchAll(PDO::FETCH_ASSOC);
$workloads_stmt = $pdo->query("
SELECT SELECT
w.id, w.class_id, w.subject_id, w.teacher_id, w.lessons_per_week,
c.id as class_id, s.name as subject_name, s.has_double_lesson, s.elective_group_id,
c.name as class_name, c.name as class_name,
s.id as subject_id,
s.name as subject_name,
s.has_double_lesson,
s.elective_group_id,
eg.name as elective_group_name,
t.id as teacher_id,
t.name as teacher_name, t.name as teacher_name,
w.lessons_per_week eg.name as elective_group_name
FROM workloads w FROM workloads w
JOIN classes c ON w.class_id = c.id
JOIN subjects s ON w.subject_id = s.id JOIN subjects s ON w.subject_id = s.id
JOIN classes c ON w.class_id = c.id
JOIN teachers t ON w.teacher_id = t.id JOIN teachers t ON w.teacher_id = t.id
LEFT JOIN elective_groups eg ON s.elective_group_id = eg.id LEFT JOIN elective_groups eg ON s.elective_group_id = eg.id
ORDER BY c.name, s.name
"); ");
return $stmt->fetchAll(PDO::FETCH_ASSOC); $data['workloads'] = $workloads_stmt->fetchAll(PDO::FETCH_ASSOC);
return $data;
} }
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)) {
return $matches[1];
}
return $class_name;
}
// --- Scoring and Placement Logic ---
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 = -1000;
$class_id = $lesson['type'] === 'horizontal_elective' ? null : $lesson['class_id'];
$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
// 1. Check basic availability
$slot_available = true;
if ($is_double) {
if ($period + 1 >= $periods_per_day) continue; // Not enough space for a double
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;
}
}
if (!$slot_available) continue;
foreach ($teachers_in_lesson as $teacher_id) {
if (isset($teacher_timetables[$teacher_id][$day][$period]) || isset($teacher_timetables[$teacher_id][$day][$period + 1])) {
$slot_available = false; break;
}
}
} else {
foreach ($class_ids_in_lesson as $cid) {
if (isset($class_timetables[$cid][$day][$period])) {
$slot_available = false; break;
}
}
if (!$slot_available) continue;
foreach ($teachers_in_lesson as $teacher_id) {
if (isset($teacher_timetables[$teacher_id][$day][$period])) {
$slot_available = false; break;
}
}
}
if (!$slot_available) continue;
// 2. Apply scoring rules
// A. Penalize placing the same subject on the same day
foreach ($class_ids_in_lesson as $cid) {
$lessons_on_day = 0;
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']) {
$lessons_on_day++;
}
}
$current_score -= $lessons_on_day * 50; // Heavy penalty for each existing lesson of same subject
}
// B. Penalize creating gaps for teachers and classes
foreach ($teachers_in_lesson as $teacher_id) {
// Check for gap before the lesson
if ($period > 0 && !isset($teacher_timetables[$teacher_id][$day][$period - 1])) {
if (isset($teacher_timetables[$teacher_id][$day][$period - 2])) $current_score -= 25; // Gap of 1
}
// Check for gap after the lesson
$after_period = $is_double ? $period + 2 : $period + 1;
if ($after_period < $periods_per_day && !isset($teacher_timetables[$teacher_id][$day][$after_period])) {
if (isset($teacher_timetables[$teacher_id][$day][$after_period + 1])) $current_score -= 25; // Gap of 1
}
}
foreach ($class_ids_in_lesson as $cid) {
if ($period > 0 && !isset($class_timetables[$cid][$day][$period - 1])) {
if (isset($class_timetables[$cid][$day][$period - 2])) $current_score -= 10;
}
$after_period = $is_double ? $period + 2 : $period + 1;
if ($after_period < $periods_per_day && !isset($class_timetables[$cid][$day][$after_period])) {
if (isset($class_timetables[$cid][$day][$after_period + 1])) $current_score -= 10;
}
}
// C. Penalize placing a double lesson of the same subject in parallel with another class
if ($is_double) {
$subject_name_to_check = $lesson['display_name'];
foreach ($all_class_ids as $cid) {
if (in_array($cid, $class_ids_in_lesson)) continue; // Don't check against itself
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 -= 200; // Very high penalty for parallel doubles of same subject
break;
}
}
}
// D. Prefer ends of day for double lessons
if ($is_double) {
if ($period == 0 || $period + 1 == $periods_per_day -1) {
$current_score += 10;
}
}
if ($current_score > $highest_score) {
$highest_score = $current_score;
$best_slot = ['day' => $day, 'period' => $period];
}
}
}
return $best_slot;
}
// --- Main Scheduling Engine --- // --- Main Scheduling Engine ---
function generate_timetable($workloads, $classes, $days_of_week, $periods_per_day) { function generate_timetable($data, $days_of_week) {
$periods = array_values(array_filter($data['timeslots'], function($ts) { return !$ts['is_break']; }));
$periods_per_day = count($periods);
// 1. Initialize Timetables
$class_timetables = []; $class_timetables = [];
foreach ($classes as $class) { $teacher_timetables = [];
foreach ($data['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 = []; foreach ($data['teachers'] as $teacher_id => $teacher_name) {
$teacher_timetables[$teacher_id] = array_fill(0, count($days_of_week), array_fill(0, $periods_per_day, null));
// --- Lesson Preparation ---
$all_lessons = [];
$workloads_by_grade_and_elective_group = [];
// 1. Group horizontal electives
foreach ($workloads as $workload) {
if (!empty($workload['elective_group_id'])) {
$grade_name = get_grade_from_class_name($workload['class_name']);
$workloads_by_grade_and_elective_group[$grade_name][$workload['elective_group_id']][] = $workload;
} else {
// This will be handled in the next step
}
} }
$processed_workload_ids = []; // 2. Prepare Lessons
foreach ($workloads_by_grade_and_elective_group as $grade_name => $elective_groups) { $lessons_to_schedule = [];
foreach ($elective_groups as $elective_group_id => $group_workloads) { $electives_by_group_grade = [];
$participating_class_ids = array_unique(array_column($group_workloads, 'class_id'));
if (count($participating_class_ids) > 1) { foreach ($data['workloads'] as $workload) {
$first = $group_workloads[0]; if (empty($workload['elective_group_id'])) { // Regular lesson
$block = [ for ($i = 0; $i < $workload['lessons_per_week']; $i++) {
'type' => 'horizontal_elective', $is_double = ($i == 0 && $workload['has_double_lesson'] && $workload['lessons_per_week'] >= 2);
'display_name' => $first['elective_group_name'], $lessons_to_schedule[] = [
'participating_class_ids' => $participating_class_ids, 'type' => 'single',
'lessons_per_week' => $first['lessons_per_week'], 'class_id' => $workload['class_id'],
'has_double_lesson' => $first['has_double_lesson'], 'subject_id' => $workload['subject_id'],
'component_lessons' => $group_workloads, 'teacher_ids' => [$workload['teacher_id']],
'priority' => 4 // Highest priority 'display_name' => $workload['subject_name'],
'teacher_name' => $workload['teacher_name'],
'is_double' => $is_double,
'is_elective' => false
]; ];
$all_lessons[] = $block; if ($is_double) $i++;
foreach($group_workloads as $w) $processed_workload_ids[] = $w['id'];
} }
} else { // Elective lesson part
$grade = preg_replace('/[^0-9]/', '', $workload['class_name']);
$key = $workload['elective_group_id'] . '_grade_' . $grade;
if (!isset($electives_by_group_grade[$key])) {
$electives_by_group_grade[$key] = [
'type' => 'elective_group',
'display_name' => $workload['elective_group_name'] . " (Form " . $grade . ")",
'lessons_per_week' => $workload['lessons_per_week'],
'has_double_lesson' => $workload['has_double_lesson'],
'is_elective' => true,
'component_lessons' => []
];
}
$electives_by_group_grade[$key]['component_lessons'][] = $workload;
} }
} }
// 2. Group remaining lessons (single, elective, and normal doubles) foreach ($electives_by_group_grade as $group) {
$remaining_workloads = array_filter($workloads, function($w) use ($processed_workload_ids) { for ($i = 0; $i < $group['lessons_per_week']; $i++) {
return !in_array($w['id'], $processed_workload_ids); $is_double = ($i == 0 && $group['has_double_lesson'] && $group['lessons_per_week'] >= 2);
$class_ids = array_unique(array_column($group['component_lessons'], 'class_id'));
$teacher_ids = array_unique(array_column($group['component_lessons'], 'teacher_id'));
$lessons_to_schedule[] = [
'type' => 'elective_group',
'class_id' => $class_ids, // Now an array of classes
'subject_id' => null, // Grouped subject
'teacher_ids' => $teacher_ids,
'display_name' => $group['display_name'],
'is_double' => $is_double,
'is_elective' => true,
'component_lessons' => $group['component_lessons']
];
if ($is_double) $i++;
}
}
// 3. Sort lessons (place doubles and electives first)
usort($lessons_to_schedule, function($a, $b) {
if ($b['is_double'] != $a['is_double']) return $b['is_double'] <=> $a['is_double'];
$a_count = is_array($a['class_id']) ? count($a['class_id']) : 1;
$b_count = is_array($b['class_id']) ? count($b['class_id']) : 1;
return $b_count <=> $a_count;
}); });
$workloads_by_class = []; // 4. Placement
foreach ($remaining_workloads as $workload) { foreach ($lessons_to_schedule as $lesson) {
$workloads_by_class[$workload['class_id']][] = $workload; $best_slot = find_best_slot_for_lesson($lesson, $class_timetables, $teacher_timetables, $days_of_week, $periods_per_day);
}
foreach ($workloads_by_class as $class_id => $class_workloads) {
$elective_groups = [];
$individual_lessons = [];
foreach ($class_workloads as $workload) {
if (!empty($workload['elective_group_id'])) {
$elective_groups[$workload['elective_group_id']][] = $workload;
} else {
$individual_lessons[] = $workload;
}
}
foreach ($elective_groups as $group_id => $group_workloads) {
$first = $group_workloads[0];
$all_lessons[] = [
'type' => 'elective', 'class_id' => $class_id, 'display_name' => $first['elective_group_name'],
'lessons_per_week' => $first['lessons_per_week'], 'has_double_lesson' => $first['has_double_lesson'],
'component_lessons' => $group_workloads,
'priority' => 3
];
}
foreach ($individual_lessons as $workload) {
$all_lessons[] = [
'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],
'priority' => $workload['has_double_lesson'] ? 2 : 1
];
}
}
// 3. Explode lessons into single and double instances
$final_lesson_list = [];
foreach ($all_lessons as $lesson_block) {
$num_doubles = 0;
$num_singles = $lesson_block['lessons_per_week'];
if ($lesson_block['has_double_lesson'] && $lesson_block['lessons_per_week'] >= 2) {
$num_doubles = 1;
$num_singles -= 2;
}
for ($i=0; $i < $num_doubles; $i++) {
$final_lesson_list[] = ['is_double' => true, 'lesson_details' => $lesson_block];
}
for ($i=0; $i < $num_singles; $i++) {
$final_lesson_list[] = ['is_double' => false, 'lesson_details' => $lesson_block];
}
}
// 4. Sort by priority (desc) and then shuffle to vary timetable
usort($final_lesson_list, function($a, $b) {
$prio_a = $a['lesson_details']['priority'];
$prio_b = $b['lesson_details']['priority'];
if ($prio_a != $prio_b) return $prio_b - $prio_a;
if ($a['is_double'] != $b['is_double']) return $b['is_double'] - $a['is_double'];
return rand(-1, 1);
});
// --- Placement using Scoring ---
$all_class_ids = array_column($classes, 'id');
foreach ($final_lesson_list as $lesson_item) {
$lesson = $lesson_item['lesson_details'];
$is_double = $lesson_item['is_double'];
$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) { if ($best_slot) {
$day = $best_slot['day']; $day = $best_slot['day'];
$period = $best_slot['period']; $period = $best_slot['period'];
$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')); if ($lesson['type'] === 'single') {
$class_id = $lesson['class_id'];
$teacher_id = $lesson['teacher_ids'][0];
$lesson_data = [
'subject_name' => $lesson['display_name'],
'teacher_name' => $lesson['teacher_name'],
'subject_id' => $lesson['subject_id'],
'teacher_ids' => $lesson['teacher_ids'],
'is_double' => $lesson['is_double'],
'is_elective' => false,
];
$subject_id = null; $class_timetables[$class_id][$day][$period] = $lesson_data;
$teacher_id = null; $teacher_timetables[$teacher_id][$day][$period] = true;
// For single-teacher, single-subject lessons, store the IDs directly if ($lesson['is_double']) {
if (count($lesson['component_lessons']) === 1) { $class_timetables[$class_id][$day][$period + 1] = $lesson_data;
$subject_id = $lesson['component_lessons'][0]['subject_id']; $teacher_timetables[$teacher_id][$day][$period + 1] = true;
$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,
'is_elective' => $lesson['type'] === 'elective',
'is_horizontal_elective' => $lesson['type'] === 'horizontal_elective',
'component_lessons' => $lesson['component_lessons']
];
if ($is_double) {
foreach ($class_ids_to_place as $cid) {
$class_timetables[$cid][$day][$period] = $lesson_info;
$class_timetables[$cid][$day][$period + 1] = $lesson_info;
} }
foreach ($teachers_to_place as $tid) { } else { // Elective Group
$teacher_timetables[$tid][$day][$period] = true; foreach($lesson['component_lessons'] as $comp_lesson) {
$teacher_timetables[$tid][$day][$period + 1] = true; $class_id = $comp_lesson['class_id'];
$teacher_id = $comp_lesson['teacher_id'];
$lesson_data = [
'subject_name' => $comp_lesson['subject_name'],
'teacher_name' => $comp_lesson['teacher_name'],
'subject_id' => $comp_lesson['subject_id'],
'teacher_ids' => [$teacher_id], // Specific teacher for this part
'is_double' => $lesson['is_double'],
'is_elective' => true,
'group_name' => $lesson['display_name']
];
$class_timetables[$class_id][$day][$period] = $lesson_data;
$teacher_timetables[$teacher_id][$day][$period] = true;
if ($lesson['is_double']) {
$class_timetables[$class_id][$day][$period + 1] = $lesson_data;
$teacher_timetables[$teacher_id][$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;
} }
} }
} }
return $class_timetables; return $class_timetables;
} }
function find_best_slot_for_lesson($lesson, &$class_timetables, &$teacher_timetables, $days_of_week, $periods_per_day) {
$is_double = $lesson['is_double'];
$class_ids = is_array($lesson['class_id']) ? $lesson['class_id'] : [$lesson['class_id']];
$teacher_ids = $lesson['teacher_ids'];
for ($day = 0; $day < count($days_of_week); $day++) {
for ($period = 0; $period < $periods_per_day; $period++) {
if ($is_double && $period + 1 >= $periods_per_day) continue;
// Check availability for all classes and teachers
$slot_available = true;
foreach ($class_ids as $cid) {
if ($class_timetables[$cid][$day][$period] !== null || ($is_double && $class_timetables[$cid][$day][$period + 1] !== null)) {
$slot_available = false; break;
}
}
if (!$slot_available) continue;
foreach ($teacher_ids as $tid) {
if ($teacher_timetables[$tid][$day][$period] !== null || ($is_double && $teacher_timetables[$tid][$day][$period + 1] !== null)) {
$slot_available = false; break;
}
}
if ($slot_available) {
return ['day' => $day, 'period' => $period]; // Return first available slot (simplistic)
}
}
}
return null; // No slot found
}
// --- Timetable Persistence --- // --- Timetable Persistence ---
function save_timetable($pdo, $class_timetables, $timeslots) { function save_timetable($pdo, $class_timetables, $timeslots) {
$pdo->exec('TRUNCATE TABLE schedules'); try {
$pdo->exec('TRUNCATE TABLE schedule_teachers'); $pdo->beginTransaction();
$pdo->exec('SET FOREIGN_KEY_CHECKS=0');
$pdo->exec('TRUNCATE TABLE schedule_teachers');
$pdo->exec('TRUNCATE TABLE schedules');
$pdo->exec('SET FOREIGN_KEY_CHECKS=1');
$stmt = $pdo->prepare( $stmt = $pdo->prepare(
'INSERT INTO schedules (class_id, day_of_week, timeslot_id, subject_id, lesson_display_name, teacher_display_name, is_double, is_elective, is_horizontal_elective) ' . 'INSERT INTO schedules (class_id, day_of_week, timeslot_id, subject_id, lesson_display_name, teacher_display_name, is_double, is_elective) ' .
'VALUES (:class_id, :day_of_week, :timeslot_id, :subject_id, :lesson_display_name, :teacher_display_name, :is_double, :is_elective, :is_horizontal_elective)' 'VALUES (:class_id, :day_of_week, :timeslot_id, :subject_id, :lesson_display_name, :teacher_display_name, :is_double, :is_elective)'
); );
$teacher_stmt = $pdo->prepare( $teacher_stmt = $pdo->prepare(
'INSERT INTO schedule_teachers (schedule_id, teacher_id) VALUES (:schedule_id, :teacher_id)' 'INSERT INTO schedule_teachers (schedule_id, teacher_id) VALUES (:schedule_id, :teacher_id)'
); );
$lesson_periods = array_values(array_filter($timeslots, function($ts) { return !$ts['is_break']; })); $lesson_periods = array_values(array_filter($timeslots, function($ts) { return !$ts['is_break']; }));
foreach ($class_timetables as $class_id => $day_schedule) { foreach ($class_timetables as $class_id => $day_schedule) {
foreach ($day_schedule as $day_idx => $period_schedule) { foreach ($day_schedule as $day_idx => $period_schedule) {
$processed_periods = []; $processed_periods = [];
foreach ($period_schedule as $period_idx => $lesson) { foreach ($period_schedule as $period_idx => $lesson) {
if ($lesson && !in_array($period_idx, $processed_periods)) { if ($lesson && !in_array($period_idx, $processed_periods)) {
if (!isset($lesson_periods[$period_idx])) continue; $timeslot_id = $lesson_periods[$period_idx]['id'];
$timeslot_id = $lesson_periods[$period_idx]['id'];
$display_name = $lesson['is_elective'] ? ($lesson['group_name'] . ' / ' . $lesson['subject_name']) : $lesson['subject_name'];
$stmt->execute([ $stmt->execute([
':class_id' => $class_id, ':class_id' => $class_id,
':day_of_week' => $day_idx, ':day_of_week' => $day_idx,
':timeslot_id' => $timeslot_id, ':timeslot_id' => $timeslot_id,
':subject_id' => $lesson['subject_id'], ':subject_id' => $lesson['subject_id'],
':lesson_display_name' => $lesson['subject_name'], ':lesson_display_name' => $display_name,
':teacher_display_name' => $lesson['teacher_name'], ':teacher_display_name' => $lesson['teacher_name'],
':is_double' => (int)$lesson['is_double'], ':is_double' => (int)$lesson['is_double'],
':is_elective' => (int)$lesson['is_elective'], ':is_elective' => (int)$lesson['is_elective']
':is_horizontal_elective' => (int)$lesson['is_horizontal_elective'] ]);
]);
$schedule_id = $pdo->lastInsertId(); $schedule_id = $pdo->lastInsertId();
if (!empty($lesson['component_lessons'])) { foreach ($lesson['teacher_ids'] as $teacher_id) {
$teacher_ids = array_unique(array_column($lesson['component_lessons'], 'teacher_id')); $teacher_stmt->execute([':schedule_id' => $schedule_id, ':teacher_id' => $teacher_id]);
foreach ($teacher_ids as $teacher_id) {
if ($teacher_id) {
$teacher_stmt->execute([
':schedule_id' => $schedule_id,
':teacher_id' => $teacher_id
]);
}
} }
}
$processed_periods[] = $period_idx; $processed_periods[] = $period_idx;
if ($lesson['is_double']) { if ($lesson['is_double']) {
$processed_periods[] = $period_idx + 1; $processed_periods[] = $period_idx + 1;
}
} }
} }
} }
} }
$pdo->commit();
} catch (Exception $e) {
$pdo->rollBack();
error_log("Timetable save failed: " . $e->getMessage());
} }
} }
@ -376,13 +264,8 @@ function get_timetable_from_db($pdo, $classes, $timeslots) {
$saved_lessons = $stmt->fetchAll(PDO::FETCH_ASSOC); $saved_lessons = $stmt->fetchAll(PDO::FETCH_ASSOC);
if (empty($saved_lessons)) return []; if (empty($saved_lessons)) return [];
// Get teacher associations
$schedule_teachers_stmt = $pdo->query('SELECT st.schedule_id, t.name, t.id FROM schedule_teachers st JOIN teachers t ON st.teacher_id = t.id');
$schedule_teachers = $schedule_teachers_stmt->fetchAll(PDO::FETCH_GROUP | PDO::FETCH_ASSOC);
$days_of_week = ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday']; $days_of_week = ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday'];
$periods = array_filter($timeslots, function($ts) { return !$ts['is_break']; }); $periods = array_values(array_filter($timeslots, function($ts) { return !$ts['is_break']; }));
$periods_per_day = count($periods); $periods_per_day = count($periods);
$class_timetables = []; $class_timetables = [];
@ -390,9 +273,8 @@ function get_timetable_from_db($pdo, $classes, $timeslots) {
$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));
} }
$lesson_periods = array_values($periods);
$timeslot_id_to_period_idx = []; $timeslot_id_to_period_idx = [];
foreach($lesson_periods as $idx => $period) { foreach($periods as $idx => $period) {
$timeslot_id_to_period_idx[$period['id']] = $idx; $timeslot_id_to_period_idx[$period['id']] = $idx;
} }
@ -402,25 +284,15 @@ function get_timetable_from_db($pdo, $classes, $timeslots) {
$timeslot_id = $lesson['timeslot_id']; $timeslot_id = $lesson['timeslot_id'];
if (!isset($timeslot_id_to_period_idx[$timeslot_id]) || !isset($class_timetables[$class_id])) continue; if (!isset($timeslot_id_to_period_idx[$timeslot_id]) || !isset($class_timetables[$class_id])) continue;
$period_idx = $timeslot_id_to_period_idx[$timeslot_id]; $period_idx = $timeslot_id_to_period_idx[$timeslot_id];
$teachers_for_lesson = $schedule_teachers[$lesson['id']] ?? [];
$teacher_name = $lesson['teacher_display_name'];
if (count($teachers_for_lesson) > 1) {
$teacher_name = 'Multiple';
} elseif (count($teachers_for_lesson) === 1) {
$teacher_name = $teachers_for_lesson[0]['name'];
}
$lesson_data = [ $lesson_data = [
'id' => $lesson['id'], 'id' => $lesson['id'],
'subject_id' => $lesson['subject_id'],
'subject_name' => $lesson['lesson_display_name'], 'subject_name' => $lesson['lesson_display_name'],
'teacher_name' => $teacher_name, 'teacher_name' => $lesson['teacher_display_name'],
'is_double' => (bool)$lesson['is_double'], 'is_double' => (bool)$lesson['is_double'],
'is_elective' => (bool)$lesson['is_elective'], 'is_elective' => (bool)$lesson['is_elective'],
'is_horizontal_elective' => (bool)$lesson['is_horizontal_elective']
]; ];
$class_timetables[$class_id][$day_idx][$period_idx] = $lesson_data; $class_timetables[$class_id][$day_idx][$period_idx] = $lesson_data;
@ -433,17 +305,19 @@ function get_timetable_from_db($pdo, $classes, $timeslots) {
// --- Main Logic --- // --- Main Logic ---
$pdoconn = db(); $pdoconn = db();
$workloads = get_workloads($pdoconn); $all_data = get_all_data($pdoconn);
$classes = get_classes($pdoconn); $classes = $all_data['classes'];
$timeslots = get_timeslots($pdoconn); $timeslots = $all_data['timeslots'];
$workloads = $all_data['workloads'];
$days_of_week = ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday']; $days_of_week = ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday'];
$periods = array_filter($timeslots, function($timeslot) { return !$timeslot['is_break']; }); $class_timetables = [];
$periods_per_day = count($periods);
if (isset($_POST['generate'])) { if (isset($_POST['generate'])) {
$class_timetables = generate_timetable($workloads, $classes, $days_of_week, $periods_per_day); if (!empty($workloads)) {
save_timetable($pdoconn, $class_timetables, $timeslots); $class_timetables = generate_timetable($all_data, $days_of_week);
save_timetable($pdoconn, $class_timetables, $timeslots);
}
} else { } else {
$class_timetables = get_timetable_from_db($pdoconn, $classes, $timeslots); $class_timetables = get_timetable_from_db($pdoconn, $classes, $timeslots);
} }
@ -456,81 +330,34 @@ if (isset($_POST['generate'])) {
<title>Timetable - Haki Schedule</title> <title>Timetable - Haki Schedule</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet"> <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
<link rel="stylesheet" href="assets/css/custom.css?v=<?php echo time(); ?>"> <link rel="stylesheet" href="assets/css/custom.css?v=<?php echo time(); ?>">
<script src="https://cdn.jsdelivr.net/npm/sortablejs@latest/Sortable.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/html2canvas/1.4.1/html2canvas.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jspdf/2.5.1/jspdf.umd.min.js"></script>
<style> <style>
.lesson.is-elective { background-color: #e0f7fa; border-left: 3px solid #00bcd4; }
.lesson.is-double { background-color: #fce4ec; }
@media print { @media print {
body * { body * { visibility: hidden; }
visibility: hidden; #timetables-container, #timetables-container * { visibility: visible; }
} #timetables-container { position: absolute; left: 0; top: 0; width: 100%; }
#timetables-container, #timetables-container * { .card { border: 1px solid #dee2e6 !important; box-shadow: none !important; }
visibility: visible;
}
#timetables-container {
position: absolute;
left: 0;
top: 0;
width: 100%;
}
.card {
border: 1px solid #dee2e6 !important;
box-shadow: none !important;
}
} }
</style> </style>
</head> </head>
<body> <body>
<nav class="navbar navbar-expand-lg navbar-light bg-white sticky-top shadow-sm"> <?php include 'includes/navbar.php'; ?>
<div class="container">
<a class="navbar-brand fw-bold" href="/">Haki Schedule</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarNav">
<ul class="navbar-nav ms-auto">
<li class="nav-item"><a class="nav-link" href="/">Home</a></li>
<?php if (isset($_SESSION['user_id'])): ?>
<li class="nav-item dropdown">
<a class="nav-link dropdown-toggle" href="#" id="manageDropdown" role="button" data-bs-toggle="dropdown" aria-expanded="false">
Manage
</a>
<ul class="dropdown-menu" aria-labelledby="manageDropdown">
<li><a class="dropdown-item" href="/admin_classes.php">Classes</a></li>
<li><a class="dropdown-item" href="/admin_subjects.php">Subjects</a></li>
<li><a class="dropdown-item" href="/admin_teachers.php">Teachers</a></li>
<li><a class="dropdown-item" href="/admin_workloads.php">Workloads</a></li>
<li><a class="dropdown-item" href="/admin_timeslots.php">Timeslots</a></li>
</ul>
</li>
<li class="nav-item"><a class="nav-link active" href="/timetable.php">Class Timetable</a></li>
<li class="nav-item"><a class="nav-link" href="/teacher_timetable.php">Teacher Timetable</a></li>
<li class="nav-item"><a class="nav-link" href="/logout.php">Logout</a></li>
<?php else: ?>
<li class="nav-item"><a class="nav-link" href="/demo.php">Demo</a></li>
<li class="nav-item"><a class="nav-link" href="/login.php">Login</a></li>
<li class="nav-item"><a class="nav-link" href="/register.php">Register</a></li>
<?php endif; ?>
</ul>
</div>
</div>
</nav>
<div class="container mt-4"> <div class="container mt-4">
<div class="d-flex justify-content-between align-items-center mb-4"> <div class="d-flex justify-content-between align-items-center mb-4">
<h1>Class Timetable</h1> <h1>Class Timetable</h1>
<div class="d-flex gap-2"> <div class="d-flex gap-2">
<form method="POST" action=""> <form method="POST" action="">
<button type="submit" name="generate" class="btn btn-primary">Generate Timetable</button> <button type="submit" name="generate" class="btn btn-primary" <?php echo empty($workloads) ? 'disabled' : ''; ?>>Generate Timetable</button>
</form> </form>
<button id="print-btn" class="btn btn-secondary">Print</button> <button id="print-btn" class="btn btn-secondary">Print</button>
<button id="download-btn" class="btn btn-secondary">Download as PDF</button>
</div> </div>
</div> </div>
<div id="timetables-container"> <div id="timetables-container">
<?php if (!empty($class_timetables)): ?> <?php if (!empty($class_timetables)) : ?>
<?php foreach ($classes as $class): ?> <?php foreach ($classes as $class) : ?>
<?php if (!isset($class_timetables[$class['id']])) continue; ?> <?php if (!isset($class_timetables[$class['id']])) continue; ?>
<div class="timetable-wrapper mb-5"> <div class="timetable-wrapper mb-5">
<h3 class="mt-5"><?php echo htmlspecialchars($class['name']); ?></h3> <h3 class="mt-5"><?php echo htmlspecialchars($class['name']); ?></h3>
@ -540,35 +367,34 @@ if (isset($_POST['generate'])) {
<thead> <thead>
<tr> <tr>
<th style="width: 12%;">Time</th> <th style="width: 12%;">Time</th>
<?php foreach ($days_of_week as $day): ?> <?php foreach ($days_of_week as $day) : ?>
<th><?php echo $day; ?></th> <th><?php echo $day; ?></th>
<?php endforeach; ?> <?php endforeach; ?>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
<?php <?php
$period_idx = 0; $period_idx = 0;
foreach ($timeslots as $timeslot): foreach ($timeslots as $timeslot) :
?> ?>
<tr> <tr>
<td> <td>
<strong><?php echo htmlspecialchars($timeslot['name']); ?></strong><br> <strong><?php echo htmlspecialchars($timeslot['name']); ?></strong><br>
<small class="text-muted"><?php echo date("g:i A", strtotime($timeslot['start_time'])); ?> - <?php echo date("g:i A", strtotime($timeslot['end_time'])); ?></small> <small class="text-muted"><?php echo date("g:i A", strtotime($timeslot['start_time'])); ?> - <?php echo date("g:i A", strtotime($timeslot['end_time'])); ?></small>
</td> </td>
<?php if ($timeslot['is_break']): ?> <?php if ($timeslot['is_break']) : ?>
<td colspan="<?php echo count($days_of_week); ?>" class="text-center table-secondary"><strong>Break</strong></td> <td colspan="<?php echo count($days_of_week); ?>" class="text-center table-secondary"><strong>Break</strong></td>
<?php else: ?> <?php else : ?>
<?php for ($day_idx = 0; $day_idx < count($days_of_week); $day_idx++): ?> <?php for ($day_idx = 0; $day_idx < count($days_of_week); $day_idx++) : ?>
<td class="timetable-slot" data-class-id="<?php echo $class['id']; ?>" data-day="<?php echo $day_idx; ?>" data-timeslot-id="<?php echo $timeslot['id']; ?>"> <td class="timetable-slot">
<?php <?php
$lesson = $class_timetables[$class['id']][$day_idx][$period_idx] ?? null; $lesson = $class_timetables[$class['id']][$day_idx][$period_idx] ?? null;
if ($lesson): if ($lesson) :
$class_str = ''; $css_class = 'lesson p-1';
if ($lesson['is_horizontal_elective']) $class_str = 'bg-light-purple'; if ($lesson['is_elective']) $css_class .= ' is-elective';
elseif ($lesson['is_elective']) $class_str = 'bg-light-green'; if ($lesson['is_double']) $css_class .= ' is-double';
elseif ($lesson['is_double']) $class_str = 'bg-light-blue';
?> ?>
<div class="lesson p-1 <?php echo $class_str; ?>" data-lesson-id="<?php echo $lesson['id']; ?>" draggable="true"> <div class="<?php echo $css_class; ?>" data-lesson-id="<?php echo $lesson['id'] ?? ''; ?>">
<strong><?php echo htmlspecialchars($lesson['subject_name']); ?></strong><br> <strong><?php echo htmlspecialchars($lesson['subject_name']); ?></strong><br>
<small><?php echo htmlspecialchars($lesson['teacher_name']); ?></small> <small><?php echo htmlspecialchars($lesson['teacher_name']); ?></small>
</div> </div>
@ -585,11 +411,11 @@ if (isset($_POST['generate'])) {
</div> </div>
</div> </div>
<?php endforeach; ?> <?php endforeach; ?>
<?php else: ?> <?php else : ?>
<div class="alert alert-info"> <div class="alert alert-info">
<?php if (empty($workloads)): ?> <?php if (empty($workloads)) : ?>
No workloads found. Please add classes, subjects, teachers, and workloads in the "Manage" section first. No workloads found. Please add classes, subjects, teachers, and workloads in the "Manage" section first. The "Generate Timetable" button is disabled.
<?php else: ?> <?php else : ?>
Click the "Generate Timetable" button to create a schedule. Click the "Generate Timetable" button to create a schedule.
<?php endif; ?> <?php endif; ?>
</div> </div>
@ -601,90 +427,9 @@ if (isset($_POST['generate'])) {
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script>
<script> <script>
document.addEventListener('DOMContentLoaded', function () { document.addEventListener('DOMContentLoaded', function () {
// Drag and drop
const slots = document.querySelectorAll('.timetable-slot');
slots.forEach(function (slot) {
new Sortable(slot, {
group: 'lessons',
animation: 150,
onEnd: function (evt) {
const itemEl = evt.item;
const to = evt.to;
const lessonId = itemEl.dataset.lessonId;
const toClassId = to.dataset.classId;
const toDay = to.dataset.day;
const toTimeslotId = to.dataset.timeslotId;
fetch('/api/move_lesson.php', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
lesson_id: lessonId,
to_class_id: toClassId,
to_day: toDay,
to_timeslot_id: toTimeslotId,
}),
})
.then(response => response.json())
.then(data => {
if (!data.success) {
console.error('Error moving lesson:', data.error);
alert('Error moving lesson: ' + data.error);
location.reload();
}
})
.catch((error) => {
console.error('Error:', error);
alert('An unexpected error occurred.');
location.reload();
});
}
});
});
// Print and Download
const { jsPDF } = window.jspdf;
document.getElementById('print-btn').addEventListener('click', function () { document.getElementById('print-btn').addEventListener('click', function () {
window.print(); window.print();
}); });
document.getElementById('download-btn').addEventListener('click', function () {
const container = document.getElementById('timetables-container');
const doc = new jsPDF({
orientation: 'l',
unit: 'pt',
format: 'a4'
});
const margin = 20;
const pageHeight = doc.internal.pageSize.getHeight() - (margin * 2);
const timetableWrappers = container.querySelectorAll('.timetable-wrapper');
let yPos = margin;
let pageNum = 1;
function addPageContent(element, isFirstPage) {
return html2canvas(element, { scale: 2 }).then(canvas => {
const imgData = canvas.toDataURL('image/png');
const imgWidth = doc.internal.pageSize.getWidth() - (margin * 2);
const imgHeight = canvas.height * imgWidth / canvas.width;
if (!isFirstPage) {
doc.addPage();
}
doc.addImage(imgData, 'PNG', margin, margin, imgWidth, imgHeight);
});
}
let promise = Promise.resolve();
timetableWrappers.forEach((wrapper, index) => {
promise = promise.then(() => addPageContent(wrapper, index === 0));
});
promise.then(() => {
doc.save('class-timetables.pdf');
});
});
}); });
</script> </script>
</body> </body>