";
$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 "
📅 Expected End Date Based on Andragogical Days
";
//echo "
$expectedEndDate (" . (new DateTime($expectedEndDate))->format('l') . ")
";
// 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, Course_Code FROM Courses");
while ($row = $courseRes->fetch_assoc()) {
$courseNames[$row['Course_ID']] = $row['Course_Name'];
$courseCodes[$row['Course_ID']] = $row['Course_Code'];
}
//$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;
}
}
}
**/
// Rewrite Scheduling Section Only
$specialCourseOnlyMode = false;
$specialCourseID = null;
$lockedSlot = []; // 🔒 Lock course to a slot once it's one of the last two
foreach ($validDates as $date) {
$usedToday = [];
$dayName = (new DateTime($date))->format('l');
$activeCourses = array_filter($remainingSessions, fn($s) => $s > 0);
$activeCourseIDs = array_keys($activeCourses);
// Check for special mode (only special course left)
$specialCourseOnlyMode = false;
$specialCourseID = null;
if (count($activeCourseIDs) === 1) {
$cid = $activeCourseIDs[0];
$code = $courseCodes[$cid] ?? '';
if (in_array($code, ['961-238', '960-746'])) {
$specialCourseOnlyMode = true;
$specialCourseID = $cid;
}
}
foreach ([0, 1] as $slotIndex) {
$from = $timeFrom[$slotIndex] ?? null;
$to = $timeTo[$slotIndex] ?? null;
if (!$from || !$to) continue;
// ⛔ Skip if a Retake exists at this time slot
if (isset($retakeDates[$date][$slotIndex])) continue;
if ($specialCourseOnlyMode) {
$courseID = $specialCourseID;
} else {
// Filter valid candidates based on locked slot rules
$candidates = array_filter($selectedCourses, function($cid) use ($remainingSessions, $slotIndex, $lockedSlot) {
if (($remainingSessions[$cid] ?? 0) <= 0) return false;
if (isset($lockedSlot[$cid]) && $lockedSlot[$cid] !== $slotIndex) return false;
return true;
});
$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;
}
// 🔒 Lock course to this slot if it's one of the last two remaining
if (count($activeCourses) === 2) {
foreach ($remainingSessions as $cid => $cnt) {
if ($cnt > 0 && $cid !== $specialCourseID && !isset($lockedSlot[$cid])) {
$lockedSlot[$cid] = $slotIndex;
}
}
}
$slotLabel = $slotLabels[$slotIndex];
$courseName = $courseNames[$courseID] ?? "Course #$courseID";
$schedule[] = [
'Date' => $date,
'Slot' => "Slot " . ($slotIndex + 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 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 "
📘 Final Generated Schedule
"; print_r($schedule); echo "
";
//echo "
📋 Generated Schedule Table
";
//echo "
Group: " . htmlspecialchars($groupName) . "
";
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
// ]);
// }
//}
foreach ($retakeDates as $rDate => $type) {
if (!isset($displayDates[$rDate])) {
// Only inject retake row if there's no real retake already scheduled
$displayDates[$rDate][] = [
'Slot' => null,
'Time' => null,
'Course_ID' => null,
'IsRetake' => true,
'Retake_Type' => $type
];
}
}
// Sort the dates chronologically
ksort($displayDates);
} else {
echo "
🚫 No schedule generated — check if valid time slots, sessions, or dates are missing.
";
}
}
?>
Generate Group Schedule
Generate Group Schedule
//
//
";
if (!empty($displayDates)) {
echo "";
echo "
";
echo "
";
echo "
";
echo "
Date
Slot
Time
Course
Action
";
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 "
($dayName) $date
-
-
$label
-
";
continue;
}
// 🗓️ Loop through scheduled classes on that day
foreach ($rows as $row) {
// ——— handle our injected “retake-morning” row ———
if (!empty($row['IsRetake'])) {
echo "