// 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 veranstaltungstypBox = new ComboBox<>(); private final ComboBox 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 modusBox = new ComboBox<>(); private final TextField teilnehmerAnzahlField = new TextField(); private final ComboBox teilnehmerTypBox = new ComboBox<>(); //private final TextField durchfuehrendePersonField = new TextField(); private final ComboBox durchfuehrendePersonBox = new ComboBox<>(); private final ComboBox 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 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 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 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 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(); } }