/
home
/
techb158
/
workloadmatch.com
/
Manager
/
/home/techb158/workloadmatch.com/Manager
mkdir
upload
Name
Size
Mode
Actions
core/
-
0755
rm
css/
-
0755
rm
data/
-
0755
rm
fonts/
-
0755
rm
Inc/
-
0755
rm
js/
-
0755
rm
php/
-
0755
rm
addcourse.php
18003
0644
edit
dl
rm
addcoursesession.php
14424
0644
edit
dl
rm
addcoursesession_bak.php.disabled
20860
0644
edit
dl
rm
addcoursetime.php
17564
0644
edit
dl
rm
addgroup.php
23465
0644
edit
dl
rm
addgroup1.php
20463
0644
edit
dl
rm
addholiday.php
18283
0644
edit
dl
rm
addmanager.php
19060
0644
edit
dl
rm
addremediation.php
18140
0644
edit
dl
rm
addretake.php
22284
0644
edit
dl
rm
adduser.php
30485
0644
edit
dl
rm
adduser1.php
23166
0644
edit
dl
rm
Add_Course_Session.php
6931
0644
edit
dl
rm
Add_Course_Session_bak.php.disabled
6931
0644
edit
dl
rm
add_event.php
2004
0644
edit
dl
rm
Add_Teacher_Unavailability.php
1724
0644
edit
dl
rm
AI_Generate_Schedule.php
11031
0644
edit
dl
rm
ai_schedule.php
19975
0644
edit
dl
rm
ajax_fetch_schedule.php
3856
0644
edit
dl
rm
ajax_get_group_slots.php
897
0644
edit
dl
rm
ajax_replace_saturday.php
31674
0644
edit
dl
rm
ajax_replace_saturday_backup_latest.php
31474
0644
edit
dl
rm
ajax_replace_saturday_bak.php.disabled
31812
0644
edit
dl
rm
ajax_swap_courses.php
4394
0644
edit
dl
rm
approve_teacher_absence.php
2230
0644
edit
dl
rm
approve_teacher_unavailability.php
2274
0644
edit
dl
rm
assign_course_group.php
18115
0644
edit
dl
rm
assign_course_group1.php
20752
0644
edit
dl
rm
Assign_Course_Report.php
2318
0644
edit
dl
rm
assign_teacher_legacy.php
16490
0644
edit
dl
rm
assign_teacher_manual.php
17551
0644
edit
dl
rm
assign_teacher_manual_bak.php.disabled
22718
0644
edit
dl
rm
assign_teacher_manual_force_conflicts.php
22749
0644
edit
dl
rm
availableteacherreport.php
12595
0644
edit
dl
rm
Available_Teacher_Report.php
3554
0644
edit
dl
rm
batch_schedule.php
9582
0644
edit
dl
rm
calendar.php
31502
0644
edit
dl
rm
calendar_schedule.php
8086
0644
edit
dl
rm
chat.php
16437
0644
edit
dl
rm
chat_bak.php.disabled
10265
0644
edit
dl
rm
Check_if_Teacher_Report.php
1639
0644
edit
dl
rm
complete_schedule_report_by_teacher.php
19799
0644
edit
dl
rm
courseflagtime.php
3518
0644
edit
dl
rm
Create_Group_Schedule_Algorithm.php
3939
0644
edit
dl
rm
Create_Schedule_Algorithm.php
90629
0644
edit
dl
rm
Create_Schedule_Algorithm_1.php
42430
0644
edit
dl
rm
Create_Schedule_Algorithm_2.php
78532
0644
edit
dl
rm
Create_Schedule_Algorithm_2_combined.php
92157
0644
edit
dl
rm
Create_Schedule_Algorithm_2_combined_both_slots.php
94546
0644
edit
dl
rm
Create_Schedule_Algorithm_2_combined_both_slots_bak.php.disabled
94611
0644
edit
dl
rm
Create_Schedule_Algorithm_2_fixed.php
78869
0644
edit
dl
rm
Create_Schedule_Algorithm_3.php
89594
0644
edit
dl
rm
Create_Schedule_Algorithm_4.php
89594
0644
edit
dl
rm
dashboard.php
21103
0644
edit
dl
rm
deletecoursesession.php
8591
0644
edit
dl
rm
deleteschedulefromgroup.php
17424
0644
edit
dl
rm
delete_course.php
607
0644
edit
dl
rm
delete_course_from_teacher.php
5126
0644
edit
dl
rm
delete_course_session_handler.php
791
0644
edit
dl
rm
delete_duplicate_assignments.php
2537
0644
edit
dl
rm
delete_event.php
1255
0644
edit
dl
rm
delete_group.php
1913
0644
edit
dl
rm
delete_holiday.php
743
0644
edit
dl
rm
delete_retake.php
1324
0644
edit
dl
rm
delete_schedule_from_group.php
1940
0644
edit
dl
rm
delete_teacher_absence.php
1015
0644
edit
dl
rm
delete_teacher_level.php
828
0644
edit
dl
rm
delete_teacher_unavailability.php
1350
0644
edit
dl
rm
delete_timeslot.php
626
0644
edit
dl
rm
display_schedule.php
18662
0644
edit
dl
rm
do_remove_saturday.php
6402
0644
edit
dl
rm
duplicate_assignment_report.php
9423
0644
edit
dl
rm
edit_event.php
1663
0644
edit
dl
rm
examschedulereport.php
16510
0644
edit
dl
rm
Exam_Schedule_Report.php
2762
0644
edit
dl
rm
export_absences.php
1258
0644
edit
dl
rm
fetch_all_scheduled_courses.php
6325
0644
edit
dl
rm
fetch_courses.php
9373
0644
edit
dl
rm
fetch_courses_for_program.php
815
0644
edit
dl
rm
fetch_courses_for_program_by_schedule.php
4047
0644
edit
dl
rm
fetch_courses_teacher_preference.php
3954
0644
edit
dl
rm
fetch_course_for_added_group.php
1046
0644
edit
dl
rm
fetch_course_hours.php
714
0644
edit
dl
rm
fetch_course_sessions.php
1405
0644
edit
dl
rm
fetch_dashboard_charts.php
4282
0644
edit
dl
rm
fetch_disabled_dates.php
543
0644
edit
dl
rm
fetch_duplicate_assignments.php
4902
0644
edit
dl
rm
fetch_events.php
1014
0644
edit
dl
rm
fetch_exist_dates.php
1141
0644
edit
dl
rm
fetch_groups_exist_in_schedule.php
959
0644
edit
dl
rm
fetch_groups_for_program.php
871
0644
edit
dl
rm
fetch_groups_for_program_legacy.php
775
0644
edit
dl
rm
fetch_group_timeslot.php
878
0644
edit
dl
rm
fetch_lockup_courses-1-1.php
14086
0644
edit
dl
rm
fetch_lockup_courses.php
14587
0644
edit
dl
rm
fetch_lockup_courses_1.php
13638
0644
edit
dl
rm
fetch_lockup_courses_2.php
13850
0644
edit
dl
rm
fetch_lockup_courses_bak.php
8899
0644
edit
dl
rm
fetch_reserve_courses.php
7567
0644
edit
dl
rm
fetch_retake_courses.php
957
0644
edit
dl
rm
fetch_scheduled_courses.php
2254
0644
edit
dl
rm
fetch_schedule_report_by_group.php
5554
0644
edit
dl
rm
fetch_teachers_by_group.php
4888
0644
edit
dl
rm
fetch_teachers_by_group_legacy.php
2421
0644
edit
dl
rm
fetch_teachers_by_program.php
1871
0644
edit
dl
rm
fetch_teachers_for_group.php
3265
0644
edit
dl
rm
fetch_teacher_assignment.php
25405
0644
edit
dl
rm
fetch_teacher_by_course.php
3982
0644
edit
dl
rm
fetch_teacher_conflicts-1-1.php
5793
0644
edit
dl
rm
fetch_teacher_conflicts.php
5766
0644
edit
dl
rm
fetch_teacher_conflicts_only-1-1.php
8275
0644
edit
dl
rm
fetch_teacher_conflicts_only-1.php
8737
0644
edit
dl
rm
fetch_teacher_conflicts_only.php
7725
0644
edit
dl
rm
fetch_teacher_conflicts_only_force_conflicts.php
7287
0644
edit
dl
rm
fetch_teacher_conflicts_only_legacy.php
6702
0644
edit
dl
rm
fetch_teacher_conflicts_showonly-1-1.php
10656
0644
edit
dl
rm
fetch_teacher_conflicts_showonly.php
10531
0644
edit
dl
rm
fetch_teacher_load.php
1633
0644
edit
dl
rm
fetch_teacher_load_legacy.php
1144
0644
edit
dl
rm
fetch_teacher_prefrence_courses.php
1848
0644
edit
dl
rm
fetch_teacher_prefrence_courses_accounting.php
1647
0644
edit
dl
rm
fetch_workload_data.php
4520
0644
edit
dl
rm
fetch_workload_hours-1.php
2291
0644
edit
dl
rm
fetch_workload_hours-2.php
2147
0644
edit
dl
rm
fetch_workload_hours.php
2341
0644
edit
dl
rm
find_replacement-1-1.php
18603
0644
edit
dl
rm
find_replacement.php
18313
0644
edit
dl
rm
Generate Group Schedule.php
17330
0644
edit
dl
rm
Generate Group Schedule 1.php
18490
0644
edit
dl
rm
generateschedule.php
15946
0644
edit
dl
rm
Generate_Schedule.php
34743
0644
edit
dl
rm
generate_schedule_handler.php
7375
0644
edit
dl
rm
Generate_Schedule_Script.php
30729
0644
edit
dl
rm
getlang.php
351
0644
edit
dl
rm
get_groups_by_program.php
629
0644
edit
dl
rm
get_schedule_ai.php
37712
0644
edit
dl
rm
get_schedule_report.php
2471
0644
edit
dl
rm
group_schedule_form.php
17337
0644
edit
dl
rm
Lockup_to_Assign.php
12052
0644
edit
dl
rm
Lockup_to_Assign_bak.php
11301
0644
edit
dl
rm
main-header-inc.php
7794
0644
edit
dl
rm
main-header.php
19656
0644
edit
dl
rm
Preference_Accounting.php
9810
0644
edit
dl
rm
preference_accounting.php
9603
0644
edit
dl
rm
preference_secretarial.php
9810
0644
edit
dl
rm
profile.php
20976
0644
edit
dl
rm
remove_saturday_course.php
19313
0644
edit
dl
rm
report_absence.php
21056
0644
edit
dl
rm
report_absence_1.php
6385
0644
edit
dl
rm
reserve_course.php
19285
0644
edit
dl
rm
rewrite_ajax_replace_saturday.php
13189
0644
edit
dl
rm
rewrite_Create_Schedule_Algorithm.php
90672
0644
edit
dl
rm
rewrite_Create_Schedule_Algorithm_2.php
91268
0644
edit
dl
rm
save_schedule.php
3102
0644
edit
dl
rm
save_schedule_group_course.php
26301
0644
edit
dl
rm
save_teacher_preference.php
2216
0644
edit
dl
rm
schedulereportbygroup.php
18013
0644
edit
dl
rm
schedulereportbyteacher.php
19424
0644
edit
dl
rm
scheduleteacherbygroupreport.php
37418
0644
edit
dl
rm
scheduleteacherreport.php
28788
0644
edit
dl
rm
schedule_report.php
17944
0644
edit
dl
rm
Schedule_Report_by_Group.php
3741
0644
edit
dl
rm
Schedule_Report_by_Teacher.php
94634
0644
edit
dl
rm
swapcourses.php
19813
0644
edit
dl
rm
teacher_notes.php
18936
0644
edit
dl
rm
teacher_preference.php
18496
0644
edit
dl
rm
teacher_preference_by_course.php
19587
0644
edit
dl
rm
teacher_report_visibility.php
15761
0644
edit
dl
rm
teacher_unavailability.php
20577
0644
edit
dl
rm
test.php
14278
0644
edit
dl
rm
test1.php
22425
0644
edit
dl
rm
unassigncoursereport.php
15075
0644
edit
dl
rm
unassign_course.php
19324
0644
edit
dl
rm
Unassign_Course_Report.php
2058
0644
edit
dl
rm
unscheduledgroupreport.php
16396
0644
edit
dl
rm
Unscheduled_Group_Report.php
2411
0644
edit
dl
rm
updatecourse.php
22454
0644
edit
dl
rm
updatecoursesessionhours.php
18905
0644
edit
dl
rm
updatecoursetime.php
22670
0644
edit
dl
rm
updategroup.php
18619
0644
edit
dl
rm
updateholiday.php
23332
0644
edit
dl
rm
updateteacherlevel.php
25433
0644
edit
dl
rm
updateteacherunavailability.php
11833
0644
edit
dl
rm
UpdateUser.php
19261
0644
edit
dl
rm
Update_Course_Hours.php
2027
0644
edit
dl
rm
update_reserve_course.php
2004
0644
edit
dl
rm
update_teacher_absence.php
12135
0644
edit
dl
rm
update_teacher_level
2508
0644
edit
dl
rm
update_teacher_level.php
2499
0644
edit
dl
rm
useraccesslist.php
13145
0644
edit
dl
rm
users.php
9171
0644
edit
dl
rm
view_swap_log.php
929
0644
edit
dl
rm
workloadhoursreport.php
8182
0644
edit
dl
rm
Edit:
/home/techb158/workloadmatch.com/Manager/Create_Schedule_Algorithm.php
(90629B)
<?php include_once '../includes/db_connect.php'; include_once '../includes/functions.php'; //include_once 'ajax_replace_saturday.php'; //include_once 'Create_Group_Schedule_Algorithm.php'; sec_session_start(); require '../includes/CSRF_Protect.php'; $csrf = new CSRF_Protect(); $page = 'Manager | Generate Group Schedule'; addinfo($page, $mysqli); // ========================================= // 2. Fetch Available Programs (Dropdown) // ========================================= $stmtGroup = $mysqli->prepare("SELECT Program_ID, Program_Name FROM Programs"); $stmtGroup->execute(); $programData = $stmtGroup->get_result()->fetch_all(MYSQLI_ASSOC); $stmtGroup->close(); // ========================================= // 3. Define Utility Functions (Important Modularization) // ========================================= // --------------- Helper functions (these should be at the end of the file) --------------- /** * Find the best course to "push" for a blocked slot (retake), using the same logic as normal slot selection. */ function getCourseToPush( $specialCourseOnlyMode, $specialCourseID, $selectedCourses, $remainingSessions, $slotIndex, $lockedSlot, $courseSlotMap, $activeCourses, $usedToday, $courseCodes ) { if ($specialCourseOnlyMode) return $specialCourseID; $candidates = array_filter($selectedCourses, function($cid) use ( $remainingSessions, $slotIndex, $lockedSlot, $courseSlotMap ) { if (($remainingSessions[$cid] ?? 0) <= 0) return false; if (isset($lockedSlot[$cid]) && $lockedSlot[$cid] !== $slotIndex) return false; if (isset($courseSlotMap[$cid]) && $courseSlotMap[$cid] !== $slotIndex) return false; return true; }); //################################################################## $onlyCourseIDLeft = (count($activeCourses) === 1) ? array_key_first($activeCourses) : null; return getNextCourseForSlot( $remainingSessions, $candidates, $usedToday, $courseCodes, $onlyCourseIDLeft ); //################################################################## /* $onlyCourseIDLeft = (count($activeCourses) === 1) ? array_key_first($activeCourses) : null; $courseID = getNextCourseForSlot( $remainingSessions, array_keys($activeCourses), $usedToday, $courseCodes, $onlyCourseIDLeft ); */ } /** * Find the next course to assign to a slot (normal scheduling). */ function selectNextCourse( $specialCourseOnlyMode, $specialCourseID, $selectedCourses, $remainingSessions, $slotIndex, $lockedSlot, $courseSlotMap, $activeCourses, $usedToday, $courseCodes ) { if ($specialCourseOnlyMode) return $specialCourseID; $candidates = array_filter($selectedCourses, function($cid) use ( $remainingSessions, $slotIndex, $lockedSlot, $courseSlotMap ) { if (($remainingSessions[$cid] ?? 0) <= 0) return false; if (isset($lockedSlot[$cid]) && $lockedSlot[$cid] !== $slotIndex) return false; if (isset($courseSlotMap[$cid]) && $courseSlotMap[$cid] !== $slotIndex) return false; return true; }); $onlyCourseIDLeft = (count($activeCourses) === 1) ? array_key_first($activeCourses) : null; return getNextCourseForSlot( $remainingSessions, $candidates, $usedToday, $courseCodes, $onlyCourseIDLeft ); } /** * Locks a course to a slot if there are only 2 courses left and they're running in parallel. */ function applySlotLocking( $activeCourses, $remainingSessions, $specialCourseID, &$lockedSlot, $slotIndex ) { if (count($activeCourses) === 2) { foreach ($remainingSessions as $cid => $cnt) { if ($cnt > 0 && $cid !== $specialCourseID && !isset($lockedSlot[$cid])) { $lockedSlot[$cid] = $slotIndex; } } } } // ========================================= // 4. Handle POST Request to Generate Schedule // ========================================= // Format the label of the time slot, especially for Saturday sessions function format_time_slot_label($slot, $day) { if ($day === 'Saturday') { if (stripos($slot, 'evening') !== false) return 'Morning (Sat)'; if (stripos($slot, 'morning') !== false) return 'Morning'; } return $slot; } // Format the time range of the session based on the slot and the day function format_time_slot_range($slot, $start, $end, $day) { if ($day === 'Saturday') { if (stripos($slot, 'morning') !== false) return "08:30 AM - 12:30 PM"; if (stripos($slot, 'evening') !== false) return "09:00 AM - 01:00 PM"; } return date('h:i A', strtotime($start)) . ' - ' . date('h:i A', strtotime($end)); } // Get group configuration from database function get_group_info($mysqli, $Group_ID) { $stmt = $mysqli->prepare("SELECT Time_Slot, Time_From, Time_To, class_days, Weekend_Class, Group_Name FROM manager_group_name WHERE Group_ID = ?"); $stmt->bind_param("i", $Group_ID); $stmt->execute(); return $stmt->get_result()->fetch_assoc(); } // Retrieve time slots for the group function fetch_slot_times($mysqli, $slotLabels, $managerStart, $managerEnd) { $timeFrom = []; $timeTo = []; foreach ($slotLabels as $label) { $label = trim($label); $stmt = $mysqli->prepare("SELECT Time_From, Time_To FROM time_slot_programs WHERE Time_Slot = ?"); $stmt->bind_param("s", $label); $stmt->execute(); $result = $stmt->get_result(); $validSlotFound = false; while ($row = $result->fetch_assoc()) { $slotStart = new DateTime($row['Time_From']); $slotEnd = new DateTime($row['Time_To']); if ($slotStart >= $managerStart && $slotEnd <= $managerEnd) { $timeFrom[] = $slotStart->format("H:i:s"); $timeTo[] = $slotEnd->format("H:i:s"); $validSlotFound = true; break; } } if (!$validSlotFound && count($slotLabels) === 1) { $timeFrom[] = $managerStart->format("H:i:s"); $timeTo[] = $managerEnd->format("H:i:s"); } } return [$timeFrom, $timeTo]; } // Collect all holiday dates in the current year /* function get_holidays($mysqli, $Start_Date) { $holidays = []; $res = $mysqli->query("SELECT Event_Title, Event_Start, Event_End FROM Events WHERE Calendar_Year = YEAR('$Start_Date')"); while ($row = $res->fetch_assoc()) { $start = new DateTime($row['Event_Start']); $end = new DateTime($row['Event_End']); while ($start <= $end) { $dateStr = $start->format('Y-m-d'); $holidays[$dateStr] = $row['Event_Title']; // 👈 include the title $start->modify('+1 day'); } } return $holidays; } */ function get_holidays($mysqli, $Start_Date, $End_Date = null) { $holidays = []; // If no end date is provided, default to one year after the start date if ($End_Date === null) { $End_Date = date('Y-m-d', strtotime('+1 year', strtotime($Start_Date))); } $stmt = $mysqli->prepare(" SELECT Event_Title, Event_Start, Event_End FROM Events WHERE Event_Start <= ? AND Event_End >= ? "); // All holidays that overlap the scheduling window [Start_Date, End_Date] $stmt->bind_param('ss', $End_Date, $Start_Date); $stmt->execute(); $res = $stmt->get_result(); while ($row = $res->fetch_assoc()) { $start = new DateTime($row['Event_Start']); $end = new DateTime($row['Event_End']); while ($start <= $end) { $dateStr = $start->format('Y-m-d'); $holidays[$dateStr] = $row['Event_Title']; $start->modify('+1 day'); } } return $holidays; } // Collect all retake dates for the group and program /*function get_retake_dates($mysqli, $Program_ID, $Group_ID) { $retakeDates = []; $stmt = $mysqli->prepare("SELECT Type, Retake_Date FROM retake_records WHERE Program_ID = ? AND Group_ID = ?"); $stmt->bind_param("ii", $Program_ID, $Group_ID); $stmt->execute(); $res = $stmt->get_result(); while ($row = $res->fetch_assoc()) { $retakeDates[$row['Retake_Date']] = $row['Type']; // e.g., ['2025-09-10' => 'Midterm'] } return $retakeDates; }*/ // In get_retake_dates() - ensure consistent structure function get_retake_dates($mysqli, $Program_ID, $Group_ID) { $retakeDates = []; //$sql = "SELECT Group_Slot_ID, time_slot_programs_ID, Time_Slot, Time_From, Time_To FROM group_slot_mapping WHERE Group_ID = ?"; $sql = " SELECT rr.time_slot_programs_ID, rr.Retake_Date, rr.Type, gsm.Group_Slot_ID, gsm.time_slot_programs_ID, gsm.Time_Slot, gsm.Time_From, gsm.Time_To FROM retake_records AS rr INNER JOIN group_slot_mapping AS gsm ON rr.time_slot_programs_ID = gsm.time_slot_programs_ID AND rr.Group_ID = gsm.Group_ID WHERE rr.Program_ID = ? AND rr.Group_ID = ? "; $stmt = $mysqli->prepare($sql); $stmt->bind_param("ii", $Program_ID, $Group_ID); $stmt->execute(); $res = $stmt->get_result(); //echo "<pre>Retake SQL result rows: " . $res->num_rows . "</pre>"; while ($row = $res->fetch_assoc()) { $retakeDates[$row['Retake_Date']][$row['Group_Slot_ID']] = [ 'label' => $row['Time_Slot'], 'from' => $row['Time_From'], 'to' => $row['Time_To'], 'type' => $row['Type'] ]; } return $retakeDates; } function get_retake_dates_info($mysqli, $Program_ID, $Group_ID) { $retakeDates = []; $stmt = $mysqli->prepare(" SELECT Retake_Date, Type, time_slot_programs_ID FROM retake_records WHERE Program_ID = ? AND Group_ID = ? "); $stmt->bind_param("ii", $Program_ID, $Group_ID); $stmt->execute(); $res = $stmt->get_result(); while ($row = $res->fetch_assoc()) { // Store as [date][slotID] = type $retakeDates[$row['Retake_Date']][$row['time_slot_programs_ID']] = $row['Type']; } return $retakeDates; /* $retakeDates = []; $sql = " SELECT rr.Retake_Date, rr.Type, gsm.Group_Slot_ID FROM retake_records AS rr INNER JOIN group_slot_mapping AS gsm ON rr.time_slot_programs_ID = gsm.time_slot_programs_ID AND rr.Group_ID = gsm.Group_ID WHERE rr.Program_ID = ? AND rr.Group_ID = ? "; $stmt = $mysqli->prepare($sql); $stmt->bind_param("ii", $Program_ID, $Group_ID); $stmt->execute(); $res = $stmt->get_result(); while ($row = $res->fetch_assoc()) { // now keyed by [date][slotIndex] $date = $row['Retake_Date']; $slotIdx = intval($row['Group_Slot_ID']) - 1; // zero-based $retakeDates[$row['Retake_Date']][$row['Group_Slot_ID']] = $row['Type']; //$retakeDates[$date][$slotIdx] = $row['Type']; } return $retakeDates;*/ } // Calculate the average duration of one session function calculate_session_length($timeFrom, $timeTo) { $sessionLength = 0; foreach ($timeFrom as $i => $from) { $fromTime = new DateTime($from); $toTime = new DateTime($timeTo[$i]); $sessionLength += ($toTime->getTimestamp() - $fromTime->getTimestamp()) / 3600; } $slotCount = count($timeFrom); return $slotCount > 0 ? $sessionLength / $slotCount : 3; } // Calculate the number of sessions required for each course function calculate_course_sessions($mysqli, $Program_ID, $sessionLength) { $courseSessions = []; $res = $mysqli->query("SELECT Course_ID, Course_Time FROM Courses WHERE Program_ID = $Program_ID"); while ($row = $res->fetch_assoc()) { $courseID = $row['Course_ID']; $courseHours = $row['Course_Time']; $courseSessions[$courseID] = ceil($courseHours / $sessionLength); } return $courseSessions; } // Generate all valid class dates, excluding holidays, retakes, and July function generate_valid_dates($Start_Date, $End_Date, $classDays, $holidayDates, $retakeDates) { $validDates = []; $cur = new DateTime($Start_Date); $end = $End_Date ? new DateTime($End_Date) : null; while (!$end || $cur <= $end) { $day = $cur->format('l'); $dateStr = $cur->format('Y-m-d'); $month = (int) $cur->format('m'); // if ($month === 7 || !in_array($day, $classDays) || array_key_exists($dateStr, $holidayDates) || array_key_exists($dateStr, $retakeDates)) { // $cur->modify('+1 day'); // continue; //} if ($month === 7 || !in_array($day, $classDays) || array_key_exists($dateStr, $holidayDates)) { $cur->modify('+1 day'); continue; } $validDates[] = $dateStr; $cur->modify('+1 day'); if ($end === null && count($validDates) > 365) break; // Prevent infinite loop in fallback } return $validDates; } // Build calendar excluding holidays, retakes, and July function build_schedule_calendar($Start_Date, $End_Date, $classDays, $timeSlots, $mysqli, $Program_ID, $Group_ID) { $start = new DateTime($Start_Date); $end = $End_Date ? new DateTime($End_Date) : new DateTime('+1 year'); $calendar = []; $holidayDates = get_holidays($mysqli, $Start_Date); $retakeDates = get_retake_dates($mysqli, $Program_ID, $Group_ID); $cur = clone $start; while ($cur <= $end) { $day = $cur->format('l'); $dateStr = $cur->format('Y-m-d'); $month = (int) $cur->format('m'); if ($month !== 7 && in_array($day, $classDays) && !in_array($dateStr, $holidayDates) && !in_array($dateStr, $retakeDates)) { foreach ($timeSlots as $slot) { $calendar[$dateStr][trim($slot)] = 'free'; } } $cur->modify('+1 day'); } return $calendar; } // ✅ You can now call these functions in your main POST logic to keep code readable and modular. // Helper function for fair rotation /*function getNextCourseForSlot($remainingSessions, $courseList, $usedToday) { $available = array_filter($courseList, function($courseID) use ($remainingSessions) { return ($remainingSessions[$courseID] ?? 0) > 0; }); $unusedToday = array_filter($available, fn($courseID) => !in_array($courseID, $usedToday)); if (!empty($unusedToday)) { return reset($unusedToday); } // Allow same course in both slots if only one remains if (count($available) === 1) { return reset($available); } return null; }*/ /** * Returns the next best course ID to schedule for a given slot, given remaining sessions, priorities, and special cases. * * @param array $remainingSessions courseID => number left * @param array $candidates list of course IDs eligible for this slot * @param array $usedToday list of course IDs already scheduled *today* (to avoid doubling same course per day) * @param array $courseCodes map: courseID => course code * @param int|null $onlyCourseIDLeft if only one course remains, its ID * @return int|null the selected course ID or null if none eligible */ function getNextCourseForSlot($remainingSessions, $candidates, $usedToday, $courseCodes = [], $onlyCourseIDLeft = null) { // 1. Filter to courses that have sessions left $available = array_filter($candidates, function($cid) use ($remainingSessions) { return ($remainingSessions[$cid] ?? 0) > 0; }); // 2. Prefer a course not already used today foreach ($available as $cid) { if (!in_array($cid, $usedToday)) { return $cid; } } // 3. If only one course left (last), allow re-use if (count($available) === 1 && ($onlyCourseIDLeft === reset($available))) { return reset($available); } // 4. Special: If two courses left and one is a "special" course code, prefer it if (count($available) === 2 && !empty($courseCodes)) { foreach ($available as $cid) { $code = $courseCodes[$cid] ?? ''; if (in_array($code, ['961-238', '960-746'])) { return $cid; } } } // 5. If all else fails, just return the first available return reset($available) ?: null; } $scheduleOutput = []; // Default if ($_SERVER['REQUEST_METHOD'] === 'POST') { //if (isset($_POST['reset']) || isset($_POST['Start_Date'])) { // unset($_SESSION['deletedSaturdays']); //} // Step 1: Retrieve Form Data $forceSecondSpecialAfternoon = !empty($_POST['force_second_special_afternoon']); $lastCourseBothSlots = !empty($_POST['last_course_both_slots']); $Program_ID = intval($_POST['Program_ID']); $Group_ID = intval($_POST['Group_ID']); $Start_Date = $_POST['Start_Date']; $End_Date = $_POST['End_Date'] ?? date('Y-m-d', strtotime('+1 year', strtotime($Start_Date))); $selectedCourses = $_POST['Selected_Courses'] ?? []; $courseSessions = $_POST['Course_Sessions'] ?? []; $priorityCourses = $_POST['Priority_Course'] ?? []; $group = get_group_info($mysqli, $Group_ID); if (!$group) { echo "Group info not found."; exit; } $classDays = explode(',', $group['class_days']); if (!$group['Weekend_Class'] && in_array('Saturday', $classDays)) { $classDays = array_filter($classDays, fn($day) => $day !== 'Saturday'); } $slotLabels = explode(',', $group['Time_Slot']); $managerStart = new DateTime(trim($group['Time_From'])); $managerEnd = new DateTime(trim($group['Time_To'])); //list($timeFrom, $timeTo) = fetch_slot_times($mysqli, $slotLabels, $managerStart, $managerEnd); // Example (replace with your actual fetch code): $all_slot_program_ids = []; $group_slot_labels = []; $group_time_from = []; $group_time_to = []; $stmt = $mysqli->prepare("SELECT Group_Slot_ID,time_slot_programs_ID, Time_Slot, Time_From, Time_To FROM group_slot_mapping WHERE Group_ID = ?"); $stmt->bind_param("i", $Group_ID); $stmt->execute(); $res = $stmt->get_result(); $slotLabels = $timeFrom = $timeTo = []; while ($row = $res->fetch_assoc()) { $slotLabels[] = $row['Time_Slot']; $timeFrom[] = $row['Time_From']; $timeTo[] = $row['Time_To']; $group_slot_id[] = $row['Group_Slot_ID']; $slotProgramID = $row['time_slot_programs_ID']; $all_slot_program_ids[] = $slotProgramID; $group_slot_labels[$slotProgramID] = $row['Time_Slot']; $group_time_from[$slotProgramID] = $row['Time_From']; $group_time_to[$slotProgramID] = $row['Time_To']; } $sessionLength = calculate_session_length($timeFrom, $timeTo); $courseSessions = calculate_course_sessions($mysqli, $Program_ID, $sessionLength); $holidayDates = get_holidays($mysqli, $Start_Date); $retakeDates = get_retake_dates($mysqli, $Program_ID, $Group_ID); $validDates = generate_valid_dates($Start_Date, $End_Date, $classDays, $holidayDates, $retakeDates); $suggestedSessions = calculate_course_sessions($mysqli, $Program_ID, $sessionLength); $courseSessions = []; foreach ($selectedCourses as $courseID) { $custom = $_POST['Course_Sessions'][$courseID] ?? null; if (is_numeric($custom) && intval($custom) > 0) { $courseSessions[$courseID] = intval($custom); } else { $courseSessions[$courseID] = $suggestedSessions[$courseID] ?? 0; } } //echo "<h3>📅 Valid Dates for Scheduling (Excluding July)</h3><pre>"; print_r($validDates); echo "</pre>"; // 🔄 Additional scheduling logic .... // Output valid dates //echo "<h3>📅 Valid Dates for Scheduling (Excluding July)</h3><pre>"; print_r($validDates); echo "</pre>"; $totalSessionsNeeded = array_sum($courseSessions); $availableDays = count($validDates); // Estimate sessions per day (based on number of slots) $sessionsPerDay = count($slotLabels); // Calculate how many days are needed to finish all sessions $daysRequired = ceil($totalSessionsNeeded / $sessionsPerDay); // Get expected end date $expectedEndDate = $validDates[$daysRequired - 1] ?? end($validDates); // fallback to last available if out of bounds //echo "<h3>📅 Expected End Date Based on Andragogical Days</h3>"; //echo "<p><strong>$expectedEndDate</strong> (" . (new DateTime($expectedEndDate))->format('l') . ")</p>"; // Sort by course priority usort($selectedCourses, function ($a, $b) use ($priorityCourses) { $priorityA = $priorityCourses[$a] ?? PHP_INT_MAX; $priorityB = $priorityCourses[$b] ?? PHP_INT_MAX; return $priorityA <=> $priorityB; }); // echo "<h3>🔄 Sorted Courses (Priority First)</h3><pre>"; print_r($selectedCourses); echo "</pre>"; // Prepare for fair scheduling (one course per slot) // Initialize $schedule = []; $remainingSessions = []; foreach ($selectedCourses as $courseID) { $remainingSessions[$courseID] = $courseSessions[$courseID] ?? 0; } // Queue of all courses $courseQueue = $selectedCourses; $currentCourses = [ 0 => null, // Slot 1 (e.g. Morning) 1 => null // Slot 2 (e.g. Afternoon) ]; // Assign initial two courses // Assign initial courses to slots without repeating $courseQueueCopy = $selectedCourses; $assignedCourses = []; foreach ([0, 1] as $slotIndex) { foreach ($courseQueueCopy as $courseID) { if (!in_array($courseID, $assignedCourses) && $remainingSessions[$courseID] > 0) { $currentCourses[$slotIndex] = $courseID; $assignedCourses[] = $courseID; break; } } } // Fetch all course names into array $courseNames = []; $courseRes = $mysqli->query("SELECT Course_ID, Course_Name FROM Courses"); while ($row = $courseRes->fetch_assoc()) { $courseNames[$row['Course_ID']] = $row['Course_Name']; } // --- Setup slot/course/retake tracking structures --- $courseSlotMap = []; // Tracks which slot a course is "locked" to (courseID => slotIndex) $specialCourseOnlyMode = false; // If only 1 "special" course is left, triggers special scheduling logic $specialCourseID = null; // Holds the ID of the "special" course (e.g. 961-238, 960-746) $lockedSlot = []; // Tracks if a course is locked to a slot (courseID => slotIndex) $pushedCourse = []; // If a course is "pushed" (blocked by a retake), store it for later in this slot (slotIndex => courseID) $numSlots = count($slotLabels); // Number of time slots per day /////###################################### /** * Pre-schedules every special course into $schedule, * tagging their last session as an exam. * * @param array &$schedule The schedule array to append into * @param array $specialCourseIDs Ordered list of special course IDs * @param array &$remainingSessions Course_ID => sessions remaining * @param array $allSlots Flat list of slot metadata * @param array $courseNames Course_ID => human name */ /** * Pre-schedules every special course into $schedule, * tagging their last session as an exam. Returns the last date/slot used. * * @return array [$lastDate, $lastSlotIndex] */ /** * Pre-schedules every special course into $schedule, * tagging their last session as an exam and forcing * the final session of the 2nd & 4th specials into an afternoon slot. * * @return array [$lastDate, $lastSlotIndex] */ function scheduleSpecialCourses( array &$schedule, array $specialCourseIDs, array &$remainingSessions, array $allSlots, array $courseNames, bool $forceSecondSpecialAfternoon // ← here signature to accept the new flag ): array { $spPtr = 0; $lastSpecialDate = null; $lastSpecialSlot = null; foreach ($specialCourseIDs as $idx => $spCid) { $toDo = $remainingSessions[$spCid] ?? 0; while ($toDo > 0 && isset($allSlots[$spPtr])) { $slot = $allSlots[$spPtr]; /* // Always force the last session (exam) into the Afternoon slot (slotIndex == 1) if ($toDo === 1 && $slot['slotIndex'] === 0) { // Search forward for afternoon slot on same day for ($j = $spPtr; $j < count($allSlots); $j++) { if ( $allSlots[$j]['date'] === $slot['date'] && $allSlots[$j]['slotIndex'] === 1 ) { $slot = $allSlots[$j]; $spPtr = $j; break; } } } */ // If user asked to force the *second* special into afternoon… if ( $forceSecondSpecialAfternoon && $toDo === 1 // last session && $slot['slotIndex'] === 0 ) { // scan forward to same-day slotIndex=1 for ($j = $spPtr; $j < count($allSlots); $j++) { if ( $allSlots[$j]['date'] === $slot['date'] && $allSlots[$j]['slotIndex'] === 1 ) { $slot = $allSlots[$j]; $spPtr = $j + 1; break; } } } $spPtr++; if (!$slot['from'] || !$slot['to']) continue; $isExam = ($toDo === 1); $schedule[] = [ 'Date' => $slot['date'], 'SlotIndex' => $slot['slotIndex'], 'Slot' => $slot['slotLabel'], 'Time' => format_time_slot_range( $slot['slotLabel'], $slot['from'], $slot['to'], $slot['dayName'] ), 'Course_ID' => $spCid, 'Course_Name' => $courseNames[$spCid] ?? '', 'Start_Time' => $slot['from'], 'End_Time' => $slot['to'], 'Group_Slot_ID' => $slot['groupSlotId'], 'Reserve_Course' => 0, 'IsExam' => $isExam, ]; $lastSpecialDate = $slot['date']; $lastSpecialSlot = $slot['slotIndex']; $toDo--; $remainingSessions[$spCid]--; } unset($remainingSessions[$spCid]); } return [$lastSpecialDate, $lastSpecialSlot]; } // --- SPECIAL COURSES SCHEDULING --- (RUNS ONCE BEFORE main foreach) // ------------------------------------------------------------------------ // ➊ Prepare your schedule array and remainingSessions as you already do… $schedule = []; // clear any prior entries // $remainingSessions is already set from your courseSessions logic // ➀ Build $specialCourseIDs from your Courses table & specialCourseCodes $specialCourseCodes = ['CST53001','961-012','SEC-CST53001','960-501']; $specialCourseIDs = []; $cRes = $mysqli->query(" SELECT Course_ID, Course_Code FROM Courses "); while ($r = $cRes->fetch_assoc()) { if (in_array($r['Course_Code'], $specialCourseCodes)) { $specialCourseIDs[] = $r['Course_ID']; } } // Global queue for any course bumped by a retake $pushQueue = []; //############################################################## //############################################################## // ➁ Build $allSlots from $validDates and your slot/time data $allSlots = []; foreach ($validDates as $date) { foreach (array_keys($slotLabels) as $si) { $gid = $group_slot_id[$si] ?? 0; if (!empty($retakeDates[$date][$gid]) || !empty($holidayDates[$date])) { continue; } $allSlots[] = [ 'date' => $date, 'slotIndex' => $si, 'slotLabel' => $slotLabels[$si], 'from' => $timeFrom[$si] ?? null, 'to' => $timeTo[$si] ?? null, 'groupSlotId'=> $gid, 'dayName' => (new DateTime($date))->format('l'), ]; } } // ➋ Front-load specials via the new function // front-load all specials, capturing where they ended list($lastSpecialDate, $lastSpecialSlot) = scheduleSpecialCourses( $schedule, $specialCourseIDs, $remainingSessions, $allSlots, $courseNames, // ← pass this in now $forceSecondSpecialAfternoon // ← here pass your flag ); /////###################################### // Mark already-used date/slot pairs $occupiedSlots = []; foreach ($schedule as $s) { $occupiedSlots[$s['Date']][$s['SlotIndex']] = true; } /////###################################### // --- Main scheduling loop over each valid date --- foreach ($validDates as $date) { $usedToday = []; // Reset courses used for this day $dayName = (new DateTime($date))->format('l'); // Get the day of week, e.g. "Monday" $activeCourses = array_filter($remainingSessions, fn($s) => $s > 0); // Only courses that still need sessions $activeCourseIDs = array_keys($activeCourses); // Their IDs // --- Detect special course only mode (e.g. last course is a "special" one) --- $specialCourseOnlyMode = false; $specialCourseID = null; if (count($activeCourseIDs) === 1) { $cid = $activeCourseIDs[0]; $code = $courseCodes[$cid] ?? ''; /* if (count($activeCourseIDs) === 1 && !$__special_sessions_adjusted) { $cid = $activeCourseIDs[0]; $code = $courseCodes[$cid] ?? ''; if (in_array($code, $specialCourseCodes) && $numSlots > 1) { //$remainingSessions[$cid] = ceil($remainingSessions[$cid] / $numSlots); //$__special_sessions_adjusted = true; } }*/ if (in_array($code, ['961-238', '960-746'])) { //if (in_array($code, ['', ''])) { $specialCourseOnlyMode = true; $specialCourseID = $cid; // 🟡 ADJUST THE SESSION COUNT if we allow special course in both slots // Only adjust ONCE! if ($numSlots > 1 && !$__special_sessions_adjusted) { $remainingSessions[$specialCourseID] = ceil($remainingSessions[$specialCourseID] / $numSlots); $__special_sessions_adjusted = true; } } } // ========== SINGLE SLOT LOGIC ========== if ($numSlots === 1) { $slotIndex = 0; $from = $timeFrom[$slotIndex] ?? null; $to = $timeTo[$slotIndex] ?? null; $slotLabel = $slotLabels[$slotIndex]; $groupSlot = $group_slot_id[$slotIndex] ?? 0; // If there's a retake for this date (regardless of slot), show retake only //if (isset($retakeDates[$date])) { // For old format where $retakeDates[$date] is string or array //$retakeType = is_array($retakeDates[$date]) ? reset($retakeDates[$date]) : $retakeDates[$date]; // Look for any slot on this date $slotIndex = 0; $groupSlotId = $group_slot_id[$slotIndex]; // should be 32 if (isset($retakeDates[$date][$groupSlotId])) { $retake = $retakeDates[$date][$groupSlotId]; $schedule[] = [ 'Date' => $date, 'Slot' => $retake['label'] ?? $slotLabels[0], // <-- Use label from retakeDates if present, else default 'Time' => format_time_slot_range($retake['label'], $retake['from'], $retake['to'], $dayName), 'Course_ID' => null, 'Course_Name' => "Retake: " . htmlspecialchars($retake['type']), 'Start_Time' => $retake['from'], 'End_Time' => $retake['to'], 'Group_Slot_ID'=> $groupSlotId, 'Reserve_Course' => 0, 'IsRetake' => true, 'Retake_Type' => $retake['type'] ]; continue; } // If a pushed (previously blocked) course exists, assign it now if (isset($pushedCourse[$slotIndex])) { $courseID = $pushedCourse[$slotIndex]; unset($pushedCourse[$slotIndex]); } else { // Otherwise, pick next available course normally if ($specialCourseOnlyMode) { $courseID = $specialCourseID; } else { $candidates = array_filter($selectedCourses, function($cid) use ($remainingSessions) { return ($remainingSessions[$cid] ?? 0) > 0; }); $onlyCourseIDLeft = (count($activeCourses) === 1) ? array_key_first($activeCourses) : null; $courseID = getNextCourseForSlot( $remainingSessions, $candidates, $usedToday, $courseCodes, $onlyCourseIDLeft ); } if (!$courseID || ($remainingSessions[$courseID] ?? 0) <= 0) continue; $usedToday[] = $courseID; } $courseName = $courseNames[$courseID] ?? "Course #$courseID"; $schedule[] = [ 'Date' => $date, 'Slot' => "Slot 1", 'Time' => format_time_slot_range($slotLabel, $from, $to, $dayName), 'Course_ID' => $courseID, 'Course_Name' => $courseName, 'Start_Time' => $from, 'End_Time' => $to, 'Group_Slot_ID' => $group_slot_id[$slotIndex] ?? 0, 'Reserve_Course' => 0 ]; $remainingSessions[$courseID]--; if (array_sum($remainingSessions) <= 0) break; } // ========== MULTI-SLOT LOGIC (2 or more slots) ========== else { ////##########################--- END SPECIAL SCHEDULING ---################################# // Loop through each slot (e.g. morning/afternoon) foreach (array_keys($slotLabels) as $slotIndex) { // ──────────────────────────────────────────────────────────────────────────── // Only skip slots strictly before the exam date, or the morning of the exam date // skip everything up through that exam‐morning // Don't double-book: skip if already occupied by a special course if (!empty($occupiedSlots[$date][$slotIndex])) { continue; } // ───────────────────────────────────────── // If we're down to one course, skip the morning slot… /* $onlyCourseLeft = (count($activeCourses) === 1); if ($onlyCourseLeft && $slotIndex === 0) { continue; } */ // Determine if we’re down to exactly one active course $onlyCourseLeft = count($activeCourses) === 1; // Only skip the morning slot *if* they did NOT check “both slots” if ( !$lastCourseBothSlots && $onlyCourseLeft && $slotIndex === 0 ) { continue; } // ───────────────────────────────────────── // Now do your normal multi-slot scheduling for regular courses! $from = $timeFrom[$slotIndex] ?? null; $to = $timeTo[$slotIndex] ?? null; $slotLabel = $slotLabels[$slotIndex]; $groupSlotId = $group_slot_id[$slotIndex] ?? 0; if (!$from || !$to) continue; // --- Special course only mode: skip non-morning slots //if ($specialCourseOnlyMode && $slotIndex !== 0) continue; // --- 1. RETAKE HANDLING --- if (!empty($retakeDates[$date][$groupSlotId])) { $slotInfo = $retakeDates[$date][$groupSlotId]; $retakeType = $slotInfo['type'] ?? ''; $slotLabel = $slotInfo['label'] ?? $slotLabel; $from = $slotInfo['from'] ?? $from; $to = $slotInfo['to'] ?? $to; $schedule[] = [ 'Date' => $date, 'SlotIndex' => $slotIndex, // ← NEW 'Slot' => $slotLabel, //'Slot' => "Slot " . ($slotIndex + 1), 'Time' => format_time_slot_range($slotLabel, $from, $to, $dayName), 'Course_ID' => null, 'Course_Name' => "Retake: " . htmlspecialchars($retakeType), 'Start_Time' => $from, 'End_Time' => $to, 'Group_Slot_ID' => $groupSlotId, 'Reserve_Course' => 0, 'IsRetake' => true, 'Retake_Type' => $retakeType ]; // Optionally push the blocked course for this slot if (!isset($pushedCourse[$slotIndex])) { $courseToPush = getCourseToPush( $specialCourseOnlyMode, $specialCourseID, $selectedCourses, $remainingSessions, $slotIndex, $lockedSlot, $courseSlotMap, $activeCourses, $usedToday, $courseCodes ); if ($courseToPush !== null) { $pushedCourse[$slotIndex] = $courseToPush; } if ($courseToPush !== null) { $pushQueue[] = $courseToPush; // enqueue at tail } } continue; // Only skip normal class for this *slot*, not the whole date! } // --- 2. PUSHED COURSE HANDLING (if retake blocked a course previously) --- if (isset($pushedCourse[$slotIndex])) { $courseID = $pushedCourse[$slotIndex]; unset($pushedCourse[$slotIndex]); // If no remaining sessions for that course, skip if (($remainingSessions[$courseID] ?? 0) <= 0) continue; } else { // --- 3. NORMAL COURSE SCHEDULING --- /* $courseID = selectNextCourse( $specialCourseOnlyMode, $specialCourseID, $selectedCourses, $remainingSessions, $slotIndex, $lockedSlot, $courseSlotMap, $activeCourses, $usedToday, $courseCodes ); if (!$courseID || ($remainingSessions[$courseID] ?? 0) <= 0) continue; $usedToday[] = $courseID; */ //################################################################## // --- 3. NORMAL COURSE SCHEDULING --- // ————— HERE: ONLY-ONE-COURSE-LEFT SKIP MORNING ————— /* $onlyCourseIDLeft = (count($activeCourses) === 1) ? array_key_first($activeCourses) : null; if ($onlyCourseIDLeft !== null && $slotIndex === 0) { // we’re down to one course; skip the morning slot so it lands in the afternoon continue; } // ———————————————————————————————————————————————— // --- 3. NORMAL COURSE SCHEDULING --- $courseID = getNextCourseForSlot( $remainingSessions, array_keys($activeCourses), $usedToday, $courseCodes, $onlyCourseIDLeft ); if (!$courseID || ($remainingSessions[$courseID] ?? 0) <= 0) { continue; } $usedToday[] = $courseID; */ //################################################################## if (count($activeCourses) === 1) { // Only one course left, allow it in all slots (don't check $usedToday) $courseID = array_key_first($activeCourses); } else { // Usual course selection logic $courseID = selectNextCourse( $specialCourseOnlyMode, $specialCourseID, $selectedCourses, $remainingSessions, $slotIndex, $lockedSlot, $courseSlotMap, $activeCourses, $usedToday, $courseCodes ); if (!$courseID || ($remainingSessions[$courseID] ?? 0) <= 0) continue; // Prevent same course twice if more than one course left if ($courseID && !in_array($courseID, $usedToday)) { $usedToday[] = $courseID; } } } // --- 4. SLOT LOCKING LOGIC --- We close it we need to show the last 2 courses one in the morning and other in afternoon /* applySlotLocking( $activeCourses, $remainingSessions, $specialCourseID, $lockedSlot, $slotIndex ); */ // Lock this course to this slot, if not already locked if (!isset($courseSlotMap[$courseID])) { $courseSlotMap[$courseID] = $slotIndex; } // --- 5. SCHEDULE THE COURSE --- $schedule[] = [ 'Date' => $date, 'SlotIndex' => $slotIndex, // ← NEW //'Slot' => $slotLabels[$slotIndex], // e.g. "Morning" or "Afternoon" 'Slot' => "Slot " . ($slotIndex + 1), 'Time' => format_time_slot_range($slotLabel, $from, $to, $dayName), 'Course_ID' => $courseID, 'Course_Name' => $courseNames[$courseID] ?? "Course #$courseID", 'Start_Time' => $from, 'End_Time' => $to, 'Group_Slot_ID' => $groupSlotId, 'Reserve_Course' => 0 ]; $remainingSessions[$courseID]--; // If all sessions are scheduled, break out of both loops if (array_sum($remainingSessions) <= 0) break 2; } } // End multi-slot else } // End foreach validDates /* // --- Make sure you already have getNextCourseForSlot() defined elsewhere in your file --- echo "<h3>--- DEBUGGING ARRAYS ---</h3><pre>"; echo "<pre>DEBUG Program_ID: $Program_ID, Group_ID: $Group_ID</pre>"; echo "Selected Courses:\n"; print_r($selectedCourses); echo "\nCourse Sessions (per course):\n"; print_r($courseSessions); echo "\nPriority Courses:\n"; print_r($priorityCourses); echo "\nSlot Labels:\n"; print_r($slotLabels); echo "\nTime From (per slot):\n"; print_r($timeFrom); echo "\nTime To (per slot):\n"; print_r($timeTo); echo "\nGroup Slot IDs:\n"; print_r($group_slot_id); echo "\nAll Slot Program IDs:\n"; print_r($all_slot_program_ids); echo "\nGroup Slot Labels (SlotProgramID => Label):\n"; print_r($group_slot_labels); echo "\nGroup Time From (SlotProgramID => Time):\n"; print_r($group_time_from); echo "\nGroup Time To (SlotProgramID => Time):\n"; print_r($group_time_to); echo "\nHoliday Dates:\n"; print_r($holidayDates); echo "\nRetake Dates:\n"; print_r($retakeDates); echo "\nValid Dates:\n"; print_r($validDates); echo "\nCourse Names (CourseID => Name):\n"; print_r($courseNames); echo "\nCourse Slot Map (CourseID => SlotProgramID):\n"; print_r($courseSlotMap); echo "\nRemaining Sessions (CourseID => Remaining):\n"; print_r($remainingSessions); echo "\nPushed Course (SlotProgramID => CourseID):\n"; print_r($pushedCourse); echo "\nLocked Slot (CourseID => SlotProgramID):\n"; print_r($lockedSlot); echo "\nSchedule (final result):\n"; print_r($schedule); echo "</pre>"; */ /* echo "<pre>"; foreach ($schedule as $row) { echo "{$row['Date']} slotIndex={$row['SlotIndex']} course={$row['Course_ID']} IsExam=".($row['IsExam']?1:0)."\n"; } echo "</pre>"; */ // 🔍 Identify last scheduled session for each course $lastSessions = []; foreach ($schedule as $index => $entry) { $courseID = $entry['Course_ID']; $lastSessions[$courseID] = $index; // keeps overwriting until the last index } //echo "<h3>📘 Final Generated Schedule</h3><pre>"; print_r($schedule); echo "</pre>"; //echo "<h2>📋 Generated Schedule Table</h2>"; //echo "<p><strong>Group:</strong> " . htmlspecialchars($groupName) . "</p>"; if (!empty($schedule)) { // Collect all displayed dates: scheduled, holidays, and retakes $displayDates = []; // 1. From scheduled sessions foreach ($schedule as $row) { $displayDates[$row['Date']][] = $row; // grouped by date } // 2. Add holidays (if no course scheduled on them) foreach ($holidayDates as $hDate => $title) { if (!isset($displayDates[$hDate])) { $displayDates[$hDate] = []; // Add empty row so we render it } } foreach ($retakeDates as $rDate => $slots) { if (!isset($displayDates[$rDate])) { $displayDates[$rDate] = []; } // For each slot with a retake on this date foreach ($slots as $groupSlotId => $slotInfo) { // Check if we already have a retake for this slot/date $alreadyRetake = false; foreach ($displayDates[$rDate] as $row) { if (!empty($row['IsRetake']) && ($row['Slot'] == ($slotInfo['label'] ?? ''))) { $alreadyRetake = true; break; } } if (!$alreadyRetake) { $displayDates[$rDate][] = [ 'Slot' => $slotInfo['label'] ?? null, // e.g., "Evening" 'Time' => isset($slotInfo['from'], $slotInfo['to'], $slotInfo['label']) ? format_time_slot_range($slotInfo['label'], $slotInfo['from'], $slotInfo['to'], (new DateTime($rDate))->format('l')) : null, 'Course_ID' => null, 'IsRetake' => true, 'Retake_Type' => $slotInfo['type'] ?? null ]; } } } //echo "<pre>"; //echo "\nSisplayDates (final all result):\n"; //print_r($displayDates); // //echo "</pre>"; // Sort the dates chronologically ksort($displayDates); } else { echo "<p style='color:red'>🚫 No schedule generated — check if valid time slots, sessions, or dates are missing.</p>"; } } /* $scheduleOutput = []; // Default if ($_SERVER['REQUEST_METHOD'] === 'POST') { //if (isset($_POST['reset']) || isset($_POST['Start_Date'])) { // unset($_SESSION['deletedSaturdays']); //} // Step 1: Retrieve Form Data $Program_ID = intval($_POST['Program_ID']); $Group_ID = intval($_POST['Group_ID']); $Start_Date = $_POST['Start_Date']; $End_Date = $_POST['End_Date'] ?? date('Y-m-d', strtotime('+1 year', strtotime($Start_Date))); $selectedCourses = $_POST['Selected_Courses'] ?? []; $courseSessions = $_POST['Course_Sessions'] ?? []; $priorityCourses = $_POST['Priority_Course'] ?? []; $group = get_group_info($mysqli, $Group_ID); if (!$group) { echo "Group info not found."; exit; } $classDays = explode(',', $group['class_days']); if (!$group['Weekend_Class'] && in_array('Saturday', $classDays)) { $classDays = array_filter($classDays, fn($day) => $day !== 'Saturday'); } $slotLabels = explode(',', $group['Time_Slot']); $managerStart = new DateTime(trim($group['Time_From'])); $managerEnd = new DateTime(trim($group['Time_To'])); //list($timeFrom, $timeTo) = fetch_slot_times($mysqli, $slotLabels, $managerStart, $managerEnd); $stmt = $mysqli->prepare("SELECT Group_Slot_ID, Time_Slot, Time_From, Time_To FROM group_slot_mapping WHERE Group_ID = ?"); $stmt->bind_param("i", $Group_ID); $stmt->execute(); $res = $stmt->get_result(); $slotLabels = $timeFrom = $timeTo = []; while ($row = $res->fetch_assoc()) { $slotLabels[] = $row['Time_Slot']; $timeFrom[] = $row['Time_From']; $timeTo[] = $row['Time_To']; $group_slot_id[] = $row['Group_Slot_ID']; } $sessionLength = calculate_session_length($timeFrom, $timeTo); $courseSessions = calculate_course_sessions($mysqli, $Program_ID, $sessionLength); $holidayDates = get_holidays($mysqli, $Start_Date); $retakeDates = get_retake_dates($mysqli, $Program_ID, $Group_ID); $validDates = generate_valid_dates($Start_Date, $End_Date, $classDays, $holidayDates, $retakeDates); //echo "<h3>📅 Valid Dates for Scheduling (Excluding July)</h3><pre>"; print_r($validDates); echo "</pre>"; // 🔄 Additional scheduling logic .... // Output valid dates //echo "<h3>📅 Valid Dates for Scheduling (Excluding July)</h3><pre>"; print_r($validDates); echo "</pre>"; $totalSessionsNeeded = array_sum($courseSessions); $availableDays = count($validDates); // Estimate sessions per day (based on number of slots) $sessionsPerDay = count($slotLabels); // Calculate how many days are needed to finish all sessions $daysRequired = ceil($totalSessionsNeeded / $sessionsPerDay); // Get expected end date $expectedEndDate = $validDates[$daysRequired - 1] ?? end($validDates); // fallback to last available if out of bounds //echo "<h3>📅 Expected End Date Based on Andragogical Days</h3>"; //echo "<p><strong>$expectedEndDate</strong> (" . (new DateTime($expectedEndDate))->format('l') . ")</p>"; // Sort by course priority usort($selectedCourses, function ($a, $b) use ($priorityCourses) { $priorityA = $priorityCourses[$a] ?? PHP_INT_MAX; $priorityB = $priorityCourses[$b] ?? PHP_INT_MAX; return $priorityA <=> $priorityB; }); // echo "<h3>🔄 Sorted Courses (Priority First)</h3><pre>"; print_r($selectedCourses); echo "</pre>"; // Prepare for fair scheduling (one course per slot) // Initialize $schedule = []; $remainingSessions = []; foreach ($selectedCourses as $courseID) { $remainingSessions[$courseID] = $courseSessions[$courseID] ?? 0; } // Queue of all courses $courseQueue = $selectedCourses; $currentCourses = [ 0 => null, // Slot 1 (e.g. Morning) 1 => null // Slot 2 (e.g. Afternoon) ]; // Assign initial two courses // Assign initial courses to slots without repeating $courseQueueCopy = $selectedCourses; $assignedCourses = []; foreach ([0, 1] as $slotIndex) { foreach ($courseQueueCopy as $courseID) { if (!in_array($courseID, $assignedCourses) && $remainingSessions[$courseID] > 0) { $currentCourses[$slotIndex] = $courseID; $assignedCourses[] = $courseID; break; } } } // Fetch all course names into array $courseNames = []; $courseRes = $mysqli->query("SELECT Course_ID, Course_Name FROM Courses"); while ($row = $courseRes->fetch_assoc()) { $courseNames[$row['Course_ID']] = $row['Course_Name']; } // before you start scheduling: $courseSlotMap = []; // Start scheduling loop // Start scheduling loop foreach ($validDates as $date) { $usedToday = []; // Reset per day $dayName = (new DateTime($date))->format('l'); foreach ([0, 1] as $slotIndex) { $from = $timeFrom[$slotIndex] ?? null; $to = $timeTo[$slotIndex] ?? null; // skip morning slot (index 0) if it's a retake day if (isset($retakeDates[$date]) && $slotIndex === 0) { continue; } if (!$from || !$to) continue; // 1) Filter only courses eligible for this slot $candidates = array_filter($selectedCourses, function($cid) use ($remainingSessions, $courseSlotMap, $slotIndex) { // must still have sessions remaining if (empty($remainingSessions[$cid])) { return false; } // if we've already locked this course to the other slot, skip it if (isset($courseSlotMap[$cid]) && $courseSlotMap[$cid] !== $slotIndex) { return false; } return true; }); // 2) Pick from that filtered list $courseID = getNextCourseForSlot($remainingSessions, $candidates, $usedToday); if (!$courseID) continue; // 3) Lock it into this slot if it wasn't already if (!isset($courseSlotMap[$courseID])) { $courseSlotMap[$courseID] = $slotIndex; } $slotLabel = $slotLabels[$slotIndex]; $courseName = $courseNames[$courseID] ?? "Course #$courseID"; // 4) Save the scheduled session //$schedule[] = [ // 'Date' => $date, // 'Slot' => "Slot " . ($slotIndex + 1), // 'Time' => format_time_slot_range($slotLabel, $from, $to, $dayName), // 'Course_ID' => $courseID //]; // 4) Save the scheduled session (with times + slot ID) $schedule[] = [ 'Date' => $date, 'Slot' => "Slot " . ($slotIndex + 1), 'Time' => format_time_slot_range($slotLabel, $from, $to, $dayName), 'Course_ID' => $courseID, 'Course_Name' => $courseNames[$courseID] ?? '', // ← add this 'Start_Time' => $from, 'End_Time' => $to, 'Group_Slot_ID' => $group_slot_id[$slotIndex] ?? 0, 'Reserve_Course' => 0 ]; // 5) Decrement and mark used $remainingSessions[$courseID]--; $usedToday[] = $courseID; // 6) Stop if done if (array_sum($remainingSessions) <= 0) { break 2; } } } // 🔍 Identify last scheduled session for each course $lastSessions = []; foreach ($schedule as $index => $entry) { $courseID = $entry['Course_ID']; $lastSessions[$courseID] = $index; // keeps overwriting until the last index } //echo "<h3>📘 Final Generated Schedule</h3><pre>"; print_r($schedule); echo "</pre>"; //echo "<h2>📋 Generated Schedule Table</h2>"; //echo "<p><strong>Group:</strong> " . htmlspecialchars($groupName) . "</p>"; if (!empty($schedule)) { // Collect all displayed dates: scheduled, holidays, and retakes $displayDates = []; // 1. From scheduled sessions foreach ($schedule as $row) { $displayDates[$row['Date']][] = $row; // grouped by date } // 2. Add holidays (if no course scheduled on them) foreach ($holidayDates as $hDate => $title) { if (!isset($displayDates[$hDate])) { $displayDates[$hDate] = []; // Add empty row so we render it } } // 3. Add retakes //foreach ($retakeDates as $rDate => $type) { // if (!isset($displayDates[$rDate])) { // $displayDates[$rDate] = []; // } //} // 3. Ensure every retake date has a morning placeholder foreach ($retakeDates as $rDate => $type) { // make sure the date key exists if (!isset($displayDates[$rDate])) { $displayDates[$rDate] = []; } // detect if we already have a slot-1 (afternoon) row but no slot-0 $hasMorning = false; foreach ($displayDates[$rDate] as $row) { if (isset($row['Slot']) && trim($row['Slot']) === 'Slot 1') { $hasMorning = true; break; } } if (!$hasMorning) { array_unshift($displayDates[$rDate], [ 'Slot' => null, 'Time' => null, 'Course_ID' => null, 'IsRetake' => true // our flag ]); } } // Sort the dates chronologically ksort($displayDates); } else { echo "<p style='color:red'>🚫 No schedule generated — check if valid time slots, sessions, or dates are missing.</p>"; } } */ ?> <!DOCTYPE html> <html> <head> <title>Generate Group Schedule</title> <!-- Responsive meta --> <meta content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no" name="viewport"> <link rel="icon" href="dist/img/avatar.png" sizes="32x32" /> <!-- Bootstrap --> <link rel="stylesheet" href="bower_components/bootstrap/dist/css/bootstrap.min.css"> <!-- Font Awesome --> <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.5.0/css/font-awesome.min.css"> <!-- Ionicons --> <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/ionicons/2.0.1/css/ionicons.min.css"> <!-- daterange picker --> <link rel="stylesheet" href="plugins/daterangepicker/daterangepicker-bs3.css"> <!-- bootstrap datepicker --> <link rel="stylesheet" href="plugins/datepicker/datepicker3.css"> <!-- iCheck --> <link rel="stylesheet" href="plugins/iCheck/all.css"> <!-- Bootstrap Color Picker --> <link rel="stylesheet" href="plugins/colorpicker/bootstrap-colorpicker.min.css"> <!-- Bootstrap time Picker --> <link rel="stylesheet" href="plugins/timepicker/bootstrap-timepicker.min.css"> <!-- Select2 --> <link rel="stylesheet" href="plugins/select2/select2.min.css"> <!-- Theme style --> <link rel="stylesheet" href="dist/css/AdminLTE.min.css"> <!-- AdminLTE Skins --> <link rel="stylesheet" href="dist/css/skins/skin-black-light.min.css"> <!-- DataTables CSS --> <link rel="stylesheet" href="plugins/datatables/dataTables.bootstrap.css"> <link rel="stylesheet" href="https://cdn.datatables.net/1.10.13/css/jquery.dataTables.min.css"> <link rel="stylesheet" href="https://cdn.datatables.net/buttons/1.2.4/css/buttons.dataTables.min.css"> <!--[if lt IE 9]> <script src="https://oss.maxcdn.com/html5shiv/3.7.3/html5shiv.min.js"></script> <script src="https://oss.maxcdn.com/respond/1.4.2/respond.min.js"></script> <![endif]--> <script type="text/JavaScript" src="js/_all-skins.js"></script> <script type="text/JavaScript" src="js/_all-skins.min.js"></script> <script src="https://code.jquery.com/jquery-3.6.0.min.js"></script> <!-- ✅ Include your PHP values --> <script> document.addEventListener('DOMContentLoaded', function () { const saveBtn = document.getElementById('save-schedule-btn'); if (!saveBtn) return; saveBtn.addEventListener('click', function () { const scheduleData = []; //const scheduleDataToSave = <?php echo json_encode($ajaxScheduleData); ?>; // If scheduleDataToSave is already ready, just send it directly: fetch('save_schedule_group_course', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(scheduleDataToSave) }) .then(res => res.text()) .then(data => alert("✅ Saved: " + data)) .catch(err => { console.error("❌ Error:", err); alert("❌ Failed to save."); }); }); }); </script> <script> $(document).ready(function() { $("#Program_ID").change(function() { var programID = $(this).val(); $("#Group_ID").html('<option value="">Select Group</option>').val(""); if (programID !== "") { $.ajax({ url: "fetch_groups_for_program", method: "POST", data: { Program_ID: programID }, success: function(data) { $("#Group_ID").empty().append('<option value="">Select Group</option>' + data).val(""); } }); } }); $("#Group_ID").change(function () { var programID = $("#Program_ID").val(); $("#courseTable").html(""); var groupID = $(this).val(); if (programID !== "" && groupID !== "") { $.ajax({ url: "fetch_courses_for_program_by_schedule", type: "POST", data: { Program_ID: programID, Group_ID: groupID }, success: function (data) { $("table tbody").html(data); } }); } }); }); </script> </head> <body class="hold-transition skin-black-light sidebar-mini"> <?php if (login_check($mysqli) == true) : ?> <?php include 'main-header-inc.php'; ?> <div class="wrapper"> <?php include 'main-header.php'; ?> <div class="content-wrapper"> <section class="content-header"> <h1>Generate Group Schedule</h1> </section> <section class="content"> <div class="tab-content"> <div class="active tab-pane" id="activity"> <div class="box box-primary"> <div class="box-body"> <form method="POST"> <div class="form-group"> <div class="text-center mt-6"> <label for="programSelect" class="col-sm-2 control-label"><?php echo LANG_VALUE_326; ?></label> <div class="col-sm-4"> <input type="hidden" name="reset" value="1"> <select class="form-control select2" name="Program_ID" id="Program_ID"> <option value=""><?php echo LANG_VALUE_326; ?></option> <?php $queryPrograms = "SELECT Program_ID, Program_Name FROM Programs"; $resultPrograms = $mysqli->query($queryPrograms); while ($program = $resultPrograms->fetch_assoc()) { echo "<option value='".$program["Program_ID"]."'>".$program["Program_Name"]."</option>"; } ?> </select> </div> </div> </div> <div class="form-group"> <div class="text-center mt-6"> <label for="Group_ID" class="col-sm-2 control-label"><?php echo LANG_VALUE_269; ?></label> <div class="col-sm-4"> <select class="form-control select2" name="Group_ID" id="Group_ID"> <option value=""><?php echo LANG_VALUE_269; ?></option> <!-- Groups will be loaded dynamically via AJAX --> </select> </div> </div> </div> <label>Start Date:</label> <input type="date" name="Start_Date" required> <label>End Date:</label> <input type="date" name="End_Date"> <label>Divide Days Evenly?</label> <input type="checkbox" id="courseTable" name="Divide_Evenly" value="1"> <div class="form-group"> <div class="text-center mt-6"> <label> <input type="checkbox" name="force_second_special_afternoon" <?= !empty($_POST['force_second_special_afternoon']) ? 'checked' : '' ?> /> Always push **second** special course’s **last** session into **Afternoon** </label> </div> </div> <div class="form-group"> <div class="text-center mt-6"> <label> <input type="checkbox" name="last_course_both_slots" <?= !empty($_POST['last_course_both_slots']) ? 'checked' : '' ?> /> When down to **one** course, schedule it in **both** Morning **and** Afternoon </label> </div> </div> <input type="submit" name="generate" value="Generate Schedule" class="btn btn-primary"> <div class="box-body table-responsive no-padding"> <table id="example2" class="table table-bordered table-striped"> <thead> <tr> <th>Select</th> <th>Course</th> <th>Suggested Sessions</th> <th>Custom Sessions</th> <th>Prioritize?</th> </tr> </thead> <tbody> <?php // Fetch courses to display BEFORE generating schedule if (isset($Program_ID)) { //$stmtCourses = $mysqli->prepare("SELECT Course_ID, Course_Name, Course_Time FROM Courses WHERE Program_ID = ?"); //$stmtCourses->bind_param("i", $Program_ID); //$stmtCourses->execute(); //$resultCourses = $stmtCourses->get_result(); // //while ($course = $resultCourses->fetch_assoc()) { // $suggestedSessions = ceil($course['Course_Time'] / 3); // Example: assume each session is 3 hours // echo "<tr> // <td><input type='checkbox' name='Selected_Courses[]' value='" . $course['Course_ID'] . "' /></td> // <td>" . htmlspecialchars($course['Course_Name']) . "</td> // <td><input type='number' name='Course_Sessions[" . $course['Course_ID'] . "]' value='" . $suggestedSessions . "' readonly /></td> // <td><input type='number' name='Custom_Sessions[" . $course['Course_ID'] . "]' value='" . $suggestedSessions . "' /></td> // <td><input type='checkbox' name='Priority_Course[" . $course['Course_ID'] . "]' /></td> // </tr>"; //} //$stmtCourses->close(); } ?> </tbody> </table> </div> </form> <?php // Save button //echo "<div class='text-center' style='margin-top: 20px;'> // <button id='save-schedule-btn' class='btn btn-success'>💾 Save Schedule</button> //</div>"; if (!empty($displayDates)) { echo "<script> const PROGRAM_ID = $Program_ID; const GROUP_ID = $Group_ID; </script>"; // Fetch group info before rendering table $stmt = $mysqli->prepare("SELECT Group_Name FROM manager_group_name WHERE Group_ID = ?"); $stmt->bind_param("i", $Group_ID); $stmt->execute(); $stmt->bind_result($groupName); $stmt->fetch(); $stmt->close(); // Show group name before the table echo "<div class='schedules-header'>"; echo "<h3 style='text-align:center; margin-bottom:18px; color:#29527a;'>"; echo "Schedule for <span style='color:#0d6efd'>" . htmlspecialchars($groupName) . "</span>"; echo "</h3></div>"; echo "<div id='schedule-container'>"; echo "<table id='schedule-table' class='table table-bordered table-striped'>"; echo "<thead><tr><th>Date</th><th>Slot</th><th>Time</th><th>Course</th><th>Action</th></tr></thead><tbody>"; $ajaxScheduleData = []; foreach ($displayDates as $date => $rows) { $dayName = (new DateTime($date))->format('l'); $isHoliday = array_key_exists($date, $holidayDates); $isRetake = array_key_exists($date, $retakeDates); // 🟡 Case: No sessions on this day if (empty($rows)) { $label = "No Sessions"; $bg = "#F9F9F9"; if ($isHoliday) { $label = "Holiday: " . htmlspecialchars($holidayDates[$date]); $bg = "#FFD700"; } elseif ($isRetake) { $label = "Retake: " . htmlspecialchars($retakeDates[$date]); $bg = "#CCE5FF"; } echo "<tr style='background-color: $bg; font-weight: bold;'> <td>($dayName) $date</td> <td>-</td><td>-</td><td>$label</td><td>-</td></tr>"; continue; } // 🗓️ Loop through scheduled classes on that day foreach ($rows as $row) { // --- Determine slot name correctly --- if (!empty($row['IsRetake'])) { // For retakes, the Slot already contains the correct human label (e.g., "Evening") $slotName = $row['Slot'] ?? '-'; } else if ($numSlots > 1) { // For multi-slot, use the slot index to get the label $slotIndex = (int) filter_var($row['Slot'], FILTER_SANITIZE_NUMBER_INT) - 1; // instead of parsing numbers, just use the string // $slotName = $row['Slot']; $slotName = isset($slotLabels[$slotIndex]) ? format_time_slot_label($slotLabels[$slotIndex], $dayName) : 'Unknown'; } else { // For single slot, use the only slot label $slotName = $slotLabels[0] ?? '-'; } // Universal slot label resolver /* if (!empty($row['IsRetake'])) { $slotName = $row['Slot'] ?? '-'; } else { // If Slot is numeric (e.g., "Slot 1") use slotLabels, else show as is if (preg_match('/^Slot (\d+)$/', $row['Slot'], $m)) { $slotIdx = intval($m[1]) - 1; $slotName = $slotLabels[$slotIdx] ?? $row['Slot']; } else { $slotName = $row['Slot']; // Already human label } } */ // --- Display row --- if (!empty($row['IsRetake'])) { echo "<tr style='background-color: #CCE5FF; font-weight: bold;'> <td>($dayName) $date</td> <td>{$slotName}</td> <td>{$row['Time']}</td> <td>" . htmlspecialchars($row['Retake_Type']) . "</td> <td>-</td> </tr>"; continue; } //$slotIndex = (int) filter_var($row['Slot'], FILTER_SANITIZE_NUMBER_INT) - 1; //$slotName = isset($slotLabels[$slotIndex]) // ? format_time_slot_label($slotLabels[$slotIndex], $dayName) // : 'Unknown'; // //if (!empty($row['IsRetake'])) { // echo "<tr style='background-color: #CCE5FF; font-weight: bold;'> // <td>($dayName) $date</td> // <td>{$slotName}</td> // <td>{$row['Time']}</td> // <td>" . htmlspecialchars($row['Retake_Type']) . "</td> // <td>-</td> // </tr>"; // continue; //} // ——— now your existing “real” row rendering ——— $courseName = $courseNames[$row['Course_ID']] ?? "Course ID {$row['Course_ID']}"; // More reliable way to find session index $index = null; foreach ($schedule as $i => $entry) { if ( $entry['Course_ID'] === $row['Course_ID'] && $entry['Date'] === $row['Date'] && $entry['Slot'] === $row['Slot'] ) { $index = $i; break; } } $isExam = isset($lastSessions[$row['Course_ID']]) && $index === $lastSessions[$row['Course_ID']]; // Highlight $style = ""; if ($isExam) { $style = "style='background-color: #FFCCCC; font-weight: bold;'"; } elseif ($isHoliday) { $style = "style='background-color: #FFF5D1;'"; } elseif ($isRetake) { $style = "style='background-color: #D9F0FF;'"; } $slotIndex = (int) filter_var($row['Slot'], FILTER_SANITIZE_NUMBER_INT) - 1; $slotName = isset($slotLabels[$slotIndex]) ? format_time_slot_label($slotLabels[$slotIndex], $dayName) : 'Unknown'; if(!($dayName === 'Saturday')) { if (preg_match('/^Slot (\d+)$/', $row['Slot'], $m)) { $slotIdx = intval($m[1]) - 1; $slotName = $slotLabels[$slotIdx] ?? $row['Slot']; } else { $slotName = $row['Slot']; // Already human label (Morning, Saturday, etc) } } // If Slot is numeric (e.g., "Slot 1") use slotLabels, else show as is /* if (preg_match('/^Slot (\d+)$/', $row['Slot'], $m)) { $slotIdx = intval($m[1]) - 1; $slotName = $slotLabels[$slotIdx] ?? $row['Slot']; } else { $slotName = $row['Slot']; // Already human label } */ // for both single- and multi-slot, trust the Slot label you already set //$slotName = $row['Slot']; $isSaturday = ($dayName === 'Saturday'); $actionBtn = $isSaturday ? "<button onclick=\"replaceSaturday('{$row['Date']}')\">🧹 Replace</button> <span id='replace-msg-{$row['Date']}'> </span>" : "-"; $ajaxScheduleData[] = [ 'program_id' => $Program_ID, 'course_id' => $row['Course_ID'], 'course_name' => $courseNames[$row['Course_ID']] ?? '', 'group_id' => $Group_ID, 'group_slot_id' => $row['Group_Slot_ID'], 'reserve_course' => $row['Reserve_Course'], 'time_slot' => $slotName, 'start_date' => $row['Date'], 'end_date' => $row['Date'], 'start_time' => $row['Start_Time'], 'end_time' => $row['End_Time'] ]; echo "<tr id='row-{$row['Date']}' $style>"; echo "<td>($dayName) {$row['Date']}</td>"; echo "<td>{$slotName}</td>"; echo "<td>{$row['Time']}</td>"; echo "<td>{$courseName}" . ($isExam ? " (Exam Day)" : "") . "</td>"; echo "<td>{$actionBtn}</td>"; echo "</tr>"; } } echo "</tbody><tfoot><tr><th>Date</th><th>Slot</th><th>Time</th><th>Course</th><th>Action</th></tr></tfoot>"; echo "</table></div><div class='text-center' style='margin-top: 20px;'> <button id='save-schedule-btn' class='btn btn-success'> 💾 Save Schedule </button> </div> </div>"; // *** Right here, after $ajaxScheduleData is fully built: *** echo "<script>\n"; echo " window.scheduleDataToSave = " . json_encode($ajaxScheduleData, JSON_NUMERIC_CHECK) . ";\n"; echo "</script>\n"; } ?> </div> </div> </div> </div> </section> </div> </div> <!-- Modal --> <div class="modal fade alert" id="squarespaceModal" tabindex="-1" role="dialog" aria-labelledby="modalLabel" aria-hidden="true"> <div class="modal-dialog"> <div class="modal-content"> <div class="modal-header"> <button type="button" class="close" data-dismiss="modal"> <span aria-hidden="true">x</span> <span class="sr-only"><?php echo LANG_VALUE_266; ?></span> </button> <h3 class="modal-title" id="lineModalLabel"><?php echo LANG_VALUE_265; ?></h3> </div> <div class="modal-body"> <form class="sidebar" method="post" name="registration_form" onsubmit="return validateForm()" action="<?php echo esc_url($_SERVER['REQUEST_URI']); ?>"> <?php $csrf->echoInputField(); ?> <div class="form-group"> <label for="Name"><?php echo LANG_VALUE_264; ?></label> <input type="hidden" class="form-control" id="g_id" name="g_id" required readonly> <input type="text" class="form-control" id="Group_Names" name="Group_Names" placeholder="<?php echo LANG_VALUE_264; ?>" required> </div> <div class="form-group"> <input type="submit" name="submits" value="<?php echo LANG_VALUE_5; ?>" class="btn btn-primary center-block"> </div> </form> <?php if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['Program_ID'], $_POST['Group_ID'], $_POST['Start_Date'], $_POST['End_Date'])) { $Program_ID = intval($_POST['Program_ID']); $Group_ID = intval($_POST['Group_ID']); $Start_Date = $_POST['Start_Date']; $End_Date = $_POST['End_Date']; $selectedCourses = $_POST['Selected_Courses'] ?? []; $courseSessions = $_POST['Course_Sessions'] ?? []; $priorityCourse = $_POST['Priority_Course'] ?? null; $evenlyDistribute = isset($_POST['Even_Distribution']); // Fetch group time slot configuration $stmt = $mysqli->prepare("SELECT Time_Slot, Time_From, Time_To, class_days FROM manager_group_name WHERE Program_ID = ? AND Group_ID = ?"); $stmt->bind_param("ii", $Program_ID, $Group_ID); $stmt->execute(); $groupData = $stmt->get_result()->fetch_assoc(); $stmt->close(); if (!$groupData) die("Invalid Group"); $timeSlots = explode(',', $groupData['Time_Slot']); $classDays = array_map('trim', explode(',', $groupData['class_days'])); // Build calendar date range excluding holidays and retakes $calendar = build_schedule_calendar($Start_Date, $End_Date, $classDays, $timeSlots, $mysqli, $Program_ID, $Group_ID); // Sort courses if priority specified if ($priorityCourse) { usort($selectedCourses, function($a, $b) use ($priorityCourse) { return ($a == $priorityCourse) ? -1 : 1; }); } $scheduleOutput = []; $courseIndex = 0; foreach ($selectedCourses as $courseID) { $sessionsNeeded = intval($courseSessions[$courseID]); $sessionsAssigned = 0; foreach ($calendar as $date => $slots) { foreach ($slots as $slot => $status) { if ($status === 'free' && $sessionsAssigned < $sessionsNeeded) { $scheduleOutput[] = [ 'date' => $date, 'slot' => $slot, 'course_id' => $courseID ]; $calendar[$date][$slot] = 'occupied'; $sessionsAssigned++; } } if ($sessionsAssigned >= $sessionsNeeded) break; } } // Output schedule result echo "<h3>Generated Schedule</h3>"; echo "<table class='table table-bordered'><thead><tr><th>Date</th><th>Time Slot</th><th>Course</th></tr></thead><tbody>"; foreach ($scheduleOutput as $row) { $date = $row['date']; $slot = $row['slot']; $courseID = $row['course_id']; $course = $mysqli->query("SELECT Course_Name FROM Courses WHERE Course_ID = $courseID")->fetch_assoc(); echo "<tr><td>$date</td><td>$slot</td><td>" . htmlspecialchars($course['Course_Name']) . "</td></tr>"; } echo "</tbody></table>"; } ?> </div> </div> </div> </div> <!-- jQuery 2.2.0 --> <script src="plugins/jQuery/jQuery-2.2.0.min.js"></script> <!-- Bootstrap --> <script src="bower_components/bootstrap/dist/js/bootstrap.min.js"></script> <!-- Select2 --> <script src="plugins/select2/select2.full.min.js"></script> <!-- InputMask --> <script src="plugins/input-mask/jquery.inputmask.js"></script> <script src="plugins/input-mask/jquery.inputmask.date.extensions.js"></script> <script src="plugins/input-mask/jquery.inputmask.extensions.js"></script> <!-- daterangepicker --> <script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.11.2/moment.min.js"></script> <script src="plugins/daterangepicker/daterangepicker.js"></script> <!-- bootstrap datepicker --> <script src="plugins/datepicker/bootstrap-datepicker.js"></script> <!-- bootstrap color picker --> <script src="plugins/colorpicker/bootstrap-colorpicker.min.js"></script> <!-- bootstrap time picker --> <script src="plugins/timepicker/bootstrap-timepicker.min.js"></script> <!-- SlimScroll --> <script src="plugins/slimScroll/jquery.slimscroll.min.js"></script> <!-- iCheck --> <script src="plugins/iCheck/icheck.min.js"></script> <!-- FastClick --> <script src="plugins/fastclick/fastclick.js"></script> <!-- AdminLTE App --> <script src="dist/js/app.min.js"></script> <!-- AdminLTE for demo purposes --> <script src="dist/js/demo.js"></script> <!-- DataTables --> <script src="plugins/datatables/jquery.dataTables.min.js"></script> <script src="plugins/datatables/dataTables.bootstrap.min.js"></script> <script src="https://cdn.datatables.net/buttons/1.2.4/js/dataTables.buttons.min.js"></script> <script src="https://cdn.datatables.net/buttons/1.2.4/js/buttons.flash.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/jszip/2.5.0/jszip.min.js"></script> <script src="https://cdn.rawgit.com/bpampuch/pdfmake/0.1.18/build/pdfmake.min.js"></script> <script src="https://cdn.rawgit.com/bpampuch/pdfmake/0.1.18/build/vfs_fonts.js"></script> <script src="https://cdn.datatables.net/buttons/1.2.4/js/buttons.html5.min.js"></script> <script src="https://cdn.datatables.net/buttons/1.2.4/js/buttons.print.min.js"></script> <script> function replaceSaturday(date) { if (confirm("Are you sure you want to replace the Saturday class on " + date + "?")) { console.log("🔁 Replacing Saturday class for:", date); const PROGRAM_ID = <?= json_encode($Program_ID) ?>; const GROUP_ID = <?= json_encode($Group_ID) ?>; const START_DATE = <?= json_encode($Start_Date) ?>; const SELECTED_COURSES = <?= json_encode($selectedCourses) ?>; const PRIORITY_COURSE = <?= json_encode($priorityCourses) ?>; const Course_Sessions = <?= json_encode($courseSessions) ?>; // Show loading spinner inside the schedule container const container = document.getElementById('schedule-container'); if (container) { container.innerHTML = "<div style='text-align:center; padding:20px; font-size:18px;'>⏳ Replacing Saturday... Please wait.</div>"; } const params = new URLSearchParams(); params.append('date', date); params.append('Program_ID', PROGRAM_ID); params.append('Group_ID', GROUP_ID); params.append('Start_Date', START_DATE); params.append('Selected_Courses', JSON.stringify(SELECTED_COURSES)); params.append('Priority_Course', JSON.stringify(PRIORITY_COURSE)); params.append('Course_Sessions', JSON.stringify(Course_Sessions)); fetch('ajax_replace_saturday', { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, body: params.toString() }) .then(response => response.json()) .then(result => { if (result.status === 'success') { if (container) { container.innerHTML = result.html; // Update the full table initializeScheduleTable(); } // This line updates the JS global variable window.scheduleDataToSave = result.ajaxScheduleData; alert('✅ Saturday class replaced successfully and schedule updated.'); } else { alert('⚠️ Failed to replace: ' + result.message); } }) .catch(error => { console.error('❌ AJAX Error:', error); if (container) { container.innerHTML = "<div style='color:red; text-align:center;'>❌ Failed to reload schedule. Please try again.</div>"; } }); } } function initializeScheduleTable() { $('#schedule-table').DataTable({ paging: false, lengthChange: false, searching: false, ordering: false, info: true, autoWidth: true, iDisplayLength: 100, dom: 'Bfrtip', buttons: ['copy', 'csv', 'excel', 'pdf', 'print'] }); } </script> <script> function validateForm() { var programID = document.getElementById("Program_ID").value; var groupID = document.getElementById("Group_ID").value; var teacherID = document.getElementById("Teacher_ID").value; if (programID === "" || groupID === "" || teacherID === "") { alert("Please select Program, Group and Teacher."); return false; } return true; } </script> <script> function toggleFields(checkbox) { let row = checkbox.closest('tr'); let inputs = row.querySelectorAll('.course-input'); let reserveCheckbox = row.querySelector('input[name^="Reserve_Course"]'); if (checkbox.checked) { inputs.forEach(input => input.removeAttribute('disabled')); reserveCheckbox.removeAttribute('disabled'); } else { inputs.forEach(input => input.setAttribute('disabled', 'true')); reserveCheckbox.checked = false; reserveCheckbox.setAttribute('disabled', 'true'); } countSelected(); } function countSelected() { let selectedCount = document.querySelectorAll('.activate-row:checked').length; console.log("Total Selected Courses: " + selectedCount); } </script> <script> $(document).ready(function() { $('#example3').DataTable({ "iDisplayLength": 100, dom: 'Bfrtip', buttons: ['copy', 'csv', 'excel', 'pdf', 'print'] }); }); </script> <script> $(document).ready(function() { $('#schedule-table').DataTable({ "paging": false, "lengthChange": false, "searching": false, "ordering": false, "info": true, "autoWidth": true, "iDisplayLength": 100, dom: 'Bfrtip', buttons: ['copy', 'csv', 'excel', 'pdf', 'print'] }); }); </script> <script> $(document).ready(function() { $('#example2').DataTable({ "iDisplayLength": 50, dom: 'Bfrtip', buttons: ['copy', 'csv', 'excel', 'pdf', 'print'] }); }); </script> <script> $(function () { $('#example1').dataTable({"iDisplayLength": 50}); $("#example2").DataTable(); $("#example3").DataTable(); $('#example4').DataTable({ "paging": true, "lengthChange": false, "searching": false, "ordering": true, "info": true, "autoWidth": false }); }); </script> <script> $(function () { $(".select2").select2(); $("#datemask").inputmask("dd/mm/yyyy", {"placeholder": "dd/mm/yyyy"}); $("#datemask2").inputmask("mm/dd/yyyy", {"placeholder": "mm/dd/yyyy"}); $("[data-mask]").inputmask(); $('#reservation').daterangepicker(); $('#reservationtime').daterangepicker({timePicker: true, timePickerIncrement: 30, format: 'MM/DD/YYYY h:mm A'}); $('#daterange-btn').daterangepicker({ ranges: { 'Today': [moment(), moment()], 'Yesterday': [moment().subtract(1, 'days'), moment().subtract(1, 'days')], 'Last 7 Days': [moment().subtract(6, 'days'), moment()], 'Last 30 Days': [moment().subtract(29, 'days'), moment()], 'This Month': [moment().startOf('month'), moment().endOf('month')], 'Last Month': [moment().subtract(1, 'month').startOf('month'), moment().subtract(1, 'month').endOf('month')] }, startDate: moment().subtract(29, 'days'), endDate: moment() }, function (start, end) { $('#daterange-btn span').html(start.format('MMMM D, YYYY') + ' - ' + end.format('MMMM D, YYYY')); }); $('#datepicker').datepicker({ autoclose: true }); $('input[type="checkbox"].minimal, input[type="radio"].minimal').iCheck({ checkboxClass: 'icheckbox_minimal-blue', radioClass: 'iradio_minimal-blue' }); $('input[type="checkbox"].minimal-red, input[type="radio"].minimal-red').iCheck({ checkboxClass: 'icheckbox_minimal-red', radioClass: 'iradio_minimal-red' }); $('input[type="checkbox"].flat-red, input[type="radio"].flat-red').iCheck({ checkboxClass: 'icheckbox_flat-green', radioClass: 'iradio_flat-green' }); $(".my-colorpicker1").colorpicker(); $(".my-colorpicker2").colorpicker(); $(".timepicker").timepicker({ showInputs: false }); }); </script> <script> /* document.addEventListener('DOMContentLoaded', function () { const saveBtn = document.getElementById('save-schedule-btn'); if (!saveBtn) { //alert("❌ Save button not found."); //return; } saveBtn.addEventListener('click', function () { const table = document.querySelector('#schedule-table'); const rows = table.querySelectorAll('tbody tr'); let scheduleData = []; rows.forEach(row => { const cells = row.querySelectorAll('td'); if (cells.length < 4) return; const PROGRAM_ID = <?= json_encode($Program_ID) ?>; const GROUP_ID = <?= json_encode($Group_ID) ?>; const rawCourse = cells[3].textContent.trim(); const date = cells[0].textContent.trim().replace(/\(.*?\)/, '').trim(); const slot = cells[1].textContent.trim(); const time = cells[2].textContent.trim(); const course = rawCourse.replace(" (Exam Day)", ""); scheduleData.push({ date, slot, time, course, program_id: PROGRAM_ID, group_id: GROUP_ID, is_exam: rawCourse.includes("Exam Day") }); }); console.log("📤 Sending scheduleData:", scheduleData); alert("📤 Sending " + scheduleData.length + " rows"); fetch('save_schedule', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify(scheduleData) }) .then(response => response.text()) .then(data => { alert("✅ Schedule saved: " + data); }) .catch(error => { console.error('❌ Error:', error); alert("❌ Error saving schedule."); }); }); }); */ </script> <?php else : ?> <?php include '../authorized.php'; ?> <?php endif; ?> </body> </html>
Save
cmd:
run