diff --git a/src/main/java/de/assecutor/votianlt/model/TaskEntry.java b/src/main/java/de/assecutor/votianlt/model/TaskEntry.java index be9f0a4..e0be32b 100644 --- a/src/main/java/de/assecutor/votianlt/model/TaskEntry.java +++ b/src/main/java/de/assecutor/votianlt/model/TaskEntry.java @@ -11,6 +11,8 @@ import org.springframework.data.mongodb.core.mapping.Document; import org.springframework.data.mongodb.core.mapping.Field; import java.time.LocalDateTime; +import java.util.List; +import java.util.Map; @Data @NoArgsConstructor @@ -28,6 +30,13 @@ public class TaskEntry { @Field("text") private String text; + @Field("task_type") + private TaskType taskType = TaskType.CONFIRMATION; + + // Task-specific configuration data + @Field("configuration") + private TaskConfiguration configuration; + // Completion tracking @Field("completed") private boolean completed = false; @@ -58,5 +67,51 @@ public class TaskEntry { public String getJobIdAsString() { 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 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 additionalConfig; + } } diff --git a/src/main/java/de/assecutor/votianlt/pages/view/AddJobView.java b/src/main/java/de/assecutor/votianlt/pages/view/AddJobView.java index f0ca69c..40a295d 100644 --- a/src/main/java/de/assecutor/votianlt/pages/view/AddJobView.java +++ b/src/main/java/de/assecutor/votianlt/pages/view/AddJobView.java @@ -100,6 +100,7 @@ public class AddJobView extends Main { private com.vaadin.flow.component.tabs.Tab addressesTab; private com.vaadin.flow.component.tabs.Tab appointmentsTab; private com.vaadin.flow.component.tabs.Tab cargoTab; + private com.vaadin.flow.component.tabs.Tab tasksTab; private com.vaadin.flow.component.tabs.Tab priceTab; // Submit button @@ -327,10 +328,13 @@ public class AddJobView extends Main { // Tab 2: Appointments & Processing appointmentsTab = tabSheet.add("Termine & Verarbeitung", createAppointmentsAndProcessingTab()); - // Tab 3: Cargo & Tasks - cargoTab = tabSheet.add("Ladung & Aufgaben", createCargoAndTasksTab()); + // Tab 3: Cargo + 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()); add(tabSheet); @@ -436,7 +440,7 @@ public class AddJobView extends Main { return tabContent; } - private Component createCargoAndTasksTab() { + private Component createCargoTab() { VerticalLayout tabContent = new VerticalLayout(); tabContent.setSizeFull(); tabContent.setPadding(true); @@ -445,6 +449,15 @@ public class AddJobView extends Main { // Add cargo section 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 tabContent.add(createTasksAndNotesSection()); @@ -915,7 +928,8 @@ public class AddJobView extends Main { // Check validation state for each tab and update labels with exclamation marks updateTabLabel(addressesTab, "Auftraggeber & Adressen", hasAddressValidationErrors()); updateTabLabel(appointmentsTab, "Termine & Verarbeitung", hasAppointmentValidationErrors()); - updateTabLabel(cargoTab, "Ladung & Aufgaben", hasCargoValidationErrors()); + updateTabLabel(cargoTab, "Ladung", hasCargoValidationErrors()); + updateTabLabel(tasksTab, "Aufgaben", hasTasksValidationErrors()); 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) } + private boolean hasTasksValidationErrors() { + // Tasks are optional, so no validation errors for tasks + return false; + } + private boolean hasPriceValidationErrors() { return isFieldEmpty(price); } @@ -1066,9 +1085,9 @@ public class AddJobView extends Main { } catch (Exception e) { // Other errors - // Reset cargo error - if (cargoError != null) cargoError.setVisible(false); - if (cargoAreaContainer != null) cargoAreaContainer.getStyle().set("border", "1px solid var(--lumo-contrast-20pct)"); + // Reset cargo error + if (cargoError != null) cargoError.setVisible(false); + if (cargoAreaContainer != null) cargoAreaContainer.getStyle().set("border", "1px solid var(--lumo-contrast-20pct)"); Notification errorNotification = Notification.show( "Fehler beim Erstellen des Auftrags: " + e.getMessage()); errorNotification.setDuration(5000); @@ -1265,31 +1284,7 @@ public class AddJobView extends Main { tasksList.setSpacing(true); java.util.function.Consumer addTask = (v) -> { - HorizontalLayout row = new HorizontalLayout(); - 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())); + createTaskRow(); }; // 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 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 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 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); + } + }