Merge branch 'develop' of https://github.com/itflow-org/itflow into develop

This commit is contained in:
wrongecho 2026-01-28 23:10:54 +00:00
commit 01288c8452
3 changed files with 70 additions and 47 deletions

View File

@ -80,11 +80,11 @@ if (isset($_GET['ticket_id'])) {
//Set Ticket Badge Color based of priority
if ($ticket_priority == "High") {
$ticket_priority_display = "<span class='p-2 badge badge-danger'>$ticket_priority</span>";
$ticket_priority_display = "<span class='p-2 badge badge-pill badge-danger'>$ticket_priority</span>";
} elseif ($ticket_priority == "Medium") {
$ticket_priority_display = "<span class='p-2 badge badge-warning'>$ticket_priority</span>";
$ticket_priority_display = "<span class='p-2 badge badge-pill badge-warning'>$ticket_priority</span>";
} elseif ($ticket_priority == "Low") {
$ticket_priority_display = "<span class='p-2 badge badge-info'>$ticket_priority</span>";
$ticket_priority_display = "<span class='p-2 badge badge-pill badge-info'>$ticket_priority</span>";
} else {
$ticket_priority_display = "";
}
@ -463,20 +463,21 @@ if (isset($_GET['ticket_id'])) {
<div class="card card-body">
<div title="<?php echo $ticket_updated_at; ?>">
<i class="fa fa-fw fa-history text-secondary mr-2"></i>Updated: <strong><?php echo $ticket_updated_at_ago; ?></strong>
<i class="fa fa-fw fa-history text-secondary mr-2"></i>Last updated: <strong><?= date('M d, Y • h:m A', strtotime($ticket_updated_at)) . " ($ticket_updated_at_ago)" ?></strong>
</div>
<!-- Ticket assign (disable if closed -->
<?php if (empty($ticket_closed_at)) { ?>
<div class="mt-1">
<i class="fas fa-fw fa-user-check mr-2 text-secondary"></i>Assigned Agent:
<a class="ajax-modal" href="#"
data-modal-url="modals/ticket/ticket_assign.php?id=<?= $ticket_id ?>">
<i class="fas fa-fw fa-user mr-2 text-secondary"></i><?php echo $ticket_assigned_to_display; ?>
data-modal-url="modals/ticket/ticket_assign.php?id=<?= $ticket_id ?>">
<?= $ticket_assigned_to_display ?>
</a>
</div>
<?php } else { ?>
<div class="mt-1">
<i class="fas fa-fw fa-user mr-2 text-secondary"></i><?php echo $ticket_assigned_to_display; ?>
<i class="fas fa-fw fa-user-check mr-2 text-secondary"></i>Assigned Agent: <?php echo $ticket_assigned_to_display; ?>
</div>
<?php } ?>
<!-- End ticket assign -->
@ -484,14 +485,13 @@ if (isset($_GET['ticket_id'])) {
<div class="card card-body">
<div>
<i class="fa fa-fw fa-thermometer-half text-secondary mr-1"></i>
<a href="#"
<a href="#" title="Priority"
<?php if (lookupUserPermission("module_support") >= 2 && empty($ticket_closed_at)) { ?>
class="ajax-modal"
data-modal-url="modals/ticket/ticket_priority.php?id=<?= $ticket_id ?>"
<?php } ?>
>
<?php echo $ticket_priority_display; ?>
<?= $ticket_priority_display ?>
</a>
</div>
@ -518,14 +518,14 @@ if (isset($_GET['ticket_id'])) {
</div>
<?php } else { ?>
<div class="mt-1">
<i class="fa fa-fw fa-dollar-sign text-secondary mr-2"></i>Ticket is
<i class="fa fa-fw fa-dollar-sign text-secondary mr-2"></i>Billable:
<a class="ajax-modal" href="#"
data-modal-url="modals/ticket/ticket_billable.php?id=<?= $ticket_id ?>">
<?php
if ($ticket_billable == 1) {
echo "<span class='text-bold text-dark'>Billable</span>";
echo "<span class='text-bold text-dark'>Yes</span>";
} else {
echo "<span class='text-muted'>Not Billable</span>";
echo "<span class='text-muted'>No</span>";
}
?>
</a>
@ -561,7 +561,7 @@ if (isset($_GET['ticket_id'])) {
<div class="card-header bg-dark">
<h5 class="card-title">
Ticket Details
Description / Comments
</h5>
<?php if (empty($ticket_closed_at)) { ?>
<div class="card-tools">
@ -577,7 +577,7 @@ if (isset($_GET['ticket_id'])) {
while ($ticket_attachment = mysqli_fetch_assoc($sql_ticket_attachments)) {
$name = nullable_htmlentities($ticket_attachment['ticket_attachment_name']);
$ref_name = nullable_htmlentities($ticket_attachment['ticket_attachment_reference_name']);
echo "<hr class=''><i class='fas fa-fw fa-paperclip text-secondary mr-1'></i>$name | <a href='../uploads/tickets/$ticket_id/$ref_name' download='$name'><i class='fas fa-fw fa-download mr-1'></i>Download</a> | <a target='_blank' href='../uploads/tickets/$ticket_id/$ref_name'><i class='fas fa-fw fa-external-link-alt mr-1'></i>View</a>";
echo "<hr class=''><i class='fas fa-fw fa-paperclip text-secondary mr-1'></i>$name <a target='_blank' class='mr-1 ml-1' href='../uploads/tickets/$ticket_id/$ref_name'>[View]</a><a href='../uploads/tickets/$ticket_id/$ref_name' download='$name'>[Download]</a>";
}
?>
</div>
@ -587,8 +587,6 @@ if (isset($_GET['ticket_id'])) {
<!-- Only show ticket reply modal if status is not closed -->
<?php if (lookupUserPermission("module_support") >= 2 && empty($ticket_resolved_at) && empty($ticket_closed_at)) { ?>
<form action="post.php" method="post" autocomplete="off">
<input type="hidden" name="ticket_id" id="ticket_id" value="<?php echo $ticket_id; ?>">
<input type="hidden" name="client_id" id="client_id" value="<?php echo $client_id; ?>">
@ -598,28 +596,29 @@ if (isset($_GET['ticket_id'])) {
<div class="form-group">
<div class="btn-group btn-block btn-group-toggle" data-toggle="buttons">
<label class="btn btn-outline-dark active">
<input type="radio" name="public_reply_type" value="0" checked>Internal Note
<input type="radio" name="public_reply_type" value="0" checked>Internal
</label>
<?php if ($contact_email) { ?>
<label class="btn btn-outline-info">
<input type="radio" name="public_reply_type" value="2">Public Comment & Email
<input type="radio" name="public_reply_type" value="2">Public + Email
</label>
<?php } ?>
<label class="btn btn-outline-info">
<input type="radio" name="public_reply_type" value="1">Public Comment
<input type="radio" name="public_reply_type" value="1">Public
</label>
</div>
</div>
</div>
<div class="form-group">
<div class="form-group mb-0">
<textarea
class="form-control tinymceTicket" name="ticket_reply"
placeholder="Type a response">
</textarea>
</div>
<div class="card card-body pb-0">
<div class="form-row">
@ -705,7 +704,7 @@ if (isset($_GET['ticket_id'])) {
$user_avatar = nullable_htmlentities($row['user_avatar']);
$user_initials = initials($row['user_name']);
$avatar_link = "../uploads/users/$user_id/$user_avatar";
$ticket_reply_time_worked = date_create($row['ticket_reply_time_worked']);
$ticket_reply_time_worked = $row['ticket_reply_time_worked'];
}
$sql_ticket_reply_attachments = mysqli_query(
@ -732,12 +731,19 @@ if (isset($_GET['ticket_id'])) {
</span>
<?php } ?>
<div class="ml-3">
<div class="ml-2">
<h3 class="card-title"><?php echo $ticket_reply_by_display; ?></h3>
<div>
<?php if ($ticket_reply_type !== "Client") { ?>
<?php if ($ticket_reply_type !== "Client" && $ticket_reply_time_worked !== "00:00:00") { ?>
<div>
<br><small class="text-muted">Time worked: <?php echo date_format($ticket_reply_time_worked, 'H:i:s'); ?></small>
<br>
<small>
<i class="far fa-fw fa-clock text-secondary"></i>
Time worked:
<span class="text-muted">
<?= formatDuration($ticket_reply_time_worked) ?>
</span>
</small>
</div>
<?php } ?>
</div>
@ -809,10 +815,10 @@ if (isset($_GET['ticket_id'])) {
<div class="col-md-3">
<!-- Ticket details right card -->
<div class="card <?php if(!$ticket_resolved_at) { echo "collapsed-card"; } ?>">
<!-- Ticket activity right card -->
<div class="card">
<div class="card-header">
<h5 class="card-title"><i class="fas fa-fw fa-life-ring mr-2"></i>Ticket Details</h5>
<h5 class="card-title"><i class="fas fa-fw fa-clock mr-2"></i>Activity Summary</h5>
<div class="card-tools">
<button type="button" class="btn btn-tool" data-card-widget="collapse">
@ -824,45 +830,45 @@ if (isset($_GET['ticket_id'])) {
<!-- Created -->
<div title="<?php echo $ticket_created_at; ?>">
<i class="fa fa-fw fa-calendar text-secondary mr-2"></i><strong>Created: </strong><?php echo "$ticket_date ($ticket_created_at_ago)"; ?>
<i class="fas fa-fw fa-calendar text-secondary mr-2"></i><strong class="mr-2">Created:</strong><?= date('M d, Y', strtotime($ticket_date)) . " ($ticket_created_at_ago)" ?>
</div>
<!-- Created by -->
<?php if (!empty($ticket_created_by)) {
<?php if ($ticket_created_by) {
$row = mysqli_fetch_assoc(mysqli_query($mysqli, "SELECT user_name FROM users WHERE user_id = $ticket_created_by"));
$ticket_created_by_display = nullable_htmlentities($row['user_name']);
?>
<div class="mt-1">
<i class="far fa-fw fa-user text-secondary mr-2"></i><strong>Created by: </strong><?php echo $ticket_created_by_display; ?>
<i class="far fa-fw fa-user text-secondary mr-2"></i><strong class="mr-2">Created by:</strong><?= $ticket_created_by_display ?>
</div>
<?php } ?>
<!-- Source -->
<?php if (!empty($ticket_source)) { ?>
<?php if ($ticket_source) { ?>
<div class="mt-1">
<i class="far fa-fw fa-question-circle text-secondary mr-2"></i><strong>Source: </strong><?php echo $ticket_source; ?>
<i class="far fa-fw fa-question-circle text-secondary mr-2"></i><strong class="mr-2">Source:</strong><?= $ticket_source ?>
</div>
<?php } ?>
<!-- Category -->
<?php if ($ticket_category > 0) { ?>
<?php if ($ticket_category) { ?>
<div class="mt-1">
<i class="fas fa-fw fa-layer-group mr-2 text-secondary"></i><strong>Category: </strong><?php echo $ticket_category_display; ?>
<i class="fas fa-fw fa-layer-group mr-2 text-secondary"></i><strong class="mr-2">Category:</strong><?= $ticket_category_display ?>
</div>
<?php } ?>
<!-- First response (for SLA) -->
<?php if ($ticket_first_response_at) { ?>
<div title="First Response: <?php echo $ticket_created_at; ?>">
<i class="fa fa-fw fa-user-clock text-secondary mr-2"></i><strong>FR: </strong><?php echo "$ticket_first_response_at"; ?>
<div class="mt-1">
<i class="fas fa-fw fa-user-clock text-secondary mr-2"></i><strong class="mr-2">First Response:</strong><?= date('M d, Y • h:m A', strtotime($ticket_first_response_at)) ?>
</div>
<?php } ?>
<!-- Time tracking -->
<?php if ($ticket_total_reply_time) { ?>
<div class="mt-1">
<i class="far fa-fw fa-clock text-secondary mr-2"></i><strong>Time worked: </strong><?php echo $ticket_total_reply_time; ?>
<i class="far fa-fw fa-clock text-secondary mr-2"></i><strong class="mr-2">Total time worked:</strong><?= formatDuration($ticket_total_reply_time) ?>
</div>
<?php } ?>
@ -875,15 +881,15 @@ if (isset($_GET['ticket_id'])) {
<!-- --><?php //} ?>
<!-- Resolved -->
<?php if (!empty($ticket_resolved_at)) { ?>
<?php if ($ticket_resolved_at) { ?>
<hr>
<div class="mt-1" title="<?php echo $ticket_resolved_at; ?>">
<i class="fa fa-fw fa-check text-secondary mr-2"></i><strong>Resolved: </strong><?php echo "$ticket_resolved_date ($ticket_resolved_at_ago)"; ?>
<div class="mt-1" title="<?= $ticket_resolved_at ?>">
<i class="fas fa-fw fa-check text-secondary mr-2"></i><strong class="mr-2">Resolved:</strong><?= date('M d, Y • h:m A', strtotime($ticket_resolved_at)) . " ($ticket_resolved_at_ago)" ?>
</div>
<?php } ?>
<!-- Ticket closure info -->
<?php if (!empty($ticket_closed_at)) {
<?php if ($ticket_closed_at) {
$ticket_closed_by_display = 'User';
if (!empty($ticket_closed_by)) {
@ -893,11 +899,11 @@ if (isset($_GET['ticket_id'])) {
}
?>
<div class="mt-1">
<i class="fa fa-fw fa-user text-secondary mr-2"></i><strong>Closed by: </strong><?php echo ucwords($ticket_closed_by_display); ?>
<i class="fas fa-fw fa-user text-secondary mr-2"></i><strong class="mr-2">Closed by:</strong><?= ucwords($ticket_closed_by_display) ?>
</div>
<div class="mt-1" title="<?php echo $ticket_closed_at; ?>">
<i class="fa fa-fw fa-clock text-secondary mr-2"></i><strong>Closed: </strong><?php echo "$ticket_closed_date ($ticket_closed_at_ago)"; ?>
<div class="mt-1">
<i class="fas fa-fw fa-clock text-secondary mr-2"></i><strong class="mr-2">Closed:</strong><?= date('M d, Y • h:m A', strtotime($ticket_closed_at)) . " ($ticket_closed_at_ago)" ?>
</div>
<?php if ($ticket_feedback) { ?>

View File

@ -1998,3 +1998,20 @@ function dbRollback(mysqli $mysqli): void
{
$mysqli->rollback();
}
function formatDuration($time) {
// expects "HH:MM:SS"
[$h, $m, $s] = array_map('intval', explode(':', $time));
$parts = [];
if ($h > 0) $parts[] = $h . 'h';
if ($m > 0) $parts[] = $m . 'm';
// show seconds only if under 1 minute total OR if nothing else exists
if ($h == 0 && $m == 0) {
$parts[] = $s . 's';
}
return implode(' ', $parts);
}

View File

@ -183,7 +183,7 @@ $(document).ready(function() {
browser_spellcheck: true,
contextmenu: false,
resize: true,
min_height: 300,
min_height: 150,
max_height: 600,
promotion: false,
branding: false,
@ -321,7 +321,7 @@ $(document).ready(function() {
plugins: 'link image lists table code fullscreen autoresize',
license_key: 'gpl',
setup: function(editor) {
editor.on('init', function() {
window.onbeforeunload = function() {
// If editor is dirty AND not inside a visible modal → warn