From e5e1af70fb454e181847e1280db9459fdf53c966 Mon Sep 17 00:00:00 2001 From: Sven Carstensen Date: Fri, 23 Jan 2026 10:32:44 +0100 Subject: [PATCH] Erweiterungen --- .../aimailassistant/mail/ui/MainView.java | 2 +- .../mail/ui/OrderDetailDialog.java | 209 +++++++++++------- .../mail/ui/SuccessDialog.java | 5 +- 3 files changed, 140 insertions(+), 76 deletions(-) diff --git a/src/main/java/de/assecutor/aimailassistant/mail/ui/MainView.java b/src/main/java/de/assecutor/aimailassistant/mail/ui/MainView.java index 4411927..2bfa656 100644 --- a/src/main/java/de/assecutor/aimailassistant/mail/ui/MainView.java +++ b/src/main/java/de/assecutor/aimailassistant/mail/ui/MainView.java @@ -183,7 +183,7 @@ public class MainView extends VerticalLayout { String textClass; if (email.isConfirmed()) { - statusText = "Bestätigt"; + statusText = "Bearbeitet"; bgClass = LumoUtility.Background.SUCCESS_10; textClass = LumoUtility.TextColor.SUCCESS; } else if (email.isProcessed()) { diff --git a/src/main/java/de/assecutor/aimailassistant/mail/ui/OrderDetailDialog.java b/src/main/java/de/assecutor/aimailassistant/mail/ui/OrderDetailDialog.java index f8b923e..d1fb256 100644 --- a/src/main/java/de/assecutor/aimailassistant/mail/ui/OrderDetailDialog.java +++ b/src/main/java/de/assecutor/aimailassistant/mail/ui/OrderDetailDialog.java @@ -9,6 +9,7 @@ import com.vaadin.flow.component.dnd.DragSource; import com.vaadin.flow.component.dnd.DropEffect; import com.vaadin.flow.component.dnd.DropTarget; import com.vaadin.flow.component.formlayout.FormLayout; +import com.vaadin.flow.data.binder.Binder; import com.vaadin.flow.component.html.Div; import com.vaadin.flow.component.html.H4; import com.vaadin.flow.component.html.Pre; @@ -47,10 +48,12 @@ public class OrderDetailDialog extends Dialog { private final Consumer onProcessed; private final Consumer onDelete; private final String googleMapsApiKey; + private final boolean readOnly; private TextArea offerTextArea; private TextArea confirmationTextArea; private EmailField recipientField; private HorizontalLayout contentLayout; + private Binder binder; public OrderDetailDialog( OrderEmail orderEmail, @@ -66,8 +69,10 @@ public class OrderDetailDialog extends Dialog { this.googleMapsApiKey = googleMapsApiKey; this.onProcessed = onProcessed; this.onDelete = onDelete; + this.readOnly = orderEmail.isProcessed(); - setHeaderTitle("Email-Details: " + orderEmail.getSubject()); + String titleSuffix = readOnly ? " (Bereits bearbeitet)" : ""; + setHeaderTitle("Email-Details: " + orderEmail.getSubject() + titleSuffix); setCloseOnOutsideClick(false); setWidth("80vw"); setHeight("85vh"); @@ -258,25 +263,28 @@ public class OrderDetailDialog extends Dialog { typeComboBox.setItemLabelGenerator(EmailType::getDisplayName); typeComboBox.setValue(orderEmail.getType()); typeComboBox.setWidth("200px"); + typeComboBox.setReadOnly(readOnly); - typeComboBox.addValueChangeListener(event -> { - if (event.getValue() != null && event.getValue() != event.getOldValue()) { - EmailType newType = event.getValue(); - orderEmail.setType(newType); - summary.setOrderType(newType); - onProcessed.accept(orderEmail); + if (!readOnly) { + typeComboBox.addValueChangeListener(event -> { + if (event.getValue() != null && event.getValue() != event.getOldValue()) { + EmailType newType = event.getValue(); + orderEmail.setType(newType); + summary.setOrderType(newType); + onProcessed.accept(orderEmail); - // Refresh the dialog content - remove(contentLayout); - getFooter().removeAll(); - contentLayout = createContent(); - add(contentLayout); - createFooter(); + // Refresh the dialog content + remove(contentLayout); + getFooter().removeAll(); + contentLayout = createContent(); + add(contentLayout); + createFooter(); - Notification.show("Klassifizierung geändert zu: " + newType.getDisplayName(), - 3000, Notification.Position.BOTTOM_START); - } - }); + Notification.show("Klassifizierung geändert zu: " + newType.getDisplayName(), + 3000, Notification.Position.BOTTOM_START); + } + }); + } layout.add(label, typeComboBox); return layout; @@ -300,19 +308,36 @@ public class OrderDetailDialog extends Dialog { // Geschlecht ComboBox direkt unter Anforderer ComboBox genderComboBox = new ComboBox<>(); - genderComboBox.setItems(Gender.MALE, Gender.FEMALE, Gender.UNKNOWN); + genderComboBox.setItems(Gender.MALE, Gender.FEMALE); genderComboBox.setItemLabelGenerator(Gender::getDisplayName); - genderComboBox.setValue(summary.getRequesterGender() != null ? summary.getRequesterGender() : Gender.UNKNOWN); genderComboBox.setWidth("150px"); - genderComboBox.addValueChangeListener(event -> { - if (event.getValue() != null) { - summary.setRequesterGender(event.getValue()); - orderEmail.setSummaryJson(llmService.serializeSummary(summary)); - onProcessed.accept(orderEmail); - // Aktualisiere die Mail-Templates - updateMailTemplates(); - } - }); + genderComboBox.setReadOnly(readOnly); + + // Binder für Validierung + binder = new Binder<>(OrderSummary.class); + binder.forField(genderComboBox) + .asRequired("Bitte wählen Sie eine Anrede") + .bind(OrderSummary::getRequesterGender, OrderSummary::setRequesterGender); + binder.setBean(summary); + + // Initiale Validierung auslösen, um roten Rahmen zu zeigen wenn keine Auswahl + if (!readOnly && (summary.getRequesterGender() == null || summary.getRequesterGender() == Gender.UNKNOWN)) { + genderComboBox.setInvalid(true); + } + + if (!readOnly) { + genderComboBox.addValueChangeListener(event -> { + if (event.getValue() != null) { + summary.setRequesterGender(event.getValue()); + orderEmail.setSummaryJson(llmService.serializeSummary(summary)); + onProcessed.accept(orderEmail); + // Aktualisiere die Mail-Templates + updateMailTemplates(); + // Validierung aktualisieren + genderComboBox.setInvalid(false); + } + }); + } form.addFormItem(genderComboBox, "Anrede"); } @@ -365,7 +390,9 @@ public class OrderDetailDialog extends Dialog { cardWrapper.setAlignItems(FlexComponent.Alignment.CENTER); cardWrapper.setSpacing(true); cardWrapper.setWidthFull(); - cardWrapper.getStyle().set("cursor", "grab"); + if (!readOnly) { + cardWrapper.getStyle().set("cursor", "grab"); + } // Numbered badge (circular, light blue) Span numberBadge = new Span(String.valueOf(i + 1)); @@ -438,41 +465,46 @@ public class OrderDetailDialog extends Dialog { stationInfo.add(address); } - // Edit button in top-right corner - Button editButton = new Button(new Icon(VaadinIcon.EDIT)); - editButton.addThemeVariants(ButtonVariant.LUMO_TERTIARY_INLINE, ButtonVariant.LUMO_SMALL); - editButton.getElement().setAttribute("title", "Station bearbeiten"); - editButton.getStyle() - .set("position", "absolute") - .set("top", "var(--lumo-space-xs)") - .set("right", "var(--lumo-space-xs)"); - editButton.addClickListener(e -> openStationEditDialog(station, section)); - stationContent.add(actionBadge, stationInfo); stationContent.setFlexGrow(1, stationInfo); - stationCard.add(stationContent, editButton); + stationCard.add(stationContent); + + // Edit button only in edit mode + if (!readOnly) { + Button editButton = new Button(new Icon(VaadinIcon.EDIT)); + editButton.addThemeVariants(ButtonVariant.LUMO_TERTIARY_INLINE, ButtonVariant.LUMO_SMALL); + editButton.getElement().setAttribute("title", "Station bearbeiten"); + editButton.getStyle() + .set("position", "absolute") + .set("top", "var(--lumo-space-xs)") + .set("right", "var(--lumo-space-xs)"); + editButton.addClickListener(e -> openStationEditDialog(station, section)); + stationCard.add(editButton); + } cardWrapper.add(numberBadge, stationCard); - // Drag and Drop setup - DragSource dragSource = DragSource.create(cardWrapper); - dragSource.setDragData(index); - dragSource.addDragStartListener(e -> cardWrapper.getStyle().set("opacity", "0.5")); - dragSource.addDragEndListener(e -> cardWrapper.getStyle().remove("opacity")); + // Drag and Drop setup only in edit mode + if (!readOnly) { + DragSource dragSource = DragSource.create(cardWrapper); + dragSource.setDragData(index); + dragSource.addDragStartListener(e -> cardWrapper.getStyle().set("opacity", "0.5")); + dragSource.addDragEndListener(e -> cardWrapper.getStyle().remove("opacity")); - DropTarget dropTarget = DropTarget.create(cardWrapper); - dropTarget.setDropEffect(DropEffect.MOVE); - dropTarget.addDropListener(e -> { - e.getDragSourceComponent().ifPresent(source -> { - if (source instanceof HorizontalLayout) { - e.getDragData().ifPresent(data -> { - if (data instanceof Integer sourceIndex) { - reorderStations(sourceIndex, index, section); - } - }); - } + DropTarget dropTarget = DropTarget.create(cardWrapper); + dropTarget.setDropEffect(DropEffect.MOVE); + dropTarget.addDropListener(e -> { + e.getDragSourceComponent().ifPresent(source -> { + if (source instanceof HorizontalLayout) { + e.getDragData().ifPresent(data -> { + if (data instanceof Integer sourceIndex) { + reorderStations(sourceIndex, index, section); + } + }); + } + }); }); - }); + } section.add(cardWrapper); } @@ -596,7 +628,7 @@ public class OrderDetailDialog extends Dialog { section.setSpacing(false); section.setWidthFull(); - H4 title = new H4("Angebotstext"); + H4 title = new H4(readOnly ? "Gesendetes Angebot" : "Angebotstext"); title.addClassNames(LumoUtility.Margin.Bottom.SMALL, LumoUtility.Margin.Top.SMALL); section.add(title); @@ -604,12 +636,18 @@ public class OrderDetailDialog extends Dialog { offerTextArea.setWidthFull(); offerTextArea.setMinHeight("120px"); offerTextArea.setValue(smtpEmailService.getDefaultOfferTemplate(summary)); - offerTextArea.setHelperText("Bearbeiten Sie den Angebotstext vor dem Versenden"); + offerTextArea.setReadOnly(readOnly); + if (!readOnly) { + offerTextArea.setHelperText("Bearbeiten Sie den Angebotstext vor dem Versenden"); + } recipientField = new EmailField("Empfänger"); recipientField.setWidthFull(); recipientField.setValue(orderEmail.getFromAddress()); - recipientField.setHelperText("Email-Adresse des Empfängers"); + recipientField.setReadOnly(readOnly); + if (!readOnly) { + recipientField.setHelperText("Email-Adresse des Empfängers"); + } section.add(offerTextArea, recipientField); return section; @@ -621,7 +659,7 @@ public class OrderDetailDialog extends Dialog { section.setSpacing(false); section.setWidthFull(); - H4 title = new H4("Bestätigungstext"); + H4 title = new H4(readOnly ? "Gesendete Bestätigung" : "Bestätigungstext"); title.addClassNames(LumoUtility.Margin.Bottom.SMALL, LumoUtility.Margin.Top.SMALL); section.add(title); @@ -629,38 +667,49 @@ public class OrderDetailDialog extends Dialog { confirmationTextArea.setWidthFull(); confirmationTextArea.setMinHeight("120px"); confirmationTextArea.setValue(smtpEmailService.getDefaultConfirmationTemplate(summary)); - confirmationTextArea.setHelperText("Bearbeiten Sie den Bestätigungstext vor dem Versenden"); + confirmationTextArea.setReadOnly(readOnly); + if (!readOnly) { + confirmationTextArea.setHelperText("Bearbeiten Sie den Bestätigungstext vor dem Versenden"); + } recipientField = new EmailField("Empfänger"); recipientField.setWidthFull(); recipientField.setValue(orderEmail.getFromAddress()); - recipientField.setHelperText("Email-Adresse des Empfängers"); + recipientField.setReadOnly(readOnly); + if (!readOnly) { + recipientField.setHelperText("Email-Adresse des Empfängers"); + } section.add(confirmationTextArea, recipientField); return section; } private void createFooter() { - Button cancelButton = new Button("Abbrechen", e -> close()); + Button closeButton = new Button("Schließen", e -> close()); Button deleteButton = new Button("Löschen", e -> confirmDelete()); deleteButton.addThemeVariants(ButtonVariant.LUMO_ERROR, ButtonVariant.LUMO_TERTIARY); - Button reprocessButton = new Button("Neu analysieren", e -> reprocessEmail()); - reprocessButton.addThemeVariants(ButtonVariant.LUMO_TERTIARY); - // Spacer to push action buttons to the right Div spacer = new Div(); spacer.getStyle().set("flex-grow", "1"); - if (orderEmail.getType() == EmailType.QUOTE_REQUEST) { - Button sendOfferButton = new Button("Angebot senden", e -> sendOffer()); - sendOfferButton.addThemeVariants(ButtonVariant.LUMO_PRIMARY); - getFooter().add(deleteButton, spacer, cancelButton, reprocessButton, sendOfferButton); + if (readOnly) { + // Read-only mode: only show close and delete buttons + getFooter().add(deleteButton, spacer, closeButton); } else { - Button acceptButton = new Button("Auftrag annehmen", e -> acceptOrder()); - acceptButton.addThemeVariants(ButtonVariant.LUMO_PRIMARY); - getFooter().add(deleteButton, spacer, cancelButton, reprocessButton, acceptButton); + Button reprocessButton = new Button("Neu analysieren", e -> reprocessEmail()); + reprocessButton.addThemeVariants(ButtonVariant.LUMO_TERTIARY); + + if (orderEmail.getType() == EmailType.QUOTE_REQUEST) { + Button sendOfferButton = new Button("Angebot senden", e -> sendOffer()); + sendOfferButton.addThemeVariants(ButtonVariant.LUMO_PRIMARY); + getFooter().add(deleteButton, spacer, closeButton, reprocessButton, sendOfferButton); + } else { + Button acceptButton = new Button("Auftrag annehmen", e -> acceptOrder()); + acceptButton.addThemeVariants(ButtonVariant.LUMO_PRIMARY); + getFooter().add(deleteButton, spacer, closeButton, reprocessButton, acceptButton); + } } } @@ -681,6 +730,12 @@ public class OrderDetailDialog extends Dialog { } private void acceptOrder() { + // Validierung prüfen + if (binder != null && !binder.isValid()) { + Notification.show("Bitte wählen Sie eine Anrede aus", 3000, Notification.Position.MIDDLE); + return; + } + try { String confirmationText = confirmationTextArea != null ? confirmationTextArea.getValue() : null; String recipient = recipientField != null ? recipientField.getValue() : orderEmail.getFromAddress(); @@ -699,6 +754,12 @@ public class OrderDetailDialog extends Dialog { } private void sendOffer() { + // Validierung prüfen + if (binder != null && !binder.isValid()) { + Notification.show("Bitte wählen Sie eine Anrede aus", 3000, Notification.Position.MIDDLE); + return; + } + try { String offerText = offerTextArea != null ? offerTextArea.getValue() : null; String recipient = recipientField != null ? recipientField.getValue() : orderEmail.getFromAddress(); diff --git a/src/main/java/de/assecutor/aimailassistant/mail/ui/SuccessDialog.java b/src/main/java/de/assecutor/aimailassistant/mail/ui/SuccessDialog.java index 7047cfc..72e1f59 100644 --- a/src/main/java/de/assecutor/aimailassistant/mail/ui/SuccessDialog.java +++ b/src/main/java/de/assecutor/aimailassistant/mail/ui/SuccessDialog.java @@ -43,7 +43,10 @@ public class SuccessDialog extends Dialog { checklist.setWidth("100%"); checklist.add(createChecklistItem("Bestätigungsmail versendet", true)); - checklist.add(createChecklistItem("Auftrag in Votian angelegt", true)); + + if (isOrder) { + checklist.add(createChecklistItem("Auftrag in Votian angelegt", true)); + } content.add(checklist);