Implement initial application UI including schedule generation and import views, a main controller, and update Maven compiler

This commit is contained in:
feyzagereme
2025-12-17 19:16:43 +03:00
parent cbf1d0b13c
commit ce556e92fd
6 changed files with 702 additions and 324 deletions

View File

@@ -51,8 +51,8 @@
<artifactId>maven-compiler-plugin</artifactId> <artifactId>maven-compiler-plugin</artifactId>
<version>3.13.0</version> <version>3.13.0</version>
<configuration> <configuration>
<source>21</source> <source>17</source>
<target>21</target> <target>17</target>
</configuration> </configuration>
</plugin> </plugin>
<plugin> <plugin>

View File

@@ -1,6 +1,7 @@
package org.example.se302.controller; package org.example.se302.controller;
import javafx.fxml.FXML; import javafx.fxml.FXML;
import javafx.scene.control.Button;
import javafx.scene.control.Label; import javafx.scene.control.Label;
import javafx.scene.control.Tab; import javafx.scene.control.Tab;
import javafx.scene.control.TabPane; import javafx.scene.control.TabPane;
@@ -8,7 +9,7 @@ import org.example.se302.service.DataManager;
/** /**
* Main controller for the application window. * Main controller for the application window.
* Manages the TabPane and status bar. * Manages the TabPane, status bar, and theme toggling.
*/ */
public class MainController { public class MainController {
@@ -33,7 +34,11 @@ public class MainController {
@FXML @FXML
private Label statusLabel; private Label statusLabel;
@FXML
private Button themeToggleButton;
private DataManager dataManager; private DataManager dataManager;
private boolean isDarkMode = false;
@FXML @FXML
public void initialize() { public void initialize() {
@@ -78,8 +83,28 @@ public class MainController {
"Loaded: %d Students, %d Courses, %d Classrooms", "Loaded: %d Students, %d Courses, %d Classrooms",
dataManager.getTotalStudents(), dataManager.getTotalStudents(),
dataManager.getTotalCourses(), dataManager.getTotalCourses(),
dataManager.getTotalClassrooms() dataManager.getTotalClassrooms()));
)); }
}
/**
* Toggle between light and dark themes.
*/
@FXML
private void onToggleTheme() {
var root = mainTabPane.getScene().getRoot();
var styleClass = root.getStyleClass();
if (isDarkMode) {
// Switch to light mode
styleClass.remove("dark");
themeToggleButton.setText("🌙 Dark Mode");
isDarkMode = false;
} else {
// Switch to dark mode
styleClass.add("dark");
themeToggleButton.setText("☀️ Light Mode");
isDarkMode = true;
} }
} }
} }

View File

