Stationen-Dialoge: Google-Adressvalidierung beim Speichern mit Lade-Dialog und Bestätigungsoption

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-04 19:40:47 +01:00
parent 07f9748674
commit b6acac5b9c
7 changed files with 187 additions and 16 deletions

View File

@@ -18,13 +18,18 @@ import com.vaadin.flow.component.tabs.Tab;
import com.vaadin.flow.component.tabs.TabSheet; import com.vaadin.flow.component.tabs.TabSheet;
import com.vaadin.flow.component.textfield.IntegerField; import com.vaadin.flow.component.textfield.IntegerField;
import com.vaadin.flow.component.textfield.TextField; 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.Customer;
import de.assecutor.votianlt.model.TaskTemplate; import de.assecutor.votianlt.model.TaskTemplate;
import de.assecutor.votianlt.model.task.*; import de.assecutor.votianlt.model.task.*;
import de.assecutor.votianlt.pages.service.AddressValidationService;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Optional; import java.util.Optional;
import java.util.concurrent.CompletableFuture;
/** /**
* Dialog for editing delivery station data. Contains address form fields and a * 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 String city;
private boolean saveAddress; private boolean saveAddress;
private List<BaseTask> tasks = new ArrayList<>(); private List<BaseTask> tasks = new ArrayList<>();
private boolean addressValidatedByGoogle;
public boolean isAddressValidatedByGoogle() {
return addressValidatedByGoogle;
}
public void setAddressValidatedByGoogle(boolean addressValidatedByGoogle) {
this.addressValidatedByGoogle = addressValidatedByGoogle;
}
public String getCompany() { public String getCompany() {
return company; return company;
@@ -176,12 +190,15 @@ public class DeliveryStationDialog extends Dialog {
private Span tasksTabError; private Span tasksTabError;
private final DeliveryStationTile.TranslationHelper translationHelper; private final DeliveryStationTile.TranslationHelper translationHelper;
private final AddressValidationService addressValidationService;
public DeliveryStationDialog(String dialogTitle, List<Customer> customers, public DeliveryStationDialog(String dialogTitle, List<Customer> customers,
DeliveryStationTile.TranslationHelper translationHelper, SaveListener saveListener, DeliveryStationTile.TranslationHelper translationHelper, SaveListener saveListener,
List<TaskTemplate> templates, TemplateSaveCallback templateSaveCallback) { List<TaskTemplate> templates, TemplateSaveCallback templateSaveCallback,
AddressValidationService addressValidationService) {
this.translationHelper = translationHelper; this.translationHelper = translationHelper;
this.addressValidationService = addressValidationService;
setHeaderTitle(dialogTitle); setHeaderTitle(dialogTitle);
setCloseOnOutsideClick(false); setCloseOnOutsideClick(false);
@@ -278,7 +295,8 @@ public class DeliveryStationDialog extends Dialog {
saveAddress.setWidthFull(); saveAddress.setWidthFull();
formLayout.add(saveAddress); 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()); firstName.addValueChangeListener(ev -> validateRequiredFields());
lastName.addValueChangeListener(ev -> validateRequiredFields()); lastName.addValueChangeListener(ev -> validateRequiredFields());
street.addValueChangeListener(ev -> validateRequiredFields()); street.addValueChangeListener(ev -> validateRequiredFields());
@@ -309,11 +327,65 @@ public class DeliveryStationDialog extends Dialog {
Notification.Position.BOTTOM_END); Notification.Position.BOTTOM_END);
return; return;
} }
DeliveryData data = collectData();
if (saveListener != null) { // Warte-Dialog anzeigen
saveListener.onSave(data); Dialog loadingDialog = new Dialog();
} loadingDialog.setCloseOnOutsideClick(false);
close(); 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); saveButton.addThemeVariants(ButtonVariant.LUMO_PRIMARY);

View File

@@ -19,10 +19,15 @@ import com.vaadin.flow.component.textfield.IntegerField;
import com.vaadin.flow.component.textfield.NumberField; import com.vaadin.flow.component.textfield.NumberField;
import com.vaadin.flow.component.textfield.TextField; import com.vaadin.flow.component.textfield.TextField;
import com.vaadin.flow.component.timepicker.TimePicker; 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.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.AppUser;
import de.assecutor.votianlt.model.CargoItem; import de.assecutor.votianlt.model.CargoItem;
import de.assecutor.votianlt.model.Customer; import de.assecutor.votianlt.model.Customer;
import de.assecutor.votianlt.pages.service.AddressValidationService;
import java.time.LocalDate; import java.time.LocalDate;
import java.time.LocalTime; import java.time.LocalTime;
@@ -31,6 +36,7 @@ import java.util.LinkedHashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Optional; import java.util.Optional;
import java.util.concurrent.CompletableFuture;
/** /**
* Dialog for editing pickup station data. Contains address form fields, * Dialog for editing pickup station data. Contains address form fields,
@@ -59,6 +65,15 @@ public class PickupStationDialog extends Dialog {
private boolean digitalProcessing; private boolean digitalProcessing;
private AppUser appUser; private AppUser appUser;
private List<CargoItem> cargoItems = new ArrayList<>(); private List<CargoItem> cargoItems = new ArrayList<>();
private boolean addressValidatedByGoogle;
public boolean isAddressValidatedByGoogle() {
return addressValidatedByGoogle;
}
public void setAddressValidatedByGoogle(boolean addressValidatedByGoogle) {
this.addressValidatedByGoogle = addressValidatedByGoogle;
}
public String getCompany() { public String getCompany() {
return company; return company;
@@ -226,10 +241,13 @@ public class PickupStationDialog extends Dialog {
private Span cargoTabError; private Span cargoTabError;
private final DeliveryStationTile.TranslationHelper translationHelper; private final DeliveryStationTile.TranslationHelper translationHelper;
private final AddressValidationService addressValidationService;
public PickupStationDialog(String dialogTitle, List<Customer> customers, public PickupStationDialog(String dialogTitle, List<Customer> customers,
DeliveryStationTile.TranslationHelper translationHelper, SaveListener saveListener, DeliveryStationTile.TranslationHelper translationHelper, SaveListener saveListener,
List<AppUser> availableAppUsers) { List<AppUser> availableAppUsers, AddressValidationService addressValidationService) {
this.addressValidationService = addressValidationService;
this.translationHelper = translationHelper; this.translationHelper = translationHelper;
@@ -345,7 +363,8 @@ public class PickupStationDialog extends Dialog {
saveAddress.setValue(true); saveAddress.setValue(true);
saveAddress.setWidthFull(); 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()); firstName.addValueChangeListener(ev -> validateRequiredFields());
lastName.addValueChangeListener(ev -> validateRequiredFields()); lastName.addValueChangeListener(ev -> validateRequiredFields());
street.addValueChangeListener(ev -> validateRequiredFields()); street.addValueChangeListener(ev -> validateRequiredFields());
@@ -434,11 +453,65 @@ public class PickupStationDialog extends Dialog {
Notification.Position.BOTTOM_END); Notification.Position.BOTTOM_END);
return; return;
} }
PickupData data = collectData();
if (saveListener != null) { // Warte-Dialog anzeigen
saveListener.onSave(data); Dialog loadingDialog = new Dialog();
} loadingDialog.setCloseOnOutsideClick(false);
close(); 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); saveButton.addThemeVariants(ButtonVariant.LUMO_PRIMARY);

View File

@@ -172,4 +172,14 @@ public class StationTile extends VerticalLayout {
public void setDeleteListener(DeleteListener listener) { public void setDeleteListener(DeleteListener listener) {
this.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)");
}
}
} }

View File

@@ -82,6 +82,7 @@ public class AddJobView extends Main implements HasDynamicTitle {
private final TaskTemplateService taskTemplateService; private final TaskTemplateService taskTemplateService;
private final SecurityService securityService; private final SecurityService securityService;
private final ServiceRepository serviceRepository; private final ServiceRepository serviceRepository;
private final AddressValidationService addressValidationService;
// Customer selection // Customer selection
private ComboBox<String> customerSelection; private ComboBox<String> customerSelection;
@@ -167,6 +168,7 @@ public class AddJobView extends Main implements HasDynamicTitle {
this.taskTemplateService = taskTemplateService; this.taskTemplateService = taskTemplateService;
this.securityService = securityService; this.securityService = securityService;
this.serviceRepository = serviceRepository; this.serviceRepository = serviceRepository;
this.addressValidationService = addressValidationService;
initializeComponents(); initializeComponents();
setupLayout(); setupLayout();
setupValidation(); setupValidation();
@@ -750,11 +752,12 @@ public class AddJobView extends Main implements HasDynamicTitle {
// Update tile preview // Update tile preview
pickupTile.updatePreview(data.getCompany(), data.getFirstName(), data.getLastName(), pickupTile.updatePreview(data.getCompany(), data.getFirstName(), data.getLastName(),
data.getStreet(), data.getHouseNumber(), data.getZip(), data.getCity()); data.getStreet(), data.getHouseNumber(), data.getZip(), data.getCity());
pickupTile.setAddressValidated(data.isAddressValidatedByGoogle());
resetRouteInformation(); resetRouteInformation();
triggerValidation(); triggerValidation();
updateTabLabels(); updateTabLabels();
}, availableAppUsers); }, availableAppUsers, addressValidationService);
// Pre-fill dialog with current pickup field values // Pre-fill dialog with current pickup field values
PickupStationDialog.PickupData currentData = new PickupStationDialog.PickupData(); PickupStationDialog.PickupData currentData = new PickupStationDialog.PickupData();
@@ -819,6 +822,7 @@ public class AddJobView extends Main implements HasDynamicTitle {
// Update tile preview // Update tile preview
tile.updatePreview(data.getCompany(), data.getFirstName(), data.getLastName(), data.getStreet(), tile.updatePreview(data.getCompany(), data.getFirstName(), data.getLastName(), data.getStreet(),
data.getHouseNumber(), data.getZip(), data.getCity()); data.getHouseNumber(), data.getZip(), data.getCity());
tile.setAddressValidated(data.isAddressValidatedByGoogle());
resetRouteInformation(); resetRouteInformation();
triggerValidation(); triggerValidation();
@@ -834,7 +838,7 @@ public class AddJobView extends Main implements HasDynamicTitle {
Notification.show(getTranslation("addjob.tasks.template.save.error", ex.getMessage()), 4000, Notification.show(getTranslation("addjob.tasks.template.save.error", ex.getMessage()), 4000,
Notification.Position.MIDDLE); Notification.Position.MIDDLE);
} }
}); }, addressValidationService);
// Pre-fill dialog with current station data // Pre-fill dialog with current station data
DeliveryStation station = deliveryStationsState.get(actualIndex); DeliveryStation station = deliveryStationsState.get(actualIndex);

View File

@@ -561,6 +561,10 @@ addjob.validation.dialog.continue.anyway=Trotzdem weiter
addjob.validation.pickup.address=Abholadresse addjob.validation.pickup.address=Abholadresse
addjob.validation.delivery.address=Lieferadresse addjob.validation.delivery.address=Lieferadresse
addjob.validation.route=Route 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 # Job Summary
jobsummary.title=Zusammenfassung jobsummary.title=Zusammenfassung

View File

@@ -561,6 +561,10 @@ addjob.validation.dialog.continue.anyway=Continue anyway
addjob.validation.pickup.address=Pickup Address addjob.validation.pickup.address=Pickup Address
addjob.validation.delivery.address=Delivery Address addjob.validation.delivery.address=Delivery Address
addjob.validation.route=Route 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 # Job Summary
jobsummary.title=Summary jobsummary.title=Summary

View File

@@ -555,6 +555,10 @@ addjob.validation.dialog.continue.anyway=Continuer quand même
addjob.validation.pickup.address=Adresse d'Enlèvement addjob.validation.pickup.address=Adresse d'Enlèvement
addjob.validation.delivery.address=Adresse de Livraison addjob.validation.delivery.address=Adresse de Livraison
addjob.validation.route=Itinéraire 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 # Job Summary
jobsummary.title=Résumé jobsummary.title=Résumé