Fix notification comment query, status title, and is-hidden visibility

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 <noreply@anthropic.com>
This commit is contained in:
2026-04-05 10:47:39 -04:00
parent 2ccf4f2261
commit df6c4de196
3 changed files with 18 additions and 10 deletions
+16 -8
View File
@@ -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}",
];
}