Răsfoiți Sursa

fix(kanban): fix DnD — use reactive columnMap instead of filtered array

vuedraggable requires a direct reactive array reference per list.
Passing columnCards(phase) returned a new array each render, breaking
cross-column drag. Each phase now has its own array in columnMap.
ebagabee 2 săptămâni în urmă
părinte
comite
b8d8c92bbb
1 a modificat fișierele cu 39 adăugiri și 39 ștergeri
  1. 39 39
      src/pages/kanban/KanbanPage.vue

+ 39 - 39
src/pages/kanban/KanbanPage.vue

@@ -40,21 +40,22 @@
           <q-badge
             color="white"
             :text-color="column.badgeTextColor"
-            :label="columnCards(column.phase).length"
+            :label="columnMap[column.phase].length"
             style="font-size: 11px"
           />
         </div>
 
         <!-- Draggable card list -->
         <draggable
-          :list="columnCards(column.phase)"
+          :list="columnMap[column.phase]"
+          :data-phase="column.phase"
           group="kanban"
           item-key="id"
           :animation="180"
           ghost-class="drag-ghost"
           drag-class="drag-active"
-          style="display: flex; flex-direction: column; gap: 8px; min-height: 8px; flex: 1"
-          @end="onDragEnd($event, column.phase)"
+          style="display: flex; flex-direction: column; gap: 8px; min-height: 48px; flex: 1"
+          @end="onDragEnd"
         >
           <template #item="{ element }">
             <KanbanCard
@@ -80,7 +81,7 @@
 </template>
 
 <script setup>
-import { ref, defineAsyncComponent, onMounted } from "vue";
+import { ref, reactive, defineAsyncComponent, onMounted } from "vue";
 import { useQuasar } from "quasar";
 import draggable from "vuedraggable";
 
@@ -93,9 +94,7 @@ const AddEditKanbanDialog = defineAsyncComponent(
 );
 
 const $q = useQuasar();
-
 const loading = ref(false);
-const cards = ref([]);
 
 const columns = [
   { phase: "a_fazer",            label: "A Fazer",            color: "#757575", badgeTextColor: "grey-9"   },
@@ -105,50 +104,50 @@ const columns = [
   { phase: "demandas_especiais", label: "Demandas Especiais", color: "#F9A825", badgeTextColor: "yellow-9" },
 ];
 
-/** Returns a reactive slice of cards for the given phase, in order. */
-const columnCards = (phase) =>
-  cards.value.filter((c) => c.phase === phase);
+// Each phase has its own reactive array — required for vuedraggable cross-list DnD
+const columnMap = reactive({
+  a_fazer: [],
+  em_progresso: [],
+  em_revisao: [],
+  concluido: [],
+  demandas_especiais: [],
+});
 
 const loadCards = async () => {
   loading.value = true;
   try {
-    cards.value = await getKanbans();
+    const data = await getKanbans();
+
+    // Reset all columns before filling
+    Object.keys(columnMap).forEach((k) => (columnMap[k] = []));
+
+    data.forEach((card) => {
+      if (columnMap[card.phase]) {
+        columnMap[card.phase].push(card);
+      }
+    });
   } finally {
     loading.value = false;
   }
 };
 
 /**
- * Called when any drag ends.
- * Rebuilds orders for every column that was affected and persists to the API.
+ * Called once when drag ends.
+ * By this point vuedraggable has already moved the item between the two arrays.
+ * We read source/destination phases from the DOM data-phase attribute and persist.
  */
-const onDragEnd = async (evt, targetPhase) => {
-  // Identify the card that was moved
-  const movedCard = evt.item?.__draggable_context?.element;
-  if (!movedCard) return;
-
-  // Update the in-memory phase for the moved card
-  movedCard.phase = targetPhase;
-
-  // Collect all cards from source and destination columns (may be the same)
-  const affectedPhases = new Set([targetPhase]);
-  if (evt.from !== evt.to) {
-    // The original phase is stored on the element before the move;
-    // we resolve it from the source list id (set as data-phase attr) or
-    // simply by iterating all columns — safest approach.
-    columns.forEach((col) => {
-      const colCards = columnCards(col.phase);
-      if (colCards.some((c) => c.id === movedCard.id)) {
-        affectedPhases.add(col.phase);
-      }
-    });
-  }
+const onDragEnd = async (evt) => {
+  const sourcePhase = evt.from.dataset.phase;
+  const targetPhase = evt.to.dataset.phase;
+
+  // Collect phases to update (may be the same if reordering in-column)
+  const phasesToUpdate = new Set([sourcePhase, targetPhase].filter(Boolean));
 
-  // Build the reorder payload for each affected phase
   const items = [];
-  affectedPhases.forEach((phase) => {
-    columnCards(phase).forEach((card, idx) => {
-      card.phase = phase; // ensure phase is in sync
+  phasesToUpdate.forEach((phase) => {
+    columnMap[phase].forEach((card, idx) => {
+      // Sync the card's phase in memory too
+      card.phase = phase;
       items.push({ id: card.id, phase, order: idx });
     });
   });
@@ -156,7 +155,7 @@ const onDragEnd = async (evt, targetPhase) => {
   try {
     await reorderKanbans(items);
   } catch {
-    // On failure reload from server to restore consistent state
+    // On failure restore from server
     await loadCards();
   }
 };
@@ -181,6 +180,7 @@ onMounted(loadCards);
 :deep(.drag-ghost) {
   opacity: 0.4;
   border: 2px dashed #aaa;
+  border-radius: 10px;
 }
 
 :deep(.drag-active) {