From 60547ec442bce0068a2092c5311c93132157b967 Mon Sep 17 00:00:00 2001 From: Sven Carstensen Date: Wed, 13 Aug 2025 12:52:22 +0200 Subject: [PATCH] Erweiterungen --- .../pages/add_job/ui/view/AddJobView.java | 238 +++++++++++++----- 1 file changed, 179 insertions(+), 59 deletions(-) diff --git a/src/main/java/de/assecutor/votianlt/pages/add_job/ui/view/AddJobView.java b/src/main/java/de/assecutor/votianlt/pages/add_job/ui/view/AddJobView.java index 176dae5..897b89d 100644 --- a/src/main/java/de/assecutor/votianlt/pages/add_job/ui/view/AddJobView.java +++ b/src/main/java/de/assecutor/votianlt/pages/add_job/ui/view/AddJobView.java @@ -5,6 +5,8 @@ import com.vaadin.flow.component.button.ButtonVariant; import com.vaadin.flow.component.checkbox.Checkbox; import com.vaadin.flow.component.combobox.ComboBox; +import com.vaadin.flow.component.datepicker.DatePicker; +import com.vaadin.flow.component.timepicker.TimePicker; import com.vaadin.flow.component.html.Div; import com.vaadin.flow.component.html.H3; import com.vaadin.flow.component.html.Main; @@ -17,6 +19,8 @@ import com.vaadin.flow.component.orderedlayout.HorizontalLayout; import com.vaadin.flow.component.orderedlayout.VerticalLayout; import com.vaadin.flow.component.textfield.TextField; import com.vaadin.flow.component.ClientCallable; +import com.vaadin.flow.component.textfield.IntegerField; +import com.vaadin.flow.component.textfield.NumberField; import com.vaadin.flow.component.UI; import com.vaadin.flow.component.dnd.DragSource; import com.vaadin.flow.component.dnd.DropTarget; @@ -24,6 +28,7 @@ import com.vaadin.flow.component.dnd.EffectAllowed; import com.vaadin.flow.data.binder.Binder; import com.vaadin.flow.data.binder.ValidationException; import com.vaadin.flow.router.Menu; +import com.vaadin.flow.component.Component; import com.vaadin.flow.router.PageTitle; import com.vaadin.flow.router.Route; import com.vaadin.flow.theme.lumo.LumoUtility; @@ -47,10 +52,10 @@ public class AddJobView extends Main { // Customer selection private ComboBox customerSelection; private Button preloadAddressButton; - + // Required fields notice private Span requiredFieldsNotice; - + // Pickup address fields private TextField pickupCompany; private ComboBox pickupSalutation; @@ -63,7 +68,7 @@ public class AddJobView extends Main { private TextField pickupZip; private TextField pickupCity; private Checkbox savePickupAddress; - + // Delivery address fields private TextField deliveryCompany; private ComboBox deliverySalutation; @@ -76,27 +81,25 @@ public class AddJobView extends Main { private TextField deliveryZip; private TextField deliveryCity; private Checkbox saveDeliveryAddress; - + // Digital processing private Checkbox digitalProcessing; private ComboBox appUser; // Submit button private Button submitButton; - + // Stage sections for drag and drop private VerticalLayout pickupSection; private VerticalLayout deliverySection; private HorizontalLayout mainLayout; - + // Drag sources for dynamic control private DragSource pickupDragSource; private DragSource deliveryDragSource; private final Binder binder = new Binder<>(Job.class); - - public AddJobView(AddJobService addJobService) { this.addJobService = addJobService; initializeComponents(); @@ -111,11 +114,11 @@ public class AddJobView extends Main { customerSelection.setItems("Kunde01 | KOTVor K01Nach"); customerSelection.setPlaceholder("Wählen Sie einen Auftraggeber aus..."); customerSelection.setWidthFull(); - + preloadAddressButton = new Button("Vorbelegte Adressfelder leeren"); preloadAddressButton.addThemeVariants(ButtonVariant.LUMO_PRIMARY); preloadAddressButton.addClickListener(event -> clearAllFields()); - + // Pickup address pickupCompany = new TextField("Firma"); pickupCompany.setPlaceholder("z.B. IKEA, McDonald's, DHL..."); @@ -146,7 +149,7 @@ public class AddJobView extends Main { pickupCity.setPlaceholder("Hamburg"); pickupCity.setRequiredIndicatorVisible(true); savePickupAddress = new Checkbox("Die Adresse für zukünftige Aufträge speichern."); - + // Delivery address deliveryCompany = new TextField("Firma"); deliveryCompany.setPlaceholder("z.B. EDEKA, Bauhaus, Amazon..."); @@ -191,12 +194,12 @@ public class AddJobView extends Main { private void setupLayout() { setSizeFull(); - addClassNames(LumoUtility.BoxSizing.BORDER, LumoUtility.Display.FLEX, - LumoUtility.FlexDirection.COLUMN, LumoUtility.Padding.MEDIUM, + addClassNames(LumoUtility.BoxSizing.BORDER, LumoUtility.Display.FLEX, + LumoUtility.FlexDirection.COLUMN, LumoUtility.Padding.MEDIUM, LumoUtility.Gap.SMALL); add(new ViewToolbar("Neuen Auftrag anlegen")); - + // Customer selection section HorizontalLayout customerLayout = new HorizontalLayout(); customerLayout.setWidthFull(); @@ -204,7 +207,7 @@ public class AddJobView extends Main { customerLayout.add(customerSelection, preloadAddressButton); customerSelection.setWidth("70%"); preloadAddressButton.setWidth("30%"); - + add(customerLayout); // Main content layout with two equal columns (50% each) @@ -222,19 +225,70 @@ public class AddJobView extends Main { deliverySection = createDeliverySection(); deliverySection.setWidth("50%"); deliveryDragSource = configureDragAndDrop(deliverySection, "delivery"); - + // Setup focus listeners for input fields setupInputFieldFocusListeners(); mainLayout.add(pickupSection, deliverySection); - + add(mainLayout); - - // Digital processing section - VerticalLayout digitalSection = new VerticalLayout(); - digitalSection.setSpacing(false); - digitalSection.add(digitalProcessing, appUser); - add(digitalSection); + + // Section under the stages (centered) + VerticalLayout belowSection = new VerticalLayout(); + belowSection.setWidthFull(); + belowSection.setPadding(false); + belowSection.setSpacing(true); + belowSection.setDefaultHorizontalComponentAlignment(FlexComponent.Alignment.CENTER); + + // Container with fixed width to center content + VerticalLayout content = new VerticalLayout(); + content.setPadding(false); + content.setSpacing(true); + content.setWidth("720px"); + content.setDefaultHorizontalComponentAlignment(FlexComponent.Alignment.STRETCH); + + // Row: Digital processing + App user + HorizontalLayout digitalRow = new HorizontalLayout(); + digitalRow.setWidthFull(); + digitalRow.setAlignItems(FlexComponent.Alignment.BASELINE); + digitalRow.setJustifyContentMode(FlexComponent.JustifyContentMode.START); + digitalProcessing.getStyle().set("margin-right", "12px"); + digitalRow.add(digitalProcessing); + + // App user selector full width + appUser.setWidthFull(); + content.add(digitalRow, appUser); + + // Appointment (Pickup) + H3 pickupApptTitle = new H3("Termin (Abholung)"); + pickupApptTitle.getStyle().set("margin", "0"); + DatePicker pickupDate = new DatePicker("Datum"); + pickupDate.setRequiredIndicatorVisible(true); + TimePicker pickupTime = new TimePicker("Uhrzeit"); + HorizontalLayout pickupApptRow = new HorizontalLayout(pickupDate, pickupTime); + pickupApptRow.setWidthFull(); + pickupApptRow.setSpacing(true); + pickupDate.setWidth("50%"); + pickupTime.setWidth("50%"); + content.add(pickupApptTitle, pickupApptRow); + + // Appointment (Delivery) + H3 deliveryApptTitle = new H3("Termin (Lieferung)"); + deliveryApptTitle.getStyle().set("margin", "0"); + DatePicker deliveryDate = new DatePicker("Datum"); + deliveryDate.setRequiredIndicatorVisible(true); + TimePicker deliveryTime = new TimePicker("Uhrzeit"); + HorizontalLayout deliveryApptRow = new HorizontalLayout(deliveryDate, deliveryTime); + deliveryApptRow.setWidthFull(); + deliveryApptRow.setSpacing(true); + deliveryDate.setWidth("50%"); + deliveryTime.setWidth("50%"); + content.add(deliveryApptTitle, deliveryApptRow); + + belowSection.add(content); + add(belowSection); + // Ladung – Bereich vor dem Button + add(createCargoSection()); // Submit button HorizontalLayout submitLayout = new HorizontalLayout(); @@ -291,6 +345,7 @@ public class AddJobView extends Main { section.add(pickupAddressAddition); + // zip/city row HorizontalLayout zipCityLayout = new HorizontalLayout(); zipCityLayout.setWidthFull(); zipCityLayout.setSpacing(true); @@ -387,27 +442,27 @@ public class AddJobView extends Main { binder.forField(deliveryFirstName) .asRequired("") .bind(Job::getDeliveryFirstName, Job::setDeliveryFirstName); - + binder.forField(deliveryLastName) .asRequired("") .bind(Job::getDeliveryLastName, Job::setDeliveryLastName); - + binder.forField(deliveryStreet) .asRequired("") .bind(Job::getDeliveryStreet, Job::setDeliveryStreet); - + binder.forField(deliveryHouseNumber) .asRequired("") .bind(Job::getDeliveryHouseNumber, Job::setDeliveryHouseNumber); - + binder.forField(deliveryZip) .asRequired("") .bind(Job::getDeliveryZip, Job::setDeliveryZip); - + binder.forField(deliveryCity) .asRequired("") .bind(Job::getDeliveryCity, Job::setDeliveryCity); - + // Bind optional fields without validation binder.bind(customerSelection, Job::getCustomerSelection, Job::setCustomerSelection); binder.bind(pickupCompany, Job::getPickupCompany, Job::setPickupCompany); @@ -415,56 +470,56 @@ public class AddJobView extends Main { binder.bind(pickupPhone, Job::getPickupPhone, Job::setPickupPhone); binder.bind(pickupAddressAddition, Job::getPickupAddressAddition, Job::setPickupAddressAddition); binder.bind(savePickupAddress, Job::isSavePickupAddress, Job::setSavePickupAddress); - + binder.bind(deliveryCompany, Job::getDeliveryCompany, Job::setDeliveryCompany); binder.bind(deliverySalutation, Job::getDeliverySalutation, Job::setDeliverySalutation); binder.bind(deliveryPhone, Job::getDeliveryPhone, Job::setDeliveryPhone); binder.bind(deliveryAddressAddition, Job::getDeliveryAddressAddition, Job::setDeliveryAddressAddition); binder.bind(saveDeliveryAddress, Job::isSaveDeliveryAddress, Job::setSaveDeliveryAddress); - + binder.bind(digitalProcessing, Job::isDigitalProcessing, Job::setDigitalProcessing); binder.bind(appUser, Job::getAppUser, Job::setAppUser); - + // Set up validation triggers and visual styling setupValidationTriggers(); - + // Trigger initial validation when view is displayed triggerValidation(); } - + private void setupValidationTriggers() { // List of all required fields TextField[] requiredFields = { pickupFirstName, pickupLastName, pickupStreet, pickupHouseNumber, pickupZip, pickupCity, deliveryFirstName, deliveryLastName, deliveryStreet, deliveryHouseNumber, deliveryZip, deliveryCity }; - + // Add value change listeners to trigger validation on every change for (TextField field : requiredFields) { field.addValueChangeListener(event -> { triggerValidation(); updateFieldStyling(field); }); - + // Add focus listeners for immediate visual feedback field.addFocusListener(event -> updateFieldStyling(field)); field.addBlurListener(event -> updateFieldStyling(field)); - + // Set initial styling updateFieldStyling(field); } } - + private void triggerValidation() { // Create a temporary job object to trigger validation Job tempJob = new Job(); binder.validate(); } - + private void updateFieldStyling(TextField field) { String value = field.getValue(); boolean isEmpty = value == null || value.trim().isEmpty(); - + if (isEmpty) { // Apply transparent red background only to the input field, not the label field.getStyle().set("--vaadin-input-field-background", "rgba(255, 0, 0, 0.1)"); @@ -479,7 +534,7 @@ public class AddJobView extends Main { private void submit() { try { Job job = new Job(); - + // Validate all required fields using the binder if (binder.writeBeanIfValid(job)) { // All validations passed, save the job @@ -513,7 +568,7 @@ public class AddJobView extends Main { private void clearForm() { // Reset binder to clear validation state binder.readBean(new Job()); - + // Customer selection customerSelection.clear(); @@ -644,7 +699,72 @@ public class AddJobView extends Main { if (job.getCustomerSelection() != null) { customerSelection.setValue(job.getCustomerSelection()); } + } + private Component createCargoSection() { + VerticalLayout wrapper = new VerticalLayout(); + wrapper.setWidthFull(); + wrapper.setSpacing(true); + + VerticalLayout cargoArea = new VerticalLayout(); + cargoArea.setWidthFull(); + cargoArea.setSpacing(true); + cargoArea.getStyle().set("background", "var(--lumo-base-color)"); + cargoArea.getStyle().set("border", "1px solid var(--lumo-contrast-20pct)"); + cargoArea.getStyle().set("border-radius", "var(--lumo-border-radius-m)"); + cargoArea.getStyle().set("padding", "var(--lumo-space-m)"); + + VerticalLayout cargoList = new VerticalLayout(); + cargoList.setPadding(false); + cargoList.setSpacing(true); + cargoArea.add(cargoList); + + java.util.function.BiConsumer> addCargoRow = (iconName, afterCreate) -> { + HorizontalLayout row = new HorizontalLayout(); + row.setWidthFull(); + row.setAlignItems(FlexComponent.Alignment.END); + + TextField desc = new TextField("Beschreibung"); + desc.setPlaceholder("z. B. Gitterboxpalette, Paket …"); + desc.setWidth("40%"); + + IntegerField qty = new IntegerField("Anzahl"); + qty.setMin(1); + qty.setValue(1); + qty.setWidth("10%"); + + NumberField weight = new NumberField("Gewicht"); + weight.setSuffixComponent(new Span("kg")); + weight.setWidth("15%"); + + NumberField len = new NumberField("Länge"); + len.setSuffixComponent(new Span("mm")); + len.setWidth("12%"); + NumberField wid = new NumberField("Breite"); + wid.setSuffixComponent(new Span("mm")); + wid.setWidth("12%"); + NumberField hei = new NumberField("Höhe"); + hei.setSuffixComponent(new Span("mm")); + hei.setWidth("12%"); + + Button remove = new Button(new Icon(VaadinIcon.CLOSE_SMALL)); + remove.addThemeVariants(ButtonVariant.LUMO_ERROR, ButtonVariant.LUMO_TERTIARY); + remove.addClickListener(e -> cargoList.remove(row)); + + row.add(desc, qty, weight, len, wid, hei, remove); + cargoList.add(row); + if (afterCreate != null) afterCreate.accept(row); + }; + + addCargoRow.accept("gitterbox", r -> {}); + addCargoRow.accept("paket", r -> {}); + addCargoRow.accept("", r -> {}); + + wrapper.add(cargoArea); + return wrapper; + } + + private void populateFromJob(Job job) { // Pickup address if (job.getPickupCompany() != null) pickupCompany.setValue(job.getPickupCompany()); if (job.getPickupSalutation() != null) pickupSalutation.setValue(job.getPickupSalutation()); @@ -812,7 +932,7 @@ public class AddJobView extends Main { // Customer selection customerSelection.addFocusListener(e -> disableDragSources()); customerSelection.addBlurListener(e -> enableDragSources()); - + // Pickup fields pickupCompany.addFocusListener(e -> disableDragSources()); pickupCompany.addBlurListener(e -> enableDragSources()); @@ -834,7 +954,7 @@ public class AddJobView extends Main { pickupZip.addBlurListener(e -> enableDragSources()); pickupCity.addFocusListener(e -> disableDragSources()); pickupCity.addBlurListener(e -> enableDragSources()); - + // Delivery fields deliveryCompany.addFocusListener(e -> disableDragSources()); deliveryCompany.addBlurListener(e -> enableDragSources()); @@ -856,12 +976,12 @@ public class AddJobView extends Main { deliveryZip.addBlurListener(e -> enableDragSources()); deliveryCity.addFocusListener(e -> disableDragSources()); deliveryCity.addBlurListener(e -> enableDragSources()); - + // Digital processing appUser.addFocusListener(e -> disableDragSources()); appUser.addBlurListener(e -> enableDragSources()); } - + /** * Deaktiviert alle Drag-Sources durch CSS */ @@ -875,7 +995,7 @@ public class AddJobView extends Main { deliverySection.getElement().setAttribute("draggable", "false"); } } - + /** * Aktiviert alle Drag-Sources durch CSS */ @@ -898,13 +1018,13 @@ public class AddJobView extends Main { DragSource dragSource = DragSource.create(section); dragSource.setEffectAllowed(EffectAllowed.MOVE); dragSource.setDragData(sectionType); - + // Visual feedback beim Drag-Start dragSource.addDragStartListener(event -> { section.getStyle().set("opacity", "0.5"); section.getStyle().set("border", "2px dashed var(--lumo-primary-color)"); }); - + // Visual feedback beim Drag-Ende dragSource.addDragEndListener(event -> { section.getStyle().remove("opacity"); @@ -914,35 +1034,35 @@ public class AddJobView extends Main { // Drop Target konfigurieren DropTarget dropTarget = DropTarget.create(section); dropTarget.setActive(true); - + // Drop Handler - Etappen tauschen dropTarget.addDropListener(event -> { Object draggedData = event.getDragData().orElse(null); String draggedSectionType = draggedData != null ? draggedData.toString() : ""; - + if (!sectionType.equals(draggedSectionType)) { swapStages(); } - + // Styles zurücksetzen section.getStyle().set("background-color", "var(--lumo-base-color)"); section.getStyle().set("border", "1px solid var(--lumo-contrast-20pct)"); }); - + // Visual feedback bei Dragover mit JavaScript section.getElement().addEventListener("dragover", e -> { section.getStyle().set("background-color", "var(--lumo-primary-color-10pct)"); section.getStyle().set("border", "2px solid var(--lumo-primary-color)"); }); - + section.getElement().addEventListener("dragleave", e -> { section.getStyle().set("background-color", "var(--lumo-base-color)"); section.getStyle().set("border", "1px solid var(--lumo-contrast-20pct)"); }); - + return dragSource; } - + /** * Tauscht die Inhalte der beiden Etappen (Pickup und Delivery) */ @@ -959,7 +1079,7 @@ public class AddJobView extends Main { String tempZip = pickupZip.getValue(); String tempCity = pickupCity.getValue(); Boolean tempSaveAddress = savePickupAddress.getValue(); - + // Pickup mit Delivery-Werten überschreiben pickupCompany.setValue(deliveryCompany.getValue()); pickupSalutation.setValue(deliverySalutation.getValue()); @@ -972,7 +1092,7 @@ public class AddJobView extends Main { pickupZip.setValue(deliveryZip.getValue()); pickupCity.setValue(deliveryCity.getValue()); savePickupAddress.setValue(saveDeliveryAddress.getValue()); - + // Delivery mit zwischengespeicherten Pickup-Werten überschreiben deliveryCompany.setValue(tempCompany); deliverySalutation.setValue(tempSalutation); @@ -985,7 +1105,7 @@ public class AddJobView extends Main { deliveryZip.setValue(tempZip); deliveryCity.setValue(tempCity); saveDeliveryAddress.setValue(tempSaveAddress); - + // Benutzer-Feedback Notification.show("Etappen wurden erfolgreich getauscht!", 3000, Notification.Position.BOTTOM_CENTER); }