feat: Added teachers
This commit is contained in:
@@ -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<String, Object> 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");
|
||||
}
|
||||
}
|
||||
@@ -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<TeacherReference> allTeachers = new ArrayList<>();
|
||||
|
||||
@Override
|
||||
public void onNavigate(Map<String, Object> 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<TeacherReference> 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");
|
||||
}
|
||||
}
|
||||
@@ -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<LessonPeriod> periods = timetable.getLessonPeriods();
|
||||
List<DayOfWeek> 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<LessonSpot> 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<Void> 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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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<LessonPeriod> periods = timetable.getLessonPeriods();
|
||||
List<DayOfWeek> 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<LessonSpot> 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<Void> 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";
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -31,18 +31,18 @@
|
||||
<ProgressIndicator fx:id="loadingIndicator" maxWidth="50" maxHeight="50" />
|
||||
|
||||
<ScrollPane fx:id="scrollPane" fitToWidth="true" style="-fx-background: transparent; -fx-background-color: transparent;" visible="false">
|
||||
<padding>
|
||||
<Insets top="20" right="30" bottom="30" left="30"/>
|
||||
</padding>
|
||||
<VBox spacing="25" alignment="TOP_CENTER" maxWidth="900" StackPane.alignment="TOP_CENTER">
|
||||
|
||||
<!-- Summary Box -->
|
||||
<HBox fx:id="summaryBox" spacing="20" alignment="CENTER" />
|
||||
|
||||
<!-- Detail Grid -->
|
||||
<FlowPane fx:id="detailFlow" hgap="15" vgap="15" alignment="CENTER" />
|
||||
|
||||
</VBox>
|
||||
<StackPane alignment="TOP_CENTER">
|
||||
<padding>
|
||||
<Insets top="20" right="30" bottom="30" left="30"/>
|
||||
</padding>
|
||||
<VBox spacing="25" alignment="TOP_CENTER" maxWidth="900">
|
||||
|
||||
<HBox fx:id="summaryBox" spacing="20" alignment="CENTER" />
|
||||
|
||||
<FlowPane fx:id="detailFlow" hgap="15" vgap="15" alignment="CENTER" />
|
||||
|
||||
</VBox>
|
||||
</StackPane>
|
||||
</ScrollPane>
|
||||
</StackPane>
|
||||
</center>
|
||||
|
||||
@@ -39,7 +39,7 @@
|
||||
<!-- Row 0 -->
|
||||
<Button text="Známky" GridPane.rowIndex="0" GridPane.columnIndex="0" onAction="#onNavigateToGrades" styleClass="card"/>
|
||||
<Button text="Rozvrh" GridPane.rowIndex="0" GridPane.columnIndex="1" onAction="#onNavigateToTimetable" styleClass="card"/>
|
||||
<Button text="Učitelé" GridPane.rowIndex="0" GridPane.columnIndex="2" onAction="#onDoNothing" styleClass="card"/>
|
||||
<Button text="Učitelé" GridPane.rowIndex="0" GridPane.columnIndex="2" onAction="#onNavigateToTeachers" styleClass="card"/>
|
||||
|
||||
<!-- Row 1 -->
|
||||
<Button text="Učebny" GridPane.rowIndex="1" GridPane.columnIndex="0" onAction="#onDoNothing" styleClass="card"/>
|
||||
|
||||
@@ -29,10 +29,12 @@
|
||||
<ProgressIndicator fx:id="loadingIndicator" maxWidth="50" maxHeight="50" />
|
||||
|
||||
<ScrollPane fx:id="scrollPane" fitToWidth="true" style="-fx-background: transparent; -fx-background-color: transparent;" visible="false">
|
||||
<padding>
|
||||
<Insets top="20" right="30" bottom="30" left="30"/>
|
||||
</padding>
|
||||
<VBox fx:id="contentBox" spacing="15" alignment="TOP_CENTER" maxWidth="900" />
|
||||
<StackPane alignment="TOP_CENTER">
|
||||
<padding>
|
||||
<Insets top="20" right="30" bottom="30" left="30"/>
|
||||
</padding>
|
||||
<VBox fx:id="contentBox" spacing="15" alignment="TOP_CENTER" maxWidth="900" />
|
||||
</StackPane>
|
||||
</ScrollPane>
|
||||
</StackPane>
|
||||
</center>
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<?import javafx.geometry.Insets?>
|
||||
<?import javafx.scene.control.Button?>
|
||||
<?import javafx.scene.control.Label?>
|
||||
<?import javafx.scene.control.ProgressIndicator?>
|
||||
<?import javafx.scene.control.ScrollPane?>
|
||||
<?import javafx.scene.layout.BorderPane?>
|
||||
<?import javafx.scene.layout.GridPane?>
|
||||
<?import javafx.scene.layout.HBox?>
|
||||
<?import javafx.scene.layout.ColumnConstraints?>
|
||||
<?import javafx.scene.layout.StackPane?>
|
||||
<?import javafx.scene.layout.VBox?>
|
||||
<?import javafx.scene.shape.Rectangle?>
|
||||
<?import javafx.scene.image.ImageView?>
|
||||
|
||||
<BorderPane xmlns="http://javafx.com/javafx/21" xmlns:fx="http://javafx.com/fxml/1"
|
||||
fx:controller="cz.jzitnik.controllers.TeacherProfileController"
|
||||
style="-fx-background-color: linear-gradient(#0f1724, #071023);"
|
||||
stylesheets="@styles/teachers.css">
|
||||
|
||||
<top>
|
||||
<HBox spacing="15" alignment="CENTER_LEFT" style="-fx-background-color: rgba(255, 255, 255, 0.05); -fx-padding: 15 25;">
|
||||
<Button text="← Zpět" onAction="#onBack" styleClass="flat, small" />
|
||||
<Label fx:id="headerNameLabel" text="Profil" styleClass="title-2, text-light" />
|
||||
</HBox>
|
||||
</top>
|
||||
|
||||
<center>
|
||||
<StackPane>
|
||||
<ProgressIndicator fx:id="loadingIndicator" maxWidth="50" maxHeight="50" />
|
||||
|
||||
<ScrollPane fx:id="scrollPane" fitToWidth="true" style="-fx-background: transparent; -fx-background-color: transparent;" visible="false">
|
||||
<StackPane alignment="TOP_CENTER">
|
||||
<padding>
|
||||
<Insets top="30" right="30" bottom="30" left="30"/>
|
||||
</padding>
|
||||
|
||||
<VBox spacing="25" alignment="TOP_CENTER" maxWidth="1000">
|
||||
|
||||
<HBox spacing="20" alignment="CENTER_LEFT" styleClass="teacher-profile-header" maxWidth="800">
|
||||
<StackPane>
|
||||
<ImageView fx:id="avatarView" fitWidth="150" fitHeight="193" preserveRatio="false">
|
||||
<clip>
|
||||
<Rectangle width="150" height="193" arcWidth="20" arcHeight="20" />
|
||||
</clip>
|
||||
</ImageView>
|
||||
</StackPane>
|
||||
|
||||
<VBox spacing="5" alignment="CENTER_LEFT">
|
||||
<Label fx:id="fullNameLabel" styleClass="title-1, text-light" />
|
||||
<Label fx:id="tagLabel" styleClass="teacher-tag, large" />
|
||||
</VBox>
|
||||
</HBox>
|
||||
|
||||
<GridPane fx:id="detailsGrid" hgap="20" vgap="15" styleClass="teacher-details-grid" maxWidth="800">
|
||||
<columnConstraints>
|
||||
<ColumnConstraints minWidth="150" halignment="RIGHT" />
|
||||
<ColumnConstraints hgrow="ALWAYS" />
|
||||
</columnConstraints>
|
||||
</GridPane>
|
||||
|
||||
<VBox spacing="10" alignment="TOP_CENTER" styleClass="teacher-timetable-container">
|
||||
<Label text="Rozvrh učitele" styleClass="title-2, text-light" />
|
||||
<ScrollPane vbarPolicy="NEVER" fitToHeight="true" fitToWidth="true" style="-fx-background: transparent; -fx-background-color: transparent;">
|
||||
<GridPane fx:id="timetableGrid" hgap="10" vgap="10" alignment="CENTER" />
|
||||
</ScrollPane>
|
||||
</VBox>
|
||||
</VBox>
|
||||
</StackPane>
|
||||
</ScrollPane>
|
||||
</StackPane>
|
||||
</center>
|
||||
|
||||
</BorderPane>
|
||||
@@ -0,0 +1,45 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<?import javafx.geometry.Insets?>
|
||||
<?import javafx.scene.control.Button?>
|
||||
<?import javafx.scene.control.Label?>
|
||||
<?import javafx.scene.control.ProgressIndicator?>
|
||||
<?import javafx.scene.control.ScrollPane?>
|
||||
<?import javafx.scene.control.TextField?>
|
||||
<?import javafx.scene.layout.BorderPane?>
|
||||
<?import javafx.scene.layout.FlowPane?>
|
||||
<?import javafx.scene.layout.HBox?>
|
||||
<?import javafx.scene.layout.Region?>
|
||||
<?import javafx.scene.layout.StackPane?>
|
||||
<?import javafx.scene.layout.VBox?>
|
||||
|
||||
<BorderPane xmlns="http://javafx.com/javafx/21" xmlns:fx="http://javafx.com/fxml/1"
|
||||
fx:controller="cz.jzitnik.controllers.TeachersController"
|
||||
style="-fx-background-color: linear-gradient(#0f1724, #071023);"
|
||||
stylesheets="@styles/teachers.css">
|
||||
|
||||
<top>
|
||||
<HBox spacing="15" alignment="CENTER_LEFT" style="-fx-background-color: rgba(255, 255, 255, 0.05); -fx-padding: 15 25;">
|
||||
<Button text="← Zpět" onAction="#onBackToDashboard" styleClass="flat, small" />
|
||||
<Label text="Učitelé" styleClass="title-2, text-light" />
|
||||
<Region HBox.hgrow="ALWAYS" />
|
||||
<TextField fx:id="searchField" promptText="Hledat učitele..." styleClass="search-field" />
|
||||
</HBox>
|
||||
</top>
|
||||
|
||||
<center>
|
||||
<StackPane>
|
||||
<ProgressIndicator fx:id="loadingIndicator" maxWidth="50" maxHeight="50" />
|
||||
|
||||
<ScrollPane fx:id="scrollPane" fitToWidth="true" style="-fx-background: transparent; -fx-background-color: transparent;" visible="false">
|
||||
<padding>
|
||||
<Insets top="20" right="30" bottom="30" left="30"/>
|
||||
</padding>
|
||||
<VBox alignment="TOP_CENTER">
|
||||
<FlowPane fx:id="teachersFlow" hgap="15" vgap="15" alignment="CENTER" maxWidth="900" />
|
||||
</VBox>
|
||||
</ScrollPane>
|
||||
</StackPane>
|
||||
</center>
|
||||
|
||||
</BorderPane>
|
||||
Reference in New Issue
Block a user