diff --git a/src/main/java/cz/jzitnik/controllers/ClassroomDetailController.java b/src/main/java/cz/jzitnik/controllers/ClassroomDetailController.java new file mode 100644 index 0000000..35a6ac5 --- /dev/null +++ b/src/main/java/cz/jzitnik/controllers/ClassroomDetailController.java @@ -0,0 +1,128 @@ +package cz.jzitnik.controllers; + +import cz.jzitnik.router.Route; +import cz.jzitnik.router.Router; +import cz.jzitnik.query.QueryOptions; +import io.github.tomhula.jecnaapi.data.room.Room; +import javafx.application.Platform; +import javafx.fxml.FXML; +import javafx.scene.control.*; +import javafx.scene.layout.*; + +import java.util.Map; + +@Route(path = "/classroom_detail", fxml = "/classroom_detail.fxml") +public class ClassroomDetailController extends DashboardBaseController { + + @FXML + private ProgressIndicator loadingIndicator; + + @FXML + private ScrollPane scrollPane; + + @FXML + private Label headerNameLabel; + + @FXML + private GridPane detailsGrid; + + @FXML + private GridPane timetableGrid; + + private String roomCode; + + @Override + public void onNavigate(Map props) { + super.onNavigate(props); + + roomCode = (String) props.get("code"); + if (roomCode == null || roomCode.isEmpty()) { + onBack(); + return; + } + + loadRoom(); + } + + private void loadRoom() { + loadingIndicator.setVisible(true); + scrollPane.setVisible(false); + detailsGrid.getChildren().clear(); + + appState.getQueryClient().fetch("room:" + roomCode, () -> { + try { + return appState.getClient().getRoom(roomCode).join(); + } catch (Exception e) { + e.printStackTrace(); + return null; + } + }, QueryOptions.defaultOptions()).thenAccept(result -> { + Platform.runLater(() -> { + loadingIndicator.setVisible(false); + + if (result.isSuccess() && result.getData().isPresent()) { + renderRoom(result.getData().get()); + scrollPane.setVisible(true); + } else { + headerNameLabel.setText("Chyba"); + detailsGrid.add(new Label("Failed to load room details."), 0, 0); + scrollPane.setVisible(true); + } + }); + }); + } + + private void renderRoom(Room room) { + headerNameLabel.setText(room.getName()); + + int row = 0; + row = addDetailRow("Název", room.getName(), row); + row = addDetailRow("Kód", room.getRoomCode(), row); + row = addDetailRow("Patro", room.getFloor(), row); + row = addDetailRow("Domovská učebna pro", room.getHomeroomOf(), row); + + if (room.getManager() != null) { + Hyperlink managerLink = new Hyperlink(room.getManager().getFullName()); + managerLink.getStyleClass().add("teacher-link"); + managerLink.setOnAction(e -> { + Router.getInstance().navigate("/teacher_profile", Map.of("tag", room.getManager().getTag())); + }); + detailsGrid.add(new Label("Správce:"), 0, row); + detailsGrid.add(managerLink, 1, row++); + } + + if (timetableGrid != null) { + if (room.getTimetable() != null) { + cz.jzitnik.util.TimetableRenderer.renderTimetable(timetableGrid, room.getTimetable()); + } else { + timetableGrid.getChildren().clear(); + timetableGrid.getColumnConstraints().clear(); + timetableGrid.getRowConstraints().clear(); + } + } + } + + private int addDetailRow(String labelText, String value, int row) { + if (value == null || value.isBlank()) { + return row; + } + + Label lbl = new Label(labelText + ":"); + lbl.getStyleClass().add("text-muted"); + + Label val = new Label(value); + val.getStyleClass().addAll("text-light", "text-bold"); + val.setWrapText(true); + + detailsGrid.add(lbl, 0, row); + detailsGrid.add(val, 1, row); + + return row + 1; + } + + @FXML + @Override + protected void onBack() { + Router.getInstance().navigate("/classrooms"); + } +} diff --git a/src/main/java/cz/jzitnik/controllers/ClassroomsController.java b/src/main/java/cz/jzitnik/controllers/ClassroomsController.java new file mode 100644 index 0000000..838c0e5 --- /dev/null +++ b/src/main/java/cz/jzitnik/controllers/ClassroomsController.java @@ -0,0 +1,121 @@ +package cz.jzitnik.controllers; + +import cz.jzitnik.router.Route; +import cz.jzitnik.router.Router; +import cz.jzitnik.query.QueryOptions; +import io.github.tomhula.jecnaapi.data.room.RoomReference; +import io.github.tomhula.jecnaapi.data.room.RoomsPage; +import javafx.application.Platform; +import javafx.fxml.FXML; +import javafx.geometry.Pos; +import javafx.scene.control.*; +import javafx.scene.layout.*; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +@Route(path = "/classrooms", fxml = "/classrooms.fxml") +public class ClassroomsController extends DashboardBaseController { + + @FXML + private ProgressIndicator loadingIndicator; + + @FXML + private ScrollPane scrollPane; + + @FXML + private FlowPane classroomsFlow; + + @FXML + private TextField searchField; + + private List allRooms = new ArrayList<>(); + + @Override + public void onNavigate(Map props) { + super.onNavigate(props); + + searchField.textProperty().addListener((obs, oldVal, newVal) -> { + filterRooms(newVal); + }); + + loadRooms(); + } + + private void loadRooms() { + loadingIndicator.setVisible(true); + scrollPane.setVisible(false); + classroomsFlow.getChildren().clear(); + + appState.getQueryClient().fetch("rooms:page", () -> { + try { + return appState.getClient().getRoomsPage().join(); + } catch (Exception e) { + e.printStackTrace(); + return null; + } + }, QueryOptions.defaultOptions()).thenAccept(result -> { + Platform.runLater(() -> { + loadingIndicator.setVisible(false); + + if (result.isSuccess() && result.getData().isPresent()) { + RoomsPage page = result.getData().get(); + allRooms = new ArrayList<>(page.getRoomReferences()); + // Sort by name + allRooms.sort((r1, r2) -> r1.getName().compareToIgnoreCase(r2.getName())); + filterRooms(searchField.getText()); + scrollPane.setVisible(true); + } else { + Label errorLabel = new Label("Failed to load rooms."); + errorLabel.getStyleClass().addAll("text-danger", "title-3"); + classroomsFlow.getChildren().add(errorLabel); + scrollPane.setVisible(true); + } + }); + }); + } + + private void filterRooms(String query) { + classroomsFlow.getChildren().clear(); + + String lowerQuery = query == null ? "" : query.toLowerCase(); + + List filtered = allRooms.stream() + .filter(r -> r.getName().toLowerCase().contains(lowerQuery) || r.getRoomCode().toLowerCase().contains(lowerQuery)) + .toList(); + + if (filtered.isEmpty()) { + Label empty = new Label("Nenalezena žádná učebna."); + empty.getStyleClass().addAll("text-muted", "title-3"); + classroomsFlow.getChildren().add(empty); + return; + } + + for (RoomReference room : filtered) { + VBox card = new VBox(5); + card.setAlignment(Pos.CENTER); + card.getStyleClass().addAll("card", "classroom-card"); + + Label nameLbl = new Label(room.getName()); + nameLbl.getStyleClass().addAll("title-4", "text-light"); + + Label codeLbl = new Label(room.getRoomCode()); + codeLbl.getStyleClass().addAll("text-muted"); + + card.getChildren().addAll(nameLbl, codeLbl); + + card.setOnMouseClicked(e -> { + Router.getInstance().navigate("/classroom_detail", Map.of("code", room.getRoomCode())); + }); + + classroomsFlow.getChildren().add(card); + } + } + + @FXML + protected void onBackToDashboard() { + Router.getInstance().navigate("/dashboard"); + } +} diff --git a/src/main/java/cz/jzitnik/controllers/DashboardBaseController.java b/src/main/java/cz/jzitnik/controllers/DashboardBaseController.java index 211aeb4..01760d2 100644 --- a/src/main/java/cz/jzitnik/controllers/DashboardBaseController.java +++ b/src/main/java/cz/jzitnik/controllers/DashboardBaseController.java @@ -33,7 +33,7 @@ public class DashboardBaseController implements Routable { protected void onNavigateToTeachers() { Router.getInstance().navigate("/teachers"); } @FXML - protected void onNavigateToRooms() { Router.getInstance().navigate("/rooms"); } + protected void onNavigateToClassrooms() { Router.getInstance().navigate("/classrooms"); } @FXML protected void onNavigateToAbsences() { Router.getInstance().navigate("/absences"); } diff --git a/src/main/java/cz/jzitnik/util/TimetableRenderer.java b/src/main/java/cz/jzitnik/util/TimetableRenderer.java index 112069f..1bb16d6 100644 --- a/src/main/java/cz/jzitnik/util/TimetableRenderer.java +++ b/src/main/java/cz/jzitnik/util/TimetableRenderer.java @@ -187,8 +187,14 @@ public class TimetableRenderer { grid.add(teacherLbl, 1, row++); } if (lesson.getClassroom() != null) { + Hyperlink roomLink = new Hyperlink(lesson.getClassroom()); + roomLink.getStyleClass().addAll("teacher-link"); + roomLink.setOnAction(e -> { + dialog.close(); + Router.getInstance().navigate("/classroom_detail", Map.of("code", lesson.getClassroom())); + }); grid.add(new Label("Učebna:"), 0, row); - grid.add(new Label(lesson.getClassroom()), 1, row++); + grid.add(roomLink, 1, row++); } if (lesson.getGroup() != null) { grid.add(new Label("Skupina:"), 0, row); diff --git a/src/main/resources/classroom_detail.fxml b/src/main/resources/classroom_detail.fxml new file mode 100644 index 0000000..34c68f8 --- /dev/null +++ b/src/main/resources/classroom_detail.fxml @@ -0,0 +1,59 @@ + + + + + + + + + + + + + + + + + + +