diff --git a/src/main/java/org/example/se302/controller/CoursesController.java b/src/main/java/org/example/se302/controller/CoursesController.java index cbd7a47..ee3bc74 100644 --- a/src/main/java/org/example/se302/controller/CoursesController.java +++ b/src/main/java/org/example/se302/controller/CoursesController.java @@ -15,20 +15,33 @@ import org.example.se302.service.DataManager; */ public class CoursesController { - @FXML private TextField searchField; - @FXML private Label resultCountLabel; - @FXML private TableView coursesTable; - @FXML private TableColumn courseCodeColumn; - @FXML private TableColumn studentCountColumn; - @FXML private TableColumn classroomColumn; - @FXML private TableColumn examDateColumn; - @FXML private TableColumn actionColumn; + @FXML + private TextField searchField; + @FXML + private Label resultCountLabel; + @FXML + private TableView coursesTable; + @FXML + private TableColumn courseCodeColumn; + @FXML + private TableColumn studentCountColumn; + @FXML + private TableColumn classroomColumn; + @FXML + private TableColumn examDateColumn; + @FXML + private TableColumn actionColumn; - @FXML private VBox studentListPanel; - @FXML private Label studentListTitleLabel; - @FXML private TableView enrolledStudentsTable; - @FXML private TableColumn enrolledStudentIdColumn; - @FXML private Label enrolledCountLabel; + @FXML + private VBox studentListPanel; + @FXML + private Label studentListTitleLabel; + @FXML + private TableView enrolledStudentsTable; + @FXML + private TableColumn enrolledStudentIdColumn; + @FXML + private Label enrolledCountLabel; private DataManager dataManager; private FilteredList filteredCourses; @@ -38,11 +51,10 @@ public class CoursesController { dataManager = DataManager.getInstance(); // Set up table columns - courseCodeColumn.setCellValueFactory(cellData -> - new SimpleStringProperty(cellData.getValue().getCourseCode())); + courseCodeColumn.setCellValueFactory(cellData -> new SimpleStringProperty(cellData.getValue().getCourseCode())); - studentCountColumn.setCellValueFactory(cellData -> - new SimpleIntegerProperty(cellData.getValue().getEnrolledStudentsCount())); + studentCountColumn.setCellValueFactory( + cellData -> new SimpleIntegerProperty(cellData.getValue().getEnrolledStudentsCount())); classroomColumn.setCellValueFactory(cellData -> { String classroom = cellData.getValue().getAssignedClassroom(); @@ -50,8 +62,12 @@ public class CoursesController { }); examDateColumn.setCellValueFactory(cellData -> { - String examDate = cellData.getValue().getExamDateTime(); - return new SimpleStringProperty(examDate != null ? examDate : "Not Scheduled"); + Course course = cellData.getValue(); + if (course.isScheduled()) { + return new SimpleStringProperty("Day " + (course.getExamDay() + 1) + + ", Slot " + (course.getExamTimeSlot() + 1)); + } + return new SimpleStringProperty("Not Scheduled"); }); // Add "View Students" button to action column @@ -73,8 +89,7 @@ public class CoursesController { }); // Set up enrolled students table column - enrolledStudentIdColumn.setCellValueFactory(cellData -> - new SimpleStringProperty(cellData.getValue())); + enrolledStudentIdColumn.setCellValueFactory(cellData -> new SimpleStringProperty(cellData.getValue())); // Set up filtered list filteredCourses = new FilteredList<>(dataManager.getCourses(), p -> true); @@ -106,14 +121,13 @@ public class CoursesController { filteredCourses.setPredicate(course -> true); } else { String lowerCaseFilter = searchText.toLowerCase().trim(); - filteredCourses.setPredicate(course -> - course.getCourseCode().toLowerCase().contains(lowerCaseFilter) - ); + filteredCourses.setPredicate(course -> course.getCourseCode().toLowerCase().contains(lowerCaseFilter)); } } private void showEnrolledStudents(Course course) { - if (course == null) return; + if (course == null) + return; studentListTitleLabel.setText("Students Enrolled in " + course.getCourseCode()); enrolledStudentsTable.setItems(FXCollections.observableArrayList(course.getEnrolledStudents())); 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/ScheduleCalendarController.java b/src/main/java/org/example/se302/controller/ScheduleCalendarController.java index 2299dc5..9e29ca3 100644 --- a/src/main/java/org/example/se302/controller/ScheduleCalendarController.java +++ b/src/main/java/org/example/se302/controller/ScheduleCalendarController.java @@ -737,4 +737,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 b0a9a58..379cb87 100644 --- a/src/main/java/org/example/se302/controller/ScheduleClassroomController.java +++ b/src/main/java/org/example/se302/controller/ScheduleClassroomController.java @@ -11,14 +11,14 @@ 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.model.TimeSlot; 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 +47,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 +108,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,69 +148,64 @@ 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())) { + for (Course course : dataManager.getCourses()) { + if (!course.isScheduled() || !selected.getClassroomId().equals(course.getAssignedClassroom())) + continue; - // 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(); + int studentCount = course.getEnrolledStudentsCount(); + int utilization = selected.getCapacity() > 0 + ? (studentCount * 100) / selected.getCapacity() + : 0; - // Format time - String timeStr = "Slot " + (assignment.getTimeSlotIndex() + 1); + entries.add(new ClassroomSlotEntry( + formatDate(config, dayIndex), + formatTime(config, dayIndex, slotIndex), + course.getCourseCode(), studentCount, utilization + "%", + utilization, dayIndex, slotIndex)); - // 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; - } - } - - // Calculate total possible slots - if (configuration != null) { - totalSlots = configuration.getNumDays() * configuration.getSlotsPerDay(); - } + usedSlots++; + totalStudents += studentCount; } - // Sort by day then slot entries.sort(Comparator.comparingInt(ClassroomSlotEntry::getDayIndex) .thenComparingInt(ClassroomSlotEntry::getSlotIndex)); - scheduleTable.setItems(entries); - // Update overall utilization label + int totalSlots = config != null ? config.getNumDays() * config.getSlotsPerDay() : 0; if (totalSlots > 0) { - int overallUtilization = (usedSlots * 100) / totalSlots; + int overallUtil = (usedSlots * 100) / totalSlots; utilizationLabel.setText(String.format( "Overall Utilization: %d%% (%d/%d slots used, %d total students)", - overallUtilization, usedSlots, totalSlots, totalStudents)); + overallUtil, usedSlots, totalSlots, totalStudents)); } else { utilizationLabel.setText("Overall Utilization: 0% (No schedule data available)"); } } + private String formatDate(ScheduleConfiguration config, int dayIndex) { + if (config != null && config.getStartDate() != null) { + LocalDate examDate = config.getStartDate().plusDays(dayIndex); + return examDate.format(DateTimeFormatter.ofPattern("dd/MM/yyyy")); + } + return "Day " + (dayIndex + 1); + } + + private String formatTime(ScheduleConfiguration config, int dayIndex, int slotIndex) { + if (config != null) { + TimeSlot slot = config.getTimeSlot(dayIndex, slotIndex); + if (slot != null) + return slot.getStartTime() + " - " + slot.getEndTime(); + } + return "Slot " + (slotIndex + 1); + } + // Helper class for table entries public static class ClassroomSlotEntry { private final String date; @@ -250,4 +261,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 bae7a9a..be28bbe 100644 --- a/src/main/java/org/example/se302/controller/ScheduleCourseController.java +++ b/src/main/java/org/example/se302/controller/ScheduleCourseController.java @@ -9,14 +9,13 @@ 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.model.TimeSlot; 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 +42,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,68 +116,83 @@ 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()) { - String courseCode = course.getCourseCode(); - int enrolled = course.getEnrolledStudentsCount(); - 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); - - 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() - : "-"; - } + if (course.isScheduled()) { + dayIndex = course.getExamDay(); + slotIndex = course.getExamTimeSlot(); + classroomStr = course.getAssignedClassroom(); + dateStr = formatDate(config, dayIndex); + timeStr = formatTime(config, dayIndex, slotIndex); } - entries.add(new CourseScheduleEntry(courseCode, enrolled, dateStr, timeStr, classroomStr, + entries.add(new CourseScheduleEntry(course.getCourseCode(), + course.getEnrolledStudentsCount(), dateStr, timeStr, classroomStr, dayIndex, slotIndex)); } - // Sort by day first, then by slot entries.sort(Comparator.comparingInt(CourseScheduleEntry::getDayIndex) .thenComparingInt(CourseScheduleEntry::getSlotIndex)); - courseScheduleTable.setItems(entries); } + private String formatDate(ScheduleConfiguration config, int dayIndex) { + if (config != null && config.getStartDate() != null) { + LocalDate examDate = config.getStartDate().plusDays(dayIndex); + return examDate.format(DateTimeFormatter.ofPattern("dd/MM/yyyy (EEEE)")); + } + return "Day " + (dayIndex + 1); + } + + private String formatTime(ScheduleConfiguration config, int dayIndex, int slotIndex) { + if (config != null) { + TimeSlot slot = config.getTimeSlot(dayIndex, slotIndex); + if (slot != null) { + return slot.getStartTime() + " - " + slot.getEndTime(); + } + } + return "Slot " + (slotIndex + 1); + } + // Helper class for table entries public static class CourseScheduleEntry { private final String courseCode; @@ -233,4 +242,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..8b2d49a 100644 --- a/src/main/java/org/example/se302/controller/ScheduleStudentController.java +++ b/src/main/java/org/example/se302/controller/ScheduleStudentController.java @@ -105,77 +105,58 @@ public class ScheduleStudentController { for (String courseCode : student.getEnrolledCourses()) { Course course = dataManager.getCourse(courseCode); - if (course != null) { - String dateStr = "Not Scheduled"; - String timeStr = "-"; - String classroom = "-"; - int dayIndex = -1; - int slotIndex = -1; + if (course == null) + continue; - if (course.isScheduled()) { - dayIndex = course.getExamDay(); - slotIndex = course.getExamTimeSlot(); - classroom = course.getAssignedClassroom(); + String dateStr = "Not Scheduled"; + String timeStr = "-"; + String classroom = "-"; + int dayIndex = -1, slotIndex = -1; - if (config != null) { - TimeSlot slot = config.getTimeSlot(dayIndex, slotIndex); - if (slot != null) { - dateStr = slot.getDate().toString(); // YYYY-MM-DD - timeStr = slot.getStartTime().toString() + " - " + slot.getEndTime().toString(); - } else { - dateStr = "Day " + (dayIndex + 1); - timeStr = "Slot " + (slotIndex + 1); - } - } else { - // Fallback if no config saved - dateStr = "Day " + (dayIndex + 1); - timeStr = "Slot " + (slotIndex + 1); - } + if (course.isScheduled()) { + dayIndex = course.getExamDay(); + slotIndex = course.getExamTimeSlot(); + classroom = course.getAssignedClassroom(); + + TimeSlot slot = config != null ? config.getTimeSlot(dayIndex, slotIndex) : null; + if (slot != null) { + dateStr = slot.getDate().toString(); + timeStr = slot.getStartTime() + " - " + slot.getEndTime(); + } else { + dateStr = "Day " + (dayIndex + 1); + timeStr = "Slot " + (slotIndex + 1); } - - entries.add(new CourseScheduleEntry(courseCode, dateStr, timeStr, classroom, dayIndex, slotIndex)); } + + entries.add(new CourseScheduleEntry(courseCode, dateStr, timeStr, classroom, dayIndex, slotIndex)); } - // Sort by day and time entries.sort(Comparator.comparingInt(CourseScheduleEntry::getDayIndex) .thenComparingInt(CourseScheduleEntry::getSlotIndex)); - - // Analyze for highlights analyzeSchedule(entries); - scheduleTable.setItems(FXCollections.observableArrayList(entries)); } private void analyzeSchedule(List entries) { - if (entries.isEmpty()) - return; - for (int i = 0; i < entries.size(); i++) { CourseScheduleEntry current = entries.get(i); if (current.getDayIndex() == -1) - continue; // Skip unscheduled + continue; - // Check for multiple exams on same day - int examsOnDay = 0; + // Check for multiple exams and conflicts on same day for (CourseScheduleEntry other : entries) { if (other.getDayIndex() == current.getDayIndex() && other.getDayIndex() != -1) { - examsOnDay++; + current.isMultipleExamsOnDay = true; if (other.getSlotIndex() == current.getSlotIndex() && other != current) { current.hasConflictWarning = true; } } } - if (examsOnDay > 1) { - current.isMultipleExamsOnDay = true; - } - // Check for consecutive days (look at previous scheduled exam) - // Since list is sorted, we can look at previous entry if it exists + // Check for consecutive days with previous exam if (i > 0) { CourseScheduleEntry prev = entries.get(i - 1); - if (prev.getDayIndex() != -1 && - current.getDayIndex() == prev.getDayIndex() + 1) { + if (prev.getDayIndex() != -1 && current.getDayIndex() == prev.getDayIndex() + 1) { current.isConsecutiveDay = true; } } @@ -230,4 +211,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/java/org/example/se302/model/Course.java b/src/main/java/org/example/se302/model/Course.java index 48250c4..8bc4246 100644 --- a/src/main/java/org/example/se302/model/Course.java +++ b/src/main/java/org/example/se302/model/Course.java @@ -4,72 +4,40 @@ import java.util.ArrayList; import java.util.List; /** - * Represents a course in the exam scheduling system. - * Contains course information and exam scheduling details. + * Represents a course with exam scheduling details. */ public class Course { private String courseCode; private List enrolledStudents; - - // Exam schedule fields (index-based) - private int examDay; // -1 if not scheduled, 0-based day index - private int examTimeSlot; // -1 if not scheduled, 0-based slot index - private String assignedClassroom; // null if not scheduled - - // Legacy field for backward compatibility - private String examDateTime; // null if not scheduled (string format) + private int examDay = -1; + private int examTimeSlot = -1; + private String assignedClassroom; public Course(String courseCode) { this.courseCode = courseCode; this.enrolledStudents = new ArrayList<>(); - this.examDay = -1; - this.examTimeSlot = -1; - this.assignedClassroom = null; - this.examDateTime = null; } - /** - * Checks if this course has been scheduled for an exam. - */ public boolean isScheduled() { return examDay >= 0 && examTimeSlot >= 0 && assignedClassroom != null; } - /** - * Clears the exam schedule for this course. - */ public void clearSchedule() { this.examDay = -1; this.examTimeSlot = -1; this.assignedClassroom = null; - this.examDateTime = null; } - /** - * Sets the complete exam schedule. - * - * @param day Day index (0-based) - * @param timeSlot Time slot index (0-based) - * @param classroomId Classroom ID - */ public void setExamSchedule(int day, int timeSlot, String classroomId) { this.examDay = day; this.examTimeSlot = timeSlot; this.assignedClassroom = classroomId; } - /** - * Gets a unique key for this course's time slot. - */ public String getTimeSlotKey() { - if (!isScheduled()) { - return null; - } - return "D" + examDay + "_S" + examTimeSlot; + return isScheduled() ? "D" + examDay + "_S" + examTimeSlot : null; } - // Basic getters and setters - public String getCourseCode() { return courseCode; } @@ -82,14 +50,13 @@ public class Course { return enrolledStudents; } - public void setEnrolledStudents(List enrolledStudents) { - this.enrolledStudents = enrolledStudents; + public void setEnrolledStudents(List students) { + this.enrolledStudents = students; } public void addStudent(String studentId) { - if (!enrolledStudents.contains(studentId)) { + if (!enrolledStudents.contains(studentId)) enrolledStudents.add(studentId); - } } public void removeStudent(String studentId) { @@ -100,8 +67,6 @@ public class Course { return enrolledStudents.size(); } - // Schedule field getters and setters - public int getExamDay() { return examDay; } @@ -114,24 +79,16 @@ public class Course { return examTimeSlot; } - public void setExamTimeSlot(int examTimeSlot) { - this.examTimeSlot = examTimeSlot; + public void setExamTimeSlot(int slot) { + this.examTimeSlot = slot; } public String getAssignedClassroom() { return assignedClassroom; } - public void setAssignedClassroom(String assignedClassroom) { - this.assignedClassroom = assignedClassroom; - } - - public String getExamDateTime() { - return examDateTime; - } - - public void setExamDateTime(String examDateTime) { - this.examDateTime = examDateTime; + public void setAssignedClassroom(String classroom) { + this.assignedClassroom = classroom; } @Override 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 @@