From de72f44a4e71028aff66ee89230ee4cfd87ab81b Mon Sep 17 00:00:00 2001 From: Sven Carstensen Date: Wed, 13 Aug 2025 10:56:11 +0200 Subject: [PATCH] Erweiterungen --- .../pages/add_job/ui/view/AddJobView.java | 363 +++++++++++++++--- 1 file changed, 318 insertions(+), 45 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 2dd9235..176dae5 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 @@ -18,6 +18,9 @@ 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.UI; +import com.vaadin.flow.component.dnd.DragSource; +import com.vaadin.flow.component.dnd.DropTarget; +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; @@ -80,6 +83,15 @@ public class AddJobView extends Main { // 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); @@ -196,18 +208,23 @@ public class AddJobView extends Main { add(customerLayout); // Main content layout with two equal columns (50% each) - HorizontalLayout mainLayout = new HorizontalLayout(); + mainLayout = new HorizontalLayout(); mainLayout.setWidthFull(); mainLayout.setSpacing(true); mainLayout.setDefaultVerticalComponentAlignment(FlexComponent.Alignment.START); // Left column (50%) - Pickup address section - VerticalLayout pickupSection = createPickupSection(); + pickupSection = createPickupSection(); pickupSection.setWidth("50%"); + pickupDragSource = configureDragAndDrop(pickupSection, "pickup"); // Right column (50%) - Delivery address section - VerticalLayout deliverySection = createDeliverySection(); + deliverySection = createDeliverySection(); deliverySection.setWidth("50%"); + deliveryDragSource = configureDragAndDrop(deliverySection, "delivery"); + + // Setup focus listeners for input fields + setupInputFieldFocusListeners(); mainLayout.add(pickupSection, deliverySection); @@ -366,56 +383,124 @@ public class AddJobView extends Main { } private void setupValidation() { - // Basic validation setup - detailed validation can be added later + // Bind delivery address fields with validation + 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); + binder.bind(pickupSalutation, Job::getPickupSalutation, Job::setPickupSalutation); + 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)"); + field.getStyle().set("--vaadin-input-field-border-color", "rgba(255, 0, 0, 0.3)"); + } else { + // Remove styling when field is filled + field.getStyle().remove("--vaadin-input-field-background"); + field.getStyle().remove("--vaadin-input-field-border-color"); + } } private void submit() { - Job job = new Job(); - - // Set pickup address - job.setPickupCompany(pickupCompany.getValue()); - job.setPickupSalutation(pickupSalutation.getValue()); - job.setPickupFirstName(pickupFirstName.getValue()); - job.setPickupLastName(pickupLastName.getValue()); - job.setPickupPhone(pickupPhone.getValue()); - job.setPickupStreet(pickupStreet.getValue()); - job.setPickupHouseNumber(pickupHouseNumber.getValue()); - job.setPickupAddressAddition(pickupAddressAddition.getValue()); - job.setPickupZip(pickupZip.getValue()); - job.setPickupCity(pickupCity.getValue()); - job.setSavePickupAddress(savePickupAddress.getValue()); - - // Set delivery address - job.setDeliveryCompany(deliveryCompany.getValue()); - job.setDeliverySalutation(deliverySalutation.getValue()); - job.setDeliveryFirstName(deliveryFirstName.getValue()); - job.setDeliveryLastName(deliveryLastName.getValue()); - job.setDeliveryPhone(deliveryPhone.getValue()); - job.setDeliveryStreet(deliveryStreet.getValue()); - job.setDeliveryHouseNumber(deliveryHouseNumber.getValue()); - job.setDeliveryAddressAddition(deliveryAddressAddition.getValue()); - job.setDeliveryZip(deliveryZip.getValue()); - job.setDeliveryCity(deliveryCity.getValue()); - job.setSaveDeliveryAddress(saveDeliveryAddress.getValue()); - - // Set digital processing - job.setDigitalProcessing(digitalProcessing.getValue()); - job.setAppUser(appUser.getValue()); - job.setCustomerSelection(customerSelection.getValue()); - try { - Job savedJob = addJobService.addJob(job); + Job job = new Job(); + + // Validate all required fields using the binder + if (binder.writeBeanIfValid(job)) { + // All validations passed, save the job + Job savedJob = addJobService.addJob(job); - // Erfolgsmeldung anzeigen - Notification successNotification = Notification.show( - "Auftrag erfolgreich erstellt! Auftragsnummer: " + savedJob.getJobNumber()); - successNotification.setDuration(5000); + // Erfolgsmeldung anzeigen + Notification successNotification = Notification.show( + "Auftrag erfolgreich erstellt! Auftragsnummer: " + savedJob.getJobNumber()); + successNotification.setDuration(5000); - // Formular zurücksetzen - clearForm(); + // Formular zurücksetzen + clearForm(); + } else { + // Validation failed, show error message + Notification errorNotification = Notification.show( + "Bitte füllen Sie alle Pflichtfelder aus (markiert mit *)"); + errorNotification.setDuration(5000); + } } catch (Exception e) { - // Fehlermeldung anzeigen + // Other errors Notification errorNotification = Notification.show( "Fehler beim Erstellen des Auftrags: " + e.getMessage()); errorNotification.setDuration(5000); @@ -426,6 +511,9 @@ public class AddJobView extends Main { * Setzt alle Formularfelder zurück */ private void clearForm() { + // Reset binder to clear validation state + binder.readBean(new Job()); + // Customer selection customerSelection.clear(); @@ -717,6 +805,191 @@ public class AddJobView extends Main { ); } + /** + * Konfiguriert Focus-Listener für alle Eingabefelder um Drag-and-Drop zu steuern + */ + private void setupInputFieldFocusListeners() { + // Customer selection + customerSelection.addFocusListener(e -> disableDragSources()); + customerSelection.addBlurListener(e -> enableDragSources()); + + // Pickup fields + pickupCompany.addFocusListener(e -> disableDragSources()); + pickupCompany.addBlurListener(e -> enableDragSources()); + pickupSalutation.addFocusListener(e -> disableDragSources()); + pickupSalutation.addBlurListener(e -> enableDragSources()); + pickupFirstName.addFocusListener(e -> disableDragSources()); + pickupFirstName.addBlurListener(e -> enableDragSources()); + pickupLastName.addFocusListener(e -> disableDragSources()); + pickupLastName.addBlurListener(e -> enableDragSources()); + pickupPhone.addFocusListener(e -> disableDragSources()); + pickupPhone.addBlurListener(e -> enableDragSources()); + pickupStreet.addFocusListener(e -> disableDragSources()); + pickupStreet.addBlurListener(e -> enableDragSources()); + pickupHouseNumber.addFocusListener(e -> disableDragSources()); + pickupHouseNumber.addBlurListener(e -> enableDragSources()); + pickupAddressAddition.addFocusListener(e -> disableDragSources()); + pickupAddressAddition.addBlurListener(e -> enableDragSources()); + pickupZip.addFocusListener(e -> disableDragSources()); + pickupZip.addBlurListener(e -> enableDragSources()); + pickupCity.addFocusListener(e -> disableDragSources()); + pickupCity.addBlurListener(e -> enableDragSources()); + + // Delivery fields + deliveryCompany.addFocusListener(e -> disableDragSources()); + deliveryCompany.addBlurListener(e -> enableDragSources()); + deliverySalutation.addFocusListener(e -> disableDragSources()); + deliverySalutation.addBlurListener(e -> enableDragSources()); + deliveryFirstName.addFocusListener(e -> disableDragSources()); + deliveryFirstName.addBlurListener(e -> enableDragSources()); + deliveryLastName.addFocusListener(e -> disableDragSources()); + deliveryLastName.addBlurListener(e -> enableDragSources()); + deliveryPhone.addFocusListener(e -> disableDragSources()); + deliveryPhone.addBlurListener(e -> enableDragSources()); + deliveryStreet.addFocusListener(e -> disableDragSources()); + deliveryStreet.addBlurListener(e -> enableDragSources()); + deliveryHouseNumber.addFocusListener(e -> disableDragSources()); + deliveryHouseNumber.addBlurListener(e -> enableDragSources()); + deliveryAddressAddition.addFocusListener(e -> disableDragSources()); + deliveryAddressAddition.addBlurListener(e -> enableDragSources()); + deliveryZip.addFocusListener(e -> disableDragSources()); + 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 + */ + private void disableDragSources() { + if (pickupSection != null) { + pickupSection.getStyle().set("pointer-events", "none"); + pickupSection.getElement().setAttribute("draggable", "false"); + } + if (deliverySection != null) { + deliverySection.getStyle().set("pointer-events", "none"); + deliverySection.getElement().setAttribute("draggable", "false"); + } + } + + /** + * Aktiviert alle Drag-Sources durch CSS + */ + private void enableDragSources() { + if (pickupSection != null) { + pickupSection.getStyle().remove("pointer-events"); + pickupSection.getElement().setAttribute("draggable", "true"); + } + if (deliverySection != null) { + deliverySection.getStyle().remove("pointer-events"); + deliverySection.getElement().setAttribute("draggable", "true"); + } + } + + /** + * Konfiguriert Drag-and-Drop für eine Etappen-Sektion + */ + private DragSource configureDragAndDrop(VerticalLayout section, String sectionType) { + // Drag Source konfigurieren + 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"); + section.getStyle().set("border", "1px solid var(--lumo-contrast-20pct)"); + }); + + // 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) + */ + private void swapStages() { + // Alle Pickup-Werte zwischenspeichern + String tempCompany = pickupCompany.getValue(); + String tempSalutation = pickupSalutation.getValue(); + String tempFirstName = pickupFirstName.getValue(); + String tempLastName = pickupLastName.getValue(); + String tempPhone = pickupPhone.getValue(); + String tempStreet = pickupStreet.getValue(); + String tempHouseNumber = pickupHouseNumber.getValue(); + String tempAddressAddition = pickupAddressAddition.getValue(); + 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()); + pickupFirstName.setValue(deliveryFirstName.getValue()); + pickupLastName.setValue(deliveryLastName.getValue()); + pickupPhone.setValue(deliveryPhone.getValue()); + pickupStreet.setValue(deliveryStreet.getValue()); + pickupHouseNumber.setValue(deliveryHouseNumber.getValue()); + pickupAddressAddition.setValue(deliveryAddressAddition.getValue()); + pickupZip.setValue(deliveryZip.getValue()); + pickupCity.setValue(deliveryCity.getValue()); + savePickupAddress.setValue(saveDeliveryAddress.getValue()); + + // Delivery mit zwischengespeicherten Pickup-Werten überschreiben + deliveryCompany.setValue(tempCompany); + deliverySalutation.setValue(tempSalutation); + deliveryFirstName.setValue(tempFirstName); + deliveryLastName.setValue(tempLastName); + deliveryPhone.setValue(tempPhone); + deliveryStreet.setValue(tempStreet); + deliveryHouseNumber.setValue(tempHouseNumber); + deliveryAddressAddition.setValue(tempAddressAddition); + deliveryZip.setValue(tempZip); + deliveryCity.setValue(tempCity); + saveDeliveryAddress.setValue(tempSaveAddress); + + // Benutzer-Feedback + Notification.show("Etappen wurden erfolgreich getauscht!", 3000, Notification.Position.BOTTOM_CENTER); + } + /** * Handler für Google Places Auswahl - wird vom JavaScript aufgerufen */