Erweiterungen

This commit is contained in:
2025-09-11 10:59:50 +02:00
parent 127eee4e73
commit 04fe67574b
2 changed files with 305 additions and 33 deletions

View File

@@ -11,6 +11,8 @@ import org.springframework.data.mongodb.core.mapping.Document;
import org.springframework.data.mongodb.core.mapping.Field; import org.springframework.data.mongodb.core.mapping.Field;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.util.List;
import java.util.Map;
@Data @Data
@NoArgsConstructor @NoArgsConstructor
@@ -28,6 +30,13 @@ public class TaskEntry {
@Field("text") @Field("text")
private String text; private String text;
@Field("task_type")
private TaskType taskType = TaskType.CONFIRMATION;
// Task-specific configuration data
@Field("configuration")
private TaskConfiguration configuration;
// Completion tracking // Completion tracking
@Field("completed") @Field("completed")
private boolean completed = false; private boolean completed = false;
@@ -58,5 +67,51 @@ public class TaskEntry {
public String getJobIdAsString() { public String getJobIdAsString() {
return jobId != null ? jobId.toString() : null; return jobId != null ? jobId.toString() : null;
} }
/**
* Enum for different task types
*/
public enum TaskType {
CONFIRMATION("Bestätigung"),
SIGNATURE("Unterschrift"),
TODOLIST("To-Do Liste"),
PHOTO("Foto"),
BARCODE("Barcode");
private final String displayName;
TaskType(String displayName) {
this.displayName = displayName;
}
public String getDisplayName() {
return displayName;
}
}
/**
* Configuration data for different task types
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
public static class TaskConfiguration {
// For CONFIRMATION: button text
private String buttonText;
// For TODOLIST: list of todo items
private List<String> todoItems;
// For PHOTO: min and max photo count
private Integer minPhotoCount;
private Integer maxPhotoCount;
// For BARCODE: min and max barcode count
private Integer minBarcodeCount;
private Integer maxBarcodeCount;
// Generic configuration map for future extensions
private Map<String, Object> additionalConfig;
}
} }

View File

@@ -100,6 +100,7 @@ public class AddJobView extends Main {
private com.vaadin.flow.component.tabs.Tab addressesTab; private com.vaadin.flow.component.tabs.Tab addressesTab;
private com.vaadin.flow.component.tabs.Tab appointmentsTab; private com.vaadin.flow.component.tabs.Tab appointmentsTab;
private com.vaadin.flow.component.tabs.Tab cargoTab; private com.vaadin.flow.component.tabs.Tab cargoTab;
private com.vaadin.flow.component.tabs.Tab tasksTab;
private com.vaadin.flow.component.tabs.Tab priceTab; private com.vaadin.flow.component.tabs.Tab priceTab;
// Submit button // Submit button
@@ -327,10 +328,13 @@ public class AddJobView extends Main {
// Tab 2: Appointments & Processing // Tab 2: Appointments & Processing
appointmentsTab = tabSheet.add("Termine & Verarbeitung", createAppointmentsAndProcessingTab()); appointmentsTab = tabSheet.add("Termine & Verarbeitung", createAppointmentsAndProcessingTab());
// Tab 3: Cargo & Tasks // Tab 3: Cargo
cargoTab = tabSheet.add("Ladung & Aufgaben", createCargoAndTasksTab()); cargoTab = tabSheet.add("Ladung", createCargoTab());
// Tab 4: Price & Submit // Tab 4: Tasks
tasksTab = tabSheet.add("Aufgaben", createTasksTab());
// Tab 5: Price & Submit
priceTab = tabSheet.add("Preis & Abschluss", createPriceAndSubmitTab()); priceTab = tabSheet.add("Preis & Abschluss", createPriceAndSubmitTab());
add(tabSheet); add(tabSheet);
@@ -436,7 +440,7 @@ public class AddJobView extends Main {
return tabContent; return tabContent;
} }
private Component createCargoAndTasksTab() { private Component createCargoTab() {
VerticalLayout tabContent = new VerticalLayout(); VerticalLayout tabContent = new VerticalLayout();
tabContent.setSizeFull(); tabContent.setSizeFull();
tabContent.setPadding(true); tabContent.setPadding(true);
@@ -445,6 +449,15 @@ public class AddJobView extends Main {
// Add cargo section // Add cargo section
tabContent.add(createCargoSection()); tabContent.add(createCargoSection());
return tabContent;
}
private Component createTasksTab() {
VerticalLayout tabContent = new VerticalLayout();
tabContent.setSizeFull();
tabContent.setPadding(true);
tabContent.setSpacing(true);
// Add tasks and notes section // Add tasks and notes section
tabContent.add(createTasksAndNotesSection()); tabContent.add(createTasksAndNotesSection());
@@ -915,7 +928,8 @@ public class AddJobView extends Main {
// Check validation state for each tab and update labels with exclamation marks // Check validation state for each tab and update labels with exclamation marks
updateTabLabel(addressesTab, "Auftraggeber & Adressen", hasAddressValidationErrors()); updateTabLabel(addressesTab, "Auftraggeber & Adressen", hasAddressValidationErrors());
updateTabLabel(appointmentsTab, "Termine & Verarbeitung", hasAppointmentValidationErrors()); updateTabLabel(appointmentsTab, "Termine & Verarbeitung", hasAppointmentValidationErrors());
updateTabLabel(cargoTab, "Ladung & Aufgaben", hasCargoValidationErrors()); updateTabLabel(cargoTab, "Ladung", hasCargoValidationErrors());
updateTabLabel(tasksTab, "Aufgaben", hasTasksValidationErrors());
updateTabLabel(priceTab, "Preis & Abschluss", hasPriceValidationErrors()); updateTabLabel(priceTab, "Preis & Abschluss", hasPriceValidationErrors());
} }
@@ -969,6 +983,11 @@ public class AddJobView extends Main {
return !allCargoItemsValid; // Return true if ANY cargo item is incomplete (show warning) return !allCargoItemsValid; // Return true if ANY cargo item is incomplete (show warning)
} }
private boolean hasTasksValidationErrors() {
// Tasks are optional, so no validation errors for tasks
return false;
}
private boolean hasPriceValidationErrors() { private boolean hasPriceValidationErrors() {
return isFieldEmpty(price); return isFieldEmpty(price);
} }
@@ -1265,31 +1284,7 @@ public class AddJobView extends Main {
tasksList.setSpacing(true); tasksList.setSpacing(true);
java.util.function.Consumer<Void> addTask = (v) -> { java.util.function.Consumer<Void> addTask = (v) -> {
HorizontalLayout row = new HorizontalLayout(); createTaskRow();
row.setWidthFull();
row.setAlignItems(FlexComponent.Alignment.END);
TextField taskField = new TextField();
taskField.setPlaceholder("Aufgabe");
taskField.setWidth("100%");
Button remove = new Button(new Icon(VaadinIcon.CLOSE_SMALL));
remove.addThemeVariants(ButtonVariant.LUMO_ERROR, ButtonVariant.LUMO_TERTIARY);
remove.addClickListener(e -> {
int idx = tasksList.getChildren().toList().indexOf(row);
if (idx >= 0 && idx < tasksState.size()) tasksState.remove(idx);
tasksList.remove(row);
});
row.add(taskField, remove);
row.setFlexGrow(1, taskField);
tasksList.add(row);
// Keep backing tasks list in sync
TaskEntry te = new TaskEntry();
te.setText(taskField.getValue());
tasksState.add(te);
taskField.addValueChangeListener(ev -> te.setText(ev.getValue()));
}; };
// 1 Beispielzeile // 1 Beispielzeile
@@ -1458,4 +1453,226 @@ public class AddJobView extends Main {
} }
} }
private void createTaskRow() {
VerticalLayout taskContainer = new VerticalLayout();
taskContainer.setPadding(true);
taskContainer.setSpacing(true);
taskContainer.getStyle().set("border", "1px solid var(--lumo-contrast-20pct)");
taskContainer.getStyle().set("border-radius", "var(--lumo-border-radius-m)");
taskContainer.getStyle().set("background-color", "var(--lumo-base-color)");
taskContainer.getStyle().set("position", "relative");
// Task type selection
ComboBox<TaskEntry.TaskType> taskTypeCombo = new ComboBox<>("Aufgabentyp");
taskTypeCombo.setItems(TaskEntry.TaskType.values());
taskTypeCombo.setItemLabelGenerator(TaskEntry.TaskType::getDisplayName);
taskTypeCombo.setPlaceholder("Aufgabentyp wählen...");
taskTypeCombo.setWidthFull();
// Configuration container for dynamic fields
VerticalLayout configContainer = new VerticalLayout();
configContainer.setPadding(false);
configContainer.setSpacing(true);
// Red X button positioned in top-right corner
Button deleteXButton = new Button(new Icon(VaadinIcon.CLOSE_SMALL));
deleteXButton.addThemeVariants(ButtonVariant.LUMO_ERROR, ButtonVariant.LUMO_TERTIARY);
deleteXButton.getStyle().set("position", "absolute");
deleteXButton.getStyle().set("top", "8px");
deleteXButton.getStyle().set("right", "8px");
deleteXButton.getStyle().set("z-index", "10");
deleteXButton.getStyle().set("padding", "4px");
deleteXButton.getStyle().set("min-width", "24px");
deleteXButton.getStyle().set("min-height", "24px");
deleteXButton.addClickListener(e -> {
int idx = tasksList.getChildren().toList().indexOf(taskContainer);
if (idx >= 0 && idx < tasksState.size()) tasksState.remove(idx);
tasksList.remove(taskContainer);
});
taskContainer.add(taskTypeCombo, configContainer);
taskContainer.add(deleteXButton);
// Create TaskEntry and add to state
TaskEntry taskEntry = new TaskEntry();
taskEntry.setText("");
taskEntry.setTaskType(TaskEntry.TaskType.CONFIRMATION);
taskEntry.setConfiguration(new TaskEntry.TaskConfiguration());
tasksState.add(taskEntry);
taskTypeCombo.addValueChangeListener(ev -> {
TaskEntry.TaskType selectedType = ev.getValue();
if (selectedType != null) {
taskEntry.setTaskType(selectedType);
updateTaskConfiguration(configContainer, taskEntry);
}
});
// Set initial configuration
taskTypeCombo.setValue(TaskEntry.TaskType.CONFIRMATION);
updateTaskConfiguration(configContainer, taskEntry);
tasksList.add(taskContainer);
}
private void updateTaskConfiguration(VerticalLayout configContainer, TaskEntry taskEntry) {
configContainer.removeAll();
TaskEntry.TaskType taskType = taskEntry.getTaskType();
if (taskType == null) return;
// Ensure configuration is initialized
if (taskEntry.getConfiguration() == null) {
taskEntry.setConfiguration(new TaskEntry.TaskConfiguration());
}
switch (taskType) {
case CONFIRMATION:
TextField buttonTextField = new TextField("Button-Text");
buttonTextField.setPlaceholder("z.B. 'Bestätigen', 'Abgeschlossen'");
buttonTextField.setWidthFull();
buttonTextField.setValue(taskEntry.getConfiguration().getButtonText() != null ?
taskEntry.getConfiguration().getButtonText() : "");
buttonTextField.addValueChangeListener(ev -> {
taskEntry.getConfiguration().setButtonText(ev.getValue());
});
configContainer.add(buttonTextField);
break;
case SIGNATURE:
// No additional configuration needed
Span info = new Span("Keine zusätzliche Konfiguration erforderlich");
info.getStyle().set("color", "var(--lumo-secondary-text-color)");
info.getStyle().set("font-style", "italic");
configContainer.add(info);
break;
case TODOLIST:
VerticalLayout todoContainer = new VerticalLayout();
todoContainer.setPadding(false);
todoContainer.setSpacing(true);
H3 todoTitle = new H3("To-Do Punkte");
todoTitle.getStyle().set("margin", "0");
todoContainer.add(todoTitle);
// Dynamic todo list
VerticalLayout todoList = new VerticalLayout();
todoList.setPadding(false);
todoList.setSpacing(true);
java.util.function.Consumer<Void> addTodoItem = (v) -> {
HorizontalLayout todoRow = new HorizontalLayout();
todoRow.setWidthFull();
todoRow.setAlignItems(FlexComponent.Alignment.END);
TextField todoField = new TextField();
todoField.setPlaceholder("To-Do Punkt");
todoField.setWidth("100%");
Button removeTodo = new Button(new Icon(VaadinIcon.CLOSE_SMALL));
removeTodo.addThemeVariants(ButtonVariant.LUMO_ERROR, ButtonVariant.LUMO_TERTIARY);
removeTodo.addClickListener(e -> {
todoList.remove(todoRow);
updateTodoItems(todoList, taskEntry);
});
todoRow.add(todoField, removeTodo);
todoRow.setFlexGrow(1, todoField);
todoList.add(todoRow);
todoField.addValueChangeListener(ev -> updateTodoItems(todoList, taskEntry));
};
Button addTodoBtn = new Button("To-Do Punkt hinzufügen", new Icon(VaadinIcon.PLUS));
addTodoBtn.addThemeVariants(ButtonVariant.LUMO_TERTIARY);
addTodoBtn.addClickListener(e -> addTodoItem.accept(null));
// Add initial todo item
addTodoItem.accept(null);
todoContainer.add(todoList, addTodoBtn);
configContainer.add(todoContainer);
break;
case PHOTO:
HorizontalLayout photoLayout = new HorizontalLayout();
photoLayout.setWidthFull();
photoLayout.setSpacing(true);
IntegerField minPhotos = new IntegerField("Min. Anzahl Fotos");
minPhotos.setPlaceholder("1");
minPhotos.setMin(1);
minPhotos.setValue(taskEntry.getConfiguration().getMinPhotoCount() != null ?
taskEntry.getConfiguration().getMinPhotoCount() : 1);
IntegerField maxPhotos = new IntegerField("Max. Anzahl Fotos");
maxPhotos.setPlaceholder("10");
maxPhotos.setMin(1);
maxPhotos.setValue(taskEntry.getConfiguration().getMaxPhotoCount() != null ?
taskEntry.getConfiguration().getMaxPhotoCount() : 10);
photoLayout.add(minPhotos, maxPhotos);
minPhotos.addValueChangeListener(ev -> {
taskEntry.getConfiguration().setMinPhotoCount(ev.getValue());
});
maxPhotos.addValueChangeListener(ev -> {
taskEntry.getConfiguration().setMaxPhotoCount(ev.getValue());
});
configContainer.add(photoLayout);
break;
case BARCODE:
HorizontalLayout barcodeLayout = new HorizontalLayout();
barcodeLayout.setWidthFull();
barcodeLayout.setSpacing(true);
IntegerField minBarcodes = new IntegerField("Min. Anzahl Barcodes");
minBarcodes.setPlaceholder("1");
minBarcodes.setMin(1);
minBarcodes.setValue(taskEntry.getConfiguration().getMinBarcodeCount() != null ?
taskEntry.getConfiguration().getMinBarcodeCount() : 1);
IntegerField maxBarcodes = new IntegerField("Max. Anzahl Barcodes");
maxBarcodes.setPlaceholder("10");
maxBarcodes.setMin(1);
maxBarcodes.setValue(taskEntry.getConfiguration().getMaxBarcodeCount() != null ?
taskEntry.getConfiguration().getMaxBarcodeCount() : 10);
barcodeLayout.add(minBarcodes, maxBarcodes);
minBarcodes.addValueChangeListener(ev -> {
taskEntry.getConfiguration().setMinBarcodeCount(ev.getValue());
});
maxBarcodes.addValueChangeListener(ev -> {
taskEntry.getConfiguration().setMaxBarcodeCount(ev.getValue());
});
configContainer.add(barcodeLayout);
break;
}
}
private void updateTodoItems(VerticalLayout todoList, TaskEntry taskEntry) {
List<String> todoItems = todoList.getChildren()
.map(component -> {
if (component instanceof HorizontalLayout) {
HorizontalLayout row = (HorizontalLayout) component;
TextField field = (TextField) row.getChildren().findFirst().orElse(null);
return field != null ? field.getValue() : null;
}
return null;
})
.filter(Objects::nonNull)
.filter(item -> !item.trim().isEmpty())
.collect(java.util.stream.Collectors.toList());
taskEntry.getConfiguration().setTodoItems(todoItems);
}
} }