diff --git a/src/main/java/de/assecutor/votianlt/model/Customer.java b/src/main/java/de/assecutor/votianlt/model/Customer.java index 7c3066e..31cddc1 100644 --- a/src/main/java/de/assecutor/votianlt/model/Customer.java +++ b/src/main/java/de/assecutor/votianlt/model/Customer.java @@ -51,4 +51,7 @@ public class Customer @Field("created_by") private ObjectId createdBy; + + @Field("owner") + private ObjectId owner; } \ No newline at end of file diff --git a/src/main/java/de/assecutor/votianlt/pages/domain/CustomerRepository.java b/src/main/java/de/assecutor/votianlt/pages/domain/CustomerRepository.java index cc4811c..9da3373 100644 --- a/src/main/java/de/assecutor/votianlt/pages/domain/CustomerRepository.java +++ b/src/main/java/de/assecutor/votianlt/pages/domain/CustomerRepository.java @@ -5,9 +5,12 @@ import org.bson.types.ObjectId; import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Slice; import org.springframework.data.mongodb.repository.MongoRepository; +import java.util.List; public interface CustomerRepository extends MongoRepository { // If you don't need a total row count, Slice is better than Page. Slice findAllBy(Pageable pageable); + + List findByOwner(ObjectId owner); } diff --git a/src/main/java/de/assecutor/votianlt/pages/service/AddCustomerService.java b/src/main/java/de/assecutor/votianlt/pages/service/AddCustomerService.java index 3300e43..224e31a 100644 --- a/src/main/java/de/assecutor/votianlt/pages/service/AddCustomerService.java +++ b/src/main/java/de/assecutor/votianlt/pages/service/AddCustomerService.java @@ -24,6 +24,7 @@ public class AddCustomerService { // Setze den aktuellen Benutzer als Ersteller - jetzt direkt aus der Session de.assecutor.votianlt.model.User currentUser = securityService.getCurrentDatabaseUser(); customer.setCreatedBy(currentUser.getId()); + customer.setOwner(currentUser.getId()); addCustomerRepository.save(customer); } diff --git a/src/main/java/de/assecutor/votianlt/pages/service/CustomerService.java b/src/main/java/de/assecutor/votianlt/pages/service/CustomerService.java index cbd5b2a..b751590 100644 --- a/src/main/java/de/assecutor/votianlt/pages/service/CustomerService.java +++ b/src/main/java/de/assecutor/votianlt/pages/service/CustomerService.java @@ -8,15 +8,18 @@ import org.springframework.transaction.annotation.Transactional; import java.util.List; import org.bson.types.ObjectId; +import de.assecutor.votianlt.security.SecurityService; @Service @Transactional(propagation = Propagation.REQUIRES_NEW) public class CustomerService { private final CustomerRepository todoRepository; + private final SecurityService securityService; - CustomerService(CustomerRepository todoRepository) { + CustomerService(CustomerRepository todoRepository, SecurityService securityService) { this.todoRepository = todoRepository; + this.securityService = securityService; } public List list(org.springframework.data.domain.Pageable pageable) { @@ -29,6 +32,11 @@ public class CustomerService { return todoRepository.findAll(); } + public List findAllForCurrentOwner() { + ObjectId ownerId = securityService.getCurrentUserId(); + return todoRepository.findByOwner(ownerId); + } + public Customer save(Customer customer) { return todoRepository.save(customer); } 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 0c1b76b..85eedb7 100644 --- a/src/main/java/de/assecutor/votianlt/pages/view/AddJobView.java +++ b/src/main/java/de/assecutor/votianlt/pages/view/AddJobView.java @@ -32,7 +32,10 @@ import com.vaadin.flow.theme.lumo.LumoUtility; import de.assecutor.votianlt.model.Job; import de.assecutor.votianlt.model.TaskEntry; import de.assecutor.votianlt.pages.service.AddJobService; +import de.assecutor.votianlt.pages.service.CustomerService; import de.assecutor.votianlt.pages.base.ui.component.ViewToolbar; +import de.assecutor.votianlt.pages.service.AddCustomerService; +import de.assecutor.votianlt.model.Customer; import jakarta.annotation.security.RolesAllowed; import lombok.extern.slf4j.Slf4j; @@ -49,6 +52,8 @@ import java.util.Optional; public class AddJobView extends Main { private final AddJobService addJobService; + private final CustomerService customerService; + private final AddCustomerService addCustomerService; // Customer selection private ComboBox customerSelection; @@ -116,10 +121,14 @@ public class AddJobView extends Main { private final Binder binder = new Binder<>(Job.class); - public AddJobView(AddJobService addJobService) { + // Mapping für die Anzeige-Labels der Kunden zur Entität + private Map customerLabelToEntity = new LinkedHashMap<>(); + + public AddJobView(AddJobService addJobService, AddCustomerService addCustomerService, CustomerService customerService) { this.addJobService = addJobService; + this.addCustomerService = addCustomerService; + this.customerService = customerService; initializeComponents(); - populateTestData(); // Pre-populate all required fields with test data setupLayout(); setupValidation(); loadDraftIfExists(); @@ -128,9 +137,56 @@ public class AddJobView extends Main { private void initializeComponents() { // Customer selection customerSelection = new ComboBox<>("Auftraggeber/Rechnungsempfänger"); - customerSelection.setItems("Kunde01 | KOTVor K01Nach"); customerSelection.setPlaceholder("Wählen Sie einen Auftraggeber aus..."); customerSelection.setWidthFull(); + // Mit Kunden des angemeldeten Benutzers befüllen und Mapping aufbauen + List ownerCustomers = customerService.findAllForCurrentOwner(); + customerLabelToEntity.clear(); + for (Customer c : ownerCustomers) { + String label = (c.getCompanyName() != null && !c.getCompanyName().isBlank()) + ? c.getCompanyName() + " | " + + ((c.getFirstname() != null ? c.getFirstname() : "") + " " + (c.getLastName() != null ? c.getLastName() : "")).trim() + : ((c.getFirstname() != null ? c.getFirstname() : "") + " " + (c.getLastName() != null ? c.getLastName() : "")).trim(); + if (label.isBlank()) { + label = "Unbenannter Kunde"; + } + // Bei Duplikaten Label einzigartig machen + String uniqueLabel = label; + int counter = 2; + while (customerLabelToEntity.containsKey(uniqueLabel)) { + uniqueLabel = label + " (" + counter++ + ")"; + } + customerLabelToEntity.put(uniqueLabel, c); + } + customerSelection.setItems(new ArrayList<>(customerLabelToEntity.keySet())); + + // Bei Auswahl eines Kunden Abholfelder befüllen + customerSelection.addValueChangeListener(ev -> { + String selected = ev.getValue(); + if (selected == null) return; + Customer c = customerLabelToEntity.get(selected); + if (c == null) return; + + // Firma + if (c.getCompanyName() != null) { pickupCompany.setValue(c.getCompanyName()); } else { pickupCompany.clear(); } + // Anrede (nur setzen, wenn vorhanden und zulässig) + if (c.getTitle() != null && ("Herr".equalsIgnoreCase(c.getTitle()) || "Frau".equalsIgnoreCase(c.getTitle()) || "Divers".equalsIgnoreCase(c.getTitle()))) { + pickupSalutation.setValue(c.getTitle()); + } else { + pickupSalutation.clear(); + } + // Namen + if (c.getFirstname() != null) { pickupFirstName.setValue(c.getFirstname()); } else { pickupFirstName.clear(); } + if (c.getLastName() != null) { pickupLastName.setValue(c.getLastName()); } else { pickupLastName.clear(); } + // Telefon + if (c.getTelephone() != null) { pickupPhone.setValue(c.getTelephone()); } else { pickupPhone.clear(); } + // Adresse + if (c.getStreet() != null) { pickupStreet.setValue(c.getStreet()); } else { pickupStreet.clear(); } + if (c.getHouseNumber() != null) { pickupHouseNumber.setValue(c.getHouseNumber()); } else { pickupHouseNumber.clear(); } + if (c.getAddressAddition() != null) { pickupAddressAddition.setValue(c.getAddressAddition()); } else { pickupAddressAddition.clear(); } + if (c.getZip() != null) { pickupZip.setValue(c.getZip()); } else { pickupZip.clear(); } + if (c.getCity() != null) { pickupCity.setValue(c.getCity()); } else { pickupCity.clear(); } + }); preloadAddressButton = new Button("Vorbelegte Adressfelder leeren"); preloadAddressButton.addThemeVariants(ButtonVariant.LUMO_PRIMARY); @@ -138,30 +194,29 @@ public class AddJobView extends Main { // Pickup address pickupCompany = new TextField("Firma"); - pickupCompany.setPlaceholder("z.B. IKEA, McDonald's, DHL..."); - pickupCompany.setValue("Test Abholfirma GmbH"); + pickupCompany.setPlaceholder("Firmenname"); addGooglePlacesAutocomplete(pickupCompany, 0); // Stage 0 für Pickup pickupSalutation = new ComboBox<>("Anrede"); pickupSalutation.setItems("Herr", "Frau", "Divers"); pickupSalutation.setPlaceholder("Anrede wählen..."); pickupFirstName = new TextField("Vorname"); - pickupFirstName.setPlaceholder("Max"); + pickupFirstName.setPlaceholder("Vorname"); pickupFirstName.setRequiredIndicatorVisible(true); pickupLastName = new TextField("Nachname"); - pickupLastName.setPlaceholder("Mustermann"); + pickupLastName.setPlaceholder("Nachname"); pickupLastName.setRequiredIndicatorVisible(true); pickupPhone = new TextField("Telefonnummer"); - pickupPhone.setPlaceholder("+49 123 456789"); + pickupPhone.setPlaceholder("Telefonnummer"); pickupStreet = new TextField("Straße"); pickupStreet.setPlaceholder("Musterstraße"); pickupStreet.setRequiredIndicatorVisible(true); pickupHouseNumber = new TextField("Hausnummer"); - pickupHouseNumber.setPlaceholder("123"); + pickupHouseNumber.setPlaceholder("Hausnummer"); pickupHouseNumber.setRequiredIndicatorVisible(true); pickupAddressAddition = new TextField("Adresszusatz"); pickupAddressAddition.setPlaceholder("2. OG, Hinterhaus..."); pickupZip = new TextField("Postleitzahl"); - pickupZip.setPlaceholder("12345"); + pickupZip.setPlaceholder("Postleitzahl"); pickupZip.setRequiredIndicatorVisible(true); pickupCity = new TextField("Ort"); pickupCity.setPlaceholder("Hamburg"); @@ -170,30 +225,29 @@ public class AddJobView extends Main { // Delivery address deliveryCompany = new TextField("Firma"); - deliveryCompany.setPlaceholder("z.B. EDEKA, Bauhaus, Amazon..."); - deliveryCompany.setValue("Test Lieferfirma AG"); + deliveryCompany.setPlaceholder("Firmenname"); addGooglePlacesAutocomplete(deliveryCompany, 1); // Stage 1 für Delivery deliverySalutation = new ComboBox<>("Anrede"); deliverySalutation.setItems("Herr", "Frau", "Divers"); deliverySalutation.setPlaceholder("Anrede wählen..."); deliveryFirstName = new TextField("Vorname"); - deliveryFirstName.setPlaceholder("Anna"); + deliveryFirstName.setPlaceholder("Vorname"); deliveryFirstName.setRequiredIndicatorVisible(true); deliveryLastName = new TextField("Nachname"); - deliveryLastName.setPlaceholder("Beispiel"); + deliveryLastName.setPlaceholder("Nachname"); deliveryLastName.setRequiredIndicatorVisible(true); deliveryPhone = new TextField("Telefonnummer"); - deliveryPhone.setPlaceholder("+49 987 654321"); + deliveryPhone.setPlaceholder("Telefonnummer"); deliveryStreet = new TextField("Straße"); deliveryStreet.setPlaceholder("Beispielweg"); deliveryStreet.setRequiredIndicatorVisible(true); deliveryHouseNumber = new TextField("Hausnr"); - deliveryHouseNumber.setPlaceholder("456"); + deliveryHouseNumber.setPlaceholder("Hausnummer"); deliveryHouseNumber.setRequiredIndicatorVisible(true); deliveryAddressAddition = new TextField("Adresszusatz"); deliveryAddressAddition.setPlaceholder("Erdgeschoss, links..."); deliveryZip = new TextField("Postleitzahl"); - deliveryZip.setPlaceholder("54321"); + deliveryZip.setPlaceholder("Postleitzahl"); deliveryZip.setRequiredIndicatorVisible(true); deliveryCity = new TextField("Ort"); deliveryCity.setPlaceholder("Berlin"); @@ -208,7 +262,7 @@ public class AddJobView extends Main { // Price field price = new TextField("Preis"); - price.setPlaceholder("z.B. 150.00"); + price.setPlaceholder("Betrag eingeben"); price.setRequiredIndicatorVisible(true); // Erzwinge Komma als Dezimaltrennzeichen: ersetze Punkt beim Tippen @@ -230,32 +284,7 @@ public class AddJobView extends Main { submitButton.addThemeVariants(ButtonVariant.LUMO_PRIMARY); } - private void populateTestData() { - // Populate pickup address required fields - pickupCompany.setValue("Firma 1"); - pickupFirstName.setValue("Max"); - pickupLastName.setValue("Mustermann"); - pickupStreet.setValue("Musterstraße"); - pickupHouseNumber.setValue("123"); - pickupZip.setValue("20095"); - pickupCity.setValue("Hamburg"); - - // Populate delivery address required fields - deliveryFirstName.setValue("Anna"); - deliveryLastName.setValue("Beispiel"); - deliveryStreet.setValue("Beispielweg"); - deliveryHouseNumber.setValue("456"); - deliveryZip.setValue("10115"); - deliveryCity.setValue("Berlin"); - - // Populate price field - price.setValue("150.00"); - - // Populate date fields with current date + 1 day for pickup, +2 days for delivery - java.time.LocalDate today = java.time.LocalDate.now(); - pickupDate.setValue(today.plusDays(1)); - deliveryDate.setValue(today.plusDays(2)); - } + // Testdaten entfernt private void setupLayout() { setSizeFull(); @@ -805,16 +834,44 @@ public class AddJobView extends Main { cargoAreaContainer.getStyle().set("border", "1px solid var(--lumo-contrast-20pct)"); } + // NEU: Kunden anlegen, wenn Checkboxen aktiviert + if (savePickupAddress.getValue()) { + Customer pickupCustomer = new Customer(); + pickupCustomer.setCompanyName(pickupCompany.getValue()); + pickupCustomer.setTitle(pickupSalutation.getValue()); + pickupCustomer.setFirstname(pickupFirstName.getValue()); + pickupCustomer.setLastName(pickupLastName.getValue()); + pickupCustomer.setTelephone(pickupPhone.getValue()); + pickupCustomer.setStreet(pickupStreet.getValue()); + pickupCustomer.setHouseNumber(pickupHouseNumber.getValue()); + pickupCustomer.setAddressAddition(pickupAddressAddition.getValue()); + pickupCustomer.setZip(pickupZip.getValue()); + pickupCustomer.setCity(pickupCity.getValue()); + addCustomerService.addCustomer(pickupCustomer); + } + if (saveDeliveryAddress.getValue()) { + Customer deliveryCustomer = new Customer(); + deliveryCustomer.setCompanyName(deliveryCompany.getValue()); + deliveryCustomer.setTitle(deliverySalutation.getValue()); + deliveryCustomer.setFirstname(deliveryFirstName.getValue()); + deliveryCustomer.setLastName(deliveryLastName.getValue()); + deliveryCustomer.setTelephone(deliveryPhone.getValue()); + deliveryCustomer.setStreet(deliveryStreet.getValue()); + deliveryCustomer.setHouseNumber(deliveryHouseNumber.getValue()); + deliveryCustomer.setAddressAddition(deliveryAddressAddition.getValue()); + deliveryCustomer.setZip(deliveryZip.getValue()); + deliveryCustomer.setCity(deliveryCity.getValue()); + addCustomerService.addCustomer(deliveryCustomer); + } + // All validations passed, save the job with cargo items and tasks (tasks may be empty) Job savedJob = addJobService.addJobWithCargo(job, cargoFilled, tasksState); - // Erfolgsmeldung anzeigen + // Erfolgsmeldung und Navigation zur Zusammenfassung Notification successNotification = Notification.show( "Auftrag erfolgreich erstellt! Auftragsnummer: " + savedJob.getJobNumber()); - successNotification.setDuration(5000); - - // Formular zurücksetzen - clearForm(); + successNotification.setDuration(2000); + getUI().ifPresent(ui -> ui.navigate(JobSummaryView.class, savedJob.getId().toHexString())); } else { // Validation failed, show error message Notification errorNotification = Notification.show( @@ -824,15 +881,19 @@ public class AddJobView extends Main { } catch (Exception e) { // Other errors - // Reset cargo error - if (cargoError != null) cargoError.setVisible(false); - if (cargoAreaContainer != null) cargoAreaContainer.getStyle().set("border", "1px solid var(--lumo-contrast-20pct)"); + // Reset cargo error + if (cargoError != null) cargoError.setVisible(false); + if (cargoAreaContainer != null) cargoAreaContainer.getStyle().set("border", "1px solid var(--lumo-contrast-20pct)"); Notification errorNotification = Notification.show( "Fehler beim Erstellen des Auftrags: " + e.getMessage()); errorNotification.setDuration(5000); } } + // showSummary entfernt; Zusammenfassung als eigene Route + + // Zusammenfassungs-Helfer entfernt (Route übernimmt Darstellung) + /** cargoItemsState.clear(); cargoList.removeAll(); diff --git a/src/main/java/de/assecutor/votianlt/pages/view/JobSummaryView.java b/src/main/java/de/assecutor/votianlt/pages/view/JobSummaryView.java new file mode 100644 index 0000000..b037805 --- /dev/null +++ b/src/main/java/de/assecutor/votianlt/pages/view/JobSummaryView.java @@ -0,0 +1,257 @@ +package de.assecutor.votianlt.pages.view; + +import com.vaadin.flow.component.html.H3; +import com.vaadin.flow.component.html.Main; +import com.vaadin.flow.component.html.Span; +import com.vaadin.flow.component.html.Div; +import com.vaadin.flow.component.orderedlayout.HorizontalLayout; +import com.vaadin.flow.component.orderedlayout.VerticalLayout; +import com.vaadin.flow.component.UI; +import com.vaadin.flow.router.BeforeEvent; +import com.vaadin.flow.router.HasUrlParameter; +import com.vaadin.flow.router.PageTitle; +import com.vaadin.flow.router.Route; +import com.vaadin.flow.theme.lumo.LumoUtility; +import de.assecutor.votianlt.model.CargoItem; +import de.assecutor.votianlt.model.Job; +import de.assecutor.votianlt.model.TaskEntry; +import de.assecutor.votianlt.pages.base.ui.component.ViewToolbar; +import de.assecutor.votianlt.repository.CargoItemRepository; +import de.assecutor.votianlt.repository.JobRepository; +import de.assecutor.votianlt.repository.TaskRepository; +import jakarta.annotation.security.RolesAllowed; +import org.bson.types.ObjectId; + +import java.util.List; +import java.util.Locale; +import java.util.Objects; + +@Route(value = "job_summary", layout = de.assecutor.votianlt.pages.base.ui.view.MainLayout.class) +@PageTitle("Zusammenfassung") +@RolesAllowed("USER") +public class JobSummaryView extends Main implements HasUrlParameter { + + private final JobRepository jobRepository; + private final CargoItemRepository cargoItemRepository; + private final TaskRepository taskRepository; + + private VerticalLayout content; + + public JobSummaryView(JobRepository jobRepository, + CargoItemRepository cargoItemRepository, + TaskRepository taskRepository) { + this.jobRepository = jobRepository; + this.cargoItemRepository = cargoItemRepository; + this.taskRepository = taskRepository; + + setSizeFull(); + addClassNames(LumoUtility.BoxSizing.BORDER, LumoUtility.Display.FLEX, + LumoUtility.FlexDirection.COLUMN, LumoUtility.Padding.MEDIUM, + LumoUtility.Gap.SMALL); + + add(new ViewToolbar("Zusammenfassung")); + content = new VerticalLayout(); + content.setSpacing(true); + content.setPadding(true); + content.setWidthFull(); + add(content); + } + + @Override + public void setParameter(BeforeEvent event, String parameter) { + if (parameter == null || parameter.isBlank()) return; + ObjectId jobId; + try { jobId = new ObjectId(parameter); } catch (Exception e) { return; } + + Job job = jobRepository.findById(jobId).orElse(null); + if (job == null) return; + List cargo = cargoItemRepository.findByJobId(jobId); + List tasks = taskRepository.findByJobId(jobId); + + render(job, cargo, tasks); + } + + private void render(Job job, List cargoItems, List tasks) { + content.removeAll(); + + // Kopfzeile: Abholung/Lieferung + HorizontalLayout topRow = new HorizontalLayout(); + topRow.setWidthFull(); + topRow.setSpacing(true); + + VerticalLayout pickupBox = borderedBox(); + pickupBox.add(new H3("Abholung " + (job.getPickupDate() != null ? formatLocalDate(job.getPickupDate()) : ""))); + pickupBox.add(new Span(valueOrEmpty(job.getPickupCompany()))); + pickupBox.add(new Span(valueOrEmpty(job.getPickupSalutation()) + + (job.getPickupSalutation() != null ? " " : "") + + valueOrEmpty(job.getPickupFirstName()) + + (job.getPickupFirstName() != null ? " " : "") + + valueOrEmpty(job.getPickupLastName()))); + pickupBox.add(new Span(concatAddress(job.getPickupStreet(), job.getPickupHouseNumber()))); + pickupBox.add(new Span(concatZipCity(job.getPickupZip(), job.getPickupCity()))); + + VerticalLayout deliveryBox = borderedBox(); + deliveryBox.add(new H3("Lieferung " + (job.getDeliveryDate() != null ? formatLocalDate(job.getDeliveryDate()) : ""))); + deliveryBox.add(new Span(valueOrEmpty(job.getDeliveryCompany()))); + deliveryBox.add(new Span(valueOrEmpty(job.getDeliverySalutation()) + + (job.getDeliverySalutation() != null ? " " : "") + + valueOrEmpty(job.getDeliveryFirstName()) + + (job.getDeliveryFirstName() != null ? " " : "") + + valueOrEmpty(job.getDeliveryLastName()))); + deliveryBox.add(new Span(concatAddress(job.getDeliveryStreet(), job.getDeliveryHouseNumber()))); + deliveryBox.add(new Span(concatZipCity(job.getDeliveryZip(), job.getDeliveryCity()))); + + pickupBox.setWidth("50%"); + deliveryBox.setWidth("50%"); + topRow.add(pickupBox, deliveryBox); + content.add(topRow); + + // Aufgaben + VerticalLayout tasksBox = borderedBox(); + tasksBox.add(new H3("Zu quittierende Aufgaben")); + if (tasks == null || tasks.stream().filter(Objects::nonNull).map(TaskEntry::getText).filter(t -> t != null && !t.isBlank()).findAny().isEmpty()) { + tasksBox.add(new Span("Keine Aufgaben")); + } else { + tasks.stream().filter(Objects::nonNull).map(TaskEntry::getText).filter(t -> t != null && !t.isBlank()).forEach(t -> tasksBox.add(new Span("• " + t))); + } + content.add(tasksBox); + + // Fracht und weitere Infos + HorizontalLayout midRow = new HorizontalLayout(); + midRow.setWidthFull(); + midRow.setSpacing(true); + + VerticalLayout cargoBox = borderedBox(); + cargoBox.add(new H3("Zu transportierende Fracht")); + if (cargoItems == null || cargoItems.isEmpty()) { + cargoBox.add(new Span("Keine Frachtangaben")); + } else { + for (CargoItem ci : cargoItems) { + if (ci == null) continue; + String desc = ci.getDescription(); + Integer qty = ci.getQuantity(); + String dims = dimString(ci); + String weight = ci.getWeightKg() != null ? ci.getWeightKg() + " kg" : ""; + String line = (qty != null ? qty + " x " : "") + (desc != null ? desc : "") + (dims.isBlank() ? "" : " " + dims) + (weight.isBlank() ? "" : " " + weight); + if (!line.isBlank()) cargoBox.add(new Span(line)); + } + } + + VerticalLayout infoBox = borderedBox(); + infoBox.add(new H3("Weitere Informationen")); + infoBox.add(new Span("Preis: " + (job.getPrice() != null ? formatPrice(job.getPrice()) : "-"))); + if (job.getRemark() != null && !job.getRemark().isBlank()) { + infoBox.add(new Span("Bemerkung: " + job.getRemark())); + } + if (job.isDigitalProcessing()) { + infoBox.add(new Span("Digitale Abwicklung per App: aktiviert")); + } + if (job.getAppUser() != null && !job.getAppUser().isBlank()) { + infoBox.add(new Span("App-Nutzer: " + job.getAppUser())); + } + + cargoBox.setWidth("50%"); + infoBox.setWidth("50%"); + midRow.add(cargoBox, infoBox); + content.add(midRow); + + // Google Maps Karte mit Route + addRouteMap(job); + } + + private VerticalLayout borderedBox() { + VerticalLayout box = new VerticalLayout(); + box.getStyle().set("border", "1px solid var(--lumo-contrast-20pct)"); + box.getStyle().set("border-radius", "var(--lumo-border-radius-m)"); + box.getStyle().set("background-color", "var(--lumo-base-color)"); + box.setPadding(true); + box.setSpacing(false); + return box; + } + + private String formatLocalDate(java.time.LocalDate date) { + try { + java.time.format.DateTimeFormatter fmt = java.time.format.DateTimeFormatter.ofPattern("dd.MM.yyyy").withLocale(Locale.GERMANY); + return date.format(fmt); + } catch (Exception e) { return ""; } + } + + private String valueOrEmpty(String v) { return v == null ? "" : v; } + + private String concatAddress(String street, String house) { + String s = valueOrEmpty(street); + String h = valueOrEmpty(house); + return (s + (h.isBlank() ? "" : " " + h)).trim(); + } + + private String concatZipCity(String zip, String city) { + String z = valueOrEmpty(zip); + String c = valueOrEmpty(city); + if (!z.isBlank() && !c.isBlank()) return z + " " + c; + return (z + " " + c).trim(); + } + + private String dimString(CargoItem ci) { + String len = ci.getLengthMm() != null ? ci.getLengthMm().intValue() + " mm" : ""; + String wid = ci.getWidthMm() != null ? ci.getWidthMm().intValue() + " mm" : ""; + String hei = ci.getHeightMm() != null ? ci.getHeightMm().intValue() + " mm" : ""; + String combined = String.join(" x ", java.util.stream.Stream.of(len, wid, hei) + .filter(s -> s != null && !s.isBlank()).toList()); + return combined.isBlank() ? "" : combined; + } + + private String formatPrice(java.math.BigDecimal price) { + java.text.NumberFormat nf = java.text.NumberFormat.getCurrencyInstance(Locale.GERMANY); + return nf.format(price); + } + + private void addRouteMap(Job job) { + // Baue Adress-Strings + String origin = (concatAddress(job.getPickupStreet(), job.getPickupHouseNumber()) + ", " + concatZipCity(job.getPickupZip(), job.getPickupCity())).trim(); + String destination = (concatAddress(job.getDeliveryStreet(), job.getDeliveryHouseNumber()) + ", " + concatZipCity(job.getDeliveryZip(), job.getDeliveryCity())).trim(); + + if (origin.isBlank() || destination.isBlank()) { + // Wenn nicht genug Daten vorhanden sind, Karte nicht anzeigen + return; + } + + Div map = new Div(); + map.setWidthFull(); + map.setHeight("520px"); + map.getStyle().set("border", "1px solid var(--lumo-contrast-20pct)"); + map.getStyle().set("border-radius", "var(--lumo-border-radius-m)"); + content.add(map); + + String js = ( + "(function(){" + + " var host = $0;" + + " function init(){" + + " var map = new google.maps.Map(host, {center: {lat: 51.163, lng: 10.447}, zoom: 6});" + + " var ds = new google.maps.DirectionsService();" + + " var dr = new google.maps.DirectionsRenderer({map: map});" + + " ds.route({" + + " origin: '" + escapeJs(origin) + "'," + + " destination: '" + escapeJs(destination) + "'," + + " travelMode: google.maps.TravelMode.DRIVING" + + " }, function(res, status){ if(status==='OK'){ dr.setDirections(res); } });" + + " }" + + " if (!(window.google && window.google.maps)) {" + + " var s=document.createElement('script');" + + " s.src='https://maps.googleapis.com/maps/api/js?key=AIzaSyDnbitL06iLp3elmj-WtPudCykX9xvXcVE&libraries=places';" + + " s.onload=init; document.head.appendChild(s);" + + " } else { init(); }" + + "})();" + ); + + // Ausführen im UI-Kontext + map.getElement().executeJs(js, map.getElement()); + } + + // Hilfsfunktion zum einfachen Escapen von JS-Zeichen in Strings + private String escapeJs(String s) { + if (s == null) return ""; + return s.replace("\\", "\\\\").replace("'", "\\'").replace("\n", " ").replace("\r", " "); + } +} + +