diff --git a/.idea/FuzzierSettings.xml b/.idea/FuzzierSettings.xml
new file mode 100644
index 0000000..ff970c5
--- /dev/null
+++ b/.idea/FuzzierSettings.xml
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/main/java/cz/jzitnik/controllers/TeacherProfileController.java b/src/main/java/cz/jzitnik/controllers/TeacherProfileController.java
new file mode 100644
index 0000000..7b9cd6c
--- /dev/null
+++ b/src/main/java/cz/jzitnik/controllers/TeacherProfileController.java
@@ -0,0 +1,152 @@
+package cz.jzitnik.controllers;
+
+import cz.jzitnik.router.Route;
+import cz.jzitnik.router.Router;
+import cz.jzitnik.query.QueryOptions;
+import cz.jzitnik.util.ImageFetcher;
+import io.github.tomhula.jecnaapi.data.schoolStaff.Teacher;
+import javafx.application.Platform;
+import javafx.fxml.FXML;
+import javafx.scene.control.*;
+import javafx.scene.image.Image;
+import javafx.scene.image.ImageView;
+import javafx.scene.layout.*;
+
+import java.util.Map;
+
+@Route(path = "/teacher_profile", fxml = "/teacher_profile.fxml")
+public class TeacherProfileController extends DashboardBaseController {
+
+ @FXML
+ private ProgressIndicator loadingIndicator;
+
+ @FXML
+ private ScrollPane scrollPane;
+
+ @FXML
+ private Label headerNameLabel;
+
+ @FXML
+ private Label fullNameLabel;
+
+ @FXML
+ private Label tagLabel;
+
+ @FXML
+ private GridPane detailsGrid;
+
+ @FXML
+ private GridPane timetableGrid;
+
+ @FXML
+ private ImageView avatarView;
+
+ private String teacherTag;
+
+ @Override
+ public void onNavigate(Map props) {
+ super.onNavigate(props);
+
+ teacherTag = (String) props.get("tag");
+ if (teacherTag == null || teacherTag.isEmpty()) {
+ onBack();
+ return;
+ }
+
+ loadTeacher();
+ }
+
+ private void loadTeacher() {
+ loadingIndicator.setVisible(true);
+ scrollPane.setVisible(false);
+ detailsGrid.getChildren().clear();
+
+ appState.getQueryClient().fetch("teacher:" + teacherTag, () -> {
+ try {
+ return appState.getClient().getTeacher(teacherTag).join();
+ } catch (Exception e) {
+ e.printStackTrace();
+ return null;
+ }
+ }, QueryOptions.defaultOptions()).thenAccept(result -> {
+ Platform.runLater(() -> {
+ loadingIndicator.setVisible(false);
+
+ if (result.isSuccess() && result.getData().isPresent()) {
+ renderTeacher(result.getData().get());
+ scrollPane.setVisible(true);
+ } else {
+ headerNameLabel.setText("Chyba");
+ detailsGrid.add(new Label("Failed to load teacher details."), 0, 0);
+ scrollPane.setVisible(true);
+ }
+ });
+ });
+ }
+
+ private void renderTeacher(Teacher teacher) {
+ headerNameLabel.setText(teacher.getFullName());
+ fullNameLabel.setText(teacher.getFullName());
+ tagLabel.setText(teacher.getTag());
+
+ if (teacher.getProfilePicturePath() != null && !teacher.getProfilePicturePath().isEmpty()) {
+ new Thread(() -> {
+ Image image = ImageFetcher.fetchImage(teacher.getProfilePicturePath());
+ Platform.runLater(() -> {
+ avatarView.setImage(image);
+ });
+ }).start();
+ } else {
+ avatarView.setImage(null);
+ }
+
+ int row = 0;
+
+ row = addDetailRow("Kabinet", teacher.getCabinet(), row);
+ row = addDetailRow("Školní email", teacher.getSchoolMail(), row);
+ row = addDetailRow("Soukromý email", teacher.getPrivateMail(), row);
+
+ if (!teacher.getPhoneNumbers().isEmpty()) {
+ row = addDetailRow("Telefon", String.join(", ", teacher.getPhoneNumbers()), row);
+ }
+
+ row = addDetailRow("Pevná linka", teacher.getLandline(), row);
+ row = addDetailRow("Soukromý telefon", teacher.getPrivatePhoneNumber(), row);
+ row = addDetailRow("Třídní", teacher.getTutorOfClass(), row);
+ row = addDetailRow("Konzultační hodiny", teacher.getConsultationHours(), row);
+
+ if (timetableGrid != null) {
+ if (teacher.getTimetable() != null) {
+ cz.jzitnik.util.TimetableRenderer.renderTimetable(timetableGrid, teacher.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("/teachers");
+ }
+}
diff --git a/src/main/java/cz/jzitnik/controllers/TeachersController.java b/src/main/java/cz/jzitnik/controllers/TeachersController.java
new file mode 100644
index 0000000..2775e8a
--- /dev/null
+++ b/src/main/java/cz/jzitnik/controllers/TeachersController.java
@@ -0,0 +1,119 @@
+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.schoolStaff.TeacherReference;
+import io.github.tomhula.jecnaapi.data.schoolStaff.TeachersPage;
+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;
+
+@Route(path = "/teachers", fxml = "/teachers.fxml")
+public class TeachersController extends DashboardBaseController {
+
+ @FXML
+ private ProgressIndicator loadingIndicator;
+
+ @FXML
+ private ScrollPane scrollPane;
+
+ @FXML
+ private FlowPane teachersFlow;
+
+ @FXML
+ private TextField searchField;
+
+ private List allTeachers = new ArrayList<>();
+
+ @Override
+ public void onNavigate(Map props) {
+ super.onNavigate(props);
+
+ searchField.textProperty().addListener((obs, oldVal, newVal) -> {
+ filterTeachers(newVal);
+ });
+
+ loadTeachers();
+ }
+
+ private void loadTeachers() {
+ loadingIndicator.setVisible(true);
+ scrollPane.setVisible(false);
+ teachersFlow.getChildren().clear();
+
+ appState.getQueryClient().fetch("teachers:page", () -> {
+ try {
+ return appState.getClient().getTeachersPage().join();
+ } catch (Exception e) {
+ e.printStackTrace();
+ return null;
+ }
+ }, QueryOptions.defaultOptions()).thenAccept(result -> {
+ Platform.runLater(() -> {
+ loadingIndicator.setVisible(false);
+
+ if (result.isSuccess() && result.getData().isPresent()) {
+ TeachersPage page = result.getData().get();
+ allTeachers = new ArrayList<>(page.getTeachersReferences());
+ allTeachers.sort((t1, t2) -> t1.getFullName().compareToIgnoreCase(t2.getFullName()));
+ filterTeachers(searchField.getText());
+ scrollPane.setVisible(true);
+ } else {
+ Label errorLabel = new Label("Failed to load teachers.");
+ errorLabel.getStyleClass().addAll("text-danger", "title-3");
+ teachersFlow.getChildren().add(errorLabel);
+ scrollPane.setVisible(true);
+ }
+ });
+ });
+ }
+
+ private void filterTeachers(String query) {
+ teachersFlow.getChildren().clear();
+
+ String lowerQuery = query == null ? "" : query.toLowerCase();
+
+ List filtered = allTeachers.stream()
+ .filter(t -> t.getFullName().toLowerCase().contains(lowerQuery) || t.getTag().toLowerCase().contains(lowerQuery))
+ .toList();
+
+ if (filtered.isEmpty()) {
+ Label empty = new Label("Nenalezen žádný učitel.");
+ empty.getStyleClass().addAll("text-muted", "title-3");
+ teachersFlow.getChildren().add(empty);
+ return;
+ }
+
+ for (TeacherReference teacher : filtered) {
+ VBox card = new VBox(5);
+ card.setAlignment(Pos.CENTER_LEFT);
+ card.getStyleClass().addAll("card", "teacher-card");
+
+ Label nameLbl = new Label(teacher.getFullName());
+ nameLbl.getStyleClass().addAll("title-4", "text-light");
+
+ Label tagLbl = new Label(teacher.getTag());
+ tagLbl.getStyleClass().addAll("teacher-tag");
+
+ card.getChildren().addAll(nameLbl, tagLbl);
+
+ card.setOnMouseClicked(e -> {
+ Router.getInstance().navigate("/teacher_profile", Map.of("tag", teacher.getTag()));
+ });
+
+ teachersFlow.getChildren().add(card);
+ }
+ }
+
+ @FXML
+ protected void onBackToDashboard() {
+ Router.getInstance().navigate("/dashboard");
+ }
+}
diff --git a/src/main/java/cz/jzitnik/controllers/TimetableController.java b/src/main/java/cz/jzitnik/controllers/TimetableController.java
index 553562e..ca86ab0 100644
--- a/src/main/java/cz/jzitnik/controllers/TimetableController.java
+++ b/src/main/java/cz/jzitnik/controllers/TimetableController.java
@@ -4,6 +4,7 @@ import cz.jzitnik.router.Route;
import cz.jzitnik.router.Router;
import cz.jzitnik.query.QueryOptions;
import cz.jzitnik.query.QueryResult;
+import cz.jzitnik.util.TimetableRenderer;
import io.github.tomhula.jecnaapi.data.timetable.Lesson;
import io.github.tomhula.jecnaapi.data.timetable.LessonPeriod;
import io.github.tomhula.jecnaapi.data.timetable.LessonSpot;
@@ -80,183 +81,7 @@ public class TimetableController extends DashboardBaseController {
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";
- };
+ TimetableRenderer.renderTimetable(timetableGrid, timetable);
}
@FXML
diff --git a/src/main/java/cz/jzitnik/util/ImageFetcher.java b/src/main/java/cz/jzitnik/util/ImageFetcher.java
new file mode 100644
index 0000000..d1d794b
--- /dev/null
+++ b/src/main/java/cz/jzitnik/util/ImageFetcher.java
@@ -0,0 +1,58 @@
+package cz.jzitnik.util;
+
+import javafx.scene.image.Image;
+
+import java.io.InputStream;
+import java.net.HttpURLConnection;
+import java.net.URL;
+import java.util.zip.GZIPInputStream;
+import java.util.zip.InflaterInputStream;
+
+public class ImageFetcher {
+
+ public static Image fetchImage(String imagePath) {
+ if (imagePath == null || imagePath.isEmpty()) {
+ return null;
+ }
+
+ try {
+ if (imagePath.startsWith("/")) {
+ imagePath = "https://www.spsejecna.cz" + imagePath;
+ }
+
+ URL url = new URL(imagePath);
+ HttpURLConnection conn = (HttpURLConnection) url.openConnection();
+ conn.setRequestMethod("GET");
+ conn.setRequestProperty("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36");
+ conn.setRequestProperty("Accept", "image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8");
+ conn.setRequestProperty("Accept-Language", "cs,en;q=0.9,en-US;q=0.8");
+ conn.setRequestProperty("Accept-Encoding", "gzip, deflate");
+ conn.setRequestProperty("Referer", "https://www.spsejecna.cz/");
+ conn.setRequestProperty("Connection", "keep-alive");
+
+ conn.setConnectTimeout(5000);
+ conn.setReadTimeout(5000);
+
+ int responseCode = conn.getResponseCode();
+ if (responseCode >= 200 && responseCode < 300) {
+ InputStream in = conn.getInputStream();
+ String encoding = conn.getContentEncoding();
+
+ if ("gzip".equalsIgnoreCase(encoding)) {
+ in = new GZIPInputStream(in);
+ } else if ("deflate".equalsIgnoreCase(encoding)) {
+ in = new InflaterInputStream(in);
+ }
+
+ Image img = new Image(in);
+ in.close();
+ return img;
+ } else {
+ System.err.println("Failed to fetch image: HTTP " + responseCode + " for " + imagePath);
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ return null;
+ }
+}
diff --git a/src/main/java/cz/jzitnik/util/TimetableRenderer.java b/src/main/java/cz/jzitnik/util/TimetableRenderer.java
new file mode 100644
index 0000000..112069f
--- /dev/null
+++ b/src/main/java/cz/jzitnik/util/TimetableRenderer.java
@@ -0,0 +1,217 @@
+package cz.jzitnik.util;
+
+import cz.jzitnik.router.Router;
+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 javafx.geometry.Insets;
+import javafx.geometry.Pos;
+import javafx.scene.control.ButtonType;
+import javafx.scene.control.Dialog;
+import javafx.scene.control.Hyperlink;
+import javafx.scene.control.Label;
+import javafx.scene.control.Separator;
+import javafx.scene.layout.ColumnConstraints;
+import javafx.scene.layout.GridPane;
+import javafx.scene.layout.HBox;
+import javafx.scene.layout.VBox;
+import kotlinx.datetime.DayOfWeek;
+
+import java.util.List;
+import java.util.Map;
+
+public class TimetableRenderer {
+
+ public static void renderTimetable(GridPane timetableGrid, Timetable timetable) {
+ timetableGrid.getChildren().clear();
+ timetableGrid.getColumnConstraints().clear();
+ timetableGrid.getRowConstraints().clear();
+ timetableGrid.setAlignment(Pos.CENTER);
+
+ 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 static 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 static 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 static 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) {
+ Hyperlink teacherLbl = new Hyperlink(lesson.getTeacherName().getFull());
+ teacherLbl.getStyleClass().addAll("timetable-teacher", "teacher-link");
+ teacherLbl.setOnAction(e -> {
+ dialog.close();
+ String tag = lesson.getTeacherName().getShort() != null ? lesson.getTeacherName().getShort() : lesson.getTeacherName().getFull();
+ Router.getInstance().navigate("/teacher_profile", Map.of("tag", tag));
+ });
+ grid.add(new Label("Učitel:"), 0, row);
+ grid.add(teacherLbl, 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 static 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";
+ };
+ }
+}
diff --git a/src/main/resources/absences.fxml b/src/main/resources/absences.fxml
index 1d010eb..bf17825 100644
--- a/src/main/resources/absences.fxml
+++ b/src/main/resources/absences.fxml
@@ -31,18 +31,18 @@
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/main/resources/dashboard_modern.fxml b/src/main/resources/dashboard_modern.fxml
index 7a01295..c9c7311 100644
--- a/src/main/resources/dashboard_modern.fxml
+++ b/src/main/resources/dashboard_modern.fxml
@@ -39,7 +39,7 @@
-
+
diff --git a/src/main/resources/grades.fxml b/src/main/resources/grades.fxml
index af1f443..a8d3d95 100644
--- a/src/main/resources/grades.fxml
+++ b/src/main/resources/grades.fxml
@@ -29,10 +29,12 @@
-
-
-
-
+
+
+
+
+
+
diff --git a/src/main/resources/styles/teachers.css b/src/main/resources/styles/teachers.css
new file mode 100644
index 0000000..86eda06
--- /dev/null
+++ b/src/main/resources/styles/teachers.css
@@ -0,0 +1,60 @@
+.search-field {
+ -fx-min-width: 250px;
+ -fx-background-color: rgba(0, 0, 0, 0.2);
+ -fx-border-color: rgba(255, 255, 255, 0.1);
+ -fx-border-radius: 4px;
+ -fx-background-radius: 4px;
+ -fx-text-fill: white;
+}
+
+.search-field:focused {
+ -fx-border-color: #58a6ff;
+}
+
+.teacher-card {
+ -fx-min-width: 220px;
+ -fx-background-color: rgba(30, 40, 56, 0.7);
+ -fx-border-color: rgba(255, 255, 255, 0.05);
+ -fx-border-radius: 8px;
+ -fx-background-radius: 8px;
+ -fx-padding: 15px 20px;
+ -fx-cursor: hand;
+}
+
+.teacher-card:hover {
+ -fx-background-color: rgba(45, 55, 71, 0.9);
+ -fx-border-color: rgba(255, 255, 255, 0.15);
+ -fx-effect: dropshadow(two-pass-box, rgba(0,0,0,0.3), 8, 0.0, 0, 3);
+}
+
+.teacher-tag {
+ -fx-text-fill: #58a6ff;
+ -fx-font-size: 12px;
+ -fx-font-weight: bold;
+ -fx-padding: 2px 6px;
+ -fx-background-color: rgba(88, 166, 255, 0.1);
+ -fx-background-radius: 4px;
+}
+
+.teacher-tag.large {
+ -fx-font-size: 16px;
+ -fx-padding: 4px 10px;
+}
+
+.teacher-profile-header {
+ -fx-background-color: rgba(255, 255, 255, 0.03);
+ -fx-padding: 20px;
+ -fx-background-radius: 12px;
+ -fx-border-color: rgba(255, 255, 255, 0.05);
+ -fx-border-radius: 12px;
+}
+
+.teacher-details-grid {
+ -fx-background-color: rgba(0, 0, 0, 0.15);
+ -fx-padding: 25px;
+ -fx-background-radius: 12px;
+}
+
+.text-bold {
+ -fx-font-weight: bold;
+}
\ No newline at end of file
diff --git a/src/main/resources/styles/timetable.css b/src/main/resources/styles/timetable.css
index 7f1e8fd..911598b 100644
--- a/src/main/resources/styles/timetable.css
+++ b/src/main/resources/styles/timetable.css
@@ -95,3 +95,12 @@
-fx-border-color: rgba(255, 255, 255, 0.1);
-fx-border-radius: 8px;
}
+
+.teacher-link {
+ -fx-border-color: transparent;
+ -fx-padding: 0;
+ -fx-text-fill: #58a6ff;
+}
+.teacher-link:hover {
+ -fx-underline: true;
+}
diff --git a/src/main/resources/teacher_profile.fxml b/src/main/resources/teacher_profile.fxml
new file mode 100644
index 0000000..712df45
--- /dev/null
+++ b/src/main/resources/teacher_profile.fxml
@@ -0,0 +1,75 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/main/resources/teachers.fxml b/src/main/resources/teachers.fxml
new file mode 100644
index 0000000..4206192
--- /dev/null
+++ b/src/main/resources/teachers.fxml
@@ -0,0 +1,45 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file