Merge branch 'master' into techbar
merge conflict on filter_header but i like my implementation better
This commit is contained in:
21
CHANGELOG.md
21
CHANGELOG.md
@@ -2,6 +2,27 @@
|
|||||||
|
|
||||||
This file documents all notable changes made to ITFlow.
|
This file documents all notable changes made to ITFlow.
|
||||||
|
|
||||||
|
## [25.03.6]
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- Set default to date to 2035-12-31 as 9999-12-31 and 2999-12-31 broke certain browsers.
|
||||||
|
- Update Client PDF Export, add header added company logo.
|
||||||
|
- Present Larger clearer Warning about updates on update page.
|
||||||
|
- Allow to search by project reference.
|
||||||
|
|
||||||
|
## [25.03.5]
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- Fixed the user listing issue when copying a trip.
|
||||||
|
- Corrected the display of recurring invoice amounts on the dashboard.
|
||||||
|
- Fixed the linking of entities with assets and contacts.
|
||||||
|
- Resolved the issue with displaying the correct mobile country code in the contact listing.
|
||||||
|
- Set the default date to `9999-12-31` to ensure future items (like invoices) are displayed by default.
|
||||||
|
- Fixed the display issue where file folders were not showing properly during document creation.
|
||||||
|
- Migrated from Dragula to SortableJS for a more modern, mobile-friendly solution.
|
||||||
|
- Added Handlebars icons for drag-and-drop items.
|
||||||
|
- Changed behavior to open Contact and Asset Details pages directly instead of using a modal.
|
||||||
|
|
||||||
## [25.03.4]
|
## [25.03.4]
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|||||||
@@ -93,6 +93,7 @@ If you want to improve ITFlow, feel free to fork the repo and create a pull requ
|
|||||||
We’re incredibly grateful to the organizations and individuals who support the project - a big thank you to:
|
We’re incredibly grateful to the organizations and individuals who support the project - a big thank you to:
|
||||||
- CompuMatter
|
- CompuMatter
|
||||||
- F1 for HELP
|
- F1 for HELP
|
||||||
|
- JetBrains (PhpStorm)
|
||||||
|
|
||||||
## License
|
## License
|
||||||
ITFlow is distributed "as is" under the GPL License, WITHOUT WARRANTY OF ANY KIND. See [`LICENSE`](https://github.com/itflow-org/itflow/blob/master/LICENSE) for details.
|
ITFlow is distributed "as is" under the GPL License, WITHOUT WARRANTY OF ANY KIND. See [`LICENSE`](https://github.com/itflow-org/itflow/blob/master/LICENSE) for details.
|
||||||
|
|||||||
@@ -72,6 +72,31 @@ require_once "includes/inc_all_admin.php";
|
|||||||
<input type="email" class="form-control" name="config_ticket_new_ticket_notification_email" placeholder="Address to notify for new tickets, leave blank for none" value="<?php echo nullable_htmlentities($config_ticket_new_ticket_notification_email); ?>">
|
<input type="email" class="form-control" name="config_ticket_new_ticket_notification_email" placeholder="Address to notify for new tickets, leave blank for none" value="<?php echo nullable_htmlentities($config_ticket_new_ticket_notification_email); ?>">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label>Tickets Default View</label>
|
||||||
|
<div class="input-group">
|
||||||
|
<div class="input-group-prepend">
|
||||||
|
<span class="input-group-text"><i class="fa fa-fw fa-eye"></i></span>
|
||||||
|
</div>
|
||||||
|
<select class="form-control" name="config_ticket_default_view">
|
||||||
|
<option value=0 <?php if ($config_ticket_default_view == 0) { echo "selected"; } ?>>List</option>
|
||||||
|
<option value=1 <?php if ($config_ticket_default_view == 1) { echo "selected"; } ?>>Compact</option>
|
||||||
|
<option value=2 <?php if ($config_ticket_default_view == 2) { echo "selected"; } ?>>Kanban</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label>Kanban Settings</label>
|
||||||
|
<div class="custom-control custom-switch">
|
||||||
|
<input type="checkbox" class="custom-control-input" name="config_ticket_ordering" <?php if ($config_ticket_ordering == 1) { echo "checked"; } ?> value="1" id="ticketOrderingSwitch">
|
||||||
|
<label class="custom-control-label" for="ticketOrderingSwitch">Allow ticket ordering within its column<small class="text-secondary">(uncheked will result in ordering it by priority and id)</small></label>
|
||||||
|
</div>
|
||||||
|
<div class="custom-control custom-switch">
|
||||||
|
<input type="checkbox" class="custom-control-input" name="config_ticket_moving_columns" <?php if ($config_ticket_moving_columns == 1) { echo "checked"; } ?> value="1" id="ticketMovingColumnsSwitch">
|
||||||
|
<label class="custom-control-label" for="ticketMovingColumnsSwitch">Allow moving columns</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<hr>
|
<hr>
|
||||||
|
|
||||||
|
|||||||
@@ -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");
|
$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");
|
||||||
|
|
||||||
?>
|
?>
|
||||||
<link rel="stylesheet" href="plugins/dragula/dragula.min.css">
|
|
||||||
|
|
||||||
<ol class="breadcrumb d-print-none">
|
<ol class="breadcrumb d-print-none">
|
||||||
<li class="breadcrumb-item">
|
<li class="breadcrumb-item">
|
||||||
<a href="clients.php">Home</a>
|
<a href="clients.php">Home</a>
|
||||||
</li>
|
</li>
|
||||||
<li class="breadcrumb-item">
|
<li class="breadcrumb-item">
|
||||||
<a href="admin_user.php">Admin</a>
|
<a href="admin_user.php">Admin</a>
|
||||||
</li>
|
</li>
|
||||||
<li class="breadcrumb-item">
|
<li class="breadcrumb-item">
|
||||||
<a href="admin_ticket_template.php">Ticket Templates</a>
|
<a href="admin_ticket_template.php">Ticket Templates</a>
|
||||||
</li>
|
</li>
|
||||||
<li class="breadcrumb-item active"><i class="fas fa-life-ring mr-2"></i><?php echo $ticket_template_name; ?></li>
|
<li class="breadcrumb-item active"><i class="fas fa-life-ring mr-2"></i><?php echo $ticket_template_name; ?></li>
|
||||||
</ol>
|
</ol>
|
||||||
|
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-8">
|
<div class="col-8">
|
||||||
|
|
||||||
<div class="card card-dark">
|
<div class="card card-dark">
|
||||||
<div class="card-header">
|
<div class="card-header">
|
||||||
<h3 class="card-title mt-2">
|
<h3 class="card-title mt-2">
|
||||||
<div class="media">
|
<div class="media">
|
||||||
<i class="fa fa-fw fa-2x fa-life-ring mr-3"></i>
|
<i class="fa fa-fw fa-2x fa-life-ring mr-3"></i>
|
||||||
<div class="media-body">
|
<div class="media-body">
|
||||||
<h3 class="mb-0"><?php echo $ticket_template_name; ?></h3>
|
<h3 class="mb-0"><?php echo $ticket_template_name; ?></h3>
|
||||||
<div><small class="text-secondary"><?php echo $ticket_template_description; ?></small></div>
|
<div><small class="text-secondary"><?php echo $ticket_template_description; ?></small></div>
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</h3>
|
|
||||||
<div class="card-tools">
|
|
||||||
<button type="button" class="btn btn-default btn-sm" data-toggle="modal" data-target="#editTicketTemplateModal">
|
|
||||||
<i class="fas fa-edit"></i>
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</h3>
|
||||||
<h5><?php echo $ticket_template_subject; ?></h5>
|
<div class="card-tools">
|
||||||
<div class="card-body prettyContent">
|
<button type="button" class="btn btn-default btn-sm" data-toggle="modal" data-target="#editTicketTemplateModal">
|
||||||
<?php echo $ticket_template_details; ?>
|
<i class="fas fa-edit"></i>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<h5><?php echo $ticket_template_subject; ?></h5>
|
||||||
</div>
|
<div class="card-body prettyContent">
|
||||||
|
<?php echo $ticket_template_details; ?>
|
||||||
<div class="col-4">
|
|
||||||
|
|
||||||
<div class="card card-dark">
|
|
||||||
<div class="card-header">
|
|
||||||
<h5 class="card-title"><i class="fa fa-fw fa-tasks mr-2"></i>Tasks</h5>
|
|
||||||
</div>
|
|
||||||
<div class="card-body">
|
|
||||||
<form action="post.php" method="post" autocomplete="off">
|
|
||||||
<input type="hidden" name="ticket_template_id" value="<?php echo $ticket_template_id; ?>">
|
|
||||||
<div class="form-group">
|
|
||||||
<div class="input-group">
|
|
||||||
<div class="input-group-prepend">
|
|
||||||
<span class="input-group-text"><i class="fa fa-fw fa-tasks"></i></span>
|
|
||||||
</div>
|
|
||||||
<input type="text" class="form-control" name="task_name" placeholder="Create a task" required>
|
|
||||||
<div class="input-group-append">
|
|
||||||
<button type="submit" name="add_ticket_template_task" class="btn btn-primary"><i class="fas fa-fw fa-check"></i></button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
<table class="table table-striped table-sm">
|
|
||||||
<?php
|
|
||||||
while($row = mysqli_fetch_array($sql_task_templates)){
|
|
||||||
$task_id = intval($row['task_template_id']);
|
|
||||||
$task_name = nullable_htmlentities($row['task_template_name']);
|
|
||||||
$task_completion_estimate = intval($row['task_template_completion_estimate']);
|
|
||||||
$task_description = nullable_htmlentities($row['task_template_description']);
|
|
||||||
?>
|
|
||||||
<tr data-task-id="<?php echo $task_id; ?>">
|
|
||||||
<td><i class="far fa-fw fa-square text-secondary"></i></td>
|
|
||||||
<td>
|
|
||||||
<a href="#" class="grab-cursor">
|
|
||||||
<span class="text-secondary"><?php echo $task_completion_estimate; ?>m</span>
|
|
||||||
<span class="text-dark"> - <?php echo $task_name; ?></span>
|
|
||||||
</a>
|
|
||||||
</td>
|
|
||||||
<td class="text-right">
|
|
||||||
<div class="float-right">
|
|
||||||
<div class="dropdown dropleft text-center">
|
|
||||||
<button class="btn btn-link text-secondary btn-sm" type="button" data-toggle="dropdown">
|
|
||||||
<i class="fas fa-fw fa-ellipsis-v"></i>
|
|
||||||
</button>
|
|
||||||
<div class="dropdown-menu">
|
|
||||||
<a class="dropdown-item" href="#"
|
|
||||||
data-toggle = "ajax-modal"
|
|
||||||
data-ajax-url = "ajax/ajax_ticket_template_task_edit.php"
|
|
||||||
data-ajax-id = "<?php echo $task_id; ?>"
|
|
||||||
>
|
|
||||||
<i class="fas fa-fw fa-edit mr-2"></i>Edit
|
|
||||||
</a>
|
|
||||||
<div class="dropdown-divider"></div>
|
|
||||||
<a class="dropdown-item text-danger confirm-link" href="post.php?delete_task_template=<?php echo $task_id; ?>&csrf_token=<?php echo $_SESSION['csrf_token'] ?>">
|
|
||||||
<i class="fas fa-fw fa-trash-alt mr-2"></i>Delete
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<?php
|
|
||||||
}
|
|
||||||
?>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script src="js/pretty_content.js"></script>
|
<div class="col-4">
|
||||||
<script src="plugins/dragula/dragula.min.js"></script>
|
|
||||||
<script>
|
|
||||||
$(document).ready(function() {
|
|
||||||
var container = $('.table tbody')[0];
|
|
||||||
|
|
||||||
dragula([container])
|
<div class="card card-dark">
|
||||||
.on('drop', function (el, target, source, sibling) {
|
<div class="card-header">
|
||||||
// Handle the drop event to update the order in the database
|
<h5 class="card-title"><i class="fa fa-fw fa-tasks mr-2"></i>Tasks</h5>
|
||||||
var rows = $(container).children();
|
</div>
|
||||||
var positions = rows.map(function(index, row) {
|
<div class="card-body">
|
||||||
return {
|
<form action="post.php" method="post" autocomplete="off">
|
||||||
id: $(row).data('taskId'),
|
<input type="hidden" name="ticket_template_id" value="<?php echo $ticket_template_id; ?>">
|
||||||
order: index
|
<div class="form-group">
|
||||||
};
|
<div class="input-group">
|
||||||
}).get();
|
<div class="input-group-prepend">
|
||||||
|
<span class="input-group-text"><i class="fa fa-fw fa-tasks"></i></span>
|
||||||
// Send the new order to the server
|
</div>
|
||||||
$.ajax({
|
<input type="text" class="form-control" name="task_name" placeholder="Create a task" required>
|
||||||
url: 'ajax.php',
|
<div class="input-group-append">
|
||||||
method: 'POST',
|
<button type="submit" name="add_ticket_template_task" class="btn btn-primary"><i class="fas fa-fw fa-check"></i></button>
|
||||||
data: {
|
</div>
|
||||||
update_task_templates_order: true, // Adjust the parameter name if needed
|
</div>
|
||||||
ticket_template_id: <?php echo $ticket_template_id; ?>,
|
</div>
|
||||||
positions: positions
|
</form>
|
||||||
},
|
<table class="table table-sm" id="tasks">
|
||||||
success: function(data) {
|
<?php
|
||||||
// Handle success
|
while($row = mysqli_fetch_array($sql_task_templates)){
|
||||||
},
|
$task_id = intval($row['task_template_id']);
|
||||||
error: function(error) {
|
$task_name = nullable_htmlentities($row['task_template_name']);
|
||||||
console.error('Error updating order:', error);
|
$task_completion_estimate = intval($row['task_template_completion_estimate']);
|
||||||
|
$task_description = nullable_htmlentities($row['task_template_description']);
|
||||||
|
?>
|
||||||
|
<tr data-task-id="<?php echo $task_id; ?>">
|
||||||
|
<td>
|
||||||
|
<a href="#" class="drag-handle"><i class="fas fa-bars text-muted mr-1"></i></a>
|
||||||
|
<span class="text-secondary"><?php echo $task_completion_estimate; ?>m</span>
|
||||||
|
<span class="text-dark"> - <?php echo $task_name; ?></span>
|
||||||
|
</td>
|
||||||
|
<td class="text-right">
|
||||||
|
<div class="float-right">
|
||||||
|
<div class="dropdown dropleft text-center">
|
||||||
|
<button class="btn btn-link text-secondary btn-sm" type="button" data-toggle="dropdown">
|
||||||
|
<i class="fas fa-fw fa-ellipsis-v"></i>
|
||||||
|
</button>
|
||||||
|
<div class="dropdown-menu">
|
||||||
|
<a class="dropdown-item" href="#"
|
||||||
|
data-toggle = "ajax-modal"
|
||||||
|
data-ajax-url = "ajax/ajax_ticket_template_task_edit.php"
|
||||||
|
data-ajax-id = "<?php echo $task_id; ?>"
|
||||||
|
>
|
||||||
|
<i class="fas fa-fw fa-edit mr-2"></i>Edit
|
||||||
|
</a>
|
||||||
|
<div class="dropdown-divider"></div>
|
||||||
|
<a class="dropdown-item text-danger confirm-link" href="post.php?delete_task_template=<?php echo $task_id; ?>&csrf_token=<?php echo $_SESSION['csrf_token'] ?>">
|
||||||
|
<i class="fas fa-fw fa-trash-alt mr-2"></i>Delete
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<?php
|
||||||
}
|
}
|
||||||
});
|
?>
|
||||||
});
|
</table>
|
||||||
});
|
</div>
|
||||||
</script>
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script src="js/pretty_content.js"></script>
|
||||||
|
|
||||||
|
<script src="plugins/SortableJS/Sortable.min.js"></script>
|
||||||
|
<script>
|
||||||
|
new Sortable(document.querySelector('table#tasks tbody'), {
|
||||||
|
handle: '.drag-handle',
|
||||||
|
animation: 150,
|
||||||
|
onEnd: function (evt) {
|
||||||
|
const rows = document.querySelectorAll('table#tasks tbody tr');
|
||||||
|
const positions = Array.from(rows).map((row, index) => ({
|
||||||
|
id: row.dataset.taskId,
|
||||||
|
order: index
|
||||||
|
}));
|
||||||
|
|
||||||
|
$.post('ajax.php', {
|
||||||
|
update_task_templates_order: true,
|
||||||
|
ticket_template_id: <?php echo $ticket_template_id; ?>,
|
||||||
|
positions: positions
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
|||||||
@@ -32,8 +32,11 @@ $git_log = shell_exec("git log $repo_branch..origin/$repo_branch --pretty=format
|
|||||||
<?php } ?>
|
<?php } ?>
|
||||||
|
|
||||||
<?php if (LATEST_DATABASE_VERSION > CURRENT_DATABASE_VERSION) { ?>
|
<?php if (LATEST_DATABASE_VERSION > CURRENT_DATABASE_VERSION) { ?>
|
||||||
<div class="alert alert-warning">
|
<div class="alert alert-danger">
|
||||||
<strong>Ensure you have a current <a href="https://docs.itflow.org/backups">app & database backup</a> before updating!</strong>
|
<h1 class="font-weight-bold text-center">⚠️ DANGER ⚠️</h1>
|
||||||
|
<h2 class="font-weight-bold text-center">Do NOT run updates without first taking a backup</h2>
|
||||||
|
<p>VM Snapshots are highly recommended over other methods - see the <a href="https://docs.itflow.org/backups" class="alert-link" target="_blank">docs</a>. Review the <a href="https://github.com/itflow-org/itflow/blob/master/CHANGELOG.md" class="alert-link" target="_blank">changelog</a> for breaking changes that may require manual remediation.</p>
|
||||||
|
<p class="text-center font-weight-bold">Ignore this warning at your own risk.</p>
|
||||||
</div>
|
</div>
|
||||||
<br>
|
<br>
|
||||||
<a class="btn btn-dark btn-lg my-4" href="post.php?update_db"><i class="fas fa-fw fa-4x fa-download mb-1"></i><h5>Update Database</h5></a>
|
<a class="btn btn-dark btn-lg my-4" href="post.php?update_db"><i class="fas fa-fw fa-4x fa-download mb-1"></i><h5>Update Database</h5></a>
|
||||||
@@ -46,9 +49,17 @@ $git_log = shell_exec("git log $repo_branch..origin/$repo_branch --pretty=format
|
|||||||
|
|
||||||
<?php } else {
|
<?php } else {
|
||||||
if (!empty($git_log)) { ?>
|
if (!empty($git_log)) { ?>
|
||||||
|
<div class="alert alert-danger">
|
||||||
|
<h1 class="font-weight-bold text-center">⚠️ DANGER ⚠️</h1>
|
||||||
|
<h2 class="font-weight-bold text-center">Do NOT run updates without first taking a backup</h2>
|
||||||
|
<p>VM Snapshots are highly recommended over other methods - see the <a href="https://docs.itflow.org/backups" class="alert-link" target="_blank">docs</a>. Review the <a href="https://github.com/itflow-org/itflow/blob/master/CHANGELOG.md" class="alert-link" target="_blank">changelog</a> for breaking changes that may require manual remediation.</p>
|
||||||
|
<p class="text-center font-weight-bold">Ignore this warning at your own risk.</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
<a class="btn btn-primary btn-lg my-4" href="post.php?update"><i class="fas fa-fw fa-4x fa-download mb-1"></i><h5>Update App</h5></a>
|
<a class="btn btn-primary btn-lg my-4 confirm-link" href="post.php?no"><i class="fas fa-fw fa-4x fa-download mb-1"></i><h5>TEST</h5></a>
|
||||||
<a class="btn btn-danger btn-lg" href="post.php?update&force_update=1"><i class="fas fa-fw fa-4x fa-hammer mb-1"></i><h5>FORCE Update App</h5></a>
|
|
||||||
|
<a class="btn btn-primary btn-lg my-4 confirm-link" href="post.php?update"><i class="fas fa-fw fa-4x fa-download mb-1"></i><h5>Update App</h5></a>
|
||||||
|
<a class="btn btn-danger btn-lg confirm-link" href="post.php?update&force_update=1"><i class="fas fa-fw fa-4x fa-hammer mb-1"></i><h5>FORCE Update App</h5></a>
|
||||||
|
|
||||||
<?php } else { ?>
|
<?php } else { ?>
|
||||||
<p><strong>Application Release Version:<br><strong class="text-dark"><?php echo APP_VERSION; ?></strong></p>
|
<p><strong>Application Release Version:<br><strong class="text-dark"><?php echo APP_VERSION; ?></strong></p>
|
||||||
@@ -56,6 +67,17 @@ $git_log = shell_exec("git log $repo_branch..origin/$repo_branch --pretty=format
|
|||||||
<p class="text-secondary">Code Commit:<br><strong class="text-dark"><?php echo $current_version; ?></strong></p>
|
<p class="text-secondary">Code Commit:<br><strong class="text-dark"><?php echo $current_version; ?></strong></p>
|
||||||
<p class="text-muted">You are up to date!<br>Everything is going to be alright</p>
|
<p class="text-muted">You are up to date!<br>Everything is going to be alright</p>
|
||||||
<i class="far fa-3x text-dark fa-smile-wink"></i><br>
|
<i class="far fa-3x text-dark fa-smile-wink"></i><br>
|
||||||
|
|
||||||
|
<?php if (rand(1,10) == 1) { ?>
|
||||||
|
<br>
|
||||||
|
<div class="alert alert-info alert-dismissible fade show" role="alert">
|
||||||
|
You're up to date, but when was the last time you checked your ITFlow backup works?
|
||||||
|
<button type="button" class="close" data-dismiss="alert" aria-label="Close">
|
||||||
|
<span aria-hidden="true">×</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<?php } ?>
|
||||||
|
|
||||||
<?php }
|
<?php }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
4
ajax.php
4
ajax.php
@@ -586,13 +586,13 @@ if (isset($_POST['update_recurring_invoice_items_order'])) {
|
|||||||
enforceUserPermission('module_sales', 2);
|
enforceUserPermission('module_sales', 2);
|
||||||
|
|
||||||
$positions = $_POST['positions'];
|
$positions = $_POST['positions'];
|
||||||
$recurring_id = intval($_POST['recurring_id']);
|
$recurring_invoice_id = intval($_POST['recurring_invoice_id']);
|
||||||
|
|
||||||
foreach ($positions as $position) {
|
foreach ($positions as $position) {
|
||||||
$id = intval($position['id']);
|
$id = intval($position['id']);
|
||||||
$order = intval($position['order']);
|
$order = intval($position['order']);
|
||||||
|
|
||||||
mysqli_query($mysqli, "UPDATE invoice_items SET item_order = $order WHERE item_recurring_id = $recurring_id AND item_id = $id");
|
mysqli_query($mysqli, "UPDATE invoice_items SET item_order = $order WHERE item_recurring_invoice_id = $recurring_invoice_id AND item_id = $id");
|
||||||
}
|
}
|
||||||
|
|
||||||
// return a response
|
// return a response
|
||||||
|
|||||||
@@ -115,7 +115,7 @@ ob_start();
|
|||||||
|
|
||||||
$sql_users = mysqli_query($mysqli, "SELECT users.user_id, user_name FROM users
|
$sql_users = mysqli_query($mysqli, "SELECT users.user_id, user_name FROM users
|
||||||
LEFT JOIN user_settings on users.user_id = user_settings.user_id
|
LEFT JOIN user_settings on users.user_id = user_settings.user_id
|
||||||
WHERE user_role > 1 AND user_archived_at IS NULL ORDER BY user_name ASC"
|
WHERE user_role_id > 1 AND user_archived_at IS NULL ORDER BY user_name ASC"
|
||||||
);
|
);
|
||||||
while ($row = mysqli_fetch_array($sql_users)) {
|
while ($row = mysqli_fetch_array($sql_users)) {
|
||||||
$user_id_select = intval($row['user_id']);
|
$user_id_select = intval($row['user_id']);
|
||||||
|
|||||||
@@ -244,7 +244,7 @@ if (isset($_GET['asset_id'])) {
|
|||||||
data-ajax-id="<?php echo $asset_id; ?>">
|
data-ajax-id="<?php echo $asset_id; ?>">
|
||||||
<i class="fas fa-fw fa-edit"></i>
|
<i class="fas fa-fw fa-edit"></i>
|
||||||
</button>
|
</button>
|
||||||
<h3 class="text-bold"><i class="fa fa-fw text-secondary fa-<?php echo $device_icon; ?> mr-3"></i><?php echo $asset_name; ?></h3>
|
<h4 class="text-bold"><i class="fa fa-fw text-secondary fa-<?php echo $device_icon; ?> mr-3"></i><?php echo $asset_name; ?></h4>
|
||||||
<?php if ($asset_photo) { ?>
|
<?php if ($asset_photo) { ?>
|
||||||
<img class="img-fluid img-circle p-3" alt="asset_photo" src="<?php echo "uploads/clients/$client_id/$asset_photo"; ?>">
|
<img class="img-fluid img-circle p-3" alt="asset_photo" src="<?php echo "uploads/clients/$client_id/$asset_photo"; ?>">
|
||||||
<?php } ?>
|
<?php } ?>
|
||||||
@@ -405,7 +405,7 @@ if (isset($_GET['asset_id'])) {
|
|||||||
|
|
||||||
<div class="card card-dark">
|
<div class="card card-dark">
|
||||||
<div class="card-header py-2">
|
<div class="card-header py-2">
|
||||||
<h3 class="card-title mt-2"><i class="fa fa-fw fa-ethernet mr-2"></i><?php echo $asset_name; ?> Network Interfaces</h3>
|
<h3 class="card-title mt-2"><i class="fa fa-fw fa-ethernet mr-2"></i>Interfaces</h3>
|
||||||
<div class="card-tools">
|
<div class="card-tools">
|
||||||
<div class="btn-group">
|
<div class="btn-group">
|
||||||
<button type="button" class="btn btn-primary" data-toggle="modal" data-target="#addAssetInterfaceModal">
|
<button type="button" class="btn btn-primary" data-toggle="modal" data-target="#addAssetInterfaceModal">
|
||||||
|
|||||||
@@ -550,11 +550,7 @@ if (mysqli_num_rows($os_sql) > 0) {
|
|||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<a class="text-dark" href="#"
|
<a class="text-dark" href="asset_details.php?client_id=<?php echo $client_id; ?>&asset_id=<?php echo $asset_id; ?>">
|
||||||
data-toggle="ajax-modal"
|
|
||||||
data-modal-size="lg"
|
|
||||||
data-ajax-url="ajax/ajax_asset_details.php?<?php echo $client_url; ?>"
|
|
||||||
data-ajax-id="<?php echo $asset_id; ?>">
|
|
||||||
<div class="media">
|
<div class="media">
|
||||||
<i class="fa fa-fw fa-2x fa-<?php echo $device_icon; ?> mr-3 mt-1"></i>
|
<i class="fa fa-fw fa-2x fa-<?php echo $device_icon; ?> mr-3 mt-1"></i>
|
||||||
<div class="media-body">
|
<div class="media-body">
|
||||||
|
|||||||
@@ -319,7 +319,7 @@ $num_rows = mysqli_fetch_row(mysqli_query($mysqli, "SELECT FOUND_ROWS()"));
|
|||||||
} else {
|
} else {
|
||||||
$contact_phone_display = "<div><i class='fas fa-fw fa-phone mr-2'></i><a href='tel:$contact_phone'>$contact_phone$contact_extension_display</a></div>";
|
$contact_phone_display = "<div><i class='fas fa-fw fa-phone mr-2'></i><a href='tel:$contact_phone'>$contact_phone$contact_extension_display</a></div>";
|
||||||
}
|
}
|
||||||
$contact_mobile_country_code = nullable_htmlentities($row['contact_phone_country_code']);
|
$contact_mobile_country_code = nullable_htmlentities($row['contact_mobile_country_code']);
|
||||||
$contact_mobile = nullable_htmlentities(formatPhoneNumber($row['contact_mobile'], $contact_mobile_country_code));
|
$contact_mobile = nullable_htmlentities(formatPhoneNumber($row['contact_mobile'], $contact_mobile_country_code));
|
||||||
if (empty($contact_mobile)) {
|
if (empty($contact_mobile)) {
|
||||||
$contact_mobile_display = "";
|
$contact_mobile_display = "";
|
||||||
@@ -445,11 +445,7 @@ $num_rows = mysqli_fetch_row(mysqli_query($mysqli, "SELECT FOUND_ROWS()"));
|
|||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<a class="text-dark" href="#"
|
<a class="text-dark" href="contact_details.php?client_id=<?php echo $client_id; ?>&contact_id=<?php echo $contact_id; ?>">
|
||||||
data-toggle="ajax-modal"
|
|
||||||
data-modal-size="lg"
|
|
||||||
data-ajax-url="ajax/ajax_contact_details.php?<?php echo $client_url; ?>"
|
|
||||||
data-ajax-id="<?php echo $contact_id; ?>">
|
|
||||||
<div class="media">
|
<div class="media">
|
||||||
<?php if ($contact_photo) { ?>
|
<?php if ($contact_photo) { ?>
|
||||||
<span class="fa-stack fa-2x mr-3 text-center">
|
<span class="fa-stack fa-2x mr-3 text-center">
|
||||||
|
|||||||
@@ -20,10 +20,11 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.grab-cursor {
|
.drag-handle {
|
||||||
cursor: grab;
|
cursor: grab;
|
||||||
|
touch-action: none;
|
||||||
|
user-select: none;
|
||||||
}
|
}
|
||||||
|
.drag-handle:active {
|
||||||
.grab-cursor:active {
|
|
||||||
cursor: grabbing;
|
cursor: grabbing;
|
||||||
}
|
}
|
||||||
@@ -1,41 +1,83 @@
|
|||||||
|
/* General Popover Styling */
|
||||||
.popover {
|
.popover {
|
||||||
max-width: 600px;
|
max-width: 600px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Kanban Board Container */
|
||||||
#kanban-board {
|
#kanban-board {
|
||||||
display: flex;
|
display: flex;
|
||||||
box-sizing: border-box;
|
|
||||||
overflow-x: auto;
|
overflow-x: auto;
|
||||||
|
box-sizing: border-box;
|
||||||
min-width: 400px;
|
min-width: 400px;
|
||||||
height: calc(100vh - 210px);
|
height: calc(100vh - 210px);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Kanban Column */
|
||||||
.kanban-column {
|
.kanban-column {
|
||||||
flex: 1; /* Allows columns to grow equally */
|
flex: 1;
|
||||||
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;
|
||||||
padding: 10px;
|
|
||||||
border: 1px solid #ccc;
|
border: 1px solid #ccc;
|
||||||
|
border-radius: 4px;
|
||||||
|
padding: 10px;
|
||||||
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
.kanban-column div {
|
/* Column Inner Scrollable Task Area */
|
||||||
max-height: calc(100vh - 280px); /* Set your desired max height */
|
.kanban-status {
|
||||||
overflow-y: auto; /* Adds a scrollbar when content exceeds max height */
|
flex: 1;
|
||||||
|
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;
|
||||||
user-select: none; /* Prevent text selection */
|
border-radius: 4px;
|
||||||
|
cursor: grab;
|
||||||
|
user-select: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.drag-handle-class {
|
/* Grabbing Cursor State */
|
||||||
touch-action: none;
|
.task:active {
|
||||||
float: right;
|
cursor: grabbing;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Drag Handle (shown on mobile or with class targeting) */
|
||||||
|
.drag-handle-class {
|
||||||
|
float: right;
|
||||||
|
touch-action: none;
|
||||||
|
cursor: grab;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -151,7 +151,7 @@ if ($user_config_dashboard_financial_enable == 1) {
|
|||||||
$row = mysqli_fetch_array($sql_unbilled_tickets);
|
$row = mysqli_fetch_array($sql_unbilled_tickets);
|
||||||
$unbilled_tickets = intval($row['unbilled_tickets']);
|
$unbilled_tickets = intval($row['unbilled_tickets']);
|
||||||
} else {
|
} else {
|
||||||
$row = mysqli_fetch_assoc(mysqli_query($mysqli, "SELECT COUNT(recurring_id) AS recurring_invoices_added FROM recurring WHERE YEAR(recurring_created_at) = $year"));
|
$row = mysqli_fetch_assoc(mysqli_query($mysqli, "SELECT COUNT(recurring_invoice_id) AS recurring_invoices_added FROM recurring_invoices WHERE YEAR(recurring_invoice_created_at) = $year"));
|
||||||
$recurring_invoices_added = intval($row['recurring_invoices_added']);
|
$recurring_invoices_added = intval($row['recurring_invoices_added']);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1622,10 +1622,14 @@ function getFieldById($table, $id, $field, $escape_method = 'sql') {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Recursive function to display folder options - Used in folders files and documents
|
// Recursive function to display folder options - Used in folders files and documents
|
||||||
function display_folder_options($parent_folder_id, $client_id, $indent = 0) {
|
function display_folder_options($parent_folder_id, $client_id, $folder_location = 0, $indent = 0) {
|
||||||
global $mysqli;
|
global $mysqli;
|
||||||
|
|
||||||
$sql_folders = mysqli_query($mysqli, "SELECT * FROM folders WHERE parent_folder = $parent_folder_id AND folder_location = 1 AND folder_client_id = $client_id ORDER BY folder_name ASC");
|
$folder_location = intval($folder_location);
|
||||||
|
// 0 = Document Folders
|
||||||
|
// 1 = File Folders
|
||||||
|
|
||||||
|
$sql_folders = mysqli_query($mysqli, "SELECT * FROM folders WHERE parent_folder = $parent_folder_id AND folder_location = $folder_location AND folder_client_id = $client_id ORDER BY folder_name ASC");
|
||||||
while ($row = mysqli_fetch_array($sql_folders)) {
|
while ($row = mysqli_fetch_array($sql_folders)) {
|
||||||
$folder_id = intval($row['folder_id']);
|
$folder_id = intval($row['folder_id']);
|
||||||
$folder_name = nullable_htmlentities($row['folder_name']);
|
$folder_name = nullable_htmlentities($row['folder_name']);
|
||||||
@@ -1635,13 +1639,14 @@ function display_folder_options($parent_folder_id, $client_id, $indent = 0) {
|
|||||||
|
|
||||||
// Check if this folder is selected
|
// Check if this folder is selected
|
||||||
$selected = '';
|
$selected = '';
|
||||||
if ((isset($_GET['folder_id']) && $_GET['folder_id'] == $folder_id) || (isset($_POST['folder']) && $_POST['folder'] == $folder_id)) {
|
if ((isset($_GET['folder_id']) && intval($_GET['folder_id']) === $folder_id) ||
|
||||||
|
(isset($_POST['folder']) && intval($_POST['folder']) === $folder_id)) {
|
||||||
$selected = 'selected';
|
$selected = 'selected';
|
||||||
}
|
}
|
||||||
|
|
||||||
echo "<option value=\"$folder_id\" $selected>$indentation$folder_name</option>";
|
echo "<option value=\"$folder_id\" $selected>$indentation$folder_name</option>";
|
||||||
|
|
||||||
// Recursively display subfolders
|
// Recursively display subfolders
|
||||||
display_folder_options($folder_id, $client_id, $indent + 1);
|
display_folder_options($folder_id, $client_id, $folder_location, $indent + 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -5,4 +5,4 @@
|
|||||||
* Update this file each time we merge develop into master. Format is YY.MM (add a .v if there is more than one release a month.
|
* Update this file each time we merge develop into master. Format is YY.MM (add a .v if there is more than one release a month.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
DEFINE("APP_VERSION", "25.03.4");
|
DEFINE("APP_VERSION", "25.03.6");
|
||||||
|
|||||||
@@ -14,7 +14,11 @@ if (file_exists("config.php")) {
|
|||||||
<!-- Page Content -->
|
<!-- Page Content -->
|
||||||
<h1>Blank Page</h1>
|
<h1>Blank Page</h1>
|
||||||
<hr>
|
<hr>
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
if (isset($config_start_page)) { ?>
|
||||||
|
<meta http-equiv="refresh" content="0;url=<?php echo $config_start_page; ?>">
|
||||||
|
<?php }
|
||||||
|
|
||||||
require_once "includes/footer.php";
|
require_once "includes/footer.php";
|
||||||
|
|
||||||
|
|||||||
86
invoice.php
86
invoice.php
@@ -165,7 +165,6 @@ if (isset($_GET['invoice_id'])) {
|
|||||||
|
|
||||||
|
|
||||||
?>
|
?>
|
||||||
<link rel="stylesheet" href="plugins/dragula/dragula.min.css">
|
|
||||||
|
|
||||||
<ol class="breadcrumb d-print-none">
|
<ol class="breadcrumb d-print-none">
|
||||||
<?php if (isset($_GET['client_id'])) { ?>
|
<?php if (isset($_GET['client_id'])) { ?>
|
||||||
@@ -381,26 +380,34 @@ if (isset($_GET['invoice_id'])) {
|
|||||||
<tr data-item-id="<?php echo $item_id; ?>">
|
<tr data-item-id="<?php echo $item_id; ?>">
|
||||||
<td class="d-print-none">
|
<td class="d-print-none">
|
||||||
<?php if ($invoice_status !== "Paid" && $invoice_status !== "Cancelled") { ?>
|
<?php if ($invoice_status !== "Paid" && $invoice_status !== "Cancelled") { ?>
|
||||||
<div class="dropdown">
|
<div class="row">
|
||||||
<button class="btn btn-sm btn-light" type="button" data-toggle="dropdown">
|
<div class="col">
|
||||||
<i class="fas fa-ellipsis-v"></i>
|
<button type="button" class="btn btn-sm btn-light drag-handle">
|
||||||
</button>
|
<i class="fas fa-bars text-muted"></i>
|
||||||
<div class="dropdown-menu">
|
</button>
|
||||||
<a class="dropdown-item" href="#"
|
</div>
|
||||||
data-toggle="ajax-modal"
|
<div class="col">
|
||||||
data-ajax-url="ajax/ajax_item_edit.php"
|
<div class="dropdown">
|
||||||
data-ajax-id="<?php echo $item_id; ?>"
|
<button class="btn btn-sm btn-light" type="button" data-toggle="dropdown">
|
||||||
>
|
<i class="fas fa-ellipsis-v"></i>
|
||||||
<i class="fa fa-fw fa-edit mr-2"></i>Edit
|
</button>
|
||||||
</a>
|
<div class="dropdown-menu">
|
||||||
<div class="dropdown-divider"></div>
|
<a class="dropdown-item" href="#"
|
||||||
<a class="dropdown-item text-danger confirm-link" href="post.php?delete_invoice_item=<?php echo $item_id; ?>"><i class="fa fa-fw fa-trash mr-2"></i>Delete</a>
|
data-toggle="ajax-modal"
|
||||||
|
data-ajax-url="ajax/ajax_item_edit.php"
|
||||||
|
data-ajax-id="<?php echo $item_id; ?>"
|
||||||
|
>
|
||||||
|
<i class="fa fa-fw fa-edit mr-2"></i>Edit
|
||||||
|
</a>
|
||||||
|
<div class="dropdown-divider"></div>
|
||||||
|
<a class="dropdown-item text-danger confirm-link" href="post.php?delete_invoice_item=<?php echo $item_id; ?>"><i class="fa fa-fw fa-trash mr-2"></i>Delete</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<?php } ?>
|
<?php } ?>
|
||||||
</td>
|
</td>
|
||||||
<td class="grab-cursor"><?php echo $item_name; ?></td>
|
<td><?php echo $item_name; ?></td>
|
||||||
<td><?php echo nl2br($item_description); ?></td>
|
<td><?php echo nl2br($item_description); ?></td>
|
||||||
<td class="text-center"><?php echo number_format($item_quantity, 2); ?></td>
|
<td class="text-center"><?php echo number_format($item_quantity, 2); ?></td>
|
||||||
<td class="text-right"><?php echo numfmt_format_currency($currency_format, $item_price, $invoice_currency_code); ?></td>
|
<td class="text-right"><?php echo numfmt_format_currency($currency_format, $item_price, $invoice_currency_code); ?></td>
|
||||||
@@ -1178,38 +1185,23 @@ require_once "includes/footer.php";
|
|||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<script src="plugins/dragula/dragula.min.js"></script>
|
<script src="plugins/SortableJS/Sortable.min.js"></script>
|
||||||
<script>
|
<script>
|
||||||
$(document).ready(function() {
|
new Sortable(document.querySelector('table#items tbody'), {
|
||||||
var container = $('table#items tbody')[0];
|
handle: '.drag-handle',
|
||||||
|
animation: 150,
|
||||||
|
onEnd: function (evt) {
|
||||||
|
const rows = document.querySelectorAll('table#items tbody tr');
|
||||||
|
const positions = Array.from(rows).map((row, index) => ({
|
||||||
|
id: row.dataset.itemId,
|
||||||
|
order: index
|
||||||
|
}));
|
||||||
|
|
||||||
dragula([container])
|
$.post('ajax.php', {
|
||||||
.on('drop', function (el, target, source, sibling) {
|
update_invoice_items_order: true,
|
||||||
// Handle the drop event to update the order in the database
|
invoice_id: <?php echo $invoice_id; ?>,
|
||||||
var rows = $(container).children();
|
positions: positions
|
||||||
var positions = rows.map(function(index, row) {
|
|
||||||
return {
|
|
||||||
id: $(row).data('itemId'),
|
|
||||||
order: index
|
|
||||||
};
|
|
||||||
}).get();
|
|
||||||
|
|
||||||
// Send the new order to the server
|
|
||||||
$.ajax({
|
|
||||||
url: 'ajax.php',
|
|
||||||
method: 'POST',
|
|
||||||
data: {
|
|
||||||
update_invoice_items_order: true,
|
|
||||||
invoice_id: <?php echo $invoice_id; ?>,
|
|
||||||
positions: positions
|
|
||||||
},
|
|
||||||
success: function(data) {
|
|
||||||
// Handle success
|
|
||||||
},
|
|
||||||
error: function(error) {
|
|
||||||
console.error('Error updating order:', error);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
}
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -1,146 +1,126 @@
|
|||||||
$(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
|
// -------------------------------
|
||||||
function isTouchDevice() {
|
// Drag: Kanban Columns (Statuses)
|
||||||
return 'ontouchstart' in window || navigator.maxTouchPoints;
|
// -------------------------------
|
||||||
}
|
new Sortable(document.querySelector('#kanban-board'), {
|
||||||
|
animation: 150,
|
||||||
// Initialize Dragula for the Kanban board
|
handle: '.panel-title',
|
||||||
let boardDrake = dragula([
|
draggable: '.kanban-column',
|
||||||
document.querySelector('#kanban-board')
|
onEnd: function () {
|
||||||
], {
|
const columnPositions = Array.from(document.querySelectorAll('#kanban-board .kanban-column')).map((col, index) => ({
|
||||||
moves: function(el, container, handle) {
|
status_id: $(col).data('status-id'),
|
||||||
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
|
||||||
});
|
}));
|
||||||
});
|
|
||||||
|
|
||||||
// Send AJAX request to update all column positions
|
if (CONFIG_TICKET_MOVING_COLUMNS === 1) {
|
||||||
$.ajax({
|
$.post('ajax.php', {
|
||||||
url: 'ajax.php',
|
update_kanban_status_position: true,
|
||||||
type: 'POST',
|
positions: columnPositions
|
||||||
data: {
|
}).done(() => {
|
||||||
update_kanban_status_position: true,
|
console.log('Ticket status kanban orders updated.');
|
||||||
positions: columnPositions
|
}).fail((xhr) => {
|
||||||
},
|
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
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// -------------------------------
|
||||||
|
// 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()) {
|
if (isTouchDevice()) {
|
||||||
const moveList = document.querySelectorAll('.task');
|
$('.drag-handle-class').css('display', 'inline');
|
||||||
moveList.forEach(task => {
|
}
|
||||||
task.querySelector('.drag-handle-class').style.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) {
|
function hidePlaceholders() {
|
||||||
el.style.cursor = 'grabbing';
|
document.querySelectorAll('.empty-placeholder').forEach(el => el.remove());
|
||||||
});
|
}
|
||||||
|
|
||||||
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'));
|
|
||||||
|
|
||||||
if (CONFIG_TICKET_ORDERING === 0 && source == target) {
|
// Run once on load
|
||||||
drake.cancel(true); // Move the card back to its original position
|
showPlaceholders();
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get all cards in the target column and their positions
|
|
||||||
let cards = $(target).children('.task');
|
|
||||||
let positions = [];
|
|
||||||
|
|
||||||
//id of current status / column
|
// -------------------------------
|
||||||
let columnId = $(target).data('status-id');
|
// Utility: Detect touch device
|
||||||
|
// -------------------------------
|
||||||
let movedTicketId = $(el).data('ticket-id');
|
function isTouchDevice() {
|
||||||
let movedTicketStatusId = $(el).data('ticket-status-id');
|
return 'ontouchstart' in window || navigator.maxTouchPoints > 0;
|
||||||
|
}
|
||||||
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);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<form action="post.php" method="post" autocomplete="off">
|
<form action="post.php" method="post" autocomplete="off">
|
||||||
<input type="hidden" name="asset_id" value="<?php echo $asset_id; ?>">
|
<input type="hidden" name="asset_id" value="<?php echo intval($_GET['asset_id']); ?>">
|
||||||
<div class="modal-body bg-white">
|
<div class="modal-body bg-white">
|
||||||
|
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<form action="post.php" method="post" autocomplete="off">
|
<form action="post.php" method="post" autocomplete="off">
|
||||||
<input type="hidden" name="asset_id" value="<?php echo $asset_id; ?>">
|
<input type="hidden" name="asset_id" value="<?php echo intval($_GET['asset_id']); ?>">
|
||||||
<div class="modal-body bg-white">
|
<div class="modal-body bg-white">
|
||||||
|
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<form action="post.php" method="post" autocomplete="off">
|
<form action="post.php" method="post" autocomplete="off">
|
||||||
<input type="hidden" name="asset_id" value="<?php echo $asset_id; ?>">
|
<input type="hidden" name="asset_id" value="<?php echo intval($_GET['asset_id']); ?>">
|
||||||
<div class="modal-body bg-white">
|
<div class="modal-body bg-white">
|
||||||
|
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<form action="post.php" method="post" autocomplete="off">
|
<form action="post.php" method="post" autocomplete="off">
|
||||||
<input type="hidden" name="asset_id" value="<?php echo $asset_id; ?>">
|
<input type="hidden" name="asset_id" value="<?php echo intval($_GET['asset_id']); ?>">
|
||||||
<div class="modal-body bg-white">
|
<div class="modal-body bg-white">
|
||||||
|
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<form action="post.php" method="post" autocomplete="off">
|
<form action="post.php" method="post" autocomplete="off">
|
||||||
<input type="hidden" name="asset_id" value="<?php echo $asset_id; ?>">
|
<input type="hidden" name="asset_id" value="<?php echo intval($_GET['asset_id']); ?>">
|
||||||
<div class="modal-body bg-white">
|
<div class="modal-body bg-white">
|
||||||
|
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
|
|||||||
@@ -37,7 +37,7 @@
|
|||||||
<option value="0">/</option>
|
<option value="0">/</option>
|
||||||
<?php
|
<?php
|
||||||
// Start displaying folder options from the root (parent_folder = 0)
|
// Start displaying folder options from the root (parent_folder = 0)
|
||||||
display_folder_options(0, $client_id);
|
display_folder_options(0, $client_id, 1);
|
||||||
?>
|
?>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<form action="post.php" method="post" autocomplete="off">
|
<form action="post.php" method="post" autocomplete="off">
|
||||||
<input type="hidden" name="contact_id" value="<?php echo $contact_id; ?>">
|
<input type="hidden" name="contact_id" value="<?php echo intval($_GET['contact_id']); ?>">
|
||||||
<div class="modal-body bg-white">
|
<div class="modal-body bg-white">
|
||||||
|
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<form action="post.php" method="post" autocomplete="off">
|
<form action="post.php" method="post" autocomplete="off">
|
||||||
<input type="hidden" name="contact_id" value="<?php echo $contact_id; ?>">
|
<input type="hidden" name="contact_id" value="<?php echo intval($_GET['contact_id']); ?>">
|
||||||
<div class="modal-body bg-white">
|
<div class="modal-body bg-white">
|
||||||
|
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<form action="post.php" method="post" autocomplete="off">
|
<form action="post.php" method="post" autocomplete="off">
|
||||||
<input type="hidden" name="contact_id" value="<?php echo $contact_id; ?>">
|
<input type="hidden" name="contact_id" value="<?php echo intval($_GET['contact_id']); ?>">
|
||||||
<div class="modal-body bg-white">
|
<div class="modal-body bg-white">
|
||||||
|
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<form action="post.php" method="post" autocomplete="off">
|
<form action="post.php" method="post" autocomplete="off">
|
||||||
<input type="hidden" name="contact_id" value="<?php echo $contact_id; ?>">
|
<input type="hidden" name="contact_id" value="<?php echo intval($_GET['contact_id']); ?>">
|
||||||
<div class="modal-body bg-white">
|
<div class="modal-body bg-white">
|
||||||
|
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<form action="post.php" method="post" autocomplete="off">
|
<form action="post.php" method="post" autocomplete="off">
|
||||||
<input type="hidden" name="contact_id" value="<?php echo $contact_id; ?>">
|
<input type="hidden" name="contact_id" value="<?php echo intval($_GET['contact_id']); ?>">
|
||||||
<div class="modal-body bg-white">
|
<div class="modal-body bg-white">
|
||||||
|
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<form action="post.php" method="post" autocomplete="off">
|
<form action="post.php" method="post" autocomplete="off">
|
||||||
<input type="hidden" name="contact_id" value="<?php echo $contact_id; ?>">
|
<input type="hidden" name="contact_id" value="<?php echo intval($_GET['contact_id']); ?>">
|
||||||
<div class="modal-body bg-white">
|
<div class="modal-body bg-white">
|
||||||
|
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
|
|||||||
2
plugins/SortableJS/Sortable.min.js
vendored
Normal file
2
plugins/SortableJS/Sortable.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
1
plugins/dragula/dragula.min.css
vendored
1
plugins/dragula/dragula.min.css
vendored
@@ -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}
|
|
||||||
1
plugins/dragula/dragula.min.js
vendored
1
plugins/dragula/dragula.min.js
vendored
File diff suppressed because one or more lines are too long
@@ -597,6 +597,15 @@ if (isset($_POST["export_client_pdf"])) {
|
|||||||
enforceUserPermission("module_sales", 1);
|
enforceUserPermission("module_sales", 1);
|
||||||
enforceUserPermission("module_financial", 1);
|
enforceUserPermission("module_financial", 1);
|
||||||
|
|
||||||
|
$sql = mysqli_query($mysqli, "SELECT * FROM companies, settings WHERE companies.company_id = settings.company_id AND companies.company_id = 1");
|
||||||
|
$row = mysqli_fetch_array($sql);
|
||||||
|
$company_name = nullable_htmlentities($row['company_name']);
|
||||||
|
$company_phone_country_code = nullable_htmlentities($row['company_phone_country_code']);
|
||||||
|
$company_phone = nullable_htmlentities(formatPhoneNumber($row['company_phone'], $company_phone_country_code));
|
||||||
|
$company_email = nullable_htmlentities($row['company_email']);
|
||||||
|
$company_website = nullable_htmlentities($row['company_website']);
|
||||||
|
$company_logo = nullable_htmlentities($row['company_logo']);
|
||||||
|
|
||||||
$client_id = intval($_POST["client_id"]);
|
$client_id = intval($_POST["client_id"]);
|
||||||
$export_contacts = intval($_POST["export_contacts"]);
|
$export_contacts = intval($_POST["export_contacts"]);
|
||||||
$export_locations = intval($_POST["export_locations"]);
|
$export_locations = intval($_POST["export_locations"]);
|
||||||
@@ -604,7 +613,7 @@ if (isset($_POST["export_client_pdf"])) {
|
|||||||
$export_software = intval($_POST["export_software"]);
|
$export_software = intval($_POST["export_software"]);
|
||||||
$export_credentials = 0;
|
$export_credentials = 0;
|
||||||
if (lookupUserPermission("module_credential") >= 1) {
|
if (lookupUserPermission("module_credential") >= 1) {
|
||||||
$export_credentials = intval($_POST["export_credentials"]);
|
$export_credentials = intval($_POST["export_credentials"] ?? 0);
|
||||||
}
|
}
|
||||||
$export_networks = intval($_POST["export_networks"]);
|
$export_networks = intval($_POST["export_networks"]);
|
||||||
$export_certificates = intval($_POST["export_certificates"]);
|
$export_certificates = intval($_POST["export_certificates"]);
|
||||||
@@ -747,6 +756,10 @@ if (isset($_POST["export_client_pdf"])) {
|
|||||||
$pdf->SetAuthor($session_company_name);
|
$pdf->SetAuthor($session_company_name);
|
||||||
$pdf->SetTitle("$client_name - IT Documentation");
|
$pdf->SetTitle("$client_name - IT Documentation");
|
||||||
|
|
||||||
|
// TODO: Add page numbers to footer, but can't work out how to do it without the ugly line
|
||||||
|
// $pdf->SetFooterMargin(PDF_MARGIN_FOOTER);
|
||||||
|
// $pdf->setFooterData();
|
||||||
|
|
||||||
// Enable auto page breaks with a margin from the bottom
|
// Enable auto page breaks with a margin from the bottom
|
||||||
$pdf->SetAutoPageBreak(true, 15);
|
$pdf->SetAutoPageBreak(true, 15);
|
||||||
|
|
||||||
@@ -779,17 +792,27 @@ if (isset($_POST["export_client_pdf"])) {
|
|||||||
";
|
";
|
||||||
|
|
||||||
// Cover page section (for main content, not the TOC)
|
// Cover page section (for main content, not the TOC)
|
||||||
|
$html .= '<div class="cover">';
|
||||||
|
if (!empty($company_logo)) {
|
||||||
|
$pdf->Image('uploads/settings/' . $company_logo, '', '', 35, 35, '', '', 'L', false, 300, '', false, false, 1, false, false, false);
|
||||||
|
}
|
||||||
$html .= "
|
$html .= "
|
||||||
<div class='cover'>
|
<h1>IT Documentation</h1>
|
||||||
<h1>$client_name</h1>
|
<h2>$client_name</h2>
|
||||||
<h2>IT Documentation</h2>
|
<h4>Prepared by $session_name on " . date("F j, Y") . "</h4>
|
||||||
<p>Export Date: " . date("F j, Y") . "</p>
|
|
||||||
</div>
|
</div>
|
||||||
<hr>";
|
";
|
||||||
|
$html .= "
|
||||||
|
<br>
|
||||||
|
<h4>$session_company_name</h4>
|
||||||
|
$company_phone<br>$company_email<br>
|
||||||
|
<hr>
|
||||||
|
";
|
||||||
|
|
||||||
// Client header information (non-table)
|
// Client header information (non-table)
|
||||||
$html .= "
|
$html .= "
|
||||||
<div class='client-header'>
|
<div class='client-header'>
|
||||||
|
<h3>$client_name</h3>
|
||||||
<p><strong>Address:</strong> $location_address</p>
|
<p><strong>Address:</strong> $location_address</p>
|
||||||
<p><strong>City State Zip:</strong> $location_city $location_state $location_zip</p>
|
<p><strong>City State Zip:</strong> $location_city $location_state $location_zip</p>
|
||||||
<p><strong>Phone:</strong> $contact_phone</p>
|
<p><strong>Phone:</strong> $contact_phone</p>
|
||||||
@@ -1203,6 +1226,8 @@ if (isset($_POST["export_client_pdf"])) {
|
|||||||
<th>Type</th>
|
<th>Type</th>
|
||||||
<th>License</th>
|
<th>License</th>
|
||||||
<th>License Key</th>
|
<th>License Key</th>
|
||||||
|
<th>Purchase Date</th>
|
||||||
|
<th>Expiration Date</th>
|
||||||
<th>Notes</th>
|
<th>Notes</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
@@ -1212,6 +1237,8 @@ if (isset($_POST["export_client_pdf"])) {
|
|||||||
$software_type = nullable_htmlentities($row["software_type"]);
|
$software_type = nullable_htmlentities($row["software_type"]);
|
||||||
$software_license_type = nullable_htmlentities($row["software_license_type"]);
|
$software_license_type = nullable_htmlentities($row["software_license_type"]);
|
||||||
$software_key = nullable_htmlentities($row["software_key"]);
|
$software_key = nullable_htmlentities($row["software_key"]);
|
||||||
|
$software_purchase = nullable_htmlentities($row['software_purchase']);
|
||||||
|
$software_expire = nullable_htmlentities($row['software_expire']);
|
||||||
$software_notes = nullable_htmlentities($row["software_notes"]);
|
$software_notes = nullable_htmlentities($row["software_notes"]);
|
||||||
$html .= "
|
$html .= "
|
||||||
<tr style='page-break-inside: avoid;'>
|
<tr style='page-break-inside: avoid;'>
|
||||||
@@ -1219,6 +1246,8 @@ if (isset($_POST["export_client_pdf"])) {
|
|||||||
<td>$software_type</td>
|
<td>$software_type</td>
|
||||||
<td>$software_license_type</td>
|
<td>$software_license_type</td>
|
||||||
<td>$software_key</td>
|
<td>$software_key</td>
|
||||||
|
<td>$software_purchase</td>
|
||||||
|
<td>$software_expire</td>
|
||||||
<td>$software_notes</td>
|
<td>$software_notes</td>
|
||||||
</tr>";
|
</tr>";
|
||||||
}
|
}
|
||||||
@@ -1326,7 +1355,7 @@ if (isset($_POST["export_client_pdf"])) {
|
|||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>Domain Name</th>
|
<th>Domain Name</th>
|
||||||
<th>Expire</th>
|
<th>Expiration Date</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>";
|
<tbody>";
|
||||||
@@ -1383,4 +1412,4 @@ if (isset($_POST["export_client_pdf"])) {
|
|||||||
// Output the PDF document for download
|
// Output the PDF document for download
|
||||||
$pdf->Output(strtoAZaz09($client_name) . "-IT_Documentation-" . date("Y-m-d") . ".pdf", "D");
|
$pdf->Output(strtoAZaz09($client_name) . "-IT_Documentation-" . date("Y-m-d") . ".pdf", "D");
|
||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ $sql_projects = mysqli_query(
|
|||||||
LEFT JOIN clients ON client_id = project_client_id
|
LEFT JOIN clients ON client_id = project_client_id
|
||||||
LEFT JOIN users ON user_id = project_manager
|
LEFT JOIN users ON user_id = project_manager
|
||||||
WHERE DATE(project_created_at) BETWEEN '$dtf' AND '$dtt'
|
WHERE DATE(project_created_at) BETWEEN '$dtf' AND '$dtt'
|
||||||
AND (project_name LIKE '%$q%' OR project_description LIKE '%$q%' OR user_name LIKE '%$q%')
|
AND (CONCAT(project_prefix,project_number) LIKE '%$q%' OR project_name LIKE '%$q%' OR project_description LIKE '%$q%' OR user_name LIKE '%$q%')
|
||||||
AND project_completed_at $status_query
|
AND project_completed_at $status_query
|
||||||
$project_permission_snippet
|
$project_permission_snippet
|
||||||
AND project_$archive_query
|
AND project_$archive_query
|
||||||
|
|||||||
90
quote.php
90
quote.php
@@ -123,7 +123,6 @@ if (isset($_GET['quote_id'])) {
|
|||||||
);
|
);
|
||||||
|
|
||||||
?>
|
?>
|
||||||
<link rel="stylesheet" href="plugins/dragula/dragula.min.css">
|
|
||||||
|
|
||||||
<ol class="breadcrumb d-print-none">
|
<ol class="breadcrumb d-print-none">
|
||||||
<?php if (isset($_GET['client_id'])) { ?>
|
<?php if (isset($_GET['client_id'])) { ?>
|
||||||
@@ -326,27 +325,37 @@ if (isset($_GET['quote_id'])) {
|
|||||||
<tr data-item-id="<?php echo $item_id; ?>">
|
<tr data-item-id="<?php echo $item_id; ?>">
|
||||||
<td class="d-print-none">
|
<td class="d-print-none">
|
||||||
<?php if ($quote_status !== "Invoiced" && $quote_status !== "Accepted" && $quote_status !== "Declined" && lookupUserPermission("module_sales") >= 2) { ?>
|
<?php if ($quote_status !== "Invoiced" && $quote_status !== "Accepted" && $quote_status !== "Declined" && lookupUserPermission("module_sales") >= 2) { ?>
|
||||||
<div class="dropdown">
|
<div class="row">
|
||||||
<button class="btn btn-sm btn-light" type="button" data-toggle="dropdown">
|
<div class="col">
|
||||||
<i class="fas fa-ellipsis-v"></i>
|
<button type="button" class="btn btn-sm btn-light drag-handle">
|
||||||
</button>
|
<i class="fas fa-bars text-muted"></i>
|
||||||
<div class="dropdown-menu">
|
</button>
|
||||||
<a class="dropdown-item" href="#"
|
</div>
|
||||||
data-toggle="ajax-modal"
|
<div class="col">
|
||||||
data-ajax-url="ajax/ajax_item_edit.php"
|
|
||||||
data-ajax-id="<?php echo $item_id; ?>"
|
<div class="dropdown">
|
||||||
>
|
<button class="btn btn-sm btn-light" type="button" data-toggle="dropdown">
|
||||||
<i class="fa fa-fw fa-edit mr-2"></i>Edit
|
<i class="fas fa-ellipsis-v"></i>
|
||||||
</a>
|
</button>
|
||||||
<div class="dropdown-divider"></div>
|
<div class="dropdown-menu">
|
||||||
<a class="dropdown-item text-danger confirm-link" href="post.php?delete_quote_item=<?php echo $item_id; ?>">
|
<a class="dropdown-item" href="#"
|
||||||
<i class="fa fa-fw fa-trash mr-2"></i>Delete
|
data-toggle="ajax-modal"
|
||||||
</a>
|
data-ajax-url="ajax/ajax_item_edit.php"
|
||||||
|
data-ajax-id="<?php echo $item_id; ?>"
|
||||||
|
>
|
||||||
|
<i class="fa fa-fw fa-edit mr-2"></i>Edit
|
||||||
|
</a>
|
||||||
|
<div class="dropdown-divider"></div>
|
||||||
|
<a class="dropdown-item text-danger confirm-link" href="post.php?delete_quote_item=<?php echo $item_id; ?>">
|
||||||
|
<i class="fa fa-fw fa-trash mr-2"></i>Delete
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<?php } ?>
|
<?php } ?>
|
||||||
</td>
|
</td>
|
||||||
<td class="grab-cursor"><?php echo $item_name; ?></td>
|
<td><?php echo $item_name; ?></td>
|
||||||
<td><?php echo nl2br($item_description); ?></td>
|
<td><?php echo nl2br($item_description); ?></td>
|
||||||
<td class="text-center"><?php echo number_format($item_quantity, 2); ?></td>
|
<td class="text-center"><?php echo number_format($item_quantity, 2); ?></td>
|
||||||
<td class="text-right"><?php echo numfmt_format_currency($currency_format, $item_price, $quote_currency_code); ?></td>
|
<td class="text-right"><?php echo numfmt_format_currency($currency_format, $item_price, $quote_currency_code); ?></td>
|
||||||
@@ -992,38 +1001,23 @@ require_once "includes/footer.php";
|
|||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<script src="plugins/dragula/dragula.min.js"></script>
|
<script src="plugins/SortableJS/Sortable.min.js"></script>
|
||||||
<script>
|
<script>
|
||||||
$(document).ready(function() {
|
new Sortable(document.querySelector('table#items tbody'), {
|
||||||
var container = $('table#items tbody')[0];
|
handle: '.drag-handle',
|
||||||
|
animation: 150,
|
||||||
|
onEnd: function (evt) {
|
||||||
|
const rows = document.querySelectorAll('table#items tbody tr');
|
||||||
|
const positions = Array.from(rows).map((row, index) => ({
|
||||||
|
id: row.dataset.itemId,
|
||||||
|
order: index
|
||||||
|
}));
|
||||||
|
|
||||||
dragula([container])
|
$.post('ajax.php', {
|
||||||
.on('drop', function (el, target, source, sibling) {
|
update_quote_items_order: true,
|
||||||
// Handle the drop event to update the order in the database
|
quote_id: <?php echo $quote_id; ?>,
|
||||||
var rows = $(container).children();
|
positions: positions
|
||||||
var positions = rows.map(function(index, row) {
|
|
||||||
return {
|
|
||||||
id: $(row).data('itemId'),
|
|
||||||
order: index
|
|
||||||
};
|
|
||||||
}).get();
|
|
||||||
|
|
||||||
// Send the new order to the server
|
|
||||||
$.ajax({
|
|
||||||
url: 'ajax.php',
|
|
||||||
method: 'POST',
|
|
||||||
data: {
|
|
||||||
update_quote_items_order: true,
|
|
||||||
quote_id: <?php echo $quote_id; ?>,
|
|
||||||
positions: positions
|
|
||||||
},
|
|
||||||
success: function(data) {
|
|
||||||
// Handle success
|
|
||||||
},
|
|
||||||
error: function(error) {
|
|
||||||
console.error('Error updating order:', error);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
}
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -272,26 +272,34 @@ if (isset($_GET['recurring_invoice_id'])) {
|
|||||||
|
|
||||||
<tr data-item-id="<?php echo $item_id; ?>">
|
<tr data-item-id="<?php echo $item_id; ?>">
|
||||||
<td class="d-print-none">
|
<td class="d-print-none">
|
||||||
<div class="dropdown">
|
<div class="row">
|
||||||
<button class="btn btn-sm btn-light" type="button" data-toggle="dropdown">
|
<div class="col">
|
||||||
<i class="fas fa-ellipsis-v"></i>
|
<button type="button" class="btn btn-sm btn-light drag-handle">
|
||||||
</button>
|
<i class="fas fa-bars text-muted"></i>
|
||||||
<div class="dropdown-menu">
|
</button>
|
||||||
<a class="dropdown-item" href="#"
|
</div>
|
||||||
data-toggle="ajax-modal"
|
<div class="col">
|
||||||
data-ajax-url="ajax/ajax_item_edit.php"
|
|
||||||
data-ajax-id="<?php echo $item_id; ?>"
|
|
||||||
>
|
|
||||||
<i class="fa fa-fw fa-edit mr-2"></i>Edit
|
|
||||||
</a>
|
|
||||||
<div class="dropdown-divider"></div>
|
|
||||||
<a class="dropdown-item text-danger confirm-link" href="post.php?delete_recurring_invoice_item=<?php echo $item_id; ?>"><i class="fa fa-fw fa-trash mr-2"></i>Delete</a>
|
|
||||||
|
|
||||||
|
|
||||||
|
<div class="dropdown">
|
||||||
|
<button class="btn btn-sm btn-light" type="button" data-toggle="dropdown">
|
||||||
|
<i class="fas fa-ellipsis-v"></i>
|
||||||
|
</button>
|
||||||
|
<div class="dropdown-menu">
|
||||||
|
<a class="dropdown-item" href="#"
|
||||||
|
data-toggle="ajax-modal"
|
||||||
|
data-ajax-url="ajax/ajax_item_edit.php"
|
||||||
|
data-ajax-id="<?php echo $item_id; ?>"
|
||||||
|
>
|
||||||
|
<i class="fa fa-fw fa-edit mr-2"></i>Edit
|
||||||
|
</a>
|
||||||
|
<div class="dropdown-divider"></div>
|
||||||
|
<a class="dropdown-item text-danger confirm-link" href="post.php?delete_recurring_invoice_item=<?php echo $item_id; ?>"><i class="fa fa-fw fa-trash mr-2"></i>Delete</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td class="grab-cursor"><?php echo $item_name; ?></td>
|
<td><?php echo $item_name; ?></td>
|
||||||
<td><?php echo nl2br($item_description); ?></td>
|
<td><?php echo nl2br($item_description); ?></td>
|
||||||
<td class="text-center"><?php echo $item_quantity; ?></td>
|
<td class="text-center"><?php echo $item_quantity; ?></td>
|
||||||
<td class="text-right"><?php echo numfmt_format_currency($currency_format, $item_price, $recurring_invoice_currency_code); ?></td>
|
<td class="text-right"><?php echo numfmt_format_currency($currency_format, $item_price, $recurring_invoice_currency_code); ?></td>
|
||||||
@@ -483,39 +491,23 @@ require_once "includes/footer.php";
|
|||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<link rel="stylesheet" href="plugins/dragula/dragula.min.css">
|
<script src="plugins/SortableJS/Sortable.min.js"></script>
|
||||||
<script src="plugins/dragula/dragula.min.js"></script>
|
|
||||||
<script>
|
<script>
|
||||||
$(document).ready(function() {
|
new Sortable(document.querySelector('table#items tbody'), {
|
||||||
var container = $('table#items tbody')[0];
|
handle: '.drag-handle',
|
||||||
|
animation: 150,
|
||||||
|
onEnd: function (evt) {
|
||||||
|
const rows = document.querySelectorAll('table#items tbody tr');
|
||||||
|
const positions = Array.from(rows).map((row, index) => ({
|
||||||
|
id: row.dataset.itemId,
|
||||||
|
order: index
|
||||||
|
}));
|
||||||
|
|
||||||
dragula([container])
|
$.post('ajax.php', {
|
||||||
.on('drop', function (el, target, source, sibling) {
|
update_recurring_invoice_items_order: true,
|
||||||
// Handle the drop event to update the order in the database
|
recurring_invoice_id: <?php echo $recurring_invoice_id; ?>,
|
||||||
var rows = $(container).children();
|
positions: positions
|
||||||
var positions = rows.map(function(index, row) {
|
|
||||||
return {
|
|
||||||
id: $(row).data('itemId'),
|
|
||||||
order: index
|
|
||||||
};
|
|
||||||
}).get();
|
|
||||||
|
|
||||||
// Send the new order to the server
|
|
||||||
$.ajax({
|
|
||||||
url: 'ajax.php',
|
|
||||||
method: 'POST',
|
|
||||||
data: {
|
|
||||||
update_recurring_invoice_items_order: true,
|
|
||||||
recurring_invoice_id: <?php echo $recurring_invoice_id; ?>,
|
|
||||||
positions: positions
|
|
||||||
},
|
|
||||||
success: function(data) {
|
|
||||||
// Handle success
|
|
||||||
},
|
|
||||||
error: function(error) {
|
|
||||||
console.error('Error updating order:', error);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
}
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
64
ticket.php
64
ticket.php
@@ -341,7 +341,6 @@ if (isset($_GET['ticket_id'])) {
|
|||||||
$ticket_collaborators = nullable_htmlentities($row['user_names']);
|
$ticket_collaborators = nullable_htmlentities($row['user_names']);
|
||||||
|
|
||||||
?>
|
?>
|
||||||
<link rel="stylesheet" href="plugins/dragula/dragula.min.css">
|
|
||||||
|
|
||||||
<!-- Breadcrumbs-->
|
<!-- Breadcrumbs-->
|
||||||
<ol class="breadcrumb d-print-none">
|
<ol class="breadcrumb d-print-none">
|
||||||
@@ -940,7 +939,7 @@ if (isset($_GET['ticket_id'])) {
|
|||||||
</form>
|
</form>
|
||||||
<?php } ?>
|
<?php } ?>
|
||||||
|
|
||||||
<table class="table table-sm">
|
<table class="table table-sm" id="tasks">
|
||||||
<?php
|
<?php
|
||||||
while($row = mysqli_fetch_array($sql_tasks)){
|
while($row = mysqli_fetch_array($sql_tasks)){
|
||||||
$task_id = intval($row['task_id']);
|
$task_id = intval($row['task_id']);
|
||||||
@@ -960,14 +959,14 @@ if (isset($_GET['ticket_id'])) {
|
|||||||
<?php } ?>
|
<?php } ?>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<a href="#" class="grab-cursor">
|
<a href="#" class="drag-handle"><i class="fas fa-bars text-muted mr-1"></i></a>
|
||||||
<span class="text-secondary"><?php echo $task_completion_estimate; ?>m</span>
|
<span class="text-secondary"><?php echo $task_completion_estimate; ?>m</span>
|
||||||
<span class="text-dark"> - <?php echo $task_name; ?></span>
|
<span class="text-dark"> - <?php echo $task_name; ?></span>
|
||||||
</a>
|
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<div class="float-right">
|
<div class="float-right">
|
||||||
<?php if (empty($ticket_resolved_at) && lookupUserPermission("module_support") >= 2) { ?>
|
<?php if (empty($ticket_resolved_at) && lookupUserPermission("module_support") >= 2) { ?>
|
||||||
|
|
||||||
<div class="dropdown dropleft text-center">
|
<div class="dropdown dropleft text-center">
|
||||||
<button class="btn btn-link text-secondary btn-sm" type="button" data-toggle="dropdown">
|
<button class="btn btn-link text-secondary btn-sm" type="button" data-toggle="dropdown">
|
||||||
<i class="fas fa-fw fa-ellipsis-v"></i>
|
<i class="fas fa-fw fa-ellipsis-v"></i>
|
||||||
@@ -991,6 +990,7 @@ if (isset($_GET['ticket_id'])) {
|
|||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<?php } ?>
|
<?php } ?>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
@@ -1207,41 +1207,23 @@ require_once "includes/footer.php";
|
|||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<script src="plugins/SortableJS/Sortable.min.js"></script>
|
||||||
<script src="plugins/dragula/dragula.min.js"></script>
|
|
||||||
<script>
|
<script>
|
||||||
$(document).ready(function() {
|
new Sortable(document.querySelector('table#tasks tbody'), {
|
||||||
var container = $('.table tbody')[0];
|
handle: '.drag-handle',
|
||||||
|
animation: 150,
|
||||||
|
onEnd: function (evt) {
|
||||||
|
const rows = document.querySelectorAll('table#tasks tbody tr');
|
||||||
|
const positions = Array.from(rows).map((row, index) => ({
|
||||||
|
id: row.dataset.taskId,
|
||||||
|
order: index
|
||||||
|
}));
|
||||||
|
|
||||||
dragula([container])
|
$.post('ajax.php', {
|
||||||
.on('drop', function (el, target, source, sibling) {
|
update_ticket_tasks_order: true,
|
||||||
// Handle the drop event to update the order in the database
|
ticket_id: <?php echo $ticket_id; ?>,
|
||||||
var rows = $(container).children();
|
positions: positions
|
||||||
var positions = rows.map(function(index, row) {
|
});
|
||||||
return {
|
}
|
||||||
id: $(row).data('taskId'),
|
});
|
||||||
order: index
|
|
||||||
};
|
|
||||||
}).get();
|
|
||||||
|
|
||||||
//console.log('New positions:', positions);
|
|
||||||
|
|
||||||
// Send the new order to the server (example using fetch)
|
|
||||||
$.ajax({
|
|
||||||
url: 'ajax.php',
|
|
||||||
method: 'POST',
|
|
||||||
data: {
|
|
||||||
update_ticket_tasks_order: true,
|
|
||||||
ticket_id: <?php echo $ticket_id; ?>,
|
|
||||||
positions: positions
|
|
||||||
},
|
|
||||||
success: function(data) {
|
|
||||||
//console.log('Order updated:', data);
|
|
||||||
},
|
|
||||||
error: function(error) {
|
|
||||||
console.error('Error updating order:', error);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
<link rel="stylesheet" href="plugins/dragula/dragula.min.css">
|
|
||||||
<link rel="stylesheet" href="css/tickets_kanban.css">
|
<link rel="stylesheet" href="css/tickets_kanban.css">
|
||||||
|
|
||||||
<?php
|
<?php
|
||||||
@@ -82,7 +81,7 @@ $kanban = array_values($statuses);
|
|||||||
?>
|
?>
|
||||||
<div class="kanban-column card card-dark" data-status-id="<?=htmlspecialchars($kanban_column->id); ?>">
|
<div class="kanban-column card card-dark" data-status-id="<?=htmlspecialchars($kanban_column->id); ?>">
|
||||||
<h6 class="panel-title"><?=htmlspecialchars($kanban_column->name); ?></h6>
|
<h6 class="panel-title"><?=htmlspecialchars($kanban_column->name); ?></h6>
|
||||||
<div id="status" data-column-name="<?=$kanban_column->name?>" data-status-id="<?=htmlspecialchars($kanban_column->id); ?>" style="height: 100%;" >
|
<div class="kanban-status" data-column-name="<?=$kanban_column->name?>" data-status-id="<?=htmlspecialchars($kanban_column->id); ?>">
|
||||||
<?php
|
<?php
|
||||||
foreach($kanban_column->tickets as $item){
|
foreach($kanban_column->tickets as $item){
|
||||||
if ($item['ticket_priority'] == "High") {
|
if ($item['ticket_priority'] == "High") {
|
||||||
@@ -106,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>
|
||||||
@@ -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 "const CONFIG_TICKET_ORDERING = " . json_encode($config_ticket_ordering) . ";";
|
||||||
echo "</script>";
|
echo "</script>";
|
||||||
?>
|
?>
|
||||||
<script src="plugins/dragula/dragula.min.js"></script>
|
<script src="plugins/SortableJS/Sortable.min.js"></script>
|
||||||
<script src="js/tickets_kanban.js"></script>
|
<script src="js/tickets_kanban.js"></script>
|
||||||
Reference in New Issue
Block a user