fix: watcher avatars, dependency TDS styling, asset versions, nav dropdown light theme

- watch_ticket.php GET now returns watcher list (up to 6 users) for avatar group
- TicketView: watcher avatar group rendered next to WATCH button, refreshes on toggle
- Rewrite renderDependencies/renderDependents to use TDS lt-kv-grid/lt-badge/lt-btn classes
- renderDependencies: show lt-alert--warning blocker banner when blocked_by has open tickets
- Fix ALL hardcoded ?v=20260327 asset version strings in CreateTicketView + all admin views
- base.css: fix .lt-nav-dropdown-menu hardcoded background → var(--bg-overlay)
- base.css: add light-theme overrides for nav dropdown menu (background, links, hover)
- ticket.css: add .lt-avatar-group and .lt-avatar--overflow styles for watcher display

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-04-04 12:02:30 -04:00
parent c0dfbdbc26
commit fca4896e0d
13 changed files with 162 additions and 56 deletions
+3 -2
View File
@@ -10,9 +10,10 @@ require_once __DIR__ . '/../middleware/CsrfMiddleware.php';
$nonce = SecurityHeadersMiddleware::getNonce();
$pageTitle = 'New Ticket';
$activeNav = 'dashboard';
$pageStyles = ['/assets/css/dashboard.css?v=20260327', '/assets/css/ticket.css?v=20260327'];
$_v = $GLOBALS['config']['ASSET_VERSION'] ?? '1';
$pageStyles = ["/assets/css/dashboard.css?v={$_v}", "/assets/css/ticket.css?v={$_v}"];
$pageScripts = [
'/assets/js/keyboard-shortcuts.js?v=20260327',
"/assets/js/keyboard-shortcuts.js?v={$_v}",
];
include __DIR__ . '/layout_header.php';
+32
View File
@@ -163,6 +163,7 @@ include __DIR__ . '/layout_header.php';
</option>
<?php endforeach ?>
</select>
<span id="watcherAvatarGroup" class="lt-avatar-group lt-avatar-group--sm" aria-label="Watchers" style="display:none"></span>
<button type="button" id="watchButton" class="lt-btn lt-btn-ghost lt-btn-sm"
title="Watch this ticket to receive Matrix notifications on updates">WATCH</button>
<button type="button" id="editButton" class="lt-btn lt-btn-primary lt-btn-sm">EDIT</button>
@@ -868,6 +869,32 @@ document.addEventListener('DOMContentLoaded', function () {
// Watch / Unwatch button
var watchBtn = document.getElementById('watchButton');
var watcherGroup = document.getElementById('watcherAvatarGroup');
function _renderWatcherAvatars(watchers) {
if (!watcherGroup) return;
if (!watchers || !watchers.length) { watcherGroup.style.display = 'none'; return; }
var avatarColors = ['lt-avatar--orange', 'lt-avatar--green', 'lt-avatar--purple', ''];
var html = '';
var shown = watchers.slice(0, 4);
shown.forEach(function (w) {
var words = (w.display_name || '').trim().split(/\s+/).filter(Boolean);
var initials = words.slice(0, 2).map(function (x) { return x[0].toUpperCase(); }).join('');
var hash = 0;
for (var i = 0; i < (w.display_name || '').length; i++) hash = ((hash << 5) - hash + (w.display_name || '').charCodeAt(i)) | 0;
var color = avatarColors[Math.abs(hash) % 4];
html += '<div class="lt-avatar lt-avatar--xs ' + color + '" title="' + lt.escHtml(w.display_name) + '" aria-label="' + lt.escHtml(w.display_name) + '">' +
'<img src="/api/user_avatar.php?user_id=' + w.user_id + '" alt="" class="lt-avatar-img" onerror="this.style.display=\'none\'">' +
'<span class="lt-avatar-initials">' + lt.escHtml(initials) + '</span>' +
'</div>';
});
if (watchers.length > 4) {
html += '<div class="lt-avatar lt-avatar--xs lt-avatar--overflow" title="' + (watchers.length - 4) + ' more watchers">+' + (watchers.length - 4) + '</div>';
}
watcherGroup.innerHTML = html;
watcherGroup.style.display = 'flex';
}
if (watchBtn) {
var _watching = false;
// Fetch initial state
@@ -880,6 +907,7 @@ document.addEventListener('DOMContentLoaded', function () {
? 'You are watching this ticket. Click to stop.'
: 'Watch this ticket for Matrix notifications on updates.';
if (_watching) watchBtn.classList.add('lt-btn-active');
_renderWatcherAvatars(d.watchers || []);
}
})
.catch(function () {});
@@ -897,6 +925,10 @@ document.addEventListener('DOMContentLoaded', function () {
: 'Watch this ticket for Matrix notifications on updates.';
watchBtn.classList.toggle('lt-btn-active', _watching);
lt.toast.success(_watching ? 'Watching ticket' : 'Stopped watching ticket');
// Refresh watcher avatars from server
lt.api.get('/api/watch_ticket.php?ticket_id=' + window.ticketData.ticket_id)
.then(function (d2) { if (d2.success) _renderWatcherAvatars(d2.watchers || []); })
.catch(function () {});
} else {
lt.toast.error('Failed: ' + (d.error || 'Unknown error'));
}
+2 -1
View File
@@ -4,7 +4,8 @@ require_once __DIR__ . '/../../middleware/CsrfMiddleware.php';
$nonce = SecurityHeadersMiddleware::getNonce();
$pageTitle = 'API Keys';
$activeNav = 'admin-api-keys';
$pageStyles = ['/assets/css/dashboard.css?v=20260327'];
$_v = $GLOBALS['config']['ASSET_VERSION'] ?? '1';
$pageStyles = ["/assets/css/dashboard.css?v={$_v}"];
$pageScripts = ['/assets/js/keyboard-shortcuts.js'];
include __DIR__ . '/../../views/layout_header.php';
?>
+2 -1
View File
@@ -4,7 +4,8 @@ require_once __DIR__ . '/../../middleware/CsrfMiddleware.php';
$nonce = SecurityHeadersMiddleware::getNonce();
$pageTitle = 'Audit Log';
$activeNav = 'admin-audit-log';
$pageStyles = ['/assets/css/dashboard.css?v=20260327'];
$_v = $GLOBALS['config']['ASSET_VERSION'] ?? '1';
$pageStyles = ["/assets/css/dashboard.css?v={$_v}"];
$pageScripts = ['/assets/js/keyboard-shortcuts.js'];
include __DIR__ . '/../../views/layout_header.php';
?>
+2 -1
View File
@@ -4,7 +4,8 @@ require_once __DIR__ . '/../../middleware/CsrfMiddleware.php';
$nonce = SecurityHeadersMiddleware::getNonce();
$pageTitle = 'Custom Fields';
$activeNav = 'admin-custom-fields';
$pageStyles = ['/assets/css/dashboard.css?v=20260327'];
$_v = $GLOBALS['config']['ASSET_VERSION'] ?? '1';
$pageStyles = ["/assets/css/dashboard.css?v={$_v}"];
$pageScripts = ['/assets/js/keyboard-shortcuts.js'];
include __DIR__ . '/../../views/layout_header.php';
?>
+2 -1
View File
@@ -4,7 +4,8 @@ require_once __DIR__ . '/../../middleware/CsrfMiddleware.php';
$nonce = SecurityHeadersMiddleware::getNonce();
$pageTitle = 'Recurring Tickets';
$activeNav = 'admin-recurring';
$pageStyles = ['/assets/css/dashboard.css?v=20260327'];
$_v = $GLOBALS['config']['ASSET_VERSION'] ?? '1';
$pageStyles = ["/assets/css/dashboard.css?v={$_v}"];
$pageScripts = ['/assets/js/keyboard-shortcuts.js'];
include __DIR__ . '/../../views/layout_header.php';
?>
+2 -1
View File
@@ -4,7 +4,8 @@ require_once __DIR__ . '/../../middleware/CsrfMiddleware.php';
$nonce = SecurityHeadersMiddleware::getNonce();
$pageTitle = 'Templates';
$activeNav = 'admin-templates';
$pageStyles = ['/assets/css/dashboard.css?v=20260327'];
$_v = $GLOBALS['config']['ASSET_VERSION'] ?? '1';
$pageStyles = ["/assets/css/dashboard.css?v={$_v}"];
$pageScripts = ['/assets/js/keyboard-shortcuts.js'];
include __DIR__ . '/../../views/layout_header.php';
?>
+2 -1
View File
@@ -4,7 +4,8 @@ require_once __DIR__ . '/../../middleware/CsrfMiddleware.php';
$nonce = SecurityHeadersMiddleware::getNonce();
$pageTitle = 'User Activity';
$activeNav = 'admin-user-activity';
$pageStyles = ['/assets/css/dashboard.css?v=20260327'];
$_v = $GLOBALS['config']['ASSET_VERSION'] ?? '1';
$pageStyles = ["/assets/css/dashboard.css?v={$_v}"];
$pageScripts = ['/assets/js/keyboard-shortcuts.js'];
include __DIR__ . '/../../views/layout_header.php';
?>
+2 -1
View File
@@ -4,7 +4,8 @@ require_once __DIR__ . '/../../middleware/CsrfMiddleware.php';
$nonce = SecurityHeadersMiddleware::getNonce();
$pageTitle = 'Workflow Designer';
$activeNav = 'admin-workflow';
$pageStyles = ['/assets/css/dashboard.css?v=20260327'];
$_v = $GLOBALS['config']['ASSET_VERSION'] ?? '1';
$pageStyles = ["/assets/css/dashboard.css?v={$_v}"];
$pageScripts = ['/assets/js/keyboard-shortcuts.js'];
include __DIR__ . '/../../views/layout_header.php';
?>