mirror of
https://github.com/sabazadam/Se302.git
synced 2025-12-31 12:21:22 +00:00
Implement exam schedule generation UI, data management, and associated controllers.
This commit is contained in:
@@ -5,6 +5,7 @@ import javafx.collections.FXCollections;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.Cursor;
|
||||
import javafx.scene.control.*;
|
||||
import javafx.scene.layout.*;
|
||||
import org.example.se302.model.*;
|
||||
@@ -51,9 +52,13 @@ public class ScheduleCalendarController {
|
||||
@FXML
|
||||
private VBox progressContainer;
|
||||
@FXML
|
||||
private ProgressIndicator progressIndicator;
|
||||
@FXML
|
||||
private ProgressBar progressBar;
|
||||
@FXML
|
||||
private Label progressLabel;
|
||||
@FXML
|
||||
private Label progressDetailLabel;
|
||||
|
||||
// Schedule display
|
||||
@FXML
|
||||
@@ -248,7 +253,11 @@ public class ScheduleCalendarController {
|
||||
progressContainer.setVisible(true);
|
||||
progressContainer.setManaged(true);
|
||||
progressBar.setProgress(0);
|
||||
progressLabel.setText("Initializing schedule generation...");
|
||||
if (progressIndicator != null)
|
||||
progressIndicator.setProgress(-1.0); // Indeterminate
|
||||
progressLabel.setText("Generating Schedule...");
|
||||
if (progressDetailLabel != null)
|
||||
progressDetailLabel.setText("Initializing CSP solver...");
|
||||
statusLabel.setText("⏳ Generating schedule...");
|
||||
statusLabel.setStyle("-fx-text-fill: #3498db;");
|
||||
|
||||
@@ -259,7 +268,10 @@ public class ScheduleCalendarController {
|
||||
generatorService.setProgressListener((progress, message) -> {
|
||||
Platform.runLater(() -> {
|
||||
progressBar.setProgress(progress);
|
||||
progressLabel.setText(message);
|
||||
if (progressIndicator != null)
|
||||
progressIndicator.setProgress(progress);
|
||||
if (progressDetailLabel != null)
|
||||
progressDetailLabel.setText(message);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -289,6 +301,9 @@ public class ScheduleCalendarController {
|
||||
if (result.isSuccess()) {
|
||||
currentSchedule = result.getScheduleState();
|
||||
|
||||
// Save schedule to DataManager (so other views can see it)
|
||||
saveScheduleToDataManager(currentSchedule, config);
|
||||
|
||||
// Update status
|
||||
statusLabel.setText("✓ Schedule generated successfully!");
|
||||
statusLabel.setStyle("-fx-text-fill: #27ae60;");
|
||||
@@ -303,7 +318,8 @@ public class ScheduleCalendarController {
|
||||
"Exam schedule generated successfully!\n\n" +
|
||||
"Scheduled: " + currentSchedule.getAssignedCourses() + "/"
|
||||
+ currentSchedule.getTotalCourses() + " courses\n" +
|
||||
"Time: " + durationMs + "ms");
|
||||
"Time: " + durationMs + "ms\n\n" +
|
||||
"The schedule has been saved. You can now view individual schedules in 'Student Schedule' and 'Course Schedule' tabs.");
|
||||
} else if (result.wasCancelled()) {
|
||||
statusLabel.setText("⚠️ Generation cancelled");
|
||||
statusLabel.setStyle("-fx-text-fill: #f39c12;");
|
||||
@@ -313,7 +329,30 @@ public class ScheduleCalendarController {
|
||||
|
||||
showAlert(Alert.AlertType.ERROR, "Generation Failed",
|
||||
result.getMessage()
|
||||
+ "\n\nTry:\n• Increasing number of days\n• Increasing slots per day\n• Adding more classrooms");
|
||||
+ "\n\nTry:\n• Increasing number of days\n• Increasing slots per day\n• Adding more classrooms\n• Switching to 'Allow back-to-back exams'");
|
||||
}
|
||||
}
|
||||
|
||||
private void saveScheduleToDataManager(ScheduleState schedule, ScheduleConfiguration config) {
|
||||
// 1. Save configuration
|
||||
dataManager.setActiveConfiguration(config);
|
||||
|
||||
// 2. Clear old schedule data from courses
|
||||
for (Course course : dataManager.getCourses()) {
|
||||
course.setExamSchedule(-1, -1, null);
|
||||
}
|
||||
|
||||
// 3. Apply new schedule
|
||||
for (ExamAssignment assignment : schedule.getAssignments().values()) {
|
||||
if (assignment.isAssigned()) {
|
||||
Course course = dataManager.getCourse(assignment.getCourseCode());
|
||||
if (course != null) {
|
||||
course.setExamSchedule(
|
||||
assignment.getDay(),
|
||||
assignment.getTimeSlotIndex(),
|
||||
assignment.getClassroomId());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -433,6 +472,10 @@ public class ScheduleCalendarController {
|
||||
HBox examBox = new HBox(5);
|
||||
examBox.setAlignment(Pos.CENTER_LEFT);
|
||||
examBox.setStyle("-fx-background-color: #3498db; -fx-background-radius: 3; -fx-padding: 3 6;");
|
||||
examBox.setCursor(Cursor.HAND);
|
||||
|
||||
// Add click handler
|
||||
examBox.setOnMouseClicked(e -> showAssignmentDetails(exam));
|
||||
|
||||
Label courseLabel = new Label(exam.getCourseCode());
|
||||
courseLabel.setStyle("-fx-text-fill: white; -fx-font-weight: bold; -fx-font-size: 11;");
|
||||
@@ -441,6 +484,11 @@ public class ScheduleCalendarController {
|
||||
roomLabel.setStyle("-fx-text-fill: #d4e6f1; -fx-font-size: 10;");
|
||||
|
||||
examBox.getChildren().addAll(courseLabel, roomLabel);
|
||||
|
||||
// Add tooltip
|
||||
Tooltip tooltip = new Tooltip(exam.getCourseCode() + "\n" + exam.getClassroomId());
|
||||
Tooltip.install(examBox, tooltip);
|
||||
|
||||
cell.getChildren().add(examBox);
|
||||
}
|
||||
|
||||
@@ -455,6 +503,35 @@ public class ScheduleCalendarController {
|
||||
return cell;
|
||||
}
|
||||
|
||||
private void showAssignmentDetails(ExamAssignment exam) {
|
||||
Course course = dataManager.getCourse(exam.getCourseCode());
|
||||
Classroom room = dataManager.getClassroom(exam.getClassroomId());
|
||||
|
||||
Alert alert = new Alert(Alert.AlertType.INFORMATION);
|
||||
alert.setTitle("Exam Details");
|
||||
alert.setHeaderText(exam.getCourseCode());
|
||||
|
||||
StringBuilder content = new StringBuilder();
|
||||
content.append("Course: ").append(exam.getCourseCode()).append("\n");
|
||||
if (course != null) {
|
||||
content.append("Students: ").append(course.getEnrolledStudentsCount()).append("\n");
|
||||
}
|
||||
content.append("\n");
|
||||
content.append("Assigned Classroom: ").append(exam.getClassroomId()).append("\n");
|
||||
if (room != null) {
|
||||
content.append("Capacity: ").append(room.getCapacity()).append("\n");
|
||||
double fillRatio = course != null ? (double) course.getEnrolledStudentsCount() / room.getCapacity() * 100
|
||||
: 0;
|
||||
content.append(String.format("Utilization: %.1f%%\n", fillRatio));
|
||||
}
|
||||
content.append("\n");
|
||||
content.append("Day: ").append(exam.getDay() + 1).append("\n");
|
||||
content.append("Slot: ").append(exam.getTimeSlotIndex() + 1).append("\n");
|
||||
|
||||
alert.setContentText(content.toString());
|
||||
alert.showAndWait();
|
||||
}
|
||||
|
||||
@FXML
|
||||
private void onCancelGeneration() {
|
||||
if (generatorService != null) {
|
||||
|
||||
@@ -2,27 +2,39 @@ package org.example.se302.controller;
|
||||
|
||||
import javafx.beans.property.SimpleStringProperty;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.control.ComboBox;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.TableColumn;
|
||||
import javafx.scene.control.TableView;
|
||||
import javafx.scene.control.*;
|
||||
import javafx.util.Callback;
|
||||
import org.example.se302.model.Course;
|
||||
import org.example.se302.model.ScheduleConfiguration;
|
||||
import org.example.se302.model.Student;
|
||||
import org.example.se302.model.TimeSlot;
|
||||
import org.example.se302.service.DataManager;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Controller for the Student Schedule view.
|
||||
* Displays exam schedule for a selected student.
|
||||
*/
|
||||
public class ScheduleStudentController {
|
||||
|
||||
@FXML private ComboBox<Student> studentComboBox;
|
||||
@FXML private Label selectedStudentLabel;
|
||||
@FXML private TableView<CourseScheduleEntry> scheduleTable;
|
||||
@FXML private TableColumn<CourseScheduleEntry, String> courseColumn;
|
||||
@FXML private TableColumn<CourseScheduleEntry, String> dateColumn;
|
||||
@FXML private TableColumn<CourseScheduleEntry, String> timeColumn;
|
||||
@FXML private TableColumn<CourseScheduleEntry, String> classroomColumn;
|
||||
@FXML
|
||||
private ComboBox<Student> studentComboBox;
|
||||
@FXML
|
||||
private Label selectedStudentLabel;
|
||||
@FXML
|
||||
private TableView<CourseScheduleEntry> scheduleTable;
|
||||
@FXML
|
||||
private TableColumn<CourseScheduleEntry, String> courseColumn;
|
||||
@FXML
|
||||
private TableColumn<CourseScheduleEntry, String> dateColumn;
|
||||
@FXML
|
||||
private TableColumn<CourseScheduleEntry, String> timeColumn;
|
||||
@FXML
|
||||
private TableColumn<CourseScheduleEntry, String> classroomColumn;
|
||||
|
||||
private DataManager dataManager;
|
||||
|
||||
@@ -33,50 +45,190 @@ public class ScheduleStudentController {
|
||||
// Populate student combo box
|
||||
studentComboBox.setItems(dataManager.getStudents());
|
||||
|
||||
// Enable filtering/search in combobox ideally, but for now simple selection
|
||||
studentComboBox.setPromptText("Select a student...");
|
||||
|
||||
// Set up table columns
|
||||
courseColumn.setCellValueFactory(cellData ->
|
||||
new SimpleStringProperty(cellData.getValue().getCourseCode()));
|
||||
dateColumn.setCellValueFactory(cellData ->
|
||||
new SimpleStringProperty(cellData.getValue().getDate()));
|
||||
timeColumn.setCellValueFactory(cellData ->
|
||||
new SimpleStringProperty(cellData.getValue().getTime()));
|
||||
classroomColumn.setCellValueFactory(cellData ->
|
||||
new SimpleStringProperty(cellData.getValue().getClassroom()));
|
||||
courseColumn.setCellValueFactory(cellData -> new SimpleStringProperty(cellData.getValue().getCourseCode()));
|
||||
dateColumn.setCellValueFactory(cellData -> new SimpleStringProperty(cellData.getValue().getDateDisplay()));
|
||||
timeColumn.setCellValueFactory(cellData -> new SimpleStringProperty(cellData.getValue().getTimeDisplay()));
|
||||
classroomColumn.setCellValueFactory(cellData -> new SimpleStringProperty(cellData.getValue().getClassroom()));
|
||||
|
||||
// Custom row factory for highlighting
|
||||
scheduleTable.setRowFactory(tv -> new TableRow<CourseScheduleEntry>() {
|
||||
@Override
|
||||
protected void updateItem(CourseScheduleEntry item, boolean empty) {
|
||||
super.updateItem(item, empty);
|
||||
if (item == null || empty) {
|
||||
setStyle("");
|
||||
setTooltip(null);
|
||||
} else {
|
||||
// Logic to highlight rows
|
||||
boolean styleSet = false;
|
||||
|
||||
if (item.hasConflictWarning) {
|
||||
setStyle("-fx-background-color: #ffcdd2;"); // Red tint (conflict)
|
||||
setTooltip(new Tooltip("Conflict: Multiple exams at same time!"));
|
||||
styleSet = true;
|
||||
} else if (item.isMultipleExamsOnDay) {
|
||||
setStyle("-fx-background-color: #fff3cd;"); // Yellow tint
|
||||
setTooltip(new Tooltip("Warning: Multiple exams on this day"));
|
||||
styleSet = true;
|
||||
} else if (item.isConsecutiveDay) {
|
||||
setStyle("-fx-background-color: #e3f2fd;"); // Blue tint
|
||||
setTooltip(new Tooltip("Info: Exam on consecutive day"));
|
||||
styleSet = true;
|
||||
}
|
||||
|
||||
if (!styleSet) {
|
||||
setStyle("");
|
||||
setTooltip(null);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@FXML
|
||||
private void onShowSchedule() {
|
||||
Student selected = studentComboBox.getValue();
|
||||
if (selected == null) return;
|
||||
if (selected == null)
|
||||
return;
|
||||
|
||||
selectedStudentLabel.setText("Exam Schedule for: " + selected.getStudentId());
|
||||
selectedStudentLabel.setText("Exam Schedule for: " + selected.getStudentId() + " (" + selected.getName() + ")");
|
||||
|
||||
// For demo: show enrolled courses with "Not Scheduled" status
|
||||
ObservableList<CourseScheduleEntry> entries = FXCollections.observableArrayList();
|
||||
for (String courseCode : selected.getEnrolledCourses()) {
|
||||
entries.add(new CourseScheduleEntry(courseCode, "Not Scheduled", "-", "-"));
|
||||
refreshTable(selected);
|
||||
}
|
||||
|
||||
private void refreshTable(Student student) {
|
||||
List<CourseScheduleEntry> entries = new ArrayList<>();
|
||||
ScheduleConfiguration config = dataManager.getActiveConfiguration();
|
||||
|
||||
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.isScheduled()) {
|
||||
dayIndex = course.getExamDay();
|
||||
slotIndex = course.getExamTimeSlot();
|
||||
classroom = course.getAssignedClassroom();
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
entries.add(new CourseScheduleEntry(courseCode, dateStr, timeStr, classroom, dayIndex, slotIndex));
|
||||
}
|
||||
}
|
||||
|
||||
scheduleTable.setItems(entries);
|
||||
// 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<CourseScheduleEntry> 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
|
||||
|
||||
// Check for multiple exams on same day
|
||||
int examsOnDay = 0;
|
||||
for (CourseScheduleEntry other : entries) {
|
||||
if (other.getDayIndex() == current.getDayIndex() && other.getDayIndex() != -1) {
|
||||
examsOnDay++;
|
||||
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
|
||||
if (i > 0) {
|
||||
CourseScheduleEntry prev = entries.get(i - 1);
|
||||
if (prev.getDayIndex() != -1 &&
|
||||
current.getDayIndex() == prev.getDayIndex() + 1) {
|
||||
current.isConsecutiveDay = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Helper class for table entries
|
||||
public static class CourseScheduleEntry {
|
||||
private final String courseCode;
|
||||
private final String date;
|
||||
private final String time;
|
||||
private final String dateDisplay;
|
||||
private final String timeDisplay;
|
||||
private final String classroom;
|
||||
private final int dayIndex;
|
||||
private final int slotIndex;
|
||||
|
||||
public CourseScheduleEntry(String courseCode, String date, String time, String classroom) {
|
||||
// View flags
|
||||
public boolean isMultipleExamsOnDay = false;
|
||||
public boolean isConsecutiveDay = false;
|
||||
public boolean hasConflictWarning = false;
|
||||
|
||||
public CourseScheduleEntry(String courseCode, String dateDisplay, String timeDisplay, String classroom,
|
||||
int dayIndex, int slotIndex) {
|
||||
this.courseCode = courseCode;
|
||||
this.date = date;
|
||||
this.time = time;
|
||||
this.dateDisplay = dateDisplay;
|
||||
this.timeDisplay = timeDisplay;
|
||||
this.classroom = classroom;
|
||||
this.dayIndex = dayIndex;
|
||||
this.slotIndex = slotIndex;
|
||||
}
|
||||
|
||||
public String getCourseCode() { return courseCode; }
|
||||
public String getDate() { return date; }
|
||||
public String getTime() { return time; }
|
||||
public String getClassroom() { return classroom; }
|
||||
public String getCourseCode() {
|
||||
return courseCode;
|
||||
}
|
||||
|
||||
public String getDateDisplay() {
|
||||
return dateDisplay;
|
||||
}
|
||||
|
||||
public String getTimeDisplay() {
|
||||
return timeDisplay;
|
||||
}
|
||||
|
||||
public String getClassroom() {
|
||||
return classroom;
|
||||
}
|
||||
|
||||
public int getDayIndex() {
|
||||
return dayIndex;
|
||||
}
|
||||
|
||||
public int getSlotIndex() {
|
||||
return slotIndex;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -131,4 +131,15 @@ public class DataManager {
|
||||
course.addStudent(studentId);
|
||||
}
|
||||
}
|
||||
|
||||
// Schedule Configuration
|
||||
private org.example.se302.model.ScheduleConfiguration activeConfiguration;
|
||||
|
||||
public org.example.se302.model.ScheduleConfiguration getActiveConfiguration() {
|
||||
return activeConfiguration;
|
||||
}
|
||||
|
||||
public void setActiveConfiguration(org.example.se302.model.ScheduleConfiguration activeConfiguration) {
|
||||
this.activeConfiguration = activeConfiguration;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -94,10 +94,19 @@
|
||||
<Label fx:id="statusLabel" text="Ready" styleClass="status-label"/>
|
||||
</HBox>
|
||||
|
||||
<!-- Progress Bar -->
|
||||
<VBox fx:id="progressContainer" spacing="5" visible="false" managed="false">
|
||||
<ProgressBar fx:id="progressBar" prefWidth="400" progress="0"/>
|
||||
<Label fx:id="progressLabel" text="Initializing..."/>
|
||||
<!-- Progress Container with Indicator -->
|
||||
<VBox fx:id="progressContainer" spacing="8" visible="false" managed="false"
|
||||
style="-fx-background-color: #f8f9fa; -fx-padding: 15; -fx-background-radius: 5;">
|
||||
<HBox spacing="15" alignment="CENTER_LEFT">
|
||||
<ProgressIndicator fx:id="progressIndicator" prefWidth="30" prefHeight="30"/>
|
||||
<VBox spacing="3">
|
||||
<Label fx:id="progressLabel" text="Initializing..."
|
||||
style="-fx-font-weight: bold; -fx-font-size: 13;"/>
|
||||
<ProgressBar fx:id="progressBar" prefWidth="350" progress="0"/>
|
||||
</VBox>
|
||||
</HBox>
|
||||
<Label fx:id="progressDetailLabel" text="Please wait..."
|
||||
style="-fx-text-fill: #7f8c8d; -fx-font-size: 11;"/>
|
||||
</VBox>
|
||||
</VBox>
|
||||
</top>
|
||||
|
||||
Reference in New Issue
Block a user