feat: Added absences and moodle integration
This commit is contained in:
@@ -1,16 +0,0 @@
|
|||||||
import io.github.tomhula.jecnaapi.data.grade.*;
|
|
||||||
import io.github.tomhula.jecnaapi.util.Name;
|
|
||||||
import kotlinx.datetime.LocalDate;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
public class TestAvg {
|
|
||||||
public static void main(String[] args) {
|
|
||||||
// Let's see if we can create a Grade
|
|
||||||
Grade g1 = new Grade(1, false, new Name("Jan", "Novak"), "Test", new LocalDate(2023, 1, 1), 0);
|
|
||||||
Grade g2 = new Grade(2, true, new Name("Jan", "Novak"), "Test", new LocalDate(2023, 1, 1), 0);
|
|
||||||
|
|
||||||
System.out.println("Grade 1: " + g1.getValue() + " small: " + g1.getSmall());
|
|
||||||
System.out.println("Grade 2: " + g2.getValue() + " small: " + g2.getSmall());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -24,6 +24,14 @@
|
|||||||
<mainClass>cz.jzitnik.Main</mainClass>
|
<mainClass>cz.jzitnik.Main</mainClass>
|
||||||
</configuration>
|
</configuration>
|
||||||
</plugin>
|
</plugin>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.codehaus.mojo</groupId>
|
||||||
|
<artifactId>exec-maven-plugin</artifactId>
|
||||||
|
<version>3.1.1</version>
|
||||||
|
<configuration>
|
||||||
|
<mainClass>cz.jzitnik.Main</mainClass>
|
||||||
|
</configuration>
|
||||||
|
</plugin>
|
||||||
</plugins>
|
</plugins>
|
||||||
</build>
|
</build>
|
||||||
|
|
||||||
@@ -39,6 +47,11 @@
|
|||||||
<artifactId>javafx-fxml</artifactId>
|
<artifactId>javafx-fxml</artifactId>
|
||||||
<version>21.0.1</version>
|
<version>21.0.1</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.openjfx</groupId>
|
||||||
|
<artifactId>javafx-web</artifactId>
|
||||||
|
<version>21.0.1</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
<!-- AtlantaFX Theme -->
|
<!-- AtlantaFX Theme -->
|
||||||
<dependency>
|
<dependency>
|
||||||
|
|||||||
@@ -0,0 +1,173 @@
|
|||||||
|
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.absence.AbsenceInfo;
|
||||||
|
import io.github.tomhula.jecnaapi.data.absence.AbsencesPage;
|
||||||
|
import kotlinx.datetime.LocalDate;
|
||||||
|
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.Comparator;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
@Route(path = "/absences", fxml = "/absences.fxml")
|
||||||
|
public class AbsencesController extends DashboardBaseController {
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private ProgressIndicator loadingIndicator;
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private ScrollPane scrollPane;
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private HBox summaryBox;
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private FlowPane detailFlow;
|
||||||
|
|
||||||
|
private AbsencesPage currentPage;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onNavigate(Map<String, Object> props) {
|
||||||
|
super.onNavigate(props);
|
||||||
|
loadAbsences();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void loadAbsences() {
|
||||||
|
loadingIndicator.setVisible(true);
|
||||||
|
scrollPane.setVisible(false);
|
||||||
|
summaryBox.getChildren().clear();
|
||||||
|
detailFlow.getChildren().clear();
|
||||||
|
|
||||||
|
appState.getQueryClient().fetch("absences:page", () -> {
|
||||||
|
try {
|
||||||
|
return appState.getClient().getAbsencesPage().join();
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}, QueryOptions.defaultOptions()).thenAccept(this::handleAbsencesResult);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleAbsencesResult(QueryResult<AbsencesPage> result) {
|
||||||
|
Platform.runLater(() -> {
|
||||||
|
loadingIndicator.setVisible(false);
|
||||||
|
|
||||||
|
if (result.isSuccess() && result.getData().isPresent()) {
|
||||||
|
currentPage = result.getData().get();
|
||||||
|
renderAbsences();
|
||||||
|
scrollPane.setVisible(true);
|
||||||
|
} else {
|
||||||
|
Label errorLabel = new Label("Failed to load absences.");
|
||||||
|
errorLabel.getStyleClass().addAll("text-danger", "title-3");
|
||||||
|
summaryBox.getChildren().add(errorLabel);
|
||||||
|
scrollPane.setVisible(true);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void renderAbsences() {
|
||||||
|
summaryBox.getChildren().clear();
|
||||||
|
detailFlow.getChildren().clear();
|
||||||
|
|
||||||
|
if (currentPage.getDays().isEmpty()) {
|
||||||
|
Label noAbsences = new Label("Žádné absence v tomto roce!");
|
||||||
|
noAbsences.getStyleClass().addAll("title-3", "text-success");
|
||||||
|
summaryBox.getChildren().add(noAbsences);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int totalAbsent = 0;
|
||||||
|
int totalUnexcused = 0;
|
||||||
|
int totalLate = 0;
|
||||||
|
|
||||||
|
List<LocalDate> sortedDays = new ArrayList<>(currentPage.getDays());
|
||||||
|
sortedDays.sort(Comparator.reverseOrder());
|
||||||
|
|
||||||
|
for (LocalDate day : sortedDays) {
|
||||||
|
AbsenceInfo info = currentPage.get(day);
|
||||||
|
if (info != null) {
|
||||||
|
totalAbsent += info.getHoursAbsent();
|
||||||
|
totalUnexcused += info.getUnexcusedHours();
|
||||||
|
totalLate += info.getLateEntryCount();
|
||||||
|
|
||||||
|
detailFlow.getChildren().add(createAbsenceCard(day, info));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
summaryBox.getChildren().addAll(
|
||||||
|
createSummaryCard("Zameškané hodiny", totalAbsent, "absence-summary-total"),
|
||||||
|
createSummaryCard("Neomluvené", totalUnexcused, totalUnexcused > 0 ? "absence-summary-danger" : "absence-summary-safe"),
|
||||||
|
createSummaryCard("Pozdní příchody", totalLate, totalLate > 0 ? "absence-summary-warning" : "absence-summary-safe")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private VBox createSummaryCard(String title, int count, String styleClass) {
|
||||||
|
VBox card = new VBox(5);
|
||||||
|
card.setAlignment(Pos.CENTER);
|
||||||
|
card.getStyleClass().addAll("card", "absence-summary-card", styleClass);
|
||||||
|
|
||||||
|
Label valueLbl = new Label(String.valueOf(count));
|
||||||
|
valueLbl.getStyleClass().add("absence-summary-value");
|
||||||
|
|
||||||
|
Label titleLbl = new Label(title);
|
||||||
|
titleLbl.getStyleClass().add("text-muted");
|
||||||
|
|
||||||
|
card.getChildren().addAll(valueLbl, titleLbl);
|
||||||
|
return card;
|
||||||
|
}
|
||||||
|
|
||||||
|
private VBox createAbsenceCard(LocalDate day, AbsenceInfo info) {
|
||||||
|
VBox card = new VBox(8);
|
||||||
|
card.getStyleClass().addAll("card", "absence-card");
|
||||||
|
|
||||||
|
if (info.getUnexcusedHours() > 0) {
|
||||||
|
card.getStyleClass().add("absence-card-danger");
|
||||||
|
} else if (info.getLateEntryCount() > 0) {
|
||||||
|
card.getStyleClass().add("absence-card-warning");
|
||||||
|
}
|
||||||
|
|
||||||
|
Label dateLbl = new Label(formatDate(day));
|
||||||
|
dateLbl.getStyleClass().addAll("title-4", "absence-card-date");
|
||||||
|
|
||||||
|
HBox stats = new HBox(15);
|
||||||
|
stats.setAlignment(Pos.CENTER_LEFT);
|
||||||
|
|
||||||
|
Label hoursLbl = new Label("Hodiny: " + info.getHoursAbsent());
|
||||||
|
hoursLbl.getStyleClass().add("text-light");
|
||||||
|
stats.getChildren().add(hoursLbl);
|
||||||
|
|
||||||
|
if (info.getUnexcusedHours() > 0) {
|
||||||
|
Label unexcusedLbl = new Label("Neomluveno: " + info.getUnexcusedHours());
|
||||||
|
unexcusedLbl.getStyleClass().addAll("text-danger", "text-bold");
|
||||||
|
stats.getChildren().add(unexcusedLbl);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (info.getLateEntryCount() > 0) {
|
||||||
|
Label lateLbl = new Label("Pozdě: " + info.getLateEntryCount());
|
||||||
|
lateLbl.getStyleClass().addAll("text-warning", "text-bold");
|
||||||
|
stats.getChildren().add(lateLbl);
|
||||||
|
}
|
||||||
|
|
||||||
|
card.getChildren().addAll(dateLbl, stats);
|
||||||
|
return card;
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("deprecation")
|
||||||
|
private String formatDate(LocalDate date) {
|
||||||
|
return date.getDayOfMonth() + ". " + date.getMonthNumber() + ". " + date.getYear();
|
||||||
|
}
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
protected void onBackToDashboard() {
|
||||||
|
Router.getInstance().navigate("/dashboard");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -41,6 +41,9 @@ public class DashboardBaseController implements Routable {
|
|||||||
@FXML
|
@FXML
|
||||||
protected void onNavigateToSpecial() { Router.getInstance().navigate("/special"); }
|
protected void onNavigateToSpecial() { Router.getInstance().navigate("/special"); }
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
protected void onNavigateToMoodle() { Router.getInstance().navigate("/moodle"); }
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
protected void onDoNothing() {
|
protected void onDoNothing() {
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,167 @@
|
|||||||
|
package cz.jzitnik.controllers;
|
||||||
|
|
||||||
|
import cz.jzitnik.router.Route;
|
||||||
|
import cz.jzitnik.router.Router;
|
||||||
|
import javafx.concurrent.Worker;
|
||||||
|
import javafx.fxml.FXML;
|
||||||
|
import javafx.scene.control.Button;
|
||||||
|
import javafx.scene.control.Label;
|
||||||
|
import javafx.scene.control.ProgressIndicator;
|
||||||
|
import javafx.scene.web.WebEngine;
|
||||||
|
import javafx.scene.web.WebHistory;
|
||||||
|
import javafx.scene.web.WebView;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
@Route(path = "/moodle", fxml = "/moodle.fxml")
|
||||||
|
public class MoodleController extends DashboardBaseController {
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private WebView webView;
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private ProgressIndicator loadingIndicator;
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private Label titleLabel;
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private Button btnBack;
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private Button btnForward;
|
||||||
|
|
||||||
|
private WebEngine webEngine;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onNavigate(Map<String, Object> props) {
|
||||||
|
super.onNavigate(props);
|
||||||
|
|
||||||
|
webEngine = webView.getEngine();
|
||||||
|
|
||||||
|
webEngine.getLoadWorker().stateProperty().addListener((obs, oldState, newState) -> {
|
||||||
|
if (newState == Worker.State.SUCCEEDED) {
|
||||||
|
String currentUrl = webEngine.getLocation();
|
||||||
|
boolean isLoginPage = currentUrl != null && currentUrl.contains("login/index.php");
|
||||||
|
|
||||||
|
Boolean isLoggedOut = false;
|
||||||
|
try {
|
||||||
|
Object result = webEngine.executeScript("document.body && document.body.classList.contains('notloggedin')");
|
||||||
|
if (result instanceof Boolean) {
|
||||||
|
isLoggedOut = (Boolean) result;
|
||||||
|
}
|
||||||
|
} catch (Exception e) {}
|
||||||
|
|
||||||
|
if (isLoginPage) {
|
||||||
|
String username = appState.getUsername();
|
||||||
|
String password = appState.getPassword();
|
||||||
|
|
||||||
|
if (username != null && password != null) {
|
||||||
|
String script = String.format(
|
||||||
|
"var u = document.getElementById('username');" +
|
||||||
|
"var p = document.getElementById('password');" +
|
||||||
|
"var b = document.getElementById('loginbtn');" +
|
||||||
|
"if (u && p && b) {" +
|
||||||
|
" u.value = '%s';" +
|
||||||
|
" p.value = '%s';" +
|
||||||
|
" b.click();" +
|
||||||
|
"}",
|
||||||
|
username.replace("'", "\\'"),
|
||||||
|
password.replace("'", "\\'")
|
||||||
|
);
|
||||||
|
webEngine.executeScript(script);
|
||||||
|
} else {
|
||||||
|
loadingIndicator.setVisible(false);
|
||||||
|
webView.setVisible(true);
|
||||||
|
}
|
||||||
|
} else if (isLoggedOut) {
|
||||||
|
webEngine.executeScript("window.location.href = 'https://moodle.spsejecna.cz/login/index.php';");
|
||||||
|
} else {
|
||||||
|
loadingIndicator.setVisible(false);
|
||||||
|
webView.setVisible(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
} else if (newState == Worker.State.FAILED) {
|
||||||
|
titleLabel.setText("Failed to load");
|
||||||
|
loadingIndicator.setVisible(false);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
webEngine.titleProperty().addListener((obs, oldTitle, newTitle) -> {
|
||||||
|
if (newTitle != null && !newTitle.isEmpty()) {
|
||||||
|
titleLabel.setText(newTitle);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
webEngine.getHistory().currentIndexProperty().addListener((obs, oldVal, newVal) -> {
|
||||||
|
updateNavigationButtons();
|
||||||
|
});
|
||||||
|
updateNavigationButtons();
|
||||||
|
|
||||||
|
loadingIndicator.setVisible(true);
|
||||||
|
webView.setVisible(false);
|
||||||
|
webEngine.setUserStyleSheetLocation(Objects.requireNonNull(getClass().getResource("/styles/moodle-dark.css")).toExternalForm());
|
||||||
|
webEngine.load("https://moodle.spsejecna.cz/");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateNavigationButtons() {
|
||||||
|
WebHistory history = webEngine.getHistory();
|
||||||
|
int currentIndex = history.getCurrentIndex();
|
||||||
|
|
||||||
|
int validBackIndex = -1;
|
||||||
|
for (int i = currentIndex - 1; i >= 0; i--) {
|
||||||
|
String url = history.getEntries().get(i).getUrl();
|
||||||
|
if (url == null || !url.contains("login/index.php")) {
|
||||||
|
validBackIndex = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
btnBack.setDisable(validBackIndex == -1);
|
||||||
|
|
||||||
|
int validForwardIndex = -1;
|
||||||
|
for (int i = currentIndex + 1; i < history.getEntries().size(); i++) {
|
||||||
|
String url = history.getEntries().get(i).getUrl();
|
||||||
|
if (url == null || !url.contains("login/index.php")) {
|
||||||
|
validForwardIndex = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
btnForward.setDisable(validForwardIndex == -1);
|
||||||
|
}
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
protected void onBrowserBack() {
|
||||||
|
WebHistory history = webEngine.getHistory();
|
||||||
|
int currentIndex = history.getCurrentIndex();
|
||||||
|
for (int i = currentIndex - 1; i >= 0; i--) {
|
||||||
|
String url = history.getEntries().get(i).getUrl();
|
||||||
|
if (url == null || !url.contains("login/index.php")) {
|
||||||
|
history.go(i - currentIndex);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
protected void onBrowserForward() {
|
||||||
|
WebHistory history = webEngine.getHistory();
|
||||||
|
int currentIndex = history.getCurrentIndex();
|
||||||
|
for (int i = currentIndex + 1; i < history.getEntries().size(); i++) {
|
||||||
|
String url = history.getEntries().get(i).getUrl();
|
||||||
|
if (url == null || !url.contains("login/index.php")) {
|
||||||
|
history.go(i - currentIndex);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
protected void onBrowserReload() {
|
||||||
|
webEngine.reload();
|
||||||
|
}
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
protected void onBackToDashboard() {
|
||||||
|
Router.getInstance().navigate("/dashboard");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -8,6 +8,7 @@ import cz.jzitnik.query.QueryClient;
|
|||||||
public class AppState {
|
public class AppState {
|
||||||
|
|
||||||
private String username;
|
private String username;
|
||||||
|
private String password;
|
||||||
private JecnaClientJavaWrapper client;
|
private JecnaClientJavaWrapper client;
|
||||||
private QueryClient queryClient;
|
private QueryClient queryClient;
|
||||||
|
|
||||||
@@ -15,6 +16,10 @@ public class AppState {
|
|||||||
return username;
|
return username;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getPassword() {
|
||||||
|
return password;
|
||||||
|
}
|
||||||
|
|
||||||
public JecnaClientJavaWrapper getClient() {
|
public JecnaClientJavaWrapper getClient() {
|
||||||
return client;
|
return client;
|
||||||
}
|
}
|
||||||
@@ -38,6 +43,7 @@ public class AppState {
|
|||||||
boolean ok = client.login(username, password).join();
|
boolean ok = client.login(username, password).join();
|
||||||
if (ok) {
|
if (ok) {
|
||||||
this.username = username;
|
this.username = username;
|
||||||
|
this.password = password;
|
||||||
try {
|
try {
|
||||||
CredentialStore.saveCredentials(username, password);
|
CredentialStore.saveCredentials(username, password);
|
||||||
} catch (Throwable t) {
|
} catch (Throwable t) {
|
||||||
@@ -57,6 +63,7 @@ public class AppState {
|
|||||||
|
|
||||||
public void clear() {
|
public void clear() {
|
||||||
this.username = null;
|
this.username = null;
|
||||||
|
this.password = null;
|
||||||
if (this.client != null) {
|
if (this.client != null) {
|
||||||
try {
|
try {
|
||||||
this.client.logout();
|
this.client.logout();
|
||||||
|
|||||||
@@ -0,0 +1,50 @@
|
|||||||
|
<?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.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.AbsencesController"
|
||||||
|
style="-fx-background-color: linear-gradient(#0f1724, #071023);"
|
||||||
|
stylesheets="@styles/absences.css">
|
||||||
|
|
||||||
|
<!-- Header -->
|
||||||
|
<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="Absence" styleClass="title-2, text-light" />
|
||||||
|
</HBox>
|
||||||
|
</top>
|
||||||
|
|
||||||
|
<!-- Content -->
|
||||||
|
<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 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>
|
||||||
|
</ScrollPane>
|
||||||
|
</StackPane>
|
||||||
|
</center>
|
||||||
|
|
||||||
|
</BorderPane>
|
||||||
@@ -43,8 +43,11 @@
|
|||||||
|
|
||||||
<!-- Row 1 -->
|
<!-- Row 1 -->
|
||||||
<Button text="Učebny" GridPane.rowIndex="1" GridPane.columnIndex="0" onAction="#onDoNothing" styleClass="card"/>
|
<Button text="Učebny" GridPane.rowIndex="1" GridPane.columnIndex="0" onAction="#onDoNothing" styleClass="card"/>
|
||||||
<Button text="Absence" GridPane.rowIndex="1" GridPane.columnIndex="1" onAction="#onDoNothing" styleClass="card"/>
|
<Button text="Absence" GridPane.rowIndex="1" GridPane.columnIndex="1" onAction="#onNavigateToAbsences" styleClass="card"/>
|
||||||
<Button text="Mimořádný rozvrh" GridPane.rowIndex="1" GridPane.columnIndex="2" onAction="#onDoNothing" styleClass="card"/>
|
<Button text="Mimořádný rozvrh" GridPane.rowIndex="1" GridPane.columnIndex="2" onAction="#onDoNothing" styleClass="card"/>
|
||||||
|
|
||||||
|
<!-- Row 2 -->
|
||||||
|
<Button text="Moodle" GridPane.rowIndex="2" GridPane.columnIndex="0" onAction="#onNavigateToMoodle" styleClass="card"/>
|
||||||
</GridPane>
|
</GridPane>
|
||||||
</center>
|
</center>
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,37 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
|
||||||
|
<?import javafx.scene.control.Button?>
|
||||||
|
<?import javafx.scene.control.Label?>
|
||||||
|
<?import javafx.scene.control.ProgressIndicator?>
|
||||||
|
<?import javafx.scene.layout.BorderPane?>
|
||||||
|
<?import javafx.scene.layout.HBox?>
|
||||||
|
<?import javafx.scene.layout.StackPane?>
|
||||||
|
<?import javafx.scene.web.WebView?>
|
||||||
|
|
||||||
|
<BorderPane xmlns="http://javafx.com/javafx/21" xmlns:fx="http://javafx.com/fxml/1"
|
||||||
|
fx:controller="cz.jzitnik.controllers.MoodleController"
|
||||||
|
style="-fx-background-color: linear-gradient(#0f1724, #071023);"
|
||||||
|
stylesheets="@styles/moodle.css">
|
||||||
|
|
||||||
|
<top>
|
||||||
|
<HBox spacing="10" 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" />
|
||||||
|
|
||||||
|
<HBox spacing="5" alignment="CENTER_LEFT">
|
||||||
|
<Button fx:id="btnBack" text="<" onAction="#onBrowserBack" styleClass="browser-btn" />
|
||||||
|
<Button fx:id="btnForward" text=">" onAction="#onBrowserForward" styleClass="browser-btn" />
|
||||||
|
<Button fx:id="btnReload" text="↻" onAction="#onBrowserReload" styleClass="browser-btn" />
|
||||||
|
</HBox>
|
||||||
|
|
||||||
|
<Label fx:id="titleLabel" text="Načítání..." styleClass="title-3, text-light, moodle-title" />
|
||||||
|
</HBox>
|
||||||
|
</top>
|
||||||
|
|
||||||
|
<center>
|
||||||
|
<StackPane>
|
||||||
|
<WebView fx:id="webView" visible="false" />
|
||||||
|
<ProgressIndicator fx:id="loadingIndicator" maxWidth="50" maxHeight="50" />
|
||||||
|
</StackPane>
|
||||||
|
</center>
|
||||||
|
|
||||||
|
</BorderPane>
|
||||||
@@ -0,0 +1,77 @@
|
|||||||
|
/* Absences Styles */
|
||||||
|
|
||||||
|
.absence-summary-card {
|
||||||
|
-fx-min-width: 150px;
|
||||||
|
-fx-min-height: 100px;
|
||||||
|
-fx-background-color: rgba(255, 255, 255, 0.05);
|
||||||
|
-fx-border-radius: 12px;
|
||||||
|
-fx-background-radius: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.absence-summary-value {
|
||||||
|
-fx-font-size: 32px;
|
||||||
|
-fx-font-weight: bold;
|
||||||
|
-fx-text-fill: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.absence-summary-total .absence-summary-value {
|
||||||
|
-fx-text-fill: #58a6ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.absence-summary-safe .absence-summary-value {
|
||||||
|
-fx-text-fill: #2ea043;
|
||||||
|
}
|
||||||
|
|
||||||
|
.absence-summary-warning {
|
||||||
|
-fx-border-color: rgba(210, 153, 34, 0.4);
|
||||||
|
}
|
||||||
|
.absence-summary-warning .absence-summary-value {
|
||||||
|
-fx-text-fill: #d29922;
|
||||||
|
}
|
||||||
|
|
||||||
|
.absence-summary-danger {
|
||||||
|
-fx-border-color: rgba(248, 81, 73, 0.4);
|
||||||
|
}
|
||||||
|
.absence-summary-danger .absence-summary-value {
|
||||||
|
-fx-text-fill: #f85149;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Detail Cards */
|
||||||
|
.absence-card {
|
||||||
|
-fx-min-width: 250px;
|
||||||
|
-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;
|
||||||
|
-fx-alignment: center-left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.absence-card-warning {
|
||||||
|
-fx-border-color: rgba(210, 153, 34, 0.5);
|
||||||
|
-fx-background-color: rgba(210, 153, 34, 0.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
.absence-card-danger {
|
||||||
|
-fx-border-color: rgba(248, 81, 73, 0.5);
|
||||||
|
-fx-background-color: rgba(248, 81, 73, 0.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
.absence-card-date {
|
||||||
|
-fx-text-fill: white;
|
||||||
|
-fx-font-size: 16px;
|
||||||
|
-fx-font-weight: bold;
|
||||||
|
-fx-padding: 0 0 5 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-success {
|
||||||
|
-fx-text-fill: #2ea043;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-warning {
|
||||||
|
-fx-text-fill: #d29922;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-bold {
|
||||||
|
-fx-font-weight: bold;
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
html {
|
||||||
|
filter: invert(100%) hue-rotate(180deg) brightness(105%) contrast(85%);
|
||||||
|
background: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
img, video, iframe, canvas, svg, [style*='background-image'] {
|
||||||
|
filter: invert(100%) hue-rotate(180deg) !important;
|
||||||
|
}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
.browser-btn {
|
||||||
|
-fx-background-color: transparent;
|
||||||
|
-fx-border-color: transparent;
|
||||||
|
-fx-text-fill: #c9d1d9;
|
||||||
|
-fx-font-weight: bold;
|
||||||
|
-fx-font-size: 14px;
|
||||||
|
-fx-min-width: 35px;
|
||||||
|
-fx-min-height: 35px;
|
||||||
|
-fx-padding: 0;
|
||||||
|
-fx-cursor: hand;
|
||||||
|
-fx-background-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.browser-btn:hover {
|
||||||
|
-fx-background-color: rgba(255, 255, 255, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.browser-btn:pressed {
|
||||||
|
-fx-background-color: rgba(255, 255, 255, 0.15);
|
||||||
|
}
|
||||||
|
|
||||||
|
.browser-btn:disabled {
|
||||||
|
-fx-opacity: 0.3;
|
||||||
|
-fx-cursor: default;
|
||||||
|
}
|
||||||
|
|
||||||
|
.moodle-title {
|
||||||
|
-fx-padding: 0 0 0 15px;
|
||||||
|
-fx-text-fill: white;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user