diff --git a/admin/audit_logs.php b/admin/audit_logs.php index 2adffd3c2..999540924 100644 --- a/admin/audit_logs.php +++ b/admin/audit_logs.php @@ -94,12 +94,12 @@ $num_rows = mysqli_fetch_row(mysqli_query($mysqli, "SELECT FOUND_ROWS()")); - + @@ -114,12 +114,12 @@ $num_rows = mysqli_fetch_row(mysqli_query($mysqli, "SELECT FOUND_ROWS()")); - + diff --git a/admin/modals/api/api_key_add.php b/admin/modals/api/api_key_add.php index 7c3d4f8dc..e3b55ce2c 100644 --- a/admin/modals/api/api_key_add.php +++ b/admin/modals/api/api_key_add.php @@ -56,11 +56,11 @@ ob_start(); diff --git a/admin/modals/api/api_key_edit.php b/admin/modals/api/api_key_edit.php index c6749d57b..c3693213f 100644 --- a/admin/modals/api/api_key_edit.php +++ b/admin/modals/api/api_key_edit.php @@ -49,11 +49,11 @@ ob_start(); diff --git a/admin/modals/canned_response/canned_response_add.php b/admin/modals/canned_response/canned_response_add.php index e71ceed93..fd2da30f7 100644 --- a/admin/modals/canned_response/canned_response_add.php +++ b/admin/modals/canned_response/canned_response_add.php @@ -30,11 +30,11 @@ ob_start(); - + diff --git a/admin/modals/canned_response/canned_response_edit.php b/admin/modals/canned_response/canned_response_edit.php index 1b9cfc4c3..031ee7ff0 100644 --- a/admin/modals/canned_response/canned_response_edit.php +++ b/admin/modals/canned_response/canned_response_edit.php @@ -44,11 +44,11 @@ ob_start(); - + diff --git a/admin/modals/payment_provider/payment_provider_add.php b/admin/modals/payment_provider/payment_provider_add.php index 2210485a6..4b7c0adcf 100644 --- a/admin/modals/payment_provider/payment_provider_add.php +++ b/admin/modals/payment_provider/payment_provider_add.php @@ -133,12 +133,12 @@ ob_start(); - + - Select a Category - - + - Select Type - < - + > diff --git a/admin/modals/software_template/software_template_edit.php b/admin/modals/software_template/software_template_edit.php index d1e8216ce..78cf09e95 100644 --- a/admin/modals/software_template/software_template_edit.php +++ b/admin/modals/software_template/software_template_edit.php @@ -70,7 +70,7 @@ ob_start(); < - diff --git a/admin/modals/user/user_archive.php b/admin/modals/user/user_archive.php index 92f9c7e38..9f4d67d4b 100644 --- a/admin/modals/user/user_archive.php +++ b/admin/modals/user/user_archive.php @@ -55,13 +55,13 @@ ob_start(); diff --git a/admin/settings_company.php b/admin/settings_company.php index 7d6ed0ddb..220405dc1 100644 --- a/admin/settings_company.php +++ b/admin/settings_company.php @@ -152,12 +152,12 @@ $company_initials = escapeHtml(initials($company_name)); diff --git a/admin/settings_default.php b/admin/settings_default.php index e9356bf2e..c7cd4ae15 100644 --- a/admin/settings_default.php +++ b/admin/settings_default.php @@ -162,12 +162,12 @@ $net_terms_array = array ( + } ?>> @@ -182,12 +182,12 @@ $net_terms_array = array ( + } ?>> diff --git a/agent/ajax.php b/agent/ajax.php index 760562999..67cfd38ac 100644 --- a/agent/ajax.php +++ b/agent/ajax.php @@ -561,7 +561,7 @@ if (isset($_GET['get_client_contacts'])) { $contact_sql = mysqli_query( $mysqli, - "SELECT contact_id, contact_name, contact_title, contact_email, contact_primary, contact_important, contact_technical FROM contacts + "SELECT contact_id, contact_name, contact_title, contact_email, contact_photo, contact_primary, contact_important, contact_technical FROM contacts LEFT JOIN clients on contact_client_id = client_id WHERE contacts.contact_archived_at IS NULL AND contact_client_id = $client_id " . clientScopeSql('contact_client_id') . " @@ -572,6 +572,10 @@ if (isset($_GET['get_client_contacts'])) { $response['contacts'] = []; while ($row = mysqli_fetch_assoc($contact_sql)) { + // For the picker: initials, or the photo when there is one + $row['contact_initials'] = personInitials($row['contact_name']); + $row['contact_photo_url'] = $row['contact_photo'] ? "/uploads/clients/$client_id/" . rawurlencode($row['contact_photo']) : ''; + unset($row['contact_photo']); $response['contacts'][] = $row; } @@ -602,6 +606,7 @@ if (isset($_GET['get_client_assets'])) { $response['assets'] = []; while ($row = mysqli_fetch_assoc($asset_sql)) { + $row['asset_icon'] = getAssetIcon($row['asset_type']); $response['assets'][] = $row; } diff --git a/agent/assets.php b/agent/assets.php index cf9518680..80c98e602 100644 --- a/agent/assets.php +++ b/agent/assets.php @@ -289,7 +289,7 @@ $num_rows = mysqli_fetch_row(mysqli_query($mysqli, "SELECT FOUND_ROWS()")); - + @@ -315,7 +315,7 @@ $num_rows = mysqli_fetch_row(mysqli_query($mysqli, "SELECT FOUND_ROWS()")); - + diff --git a/agent/certificates.php b/agent/certificates.php index dc3aba372..69127855c 100644 --- a/agent/certificates.php +++ b/agent/certificates.php @@ -118,7 +118,7 @@ $num_rows = mysqli_fetch_row(mysqli_query($mysqli, "SELECT FOUND_ROWS()")); - + diff --git a/agent/clients.php b/agent/clients.php index 1b7e536f0..a6f330e4c 100644 --- a/agent/clients.php +++ b/agent/clients.php @@ -372,7 +372,7 @@ $total_leads = intval($row['leads']); diff --git a/agent/contacts.php b/agent/contacts.php index 8a28e5725..6eaf1803f 100644 --- a/agent/contacts.php +++ b/agent/contacts.php @@ -136,7 +136,7 @@ $num_rows = mysqli_fetch_row(mysqli_query($mysqli, "SELECT FOUND_ROWS()")); - + @@ -190,7 +190,7 @@ $num_rows = mysqli_fetch_row(mysqli_query($mysqli, "SELECT FOUND_ROWS()")); - + diff --git a/agent/credentials.php b/agent/credentials.php index fc559bbd1..17b9d702a 100644 --- a/agent/credentials.php +++ b/agent/credentials.php @@ -150,7 +150,7 @@ $num_rows = mysqli_fetch_row(mysqli_query($mysqli, "SELECT FOUND_ROWS()")); - + @@ -198,7 +198,7 @@ $num_rows = mysqli_fetch_row(mysqli_query($mysqli, "SELECT FOUND_ROWS()")); - + diff --git a/agent/domains.php b/agent/domains.php index faba07e2c..6897f2459 100644 --- a/agent/domains.php +++ b/agent/domains.php @@ -128,7 +128,7 @@ $num_rows = mysqli_fetch_row(mysqli_query($mysqli, "SELECT FOUND_ROWS()")); - + diff --git a/agent/expenses.php b/agent/expenses.php index 98a85115d..302cf9403 100644 --- a/agent/expenses.php +++ b/agent/expenses.php @@ -166,12 +166,12 @@ $num_rows = mysqli_fetch_row(mysqli_query($mysqli, "SELECT FOUND_ROWS()")); - + diff --git a/agent/income.php b/agent/income.php index 3db486151..4b252d2cb 100644 --- a/agent/income.php +++ b/agent/income.php @@ -216,7 +216,7 @@ $summary_total_income = floatval($row['total_income']); - + diff --git a/agent/invoices.php b/agent/invoices.php index 61b9f4e01..b82a21236 100644 --- a/agent/invoices.php +++ b/agent/invoices.php @@ -218,12 +218,12 @@ $num_rows = mysqli_fetch_row(mysqli_query($mysqli, "SELECT FOUND_ROWS()")); - + diff --git a/agent/js/share_modal.js b/agent/js/share_modal.js index c888ca43d..7a9ff6f24 100644 --- a/agent/js/share_modal.js +++ b/agent/js/share_modal.js @@ -12,12 +12,6 @@ function populateShareModal(client_id, item_type, item_ref_id) { // Show form and generate button document.getElementById("div_share_link_form").hidden = false; document.getElementById("div_share_link_generate").hidden = false; - - initTomSelect(document.getElementById('share_email'), { - create: true, - placeholder: 'Select or type a value', - allowEmptyOption: true - }); } function generateShareLink() { diff --git a/agent/js/ticket_change_client.js b/agent/js/ticket_change_client.js index 5d8ba0441..d0c1d32a7 100644 --- a/agent/js/ticket_change_client.js +++ b/agent/js/ticket_change_client.js @@ -42,9 +42,6 @@ itflowReady(function () { } else { contactSelect.appendChild(new Option('No contacts found', '')); } - - // Let Tom Select re-read the replaced option list - refreshTomSelect(contactSelect); }) .catch(function (error) { console.error('AJAX Error:', error); diff --git a/agent/js/tickets_add_modal.js b/agent/js/tickets_add_modal.js index 1fd8a616a..d89fb366c 100644 --- a/agent/js/tickets_add_modal.js +++ b/agent/js/tickets_add_modal.js @@ -6,7 +6,6 @@ // touching it, rather than assuming the full set is present. // Client selected listener -// We seem to have to use jQuery to listen for events, as the client input is a select2 component? // Wrapped in an IIFE because a modal can be opened, closed and opened again in one // page load, which re-runs this file - a top-level const would throw on the second @@ -73,12 +72,10 @@ return null; } - // innerHTML rather than removing options one by one, which leaves empty optgroups behind + // innerHTML rather than removing options one by one, which leaves empty + // optgroups behind. The picker (js/itflow_select.js) redraws by itself. dropdown.innerHTML = ''; - // A multi-select keeps showing its old selections until select2 is told the value changed - clearTomSelect(dropdown); - if (placeholderLabel !== null) { dropdown[dropdown.length] = new Option(placeholderLabel, placeholderValue); } @@ -86,13 +83,6 @@ return dropdown; } - // Redraws a select2 component after its options have been replaced - function refreshDropdown(dropdown) { - if (dropdown) { - refreshTomSelect(dropdown); - } - } - // Re-applies the value the modal was opened with (e.g. ticket_add.php?project_id=4), // which can only be selected once the options it refers to exist function applyPreselection(dropdown) { @@ -126,13 +116,17 @@ } // Adds an option to an optgroup - function appendGroupedOption(group, label, value) { + function appendGroupedOption(group, label, value, icon) { if (!group) { return; } - group.appendChild(new Option(label, value)); + const option = new Option(label, value); + if (icon) { + option.dataset.icon = 'fas fa-fw fa-' + icon; + } + group.appendChild(option); } // Builds the asset label as "Name - Make Model - (Contact)", matching how assets read elsewhere @@ -181,20 +175,28 @@ // Watchers is a tags field - any address can be typed in, these are just the handy ones const watchersDropdown = resetDropdown("watchersSelect", null, null); - // Populate dropdown + // Populate dropdown - the name, with title and role on the line under it contacts.forEach(contact => { - var appendText = ""; + const details = []; if (contact.contact_title) { - appendText = " - " + contact.contact_title; + details.push(contact.contact_title); } if (contact.contact_primary == "1") { - appendText = appendText + " (Primary)"; + details.push("Primary"); } else if (contact.contact_technical == "1") { - appendText = appendText + " (Technical)"; + details.push("Technical"); } if (contactSelectDropdown) { - contactSelectDropdown[contactSelectDropdown.length] = new Option(contact.contact_name + appendText, contact.contact_id); + const option = new Option(contact.contact_name, contact.contact_id); + option.dataset.description = details.join(', '); + option.dataset.initials = contact.contact_initials; + option.dataset.shape = 'circle'; + option.dataset.tone = 'secondary'; + if (contact.contact_photo_url) { + option.dataset.image = contact.contact_photo_url; + } + contactSelectDropdown[contactSelectDropdown.length] = option; } if (watchersDropdown && contact.contact_email) { @@ -214,9 +216,6 @@ applyPreselection(contactSelectDropdown); - refreshDropdown(contactSelectDropdown); - refreshDropdown(watchersDropdown); - } ); } @@ -259,15 +258,12 @@ const assetLabel = buildAssetLabel(asset); - appendGroupedOption(assetGroup, assetLabel, asset.asset_id); - appendGroupedOption(additionalAssetGroup, assetLabel, asset.asset_id); + appendGroupedOption(assetGroup, assetLabel, asset.asset_id, asset.asset_icon); + appendGroupedOption(additionalAssetGroup, assetLabel, asset.asset_id, asset.asset_icon); }); applyPreselection(assetSelectDropdown); - refreshDropdown(assetSelectDropdown); - refreshDropdown(additionalAssetsDropdown); - } ); } @@ -300,8 +296,6 @@ applyPreselection(locationSelectDropdown); - refreshDropdown(locationSelectDropdown); - } ); } @@ -334,8 +328,6 @@ applyPreselection(vendorSelectDropdown); - refreshDropdown(vendorSelectDropdown); - } ); } @@ -368,8 +360,6 @@ applyPreselection(projectSelectDropdown); - refreshDropdown(projectSelectDropdown); - } ); } diff --git a/agent/locations.php b/agent/locations.php index e01076877..68e8bffd5 100644 --- a/agent/locations.php +++ b/agent/locations.php @@ -122,7 +122,7 @@ $num_rows = mysqli_fetch_row(mysqli_query($mysqli, "SELECT FOUND_ROWS()")); @@ -151,7 +151,7 @@ $num_rows = mysqli_fetch_row(mysqli_query($mysqli, "SELECT FOUND_ROWS()")); - + diff --git a/agent/modals/asset/asset_add.php b/agent/modals/asset/asset_add.php index 9ba0eac98..3425cbc87 100644 --- a/agent/modals/asset/asset_add.php +++ b/agent/modals/asset/asset_add.php @@ -232,7 +232,7 @@ ob_start(); - + > diff --git a/agent/modals/asset/asset_bulk_add_ticket.php b/agent/modals/asset/asset_bulk_add_ticket.php index 0e1c2fdfb..f6597be6a 100644 --- a/agent/modals/asset/asset_bulk_add_ticket.php +++ b/agent/modals/asset/asset_bulk_add_ticket.php @@ -41,10 +41,10 @@ ob_start();
@@ -58,13 +58,13 @@ ob_start(); @@ -84,13 +84,13 @@ ob_start(); $sql = mysqli_query( $mysqli, - "SELECT user_id, user_name FROM users + "SELECT user_id, user_name, user_avatar FROM users WHERE user_type = 1 AND user_status = 1 AND user_archived_at IS NULL ORDER BY user_name ASC" ); while ($row = mysqli_fetch_assoc($sql)) { $user_id = intval($row['user_id']); $user_name = escapeHtml($row['user_name']); ?> - + diff --git a/agent/modals/asset/asset_bulk_assign_contact.php b/agent/modals/asset/asset_bulk_assign_contact.php index e19f1bc22..5599bd19a 100644 --- a/agent/modals/asset/asset_bulk_assign_contact.php +++ b/agent/modals/asset/asset_bulk_assign_contact.php @@ -33,12 +33,12 @@ ob_start(); - + diff --git a/agent/modals/asset/asset_bulk_assign_tags.php b/agent/modals/asset/asset_bulk_assign_tags.php index f1a222907..539681897 100644 --- a/agent/modals/asset/asset_bulk_assign_tags.php +++ b/agent/modals/asset/asset_bulk_assign_tags.php @@ -33,12 +33,12 @@ ob_start(); diff --git a/agent/modals/asset/asset_bulk_edit_status.php b/agent/modals/asset/asset_bulk_edit_status.php index ed9026af3..fc1715eee 100644 --- a/agent/modals/asset/asset_bulk_edit_status.php +++ b/agent/modals/asset/asset_bulk_edit_status.php @@ -28,7 +28,7 @@ ob_start(); - + > diff --git a/agent/modals/asset/asset_bulk_transfer_client.php b/agent/modals/asset/asset_bulk_transfer_client.php index 604150eeb..712fabe7d 100644 --- a/agent/modals/asset/asset_bulk_transfer_client.php +++ b/agent/modals/asset/asset_bulk_transfer_client.php @@ -34,13 +34,13 @@ ob_start(); @@ -216,7 +216,7 @@ ob_start(); - diff --git a/agent/modals/asset/asset_edit.php b/agent/modals/asset/asset_edit.php index 98c3a48fc..306a27f6f 100644 --- a/agent/modals/asset/asset_edit.php +++ b/agent/modals/asset/asset_edit.php @@ -229,7 +229,7 @@ ob_start(); - @@ -257,7 +257,7 @@ ob_start(); - @@ -455,12 +455,12 @@ ob_start(); diff --git a/agent/modals/asset/asset_export.php b/agent/modals/asset/asset_export.php index 01a6daf65..ad3ba8228 100644 --- a/agent/modals/asset/asset_export.php +++ b/agent/modals/asset/asset_export.php @@ -82,12 +82,12 @@ ob_start(); - + diff --git a/agent/modals/asset/asset_interface_add.php b/agent/modals/asset/asset_interface_add.php index 4e9145a0a..0d0e0a49a 100644 --- a/agent/modals/asset/asset_interface_add.php +++ b/agent/modals/asset/asset_interface_add.php @@ -58,7 +58,7 @@ ob_start(); - + > diff --git a/agent/modals/asset/asset_interface_bulk_edit_type.php b/agent/modals/asset/asset_interface_bulk_edit_type.php index 8b938c39e..5df59bdef 100644 --- a/agent/modals/asset/asset_interface_bulk_edit_type.php +++ b/agent/modals/asset/asset_interface_bulk_edit_type.php @@ -31,7 +31,7 @@ ob_start(); - + > diff --git a/agent/modals/asset/asset_interface_edit.php b/agent/modals/asset/asset_interface_edit.php index 376425177..6c91f2d1b 100644 --- a/agent/modals/asset/asset_interface_edit.php +++ b/agent/modals/asset/asset_interface_edit.php @@ -125,7 +125,7 @@ ob_start(); - diff --git a/agent/modals/asset/asset_interface_multiple_add.php b/agent/modals/asset/asset_interface_multiple_add.php index c59b9a12c..8d769f6aa 100644 --- a/agent/modals/asset/asset_interface_multiple_add.php +++ b/agent/modals/asset/asset_interface_multiple_add.php @@ -39,7 +39,7 @@ - + > diff --git a/agent/modals/asset/asset_note_add.php b/agent/modals/asset/asset_note_add.php index df7c6195a..69cac2dab 100644 --- a/agent/modals/asset/asset_note_add.php +++ b/agent/modals/asset/asset_note_add.php @@ -35,7 +35,7 @@ ob_start(); diff --git a/agent/modals/calendar/calendar_event_add.php b/agent/modals/calendar/calendar_event_add.php index 85cf59b4d..a5edeed12 100644 --- a/agent/modals/calendar/calendar_event_add.php +++ b/agent/modals/calendar/calendar_event_add.php @@ -147,13 +147,13 @@ - + diff --git a/agent/modals/calendar/calendar_event_edit.php b/agent/modals/calendar/calendar_event_edit.php index 51243c024..1b2a6dfe6 100644 --- a/agent/modals/calendar/calendar_event_edit.php +++ b/agent/modals/calendar/calendar_event_edit.php @@ -193,13 +193,13 @@ ob_start(); - + diff --git a/agent/modals/certificate/certificate_add.php b/agent/modals/certificate/certificate_add.php index ea26f4b38..1b3538501 100644 --- a/agent/modals/certificate/certificate_add.php +++ b/agent/modals/certificate/certificate_add.php @@ -46,11 +46,11 @@ ob_start(); - + diff --git a/agent/modals/certificate/certificate_export.php b/agent/modals/certificate/certificate_export.php index 928051eb3..6052eb1ce 100644 --- a/agent/modals/certificate/certificate_export.php +++ b/agent/modals/certificate/certificate_export.php @@ -58,12 +58,12 @@ ob_start(); - - - - + + + + @@ -56,13 +56,13 @@ ob_start(); @@ -85,13 +85,13 @@ ob_start(); $sql = mysqli_query( $mysqli, - "SELECT user_id, user_name FROM users + "SELECT user_id, user_name, user_avatar FROM users WHERE user_type = 1 AND user_status = 1 AND user_archived_at IS NULL ORDER BY user_name ASC" ); while ($row = mysqli_fetch_assoc($sql)) { $user_id = intval($row['user_id']); $user_name = escapeHtml($row['user_name']); ?> - + diff --git a/agent/modals/client/client_bulk_assign_tags.php b/agent/modals/client/client_bulk_assign_tags.php index d9191de55..9c41fb281 100644 --- a/agent/modals/client/client_bulk_assign_tags.php +++ b/agent/modals/client/client_bulk_assign_tags.php @@ -52,12 +52,12 @@ ob_start(); diff --git a/agent/modals/client/client_bulk_edit_referral.php b/agent/modals/client/client_bulk_edit_referral.php index 0c7712cb5..7732c66d3 100644 --- a/agent/modals/client/client_bulk_edit_referral.php +++ b/agent/modals/client/client_bulk_edit_referral.php @@ -27,10 +27,10 @@ ob_start(); diff --git a/agent/modals/client/client_credit_add.php b/agent/modals/client/client_credit_add.php index 26c46e981..81be66557 100644 --- a/agent/modals/client/client_credit_add.php +++ b/agent/modals/client/client_credit_add.php @@ -58,12 +58,12 @@ - + - Select Referral - '$client_created_at' OR category_archived_at IS NULL) ORDER BY category_name ASC"); + $referral_sql = mysqli_query($mysqli, "SELECT category_name, category_color, category_icon FROM categories WHERE category_type = 'Referral' AND (category_archived_at > '$client_created_at' OR category_archived_at IS NULL) ORDER BY category_name ASC"); while ($row = mysqli_fetch_assoc($referral_sql)) { $referral = escapeHtml($row['category_name']); ?> @@ -159,12 +159,12 @@ ob_start(); diff --git a/agent/modals/client/client_export.php b/agent/modals/client/client_export.php index 1e184803a..f932a3637 100644 --- a/agent/modals/client/client_export.php +++ b/agent/modals/client/client_export.php @@ -114,12 +114,12 @@ ob_start(); - + diff --git a/agent/modals/contact/contact_edit.php b/agent/modals/contact/contact_edit.php index 8a921d24c..a2867d977 100644 --- a/agent/modals/contact/contact_edit.php +++ b/agent/modals/contact/contact_edit.php @@ -307,12 +307,12 @@ ob_start(); diff --git a/agent/modals/contact/contact_export.php b/agent/modals/contact/contact_export.php index c35ac838a..c3d55552d 100644 --- a/agent/modals/contact/contact_export.php +++ b/agent/modals/contact/contact_export.php @@ -61,12 +61,12 @@ ob_start(); - + diff --git a/agent/modals/contact/contact_link_asset.php b/agent/modals/contact/contact_link_asset.php index 83b5fd044..317973beb 100644 --- a/agent/modals/contact/contact_link_asset.php +++ b/agent/modals/contact/contact_link_asset.php @@ -35,7 +35,7 @@ ob_start(); - + diff --git a/agent/modals/contact/contact_note_add.php b/agent/modals/contact/contact_note_add.php index 1df866387..10e9942e2 100644 --- a/agent/modals/contact/contact_note_add.php +++ b/agent/modals/contact/contact_note_add.php @@ -35,7 +35,7 @@ ob_start(); diff --git a/agent/modals/credential/credential_add.php b/agent/modals/credential/credential_add.php index ef8aae055..4822b7399 100644 --- a/agent/modals/credential/credential_add.php +++ b/agent/modals/credential/credential_add.php @@ -50,11 +50,11 @@ ob_start(); - + @@ -139,14 +139,14 @@ ob_start(); - Select Asset - + value=""> - + diff --git a/agent/modals/credential/credential_bulk_assign_tags.php b/agent/modals/credential/credential_bulk_assign_tags.php index 911bf0c68..7ea5e5e55 100644 --- a/agent/modals/credential/credential_bulk_assign_tags.php +++ b/agent/modals/credential/credential_bulk_assign_tags.php @@ -32,12 +32,12 @@ ob_start(); diff --git a/agent/modals/credential/credential_edit.php b/agent/modals/credential/credential_edit.php index def62337b..8e496e8cb 100644 --- a/agent/modals/credential/credential_edit.php +++ b/agent/modals/credential/credential_edit.php @@ -158,12 +158,12 @@ ob_start(); - + @@ -177,7 +177,7 @@ ob_start(); - + @@ -211,12 +211,12 @@ ob_start(); diff --git a/agent/modals/credential/credential_export.php b/agent/modals/credential/credential_export.php index ce4d68470..517706f3b 100644 --- a/agent/modals/credential/credential_export.php +++ b/agent/modals/credential/credential_export.php @@ -58,12 +58,12 @@ ob_start(); - + diff --git a/agent/modals/document/document_link_asset.php b/agent/modals/document/document_link_asset.php index 4cc263057..a7e6a7e0c 100644 --- a/agent/modals/document/document_link_asset.php +++ b/agent/modals/document/document_link_asset.php @@ -35,7 +35,7 @@ ob_start(); - + diff --git a/agent/modals/document/document_link_contact.php b/agent/modals/document/document_link_contact.php index 288179909..6946a3a8c 100644 --- a/agent/modals/document/document_link_contact.php +++ b/agent/modals/document/document_link_contact.php @@ -35,7 +35,7 @@ ob_start(); - + diff --git a/agent/modals/domain/domain_add.php b/agent/modals/domain/domain_add.php index 856c046b8..eaa422f21 100644 --- a/agent/modals/domain/domain_add.php +++ b/agent/modals/domain/domain_add.php @@ -44,11 +44,11 @@ ob_start(); - + diff --git a/agent/modals/domain/domain_export.php b/agent/modals/domain/domain_export.php index 520fe13ae..90447dcf7 100644 --- a/agent/modals/domain/domain_export.php +++ b/agent/modals/domain/domain_export.php @@ -58,12 +58,12 @@ ob_start(); @@ -153,12 +153,12 @@ ob_start(); - + @@ -220,12 +220,12 @@ ob_start(); diff --git a/agent/modals/location/location_bulk_assign_tags.php b/agent/modals/location/location_bulk_assign_tags.php index 99cc95960..4a84c1069 100644 --- a/agent/modals/location/location_bulk_assign_tags.php +++ b/agent/modals/location/location_bulk_assign_tags.php @@ -32,12 +32,12 @@ ob_start(); diff --git a/agent/modals/location/location_edit.php b/agent/modals/location/location_edit.php index 1bec56e1d..b294bdaa4 100644 --- a/agent/modals/location/location_edit.php +++ b/agent/modals/location/location_edit.php @@ -170,7 +170,7 @@ ob_start(); '$location_created_at' OR contact_archived_at IS NULL) AND contact_client_id = $client_id ORDER BY contact_archived_at ASC, contact_name ASC"); + $sql_contacts = mysqli_query($mysqli, "SELECT contact_archived_at, contact_id, contact_name, contact_client_id, contact_photo, contact_title FROM contacts WHERE (contact_archived_at > '$location_created_at' OR contact_archived_at IS NULL) AND contact_client_id = $client_id ORDER BY contact_archived_at ASC, contact_name ASC"); while ($row = mysqli_fetch_assoc($sql_contacts)) { $contact_id_select = intval($row['contact_id']); $contact_name_select = escapeHtml($row['contact_name']); @@ -182,7 +182,7 @@ ob_start(); } ?> - + @@ -243,12 +243,12 @@ ob_start(); diff --git a/agent/modals/location/location_export.php b/agent/modals/location/location_export.php index 850dc0233..99d12da69 100644 --- a/agent/modals/location/location_export.php +++ b/agent/modals/location/location_export.php @@ -58,12 +58,12 @@ ob_start(); - + diff --git a/agent/modals/network/network_add.php b/agent/modals/network/network_add.php index 77fd38930..f612bcb1a 100644 --- a/agent/modals/network/network_add.php +++ b/agent/modals/network/network_add.php @@ -50,11 +50,11 @@ ob_start(); - + diff --git a/agent/modals/network/network_export.php b/agent/modals/network/network_export.php index da3a4f005..0c90bc723 100644 --- a/agent/modals/network/network_export.php +++ b/agent/modals/network/network_export.php @@ -58,12 +58,12 @@ ob_start(); - + @@ -91,13 +91,13 @@ ob_start(); $sql = mysqli_query( $mysqli, - "SELECT user_id, user_name FROM users + "SELECT user_id, user_name, user_avatar FROM users WHERE user_role_id > 1 AND user_status = 1 AND user_archived_at IS NULL ORDER BY user_name ASC" ); while ($row = mysqli_fetch_assoc($sql)) { $user_id = intval($row['user_id']); $user_name = escapeHtml($row['user_name']); ?> - + diff --git a/agent/modals/project/project_edit.php b/agent/modals/project/project_edit.php index a78643e00..0c70758d7 100644 --- a/agent/modals/project/project_edit.php +++ b/agent/modals/project/project_edit.php @@ -73,13 +73,13 @@ ob_start(); 1 AND user_status = 1 AND user_archived_at IS NULL ORDER BY user_name ASC" ); while ($row = mysqli_fetch_assoc($sql_project_managers_select)) { $user_id_select = intval($row['user_id']); $user_name_select = escapeHtml($row['user_name']); ?> - + diff --git a/agent/modals/quote/quote_add.php b/agent/modals/quote/quote_add.php index f863c0105..8f5d4fd85 100644 --- a/agent/modals/quote/quote_add.php +++ b/agent/modals/quote/quote_add.php @@ -29,12 +29,12 @@ ob_start(); - + @@ -59,12 +59,12 @@ ob_start(); - + diff --git a/agent/modals/quote/quote_copy.php b/agent/modals/quote/quote_copy.php index 1082ffc10..129e9e84a 100644 --- a/agent/modals/quote/quote_copy.php +++ b/agent/modals/quote/quote_copy.php @@ -37,12 +37,12 @@ ob_start(); diff --git a/agent/modals/quote/quote_edit.php b/agent/modals/quote/quote_edit.php index a848dddf2..6437b1398 100644 --- a/agent/modals/quote/quote_edit.php +++ b/agent/modals/quote/quote_edit.php @@ -61,12 +61,12 @@ ob_start(); diff --git a/agent/modals/rack/rack_device_add.php b/agent/modals/rack/rack_device_add.php index 56aecdf58..6e430fabb 100644 --- a/agent/modals/rack/rack_device_add.php +++ b/agent/modals/rack/rack_device_add.php @@ -52,12 +52,12 @@ ob_start(); $assigned_assets_list = empty($assigned_assets_list) ? '0' : $assigned_assets_list; // Fetch assets not assigned to any rack - $sql_assets = mysqli_query($mysqli, "SELECT asset_id, asset_name FROM assets WHERE asset_archived_at IS NULL AND asset_client_id = $client_id AND asset_id NOT IN ($assigned_assets_list) ORDER BY asset_name ASC"); + $sql_assets = mysqli_query($mysqli, "SELECT asset_id, asset_name, asset_type, asset_make, asset_model FROM assets WHERE asset_archived_at IS NULL AND asset_client_id = $client_id AND asset_id NOT IN ($assigned_assets_list) ORDER BY asset_name ASC"); while ($row = mysqli_fetch_assoc($sql_assets)) { $asset_id = intval($row['asset_id']); $asset_name = escapeHtml($row['asset_name']); ?> - + diff --git a/agent/modals/rack/rack_edit.php b/agent/modals/rack/rack_edit.php index fa8a7b67e..9227ffb4f 100644 --- a/agent/modals/rack/rack_edit.php +++ b/agent/modals/rack/rack_edit.php @@ -64,7 +64,7 @@ ob_start(); - diff --git a/agent/modals/recurring_expense/recurring_expense_add.php b/agent/modals/recurring_expense/recurring_expense_add.php index a80b5593c..ab9559f3f 100644 --- a/agent/modals/recurring_expense/recurring_expense_add.php +++ b/agent/modals/recurring_expense/recurring_expense_add.php @@ -157,12 +157,12 @@ ob_start(); - + - Client (Optional) - - + '$recurring_expense_created_at' OR category_archived_at IS NULL) ORDER BY category_name ASC"); + $sql_select = mysqli_query($mysqli, "SELECT category_id, category_name, category_color, category_icon FROM categories WHERE category_type = 'Expense' AND (category_archived_at > '$recurring_expense_created_at' OR category_archived_at IS NULL) ORDER BY category_name ASC"); while ($row = mysqli_fetch_assoc($sql_select)) { $category_id_select = intval($row['category_id']); $category_name_select = escapeHtml($row['category_name']); ?> - + - Select Client - - + - Select a Client - - + - Category - - + - Category - '$recurring_invoice_created_at' OR category_archived_at IS NULL) ORDER BY category_name ASC"); + $sql_income_category = mysqli_query($mysqli, "SELECT category_id, category_name, category_color, category_icon FROM categories WHERE category_type = 'Income' AND (category_archived_at > '$recurring_invoice_created_at' OR category_archived_at IS NULL) ORDER BY category_name ASC"); while ($row = mysqli_fetch_assoc($sql_income_category)) { $category_id_select = intval($row['category_id']); $category_name_select = escapeHtml($row['category_name']); ?> - + - Select a Client - - + @@ -102,10 +102,10 @@ ob_start();
@@ -119,13 +119,13 @@ ob_start(); @@ -149,13 +149,13 @@ ob_start(); $sql = mysqli_query( $mysqli, - "SELECT user_id, user_name FROM users + "SELECT user_id, user_name, user_avatar FROM users WHERE user_type = 1 AND user_status = 1 AND user_archived_at IS NULL ORDER BY user_name ASC" ); while ($row = mysqli_fetch_assoc($sql)) { $user_id = intval($row['user_id']); $user_name = escapeHtml($row['user_name']); ?> - + diff --git a/agent/modals/recurring_ticket/recurring_ticket_bulk_agent_edit.php b/agent/modals/recurring_ticket/recurring_ticket_bulk_agent_edit.php index 02693ce8b..e961de21d 100644 --- a/agent/modals/recurring_ticket/recurring_ticket_bulk_agent_edit.php +++ b/agent/modals/recurring_ticket/recurring_ticket_bulk_agent_edit.php @@ -27,7 +27,7 @@ ob_start(); diff --git a/agent/modals/recurring_ticket/recurring_ticket_bulk_category_edit.php b/agent/modals/recurring_ticket/recurring_ticket_bulk_category_edit.php index 8426671a7..854826d1f 100644 --- a/agent/modals/recurring_ticket/recurring_ticket_bulk_category_edit.php +++ b/agent/modals/recurring_ticket/recurring_ticket_bulk_category_edit.php @@ -28,13 +28,13 @@ ob_start(); diff --git a/agent/modals/recurring_ticket/recurring_ticket_bulk_priority_edit.php b/agent/modals/recurring_ticket/recurring_ticket_bulk_priority_edit.php index 5e1d10cc4..de6ce70cd 100644 --- a/agent/modals/recurring_ticket/recurring_ticket_bulk_priority_edit.php +++ b/agent/modals/recurring_ticket/recurring_ticket_bulk_priority_edit.php @@ -26,10 +26,10 @@ ob_start();
diff --git a/agent/modals/recurring_ticket/recurring_ticket_edit.php b/agent/modals/recurring_ticket/recurring_ticket_edit.php index 24494ff92..c5d0a2861 100644 --- a/agent/modals/recurring_ticket/recurring_ticket_edit.php +++ b/agent/modals/recurring_ticket/recurring_ticket_edit.php @@ -114,10 +114,10 @@ ob_start();
@@ -131,13 +131,13 @@ ob_start(); @@ -157,7 +157,7 @@ ob_start(); @@ -199,7 +199,7 @@ ob_start(); @@ -269,13 +269,13 @@ ob_start(); - + + > diff --git a/agent/modals/revenue/revenue_add.php b/agent/modals/revenue/revenue_add.php index 9b0349c1b..ea1276635 100644 --- a/agent/modals/revenue/revenue_add.php +++ b/agent/modals/revenue/revenue_add.php @@ -82,12 +82,12 @@ ob_start(); - + - Select Category - '$revenue_created_at' OR category_archived_at IS NULL) ORDER BY category_name ASC"); + $sql_category = mysqli_query($mysqli, "SELECT category_id, category_name, category_color, category_icon FROM categories WHERE category_type = 'Income' AND (category_archived_at > '$revenue_created_at' OR category_archived_at IS NULL) ORDER BY category_name ASC"); while ($row = mysqli_fetch_assoc($sql_category)) { $category_id_select = intval($row['category_id']); $category_name = escapeHtml($row['category_name']); ?> - + - Select Client - - + @@ -124,11 +124,11 @@ ob_start(); @@ -172,11 +172,11 @@ ob_start(); diff --git a/agent/modals/service/service_edit.php b/agent/modals/service/service_edit.php index 9cdbbc0f8..983ee8f6d 100644 --- a/agent/modals/service/service_edit.php +++ b/agent/modals/service/service_edit.php @@ -183,14 +183,14 @@ ob_start(); // Get all contacts // NOTE: These are called $sql_all and $row_all for a reason - anything overwriting $sql or $row will break the current while loop we are in from client_services.php - $sql_all = mysqli_query($mysqli, "SELECT contact_id, contact_name FROM contacts WHERE (contact_archived_at > '$service_created_at' OR contact_archived_at IS NULL) AND contact_client_id = $client_id"); + $sql_all = mysqli_query($mysqli, "SELECT contact_id, contact_name, contact_client_id, contact_photo, contact_title FROM contacts WHERE (contact_archived_at > '$service_created_at' OR contact_archived_at IS NULL) AND contact_client_id = $client_id"); while ($row_all = mysqli_fetch_assoc($sql_all)) { $contact_id = intval($row_all['contact_id']); $contact_name = escapeHtml($row_all['contact_name']); if (in_array($contact_id, $selected_ids)) { - echo ""; + echo ""; } else{ echo ""; @@ -258,13 +258,13 @@ ob_start(); '$service_created_at' OR asset_archived_at IS NULL) AND asset_client_id = $client_id"); + $sql_all = mysqli_query($mysqli, "SELECT asset_id, asset_name, asset_type, asset_make, asset_model FROM assets WHERE (asset_archived_at > '$service_created_at' OR asset_archived_at IS NULL) AND asset_client_id = $client_id"); while ($row_all = mysqli_fetch_assoc($sql_all)) { $asset_id = intval($row_all['asset_id']); $asset_name = escapeHtml($row_all['asset_name']); if (in_array($asset_id, $selected_ids)) { - echo ""; + echo ""; } else{ echo ""; diff --git a/agent/modals/share_modal.php b/agent/modals/share_modal.php index abd29fdfb..c8779a01f 100644 --- a/agent/modals/share_modal.php +++ b/agent/modals/share_modal.php @@ -20,17 +20,17 @@
- '' AND contact_archived_at IS NULL ORDER BY contact_name ASC"); + $sql_client_contacts_select = mysqli_query($mysqli, "SELECT contact_email, contact_id, contact_name, contact_client_id, contact_photo, contact_title FROM contacts WHERE contact_client_id = $client_id AND contact_email <> '' AND contact_archived_at IS NULL ORDER BY contact_name ASC"); while ($row = mysqli_fetch_assoc($sql_client_contacts_select)) { $contact_id_select = intval($row['contact_id']); $contact_name_select = escapeHtml($row['contact_name']); $contact_email_select = escapeHtml($row['contact_email']); ?> - + - Select Client - - + @@ -85,7 +85,7 @@ ob_start(); < - + >
diff --git a/agent/modals/software/software_add_from_template.php b/agent/modals/software/software_add_from_template.php index 6a8a3c4d8..5ec87bd5e 100644 --- a/agent/modals/software/software_add_from_template.php +++ b/agent/modals/software/software_add_from_template.php @@ -29,11 +29,11 @@ ob_start(); - + diff --git a/agent/modals/software/software_edit.php b/agent/modals/software/software_edit.php index c79124718..213d260d8 100644 --- a/agent/modals/software/software_edit.php +++ b/agent/modals/software/software_edit.php @@ -103,7 +103,7 @@ ob_start(); < - diff --git a/agent/modals/software/software_export.php b/agent/modals/software/software_export.php index d2148db4d..e005f6104 100644 --- a/agent/modals/software/software_export.php +++ b/agent/modals/software/software_export.php @@ -58,12 +58,12 @@ ob_start(); @@ -107,10 +107,10 @@ ob_start();
@@ -124,12 +124,12 @@ ob_start(); @@ -155,13 +155,13 @@ ob_start(); $sql = mysqli_query( $mysqli, - "SELECT user_id, user_name FROM users + "SELECT user_id, user_name, user_avatar FROM users WHERE user_type = 1 AND user_status = 1 AND user_archived_at IS NULL ORDER BY user_name ASC" ); while ($row = mysqli_fetch_assoc($sql)) { $user_id = intval($row['user_id']); $user_name = escapeHtml($row['user_name']); ?> - + diff --git a/agent/modals/ticket/ticket_add_watcher.php b/agent/modals/ticket/ticket_add_watcher.php index d0c501371..32eeae060 100644 --- a/agent/modals/ticket/ticket_add_watcher.php +++ b/agent/modals/ticket/ticket_add_watcher.php @@ -34,13 +34,13 @@ ob_start(); '' ORDER BY contact_name ASC"); + $sql_client_contacts_select = mysqli_query($mysqli, "SELECT contact_id, contact_name, contact_client_id, contact_photo, contact_title, contact_email FROM contacts WHERE contact_client_id = $client_id AND contact_email <> '' ORDER BY contact_name ASC"); while ($row = mysqli_fetch_assoc($sql_client_contacts_select)) { $contact_id_select = intval($row['contact_id']); $contact_name_select = escapeHtml($row['contact_name']); $contact_email_select = escapeHtml($row['contact_email']); ?> - + - + diff --git a/agent/modals/ticket/ticket_bulk_assign.php b/agent/modals/ticket/ticket_bulk_assign.php index 6188b4bb2..69888a025 100644 --- a/agent/modals/ticket/ticket_bulk_assign.php +++ b/agent/modals/ticket/ticket_bulk_assign.php @@ -28,7 +28,7 @@ ob_start(); diff --git a/agent/modals/ticket/ticket_bulk_edit_category.php b/agent/modals/ticket/ticket_bulk_edit_category.php index b0ca3e4ac..926490baf 100644 --- a/agent/modals/ticket/ticket_bulk_edit_category.php +++ b/agent/modals/ticket/ticket_bulk_edit_category.php @@ -30,13 +30,13 @@ ob_start(); diff --git a/agent/modals/ticket/ticket_bulk_edit_priority.php b/agent/modals/ticket/ticket_bulk_edit_priority.php index a1b23dd59..3f6925192 100644 --- a/agent/modals/ticket/ticket_bulk_edit_priority.php +++ b/agent/modals/ticket/ticket_bulk_edit_priority.php @@ -26,10 +26,10 @@ ob_start();
diff --git a/agent/modals/ticket/ticket_bulk_reply.php b/agent/modals/ticket/ticket_bulk_reply.php index 22fc1ec4b..20f3e7e8d 100644 --- a/agent/modals/ticket/ticket_bulk_reply.php +++ b/agent/modals/ticket/ticket_bulk_reply.php @@ -27,12 +27,12 @@ ob_start(); diff --git a/agent/modals/ticket/ticket_change_client.php b/agent/modals/ticket/ticket_change_client.php index 7aa72748a..c93094c43 100644 --- a/agent/modals/ticket/ticket_change_client.php +++ b/agent/modals/ticket/ticket_change_client.php @@ -45,12 +45,12 @@ ob_start(); " - > + > diff --git a/agent/modals/ticket/ticket_edit.php b/agent/modals/ticket/ticket_edit.php index e3fd98e7b..9a04ea514 100644 --- a/agent/modals/ticket/ticket_edit.php +++ b/agent/modals/ticket/ticket_edit.php @@ -99,10 +99,10 @@ ob_start();
@@ -116,13 +116,13 @@ ob_start(); @@ -147,7 +147,7 @@ ob_start(); $sql = mysqli_query( $mysqli, - "SELECT user_id, user_name FROM users + "SELECT user_id, user_name, user_avatar FROM users WHERE user_role_id > 1 AND user_type = 1 AND user_status = 1 @@ -157,7 +157,7 @@ ob_start(); while ($row = mysqli_fetch_assoc($sql)) { $user_id = intval($row['user_id']); $user_name = escapeHtml($row['user_name']); ?> - + @@ -199,7 +199,7 @@ ob_start(); @@ -252,13 +252,13 @@ ob_start(); - + + > diff --git a/agent/modals/ticket/ticket_edit_asset.php b/agent/modals/ticket/ticket_edit_asset.php index faad45190..f56ceaa78 100644 --- a/agent/modals/ticket/ticket_edit_asset.php +++ b/agent/modals/ticket/ticket_edit_asset.php @@ -48,13 +48,13 @@ ob_start(); - + + > diff --git a/agent/modals/ticket/ticket_export.php b/agent/modals/ticket/ticket_export.php index 9d7c9df23..d30c463b7 100644 --- a/agent/modals/ticket/ticket_export.php +++ b/agent/modals/ticket/ticket_export.php @@ -74,12 +74,12 @@ ob_start(); - + @@ -144,12 +144,12 @@ ob_start(); - + diff --git a/agent/modals/ticket/ticket_invoice_add.php b/agent/modals/ticket/ticket_invoice_add.php index 82ea2b515..4e6d75911 100644 --- a/agent/modals/ticket/ticket_invoice_add.php +++ b/agent/modals/ticket/ticket_invoice_add.php @@ -181,12 +181,12 @@ ob_start(); - + diff --git a/agent/modals/ticket/ticket_quote_add.php b/agent/modals/ticket/ticket_quote_add.php index 64f0bcee0..9ceb2d844 100644 --- a/agent/modals/ticket/ticket_quote_add.php +++ b/agent/modals/ticket/ticket_quote_add.php @@ -104,12 +104,12 @@ ob_start(); diff --git a/agent/modals/ticket/ticket_task_approver_add.php b/agent/modals/ticket/ticket_task_approver_add.php index 0e0a6975f..c806ba423 100644 --- a/agent/modals/ticket/ticket_task_approver_add.php +++ b/agent/modals/ticket/ticket_task_approver_add.php @@ -98,8 +98,6 @@ ob_start(); // new Option() assigns text, so nothing here is parsed as markup select.appendChild(new Option(pair[1], pair[0])); }); - // the selects are Tom Select enhanced, so it has to re-read them - refreshTomSelect(select); } scopeSelect.addEventListener('change', function () { diff --git a/agent/modals/transaction/transaction_export.php b/agent/modals/transaction/transaction_export.php index 80f11ad84..4f9a8d6cb 100644 --- a/agent/modals/transaction/transaction_export.php +++ b/agent/modals/transaction/transaction_export.php @@ -141,12 +141,12 @@ ob_start(); - + @@ -163,12 +163,12 @@ ob_start(); - + diff --git a/agent/modals/trip/trip_add.php b/agent/modals/trip/trip_add.php index 8b831bbba..7a2426051 100644 --- a/agent/modals/trip/trip_add.php +++ b/agent/modals/trip/trip_add.php @@ -28,12 +28,12 @@ ob_start(); - + - Driver - - + - Driver - 1 AND user_archived_at IS NULL ORDER BY user_name ASC" ); @@ -116,7 +116,7 @@ ob_start(); $user_id_select = intval($row['user_id']); $user_name_select = escapeHtml($row['user_name']); ?> - + @@ -135,12 +135,12 @@ ob_start(); - + diff --git a/agent/modals/trip/trip_edit.php b/agent/modals/trip/trip_edit.php index a2ee38ffc..162620968 100644 --- a/agent/modals/trip/trip_edit.php +++ b/agent/modals/trip/trip_edit.php @@ -108,7 +108,7 @@ ob_start(); - + @@ -136,12 +136,12 @@ ob_start(); - + diff --git a/agent/networks.php b/agent/networks.php index 825230c40..3125826b1 100644 --- a/agent/networks.php +++ b/agent/networks.php @@ -150,7 +150,7 @@ $num_rows = mysqli_fetch_row(mysqli_query($mysqli, "SELECT FOUND_ROWS()")); - + diff --git a/agent/products.php b/agent/products.php index d2e536ca3..61f475799 100644 --- a/agent/products.php +++ b/agent/products.php @@ -103,12 +103,12 @@ $num_rows = mysqli_fetch_row(mysqli_query($mysqli, "SELECT FOUND_ROWS()")); - + diff --git a/agent/recurring_tickets.php b/agent/recurring_tickets.php index 729f4a4b6..01222d392 100644 --- a/agent/recurring_tickets.php +++ b/agent/recurring_tickets.php @@ -119,12 +119,12 @@ $num_rows = mysqli_fetch_row(mysqli_query($mysqli, "SELECT FOUND_ROWS()")); - + @@ -138,12 +138,12 @@ $num_rows = mysqli_fetch_row(mysqli_query($mysqli, "SELECT FOUND_ROWS()")); - + diff --git a/agent/services.php b/agent/services.php index fefda8881..b8d6f630e 100644 --- a/agent/services.php +++ b/agent/services.php @@ -81,7 +81,7 @@ $num_rows = mysqli_fetch_row(mysqli_query($mysqli, "SELECT FOUND_ROWS()")); - + diff --git a/agent/software.php b/agent/software.php index 320294a58..bbe8e5c49 100644 --- a/agent/software.php +++ b/agent/software.php @@ -127,7 +127,7 @@ $num_rows = mysqli_fetch_row(mysqli_query($mysqli, "SELECT FOUND_ROWS()")); - + diff --git a/agent/tickets.php b/agent/tickets.php index 9a0eba9e0..1e149795a 100644 --- a/agent/tickets.php +++ b/agent/tickets.php @@ -402,12 +402,12 @@ if ($date_filter_active) { - - - - + + + + @@ -59,13 +59,13 @@ $sql_assets = mysqli_query($mysqli, "SELECT asset_id, asset_name, asset_type FRO diff --git a/css/itflow_custom.css b/css/itflow_custom.css index 386a8869b..f4a4ac272 100644 --- a/css/itflow_custom.css +++ b/css/itflow_custom.css @@ -1053,42 +1053,18 @@ a:focus { color: #fff !important; } -/* --- Tom Select ---------------------------------------------------------- - Pre-init sizing. js/app.js runs at the end of the body, so the browser - paints the native . The field is a .form-select, so height, + border, chevron, focus ring and dark mode all come from Bootstrap; this + adds what goes inside it and the panel that opens under it. - A MULTIPLE select is the one that jumps. With no size attribute the - browser renders it as a listbox - four rows in Chrome and Firefox, ~100px - - which collapses to the ~35px control the instant Tom Select takes over, - yanking the rest of the form up with it. All 49 of them are tag pickers, - including the filter row on clients / contacts / assets / credentials / - locations / tickets, i.e. the first thing on the page. - - Sizing the raw control to the height it is about to become removes the jump - without hiding anything and without touching JS. The calc is deliberately - the same expression Tom Select uses rather than a fixed pixel value, so it - tracks the .text-sm font-size and --bs-border-width. Tom Select stamps - .tomselected onto the original element when it takes over, so this stops - applying at exactly the right moment and can never fight the live widget. - - visibility hides the native option text, which would otherwise flash in the - box before Tom Select replaces it with the data-placeholder. It reserves - layout space (unlike display:none, which would also break Tom Select's - width measurement at init), so the box holds its place and nothing moves. - - The delayed reveal is a safety net, not decoration. app.js has no typeof - guards, so if anything above the tom-select step throws, .tomselected is - never stamped and these pickers would stay invisible with no clue why. - Normal init lands in well under a second and the rule stops matching before - the delay elapses, so the animation only ever fires when init did not - happen - at which point the native control comes back and stays usable. */ -select.select2[multiple]:not(.tomselected) { + Before the script runs the browser paints the raw stays in the form, under the field, for posting and for + the browser's "please select an item" bubble - just not seen or clicked */ +.itflow-select > select.itflow-select-native { + position: absolute; + left: 0; + bottom: 0; + width: 100%; + height: 1px; + min-height: 0; + padding: 0; + border: 0; + opacity: 0; + pointer-events: none; } -/* selected values in a multi-select */ -.ts-wrapper.multi .ts-control > div.active { - background: var(--itflow-accent, #007bff); - color: #fff; +.itflow-select-control { + display: flex; + align-items: center; + gap: .375rem; + min-height: calc(1.5em + .75rem + calc(var(--bs-border-width) * 2)); + cursor: pointer; + user-select: none; + text-align: start; } -/* highlighted option in the dropdown, to match .dropdown-item.active */ -.ts-dropdown .active, -.ts-dropdown .active.create { - background-color: var(--itflow-accent, #007bff); - color: #fff; +.itflow-select-control:focus, +.itflow-select.is-open > .itflow-select-control { + border-color: var(--itflow-accent, #86b7fe); + outline: 0; + box-shadow: 0 0 0 .25rem color-mix(in srgb, var(--itflow-accent, #0d6efd) 25%, transparent); } -.ts-dropdown .option:hover { +.itflow-select.is-disabled > .itflow-select-control { + cursor: default; + color: var(--bs-secondary-color); background-color: var(--bs-secondary-bg); +} +.itflow-select.is-invalid > .itflow-select-control, +.itflow-select.is-invalid-class > .itflow-select-control, +.was-validated .itflow-select:has(> select:invalid) > .itflow-select-control { + border-color: var(--bs-form-invalid-border-color); +} +.itflow-select-value { + display: flex; + align-items: center; + gap: .5rem; + min-width: 0; + flex: 1 1 auto; +} +.itflow-select-value .itflow-select-label { + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; +} +.itflow-select-placeholder, +.itflow-select-label.is-none { + color: var(--bs-secondary-color); + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; +} +/* × to put the field back to its "nothing" option - only on optional fields */ +.itflow-select.is-clearable > .itflow-select-control { + padding-right: 3.5rem; +} +.itflow-select-clear { + position: absolute; + top: 50%; + right: 2.25rem; + display: inline-flex; + align-items: center; + justify-content: center; + width: 1.25rem; + height: 1.25rem; + padding: 0; + border: 0; + border-radius: 50%; + font-size: .7rem; + color: var(--bs-secondary-color); + background: transparent; + transform: translateY(-50%); + opacity: 0; +} +.itflow-select:hover .itflow-select-clear, +.itflow-select-control:focus .itflow-select-clear, +.itflow-select.is-open .itflow-select-clear { + opacity: 1; +} +.itflow-select-clear:hover { color: var(--bs-body-color); -} -/* the dropdown border and the disabled chips are fixed light greys - #d0d0d0 - and #fff - so they glare in dark mode */ -.ts-dropdown { - border-color: var(--bs-border-color); -} -.ts-wrapper.multi.disabled .ts-control > div, -.ts-wrapper.multi.disabled .ts-control > div.active { background: var(--bs-secondary-bg); - border-color: var(--bs-border-color); +} + +/* Multiple: chips in the field */ +.itflow-select.is-multiple > .itflow-select-control { + flex-wrap: wrap; + gap: .25rem; + padding-top: .25rem; + padding-bottom: .25rem; + padding-left: .375rem; +} +.itflow-select.is-multiple .itflow-select-placeholder { + padding-left: .375rem; +} +.itflow-select-chip { + display: inline-flex; + align-items: center; + gap: .3rem; + max-width: 100%; + height: 1.625rem; + padding: 0 .25rem 0 .55rem; + border: 1px solid var(--bs-border-color-translucent); + border-radius: 2rem; + font-size: .8125rem; + color: var(--bs-body-color); + background: var(--bs-tertiary-bg); +} +.itflow-select-chip.has-lead { + padding-left: .2rem; +} +.itflow-select-chip.is-more { + padding-right: .55rem; color: var(--bs-secondary-color); } -/* light accents need dark text - same L>0.335 cut as everywhere else */ -.theme-yellow .ts-wrapper.multi .ts-control > div.active, -.theme-orange .ts-wrapper.multi .ts-control > div.active, -.theme-teal .ts-wrapper.multi .ts-control > div.active, -.theme-yellow .ts-dropdown .active, -.theme-orange .ts-dropdown .active, -.theme-teal .ts-dropdown .active { +.itflow-select-chip-label { + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; +} +.itflow-select-chip .itflow-select-avatar, +.itflow-select-chip .itflow-select-badge { + width: 1.25rem; + height: 1.25rem; + font-size: .55rem; +} +.itflow-select-chip .itflow-select-dot { + margin: 0 .15rem 0 .3rem; +} +.itflow-select-chip-remove { + display: inline-flex; + flex: 0 0 auto; + align-items: center; + justify-content: center; + width: 1.125rem; + height: 1.125rem; + padding: 0; + border: 0; + border-radius: 50%; + font-size: .6rem; + color: var(--bs-secondary-color); + background: transparent; +} +.itflow-select-chip-remove:hover { + color: var(--bs-body-color); + background: var(--bs-secondary-bg); +} + +/* In an .input-group the picker is the flexible middle piece */ +.input-group > .itflow-select { + flex: 1 1 auto; + width: 1%; +} +.input-group > .itflow-select:focus-within, +.input-group > .itflow-select.is-open { + z-index: 5; +} +.input-group > .itflow-select:not(:first-child) > .itflow-select-control { + border-top-left-radius: 0; + border-bottom-left-radius: 0; +} +.input-group > .itflow-select:not(:last-child) > .itflow-select-control { + border-top-right-radius: 0; + border-bottom-right-radius: 0; +} + +/* Pictures, tiles, dots and icons in front of a label */ +.itflow-select-avatar { + display: inline-flex; + flex: 0 0 auto; + align-items: center; + justify-content: center; + width: 1.5rem; + height: 1.5rem; + border-radius: 50%; + font-size: .6rem; + font-weight: 600; + letter-spacing: .02em; + object-fit: cover; +} +.itflow-select-avatar.is-tile { + border-radius: var(--bs-border-radius-sm); +} +.itflow-select-control .itflow-select-avatar, +.itflow-select-control .itflow-select-badge { + width: 1.375rem; + height: 1.375rem; +} +.itflow-select-dot { + flex: 0 0 auto; + width: .625rem; + height: .625rem; + margin: 0 .3125rem; + border-radius: 50%; + background-color: var(--itflow-select-color, var(--bs-secondary)); + box-shadow: 0 0 0 1px rgba(0, 0, 0, .08); +} +.itflow-select-badge { + display: inline-flex; + flex: 0 0 auto; + align-items: center; + justify-content: center; + width: 1.5rem; + height: 1.5rem; + border-radius: var(--bs-border-radius); + font-size: .65rem; + color: #fff; + background-color: var(--itflow-select-color, var(--bs-secondary)); +} +.itflow-select-icon { + display: inline-flex; + flex: 0 0 auto; + align-items: center; + justify-content: center; + width: 1.25rem; + color: var(--bs-secondary-color); +} + +/* The panel */ +.itflow-select-panel { + position: fixed; + z-index: 1075; + display: flex; + flex-direction: column; + overflow: hidden; + font-size: .9375rem; + color: var(--bs-body-color); + background: var(--bs-body-bg); + border: 1px solid var(--bs-border-color-translucent); + border-radius: var(--bs-border-radius-lg); + box-shadow: 0 .75rem 2rem rgba(0, 0, 0, .14), 0 .125rem .375rem rgba(0, 0, 0, .06); + animation: itflowSelectIn .12s ease-out; +} +[data-bs-theme="dark"] .itflow-select-panel { + background: var(--bs-tertiary-bg); + box-shadow: 0 .75rem 2rem rgba(0, 0, 0, .5); +} +.itflow-select-panel.is-above { + animation-name: itflowSelectInAbove; +} +@keyframes itflowSelectIn { + from { opacity: 0; transform: translateY(-4px); } +} +@keyframes itflowSelectInAbove { + from { opacity: 0; transform: translateY(4px); } +} +.itflow-select-sheet-head { + display: none; +} +.itflow-select-search { + display: flex; + align-items: center; + gap: .5rem; + padding: .5rem .75rem; + border-bottom: 1px solid var(--bs-border-color-translucent); + color: var(--bs-secondary-color); + font-size: .875rem; +} +.itflow-select-search[hidden] { + display: none; +} +.itflow-select-search input { + flex: 1 1 auto; + min-width: 0; + padding: .125rem 0; + border: 0; + outline: 0; + font-size: .9375rem; + color: var(--bs-body-color); + background: transparent; +} +.itflow-select-search input::placeholder { + color: var(--bs-secondary-color); +} +.itflow-select-list { + max-height: 18rem; + padding: .25rem; + overflow-y: auto; + overscroll-behavior: contain; +} +.itflow-select-option { + display: flex; + align-items: center; + gap: .625rem; + min-height: 2.25rem; + padding: .375rem .625rem; + border-radius: var(--bs-border-radius); + cursor: pointer; + user-select: none; +} +.itflow-select-option.is-active { + background: var(--bs-secondary-bg); +} +.itflow-select-option.is-disabled { + cursor: default; + opacity: .5; +} +.itflow-select-option.is-none .itflow-select-label { + color: var(--bs-secondary-color); +} +.itflow-select-text { + display: flex; + flex: 1 1 auto; + flex-direction: column; + min-width: 0; + line-height: 1.3; +} +.itflow-select-text .itflow-select-label { + overflow-wrap: anywhere; +} +.itflow-select-description { + font-size: .8125rem; + color: var(--bs-secondary-color); + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; +} +.itflow-select-option mark { + padding: 0; + color: inherit; + font-weight: 600; + background: color-mix(in srgb, var(--itflow-accent, #0d6efd) 16%, transparent); + border-radius: 2px; +} +.itflow-select-option.is-selected .itflow-select-label { + font-weight: 600; +} +.itflow-select-tick { + flex: 0 0 auto; + font-size: .8rem; + color: var(--itflow-accent, var(--bs-primary)); + visibility: hidden; +} +.itflow-select-option.is-selected .itflow-select-tick { + visibility: visible; +} +/* Multiple: a checklist */ +.itflow-select-checkbox { + display: inline-flex; + flex: 0 0 auto; + align-items: center; + justify-content: center; + width: 1rem; + height: 1rem; + border: 1px solid var(--bs-border-color); + border-radius: .3rem; + font-size: .6rem; + color: transparent; + background: var(--bs-body-bg); +} +.itflow-select-option.is-selected .itflow-select-checkbox { + border-color: var(--itflow-accent, var(--bs-primary)); + color: #fff; + background: var(--itflow-accent, var(--bs-primary)); +} +.theme-yellow .itflow-select-option.is-selected .itflow-select-checkbox, +.theme-orange .itflow-select-option.is-selected .itflow-select-checkbox, +.theme-teal .itflow-select-option.is-selected .itflow-select-checkbox { color: #000; } +.itflow-select-create .itflow-select-icon { + color: var(--itflow-accent, var(--bs-primary)); +} +.itflow-select-group { + padding: .5rem .625rem .25rem; + font-size: .75rem; + font-weight: 600; + color: var(--bs-secondary-color); +} +.itflow-select-empty, +.itflow-select-more { + padding: .75rem .625rem; + font-size: .875rem; + color: var(--bs-secondary-color); +} +.itflow-select-more { + padding-top: .375rem; + font-size: .8125rem; +} +.itflow-select-footer { + display: flex; + align-items: center; + justify-content: space-between; + padding: .25rem .375rem .25rem .875rem; + border-top: 1px solid var(--bs-border-color-translucent); + font-size: .8125rem; + color: var(--bs-secondary-color); +} +.itflow-select-footer .btn, +.itflow-select-done { + --bs-btn-padding-y: .125rem; + text-decoration: none; +} + +/* Phones: the panel comes up from the bottom of the screen */ +.itflow-select-backdrop { + position: fixed; + inset: 0; + z-index: 1074; + background: rgba(0, 0, 0, .35); + animation: itflowSelectFade .15s ease-out; +} +.itflow-select-panel.is-sheet { + top: auto !important; + right: 0; + bottom: 0; + left: 0 !important; + width: auto !important; + max-height: 80vh; + padding-bottom: env(safe-area-inset-bottom); + border-width: 1px 0 0; + border-radius: 1rem 1rem 0 0; + font-size: 1rem; + animation: itflowSelectUp .2s ease-out; +} +.itflow-select-panel.is-sheet .itflow-select-sheet-head { + display: flex; + align-items: center; + justify-content: space-between; + padding: .75rem .75rem .5rem 1rem; + font-weight: 600; +} +.itflow-select-panel.is-sheet .itflow-select-list { + flex: 1 1 auto; + max-height: none; +} +.itflow-select-panel.is-sheet .itflow-select-option { + min-height: 2.75rem; +} +.itflow-select-panel.is-sheet .itflow-select-search input { + font-size: 1rem; +} +@keyframes itflowSelectUp { + from { transform: translateY(100%); } +} +@keyframes itflowSelectFade { + from { opacity: 0; } +} +@media (prefers-reduced-motion: reduce) { + .itflow-select-panel, + .itflow-select-backdrop { + animation: none; + } +} /* --- Muted contextual palette for dark mode ------------------------------ AdminLTE 3 swapped in softer variants of the contextual colours whenever @@ -1361,7 +1742,7 @@ select.select2[multiple]:not(.tomselected) { `data-lte-primary` on now drives buttons, links, pagination, pills, dropdowns, list groups, progress, accordions and every form focus state from AdminLTE's own palette. --itflow-accent is still ours and still drives - the navbar, sidebar, nav-tabs, TinyMCE, Tom Select, FullCalendar and the + the navbar, sidebar, nav-tabs, TinyMCE, the select picker, FullCalendar and the theme picker. 1. There is no `black` in AdminLTE's palette - its darkest neutral is diff --git a/functions/format.php b/functions/format.php index 46a4b4969..2d2e50443 100644 --- a/functions/format.php +++ b/functions/format.php @@ -346,6 +346,86 @@ function clientInitialsTone($client_id) { } +/* + * Option extras for the select picker (js/itflow_select.js): data-* + * attributes that put a picture, initials, a colour or an icon in front of + * an + * + * Takes values raw or already escaped - they are decoded and escaped again + * here, so either way the attribute comes out right. + */ +function selectOptionData(array $data) { + $html = ''; + foreach ($data as $name => $value) { + $value = html_entity_decode((string) $value, ENT_QUOTES, 'UTF-8'); + if ($value !== '') { + $html .= ' data-' . $name . '="' . escapeHtml($value) . '"'; + } + } + return $html; +} + +/** Up to two initials for a person's name - "Marla Whitfield" is MW. */ +function personInitials($name) { + $words = preg_split('/\s+/u', trim(html_entity_decode((string) $name, ENT_QUOTES, 'UTF-8')), -1, PREG_SPLIT_NO_EMPTY); + $initials = ''; + foreach (array_slice($words, 0, 2) as $word) { + $initials .= mb_strtoupper(mb_substr($word, 0, 1)); + } + return $initials; +} + +/** A client - its coloured initials tile, the same as the client list and side nav. */ +function selectOptionClient($client_id, $client_name, $client_abbreviation = '') { + return selectOptionData([ + 'initials' => clientInitials(html_entity_decode((string) $client_abbreviation, ENT_QUOTES, 'UTF-8'), html_entity_decode((string) $client_name, ENT_QUOTES, 'UTF-8')), + 'tone' => clientInitialsTone($client_id) + ]); +} + +/** An agent - their avatar, or their initials in a circle. */ +function selectOptionUser($user_id, $user_name, $user_avatar = '') { + $user_avatar = html_entity_decode((string) $user_avatar, ENT_QUOTES, 'UTF-8'); + if ($user_avatar !== '') { + return selectOptionData(['image' => '/uploads/users/' . intval($user_id) . '/' . rawurlencode($user_avatar)]); + } + return selectOptionData(['initials' => personInitials($user_name), 'shape' => 'circle', 'tone' => 'secondary']); +} + +/** A contact - their photo or initials, with their title underneath. */ +function selectOptionContact($client_id, $contact_name, $contact_photo = '', $contact_title = '') { + $contact_photo = html_entity_decode((string) $contact_photo, ENT_QUOTES, 'UTF-8'); + $data = $contact_photo !== '' + ? ['image' => '/uploads/clients/' . intval($client_id) . '/' . rawurlencode($contact_photo)] + : ['initials' => personInitials($contact_name), 'shape' => 'circle', 'tone' => 'secondary']; + $data['description'] = $contact_title; + return selectOptionData($data); +} + +/** A tag, category or status - its colour, and its icon if it has one. */ +function selectOptionColor($color, $icon = '') { + // A hex value, or a CSS colour name - categories store names like "lightblue" + $color = preg_match('/^(#[0-9a-f]{3,8}|[a-z]{3,24})$/i', trim((string) $color)) ? trim($color) : ''; + $icon = trim(preg_replace('/[^a-z0-9 -]/', '', strtolower((string) $icon))); + if ($icon !== '' && strpos($icon, 'fa-') === false) { + $icon = "fas fa-$icon"; + } + return selectOptionData(['color' => $color, 'icon' => $icon]); +} + +/** A ticket priority - the colour its badge has in the ticket lists. */ +function selectOptionPriority($priority) { + $colors = ['Urgent' => 'var(--bs-emphasis-color)', 'High' => 'var(--bs-danger)', 'Medium' => 'var(--bs-warning)', 'Low' => 'var(--bs-info)']; + return selectOptionData(['color' => $colors[html_entity_decode((string) $priority)] ?? '']); +} + +/** An asset - the icon for its type, with make and model underneath. */ +function selectOptionAsset($asset_type, $description = '') { + return selectOptionData(['icon' => 'fas fa-' . getAssetIcon(html_entity_decode((string) $asset_type, ENT_QUOTES, 'UTF-8')), 'description' => $description]); +} + function roundUpToNearestMultiple($n, $increment = 1000) { return (int) ($increment * ceil($n / $increment)); } diff --git a/guest/includes/guest_header.php b/guest/includes/guest_header.php index dade9cdf2..a912a6e7a 100644 --- a/guest/includes/guest_header.php +++ b/guest/includes/guest_header.php @@ -28,7 +28,6 @@ - - - controls it painted while parsing the body, so every byte in front of - it is time spent looking at the wrong widget. Nothing here is needed to - turn a IS THE ONLY STATE + * + * The picker keeps no copy of the options or the value. Every time it draws it + * reads them off the real : + * + * select.innerHTML = ... redraws by itself (MutationObserver) + * select.value = 4 redraws by itself (value setter, see below) + * option.selected = true redraws by itself + * form.reset() redraws by itself + * + * There is nothing to "sync" or "refresh" by hand, which is where the old + * widget kept drifting out of step with the form. + * + * WHAT IT LOOKS LIKE + * + * The field is a .form-select, so it is the same height, border, chevron and + * focus ring as every other control and drops into .input-group as before. + * Clicking it opens a panel with a search box (long lists, multi selects and + * data-tags fields), the options, and a tick on what is chosen. Multi selects + * show chips in the field and a checklist in the panel that stays open while + * you pick; their change event fires once, when the panel closes, so a filter + * that submits on change submits once with everything you picked. On a phone + * the panel comes up from the bottom of the screen instead. + * + * OPTION EXTRAS - all optional, set on the