From be20810d18537123445198d054085468e350caa9 Mon Sep 17 00:00:00 2001 From: johnnyq Date: Fri, 28 Aug 2026 16:19:27 -0400 Subject: [PATCH] Use Datatables for User Activity on the client portal --- client/activity.php | 60 +++++++++++++++----------------------- client/includes/footer.php | 5 ++++ client/includes/header.php | 4 +++ js/portal_datatables.js | 38 ++++++++++++++++++++++++ 4 files changed, 71 insertions(+), 36 deletions(-) create mode 100644 js/portal_datatables.js diff --git a/client/activity.php b/client/activity.php index 3e64725b2..04348808a 100644 --- a/client/activity.php +++ b/client/activity.php @@ -4,6 +4,10 @@ * Everything this contact has done in the portal, and every sign-in */ +// Read by client/includes/header.php and footer.php - see the note there. +// Must be set before inc_all.php, which pulls the header in. +$portal_load_datatables = true; + header("Content-Security-Policy: default-src 'self'"); require_once "includes/inc_all.php"; @@ -13,33 +17,26 @@ require_once "includes/inc_all.php"; * not a section of the portal. Scoped on log_user_id - the portal user this * contact signs in as - so an agent working this client never appears here, * and on log_client_id as a second fence. + * + * DataTables searches and paginates in the browser, so the whole set is sent at + * once rather than a page at a time. The cap is there so that a contact with + * years of history cannot turn this page into a several-megabyte document; it + * is generous enough that nobody normal will meet it, and the note below says + * so plainly when they do. */ -$page = intval($_GET['page'] ?? 1); -if ($page < 1) { - $page = 1; -} - -$records_per_page = 25; -$offset = ($page - 1) * $records_per_page; +$activity_limit = 1000; $log_scope = "log_user_id = $session_user_id AND log_client_id = $session_client_id"; $row = mysqli_fetch_assoc(mysqli_query($mysqli, "SELECT COUNT(log_id) AS total FROM logs WHERE $log_scope")); $total_records = intval($row['total']); -$total_pages = (int) ceil($total_records / $records_per_page); - -// A page number past the end would show nothing at all with no way back -if ($total_pages > 0 && $page > $total_pages) { - $page = $total_pages; - $offset = ($page - 1) * $records_per_page; -} $sql_activity = mysqli_query( $mysqli, "SELECT log_action, log_created_at, log_description, log_ip, log_type FROM logs WHERE $log_scope ORDER BY log_id DESC - LIMIT $records_per_page OFFSET $offset" + LIMIT $activity_limit" ); ?> @@ -59,10 +56,15 @@ $sql_activity = mysqli_query( - +
- + + @@ -107,25 +109,11 @@ $sql_activity = mysqli_query(
WhenWhen Type What happened From
- 1) { ?> -
-
-

- Page of - — records -

-
-
- -
-
+ $activity_limit) { ?> + + Showing your most recent records of . + Raise a ticket if you need to go back further. + diff --git a/client/includes/footer.php b/client/includes/footer.php index c8c218149..45e2e22e0 100644 --- a/client/includes/footer.php +++ b/client/includes/footer.php @@ -74,6 +74,11 @@ + + + + + diff --git a/client/includes/header.php b/client/includes/header.php index 263990415..40a14efd3 100644 --- a/client/includes/header.php +++ b/client/includes/header.php @@ -47,6 +47,10 @@ header("X-Frame-Options: DENY"); // Legacy + + + + , because portal pages send + * Content-Security-Policy: default-src 'self', which blocks inline script. + */ +function initPortalDataTables() { + if (typeof DataTable !== 'function') { + return; + } + + document.querySelectorAll('table.dataTables').forEach(function (el) { + if (DataTable.isDataTable(el)) { + return; + } + + new DataTable(el, { + // The server already ordered these rows - newest first for an + // activity log. DataTables' default is to re-sort on column 0 + // ascending, which would silently reverse that. + order: [], + pageLength: 25, + lengthMenu: [10, 25, 50, 100] + }); + }); +} + +if (document.readyState === 'loading') { + document.addEventListener('DOMContentLoaded', initPortalDataTables); +} else { + initPortalDataTables(); +}