diff --git a/src/main/java/org/example/se302/controller/ScheduleCalendarController.java b/src/main/java/org/example/se302/controller/ScheduleCalendarController.java index 9e29ca3..f4dced4 100644 --- a/src/main/java/org/example/se302/controller/ScheduleCalendarController.java +++ b/src/main/java/org/example/se302/controller/ScheduleCalendarController.java @@ -121,7 +121,7 @@ public class ScheduleCalendarController { private void initializeSpinners() { // Number of days spinner (1-30) - SpinnerValueFactory daysFactory = new SpinnerValueFactory.IntegerSpinnerValueFactory(1, 30, 5); + SpinnerValueFactory daysFactory = new SpinnerValueFactory.IntegerSpinnerValueFactory(1, 30, 8); numDaysSpinner.setValueFactory(daysFactory); // Slots per day spinner (1-10) @@ -362,6 +362,10 @@ public class ScheduleCalendarController { } private void updateStatistics(ScheduleState schedule, long durationMs) { + // Update total courses (BUG FIX: this was missing!) + totalCoursesLabel.setText(String.valueOf(schedule.getTotalCourses())); + + // Update scheduled courses scheduledCoursesLabel.setText(String.valueOf(schedule.getAssignedCourses())); // Count unique classrooms used @@ -390,6 +394,12 @@ public class ScheduleCalendarController { int numDays = config.getNumDays(); int slotsPerDay = config.getSlotsPerDay(); + // Dynamically set minimum grid width to fit all columns + // Time column (120px) + each day column (200px) + padding + double minGridWidth = 120 + (numDays * 200) + 40; + scheduleGrid.setMinWidth(minGridWidth); + scheduleGrid.setPrefWidth(minGridWidth); + // Column constraints - make columns wider for better visibility ColumnConstraints headerCol = new ColumnConstraints(); headerCol.setMinWidth(100); @@ -398,9 +408,9 @@ public class ScheduleCalendarController { for (int day = 0; day < numDays; day++) { ColumnConstraints dayCol = new ColumnConstraints(); - dayCol.setMinWidth(180); - dayCol.setPrefWidth(220); - dayCol.setHgrow(Priority.ALWAYS); + dayCol.setMinWidth(135); + dayCol.setPrefWidth(150); + dayCol.setHgrow(Priority.NEVER); // Prevent shrinking, allow horizontal scroll scheduleGrid.getColumnConstraints().add(dayCol); } @@ -531,9 +541,10 @@ public class ScheduleCalendarController { examBox.setOnDragDone(e -> { draggedExam = null; examBox.setOpacity(1.0); - // Refresh grid to reset all cell colors + // Defer grid refresh to after drag event completes to avoid crash + // when modifying scene graph during active event processing if (currentSchedule != null && currentConfig != null) { - displayScheduleGrid(currentSchedule, currentConfig); + Platform.runLater(() -> displayScheduleGrid(currentSchedule, currentConfig)); } e.consume(); }); diff --git a/src/main/java/org/example/se302/model/ScheduleConfiguration.java b/src/main/java/org/example/se302/model/ScheduleConfiguration.java index bc235cd..6f9ecf8 100644 --- a/src/main/java/org/example/se302/model/ScheduleConfiguration.java +++ b/src/main/java/org/example/se302/model/ScheduleConfiguration.java @@ -82,7 +82,7 @@ public class ScheduleConfiguration { * Creates a default schedule configuration. */ public ScheduleConfiguration() { - this.numDays = 5; + this.numDays = 8; this.slotsPerDay = 4; this.startDate = LocalDate.now().plusDays(7); // Default: start in a week this.slotDurationMinutes = 120; // 2 hours diff --git a/src/main/java/org/example/se302/service/ConstraintValidator.java b/src/main/java/org/example/se302/service/ConstraintValidator.java index abbd03d..ec41469 100644 --- a/src/main/java/org/example/se302/service/ConstraintValidator.java +++ b/src/main/java/org/example/se302/service/ConstraintValidator.java @@ -63,28 +63,36 @@ public class ConstraintValidator { ValidationResult doubleBookingResult = checkNoDoubleBooking(assignment, scheduleState); result.merge(doubleBookingResult); - // Check student constraints - only check same time slot conflicts (always - // required) + // Check student constraints Course course = dataManager.getCourse(assignment.getCourseCode()); if (course != null) { - // Check if student has another exam at the SAME time slot (hard constraint) + // HARD CONSTRAINT: Check if student has another exam at the SAME time slot + // This is ALWAYS required ValidationResult sameTimeResult = checkNoSameTimeExams(assignment, scheduleState, course); result.merge(sameTimeResult); - // Only check consecutive and max per day if back-to-back is NOT allowed + // HARD CONSTRAINT: Max 2 exams per day per student + // This is ALWAYS enforced, regardless of allowBackToBack flag + for (String studentId : course.getEnrolledStudents()) { + ValidationResult maxPerDayResult = checkMaxTwoExamsPerDay( + studentId, assignment, scheduleState); + result.merge(maxPerDayResult); + + // If max per day violated, no need to check more students + if (!result.isValid()) { + break; + } + } + + // SOFT CONSTRAINT: No consecutive exams (back-to-back) + // Only enforced when allowBackToBack = false if (!allowBackToBack) { for (String studentId : course.getEnrolledStudents()) { - // Check no consecutive exams (soft - skip if allowBackToBack) ValidationResult consecutiveResult = checkNoConsecutiveExams( studentId, assignment, scheduleState); result.merge(consecutiveResult); - // Check max 2 exams per day - ValidationResult maxPerDayResult = checkMaxTwoExamsPerDay( - studentId, assignment, scheduleState); - result.merge(maxPerDayResult); - - // If already invalid, no need to check more students + // If consecutive violation, no need to check more students if (!result.isValid()) { break; } diff --git a/src/main/java/org/example/se302/service/ScheduleGeneratorService.java b/src/main/java/org/example/se302/service/ScheduleGeneratorService.java index e5fba48..a9d6d31 100644 --- a/src/main/java/org/example/se302/service/ScheduleGeneratorService.java +++ b/src/main/java/org/example/se302/service/ScheduleGeneratorService.java @@ -170,10 +170,13 @@ public class ScheduleGeneratorService { // Try each classroom for (Classroom classroom : suitableClassrooms) { - // Temporarily assign - assignment.setDay(timeSlot.day); - assignment.setTimeSlotIndex(timeSlot.slot); - assignment.setClassroomId(classroom.getClassroomId()); + // Temporarily assign (using updateAssignment to maintain assignedCourses counter) + scheduleState.updateAssignment( + assignment.getCourseCode(), + timeSlot.day, + timeSlot.slot, + classroom.getClassroomId() + ); // Validate assignment - pass allowBackToBack from config ConstraintValidator.ValidationResult validationResult = validator.validateAssignment(assignment, @@ -186,10 +189,13 @@ public class ScheduleGeneratorService { } } - // Backtrack: reset assignment - assignment.setDay(-1); - assignment.setTimeSlotIndex(-1); - assignment.setClassroomId(null); + // Backtrack: reset assignment (using updateAssignment to maintain assignedCourses counter) + scheduleState.updateAssignment( + assignment.getCourseCode(), + -1, + -1, + null + ); } } diff --git a/src/main/resources/org/example/se302/view/import-view.fxml b/src/main/resources/org/example/se302/view/import-view.fxml index f1056c0..78e544b 100644 --- a/src/main/resources/org/example/se302/view/import-view.fxml +++ b/src/main/resources/org/example/se302/view/import-view.fxml @@ -101,9 +101,9 @@