feat: Drag-and-Drop-Reihenfolge, Station-Abschluss-Flow und UI-Verbesserungen
Lieferstationen-Dialog (Backend/Vaadin): - Aufgaben per Drag & Drop neu anordnen, inkl. Drag-Handle, komprimierter Kachelansicht während des Drags und horizontaler Einfügelinie als Drop-Target - Drop-Indikator wird unterdrückt, wenn der Drop keine Positionsänderung bewirken würde, und nach dem Abschluss clientseitig zuverlässig aufgeräumt - Drag-Handle, Aufgabentyp-Label und Close-Button auf einheitlicher Position ausgerichtet; Abstände in der Kachel komprimiert Station-Abschluss-Flow (Flutter-App + Backend): - Neuer Button "Station abschließen" unter den Aufgaben; deaktiviert, solange Pflichtaufgaben offen sind, ansonsten aktiv (auch wenn nur optionale Aufgaben existieren) - Hinweisdialog nach Erledigung der letzten Pflichtaufgabe sowie Warnung bei offenen optionalen Aufgaben vor dem Senden - Neue station_completed-Nachricht (jobId, jobNumber, stationOrder, completedAt, hasIncompleteOptionalTasks) wird an den Server gesendet - Backend: Auftrag wird nicht mehr automatisch beim Erledigen der letzten Pflichtaufgabe abgeschlossen, sondern erst beim Empfang der station_completed-Nachricht (neuer Handler in MessageController und MessagingConfig) Aufgabenliste in der App: - Farbcodierung optionaler Aufgaben entfernt; stattdessen vertikal zentrierter "Optional"-Chip am rechten Kartenrand Weitere UI-Überarbeitungen über Login, Jobs, Chats, Settings, Aufgaben-Capture- Screens, Offline-Banner und zugehörige Widgets. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1143,6 +1143,8 @@ vaadin-grid-tree-toggle[expanded] .nav-expand-icon {
|
||||
|
||||
.dialog-task-card {
|
||||
position: relative;
|
||||
padding-top: calc(var(--lumo-space-m) + 5px) !important;
|
||||
gap: calc(var(--lumo-space-m) / 16) !important;
|
||||
transition: transform 0.18s ease, box-shadow 0.18s ease, border-color 0.18s ease;
|
||||
}
|
||||
|
||||
@@ -1152,14 +1154,112 @@ vaadin-grid-tree-toggle[expanded] .nav-expand-icon {
|
||||
border-color: rgba(37, 99, 235, 0.24);
|
||||
}
|
||||
|
||||
.dialog-floating-delete {
|
||||
.dialog-task-card.drag-over-top::before,
|
||||
.dialog-task-card.drag-over-bottom::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 0.65rem;
|
||||
right: 0.65rem;
|
||||
z-index: 10;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 3px;
|
||||
background: var(--lumo-primary-color);
|
||||
border-radius: 2px;
|
||||
z-index: 20;
|
||||
pointer-events: none;
|
||||
box-shadow: 0 0 6px rgba(37, 99, 235, 0.45);
|
||||
}
|
||||
|
||||
.dialog-task-card.drag-over-top::before {
|
||||
top: calc(-0.5 * var(--lumo-space-m) - 1.5px);
|
||||
}
|
||||
|
||||
.dialog-task-card.drag-over-bottom::after {
|
||||
bottom: calc(-0.5 * var(--lumo-space-m) - 1.5px);
|
||||
}
|
||||
|
||||
.dialog-task-card.dragging {
|
||||
opacity: 0.35;
|
||||
transform: scale(0.96);
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
/* Compressed cards during reorder drag */
|
||||
.tasks-reordering .dialog-task-card {
|
||||
padding: 0.4rem 0.8rem !important;
|
||||
transition: padding 0.2s ease, max-height 0.2s ease;
|
||||
}
|
||||
|
||||
.tasks-reordering .dialog-task-card:hover {
|
||||
transform: none;
|
||||
}
|
||||
|
||||
.tasks-reordering .dialog-task-config,
|
||||
.tasks-reordering .dialog-floating-delete,
|
||||
.tasks-reordering .dialog-task-drag-handle {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.tasks-reordering .dialog-task-card vaadin-combo-box {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.dialog-task-summary {
|
||||
display: none;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
font-size: var(--lumo-font-size-s);
|
||||
color: var(--lumo-body-text-color);
|
||||
font-weight: 500;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.dialog-task-summary .task-type-label {
|
||||
color: var(--lumo-primary-color);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.dialog-task-summary .task-desc-label {
|
||||
color: var(--lumo-secondary-text-color);
|
||||
font-weight: 400;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.tasks-reordering .dialog-task-summary {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.dialog-task-card[draggable="true"] {
|
||||
cursor: grab;
|
||||
}
|
||||
|
||||
.dialog-task-drag-handle {
|
||||
cursor: grab;
|
||||
color: var(--lumo-secondary-text-color);
|
||||
padding: 0.2rem;
|
||||
min-width: 1.7rem;
|
||||
min-height: 1.7rem;
|
||||
flex-shrink: 0;
|
||||
position: absolute;
|
||||
top: -5px;
|
||||
left: 15px;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.dialog-task-drag-handle:hover {
|
||||
color: var(--lumo-primary-color);
|
||||
}
|
||||
|
||||
.dialog-floating-delete {
|
||||
padding: 0.2rem;
|
||||
min-width: 1.7rem;
|
||||
min-height: 1.7rem;
|
||||
flex-shrink: 0;
|
||||
position: absolute;
|
||||
top: -5px;
|
||||
right: 15px;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.inline-caption,
|
||||
|
||||
@@ -375,7 +375,9 @@ public class MessageController {
|
||||
String taskType = task.getTaskType() != null ? task.getTaskType().toString() : "Unknown";
|
||||
String completedBy = task.getCompletedBy() != null ? task.getCompletedBy() : "Unknown";
|
||||
emailService.sendTaskCompletionNotification(jobId, taskType, taskIdStr, completedBy);
|
||||
checkAndHandleJobCompletion(jobId, completedBy);
|
||||
// Job completion is no longer auto-triggered by task completion.
|
||||
// It is now driven by explicit station_completed messages from the app
|
||||
// (see handleStationCompleted).
|
||||
} catch (Exception e) {
|
||||
// Ignore email notification errors
|
||||
}
|
||||
@@ -430,6 +432,47 @@ public class MessageController {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle station completion message from app. Client sends to
|
||||
* /server/station_completed with payload:
|
||||
* {
|
||||
* "jobId": "jobnum:ABC123",
|
||||
* "jobNumber": "ABC123",
|
||||
* "stationOrder": 0,
|
||||
* "completedAt": "2026-04-13T12:34:56.789Z",
|
||||
* "hasIncompleteOptionalTasks": false
|
||||
* }
|
||||
*
|
||||
* The job is marked as completed once this message is received and all
|
||||
* mandatory tasks across all stations are completed.
|
||||
*/
|
||||
public void handleStationCompleted(String appUserId, Map<String, Object> payload) {
|
||||
try {
|
||||
String jobNumber = payload.get("jobNumber") != null ? payload.get("jobNumber").toString() : null;
|
||||
if (jobNumber == null || jobNumber.isBlank()) {
|
||||
log.warn("[STATION] station_completed without jobNumber");
|
||||
return;
|
||||
}
|
||||
|
||||
Optional<Job> jobOpt = jobRepository.findByJobNumber(jobNumber);
|
||||
if (jobOpt.isEmpty()) {
|
||||
log.warn("[STATION] Job with jobNumber {} not found", jobNumber);
|
||||
return;
|
||||
}
|
||||
|
||||
ObjectId jobId = jobOpt.get().getId();
|
||||
String completedBy = appUserId != null ? appUserId : "Unknown";
|
||||
|
||||
log.info("[STATION] station_completed received for jobNumber={}, stationOrder={}", jobNumber,
|
||||
payload.get("stationOrder"));
|
||||
|
||||
checkAndHandleJobCompletion(jobId, completedBy);
|
||||
jobUpdateBroadcaster.broadcast(jobId);
|
||||
} catch (Exception e) {
|
||||
log.error("[STATION] Error handling station_completed: {}", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle incoming message from a client via WebSocket. Client sends to
|
||||
* /server/message with payload: { "content": "message payload", "contentType":
|
||||
|
||||
@@ -70,6 +70,14 @@ public class MessagingConfig {
|
||||
});
|
||||
});
|
||||
|
||||
// Station completion handler — marks a job as completed once all mandatory
|
||||
// tasks have been finished and the app confirms the station is done.
|
||||
webSocketService.registerMessageHandler("station_completed", (appUserId, payload) -> {
|
||||
handlePayload(payload, payloadMap -> {
|
||||
messageController.handleStationCompleted(appUserId, payloadMap);
|
||||
});
|
||||
});
|
||||
|
||||
// Chat message handler
|
||||
webSocketService.registerMessageHandler("message", (appUserId, payload) -> {
|
||||
handlePayload(payload, payloadMap -> {
|
||||
|
||||
@@ -5,6 +5,10 @@ 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.dialog.Dialog;
|
||||
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.dnd.EffectAllowed;
|
||||
import com.vaadin.flow.component.html.Div;
|
||||
import com.vaadin.flow.component.html.H3;
|
||||
import com.vaadin.flow.component.html.Span;
|
||||
@@ -203,6 +207,7 @@ public class DeliveryStationDialog extends Dialog {
|
||||
|
||||
private final List<BaseTask> tasksState = new ArrayList<>();
|
||||
private VerticalLayout tasksList;
|
||||
private VerticalLayout draggedTaskContainer;
|
||||
|
||||
private Span addressTabError;
|
||||
private Span tasksTabError;
|
||||
@@ -866,6 +871,15 @@ public class DeliveryStationDialog extends Dialog {
|
||||
taskContainer.setSpacing(true);
|
||||
taskContainer.addClassName("dialog-task-card");
|
||||
|
||||
// Drag handle
|
||||
Button dragHandle = new Button(new Icon(VaadinIcon.GRID_SMALL));
|
||||
dragHandle.addThemeVariants(ButtonVariant.LUMO_TERTIARY);
|
||||
dragHandle.addClassName("dialog-task-drag-handle");
|
||||
|
||||
// Compact summary shown during drag
|
||||
HorizontalLayout summaryRow = createDragSummary("", "");
|
||||
summaryRow.addClassName("dialog-task-summary");
|
||||
|
||||
// Task type selection
|
||||
ComboBox<TaskType> taskTypeCombo = new ComboBox<>(translationHelper.getTranslation("addjob.tasks.tasktype"));
|
||||
taskTypeCombo.setItems(TaskType.values());
|
||||
@@ -877,6 +891,7 @@ public class DeliveryStationDialog extends Dialog {
|
||||
VerticalLayout configContainer = new VerticalLayout();
|
||||
configContainer.setPadding(false);
|
||||
configContainer.setSpacing(true);
|
||||
configContainer.addClassName("dialog-task-config");
|
||||
|
||||
// Red X button positioned in top-right corner
|
||||
Button deleteXButton = new Button(new Icon(VaadinIcon.CLOSE_SMALL));
|
||||
@@ -884,8 +899,14 @@ public class DeliveryStationDialog extends Dialog {
|
||||
deleteXButton.addClassName("dialog-floating-delete");
|
||||
deleteXButton.addClickListener(e -> removeTaskRow(taskContainer));
|
||||
|
||||
taskContainer.add(taskTypeCombo, configContainer);
|
||||
taskContainer.add(deleteXButton);
|
||||
HorizontalLayout headerRow = new HorizontalLayout(dragHandle, summaryRow, taskTypeCombo, deleteXButton);
|
||||
headerRow.setAlignItems(FlexComponent.Alignment.START);
|
||||
headerRow.setWidthFull();
|
||||
headerRow.setFlexGrow(1, taskTypeCombo);
|
||||
|
||||
taskContainer.add(headerRow, configContainer);
|
||||
|
||||
setupDragAndDrop(taskContainer);
|
||||
|
||||
// Create Task and add to state with correct order
|
||||
BaseTask task = new ConfirmationTask("");
|
||||
@@ -896,6 +917,7 @@ public class DeliveryStationDialog extends Dialog {
|
||||
|
||||
taskTypeCombo.setValue(TaskType.CONFIRMATION);
|
||||
updateTaskConfiguration(configContainer, currentTask[0]);
|
||||
updateDragSummary(summaryRow, TaskType.CONFIRMATION, task);
|
||||
|
||||
taskTypeCombo.addValueChangeListener(ev -> {
|
||||
TaskType selectedType = ev.getValue();
|
||||
@@ -940,6 +962,7 @@ public class DeliveryStationDialog extends Dialog {
|
||||
}
|
||||
|
||||
updateTaskConfiguration(configContainer, newTask);
|
||||
updateDragSummary(summaryRow, selectedType, newTask);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -953,6 +976,18 @@ public class DeliveryStationDialog extends Dialog {
|
||||
taskContainer.setSpacing(true);
|
||||
taskContainer.addClassName("dialog-task-card");
|
||||
|
||||
// Drag handle
|
||||
Button dragHandle = new Button(new Icon(VaadinIcon.GRID_SMALL));
|
||||
dragHandle.addThemeVariants(ButtonVariant.LUMO_TERTIARY);
|
||||
dragHandle.addClassName("dialog-task-drag-handle");
|
||||
|
||||
// Compact summary shown during drag
|
||||
TaskType initialTaskType = getTaskTypeFromTask(task);
|
||||
HorizontalLayout summaryRow = createDragSummary(
|
||||
initialTaskType != null ? initialTaskType.getDisplayName() : "",
|
||||
task.getDescription() != null ? task.getDescription() : "");
|
||||
summaryRow.addClassName("dialog-task-summary");
|
||||
|
||||
ComboBox<TaskType> taskTypeCombo = new ComboBox<>(translationHelper.getTranslation("addjob.tasks.tasktype"));
|
||||
taskTypeCombo.setItems(TaskType.values());
|
||||
taskTypeCombo.setItemLabelGenerator(TaskType::getDisplayName);
|
||||
@@ -962,21 +997,27 @@ public class DeliveryStationDialog extends Dialog {
|
||||
VerticalLayout configContainer = new VerticalLayout();
|
||||
configContainer.setPadding(false);
|
||||
configContainer.setSpacing(true);
|
||||
configContainer.addClassName("dialog-task-config");
|
||||
|
||||
Button deleteXButton = new Button(new Icon(VaadinIcon.CLOSE_SMALL));
|
||||
deleteXButton.addThemeVariants(ButtonVariant.LUMO_ERROR, ButtonVariant.LUMO_TERTIARY);
|
||||
deleteXButton.addClassName("dialog-floating-delete");
|
||||
deleteXButton.addClickListener(e -> removeTaskRow(taskContainer));
|
||||
|
||||
taskContainer.add(taskTypeCombo, configContainer);
|
||||
taskContainer.add(deleteXButton);
|
||||
HorizontalLayout headerRow = new HorizontalLayout(dragHandle, summaryRow, taskTypeCombo, deleteXButton);
|
||||
headerRow.setAlignItems(FlexComponent.Alignment.START);
|
||||
headerRow.setWidthFull();
|
||||
headerRow.setFlexGrow(1, taskTypeCombo);
|
||||
|
||||
taskContainer.add(headerRow, configContainer);
|
||||
|
||||
setupDragAndDrop(taskContainer);
|
||||
|
||||
final BaseTask[] currentTask = { task };
|
||||
|
||||
// Set the combo value BEFORE registering the listener
|
||||
TaskType taskType = getTaskTypeFromTask(task);
|
||||
if (taskType != null) {
|
||||
taskTypeCombo.setValue(taskType);
|
||||
if (initialTaskType != null) {
|
||||
taskTypeCombo.setValue(initialTaskType);
|
||||
}
|
||||
|
||||
// Register the listener for user-initiated type changes only
|
||||
@@ -1021,11 +1062,13 @@ public class DeliveryStationDialog extends Dialog {
|
||||
}
|
||||
|
||||
updateTaskConfiguration(configContainer, newTask);
|
||||
updateDragSummary(summaryRow, selectedType, newTask);
|
||||
}
|
||||
});
|
||||
|
||||
// Render the UI with the loaded task
|
||||
updateTaskConfiguration(configContainer, task);
|
||||
updateDragSummary(summaryRow, initialTaskType, task);
|
||||
|
||||
tasksList.add(taskContainer);
|
||||
updateTaskDeleteAvailability();
|
||||
@@ -1042,6 +1085,160 @@ public class DeliveryStationDialog extends Dialog {
|
||||
};
|
||||
}
|
||||
|
||||
private HorizontalLayout createDragSummary(String typeName, String description) {
|
||||
Span typeLabel = new Span(typeName);
|
||||
typeLabel.addClassName("task-type-label");
|
||||
Span descLabel = new Span(description != null && !description.isBlank() ? " — " + description : "");
|
||||
descLabel.addClassName("task-desc-label");
|
||||
HorizontalLayout layout = new HorizontalLayout(typeLabel, descLabel);
|
||||
layout.setSpacing(false);
|
||||
layout.setPadding(false);
|
||||
layout.setAlignItems(FlexComponent.Alignment.CENTER);
|
||||
return layout;
|
||||
}
|
||||
|
||||
private void updateDragSummary(HorizontalLayout summaryRow, TaskType taskType, BaseTask task) {
|
||||
summaryRow.getChildren()
|
||||
.filter(Span.class::isInstance)
|
||||
.map(Span.class::cast)
|
||||
.forEach(span -> {
|
||||
if (span.getClassNames().contains("task-type-label")) {
|
||||
span.setText(taskType != null ? taskType.getDisplayName() : "");
|
||||
} else if (span.getClassNames().contains("task-desc-label")) {
|
||||
String desc = task.getDescription();
|
||||
span.setText(desc != null && !desc.isBlank() ? " — " + desc : "");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void clearAllDropIndicators() {
|
||||
if (tasksList == null) {
|
||||
return;
|
||||
}
|
||||
tasksList.removeClassName("tasks-reordering");
|
||||
tasksList.getChildren()
|
||||
.filter(VerticalLayout.class::isInstance)
|
||||
.map(VerticalLayout.class::cast)
|
||||
.forEach(c -> {
|
||||
c.removeClassName("drag-over-top");
|
||||
c.removeClassName("drag-over-bottom");
|
||||
c.removeClassName("dragging");
|
||||
});
|
||||
}
|
||||
|
||||
private void setupDragAndDrop(VerticalLayout taskContainer) {
|
||||
DragSource<VerticalLayout> dragSource = DragSource.create(taskContainer);
|
||||
dragSource.setEffectAllowed(EffectAllowed.MOVE);
|
||||
dragSource.addDragStartListener(e -> {
|
||||
draggedTaskContainer = taskContainer;
|
||||
taskContainer.addClassName("dragging");
|
||||
if (tasksList != null) {
|
||||
// Update all summaries with latest description values before compressing
|
||||
List<com.vaadin.flow.component.Component> rows = tasksList.getChildren().toList();
|
||||
for (int i = 0; i < rows.size() && i < tasksState.size(); i++) {
|
||||
if (rows.get(i) instanceof VerticalLayout row) {
|
||||
row.getChildren()
|
||||
.filter(HorizontalLayout.class::isInstance)
|
||||
.map(HorizontalLayout.class::cast)
|
||||
.findFirst()
|
||||
.ifPresent(headerRow -> headerRow.getChildren()
|
||||
.filter(HorizontalLayout.class::isInstance)
|
||||
.map(HorizontalLayout.class::cast)
|
||||
.filter(c -> c.getClassNames().contains("dialog-task-summary"))
|
||||
.findFirst()
|
||||
.ifPresent(summary -> {
|
||||
int idx = rows.indexOf(row);
|
||||
if (idx >= 0 && idx < tasksState.size()) {
|
||||
BaseTask t = tasksState.get(idx);
|
||||
TaskType tt = getTaskTypeFromTask(t);
|
||||
updateDragSummary(summary, tt, t);
|
||||
}
|
||||
}));
|
||||
}
|
||||
}
|
||||
tasksList.addClassName("tasks-reordering");
|
||||
}
|
||||
});
|
||||
dragSource.addDragEndListener(e -> {
|
||||
draggedTaskContainer = null;
|
||||
clearAllDropIndicators();
|
||||
});
|
||||
|
||||
DropTarget<VerticalLayout> dropTarget = DropTarget.create(taskContainer);
|
||||
dropTarget.setDropEffect(DropEffect.MOVE);
|
||||
dropTarget.addDropListener(e -> {
|
||||
if (draggedTaskContainer != null && draggedTaskContainer != taskContainer) {
|
||||
moveTaskRow(draggedTaskContainer, taskContainer);
|
||||
}
|
||||
clearAllDropIndicators();
|
||||
});
|
||||
|
||||
// Client-side drag events with position detection and no-op suppression
|
||||
taskContainer.getElement().executeJs(
|
||||
"const el = this;"
|
||||
+ "function clearIndicators() {"
|
||||
+ " var p = el.parentElement;"
|
||||
+ " if (p) p.querySelectorAll('.drag-over-top, .drag-over-bottom').forEach("
|
||||
+ " function(c) { c.classList.remove('drag-over-top', 'drag-over-bottom'); });"
|
||||
+ "}"
|
||||
+ "function getSiblings() {"
|
||||
+ " return Array.from(el.parentElement.children).filter("
|
||||
+ " function(c) { return c.classList.contains('dialog-task-card'); });"
|
||||
+ "}"
|
||||
+ "el.addEventListener('dragstart', function() {"
|
||||
+ " el.parentElement.__draggedEl = el;"
|
||||
+ "});"
|
||||
+ "el.addEventListener('dragend', function() {"
|
||||
+ " el.parentElement.__draggedEl = null;"
|
||||
+ "});"
|
||||
+ "el.addEventListener('dragover', function(e) {"
|
||||
+ " e.preventDefault();"
|
||||
+ " var dragged = el.parentElement.__draggedEl;"
|
||||
+ " if (!dragged || dragged === el) { clearIndicators(); return; }"
|
||||
+ " var cards = getSiblings();"
|
||||
+ " var dragIdx = cards.indexOf(dragged);"
|
||||
+ " var myIdx = cards.indexOf(el);"
|
||||
+ " clearIndicators();"
|
||||
+ " var rect = el.getBoundingClientRect();"
|
||||
+ " var midY = rect.top + rect.height / 2;"
|
||||
+ " if (e.clientY < midY) {"
|
||||
+ " if (myIdx !== dragIdx + 1) el.classList.add('drag-over-top');"
|
||||
+ " } else {"
|
||||
+ " if (myIdx !== dragIdx - 1) el.classList.add('drag-over-bottom');"
|
||||
+ " }"
|
||||
+ "});"
|
||||
+ "el.addEventListener('dragleave', function() {"
|
||||
+ " el.classList.remove('drag-over-top', 'drag-over-bottom');"
|
||||
+ "});"
|
||||
+ "el.addEventListener('drop', function() {"
|
||||
+ " clearIndicators();"
|
||||
+ "});");
|
||||
}
|
||||
|
||||
private void moveTaskRow(VerticalLayout source, VerticalLayout target) {
|
||||
List<com.vaadin.flow.component.Component> rows = tasksList.getChildren().toList();
|
||||
int fromIndex = rows.indexOf(source);
|
||||
int toIndex = rows.indexOf(target);
|
||||
if (fromIndex < 0 || toIndex < 0 || fromIndex == toIndex) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Reorder tasksState
|
||||
BaseTask movedTask = tasksState.remove(fromIndex);
|
||||
tasksState.add(toIndex, movedTask);
|
||||
|
||||
// Reorder UI: remove all, re-add in new order
|
||||
List<com.vaadin.flow.component.Component> rowList = new ArrayList<>(rows);
|
||||
com.vaadin.flow.component.Component movedRow = rowList.remove(fromIndex);
|
||||
rowList.add(toIndex, movedRow);
|
||||
|
||||
tasksList.removeAll();
|
||||
rowList.forEach(tasksList::add);
|
||||
|
||||
// Update taskOrder values
|
||||
reorderTasksAfterDeletion();
|
||||
}
|
||||
|
||||
private void reorderTasksAfterDeletion() {
|
||||
for (int i = 0; i < tasksState.size(); i++) {
|
||||
BaseTask task = tasksState.get(i);
|
||||
@@ -1094,11 +1291,15 @@ public class DeliveryStationDialog extends Dialog {
|
||||
.filter(VerticalLayout.class::isInstance)
|
||||
.map(VerticalLayout.class::cast)
|
||||
.forEach(taskContainer -> taskContainer.getChildren()
|
||||
.filter(Button.class::isInstance)
|
||||
.map(Button.class::cast)
|
||||
.filter(button -> button.getClassNames().contains("dialog-floating-delete"))
|
||||
.filter(HorizontalLayout.class::isInstance)
|
||||
.map(HorizontalLayout.class::cast)
|
||||
.findFirst()
|
||||
.ifPresent(button -> button.setEnabled(deletable)));
|
||||
.ifPresent(headerRow -> headerRow.getChildren()
|
||||
.filter(Button.class::isInstance)
|
||||
.map(Button.class::cast)
|
||||
.filter(button -> button.getClassNames().contains("dialog-floating-delete"))
|
||||
.findFirst()
|
||||
.ifPresent(button -> button.setEnabled(deletable))));
|
||||
}
|
||||
|
||||
private void updateTaskConfiguration(VerticalLayout configContainer, BaseTask task) {
|
||||
|
||||
Reference in New Issue
Block a user