From 8c0e5246b499b7532fda9cfdda9e689cfb97a43c Mon Sep 17 00:00:00 2001 From: johnnyq Date: Fri, 14 Aug 2026 19:08:15 -0400 Subject: [PATCH] Fix Card Margin bottoms client header and ticket cards on the right --- agent/ajax.php | 77 +++++++++++++++++ agent/includes/inc_client_top_head.php | 2 +- agent/invoice.php | 28 ++++-- agent/modals/invoice/invoice_note.php | 23 ----- agent/modals/quote/quote_note.php | 29 ------- .../recurring_invoice_note.php | 23 ----- agent/post/invoice.php | 28 ------ agent/post/quote.php | 28 ------ agent/post/recurring_invoice.php | 28 ------ agent/quote.php | 30 ++++--- agent/recurring_invoice.php | 30 +++++-- agent/ticket.php | 8 +- js/inline_notes.js | 85 +++++++++++++++++++ 13 files changed, 228 insertions(+), 191 deletions(-) delete mode 100644 agent/modals/invoice/invoice_note.php delete mode 100644 agent/modals/quote/quote_note.php delete mode 100644 agent/modals/recurring_invoice/recurring_invoice_note.php create mode 100644 js/inline_notes.js diff --git a/agent/ajax.php b/agent/ajax.php index fccafd6ab..066959646 100644 --- a/agent/ajax.php +++ b/agent/ajax.php @@ -59,6 +59,83 @@ if (isset($_POST['client_set_notes'])) { } +/* + * Inline notes for sales documents. + * + * These replace the invoice/quote/recurring-invoice note modals, which posted + * to post.php and reloaded the page. Same permission, client-access and audit + * behaviour as those handlers had - only the delivery changed. + */ + +if (isset($_POST['invoice_set_notes'])) { + + validateCSRFToken(); + + enforceUserPermission('module_sales', 2); + + $invoice_id = intval($_POST['invoice_id']); + $note = escapeSql($_POST['note']); + + $sql = mysqli_query($mysqli, "SELECT invoice_client_id, invoice_number, invoice_prefix FROM invoices WHERE invoice_id = $invoice_id"); + $row = mysqli_fetch_assoc($sql); + $invoice_prefix = escapeSql($row['invoice_prefix']); + $invoice_number = intval($row['invoice_number']); + $client_id = intval($row['invoice_client_id']); + + enforceClientAccess(); + + mysqli_query($mysqli, "UPDATE invoices SET invoice_note = '$note' WHERE invoice_id = $invoice_id"); + + logAudit("Invoice", "Edit", "$session_name edited notes on invoice $invoice_prefix$invoice_number", $client_id, $invoice_id); + +} + +if (isset($_POST['quote_set_notes'])) { + + validateCSRFToken(); + + enforceUserPermission('module_sales', 2); + + $quote_id = intval($_POST['quote_id']); + $note = escapeSql($_POST['note']); + + $sql = mysqli_query($mysqli, "SELECT quote_client_id, quote_number, quote_prefix FROM quotes WHERE quote_id = $quote_id"); + $row = mysqli_fetch_assoc($sql); + $quote_prefix = escapeSql($row['quote_prefix']); + $quote_number = intval($row['quote_number']); + $client_id = intval($row['quote_client_id']); + + enforceClientAccess(); + + mysqli_query($mysqli, "UPDATE quotes SET quote_note = '$note' WHERE quote_id = $quote_id"); + + logAudit("Quote", "Edit", "$session_name edited notes on quote $quote_prefix$quote_number", $client_id, $quote_id); + +} + +if (isset($_POST['recurring_invoice_set_notes'])) { + + validateCSRFToken(); + + enforceUserPermission('module_sales', 2); + + $recurring_invoice_id = intval($_POST['recurring_invoice_id']); + $note = escapeSql($_POST['note']); + + $sql = mysqli_query($mysqli, "SELECT recurring_invoice_prefix, recurring_invoice_number, recurring_invoice_client_id FROM recurring_invoices WHERE recurring_invoice_id = $recurring_invoice_id"); + $row = mysqli_fetch_assoc($sql); + $recurring_invoice_prefix = escapeSql($row['recurring_invoice_prefix']); + $recurring_invoice_number = intval($row['recurring_invoice_number']); + $client_id = intval($row['recurring_invoice_client_id']); + + enforceClientAccess(); + + mysqli_query($mysqli, "UPDATE recurring_invoices SET recurring_invoice_note = '$note' WHERE recurring_invoice_id = $recurring_invoice_id"); + + logAudit("Recurring Invoice", "Edit", "$session_name edited notes on recurring invoice $recurring_invoice_prefix$recurring_invoice_number", $client_id, $recurring_invoice_id); + +} + if (isset($_POST['contact_set_notes'])) { validateCSRFToken(); diff --git a/agent/includes/inc_client_top_head.php b/agent/includes/inc_client_top_head.php index 6e9353a0f..ec8923451 100644 --- a/agent/includes/inc_client_top_head.php +++ b/agent/includes/inc_client_top_head.php @@ -1,6 +1,6 @@ -
+

diff --git a/agent/invoice.php b/agent/invoice.php index 09b6a524c..5eda4ba35 100644 --- a/agent/invoice.php +++ b/agent/invoice.php @@ -508,14 +508,22 @@ if (isset($_GET['invoice_id'])) {
Notes: -
- - - -
+
-
- +
+= 2) { ?> + + +
+ + +
@@ -738,10 +746,14 @@ if (isset($_GET['invoice_id'])) {
+ + + + diff --git a/agent/modals/invoice/invoice_note.php b/agent/modals/invoice/invoice_note.php deleted file mode 100644 index 35b5dd5fe..000000000 --- a/agent/modals/invoice/invoice_note.php +++ /dev/null @@ -1,23 +0,0 @@ - diff --git a/agent/modals/quote/quote_note.php b/agent/modals/quote/quote_note.php deleted file mode 100644 index 8de655492..000000000 --- a/agent/modals/quote/quote_note.php +++ /dev/null @@ -1,29 +0,0 @@ - diff --git a/agent/modals/recurring_invoice/recurring_invoice_note.php b/agent/modals/recurring_invoice/recurring_invoice_note.php deleted file mode 100644 index ca66d2b20..000000000 --- a/agent/modals/recurring_invoice/recurring_invoice_note.php +++ /dev/null @@ -1,23 +0,0 @@ - diff --git a/agent/post/invoice.php b/agent/post/invoice.php index 5cc734be5..23cc8d66a 100644 --- a/agent/post/invoice.php +++ b/agent/post/invoice.php @@ -393,34 +393,6 @@ if (isset($_POST['add_invoice_item'])) { } -if (isset($_POST['invoice_note'])) { - - validateCSRFToken(); - - enforceUserPermission('module_sales', 2); - - $invoice_id = intval($_POST['invoice_id']); - $note = escapeSql($_POST['note']); - - // Get Invoice Details for logging - $sql = mysqli_query($mysqli,"SELECT invoice_client_id, invoice_number, invoice_prefix FROM invoices WHERE invoice_id = $invoice_id"); - $row = mysqli_fetch_assoc($sql); - $invoice_prefix = escapeSql($row['invoice_prefix']); - $invoice_number = intval($row['invoice_number']); - $client_id = intval($row['invoice_client_id']); - - enforceClientAccess(); - - mysqli_query($mysqli,"UPDATE invoices SET invoice_note = '$note' WHERE invoice_id = $invoice_id"); - - logAudit("Invoice", "Edit", "$session_name added note to invoice $invoice_prefix$invoice_number", $client_id, $invoice_id); - - flashAlert("Notes added"); - - redirect(); - -} - if (isset($_POST['edit_invoice_item'])) { validateCSRFToken(); diff --git a/agent/post/quote.php b/agent/post/quote.php index ef62fd8d9..bc5aa4ba8 100644 --- a/agent/post/quote.php +++ b/agent/post/quote.php @@ -341,34 +341,6 @@ if (isset($_POST['edit_quote_item'])) { } -if (isset($_POST['quote_note'])) { - - validateCSRFToken(); - - enforceUserPermission('module_sales', 2); - - $quote_id = intval($_POST['quote_id']); - $note = escapeSql($_POST['note']); - - // Get Quote Details - $sql = mysqli_query($mysqli,"SELECT quote_client_id, quote_number, quote_prefix FROM quotes WHERE quote_id = $quote_id"); - $row = mysqli_fetch_assoc($sql); - $quote_prefix = escapeSql($row['quote_prefix']); - $quote_number = escapeSql($row['quote_number']); - $client_id = intval($row['quote_client_id']); - - enforceClientAccess(); - - mysqli_query($mysqli,"UPDATE quotes SET quote_note = '$note' WHERE quote_id = $quote_id"); - - logAudit("Quote", "Edit", "$session_name added notes to quote $quote_prefix$quote_number", $client_id, $quote_id); - - flashAlert("Notes added"); - - redirect(); - -} - if (isset($_POST['edit_quote'])) { validateCSRFToken(); diff --git a/agent/post/recurring_invoice.php b/agent/post/recurring_invoice.php index b6c0fa7ec..89a3ddb5b 100644 --- a/agent/post/recurring_invoice.php +++ b/agent/post/recurring_invoice.php @@ -319,34 +319,6 @@ if (isset($_POST['edit_recurring_invoice_item'])) { } -if (isset($_POST['recurring_invoice_note'])) { - - validateCSRFToken(); - - enforceUserPermission('module_sales', 2); - - $recurring_invoice_id = intval($_POST['recurring_invoice_id']); - $note = escapeSql($_POST['note']); - - // Get Recurring details for logging - $sql = mysqli_query($mysqli,"SELECT recurring_invoice_prefix, recurring_invoice_number, recurring_invoice_client_id FROM recurring_invoices WHERE recurring_invoice_id = $recurring_invoice_id"); - $row = mysqli_fetch_assoc($sql); - $recurring_invoice_prefix = escapeSql($row['recurring_invoice_prefix']); - $recurring_invoice_number = intval($row['recurring_invoice_number']); - $client_id = intval($row['recurring_invoice_client_id']); - - enforceClientAccess(); - - mysqli_query($mysqli,"UPDATE recurring_invoices SET recurring_invoice_note = '$note' WHERE recurring_invoice_id = $recurring_invoice_id"); - - logAudit("Recurring Invoice", "Edit", "$session_name added note to recurring invoice $recurring_invoice_prefix$recurring_invoice_number", $client_id, $recurring_invoice_id); - - flashAlert("Notes added"); - - redirect(); - -} - if (isset($_GET['delete_recurring_invoice_item'])) { validateCSRFToken(); diff --git a/agent/quote.php b/agent/quote.php index 93336c899..199c7e258 100644 --- a/agent/quote.php +++ b/agent/quote.php @@ -432,16 +432,22 @@ if (isset($_GET['quote_id'])) {
Notes: -
- = 2) { ?> - - - - -
+
-
- +
+= 2) { ?> + + +
+ + +
@@ -579,9 +585,13 @@ if (isset($_GET['quote_id'])) {
+ + + + diff --git a/agent/recurring_invoice.php b/agent/recurring_invoice.php index 363cfe91b..8ae82a531 100644 --- a/agent/recurring_invoice.php +++ b/agent/recurring_invoice.php @@ -396,15 +396,23 @@ if (isset($_GET['recurring_invoice_id'])) {
- Notes -
- - - -
+ Notes: +
-
- +
+= 2) { ?> + + +
+ + +
@@ -486,10 +494,14 @@ if (isset($_GET['recurring_invoice_id'])) { + + + + diff --git a/agent/ticket.php b/agent/ticket.php index c84bd5172..fc13735e7 100644 --- a/agent/ticket.php +++ b/agent/ticket.php @@ -416,7 +416,7 @@ if (isset($_GET['ticket_id'])) { Ticket header - everything you need to identify the ticket and know what state it is in, without scrolling or hunting across cards. --> -
+
@@ -932,7 +932,7 @@ if (isset($_GET['ticket_id'])) { -
+
Tasks @@ -1092,7 +1092,7 @@ if (isset($_GET['ticket_id'])) { // is for links - setting the ticket's contact belongs here $can_set_contact = $can_edit_ticket && !$ticket_is_resolved && $client_id; if ($contact_id || $can_set_contact) { ?> -
+
Contact
@@ -1163,7 +1163,7 @@ if (isset($_GET['ticket_id'])) { $can_link = $can_edit_ticket && !$ticket_is_closed; $has_links = $asset_id || $vendor_id || $project_id || $watcher_count; if ($has_links || $can_link) { ?> -
+
Linked
diff --git a/js/inline_notes.js b/js/inline_notes.js new file mode 100644 index 000000000..6a9b80a2e --- /dev/null +++ b/js/inline_notes.js @@ -0,0 +1,85 @@ +/** + * Inline notes. + * + * Replaces the invoice / quote / recurring-invoice note modals with a textarea + * that saves on blur, the same way the client overview Quick Notes field works. + * + * Markup contract: + * + * + * Losing an explicit Save button means the user needs telling that the save + * happened, so each field reports its own state next to the label rather than + * saving silently. + */ +function itflowInlineNotes(textarea) { + if (!textarea || textarea.dataset.inlineNoteReady) { + return; + } + textarea.dataset.inlineNoteReady = '1'; + + // Saving only when the text actually changed avoids a write on every + // click-through, which would otherwise spam the audit log. + let lastSaved = textarea.value; + + const status = document.createElement('span'); + status.className = 'itflow-note-status small ms-2 d-print-none'; + const label = document.querySelector('[data-note-status-for="' + textarea.id + '"]'); + (label || textarea.parentNode).appendChild(status); + + function setStatus(text, cls) { + status.textContent = text; + status.className = 'itflow-note-status small ms-2 d-print-none ' + cls; + } + + function save() { + if (textarea.value === lastSaved) { + return; + } + const payload = { + csrf_token: textarea.dataset.csrf, + note: textarea.value + }; + payload[textarea.dataset.endpoint] = 'TRUE'; + payload[textarea.dataset.idField] = textarea.dataset.id; + + setStatus('Saving...', 'text-muted'); + + itflowPost('ajax.php', payload, function () { + lastSaved = textarea.value; + setStatus('Saved', 'text-success'); + setTimeout(function () { + if (status.textContent === 'Saved') { + setStatus('', ''); + } + }, 2000); + }, function () { + setStatus('Not saved - check your connection', 'text-danger'); + }); + } + + textarea.addEventListener('blur', save); + + // Ctrl/Cmd+Enter saves without leaving the field + textarea.addEventListener('keydown', function (e) { + if ((e.ctrlKey || e.metaKey) && e.key === 'Enter') { + e.preventDefault(); + save(); + } + }); + + // Warn rather than lose the text if the page is closed mid-edit + window.addEventListener('beforeunload', function (e) { + if (textarea.value !== lastSaved) { + e.preventDefault(); + e.returnValue = ''; + } + }); +} + +document.addEventListener('DOMContentLoaded', function () { + document.querySelectorAll('.itflow-inline-note').forEach(itflowInlineNotes); +});