Stationen-Dialoge als eigene Komponenten, AddJob-Seite auf Einzelansicht ohne Tabs umgestellt
- StationTile, PickupStationDialog und DeliveryStationDialog als eigenständige UI-Komponenten extrahiert - Preis- und Leistungselemente (Streckeneingabe, Leistungen-Grid, Zusammenfassung, Bemerkung) unter das Stationen-Grid verschoben - TabSheet entfernt, alle Inhalte auf einer einzigen Seite dargestellt - LlmRestClient-Formatierung angepasst, BaseTask und TaskRepository erweitert - Übersetzungen für neue Dialog-Labels ergänzt Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -30,8 +30,7 @@ public class LlmRestClient {
|
|||||||
public LlmRestClient(@Value("${app.ai.lmstudio.base-url}") String lmstudioBaseUrl,
|
public LlmRestClient(@Value("${app.ai.lmstudio.base-url}") String lmstudioBaseUrl,
|
||||||
@Value("${app.ai.lmstudio.model}") String lmstudioModel,
|
@Value("${app.ai.lmstudio.model}") String lmstudioModel,
|
||||||
@Value("${app.ai.lmstudio.htaccess-username}") String lmstudioHtaccessUsername,
|
@Value("${app.ai.lmstudio.htaccess-username}") String lmstudioHtaccessUsername,
|
||||||
@Value("${app.ai.lmstudio.htaccess-password}") String lmstudioHtaccessPassword,
|
@Value("${app.ai.lmstudio.htaccess-password}") String lmstudioHtaccessPassword, ObjectMapper objectMapper) {
|
||||||
ObjectMapper objectMapper) {
|
|
||||||
|
|
||||||
this.model = lmstudioModel;
|
this.model = lmstudioModel;
|
||||||
this.objectMapper = objectMapper;
|
this.objectMapper = objectMapper;
|
||||||
@@ -39,17 +38,16 @@ public class LlmRestClient {
|
|||||||
WebClient.Builder builder = WebClient.builder();
|
WebClient.Builder builder = WebClient.builder();
|
||||||
builder.baseUrl(lmstudioBaseUrl + "/v1/chat/completions");
|
builder.baseUrl(lmstudioBaseUrl + "/v1/chat/completions");
|
||||||
|
|
||||||
if (lmstudioHtaccessUsername != null && !lmstudioHtaccessUsername.isBlank()
|
if (lmstudioHtaccessUsername != null && !lmstudioHtaccessUsername.isBlank() && lmstudioHtaccessPassword != null
|
||||||
&& lmstudioHtaccessPassword != null && !lmstudioHtaccessPassword.isBlank()) {
|
&& !lmstudioHtaccessPassword.isBlank()) {
|
||||||
String credentials = lmstudioHtaccessUsername + ":" + lmstudioHtaccessPassword;
|
String credentials = lmstudioHtaccessUsername + ":" + lmstudioHtaccessPassword;
|
||||||
String encoded = Base64.getEncoder()
|
String encoded = Base64.getEncoder().encodeToString(credentials.getBytes(StandardCharsets.UTF_8));
|
||||||
.encodeToString(credentials.getBytes(StandardCharsets.UTF_8));
|
|
||||||
builder.defaultHeader(HttpHeaders.AUTHORIZATION, "Basic " + encoded);
|
builder.defaultHeader(HttpHeaders.AUTHORIZATION, "Basic " + encoded);
|
||||||
log.info("LlmRestClient initialized (with HTACCESS auth) - URL: {}/v1/chat/completions, Model: {}",
|
log.info("LlmRestClient initialized (with HTACCESS auth) - URL: {}/v1/chat/completions, Model: {}",
|
||||||
lmstudioBaseUrl, lmstudioModel);
|
lmstudioBaseUrl, lmstudioModel);
|
||||||
} else {
|
} else {
|
||||||
log.info("LlmRestClient initialized - URL: {}/v1/chat/completions, Model: {}",
|
log.info("LlmRestClient initialized - URL: {}/v1/chat/completions, Model: {}", lmstudioBaseUrl,
|
||||||
lmstudioBaseUrl, lmstudioModel);
|
lmstudioModel);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.webClient = builder.build();
|
this.webClient = builder.build();
|
||||||
@@ -88,8 +86,7 @@ public class LlmRestClient {
|
|||||||
Map.of("role", "user", "content", userMessage)),
|
Map.of("role", "user", "content", userMessage)),
|
||||||
"temperature", temperature, "max_tokens", maxTokens, "stream", false);
|
"temperature", temperature, "max_tokens", maxTokens, "stream", false);
|
||||||
|
|
||||||
log.info("Sending request to LLM (model: {}, prompt length: {} chars)...", model,
|
log.info("Sending request to LLM (model: {}, prompt length: {} chars)...", model, userMessage.length());
|
||||||
userMessage.length());
|
|
||||||
long startTime = System.currentTimeMillis();
|
long startTime = System.currentTimeMillis();
|
||||||
|
|
||||||
String response = webClient.post().contentType(MediaType.APPLICATION_JSON).bodyValue(request).retrieve()
|
String response = webClient.post().contentType(MediaType.APPLICATION_JSON).bodyValue(request).retrieve()
|
||||||
|
|||||||
@@ -32,6 +32,9 @@ public abstract class BaseTask {
|
|||||||
@JsonIgnore
|
@JsonIgnore
|
||||||
private ObjectId jobId;
|
private ObjectId jobId;
|
||||||
|
|
||||||
|
@Field("station_order")
|
||||||
|
private Integer stationOrder;
|
||||||
|
|
||||||
@Field("task_order")
|
@Field("task_order")
|
||||||
private Integer taskOrder = 0;
|
private Integer taskOrder = 0;
|
||||||
|
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -188,7 +188,8 @@ public class DeliveryStationTile extends VerticalLayout {
|
|||||||
// Register change listeners on all fields
|
// Register change listeners on all fields
|
||||||
setupChangeListeners();
|
setupChangeListeners();
|
||||||
|
|
||||||
// Store references to expanded-mode components (excluding titleLayout which stays visible)
|
// Store references to expanded-mode components (excluding titleLayout which
|
||||||
|
// stays visible)
|
||||||
expandedOnlyComponents = getChildren().filter(c -> c != titleLayout).toList();
|
expandedOnlyComponents = getChildren().filter(c -> c != titleLayout).toList();
|
||||||
|
|
||||||
getStyle().set("transition", "width 0.3s ease, min-width 0.3s ease");
|
getStyle().set("transition", "width 0.3s ease, min-width 0.3s ease");
|
||||||
@@ -478,8 +479,8 @@ public class DeliveryStationTile extends VerticalLayout {
|
|||||||
private void addCollapsedLine(String text) {
|
private void addCollapsedLine(String text) {
|
||||||
if (text != null && !text.trim().isEmpty()) {
|
if (text != null && !text.trim().isEmpty()) {
|
||||||
Span span = new Span(text);
|
Span span = new Span(text);
|
||||||
span.getStyle().set("font-size", "var(--lumo-font-size-xs)").set("word-break", "break-word")
|
span.getStyle().set("font-size", "var(--lumo-font-size-xs)").set("word-break", "break-word").set("color",
|
||||||
.set("color", "var(--lumo-secondary-text-color)");
|
"var(--lumo-secondary-text-color)");
|
||||||
collapsedContent.add(span);
|
collapsedContent.add(span);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,778 @@
|
|||||||
|
package de.assecutor.votianlt.pages.base.ui.component;
|
||||||
|
|
||||||
|
import com.vaadin.flow.component.button.Button;
|
||||||
|
import com.vaadin.flow.component.button.ButtonVariant;
|
||||||
|
import com.vaadin.flow.component.checkbox.Checkbox;
|
||||||
|
import com.vaadin.flow.component.combobox.ComboBox;
|
||||||
|
import com.vaadin.flow.component.datepicker.DatePicker;
|
||||||
|
import com.vaadin.flow.component.dialog.Dialog;
|
||||||
|
import com.vaadin.flow.component.html.H3;
|
||||||
|
import com.vaadin.flow.component.html.Span;
|
||||||
|
import com.vaadin.flow.component.icon.Icon;
|
||||||
|
import com.vaadin.flow.component.icon.VaadinIcon;
|
||||||
|
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.tabs.TabSheet;
|
||||||
|
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 de.assecutor.votianlt.model.AppUser;
|
||||||
|
import de.assecutor.votianlt.model.CargoItem;
|
||||||
|
import de.assecutor.votianlt.model.Customer;
|
||||||
|
|
||||||
|
import java.time.LocalDate;
|
||||||
|
import java.time.LocalTime;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dialog for editing pickup station data. Contains address form fields,
|
||||||
|
* customer selection, appointments & processing tab, and cargo tab.
|
||||||
|
*/
|
||||||
|
public class PickupStationDialog extends Dialog {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Data holder for pickup station fields.
|
||||||
|
*/
|
||||||
|
public static class PickupData {
|
||||||
|
private String company;
|
||||||
|
private String salutation;
|
||||||
|
private String firstName;
|
||||||
|
private String lastName;
|
||||||
|
private String phone;
|
||||||
|
private String street;
|
||||||
|
private String houseNumber;
|
||||||
|
private String addressAddition;
|
||||||
|
private String zip;
|
||||||
|
private String city;
|
||||||
|
private boolean saveAddress;
|
||||||
|
private String customerSelection;
|
||||||
|
private LocalDate appointmentDate;
|
||||||
|
private LocalTime appointmentTime;
|
||||||
|
private boolean digitalProcessing;
|
||||||
|
private AppUser appUser;
|
||||||
|
private List<CargoItem> cargoItems = new ArrayList<>();
|
||||||
|
|
||||||
|
public String getCompany() {
|
||||||
|
return company;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCompany(String company) {
|
||||||
|
this.company = company;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getSalutation() {
|
||||||
|
return salutation;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSalutation(String salutation) {
|
||||||
|
this.salutation = salutation;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getFirstName() {
|
||||||
|
return firstName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setFirstName(String firstName) {
|
||||||
|
this.firstName = firstName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getLastName() {
|
||||||
|
return lastName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setLastName(String lastName) {
|
||||||
|
this.lastName = lastName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getPhone() {
|
||||||
|
return phone;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPhone(String phone) {
|
||||||
|
this.phone = phone;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getStreet() {
|
||||||
|
return street;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setStreet(String street) {
|
||||||
|
this.street = street;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getHouseNumber() {
|
||||||
|
return houseNumber;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setHouseNumber(String houseNumber) {
|
||||||
|
this.houseNumber = houseNumber;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getAddressAddition() {
|
||||||
|
return addressAddition;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAddressAddition(String addressAddition) {
|
||||||
|
this.addressAddition = addressAddition;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getZip() {
|
||||||
|
return zip;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setZip(String zip) {
|
||||||
|
this.zip = zip;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getCity() {
|
||||||
|
return city;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCity(String city) {
|
||||||
|
this.city = city;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isSaveAddress() {
|
||||||
|
return saveAddress;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSaveAddress(boolean saveAddress) {
|
||||||
|
this.saveAddress = saveAddress;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getCustomerSelection() {
|
||||||
|
return customerSelection;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCustomerSelection(String customerSelection) {
|
||||||
|
this.customerSelection = customerSelection;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LocalDate getAppointmentDate() {
|
||||||
|
return appointmentDate;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAppointmentDate(LocalDate appointmentDate) {
|
||||||
|
this.appointmentDate = appointmentDate;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LocalTime getAppointmentTime() {
|
||||||
|
return appointmentTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAppointmentTime(LocalTime appointmentTime) {
|
||||||
|
this.appointmentTime = appointmentTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isDigitalProcessing() {
|
||||||
|
return digitalProcessing;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDigitalProcessing(boolean digitalProcessing) {
|
||||||
|
this.digitalProcessing = digitalProcessing;
|
||||||
|
}
|
||||||
|
|
||||||
|
public AppUser getAppUser() {
|
||||||
|
return appUser;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAppUser(AppUser appUser) {
|
||||||
|
this.appUser = appUser;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<CargoItem> getCargoItems() {
|
||||||
|
return cargoItems;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCargoItems(List<CargoItem> cargoItems) {
|
||||||
|
this.cargoItems = cargoItems != null ? cargoItems : new ArrayList<>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface SaveListener {
|
||||||
|
void onSave(PickupData data);
|
||||||
|
}
|
||||||
|
|
||||||
|
private final ComboBox<String> company;
|
||||||
|
private final ComboBox<String> salutation;
|
||||||
|
private final TextField firstName;
|
||||||
|
private final TextField lastName;
|
||||||
|
private final TextField phone;
|
||||||
|
private final TextField street;
|
||||||
|
private final TextField houseNumber;
|
||||||
|
private final TextField addressAddition;
|
||||||
|
private final TextField zip;
|
||||||
|
private final TextField city;
|
||||||
|
private final Checkbox saveAddress;
|
||||||
|
|
||||||
|
private final ComboBox<String> customerComboBox;
|
||||||
|
private DatePicker appointmentDatePicker;
|
||||||
|
private TimePicker appointmentTimePicker;
|
||||||
|
private Checkbox digitalProcessingCheckbox;
|
||||||
|
private ComboBox<AppUser> appUserComboBox;
|
||||||
|
private final List<CargoItem> cargoItemsState = new ArrayList<>();
|
||||||
|
private VerticalLayout cargoList;
|
||||||
|
|
||||||
|
private final DeliveryStationTile.TranslationHelper translationHelper;
|
||||||
|
|
||||||
|
public PickupStationDialog(String dialogTitle, List<Customer> customers,
|
||||||
|
DeliveryStationTile.TranslationHelper translationHelper, SaveListener saveListener,
|
||||||
|
List<AppUser> availableAppUsers) {
|
||||||
|
|
||||||
|
this.translationHelper = translationHelper;
|
||||||
|
|
||||||
|
setHeaderTitle(dialogTitle);
|
||||||
|
setCloseOnOutsideClick(false);
|
||||||
|
setWidth("800px");
|
||||||
|
setHeight("80vh");
|
||||||
|
|
||||||
|
// Address form
|
||||||
|
VerticalLayout formLayout = new VerticalLayout();
|
||||||
|
formLayout.setPadding(true);
|
||||||
|
formLayout.setSpacing(true);
|
||||||
|
formLayout.setWidthFull();
|
||||||
|
|
||||||
|
// Customer selection
|
||||||
|
customerComboBox = new ComboBox<>(translationHelper.getTranslation("addjob.customer.label"));
|
||||||
|
customerComboBox.setPlaceholder(translationHelper.getTranslation("addjob.customer.placeholder"));
|
||||||
|
customerComboBox.setRequiredIndicatorVisible(true);
|
||||||
|
customerComboBox.setWidthFull();
|
||||||
|
|
||||||
|
Map<String, Customer> customerLabelMap = new LinkedHashMap<>();
|
||||||
|
for (Customer c : customers) {
|
||||||
|
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 = translationHelper.getTranslation("addjob.customer.unnamed");
|
||||||
|
}
|
||||||
|
String uniqueLabel = label;
|
||||||
|
int counter = 2;
|
||||||
|
while (customerLabelMap.containsKey(uniqueLabel)) {
|
||||||
|
uniqueLabel = label + " (" + counter++ + ")";
|
||||||
|
}
|
||||||
|
customerLabelMap.put(uniqueLabel, c);
|
||||||
|
}
|
||||||
|
customerComboBox.setItems(new ArrayList<>(customerLabelMap.keySet()));
|
||||||
|
|
||||||
|
// Company with autocomplete
|
||||||
|
company = new ComboBox<>(translationHelper.getTranslation("profile.company"));
|
||||||
|
company.setPlaceholder(translationHelper.getTranslation("addjob.address.company.placeholder"));
|
||||||
|
company.setAllowCustomValue(true);
|
||||||
|
company.setWidthFull();
|
||||||
|
setupCompanyAutocomplete(company, customers);
|
||||||
|
|
||||||
|
// Salutation
|
||||||
|
salutation = new ComboBox<>(translationHelper.getTranslation("addjob.address.salutation"));
|
||||||
|
salutation.setItems(translationHelper.getTranslation("addjob.salutation.mr"),
|
||||||
|
translationHelper.getTranslation("addjob.salutation.ms"),
|
||||||
|
translationHelper.getTranslation("addjob.salutation.other"));
|
||||||
|
salutation.setPlaceholder(translationHelper.getTranslation("addjob.address.salutation.placeholder"));
|
||||||
|
salutation.setWidthFull();
|
||||||
|
|
||||||
|
// First name
|
||||||
|
firstName = new TextField(translationHelper.getTranslation("profile.firstname"));
|
||||||
|
firstName.setPlaceholder(translationHelper.getTranslation("profile.firstname"));
|
||||||
|
firstName.setRequiredIndicatorVisible(true);
|
||||||
|
firstName.setWidthFull();
|
||||||
|
|
||||||
|
// Last name
|
||||||
|
lastName = new TextField(translationHelper.getTranslation("profile.lastname"));
|
||||||
|
lastName.setPlaceholder(translationHelper.getTranslation("profile.lastname"));
|
||||||
|
lastName.setRequiredIndicatorVisible(true);
|
||||||
|
lastName.setWidthFull();
|
||||||
|
|
||||||
|
// Phone
|
||||||
|
phone = new TextField(translationHelper.getTranslation("profile.phone"));
|
||||||
|
phone.setPlaceholder(translationHelper.getTranslation("profile.phone"));
|
||||||
|
phone.setWidthFull();
|
||||||
|
|
||||||
|
// Street + house number
|
||||||
|
street = new TextField(translationHelper.getTranslation("profile.street"));
|
||||||
|
street.setPlaceholder(translationHelper.getTranslation("profile.street"));
|
||||||
|
street.setRequiredIndicatorVisible(true);
|
||||||
|
|
||||||
|
houseNumber = new TextField(translationHelper.getTranslation("profile.housenr"));
|
||||||
|
houseNumber.setPlaceholder(translationHelper.getTranslation("addjob.address.housenumber"));
|
||||||
|
houseNumber.setRequiredIndicatorVisible(true);
|
||||||
|
|
||||||
|
HorizontalLayout streetLayout = new HorizontalLayout();
|
||||||
|
streetLayout.setWidthFull();
|
||||||
|
streetLayout.setSpacing(true);
|
||||||
|
street.setWidth("70%");
|
||||||
|
houseNumber.setWidth("30%");
|
||||||
|
streetLayout.add(street, houseNumber);
|
||||||
|
|
||||||
|
// Address addition
|
||||||
|
addressAddition = new TextField(translationHelper.getTranslation("profile.addressadd"));
|
||||||
|
addressAddition
|
||||||
|
.setPlaceholder(translationHelper.getTranslation("addjob.address.delivery.addition.placeholder"));
|
||||||
|
addressAddition.setWidthFull();
|
||||||
|
|
||||||
|
// Zip + city
|
||||||
|
zip = new TextField(translationHelper.getTranslation("profile.zip"));
|
||||||
|
zip.setPlaceholder(translationHelper.getTranslation("profile.zip"));
|
||||||
|
zip.setRequiredIndicatorVisible(true);
|
||||||
|
|
||||||
|
city = new TextField(translationHelper.getTranslation("addjob.address.city"));
|
||||||
|
city.setPlaceholder(translationHelper.getTranslation("addjob.address.city"));
|
||||||
|
city.setRequiredIndicatorVisible(true);
|
||||||
|
|
||||||
|
HorizontalLayout zipCityLayout = new HorizontalLayout();
|
||||||
|
zipCityLayout.setWidthFull();
|
||||||
|
zipCityLayout.setSpacing(true);
|
||||||
|
zip.setWidth("30%");
|
||||||
|
city.setWidth("70%");
|
||||||
|
zipCityLayout.add(zip, city);
|
||||||
|
|
||||||
|
// Save address checkbox
|
||||||
|
saveAddress = new Checkbox(translationHelper.getTranslation("addjob.address.save"));
|
||||||
|
saveAddress.setValue(true);
|
||||||
|
saveAddress.setWidthFull();
|
||||||
|
|
||||||
|
// Customer selection fills address fields
|
||||||
|
customerComboBox.addValueChangeListener(ev -> {
|
||||||
|
String selected = ev.getValue();
|
||||||
|
if (selected == null)
|
||||||
|
return;
|
||||||
|
Customer c = customerLabelMap.get(selected);
|
||||||
|
if (c == null)
|
||||||
|
return;
|
||||||
|
saveAddress.setValue(false);
|
||||||
|
if (c.getCompanyName() != null)
|
||||||
|
company.setValue(c.getCompanyName());
|
||||||
|
else
|
||||||
|
company.clear();
|
||||||
|
if (c.getTitle() != null && ("Herr".equalsIgnoreCase(c.getTitle()) || "Frau".equalsIgnoreCase(c.getTitle())
|
||||||
|
|| "Divers".equalsIgnoreCase(c.getTitle())))
|
||||||
|
salutation.setValue(c.getTitle());
|
||||||
|
else
|
||||||
|
salutation.clear();
|
||||||
|
if (c.getFirstname() != null)
|
||||||
|
firstName.setValue(c.getFirstname());
|
||||||
|
else
|
||||||
|
firstName.clear();
|
||||||
|
if (c.getLastName() != null)
|
||||||
|
lastName.setValue(c.getLastName());
|
||||||
|
else
|
||||||
|
lastName.clear();
|
||||||
|
if (c.getTelephone() != null)
|
||||||
|
phone.setValue(c.getTelephone());
|
||||||
|
else
|
||||||
|
phone.clear();
|
||||||
|
if (c.getStreet() != null)
|
||||||
|
street.setValue(c.getStreet());
|
||||||
|
else
|
||||||
|
street.clear();
|
||||||
|
if (c.getHouseNumber() != null)
|
||||||
|
houseNumber.setValue(c.getHouseNumber());
|
||||||
|
else
|
||||||
|
houseNumber.clear();
|
||||||
|
if (c.getAddressAddition() != null)
|
||||||
|
addressAddition.setValue(c.getAddressAddition());
|
||||||
|
else
|
||||||
|
addressAddition.clear();
|
||||||
|
if (c.getZip() != null)
|
||||||
|
zip.setValue(c.getZip());
|
||||||
|
else
|
||||||
|
zip.clear();
|
||||||
|
if (c.getCity() != null)
|
||||||
|
city.setValue(c.getCity());
|
||||||
|
else
|
||||||
|
city.clear();
|
||||||
|
});
|
||||||
|
|
||||||
|
formLayout.add(customerComboBox, company, salutation, firstName, lastName, phone, streetLayout, addressAddition,
|
||||||
|
zipCityLayout, saveAddress);
|
||||||
|
|
||||||
|
// TabSheet with address, appointments, and cargo tabs
|
||||||
|
TabSheet tabSheet = new TabSheet();
|
||||||
|
tabSheet.setWidthFull();
|
||||||
|
tabSheet.setSizeFull();
|
||||||
|
|
||||||
|
tabSheet.add(translationHelper.getTranslation("addjob.tab.addresses"), formLayout);
|
||||||
|
tabSheet.add(translationHelper.getTranslation("addjob.tab.appointments"),
|
||||||
|
createAppointmentsTab(availableAppUsers));
|
||||||
|
tabSheet.add(translationHelper.getTranslation("addjob.tab.cargo"), createCargoTab());
|
||||||
|
|
||||||
|
add(tabSheet);
|
||||||
|
|
||||||
|
// Footer buttons
|
||||||
|
Button saveButton = new Button(translationHelper.getTranslation("dialog.confirm"), e -> {
|
||||||
|
PickupData data = collectData();
|
||||||
|
if (saveListener != null) {
|
||||||
|
saveListener.onSave(data);
|
||||||
|
}
|
||||||
|
close();
|
||||||
|
});
|
||||||
|
saveButton.addThemeVariants(ButtonVariant.LUMO_PRIMARY);
|
||||||
|
|
||||||
|
Button cancelButton = new Button(translationHelper.getTranslation("dialog.cancel"), e -> close());
|
||||||
|
|
||||||
|
getFooter().add(cancelButton, saveButton);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pre-fills the dialog fields with existing data.
|
||||||
|
*/
|
||||||
|
public void setData(PickupData data) {
|
||||||
|
if (data == null)
|
||||||
|
return;
|
||||||
|
if (data.getCompany() != null)
|
||||||
|
company.setValue(data.getCompany());
|
||||||
|
if (data.getSalutation() != null)
|
||||||
|
salutation.setValue(data.getSalutation());
|
||||||
|
if (data.getFirstName() != null)
|
||||||
|
firstName.setValue(data.getFirstName());
|
||||||
|
if (data.getLastName() != null)
|
||||||
|
lastName.setValue(data.getLastName());
|
||||||
|
if (data.getPhone() != null)
|
||||||
|
phone.setValue(data.getPhone());
|
||||||
|
if (data.getStreet() != null)
|
||||||
|
street.setValue(data.getStreet());
|
||||||
|
if (data.getHouseNumber() != null)
|
||||||
|
houseNumber.setValue(data.getHouseNumber());
|
||||||
|
if (data.getAddressAddition() != null)
|
||||||
|
addressAddition.setValue(data.getAddressAddition());
|
||||||
|
if (data.getZip() != null)
|
||||||
|
zip.setValue(data.getZip());
|
||||||
|
if (data.getCity() != null)
|
||||||
|
city.setValue(data.getCity());
|
||||||
|
saveAddress.setValue(data.isSaveAddress());
|
||||||
|
|
||||||
|
if (data.getCustomerSelection() != null) {
|
||||||
|
customerComboBox.setValue(data.getCustomerSelection());
|
||||||
|
}
|
||||||
|
if (data.getAppointmentDate() != null) {
|
||||||
|
appointmentDatePicker.setValue(data.getAppointmentDate());
|
||||||
|
}
|
||||||
|
if (data.getAppointmentTime() != null) {
|
||||||
|
appointmentTimePicker.setValue(data.getAppointmentTime());
|
||||||
|
}
|
||||||
|
digitalProcessingCheckbox.setValue(data.isDigitalProcessing());
|
||||||
|
if (data.getAppUser() != null) {
|
||||||
|
appUserComboBox.setValue(data.getAppUser());
|
||||||
|
}
|
||||||
|
if (data.getCargoItems() != null && !data.getCargoItems().isEmpty() && cargoList != null) {
|
||||||
|
cargoItemsState.clear();
|
||||||
|
cargoList.removeAll();
|
||||||
|
for (CargoItem item : data.getCargoItems()) {
|
||||||
|
addCargoRowWithData(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private PickupData collectData() {
|
||||||
|
PickupData data = new PickupData();
|
||||||
|
data.setCompany(company.getValue());
|
||||||
|
data.setSalutation(salutation.getValue());
|
||||||
|
data.setFirstName(firstName.getValue());
|
||||||
|
data.setLastName(lastName.getValue());
|
||||||
|
data.setPhone(phone.getValue());
|
||||||
|
data.setStreet(street.getValue());
|
||||||
|
data.setHouseNumber(houseNumber.getValue());
|
||||||
|
data.setAddressAddition(addressAddition.getValue());
|
||||||
|
data.setZip(zip.getValue());
|
||||||
|
data.setCity(city.getValue());
|
||||||
|
data.setSaveAddress(saveAddress.getValue());
|
||||||
|
data.setCustomerSelection(customerComboBox.getValue());
|
||||||
|
if (appointmentDatePicker != null) {
|
||||||
|
data.setAppointmentDate(appointmentDatePicker.getValue());
|
||||||
|
}
|
||||||
|
if (appointmentTimePicker != null) {
|
||||||
|
data.setAppointmentTime(appointmentTimePicker.getValue());
|
||||||
|
}
|
||||||
|
if (digitalProcessingCheckbox != null) {
|
||||||
|
data.setDigitalProcessing(digitalProcessingCheckbox.getValue());
|
||||||
|
}
|
||||||
|
if (appUserComboBox != null) {
|
||||||
|
data.setAppUser(appUserComboBox.getValue());
|
||||||
|
}
|
||||||
|
data.setCargoItems(new ArrayList<>(cargoItemsState));
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setupCompanyAutocomplete(ComboBox<String> companyField, List<Customer> customers) {
|
||||||
|
List<String> companyNames = customers.stream().map(Customer::getCompanyName)
|
||||||
|
.filter(name -> name != null && !name.trim().isEmpty()).distinct().sorted().toList();
|
||||||
|
|
||||||
|
companyField.setItems(companyNames);
|
||||||
|
|
||||||
|
companyField.addValueChangeListener(event -> {
|
||||||
|
String selectedCompany = event.getValue();
|
||||||
|
if (selectedCompany == null || selectedCompany.trim().isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Optional<Customer> matchingCustomer = customers.stream()
|
||||||
|
.filter(c -> selectedCompany.equals(c.getCompanyName())).findFirst();
|
||||||
|
|
||||||
|
if (matchingCustomer.isPresent()) {
|
||||||
|
Customer customer = matchingCustomer.get();
|
||||||
|
if (customer.getTitle() != null
|
||||||
|
&& ("Herr".equalsIgnoreCase(customer.getTitle()) || "Frau".equalsIgnoreCase(customer.getTitle())
|
||||||
|
|| "Divers".equalsIgnoreCase(customer.getTitle()))) {
|
||||||
|
salutation.setValue(customer.getTitle());
|
||||||
|
}
|
||||||
|
if (customer.getFirstname() != null)
|
||||||
|
firstName.setValue(customer.getFirstname());
|
||||||
|
if (customer.getLastName() != null)
|
||||||
|
lastName.setValue(customer.getLastName());
|
||||||
|
if (customer.getTelephone() != null)
|
||||||
|
phone.setValue(customer.getTelephone());
|
||||||
|
if (customer.getStreet() != null)
|
||||||
|
street.setValue(customer.getStreet());
|
||||||
|
if (customer.getHouseNumber() != null)
|
||||||
|
houseNumber.setValue(customer.getHouseNumber());
|
||||||
|
if (customer.getAddressAddition() != null)
|
||||||
|
addressAddition.setValue(customer.getAddressAddition());
|
||||||
|
if (customer.getZip() != null)
|
||||||
|
zip.setValue(customer.getZip());
|
||||||
|
if (customer.getCity() != null)
|
||||||
|
city.setValue(customer.getCity());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
companyField.addCustomValueSetListener(event -> companyField.setValue(event.getDetail()));
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================
|
||||||
|
// Appointments & Processing Tab
|
||||||
|
// ============================================
|
||||||
|
|
||||||
|
private VerticalLayout createAppointmentsTab(List<AppUser> availableAppUsers) {
|
||||||
|
VerticalLayout tabContent = new VerticalLayout();
|
||||||
|
tabContent.setSizeFull();
|
||||||
|
tabContent.setPadding(true);
|
||||||
|
tabContent.setSpacing(true);
|
||||||
|
tabContent.setDefaultHorizontalComponentAlignment(FlexComponent.Alignment.CENTER);
|
||||||
|
|
||||||
|
VerticalLayout content = new VerticalLayout();
|
||||||
|
content.setPadding(false);
|
||||||
|
content.setSpacing(true);
|
||||||
|
content.setWidth("720px");
|
||||||
|
content.setDefaultHorizontalComponentAlignment(FlexComponent.Alignment.STRETCH);
|
||||||
|
|
||||||
|
// Digital processing + App user
|
||||||
|
digitalProcessingCheckbox = new Checkbox(translationHelper.getTranslation("profile.settings.digitalprocess"));
|
||||||
|
digitalProcessingCheckbox.setValue(true);
|
||||||
|
|
||||||
|
HorizontalLayout digitalRow = new HorizontalLayout();
|
||||||
|
digitalRow.setWidthFull();
|
||||||
|
digitalRow.setAlignItems(FlexComponent.Alignment.BASELINE);
|
||||||
|
digitalRow.setJustifyContentMode(FlexComponent.JustifyContentMode.START);
|
||||||
|
digitalProcessingCheckbox.getStyle().set("margin-right", "12px");
|
||||||
|
digitalRow.add(digitalProcessingCheckbox);
|
||||||
|
|
||||||
|
appUserComboBox = new ComboBox<>(translationHelper.getTranslation("addjob.appuser.label"));
|
||||||
|
appUserComboBox.setWidthFull();
|
||||||
|
if (availableAppUsers != null) {
|
||||||
|
appUserComboBox.setItems(availableAppUsers);
|
||||||
|
}
|
||||||
|
appUserComboBox.setItemLabelGenerator(
|
||||||
|
user -> user.getVorname() + " " + user.getNachname() + " (" + user.getEmail() + ")");
|
||||||
|
appUserComboBox.setPlaceholder(translationHelper.getTranslation("addjob.appuser.placeholder"));
|
||||||
|
|
||||||
|
content.add(digitalRow, appUserComboBox);
|
||||||
|
|
||||||
|
// Toggle app user visibility based on digital processing
|
||||||
|
digitalProcessingCheckbox.addValueChangeListener(e -> {
|
||||||
|
boolean required = Boolean.TRUE.equals(e.getValue());
|
||||||
|
appUserComboBox.setRequiredIndicatorVisible(required);
|
||||||
|
appUserComboBox.setVisible(required);
|
||||||
|
if (!required) {
|
||||||
|
appUserComboBox.clear();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
boolean digitalInitial = Boolean.TRUE.equals(digitalProcessingCheckbox.getValue());
|
||||||
|
appUserComboBox.setRequiredIndicatorVisible(digitalInitial);
|
||||||
|
appUserComboBox.setVisible(digitalInitial);
|
||||||
|
|
||||||
|
// Appointment date & time
|
||||||
|
H3 pickupApptTitle = new H3(translationHelper.getTranslation("addjob.appointment.pickup"));
|
||||||
|
pickupApptTitle.getStyle().set("margin", "0");
|
||||||
|
|
||||||
|
appointmentDatePicker = new DatePicker(translationHelper.getTranslation("addjob.appointment.date"));
|
||||||
|
appointmentDatePicker.setRequiredIndicatorVisible(true);
|
||||||
|
appointmentDatePicker.setMin(LocalDate.now());
|
||||||
|
appointmentDatePicker.setLocale(java.util.Locale.GERMANY);
|
||||||
|
appointmentDatePicker.setI18n(new DatePicker.DatePickerI18n().setFirstDayOfWeek(1)
|
||||||
|
.setMonthNames(java.util.Arrays.asList("Januar", "Februar", "März", "April", "Mai", "Juni", "Juli",
|
||||||
|
"August", "September", "Oktober", "November", "Dezember"))
|
||||||
|
.setWeekdays(java.util.Arrays.asList("Sonntag", "Montag", "Dienstag", "Mittwoch", "Donnerstag",
|
||||||
|
"Freitag", "Samstag"))
|
||||||
|
.setWeekdaysShort(java.util.Arrays.asList("So", "Mo", "Di", "Mi", "Do", "Fr", "Sa")));
|
||||||
|
|
||||||
|
appointmentTimePicker = new TimePicker(translationHelper.getTranslation("addjob.appointment.time"));
|
||||||
|
appointmentTimePicker.setLocale(java.util.Locale.GERMANY);
|
||||||
|
|
||||||
|
HorizontalLayout pickupApptRow = new HorizontalLayout(appointmentDatePicker, appointmentTimePicker);
|
||||||
|
pickupApptRow.setWidthFull();
|
||||||
|
pickupApptRow.setSpacing(true);
|
||||||
|
appointmentDatePicker.setWidth("50%");
|
||||||
|
appointmentTimePicker.setWidth("50%");
|
||||||
|
content.add(pickupApptTitle, pickupApptRow);
|
||||||
|
|
||||||
|
// Info about delivery dates
|
||||||
|
Span deliveryInfoLabel = new Span(translationHelper.getTranslation("addjob.appointment.delivery.info"));
|
||||||
|
deliveryInfoLabel.getStyle().set("color", "var(--lumo-secondary-text-color)");
|
||||||
|
deliveryInfoLabel.getStyle().set("font-style", "italic");
|
||||||
|
deliveryInfoLabel.getStyle().set("margin-top", "var(--lumo-space-m)");
|
||||||
|
content.add(deliveryInfoLabel);
|
||||||
|
|
||||||
|
tabContent.add(content);
|
||||||
|
return tabContent;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================
|
||||||
|
// Cargo Tab
|
||||||
|
// ============================================
|
||||||
|
|
||||||
|
private VerticalLayout createCargoTab() {
|
||||||
|
VerticalLayout tabContent = new VerticalLayout();
|
||||||
|
tabContent.setSizeFull();
|
||||||
|
tabContent.setPadding(true);
|
||||||
|
tabContent.setSpacing(true);
|
||||||
|
|
||||||
|
VerticalLayout wrapper = new VerticalLayout();
|
||||||
|
wrapper.setWidthFull();
|
||||||
|
wrapper.setSpacing(true);
|
||||||
|
|
||||||
|
VerticalLayout cargoAreaContainer = new VerticalLayout();
|
||||||
|
cargoAreaContainer.setWidthFull();
|
||||||
|
cargoAreaContainer.setSpacing(true);
|
||||||
|
cargoAreaContainer.getStyle().set("background", "var(--lumo-base-color)");
|
||||||
|
cargoAreaContainer.getStyle().set("border", "1px solid var(--lumo-contrast-20pct)");
|
||||||
|
cargoAreaContainer.getStyle().set("border-radius", "var(--lumo-border-radius-m)");
|
||||||
|
cargoAreaContainer.getStyle().set("padding", "var(--lumo-space-m)");
|
||||||
|
|
||||||
|
H3 cargoTitle = new H3(translationHelper.getTranslation("addjob.tab.cargo"));
|
||||||
|
|
||||||
|
wrapper.add(cargoTitle);
|
||||||
|
|
||||||
|
cargoList = new VerticalLayout();
|
||||||
|
cargoList.setPadding(false);
|
||||||
|
cargoList.setSpacing(true);
|
||||||
|
cargoAreaContainer.add(cargoList);
|
||||||
|
|
||||||
|
// Add one empty row by default
|
||||||
|
addCargoRow();
|
||||||
|
|
||||||
|
// Add button
|
||||||
|
Button addCargoButton = new Button(translationHelper.getTranslation("addjob.cargo.add"),
|
||||||
|
new Icon(VaadinIcon.PLUS));
|
||||||
|
addCargoButton.addThemeVariants(ButtonVariant.LUMO_PRIMARY);
|
||||||
|
addCargoButton.setWidthFull();
|
||||||
|
addCargoButton.addClickListener(e -> addCargoRow());
|
||||||
|
|
||||||
|
cargoAreaContainer.add(addCargoButton);
|
||||||
|
wrapper.add(cargoAreaContainer);
|
||||||
|
tabContent.add(wrapper);
|
||||||
|
return tabContent;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addCargoRow() {
|
||||||
|
addCargoRowWithData(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addCargoRowWithData(CargoItem existingItem) {
|
||||||
|
HorizontalLayout row = new HorizontalLayout();
|
||||||
|
row.setWidthFull();
|
||||||
|
row.setAlignItems(FlexComponent.Alignment.END);
|
||||||
|
|
||||||
|
ComboBox<String> desc = new ComboBox<>(translationHelper.getTranslation("addjob.cargo.description"));
|
||||||
|
desc.setItems(translationHelper.getTranslation("addjob.cargo.europalette"),
|
||||||
|
translationHelper.getTranslation("addjob.cargo.disposablepalette"),
|
||||||
|
translationHelper.getTranslation("addjob.cargo.dusseldorfpalette"),
|
||||||
|
translationHelper.getTranslation("addjob.cargo.gridboxpalette"),
|
||||||
|
translationHelper.getTranslation("addjob.cargo.gridcart"),
|
||||||
|
translationHelper.getTranslation("addjob.cargo.parcel"));
|
||||||
|
desc.setAllowCustomValue(true);
|
||||||
|
desc.addCustomValueSetListener(event -> desc.setValue(event.getDetail()));
|
||||||
|
desc.setPlaceholder(translationHelper.getTranslation("addjob.cargo.description.placeholder"));
|
||||||
|
desc.setWidth("40%");
|
||||||
|
desc.setRequiredIndicatorVisible(true);
|
||||||
|
|
||||||
|
IntegerField qty = new IntegerField(translationHelper.getTranslation("addjob.cargo.quantity"));
|
||||||
|
qty.setMin(1);
|
||||||
|
qty.setMax(9999);
|
||||||
|
qty.setWidth("10%");
|
||||||
|
qty.setRequiredIndicatorVisible(true);
|
||||||
|
|
||||||
|
NumberField weight = new NumberField(translationHelper.getTranslation("addjob.cargo.weight"));
|
||||||
|
weight.setSuffixComponent(new Span("kg"));
|
||||||
|
weight.setWidth("15%");
|
||||||
|
weight.setRequiredIndicatorVisible(true);
|
||||||
|
|
||||||
|
NumberField len = new NumberField(translationHelper.getTranslation("addjob.cargo.length"));
|
||||||
|
len.setSuffixComponent(new Span("cm"));
|
||||||
|
len.setWidth("12%");
|
||||||
|
len.setRequiredIndicatorVisible(true);
|
||||||
|
|
||||||
|
NumberField wid = new NumberField(translationHelper.getTranslation("addjob.cargo.width"));
|
||||||
|
wid.setSuffixComponent(new Span("cm"));
|
||||||
|
wid.setWidth("12%");
|
||||||
|
wid.setRequiredIndicatorVisible(true);
|
||||||
|
|
||||||
|
NumberField hei = new NumberField(translationHelper.getTranslation("addjob.cargo.height"));
|
||||||
|
hei.setSuffixComponent(new Span("cm"));
|
||||||
|
hei.setWidth("12%");
|
||||||
|
hei.setRequiredIndicatorVisible(true);
|
||||||
|
|
||||||
|
Button remove = new Button(new Icon(VaadinIcon.CLOSE_SMALL));
|
||||||
|
remove.addThemeVariants(ButtonVariant.LUMO_ERROR, ButtonVariant.LUMO_TERTIARY);
|
||||||
|
remove.addClickListener(e -> {
|
||||||
|
int idx = cargoList.getChildren().toList().indexOf(row);
|
||||||
|
if (idx >= 0 && idx < cargoItemsState.size()) {
|
||||||
|
cargoItemsState.remove(idx);
|
||||||
|
}
|
||||||
|
cargoList.remove(row);
|
||||||
|
});
|
||||||
|
|
||||||
|
row.add(desc, qty, weight, len, wid, hei, remove);
|
||||||
|
cargoList.add(row);
|
||||||
|
|
||||||
|
// Create or use existing CargoItem
|
||||||
|
CargoItem item = new CargoItem();
|
||||||
|
if (existingItem != null) {
|
||||||
|
item.setDescription(existingItem.getDescription());
|
||||||
|
item.setQuantity(existingItem.getQuantity());
|
||||||
|
item.setWeightKg(existingItem.getWeightKg());
|
||||||
|
item.setLengthMm(existingItem.getLengthMm());
|
||||||
|
item.setWidthMm(existingItem.getWidthMm());
|
||||||
|
item.setHeightMm(existingItem.getHeightMm());
|
||||||
|
|
||||||
|
// Pre-fill fields
|
||||||
|
if (item.getDescription() != null)
|
||||||
|
desc.setValue(item.getDescription());
|
||||||
|
if (item.getQuantity() != null)
|
||||||
|
qty.setValue(item.getQuantity());
|
||||||
|
if (item.getWeightKg() != null)
|
||||||
|
weight.setValue(item.getWeightKg());
|
||||||
|
if (item.getLengthMm() != null)
|
||||||
|
len.setValue(item.getLengthMm());
|
||||||
|
if (item.getWidthMm() != null)
|
||||||
|
wid.setValue(item.getWidthMm());
|
||||||
|
if (item.getHeightMm() != null)
|
||||||
|
hei.setValue(item.getHeightMm());
|
||||||
|
}
|
||||||
|
cargoItemsState.add(item);
|
||||||
|
|
||||||
|
// Bind change listeners
|
||||||
|
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()));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,164 @@
|
|||||||
|
package de.assecutor.votianlt.pages.base.ui.component;
|
||||||
|
|
||||||
|
import com.vaadin.flow.component.button.Button;
|
||||||
|
import com.vaadin.flow.component.button.ButtonVariant;
|
||||||
|
import com.vaadin.flow.component.html.H3;
|
||||||
|
import com.vaadin.flow.component.html.Span;
|
||||||
|
import com.vaadin.flow.component.icon.Icon;
|
||||||
|
import com.vaadin.flow.component.icon.VaadinIcon;
|
||||||
|
import com.vaadin.flow.component.orderedlayout.FlexComponent;
|
||||||
|
import com.vaadin.flow.component.orderedlayout.HorizontalLayout;
|
||||||
|
import com.vaadin.flow.component.orderedlayout.VerticalLayout;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A compact tile representing a station (pickup or delivery) in a grid layout.
|
||||||
|
* Shows only a title and text preview of entered data. Clicking the tile opens
|
||||||
|
* a dialog for data entry.
|
||||||
|
*/
|
||||||
|
public class StationTile extends VerticalLayout {
|
||||||
|
|
||||||
|
public enum StationType {
|
||||||
|
PICKUP, DELIVERY
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface ClickListener {
|
||||||
|
void onClick(StationTile tile);
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface DeleteListener {
|
||||||
|
void onDelete(StationTile tile);
|
||||||
|
}
|
||||||
|
|
||||||
|
private final StationType type;
|
||||||
|
private int stationNumber;
|
||||||
|
private final H3 title;
|
||||||
|
private final VerticalLayout previewContent;
|
||||||
|
private ClickListener clickListener;
|
||||||
|
private DeleteListener deleteListener;
|
||||||
|
|
||||||
|
public StationTile(StationType type, int stationNumber, String titleText, boolean removable) {
|
||||||
|
this.type = type;
|
||||||
|
this.stationNumber = stationNumber;
|
||||||
|
|
||||||
|
setPadding(true);
|
||||||
|
setSpacing(false);
|
||||||
|
getStyle().set("border", "1px solid var(--lumo-contrast-20pct)");
|
||||||
|
getStyle().set("border-radius", "var(--lumo-border-radius-m)");
|
||||||
|
getStyle().set("background-color", "var(--lumo-base-color)");
|
||||||
|
getStyle().set("cursor", "pointer");
|
||||||
|
getStyle().set("aspect-ratio", "1 / 1");
|
||||||
|
getStyle().set("overflow", "hidden");
|
||||||
|
|
||||||
|
// Header with title and optional delete button
|
||||||
|
title = new H3(titleText);
|
||||||
|
title.getStyle().set("margin", "0").set("flex-grow", "1").set("font-size", "var(--lumo-font-size-m)");
|
||||||
|
|
||||||
|
HorizontalLayout titleLayout = new HorizontalLayout();
|
||||||
|
titleLayout.setWidthFull();
|
||||||
|
titleLayout.setDefaultVerticalComponentAlignment(FlexComponent.Alignment.CENTER);
|
||||||
|
titleLayout.add(title);
|
||||||
|
|
||||||
|
if (removable) {
|
||||||
|
Button deleteButton = new Button(new Icon(VaadinIcon.CLOSE_SMALL));
|
||||||
|
deleteButton.addThemeVariants(ButtonVariant.LUMO_ERROR, ButtonVariant.LUMO_TERTIARY);
|
||||||
|
deleteButton.addClickListener(e -> {
|
||||||
|
e.getSource().getElement().executeJs("arguments[0].stopPropagation()", e.getSource().getElement());
|
||||||
|
if (deleteListener != null) {
|
||||||
|
deleteListener.onDelete(this);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
titleLayout.add(deleteButton);
|
||||||
|
}
|
||||||
|
|
||||||
|
add(titleLayout);
|
||||||
|
|
||||||
|
// Preview content area
|
||||||
|
previewContent = new VerticalLayout();
|
||||||
|
previewContent.setPadding(false);
|
||||||
|
previewContent.setSpacing(false);
|
||||||
|
previewContent.getStyle().set("gap", "var(--lumo-space-xs)");
|
||||||
|
add(previewContent);
|
||||||
|
|
||||||
|
// Show placeholder when no data
|
||||||
|
updateEmptyPreview();
|
||||||
|
|
||||||
|
// Click on the tile opens the dialog
|
||||||
|
addClickListener(e -> {
|
||||||
|
if (clickListener != null) {
|
||||||
|
clickListener.onClick(this);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void updatePreview(String company, String firstName, String lastName, String street, String houseNumber,
|
||||||
|
String zip, String city) {
|
||||||
|
previewContent.removeAll();
|
||||||
|
|
||||||
|
boolean hasData = false;
|
||||||
|
|
||||||
|
if (company != null && !company.trim().isEmpty()) {
|
||||||
|
addPreviewLine(company);
|
||||||
|
hasData = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
String name = ((firstName != null ? firstName : "") + " " + (lastName != null ? lastName : "")).trim();
|
||||||
|
if (!name.isEmpty()) {
|
||||||
|
addPreviewLine(name);
|
||||||
|
hasData = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
String streetLine = ((street != null ? street : "") + " " + (houseNumber != null ? houseNumber : "")).trim();
|
||||||
|
if (!streetLine.isEmpty()) {
|
||||||
|
addPreviewLine(streetLine);
|
||||||
|
hasData = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
String zipCityLine = ((zip != null ? zip : "") + " " + (city != null ? city : "")).trim();
|
||||||
|
if (!zipCityLine.isEmpty()) {
|
||||||
|
addPreviewLine(zipCityLine);
|
||||||
|
hasData = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!hasData) {
|
||||||
|
updateEmptyPreview();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateEmptyPreview() {
|
||||||
|
previewContent.removeAll();
|
||||||
|
Span placeholder = new Span("...");
|
||||||
|
placeholder.getStyle().set("color", "var(--lumo-contrast-40pct)").set("font-size", "var(--lumo-font-size-s)");
|
||||||
|
previewContent.add(placeholder);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addPreviewLine(String text) {
|
||||||
|
Span span = new Span(text);
|
||||||
|
span.getStyle().set("font-size", "var(--lumo-font-size-s)").set("word-break", "break-word").set("color",
|
||||||
|
"var(--lumo-secondary-text-color)");
|
||||||
|
previewContent.add(span);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void updateTitle(String newTitle) {
|
||||||
|
title.setText(newTitle);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void updateStationNumber(int newNumber) {
|
||||||
|
this.stationNumber = newNumber;
|
||||||
|
}
|
||||||
|
|
||||||
|
public StationType getType() {
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getStationNumber() {
|
||||||
|
return stationNumber;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setClickListener(ClickListener listener) {
|
||||||
|
this.clickListener = listener;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDeleteListener(DeleteListener listener) {
|
||||||
|
this.deleteListener = listener;
|
||||||
|
}
|
||||||
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -9,6 +9,8 @@ import java.util.List;
|
|||||||
public interface TaskRepository extends MongoRepository<BaseTask, ObjectId> {
|
public interface TaskRepository extends MongoRepository<BaseTask, ObjectId> {
|
||||||
List<BaseTask> findByJobIdOrderByTaskOrderAsc(ObjectId jobId);
|
List<BaseTask> findByJobIdOrderByTaskOrderAsc(ObjectId jobId);
|
||||||
|
|
||||||
|
List<BaseTask> findByJobIdAndStationOrderOrderByTaskOrderAsc(ObjectId jobId, int stationOrder);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Count tasks by completion status
|
* Count tasks by completion status
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -1,3 +1,7 @@
|
|||||||
|
# Common Dialog
|
||||||
|
dialog.cancel=Abbrechen
|
||||||
|
dialog.confirm=Bestätigen
|
||||||
|
|
||||||
# Navigation and Main Layout
|
# Navigation and Main Layout
|
||||||
nav.jobs=Aufträge
|
nav.jobs=Aufträge
|
||||||
nav.job.create=Auftragserstellung
|
nav.job.create=Auftragserstellung
|
||||||
|
|||||||
@@ -1,3 +1,7 @@
|
|||||||
|
# Common Dialog
|
||||||
|
dialog.cancel=Cancel
|
||||||
|
dialog.confirm=Confirm
|
||||||
|
|
||||||
# Navigation and Main Layout
|
# Navigation and Main Layout
|
||||||
nav.jobs=Jobs
|
nav.jobs=Jobs
|
||||||
nav.job.create=Create New Job
|
nav.job.create=Create New Job
|
||||||
|
|||||||
Reference in New Issue
Block a user