Change the button handlebar class

This commit is contained in:
johnnyq
2025-04-13 15:11:37 -04:00
parent 65e107d154
commit 846947ff49
3 changed files with 139 additions and 161 deletions

View File

@@ -1,83 +1,41 @@
/* General Popover Styling */
.popover { .popover {
max-width: 600px; max-width: 600px;
} }
/* Kanban Board Container */
#kanban-board { #kanban-board {
display: flex; display: flex;
overflow-x: auto;
box-sizing: border-box; box-sizing: border-box;
overflow-x: auto;
min-width: 400px; min-width: 400px;
height: calc(100vh - 210px); height: calc(100vh - 210px);
} }
/* Kanban Column */
.kanban-column { .kanban-column {
flex: 1; flex: 1; /* Allows columns to grow equally */
margin: 0 10px; /* Space between columns */
min-width: 300px; min-width: 300px;
max-width: 300px; max-width: 300px;
margin: 0 10px;
background: #f4f4f4; background: #f4f4f4;
border: 1px solid #ccc;
border-radius: 4px;
padding: 10px; padding: 10px;
border: 1px solid #ccc;
min-height: calc(100vh - 230px); min-height: calc(100vh - 230px);
max-height: calc(100vh - 230px); max-height: calc(100vh - 230px);
box-sizing: border-box; box-sizing: border-box;
display: flex;
flex-direction: column;
} }
/* Column Inner Scrollable Task Area */ .kanban-column div {
.kanban-status { max-height: calc(100vh - 280px); /* Set your desired max height */
flex: 1; overflow-y: auto; /* Adds a scrollbar when content exceeds max height */
overflow-y: auto;
min-height: 60px;
position: relative;
padding: 5px;
background-color: #f9f9f9;
border-radius: 4px;
} }
/* Individual Task Cards */
.task { .task {
background: #fff; background: #fff;
margin: 5px 0; margin: 5px 0;
padding: 10px; padding: 10px;
border: 1px solid #ddd; border: 1px solid #ddd;
border-radius: 4px; user-select: none; /* Prevent text selection */
cursor: grab;
user-select: none;
} }
/* Grabbing Cursor State */
.task:active {
cursor: grabbing;
}
/* Drag Handle (shown on mobile or with class targeting) */
.drag-handle-class { .drag-handle-class {
float: right;
touch-action: none; touch-action: none;
cursor: grab; float: right;
}
/* 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;
} }

View File

@@ -1,126 +1,146 @@
$(document).ready(function () { $(document).ready(function() {
console.log('CONFIG_TICKET_MOVING_COLUMNS:', CONFIG_TICKET_MOVING_COLUMNS); console.log('CONFIG_TICKET_MOVING_COLUMNS: ' + CONFIG_TICKET_MOVING_COLUMNS);
console.log('CONFIG_TICKET_ORDERING:', CONFIG_TICKET_ORDERING); console.log('CONFIG_TICKET_ORDERING: ' + CONFIG_TICKET_ORDERING);
// ------------------------------- // Function to detect touch devices
// Drag: Kanban Columns (Statuses) function isTouchDevice() {
// ------------------------------- return 'ontouchstart' in window || navigator.maxTouchPoints;
new Sortable(document.querySelector('#kanban-board'), { }
animation: 150,
handle: '.panel-title', // Initialize Dragula for the Kanban board
draggable: '.kanban-column', let boardDrake = dragula([
onEnd: function () { document.querySelector('#kanban-board')
const columnPositions = Array.from(document.querySelectorAll('#kanban-board .kanban-column')).map((col, index) => ({ ], {
status_id: $(col).data('status-id'), 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,
status_kanban: index status_kanban: index
})); });
});
if (CONFIG_TICKET_MOVING_COLUMNS === 1) { // Send AJAX request to update all column positions
$.post('ajax.php', { $.ajax({
update_kanban_status_position: true, url: 'ajax.php',
positions: columnPositions type: 'POST',
}).done(() => { data: {
console.log('Ticket status kanban orders updated.'); update_kanban_status_position: true,
}).fail((xhr) => { positions: columnPositions
console.error('Error updating status order:', xhr.responseText); },
}); 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 (isTouchDevice()) {
// Drag: Tasks within Columns const moveList = document.querySelectorAll('.task');
// ------------------------------- moveList.forEach(task => {
document.querySelectorAll('.kanban-status').forEach(statusCol => { task.querySelector('.drag-handle-class').style.display = 'inline';
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 drake.on('drag', function(el) {
if (CONFIG_TICKET_ORDERING === 0 && evt.from === evt.to) { el.style.cursor = 'grabbing';
evt.from.insertBefore(movedEl, evt.from.children[evt.oldIndex]); });
showPlaceholders();
return;
}
const columnId = $(target).data('status-id'); 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'));
const positions = Array.from(target.querySelectorAll('.task')).map((card, index) => { if (CONFIG_TICKET_ORDERING === 0 && source == target) {
const ticketId = $(card).data('ticket-id'); drake.cancel(true); // Move the card back to its original position
const oldStatus = ticketId === $(movedEl).data('ticket-id') return;
? $(movedEl).data('ticket-status-id') }
: false;
$(card).data('ticket-status-id', columnId); // update DOM // Get all cards in the target column and their positions
let cards = $(target).children('.task');
let positions = [];
return { //id of current status / column
ticket_id: ticketId, let columnId = $(target).data('status-id');
ticket_order: index,
ticket_oldStatus: oldStatus,
ticket_status: columnId
};
});
$.post('ajax.php', { let movedTicketId = $(el).data('ticket-id');
update_kanban_ticket: true, let movedTicketStatusId = $(el).data('ticket-status-id');
positions: positions
}).done(() => {
console.log('Updated kanban ticket positions.');
}).fail((xhr) => {
console.error('Error updating ticket positions:', xhr.responseText);
});
// Refresh placeholders after update cards.each(function(index, card) {
showPlaceholders(); 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);
} }
}); });
}); });
// -------------------------------
// 📱 Touch Support: Show drag handle on mobile
// -------------------------------
if (isTouchDevice()) {
$('.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);
}
});
}
function hidePlaceholders() {
document.querySelectorAll('.empty-placeholder').forEach(el => el.remove());
}
// Run once on load
showPlaceholders();
// -------------------------------
// Utility: Detect touch device
// -------------------------------
function isTouchDevice() {
return 'ontouchstart' in window || navigator.maxTouchPoints > 0;
}
}); });

View File

@@ -105,7 +105,7 @@ $kanban = array_values($statuses);
<span class='badge badge-secondary'> <span class='badge badge-secondary'>
<?php echo $item['category_name']; ?> <?php echo $item['category_name']; ?>
</span> </span>
<div class='btn btn-secondary drag-handle-class' style="display: none;"> <div class='btn btn-light drag-handle-class' style="display: none;">
<i class="drag-handle-class fas fa-bars"></i> <i class="drag-handle-class fas fa-bars"></i>
</div> </div>
<br> <br>