Kachel-Header: Titel, Einklapp- und Schließen-Icon auf eine Zeile gestellt, Plus-Kachel klappt mit ein wenn alle Kacheln eingeklappt sind

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-04 10:44:41 +01:00
parent 8f40fd017f
commit 7ba2880148
2 changed files with 177 additions and 15 deletions

View File

@@ -1,10 +1,12 @@
package de.assecutor.votianlt.pages.base.ui.component;
import com.vaadin.flow.component.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.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;
@@ -31,6 +33,10 @@ public class DeliveryStationTile extends VerticalLayout {
void onDelete(DeliveryStationTile tile);
}
public interface CollapseListener {
void onCollapseChanged(boolean collapsed);
}
private final int stationNumber;
private final ComboBox<String> company;
@@ -48,6 +54,12 @@ public class DeliveryStationTile extends VerticalLayout {
private ChangeListener changeListener;
private DeleteListener deleteListener;
private CollapseListener collapseListener;
private boolean collapsed = false;
private Button collapseButton;
private VerticalLayout collapsedContent;
private List<Component> expandedOnlyComponents;
public DeliveryStationTile(int stationNumber, boolean removable, List<Customer> customers,
TranslationHelper translationHelper) {
@@ -62,15 +74,14 @@ public class DeliveryStationTile extends VerticalLayout {
getStyle().set("border-radius", "var(--lumo-border-radius-m)");
getStyle().set("background-color", "var(--lumo-base-color)");
// Header
// Header with title, collapse button and delete button on one line
title = new H3(translationHelper.getTranslation("addjob.station.delivery", stationNumber));
title.getStyle().set("margin", "0");
title.getStyle().set("margin", "0").set("flex-grow", "1");
HorizontalLayout titleLayout = new HorizontalLayout();
titleLayout.setWidthFull();
titleLayout.setJustifyContentMode(FlexComponent.JustifyContentMode.BETWEEN);
titleLayout.setDefaultVerticalComponentAlignment(FlexComponent.Alignment.CENTER);
titleLayout.add(title);
collapseButton = new Button(new Icon(VaadinIcon.ANGLE_DOUBLE_LEFT));
collapseButton.addThemeVariants(ButtonVariant.LUMO_TERTIARY_INLINE);
collapseButton.getStyle().set("cursor", "pointer");
collapseButton.addClickListener(e -> toggleCollapse());
Button deleteButton = new Button(new Icon(VaadinIcon.CLOSE_SMALL));
deleteButton.addThemeVariants(ButtonVariant.LUMO_ERROR, ButtonVariant.LUMO_TERTIARY);
@@ -83,7 +94,11 @@ public class DeliveryStationTile extends VerticalLayout {
} else {
deleteButton.getStyle().set("visibility", "hidden");
}
titleLayout.add(deleteButton);
HorizontalLayout titleLayout = new HorizontalLayout();
titleLayout.setWidthFull();
titleLayout.setDefaultVerticalComponentAlignment(FlexComponent.Alignment.CENTER);
titleLayout.add(title, collapseButton, deleteButton);
add(titleLayout);
@@ -172,6 +187,19 @@ public class DeliveryStationTile extends VerticalLayout {
// Register change listeners on all fields
setupChangeListeners();
// Store references to expanded-mode components (excluding titleLayout which stays visible)
expandedOnlyComponents = getChildren().filter(c -> c != titleLayout).toList();
getStyle().set("transition", "width 0.3s ease, min-width 0.3s ease");
// Collapsed content (initially hidden)
collapsedContent = new VerticalLayout();
collapsedContent.setPadding(false);
collapsedContent.setSpacing(false);
collapsedContent.getStyle().set("gap", "var(--lumo-space-xs)");
collapsedContent.setVisible(false);
add(collapsedContent);
}
private void setupChangeListeners() {
@@ -372,6 +400,14 @@ public class DeliveryStationTile extends VerticalLayout {
this.deleteListener = listener;
}
public void setCollapseListener(CollapseListener listener) {
this.collapseListener = listener;
}
public boolean isCollapsed() {
return collapsed;
}
/**
* Returns whether the user wants to save this address as a customer.
*/
@@ -403,6 +439,51 @@ public class DeliveryStationTile extends VerticalLayout {
}
}
private void toggleCollapse() {
collapsed = !collapsed;
if (collapsed) {
updateCollapsedContent();
expandedOnlyComponents.forEach(c -> c.setVisible(false));
collapsedContent.setVisible(true);
setWidth("25%");
getStyle().set("min-width", "150px");
collapseButton.setIcon(new Icon(VaadinIcon.ANGLE_DOUBLE_RIGHT));
} else {
expandedOnlyComponents.forEach(c -> c.setVisible(true));
collapsedContent.setVisible(false);
setWidth("40%");
getStyle().set("min-width", "300px");
collapseButton.setIcon(new Icon(VaadinIcon.ANGLE_DOUBLE_LEFT));
}
if (collapseListener != null) {
collapseListener.onCollapseChanged(collapsed);
}
}
private void updateCollapsedContent() {
collapsedContent.removeAll();
addCollapsedLine(company.getValue());
String name = (getValueOrEmpty(firstName) + " " + getValueOrEmpty(lastName)).trim();
addCollapsedLine(name);
String streetLine = (getValueOrEmpty(street) + " " + getValueOrEmpty(houseNumber)).trim();
addCollapsedLine(streetLine);
String zipCityLine = (getValueOrEmpty(zip) + " " + getValueOrEmpty(city)).trim();
addCollapsedLine(zipCityLine);
}
private void addCollapsedLine(String text) {
if (text != null && !text.trim().isEmpty()) {
Span span = new Span(text);
span.getStyle().set("font-size", "var(--lumo-font-size-xs)").set("word-break", "break-word")
.set("color", "var(--lumo-secondary-text-color)");
collapsedContent.add(span);
}
}
/**
* Functional interface for accessing translations from the parent view.
*/

View File

@@ -162,6 +162,10 @@ public class AddJobView extends Main implements HasDynamicTitle {
private ComboBox<TaskTemplate> templateComboBox;
private TextArea remarkArea;
private VerticalLayout pickupSection;
private boolean pickupCollapsed = false;
private Button pickupCollapseButton;
private VerticalLayout pickupCollapsedContent;
private List<Component> pickupExpandedOnlyComponents;
private final Binder<Job> binder = new Binder<>(Job.class);
@@ -531,6 +535,7 @@ public class AddJobView extends Main implements HasDynamicTitle {
updateTabLabels();
});
tile.setDeleteListener(this::removeDeliveryStationTile);
tile.setCollapseListener(collapsed -> updateAddStationButtonSize());
deliveryStationTiles.add(tile);
@@ -571,6 +576,7 @@ public class AddJobView extends Main implements HasDynamicTitle {
resetRouteInformation();
triggerValidation();
updateTabLabels();
updateAddStationButtonSize();
});
dialog.open();
}
@@ -1013,19 +1019,22 @@ public class AddJobView extends Main implements HasDynamicTitle {
section.getStyle().set("background-color", "var(--lumo-base-color)");
H3 title = new H3(getTranslation("addjob.section.pickup"));
title.getStyle().set("margin", "0");
title.getStyle().set("margin", "0").set("flex-grow", "1");
HorizontalLayout titleLayout = new HorizontalLayout();
titleLayout.setWidthFull();
titleLayout.setJustifyContentMode(FlexComponent.JustifyContentMode.BETWEEN);
titleLayout.setDefaultVerticalComponentAlignment(FlexComponent.Alignment.CENTER);
titleLayout.add(title);
pickupCollapseButton = new Button(new Icon(VaadinIcon.ANGLE_DOUBLE_LEFT));
pickupCollapseButton.addThemeVariants(ButtonVariant.LUMO_TERTIARY_INLINE);
pickupCollapseButton.getStyle().set("cursor", "pointer");
pickupCollapseButton.addClickListener(e -> togglePickupCollapse());
// Invisible placeholder button to match DeliveryStationTile header height
Button placeholder = new Button(new Icon(VaadinIcon.CLOSE_SMALL));
placeholder.addThemeVariants(ButtonVariant.LUMO_TERTIARY);
placeholder.getStyle().set("visibility", "hidden");
titleLayout.add(placeholder);
HorizontalLayout titleLayout = new HorizontalLayout();
titleLayout.setWidthFull();
titleLayout.setDefaultVerticalComponentAlignment(FlexComponent.Alignment.CENTER);
titleLayout.add(title, pickupCollapseButton, placeholder);
// Alle einzelnen Controls auf volle Breite setzen
pickupCompany.setWidthFull();
@@ -1064,9 +1073,81 @@ public class AddJobView extends Main implements HasDynamicTitle {
section.add(savePickupAddress);
// Store references to expanded-mode components (excluding titleLayout which stays visible)
pickupExpandedOnlyComponents = section.getChildren().filter(c -> c != titleLayout).toList();
section.getStyle().set("transition", "width 0.3s ease, min-width 0.3s ease");
// Collapsed content (initially hidden)
pickupCollapsedContent = new VerticalLayout();
pickupCollapsedContent.setPadding(false);
pickupCollapsedContent.setSpacing(false);
pickupCollapsedContent.getStyle().set("gap", "var(--lumo-space-xs)");
pickupCollapsedContent.setVisible(false);
section.add(pickupCollapsedContent);
return section;
}
private void togglePickupCollapse() {
pickupCollapsed = !pickupCollapsed;
if (pickupCollapsed) {
updatePickupCollapsedContent();
pickupExpandedOnlyComponents.forEach(c -> c.setVisible(false));
pickupCollapsedContent.setVisible(true);
pickupSection.setWidth("25%");
pickupSection.getStyle().set("min-width", "150px");
pickupCollapseButton.setIcon(new Icon(VaadinIcon.ANGLE_DOUBLE_RIGHT));
} else {
pickupExpandedOnlyComponents.forEach(c -> c.setVisible(true));
pickupCollapsedContent.setVisible(false);
pickupSection.setWidth("40%");
pickupSection.getStyle().set("min-width", "300px");
pickupCollapseButton.setIcon(new Icon(VaadinIcon.ANGLE_DOUBLE_LEFT));
}
updateAddStationButtonSize();
}
private void updateAddStationButtonSize() {
boolean allCollapsed = pickupCollapsed
&& deliveryStationTiles.stream().allMatch(DeliveryStationTile::isCollapsed);
if (allCollapsed) {
addStationButton.getStyle().set("width", "25%");
addStationButton.getStyle().set("min-width", "150px");
} else {
addStationButton.getStyle().set("width", "40%");
addStationButton.getStyle().set("min-width", "300px");
}
}
private void updatePickupCollapsedContent() {
pickupCollapsedContent.removeAll();
addPickupCollapsedLine(pickupCompany.getValue());
String name = (safeValue(pickupFirstName) + " " + safeValue(pickupLastName)).trim();
addPickupCollapsedLine(name);
String streetLine = (safeValue(pickupStreet) + " " + safeValue(pickupHouseNumber)).trim();
addPickupCollapsedLine(streetLine);
String zipCityLine = (safeValue(pickupZip) + " " + safeValue(pickupCity)).trim();
addPickupCollapsedLine(zipCityLine);
}
private void addPickupCollapsedLine(String text) {
if (text != null && !text.trim().isEmpty()) {
Span span = new Span(text);
span.getStyle().set("font-size", "var(--lumo-font-size-xs)").set("word-break", "break-word")
.set("color", "var(--lumo-secondary-text-color)");
pickupCollapsedContent.add(span);
}
}
private String safeValue(TextField field) {
return field.getValue() != null ? field.getValue().trim() : "";
}
// createDeliverySection() removed - delivery stations are now handled by
// DeliveryStationTile