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:
+57
-40
@@ -552,75 +552,92 @@ function showDependencyError(message) {
|
||||
}
|
||||
}
|
||||
|
||||
function _depStatusBadge(status) {
|
||||
const slug = (status || '').toLowerCase().replace(/ /g, '-');
|
||||
const cls = status === 'Closed' ? 'lt-badge-closed' : status === 'Open' ? 'lt-badge-open' : 'lt-badge-sm';
|
||||
return `<span class="lt-badge ${cls} lt-text-xs">${lt.escHtml(status)}</span>`;
|
||||
}
|
||||
|
||||
function renderDependencies(dependencies) {
|
||||
const container = document.getElementById('dependenciesList');
|
||||
if (!container) return;
|
||||
|
||||
const typeLabels = {
|
||||
'blocks': 'Blocks',
|
||||
'blocks': 'Blocks',
|
||||
'blocked_by': 'Blocked By',
|
||||
'relates_to': 'Relates To',
|
||||
'duplicates': 'Duplicates'
|
||||
};
|
||||
|
||||
// Check for open "blocked_by" dependencies — show alert
|
||||
const blockers = (dependencies['blocked_by'] || []).filter(d => d.status !== 'Closed');
|
||||
const blockerAlert = document.getElementById('blockerAlert');
|
||||
if (blockers.length > 0) {
|
||||
const alertHtml = `<div class="lt-alert lt-alert--warning" id="blockerAlert" role="alert" style="margin-bottom:0.75rem">
|
||||
<span class="lt-alert-icon" aria-hidden="true">[!]</span>
|
||||
<div class="lt-alert-body">
|
||||
<div class="lt-alert-title">Blocked</div>
|
||||
<div class="lt-alert-msg">This ticket is blocked by ${blockers.length} open ticket${blockers.length > 1 ? 's' : ''}:
|
||||
${blockers.map(b => `<a href="/ticket/${lt.escHtml(b.depends_on_id)}" class="lt-text-cyan">#${lt.escHtml(b.depends_on_id)}</a>`).join(', ')}
|
||||
</div>
|
||||
</div>
|
||||
</div>`;
|
||||
// Insert blocker alert above the frame if not already there
|
||||
const panel = document.getElementById('dependencies-panel');
|
||||
if (panel && !panel.querySelector('#blockerAlert')) {
|
||||
panel.insertAdjacentHTML('afterbegin', alertHtml);
|
||||
}
|
||||
}
|
||||
|
||||
let html = '';
|
||||
let hasAny = false;
|
||||
|
||||
for (const [type, items] of Object.entries(dependencies)) {
|
||||
if (items.length > 0) {
|
||||
hasAny = true;
|
||||
html += `<div class="dependency-group">
|
||||
<h4>${typeLabels[type]}</h4>`;
|
||||
|
||||
items.forEach(dep => {
|
||||
const statusClass = 'status-' + dep.status.toLowerCase().replace(/ /g, '-');
|
||||
html += `<div class="dependency-item">
|
||||
<div>
|
||||
<a href="/ticket/${lt.escHtml(dep.depends_on_id)}">
|
||||
#${lt.escHtml(dep.depends_on_id)}
|
||||
</a>
|
||||
<span class="dependency-title">${lt.escHtml(dep.title)}</span>
|
||||
<span class="status-badge ${statusClass}">${lt.escHtml(dep.status)}</span>
|
||||
</div>
|
||||
<button data-action="remove-dependency" data-dependency-id="${lt.escHtml(String(dep.dependency_id))}" class="lt-btn lt-btn-sm">REMOVE</button>
|
||||
</div>`;
|
||||
});
|
||||
|
||||
html += '</div>';
|
||||
}
|
||||
if (!items.length) continue;
|
||||
hasAny = true;
|
||||
const label = typeLabels[type] || type;
|
||||
html += `<div class="lt-kv-row" style="flex-direction:column;align-items:flex-start;gap:0.3rem">
|
||||
<span class="lt-kv-label lt-text-xs">${lt.escHtml(label)}</span>`;
|
||||
items.forEach(dep => {
|
||||
html += `<div class="lt-flex lt-flex-gap-sm lt-flex-align-center" style="width:100%;padding:0.25rem 0;border-bottom:1px solid rgba(0,255,65,0.08)">
|
||||
<a href="/ticket/${lt.escHtml(dep.depends_on_id)}" class="lt-text-cyan lt-text-xs">
|
||||
#${lt.escHtml(dep.depends_on_id)}
|
||||
</a>
|
||||
<span class="lt-text-sm" style="flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap"
|
||||
title="${lt.escHtml(dep.title)}">${lt.escHtml(dep.title)}</span>
|
||||
${_depStatusBadge(dep.status)}
|
||||
<button data-action="remove-dependency"
|
||||
data-dependency-id="${lt.escHtml(String(dep.dependency_id))}"
|
||||
class="lt-btn lt-btn-ghost lt-btn-sm" aria-label="Remove dependency">✕</button>
|
||||
</div>`;
|
||||
});
|
||||
html += '</div>';
|
||||
}
|
||||
|
||||
if (!hasAny) {
|
||||
html = '<p class="lt-text-muted">No dependencies configured.</p>';
|
||||
}
|
||||
|
||||
container.innerHTML = html;
|
||||
container.innerHTML = hasAny ? `<div class="lt-kv-grid">${html}</div>` : '<p class="lt-text-muted lt-text-sm">No dependencies configured.</p>';
|
||||
}
|
||||
|
||||
function renderDependents(dependents) {
|
||||
const container = document.getElementById('dependentsList');
|
||||
if (!container) return;
|
||||
|
||||
if (dependents.length === 0) {
|
||||
container.innerHTML = '<p class="lt-text-muted">No tickets depend on this one.</p>';
|
||||
if (!dependents.length) {
|
||||
container.innerHTML = '<p class="lt-text-muted lt-text-sm">No tickets depend on this one.</p>';
|
||||
return;
|
||||
}
|
||||
|
||||
const relLabels = { 'blocks':'blocks', 'blocked_by':'blocked by', 'relates_to':'relates to', 'duplicates':'duplicates' };
|
||||
let html = '';
|
||||
dependents.forEach(dep => {
|
||||
const statusClass = 'status-' + dep.status.toLowerCase().replace(/ /g, '-');
|
||||
html += `<div class="dependency-item">
|
||||
<div>
|
||||
<a href="/ticket/${lt.escHtml(dep.ticket_id)}">
|
||||
#${lt.escHtml(dep.ticket_id)}
|
||||
</a>
|
||||
<span class="dependency-title">${lt.escHtml(dep.title)}</span>
|
||||
<span class="status-badge ${statusClass}">${lt.escHtml(dep.status)}</span>
|
||||
<span class="dependency-title lt-text-amber">(${lt.escHtml(dep.dependency_type)})</span>
|
||||
</div>
|
||||
const relLabel = relLabels[dep.dependency_type] || dep.dependency_type;
|
||||
html += `<div class="lt-flex lt-flex-gap-sm lt-flex-align-center" style="padding:0.25rem 0;border-bottom:1px solid rgba(0,255,65,0.08)">
|
||||
<a href="/ticket/${lt.escHtml(dep.ticket_id)}" class="lt-text-cyan lt-text-xs">#${lt.escHtml(dep.ticket_id)}</a>
|
||||
<span class="lt-text-xs lt-text-muted">${lt.escHtml(relLabel)}</span>
|
||||
<span class="lt-text-sm" style="flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap"
|
||||
title="${lt.escHtml(dep.title)}">${lt.escHtml(dep.title)}</span>
|
||||
${_depStatusBadge(dep.status)}
|
||||
</div>`;
|
||||
});
|
||||
|
||||
container.innerHTML = html;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user