@@ -1,70 +1,228 @@
/* ===== Root Color Palette ===== */ /* ========================================
ExamFlow v2.0 - Modern UI Stylesheet
Based on Tailwind-inspired design system
======================================== */
/* ===== Root Color Palette (Indigo & Slate) ===== */
.root { .root {
-fx-primary-color: #2196F3; /* Primary Colors (Indigo) */
-fx-primary-dark: #1976D2; -fx-primary: #4F46E5;
-fx-accent-color: #FFC107; /* indigo-600 */
-fx-success-color: #4CAF50; -fx-primary-dark: #4338CA;
-fx-error-color: #F44336; /* indigo-700 */
-fx-background: #FAFAFA; -fx-primary-light: #6366F1;
-fx-text-base-color: #212121; /* indigo-500 */
-fx-primary-50: #EEF2FF;
/* indigo-50 */
-fx-primary-100: #E0E7FF;
/* indigo-100 */
/* Slate Grays */
-fx-slate-50: #F8FAFC;
-fx-slate-100: #F1F5F9;
-fx-slate-200: #E2E8F0;
-fx-slate-300: #CBD5E1;
-fx-slate-400: #94A3B8;
-fx-slate-500: #64748B;
-fx-slate-600: #475569;
-fx-slate-700: #334155;
-fx-slate-800: #1E293B;
-fx-slate-900: #0F172A;
/* Semantic Colors */
-fx-success: #10B981;
/* emerald-500 */
-fx-success-light: #D1FAE5;
/* emerald-100 */
-fx-warning: #F59E0B;
/* amber-500 */
-fx-warning-light: #FEF3C7;
/* amber-100 */
-fx-error: #EF4444;
/* red-500 */
-fx-error-light: #FEE2E2;
/* red-100 */
-fx-info: #3B82F6;
/* blue-500 */
-fx-info-light: #DBEAFE;
/* blue-100 */
/* Background & Surface */
-fx-background: #F8FAFC;
/* slate-50 */
-fx-surface: #FFFFFF;
/* Text Colors */
-fx-text-primary: #0F172A;
/* slate-900 */
-fx-text-secondary: #475569;
/* slate-600 */
-fx-text-tertiary: #94A3B8;
/* slate-400 */
/* Shadows */
-fx-shadow-sm: 0 1px 2px 0 rgba(0, 0, 0, 0.05);
-fx-shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
-fx-shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.1);
-fx-shadow-xl: 0 20px 25px -5px rgba(0, 0, 0, 0.1);
/* Font Settings */
-fx-font-family: "Segoe UI", "Roboto", "Helvetica Neue", Arial, sans-serif;
-fx-font-size: 14px;
} }
/* ===== General Styling ===== */ /* ===== Dark Mode Color Palette ===== */
.root.dark {
/* Primary Colors (Same indigo, but brighter for dark bg) */
-fx-primary: #6366F1;
/* indigo-500 - brighter */
-fx-primary-dark: #4F46E5;
/* indigo-600 */
-fx-primary-light: #818CF8;
/* indigo-400 */
-fx-primary-50: #312E81;
/* indigo-900 - inverted */
-fx-primary-100: #3730A3;
/* indigo-800 - inverted */
/* Dark Slate Palette (inverted) */
-fx-slate-50: #0F172A;
/* slate-900 */
-fx-slate-100: #1E293B;
/* slate-800 */
-fx-slate-200: #334155;
/* slate-700 */
-fx-slate-300: #475569;
/* slate-600 */
-fx-slate-400: #64748B;
/* slate-500 */
-fx-slate-500: #94A3B8;
/* slate-400 */
-fx-slate-600: #CBD5E1;
/* slate-300 */
-fx-slate-700: #E2E8F0;
/* slate-200 */
-fx-slate-800: #F1F5F9;
/* slate-100 */
-fx-slate-900: #F8FAFC;
/* slate-50 */
/* Semantic Colors (brighter for dark) */
-fx-success: #34D399;
/* emerald-400 */
-fx-success-light: #064E3B;
/* emerald-900 */
-fx-warning: #FBBF24;
/* amber-400 */
-fx-warning-light: #78350F;
/* amber-900 */
-fx-error: #F87171;
/* red-400 */
-fx-error-light: #7F1D1D;
/* red-900 */
-fx-info: #60A5FA;
/* blue-400 */
-fx-info-light: #1E3A8A;
/* blue-900 */
/* Background & Surface (dark) */
-fx-background: #0F172A;
/* slate-900 */
-fx-surface: #1E293B;
/* slate-800 */
/* Text Colors (inverted) */
-fx-text-primary: #F8FAFC;
/* slate-50 */
-fx-text-secondary: #CBD5E1;
/* slate-300 */
-fx-text-tertiary: #64748B;
/* slate-500 */
/* Shadows (more pronounced in dark) */
-fx-shadow-sm: 0 1px 2px 0 rgba(0, 0, 0, 0.3);
-fx-shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.4);
-fx-shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.5);
-fx-shadow-xl: 0 20px 25px -5px rgba(0, 0, 0, 0.6);
}
/* ===== General Layouts ===== */
.content-pane { .content-pane {
-fx-background-color: white; -fx-background-color: -fx-slate-50;
-fx-padding: 40;
} }
/* ===== Header Styling ===== */ /* ===== Typography ===== */
.header {
-fx-background-color: -fx-primary-color;
-fx-padding: 15;
}
.title-label {
-fx-font-size: 24px;
-fx-font-weight: bold;
-fx-text-fill: white;
}
/* ===== Section Titles ===== */
.section-title { .section-title {
-fx-font-size: 20px; -fx-font-size: 28px;
-fx-font-weight: bold; -fx-font-weight: 900;
-fx-text-fill: -fx-primary-dark; /* black */
-fx-text-fill: -fx-text-primary;
-fx-padding: 0 0 16 0;
} }
.subsection-title { .subsection-title {
-fx-font-size: 14px; -fx-font-size: 16px;
-fx-font-weight: bold; -fx-font-weight: 700;
-fx-text-fill: #424242; /* bold */
-fx-text-fill: -fx-slate-800;
-fx-padding: 0 0 8 0;
} }
.info-title { .info-title {
-fx-font-size: 18px; -fx-font-size: 20px;
-fx-font-weight: bold; -fx-font-weight: 700;
-fx-text-fill: #616161; -fx-text-fill: -fx-slate-700;
} }
/* ===== Button Styling ===== */ .description-label {
-fx-font-size: 16px;
-fx-text-fill: -fx-slate-600;
-fx-padding: 0 0 8 0;
}
.stat-value {
-fx-font-size: 28px;
-fx-font-weight: 900;
-fx-text-fill: -fx-primary;
}
.stat-label {
-fx-font-size: 11px;
-fx-font-weight: 700;
-fx-text-fill: -fx-slate-400;
-fx-padding: 4 0 0 0;
}
/* ===== Buttons ===== */
.button { .button {
-fx-background-color: white; -fx-background-color: -fx-surface;
-fx-border-color: -fx-primary-color; -fx-border-color: -fx-slate-200;
-fx-border-width: 1px; -fx-border-width: 1px;
-fx-border-radius: 4px; -fx-border-radius: 12px;
-fx-background-radius: 4px; -fx-background-radius: 12px;
-fx-text-fill: -fx-primary-color; -fx-text-fill: -fx-slate-600;
-fx-padding: 8 16 8 16; -fx-padding: 10 20;
-fx-cursor: hand; -fx-cursor: hand;
-fx-font-weight: 700;
/* bold */
-fx-font-size: 13px;
-fx-effect: dropshadow(gaussian, rgba(0, 0, 0, 0.05), 2, 0, 0, 1);
} }
.button:hover { .button:hover {
-fx-background-color: #E3F2FD; -fx-background-color: -fx-slate-100;
-fx-border-color: -fx-slate-300;
} }
.primary-button { .primary-button {
-fx-background-color: -fx-primary-color; -fx-background-color: -fx-primary;
-fx-text-fill: white; -fx-text-fill: white;
-fx-border-color: -fx-primary-color; -fx-border-color: -fx-primary;
-fx-font-weight: 900;
/* black */
-fx-padding: 12 24;
-fx-effect: dropshadow(gaussian, derive(-fx-primary, -20%), 10, 0.3, 0, 4);
} }
.primary-button:hover { .primary-button:hover {
@@ -72,243 +230,386 @@
-fx-border-color: -fx-primary-dark; -fx-border-color: -fx-primary-dark;
} }
.small-button { .secondary-button {
-fx-font-size: 11px; -fx-background-color: -fx-slate-900;
-fx-padding: 4 12 4 12; -fx-text-fill: white;
-fx-border-color: -fx-slate-900;
-fx-font-weight: 700;
} }
/* ===== Table View Styling ===== */ .secondary-button:hover {
.table-view { -fx-background-color: -fx-slate-800;
-fx-background-color: white; -fx-border-color: -fx-slate-800;
-fx-border-color: #E0E0E0; }
.small-button {
-fx-font-size: 11px;
-fx-padding: 6 12;
-fx-border-radius: 8px;
-fx-background-radius: 8px;
}
/* ===== Card & Panel Styling ===== */
.card {
-fx-background-color: -fx-surface;
-fx-background-radius: 24px;
-fx-border-color: -fx-slate-200;
-fx-border-width: 1px; -fx-border-width: 1px;
-fx-border-radius: 24px;
-fx-padding: 32;
-fx-effect: dropshadow(gaussian, rgba(0, 0, 0, 0.05), 4, 0, 0, 2);
}
.config-panel {
-fx-background-color: -fx-surface;
-fx-background-radius: 20px;
-fx-border-color: -fx-slate-200;
-fx-border-width: 1px;
-fx-border-radius: 20px;
-fx-padding: 24;
}
.summary-row {
-fx-background-color: -fx-primary-50;
-fx-background-radius: 12px;
-fx-padding: 12 16;
}
.summary-value {
-fx-font-weight: 700;
-fx-text-fill: -fx-primary;
}
.stats-panel {
-fx-background-color: -fx-surface;
-fx-background-radius: 20px;
-fx-border-color: -fx-slate-200;
-fx-border-width: 1px;
-fx-border-radius: 20px;
-fx-padding: 24;
}
/* ===== Tables ===== */
.table-view {
-fx-background-color: -fx-surface;
-fx-border-color: -fx-slate-200;
-fx-border-width: 1px;
-fx-border-radius: 24px;
-fx-background-radius: 24px;
-fx-effect: dropshadow(gaussian, rgba(0, 0, 0, 0.05), 4, 0, 0, 2);
} }
.table-view .column-header { .table-view .column-header {
-fx-background-color: -fx-primary-color; -fx-background-color: derive(-fx-slate-50, 20%);
-fx-text-fill: white; -fx-text-fill: -fx-slate-500;
-fx-font-weight: bold; -fx-font-weight: 900;
-fx-padding: 10; /* black */
-fx-font-size: 10px;
-fx-padding: 16 24;
} }
.table-view .column-header-background { .table-view .column-header-background {
-fx-background-color: -fx-primary-color; -fx-background-color: derive(-fx-slate-50, 20%);
}
.table-view .column-header .label {
-fx-text-fill: -fx-slate-500;
-fx-font-weight: 900;
} }
.table-view .table-cell { .table-view .table-cell {
-fx-padding: 8; -fx-padding: 20 24;
-fx-border-color: transparent; -fx-border-color: transparent;
-fx-text-fill: -fx-slate-800;
-fx-font-size: 14px;
} }
.table-row-cell:odd { .table-row-cell {
-fx-background-color: white; -fx-background-color: -fx-surface;
-fx-border-color: -fx-slate-100;
-fx-border-width: 0 0 1 0;
} }
.table-row-cell:even { .table-row-cell:filled:hover {
-fx-background-color: #F5F5F5; -fx-background-color: derive(-fx-primary-50, 30%);
} }
.table-row-cell:selected { .table-row-cell:filled:selected {
-fx-background-color: #BBDEFB; -fx-background-color: -fx-primary-50;
-fx-text-fill: -fx-primary-dark;
} }
.table-row-cell:hover { /* ===== Text Inputs ===== */
-fx-background-color: #E3F2FD; .text-field,
}
/* ===== Text Field Styling ===== */
.text-field {
-fx-border-color: #BDBDBD;
-fx-border-width: 1px;
-fx-border-radius: 4px;
-fx-background-radius: 4px;
-fx-padding: 8;
}
.text-field:focused {
-fx-border-color: -fx-primary-color;
-fx-border-width: 2px;
}
/* ===== Text Area Styling ===== */
.text-area { .text-area {
-fx-border-color: #BDBDBD; -fx-background-color: -fx-slate-100;
-fx-border-color: transparent;
-fx-border-width: 1px; -fx-border-width: 1px;
-fx-border-radius: 4px; -fx-border-radius: 12px;
-fx-background-radius: 4px; -fx-background-radius: 12px;
-fx-padding: 10 16;
-fx-font-size: 13px;
-fx-text-fill: -fx-slate-800;
} }
.text-field:focused,
.text-area:focused { .text-area:focused {
-fx-border-color: -fx-primary-color; -fx-border-color: -fx-primary;
-fx-border-width: 2px; -fx-border-width: 2px;
-fx-background-color: -fx-surface;
-fx-effect: dropshadow(gaussian, derive(-fx-primary, -20%), 4, 0.3, 0, 0);
} }
.text-area .content { .text-area .content {
-fx-background-color: white; -fx-background-color: transparent;
} }
/* ===== Combo Box Styling ===== */ /* ===== Spinner ===== */
.combo-box { .spinner {
-fx-border-color: #BDBDBD; -fx-background-color: -fx-surface;
-fx-border-color: -fx-slate-200;
-fx-border-width: 1px; -fx-border-width: 1px;
-fx-border-radius: 4px; -fx-border-radius: 12px;
-fx-background-radius: 4px; -fx-background-radius: 12px;
}
.spinner .text-field {
-fx-background-color: transparent;
-fx-border-width: 0;
-fx-background-radius: 12px;
}
/* ===== ComboBox ===== */
.combo-box {
-fx-background-color: -fx-surface;
-fx-border-color: -fx-slate-200;
-fx-border-width: 1px;
-fx-border-radius: 12px;
-fx-background-radius: 12px;
-fx-padding: 4;
} }
.combo-box:focused { .combo-box:focused {
-fx-border-color: -fx-primary-color; -fx-border-color: -fx-primary;
-fx-border-width: 2px;
} }
/* ===== Tab Pane Styling ===== */ .combo-box .list-cell {
.tab-pane { -fx-background-color: transparent;
-fx-text-fill: -fx-slate-800;
-fx-padding: 8 12;
}
/* ===== DatePicker ===== */
.date-picker {
-fx-background-color: -fx-surface;
-fx-border-color: -fx-slate-200;
-fx-border-width: 1px;
-fx-border-radius: 12px;
-fx-background-radius: 12px;
}
.date-picker:focused {
-fx-border-color: -fx-primary;
-fx-border-width: 2px;
}
.date-picker .text-field {
-fx-background-color: transparent;
-fx-border-width: 0;
}
/* ===== CheckBox ===== */
.check-box {
-fx-text-fill: -fx-slate-700;
-fx-font-weight: 600;
}
.check-box .box {
-fx-background-color: -fx-surface;
-fx-border-color: -fx-slate-300;
-fx-border-width: 2px;
-fx-border-radius: 6px;
-fx-background-radius: 6px;
}
.check-box:selected .box {
-fx-background-color: -fx-primary;
-fx-border-color: -fx-primary;
}
.check-box:selected .mark {
-fx-background-color: white; -fx-background-color: white;
} }
.tab { /* ===== Progress Bar & Indicator ===== */
-fx-background-color: #F5F5F5; .progress-bar {
-fx-background-radius: 0; -fx-background-color: -fx-slate-100;
-fx-padding: 10 20 10 20; -fx-background-radius: 12px;
} }
.tab:selected { .progress-bar .bar {
-fx-background-color: white; -fx-background-color: -fx-primary;
-fx-border-color: -fx-primary-color transparent transparent transparent; -fx-background-radius: 12px;
-fx-border-width: 3px 0 0 0; -fx-padding: 4px;
} }
.tab:hover { .progress-indicator {
-fx-background-color: #EEEEEE; -fx-progress-color: -fx-primary;
} }
.tab .tab-label { /* ===== Scroll Pane & Scroll Bars ===== */
-fx-text-fill: #616161; .scroll-pane {
-fx-font-weight: normal; -fx-background-color: transparent;
-fx-border-color: transparent;
} }
.tab:selected .tab-label { .scroll-pane .viewport {
-fx-text-fill: -fx-primary-dark; -fx-background-color: transparent;
-fx-font-weight: bold;
} }
/* ===== Status Bar Styling ===== */ .scroll-bar {
.status-bar { -fx-background-color: transparent;
-fx-background-color: #EEEEEE; -fx-padding: 2;
-fx-border-color: #BDBDBD transparent transparent transparent;
-fx-border-width: 1px 0 0 0;
} }
/* ===== Detail Panel Styling ===== */ .scroll-bar .thumb {
.detail-panel { -fx-background-color: -fx-slate-300;
-fx-background-color: #F5F5F5; -fx-background-radius: 8px;
-fx-border-color: #E0E0E0;
-fx-border-width: 0 0 0 1px;
-fx-effect: dropshadow(gaussian, rgba(0, 0, 0, 0.1), 10, 0, -2, 0);
} }
/* ===== Summary Box Styling ===== */ .scroll-bar .thumb:hover {
.summary-box { -fx-background-color: -fx-slate-400;
-fx-background-color: #E3F2FD;
-fx-padding: 10;
-fx-border-radius: 4px;
-fx-background-radius: 4px;
} }
.summary-label { .scroll-bar .track {
-fx-font-weight: bold; -fx-background-color: -fx-slate-100;
-fx-text-fill: -fx-primary-dark; -fx-background-radius: 8px;
}
/* ===== Schedule Grid ===== */
.schedule-grid {
-fx-background-color: -fx-surface;
-fx-border-color: -fx-slate-200;
-fx-border-width: 1px;
-fx-border-radius: 24px;
-fx-background-radius: 24px;
-fx-effect: dropshadow(gaussian, rgba(0, 0, 0, 0.05), 4, 0, 0, 2);
}
.grid-header {
-fx-background-color: derive(-fx-slate-50, 20%);
-fx-font-weight: 900;
-fx-font-size: 11px;
-fx-text-fill: -fx-slate-500;
-fx-padding: 16;
-fx-alignment: center;
}
.grid-cell {
-fx-background-color: -fx-surface;
-fx-padding: 12;
-fx-alignment: top-left;
-fx-border-color: -fx-slate-100;
-fx-border-width: 0 1 1 0;
}
.placeholder-text {
-fx-font-size: 14px;
-fx-text-fill: -fx-slate-400;
-fx-font-style: italic;
-fx-padding: 40;
} }
/* ===== Status Labels ===== */ /* ===== Status Labels ===== */
.status-label { .status-label {
-fx-font-size: 12px; -fx-font-size: 13px;
-fx-font-style: italic; -fx-font-weight: 600;
-fx-text-fill: -fx-slate-600;
} }
/* ===== Separator Styling ===== */ /* ===== Separators ===== */
.separator { .separator {
-fx-background-color: #E0E0E0; -fx-background-color: -fx-slate-200;
} }
/* ===== Date Picker Styling ===== */ .separator .line {
.date-picker { -fx-border-color: -fx-slate-200;
-fx-border-color: #BDBDBD; -fx-border-width: 1px 0 0 0;
-fx-border-width: 1px;
-fx-border-radius: 4px;
-fx-background-radius: 4px;
} }
.date-picker:focused { /* ===== Tab Pane (if used) ===== */
-fx-border-color: -fx-primary-color; .tab-pane {
-fx-background-color: transparent;
-fx-border-color: transparent;
} }
/* ===== Grid Pane for Calendar ===== */ .tab {
.schedule-grid { -fx-background-color: transparent;
-fx-background-color: white; -fx-background-radius: 12px 12px 0 0;
-fx-border-color: #BDBDBD; -fx-padding: 12 24;
-fx-border-width: 1px; -fx-border-width: 0;
} }
.grid-header { .tab:selected {
-fx-background-color: #E3F2FD; -fx-background-color: -fx-primary-50;
-fx-font-weight: bold; -fx-border-color: -fx-primary;
-fx-padding: 10; -fx-border-width: 0 0 3 0;
-fx-alignment: center;
-fx-min-width: 100;
-fx-min-height: 40;
} }
.grid-cell { .tab .tab-label {
-fx-padding: 10; -fx-text-fill: -fx-slate-500;
-fx-alignment: center; -fx-font-weight: 600;
-fx-min-width: 100; -fx-font-size: 13px;
-fx-min-height: 60;
-fx-background-color: white;
} }
/* ===== List View Styling ===== */ .tab:selected .tab-label {
-fx-text-fill: -fx-primary;
-fx-font-weight: 900;
}
.tab:hover {
-fx-background-color: -fx-slate-100;
}
/* ===== Tooltips ===== */
.tooltip {
-fx-background-color: -fx-slate-900;
-fx-text-fill: white;
-fx-font-size: 12px;
-fx-font-weight: 600;
-fx-padding: 8 12;
-fx-background-radius: 8px;
-fx-effect: dropshadow(gaussian, rgba(0, 0, 0, 0.3), 8, 0.2, 0, 4);
}
/* ===== List View ===== */
.list-view { .list-view {
-fx-border-color: #E0E0E0; -fx-background-color: -fx-surface;
-fx-border-color: -fx-slate-200;
-fx-border-width: 1px; -fx-border-width: 1px;
-fx-border-radius: 16px;
-fx-background-radius: 16px;
} }
.list-cell { .list-cell {
-fx-padding: 8; -fx-background-color: transparent;
-fx-text-fill: -fx-slate-800;
-fx-padding: 12 16;
-fx-font-size: 14px;
} }
.list-cell:filled:selected { .list-cell:filled:selected {
-fx-background-color: -fx-primary-color; -fx-background-color: -fx-primary;
-fx-text-fill: white; -fx-text-fill: white;
-fx-font-weight: 700;
} }
.list-cell:filled:hover { .list-cell:filled:hover {
-fx-background-color: #E3F2FD; -fx-background-color: -fx-primary-50;
} }
/* ===== Scroll Bar Styling ===== */ /* ===== Animations & Transitions ===== */
.scroll-bar { * {
-fx-background-color: transparent; -fx-transition: all 0.2s ease-in-out;
} }
.scroll-bar .thumb {
-fx-background-color: #BDBDBD;
-fx-background-radius: 4px;
}
.scroll-bar .thumb:hover {
-fx-background-color: #9E9E9E;
}
/* ===== Progress Bar ===== */
.progress-bar {
-fx-accent: -fx-primary-color;
}
/* ===== Label Link Style ===== */
.label-link {
-fx-text-fill: -fx-primary-color;
-fx-cursor: hand;
}
.label-link:hover {
-fx-underline: true;
}

