From e53754d2a8d85193dc82e2b9844e06ab6a50038b Mon Sep 17 00:00:00 2001 From: haxala1r <53535669+haxala1r@users.noreply.github.com> Date: Thu, 18 Dec 2025 15:30:23 +0300 Subject: [PATCH 1/4] Modify Classroom and Course Schedule views to show generated schedule --- .../se302/controller/ExamEditDialog.java | 4 +- .../ScheduleClassroomController.java | 111 ++++++++++-------- .../controller/ScheduleCourseController.java | 81 +++++++------ 3 files changed, 107 insertions(+), 89 deletions(-) diff --git a/src/main/java/org/example/se302/controller/ExamEditDialog.java b/src/main/java/org/example/se302/controller/ExamEditDialog.java index 3334921..6cc451d 100644 --- a/src/main/java/org/example/se302/controller/ExamEditDialog.java +++ b/src/main/java/org/example/se302/controller/ExamEditDialog.java @@ -236,13 +236,13 @@ public class ExamEditDialog extends Dialog { validationPanel.setStyle("-fx-background-color: #e8f5e9; -fx-background-radius: 5;"); updateButtonStates(true, false); } else if (currentValidation.hasHardViolations()) { - validationStatusLabel.setText("❌ Constraint violations found"); + validationStatusLabel.setText("Constraint violations found!"); validationStatusLabel.setTextFill(Color.web("#e74c3c")); violationDetails.setText(currentValidation.getFormattedMessage()); validationPanel.setStyle("-fx-background-color: #ffebee; -fx-background-radius: 5;"); updateButtonStates(false, true); } else { - validationStatusLabel.setText("⚠️ Warnings found"); + validationStatusLabel.setText("Warnings found!"); validationStatusLabel.setTextFill(Color.web("#f39c12")); violationDetails.setText(currentValidation.getFormattedMessage()); validationPanel.setStyle("-fx-background-color: #fff3e0; -fx-background-radius: 5;"); diff --git a/src/main/java/org/example/se302/controller/ScheduleClassroomController.java b/src/main/java/org/example/se302/controller/ScheduleClassroomController.java index b0a9a58..330645f 100644 --- a/src/main/java/org/example/se302/controller/ScheduleClassroomController.java +++ b/src/main/java/org/example/se302/controller/ScheduleClassroomController.java @@ -11,14 +11,13 @@ import javafx.scene.control.TableColumn; import javafx.scene.control.TableRow; import javafx.scene.control.TableView; import org.example.se302.model.Classroom; -import org.example.se302.model.ExamAssignment; +import org.example.se302.model.Course; import org.example.se302.model.ScheduleConfiguration; import org.example.se302.service.DataManager; import java.time.LocalDate; import java.time.format.DateTimeFormatter; import java.util.Comparator; -import java.util.Map; /** * Controller for the Classroom Schedule view. @@ -47,10 +46,6 @@ public class ScheduleClassroomController { private DataManager dataManager; - // Reference to the current schedule state (set by parent controller) - private Map currentAssignments; - private ScheduleConfiguration configuration; - @FXML public void initialize() { dataManager = DataManager.getInstance(); @@ -112,15 +107,35 @@ public class ScheduleClassroomController { } } }); - } - /** - * Sets the current schedule assignments. Called by parent controller after - * schedule generation. - */ - public void setScheduleData(Map assignments, ScheduleConfiguration config) { - this.currentAssignments = assignments; - this.configuration = config; + // Refresh when tab is selected - find TabPane once scene is available + scheduleTable.sceneProperty().addListener((obs, oldScene, newScene) -> { + if (newScene != null) { + // Find parent TabPane and listen for selection changes + javafx.scene.Parent parent = scheduleTable.getParent(); + while (parent != null) { + if (parent.getParent() instanceof javafx.scene.control.TabPane) { + javafx.scene.control.TabPane tabPane = (javafx.scene.control.TabPane) parent + .getParent(); + // Find which tab contains our content + for (javafx.scene.control.Tab tab : tabPane.getTabs()) { + if (tab.getContent() == parent) { + tab.selectedProperty().addListener( + (o, wasSelected, isSelected) -> { + if (isSelected && classroomComboBox + .getValue() != null) { + onShowSchedule(); + } + }); + break; + } + } + break; + } + parent = parent.getParent(); + } + } + }); } @FXML @@ -132,52 +147,52 @@ public class ScheduleClassroomController { selectedClassroomLabel.setText("Schedule for: " + selected.getClassroomId() + " (Capacity: " + selected.getCapacity() + ")"); + ScheduleConfiguration config = dataManager.getActiveConfiguration(); ObservableList entries = FXCollections.observableArrayList(); int totalSlots = 0; int usedSlots = 0; int totalStudents = 0; - // If we have assignments, filter by this classroom - if (currentAssignments != null && !currentAssignments.isEmpty()) { - for (ExamAssignment assignment : currentAssignments.values()) { - if (assignment.isAssigned() && - selected.getClassroomId().equals(assignment.getClassroomId())) { + // Find all courses scheduled in this classroom + for (Course course : dataManager.getCourses()) { + if (course.isScheduled() && + selected.getClassroomId().equals(course.getAssignedClassroom())) { - // Format date - String dateStr; - if (configuration != null && configuration.getStartDate() != null) { - LocalDate examDate = configuration.getStartDate() - .plusDays(assignment.getDay()); - dateStr = examDate.format(DateTimeFormatter.ofPattern("dd/MM/yyyy")); - } else { - dateStr = "Day " + (assignment.getDay() + 1); - } + int dayIndex = course.getExamDay(); + int slotIndex = course.getExamTimeSlot(); - // Format time - String timeStr = "Slot " + (assignment.getTimeSlotIndex() + 1); - - // Calculate utilization percentage for this slot - int studentCount = assignment.getStudentCount(); - int capacity = selected.getCapacity(); - int utilizationPercent = capacity > 0 ? (studentCount * 100) / capacity : 0; - String utilizationStr = utilizationPercent + "%"; - - entries.add(new ClassroomSlotEntry( - dateStr, timeStr, assignment.getCourseCode(), - studentCount, utilizationStr, utilizationPercent, - assignment.getDay(), assignment.getTimeSlotIndex())); - - usedSlots++; - totalStudents += studentCount; + // Format date + String dateStr; + if (config != null && config.getStartDate() != null) { + LocalDate examDate = config.getStartDate().plusDays(dayIndex); + dateStr = examDate.format(DateTimeFormatter.ofPattern("dd/MM/yyyy")); + } else { + dateStr = "Day " + (dayIndex + 1); } - } - // Calculate total possible slots - if (configuration != null) { - totalSlots = configuration.getNumDays() * configuration.getSlotsPerDay(); + String timeStr = "Slot " + (slotIndex + 1); + + // Calculate utilization + int studentCount = course.getEnrolledStudentsCount(); + int capacity = selected.getCapacity(); + int utilizationPercent = capacity > 0 ? (studentCount * 100) / capacity : 0; + String utilizationStr = utilizationPercent + "%"; + + entries.add(new ClassroomSlotEntry( + dateStr, timeStr, course.getCourseCode(), + studentCount, utilizationStr, utilizationPercent, + dayIndex, slotIndex)); + + usedSlots++; + totalStudents += studentCount; } } + // Calculate total possible slots + if (config != null) { + totalSlots = config.getNumDays() * config.getSlotsPerDay(); + } + // Sort by day then slot entries.sort(Comparator.comparingInt(ClassroomSlotEntry::getDayIndex) .thenComparingInt(ClassroomSlotEntry::getSlotIndex)); diff --git a/src/main/java/org/example/se302/controller/ScheduleCourseController.java b/src/main/java/org/example/se302/controller/ScheduleCourseController.java index bae7a9a..e50c8ca 100644 --- a/src/main/java/org/example/se302/controller/ScheduleCourseController.java +++ b/src/main/java/org/example/se302/controller/ScheduleCourseController.java @@ -9,14 +9,12 @@ import javafx.scene.control.TableColumn; import javafx.scene.control.TableRow; import javafx.scene.control.TableView; import org.example.se302.model.Course; -import org.example.se302.model.ExamAssignment; import org.example.se302.model.ScheduleConfiguration; import org.example.se302.service.DataManager; import java.time.LocalDate; import java.time.format.DateTimeFormatter; import java.util.Comparator; -import java.util.Map; // Color palette for days (pastel colors for readability) // Day 0 = Light Blue, Day 1 = Light Green, Day 2 = Light Yellow, etc. @@ -43,11 +41,6 @@ public class ScheduleCourseController { private DataManager dataManager; - // Reference to the current schedule state (set by parent controller or loaded - // from DB) - private Map currentAssignments; - private ScheduleConfiguration configuration; - @FXML public void initialize() { dataManager = DataManager.getInstance(); @@ -122,19 +115,38 @@ public class ScheduleCourseController { // Listen for data changes dataManager.getCourses().addListener( (javafx.collections.ListChangeListener) c -> loadScheduleData()); - } - /** - * Sets the current schedule assignments. Called by parent controller after - * schedule generation. - */ - public void setScheduleData(Map assignments, ScheduleConfiguration config) { - this.currentAssignments = assignments; - this.configuration = config; - loadScheduleData(); + // Refresh when tab is selected - find TabPane once scene is available + courseScheduleTable.sceneProperty().addListener((obs, oldScene, newScene) -> { + if (newScene != null) { + loadScheduleData(); + // Find parent TabPane and listen for selection changes + javafx.scene.Parent parent = courseScheduleTable.getParent(); + while (parent != null) { + if (parent.getParent() instanceof javafx.scene.control.TabPane) { + javafx.scene.control.TabPane tabPane = (javafx.scene.control.TabPane) parent + .getParent(); + // Find which tab contains our content + for (javafx.scene.control.Tab tab : tabPane.getTabs()) { + if (tab.getContent() == parent) { + tab.selectedProperty().addListener( + (o, wasSelected, isSelected) -> { + if (isSelected) + loadScheduleData(); + }); + break; + } + } + break; + } + parent = parent.getParent(); + } + } + }); } private void loadScheduleData() { + ScheduleConfiguration config = dataManager.getActiveConfiguration(); ObservableList entries = FXCollections.observableArrayList(); for (Course course : dataManager.getCourses()) { @@ -144,33 +156,24 @@ public class ScheduleCourseController { String dateStr = "Not Scheduled"; String timeStr = "-"; String classroomStr = "-"; - int dayIndex = Integer.MAX_VALUE; // For sorting unscheduled items last + int dayIndex = Integer.MAX_VALUE; int slotIndex = Integer.MAX_VALUE; - // Check if we have an assignment for this course - if (currentAssignments != null && currentAssignments.containsKey(courseCode)) { - ExamAssignment assignment = currentAssignments.get(courseCode); + // Check if this course has been scheduled + if (course.isScheduled()) { + dayIndex = course.getExamDay(); + slotIndex = course.getExamTimeSlot(); + classroomStr = course.getAssignedClassroom(); - if (assignment.isAssigned()) { - dayIndex = assignment.getDay(); - slotIndex = assignment.getTimeSlotIndex(); - - // Format date based on configuration start date + day offset - if (configuration != null && configuration.getStartDate() != null) { - LocalDate examDate = configuration.getStartDate().plusDays(dayIndex); - dateStr = examDate.format( - DateTimeFormatter.ofPattern("dd/MM/yyyy (EEEE)")); - } else { - dateStr = "Day " + (dayIndex + 1); - } - - // Format time slot - timeStr = "Slot " + (slotIndex + 1); - - // Classroom - classroomStr = assignment.getClassroomId() != null ? assignment.getClassroomId() - : "-"; + // Format date using configuration's start date + if (config != null && config.getStartDate() != null) { + LocalDate examDate = config.getStartDate().plusDays(dayIndex); + dateStr = examDate.format(DateTimeFormatter.ofPattern("dd/MM/yyyy (EEEE)")); + } else { + dateStr = "Day " + (dayIndex + 1); } + + timeStr = "Slot " + (slotIndex + 1); } entries.add(new CourseScheduleEntry(courseCode, enrolled, dateStr, timeStr, classroomStr, From 93ac7d55a602d7219f1e05a44af2ae4751f1cddb Mon Sep 17 00:00:00 2001 From: haxala1r <53535669+haxala1r@users.noreply.github.com> Date: Thu, 18 Dec 2025 15:47:42 +0300 Subject: [PATCH 2/4] Added the option to export as CSV to student, classroom, course and calendar schedule views. --- .../ScheduleCalendarController.java | 64 +++++++++++++++++++ .../ScheduleClassroomController.java | 58 +++++++++++++++++ .../controller/ScheduleCourseController.java | 51 +++++++++++++++ .../controller/ScheduleStudentController.java | 58 +++++++++++++++++ .../se302/view/schedule-calendar-view.fxml | 2 +- .../se302/view/schedule-classroom-view.fxml | 1 + .../se302/view/schedule-course-view.fxml | 6 +- .../se302/view/schedule-student-view.fxml | 1 + 8 files changed, 239 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/example/se302/controller/ScheduleCalendarController.java b/src/main/java/org/example/se302/controller/ScheduleCalendarController.java index 550cc86..c4c333e 100644 --- a/src/main/java/org/example/se302/controller/ScheduleCalendarController.java +++ b/src/main/java/org/example/se302/controller/ScheduleCalendarController.java @@ -745,4 +745,68 @@ public class ScheduleCalendarController { "Could not update the exam assignment. The exam may be locked."); } } + + /** + * Exports the schedule as a CSV grid (days × time slots). + */ + @FXML + private void onExportCSV() { + if (currentSchedule == null || currentConfig == null) { + showAlert(Alert.AlertType.WARNING, "No Schedule", + "Please generate a schedule first."); + return; + } + + javafx.stage.FileChooser fileChooser = new javafx.stage.FileChooser(); + fileChooser.setTitle("Export Schedule as CSV"); + fileChooser.getExtensionFilters().add( + new javafx.stage.FileChooser.ExtensionFilter("CSV Files", "*.csv")); + fileChooser.setInitialFileName("schedule_calendar.csv"); + + java.io.File file = fileChooser.showSaveDialog(scheduleGrid.getScene().getWindow()); + if (file == null) + return; + + try (java.io.PrintWriter writer = new java.io.PrintWriter(file)) { + int numDays = currentConfig.getNumDays(); + int slotsPerDay = currentConfig.getSlotsPerDay(); + + // Header row: Time Slot, Day 1, Day 2, ... + StringBuilder header = new StringBuilder("Time Slot"); + for (int day = 0; day < numDays; day++) { + LocalDate date = currentConfig.getStartDate().plusDays(day); + header.append(",").append(date.toString()); + } + writer.println(header); + + // Data rows: one per time slot + for (int slot = 0; slot < slotsPerDay; slot++) { + StringBuilder row = new StringBuilder(); + TimeSlot timeSlot = currentConfig.getTimeSlot(0, slot); + String slotLabel = timeSlot != null + ? timeSlot.getStartTime() + "-" + timeSlot.getEndTime() + : "Slot " + (slot + 1); + row.append(slotLabel); + + for (int day = 0; day < numDays; day++) { + row.append(","); + // Find exams at this day/slot + List exams = new ArrayList<>(); + for (ExamAssignment a : currentSchedule.getAssignments().values()) { + if (a.isAssigned() && a.getDay() == day && a.getTimeSlotIndex() == slot) { + exams.add(a.getCourseCode() + " (" + a.getClassroomId() + ")"); + } + } + row.append("\"").append(String.join("; ", exams)).append("\""); + } + writer.println(row); + } + + showAlert(Alert.AlertType.INFORMATION, "Export Complete", + "Schedule exported to:\n" + file.getAbsolutePath()); + } catch (java.io.IOException e) { + showAlert(Alert.AlertType.ERROR, "Export Failed", + "Could not write file: " + e.getMessage()); + } + } } diff --git a/src/main/java/org/example/se302/controller/ScheduleClassroomController.java b/src/main/java/org/example/se302/controller/ScheduleClassroomController.java index 330645f..1113655 100644 --- a/src/main/java/org/example/se302/controller/ScheduleClassroomController.java +++ b/src/main/java/org/example/se302/controller/ScheduleClassroomController.java @@ -265,4 +265,62 @@ public class ScheduleClassroomController { return slotIndex; } } + + /** + * Exports the selected classroom's schedule as CSV. + */ + @FXML + private void onExportCSV() { + Classroom selected = classroomComboBox.getValue(); + if (selected == null) { + showAlert(javafx.scene.control.Alert.AlertType.WARNING, "No Classroom", + "Please select a classroom first."); + return; + } + + if (scheduleTable.getItems().isEmpty()) { + showAlert(javafx.scene.control.Alert.AlertType.WARNING, "No Data", + "No schedule data to export."); + return; + } + + javafx.stage.FileChooser fileChooser = new javafx.stage.FileChooser(); + fileChooser.setTitle("Export Classroom Schedule as CSV"); + fileChooser.getExtensionFilters().add( + new javafx.stage.FileChooser.ExtensionFilter("CSV Files", "*.csv")); + fileChooser.setInitialFileName("schedule_classroom_" + selected.getClassroomId() + ".csv"); + + java.io.File file = fileChooser.showSaveDialog(scheduleTable.getScene().getWindow()); + if (file == null) + return; + + try (java.io.PrintWriter writer = new java.io.PrintWriter(file)) { + // Header + writer.println("Classroom,Date,Time,Course,Students"); + + // Data rows + for (ClassroomSlotEntry entry : scheduleTable.getItems()) { + writer.println(String.format("%s,\"%s\",%s,%s,%d", + selected.getClassroomId(), + entry.getDate(), + entry.getTime(), + entry.getCourse(), + entry.getStudentCount())); + } + + showAlert(javafx.scene.control.Alert.AlertType.INFORMATION, "Export Complete", + "Classroom schedule exported to:\n" + file.getAbsolutePath()); + } catch (java.io.IOException e) { + showAlert(javafx.scene.control.Alert.AlertType.ERROR, "Export Failed", + "Could not write file: " + e.getMessage()); + } + } + + private void showAlert(javafx.scene.control.Alert.AlertType type, String title, String message) { + javafx.scene.control.Alert alert = new javafx.scene.control.Alert(type); + alert.setTitle(title); + alert.setHeaderText(null); + alert.setContentText(message); + alert.showAndWait(); + } } diff --git a/src/main/java/org/example/se302/controller/ScheduleCourseController.java b/src/main/java/org/example/se302/controller/ScheduleCourseController.java index e50c8ca..4c85063 100644 --- a/src/main/java/org/example/se302/controller/ScheduleCourseController.java +++ b/src/main/java/org/example/se302/controller/ScheduleCourseController.java @@ -236,4 +236,55 @@ public class ScheduleCourseController { return slotIndex; } } + + /** + * Exports the course schedule as a CSV file. + */ + @FXML + private void onExportCSV() { + if (courseScheduleTable.getItems().isEmpty()) { + showAlert(javafx.scene.control.Alert.AlertType.WARNING, "No Data", + "No schedule data to export."); + return; + } + + javafx.stage.FileChooser fileChooser = new javafx.stage.FileChooser(); + fileChooser.setTitle("Export Course Schedule as CSV"); + fileChooser.getExtensionFilters().add( + new javafx.stage.FileChooser.ExtensionFilter("CSV Files", "*.csv")); + fileChooser.setInitialFileName("schedule_courses.csv"); + + java.io.File file = fileChooser.showSaveDialog(courseScheduleTable.getScene().getWindow()); + if (file == null) + return; + + try (java.io.PrintWriter writer = new java.io.PrintWriter(file)) { + // Header + writer.println("Course Code,Date,Time,Classroom,Enrolled Students"); + + // Data rows + for (CourseScheduleEntry entry : courseScheduleTable.getItems()) { + writer.println(String.format("%s,\"%s\",%s,%s,%d", + entry.getCourseCode(), + entry.getDate(), + entry.getTime(), + entry.getClassroom(), + entry.getEnrolledCount())); + } + + showAlert(javafx.scene.control.Alert.AlertType.INFORMATION, "Export Complete", + "Course schedule exported to:\n" + file.getAbsolutePath()); + } catch (java.io.IOException e) { + showAlert(javafx.scene.control.Alert.AlertType.ERROR, "Export Failed", + "Could not write file: " + e.getMessage()); + } + } + + private void showAlert(javafx.scene.control.Alert.AlertType type, String title, String message) { + javafx.scene.control.Alert alert = new javafx.scene.control.Alert(type); + alert.setTitle(title); + alert.setHeaderText(null); + alert.setContentText(message); + alert.showAndWait(); + } } diff --git a/src/main/java/org/example/se302/controller/ScheduleStudentController.java b/src/main/java/org/example/se302/controller/ScheduleStudentController.java index 00b646d..de3a6df 100644 --- a/src/main/java/org/example/se302/controller/ScheduleStudentController.java +++ b/src/main/java/org/example/se302/controller/ScheduleStudentController.java @@ -230,4 +230,62 @@ public class ScheduleStudentController { return slotIndex; } } + + /** + * Exports the selected student's schedule as CSV. + */ + @FXML + private void onExportCSV() { + Student selected = studentComboBox.getValue(); + if (selected == null) { + showAlert(Alert.AlertType.WARNING, "No Student", + "Please select a student first."); + return; + } + + if (scheduleTable.getItems().isEmpty()) { + showAlert(Alert.AlertType.WARNING, "No Data", + "No schedule data to export."); + return; + } + + javafx.stage.FileChooser fileChooser = new javafx.stage.FileChooser(); + fileChooser.setTitle("Export Student Schedule as CSV"); + fileChooser.getExtensionFilters().add( + new javafx.stage.FileChooser.ExtensionFilter("CSV Files", "*.csv")); + fileChooser.setInitialFileName("schedule_student_" + selected.getStudentId() + ".csv"); + + java.io.File file = fileChooser.showSaveDialog(scheduleTable.getScene().getWindow()); + if (file == null) + return; + + try (java.io.PrintWriter writer = new java.io.PrintWriter(file)) { + // Header + writer.println("Student ID,Course,Date,Time,Classroom"); + + // Data rows + for (CourseScheduleEntry entry : scheduleTable.getItems()) { + writer.println(String.format("%s,%s,\"%s\",%s,%s", + selected.getStudentId(), + entry.getCourseCode(), + entry.getDateDisplay(), + entry.getTimeDisplay(), + entry.getClassroom())); + } + + showAlert(Alert.AlertType.INFORMATION, "Export Complete", + "Student schedule exported to:\n" + file.getAbsolutePath()); + } catch (java.io.IOException e) { + showAlert(Alert.AlertType.ERROR, "Export Failed", + "Could not write file: " + e.getMessage()); + } + } + + private void showAlert(Alert.AlertType type, String title, String message) { + Alert alert = new Alert(type); + alert.setTitle(title); + alert.setHeaderText(null); + alert.setContentText(message); + alert.showAndWait(); + } } diff --git a/src/main/resources/org/example/se302/view/schedule-calendar-view.fxml b/src/main/resources/org/example/se302/view/schedule-calendar-view.fxml index 1957d14..f365751 100644 --- a/src/main/resources/org/example/se302/view/schedule-calendar-view.fxml +++ b/src/main/resources/org/example/se302/view/schedule-calendar-view.fxml @@ -123,7 +123,7 @@