532 lines
20 KiB
Java
532 lines
20 KiB
Java
// File: ui/TrainingFormUIEnterEvent.java
|
||
package ui;
|
||
|
||
import db.TrainingDAO;
|
||
import db.TrainingTypeDAO;
|
||
import javafx.beans.value.ChangeListener;
|
||
import javafx.geometry.Insets;
|
||
import javafx.scene.control.*;
|
||
import javafx.scene.layout.BorderPane;
|
||
import javafx.scene.layout.GridPane;
|
||
import javafx.scene.layout.HBox;
|
||
import javafx.scene.layout.VBox;
|
||
import model.PersonMapping;
|
||
import util.Config;
|
||
|
||
import java.time.*;
|
||
import java.time.format.DateTimeFormatter;
|
||
import java.time.format.DateTimeParseException;
|
||
import java.util.List;
|
||
|
||
/**
|
||
* Tab 1: Formular zur Erfassung einer neuen Schulung oder Führung.
|
||
* Inhalt aus der bisherigen TrainingFormUI übernommen.
|
||
*/
|
||
public class TrainingFormUIEnterEvent extends BorderPane {
|
||
|
||
private final ComboBox<String> veranstaltungstypBox = new ComboBox<>();
|
||
private final ComboBox<String> inhaltstypBox = new ComboBox<>();
|
||
private final TextField titelField = new TextField();
|
||
private final DatePicker dateField = new DatePicker();
|
||
private final TimeSpinner startzeitSpinner = new TimeSpinner();
|
||
|
||
private final TextField dauerField = new TextField();
|
||
private final ComboBox<String> modusBox = new ComboBox<>();
|
||
private final TextField teilnehmerAnzahlField = new TextField();
|
||
private final ComboBox<String> teilnehmerTypBox = new ComboBox<>();
|
||
//private final TextField durchfuehrendePersonField = new TextField();
|
||
private final ComboBox<String> durchfuehrendePersonBox = new ComboBox<>();
|
||
private final ComboBox<String> ausgefallenBox = new ComboBox<>();
|
||
private final TextArea kommentarField = new TextArea();
|
||
|
||
private final TextField evaluationField = new TextField();
|
||
|
||
private final Button speichernButton = new Button("Speichern");
|
||
private final Button resetButton = new Button("Zurücksetzen");
|
||
|
||
private static final DateTimeFormatter TIME_FORMAT = DateTimeFormatter.ofPattern("HH:mm");
|
||
|
||
public TrainingFormUIEnterEvent() {
|
||
setPadding(new Insets(15));
|
||
|
||
Label heading = new Label("Neue Schulung anlegen");
|
||
heading.setStyle("-fx-font-size: 18; -fx-font-weight: bold;");
|
||
|
||
Label hint = new Label("Hinweis: Bereits geplante Veranstaltungen bitte im Tab 'Daten' bearbeiten.");
|
||
hint.setStyle("-fx-font-size: 12; -fx-text-fill: #555555; -fx-font-style: italic;");
|
||
|
||
VBox headingBox = new VBox(4, heading, hint);
|
||
setTop(headingBox);
|
||
|
||
GridPane grid = new GridPane();
|
||
grid.setPadding(new Insets(15, 0, 0, 0));
|
||
grid.setVgap(8);
|
||
grid.setHgap(10);
|
||
|
||
int row = 0;
|
||
grid.add(new Label("Veranstaltungstyp:"), 0, row); grid.add(veranstaltungstypBox, 1, row++);
|
||
grid.add(new Label("Inhaltstyp:"), 0, row); grid.add(inhaltstypBox, 1, row++);
|
||
grid.add(new Label("Titel Veranstaltung:"), 0, row); grid.add(titelField, 1, row++);
|
||
grid.add(new Label("Datum:"), 0, row); grid.add(dateField, 1, row++);
|
||
grid.add(new Label("Startzeit:"), 0, row); grid.add(startzeitSpinner, 1, row++);
|
||
grid.add(new Label("Dauer (Min):"), 0, row); grid.add(dauerField, 1, row++);
|
||
grid.add(new Label("Modus:"), 0, row); grid.add(modusBox, 1, row++);
|
||
grid.add(new Label("Teilnehmende:"), 0, row); grid.add(teilnehmerAnzahlField, 1, row++);
|
||
grid.add(new Label("Ausgefallen (Grund):"), 0, row); grid.add(ausgefallenBox, 1, row++);
|
||
grid.add(new Label("Typ Teilnehmende:"), 0, row); grid.add(teilnehmerTypBox, 1, row++);
|
||
//grid.add(new Label("Durchführende Person:"), 0, row); grid.add(durchfuehrendePersonField, 1, row++);
|
||
grid.add(new Label("Durchführende Person:"), 0, row);
|
||
grid.add(durchfuehrendePersonBox, 1, row++);
|
||
// --- Evaluation (direkte Eingabe ODER Live-Evaluation starten) ---
|
||
grid.add(new Label("Bewertung (1–6):"), 0, row);
|
||
|
||
HBox evalBox = new HBox(10);
|
||
evalBox.setPadding(new Insets(0, 0, 0, 0));
|
||
|
||
evaluationField.setPromptText("1–6");
|
||
evaluationField.setEditable(true);
|
||
evaluationField.setPrefWidth(80);
|
||
|
||
Button liveEvalButton = new Button("Live-Evaluation starten");
|
||
liveEvalButton.setStyle("-fx-font-size: 12;");
|
||
|
||
liveEvalButton.setOnAction(e -> {
|
||
EvaluationPopupUI popup = new EvaluationPopupUI();
|
||
double avg = popup.showAndWait();
|
||
if (avg > 0) {
|
||
evaluationField.setText(String.valueOf(avg));
|
||
}
|
||
});
|
||
|
||
evalBox.getChildren().addAll(evaluationField, liveEvalButton);
|
||
grid.add(evalBox, 1, row++);
|
||
|
||
grid.add(new Label("Kommentar:"), 0, row); grid.add(kommentarField, 1, row++);
|
||
grid.add(new HBox(10, speichernButton, resetButton), 1, ++row);
|
||
|
||
|
||
veranstaltungstypBox.getItems().setAll(model.DataModels.getVeranstaltungstypen());
|
||
modusBox.getItems().setAll(model.DataModels.getModi());
|
||
teilnehmerTypBox.getItems().setAll(model.DataModels.getTeilnehmendeTypen());
|
||
ausgefallenBox.getItems().setAll(model.DataModels.getAusgefallenGruende());
|
||
// Durchführende Personen: vordefinierte Namen + frei editierbar
|
||
durchfuehrendePersonBox.getItems().setAll(PersonMapping.getAllNames());
|
||
durchfuehrendePersonBox.setEditable(true);
|
||
// --- Durchführende Person: Dropdown + Standardwert ---
|
||
List<String> bekanntePersonen = new java.util.ArrayList<>(model.PersonMapping.getAllNames());
|
||
durchfuehrendePersonBox.getItems().setAll(bekanntePersonen);
|
||
durchfuehrendePersonBox.setEditable(true);
|
||
durchfuehrendePersonBox.setPromptText("Name oder Kürzel eingeben");
|
||
|
||
String currentUser = System.getProperty("user.name");
|
||
String displayName = model.PersonMapping.toName(currentUser);
|
||
durchfuehrendePersonBox.setValue(displayName);
|
||
|
||
|
||
|
||
fillInhaltstypBoxFromDatabase();
|
||
|
||
kommentarField.setPrefRowCount(3);
|
||
kommentarField.setWrapText(true);
|
||
|
||
teilnehmerAnzahlField.textProperty().addListener((obs, oldVal, newVal) -> {
|
||
Integer parsed = parseIntSafe(newVal);
|
||
int anzahl = parsed == null ? 0 : parsed;
|
||
if (anzahl <= 0) {
|
||
ausgefallenBox.setDisable(false);
|
||
} else {
|
||
ausgefallenBox.setValue("");
|
||
ausgefallenBox.setDisable(true);
|
||
}
|
||
validateFields();
|
||
});
|
||
|
||
ausgefallenBox.valueProperty().addListener((obs, oldVal, newVal) -> {
|
||
if (newVal != null && !newVal.isBlank()) {
|
||
teilnehmerAnzahlField.setText("0");
|
||
teilnehmerAnzahlField.setDisable(true);
|
||
} else {
|
||
teilnehmerAnzahlField.setDisable(false);
|
||
}
|
||
validateFields();
|
||
});
|
||
|
||
String username = System.getProperty("user.name");
|
||
durchfuehrendePersonBox.getItems().setAll(model.PersonMapping.getAllNames());
|
||
durchfuehrendePersonBox.setEditable(true);
|
||
durchfuehrendePersonBox.setPromptText("Name oder Kürzel eingeben");
|
||
durchfuehrendePersonBox.setValue(model.PersonMapping.toName(username));
|
||
|
||
|
||
veranstaltungstypBox.setOnAction(e -> updateFieldsForVeranstaltungstyp());
|
||
inhaltstypBox.setOnAction(e -> updateTitleFromInhaltstyp());
|
||
|
||
applyIntegerFilter(dauerField);
|
||
applyIntegerFilter(teilnehmerAnzahlField);
|
||
applyDecimalFilter(evaluationField);
|
||
|
||
|
||
ChangeListener<Object> validationListener = (obs, o, n) -> validateFields();
|
||
veranstaltungstypBox.valueProperty().addListener(validationListener);
|
||
inhaltstypBox.valueProperty().addListener(validationListener);
|
||
titelField.textProperty().addListener(validationListener);
|
||
dateField.valueProperty().addListener(validationListener);
|
||
dauerField.textProperty().addListener(validationListener);
|
||
modusBox.valueProperty().addListener(validationListener);
|
||
teilnehmerAnzahlField.textProperty().addListener(validationListener);
|
||
teilnehmerTypBox.valueProperty().addListener(validationListener);
|
||
startzeitSpinner.valueProperty().addListener(validationListener);
|
||
startzeitSpinner.getEditor().textProperty().addListener(validationListener);
|
||
|
||
speichernButton.setOnAction(e -> saveTraining());
|
||
speichernButton.setDisable(true);
|
||
|
||
setCenter(new ScrollPane(grid));
|
||
}
|
||
|
||
private void fillInhaltstypBoxFromDatabase() {
|
||
TrainingTypeDAO dao = new TrainingTypeDAO(Config.getDbPath());
|
||
List<String> types = dao.getAllTrainingTypes();
|
||
inhaltstypBox.getItems().setAll(types);
|
||
}
|
||
|
||
private static class TypeRule {
|
||
final String forcedInhalt;
|
||
final boolean inhaltLocked;
|
||
|
||
final LocalDate forcedDate;
|
||
final boolean dateLocked;
|
||
|
||
final Integer forcedDauer;
|
||
final boolean dauerLocked;
|
||
|
||
final String forcedModus;
|
||
final boolean modusLocked;
|
||
|
||
final LocalTime forcedStart;
|
||
final boolean startLocked;
|
||
|
||
TypeRule(String forcedInhalt, boolean inhaltLocked,
|
||
LocalDate forcedDate, boolean dateLocked,
|
||
Integer forcedDauer, boolean dauerLocked,
|
||
String forcedModus, boolean modusLocked,
|
||
LocalTime forcedStart, boolean startLocked) {
|
||
|
||
this.forcedInhalt = forcedInhalt;
|
||
this.inhaltLocked = inhaltLocked;
|
||
|
||
this.forcedDate = forcedDate;
|
||
this.dateLocked = dateLocked;
|
||
|
||
this.forcedDauer = forcedDauer;
|
||
this.dauerLocked = dauerLocked;
|
||
|
||
this.forcedModus = forcedModus;
|
||
this.modusLocked = modusLocked;
|
||
|
||
this.forcedStart = forcedStart;
|
||
this.startLocked = startLocked;
|
||
}
|
||
}
|
||
|
||
private static final java.util.Map<String, TypeRule> RULES = java.util.Map.of(
|
||
"fuehrung",
|
||
new TypeRule(
|
||
"Bibliotheksnutzung", true, // forcedInhalt + locked
|
||
null, false, // date unchanged
|
||
45, true, // Dauer
|
||
"praesenz", true, // Modus
|
||
null, false // Startzeit unchanged
|
||
),
|
||
|
||
"schulung",
|
||
new TypeRule(
|
||
null, false, // Inhalt unchanged
|
||
null, false, // Date unchanged
|
||
null, false, // Dauer unchanged
|
||
null, false, // Modus unchanged
|
||
null, false // Startzeit unchanged
|
||
),
|
||
|
||
"coffeelecture",
|
||
new TypeRule(
|
||
null, false, // identical to schulung
|
||
null, false,
|
||
null, false,
|
||
null, false,
|
||
null, false
|
||
),
|
||
|
||
"selbstlerneinheit",
|
||
new TypeRule(
|
||
null, false, // Inhalt permitted
|
||
java.time.LocalDate.now(), true, // date forced to today + locked
|
||
0, true, // Dauer = 0 + locked
|
||
"online", true, // Modus = online + locked
|
||
java.time.LocalTime.of(0, 0), true // Start = 00:00 + locked
|
||
)
|
||
);
|
||
|
||
private void applyTypeRule(String typ) {
|
||
TypeRule r = RULES.get(typ);
|
||
if (r == null) return;
|
||
|
||
// Inhalt
|
||
if (r.forcedInhalt != null) inhaltstypBox.setValue(r.forcedInhalt);
|
||
inhaltstypBox.setDisable(r.inhaltLocked);
|
||
|
||
// Date
|
||
if (r.forcedDate != null) dateField.setValue(r.forcedDate);
|
||
dateField.setDisable(r.dateLocked);
|
||
|
||
// Dauer
|
||
if (r.forcedDauer != null) dauerField.setText(String.valueOf(r.forcedDauer));
|
||
dauerField.setDisable(r.dauerLocked);
|
||
|
||
// Modus
|
||
if (r.forcedModus != null) modusBox.setValue(r.forcedModus);
|
||
modusBox.setDisable(r.modusLocked);
|
||
|
||
// Startzeit
|
||
if (r.forcedStart != null) startzeitSpinner.getValueFactory().setValue(r.forcedStart);
|
||
startzeitSpinner.setDisable(r.startLocked);
|
||
}
|
||
|
||
|
||
|
||
|
||
private void updateFieldsForVeranstaltungstyp() {
|
||
String typ = veranstaltungstypBox.getValue();
|
||
if (typ == null) return;
|
||
|
||
// FULL reset
|
||
resetDefaultsForTypeChange();
|
||
|
||
// Unlock EVERYTHING before applying rules (important!)
|
||
inhaltstypBox.setDisable(false);
|
||
dateField.setDisable(false);
|
||
dauerField.setDisable(false);
|
||
modusBox.setDisable(false);
|
||
startzeitSpinner.setDisable(false);
|
||
|
||
// Apply rule set
|
||
applyTypeRule(typ);
|
||
|
||
// Generate title based on NEW values
|
||
titelField.setText(generateTitle(
|
||
typ,
|
||
inhaltstypBox.getValue()
|
||
));
|
||
|
||
validateFields();
|
||
}
|
||
|
||
private String generateTitle(String typ, String inhalt) {
|
||
if (typ == null) return "";
|
||
|
||
// Führung: fixed title
|
||
if (typ.equals("fuehrung")) {
|
||
return "Bibliotheksführung";
|
||
}
|
||
|
||
// If no topic chosen yet → no title
|
||
if (inhalt == null || inhalt.isBlank()) {
|
||
return "";
|
||
}
|
||
|
||
switch (typ) {
|
||
|
||
case "schulung":
|
||
return "Schulung: " + inhalt;
|
||
|
||
case "coffeelecture":
|
||
return "Coffee Lecture: " + inhalt;
|
||
|
||
case "selbstlerneinheit":
|
||
return "Selbstlerneinheit: " + inhalt;
|
||
|
||
default:
|
||
return "";
|
||
}
|
||
}
|
||
|
||
|
||
private void resetDefaultsForTypeChange() {
|
||
// Reset ONLY the fields the user requested:
|
||
inhaltstypBox.setValue(null);
|
||
inhaltstypBox.setDisable(false);
|
||
|
||
titelField.clear();
|
||
|
||
modusBox.setValue(null);
|
||
modusBox.setDisable(false);
|
||
}
|
||
|
||
private void updateTitleFromInhaltstyp() {
|
||
String typ = veranstaltungstypBox.getValue();
|
||
String inhalt = inhaltstypBox.getValue();
|
||
|
||
titelField.setText(generateTitle(typ, inhalt));
|
||
|
||
validateFields();
|
||
}
|
||
|
||
public void refreshTrainingTypes() {
|
||
fillInhaltstypBoxFromDatabase();
|
||
}
|
||
|
||
private void saveTraining() {
|
||
try {
|
||
if (!validateFields()) return;
|
||
|
||
String veranstaltungstyp = veranstaltungstypBox.getValue();
|
||
String inhaltstyp = inhaltstypBox.getValue();
|
||
String titel = titelField.getText();
|
||
LocalDate datum = dateField.getValue();
|
||
|
||
LocalTime startzeit;
|
||
try {
|
||
startzeit = LocalTime.parse(startzeitSpinner.getEditor().getText(), TIME_FORMAT);
|
||
} catch (DateTimeParseException e) {
|
||
markField(startzeitSpinner.getEditor(), false);
|
||
validateFields();
|
||
return;
|
||
}
|
||
|
||
Integer dauer = parseIntSafe(dauerField.getText());
|
||
String modus = modusBox.getValue();
|
||
Integer teilnehmende = parseIntSafe(teilnehmerAnzahlField.getText());
|
||
String teilnehmendeTyp = teilnehmerTypBox.getValue();
|
||
String personInput = durchfuehrendePersonBox.getEditor().getText();
|
||
if (personInput == null || personInput.isBlank())
|
||
personInput = durchfuehrendePersonBox.getValue();
|
||
String person = PersonMapping.toKuerzel(personInput);
|
||
String grund = ausgefallenBox.getValue();
|
||
String kommentar = kommentarField.getText();
|
||
|
||
String datumStr = datum.format(DateTimeFormatter.ISO_LOCAL_DATE);
|
||
String startzeitStr = startzeit.format(TIME_FORMAT);
|
||
|
||
Double evaluation = parseDoubleSafe(evaluationField.getText());
|
||
if (evaluation != null && (evaluation < 1.0 || evaluation > 6.0)) {
|
||
new Alert(Alert.AlertType.WARNING, "Bewertung muss zwischen 1.0 und 6.0 liegen.").showAndWait();
|
||
return;
|
||
}
|
||
|
||
|
||
TrainingDAO dao = new TrainingDAO();
|
||
dao.insertTraining(
|
||
datumStr,
|
||
startzeitStr,
|
||
dauer,
|
||
modus,
|
||
teilnehmende,
|
||
teilnehmendeTyp,
|
||
person,
|
||
grund,
|
||
titel,
|
||
inhaltstyp,
|
||
veranstaltungstyp,
|
||
kommentar,
|
||
0,
|
||
evaluation
|
||
);
|
||
|
||
new Alert(Alert.AlertType.INFORMATION, "Datensatz erfolgreich gespeichert.").showAndWait();
|
||
resetForm();
|
||
|
||
} catch (Exception ex) {
|
||
new Alert(Alert.AlertType.ERROR, "Fehler beim Speichern: " + ex.getMessage()).showAndWait();
|
||
ex.printStackTrace();
|
||
}
|
||
}
|
||
|
||
// Hilfsmethoden ------------------------------------------------------------------
|
||
|
||
private void applyIntegerFilter(TextField field) {
|
||
field.textProperty().addListener((obs, old, neu) -> {
|
||
if (!neu.matches("\\d*")) field.setText(neu.replaceAll("[^\\d]", ""));
|
||
});
|
||
}
|
||
|
||
private void applyDecimalFilter(TextField field) {
|
||
field.textProperty().addListener((obs, old, neu) -> {
|
||
if (!neu.matches("\\d*(\\.\\d*)?")) field.setText(neu.replaceAll("[^\\d.]", ""));
|
||
});
|
||
}
|
||
|
||
|
||
private Integer parseIntSafe(String text) {
|
||
if (text == null || text.isBlank()) return null;
|
||
try {
|
||
return Integer.parseInt(text.trim());
|
||
} catch (NumberFormatException e) {
|
||
return null;
|
||
}
|
||
}
|
||
|
||
private Double parseDoubleSafe(String text) {
|
||
if (text == null || text.isBlank()) return null;
|
||
try {
|
||
return Double.parseDouble(text.trim());
|
||
} catch (NumberFormatException e) {
|
||
return null;
|
||
}
|
||
}
|
||
|
||
|
||
|
||
private boolean validateFields() {
|
||
boolean valid = true;
|
||
|
||
valid &= markField(veranstaltungstypBox, veranstaltungstypBox.getValue() != null);
|
||
valid &= markField(inhaltstypBox, inhaltstypBox.getValue() != null && !inhaltstypBox.getValue().isBlank());
|
||
valid &= markField(titelField, !titelField.getText().isBlank());
|
||
valid &= markField(dateField, dateField.getValue() != null);
|
||
valid &= markField(dauerField, !dauerField.getText().isBlank());
|
||
valid &= markField(modusBox, modusBox.getValue() != null);
|
||
valid &= markField(teilnehmerAnzahlField, !teilnehmerAnzahlField.getText().isBlank());
|
||
valid &= markField(teilnehmerTypBox, teilnehmerTypBox.getValue() != null);
|
||
|
||
String timeText = startzeitSpinner.getEditor().getText();
|
||
boolean validTime = false;
|
||
if (timeText != null && !timeText.isBlank()) {
|
||
try {
|
||
LocalTime.parse(timeText, TIME_FORMAT);
|
||
validTime = true;
|
||
} catch (DateTimeParseException e) {
|
||
validTime = false;
|
||
}
|
||
}
|
||
valid &= markField(startzeitSpinner.getEditor(), validTime);
|
||
|
||
speichernButton.setDisable(!valid);
|
||
return valid;
|
||
}
|
||
|
||
private boolean markField(Control field, boolean ok) {
|
||
if (ok) {
|
||
field.setStyle("");
|
||
} else {
|
||
field.setStyle("-fx-border-color: red; -fx-border-width: 2;");
|
||
}
|
||
return ok;
|
||
}
|
||
|
||
private void resetForm() {
|
||
veranstaltungstypBox.setValue(null);
|
||
inhaltstypBox.setDisable(false);
|
||
inhaltstypBox.setValue(null);
|
||
titelField.clear();
|
||
dateField.setValue(null);
|
||
startzeitSpinner.getValueFactory().setValue(LocalTime.of(10, 0));
|
||
dauerField.clear();
|
||
modusBox.setValue(null);
|
||
teilnehmerAnzahlField.clear();
|
||
teilnehmerTypBox.setValue(null);
|
||
ausgefallenBox.setValue("");
|
||
kommentarField.clear();
|
||
validateFields();
|
||
}
|
||
}
|