View File

@@ -9,71 +9,108 @@
fx:controller="org.example.se302.controller.ImportController" fx:controller="org.example.se302.controller.ImportController"
fitToWidth="true" fitToHeight="true" fitToWidth="true" fitToHeight="true"
hbarPolicy="AS_NEEDED" vbarPolicy="AS_NEEDED"> hbarPolicy="AS_NEEDED" vbarPolicy="AS_NEEDED">
<VBox spacing="20" styleClass="content-pane"> <VBox spacing="24" styleClass="content-pane">
<padding> <padding>
<Insets top="20" right="20" bottom="20" left="20"/> <Insets top="40" right="40" bottom="40" left="40"/>
</padding> </padding>
<!-- Header --> <!-- Header -->
<Label text="Import Data from CSV Files" styleClass="section-title"/>
<Label text="Select CSV files to import student, course, classroom, and enrollment data." wrapText="true"/>
<Separator/>
<!-- Students Import Section -->
<VBox spacing="8"> <VBox spacing="8">
<Label text="Student Data" styleClass="subsection-title"/> <Label text="📁 Data Integration" styleClass="section-title"/>
<HBox spacing="10" alignment="CENTER_LEFT"> <Label text="Upload CSV files to initialize the exam scheduling process."
<TextField fx:id="studentFileField" promptText="No file selected" editable="false" HBox.hgrow="ALWAYS"/> styleClass="description-label" wrapText="true"/>
<Button fx:id="studentBrowseButton" text="Browse..." onAction="#onBrowseStudents"/>
</HBox>
<Label fx:id="studentStatusLabel" text="Not Loaded" styleClass="status-label"/>
</VBox>
<!-- Courses Import Section -->
<VBox spacing="8">
<Label text="Course Data" styleClass="subsection-title"/>
<HBox spacing="10" alignment="CENTER_LEFT">
<TextField fx:id="courseFileField" promptText="No file selected" editable="false" HBox.hgrow="ALWAYS"/>
<Button fx:id="courseBrowseButton" text="Browse..." onAction="#onBrowseCourses"/>
</HBox>
<Label fx:id="courseStatusLabel" text="Not Loaded" styleClass="status-label"/>
</VBox>
<!-- Classrooms Import Section -->
<VBox spacing="8">
<Label text="Classroom Data" styleClass="subsection-title"/>
<HBox spacing="10" alignment="CENTER_LEFT">
<TextField fx:id="classroomFileField" promptText="No file selected" editable="false" HBox.hgrow="ALWAYS"/>
<Button fx:id="classroomBrowseButton" text="Browse..." onAction="#onBrowseClassrooms"/>
</HBox>
<Label fx:id="classroomStatusLabel" text="Not Loaded" styleClass="status-label"/>
</VBox>
<!-- Enrollments Import Section -->
<VBox spacing="8">
<Label text="Enrollment Data" styleClass="subsection-title"/>
<HBox spacing="10" alignment="CENTER_LEFT">
<TextField fx:id="enrollmentFileField" promptText="No file selected" editable="false" HBox.hgrow="ALWAYS"/>
<Button fx:id="enrollmentBrowseButton" text="Browse..." onAction="#onBrowseEnrollments"/>
</HBox>
<Label fx:id="enrollmentStatusLabel" text="Not Loaded" styleClass="status-label"/>
</VBox> </VBox>
<Separator/> <Separator/>
<!-- Import Messages Area --> <!-- Import Cards Grid -->
<VBox spacing="5" VBox.vgrow="ALWAYS"> <GridPane hgap="24" vgap="24">
<columnConstraints>
<ColumnConstraints percentWidth="50" hgrow="ALWAYS"/>
<ColumnConstraints percentWidth="50" hgrow="ALWAYS"/>
</columnConstraints>
<!-- Student Data Card -->
<VBox spacing="16" styleClass="card" GridPane.columnIndex="0" GridPane.rowIndex="0">
<HBox spacing="12" alignment="CENTER_LEFT">
<Label text="👤" style="-fx-font-size: 32px;"/>
<VBox>
<Label text="Student Data" styleClass="subsection-title"/>
<Label text="students.csv" styleClass="status-label"/>
</VBox>
<Region HBox.hgrow="ALWAYS"/>
<Button fx:id="studentBrowseButton" text="Browse..." onAction="#onBrowseStudents"
styleClass="secondary-button, small-button"/>
</HBox>
<TextField fx:id="studentFileField" promptText="No file selected" editable="false"/>
<Label fx:id="studentStatusLabel" text="Not Loaded" styleClass="status-label"/>
</VBox>
<!-- Course Data Card -->
<VBox spacing="16" styleClass="card" GridPane.columnIndex="1" GridPane.rowIndex="0">
<HBox spacing="12" alignment="CENTER_LEFT">
<Label text="📚" style="-fx-font-size: 32px;"/>
<VBox>
<Label text="Course Data" styleClass="subsection-title"/>
<Label text="courses.csv" styleClass="status-label"/>
</VBox>
<Region HBox.hgrow="ALWAYS"/>
<Button fx:id="courseBrowseButton" text="Browse..." onAction="#onBrowseCourses"
styleClass="secondary-button, small-button"/>
</HBox>
<TextField fx:id="courseFileField" promptText="No file selected" editable="false"/>
<Label fx:id="courseStatusLabel" text="Not Loaded" styleClass="status-label"/>
</VBox>
<!-- Classroom Data Card -->
<VBox spacing="16" styleClass="card" GridPane.columnIndex="0" GridPane.rowIndex="1">
<HBox spacing="12" alignment="CENTER_LEFT">
<Label text="🏛️" style="-fx-font-size: 32px;"/>
<VBox>
<Label text="Classroom Data" styleClass="subsection-title"/>
<Label text="classrooms.csv" styleClass="status-label"/>
</VBox>
<Region HBox.hgrow="ALWAYS"/>
<Button fx:id="classroomBrowseButton" text="Browse..." onAction="#onBrowseClassrooms"
styleClass="secondary-button, small-button"/>
</HBox>
<TextField fx:id="classroomFileField" promptText="No file selected" editable="false"/>
<Label fx:id="classroomStatusLabel" text="Not Loaded" styleClass="status-label"/>
</VBox>
<!-- Enrollment Data Card -->
<VBox spacing="16" styleClass="card" GridPane.columnIndex="1" GridPane.rowIndex="1">
<HBox spacing="12" alignment="CENTER_LEFT">
<Label text="📝" style="-fx-font-size: 32px;"/>
<VBox>
<Label text="Enrollment Data" styleClass="subsection-title"/>
<Label text="enrollments.csv" styleClass="status-label"/>
</VBox>
<Region HBox.hgrow="ALWAYS"/>
<Button fx:id="enrollmentBrowseButton" text="Browse..." onAction="#onBrowseEnrollments"
styleClass="secondary-button, small-button"/>
</HBox>
<TextField fx:id="enrollmentFileField" promptText="No file selected" editable="false"/>
<Label fx:id="enrollmentStatusLabel" text="Not Loaded" styleClass="status-label"/>
</VBox>
</GridPane>
<Separator/>
<!-- Import Log -->
<VBox spacing="12" VBox.vgrow="ALWAYS" styleClass="card">
<Label text="Import Messages" styleClass="subsection-title"/> <Label text="Import Messages" styleClass="subsection-title"/>
<TextArea fx:id="messagesArea" editable="false" wrapText="true" VBox.vgrow="ALWAYS" <TextArea fx:id="messagesArea" editable="false" wrapText="true" VBox.vgrow="ALWAYS"
promptText="Validation messages and import results will appear here..."/> prefHeight="200"
promptText="Validation messages and import results will appear here..."
style="-fx-font-family: 'Consolas', 'Monaco', monospace; -fx-font-size: 12px;"/>
</VBox> </VBox>
<!-- Action Buttons --> <!-- Action Buttons -->
<HBox spacing="10" alignment="CENTER_RIGHT"> <HBox spacing="12" alignment="CENTER_RIGHT">
<Button fx:id="importAllButton" text="Import All" onAction="#onImportAll"
styleClass="primary-button" disable="true"/>
<Button text="Clear All" onAction="#onClearAll"/> <Button text="Clear All" onAction="#onClearAll"/>
<Button fx:id="importAllButton" text="📥 Import All" onAction="#onImportAll"
styleClass="primary-button" disable="true"/>
</HBox> </HBox>
</VBox> </VBox>
</ScrollPane> </ScrollPane>

