feat: saved filter pills, mention autocomplete CSS, tooltips on dashboard table
- Dashboard: saved filter pills row above active filters bar — loads from API, click applies criteria as URL params, hidden when no saved filters exist - ticket.css: add TDS-styled CSS for @mention autocomplete dropdown (was unstyled) - Dashboard table: data-tooltip on Title and Assigned To columns for truncated text (lt.tooltip.init() auto-called by lt.init(), zero extra JS needed) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
+52
-2
@@ -365,6 +365,9 @@ include __DIR__ . '/layout_header.php';
|
||||
</div>
|
||||
</div><!-- /.lt-toolbar -->
|
||||
|
||||
<!-- Saved filter quick-switch pills -->
|
||||
<div id="savedFilterPills" class="saved-filter-pills lt-flex lt-flex-wrap lt-flex-gap-sm" style="display:none;padding:0.35rem 0 0.1rem" aria-label="Saved filters"></div>
|
||||
|
||||
<!-- Active filters bar -->
|
||||
<?php if (!empty($activeFilters)): ?>
|
||||
<div class="active-filters-bar lt-flex lt-flex-wrap lt-flex-gap-sm" role="group" aria-label="Active filters">
|
||||
@@ -502,7 +505,9 @@ include __DIR__ . '/layout_header.php';
|
||||
<?php $badgeClass = match($pNum) { 1 => 'lt-badge-p1', 2 => 'lt-badge-p2', 3 => 'lt-badge-p3', default => 'lt-badge-p4' }; ?>
|
||||
<span class="lt-badge <?= $badgeClass ?>">P<?= $pNum ?></span>
|
||||
</td>
|
||||
<td data-label="Title"><?= htmlspecialchars($row['title']) ?></td>
|
||||
<td data-label="Title" style="max-width:280px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap">
|
||||
<span data-tooltip="<?= htmlspecialchars($row['title'], ENT_QUOTES, 'UTF-8') ?>" data-tooltip-pos="top"><?= htmlspecialchars($row['title']) ?></span>
|
||||
</td>
|
||||
<td data-label="Category" class="lt-text-muted lt-text-xs"><?= htmlspecialchars($row['category']) ?></td>
|
||||
<td data-label="Type" class="lt-text-muted lt-text-xs"><?= htmlspecialchars($row['type']) ?></td>
|
||||
<td data-label="Status">
|
||||
@@ -518,7 +523,12 @@ include __DIR__ . '/layout_header.php';
|
||||
</td>
|
||||
<td data-label="Created By" class="lt-text-xs"><?= $creator ?></td>
|
||||
<td data-label="Assigned To" class="lt-text-xs">
|
||||
<?= ($row['assigned_display_name'] ?? $row['assigned_username'] ?? null) ? $assignedTo : '<span class="lt-text-muted">Unassigned</span>' ?>
|
||||
<?php $assigneeDisplay = $row['assigned_display_name'] ?? $row['assigned_username'] ?? null; ?>
|
||||
<?php if ($assigneeDisplay): ?>
|
||||
<span data-tooltip="<?= htmlspecialchars($assigneeDisplay, ENT_QUOTES, 'UTF-8') ?>"><?= htmlspecialchars($assigneeDisplay) ?></span>
|
||||
<?php else: ?>
|
||||
<span class="lt-text-muted">Unassigned</span>
|
||||
<?php endif ?>
|
||||
</td>
|
||||
<td data-label="Created" class="lt-text-xs lt-text-muted ts-cell"
|
||||
data-ts="<?= htmlspecialchars($row['created_at'], ENT_QUOTES, 'UTF-8') ?>"
|
||||
@@ -916,6 +926,46 @@ if (window.lt) {
|
||||
lt.statsFilter.init();
|
||||
}
|
||||
|
||||
// Saved filter pills — load on page init
|
||||
(function() {
|
||||
lt.api.get('/api/saved_filters.php').then(function(data) {
|
||||
if (!data.success || !data.filters || !data.filters.length) return;
|
||||
var pillsWrap = document.getElementById('savedFilterPills');
|
||||
if (!pillsWrap) return;
|
||||
var html = '<span class="lt-text-xs lt-text-muted" style="align-self:center">Saved:</span>';
|
||||
data.filters.slice(0, 8).forEach(function(f) {
|
||||
var criteria = JSON.stringify(f.filter_criteria);
|
||||
html += '<button type="button" class="lt-btn lt-btn-ghost lt-btn-sm saved-filter-pill" ' +
|
||||
'data-criteria="' + lt.escHtml(criteria) + '" ' +
|
||||
'title="Apply saved filter: ' + lt.escHtml(f.filter_name) + '">' +
|
||||
lt.escHtml(f.filter_name) +
|
||||
(f.is_default ? ' <span style="color:var(--accent-amber)">★</span>' : '') +
|
||||
'</button>';
|
||||
});
|
||||
pillsWrap.innerHTML = html;
|
||||
pillsWrap.style.display = 'flex';
|
||||
|
||||
// Wire clicks: apply filter criteria as URL params
|
||||
pillsWrap.querySelectorAll('.saved-filter-pill').forEach(function(btn) {
|
||||
btn.addEventListener('click', function() {
|
||||
try {
|
||||
var c = JSON.parse(btn.dataset.criteria);
|
||||
var params = new URLSearchParams();
|
||||
if (c.search) params.set('search', c.search);
|
||||
if (c.status && c.status.length) params.set('status', c.status.join(','));
|
||||
if (c.priority_min) params.set('priority_min', c.priority_min);
|
||||
if (c.priority_max) params.set('priority_max', c.priority_max);
|
||||
if (c.assigned_to) params.set('assigned_to', c.assigned_to);
|
||||
if (c.created_by) params.set('created_by', c.created_by);
|
||||
if (c.created_from) params.set('created_from', c.created_from);
|
||||
if (c.created_to) params.set('created_to', c.created_to);
|
||||
window.location.href = '/?' + params.toString();
|
||||
} catch(e) {}
|
||||
});
|
||||
});
|
||||
}).catch(function() {});
|
||||
})();
|
||||
|
||||
// Helper: get date in server timezone
|
||||
function getServerDate() {
|
||||
var now = new Date();
|
||||
|
||||
Reference in New Issue
Block a user