Files
itflow/client/activity.php
johnnyq c6eadf3fa3 Client portal stage 1, look, home and tickets: a new light shell with the company logo or initials on the theme colour, sections shown by what each contact may open (Home, Tickets, Billing for billing contacts, Your IT for technical contacts, custom links), New ticket and an account menu with a dark mode switch kept in a cookie, a bottom tab bar and slide-in menu on phones, and the welcome banner removed; a rebuilt home showing what needs the contact (tickets waiting on their reply or to confirm, approvals, past-due invoices), open tickets, balance with Pay invoices, renewals in the next 45 days, their devices with warranty status and the company phone and email; tickets rebuilt with Open, Waiting on you, Resolved, Closed and All tabs with counts, search, paging and the whole company's tickets for primary and technical contacts with a Mine filter, the old all-tickets page redirecting there; a ticket page with progress steps, the conversation oldest first, approvals, details alongside, replies with drag and drop attachments, It's fixed, close or reopen and rating; New Ticket with priority, category, device and attachments; fixes downloading files from internal notes, files on closed tickets not downloading, new tickets linking to another client's device or a non-ticket category, empty replies and tickets being accepted, PHP warnings on Pay invoices and Recurring invoices with no payment provider, and Bootstrap icons and the editor setup blocked by the portal CSP, which now allows data: images; new js/portal.js and portal helpers in client/functions.php, ticketPortalStatusHtml removed; changelog updated
2026-10-17 01:28:27 -04:00

127 lines
4.5 KiB
PHP

<?php
/*
* Client Portal
* 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";
/*
* No capability gate: this is the contact's own record of their own activity,
* 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.
*/
$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']);
$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 $activity_limit"
);
?>
<div class="d-flex justify-content-between align-items-center mb-3">
<h3 class="mb-0">Your activity</h3>
<a class="btn btn-secondary" href="profile.php"><i class="fa fa-fw fa-user me-2"></i>Back to profile</a>
</div>
<hr>
<div class="row">
<div class="col-md-12">
<?php if ($total_records == 0) { ?>
<?= portalEmptyState('Nothing has been recorded on your account yet.') ?>
<?php } else { ?>
<table class="table table-striped table-bordered border border-dark dataTables w-100">
<thead class="table-dark">
<tr>
<?php /* Ordering off on this column deliberately. The cell reads
"Today at 4:12 PM", and sorting that as text puts October
before September and Today next to Tomorrow. The rows
arrive newest-first from SQL and the initialiser keeps
that order, so the useful sort is the one already applied. */ ?>
<th data-dt-order="disable">When</th>
<th>Type</th>
<th>What happened</th>
<th>From</th>
</tr>
</thead>
<tbody>
<?php
while ($row = mysqli_fetch_assoc($sql_activity)) {
$log_type = escapeHtml($row['log_type']);
$log_action = escapeHtml($row['log_action']);
$log_description = escapeHtml($row['log_description']);
$log_ip = escapeHtml($row['log_ip']);
// Sign-ins are the rows people scan this page for, so they
// get the accent rather than sitting in the same grey as
// every password change
if ($row['log_type'] === 'Client Login') {
$log_badge_color = 'primary';
$log_label = 'Sign-in';
} else {
$log_badge_color = 'secondary';
$log_label = "$log_type $log_action";
}
?>
<tr>
<td class="text-nowrap"><?= portalDateTime($row['log_created_at']) ?></td>
<td><span class="p-2 badge text-bg-<?= $log_badge_color ?>"><?= $log_label ?></span></td>
<td><?= $log_description ?></td>
<td class="font-monospace"><?= $log_ip ?></td>
</tr>
<?php
}
?>
</tbody>
</table>
<?php if ($total_records > $activity_limit) { ?>
<small class="text-muted">
Showing your most recent <?= $activity_limit ?> records of <?= $total_records ?>.
Raise a ticket if you need to go back further.
</small>
<?php } ?>
<?php } ?>
</div>
</div>
<?php
require_once "includes/footer.php";