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>
|
||||
</configuration>
|
||||
</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>
|
||||
</build>
|
||||
|
||||
@@ -39,6 +47,11 @@
|
||||
<artifactId>javafx-fxml</artifactId>
|
||||
<version>21.0.1</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.openjfx</groupId>
|
||||
<artifactId>javafx-web</artifactId>
|
||||
<version>21.0.1</version>
|
||||
</dependency>
|
||||
|
||||
<!-- AtlantaFX Theme -->
|
||||
<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
|
||||
protected void onNavigateToSpecial() { Router.getInstance().navigate("/special"); }
|
||||
|
||||
@FXML
|
||||
protected void onNavigateToMoodle() { Router.getInstance().navigate("/moodle"); }
|
||||
|
||||
@FXML
|
||||
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 {
|
||||
|
||||
private String username;
|
||||
private String password;
|
||||
private JecnaClientJavaWrapper client;
|
||||
private QueryClient queryClient;
|
||||
|
||||
@@ -15,6 +16,10 @@ public class AppState {
|
||||
return username;
|
||||
}
|
||||
|
||||
public String getPassword() {
|
||||
return password;
|
||||
}
|
||||
|
||||
public JecnaClientJavaWrapper getClient() {
|
||||
return client;
|
||||
}
|
||||
@@ -38,6 +43,7 @@ public class AppState {
|
||||
boolean ok = client.login(username, password).join();
|
||||
if (ok) {
|
||||
this.username = username;
|
||||
this.password = password;
|
||||
try {
|
||||
CredentialStore.saveCredentials(username, password);
|
||||
} catch (Throwable t) {
|
||||
@@ -57,6 +63,7 @@ public class AppState {
|
||||
|
||||
public void clear() {
|
||||
this.username = null;
|
||||
this.password = null;
|
||||
if (this.client != null) {
|
||||
try {
|
||||
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 -->
|
||||
<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"/>
|
||||
|
||||
<!-- Row 2 -->
|
||||
<Button text="Moodle" GridPane.rowIndex="2" GridPane.columnIndex="0" onAction="#onNavigateToMoodle" styleClass="card"/>
|
||||
</GridPane>
|
||||
</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