Add UI enhancements and new features

Keyboard Navigation:
- Add J/K keys for Gmail-style ticket list navigation
- Add N key for new ticket, C for comment focus
- Add G then D for go to dashboard (vim-style)
- Add 1-4 number keys for quick status changes on ticket page
- Add Enter to open selected ticket
- Update keyboard help modal with all new shortcuts

Ticket Age Indicator:
- Show "Last activity: X days ago" on ticket view
- Visual warning (yellow pulse) for tickets idle >5 days
- Critical warning (red pulse) for tickets idle >10 days

Ticket Clone Feature:
- Add "Clone" button on ticket view
- Creates copy with [CLONE] prefix in title
- Preserves description, priority, category, type, visibility
- Automatically creates "relates_to" dependency to original

Active Filter Badges:
- Show visual badges above ticket table for active filters
- Click X on badge to remove individual filter
- "Clear All" button to reset all filters
- Color-coded by filter type (status, priority, search)

Visual Enhancements:
- Add keyboard-selected row highlighting for J/K navigation
- Smooth animations for filter badges

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-01-30 19:21:36 -05:00
parent 9b40a714ed
commit 1c1eb19876
7 changed files with 565 additions and 6 deletions

View File

@@ -203,10 +203,76 @@ document.addEventListener('DOMContentLoaded', function() {
case 'open-settings-modal':
if (typeof openSettingsModal === 'function') openSettingsModal();
break;
// Filter badge actions
case 'remove-filter':
removeFilter(target.dataset.filterType, target.dataset.filterValue);
break;
case 'clear-all-filters':
clearAllFilters();
break;
}
});
});
/**
* Remove a single filter and reload page
*/
function removeFilter(filterType, filterValue) {
const params = new URLSearchParams(window.location.search);
if (filterType === 'status') {
const currentStatuses = (params.get('status') || '').split(',').filter(s => s.trim());
const newStatuses = currentStatuses.filter(s => s !== filterValue);
if (newStatuses.length > 0) {
params.set('status', newStatuses.join(','));
} else {
params.delete('status');
}
} else if (filterType === 'priority') {
const currentPriorities = (params.get('priority') || '').split(',').filter(p => p.trim());
const newPriorities = currentPriorities.filter(p => p !== filterValue);
if (newPriorities.length > 0) {
params.set('priority', newPriorities.join(','));
} else {
params.delete('priority');
}
} else if (filterType === 'search') {
params.delete('search');
} else {
params.delete(filterType);
}
// Reset to page 1 when changing filters
params.delete('page');
window.location.search = params.toString();
}
/**
* Clear all filters and reload page
*/
function clearAllFilters() {
const params = new URLSearchParams(window.location.search);
// Remove all filter parameters
params.delete('status');
params.delete('priority');
params.delete('category');
params.delete('type');
params.delete('assigned_to');
params.delete('search');
params.delete('date_from');
params.delete('date_to');
params.delete('page');
// Keep sort parameters
const sortParams = new URLSearchParams();
if (params.has('sort')) sortParams.set('sort', params.get('sort'));
if (params.has('dir')) sortParams.set('dir', params.get('dir'));
window.location.search = sortParams.toString();
}
function initTableSorting() {
const tableHeaders = document.querySelectorAll('th');
tableHeaders.forEach((header, index) => {