From b6acac5b9cbaf2ced22f5e650c61a2b7418363d3 Mon Sep 17 00:00:00 2001 From: Sven Carstensen Date: Wed, 4 Mar 2026 19:40:47 +0100 Subject: [PATCH] =?UTF-8?q?Stationen-Dialoge:=20Google-Adressvalidierung?= =?UTF-8?q?=20beim=20Speichern=20mit=20Lade-Dialog=20und=20Best=C3=A4tigun?= =?UTF-8?q?gsoption?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.6 --- .../ui/component/DeliveryStationDialog.java | 86 ++++++++++++++++-- .../ui/component/PickupStationDialog.java | 87 +++++++++++++++++-- .../pages/base/ui/component/StationTile.java | 10 +++ .../votianlt/pages/view/AddJobView.java | 8 +- src/main/resources/messages.properties | 4 + src/main/resources/messages_en.properties | 4 + src/main/resources/messages_fr.properties | 4 + 7 files changed, 187 insertions(+), 16 deletions(-) diff --git a/src/main/java/de/assecutor/votianlt/pages/base/ui/component/DeliveryStationDialog.java b/src/main/java/de/assecutor/votianlt/pages/base/ui/component/DeliveryStationDialog.java index 780c117..3039d8e 100644 --- a/src/main/java/de/assecutor/votianlt/pages/base/ui/component/DeliveryStationDialog.java +++ b/src/main/java/de/assecutor/votianlt/pages/base/ui/component/DeliveryStationDialog.java @@ -18,13 +18,18 @@ import com.vaadin.flow.component.tabs.Tab; import com.vaadin.flow.component.tabs.TabSheet; import com.vaadin.flow.component.textfield.IntegerField; import com.vaadin.flow.component.textfield.TextField; +import com.vaadin.flow.component.UI; +import com.vaadin.flow.component.progressbar.ProgressBar; +import de.assecutor.votianlt.model.AddressValidationResult; import de.assecutor.votianlt.model.Customer; import de.assecutor.votianlt.model.TaskTemplate; import de.assecutor.votianlt.model.task.*; +import de.assecutor.votianlt.pages.service.AddressValidationService; import java.util.ArrayList; import java.util.List; import java.util.Optional; +import java.util.concurrent.CompletableFuture; /** * Dialog for editing delivery station data. Contains address form fields and a @@ -48,6 +53,15 @@ public class DeliveryStationDialog extends Dialog { private String city; private boolean saveAddress; private List tasks = new ArrayList<>(); + private boolean addressValidatedByGoogle; + + public boolean isAddressValidatedByGoogle() { + return addressValidatedByGoogle; + } + + public void setAddressValidatedByGoogle(boolean addressValidatedByGoogle) { + this.addressValidatedByGoogle = addressValidatedByGoogle; + } public String getCompany() { return company; @@ -176,12 +190,15 @@ public class DeliveryStationDialog extends Dialog { private Span tasksTabError; private final DeliveryStationTile.TranslationHelper translationHelper; + private final AddressValidationService addressValidationService; public DeliveryStationDialog(String dialogTitle, List customers, DeliveryStationTile.TranslationHelper translationHelper, SaveListener saveListener, - List templates, TemplateSaveCallback templateSaveCallback) { + List templates, TemplateSaveCallback templateSaveCallback, + AddressValidationService addressValidationService) { this.translationHelper = translationHelper; + this.addressValidationService = addressValidationService; setHeaderTitle(dialogTitle); setCloseOnOutsideClick(false); @@ -278,7 +295,8 @@ public class DeliveryStationDialog extends Dialog { saveAddress.setWidthFull(); formLayout.add(saveAddress); - // Clear error styling on value change for required fields and update tab indicators + // Clear error styling on value change for required fields and update tab + // indicators firstName.addValueChangeListener(ev -> validateRequiredFields()); lastName.addValueChangeListener(ev -> validateRequiredFields()); street.addValueChangeListener(ev -> validateRequiredFields()); @@ -309,11 +327,65 @@ public class DeliveryStationDialog extends Dialog { Notification.Position.BOTTOM_END); return; } - DeliveryData data = collectData(); - if (saveListener != null) { - saveListener.onSave(data); - } - close(); + + // Warte-Dialog anzeigen + Dialog loadingDialog = new Dialog(); + loadingDialog.setCloseOnOutsideClick(false); + loadingDialog.setCloseOnEsc(false); + loadingDialog.setHeaderTitle(translationHelper.getTranslation("addjob.validation.dialog.title")); + VerticalLayout loadingContent = new VerticalLayout(); + loadingContent.setAlignItems(FlexComponent.Alignment.CENTER); + loadingContent.setPadding(true); + loadingContent.setSpacing(true); + Span loadingText = new Span(translationHelper.getTranslation("addjob.validation.dialog.loading")); + ProgressBar progressBar = new ProgressBar(); + progressBar.setIndeterminate(true); + progressBar.setWidthFull(); + loadingContent.add(loadingText, progressBar); + loadingDialog.add(loadingContent); + loadingDialog.open(); + + // Adresse asynchron bei Google validieren + UI ui = UI.getCurrent(); + String streetVal = street.getValue(); + String houseNumberVal = houseNumber.getValue(); + String zipVal = zip.getValue(); + String cityVal = city.getValue(); + + CompletableFuture.supplyAsync(() -> addressValidationService.validateAddress("delivery", streetVal, + houseNumberVal, zipVal, cityVal)).thenAccept(validationResult -> ui.access(() -> { + loadingDialog.close(); + DeliveryData data = collectData(); + + if (validationResult.isValid()) { + data.setAddressValidatedByGoogle(true); + if (saveListener != null) { + saveListener.onSave(data); + } + close(); + } else { + // Adresse nicht gefunden: Benutzer fragen + ConfirmDialog confirmDialog = new ConfirmDialog(); + confirmDialog.setHeader( + translationHelper.getTranslation("addjob.validation.address.not.found.title")); + confirmDialog.setText( + translationHelper.getTranslation("addjob.validation.address.not.found.message")); + confirmDialog.setConfirmText( + translationHelper.getTranslation("addjob.validation.address.save.anyway")); + confirmDialog.setConfirmButtonTheme("primary"); + confirmDialog.setCancelable(true); + confirmDialog.setCancelText( + translationHelper.getTranslation("addjob.validation.address.correct")); + confirmDialog.addConfirmListener(ev -> { + data.setAddressValidatedByGoogle(false); + if (saveListener != null) { + saveListener.onSave(data); + } + close(); + }); + confirmDialog.open(); + } + })); }); saveButton.addThemeVariants(ButtonVariant.LUMO_PRIMARY); diff --git a/src/main/java/de/assecutor/votianlt/pages/base/ui/component/PickupStationDialog.java b/src/main/java/de/assecutor/votianlt/pages/base/ui/component/PickupStationDialog.java index c5ebfa0..8f2ff50 100644 --- a/src/main/java/de/assecutor/votianlt/pages/base/ui/component/PickupStationDialog.java +++ b/src/main/java/de/assecutor/votianlt/pages/base/ui/component/PickupStationDialog.java @@ -19,10 +19,15 @@ import com.vaadin.flow.component.textfield.IntegerField; import com.vaadin.flow.component.textfield.NumberField; import com.vaadin.flow.component.textfield.TextField; import com.vaadin.flow.component.timepicker.TimePicker; +import com.vaadin.flow.component.UI; +import com.vaadin.flow.component.confirmdialog.ConfirmDialog; import com.vaadin.flow.component.notification.Notification; +import com.vaadin.flow.component.progressbar.ProgressBar; +import de.assecutor.votianlt.model.AddressValidationResult; import de.assecutor.votianlt.model.AppUser; import de.assecutor.votianlt.model.CargoItem; import de.assecutor.votianlt.model.Customer; +import de.assecutor.votianlt.pages.service.AddressValidationService; import java.time.LocalDate; import java.time.LocalTime; @@ -31,6 +36,7 @@ import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Optional; +import java.util.concurrent.CompletableFuture; /** * Dialog for editing pickup station data. Contains address form fields, @@ -59,6 +65,15 @@ public class PickupStationDialog extends Dialog { private boolean digitalProcessing; private AppUser appUser; private List cargoItems = new ArrayList<>(); + private boolean addressValidatedByGoogle; + + public boolean isAddressValidatedByGoogle() { + return addressValidatedByGoogle; + } + + public void setAddressValidatedByGoogle(boolean addressValidatedByGoogle) { + this.addressValidatedByGoogle = addressValidatedByGoogle; + } public String getCompany() { return company; @@ -226,10 +241,13 @@ public class PickupStationDialog extends Dialog { private Span cargoTabError; private final DeliveryStationTile.TranslationHelper translationHelper; + private final AddressValidationService addressValidationService; public PickupStationDialog(String dialogTitle, List customers, DeliveryStationTile.TranslationHelper translationHelper, SaveListener saveListener, - List availableAppUsers) { + List availableAppUsers, AddressValidationService addressValidationService) { + + this.addressValidationService = addressValidationService; this.translationHelper = translationHelper; @@ -345,7 +363,8 @@ public class PickupStationDialog extends Dialog { saveAddress.setValue(true); saveAddress.setWidthFull(); - // Clear error styling on value change for required fields and update tab indicators + // Clear error styling on value change for required fields and update tab + // indicators firstName.addValueChangeListener(ev -> validateRequiredFields()); lastName.addValueChangeListener(ev -> validateRequiredFields()); street.addValueChangeListener(ev -> validateRequiredFields()); @@ -434,11 +453,65 @@ public class PickupStationDialog extends Dialog { Notification.Position.BOTTOM_END); return; } - PickupData data = collectData(); - if (saveListener != null) { - saveListener.onSave(data); - } - close(); + + // Warte-Dialog anzeigen + Dialog loadingDialog = new Dialog(); + loadingDialog.setCloseOnOutsideClick(false); + loadingDialog.setCloseOnEsc(false); + loadingDialog.setHeaderTitle(translationHelper.getTranslation("addjob.validation.dialog.title")); + VerticalLayout loadingContent = new VerticalLayout(); + loadingContent.setAlignItems(FlexComponent.Alignment.CENTER); + loadingContent.setPadding(true); + loadingContent.setSpacing(true); + Span loadingText = new Span(translationHelper.getTranslation("addjob.validation.dialog.loading")); + ProgressBar progressBar = new ProgressBar(); + progressBar.setIndeterminate(true); + progressBar.setWidthFull(); + loadingContent.add(loadingText, progressBar); + loadingDialog.add(loadingContent); + loadingDialog.open(); + + // Adresse asynchron bei Google validieren + UI ui = UI.getCurrent(); + String streetVal = street.getValue(); + String houseNumberVal = houseNumber.getValue(); + String zipVal = zip.getValue(); + String cityVal = city.getValue(); + + CompletableFuture.supplyAsync(() -> addressValidationService.validateAddress("pickup", streetVal, + houseNumberVal, zipVal, cityVal)).thenAccept(validationResult -> ui.access(() -> { + loadingDialog.close(); + PickupData data = collectData(); + + if (validationResult.isValid()) { + data.setAddressValidatedByGoogle(true); + if (saveListener != null) { + saveListener.onSave(data); + } + close(); + } else { + // Adresse nicht gefunden: Benutzer fragen + ConfirmDialog confirmDialog = new ConfirmDialog(); + confirmDialog.setHeader( + translationHelper.getTranslation("addjob.validation.address.not.found.title")); + confirmDialog.setText( + translationHelper.getTranslation("addjob.validation.address.not.found.message")); + confirmDialog.setConfirmText( + translationHelper.getTranslation("addjob.validation.address.save.anyway")); + confirmDialog.setConfirmButtonTheme("primary"); + confirmDialog.setCancelable(true); + confirmDialog.setCancelText( + translationHelper.getTranslation("addjob.validation.address.correct")); + confirmDialog.addConfirmListener(ev -> { + data.setAddressValidatedByGoogle(false); + if (saveListener != null) { + saveListener.onSave(data); + } + close(); + }); + confirmDialog.open(); + } + })); }); saveButton.addThemeVariants(ButtonVariant.LUMO_PRIMARY); diff --git a/src/main/java/de/assecutor/votianlt/pages/base/ui/component/StationTile.java b/src/main/java/de/assecutor/votianlt/pages/base/ui/component/StationTile.java index 772763c..cb45290 100644 --- a/src/main/java/de/assecutor/votianlt/pages/base/ui/component/StationTile.java +++ b/src/main/java/de/assecutor/votianlt/pages/base/ui/component/StationTile.java @@ -172,4 +172,14 @@ public class StationTile extends VerticalLayout { public void setDeleteListener(DeleteListener listener) { this.deleteListener = listener; } + + public void setAddressValidated(boolean validated) { + if (validated) { + getStyle().set("background-color", "rgba(76, 175, 80, 0.15)"); + getStyle().set("border", "1px solid rgba(76, 175, 80, 0.3)"); + } else { + getStyle().set("background-color", "var(--lumo-base-color)"); + getStyle().set("border", "1px solid var(--lumo-contrast-20pct)"); + } + } } 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 689f746..ae33aed 100644 --- a/src/main/java/de/assecutor/votianlt/pages/view/AddJobView.java +++ b/src/main/java/de/assecutor/votianlt/pages/view/AddJobView.java @@ -82,6 +82,7 @@ public class AddJobView extends Main implements HasDynamicTitle { private final TaskTemplateService taskTemplateService; private final SecurityService securityService; private final ServiceRepository serviceRepository; + private final AddressValidationService addressValidationService; // Customer selection private ComboBox customerSelection; @@ -167,6 +168,7 @@ public class AddJobView extends Main implements HasDynamicTitle { this.taskTemplateService = taskTemplateService; this.securityService = securityService; this.serviceRepository = serviceRepository; + this.addressValidationService = addressValidationService; initializeComponents(); setupLayout(); setupValidation(); @@ -750,11 +752,12 @@ public class AddJobView extends Main implements HasDynamicTitle { // Update tile preview pickupTile.updatePreview(data.getCompany(), data.getFirstName(), data.getLastName(), data.getStreet(), data.getHouseNumber(), data.getZip(), data.getCity()); + pickupTile.setAddressValidated(data.isAddressValidatedByGoogle()); resetRouteInformation(); triggerValidation(); updateTabLabels(); - }, availableAppUsers); + }, availableAppUsers, addressValidationService); // Pre-fill dialog with current pickup field values PickupStationDialog.PickupData currentData = new PickupStationDialog.PickupData(); @@ -819,6 +822,7 @@ public class AddJobView extends Main implements HasDynamicTitle { // Update tile preview tile.updatePreview(data.getCompany(), data.getFirstName(), data.getLastName(), data.getStreet(), data.getHouseNumber(), data.getZip(), data.getCity()); + tile.setAddressValidated(data.isAddressValidatedByGoogle()); resetRouteInformation(); triggerValidation(); @@ -834,7 +838,7 @@ public class AddJobView extends Main implements HasDynamicTitle { Notification.show(getTranslation("addjob.tasks.template.save.error", ex.getMessage()), 4000, Notification.Position.MIDDLE); } - }); + }, addressValidationService); // Pre-fill dialog with current station data DeliveryStation station = deliveryStationsState.get(actualIndex); diff --git a/src/main/resources/messages.properties b/src/main/resources/messages.properties index d0e31bb..60db6d3 100644 --- a/src/main/resources/messages.properties +++ b/src/main/resources/messages.properties @@ -561,6 +561,10 @@ addjob.validation.dialog.continue.anyway=Trotzdem weiter addjob.validation.pickup.address=Abholadresse addjob.validation.delivery.address=Lieferadresse addjob.validation.route=Route +addjob.validation.address.not.found.title=Adresse nicht gefunden +addjob.validation.address.not.found.message=Die eingegebene Adresse konnte bei Google nicht eindeutig gefunden werden. Möchten Sie trotzdem speichern? +addjob.validation.address.save.anyway=Trotzdem speichern +addjob.validation.address.correct=Adresse korrigieren # Job Summary jobsummary.title=Zusammenfassung diff --git a/src/main/resources/messages_en.properties b/src/main/resources/messages_en.properties index 609c0f1..315b567 100644 --- a/src/main/resources/messages_en.properties +++ b/src/main/resources/messages_en.properties @@ -561,6 +561,10 @@ addjob.validation.dialog.continue.anyway=Continue anyway addjob.validation.pickup.address=Pickup Address addjob.validation.delivery.address=Delivery Address addjob.validation.route=Route +addjob.validation.address.not.found.title=Address not found +addjob.validation.address.not.found.message=The entered address could not be clearly found on Google. Do you still want to save? +addjob.validation.address.save.anyway=Save anyway +addjob.validation.address.correct=Correct address # Job Summary jobsummary.title=Summary diff --git a/src/main/resources/messages_fr.properties b/src/main/resources/messages_fr.properties index fd297e6..a20fa8d 100644 --- a/src/main/resources/messages_fr.properties +++ b/src/main/resources/messages_fr.properties @@ -555,6 +555,10 @@ addjob.validation.dialog.continue.anyway=Continuer quand même addjob.validation.pickup.address=Adresse d'Enlèvement addjob.validation.delivery.address=Adresse de Livraison addjob.validation.route=Itinéraire +addjob.validation.address.not.found.title=Adresse introuvable +addjob.validation.address.not.found.message=L'adresse saisie n'a pas pu être trouvée clairement sur Google. Voulez-vous quand même enregistrer? +addjob.validation.address.save.anyway=Enregistrer quand même +addjob.validation.address.correct=Corriger l'adresse # Job Summary jobsummary.title=Résumé