View File

@@ -9,10 +9,17 @@
fx:controller="org.example.se302.controller.MainController" fx:controller="org.example.se302.controller.MainController"
prefHeight="800.0" prefWidth="1200.0"> prefHeight="800.0" prefWidth="1200.0">
<!-- Top: Title Bar --> <!-- Top: Title Bar with Theme Toggle -->
<top> <top>
<VBox styleClass="header"> <VBox styleClass="header">
<Label text="Exam Scheduling System" styleClass="title-label"/> <HBox alignment="CENTER_LEFT" spacing="20">
<padding>
<Insets top="10" right="20" bottom="10" left="20"/>
</padding>
<Label text="📅 Exam Scheduling System" styleClass="title-label" HBox.hgrow="ALWAYS"/>
<Button fx:id="themeToggleButton" text="🌙 Dark Mode" onAction="#onToggleTheme"
style="-fx-background-color: rgba(255,255,255,0.2); -fx-text-fill: white; -fx-border-color: rgba(255,255,255,0.3); -fx-border-width: 1; -fx-border-radius: 8; -fx-background-radius: 8; -fx-padding: 8 16; -fx-font-weight: 700;"/>
</HBox>
<Separator/> <Separator/>
</VBox> </VBox>
</top> </top>
@@ -21,27 +28,27 @@
<center> <center>
<TabPane fx:id="mainTabPane" tabClosingPolicy="UNAVAILABLE"> <TabPane fx:id="mainTabPane" tabClosingPolicy="UNAVAILABLE">
<!-- Import Data Tab --> <!-- Import Data Tab -->
<Tab fx:id="importTab" text="Import Data"> <Tab fx:id="importTab" text="📁 Import Data">
<fx:include source="import-view.fxml"/> <fx:include source="import-view.fxml"/>
</Tab> </Tab>
<!-- Students Tab --> <!-- Students Tab -->
<Tab fx:id="studentsTab" text="Students"> <Tab fx:id="studentsTab" text="👤 Students">
<fx:include source="students-view.fxml"/> <fx:include source="students-view.fxml"/>
</Tab> </Tab>
<!-- Courses Tab --> <!-- Courses Tab -->
<Tab fx:id="coursesTab" text="Courses"> <Tab fx:id="coursesTab" text="📚 Courses">
<fx:include source="courses-view.fxml"/> <fx:include source="courses-view.fxml"/>
</Tab> </Tab>
<!-- Classrooms Tab --> <!-- Classrooms Tab -->
<Tab fx:id="classroomsTab" text="Classrooms"> <Tab fx:id="classroomsTab" text="🏛️ Classrooms">
<fx:include source="classrooms-view.fxml"/> <fx:include source="classrooms-view.fxml"/>
</Tab> </Tab>
<!-- Schedule Views Tab --> <!-- Schedule Views Tab -->
<Tab fx:id="scheduleTab" text="Schedule Views"> <Tab fx:id="scheduleTab" text="📅 Schedule Views">
<fx:include source="schedule-views.fxml"/> <fx:include source="schedule-views.fxml"/>
</Tab> </Tab>
</TabPane> </TabPane>
@@ -51,9 +58,11 @@
<bottom> <bottom>
<HBox styleClass="status-bar" spacing="10"> <HBox styleClass="status-bar" spacing="10">
<padding> <padding>
<Insets top="5" right="10" bottom="5" left="10"/> <Insets top="8" right="20" bottom="8" left="20"/>
</padding> </padding>
<Label fx:id="statusLabel" text="Ready - No data loaded"/> <Label text="🟢" style="-fx-font-size: 10px;"/>
<Label fx:id="statusLabel" text="Ready - No data loaded" HBox.hgrow="ALWAYS"/>
<Label text="ExamFlow v2.0" style="-fx-font-size: 11px; -fx-text-fill: -fx-slate-400; -fx-font-weight: 700;"/>
</HBox> </HBox>
</bottom> </bottom>
</BorderPane> </BorderPane>

