diff --git a/admin_ticket_template_details.php b/admin_ticket_template_details.php
index 03ad6fa0..b8280aed 100644
--- a/admin_ticket_template_details.php
+++ b/admin_ticket_template_details.php
@@ -30,156 +30,139 @@ $ticket_template_updated_at = nullable_htmlentities($row['ticket_template_update
$sql_task_templates = mysqli_query($mysqli, "SELECT * FROM task_templates WHERE task_template_ticket_template_id = $ticket_template_id ORDER BY task_template_order ASC, task_template_id ASC");
?>
-
-
- -
- Home
-
- -
- Admin
-
- -
- Ticket Templates
-
-
-
+
+ -
+ Home
+
+ -
+ Admin
+
+ -
+ Ticket Templates
+
+
+
-
-
+
+
-
-
+
+
+
+
+
+
+
-
-
-
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;
+ }
+});
diff --git a/plugins/dragula/dragula.min.css b/plugins/dragula/dragula.min.css
deleted file mode 100644
index a080100e..00000000
--- a/plugins/dragula/dragula.min.css
+++ /dev/null
@@ -1 +0,0 @@
-.gu-mirror{position:fixed!important;margin:0!important;z-index:9999!important;opacity:.8}.gu-hide{display:none!important}.gu-unselectable{-webkit-user-select:none!important;-moz-user-select:none!important;-ms-user-select:none!important;user-select:none!important}.gu-transit{opacity:.2}
\ No newline at end of file
diff --git a/plugins/dragula/dragula.min.js b/plugins/dragula/dragula.min.js
deleted file mode 100644
index dd61cd87..00000000
--- a/plugins/dragula/dragula.min.js
+++ /dev/null
@@ -1 +0,0 @@
-!function(e){"object"==typeof exports&&"undefined"!=typeof module?module.exports=e():"function"==typeof define&&define.amd?define([],e):("undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof self?self:this).dragula=e()}(function(){return function o(r,i,u){function c(t,e){if(!i[t]){if(!r[t]){var n="function"==typeof require&&require;if(!e&&n)return n(t,!0);if(a)return a(t,!0);throw(n=new Error("Cannot find module '"+t+"'")).code="MODULE_NOT_FOUND",n}n=i[t]={exports:{}},r[t][0].call(n.exports,function(e){return c(r[t][1][e]||e)},n,n.exports,o,r,i,u)}return i[t].exports}for(var a="function"==typeof require&&require,e=0;ee.left+G(e)/2);return n(u>e.top+J(e)/2)}:function(){var e,t,n,o=r.children.length;for(e=0;ei)return t;if(!c&&n.top+n.height/2>u)return t}return null})();function n(e){return e?Z(t):t}}}}).call(this,"undefined"!=typeof global?global:"undefined"!=typeof self?self:"undefined"!=typeof window?window:{})},{"./classes":1,"contra/emitter":5,crossvent:6}],3:[function(e,t,n){t.exports=function(e,t){return Array.prototype.slice.call(e,t)}},{}],4:[function(e,t,n){"use strict";var o=e("ticky");t.exports=function(e,t,n){e&&o(function(){e.apply(n||null,t||[])})}},{ticky:10}],5:[function(e,t,n){"use strict";var c=e("atoa"),a=e("./debounce");t.exports=function(r,e){var i=e||{},u={};return void 0===r&&(r={}),r.on=function(e,t){return u[e]?u[e].push(t):u[e]=[t],r},r.once=function(e,t){return t._once=!0,r.on(e,t),r},r.off=function(e,t){var n=arguments.length;if(1===n)delete u[e];else if(0===n)u={};else{e=u[e];if(!e)return r;e.splice(e.indexOf(t),1)}return r},r.emit=function(){var e=c(arguments);return r.emitterSnapshot(e.shift()).apply(this,e)},r.emitterSnapshot=function(o){var e=(u[o]||[]).slice(0);return function(){var t=c(arguments),n=this||r;if("error"===o&&!1!==i.throws&&!e.length)throw 1===t.length?t[0]:t;return e.forEach(function(e){i.async?a(e,t,n):e.apply(n,t),e._once&&r.off(o,e)}),r}},r}},{"./debounce":4,atoa:3}],6:[function(n,o,e){(function(r){"use strict";var i=n("custom-event"),u=n("./eventmap"),c=r.document,e=function(e,t,n,o){return e.addEventListener(t,n,o)},t=function(e,t,n,o){return e.removeEventListener(t,n,o)},a=[];function l(e,t,n){t=function(e,t,n){var o,r;for(o=0;o
-
diff --git a/ticket.php b/ticket.php
index 3b28d0f2..7f6fbcb5 100644
--- a/ticket.php
+++ b/ticket.php
@@ -341,7 +341,6 @@ if (isset($_GET['ticket_id'])) {
$ticket_collaborators = nullable_htmlentities($row['user_names']);
?>
-
@@ -940,7 +939,7 @@ if (isset($_GET['ticket_id'])) {
-
+
|
-
- m
- -
-
+
+ m
+ -
|
= 2) { ?>
+
+
|
@@ -1207,41 +1207,23 @@ require_once "includes/footer.php";
});
-
-
+
diff --git a/tickets_kanban.php b/tickets_kanban.php
index b8593bfe..bb4e3ab0 100644
--- a/tickets_kanban.php
+++ b/tickets_kanban.php
@@ -1,4 +1,3 @@
-
=htmlspecialchars($kanban_column->name); ?>
-
+
tickets as $item){
if ($item['ticket_priority'] == "High") {
@@ -154,5 +153,5 @@ echo "const CONFIG_TICKET_MOVING_COLUMNS = " . json_encode($config_ticket_movi
echo "const CONFIG_TICKET_ORDERING = " . json_encode($config_ticket_ordering) . ";";
echo "";
?>
-
+
\ No newline at end of file