From 0a80f891365fa6d9910c350d159ccb17441cff55 Mon Sep 17 00:00:00 2001 From: jzitnik-dev Date: Fri, 29 May 2026 19:17:40 +0200 Subject: [PATCH] feat: Added grades --- .../controllers/TimetableController.java | 266 ++++++++++++++++++ src/main/resources/dashboard_modern.fxml | 2 +- src/main/resources/grades.fxml | 4 +- src/main/resources/styles/dashboard.css | 71 ----- src/main/resources/styles/grades.css | 70 +++++ src/main/resources/styles/timetable.css | 97 +++++++ src/main/resources/timetable.fxml | 38 +++ 7 files changed, 474 insertions(+), 74 deletions(-) create mode 100644 src/main/java/cz/jzitnik/controllers/TimetableController.java create mode 100644 src/main/resources/styles/grades.css create mode 100644 src/main/resources/styles/timetable.css create mode 100644 src/main/resources/timetable.fxml diff --git a/src/main/java/cz/jzitnik/controllers/TimetableController.java b/src/main/java/cz/jzitnik/controllers/TimetableController.java new file mode 100644 index 0000000..553562e --- /dev/null +++ b/src/main/java/cz/jzitnik/controllers/TimetableController.java @@ -0,0 +1,266 @@ +package cz.jzitnik.controllers; + +import cz.jzitnik.router.Route; +import cz.jzitnik.router.Router; +import cz.jzitnik.query.QueryOptions; +import cz.jzitnik.query.QueryResult; +import io.github.tomhula.jecnaapi.data.timetable.Lesson; +import io.github.tomhula.jecnaapi.data.timetable.LessonPeriod; +import io.github.tomhula.jecnaapi.data.timetable.LessonSpot; +import io.github.tomhula.jecnaapi.data.timetable.Timetable; +import io.github.tomhula.jecnaapi.data.timetable.TimetablePage; +import javafx.application.Platform; +import javafx.fxml.FXML; +import javafx.geometry.Insets; +import javafx.geometry.Pos; +import javafx.scene.control.*; +import javafx.scene.layout.*; + +import kotlinx.datetime.DayOfWeek; +import java.util.List; +import java.util.Map; + +@Route(path = "/timetable", fxml = "/timetable.fxml") +public class TimetableController extends DashboardBaseController { + + @FXML + private GridPane timetableGrid; + + @FXML + private ProgressIndicator loadingIndicator; + + @FXML + private ScrollPane scrollPane; + + private TimetablePage currentPage; + + @Override + public void onNavigate(Map props) { + super.onNavigate(props); + loadTimetable(); + } + + private void loadTimetable() { + loadingIndicator.setVisible(true); + scrollPane.setVisible(false); + timetableGrid.getChildren().clear(); + + appState.getQueryClient().fetch("timetable:page", () -> { + try { + return appState.getClient().getTimetablePage().join(); + } catch (Exception e) { + e.printStackTrace(); + return null; + } + }, QueryOptions.defaultOptions()).thenAccept(this::handleTimetableResult); + } + + private void handleTimetableResult(QueryResult result) { + Platform.runLater(() -> { + loadingIndicator.setVisible(false); + + if (result.isSuccess() && result.getData().isPresent()) { + scrollPane.setVisible(true); + currentPage = result.getData().get(); + renderTimetable(); + } else { + Label errorLabel = new Label("Failed to load timetable."); + errorLabel.getStyleClass().addAll("text-danger", "title-3"); + timetableGrid.add(errorLabel, 0, 0); + timetableGrid.setAlignment(Pos.CENTER); + scrollPane.setVisible(true); + } + }); + } + + private void renderTimetable() { + timetableGrid.getChildren().clear(); + timetableGrid.getColumnConstraints().clear(); + timetableGrid.getRowConstraints().clear(); + timetableGrid.setAlignment(Pos.CENTER); + + Timetable timetable = currentPage.getTimetable(); + List periods = timetable.getLessonPeriods(); + List days = timetable.getDaysSorted(); + + ColumnConstraints dayCol = new ColumnConstraints(); + dayCol.setMinWidth(120); + timetableGrid.getColumnConstraints().add(dayCol); + + for (int i = 0; i < periods.size(); i++) { + LessonPeriod p = periods.get(i); + VBox header = new VBox(2); + header.setAlignment(Pos.CENTER); + header.getStyleClass().add("timetable-header"); + + Label indexLbl = new Label(String.valueOf(i)); + indexLbl.getStyleClass().add("timetable-header-index"); + + Label timeLbl = new Label(p.toString()); + timeLbl.getStyleClass().add("timetable-header-time"); + + header.getChildren().addAll(indexLbl, timeLbl); + + ColumnConstraints col = new ColumnConstraints(); + col.setMinWidth(110); + timetableGrid.getColumnConstraints().add(col); + + timetableGrid.add(header, i + 1, 0); + } + + int rowIndex = 1; + for (DayOfWeek day : days) { + VBox dayCell = new VBox(); + dayCell.setAlignment(Pos.CENTER_RIGHT); + dayCell.getStyleClass().add("timetable-day-cell"); + + Label dayLabel = new Label(translateDay(day)); + dayLabel.getStyleClass().addAll("timetable-day-label", "title-3"); + dayCell.getChildren().add(dayLabel); + + timetableGrid.add(dayCell, 0, rowIndex); + + List spots = timetable.get(day); + if (spots != null) { + int colIndex = 1; + for (LessonSpot spot : spots) { + int span = spot.getPeriodSpan(); + + if (spot.isNotEmpty()) { + VBox cell = createLessonCell(spot); + timetableGrid.add(cell, colIndex, rowIndex, span, 1); + } else { + VBox emptyCell = new VBox(); + emptyCell.getStyleClass().add("timetable-cell-empty"); + timetableGrid.add(emptyCell, colIndex, rowIndex, span, 1); + } + + colIndex += span; + } + } + + rowIndex++; + } + } + + private VBox createLessonCell(LessonSpot spot) { + VBox cell = new VBox(5); + cell.setAlignment(Pos.CENTER); + cell.getStyleClass().addAll("card", "timetable-cell"); + + if (spot.getSize() == 1) { + Lesson lesson = spot.getLesson(0); + addLessonInfoToCell(cell, lesson); + } else { + VBox groupContainer = new VBox(5); + groupContainer.setAlignment(Pos.CENTER); + + for (int i = 0; i < spot.getSize(); i++) { + Lesson lesson = spot.getLesson(i); + VBox groupCell = new VBox(2); + groupCell.setAlignment(Pos.CENTER); + addLessonInfoToCell(groupCell, lesson); + + if (lesson.getGroup() != null) { + Label groupLbl = new Label(lesson.getGroup()); + groupLbl.getStyleClass().add("timetable-group-label"); + groupCell.getChildren().add(0, groupLbl); + } + + groupContainer.getChildren().add(groupCell); + + if (i < spot.getSize() - 1) { + Separator sep = new Separator(javafx.geometry.Orientation.HORIZONTAL); + groupContainer.getChildren().add(sep); + } + } + cell.getChildren().add(groupContainer); + } + + cell.setOnMouseClicked(e -> showLessonDetails(spot)); + + return cell; + } + + private void addLessonInfoToCell(VBox cell, Lesson lesson) { + Label subjectLbl = new Label(lesson.getSubjectName().getShort() != null ? lesson.getSubjectName().getShort() : lesson.getSubjectName().getFull()); + subjectLbl.getStyleClass().add("timetable-subject"); + + HBox bottomInfo = new HBox(5); + bottomInfo.setAlignment(Pos.CENTER); + + if (lesson.getTeacherName() != null) { + Label teacherLbl = new Label(lesson.getTeacherName().getShort() != null ? lesson.getTeacherName().getShort() : lesson.getTeacherName().getFull()); + teacherLbl.getStyleClass().add("timetable-teacher"); + bottomInfo.getChildren().add(teacherLbl); + } + + if (lesson.getClassroom() != null) { + Label roomLbl = new Label(lesson.getClassroom()); + roomLbl.getStyleClass().add("timetable-room"); + bottomInfo.getChildren().add(roomLbl); + } + + cell.getChildren().addAll(subjectLbl, bottomInfo); + } + + private void showLessonDetails(LessonSpot spot) { + Dialog dialog = new Dialog<>(); + dialog.setTitle("Detail hodiny"); + dialog.setHeaderText(null); + dialog.getDialogPane().getButtonTypes().add(ButtonType.CLOSE); + + VBox content = new VBox(15); + content.setPadding(new Insets(20)); + + for (Lesson lesson : spot) { + VBox lessonInfo = new VBox(5); + lessonInfo.getStyleClass().add("timetable-detail-box"); + + Label title = new Label(lesson.getSubjectName().getFull()); + title.getStyleClass().add("title-3"); + lessonInfo.getChildren().add(title); + + GridPane grid = new GridPane(); + grid.setHgap(10); + grid.setVgap(5); + + int row = 0; + if (lesson.getTeacherName() != null) { + grid.add(new Label("Učitel:"), 0, row); + grid.add(new Label(lesson.getTeacherName().getFull()), 1, row++); + } + if (lesson.getClassroom() != null) { + grid.add(new Label("Učebna:"), 0, row); + grid.add(new Label(lesson.getClassroom()), 1, row++); + } + if (lesson.getGroup() != null) { + grid.add(new Label("Skupina:"), 0, row); + grid.add(new Label(lesson.getGroup()), 1, row++); + } + + lessonInfo.getChildren().add(grid); + content.getChildren().add(lessonInfo); + } + + dialog.getDialogPane().setContent(content); + dialog.show(); + } + + private String translateDay(DayOfWeek day) { + return switch (day) { + case MONDAY -> "Pondělí"; + case TUESDAY -> "Úterý"; + case WEDNESDAY -> "Středa"; + case THURSDAY -> "Čtvrtek"; + case FRIDAY -> "Pátek"; + case SATURDAY -> "Sobota"; + case SUNDAY -> "Neděle"; + }; + } + + @FXML + protected void onBackToDashboard() { + Router.getInstance().navigate("/dashboard"); + } +} diff --git a/src/main/resources/dashboard_modern.fxml b/src/main/resources/dashboard_modern.fxml index e2843c8..47b49ce 100644 --- a/src/main/resources/dashboard_modern.fxml +++ b/src/main/resources/dashboard_modern.fxml @@ -38,7 +38,7 @@