Stationen-Dialoge: Validierung mit Tab-Fehlerindikatoren, Template-Laden aktualisiert Validierungsstatus
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -14,6 +14,7 @@ import com.vaadin.flow.component.notification.Notification;
|
||||
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.Tab;
|
||||
import com.vaadin.flow.component.tabs.TabSheet;
|
||||
import com.vaadin.flow.component.textfield.IntegerField;
|
||||
import com.vaadin.flow.component.textfield.TextField;
|
||||
@@ -171,6 +172,9 @@ public class DeliveryStationDialog extends Dialog {
|
||||
private final List<BaseTask> tasksState = new ArrayList<>();
|
||||
private VerticalLayout tasksList;
|
||||
|
||||
private Span addressTabError;
|
||||
private Span tasksTabError;
|
||||
|
||||
private final DeliveryStationTile.TranslationHelper translationHelper;
|
||||
|
||||
public DeliveryStationDialog(String dialogTitle, List<Customer> customers,
|
||||
@@ -181,7 +185,7 @@ public class DeliveryStationDialog extends Dialog {
|
||||
|
||||
setHeaderTitle(dialogTitle);
|
||||
setCloseOnOutsideClick(false);
|
||||
setWidth("800px");
|
||||
setWidth("960px");
|
||||
setHeight("80vh");
|
||||
|
||||
// Address form
|
||||
@@ -274,19 +278,37 @@ public class DeliveryStationDialog extends Dialog {
|
||||
saveAddress.setWidthFull();
|
||||
formLayout.add(saveAddress);
|
||||
|
||||
// Clear error styling on value change for required fields and update tab indicators
|
||||
firstName.addValueChangeListener(ev -> validateRequiredFields());
|
||||
lastName.addValueChangeListener(ev -> validateRequiredFields());
|
||||
street.addValueChangeListener(ev -> validateRequiredFields());
|
||||
houseNumber.addValueChangeListener(ev -> validateRequiredFields());
|
||||
zip.addValueChangeListener(ev -> validateRequiredFields());
|
||||
city.addValueChangeListener(ev -> validateRequiredFields());
|
||||
|
||||
// TabSheet with address and tasks tabs
|
||||
TabSheet tabSheet = new TabSheet();
|
||||
tabSheet.setWidthFull();
|
||||
tabSheet.setSizeFull();
|
||||
|
||||
tabSheet.add(translationHelper.getTranslation("addjob.tab.addresses"), formLayout);
|
||||
tabSheet.add(translationHelper.getTranslation("addjob.tab.tasks"),
|
||||
addressTabError = createTabErrorIndicator();
|
||||
tasksTabError = createTabErrorIndicator();
|
||||
|
||||
Tab addressTab = tabSheet.add(translationHelper.getTranslation("addjob.tab.addresses"), formLayout);
|
||||
addressTab.add(addressTabError);
|
||||
Tab tasksTab = tabSheet.add(translationHelper.getTranslation("addjob.tab.tasks"),
|
||||
createTasksTab(templates, templateSaveCallback));
|
||||
tasksTab.add(tasksTabError);
|
||||
|
||||
add(tabSheet);
|
||||
|
||||
// Footer buttons
|
||||
Button saveButton = new Button(translationHelper.getTranslation("dialog.confirm"), e -> {
|
||||
if (!validateRequiredFields()) {
|
||||
Notification.show(translationHelper.getTranslation("addjob.validation.required.fields"), 3000,
|
||||
Notification.Position.BOTTOM_END);
|
||||
return;
|
||||
}
|
||||
DeliveryData data = collectData();
|
||||
if (saveListener != null) {
|
||||
saveListener.onSave(data);
|
||||
@@ -298,6 +320,12 @@ public class DeliveryStationDialog extends Dialog {
|
||||
Button cancelButton = new Button(translationHelper.getTranslation("dialog.cancel"), e -> close());
|
||||
|
||||
getFooter().add(cancelButton, saveButton);
|
||||
|
||||
addOpenedChangeListener(event -> {
|
||||
if (event.isOpened()) {
|
||||
validateRequiredFields();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -363,6 +391,69 @@ public class DeliveryStationDialog extends Dialog {
|
||||
return data;
|
||||
}
|
||||
|
||||
private boolean validateRequiredFields() {
|
||||
// Address tab validation
|
||||
boolean addressValid = true;
|
||||
addressValid &= validateTextField(firstName);
|
||||
addressValid &= validateTextField(lastName);
|
||||
addressValid &= validateTextField(street);
|
||||
addressValid &= validateTextField(houseNumber);
|
||||
addressValid &= validateTextField(zip);
|
||||
addressValid &= validateTextField(city);
|
||||
addressTabError.setVisible(!addressValid);
|
||||
|
||||
// Tasks tab validation
|
||||
boolean tasksValid = validateTasks();
|
||||
tasksTabError.setVisible(!tasksValid);
|
||||
|
||||
return addressValid && tasksValid;
|
||||
}
|
||||
|
||||
private boolean validateTasks() {
|
||||
boolean valid = true;
|
||||
for (BaseTask task : tasksState) {
|
||||
if (task instanceof ConfirmationTask ct) {
|
||||
if (ct.getDescription() == null || ct.getDescription().trim().isEmpty()) {
|
||||
valid = false;
|
||||
}
|
||||
if (ct.getButtonText() == null || ct.getButtonText().trim().isEmpty()) {
|
||||
valid = false;
|
||||
}
|
||||
} else if (task instanceof TodoListTask tt) {
|
||||
if (tt.getTodoItems() == null || tt.getTodoItems().isEmpty()
|
||||
|| tt.getTodoItems().stream().allMatch(item -> item == null || item.trim().isEmpty())) {
|
||||
valid = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return valid;
|
||||
}
|
||||
|
||||
private boolean validateTextField(TextField field) {
|
||||
boolean empty = field.getValue() == null || field.getValue().trim().isEmpty();
|
||||
applyErrorStyling(field, empty);
|
||||
return !empty;
|
||||
}
|
||||
|
||||
private void applyErrorStyling(com.vaadin.flow.component.Component field, boolean error) {
|
||||
if (error) {
|
||||
field.getElement().getStyle().set("--vaadin-input-field-background", "rgba(255, 0, 0, 0.1)");
|
||||
field.getElement().getStyle().set("--vaadin-input-field-border-color", "rgba(255, 0, 0, 0.3)");
|
||||
} else {
|
||||
field.getElement().getStyle().remove("--vaadin-input-field-background");
|
||||
field.getElement().getStyle().remove("--vaadin-input-field-border-color");
|
||||
}
|
||||
}
|
||||
|
||||
private Span createTabErrorIndicator() {
|
||||
Span indicator = new Span(" !");
|
||||
indicator.getStyle().set("color", "var(--lumo-error-color)");
|
||||
indicator.getStyle().set("font-weight", "bold");
|
||||
indicator.getStyle().set("margin-left", "4px");
|
||||
indicator.setVisible(false);
|
||||
return indicator;
|
||||
}
|
||||
|
||||
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();
|
||||
@@ -713,14 +804,8 @@ public class DeliveryStationDialog extends Dialog {
|
||||
descriptionField.setValue(task.getDescription() != null ? task.getDescription() : "");
|
||||
descriptionField.addValueChangeListener(ev -> {
|
||||
task.setDescription(ev.getValue());
|
||||
boolean isEmpty = ev.getValue() == null || ev.getValue().trim().isEmpty();
|
||||
if (isEmpty) {
|
||||
descriptionField.getStyle().set("--vaadin-input-field-background", "rgba(255, 0, 0, 0.1)");
|
||||
descriptionField.getStyle().set("--vaadin-input-field-border-color", "rgba(255, 0, 0, 0.3)");
|
||||
} else {
|
||||
descriptionField.getStyle().remove("--vaadin-input-field-background");
|
||||
descriptionField.getStyle().remove("--vaadin-input-field-border-color");
|
||||
}
|
||||
applyErrorStyling(descriptionField, ev.getValue() == null || ev.getValue().trim().isEmpty());
|
||||
validateRequiredFields();
|
||||
});
|
||||
if (task.getDescription() == null || task.getDescription().trim().isEmpty()) {
|
||||
descriptionField.getStyle().set("--vaadin-input-field-background", "rgba(255, 0, 0, 0.1)");
|
||||
@@ -735,14 +820,8 @@ public class DeliveryStationDialog extends Dialog {
|
||||
buttonTextField.setValue(confirmationTask.getButtonText() != null ? confirmationTask.getButtonText() : "");
|
||||
buttonTextField.addValueChangeListener(ev -> {
|
||||
confirmationTask.setButtonText(ev.getValue());
|
||||
boolean isEmpty = ev.getValue() == null || ev.getValue().trim().isEmpty();
|
||||
if (isEmpty) {
|
||||
buttonTextField.getStyle().set("--vaadin-input-field-background", "rgba(255, 0, 0, 0.1)");
|
||||
buttonTextField.getStyle().set("--vaadin-input-field-border-color", "rgba(255, 0, 0, 0.3)");
|
||||
} else {
|
||||
buttonTextField.getStyle().remove("--vaadin-input-field-background");
|
||||
buttonTextField.getStyle().remove("--vaadin-input-field-border-color");
|
||||
}
|
||||
applyErrorStyling(buttonTextField, ev.getValue() == null || ev.getValue().trim().isEmpty());
|
||||
validateRequiredFields();
|
||||
});
|
||||
if (confirmationTask.getButtonText() == null || confirmationTask.getButtonText().trim().isEmpty()) {
|
||||
buttonTextField.getStyle().set("--vaadin-input-field-background", "rgba(255, 0, 0, 0.1)");
|
||||
@@ -807,6 +886,7 @@ public class DeliveryStationDialog extends Dialog {
|
||||
todoField.addValueChangeListener(ev -> {
|
||||
updateTodoFieldStyling.accept(todoField);
|
||||
updateTodoItems(todoList, task);
|
||||
validateRequiredFields();
|
||||
});
|
||||
};
|
||||
|
||||
@@ -1080,6 +1160,7 @@ public class DeliveryStationDialog extends Dialog {
|
||||
}
|
||||
|
||||
templateComboBox.clear();
|
||||
validateRequiredFields();
|
||||
|
||||
Notification.show(
|
||||
translationHelper.getTranslation("addjob.tasks.template.loaded", template.getTemplateName()), 3000,
|
||||
|
||||
@@ -13,11 +13,13 @@ 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.Tab;
|
||||
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 com.vaadin.flow.component.notification.Notification;
|
||||
import de.assecutor.votianlt.model.AppUser;
|
||||
import de.assecutor.votianlt.model.CargoItem;
|
||||
import de.assecutor.votianlt.model.Customer;
|
||||
@@ -219,6 +221,10 @@ public class PickupStationDialog extends Dialog {
|
||||
private final List<CargoItem> cargoItemsState = new ArrayList<>();
|
||||
private VerticalLayout cargoList;
|
||||
|
||||
private Span addressTabError;
|
||||
private Span appointmentsTabError;
|
||||
private Span cargoTabError;
|
||||
|
||||
private final DeliveryStationTile.TranslationHelper translationHelper;
|
||||
|
||||
public PickupStationDialog(String dialogTitle, List<Customer> customers,
|
||||
@@ -229,7 +235,7 @@ public class PickupStationDialog extends Dialog {
|
||||
|
||||
setHeaderTitle(dialogTitle);
|
||||
setCloseOnOutsideClick(false);
|
||||
setWidth("800px");
|
||||
setWidth("960px");
|
||||
setHeight("80vh");
|
||||
|
||||
// Address form
|
||||
@@ -339,6 +345,14 @@ public class PickupStationDialog extends Dialog {
|
||||
saveAddress.setValue(true);
|
||||
saveAddress.setWidthFull();
|
||||
|
||||
// Clear error styling on value change for required fields and update tab indicators
|
||||
firstName.addValueChangeListener(ev -> validateRequiredFields());
|
||||
lastName.addValueChangeListener(ev -> validateRequiredFields());
|
||||
street.addValueChangeListener(ev -> validateRequiredFields());
|
||||
houseNumber.addValueChangeListener(ev -> validateRequiredFields());
|
||||
zip.addValueChangeListener(ev -> validateRequiredFields());
|
||||
city.addValueChangeListener(ev -> validateRequiredFields());
|
||||
|
||||
// Customer selection fills address fields
|
||||
customerComboBox.addValueChangeListener(ev -> {
|
||||
String selected = ev.getValue();
|
||||
@@ -399,15 +413,27 @@ public class PickupStationDialog extends Dialog {
|
||||
tabSheet.setWidthFull();
|
||||
tabSheet.setSizeFull();
|
||||
|
||||
tabSheet.add(translationHelper.getTranslation("addjob.tab.addresses"), formLayout);
|
||||
tabSheet.add(translationHelper.getTranslation("addjob.tab.appointments"),
|
||||
addressTabError = createTabErrorIndicator();
|
||||
appointmentsTabError = createTabErrorIndicator();
|
||||
cargoTabError = createTabErrorIndicator();
|
||||
|
||||
Tab addressTab = tabSheet.add(translationHelper.getTranslation("addjob.tab.addresses"), formLayout);
|
||||
addressTab.add(addressTabError);
|
||||
Tab appointmentsTab = tabSheet.add(translationHelper.getTranslation("addjob.tab.appointments"),
|
||||
createAppointmentsTab(availableAppUsers));
|
||||
tabSheet.add(translationHelper.getTranslation("addjob.tab.cargo"), createCargoTab());
|
||||
appointmentsTab.add(appointmentsTabError);
|
||||
Tab cargoTab = tabSheet.add(translationHelper.getTranslation("addjob.tab.cargo"), createCargoTab());
|
||||
cargoTab.add(cargoTabError);
|
||||
|
||||
add(tabSheet);
|
||||
|
||||
// Footer buttons
|
||||
Button saveButton = new Button(translationHelper.getTranslation("dialog.confirm"), e -> {
|
||||
if (!validateRequiredFields()) {
|
||||
Notification.show(translationHelper.getTranslation("addjob.validation.required.fields"), 3000,
|
||||
Notification.Position.BOTTOM_END);
|
||||
return;
|
||||
}
|
||||
PickupData data = collectData();
|
||||
if (saveListener != null) {
|
||||
saveListener.onSave(data);
|
||||
@@ -419,6 +445,12 @@ public class PickupStationDialog extends Dialog {
|
||||
Button cancelButton = new Button(translationHelper.getTranslation("dialog.cancel"), e -> close());
|
||||
|
||||
getFooter().add(cancelButton, saveButton);
|
||||
|
||||
addOpenedChangeListener(event -> {
|
||||
if (event.isOpened()) {
|
||||
validateRequiredFields();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -501,6 +533,92 @@ public class PickupStationDialog extends Dialog {
|
||||
return data;
|
||||
}
|
||||
|
||||
private boolean validateRequiredFields() {
|
||||
// Address tab validation
|
||||
boolean addressValid = true;
|
||||
addressValid &= validateTextField(firstName);
|
||||
addressValid &= validateTextField(lastName);
|
||||
addressValid &= validateTextField(street);
|
||||
addressValid &= validateTextField(houseNumber);
|
||||
addressValid &= validateTextField(zip);
|
||||
addressValid &= validateTextField(city);
|
||||
addressTabError.setVisible(!addressValid);
|
||||
|
||||
// Appointments tab validation
|
||||
boolean appointmentsValid = true;
|
||||
if (appointmentDatePicker != null) {
|
||||
boolean dateEmpty = appointmentDatePicker.getValue() == null;
|
||||
applyErrorStyling(appointmentDatePicker, dateEmpty);
|
||||
appointmentsValid &= !dateEmpty;
|
||||
}
|
||||
if (Boolean.TRUE.equals(digitalProcessingCheckbox.getValue()) && appUserComboBox != null) {
|
||||
boolean appUserEmpty = appUserComboBox.getValue() == null;
|
||||
applyErrorStyling(appUserComboBox, appUserEmpty);
|
||||
appointmentsValid &= !appUserEmpty;
|
||||
}
|
||||
appointmentsTabError.setVisible(!appointmentsValid);
|
||||
|
||||
// Cargo tab validation
|
||||
boolean cargoValid = validateCargoItems();
|
||||
cargoTabError.setVisible(!cargoValid);
|
||||
|
||||
return addressValid && appointmentsValid && cargoValid;
|
||||
}
|
||||
|
||||
private boolean validateTextField(TextField field) {
|
||||
boolean empty = field.getValue() == null || field.getValue().trim().isEmpty();
|
||||
applyErrorStyling(field, empty);
|
||||
return !empty;
|
||||
}
|
||||
|
||||
private boolean validateCargoItems() {
|
||||
boolean valid = true;
|
||||
if (cargoList == null)
|
||||
return true;
|
||||
for (com.vaadin.flow.component.Component rowComp : cargoList.getChildren().toList()) {
|
||||
if (rowComp instanceof HorizontalLayout row) {
|
||||
for (com.vaadin.flow.component.Component field : row.getChildren().toList()) {
|
||||
if (field instanceof ComboBox<?> combo && combo.isRequiredIndicatorVisible()) {
|
||||
boolean empty = combo.getValue() == null || combo.getValue().toString().trim().isEmpty();
|
||||
applyErrorStyling(combo, empty);
|
||||
if (empty)
|
||||
valid = false;
|
||||
} else if (field instanceof IntegerField intField && intField.isRequiredIndicatorVisible()) {
|
||||
boolean empty = intField.getValue() == null;
|
||||
applyErrorStyling(intField, empty);
|
||||
if (empty)
|
||||
valid = false;
|
||||
} else if (field instanceof NumberField numField && numField.isRequiredIndicatorVisible()) {
|
||||
boolean empty = numField.getValue() == null;
|
||||
applyErrorStyling(numField, empty);
|
||||
if (empty)
|
||||
valid = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return valid;
|
||||
}
|
||||
|
||||
private void applyErrorStyling(com.vaadin.flow.component.Component field, boolean error) {
|
||||
if (error) {
|
||||
field.getElement().getStyle().set("--vaadin-input-field-background", "rgba(255, 0, 0, 0.1)");
|
||||
field.getElement().getStyle().set("--vaadin-input-field-border-color", "rgba(255, 0, 0, 0.3)");
|
||||
} else {
|
||||
field.getElement().getStyle().remove("--vaadin-input-field-background");
|
||||
field.getElement().getStyle().remove("--vaadin-input-field-border-color");
|
||||
}
|
||||
}
|
||||
|
||||
private Span createTabErrorIndicator() {
|
||||
Span indicator = new Span(" !");
|
||||
indicator.getStyle().set("color", "var(--lumo-error-color)");
|
||||
indicator.getStyle().set("font-weight", "bold");
|
||||
indicator.getStyle().set("margin-left", "4px");
|
||||
indicator.setVisible(false);
|
||||
return indicator;
|
||||
}
|
||||
|
||||
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();
|
||||
@@ -581,6 +699,7 @@ public class PickupStationDialog extends Dialog {
|
||||
appUserComboBox.setItemLabelGenerator(
|
||||
user -> user.getVorname() + " " + user.getNachname() + " (" + user.getEmail() + ")");
|
||||
appUserComboBox.setPlaceholder(translationHelper.getTranslation("addjob.appuser.placeholder"));
|
||||
appUserComboBox.addValueChangeListener(ev -> validateRequiredFields());
|
||||
|
||||
content.add(digitalRow, appUserComboBox);
|
||||
|
||||
@@ -592,6 +711,7 @@ public class PickupStationDialog extends Dialog {
|
||||
if (!required) {
|
||||
appUserComboBox.clear();
|
||||
}
|
||||
validateRequiredFields();
|
||||
});
|
||||
boolean digitalInitial = Boolean.TRUE.equals(digitalProcessingCheckbox.getValue());
|
||||
appUserComboBox.setRequiredIndicatorVisible(digitalInitial);
|
||||
@@ -615,6 +735,8 @@ public class PickupStationDialog extends Dialog {
|
||||
appointmentTimePicker = new TimePicker(translationHelper.getTranslation("addjob.appointment.time"));
|
||||
appointmentTimePicker.setLocale(java.util.Locale.GERMANY);
|
||||
|
||||
appointmentDatePicker.addValueChangeListener(ev -> validateRequiredFields());
|
||||
|
||||
HorizontalLayout pickupApptRow = new HorizontalLayout(appointmentDatePicker, appointmentTimePicker);
|
||||
pickupApptRow.setWidthFull();
|
||||
pickupApptRow.setSpacing(true);
|
||||
@@ -768,11 +890,35 @@ public class PickupStationDialog extends Dialog {
|
||||
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()));
|
||||
desc.addValueChangeListener(ev -> {
|
||||
item.setDescription(ev.getValue());
|
||||
applyErrorStyling(desc, ev.getValue() == null || ev.getValue().trim().isEmpty());
|
||||
validateRequiredFields();
|
||||
});
|
||||
qty.addValueChangeListener(ev -> {
|
||||
item.setQuantity(ev.getValue());
|
||||
applyErrorStyling(qty, ev.getValue() == null);
|
||||
validateRequiredFields();
|
||||
});
|
||||
weight.addValueChangeListener(ev -> {
|
||||
item.setWeightKg(ev.getValue());
|
||||
applyErrorStyling(weight, ev.getValue() == null);
|
||||
validateRequiredFields();
|
||||
});
|
||||
len.addValueChangeListener(ev -> {
|
||||
item.setLengthMm(ev.getValue());
|
||||
applyErrorStyling(len, ev.getValue() == null);
|
||||
validateRequiredFields();
|
||||
});
|
||||
wid.addValueChangeListener(ev -> {
|
||||
item.setWidthMm(ev.getValue());
|
||||
applyErrorStyling(wid, ev.getValue() == null);
|
||||
validateRequiredFields();
|
||||
});
|
||||
hei.addValueChangeListener(ev -> {
|
||||
item.setHeightMm(ev.getValue());
|
||||
applyErrorStyling(hei, ev.getValue() == null);
|
||||
validateRequiredFields();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -55,14 +55,20 @@ public class StationTile extends VerticalLayout {
|
||||
|
||||
HorizontalLayout titleLayout = new HorizontalLayout();
|
||||
titleLayout.setWidthFull();
|
||||
titleLayout.setDefaultVerticalComponentAlignment(FlexComponent.Alignment.CENTER);
|
||||
titleLayout.setPadding(false);
|
||||
titleLayout.setSpacing(false);
|
||||
titleLayout.setDefaultVerticalComponentAlignment(FlexComponent.Alignment.START);
|
||||
titleLayout.add(title);
|
||||
|
||||
if (removable) {
|
||||
Button deleteButton = new Button(new Icon(VaadinIcon.CLOSE_SMALL));
|
||||
deleteButton.addThemeVariants(ButtonVariant.LUMO_ERROR, ButtonVariant.LUMO_TERTIARY);
|
||||
deleteButton.addThemeVariants(ButtonVariant.LUMO_ERROR, ButtonVariant.LUMO_TERTIARY,
|
||||
ButtonVariant.LUMO_ICON);
|
||||
deleteButton.getStyle().set("min-width", "0").set("min-height", "0").set("padding", "0").set("margin", "0")
|
||||
.set("height", "var(--lumo-font-size-m)").set("line-height", "var(--lumo-font-size-m)");
|
||||
// Stop propagation on the client side to prevent the tile click from firing
|
||||
deleteButton.getElement().setAttribute("onclick", "event.stopPropagation()");
|
||||
deleteButton.addClickListener(e -> {
|
||||
e.getSource().getElement().executeJs("arguments[0].stopPropagation()", e.getSource().getElement());
|
||||
if (deleteListener != null) {
|
||||
deleteListener.onDelete(this);
|
||||
}
|
||||
@@ -77,6 +83,7 @@ public class StationTile extends VerticalLayout {
|
||||
previewContent.setPadding(false);
|
||||
previewContent.setSpacing(false);
|
||||
previewContent.getStyle().set("gap", "var(--lumo-space-xs)");
|
||||
previewContent.getStyle().set("flex-grow", "1");
|
||||
add(previewContent);
|
||||
|
||||
// Show placeholder when no data
|
||||
@@ -93,6 +100,8 @@ public class StationTile extends VerticalLayout {
|
||||
public void updatePreview(String company, String firstName, String lastName, String street, String houseNumber,
|
||||
String zip, String city) {
|
||||
previewContent.removeAll();
|
||||
previewContent.setJustifyContentMode(FlexComponent.JustifyContentMode.START);
|
||||
previewContent.setDefaultHorizontalComponentAlignment(FlexComponent.Alignment.START);
|
||||
|
||||
boolean hasData = false;
|
||||
|
||||
@@ -126,8 +135,10 @@ public class StationTile extends VerticalLayout {
|
||||
|
||||
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.setJustifyContentMode(FlexComponent.JustifyContentMode.CENTER);
|
||||
previewContent.setDefaultHorizontalComponentAlignment(FlexComponent.Alignment.CENTER);
|
||||
Span placeholder = new Span(getTranslation("addjob.station.unused"));
|
||||
placeholder.getStyle().set("color", "var(--lumo-contrast-30pct)").set("font-size", "var(--lumo-font-size-xl)");
|
||||
previewContent.add(placeholder);
|
||||
}
|
||||
|
||||
|
||||
@@ -82,8 +82,6 @@ public class AddJobView extends Main implements HasDynamicTitle {
|
||||
private final TaskTemplateService taskTemplateService;
|
||||
private final SecurityService securityService;
|
||||
private final ServiceRepository serviceRepository;
|
||||
private final AddressValidationService addressValidationService;
|
||||
|
||||
// Customer selection
|
||||
private ComboBox<String> customerSelection;
|
||||
|
||||
@@ -139,6 +137,7 @@ public class AddJobView extends Main implements HasDynamicTitle {
|
||||
|
||||
// Submit button
|
||||
private Button submitButton;
|
||||
private HorizontalLayout submitButtonLayout;
|
||||
|
||||
// Backing list for cargo items to mirror UI rows
|
||||
private final List<CargoItem> cargoItemsState = new ArrayList<>();
|
||||
@@ -168,7 +167,6 @@ public class AddJobView extends Main implements HasDynamicTitle {
|
||||
this.taskTemplateService = taskTemplateService;
|
||||
this.securityService = securityService;
|
||||
this.serviceRepository = serviceRepository;
|
||||
this.addressValidationService = addressValidationService;
|
||||
initializeComponents();
|
||||
setupLayout();
|
||||
setupValidation();
|
||||
@@ -298,13 +296,14 @@ public class AddJobView extends Main implements HasDynamicTitle {
|
||||
add(createCustomerAndAddressesTab());
|
||||
|
||||
// Add submit button horizontally centered below the content
|
||||
HorizontalLayout buttonLayout = new HorizontalLayout();
|
||||
buttonLayout.setWidthFull();
|
||||
buttonLayout.setJustifyContentMode(FlexComponent.JustifyContentMode.CENTER);
|
||||
buttonLayout.setPadding(true);
|
||||
buttonLayout.add(submitButton);
|
||||
submitButtonLayout = new HorizontalLayout();
|
||||
submitButtonLayout.setWidthFull();
|
||||
submitButtonLayout.setJustifyContentMode(FlexComponent.JustifyContentMode.CENTER);
|
||||
submitButtonLayout.setPadding(true);
|
||||
submitButtonLayout.add(submitButton);
|
||||
submitButtonLayout.setVisible(false);
|
||||
|
||||
add(buttonLayout);
|
||||
add(submitButtonLayout);
|
||||
}
|
||||
|
||||
private Component createCustomerAndAddressesTab() {
|
||||
@@ -337,6 +336,24 @@ public class AddJobView extends Main implements HasDynamicTitle {
|
||||
|
||||
tabContent.add(stationsGridContainer);
|
||||
|
||||
// Wrapper für alle Elemente nach Stationen (initial versteckt)
|
||||
VerticalLayout priceAndDetailsSection = new VerticalLayout();
|
||||
priceAndDetailsSection.setWidthFull();
|
||||
priceAndDetailsSection.setPadding(false);
|
||||
priceAndDetailsSection.setSpacing(true);
|
||||
priceAndDetailsSection.setVisible(false);
|
||||
|
||||
// "Stationen übernehmen" Button
|
||||
Button applyStationsButton = new Button(getTranslation("addjob.stations.apply"));
|
||||
applyStationsButton.addThemeVariants(ButtonVariant.LUMO_PRIMARY);
|
||||
applyStationsButton.setWidthFull();
|
||||
applyStationsButton.addClickListener(e -> {
|
||||
applyStationsButton.setVisible(false);
|
||||
priceAndDetailsSection.setVisible(true);
|
||||
submitButtonLayout.setVisible(true);
|
||||
});
|
||||
tabContent.add(applyStationsButton);
|
||||
|
||||
// Route Info Box
|
||||
routeInfoBox = new VerticalLayout();
|
||||
routeInfoBox.setPadding(true);
|
||||
@@ -378,7 +395,7 @@ public class AddJobView extends Main implements HasDynamicTitle {
|
||||
durationRow.add(durationLabel, routeDurationLabel);
|
||||
|
||||
routeInfoBox.add(routeTitle, routeRow, durationRow);
|
||||
tabContent.add(routeInfoBox);
|
||||
priceAndDetailsSection.add(routeInfoBox);
|
||||
|
||||
// Manuelle Streckeneingabe (wenn keine Route berechnet wurde)
|
||||
manualRouteInputBox = new VerticalLayout();
|
||||
@@ -430,12 +447,12 @@ public class AddJobView extends Main implements HasDynamicTitle {
|
||||
manualRouteHint.getStyle().set("font-style", "italic");
|
||||
|
||||
manualRouteInputBox.add(manualRouteTitle, manualInputRow, manualRouteHint);
|
||||
tabContent.add(manualRouteInputBox);
|
||||
priceAndDetailsSection.add(manualRouteInputBox);
|
||||
|
||||
// Leistungen
|
||||
H3 servicesTitle = new H3(getTranslation("addjob.services.title"));
|
||||
servicesTitle.getStyle().set("margin", "0");
|
||||
tabContent.add(servicesTitle);
|
||||
priceAndDetailsSection.add(servicesTitle);
|
||||
|
||||
// Services Grid
|
||||
servicesGrid = new Grid<>();
|
||||
@@ -493,13 +510,13 @@ public class AddJobView extends Main implements HasDynamicTitle {
|
||||
return removeButton;
|
||||
}).setHeader(getTranslation("common.actions")).setAutoWidth(true).setFlexGrow(0);
|
||||
|
||||
tabContent.add(servicesGrid);
|
||||
priceAndDetailsSection.add(servicesGrid);
|
||||
|
||||
// Add Service Button
|
||||
Button addServiceButton = new Button(getTranslation("addjob.services.add"), new Icon(VaadinIcon.PLUS));
|
||||
addServiceButton.addThemeVariants(ButtonVariant.LUMO_PRIMARY);
|
||||
addServiceButton.addClickListener(e -> openAddServiceDialog());
|
||||
tabContent.add(addServiceButton);
|
||||
priceAndDetailsSection.add(addServiceButton);
|
||||
|
||||
// Price Summary
|
||||
VerticalLayout summaryLayout = new VerticalLayout();
|
||||
@@ -552,7 +569,7 @@ public class AddJobView extends Main implements HasDynamicTitle {
|
||||
|
||||
summaryLayout.add(priceTable);
|
||||
|
||||
tabContent.add(summaryLayout);
|
||||
priceAndDetailsSection.add(summaryLayout);
|
||||
|
||||
// Bemerkung
|
||||
H3 remarksTitle = new H3(getTranslation("addjob.tasks.remark"));
|
||||
@@ -561,7 +578,9 @@ public class AddJobView extends Main implements HasDynamicTitle {
|
||||
remarkArea.setPlaceholder(getTranslation("addjob.tasks.remark.placeholder"));
|
||||
remarkArea.setWidthFull();
|
||||
remarkArea.setMinHeight("180px");
|
||||
tabContent.add(remarksTitle, remarkArea);
|
||||
priceAndDetailsSection.add(remarksTitle, remarkArea);
|
||||
|
||||
tabContent.add(priceAndDetailsSection);
|
||||
|
||||
return tabContent;
|
||||
}
|
||||
|
||||
@@ -450,10 +450,12 @@ addjob.address.delivery.addition.placeholder=Adresszusatz (Lieferung)
|
||||
addjob.address.save=Adresse speichern
|
||||
addjob.section.pickup=Abholung
|
||||
addjob.section.delivery=Lieferung
|
||||
addjob.stations.apply=Stationen \u00fcbernehmen
|
||||
addjob.station.delivery=Lieferstation {0}
|
||||
addjob.station.add=Lieferstation hinzuf\u00fcgen
|
||||
addjob.station.remove.confirm=Lieferstation {0} wirklich entfernen?
|
||||
addjob.station.max.reached=Maximale Anzahl von 25 Lieferstationen erreicht
|
||||
addjob.station.unused=Nicht genutzt
|
||||
addjob.appointment.delivery.info=Liefertermine werden direkt in den Lieferstationen festgelegt.
|
||||
addjob.tab.addresses=Auftraggeber & Adressen
|
||||
addjob.tab.appointments=Termine & Verarbeitung
|
||||
|
||||
@@ -450,10 +450,12 @@ addjob.address.delivery.addition.placeholder=Address addition (Delivery)
|
||||
addjob.address.save=Save Address
|
||||
addjob.section.pickup=Pickup
|
||||
addjob.section.delivery=Delivery
|
||||
addjob.stations.apply=Apply Stations
|
||||
addjob.station.delivery=Delivery Station {0}
|
||||
addjob.station.add=Add delivery station
|
||||
addjob.station.remove.confirm=Really remove delivery station {0}?
|
||||
addjob.station.max.reached=Maximum of 25 delivery stations reached
|
||||
addjob.station.unused=Not used
|
||||
addjob.appointment.delivery.info=Delivery dates are set directly in the delivery stations.
|
||||
addjob.tab.addresses=Customer & Addresses
|
||||
addjob.tab.appointments=Appointments & Processing
|
||||
|
||||
Reference in New Issue
Block a user