View File

@@ -12,125 +12,131 @@
<BorderPane styleClass="content-pane"> <BorderPane styleClass="content-pane">
<top> <top>
<VBox spacing="15"> <VBox spacing="20">
<padding> <padding>
<Insets top="20" right="20" bottom="10" left="20"/> <Insets top="40" right="40" bottom="20" left="40"/>
</padding> </padding>
<!-- Page Title --> <!-- Page Title -->
<Label text="📅 Calendar View - Exam Schedule Generator" styleClass="section-title"/> <VBox spacing="8">
<Label text="Configure and generate an optimized exam schedule using CSP algorithm." <Label text="📅 Auto Schedule Generator" styleClass="section-title"/>
wrapText="true" styleClass="description-label"/> <Label text="Configure and generate an optimized exam schedule using CSP algorithm with intelligent heuristics."
wrapText="true" styleClass="description-label"/>
</VBox>
<Separator/> <Separator/>
<!-- Configuration Panel --> <!-- Configuration Panel -->
<VBox spacing="15" styleClass="config-panel"> <VBox spacing="20" styleClass="config-panel">
<Label text="Schedule Configuration" styleClass="subsection-title"/> <Label text="⚙️ Schedule Configuration" styleClass="subsection-title"/>
<!-- Row 1: Days and Slots --> <!-- Row 1: Time Configuration -->
<HBox spacing="20" alignment="CENTER_LEFT"> <GridPane hgap="16" vgap="12">
<VBox spacing="5"> <columnConstraints>
<Label text="Number of Days:"/> <ColumnConstraints percentWidth="25"/>
<Spinner fx:id="numDaysSpinner" min="1" max="30" initialValue="5" <ColumnConstraints percentWidth="25"/>
editable="true" prefWidth="100"/> <ColumnConstraints percentWidth="25"/>
<ColumnConstraints percentWidth="25"/>
</columnConstraints>
<VBox spacing="6" GridPane.columnIndex="0" GridPane.rowIndex="0">
<Label text="Number of Days" styleClass="stat-label"/>
<Spinner fx:id="numDaysSpinner" editable="true" prefWidth="120"/>
</VBox> </VBox>
<VBox spacing="5"> <VBox spacing="6" GridPane.columnIndex="1" GridPane.rowIndex="0">
<Label text="Slots per Day:"/> <Label text="Slots per Day" styleClass="stat-label"/>
<Spinner fx:id="slotsPerDaySpinner" min="1" max="10" initialValue="4" <Spinner fx:id="slotsPerDaySpinner" editable="true" prefWidth="120"/>
editable="true" prefWidth="100"/>
</VBox> </VBox>
<VBox spacing="5"> <VBox spacing="6" GridPane.columnIndex="2" GridPane.rowIndex="0">
<Label text="Start Date:"/> <Label text="Start Date" styleClass="stat-label"/>
<DatePicker fx:id="startDatePicker" promptText="Select Start Date" prefWidth="150"/> <DatePicker fx:id="startDatePicker" promptText="Select Date" prefWidth="150"/>
</VBox> </VBox>
<VBox spacing="5"> <VBox spacing="6" GridPane.columnIndex="3" GridPane.rowIndex="0">
<Label text="Slot Duration (min):"/> <Label text="Slot Duration (min)" styleClass="stat-label"/>
<Spinner fx:id="slotDurationSpinner" min="30" max="240" initialValue="120" <Spinner fx:id="slotDurationSpinner" editable="true" prefWidth="120"/>
editable="true" prefWidth="100"/>
</VBox>
</HBox>
<!-- Row 2: Strategy and Options -->
<HBox spacing="20" alignment="CENTER_LEFT">
<VBox spacing="5">
<Label text="Optimization Strategy:"/>
<ComboBox fx:id="strategyComboBox" prefWidth="200" promptText="Select Strategy"/>
</VBox> </VBox>
<VBox spacing="5"> <!-- Row 2: Strategy Configuration -->
<Label text="Day Start Time:"/> <VBox spacing="6" GridPane.columnIndex="0" GridPane.rowIndex="1" GridPane.columnSpan="2">
<Label text="Optimization Strategy" styleClass="stat-label"/>
<ComboBox fx:id="strategyComboBox" prefWidth="250" promptText="Select Strategy"/>
</VBox>
<VBox spacing="6" GridPane.columnIndex="2" GridPane.rowIndex="1">
<Label text="Day Start Time" styleClass="stat-label"/>
<ComboBox fx:id="startTimeComboBox" prefWidth="120" promptText="09:00"/> <ComboBox fx:id="startTimeComboBox" prefWidth="120" promptText="09:00"/>
</VBox> </VBox>
<VBox spacing="5" alignment="CENTER_LEFT"> <VBox spacing="6" alignment="CENTER_LEFT" GridPane.columnIndex="3" GridPane.rowIndex="1">
<padding> <Label text="Options" styleClass="stat-label"/>
<Insets top="18"/> <CheckBox fx:id="allowBackToBackCheckBox" text="Allow back-to-back" selected="true"/>
</padding>
<CheckBox fx:id="allowBackToBackCheckBox" text="Allow back-to-back exams" selected="true"/>
</VBox> </VBox>
</HBox> </GridPane>
<!-- Row 3: Summary --> <!-- Summary -->
<HBox spacing="10" alignment="CENTER_LEFT" styleClass="summary-row"> <HBox spacing="12" alignment="CENTER_LEFT" styleClass="summary-row">
<Label text="Configuration Summary:"/> <Label text="📊 Total Time Slots:" style="-fx-font-weight: 700;"/>
<Label fx:id="summaryLabel" text="5 days × 4 slots = 20 total time slots" <Label fx:id="summaryLabel" text="5 days × 4 slots = 20 slots"
styleClass="summary-value"/> styleClass="summary-value"/>
</HBox> </HBox>
</VBox> </VBox>
<Separator/> <Separator/>
<!-- Action Buttons --> <!-- Action Bar -->
<HBox spacing="15" alignment="CENTER_LEFT"> <HBox spacing="16" alignment="CENTER_LEFT">
<Button fx:id="generateButton" text="🚀 Generate Schedule" <Button fx:id="generateButton" text="🚀 Generate Schedule"
onAction="#onGenerateSchedule" styleClass="primary-button" prefWidth="180"/> onAction="#onGenerateSchedule" styleClass="primary-button"/>
<Button fx:id="cancelButton" text="Cancel" onAction="#onCancelGeneration" <Button fx:id="cancelButton" text="Cancel" onAction="#onCancelGeneration"
disable="true" prefWidth="100"/> disable="true"/>
<Region HBox.hgrow="ALWAYS"/> <Region HBox.hgrow="ALWAYS"/>
<Label fx:id="statusLabel" text="Ready" styleClass="status-label"/> <Label fx:id="statusLabel" text="Ready" styleClass="status-label"/>
</HBox> </HBox>
<!-- Progress Container with Indicator --> <!-- Progress Container -->
<VBox fx:id="progressContainer" spacing="8" visible="false" managed="false" <VBox fx:id="progressContainer" spacing="12" visible="false" managed="false"
style="-fx-background-color: #f8f9fa; -fx-padding: 15; -fx-background-radius: 5;"> styleClass="card">
<HBox spacing="15" alignment="CENTER_LEFT"> <HBox spacing="16" alignment="CENTER_LEFT">
<ProgressIndicator fx:id="progressIndicator" prefWidth="30" prefHeight="30"/> <ProgressIndicator fx:id="progressIndicator" prefWidth="32" prefHeight="32"/>
<VBox spacing="3"> <VBox spacing="4" HBox.hgrow="ALWAYS">
<Label fx:id="progressLabel" text="Initializing..." <Label fx:id="progressLabel" text="Initializing..."
style="-fx-font-weight: bold; -fx-font-size: 13;"/> style="-fx-font-weight: 700; -fx-font-size: 14px;"/>
<ProgressBar fx:id="progressBar" prefWidth="350" progress="0"/> <ProgressBar fx:id="progressBar" prefWidth="400" progress="0" maxWidth="Infinity"/>
</VBox> </VBox>
</HBox> </HBox>
<Label fx:id="progressDetailLabel" text="Please wait..." <Label fx:id="progressDetailLabel" text="Please wait..."
style="-fx-text-fill: #7f8c8d; -fx-font-size: 11;"/> style="-fx-text-fill: -fx-slate-600; -fx-font-size: 12px;"/>
</VBox> </VBox>
</VBox> </VBox>
</top> </top>
<center> <center>
<VBox spacing="15"> <VBox spacing="20">
<padding> <padding>
<Insets top="20" right="20" bottom="20" left="20"/> <Insets top="20" right="40" bottom="40" left="40"/>
</padding> </padding>
<!-- Schedule Grid Container --> <!-- Schedule Grid Header -->
<Label text="Generated Schedule" styleClass="subsection-title"/> <HBox spacing="12" alignment="CENTER_LEFT">
<Label text="📆 Generated Schedule" styleClass="subsection-title"/>
<Region HBox.hgrow="ALWAYS"/>
<Button text="📥 Export PDF" styleClass="small-button"/>
</HBox>
<!-- Schedule Grid -->
<ScrollPane fx:id="scheduleScrollPane" fitToWidth="true" fitToHeight="false" <ScrollPane fx:id="scheduleScrollPane" fitToWidth="true" fitToHeight="false"
prefHeight="500" minHeight="300" prefHeight="500" minHeight="300"
hbarPolicy="ALWAYS" vbarPolicy="ALWAYS" hbarPolicy="ALWAYS" vbarPolicy="ALWAYS"
VBox.vgrow="ALWAYS" VBox.vgrow="ALWAYS"
style="-fx-background-color: white; -fx-border-color: #bdc3c7; -fx-border-width: 1;"> style="-fx-background: -fx-surface; -fx-border-color: -fx-slate-200; -fx-border-width: 1; -fx-border-radius: 24; -fx-background-radius: 24;">
<GridPane fx:id="scheduleGrid" gridLinesVisible="true" styleClass="schedule-grid" <GridPane fx:id="scheduleGrid" gridLinesVisible="true" styleClass="schedule-grid"
minWidth="800" minHeight="400"> minWidth="800" minHeight="400">
<padding> <padding>
<Insets top="10" right="10" bottom="10" left="10"/> <Insets top="10" right="10" bottom="10" left="10"/>
</padding> </padding>
<!-- Grid will be populated dynamically -->
<Label text="Click 'Generate Schedule' to create an exam schedule" <Label text="Click 'Generate Schedule' to create an exam schedule"
GridPane.columnIndex="0" GridPane.rowIndex="0" GridPane.columnIndex="0" GridPane.rowIndex="0"
styleClass="placeholder-text" wrapText="true"/> styleClass="placeholder-text" wrapText="true"/>
@@ -138,23 +144,23 @@
</ScrollPane> </ScrollPane>
<!-- Statistics Panel --> <!-- Statistics Panel -->
<HBox spacing="30" alignment="CENTER_LEFT" styleClass="stats-panel"> <HBox spacing="24" alignment="CENTER_LEFT" styleClass="stats-panel">
<VBox spacing="3" alignment="CENTER"> <VBox spacing="4" alignment="CENTER">
<Label fx:id="totalCoursesLabel" text="0" styleClass="stat-value"/> <Label fx:id="totalCoursesLabel" text="0" styleClass="stat-value"/>
<Label text="Total Courses" styleClass="stat-label"/> <Label text="Total Courses" styleClass="stat-label"/>
</VBox> </VBox>
<Separator orientation="VERTICAL"/> <Separator orientation="VERTICAL"/>
<VBox spacing="3" alignment="CENTER"> <VBox spacing="4" alignment="CENTER">
<Label fx:id="scheduledCoursesLabel" text="0" styleClass="stat-value"/> <Label fx:id="scheduledCoursesLabel" text="0" styleClass="stat-value"/>
<Label text="Scheduled" styleClass="stat-label"/> <Label text="Scheduled" styleClass="stat-label"/>
</VBox> </VBox>
<Separator orientation="VERTICAL"/> <Separator orientation="VERTICAL"/>
<VBox spacing="3" alignment="CENTER"> <VBox spacing="4" alignment="CENTER">
<Label fx:id="classroomsUsedLabel" text="0" styleClass="stat-value"/> <Label fx:id="classroomsUsedLabel" text="0" styleClass="stat-value"/>
<Label text="Classrooms Used" styleClass="stat-label"/> <Label text="Classrooms Used" styleClass="stat-label"/>
</VBox> </VBox>
<Separator orientation="VERTICAL"/> <Separator orientation="VERTICAL"/>
<VBox spacing="3" alignment="CENTER"> <VBox spacing="4" alignment="CENTER">
<Label fx:id="generationTimeLabel" text="-" styleClass="stat-value"/> <Label fx:id="generationTimeLabel" text="-" styleClass="stat-value"/>
<Label text="Generation Time" styleClass="stat-label"/> <Label text="Generation Time" styleClass="stat-label"/>
</VBox> </VBox>