/** * Project Board JavaScript * Handles drag and drop reordering for kanban board */ (function() { 'use strict'; // Initialize drag and drop when DOM is ready document.addEventListener('DOMContentLoaded', initDragAndDrop); // Re-initialize after HTMX swaps document.body.addEventListener('htmx:afterSwap', function(evt) { initDragAndDrop(); }); function initDragAndDrop() { const taskLists = document.querySelectorAll('[id^="phase-tasks-"]'); taskLists.forEach(list => { const tasks = list.querySelectorAll('.task-item'); tasks.forEach(task => { task.draggable = true; task.addEventListener('dragstart', function(e) { e.dataTransfer.effectAllowed = 'move'; e.dataTransfer.setData('text/plain', task.dataset.taskId); task.classList.add('dragging'); }); task.addEventListener('dragend', function() { task.classList.remove('dragging'); document.querySelectorAll('.task-item').forEach(t => t.classList.remove('drag-over')); }); task.addEventListener('dragover', function(e) { e.preventDefault(); e.dataTransfer.dropEffect = 'move'; const draggingTask = document.querySelector('.task-item.dragging'); if (draggingTask && draggingTask !== task) { task.classList.add('drag-over'); } }); task.addEventListener('dragleave', function() { task.classList.remove('drag-over'); }); task.addEventListener('drop', function(e) { e.preventDefault(); task.classList.remove('drag-over'); const draggedId = e.dataTransfer.getData('text/plain'); const draggedTask = document.querySelector(`[data-task-id="${draggedId}"]`); if (draggedTask && draggedTask !== task) { const list = task.parentElement; const rect = task.getBoundingClientRect(); const midpoint = rect.top + rect.height / 2; if (e.clientY < midpoint) { list.insertBefore(draggedTask, task); } else { list.insertBefore(draggedTask, task.nextSibling); } // Update order via HTMX updateTaskOrder(list); } }); }); // Allow dropping at the end of the list list.addEventListener('dragover', function(e) { e.preventDefault(); const draggingTask = document.querySelector('.task-item.dragging'); if (draggingTask) { list.appendChild(draggingTask); } }); }); } function updateTaskOrder(list) { const tasks = list.querySelectorAll('.task-item'); const taskIds = Array.from(tasks).map(t => t.dataset.taskId); // Get phase info from list ID const listId = list.id; // e.g., "phase-tasks-pdca-plan" const parts = listId.split('-'); const phaseType = parts[2]; const phaseKey = parts[3]; // Update each task's order taskIds.forEach((taskId, index) => { const csrfToken = document.querySelector('[name=csrfmiddlewaretoken]')?.value; fetch(`/projects/${getProjectId()}/htmx/tasks/${taskId}/reorder/`, { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded', 'X-CSRFToken': csrfToken || '', }, body: `order=${index}` }).catch(err => console.error('Failed to update task order:', err)); }); } function getProjectId() { // Extract project ID from URL const match = window.location.pathname.match(/\/projects\/([a-f0-9-]+)\//); return match ? match[1] : ''; } })();