From 20cd516317780975f394ae4c0c17a78134dcb7d6 Mon Sep 17 00:00:00 2001 From: Sven Carstensen Date: Wed, 7 Jan 2026 09:56:34 +0100 Subject: [PATCH] Erweiterungen --- .../votianlt/model/task/BaseTask.java | 3 + .../votianlt/pages/view/AddJobView.java | 133 ++++++++++++++++-- .../pages/view/AuthenticatedStartView.java | 17 ++- .../votianlt/pages/view/StartView.java | 1 + 4 files changed, 135 insertions(+), 19 deletions(-) diff --git a/src/main/java/de/assecutor/votianlt/model/task/BaseTask.java b/src/main/java/de/assecutor/votianlt/model/task/BaseTask.java index 5af066e..b2ea33a 100644 --- a/src/main/java/de/assecutor/votianlt/model/task/BaseTask.java +++ b/src/main/java/de/assecutor/votianlt/model/task/BaseTask.java @@ -35,6 +35,9 @@ public abstract class BaseTask { @Field("task_order") private Integer taskOrder = 0; + @Field("description") + private String description; + @Field("completed") private boolean completed = false; diff --git a/src/main/java/de/assecutor/votianlt/pages/view/AddJobView.java b/src/main/java/de/assecutor/votianlt/pages/view/AddJobView.java index fab80ff..714d010 100644 --- a/src/main/java/de/assecutor/votianlt/pages/view/AddJobView.java +++ b/src/main/java/de/assecutor/votianlt/pages/view/AddJobView.java @@ -391,9 +391,10 @@ public class AddJobView extends Main { .setWeekdaysShort(java.util.Arrays.asList( "So", "Mo", "Di", "Mi", "Do", "Fr", "Sa"))); - // Submit button + // Submit button - initially disabled until all required fields are valid submitButton = new Button("Auftrag anlegen", event -> submit()); submitButton.addThemeVariants(ButtonVariant.LUMO_PRIMARY); + submitButton.setEnabled(false); } // Testdaten entfernt @@ -951,6 +952,16 @@ public class AddJobView extends Main { private void triggerValidation() { // Create a temporary job object to trigger validation binder.validate(); + + // Update submit button state based on all validation checks + if (submitButton != null) { + boolean hasErrors = hasAddressValidationErrors() + || hasAppointmentValidationErrors() + || hasCargoValidationErrors() + || hasPriceValidationErrors() + || hasTasksValidationErrors(); + submitButton.setEnabled(!hasErrors); + } } private void updateFieldStyling(TextField field) { @@ -992,6 +1003,9 @@ public class AddJobView extends Main { } private void updateTabLabel(com.vaadin.flow.component.tabs.Tab tab, String baseLabel, boolean hasErrors) { + if (tab == null) { + return; + } if (hasErrors) { tab.setLabel(baseLabel + " ⚠️"); } else { @@ -1043,7 +1057,28 @@ public class AddJobView extends Main { } private boolean hasTasksValidationErrors() { - // Tasks are optional, so no validation errors for tasks + for (BaseTask task : tasksState) { + // Check if any ConfirmationTask has an empty description (required field) + if (task instanceof ConfirmationTask) { + String description = task.getDescription(); + if (description == null || description.trim().isEmpty()) { + return true; + } + } + // Check if any TodoListTask has at least one non-empty todo item + if (task instanceof TodoListTask todoListTask) { + List todoItems = todoListTask.getTodoItems(); + if (todoItems == null || todoItems.isEmpty()) { + return true; + } + // Check if at least one todo item is non-empty + boolean hasValidTodoItem = todoItems.stream() + .anyMatch(item -> item != null && !item.trim().isEmpty()); + if (!hasValidTodoItem) { + return true; + } + } + } return false; } @@ -1275,7 +1310,8 @@ public class AddJobView extends Main { cargoItemsState.remove(idx); } cargoList.remove(row); - updateTabLabels(); // Update tab validation when cargo item is removed + triggerValidation(); + updateTabLabels(); }); row.add(desc, qty, weight, len, wid, hei, remove); @@ -1317,32 +1353,38 @@ public class AddJobView extends Main { desc.addValueChangeListener(ev -> { item.setDescription(ev.getValue()); validateField.accept(desc); - updateTabLabels(); // Update tab validation when cargo description changes + triggerValidation(); + updateTabLabels(); }); qty.addValueChangeListener(ev -> { item.setQuantity(ev.getValue()); validateField.accept(qty); - updateTabLabels(); // Update tab validation when cargo quantity changes + triggerValidation(); + updateTabLabels(); }); weight.addValueChangeListener(ev -> { item.setWeightKg(ev.getValue()); validateField.accept(weight); - updateTabLabels(); // Update tab validation when cargo weight changes + triggerValidation(); + updateTabLabels(); }); len.addValueChangeListener(ev -> { item.setLengthMm(ev.getValue()); validateField.accept(len); - updateTabLabels(); // Update tab validation when cargo length changes + triggerValidation(); + updateTabLabels(); }); wid.addValueChangeListener(ev -> { item.setWidthMm(ev.getValue()); validateField.accept(wid); - updateTabLabels(); // Update tab validation when cargo width changes + triggerValidation(); + updateTabLabels(); }); hei.addValueChangeListener(ev -> { item.setHeightMm(ev.getValue()); validateField.accept(hei); - updateTabLabels(); // Update tab validation when cargo height changes + triggerValidation(); + updateTabLabels(); }); // Initial validation @@ -1679,12 +1721,16 @@ public class AddJobView extends Main { } updateTaskConfiguration(configContainer, newTask); + triggerValidation(); + updateTabLabels(); } }); // Set initial configuration taskTypeCombo.setValue(TaskType.CONFIRMATION); updateTaskConfiguration(configContainer, currentTask[0]); + triggerValidation(); + updateTabLabels(); tasksList.add(taskContainer); } @@ -1717,6 +1763,33 @@ public class AddJobView extends Main { switch (taskType) { case CONFIRMATION: + // Description field (required) + TextField descriptionField = new TextField("Beschreibung"); + descriptionField.setPlaceholder("Beschreibung der Aufgabe..."); + descriptionField.setWidthFull(); + descriptionField.setRequiredIndicatorVisible(true); + descriptionField.setValue(task.getDescription() != null ? task.getDescription() : ""); + descriptionField.addValueChangeListener(ev -> { + task.setDescription(ev.getValue()); + // Update field styling based on value + boolean isEmpty = ev.getValue() == null || ev.getValue().trim().isEmpty(); + if (isEmpty) { + descriptionField.getStyle().set("--vaadin-input-field-background", "rgba(255, 0, 0, 0.1)"); + descriptionField.getStyle().set("--vaadin-input-field-border-color", "rgba(255, 0, 0, 0.3)"); + } else { + descriptionField.getStyle().remove("--vaadin-input-field-background"); + descriptionField.getStyle().remove("--vaadin-input-field-border-color"); + } + triggerValidation(); + updateTabLabels(); + }); + // Initial styling for empty field + if (task.getDescription() == null || task.getDescription().trim().isEmpty()) { + descriptionField.getStyle().set("--vaadin-input-field-background", "rgba(255, 0, 0, 0.1)"); + descriptionField.getStyle().set("--vaadin-input-field-border-color", "rgba(255, 0, 0, 0.3)"); + } + + // Button text field TextField buttonTextField = new TextField("Button-Text"); buttonTextField.setPlaceholder("z.B. 'Bestätigen', 'Abgeschlossen'"); buttonTextField.setWidthFull(); @@ -1725,10 +1798,12 @@ public class AddJobView extends Main { buttonTextField.addValueChangeListener(ev -> { // Find the current ConfirmationTask in tasksState and update it for (BaseTask stateTask : tasksState) { - ((ConfirmationTask) stateTask).setButtonText(ev.getValue()); + if (stateTask instanceof ConfirmationTask) { + ((ConfirmationTask) stateTask).setButtonText(ev.getValue()); + } } }); - configContainer.add(buttonTextField); + configContainer.add(descriptionField, buttonTextField); break; case SIGNATURE: @@ -1753,6 +1828,18 @@ public class AddJobView extends Main { todoList.setPadding(false); todoList.setSpacing(true); + // Helper to update todo field styling based on value + java.util.function.Consumer updateTodoFieldStyling = (field) -> { + boolean isEmpty = field.getValue() == null || field.getValue().trim().isEmpty(); + if (isEmpty) { + field.getStyle().set("--vaadin-input-field-background", "rgba(255, 0, 0, 0.1)"); + field.getStyle().set("--vaadin-input-field-border-color", "rgba(255, 0, 0, 0.3)"); + } else { + field.getStyle().remove("--vaadin-input-field-background"); + field.getStyle().remove("--vaadin-input-field-border-color"); + } + }; + java.util.function.Consumer addTodoItem = (v) -> { HorizontalLayout todoRow = new HorizontalLayout(); todoRow.setWidthFull(); @@ -1761,6 +1848,9 @@ public class AddJobView extends Main { TextField todoField = new TextField(); todoField.setPlaceholder("To-Do Punkt"); todoField.setWidth("100%"); + todoField.setRequiredIndicatorVisible(true); + // Initial red styling for empty field + updateTodoFieldStyling.accept(todoField); Button removeTodo = new Button(new Icon(VaadinIcon.CLOSE_SMALL)); removeTodo.addThemeVariants(ButtonVariant.LUMO_ERROR, ButtonVariant.LUMO_TERTIARY); @@ -1773,7 +1863,10 @@ public class AddJobView extends Main { todoRow.setFlexGrow(1, todoField); todoList.add(todoRow); - todoField.addValueChangeListener(ev -> updateTodoItems(todoList, task)); + todoField.addValueChangeListener(ev -> { + updateTodoFieldStyling.accept(todoField); + updateTodoItems(todoList, task); + }); }; Button addTodoBtn = new Button("To-Do Punkt hinzufügen", new Icon(VaadinIcon.PLUS)); @@ -1793,7 +1886,10 @@ public class AddJobView extends Main { TextField todoField = new TextField(); todoField.setPlaceholder("To-Do Punkt"); todoField.setWidth("100%"); + todoField.setRequiredIndicatorVisible(true); todoField.setValue(todoText != null ? todoText : ""); // Set the saved text + // Apply styling based on value + updateTodoFieldStyling.accept(todoField); Button removeTodo = new Button(new Icon(VaadinIcon.CLOSE_SMALL)); removeTodo.addThemeVariants(ButtonVariant.LUMO_ERROR, ButtonVariant.LUMO_TERTIARY); @@ -1806,7 +1902,10 @@ public class AddJobView extends Main { todoRow.setFlexGrow(1, todoField); todoList.add(todoRow); - todoField.addValueChangeListener(ev -> updateTodoItems(todoList, task)); + todoField.addValueChangeListener(ev -> { + updateTodoFieldStyling.accept(todoField); + updateTodoItems(todoList, task); + }); } } else { // Add initial empty todo item if no existing items @@ -1894,6 +1993,10 @@ public class AddJobView extends Main { if (task instanceof TodoListTask) { ((TodoListTask) task).setTodoItems(todoItems); } + + // Trigger validation to update submit button state + triggerValidation(); + updateTabLabels(); } /** @@ -2193,6 +2296,8 @@ public class AddJobView extends Main { } updateTaskConfiguration(configContainer, newTask); + triggerValidation(); + updateTabLabels(); } }); @@ -2201,6 +2306,8 @@ public class AddJobView extends Main { if (taskType != null) { taskTypeCombo.setValue(taskType); updateTaskConfiguration(configContainer, task); + triggerValidation(); + updateTabLabels(); } tasksList.add(taskContainer); diff --git a/src/main/java/de/assecutor/votianlt/pages/view/AuthenticatedStartView.java b/src/main/java/de/assecutor/votianlt/pages/view/AuthenticatedStartView.java index 8af45ba..d3f6852 100644 --- a/src/main/java/de/assecutor/votianlt/pages/view/AuthenticatedStartView.java +++ b/src/main/java/de/assecutor/votianlt/pages/view/AuthenticatedStartView.java @@ -12,7 +12,6 @@ import com.vaadin.flow.router.Route; import de.assecutor.votianlt.security.SecurityService; import de.assecutor.votianlt.pages.base.ui.view.MainLayout; import jakarta.annotation.security.RolesAllowed; -import com.vaadin.flow.component.UI; @Route(value = "dashboard", layout = MainLayout.class) @PageTitle("VotianLT - Dashboard") @@ -96,12 +95,13 @@ public class AuthenticatedStartView extends VerticalLayout { systemIntro.getStyle().set("max-width", "800px"); systemIntro.getStyle().set("margin-bottom", "var(--lumo-space-xl)"); - // Features Grid - HorizontalLayout featuresGrid = new HorizontalLayout(); - featuresGrid.setSpacing(true); - featuresGrid.setJustifyContentMode(FlexComponent.JustifyContentMode.CENTER); - featuresGrid.setDefaultVerticalComponentAlignment(FlexComponent.Alignment.START); + // Features Grid - using Div with CSS flexbox for proper centering + Div featuresGrid = new Div(); + featuresGrid.getStyle().set("display", "flex"); featuresGrid.getStyle().set("flex-wrap", "wrap"); + featuresGrid.getStyle().set("justify-content", "center"); + featuresGrid.getStyle().set("align-items", "flex-start"); + featuresGrid.getStyle().set("gap", "var(--lumo-space-l)"); featuresGrid.getStyle().set("width", "100%"); // Feature Cards @@ -126,6 +126,10 @@ public class AuthenticatedStartView extends VerticalLayout { card.getStyle().set("border-radius", "var(--lumo-border-radius-m)"); card.getStyle().set("box-shadow", "var(--lumo-box-shadow-s)"); card.setWidth("300px"); + card.setMinWidth("300px"); + card.setMaxWidth("300px"); + card.getStyle().set("flex-grow", "0"); + card.getStyle().set("flex-shrink", "0"); Icon icon = iconType.create(); icon.setSize("48px"); @@ -177,6 +181,7 @@ public class AuthenticatedStartView extends VerticalLayout { footer.setDefaultHorizontalComponentAlignment(FlexComponent.Alignment.CENTER); footer.getStyle().set("background-color", "var(--lumo-contrast-10pct)"); footer.getStyle().set("border-top", "1px solid var(--lumo-contrast-20pct)"); + footer.getStyle().set("margin-top", "auto"); HorizontalLayout footerContent = new HorizontalLayout(); footerContent.setDefaultVerticalComponentAlignment(FlexComponent.Alignment.CENTER); diff --git a/src/main/java/de/assecutor/votianlt/pages/view/StartView.java b/src/main/java/de/assecutor/votianlt/pages/view/StartView.java index 6a04186..10719f1 100644 --- a/src/main/java/de/assecutor/votianlt/pages/view/StartView.java +++ b/src/main/java/de/assecutor/votianlt/pages/view/StartView.java @@ -221,6 +221,7 @@ public class StartView extends VerticalLayout implements BeforeEnterObserver { HorizontalLayout featuresGrid = new HorizontalLayout(); featuresGrid.setWidthFull(); featuresGrid.setSpacing(true); + featuresGrid.setJustifyContentMode(FlexComponent.JustifyContentMode.CENTER); featuresGrid.setDefaultVerticalComponentAlignment(FlexComponent.Alignment.START); // Feature Cards