fix: drag-and-drop en tablero de tareas (SortableJS + Alpine x-for)
SortableJS mueve el nodo DOM físicamente antes de que Alpine re-renderice, causando conflicto: la tarjeta desaparecía o volvía a su columna original. Se revierte el movimiento DOM de SortableJS en onEnd y se deja que Alpine re-renderice solo al actualizar t.estado; luego se re-inicia Sortable. Se agrega cursor grab/grabbing como indicador visual de arrastre. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 4.6
parent
65c988dc70
commit
9305cd6eb2
@@ -69,7 +69,7 @@
|
|||||||
data-estado="">
|
data-estado="">
|
||||||
<template x-for="t in tareasFiltradas(col.id)" :key="t.ID">
|
<template x-for="t in tareasFiltradas(col.id)" :key="t.ID">
|
||||||
<div :data-id="t.ID" @click="abrirDetalle(t)"
|
<div :data-id="t.ID" @click="abrirDetalle(t)"
|
||||||
class="bg-white rounded-xl border border-slate-200 shadow-sm p-3 cursor-pointer hover:shadow-md transition-all group">
|
class="bg-white rounded-xl border border-slate-200 shadow-sm p-3 cursor-grab active:cursor-grabbing hover:shadow-md transition-all group">
|
||||||
|
|
||||||
<!-- Prioridad strip -->
|
<!-- Prioridad strip -->
|
||||||
<div class="h-1 rounded-full mb-2 -mt-1 -mx-1"
|
<div class="h-1 rounded-full mb-2 -mt-1 -mx-1"
|
||||||
@@ -470,15 +470,30 @@
|
|||||||
onEnd: (evt) => {
|
onEnd: (evt) => {
|
||||||
const id = parseInt(evt.item.dataset.id);
|
const id = parseInt(evt.item.dataset.id);
|
||||||
const nuevoEstado = evt.to.id.replace('col-', '');
|
const nuevoEstado = evt.to.id.replace('col-', '');
|
||||||
|
const estadoAnterior = evt.from.id.replace('col-', '');
|
||||||
|
if (nuevoEstado === estadoAnterior) return;
|
||||||
|
|
||||||
|
// Revertir el movimiento DOM de SortableJS para que Alpine
|
||||||
|
// pueda re-renderizar el x-for sin conflicto de nodos.
|
||||||
|
evt.from.insertBefore(evt.item, evt.from.children[evt.oldIndex] || null);
|
||||||
|
|
||||||
const t = this.tareas.find(t => t.ID === id);
|
const t = this.tareas.find(t => t.ID === id);
|
||||||
if (t && t.estado !== nuevoEstado) {
|
if (!t) return;
|
||||||
t.estado = nuevoEstado;
|
t.estado = nuevoEstado; // dispara el re-render de Alpine
|
||||||
fetch(`/app/tarea/${id}/estado`, {
|
|
||||||
method: 'PUT',
|
// Re-iniciar Sortable después de que Alpine actualice el DOM
|
||||||
headers: { 'Content-Type': 'application/json' },
|
this.$nextTick(() => this.iniciarSortable());
|
||||||
body: JSON.stringify({ estado: nuevoEstado }),
|
|
||||||
});
|
fetch(`/app/tarea/${id}/estado`, {
|
||||||
}
|
method: 'PUT',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify({ estado: nuevoEstado }),
|
||||||
|
}).catch(() => {
|
||||||
|
// Revertir estado local si la API falla
|
||||||
|
const tRev = this.tareas.find(t => t.ID === id);
|
||||||
|
if (tRev) tRev.estado = estadoAnterior;
|
||||||
|
this.$nextTick(() => this.iniciarSortable());
|
||||||
|
});
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
this.sortables.push(s);
|
this.sortables.push(s);
|
||||||
|
|||||||
Reference in New Issue
Block a user