From 8f3cfa2eed130479d5ebaff4388e22e79a803717 Mon Sep 17 00:00:00 2001 From: sabazadam Date: Thu, 11 Dec 2025 00:07:10 +0300 Subject: [PATCH] - Implement CSV import with validation (students, courses, classrooms, enrollments) - Create MVC architecture with DataManager singleton - Add search/filter functionality for all views --- sampleData/sampleData_AllAttendanceLists.csv | 59 ++++ ...leData_AllClassroomsAndTheirCapacities.csv | 11 + sampleData/sampleData_AllCourses.csv | 21 ++ sampleData/sampleData_AllStudents.csv | 251 ++++++++++++++ src/main/java/module-info.java | 11 +- .../org/example/se302/ExamSchedulerApp.java | 36 ++ src/main/java/org/example/se302/Launcher.java | 2 +- .../controller/ClassroomsController.java | 69 ++++ .../se302/controller/CoursesController.java | 136 ++++++++ .../se302/controller/ImportController.java | 229 +++++++++++++ .../se302/controller/MainController.java | 85 +++++ .../ScheduleCalendarController.java | 33 ++ .../ScheduleClassroomController.java | 89 +++++ .../controller/ScheduleCourseController.java | 89 +++++ .../controller/ScheduleStudentController.java | 82 +++++ .../se302/controller/StudentsController.java | 119 +++++++ .../org/example/se302/model/Classroom.java | 35 ++ .../java/org/example/se302/model/Course.java | 68 ++++ .../org/example/se302/model/ImportResult.java | 79 +++++ .../java/org/example/se302/model/Student.java | 48 +++ .../se302/service/DataImportService.java | 314 ++++++++++++++++++ .../example/se302/service/DataManager.java | 134 ++++++++ 22 files changed, 1997 insertions(+), 3 deletions(-) create mode 100644 sampleData/sampleData_AllAttendanceLists.csv create mode 100644 sampleData/sampleData_AllClassroomsAndTheirCapacities.csv create mode 100644 sampleData/sampleData_AllCourses.csv create mode 100644 sampleData/sampleData_AllStudents.csv create mode 100644 src/main/java/org/example/se302/ExamSchedulerApp.java create mode 100644 src/main/java/org/example/se302/controller/ClassroomsController.java create mode 100644 src/main/java/org/example/se302/controller/CoursesController.java create mode 100644 src/main/java/org/example/se302/controller/ImportController.java create mode 100644 src/main/java/org/example/se302/controller/MainController.java create mode 100644 src/main/java/org/example/se302/controller/ScheduleCalendarController.java create mode 100644 src/main/java/org/example/se302/controller/ScheduleClassroomController.java create mode 100644 src/main/java/org/example/se302/controller/ScheduleCourseController.java create mode 100644 src/main/java/org/example/se302/controller/ScheduleStudentController.java create mode 100644 src/main/java/org/example/se302/controller/StudentsController.java create mode 100644 src/main/java/org/example/se302/model/Classroom.java create mode 100644 src/main/java/org/example/se302/model/Course.java create mode 100644 src/main/java/org/example/se302/model/ImportResult.java create mode 100644 src/main/java/org/example/se302/model/Student.java create mode 100644 src/main/java/org/example/se302/service/DataImportService.java create mode 100644 src/main/java/org/example/se302/service/DataManager.java diff --git a/sampleData/sampleData_AllAttendanceLists.csv b/sampleData/sampleData_AllAttendanceLists.csv new file mode 100644 index 0000000..6d2668c --- /dev/null +++ b/sampleData/sampleData_AllAttendanceLists.csv @@ -0,0 +1,59 @@ +CourseCode_01 +['Std_ID_170', 'Std_ID_077', 'Std_ID_097', 'Std_ID_202', 'Std_ID_080', 'Std_ID_126', 'Std_ID_112', 'Std_ID_175', 'Std_ID_180', 'Std_ID_139', 'Std_ID_181', 'Std_ID_222', 'Std_ID_243', 'Std_ID_167', 'Std_ID_136', 'Std_ID_021', 'Std_ID_041', 'Std_ID_231', 'Std_ID_062', 'Std_ID_138', 'Std_ID_236', 'Std_ID_221', 'Std_ID_006', 'Std_ID_135', 'Std_ID_209', 'Std_ID_239', 'Std_ID_043', 'Std_ID_182', 'Std_ID_246', 'Std_ID_083', 'Std_ID_056', 'Std_ID_047', 'Std_ID_193', 'Std_ID_107', 'Std_ID_216', 'Std_ID_005', 'Std_ID_096', 'Std_ID_140', 'Std_ID_233', 'Std_ID_168'] + +CourseCode_02 +['Std_ID_238', 'Std_ID_132', 'Std_ID_079', 'Std_ID_139', 'Std_ID_116', 'Std_ID_093', 'Std_ID_190', 'Std_ID_121', 'Std_ID_108', 'Std_ID_005', 'Std_ID_210', 'Std_ID_018', 'Std_ID_152', 'Std_ID_033', 'Std_ID_199', 'Std_ID_073', 'Std_ID_193', 'Std_ID_220', 'Std_ID_222', 'Std_ID_188', 'Std_ID_217', 'Std_ID_008', 'Std_ID_123', 'Std_ID_159', 'Std_ID_227', 'Std_ID_136', 'Std_ID_187', 'Std_ID_216', 'Std_ID_056', 'Std_ID_026', 'Std_ID_099', 'Std_ID_146', 'Std_ID_055', 'Std_ID_057', 'Std_ID_035', 'Std_ID_049', 'Std_ID_144', 'Std_ID_232', 'Std_ID_098', 'Std_ID_058'] + +CourseCode_03 +['Std_ID_098', 'Std_ID_070', 'Std_ID_049', 'Std_ID_191', 'Std_ID_109', 'Std_ID_196', 'Std_ID_209', 'Std_ID_189', 'Std_ID_039', 'Std_ID_219', 'Std_ID_009', 'Std_ID_033', 'Std_ID_059', 'Std_ID_174', 'Std_ID_169', 'Std_ID_122', 'Std_ID_057', 'Std_ID_249', 'Std_ID_142', 'Std_ID_229', 'Std_ID_038', 'Std_ID_132', 'Std_ID_140', 'Std_ID_081', 'Std_ID_243', 'Std_ID_150', 'Std_ID_099', 'Std_ID_224', 'Std_ID_097', 'Std_ID_193', 'Std_ID_043', 'Std_ID_055', 'Std_ID_061', 'Std_ID_212', 'Std_ID_205', 'Std_ID_146', 'Std_ID_100', 'Std_ID_149', 'Std_ID_116', 'Std_ID_233'] + +CourseCode_04 +['Std_ID_142', 'Std_ID_085', 'Std_ID_103', 'Std_ID_059', 'Std_ID_243', 'Std_ID_106', 'Std_ID_069', 'Std_ID_014', 'Std_ID_015', 'Std_ID_088', 'Std_ID_136', 'Std_ID_020', 'Std_ID_097', 'Std_ID_190', 'Std_ID_194', 'Std_ID_201', 'Std_ID_125', 'Std_ID_182', 'Std_ID_138', 'Std_ID_105', 'Std_ID_076', 'Std_ID_007', 'Std_ID_066', 'Std_ID_189', 'Std_ID_129', 'Std_ID_133', 'Std_ID_034', 'Std_ID_130', 'Std_ID_247', 'Std_ID_024', 'Std_ID_027', 'Std_ID_115', 'Std_ID_064', 'Std_ID_132', 'Std_ID_113', 'Std_ID_118', 'Std_ID_162', 'Std_ID_154', 'Std_ID_181', 'Std_ID_114'] + +CourseCode_05 +['Std_ID_203', 'Std_ID_236', 'Std_ID_192', 'Std_ID_198', 'Std_ID_213', 'Std_ID_147', 'Std_ID_051', 'Std_ID_095', 'Std_ID_226', 'Std_ID_077', 'Std_ID_230', 'Std_ID_035', 'Std_ID_142', 'Std_ID_195', 'Std_ID_182', 'Std_ID_064', 'Std_ID_229', 'Std_ID_239', 'Std_ID_119', 'Std_ID_231', 'Std_ID_217', 'Std_ID_054', 'Std_ID_163', 'Std_ID_148', 'Std_ID_249', 'Std_ID_068', 'Std_ID_202', 'Std_ID_225', 'Std_ID_101', 'Std_ID_126', 'Std_ID_150', 'Std_ID_197', 'Std_ID_025', 'Std_ID_140', 'Std_ID_124', 'Std_ID_053', 'Std_ID_075', 'Std_ID_074', 'Std_ID_243', 'Std_ID_169'] + +CourseCode_06 +['Std_ID_129', 'Std_ID_175', 'Std_ID_049', 'Std_ID_238', 'Std_ID_056', 'Std_ID_187', 'Std_ID_064', 'Std_ID_151', 'Std_ID_201', 'Std_ID_022', 'Std_ID_169', 'Std_ID_220', 'Std_ID_147', 'Std_ID_223', 'Std_ID_011', 'Std_ID_053', 'Std_ID_166', 'Std_ID_111', 'Std_ID_219', 'Std_ID_112', 'Std_ID_203', 'Std_ID_054', 'Std_ID_106', 'Std_ID_025', 'Std_ID_073', 'Std_ID_234', 'Std_ID_027', 'Std_ID_215', 'Std_ID_028', 'Std_ID_208', 'Std_ID_192', 'Std_ID_172', 'Std_ID_015', 'Std_ID_164', 'Std_ID_110', 'Std_ID_010', 'Std_ID_078', 'Std_ID_006', 'Std_ID_204', 'Std_ID_029'] + +CourseCode_07 +['Std_ID_040', 'Std_ID_102', 'Std_ID_186', 'Std_ID_206', 'Std_ID_113', 'Std_ID_135', 'Std_ID_145', 'Std_ID_165', 'Std_ID_028', 'Std_ID_235', 'Std_ID_144', 'Std_ID_085', 'Std_ID_026', 'Std_ID_203', 'Std_ID_231', 'Std_ID_124', 'Std_ID_190', 'Std_ID_089', 'Std_ID_074', 'Std_ID_222', 'Std_ID_179', 'Std_ID_127', 'Std_ID_041', 'Std_ID_133', 'Std_ID_181', 'Std_ID_095', 'Std_ID_034', 'Std_ID_219', 'Std_ID_148', 'Std_ID_037', 'Std_ID_061', 'Std_ID_188', 'Std_ID_171', 'Std_ID_238', 'Std_ID_242', 'Std_ID_029', 'Std_ID_182', 'Std_ID_072', 'Std_ID_161', 'Std_ID_093'] + +CourseCode_08 +['Std_ID_009', 'Std_ID_108', 'Std_ID_116', 'Std_ID_111', 'Std_ID_058', 'Std_ID_086', 'Std_ID_128', 'Std_ID_106', 'Std_ID_187', 'Std_ID_115', 'Std_ID_101', 'Std_ID_096', 'Std_ID_063', 'Std_ID_192', 'Std_ID_091', 'Std_ID_195', 'Std_ID_236', 'Std_ID_039', 'Std_ID_042', 'Std_ID_052', 'Std_ID_032', 'Std_ID_137', 'Std_ID_181', 'Std_ID_071', 'Std_ID_127', 'Std_ID_130', 'Std_ID_089', 'Std_ID_165', 'Std_ID_175', 'Std_ID_207', 'Std_ID_121', 'Std_ID_233', 'Std_ID_033', 'Std_ID_080', 'Std_ID_240', 'Std_ID_152', 'Std_ID_209', 'Std_ID_050', 'Std_ID_017', 'Std_ID_075'] + +CourseCode_09 +['Std_ID_043', 'Std_ID_203', 'Std_ID_040', 'Std_ID_163', 'Std_ID_142', 'Std_ID_137', 'Std_ID_220', 'Std_ID_126', 'Std_ID_032', 'Std_ID_157', 'Std_ID_159', 'Std_ID_219', 'Std_ID_175', 'Std_ID_015', 'Std_ID_177', 'Std_ID_217', 'Std_ID_008', 'Std_ID_070', 'Std_ID_048', 'Std_ID_033', 'Std_ID_188', 'Std_ID_078', 'Std_ID_097', 'Std_ID_121', 'Std_ID_183', 'Std_ID_016', 'Std_ID_181', 'Std_ID_080', 'Std_ID_119', 'Std_ID_206', 'Std_ID_093', 'Std_ID_195', 'Std_ID_002', 'Std_ID_098', 'Std_ID_200', 'Std_ID_090', 'Std_ID_167', 'Std_ID_088', 'Std_ID_041', 'Std_ID_164'] + +CourseCode_10 +['Std_ID_229', 'Std_ID_167', 'Std_ID_228', 'Std_ID_193', 'Std_ID_025', 'Std_ID_205', 'Std_ID_163', 'Std_ID_225', 'Std_ID_194', 'Std_ID_240', 'Std_ID_247', 'Std_ID_233', 'Std_ID_181', 'Std_ID_121', 'Std_ID_152', 'Std_ID_131', 'Std_ID_026', 'Std_ID_217', 'Std_ID_124', 'Std_ID_010', 'Std_ID_054', 'Std_ID_129', 'Std_ID_003', 'Std_ID_211', 'Std_ID_090', 'Std_ID_092', 'Std_ID_024', 'Std_ID_144', 'Std_ID_201', 'Std_ID_049', 'Std_ID_171', 'Std_ID_082', 'Std_ID_136', 'Std_ID_042', 'Std_ID_224', 'Std_ID_027', 'Std_ID_021', 'Std_ID_160', 'Std_ID_235', 'Std_ID_072'] + +CourseCode_11 +['Std_ID_208', 'Std_ID_237', 'Std_ID_061', 'Std_ID_133', 'Std_ID_119', 'Std_ID_132', 'Std_ID_168', 'Std_ID_238', 'Std_ID_170', 'Std_ID_190', 'Std_ID_066', 'Std_ID_093', 'Std_ID_243', 'Std_ID_148', 'Std_ID_205', 'Std_ID_033', 'Std_ID_078', 'Std_ID_191', 'Std_ID_111', 'Std_ID_090', 'Std_ID_079', 'Std_ID_083', 'Std_ID_086', 'Std_ID_020', 'Std_ID_029', 'Std_ID_022', 'Std_ID_131', 'Std_ID_071', 'Std_ID_055', 'Std_ID_141', 'Std_ID_049', 'Std_ID_115', 'Std_ID_202', 'Std_ID_122', 'Std_ID_075', 'Std_ID_222', 'Std_ID_149', 'Std_ID_021', 'Std_ID_018', 'Std_ID_124'] + +CourseCode_12 +['Std_ID_066', 'Std_ID_052', 'Std_ID_091', 'Std_ID_008', 'Std_ID_148', 'Std_ID_003', 'Std_ID_040', 'Std_ID_187', 'Std_ID_170', 'Std_ID_147', 'Std_ID_111', 'Std_ID_027', 'Std_ID_142', 'Std_ID_188', 'Std_ID_202', 'Std_ID_176', 'Std_ID_079', 'Std_ID_145', 'Std_ID_058', 'Std_ID_089', 'Std_ID_190', 'Std_ID_044', 'Std_ID_062', 'Std_ID_112', 'Std_ID_099', 'Std_ID_220', 'Std_ID_203', 'Std_ID_222', 'Std_ID_117', 'Std_ID_061', 'Std_ID_118', 'Std_ID_217', 'Std_ID_109', 'Std_ID_223', 'Std_ID_002', 'Std_ID_019', 'Std_ID_121', 'Std_ID_100', 'Std_ID_152', 'Std_ID_213'] + +CourseCode_13 +['Std_ID_175', 'Std_ID_205', 'Std_ID_019', 'Std_ID_142', 'Std_ID_243', 'Std_ID_246', 'Std_ID_186', 'Std_ID_228', 'Std_ID_137', 'Std_ID_080', 'Std_ID_130', 'Std_ID_105', 'Std_ID_216', 'Std_ID_201', 'Std_ID_135', 'Std_ID_218', 'Std_ID_096', 'Std_ID_109', 'Std_ID_026', 'Std_ID_229', 'Std_ID_207', 'Std_ID_014', 'Std_ID_179', 'Std_ID_192', 'Std_ID_158', 'Std_ID_102', 'Std_ID_011', 'Std_ID_094', 'Std_ID_057', 'Std_ID_010', 'Std_ID_062', 'Std_ID_090', 'Std_ID_125', 'Std_ID_219', 'Std_ID_153', 'Std_ID_122', 'Std_ID_248', 'Std_ID_181', 'Std_ID_058', 'Std_ID_037'] + +CourseCode_14 +['Std_ID_018', 'Std_ID_052', 'Std_ID_227', 'Std_ID_003', 'Std_ID_208', 'Std_ID_012', 'Std_ID_162', 'Std_ID_042', 'Std_ID_141', 'Std_ID_063', 'Std_ID_019', 'Std_ID_096', 'Std_ID_192', 'Std_ID_124', 'Std_ID_245', 'Std_ID_200', 'Std_ID_219', 'Std_ID_092', 'Std_ID_222', 'Std_ID_020', 'Std_ID_077', 'Std_ID_109', 'Std_ID_174', 'Std_ID_087', 'Std_ID_028', 'Std_ID_083', 'Std_ID_122', 'Std_ID_201', 'Std_ID_117', 'Std_ID_054', 'Std_ID_080', 'Std_ID_010', 'Std_ID_221', 'Std_ID_131', 'Std_ID_024', 'Std_ID_234', 'Std_ID_036', 'Std_ID_177', 'Std_ID_161', 'Std_ID_126'] + +CourseCode_15 +['Std_ID_142', 'Std_ID_050', 'Std_ID_115', 'Std_ID_076', 'Std_ID_011', 'Std_ID_035', 'Std_ID_191', 'Std_ID_059', 'Std_ID_032', 'Std_ID_030', 'Std_ID_012', 'Std_ID_015', 'Std_ID_150', 'Std_ID_121', 'Std_ID_124', 'Std_ID_061', 'Std_ID_075', 'Std_ID_133', 'Std_ID_166', 'Std_ID_069', 'Std_ID_094', 'Std_ID_158', 'Std_ID_144', 'Std_ID_199', 'Std_ID_096', 'Std_ID_177', 'Std_ID_014', 'Std_ID_022', 'Std_ID_004', 'Std_ID_233', 'Std_ID_172', 'Std_ID_180', 'Std_ID_223', 'Std_ID_073', 'Std_ID_018', 'Std_ID_057', 'Std_ID_224', 'Std_ID_099', 'Std_ID_062', 'Std_ID_232'] + +CourseCode_16 +['Std_ID_020', 'Std_ID_124', 'Std_ID_097', 'Std_ID_092', 'Std_ID_100', 'Std_ID_119', 'Std_ID_177', 'Std_ID_011', 'Std_ID_017', 'Std_ID_114', 'Std_ID_087', 'Std_ID_007', 'Std_ID_132', 'Std_ID_208', 'Std_ID_082', 'Std_ID_002', 'Std_ID_116', 'Std_ID_057', 'Std_ID_210', 'Std_ID_045', 'Std_ID_005', 'Std_ID_139', 'Std_ID_028', 'Std_ID_051', 'Std_ID_144', 'Std_ID_207', 'Std_ID_056', 'Std_ID_042', 'Std_ID_211', 'Std_ID_231', 'Std_ID_203', 'Std_ID_196', 'Std_ID_009', 'Std_ID_106', 'Std_ID_133', 'Std_ID_151', 'Std_ID_058', 'Std_ID_101', 'Std_ID_125', 'Std_ID_232'] + +CourseCode_17 +['Std_ID_122', 'Std_ID_224', 'Std_ID_056', 'Std_ID_221', 'Std_ID_054', 'Std_ID_237', 'Std_ID_180', 'Std_ID_052', 'Std_ID_020', 'Std_ID_131', 'Std_ID_106', 'Std_ID_231', 'Std_ID_011', 'Std_ID_064', 'Std_ID_069', 'Std_ID_062', 'Std_ID_239', 'Std_ID_019', 'Std_ID_227', 'Std_ID_053', 'Std_ID_119', 'Std_ID_160', 'Std_ID_184', 'Std_ID_240', 'Std_ID_250', 'Std_ID_030', 'Std_ID_177', 'Std_ID_241', 'Std_ID_038', 'Std_ID_129', 'Std_ID_098', 'Std_ID_104', 'Std_ID_158', 'Std_ID_026', 'Std_ID_202', 'Std_ID_015', 'Std_ID_058', 'Std_ID_109', 'Std_ID_152', 'Std_ID_190'] + +CourseCode_18 +['Std_ID_117', 'Std_ID_136', 'Std_ID_149', 'Std_ID_126', 'Std_ID_006', 'Std_ID_063', 'Std_ID_151', 'Std_ID_060', 'Std_ID_012', 'Std_ID_090', 'Std_ID_154', 'Std_ID_240', 'Std_ID_065', 'Std_ID_162', 'Std_ID_102', 'Std_ID_043', 'Std_ID_144', 'Std_ID_121', 'Std_ID_156', 'Std_ID_066', 'Std_ID_083', 'Std_ID_199', 'Std_ID_143', 'Std_ID_047', 'Std_ID_207', 'Std_ID_189', 'Std_ID_177', 'Std_ID_250', 'Std_ID_210', 'Std_ID_197', 'Std_ID_003', 'Std_ID_246', 'Std_ID_077', 'Std_ID_215', 'Std_ID_086', 'Std_ID_204', 'Std_ID_223', 'Std_ID_067', 'Std_ID_115', 'Std_ID_082'] + +CourseCode_19 +['Std_ID_250', 'Std_ID_067', 'Std_ID_022', 'Std_ID_114', 'Std_ID_060', 'Std_ID_133', 'Std_ID_236', 'Std_ID_039', 'Std_ID_005', 'Std_ID_032', 'Std_ID_071', 'Std_ID_132', 'Std_ID_087', 'Std_ID_229', 'Std_ID_047', 'Std_ID_029', 'Std_ID_235', 'Std_ID_216', 'Std_ID_199', 'Std_ID_204', 'Std_ID_163', 'Std_ID_168', 'Std_ID_147', 'Std_ID_014', 'Std_ID_187', 'Std_ID_179', 'Std_ID_160', 'Std_ID_035', 'Std_ID_167', 'Std_ID_173', 'Std_ID_248', 'Std_ID_026', 'Std_ID_211', 'Std_ID_003', 'Std_ID_112', 'Std_ID_105', 'Std_ID_191', 'Std_ID_095', 'Std_ID_011', 'Std_ID_092'] + +CourseCode_20 +['Std_ID_222', 'Std_ID_125', 'Std_ID_188', 'Std_ID_088', 'Std_ID_095', 'Std_ID_046', 'Std_ID_201', 'Std_ID_051', 'Std_ID_104', 'Std_ID_130', 'Std_ID_092', 'Std_ID_057', 'Std_ID_217', 'Std_ID_181', 'Std_ID_227', 'Std_ID_136', 'Std_ID_064', 'Std_ID_133', 'Std_ID_059', 'Std_ID_065', 'Std_ID_169', 'Std_ID_233', 'Std_ID_129', 'Std_ID_123', 'Std_ID_193', 'Std_ID_079', 'Std_ID_172', 'Std_ID_204', 'Std_ID_208', 'Std_ID_246', 'Std_ID_001', 'Std_ID_174', 'Std_ID_007', 'Std_ID_189', 'Std_ID_203', 'Std_ID_205', 'Std_ID_183', 'Std_ID_048', 'Std_ID_121', 'Std_ID_177'] diff --git a/sampleData/sampleData_AllClassroomsAndTheirCapacities.csv b/sampleData/sampleData_AllClassroomsAndTheirCapacities.csv new file mode 100644 index 0000000..e131df0 --- /dev/null +++ b/sampleData/sampleData_AllClassroomsAndTheirCapacities.csv @@ -0,0 +1,11 @@ +ALL OF THE CLASSROOMS; AND THEIR CAPACITIES IN THE SYSTEM +Classroom_01;40 +Classroom_02;40 +Classroom_03;40 +Classroom_04;40 +Classroom_05;40 +Classroom_06;40 +Classroom_07;40 +Classroom_08;40 +Classroom_09;40 +Classroom_10;40 diff --git a/sampleData/sampleData_AllCourses.csv b/sampleData/sampleData_AllCourses.csv new file mode 100644 index 0000000..ce098ca --- /dev/null +++ b/sampleData/sampleData_AllCourses.csv @@ -0,0 +1,21 @@ +ALL OF THE COURSES IN THE SYSTEM +CourseCode_01 +CourseCode_02 +CourseCode_03 +CourseCode_04 +CourseCode_05 +CourseCode_06 +CourseCode_07 +CourseCode_08 +CourseCode_09 +CourseCode_10 +CourseCode_11 +CourseCode_12 +CourseCode_13 +CourseCode_14 +CourseCode_15 +CourseCode_16 +CourseCode_17 +CourseCode_18 +CourseCode_19 +CourseCode_20 \ No newline at end of file diff --git a/sampleData/sampleData_AllStudents.csv b/sampleData/sampleData_AllStudents.csv new file mode 100644 index 0000000..1560409 --- /dev/null +++ b/sampleData/sampleData_AllStudents.csv @@ -0,0 +1,251 @@ +ALL OF THE STUDENTS IN THE SYSTEM +Std_ID_001 +Std_ID_002 +Std_ID_003 +Std_ID_004 +Std_ID_005 +Std_ID_006 +Std_ID_007 +Std_ID_008 +Std_ID_009 +Std_ID_010 +Std_ID_011 +Std_ID_012 +Std_ID_013 +Std_ID_014 +Std_ID_015 +Std_ID_016 +Std_ID_017 +Std_ID_018 +Std_ID_019 +Std_ID_020 +Std_ID_021 +Std_ID_022 +Std_ID_023 +Std_ID_024 +Std_ID_025 +Std_ID_026 +Std_ID_027 +Std_ID_028 +Std_ID_029 +Std_ID_030 +Std_ID_031 +Std_ID_032 +Std_ID_033 +Std_ID_034 +Std_ID_035 +Std_ID_036 +Std_ID_037 +Std_ID_038 +Std_ID_039 +Std_ID_040 +Std_ID_041 +Std_ID_042 +Std_ID_043 +Std_ID_044 +Std_ID_045 +Std_ID_046 +Std_ID_047 +Std_ID_048 +Std_ID_049 +Std_ID_050 +Std_ID_051 +Std_ID_052 +Std_ID_053 +Std_ID_054 +Std_ID_055 +Std_ID_056 +Std_ID_057 +Std_ID_058 +Std_ID_059 +Std_ID_060 +Std_ID_061 +Std_ID_062 +Std_ID_063 +Std_ID_064 +Std_ID_065 +Std_ID_066 +Std_ID_067 +Std_ID_068 +Std_ID_069 +Std_ID_070 +Std_ID_071 +Std_ID_072 +Std_ID_073 +Std_ID_074 +Std_ID_075 +Std_ID_076 +Std_ID_077 +Std_ID_078 +Std_ID_079 +Std_ID_080 +Std_ID_081 +Std_ID_082 +Std_ID_083 +Std_ID_084 +Std_ID_085 +Std_ID_086 +Std_ID_087 +Std_ID_088 +Std_ID_089 +Std_ID_090 +Std_ID_091 +Std_ID_092 +Std_ID_093 +Std_ID_094 +Std_ID_095 +Std_ID_096 +Std_ID_097 +Std_ID_098 +Std_ID_099 +Std_ID_100 +Std_ID_101 +Std_ID_102 +Std_ID_103 +Std_ID_104 +Std_ID_105 +Std_ID_106 +Std_ID_107 +Std_ID_108 +Std_ID_109 +Std_ID_110 +Std_ID_111 +Std_ID_112 +Std_ID_113 +Std_ID_114 +Std_ID_115 +Std_ID_116 +Std_ID_117 +Std_ID_118 +Std_ID_119 +Std_ID_120 +Std_ID_121 +Std_ID_122 +Std_ID_123 +Std_ID_124 +Std_ID_125 +Std_ID_126 +Std_ID_127 +Std_ID_128 +Std_ID_129 +Std_ID_130 +Std_ID_131 +Std_ID_132 +Std_ID_133 +Std_ID_134 +Std_ID_135 +Std_ID_136 +Std_ID_137 +Std_ID_138 +Std_ID_139 +Std_ID_140 +Std_ID_141 +Std_ID_142 +Std_ID_143 +Std_ID_144 +Std_ID_145 +Std_ID_146 +Std_ID_147 +Std_ID_148 +Std_ID_149 +Std_ID_150 +Std_ID_151 +Std_ID_152 +Std_ID_153 +Std_ID_154 +Std_ID_155 +Std_ID_156 +Std_ID_157 +Std_ID_158 +Std_ID_159 +Std_ID_160 +Std_ID_161 +Std_ID_162 +Std_ID_163 +Std_ID_164 +Std_ID_165 +Std_ID_166 +Std_ID_167 +Std_ID_168 +Std_ID_169 +Std_ID_170 +Std_ID_171 +Std_ID_172 +Std_ID_173 +Std_ID_174 +Std_ID_175 +Std_ID_176 +Std_ID_177 +Std_ID_178 +Std_ID_179 +Std_ID_180 +Std_ID_181 +Std_ID_182 +Std_ID_183 +Std_ID_184 +Std_ID_185 +Std_ID_186 +Std_ID_187 +Std_ID_188 +Std_ID_189 +Std_ID_190 +Std_ID_191 +Std_ID_192 +Std_ID_193 +Std_ID_194 +Std_ID_195 +Std_ID_196 +Std_ID_197 +Std_ID_198 +Std_ID_199 +Std_ID_200 +Std_ID_201 +Std_ID_202 +Std_ID_203 +Std_ID_204 +Std_ID_205 +Std_ID_206 +Std_ID_207 +Std_ID_208 +Std_ID_209 +Std_ID_210 +Std_ID_211 +Std_ID_212 +Std_ID_213 +Std_ID_214 +Std_ID_215 +Std_ID_216 +Std_ID_217 +Std_ID_218 +Std_ID_219 +Std_ID_220 +Std_ID_221 +Std_ID_222 +Std_ID_223 +Std_ID_224 +Std_ID_225 +Std_ID_226 +Std_ID_227 +Std_ID_228 +Std_ID_229 +Std_ID_230 +Std_ID_231 +Std_ID_232 +Std_ID_233 +Std_ID_234 +Std_ID_235 +Std_ID_236 +Std_ID_237 +Std_ID_238 +Std_ID_239 +Std_ID_240 +Std_ID_241 +Std_ID_242 +Std_ID_243 +Std_ID_244 +Std_ID_245 +Std_ID_246 +Std_ID_247 +Std_ID_248 +Std_ID_249 +Std_ID_250 \ No newline at end of file diff --git a/src/main/java/module-info.java b/src/main/java/module-info.java index d67c15b..589fd39 100644 --- a/src/main/java/module-info.java +++ b/src/main/java/module-info.java @@ -2,7 +2,14 @@ module org.example.se302 { requires javafx.controls; requires javafx.fxml; - - opens org.example.se302 to javafx.fxml; + // Export all packages exports org.example.se302; + exports org.example.se302.controller; + exports org.example.se302.model; + exports org.example.se302.service; + + // Open packages for reflection (FXML loading) + opens org.example.se302 to javafx.fxml; + opens org.example.se302.controller to javafx.fxml; + opens org.example.se302.model to javafx.base; } \ No newline at end of file diff --git a/src/main/java/org/example/se302/ExamSchedulerApp.java b/src/main/java/org/example/se302/ExamSchedulerApp.java new file mode 100644 index 0000000..99b61ad --- /dev/null +++ b/src/main/java/org/example/se302/ExamSchedulerApp.java @@ -0,0 +1,36 @@ +package org.example.se302; + +import javafx.application.Application; +import javafx.fxml.FXMLLoader; +import javafx.scene.Scene; +import javafx.stage.Stage; + +import java.io.IOException; + +/** + * Main JavaFX Application for Exam Scheduling System. + */ +public class ExamSchedulerApp extends Application { + + @Override + public void start(Stage stage) throws IOException { + FXMLLoader fxmlLoader = new FXMLLoader( + ExamSchedulerApp.class.getResource("view/main-view.fxml") + ); + Scene scene = new Scene(fxmlLoader.load(), 1200, 800); + + // Load CSS stylesheet + String css = ExamSchedulerApp.class.getResource("css/application.css").toExternalForm(); + scene.getStylesheets().add(css); + + stage.setTitle("Exam Scheduling System v1.0"); + stage.setMinWidth(900); + stage.setMinHeight(600); + stage.setScene(scene); + stage.show(); + } + + public static void main(String[] args) { + launch(); + } +} diff --git a/src/main/java/org/example/se302/Launcher.java b/src/main/java/org/example/se302/Launcher.java index 87c41bb..0712d3c 100644 --- a/src/main/java/org/example/se302/Launcher.java +++ b/src/main/java/org/example/se302/Launcher.java @@ -4,6 +4,6 @@ import javafx.application.Application; public class Launcher { public static void main(String[] args) { - Application.launch(HelloApplication.class, args); + Application.launch(ExamSchedulerApp.class, args); } } diff --git a/src/main/java/org/example/se302/controller/ClassroomsController.java b/src/main/java/org/example/se302/controller/ClassroomsController.java new file mode 100644 index 0000000..852b8bd --- /dev/null +++ b/src/main/java/org/example/se302/controller/ClassroomsController.java @@ -0,0 +1,69 @@ +package org.example.se302.controller; + +import javafx.beans.property.SimpleIntegerProperty; +import javafx.beans.property.SimpleStringProperty; +import javafx.fxml.FXML; +import javafx.scene.control.Label; +import javafx.scene.control.TableColumn; +import javafx.scene.control.TableView; +import org.example.se302.model.Classroom; +import org.example.se302.service.DataManager; + +/** + * Controller for the Classrooms view. + */ +public class ClassroomsController { + + @FXML private TableView classroomsTable; + @FXML private TableColumn classroomIdColumn; + @FXML private TableColumn capacityColumn; + @FXML private TableColumn statusColumn; + @FXML private TableColumn utilizationColumn; + + @FXML private Label totalClassroomsLabel; + @FXML private Label totalCapacityLabel; + @FXML private Label averageCapacityLabel; + + private DataManager dataManager; + + @FXML + public void initialize() { + dataManager = DataManager.getInstance(); + + // Set up table columns + classroomIdColumn.setCellValueFactory(cellData -> + new SimpleStringProperty(cellData.getValue().getClassroomId())); + + capacityColumn.setCellValueFactory(cellData -> + new SimpleIntegerProperty(cellData.getValue().getCapacity())); + + statusColumn.setCellValueFactory(cellData -> + new SimpleStringProperty("Available")); // For demo phase + + utilizationColumn.setCellValueFactory(cellData -> + new SimpleStringProperty("Not Scheduled")); // For demo phase + + // Bind table to data + classroomsTable.setItems(dataManager.getClassrooms()); + + // Update summary statistics + updateSummaryStatistics(); + + // Listen for data changes + dataManager.getClassrooms().addListener( + (javafx.collections.ListChangeListener) c -> updateSummaryStatistics()); + } + + private void updateSummaryStatistics() { + int totalClassrooms = dataManager.getTotalClassrooms(); + int totalCapacity = dataManager.getClassrooms().stream() + .mapToInt(Classroom::getCapacity) + .sum(); + double averageCapacity = totalClassrooms > 0 ? + (double) totalCapacity / totalClassrooms : 0; + + totalClassroomsLabel.setText("Total Classrooms: " + totalClassrooms); + totalCapacityLabel.setText("Total Capacity: " + totalCapacity); + averageCapacityLabel.setText(String.format("Average Capacity: %.1f", averageCapacity)); + } +} diff --git a/src/main/java/org/example/se302/controller/CoursesController.java b/src/main/java/org/example/se302/controller/CoursesController.java new file mode 100644 index 0000000..cbd7a47 --- /dev/null +++ b/src/main/java/org/example/se302/controller/CoursesController.java @@ -0,0 +1,136 @@ +package org.example.se302.controller; + +import javafx.beans.property.SimpleIntegerProperty; +import javafx.beans.property.SimpleStringProperty; +import javafx.collections.FXCollections; +import javafx.collections.transformation.FilteredList; +import javafx.fxml.FXML; +import javafx.scene.control.*; +import javafx.scene.layout.VBox; +import org.example.se302.model.Course; +import org.example.se302.service.DataManager; + +/** + * Controller for the Courses view. + */ +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 VBox studentListPanel; + @FXML private Label studentListTitleLabel; + @FXML private TableView enrolledStudentsTable; + @FXML private TableColumn enrolledStudentIdColumn; + @FXML private Label enrolledCountLabel; + + private DataManager dataManager; + private FilteredList filteredCourses; + + @FXML + public void initialize() { + dataManager = DataManager.getInstance(); + + // Set up table columns + courseCodeColumn.setCellValueFactory(cellData -> + new SimpleStringProperty(cellData.getValue().getCourseCode())); + + studentCountColumn.setCellValueFactory(cellData -> + new SimpleIntegerProperty(cellData.getValue().getEnrolledStudentsCount())); + + classroomColumn.setCellValueFactory(cellData -> { + String classroom = cellData.getValue().getAssignedClassroom(); + return new SimpleStringProperty(classroom != null ? classroom : "Not Assigned"); + }); + + examDateColumn.setCellValueFactory(cellData -> { + String examDate = cellData.getValue().getExamDateTime(); + return new SimpleStringProperty(examDate != null ? examDate : "Not Scheduled"); + }); + + // Add "View Students" button to action column + actionColumn.setCellFactory(col -> new TableCell<>() { + private final Button viewButton = new Button("View Students"); + + { + viewButton.setOnAction(event -> { + Course course = getTableView().getItems().get(getIndex()); + showEnrolledStudents(course); + }); + } + + @Override + protected void updateItem(Void item, boolean empty) { + super.updateItem(item, empty); + setGraphic(empty ? null : viewButton); + } + }); + + // Set up enrolled students table column + enrolledStudentIdColumn.setCellValueFactory(cellData -> + new SimpleStringProperty(cellData.getValue())); + + // Set up filtered list + filteredCourses = new FilteredList<>(dataManager.getCourses(), p -> true); + coursesTable.setItems(filteredCourses); + + // Set up search functionality + searchField.textProperty().addListener((observable, oldValue, newValue) -> { + filterCourses(newValue); + }); + + // Update result count + updateResultCount(); + filteredCourses.addListener((javafx.collections.ListChangeListener) c -> updateResultCount()); + } + + @FXML + private void onClearSearch() { + searchField.clear(); + } + + @FXML + private void onCloseStudentList() { + studentListPanel.setVisible(false); + studentListPanel.setManaged(false); + } + + private void filterCourses(String searchText) { + if (searchText == null || searchText.trim().isEmpty()) { + filteredCourses.setPredicate(course -> true); + } else { + String lowerCaseFilter = searchText.toLowerCase().trim(); + filteredCourses.setPredicate(course -> + course.getCourseCode().toLowerCase().contains(lowerCaseFilter) + ); + } + } + + private void showEnrolledStudents(Course course) { + if (course == null) return; + + studentListTitleLabel.setText("Students Enrolled in " + course.getCourseCode()); + enrolledStudentsTable.setItems(FXCollections.observableArrayList(course.getEnrolledStudents())); + enrolledCountLabel.setText("Total: " + course.getEnrolledStudentsCount() + " students"); + + studentListPanel.setVisible(true); + studentListPanel.setManaged(true); + } + + private void updateResultCount() { + int total = filteredCourses.size(); + int overall = dataManager.getTotalCourses(); + + if (total == overall) { + resultCountLabel.setText("Total: " + total + " courses"); + } else { + resultCountLabel.setText("Showing: " + total + " of " + overall + " courses"); + } + } +} diff --git a/src/main/java/org/example/se302/controller/ImportController.java b/src/main/java/org/example/se302/controller/ImportController.java new file mode 100644 index 0000000..1f859c6 --- /dev/null +++ b/src/main/java/org/example/se302/controller/ImportController.java @@ -0,0 +1,229 @@ +package org.example.se302.controller; + +import javafx.fxml.FXML; +import javafx.scene.control.Button; +import javafx.scene.control.Label; +import javafx.scene.control.TextArea; +import javafx.scene.control.TextField; +import javafx.stage.FileChooser; +import org.example.se302.model.ImportResult; +import org.example.se302.service.DataImportService; +import org.example.se302.service.DataManager; + +import java.io.File; + +/** + * Controller for the Import Data view. + */ +public class ImportController { + + @FXML private TextField studentFileField; + @FXML private TextField courseFileField; + @FXML private TextField classroomFileField; + @FXML private TextField enrollmentFileField; + + @FXML private Label studentStatusLabel; + @FXML private Label courseStatusLabel; + @FXML private Label classroomStatusLabel; + @FXML private Label enrollmentStatusLabel; + + @FXML private TextArea messagesArea; + @FXML private Button importAllButton; + + private File studentFile; + private File courseFile; + private File classroomFile; + private File enrollmentFile; + + private DataImportService importService; + private DataManager dataManager; + + @FXML + public void initialize() { + importService = new DataImportService(); + dataManager = DataManager.getInstance(); + } + + @FXML + private void onBrowseStudents() { + FileChooser fileChooser = createFileChooser("Select Student Data CSV"); + studentFile = fileChooser.showOpenDialog(studentFileField.getScene().getWindow()); + + if (studentFile != null) { + studentFileField.setText(studentFile.getAbsolutePath()); + studentStatusLabel.setText("File selected - Ready to import"); + studentStatusLabel.setStyle("-fx-text-fill: blue;"); + checkAllFilesSelected(); + } + } + + @FXML + private void onBrowseCourses() { + FileChooser fileChooser = createFileChooser("Select Course Data CSV"); + courseFile = fileChooser.showOpenDialog(courseFileField.getScene().getWindow()); + + if (courseFile != null) { + courseFileField.setText(courseFile.getAbsolutePath()); + courseStatusLabel.setText("File selected - Ready to import"); + courseStatusLabel.setStyle("-fx-text-fill: blue;"); + checkAllFilesSelected(); + } + } + + @FXML + private void onBrowseClassrooms() { + FileChooser fileChooser = createFileChooser("Select Classroom Data CSV"); + classroomFile = fileChooser.showOpenDialog(classroomFileField.getScene().getWindow()); + + if (classroomFile != null) { + classroomFileField.setText(classroomFile.getAbsolutePath()); + classroomStatusLabel.setText("File selected - Ready to import"); + classroomStatusLabel.setStyle("-fx-text-fill: blue;"); + checkAllFilesSelected(); + } + } + + @FXML + private void onBrowseEnrollments() { + FileChooser fileChooser = createFileChooser("Select Enrollment Data CSV"); + enrollmentFile = fileChooser.showOpenDialog(enrollmentFileField.getScene().getWindow()); + + if (enrollmentFile != null) { + enrollmentFileField.setText(enrollmentFile.getAbsolutePath()); + enrollmentStatusLabel.setText("File selected - Ready to import"); + enrollmentStatusLabel.setStyle("-fx-text-fill: blue;"); + checkAllFilesSelected(); + } + } + + @FXML + private void onImportAll() { + messagesArea.clear(); + messagesArea.appendText("Starting import process...\n\n"); + + // Clear existing data + dataManager.clearAll(); + + // Import students + messagesArea.appendText("Importing students...\n"); + ImportResult studentResult = importService.importStudents(studentFile); + updateStatus(studentStatusLabel, studentResult); + messagesArea.appendText(studentResult.getFormattedMessage() + "\n"); + + // Import courses + messagesArea.appendText("Importing courses...\n"); + ImportResult courseResult = importService.importCourses(courseFile); + updateStatus(courseStatusLabel, courseResult); + messagesArea.appendText(courseResult.getFormattedMessage() + "\n"); + + // Import classrooms + messagesArea.appendText("Importing classrooms...\n"); + ImportResult classroomResult = importService.importClassrooms(classroomFile); + updateStatus(classroomStatusLabel, classroomResult); + messagesArea.appendText(classroomResult.getFormattedMessage() + "\n"); + + // Import enrollments (must be after students and courses) + messagesArea.appendText("Importing enrollments...\n"); + ImportResult enrollmentResult = importService.importEnrollments(enrollmentFile); + updateStatus(enrollmentStatusLabel, enrollmentResult); + messagesArea.appendText(enrollmentResult.getFormattedMessage() + "\n"); + + // Check if all imports were successful + if (studentResult.isSuccess() && courseResult.isSuccess() && + classroomResult.isSuccess() && enrollmentResult.isSuccess()) { + + messagesArea.appendText("\n========================================\n"); + messagesArea.appendText("IMPORT SUCCESSFUL!\n"); + messagesArea.appendText("========================================\n"); + messagesArea.appendText(String.format("- Loaded %d students\n", dataManager.getTotalStudents())); + messagesArea.appendText(String.format("- Loaded %d courses\n", dataManager.getTotalCourses())); + messagesArea.appendText(String.format("- Loaded %d classrooms\n", dataManager.getTotalClassrooms())); + messagesArea.appendText("- Data is ready for viewing and scheduling\n"); + messagesArea.appendText("\nYou can now navigate to other tabs to view the data.\n"); + + // Enable other tabs + enableDataTabs(); + } else { + messagesArea.appendText("\n========================================\n"); + messagesArea.appendText("IMPORT COMPLETED WITH ERRORS\n"); + messagesArea.appendText("========================================\n"); + messagesArea.appendText("Please check the error messages above and fix the CSV files.\n"); + } + } + + @FXML + private void onClearAll() { + studentFile = null; + courseFile = null; + classroomFile = null; + enrollmentFile = null; + + studentFileField.clear(); + courseFileField.clear(); + classroomFileField.clear(); + enrollmentFileField.clear(); + + studentStatusLabel.setText("Not Loaded"); + courseStatusLabel.setText("Not Loaded"); + classroomStatusLabel.setText("Not Loaded"); + enrollmentStatusLabel.setText("Not Loaded"); + + studentStatusLabel.setStyle(""); + courseStatusLabel.setStyle(""); + classroomStatusLabel.setStyle(""); + enrollmentStatusLabel.setStyle(""); + + messagesArea.clear(); + importAllButton.setDisable(true); + + dataManager.clearAll(); + } + + private FileChooser createFileChooser(String title) { + FileChooser fileChooser = new FileChooser(); + fileChooser.setTitle(title); + fileChooser.getExtensionFilters().add( + new FileChooser.ExtensionFilter("CSV Files", "*.csv") + ); + + // Set initial directory to sampleData if it exists + File sampleDataDir = new File("sampleData"); + if (sampleDataDir.exists() && sampleDataDir.isDirectory()) { + fileChooser.setInitialDirectory(sampleDataDir); + } + + return fileChooser; + } + + private void checkAllFilesSelected() { + boolean allSelected = studentFile != null && courseFile != null && + classroomFile != null && enrollmentFile != null; + importAllButton.setDisable(!allSelected); + } + + private void updateStatus(Label statusLabel, ImportResult result) { + if (result.isSuccess()) { + statusLabel.setText("Loaded: " + result.getRecordCount() + " records"); + statusLabel.setStyle("-fx-text-fill: green;"); + } else { + statusLabel.setText("Error - See messages below"); + statusLabel.setStyle("-fx-text-fill: red;"); + } + } + + private void enableDataTabs() { + // The tabs will be enabled via the MainController + // For now, we'll use a simple approach by navigating up the scene graph + try { + // Get the root BorderPane from main-view.fxml + javafx.scene.Parent root = studentFileField.getScene().getRoot(); + + // Since we can't easily access the MainController from here, + // we'll just let the user know they can navigate to other tabs + // The tabs are already set to enabled in the success message + messagesArea.appendText("Note: You can now switch to the Students, Courses, Classrooms, or Schedule Views tabs.\n"); + } catch (Exception e) { + // Ignore if we can't access the scene graph + } + } +} diff --git a/src/main/java/org/example/se302/controller/MainController.java b/src/main/java/org/example/se302/controller/MainController.java new file mode 100644 index 0000000..ea05026 --- /dev/null +++ b/src/main/java/org/example/se302/controller/MainController.java @@ -0,0 +1,85 @@ +package org.example.se302.controller; + +import javafx.fxml.FXML; +import javafx.scene.control.Label; +import javafx.scene.control.Tab; +import javafx.scene.control.TabPane; +import org.example.se302.service.DataManager; + +/** + * Main controller for the application window. + * Manages the TabPane and status bar. + */ +public class MainController { + + @FXML + private TabPane mainTabPane; + + @FXML + private Tab importTab; + + @FXML + private Tab studentsTab; + + @FXML + private Tab coursesTab; + + @FXML + private Tab classroomsTab; + + @FXML + private Tab scheduleTab; + + @FXML + private Label statusLabel; + + private DataManager dataManager; + + @FXML + public void initialize() { + dataManager = DataManager.getInstance(); + + // Initially disable data tabs until import is complete + studentsTab.setDisable(true); + coursesTab.setDisable(true); + classroomsTab.setDisable(true); + scheduleTab.setDisable(true); + + updateStatusBar(); + + // Listen for data changes to automatically enable tabs + dataManager.getStudents().addListener( + (javafx.collections.ListChangeListener) c -> { + if (dataManager.hasData()) { + enableDataTabs(); + } + }); + } + + /** + * Enable data tabs after successful import. + */ + public void enableDataTabs() { + studentsTab.setDisable(false); + coursesTab.setDisable(false); + classroomsTab.setDisable(false); + scheduleTab.setDisable(false); + updateStatusBar(); + } + + /** + * Update the status bar with current data counts. + */ + public void updateStatusBar() { + if (!dataManager.hasData()) { + statusLabel.setText("Ready - No data loaded"); + } else { + statusLabel.setText(String.format( + "Loaded: %d Students, %d Courses, %d Classrooms", + dataManager.getTotalStudents(), + dataManager.getTotalCourses(), + dataManager.getTotalClassrooms() + )); + } + } +} diff --git a/src/main/java/org/example/se302/controller/ScheduleCalendarController.java b/src/main/java/org/example/se302/controller/ScheduleCalendarController.java new file mode 100644 index 0000000..f625cf4 --- /dev/null +++ b/src/main/java/org/example/se302/controller/ScheduleCalendarController.java @@ -0,0 +1,33 @@ +package org.example.se302.controller; + +import javafx.fxml.FXML; +import javafx.scene.control.Alert; +import javafx.scene.control.DatePicker; + +/** + * Controller for the Calendar/Day schedule view. + */ +public class ScheduleCalendarController { + + @FXML private DatePicker startDatePicker; + @FXML private DatePicker endDatePicker; + + @FXML + public void initialize() { + // Initialize with default date range if needed + } + + @FXML + private void onGenerateSchedule() { + Alert alert = new Alert(Alert.AlertType.INFORMATION); + alert.setTitle("Coming Soon"); + alert.setHeaderText("Schedule Generation"); + alert.setContentText("The CSP-based schedule generation algorithm will be implemented in Phase 2.\n\n" + + "It will automatically create an exam schedule that satisfies all constraints:\n" + + "- No consecutive exams for students\n" + + "- Max 2 exams per day per student\n" + + "- Classroom capacity limits\n" + + "- No double-booking"); + alert.showAndWait(); + } +} diff --git a/src/main/java/org/example/se302/controller/ScheduleClassroomController.java b/src/main/java/org/example/se302/controller/ScheduleClassroomController.java new file mode 100644 index 0000000..0ceee1e --- /dev/null +++ b/src/main/java/org/example/se302/controller/ScheduleClassroomController.java @@ -0,0 +1,89 @@ +package org.example.se302.controller; + +import javafx.beans.property.SimpleIntegerProperty; +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 org.example.se302.model.Classroom; +import org.example.se302.service.DataManager; + +/** + * Controller for the Classroom Schedule view. + */ +public class ScheduleClassroomController { + + @FXML private ComboBox classroomComboBox; + @FXML private Label selectedClassroomLabel; + @FXML private TableView scheduleTable; + @FXML private TableColumn dateColumn; + @FXML private TableColumn timeColumn; + @FXML private TableColumn courseColumn; + @FXML private TableColumn studentsColumn; + @FXML private TableColumn utilizationColumn; + @FXML private Label utilizationLabel; + + private DataManager dataManager; + + @FXML + public void initialize() { + dataManager = DataManager.getInstance(); + + // Populate classroom combo box + classroomComboBox.setItems(dataManager.getClassrooms()); + + // Set up table columns + dateColumn.setCellValueFactory(cellData -> + new SimpleStringProperty(cellData.getValue().getDate())); + timeColumn.setCellValueFactory(cellData -> + new SimpleStringProperty(cellData.getValue().getTime())); + courseColumn.setCellValueFactory(cellData -> + new SimpleStringProperty(cellData.getValue().getCourse())); + studentsColumn.setCellValueFactory(cellData -> + new SimpleIntegerProperty(cellData.getValue().getStudentCount())); + utilizationColumn.setCellValueFactory(cellData -> + new SimpleStringProperty(cellData.getValue().getUtilization())); + } + + @FXML + private void onShowSchedule() { + Classroom selected = classroomComboBox.getValue(); + if (selected == null) return; + + selectedClassroomLabel.setText("Schedule for: " + selected.getClassroomId() + + " (Capacity: " + selected.getCapacity() + ")"); + + // For demo: show empty schedule + ObservableList entries = FXCollections.observableArrayList(); + scheduleTable.setItems(entries); + + utilizationLabel.setText("Overall Utilization: 0% (No exams scheduled)"); + } + + // Helper class for table entries + public static class ClassroomSlotEntry { + private final String date; + private final String time; + private final String course; + private final int studentCount; + private final String utilization; + + public ClassroomSlotEntry(String date, String time, String course, int studentCount, String utilization) { + this.date = date; + this.time = time; + this.course = course; + this.studentCount = studentCount; + this.utilization = utilization; + } + + public String getDate() { return date; } + public String getTime() { return time; } + public String getCourse() { return course; } + public int getStudentCount() { return studentCount; } + public String getUtilization() { return utilization; } + } +} diff --git a/src/main/java/org/example/se302/controller/ScheduleCourseController.java b/src/main/java/org/example/se302/controller/ScheduleCourseController.java new file mode 100644 index 0000000..8d7bcc6 --- /dev/null +++ b/src/main/java/org/example/se302/controller/ScheduleCourseController.java @@ -0,0 +1,89 @@ +package org.example.se302.controller; + +import javafx.beans.property.SimpleIntegerProperty; +import javafx.beans.property.SimpleStringProperty; +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; +import javafx.fxml.FXML; +import javafx.scene.control.TableColumn; +import javafx.scene.control.TableView; +import org.example.se302.model.Course; +import org.example.se302.service.DataManager; + +/** + * Controller for the Course Schedule view. + */ +public class ScheduleCourseController { + + @FXML private TableView courseScheduleTable; + @FXML private TableColumn courseCodeColumn; + @FXML private TableColumn enrolledColumn; + @FXML private TableColumn dateColumn; + @FXML private TableColumn timeColumn; + @FXML private TableColumn classroomColumn; + + private DataManager dataManager; + + @FXML + public void initialize() { + dataManager = DataManager.getInstance(); + + // Set up table columns + courseCodeColumn.setCellValueFactory(cellData -> + new SimpleStringProperty(cellData.getValue().getCourseCode())); + enrolledColumn.setCellValueFactory(cellData -> + new SimpleIntegerProperty(cellData.getValue().getEnrolledCount())); + dateColumn.setCellValueFactory(cellData -> + new SimpleStringProperty(cellData.getValue().getDate())); + timeColumn.setCellValueFactory(cellData -> + new SimpleStringProperty(cellData.getValue().getTime())); + classroomColumn.setCellValueFactory(cellData -> + new SimpleStringProperty(cellData.getValue().getClassroom())); + + // Load data + loadScheduleData(); + + // Listen for data changes + dataManager.getCourses().addListener( + (javafx.collections.ListChangeListener) c -> loadScheduleData()); + } + + private void loadScheduleData() { + ObservableList entries = FXCollections.observableArrayList(); + + for (Course course : dataManager.getCourses()) { + entries.add(new CourseScheduleEntry( + course.getCourseCode(), + course.getEnrolledStudentsCount(), + "Not Scheduled", + "-", + "-" + )); + } + + courseScheduleTable.setItems(entries); + } + + // Helper class for table entries + public static class CourseScheduleEntry { + private final String courseCode; + private final int enrolledCount; + private final String date; + private final String time; + private final String classroom; + + public CourseScheduleEntry(String courseCode, int enrolledCount, String date, String time, String classroom) { + this.courseCode = courseCode; + this.enrolledCount = enrolledCount; + this.date = date; + this.time = time; + this.classroom = classroom; + } + + public String getCourseCode() { return courseCode; } + public int getEnrolledCount() { return enrolledCount; } + public String getDate() { return date; } + public String getTime() { return time; } + public String getClassroom() { return classroom; } + } +} diff --git a/src/main/java/org/example/se302/controller/ScheduleStudentController.java b/src/main/java/org/example/se302/controller/ScheduleStudentController.java new file mode 100644 index 0000000..afecefa --- /dev/null +++ b/src/main/java/org/example/se302/controller/ScheduleStudentController.java @@ -0,0 +1,82 @@ +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 org.example.se302.model.Student; +import org.example.se302.service.DataManager; + +/** + * Controller for the Student Schedule view. + */ +public class ScheduleStudentController { + + @FXML private ComboBox studentComboBox; + @FXML private Label selectedStudentLabel; + @FXML private TableView scheduleTable; + @FXML private TableColumn courseColumn; + @FXML private TableColumn dateColumn; + @FXML private TableColumn timeColumn; + @FXML private TableColumn classroomColumn; + + private DataManager dataManager; + + @FXML + public void initialize() { + dataManager = DataManager.getInstance(); + + // Populate student combo box + studentComboBox.setItems(dataManager.getStudents()); + + // 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())); + } + + @FXML + private void onShowSchedule() { + Student selected = studentComboBox.getValue(); + if (selected == null) return; + + selectedStudentLabel.setText("Exam Schedule for: " + selected.getStudentId()); + + // For demo: show enrolled courses with "Not Scheduled" status + ObservableList entries = FXCollections.observableArrayList(); + for (String courseCode : selected.getEnrolledCourses()) { + entries.add(new CourseScheduleEntry(courseCode, "Not Scheduled", "-", "-")); + } + + scheduleTable.setItems(entries); + } + + // Helper class for table entries + public static class CourseScheduleEntry { + private final String courseCode; + private final String date; + private final String time; + private final String classroom; + + public CourseScheduleEntry(String courseCode, String date, String time, String classroom) { + this.courseCode = courseCode; + this.date = date; + this.time = time; + this.classroom = classroom; + } + + public String getCourseCode() { return courseCode; } + public String getDate() { return date; } + public String getTime() { return time; } + public String getClassroom() { return classroom; } + } +} diff --git a/src/main/java/org/example/se302/controller/StudentsController.java b/src/main/java/org/example/se302/controller/StudentsController.java new file mode 100644 index 0000000..32889b6 --- /dev/null +++ b/src/main/java/org/example/se302/controller/StudentsController.java @@ -0,0 +1,119 @@ +package org.example.se302.controller; + +import javafx.beans.property.SimpleIntegerProperty; +import javafx.beans.property.SimpleStringProperty; +import javafx.collections.FXCollections; +import javafx.collections.transformation.FilteredList; +import javafx.fxml.FXML; +import javafx.scene.control.*; +import javafx.scene.layout.VBox; +import org.example.se302.model.Student; +import org.example.se302.service.DataManager; + +/** + * Controller for the Students view. + */ +public class StudentsController { + + @FXML private TextField searchField; + @FXML private Label resultCountLabel; + @FXML private TableView studentsTable; + @FXML private TableColumn studentIdColumn; + @FXML private TableColumn courseCountColumn; + @FXML private TableColumn actionColumn; + + @FXML private VBox detailPanel; + @FXML private Label detailStudentIdLabel; + @FXML private ListView coursesList; + @FXML private Label detailCourseCountLabel; + + private DataManager dataManager; + private FilteredList filteredStudents; + + @FXML + public void initialize() { + dataManager = DataManager.getInstance(); + + // Set up table columns + studentIdColumn.setCellValueFactory(cellData -> + new SimpleStringProperty(cellData.getValue().getStudentId())); + + courseCountColumn.setCellValueFactory(cellData -> + new SimpleIntegerProperty(cellData.getValue().getEnrolledCoursesCount())); + + // Add "View Details" button to action column + actionColumn.setCellFactory(col -> new TableCell<>() { + private final Button viewButton = new Button("View Details"); + + { + viewButton.setOnAction(event -> { + Student student = getTableView().getItems().get(getIndex()); + showStudentDetails(student); + }); + } + + @Override + protected void updateItem(Void item, boolean empty) { + super.updateItem(item, empty); + setGraphic(empty ? null : viewButton); + } + }); + + // Set up filtered list + filteredStudents = new FilteredList<>(dataManager.getStudents(), p -> true); + studentsTable.setItems(filteredStudents); + + // Set up search functionality + searchField.textProperty().addListener((observable, oldValue, newValue) -> { + filterStudents(newValue); + }); + + // Update result count + updateResultCount(); + filteredStudents.addListener((javafx.collections.ListChangeListener) c -> updateResultCount()); + } + + @FXML + private void onClearSearch() { + searchField.clear(); + } + + @FXML + private void onCloseDetail() { + detailPanel.setVisible(false); + detailPanel.setManaged(false); + } + + private void filterStudents(String searchText) { + if (searchText == null || searchText.trim().isEmpty()) { + filteredStudents.setPredicate(student -> true); + } else { + String lowerCaseFilter = searchText.toLowerCase().trim(); + filteredStudents.setPredicate(student -> + student.getStudentId().toLowerCase().contains(lowerCaseFilter) + ); + } + } + + private void showStudentDetails(Student student) { + if (student == null) return; + + detailStudentIdLabel.setText("Student ID: " + student.getStudentId()); + coursesList.setItems(FXCollections.observableArrayList(student.getEnrolledCourses())); + detailCourseCountLabel.setText("Total: " + student.getEnrolledCoursesCount() + " courses"); + + detailPanel.setVisible(true); + detailPanel.setManaged(true); + } + + private void updateResultCount() { + int total = filteredStudents.size(); + int overall = dataManager.getTotalStudents(); + + if (total == overall) { + resultCountLabel.setText("Total: " + total + " students"); + } else { + resultCountLabel.setText("Showing: " + total + " of " + overall + " students"); + } + } +} diff --git a/src/main/java/org/example/se302/model/Classroom.java b/src/main/java/org/example/se302/model/Classroom.java new file mode 100644 index 0000000..201132b --- /dev/null +++ b/src/main/java/org/example/se302/model/Classroom.java @@ -0,0 +1,35 @@ +package org.example.se302.model; + +/** + * Represents a classroom in the exam scheduling system. + */ +public class Classroom { + private String classroomId; + private int capacity; + + public Classroom(String classroomId, int capacity) { + this.classroomId = classroomId; + this.capacity = capacity; + } + + public String getClassroomId() { + return classroomId; + } + + public void setClassroomId(String classroomId) { + this.classroomId = classroomId; + } + + public int getCapacity() { + return capacity; + } + + public void setCapacity(int capacity) { + this.capacity = capacity; + } + + @Override + public String toString() { + return classroomId + " (Capacity: " + capacity + ")"; + } +} diff --git a/src/main/java/org/example/se302/model/Course.java b/src/main/java/org/example/se302/model/Course.java new file mode 100644 index 0000000..db26619 --- /dev/null +++ b/src/main/java/org/example/se302/model/Course.java @@ -0,0 +1,68 @@ +package org.example.se302.model; + +import java.util.ArrayList; +import java.util.List; + +/** + * Represents a course in the exam scheduling system. + */ +public class Course { + private String courseCode; + private List enrolledStudents; + private String assignedClassroom; // null if not scheduled + private String examDateTime; // null if not scheduled + + public Course(String courseCode) { + this.courseCode = courseCode; + this.enrolledStudents = new ArrayList<>(); + this.assignedClassroom = null; + this.examDateTime = null; + } + + public String getCourseCode() { + return courseCode; + } + + public void setCourseCode(String courseCode) { + this.courseCode = courseCode; + } + + public List getEnrolledStudents() { + return enrolledStudents; + } + + public void setEnrolledStudents(List enrolledStudents) { + this.enrolledStudents = enrolledStudents; + } + + public void addStudent(String studentId) { + if (!enrolledStudents.contains(studentId)) { + enrolledStudents.add(studentId); + } + } + + public int getEnrolledStudentsCount() { + return enrolledStudents.size(); + } + + 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; + } + + @Override + public String toString() { + return courseCode; + } +} diff --git a/src/main/java/org/example/se302/model/ImportResult.java b/src/main/java/org/example/se302/model/ImportResult.java new file mode 100644 index 0000000..d7eb908 --- /dev/null +++ b/src/main/java/org/example/se302/model/ImportResult.java @@ -0,0 +1,79 @@ +package org.example.se302.model; + +import java.util.ArrayList; +import java.util.List; + +/** + * Represents the result of a CSV import operation. + */ +public class ImportResult { + private boolean success; + private int recordCount; + private List errors; + private String message; + + public ImportResult() { + this.success = false; + this.recordCount = 0; + this.errors = new ArrayList<>(); + this.message = ""; + } + + public ImportResult(boolean success, int recordCount, String message) { + this.success = success; + this.recordCount = recordCount; + this.message = message; + this.errors = new ArrayList<>(); + } + + public boolean isSuccess() { + return success; + } + + public void setSuccess(boolean success) { + this.success = success; + } + + public int getRecordCount() { + return recordCount; + } + + public void setRecordCount(int recordCount) { + this.recordCount = recordCount; + } + + public List getErrors() { + return errors; + } + + public void addError(String error) { + this.errors.add(error); + this.success = false; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public boolean hasErrors() { + return !errors.isEmpty(); + } + + public String getFormattedMessage() { + StringBuilder sb = new StringBuilder(); + sb.append(message).append("\n"); + + if (hasErrors()) { + sb.append("\nErrors:\n"); + for (String error : errors) { + sb.append(" - ").append(error).append("\n"); + } + } + + return sb.toString(); + } +} diff --git a/src/main/java/org/example/se302/model/Student.java b/src/main/java/org/example/se302/model/Student.java new file mode 100644 index 0000000..ab5778f --- /dev/null +++ b/src/main/java/org/example/se302/model/Student.java @@ -0,0 +1,48 @@ +package org.example.se302.model; + +import java.util.ArrayList; +import java.util.List; + +/** + * Represents a student in the exam scheduling system. + */ +public class Student { + private String studentId; + private List enrolledCourses; + + public Student(String studentId) { + this.studentId = studentId; + this.enrolledCourses = new ArrayList<>(); + } + + public String getStudentId() { + return studentId; + } + + public void setStudentId(String studentId) { + this.studentId = studentId; + } + + public List getEnrolledCourses() { + return enrolledCourses; + } + + public void setEnrolledCourses(List enrolledCourses) { + this.enrolledCourses = enrolledCourses; + } + + public void addCourse(String courseCode) { + if (!enrolledCourses.contains(courseCode)) { + enrolledCourses.add(courseCode); + } + } + + public int getEnrolledCoursesCount() { + return enrolledCourses.size(); + } + + @Override + public String toString() { + return studentId; + } +} diff --git a/src/main/java/org/example/se302/service/DataImportService.java b/src/main/java/org/example/se302/service/DataImportService.java new file mode 100644 index 0000000..6a87643 --- /dev/null +++ b/src/main/java/org/example/se302/service/DataImportService.java @@ -0,0 +1,314 @@ +package org.example.se302.service; + +import org.example.se302.model.Classroom; +import org.example.se302.model.Course; +import org.example.se302.model.ImportResult; +import org.example.se302.model.Student; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileReader; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.regex.Pattern; + +/** + * Service class for importing data from CSV files. + */ +public class DataImportService { + private static final Pattern STUDENT_ID_PATTERN = Pattern.compile("Std_ID_\\d{3}"); + private static final Pattern COURSE_CODE_PATTERN = Pattern.compile("CourseCode_\\d{2}"); + private static final Pattern CLASSROOM_ID_PATTERN = Pattern.compile("Classroom_\\d{2}"); + + private final DataManager dataManager; + + public DataImportService() { + this.dataManager = DataManager.getInstance(); + } + + /** + * Import students from CSV file. + * Format: One student ID per line (after header) + */ + public ImportResult importStudents(File file) { + ImportResult result = new ImportResult(); + + try (BufferedReader reader = new BufferedReader(new FileReader(file))) { + List lines = new ArrayList<>(); + String line; + while ((line = reader.readLine()) != null) { + lines.add(line); + } + + if (lines.isEmpty()) { + result.addError("File is empty"); + return result; + } + + // Skip header line + int count = 0; + for (int i = 1; i < lines.size(); i++) { + String studentId = lines.get(i).trim(); + + if (studentId.isEmpty()) { + continue; // Skip empty lines + } + + if (!STUDENT_ID_PATTERN.matcher(studentId).matches()) { + result.addError("Line " + (i + 1) + ": Invalid student ID format: " + studentId); + continue; + } + + Student student = new Student(studentId); + dataManager.addStudent(student); + count++; + } + + result.setRecordCount(count); + if (!result.hasErrors()) { + result.setSuccess(true); + result.setMessage("Successfully imported " + count + " students"); + } else { + result.setMessage("Imported " + count + " students with errors"); + } + + } catch (IOException e) { + result.addError("Failed to read file: " + e.getMessage()); + } + + return result; + } + + /** + * Import courses from CSV file. + * Format: One course code per line (after header) + */ + public ImportResult importCourses(File file) { + ImportResult result = new ImportResult(); + + try (BufferedReader reader = new BufferedReader(new FileReader(file))) { + List lines = new ArrayList<>(); + String line; + while ((line = reader.readLine()) != null) { + lines.add(line); + } + + if (lines.isEmpty()) { + result.addError("File is empty"); + return result; + } + + // Skip header line + int count = 0; + for (int i = 1; i < lines.size(); i++) { + String courseCode = lines.get(i).trim(); + + if (courseCode.isEmpty()) { + continue; // Skip empty lines + } + + if (!COURSE_CODE_PATTERN.matcher(courseCode).matches()) { + result.addError("Line " + (i + 1) + ": Invalid course code format: " + courseCode); + continue; + } + + Course course = new Course(courseCode); + dataManager.addCourse(course); + count++; + } + + result.setRecordCount(count); + if (!result.hasErrors()) { + result.setSuccess(true); + result.setMessage("Successfully imported " + count + " courses"); + } else { + result.setMessage("Imported " + count + " courses with errors"); + } + + } catch (IOException e) { + result.addError("Failed to read file: " + e.getMessage()); + } + + return result; + } + + /** + * Import classrooms from CSV file. + * Format: ClassroomID;Capacity (semicolon-separated) + */ + public ImportResult importClassrooms(File file) { + ImportResult result = new ImportResult(); + + try (BufferedReader reader = new BufferedReader(new FileReader(file))) { + List lines = new ArrayList<>(); + String line; + while ((line = reader.readLine()) != null) { + lines.add(line); + } + + if (lines.isEmpty()) { + result.addError("File is empty"); + return result; + } + + // Skip header line + int count = 0; + for (int i = 1; i < lines.size(); i++) { + String classroomLine = lines.get(i).trim(); + + if (classroomLine.isEmpty()) { + continue; // Skip empty lines + } + + String[] parts = classroomLine.split(";"); + if (parts.length != 2) { + result.addError("Line " + (i + 1) + ": Invalid format. Expected 'ClassroomID;Capacity'"); + continue; + } + + String classroomId = parts[0].trim(); + String capacityStr = parts[1].trim(); + + if (!CLASSROOM_ID_PATTERN.matcher(classroomId).matches()) { + result.addError("Line " + (i + 1) + ": Invalid classroom ID format: " + classroomId); + continue; + } + + try { + int capacity = Integer.parseInt(capacityStr); + if (capacity <= 0) { + result.addError("Line " + (i + 1) + ": Capacity must be positive"); + continue; + } + + Classroom classroom = new Classroom(classroomId, capacity); + dataManager.addClassroom(classroom); + count++; + + } catch (NumberFormatException e) { + result.addError("Line " + (i + 1) + ": Invalid capacity number: " + capacityStr); + } + } + + result.setRecordCount(count); + if (!result.hasErrors()) { + result.setSuccess(true); + result.setMessage("Successfully imported " + count + " classrooms"); + } else { + result.setMessage("Imported " + count + " classrooms with errors"); + } + + } catch (IOException e) { + result.addError("Failed to read file: " + e.getMessage()); + } + + return result; + } + + /** + * Import enrollment data from CSV file. + * Format: Alternating lines - course code, then Python list of student IDs + */ + public ImportResult importEnrollments(File file) { + ImportResult result = new ImportResult(); + + try (BufferedReader reader = new BufferedReader(new FileReader(file))) { + List lines = new ArrayList<>(); + String line; + while ((line = reader.readLine()) != null) { + lines.add(line); + } + + if (lines.isEmpty()) { + result.addError("File is empty"); + return result; + } + + int courseCount = 0; + int enrollmentCount = 0; + + // Skip header, then process in pairs (course code, student list) + for (int i = 1; i < lines.size(); i++) { + String courseCode = lines.get(i).trim(); + + if (courseCode.isEmpty()) { + continue; // Skip empty lines + } + + // Next line should be the student list + if (i + 1 >= lines.size()) { + result.addError("Line " + (i + 1) + ": Missing student list for course " + courseCode); + break; + } + + i++; // Move to student list line + String studentListStr = lines.get(i).trim(); + + // Verify course exists + Course course = dataManager.getCourse(courseCode); + if (course == null) { + result.addError("Line " + (i) + ": Course not found: " + courseCode); + continue; + } + + // Parse Python list format: ['Std_ID_001', 'Std_ID_002', ...] + List studentIds = parseStudentList(studentListStr); + + for (String studentId : studentIds) { + Student student = dataManager.getStudent(studentId); + if (student == null) { + result.addError("Line " + (i + 1) + ": Student not found: " + studentId); + continue; + } + + dataManager.addEnrollment(studentId, courseCode); + enrollmentCount++; + } + + courseCount++; + } + + result.setRecordCount(enrollmentCount); + if (!result.hasErrors()) { + result.setSuccess(true); + result.setMessage("Successfully imported " + enrollmentCount + " enrollments for " + courseCount + " courses"); + } else { + result.setMessage("Imported " + enrollmentCount + " enrollments with errors"); + } + + } catch (IOException e) { + result.addError("Failed to read file: " + e.getMessage()); + } + + return result; + } + + /** + * Parse a Python-style list of student IDs. + * Example: ['Std_ID_001', 'Std_ID_002', 'Std_ID_003'] + */ + private List parseStudentList(String pythonList) { + List studentIds = new ArrayList<>(); + + // Remove brackets and split by comma + if (pythonList.startsWith("[") && pythonList.endsWith("]")) { + String content = pythonList.substring(1, pythonList.length() - 1); + + // Split by comma and clean up each ID + String[] parts = content.split(","); + for (String part : parts) { + String cleanId = part.trim() + .replace("'", "") // Remove single quotes + .replace("\"", "") // Remove double quotes + .trim(); + + if (!cleanId.isEmpty()) { + studentIds.add(cleanId); + } + } + } + + return studentIds; + } +} diff --git a/src/main/java/org/example/se302/service/DataManager.java b/src/main/java/org/example/se302/service/DataManager.java new file mode 100644 index 0000000..5d8d3d5 --- /dev/null +++ b/src/main/java/org/example/se302/service/DataManager.java @@ -0,0 +1,134 @@ +package org.example.se302.service; + +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; +import org.example.se302.model.Classroom; +import org.example.se302.model.Course; +import org.example.se302.model.Student; + +import java.util.HashMap; +import java.util.Map; + +/** + * Singleton class that manages all application data. + * Provides ObservableList collections for UI binding. + */ +public class DataManager { + private static DataManager instance; + + private ObservableList students; + private ObservableList courses; + private ObservableList classrooms; + + private Map studentMap; + private Map courseMap; + private Map classroomMap; + + private DataManager() { + students = FXCollections.observableArrayList(); + courses = FXCollections.observableArrayList(); + classrooms = FXCollections.observableArrayList(); + + studentMap = new HashMap<>(); + courseMap = new HashMap<>(); + classroomMap = new HashMap<>(); + } + + public static DataManager getInstance() { + if (instance == null) { + instance = new DataManager(); + } + return instance; + } + + // Student operations + public ObservableList getStudents() { + return students; + } + + public void addStudent(Student student) { + students.add(student); + studentMap.put(student.getStudentId(), student); + } + + public Student getStudent(String studentId) { + return studentMap.get(studentId); + } + + public void clearStudents() { + students.clear(); + studentMap.clear(); + } + + // Course operations + public ObservableList getCourses() { + return courses; + } + + public void addCourse(Course course) { + courses.add(course); + courseMap.put(course.getCourseCode(), course); + } + + public Course getCourse(String courseCode) { + return courseMap.get(courseCode); + } + + public void clearCourses() { + courses.clear(); + courseMap.clear(); + } + + // Classroom operations + public ObservableList getClassrooms() { + return classrooms; + } + + public void addClassroom(Classroom classroom) { + classrooms.add(classroom); + classroomMap.put(classroom.getClassroomId(), classroom); + } + + public Classroom getClassroom(String classroomId) { + return classroomMap.get(classroomId); + } + + public void clearClassrooms() { + classrooms.clear(); + classroomMap.clear(); + } + + // Utility methods + public void clearAll() { + clearStudents(); + clearCourses(); + clearClassrooms(); + } + + public int getTotalStudents() { + return students.size(); + } + + public int getTotalCourses() { + return courses.size(); + } + + public int getTotalClassrooms() { + return classrooms.size(); + } + + public boolean hasData() { + return !students.isEmpty() || !courses.isEmpty() || !classrooms.isEmpty(); + } + + // Enrollment operations + public void addEnrollment(String studentId, String courseCode) { + Student student = getStudent(studentId); + Course course = getCourse(courseCode); + + if (student != null && course != null) { + student.addCourse(courseCode); + course.addStudent(studentId); + } + } +}