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:
@@ -1,10 +1,12 @@
|
|||||||
package de.assecutor.votianlt.pages.base.ui.component;
|
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.Button;
|
||||||
import com.vaadin.flow.component.button.ButtonVariant;
|
import com.vaadin.flow.component.button.ButtonVariant;
|
||||||
import com.vaadin.flow.component.checkbox.Checkbox;
|
import com.vaadin.flow.component.checkbox.Checkbox;
|
||||||
import com.vaadin.flow.component.combobox.ComboBox;
|
import com.vaadin.flow.component.combobox.ComboBox;
|
||||||
import com.vaadin.flow.component.html.H3;
|
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.Icon;
|
||||||
import com.vaadin.flow.component.icon.VaadinIcon;
|
import com.vaadin.flow.component.icon.VaadinIcon;
|
||||||
import com.vaadin.flow.component.orderedlayout.FlexComponent;
|
import com.vaadin.flow.component.orderedlayout.FlexComponent;
|
||||||
@@ -31,6 +33,10 @@ public class DeliveryStationTile extends VerticalLayout {
|
|||||||
void onDelete(DeliveryStationTile tile);
|
void onDelete(DeliveryStationTile tile);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public interface CollapseListener {
|
||||||
|
void onCollapseChanged(boolean collapsed);
|
||||||
|
}
|
||||||
|
|
||||||
private final int stationNumber;
|
private final int stationNumber;
|
||||||
|
|
||||||
private final ComboBox<String> company;
|
private final ComboBox<String> company;
|
||||||
@@ -48,6 +54,12 @@ public class DeliveryStationTile extends VerticalLayout {
|
|||||||
|
|
||||||
private ChangeListener changeListener;
|
private ChangeListener changeListener;
|
||||||
private DeleteListener deleteListener;
|
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,
|
public DeliveryStationTile(int stationNumber, boolean removable, List<Customer> customers,
|
||||||
TranslationHelper translationHelper) {
|
TranslationHelper translationHelper) {
|
||||||
@@ -62,15 +74,14 @@ public class DeliveryStationTile extends VerticalLayout {
|
|||||||
getStyle().set("border-radius", "var(--lumo-border-radius-m)");
|
getStyle().set("border-radius", "var(--lumo-border-radius-m)");
|
||||||
getStyle().set("background-color", "var(--lumo-base-color)");
|
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 = 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();
|
collapseButton = new Button(new Icon(VaadinIcon.ANGLE_DOUBLE_LEFT));
|
||||||
titleLayout.setWidthFull();
|
collapseButton.addThemeVariants(ButtonVariant.LUMO_TERTIARY_INLINE);
|
||||||
titleLayout.setJustifyContentMode(FlexComponent.JustifyContentMode.BETWEEN);
|
collapseButton.getStyle().set("cursor", "pointer");
|
||||||
titleLayout.setDefaultVerticalComponentAlignment(FlexComponent.Alignment.CENTER);
|
collapseButton.addClickListener(e -> toggleCollapse());
|
||||||
titleLayout.add(title);
|
|
||||||
|
|
||||||
Button deleteButton = new Button(new Icon(VaadinIcon.CLOSE_SMALL));
|
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);
|
||||||
@@ -83,7 +94,11 @@ public class DeliveryStationTile extends VerticalLayout {
|
|||||||
} else {
|
} else {
|
||||||
deleteButton.getStyle().set("visibility", "hidden");
|
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);
|
add(titleLayout);
|
||||||
|
|
||||||
@@ -172,6 +187,19 @@ 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)
|
||||||
|
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() {
|
private void setupChangeListeners() {
|
||||||
@@ -372,6 +400,14 @@ public class DeliveryStationTile extends VerticalLayout {
|
|||||||
this.deleteListener = listener;
|
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.
|
* 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.
|
* Functional interface for accessing translations from the parent view.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -162,6 +162,10 @@ public class AddJobView extends Main implements HasDynamicTitle {
|
|||||||
private ComboBox<TaskTemplate> templateComboBox;
|
private ComboBox<TaskTemplate> templateComboBox;
|
||||||
private TextArea remarkArea;
|
private TextArea remarkArea;
|
||||||
private VerticalLayout pickupSection;
|
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);
|
private final Binder<Job> binder = new Binder<>(Job.class);
|
||||||
|
|
||||||
@@ -531,6 +535,7 @@ public class AddJobView extends Main implements HasDynamicTitle {
|
|||||||
updateTabLabels();
|
updateTabLabels();
|
||||||
});
|
});
|
||||||
tile.setDeleteListener(this::removeDeliveryStationTile);
|
tile.setDeleteListener(this::removeDeliveryStationTile);
|
||||||
|
tile.setCollapseListener(collapsed -> updateAddStationButtonSize());
|
||||||
|
|
||||||
deliveryStationTiles.add(tile);
|
deliveryStationTiles.add(tile);
|
||||||
|
|
||||||
@@ -571,6 +576,7 @@ public class AddJobView extends Main implements HasDynamicTitle {
|
|||||||
resetRouteInformation();
|
resetRouteInformation();
|
||||||
triggerValidation();
|
triggerValidation();
|
||||||
updateTabLabels();
|
updateTabLabels();
|
||||||
|
updateAddStationButtonSize();
|
||||||
});
|
});
|
||||||
dialog.open();
|
dialog.open();
|
||||||
}
|
}
|
||||||
@@ -1013,19 +1019,22 @@ public class AddJobView extends Main implements HasDynamicTitle {
|
|||||||
section.getStyle().set("background-color", "var(--lumo-base-color)");
|
section.getStyle().set("background-color", "var(--lumo-base-color)");
|
||||||
|
|
||||||
H3 title = new H3(getTranslation("addjob.section.pickup"));
|
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();
|
pickupCollapseButton = new Button(new Icon(VaadinIcon.ANGLE_DOUBLE_LEFT));
|
||||||
titleLayout.setWidthFull();
|
pickupCollapseButton.addThemeVariants(ButtonVariant.LUMO_TERTIARY_INLINE);
|
||||||
titleLayout.setJustifyContentMode(FlexComponent.JustifyContentMode.BETWEEN);
|
pickupCollapseButton.getStyle().set("cursor", "pointer");
|
||||||
titleLayout.setDefaultVerticalComponentAlignment(FlexComponent.Alignment.CENTER);
|
pickupCollapseButton.addClickListener(e -> togglePickupCollapse());
|
||||||
titleLayout.add(title);
|
|
||||||
|
|
||||||
// Invisible placeholder button to match DeliveryStationTile header height
|
// Invisible placeholder button to match DeliveryStationTile header height
|
||||||
Button placeholder = new Button(new Icon(VaadinIcon.CLOSE_SMALL));
|
Button placeholder = new Button(new Icon(VaadinIcon.CLOSE_SMALL));
|
||||||
placeholder.addThemeVariants(ButtonVariant.LUMO_TERTIARY);
|
placeholder.addThemeVariants(ButtonVariant.LUMO_TERTIARY);
|
||||||
placeholder.getStyle().set("visibility", "hidden");
|
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
|
// Alle einzelnen Controls auf volle Breite setzen
|
||||||
pickupCompany.setWidthFull();
|
pickupCompany.setWidthFull();
|
||||||
@@ -1064,9 +1073,81 @@ public class AddJobView extends Main implements HasDynamicTitle {
|
|||||||
|
|
||||||
section.add(savePickupAddress);
|
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;
|
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
|
// createDeliverySection() removed - delivery stations are now handled by
|
||||||
// DeliveryStationTile
|
// DeliveryStationTile
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user