From df6c4de196a9d4f036c3d8811a1c4d21bd1a2ffd Mon Sep 17 00:00:00 2001 From: Jared Vititoe Date: Sun, 5 Apr 2026 10:47:39 -0400 Subject: [PATCH] Fix notification comment query, status title, and is-hidden visibility MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit notifications.php: comment notifications never fired because the query used action_type='comment'/entity_type='ticket' but logCommentCreate logs action_type='create'/entity_type='comment'. Fix query to match actual log format and extract ticket_id from details JSON. notifications.php: status change notification titles always showed "? → ?" because code read details.old_value/new_value but logTicketUpdate stores the delta as {"status": {"from": ..., "to": ...}}. base.css: move .is-hidden to base.css (global) — it was only defined in ticket.css, so on the dashboard the ticket-preview popup had no hide rule applied and was visible in the DOM at all times. Co-Authored-By: Claude Sonnet 4.6 --- api/notifications.php | 24 ++++++++++++++++-------- assets/css/base.css | 1 + assets/css/ticket.css | 3 +-- 3 files changed, 18 insertions(+), 10 deletions(-) diff --git a/api/notifications.php b/api/notifications.php index 7c88ef9..8f3d95d 100644 --- a/api/notifications.php +++ b/api/notifications.php @@ -65,15 +65,16 @@ $assignRows = $stmt->get_result()->fetch_all(MYSQLI_ASSOC); $stmt->close(); // Query 2: Comments on tickets I own or watch (events from other users) +// Comments are logged as action_type='create', entity_type='comment', with ticket_id in details JSON. $commentSql = "SELECT DISTINCT al.log_id, al.action_type, al.entity_type, al.entity_id, al.details, al.created_at, COALESCE(u.display_name, u.username, 'System') AS actor_name FROM audit_log al LEFT JOIN users u ON al.user_id = u.user_id - INNER JOIN tickets t ON t.ticket_id = CAST(al.entity_id AS UNSIGNED) + INNER JOIN tickets t ON t.ticket_id = CAST(JSON_UNQUOTE(JSON_EXTRACT(al.details, '$.ticket_id')) AS UNSIGNED) LEFT JOIN ticket_watchers tw ON tw.ticket_id = t.ticket_id AND tw.user_id = ? - WHERE al.action_type = 'comment' - AND al.entity_type = 'ticket' + WHERE al.action_type = 'create' + AND al.entity_type = 'comment' AND al.user_id != ? AND al.created_at >= DATE_SUB(NOW(), INTERVAL 7 DAY) AND (t.assigned_to = ? OR t.created_by = ? OR tw.user_id IS NOT NULL) @@ -123,16 +124,23 @@ $all = array_slice($all, 0, 30); $notifications = []; foreach ($all as $row) { $details = json_decode($row['details'] ?? '{}', true) ?? []; - $ticketId = (int)$row['entity_id']; + // Comment rows: entity_id is the comment_id; real ticket_id is in details + $actionType = ($row['action_type'] === 'create' && $row['entity_type'] === 'comment') + ? 'comment' + : $row['action_type']; + $ticketId = ($actionType === 'comment') + ? (int)($details['ticket_id'] ?? 0) + : (int)$row['entity_id']; $isRead = $lastSeen && $row['created_at'] <= $lastSeen; // Build human-readable title - $title = match($row['action_type']) { + $title = match($actionType) { 'assign' => "{$row['actor_name']} assigned ticket #{$ticketId} to you", 'comment' => "{$row['actor_name']} commented on ticket #{$ticketId}", 'update' => (function() use ($row, $details, $ticketId) { - $from = $details['old_value'] ?? '?'; - $to = $details['new_value'] ?? '?'; + // logTicketUpdate stores delta as {"status": {"from": "Open", "to": "In Progress"}} + $from = $details['status']['from'] ?? ($details['old_value'] ?? '?'); + $to = $details['status']['to'] ?? ($details['new_value'] ?? '?'); return "{$row['actor_name']} changed status on #{$ticketId}: {$from} → {$to}"; })(), default => "{$row['actor_name']} updated ticket #{$ticketId}", @@ -149,7 +157,7 @@ foreach ($all as $row) { 'title' => $title, 'created_at' => $row['created_at'], 'is_read' => $isRead, - 'action' => $row['action_type'], + 'action' => $actionType, 'url' => "/ticket/{$ticketId}", ]; } diff --git a/assets/css/base.css b/assets/css/base.css index 6c17c7d..cdeba52 100644 --- a/assets/css/base.css +++ b/assets/css/base.css @@ -2341,6 +2341,7 @@ select option:checked { .lt-text-upper { text-transform: uppercase; letter-spacing: 0.1em; } .lt-hidden { display: none !important; } +.is-hidden { display: none !important; } /* Skip navigation link — visible only on focus */ .lt-skip-link { diff --git a/assets/css/ticket.css b/assets/css/ticket.css index e41163c..3ddaf61 100644 --- a/assets/css/ticket.css +++ b/assets/css/ticket.css @@ -203,8 +203,7 @@ body.edit-mode .editable-metadata { } /* ── Visibility groups toggle ────────────────────────────────── */ -.ticket-visibility-groups.is-hidden, -.is-hidden { display: none !important; } +.ticket-visibility-groups.is-hidden { display: none !important; } /* ── Page header utility ─────────────────────────────────────── */ .lt-page-header {