Erweiterungen

This commit is contained in:
2025-08-13 12:52:22 +02:00
parent de72f44a4e
commit 60547ec442

View File

@@ -5,6 +5,8 @@ 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.timepicker.TimePicker;
import com.vaadin.flow.component.html.Div;
import com.vaadin.flow.component.html.H3;
import com.vaadin.flow.component.html.Main;
@@ -17,6 +19,8 @@ import com.vaadin.flow.component.orderedlayout.HorizontalLayout;
import com.vaadin.flow.component.orderedlayout.VerticalLayout;
import com.vaadin.flow.component.textfield.TextField;
import com.vaadin.flow.component.ClientCallable;
import com.vaadin.flow.component.textfield.IntegerField;
import com.vaadin.flow.component.textfield.NumberField;
import com.vaadin.flow.component.UI;
import com.vaadin.flow.component.dnd.DragSource;
import com.vaadin.flow.component.dnd.DropTarget;
@@ -24,6 +28,7 @@ import com.vaadin.flow.component.dnd.EffectAllowed;
import com.vaadin.flow.data.binder.Binder;
import com.vaadin.flow.data.binder.ValidationException;
import com.vaadin.flow.router.Menu;
import com.vaadin.flow.component.Component;
import com.vaadin.flow.router.PageTitle;
import com.vaadin.flow.router.Route;
import com.vaadin.flow.theme.lumo.LumoUtility;
@@ -47,10 +52,10 @@ public class AddJobView extends Main {
// Customer selection
private ComboBox<String> customerSelection;
private Button preloadAddressButton;
// Required fields notice
private Span requiredFieldsNotice;
// Pickup address fields
private TextField pickupCompany;
private ComboBox<String> pickupSalutation;
@@ -63,7 +68,7 @@ public class AddJobView extends Main {
private TextField pickupZip;
private TextField pickupCity;
private Checkbox savePickupAddress;
// Delivery address fields
private TextField deliveryCompany;
private ComboBox<String> deliverySalutation;
@@ -76,27 +81,25 @@ public class AddJobView extends Main {
private TextField deliveryZip;
private TextField deliveryCity;
private Checkbox saveDeliveryAddress;
// Digital processing
private Checkbox digitalProcessing;
private ComboBox<String> appUser;
// Submit button
private Button submitButton;
// Stage sections for drag and drop
private VerticalLayout pickupSection;
private VerticalLayout deliverySection;
private HorizontalLayout mainLayout;
// Drag sources for dynamic control
private DragSource<VerticalLayout> pickupDragSource;
private DragSource<VerticalLayout> deliveryDragSource;
private final Binder<Job> binder = new Binder<>(Job.class);
public AddJobView(AddJobService addJobService) {
this.addJobService = addJobService;
initializeComponents();
@@ -111,11 +114,11 @@ public class AddJobView extends Main {
customerSelection.setItems("Kunde01 | KOTVor K01Nach");
customerSelection.setPlaceholder("Wählen Sie einen Auftraggeber aus...");
customerSelection.setWidthFull();
preloadAddressButton = new Button("Vorbelegte Adressfelder leeren");
preloadAddressButton.addThemeVariants(ButtonVariant.LUMO_PRIMARY);
preloadAddressButton.addClickListener(event -> clearAllFields());
// Pickup address
pickupCompany = new TextField("Firma");
pickupCompany.setPlaceholder("z.B. IKEA, McDonald's, DHL...");
@@ -146,7 +149,7 @@ public class AddJobView extends Main {
pickupCity.setPlaceholder("Hamburg");
pickupCity.setRequiredIndicatorVisible(true);
savePickupAddress = new Checkbox("Die Adresse für zukünftige Aufträge speichern.");
// Delivery address
deliveryCompany = new TextField("Firma");
deliveryCompany.setPlaceholder("z.B. EDEKA, Bauhaus, Amazon...");
@@ -191,12 +194,12 @@ public class AddJobView extends Main {
private void setupLayout() {
setSizeFull();
addClassNames(LumoUtility.BoxSizing.BORDER, LumoUtility.Display.FLEX,
LumoUtility.FlexDirection.COLUMN, LumoUtility.Padding.MEDIUM,
addClassNames(LumoUtility.BoxSizing.BORDER, LumoUtility.Display.FLEX,
LumoUtility.FlexDirection.COLUMN, LumoUtility.Padding.MEDIUM,
LumoUtility.Gap.SMALL);
add(new ViewToolbar("Neuen Auftrag anlegen"));
// Customer selection section
HorizontalLayout customerLayout = new HorizontalLayout();
customerLayout.setWidthFull();
@@ -204,7 +207,7 @@ public class AddJobView extends Main {
customerLayout.add(customerSelection, preloadAddressButton);
customerSelection.setWidth("70%");
preloadAddressButton.setWidth("30%");
add(customerLayout);
// Main content layout with two equal columns (50% each)
@@ -222,19 +225,70 @@ public class AddJobView extends Main {
deliverySection = createDeliverySection();
deliverySection.setWidth("50%");
deliveryDragSource = configureDragAndDrop(deliverySection, "delivery");
// Setup focus listeners for input fields
setupInputFieldFocusListeners();
mainLayout.add(pickupSection, deliverySection);
add(mainLayout);
// Digital processing section
VerticalLayout digitalSection = new VerticalLayout();
digitalSection.setSpacing(false);
digitalSection.add(digitalProcessing, appUser);
add(digitalSection);
// Section under the stages (centered)
VerticalLayout belowSection = new VerticalLayout();
belowSection.setWidthFull();
belowSection.setPadding(false);
belowSection.setSpacing(true);
belowSection.setDefaultHorizontalComponentAlignment(FlexComponent.Alignment.CENTER);
// Container with fixed width to center content
VerticalLayout content = new VerticalLayout();
content.setPadding(false);
content.setSpacing(true);
content.setWidth("720px");
content.setDefaultHorizontalComponentAlignment(FlexComponent.Alignment.STRETCH);
// Row: Digital processing + App user
HorizontalLayout digitalRow = new HorizontalLayout();
digitalRow.setWidthFull();
digitalRow.setAlignItems(FlexComponent.Alignment.BASELINE);
digitalRow.setJustifyContentMode(FlexComponent.JustifyContentMode.START);
digitalProcessing.getStyle().set("margin-right", "12px");
digitalRow.add(digitalProcessing);
// App user selector full width
appUser.setWidthFull();
content.add(digitalRow, appUser);
// Appointment (Pickup)
H3 pickupApptTitle = new H3("Termin (Abholung)");
pickupApptTitle.getStyle().set("margin", "0");
DatePicker pickupDate = new DatePicker("Datum");
pickupDate.setRequiredIndicatorVisible(true);
TimePicker pickupTime = new TimePicker("Uhrzeit");
HorizontalLayout pickupApptRow = new HorizontalLayout(pickupDate, pickupTime);
pickupApptRow.setWidthFull();
pickupApptRow.setSpacing(true);
pickupDate.setWidth("50%");
pickupTime.setWidth("50%");
content.add(pickupApptTitle, pickupApptRow);
// Appointment (Delivery)
H3 deliveryApptTitle = new H3("Termin (Lieferung)");
deliveryApptTitle.getStyle().set("margin", "0");
DatePicker deliveryDate = new DatePicker("Datum");
deliveryDate.setRequiredIndicatorVisible(true);
TimePicker deliveryTime = new TimePicker("Uhrzeit");
HorizontalLayout deliveryApptRow = new HorizontalLayout(deliveryDate, deliveryTime);
deliveryApptRow.setWidthFull();
deliveryApptRow.setSpacing(true);
deliveryDate.setWidth("50%");
deliveryTime.setWidth("50%");
content.add(deliveryApptTitle, deliveryApptRow);
belowSection.add(content);
add(belowSection);
// Ladung Bereich vor dem Button
add(createCargoSection());
// Submit button
HorizontalLayout submitLayout = new HorizontalLayout();
@@ -291,6 +345,7 @@ public class AddJobView extends Main {
section.add(pickupAddressAddition);
// zip/city row
HorizontalLayout zipCityLayout = new HorizontalLayout();
zipCityLayout.setWidthFull();
zipCityLayout.setSpacing(true);
@@ -387,27 +442,27 @@ public class AddJobView extends Main {
binder.forField(deliveryFirstName)
.asRequired("")
.bind(Job::getDeliveryFirstName, Job::setDeliveryFirstName);
binder.forField(deliveryLastName)
.asRequired("")
.bind(Job::getDeliveryLastName, Job::setDeliveryLastName);
binder.forField(deliveryStreet)
.asRequired("")
.bind(Job::getDeliveryStreet, Job::setDeliveryStreet);
binder.forField(deliveryHouseNumber)
.asRequired("")
.bind(Job::getDeliveryHouseNumber, Job::setDeliveryHouseNumber);
binder.forField(deliveryZip)
.asRequired("")
.bind(Job::getDeliveryZip, Job::setDeliveryZip);
binder.forField(deliveryCity)
.asRequired("")
.bind(Job::getDeliveryCity, Job::setDeliveryCity);
// Bind optional fields without validation
binder.bind(customerSelection, Job::getCustomerSelection, Job::setCustomerSelection);
binder.bind(pickupCompany, Job::getPickupCompany, Job::setPickupCompany);
@@ -415,56 +470,56 @@ public class AddJobView extends Main {
binder.bind(pickupPhone, Job::getPickupPhone, Job::setPickupPhone);
binder.bind(pickupAddressAddition, Job::getPickupAddressAddition, Job::setPickupAddressAddition);
binder.bind(savePickupAddress, Job::isSavePickupAddress, Job::setSavePickupAddress);
binder.bind(deliveryCompany, Job::getDeliveryCompany, Job::setDeliveryCompany);
binder.bind(deliverySalutation, Job::getDeliverySalutation, Job::setDeliverySalutation);
binder.bind(deliveryPhone, Job::getDeliveryPhone, Job::setDeliveryPhone);
binder.bind(deliveryAddressAddition, Job::getDeliveryAddressAddition, Job::setDeliveryAddressAddition);
binder.bind(saveDeliveryAddress, Job::isSaveDeliveryAddress, Job::setSaveDeliveryAddress);
binder.bind(digitalProcessing, Job::isDigitalProcessing, Job::setDigitalProcessing);
binder.bind(appUser, Job::getAppUser, Job::setAppUser);
// Set up validation triggers and visual styling
setupValidationTriggers();
// Trigger initial validation when view is displayed
triggerValidation();
}
private void setupValidationTriggers() {
// List of all required fields
TextField[] requiredFields = {
pickupFirstName, pickupLastName, pickupStreet, pickupHouseNumber, pickupZip, pickupCity,
deliveryFirstName, deliveryLastName, deliveryStreet, deliveryHouseNumber, deliveryZip, deliveryCity
};
// Add value change listeners to trigger validation on every change
for (TextField field : requiredFields) {
field.addValueChangeListener(event -> {
triggerValidation();
updateFieldStyling(field);
});
// Add focus listeners for immediate visual feedback
field.addFocusListener(event -> updateFieldStyling(field));
field.addBlurListener(event -> updateFieldStyling(field));
// Set initial styling
updateFieldStyling(field);
}
}
private void triggerValidation() {
// Create a temporary job object to trigger validation
Job tempJob = new Job();
binder.validate();
}
private void updateFieldStyling(TextField field) {
String value = field.getValue();
boolean isEmpty = value == null || value.trim().isEmpty();
if (isEmpty) {
// Apply transparent red background only to the input field, not the label
field.getStyle().set("--vaadin-input-field-background", "rgba(255, 0, 0, 0.1)");
@@ -479,7 +534,7 @@ public class AddJobView extends Main {
private void submit() {
try {
Job job = new Job();
// Validate all required fields using the binder
if (binder.writeBeanIfValid(job)) {
// All validations passed, save the job
@@ -513,7 +568,7 @@ public class AddJobView extends Main {
private void clearForm() {
// Reset binder to clear validation state
binder.readBean(new Job());
// Customer selection
customerSelection.clear();
@@ -644,7 +699,72 @@ public class AddJobView extends Main {
if (job.getCustomerSelection() != null) {
customerSelection.setValue(job.getCustomerSelection());
}
}
private Component createCargoSection() {
VerticalLayout wrapper = new VerticalLayout();
wrapper.setWidthFull();
wrapper.setSpacing(true);
VerticalLayout cargoArea = new VerticalLayout();
cargoArea.setWidthFull();
cargoArea.setSpacing(true);
cargoArea.getStyle().set("background", "var(--lumo-base-color)");
cargoArea.getStyle().set("border", "1px solid var(--lumo-contrast-20pct)");
cargoArea.getStyle().set("border-radius", "var(--lumo-border-radius-m)");
cargoArea.getStyle().set("padding", "var(--lumo-space-m)");
VerticalLayout cargoList = new VerticalLayout();
cargoList.setPadding(false);
cargoList.setSpacing(true);
cargoArea.add(cargoList);
java.util.function.BiConsumer<String, java.util.function.Consumer<HorizontalLayout>> addCargoRow = (iconName, afterCreate) -> {
HorizontalLayout row = new HorizontalLayout();
row.setWidthFull();
row.setAlignItems(FlexComponent.Alignment.END);
TextField desc = new TextField("Beschreibung");
desc.setPlaceholder("z. B. Gitterboxpalette, Paket …");
desc.setWidth("40%");
IntegerField qty = new IntegerField("Anzahl");
qty.setMin(1);
qty.setValue(1);
qty.setWidth("10%");
NumberField weight = new NumberField("Gewicht");
weight.setSuffixComponent(new Span("kg"));
weight.setWidth("15%");
NumberField len = new NumberField("Länge");
len.setSuffixComponent(new Span("mm"));
len.setWidth("12%");
NumberField wid = new NumberField("Breite");
wid.setSuffixComponent(new Span("mm"));
wid.setWidth("12%");
NumberField hei = new NumberField("Höhe");
hei.setSuffixComponent(new Span("mm"));
hei.setWidth("12%");
Button remove = new Button(new Icon(VaadinIcon.CLOSE_SMALL));
remove.addThemeVariants(ButtonVariant.LUMO_ERROR, ButtonVariant.LUMO_TERTIARY);
remove.addClickListener(e -> cargoList.remove(row));
row.add(desc, qty, weight, len, wid, hei, remove);
cargoList.add(row);
if (afterCreate != null) afterCreate.accept(row);
};
addCargoRow.accept("gitterbox", r -> {});
addCargoRow.accept("paket", r -> {});
addCargoRow.accept("", r -> {});
wrapper.add(cargoArea);
return wrapper;
}
private void populateFromJob(Job job) {
// Pickup address
if (job.getPickupCompany() != null) pickupCompany.setValue(job.getPickupCompany());
if (job.getPickupSalutation() != null) pickupSalutation.setValue(job.getPickupSalutation());
@@ -812,7 +932,7 @@ public class AddJobView extends Main {
// Customer selection
customerSelection.addFocusListener(e -> disableDragSources());
customerSelection.addBlurListener(e -> enableDragSources());
// Pickup fields
pickupCompany.addFocusListener(e -> disableDragSources());
pickupCompany.addBlurListener(e -> enableDragSources());
@@ -834,7 +954,7 @@ public class AddJobView extends Main {
pickupZip.addBlurListener(e -> enableDragSources());
pickupCity.addFocusListener(e -> disableDragSources());
pickupCity.addBlurListener(e -> enableDragSources());
// Delivery fields
deliveryCompany.addFocusListener(e -> disableDragSources());
deliveryCompany.addBlurListener(e -> enableDragSources());
@@ -856,12 +976,12 @@ public class AddJobView extends Main {
deliveryZip.addBlurListener(e -> enableDragSources());
deliveryCity.addFocusListener(e -> disableDragSources());
deliveryCity.addBlurListener(e -> enableDragSources());
// Digital processing
appUser.addFocusListener(e -> disableDragSources());
appUser.addBlurListener(e -> enableDragSources());
}
/**
* Deaktiviert alle Drag-Sources durch CSS
*/
@@ -875,7 +995,7 @@ public class AddJobView extends Main {
deliverySection.getElement().setAttribute("draggable", "false");
}
}
/**
* Aktiviert alle Drag-Sources durch CSS
*/
@@ -898,13 +1018,13 @@ public class AddJobView extends Main {
DragSource<VerticalLayout> dragSource = DragSource.create(section);
dragSource.setEffectAllowed(EffectAllowed.MOVE);
dragSource.setDragData(sectionType);
// Visual feedback beim Drag-Start
dragSource.addDragStartListener(event -> {
section.getStyle().set("opacity", "0.5");
section.getStyle().set("border", "2px dashed var(--lumo-primary-color)");
});
// Visual feedback beim Drag-Ende
dragSource.addDragEndListener(event -> {
section.getStyle().remove("opacity");
@@ -914,35 +1034,35 @@ public class AddJobView extends Main {
// Drop Target konfigurieren
DropTarget<VerticalLayout> dropTarget = DropTarget.create(section);
dropTarget.setActive(true);
// Drop Handler - Etappen tauschen
dropTarget.addDropListener(event -> {
Object draggedData = event.getDragData().orElse(null);
String draggedSectionType = draggedData != null ? draggedData.toString() : "";
if (!sectionType.equals(draggedSectionType)) {
swapStages();
}
// Styles zurücksetzen
section.getStyle().set("background-color", "var(--lumo-base-color)");
section.getStyle().set("border", "1px solid var(--lumo-contrast-20pct)");
});
// Visual feedback bei Dragover mit JavaScript
section.getElement().addEventListener("dragover", e -> {
section.getStyle().set("background-color", "var(--lumo-primary-color-10pct)");
section.getStyle().set("border", "2px solid var(--lumo-primary-color)");
});
section.getElement().addEventListener("dragleave", e -> {
section.getStyle().set("background-color", "var(--lumo-base-color)");
section.getStyle().set("border", "1px solid var(--lumo-contrast-20pct)");
});
return dragSource;
}
/**
* Tauscht die Inhalte der beiden Etappen (Pickup und Delivery)
*/
@@ -959,7 +1079,7 @@ public class AddJobView extends Main {
String tempZip = pickupZip.getValue();
String tempCity = pickupCity.getValue();
Boolean tempSaveAddress = savePickupAddress.getValue();
// Pickup mit Delivery-Werten überschreiben
pickupCompany.setValue(deliveryCompany.getValue());
pickupSalutation.setValue(deliverySalutation.getValue());
@@ -972,7 +1092,7 @@ public class AddJobView extends Main {
pickupZip.setValue(deliveryZip.getValue());
pickupCity.setValue(deliveryCity.getValue());
savePickupAddress.setValue(saveDeliveryAddress.getValue());
// Delivery mit zwischengespeicherten Pickup-Werten überschreiben
deliveryCompany.setValue(tempCompany);
deliverySalutation.setValue(tempSalutation);
@@ -985,7 +1105,7 @@ public class AddJobView extends Main {
deliveryZip.setValue(tempZip);
deliveryCity.setValue(tempCity);
saveDeliveryAddress.setValue(tempSaveAddress);
// Benutzer-Feedback
Notification.show("Etappen wurden erfolgreich getauscht!", 3000, Notification.Position.BOTTOM_CENTER);
}