Erweiterungen
This commit is contained in:
@@ -17,10 +17,8 @@ import com.vaadin.flow.component.orderedlayout.FlexComponent;
|
||||
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.data.binder.Binder;
|
||||
import com.vaadin.flow.router.Menu;
|
||||
import com.vaadin.flow.component.Component;
|
||||
@@ -60,7 +58,7 @@ public class AddJobView extends Main {
|
||||
private Button preloadAddressButton;
|
||||
|
||||
// Pickup address fields
|
||||
private TextField pickupCompany;
|
||||
private ComboBox<String> pickupCompany;
|
||||
private ComboBox<String> pickupSalutation;
|
||||
private TextField pickupFirstName;
|
||||
private TextField pickupLastName;
|
||||
@@ -73,7 +71,7 @@ public class AddJobView extends Main {
|
||||
private Checkbox savePickupAddress;
|
||||
|
||||
// Delivery address fields
|
||||
private TextField deliveryCompany;
|
||||
private ComboBox<String> deliveryCompany;
|
||||
private ComboBox<String> deliverySalutation;
|
||||
private TextField deliveryFirstName;
|
||||
private TextField deliveryLastName;
|
||||
@@ -122,7 +120,7 @@ public class AddJobView extends Main {
|
||||
private final Binder<Job> binder = new Binder<>(Job.class);
|
||||
|
||||
// Mapping für die Anzeige-Labels der Kunden zur Entität
|
||||
private Map<String, Customer> customerLabelToEntity = new LinkedHashMap<>();
|
||||
private final Map<String, Customer> customerLabelToEntity = new LinkedHashMap<>();
|
||||
|
||||
public AddJobView(AddJobService addJobService, AddCustomerService addCustomerService, CustomerService customerService) {
|
||||
this.addJobService = addJobService;
|
||||
@@ -164,10 +162,17 @@ public class AddJobView extends Main {
|
||||
// Bei Auswahl eines Kunden Abholfelder befüllen
|
||||
customerSelection.addValueChangeListener(ev -> {
|
||||
String selected = ev.getValue();
|
||||
if (selected == null) return;
|
||||
if (selected == null) {
|
||||
// Wenn kein Kunde ausgewählt ist, Checkbox wieder aktivieren
|
||||
savePickupAddress.setValue(true);
|
||||
return;
|
||||
}
|
||||
Customer c = customerLabelToEntity.get(selected);
|
||||
if (c == null) return;
|
||||
|
||||
// Pickup-Checkbox deaktivieren, da Kunde bereits existiert
|
||||
savePickupAddress.setValue(false);
|
||||
|
||||
// Firma
|
||||
if (c.getCompanyName() != null) { pickupCompany.setValue(c.getCompanyName()); } else { pickupCompany.clear(); }
|
||||
// Anrede (nur setzen, wenn vorhanden und zulässig)
|
||||
@@ -194,9 +199,10 @@ public class AddJobView extends Main {
|
||||
preloadAddressButton.addClickListener(event -> clearAllFields());
|
||||
|
||||
// Pickup address
|
||||
pickupCompany = new TextField("Firma");
|
||||
pickupCompany = new ComboBox<>("Firma");
|
||||
pickupCompany.setPlaceholder("Firmenname");
|
||||
addGooglePlacesAutocomplete(pickupCompany, 0); // Stage 0 für Pickup
|
||||
pickupCompany.setAllowCustomValue(true);
|
||||
setupCompanyAutocomplete(pickupCompany, true); // true für Pickup
|
||||
pickupSalutation = new ComboBox<>("Anrede");
|
||||
pickupSalutation.setItems("Herr", "Frau", "Divers");
|
||||
pickupSalutation.setPlaceholder("Anrede wählen...");
|
||||
@@ -223,11 +229,13 @@ public class AddJobView extends Main {
|
||||
pickupCity.setPlaceholder("Hamburg");
|
||||
pickupCity.setRequiredIndicatorVisible(true);
|
||||
savePickupAddress = new Checkbox("Die Adresse für zukünftige Aufträge speichern.");
|
||||
savePickupAddress.setValue(true);
|
||||
|
||||
// Delivery address
|
||||
deliveryCompany = new TextField("Firma");
|
||||
deliveryCompany = new ComboBox<>("Firma");
|
||||
deliveryCompany.setPlaceholder("Firmenname");
|
||||
addGooglePlacesAutocomplete(deliveryCompany, 1); // Stage 1 für Delivery
|
||||
deliveryCompany.setAllowCustomValue(true);
|
||||
setupCompanyAutocomplete(deliveryCompany, false); // false für Delivery
|
||||
deliverySalutation = new ComboBox<>("Anrede");
|
||||
deliverySalutation.setItems("Herr", "Frau", "Divers");
|
||||
deliverySalutation.setPlaceholder("Anrede wählen...");
|
||||
@@ -254,6 +262,7 @@ public class AddJobView extends Main {
|
||||
deliveryCity.setPlaceholder("Berlin");
|
||||
deliveryCity.setRequiredIndicatorVisible(true);
|
||||
saveDeliveryAddress = new Checkbox("Die Adresse für zukünftige Aufträge speichern.");
|
||||
saveDeliveryAddress.setValue(true);
|
||||
|
||||
// Digital processing
|
||||
digitalProcessing = new Checkbox("Digitale Abwicklung per App");
|
||||
@@ -576,6 +585,88 @@ public class AddJobView extends Main {
|
||||
return section;
|
||||
}
|
||||
|
||||
private void setupCompanyAutocomplete(ComboBox<String> companyField, boolean isPickup) {
|
||||
// Get all customers for the current owner
|
||||
List<Customer> allCustomers = customerService.findAllForCurrentOwner();
|
||||
|
||||
// Extract unique company names (filter out null/empty values)
|
||||
List<String> companyNames = allCustomers.stream()
|
||||
.map(Customer::getCompanyName)
|
||||
.filter(name -> name != null && !name.trim().isEmpty())
|
||||
.distinct()
|
||||
.sorted()
|
||||
.toList();
|
||||
|
||||
// Set items for autocomplete
|
||||
companyField.setItems(companyNames);
|
||||
|
||||
// Add selection listener to auto-fill address fields when company is selected
|
||||
companyField.addValueChangeListener(event -> {
|
||||
String selectedCompany = event.getValue();
|
||||
if (selectedCompany == null || selectedCompany.trim().isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Find the first customer with this company name
|
||||
Optional<Customer> matchingCustomer = allCustomers.stream()
|
||||
.filter(c -> selectedCompany.equals(c.getCompanyName()))
|
||||
.findFirst();
|
||||
|
||||
if (matchingCustomer.isPresent()) {
|
||||
Customer customer = matchingCustomer.get();
|
||||
|
||||
if (isPickup) {
|
||||
// Fill pickup address fields
|
||||
if (customer.getTitle() != null && ("Herr".equalsIgnoreCase(customer.getTitle()) ||
|
||||
"Frau".equalsIgnoreCase(customer.getTitle()) || "Divers".equalsIgnoreCase(customer.getTitle()))) {
|
||||
pickupSalutation.setValue(customer.getTitle());
|
||||
}
|
||||
if (customer.getFirstname() != null) pickupFirstName.setValue(customer.getFirstname());
|
||||
if (customer.getLastName() != null) pickupLastName.setValue(customer.getLastName());
|
||||
if (customer.getTelephone() != null) pickupPhone.setValue(customer.getTelephone());
|
||||
if (customer.getStreet() != null) pickupStreet.setValue(customer.getStreet());
|
||||
if (customer.getHouseNumber() != null) pickupHouseNumber.setValue(customer.getHouseNumber());
|
||||
if (customer.getAddressAddition() != null) pickupAddressAddition.setValue(customer.getAddressAddition());
|
||||
if (customer.getZip() != null) pickupZip.setValue(customer.getZip());
|
||||
if (customer.getCity() != null) pickupCity.setValue(customer.getCity());
|
||||
|
||||
// Deactivate save checkbox since customer already exists
|
||||
savePickupAddress.setValue(false);
|
||||
} else {
|
||||
// Fill delivery address fields
|
||||
if (customer.getTitle() != null && ("Herr".equalsIgnoreCase(customer.getTitle()) ||
|
||||
"Frau".equalsIgnoreCase(customer.getTitle()) || "Divers".equalsIgnoreCase(customer.getTitle()))) {
|
||||
deliverySalutation.setValue(customer.getTitle());
|
||||
}
|
||||
if (customer.getFirstname() != null) deliveryFirstName.setValue(customer.getFirstname());
|
||||
if (customer.getLastName() != null) deliveryLastName.setValue(customer.getLastName());
|
||||
if (customer.getTelephone() != null) deliveryPhone.setValue(customer.getTelephone());
|
||||
if (customer.getStreet() != null) deliveryStreet.setValue(customer.getStreet());
|
||||
if (customer.getHouseNumber() != null) deliveryHouseNumber.setValue(customer.getHouseNumber());
|
||||
if (customer.getAddressAddition() != null) deliveryAddressAddition.setValue(customer.getAddressAddition());
|
||||
if (customer.getZip() != null) deliveryZip.setValue(customer.getZip());
|
||||
if (customer.getCity() != null) deliveryCity.setValue(customer.getCity());
|
||||
|
||||
// Deactivate save checkbox since customer already exists
|
||||
saveDeliveryAddress.setValue(false);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Handle custom values (when user types something not in the list)
|
||||
companyField.addCustomValueSetListener(event -> {
|
||||
String customValue = event.getDetail();
|
||||
companyField.setValue(customValue);
|
||||
|
||||
// Reactivate save checkbox for custom values
|
||||
if (isPickup) {
|
||||
savePickupAddress.setValue(true);
|
||||
} else {
|
||||
saveDeliveryAddress.setValue(true);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void setupValidation() {
|
||||
// Bind pickup address fields with validation
|
||||
binder.forField(pickupFirstName)
|
||||
@@ -773,7 +864,7 @@ public class AddJobView extends Main {
|
||||
// Check validation state for each tab and update labels with exclamation marks
|
||||
updateTabLabel(addressesTab, "Auftraggeber & Adressen", hasAddressValidationErrors());
|
||||
updateTabLabel(appointmentsTab, "Termine & Verarbeitung", hasAppointmentValidationErrors());
|
||||
updateTabLabel(cargoTab, "Ladung & Aufgaben", false); // No required fields in cargo tab
|
||||
updateTabLabel(cargoTab, "Ladung & Aufgaben", hasCargoValidationErrors());
|
||||
updateTabLabel(priceTab, "Preis & Abschluss", hasPriceValidationErrors());
|
||||
}
|
||||
|
||||
@@ -806,6 +897,27 @@ public class AddJobView extends Main {
|
||||
return pickupDate.getValue() == null || deliveryDate.getValue() == null;
|
||||
}
|
||||
|
||||
private boolean hasCargoValidationErrors() {
|
||||
// Check if ALL cargo items have all required fields filled
|
||||
// When multiple cargo rows exist, ALL must be complete
|
||||
if (cargoItemsState.isEmpty()) {
|
||||
return true; // No cargo items at all - show warning
|
||||
}
|
||||
|
||||
// Check that ALL cargo items are complete
|
||||
// A cargo item is considered complete if it has: Description, Quantity, Weight, Length, Width, Height
|
||||
boolean allCargoItemsValid = cargoItemsState.stream()
|
||||
.allMatch(cargoItem -> cargoItem != null &&
|
||||
cargoItem.getDescription() != null && !cargoItem.getDescription().trim().isEmpty() &&
|
||||
cargoItem.getQuantity() != null && cargoItem.getQuantity() > 0 &&
|
||||
cargoItem.getWeightKg() != null && cargoItem.getWeightKg() > 0 &&
|
||||
cargoItem.getLengthMm() != null && cargoItem.getLengthMm() > 0 &&
|
||||
cargoItem.getWidthMm() != null && cargoItem.getWidthMm() > 0 &&
|
||||
cargoItem.getHeightMm() != null && cargoItem.getHeightMm() > 0);
|
||||
|
||||
return !allCargoItemsValid; // Return true if ANY cargo item is incomplete (show warning)
|
||||
}
|
||||
|
||||
private boolean hasPriceValidationErrors() {
|
||||
return isFieldEmpty(price);
|
||||
}
|
||||
@@ -840,7 +952,7 @@ public class AddJobView extends Main {
|
||||
return;
|
||||
}
|
||||
// toggle cargo error highlight
|
||||
boolean hasCargo = !cargoFilled.isEmpty();
|
||||
boolean hasCargo = true;
|
||||
cargoError.setVisible(!hasCargo);
|
||||
if (!hasCargo) {
|
||||
cargoAreaContainer.getStyle().set("border", "1px solid var(--lumo-error-color-50pct)");
|
||||
@@ -1054,6 +1166,7 @@ public class AddJobView extends Main {
|
||||
cargoItemsState.remove(idx);
|
||||
}
|
||||
cargoList.remove(row);
|
||||
updateTabLabels(); // Update tab validation when cargo item is removed
|
||||
});
|
||||
|
||||
row.add(desc, qty, weight, len, wid, hei, remove);
|
||||
@@ -1078,20 +1191,43 @@ public class AddJobView extends Main {
|
||||
item.setWidthMm(wid.getValue());
|
||||
item.setHeightMm(hei.getValue());
|
||||
|
||||
desc.addValueChangeListener(ev -> item.setDescription(ev.getValue()));
|
||||
qty.addValueChangeListener(ev -> item.setQuantity(ev.getValue()));
|
||||
weight.addValueChangeListener(ev -> item.setWeightKg(ev.getValue()));
|
||||
len.addValueChangeListener(ev -> item.setLengthMm(ev.getValue()));
|
||||
wid.addValueChangeListener(ev -> item.setWidthMm(ev.getValue()));
|
||||
hei.addValueChangeListener(ev -> item.setHeightMm(ev.getValue()));
|
||||
desc.addValueChangeListener(ev -> {
|
||||
item.setDescription(ev.getValue());
|
||||
updateTabLabels(); // Update tab validation when cargo description changes
|
||||
});
|
||||
qty.addValueChangeListener(ev -> {
|
||||
item.setQuantity(ev.getValue());
|
||||
updateTabLabels(); // Update tab validation when cargo quantity changes
|
||||
});
|
||||
weight.addValueChangeListener(ev -> {
|
||||
item.setWeightKg(ev.getValue());
|
||||
updateTabLabels(); // Update tab validation when cargo weight changes
|
||||
});
|
||||
len.addValueChangeListener(ev -> {
|
||||
item.setLengthMm(ev.getValue());
|
||||
updateTabLabels(); // Update tab validation when cargo length changes
|
||||
});
|
||||
wid.addValueChangeListener(ev -> {
|
||||
item.setWidthMm(ev.getValue());
|
||||
updateTabLabels(); // Update tab validation when cargo width changes
|
||||
});
|
||||
hei.addValueChangeListener(ev -> {
|
||||
item.setHeightMm(ev.getValue());
|
||||
updateTabLabels(); // Update tab validation when cargo height changes
|
||||
});
|
||||
|
||||
if (afterCreate != null) afterCreate.accept(row);
|
||||
};
|
||||
|
||||
addCargoRow.accept("gitterbox", r -> {});
|
||||
addCargoRow.accept("paket", r -> {});
|
||||
addCargoRow.accept("", r -> {});
|
||||
addCargoRow.accept("", r -> {}); // Show only one empty row by default
|
||||
|
||||
// Add button to add more cargo rows
|
||||
Button addCargoButton = new Button("Ladung hinzufügen", new Icon(VaadinIcon.PLUS));
|
||||
addCargoButton.addThemeVariants(ButtonVariant.LUMO_PRIMARY);
|
||||
addCargoButton.setWidthFull(); // Make button full width of container
|
||||
addCargoButton.addClickListener(e -> addCargoRow.accept("", r -> {}));
|
||||
|
||||
cargoAreaContainer.add(addCargoButton);
|
||||
wrapper.add(cargoAreaContainer);
|
||||
return wrapper;
|
||||
}
|
||||
@@ -1226,80 +1362,6 @@ public class AddJobView extends Main {
|
||||
Notification.show("Alle Felder wurden geleert", 2000, Notification.Position.BOTTOM_CENTER);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fügt Google Places Autocomplete zu einem TextField hinzu
|
||||
*/
|
||||
private void addGooglePlacesAutocomplete(TextField textField, int stageIndex) {
|
||||
// Initialisierung als Runnable kapseln
|
||||
Runnable initAutocomplete = () -> {
|
||||
// Google Places API Script laden (falls noch nicht geladen)
|
||||
UI.getCurrent().getPage().addJavaScript("https://maps.googleapis.com/maps/api/js?key=AIzaSyDnbitL06iLp3elmj-WtPudCykX9xvXcVE&libraries=places");
|
||||
|
||||
// JavaScript für Google Places Autocomplete - nutzt direkt das Input-Element
|
||||
String script = """
|
||||
setTimeout(function() {
|
||||
var host = $0; // Vaadin TextField element
|
||||
var input = host && host.inputElement ? host.inputElement : (host && host.shadowRoot ? host.shadowRoot.querySelector('input') : null);
|
||||
if (input && window.google && window.google.maps && window.google.maps.places) {
|
||||
var autocomplete = new google.maps.places.Autocomplete(input, {
|
||||
types: ['establishment'],
|
||||
componentRestrictions: {country: 'de'}
|
||||
});
|
||||
|
||||
autocomplete.addListener('place_changed', function() {
|
||||
var place = autocomplete.getPlace();
|
||||
if (place && place.address_components) {
|
||||
var streetNumber = '';
|
||||
var route = '';
|
||||
var locality = '';
|
||||
var postalCode = '';
|
||||
var country = '';
|
||||
var companyName = place.name || '';
|
||||
|
||||
for (var i = 0; i < place.address_components.length; i++) {
|
||||
var component = place.address_components[i];
|
||||
var types = component.types || [];
|
||||
|
||||
if (types.indexOf('street_number') !== -1) {
|
||||
streetNumber = component.long_name;
|
||||
} else if (types.indexOf('route') !== -1) {
|
||||
route = component.long_name;
|
||||
} else if (types.indexOf('locality') !== -1) {
|
||||
locality = component.long_name;
|
||||
} else if (types.indexOf('postal_code') !== -1) {
|
||||
postalCode = component.long_name;
|
||||
} else if (types.indexOf('country') !== -1) {
|
||||
country = component.long_name;
|
||||
}
|
||||
}
|
||||
|
||||
// Setze den Firmennamen in das Feld (sichtbar und Vaadin-Wert)
|
||||
input.value = companyName || '';
|
||||
host.value = companyName || '';
|
||||
|
||||
// Sende Daten an Server mit Stage-Index
|
||||
$1.$server.handleGooglePlaceSelected(%d, companyName, route, streetNumber, postalCode, locality, country);
|
||||
}
|
||||
});
|
||||
|
||||
} else {
|
||||
setTimeout(arguments.callee, 500);
|
||||
}
|
||||
}, 500);
|
||||
""".formatted(stageIndex);
|
||||
|
||||
// executeJs mit Referenzen: $0 => input element (shadowRoot), $1 => Server (this)
|
||||
textField.getElement().executeJs(script, textField.getElement(), getElement());
|
||||
|
||||
log.debug("Google Places Autocomplete initialisiert (Stage {})", stageIndex);
|
||||
};
|
||||
|
||||
// Wenn UI bereits existiert, direkt initialisieren; sonst beim Attach initialisieren
|
||||
textField.getUI().ifPresentOrElse(
|
||||
ui -> ui.access((com.vaadin.flow.server.Command) initAutocomplete::run),
|
||||
() -> textField.addAttachListener(event -> event.getUI().access((com.vaadin.flow.server.Command) initAutocomplete::run))
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Konfiguriert Focus-Listener für alle Eingabefelder um Drag-and-Drop zu steuern
|
||||
@@ -1386,53 +1448,4 @@ public class AddJobView extends Main {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handler für Google Places Auswahl - wird vom JavaScript aufgerufen
|
||||
*/
|
||||
@ClientCallable
|
||||
public void handleGooglePlaceSelected(int stageIndex, String companyName, String route, String streetNumber, String postalCode, String locality, String country) {
|
||||
log.debug("Google Place ausgewählt - Stage: {}, Company: {}, Route: {}, StreetNumber: {}, PostalCode: {}, Locality: {}, Country: {}",
|
||||
stageIndex, companyName, route, streetNumber, postalCode, locality, country);
|
||||
|
||||
if (stageIndex == 0) {
|
||||
// Pickup address
|
||||
// Firmenname wird bereits durch das TextField selbst gesetzt
|
||||
if (route != null && !route.isEmpty()) {
|
||||
pickupStreet.setValue(route);
|
||||
}
|
||||
if (streetNumber != null && !streetNumber.isEmpty()) {
|
||||
pickupHouseNumber.setValue(streetNumber);
|
||||
}
|
||||
if (postalCode != null && !postalCode.isEmpty()) {
|
||||
pickupZip.setValue(postalCode);
|
||||
}
|
||||
if (locality != null && !locality.isEmpty()) {
|
||||
pickupCity.setValue(locality);
|
||||
}
|
||||
|
||||
// Notification für Benutzer
|
||||
Notification.show("Abholadresse automatisch ausgefüllt: " + (companyName != null ? companyName : ""),
|
||||
3000, Notification.Position.BOTTOM_CENTER);
|
||||
|
||||
} else if (stageIndex == 1) {
|
||||
// Delivery address
|
||||
// Firmenname wird bereits durch das TextField selbst gesetzt
|
||||
if (route != null && !route.isEmpty()) {
|
||||
deliveryStreet.setValue(route);
|
||||
}
|
||||
if (streetNumber != null && !streetNumber.isEmpty()) {
|
||||
deliveryHouseNumber.setValue(streetNumber);
|
||||
}
|
||||
if (postalCode != null && !postalCode.isEmpty()) {
|
||||
deliveryZip.setValue(postalCode);
|
||||
}
|
||||
if (locality != null && !locality.isEmpty()) {
|
||||
deliveryCity.setValue(locality);
|
||||
}
|
||||
|
||||
// Notification für Benutzer
|
||||
Notification.show("Lieferadresse automatisch ausgefüllt: " + (companyName != null ? companyName : ""),
|
||||
3000, Notification.Position.BOTTOM_CENTER);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user