UI-Redesign: AegisSight Design, Filter-Popover, Header-Umbau

- Session-Timeout auf 60 Minuten erhöht (ACCESS_TOKEN_EXPIRY + SESSION_TIMEOUT)
- AegisSight Light Theme: Gold-Akzent (#C8A851) statt Indigo
- Navigation-Tabs in eigene Zeile unter Header verschoben (HTML-Struktur)
- Filter-Bar durch kompaktes Popover mit Checkboxen ersetzt (Mehrfachauswahl)
- Archiv-Funktion repariert (lädt jetzt per API statt leerem Store)
- Filter-Bugs behoben: Reset-Button ID, Default-Werte, Ohne-Datum-Filter
- Mehrspalten-Layout Feature entfernt
- Online-Status vom Header an User-Avatar verschoben (grüner Punkt)
- Lupen-Icon entfernt
- CLAUDE.md: Docker-Deploy und CSS-Tricks Regeln aktualisiert

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Dieser Commit ist enthalten in:
Server Deploy
2026-03-19 18:49:38 +01:00
Ursprung 99a6b7437b
Commit 4bd57d653f
36 geänderte Dateien mit 5027 neuen und 2897 gelöschten Zeilen

Datei anzeigen

@@ -263,27 +263,33 @@ class App {
// Search - Hybrid search with client-side filtering and server search
this.setupSearch();
// Filter changes
$('#filter-priority')?.addEventListener('change', (e) => {
store.setFilter('priority', e.target.value);
// Filter toggle popover
$('#btn-filter-toggle')?.addEventListener('click', (e) => {
e.stopPropagation();
$('#filter-popover')?.classList.toggle('hidden');
});
$('#filter-assignee')?.addEventListener('change', (e) => {
store.setFilter('assignee', e.target.value);
// Close popover on click outside
document.addEventListener('click', (e) => {
const popover = $('#filter-popover');
const toggleBtn = $('#btn-filter-toggle');
if (popover && !popover.contains(e.target) && !toggleBtn?.contains(e.target)) {
popover.classList.add('hidden');
}
});
$('#filter-labels')?.addEventListener('change', (e) => {
store.setFilter('label', e.target.value);
});
$('#filter-due')?.addEventListener('change', (e) => {
store.setFilter('dueDate', e.target.value);
});
// Filter changes - checkbox listeners for static filters
$('#filter-priority-list')?.addEventListener('change', () => this.applyCheckboxFilters());
$('#filter-due-list')?.addEventListener('change', () => this.applyCheckboxFilters());
$('#filter-assignee-list')?.addEventListener('change', () => this.applyCheckboxFilters());
$('#filter-labels-list')?.addEventListener('change', () => this.applyCheckboxFilters());
// Reset filters
$('#btn-reset-filters')?.addEventListener('click', () => {
$('#btn-clear-filters')?.addEventListener('click', () => {
store.resetFilters();
this.resetFilterInputs();
this.updateFilterButtonState();
$('#filter-popover')?.classList.add('hidden');
});
// Open archive modal
@@ -301,6 +307,9 @@ class App {
// Confirm dialog events
window.addEventListener('confirm:show', (e) => this.showConfirmDialog(e.detail));
// Column select dialog events
window.addEventListener('column-select:show', (e) => this.showColumnSelectDialog(e.detail));
// Lightbox events
window.addEventListener('lightbox:open', (e) => this.openLightbox(e.detail));
@@ -702,11 +711,13 @@ class App {
// Initialize contacts view when switching to it
if (view === 'contacts') {
window.initContactsPromise = window.initContactsPromise || import('./contacts.js').then(module => {
if (module.initContacts) {
return module.initContacts();
if (module.contactsManager && !module.contactsManager.isInitialized) {
return module.contactsManager.init();
}
});
window.initContactsPromise.catch(console.error);
window.initContactsPromise.catch(error => {
console.error('[App] Contacts module error:', error);
});
}
// Render list view when switching to it (delayed to ensure store is updated)
@@ -752,65 +763,114 @@ class App {
}
populateAssigneeFilter() {
const select = $('#filter-assignee');
if (!select) return;
const list = $('#filter-assignee-list');
if (!list) return;
const users = store.get('users');
select.innerHTML = '<option value="all">Alle Bearbeiter</option>';
list.innerHTML = '';
users.forEach(user => {
const option = document.createElement('option');
option.value = user.id;
option.textContent = user.displayName || user.email || 'Unbekannt';
select.appendChild(option);
const label = document.createElement('label');
label.className = 'filter-checkbox-item';
label.innerHTML = `<input type="checkbox" name="filter-assignee" value="${user.id}"><span>${user.displayName || user.email || 'Unbekannt'}</span>`;
list.appendChild(label);
});
}
populateLabelFilter() {
const select = $('#filter-labels');
if (!select) return;
const list = $('#filter-labels-list');
if (!list) return;
const labels = store.get('labels');
select.innerHTML = '<option value="all">Alle Labels</option>';
list.innerHTML = '';
labels.forEach(label => {
const option = document.createElement('option');
option.value = label.id;
option.textContent = label.name;
select.appendChild(option);
const item = document.createElement('label');
item.className = 'filter-checkbox-item';
item.innerHTML = `<input type="checkbox" name="filter-labels" value="${label.id}"><span>${label.name}</span>`;
list.appendChild(item);
});
}
applyCheckboxFilters() {
const getCheckedValues = (name) => {
const checkboxes = document.querySelectorAll(`input[name="${name}"]:checked`);
return Array.from(checkboxes).map(cb => cb.value);
};
const priority = getCheckedValues('filter-priority');
const assignee = getCheckedValues('filter-assignee');
const label = getCheckedValues('filter-labels');
const dueDate = getCheckedValues('filter-due');
store.setFilter('priority', priority.length > 0 ? priority : '');
store.setFilter('assignee', assignee.length > 0 ? assignee : '');
store.setFilter('label', label.length > 0 ? label : '');
store.setFilter('dueDate', dueDate.length > 0 ? dueDate : '');
this.updateFilterButtonState();
}
resetFilterInputs() {
const priority = $('#filter-priority');
const assignee = $('#filter-assignee');
const labels = $('#filter-labels');
const dueDate = $('#filter-due');
const checkboxes = document.querySelectorAll('#filter-popover input[type="checkbox"]');
checkboxes.forEach(cb => cb.checked = false);
const search = $('#search-input');
const searchClear = $('#search-clear');
const searchContainer = $('.search-container');
if (priority) priority.value = 'all';
if (assignee) assignee.value = 'all';
if (labels) labels.value = 'all';
if (dueDate) dueDate.value = '';
if (search) search.value = '';
if (searchClear) searchClear.classList.add('hidden');
if (searchContainer) searchContainer.classList.remove('has-search');
}
openArchiveModal() {
this.handleModalOpen({ modalId: 'archive-modal' });
this.renderArchiveList();
updateFilterButtonState() {
const btn = $('#btn-filter-toggle');
if (!btn) return;
const groups = ['filter-priority', 'filter-assignee', 'filter-labels', 'filter-due'];
const activeCount = groups.filter(name => {
return document.querySelectorAll(`input[name="${name}"]:checked`).length > 0;
}).length;
const label = btn.querySelector('span');
if (label) {
label.textContent = activeCount > 0 ? `Filter (${activeCount})` : 'Filter';
}
btn.classList.toggle('has-filters', activeCount > 0);
}
renderArchiveList() {
async openArchiveModal() {
this.handleModalOpen({ modalId: 'archive-modal' });
const archiveList = $('#archive-list');
const archiveEmpty = $('#archive-empty');
// Ladeindikator anzeigen
if (archiveList) {
archiveList.classList.remove('hidden');
archiveList.innerHTML = '<div style="text-align:center;padding:2rem;color:var(--text-secondary)">Lade archivierte Aufgaben...</div>';
}
if (archiveEmpty) archiveEmpty.classList.add('hidden');
try {
const projectId = store.get('currentProjectId');
const allTasks = await api.getTasks(projectId, { archived: true });
const archivedTasks = allTasks.filter(t => t.archived);
this.renderArchiveList(archivedTasks);
} catch (error) {
console.error('Failed to load archived tasks:', error);
if (archiveList) {
archiveList.innerHTML = '<div style="text-align:center;padding:2rem;color:var(--danger)">Fehler beim Laden der archivierten Aufgaben</div>';
}
}
}
renderArchiveList(tasks) {
const archiveList = $('#archive-list');
const archiveEmpty = $('#archive-empty');
if (!archiveList) return;
// Get all archived tasks
const tasks = store.get('tasks').filter(t => t.archived);
const columns = store.get('columns');
if (tasks.length === 0) {
@@ -855,13 +915,16 @@ class App {
const projectId = store.get('currentProjectId');
await api.restoreTask(projectId, taskId);
store.updateTask(taskId, { archived: false });
this.renderArchiveList();
this.showSuccess('Aufgabe wiederhergestellt');
// Re-render board
if (this.board) {
this.board.render();
}
// Archivliste neu laden
const allTasks = await api.getTasks(projectId, { archived: true });
this.renderArchiveList(allTasks.filter(t => t.archived));
} catch (error) {
console.error('Restore error:', error);
this.showError('Fehler beim Wiederherstellen');
@@ -889,8 +952,11 @@ class App {
const projectId = store.get('currentProjectId');
await api.deleteTask(projectId, taskId);
store.removeTask(taskId);
this.renderArchiveList();
this.showSuccess('Aufgabe gelöscht');
// Archivliste neu laden
const allTasks = await api.getTasks(projectId, { archived: true });
this.renderArchiveList(allTasks.filter(t => t.archived));
} catch (error) {
this.showError('Fehler beim Löschen');
}
@@ -944,9 +1010,9 @@ class App {
import('./contacts.js').then(module => {
if (module.contactsManager) {
module.contactsManager.searchQuery = '';
module.contactsManager.filterContacts();
module.contactsManager.loadContacts();
}
});
}).catch(() => {});
// Cancel any pending server search
if (searchAbortController) {
@@ -1027,9 +1093,9 @@ class App {
import('./contacts.js').then(module => {
if (module.contactsManager) {
module.contactsManager.searchQuery = value;
module.contactsManager.filterContacts();
module.contactsManager.loadContacts();
}
});
}).catch(() => {});
} else {
// Immediate client-side filtering for tasks
store.setFilter('search', value);
@@ -1283,6 +1349,55 @@ class App {
cancelBtn?.addEventListener('click', handleCancel);
}
// =====================
// COLUMN SELECT DIALOG
// =====================
showColumnSelectDialog({ message, columns, onSelect }) {
const modal = $('#column-select-modal');
const messageEl = $('#column-select-message');
const dropdown = $('#column-select-dropdown');
const confirmBtn = $('#column-select-ok');
const cancelBtn = $('#column-select-cancel');
if (messageEl) messageEl.textContent = message;
// Spalten in Dropdown einfügen
if (dropdown) {
dropdown.innerHTML = '';
columns.forEach(column => {
const option = document.createElement('option');
option.value = column.id;
option.textContent = column.name;
dropdown.appendChild(option);
});
}
// Show modal
this.handleModalOpen({ modalId: 'column-select-modal' });
// One-time event handlers
const handleConfirm = () => {
const selectedColumnId = parseInt(dropdown?.value);
this.handleModalClose({ modalId: 'column-select-modal' });
if (onSelect && selectedColumnId) onSelect(selectedColumnId);
cleanup();
};
const handleCancel = () => {
this.handleModalClose({ modalId: 'column-select-modal' });
cleanup();
};
const cleanup = () => {
confirmBtn?.removeEventListener('click', handleConfirm);
cancelBtn?.removeEventListener('click', handleCancel);
};
confirmBtn?.addEventListener('click', handleConfirm);
cancelBtn?.addEventListener('click', handleCancel);
}
// =====================
// LIGHTBOX
// =====================