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:
2026-03-04 19:08:28 +01:00
parent cd8b82cd71
commit 07f9748674
6 changed files with 311 additions and 50 deletions

View File

@@ -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,

View File

@@ -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();
});
}
}

View File

@@ -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);
}

View File

@@ -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;
}

View File

@@ -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

View File

@@ -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