From bb787cdc704045d61567a7444312ddc237083211 Mon Sep 17 00:00:00 2001 From: Hugo Sampaio Date: Wed, 29 Jan 2025 11:01:53 -0300 Subject: [PATCH 01/23] tickets initial idea of views Kanban view Compact view --- tickets.php | 362 +++++++++----------------------------------- tickets_compact.php | 280 ++++++++++++++++++++++++++++++++++ tickets_kanban.php | 305 +++++++++++++++++++++++++++++++++++++ tickets_list.php | 290 +++++++++++++++++++++++++++++++++++ 4 files changed, 948 insertions(+), 289 deletions(-) create mode 100644 tickets_compact.php create mode 100644 tickets_kanban.php create mode 100644 tickets_list.php diff --git a/tickets.php b/tickets.php index b1d4f494..842a8f1c 100644 --- a/tickets.php +++ b/tickets.php @@ -36,6 +36,17 @@ if (isset($_GET['status']) && is_array($_GET['status']) && !empty($_GET['status' } } +if (isset($_GET['category'])) { + if ($_GET['category'] == 'empty') { + $category_snippet = "AND ticket_category = 0 "; + } elseif ($_GET['category'] == 'all') { + $category_snippet = ""; + } else { + $category_snippet = "AND ticket_category = " . $_GET['category']; + } +} + + // Ticket assignment status filter // Default - any $ticket_assigned_query = ''; @@ -48,7 +59,7 @@ if (isset($_GET['assigned']) & !empty($_GET['assigned'])) { $ticket_assigned_query = 'AND ticket_assigned_to = ' . intval($_GET['assigned']); $ticket_assigned_filter_id = intval($_GET['assigned']); } -} +} //Rebuild URL $url_query_strings_sort = http_build_query(array_merge($_GET, array('sort' => $sort, 'order' => $order, 'status' => $status, 'assigned' => $ticket_assigned_filter_id))); @@ -70,7 +81,9 @@ $sql = mysqli_query( LEFT JOIN locations ON ticket_location_id = location_id LEFT JOIN vendors ON ticket_vendor_id = vendor_id LEFT JOIN ticket_statuses ON ticket_status = ticket_status_id + LEFT JOIN categories ON ticket_category = category_id WHERE $ticket_status_snippet " . $ticket_assigned_query . " + $category_snippet AND DATE(ticket_created_at) BETWEEN '$dtf' AND '$dtt' AND (CONCAT(ticket_prefix,ticket_number) LIKE '%$q%' OR client_name LIKE '%$q%' OR ticket_subject LIKE '%$q%' OR ticket_status_name LIKE '%$q%' OR ticket_priority LIKE '%$q%' OR user_name LIKE '%$q%' OR contact_name LIKE '%$q%' OR asset_name LIKE '%$q%' OR vendor_name LIKE '%$q%' OR ticket_vendor_ticket_number LIKE '%q%') $ticket_permission_snippet @@ -99,6 +112,15 @@ $sql_total_tickets_assigned = mysqli_query($mysqli, "SELECT COUNT(ticket_id) AS $row = mysqli_fetch_array($sql_total_tickets_assigned); $user_active_assigned_tickets = intval($row['total_tickets_assigned']); +$sql_categories = mysqli_query( + $mysqli, + "SELECT * FROM categories + WHERE category_type = 'Ticket' + ORDER BY category_name" +); + + + ?> + + +id = $id; + $statuses[$id]->name = $name; + $statuses[$id]->tickets = []; + $statuses[$id]->order = $kanban_order; // Store the order +} + +// Fetch tickets and merge into statuses +$sql = mysqli_query( + $mysqli, + "SELECT SQL_CALC_FOUND_ROWS * FROM tickets + LEFT JOIN clients ON ticket_client_id = client_id + LEFT JOIN contacts ON ticket_contact_id = contact_id + LEFT JOIN users ON ticket_assigned_to = user_id + LEFT JOIN assets ON ticket_asset_id = asset_id + LEFT JOIN locations ON ticket_location_id = location_id + LEFT JOIN vendors ON ticket_vendor_id = vendor_id + LEFT JOIN ticket_statuses ON ticket_status = ticket_status_id + LEFT JOIN categories ON ticket_category = category_id + WHERE $ticket_status_snippet " . $ticket_assigned_query . " + $category_snippet + AND DATE(ticket_created_at) BETWEEN '$dtf' AND '$dtt' + AND (CONCAT(ticket_prefix,ticket_number) LIKE '%$q%' OR client_name LIKE '%$q%' OR ticket_subject LIKE '%$q%' OR ticket_status_name LIKE '%$q%' OR ticket_priority LIKE '%$q%' OR user_name LIKE '%$q%' OR contact_name LIKE '%$q%' OR asset_name LIKE '%$q%' OR vendor_name LIKE '%$q%' OR ticket_vendor_ticket_number LIKE '%q%') + $ticket_permission_snippet + ORDER BY $sort $order" +); + +while ($row = mysqli_fetch_array($sql)) { + $id = $row['ticket_status_id']; + $ticket_order = $row['ticket_kanban']; + $row['ticket_order'] = $ticket_order; // Store the ticket order + + if (isset($statuses[$id])) { + $statuses[$id]->tickets[] = $row; + } +} + +// Convert associative array to indexed array for sorting +$kanban_array = array_values($statuses); + +// Sort the array by the 'order' field, moving null values to the end +usort($kanban_array, function($a, $b) { + if ($a->order === null) return 1; + if ($b->order === null) return -1; + return $a->order - $b->order; +}); + +// Sort tickets within each column by 'ticket_kanban' +foreach ($kanban_array as $kanban_column) { + usort($kanban_column->tickets, function($a, $b) { + return $a['ticket_order'] - $b['ticket_order']; + }); +} + +// Re-index the sorted array back to associative array if needed +$ordered_kanban = []; +foreach ($kanban_array as $item) { + $ordered_kanban[$item->id] = $item; +} + +$kanban = $ordered_kanban; + + + + + + +?> + +
+ +
+
name); ?>
+
+ tickets as $item){ + if ($item['ticket_priority'] == "High") { + $ticket_priority_color = "danger"; + } elseif ($item['ticket_priority'] == "Medium") { + $ticket_priority_color = "warning"; + } else { + $ticket_priority_color = "info"; + } + ?> + +
+ + + + + + +
+ + + + +
+ + +
+ + +
+ +
+ + + +
+ + +
+
+ +
+ + + + + + + + + + + + + diff --git a/tickets_list.php b/tickets_list.php new file mode 100644 index 00000000..6ed456c7 --- /dev/null +++ b/tickets_list.php @@ -0,0 +1,290 @@ +
+
+
+ + +
+ + "> + + + + + + + = 2) { ?> + + + + + + + + + + + + Never

"; + } else { + $ticket_updated_at_display = "

Never

"; + } + } else { + $ticket_updated_at_display = "$ticket_updated_at_time_ago
$ticket_updated_at"; + } + + $project_id = intval($row['ticket_project_id']); + + $client_id = intval($row['ticket_client_id']); + $client_name = nullable_htmlentities($row['client_name']); + $contact_name = nullable_htmlentities($row['contact_name']); + $contact_email = nullable_htmlentities($row['contact_email']); + + if ($ticket_priority == "High") { + $ticket_priority_color = "danger"; + } elseif ($ticket_priority == "Medium") { + $ticket_priority_color = "warning"; + } else { + $ticket_priority_color = "info"; + } + + $ticket_assigned_to = intval($row['ticket_assigned_to']); + if (empty($ticket_assigned_to)) { + if (!empty($ticket_closed_at)) { + $ticket_assigned_to_display = "

Not Assigned

"; + } else { + $ticket_assigned_to_display = "

Not Assigned

"; + } + } else { + $ticket_assigned_to_display = nullable_htmlentities($row['user_name']); + } + + if (empty($contact_name)) { + $contact_display = "-"; + } else { + $contact_display = "$contact_name
$contact_email"; + } + + // Get who last updated the ticket - to be shown in the last Response column + + // Defaults to prevent undefined errors + $ticket_reply_created_at = ""; + $ticket_reply_created_at_time_ago = "Never"; + $ticket_reply_by_display = ""; + $ticket_reply_type = "Client"; // Default to client for un-replied tickets + + $sql_ticket_reply = mysqli_query($mysqli, + "SELECT ticket_reply_type, ticket_reply_created_at, contact_name, user_name FROM ticket_replies + LEFT JOIN users ON ticket_reply_by = user_id + LEFT JOIN contacts ON ticket_reply_by = contact_id + WHERE ticket_reply_ticket_id = $ticket_id + AND ticket_reply_archived_at IS NULL + ORDER BY ticket_reply_id DESC LIMIT 1" + ); + $row = mysqli_fetch_array($sql_ticket_reply); + + if ($row) { + $ticket_reply_type = nullable_htmlentities($row['ticket_reply_type']); + if ($ticket_reply_type == "Client") { + $ticket_reply_by_display = nullable_htmlentities($row['contact_name']); + } else { + $ticket_reply_by_display = nullable_htmlentities($row['user_name']); + } + $ticket_reply_created_at = nullable_htmlentities($row['ticket_reply_created_at']); + $ticket_reply_created_at_time_ago = timeAgo($ticket_reply_created_at); + } + + + // Get Tasks + $sql_tasks = mysqli_query( $mysqli, "SELECT * FROM tasks WHERE task_ticket_id = $ticket_id ORDER BY task_created_at ASC"); + $task_count = mysqli_num_rows($sql_tasks); + // Get Completed Task Count + $sql_tasks_completed = mysqli_query($mysqli, + "SELECT * FROM tasks + WHERE task_ticket_id = $ticket_id + AND task_completed_at IS NOT NULL" + ); + $completed_task_count = mysqli_num_rows($sql_tasks_completed); + + // Tasks Completed Percent + if($task_count) { + $tasks_completed_percent = round(($completed_task_count / $task_count) * 100); + } + + ?> + + "> + + + + + + + + + + + + + + + = 2) { ?> + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+
+ + Ticket + + + + Subject / Tasks + + + + Client / Contact + + + + Billable + + + + Priority + + + + Status + + + + Assigned + + + + Last Response + + + + Created + +
+ +
+ +
+ +
+ + + + + + + 0) { ?> +
+
+
+ + +
+

+
+ +
+ + +
+
+ + Yes"; + } else { + echo "No"; + } + ?> + + + + + $ticket_scheduled_for "; } ?> + + + +
+ +
+
+
+ +
+
+ + + + + + +
+ +
+
+ From 3aa26226e5ea1968ae36fe8c52a1bb2dc5113a3c Mon Sep 17 00:00:00 2001 From: Hugo Sampaio Date: Wed, 29 Jan 2025 11:08:42 -0300 Subject: [PATCH 02/23] kanban post actions --- post/user/ticket_kanban.php | 51 +++++++++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) create mode 100644 post/user/ticket_kanban.php diff --git a/post/user/ticket_kanban.php b/post/user/ticket_kanban.php new file mode 100644 index 00000000..a591ae16 --- /dev/null +++ b/post/user/ticket_kanban.php @@ -0,0 +1,51 @@ + 'success']); + exit; +} + +if (isset($_POST['update_kanban_ticket'])) { + // Update ticket kanban order and status + enforceUserPermission('module_support', 2); + + $positions = $_POST['positions']; + + foreach ($positions as $position) { + $ticket_id = intval($position['ticket_id']); + $kanban = intval($position['ticket_kanban']); // ticket kanban position + $status = intval($position['ticket_status']); // ticket statuses + + // Exit if status is null + if ($status === null) { + //echo json_encode(['status' => 'error', 'message' => 'Ticket status is null']); + continue; + } + + mysqli_query($mysqli, "UPDATE tickets SET ticket_kanban = $kanban, ticket_status = $status WHERE ticket_id = $ticket_id"); + customAction('ticket_update', $ticket_id); + } + + // return a response + echo json_encode(['status' => 'success','payload' => $positions]); + exit; +} From 3e0e72dedc07b9b7d3c69dbccb8b17e9f2f6199f Mon Sep 17 00:00:00 2001 From: Hugo Sampaio Date: Wed, 29 Jan 2025 11:39:54 -0300 Subject: [PATCH 03/23] database update for kanban --- database_updates.php | 16 +++++++++++++--- db.sql | 2 ++ includes/database_version.php | 2 +- 3 files changed, 16 insertions(+), 4 deletions(-) diff --git a/database_updates.php b/database_updates.php index 8ee3abf5..bac093b9 100644 --- a/database_updates.php +++ b/database_updates.php @@ -2469,10 +2469,20 @@ if (LATEST_DATABASE_VERSION > CURRENT_DATABASE_VERSION) { mysqli_query($mysqli, "UPDATE `settings` SET `config_current_database_version` = '1.8.0'"); } - // if (CURRENT_DATABASE_VERSION == '1.8.0') { - // // Insert queries here required to update to DB version 1.8.1 + if (CURRENT_DATABASE_VERSION == '1.8.0') { + + + mysqli_query($mysqli, "ALTER TABLE `ticket_statuses` ADD `ticket_status_kanban` int(11)"); + + mysqli_query($mysqli, "ALTER TABLE `tickets` ADD `ticket_kanban` int(11);"); + + mysqli_query($mysqli, "UPDATE `settings` SET `config_current_database_version` = '1.8.1'"); + } + + // if (CURRENT_DATABASE_VERSION == '1.8.1') { + // // Insert queries here required to update to DB version 1.8.2 // // Then, update the database to the next sequential version - // mysqli_query($mysqli, "UPDATE `settings` SET `config_current_database_version` = '1.8.1'"); + // mysqli_query($mysqli, "UPDATE `settings` SET `config_current_database_version` = '1.8.2'"); // } } else { diff --git a/db.sql b/db.sql index 31f409e0..b8236e31 100644 --- a/db.sql +++ b/db.sql @@ -2019,6 +2019,7 @@ CREATE TABLE `ticket_statuses` ( `ticket_status_name` varchar(200) NOT NULL, `ticket_status_color` varchar(200) NOT NULL, `ticket_status_active` tinyint(1) NOT NULL DEFAULT 1, + `ticket_status_kanban` int(11), PRIMARY KEY (`ticket_status_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; /*!40101 SET character_set_client = @saved_cs_client */; @@ -2113,6 +2114,7 @@ CREATE TABLE `tickets` ( `ticket_asset_id` int(11) NOT NULL DEFAULT 0, `ticket_invoice_id` int(11) NOT NULL DEFAULT 0, `ticket_project_id` int(11) NOT NULL DEFAULT 0, + `ticket_kanban` int(11), PRIMARY KEY (`ticket_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; /*!40101 SET character_set_client = @saved_cs_client */; diff --git a/includes/database_version.php b/includes/database_version.php index c1d450cd..292483e0 100644 --- a/includes/database_version.php +++ b/includes/database_version.php @@ -5,4 +5,4 @@ * It is used in conjunction with database_updates.php */ -DEFINE("LATEST_DATABASE_VERSION", "1.8.0"); +DEFINE("LATEST_DATABASE_VERSION", "1.8.1"); From ed5aa9a0c2475efa76e00e00589bb192da803a2e Mon Sep 17 00:00:00 2001 From: Hugo Sampaio Date: Wed, 29 Jan 2025 12:00:52 -0300 Subject: [PATCH 04/23] sync last changes sync last changes on days 27 and 28 --- tickets_list.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tickets_list.php b/tickets_list.php index 6ed456c7..0c9d39e6 100644 --- a/tickets_list.php +++ b/tickets_list.php @@ -1,5 +1,5 @@
-
+
@@ -19,12 +19,12 @@ - Subject / Tasks + Subject - Client / Contact + Client / Contact @@ -121,7 +121,7 @@ if (empty($contact_name)) { $contact_display = "-"; } else { - $contact_display = "$contact_name
$contact_email"; + $contact_display = "$contact_name"; } // Get who last updated the ticket - to be shown in the last Response column From c02b267d4481c204a2f99d022f2f2586b3e4113e Mon Sep 17 00:00:00 2001 From: Hugo Sampaio Date: Wed, 29 Jan 2025 15:55:58 -0300 Subject: [PATCH 05/23] update tickets.php --- tickets.php | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/tickets.php b/tickets.php index 842a8f1c..378185a9 100644 --- a/tickets.php +++ b/tickets.php @@ -37,12 +37,13 @@ if (isset($_GET['status']) && is_array($_GET['status']) && !empty($_GET['status' } if (isset($_GET['category'])) { - if ($_GET['category'] == 'empty') { + $category = sanitizeInput($_GET['category']); + if ($category == 'empty') { $category_snippet = "AND ticket_category = 0 "; - } elseif ($_GET['category'] == 'all') { + } elseif ($category == 'all') { $category_snippet = ""; } else { - $category_snippet = "AND ticket_category = " . $_GET['category']; + $category_snippet = "AND ticket_category = " . $category; } } From 1fb243df11ceed368093c8ce1fb75364f655bf2e Mon Sep 17 00:00:00 2001 From: Hugo Sampaio Date: Wed, 29 Jan 2025 16:03:24 -0300 Subject: [PATCH 06/23] update compact list --- tickets_compact.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tickets_compact.php b/tickets_compact.php index aa0107e5..b484ce87 100644 --- a/tickets_compact.php +++ b/tickets_compact.php @@ -24,7 +24,7 @@ - = 2) { ?> + = 2) { ?> Billable From dec91d116a18b9ec1e38f535f1aa2b2eaccb081a Mon Sep 17 00:00:00 2001 From: Hugo Sampaio Date: Wed, 29 Jan 2025 16:10:40 -0300 Subject: [PATCH 07/23] update tickets_compact.php --- tickets_compact.php | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/tickets_compact.php b/tickets_compact.php index b484ce87..04e47f20 100644 --- a/tickets_compact.php +++ b/tickets_compact.php @@ -9,35 +9,35 @@
- +
- +
Ticket - + Client = 2) { ?> - + Billable - + Status - + Assigned @@ -187,7 +187,7 @@ - $ticket_subject"; } else { @@ -198,14 +198,14 @@ 0) { ?> -
+
-
+

@@ -221,7 +221,7 @@ - = 2) { ?> + = 2) { ?>
Date: Wed, 29 Jan 2025 16:12:32 -0300 Subject: [PATCH 08/23] update ticket_kanban.php --- post/user/ticket_kanban.php | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/post/user/ticket_kanban.php b/post/user/ticket_kanban.php index a591ae16..e427fdd1 100644 --- a/post/user/ticket_kanban.php +++ b/post/user/ticket_kanban.php @@ -14,7 +14,7 @@ if (isset($_POST['update_kanban_status_position'])) { foreach ($positions as $position) { $status_id = intval($position['status_id']); - $kanban = intval($position['status_kanban']); + $kanban = intval($position['status_kanban']); mysqli_query($mysqli, "UPDATE ticket_statuses SET ticket_status_kanban = $kanban WHERE ticket_status_id = $status_id"); } @@ -35,9 +35,8 @@ if (isset($_POST['update_kanban_ticket'])) { $kanban = intval($position['ticket_kanban']); // ticket kanban position $status = intval($position['ticket_status']); // ticket statuses - // Exit if status is null + // Continue if status is null if ($status === null) { - //echo json_encode(['status' => 'error', 'message' => 'Ticket status is null']); continue; } From 04fac54987300a6246d498b30acf1c3391e8b25f Mon Sep 17 00:00:00 2001 From: Hugo Sampaio Date: Wed, 29 Jan 2025 16:17:29 -0300 Subject: [PATCH 09/23] update tickets_list.php --- tickets_list.php | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/tickets_list.php b/tickets_list.php index 0c9d39e6..e80687f8 100644 --- a/tickets_list.php +++ b/tickets_list.php @@ -9,7 +9,7 @@
- +
@@ -195,12 +195,12 @@
0) { ?> -
+
-
+

@@ -262,12 +262,12 @@ // Edit actions, for open tickets if (empty($ticket_closed_at)) { - require "modals/ticket_assign_modal.php"; + require_once "modals/ticket_assign_modal.php"; - require "modals/ticket_edit_priority_modal.php"; + require_once "modals/ticket_edit_priority_modal.php"; if ($config_module_enable_accounting) { - require "modals/ticket_edit_billable_modal.php"; + require_once "modals/ticket_edit_billable_modal.php"; } } } From b6e0990a780e288e2c922824645523a6acc7ce67 Mon Sep 17 00:00:00 2001 From: Hugo Sampaio Date: Wed, 29 Jan 2025 16:21:08 -0300 Subject: [PATCH 10/23] update tickets.php --- tickets.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tickets.php b/tickets.php index 378185a9..e70d7b29 100644 --- a/tickets.php +++ b/tickets.php @@ -177,13 +177,13 @@ $sql_categories = mysqli_query(