mirror of
https://github.com/itflow-org/itflow
synced 2026-03-05 13:24:51 +00:00
Change the button handlebar class
This commit is contained in:
@@ -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;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
@@ -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>
|
||||||
|
|||||||
Reference in New Issue
Block a user