From 691aebce913599230cf8ab96947c58bd04cc28a7 Mon Sep 17 00:00:00 2001 From: johnnyq Date: Sun, 13 Apr 2025 15:15:18 -0400 Subject: [PATCH] Revert Fix --- css/tickets_kanban.css | 64 +++++++++-- js/tickets_kanban.js | 246 +++++++++++++++++++---------------------- 2 files changed, 166 insertions(+), 144 deletions(-) diff --git a/css/tickets_kanban.css b/css/tickets_kanban.css index a03d6773..140c0f9e 100644 --- a/css/tickets_kanban.css +++ b/css/tickets_kanban.css @@ -1,41 +1,83 @@ +/* General Popover Styling */ .popover { max-width: 600px; } + +/* Kanban Board Container */ #kanban-board { display: flex; - box-sizing: border-box; overflow-x: auto; + box-sizing: border-box; min-width: 400px; height: calc(100vh - 210px); } +/* Kanban Column */ .kanban-column { - flex: 1; /* Allows columns to grow equally */ - margin: 0 10px; /* Space between columns */ + flex: 1; min-width: 300px; max-width: 300px; + margin: 0 10px; background: #f4f4f4; - padding: 10px; border: 1px solid #ccc; + border-radius: 4px; + padding: 10px; min-height: calc(100vh - 230px); max-height: calc(100vh - 230px); box-sizing: border-box; + display: flex; + flex-direction: column; } -.kanban-column div { - max-height: calc(100vh - 280px); /* Set your desired max height */ - overflow-y: auto; /* Adds a scrollbar when content exceeds max height */ +/* Column Inner Scrollable Task Area */ +.kanban-status { + flex: 1; + overflow-y: auto; + min-height: 60px; + position: relative; + padding: 5px; + background-color: #f9f9f9; + border-radius: 4px; } +/* Individual Task Cards */ .task { background: #fff; margin: 5px 0; padding: 10px; border: 1px solid #ddd; - user-select: none; /* Prevent text selection */ + border-radius: 4px; + cursor: grab; + user-select: none; } -.drag-handle-class { - touch-action: none; - float: right; +/* Grabbing Cursor State */ +.task:active { + cursor: grabbing; +} + +/* Drag Handle (shown on mobile or with class targeting) */ +.drag-handle-class { + float: right; + touch-action: none; + cursor: grab; +} + +/* Placeholder shown in empty columns */ +.empty-placeholder { + border: 2px dashed #ccc; + background-color: #fcfcfc; + color: #999; + font-style: italic; + padding: 12px; + margin: 10px 0; + text-align: center; + border-radius: 4px; + pointer-events: none; +} + +/* Sortable drop zone feedback (optional visual cue) */ +.kanban-status.sortable-over { + background-color: #eaf6ff; + transition: background-color 0.2s ease; } diff --git a/js/tickets_kanban.js b/js/tickets_kanban.js index 419a0ee3..c614d057 100644 --- a/js/tickets_kanban.js +++ b/js/tickets_kanban.js @@ -1,146 +1,126 @@ -$(document).ready(function() { - console.log('CONFIG_TICKET_MOVING_COLUMNS: ' + CONFIG_TICKET_MOVING_COLUMNS); - console.log('CONFIG_TICKET_ORDERING: ' + CONFIG_TICKET_ORDERING); +$(document).ready(function () { + console.log('CONFIG_TICKET_MOVING_COLUMNS:', CONFIG_TICKET_MOVING_COLUMNS); + console.log('CONFIG_TICKET_ORDERING:', CONFIG_TICKET_ORDERING); - // Function to detect touch devices - function isTouchDevice() { - return 'ontouchstart' in window || navigator.maxTouchPoints; - } - - // Initialize Dragula for the Kanban board - let boardDrake = dragula([ - document.querySelector('#kanban-board') - ], { - moves: function(el, container, handle) { - return handle.classList.contains('panel-title'); - }, - accepts: function(el, target, source, sibling) { - return CONFIG_TICKET_MOVING_COLUMNS === 1; - } - }); - - // Log the event of moving the column panel-title - boardDrake.on('drag', function(el) { - //console.log('Dragging column:', el.querySelector('.panel-title').innerText); - }); - - boardDrake.on('drop', function(el, target, source, sibling) { - //console.log('Dropped column:', el.querySelector('.panel-title').innerText); - - // Get all columns and their positions - let columns = document.querySelectorAll('#kanban-board .kanban-column'); - let columnPositions = []; - - columns.forEach(function(column, index) { - let statusId = $(column).data('status-id'); // Assuming you have a data attribute for status ID - columnPositions.push({ - status_id: statusId, + // ------------------------------- + // Drag: Kanban Columns (Statuses) + // ------------------------------- + new Sortable(document.querySelector('#kanban-board'), { + animation: 150, + handle: '.panel-title', + draggable: '.kanban-column', + onEnd: function () { + const columnPositions = Array.from(document.querySelectorAll('#kanban-board .kanban-column')).map((col, index) => ({ + status_id: $(col).data('status-id'), status_kanban: index - }); - }); + })); - // Send AJAX request to update all column positions - $.ajax({ - url: 'ajax.php', - type: 'POST', - data: { - update_kanban_status_position: true, - positions: columnPositions - }, - success: function(response) { - console.log('Ticket status kanban orders updated successfully.'); - // Optionally, you can refresh the page or update the UI here - }, - error: function(xhr, status, error) { - console.error('Error updating ticket status kanban orders:', error); - } - }); - }); - - // Initialize Dragula for the Kanban Cards - let drake = dragula([ - ...document.querySelectorAll('#status') - ], { - moves: function(el, container, handle) { - if (isTouchDevice()) { - return handle.classList.contains('drag-handle-class'); - } else { - return true; // Allow dragging on the entire task element for desktop + if (CONFIG_TICKET_MOVING_COLUMNS === 1) { + $.post('ajax.php', { + update_kanban_status_position: true, + positions: columnPositions + }).done(() => { + console.log('Ticket status kanban orders updated.'); + }).fail((xhr) => { + console.error('Error updating status order:', xhr.responseText); + }); } } }); + // ------------------------------- + // Drag: Tasks within Columns + // ------------------------------- + document.querySelectorAll('.kanban-status').forEach(statusCol => { + new Sortable(statusCol, { + group: 'tickets', + animation: 150, + handle: isTouchDevice() ? '.drag-handle-class' : undefined, + onStart: () => hidePlaceholders(), + onEnd: function (evt) { + const target = evt.to; + const movedEl = evt.item; + + // Disallow reordering in same column if config says so + if (CONFIG_TICKET_ORDERING === 0 && evt.from === evt.to) { + evt.from.insertBefore(movedEl, evt.from.children[evt.oldIndex]); + showPlaceholders(); + return; + } + + const columnId = $(target).data('status-id'); + + const positions = Array.from(target.querySelectorAll('.task')).map((card, index) => { + const ticketId = $(card).data('ticket-id'); + const oldStatus = ticketId === $(movedEl).data('ticket-id') + ? $(movedEl).data('ticket-status-id') + : false; + + $(card).data('ticket-status-id', columnId); // update DOM + + return { + ticket_id: ticketId, + ticket_order: index, + ticket_oldStatus: oldStatus, + ticket_status: columnId + }; + }); + + $.post('ajax.php', { + update_kanban_ticket: true, + positions: positions + }).done(() => { + console.log('Updated kanban ticket positions.'); + }).fail((xhr) => { + console.error('Error updating ticket positions:', xhr.responseText); + }); + + // Refresh placeholders after update + showPlaceholders(); + } + }); + }); + + // ------------------------------- + // 📱 Touch Support: Show drag handle on mobile + // ------------------------------- if (isTouchDevice()) { - const moveList = document.querySelectorAll('.task'); - moveList.forEach(task => { - task.querySelector('.drag-handle-class').style.display = 'inline'; + $('.drag-handle-class').css('display', 'inline'); + } + + // ------------------------------- + // Placeholder Management + // ------------------------------- + function showPlaceholders() { + document.querySelectorAll('.kanban-status').forEach(status => { + const placeholderClass = 'empty-placeholder'; + + // Remove existing placeholder + const existing = status.querySelector(`.${placeholderClass}`); + if (existing) existing.remove(); + + // Only show if there are no tasks + if (status.querySelectorAll('.task').length === 0) { + const placeholder = document.createElement('div'); + placeholder.className = `${placeholderClass} text-muted text-center p-2`; + placeholder.innerText = 'Drop ticket here'; + placeholder.style.pointerEvents = 'none'; + status.appendChild(placeholder); + } }); } - drake.on('drag', function(el) { - el.style.cursor = 'grabbing'; - }); - - drake.on('dragend', function(el) { - el.style.cursor = 'grab'; - }); - // Add event listener for the drop event - drake.on('drop', function (el, target, source, sibling) { - // Log the target ID to the console - //console.log('Dropped into:', target.getAttribute('data-column-name')); + function hidePlaceholders() { + document.querySelectorAll('.empty-placeholder').forEach(el => el.remove()); + } - if (CONFIG_TICKET_ORDERING === 0 && source == target) { - drake.cancel(true); // Move the card back to its original position - return; - } - - // Get all cards in the target column and their positions - let cards = $(target).children('.task'); - let positions = []; + // Run once on load + showPlaceholders(); - //id of current status / column - let columnId = $(target).data('status-id'); - - let movedTicketId = $(el).data('ticket-id'); - let movedTicketStatusId = $(el).data('ticket-status-id'); - - cards.each(function(index, card) { - let ticketId = $(card).data('ticket-id'); - let statusId = $(card).data('ticket-status-id'); - - let oldStatus = false; - if (ticketId == movedTicketId) { - oldStatus = movedTicketStatusId; - } - - //update the status id of the card if needed - if (statusId != columnId) { - $(card).data('ticket-status-id', columnId); - statusId = columnId; - } - positions.push({ - ticket_id: ticketId, - ticket_order: index, - ticket_oldStatus: oldStatus, - ticket_status: statusId ?? null// Get the new status ID from the target column - }); - }); - - //console.log(positions); - // Send AJAX request to update all ticket kanban orders and statuses - $.ajax({ - url: 'ajax.php', - type: 'POST', - data: { - update_kanban_ticket: true, - positions: positions - }, - success: function(response) { - //console.log('Ticket kanban orders and statuses updated successfully.'); - }, - error: function(xhr, status, error) { - console.error('Error updating ticket kanban orders and statuses:', error); - } - }); - }); -}); \ No newline at end of file + // ------------------------------- + // Utility: Detect touch device + // ------------------------------- + function isTouchDevice() { + return 'ontouchstart' in window || navigator.maxTouchPoints > 0; + } +});