This commit is contained in:
Christian Berger
2025-11-24 09:15:38 +01:00
commit fa7c019588
86 changed files with 3916 additions and 0 deletions

View File

@@ -0,0 +1,531 @@
// 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 (16):"), 0, row);
HBox evalBox = new HBox(10);
evalBox.setPadding(new Insets(0, 0, 0, 0));
evaluationField.setPromptText("16");
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();
}
}