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="">
|
||||
<template x-for="t in tareasFiltradas(col.id)" :key="t.ID">
|
||||
<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 -->
|
||||
<div class="h-1 rounded-full mb-2 -mt-1 -mx-1"
|
||||
@@ -470,15 +470,30 @@
|
||||
onEnd: (evt) => {
|
||||
const id = parseInt(evt.item.dataset.id);
|
||||
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);
|
||||
if (t && t.estado !== nuevoEstado) {
|
||||
t.estado = nuevoEstado;
|
||||
fetch(`/app/tarea/${id}/estado`, {
|
||||
method: 'PUT',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ estado: nuevoEstado }),
|
||||
});
|
||||
}
|
||||
if (!t) return;
|
||||
t.estado = nuevoEstado; // dispara el re-render de Alpine
|
||||
|
||||
// Re-iniciar Sortable después de que Alpine actualice el DOM
|
||||
this.$nextTick(() => this.iniciarSortable());
|
||||
|
||||
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);
|
||||
|
||||
Reference in New Issue
Block a user