feat: arrastrar con el mouse para reordenar actividades y tareas
Hook reutilizable useDragReorder (drag & drop nativo HTML5, sin libreria nueva) usado en dos lugares: ListView (opt-in via onReorder, deshabilitado mientras haya un ordenamiento por columna activo) para el listado de actividades de un proyecto, y el listado de tareas de una actividad. Orden persistido en sort_order (activities y tasks, migracion 012, posiciones siempre 1..N). Fix real encontrado durante las pruebas: sincronizar el estado interno del hook por la REFERENCIA del arreglo de entrada entraba en loop infinito de renders cuando el llamador pasa "algo || []" (un arreglo nuevo en cada render mientras los datos cargan). Corregido para sincronizar por el contenido (ids), no por referencia. Probado en dev con curl (persistencia del orden) y Playwright (arrastrar filas en el navegador, orden visible y guardado correctamente, sin errores de consola tras el fix). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,11 +1,15 @@
|
||||
import { useMemo, useState } from 'react';
|
||||
import { MoreVertical } from 'lucide-react';
|
||||
import { MoreVertical, GripVertical } from 'lucide-react';
|
||||
import { useUi } from '../context/ui-context.jsx';
|
||||
import { useDragReorder } from '../hooks/useDragReorder.js';
|
||||
import { Flyout, FlyoutItem } from './Flyout.jsx';
|
||||
|
||||
// ListView/DataGrid: columnas ordenables, buscador ligero sobre el haystack completo, clic en
|
||||
// fila abre detalle, boton "..." abre el menu de acciones (equivalente al clic derecho).
|
||||
export function ListView({ columns, rows, searchValue, getSearchHaystack, onRowClick, rowActions, getRowKey, initialSort }) {
|
||||
// `onReorder(idsEnNuevoOrden)` es opcional: si se pasa, habilita arrastrar filas con el mouse para
|
||||
// definir un orden manual (columna con icono de agarre a la izquierda) — solo mientras no haya un
|
||||
// ordenamiento por columna activo, para no mezclar los dos mecanismos de orden a la vez.
|
||||
export function ListView({ columns, rows, searchValue, getSearchHaystack, onRowClick, rowActions, getRowKey, initialSort, onReorder }) {
|
||||
const { t } = useUi();
|
||||
const [sort, setSort] = useState(initialSort || { key: null, dir: 1 });
|
||||
|
||||
@@ -26,6 +30,10 @@ export function ListView({ columns, rows, searchValue, getSearchHaystack, onRowC
|
||||
});
|
||||
}, [filtered, sort, columns]);
|
||||
|
||||
const dragEnabled = !!onReorder && !sort.key && !searchValue;
|
||||
const { order, getDragHandlers, isDragging } = useDragReorder(sorted, (ids) => onReorder?.(ids), getRowKey);
|
||||
const displayRows = dragEnabled ? order : sorted;
|
||||
|
||||
function toggleSort(key) {
|
||||
setSort((prev) => (prev.key === key ? { key, dir: prev.dir * -1 } : { key, dir: 1 }));
|
||||
}
|
||||
@@ -35,6 +43,7 @@ export function ListView({ columns, rows, searchValue, getSearchHaystack, onRowC
|
||||
<table className="list-table">
|
||||
<thead>
|
||||
<tr>
|
||||
{dragEnabled && <th style={{ width: 28 }} />}
|
||||
{columns.map((col) => (
|
||||
<th key={col.key} onClick={() => col.sortable !== false && toggleSort(col.key)}>
|
||||
{col.label}{sort.key === col.key ? (sort.dir === 1 ? ' ▲' : ' ▼') : ''}
|
||||
@@ -44,8 +53,18 @@ export function ListView({ columns, rows, searchValue, getSearchHaystack, onRowC
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{sorted.map((row) => (
|
||||
<tr key={getRowKey(row)} onClick={() => onRowClick?.(row)}>
|
||||
{displayRows.map((row) => (
|
||||
<tr
|
||||
key={getRowKey(row)}
|
||||
onClick={() => onRowClick?.(row)}
|
||||
className={dragEnabled && isDragging(getRowKey(row)) ? 'dragging' : ''}
|
||||
{...(dragEnabled ? getDragHandlers(row) : {})}
|
||||
>
|
||||
{dragEnabled && (
|
||||
<td className="drag-handle-cell" onClick={(e) => e.stopPropagation()}>
|
||||
<GripVertical size={15} />
|
||||
</td>
|
||||
)}
|
||||
{columns.map((col) => (
|
||||
<td key={col.key}>{col.render ? col.render(row) : row[col.key]}</td>
|
||||
))}
|
||||
@@ -66,7 +85,7 @@ export function ListView({ columns, rows, searchValue, getSearchHaystack, onRowC
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
{sorted.length === 0 && <div className="list-empty">{t('common_empty')}</div>}
|
||||
{displayRows.length === 0 && <div className="list-empty">{t('common_empty')}</div>}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user