Added SortableJS Library, and updated Invoice, Quote and Recurring to use it. Added Grab Bar Icons next to action buttons. Will now sort in Mobile much more efficiently, update ajax vars for recurring invoice

This commit is contained in:
johnnyq
2025-04-13 13:29:16 -04:00
parent 60fe02bb47
commit 19b809b699
6 changed files with 132 additions and 148 deletions

View File

@@ -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

View File

@@ -20,10 +20,11 @@
} }
} }
.grab-cursor { button.drag-handle {
cursor: grab; cursor: grab !important;
} touch-action: none;
user-select: none;
.grab-cursor:active {
cursor: grabbing;
} }
button.drag-handle:active {
cursor: grabbing !important;
}

View File

@@ -165,6 +165,7 @@ if (isset($_GET['invoice_id'])) {
?> ?>
<link rel="stylesheet" href="plugins/dragula/dragula.min.css"> <link rel="stylesheet" href="plugins/dragula/dragula.min.css">
<ol class="breadcrumb d-print-none"> <ol class="breadcrumb d-print-none">
@@ -381,26 +382,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 +1187,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>

2
plugins/SortableJS/Sortable.min.js vendored Normal file

File diff suppressed because one or more lines are too long

View File

@@ -326,27 +326,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 +1002,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>

View File

@@ -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>