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

@@ -570,8 +570,10 @@ class ApiClient {
return this.put(`/tasks/${taskId}/archive`, { archived: true });
}
async restoreTask(projectId, taskId) {
return this.put(`/tasks/${taskId}/archive`, { archived: false });
async restoreTask(projectId, taskId, columnId = null) {
const data = { archived: false };
if (columnId) data.columnId = columnId;
return this.put(`/tasks/${taskId}/archive`, data);
}
async getTaskHistory(projectId, taskId) {

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
// =====================

Datei anzeigen

@@ -26,9 +26,6 @@ class BoardManager {
this.weekStripDate = this.getMonday(new Date());
this.tooltip = null;
// Layout preferences
this.multiColumnLayout = this.loadLayoutPreference();
this.init();
}
@@ -56,20 +53,6 @@ class BoardManager {
this.renderWeekStrip();
});
// Apply initial layout preference
setTimeout(() => {
this.applyLayoutClass();
if (this.multiColumnLayout) {
this.checkAndApplyDynamicLayout();
}
}, 100);
// Re-check layout on window resize
window.addEventListener('resize', debounce(() => {
if (this.multiColumnLayout) {
this.checkAndApplyDynamicLayout();
}
}, 250));
}
// =====================
@@ -163,12 +146,6 @@ class BoardManager {
// Keyboard navigation
document.addEventListener('keydown', (e) => this.handleKeyboard(e));
// Layout toggle button - use delegated event handling
document.addEventListener('click', (e) => {
if (e.target.closest('#btn-toggle-layout')) {
this.toggleLayout();
}
});
}
// =====================
@@ -180,9 +157,6 @@ class BoardManager {
const columns = store.get('columns');
clearElement(this.boardElement);
// Apply layout class
this.applyLayoutClass();
columns.forEach(column => {
const columnElement = this.createColumnElement(column);
@@ -198,9 +172,9 @@ class BoardManager {
'Statuskarte hinzufügen'
]);
this.boardElement.appendChild(addColumnBtn);
// Check dynamic layout after render
setTimeout(() => this.checkAndApplyDynamicLayout(), 100);
// Emit event for mobile column navigation
document.dispatchEvent(new CustomEvent('columns:updated'));
}
createColumnElement(column) {
@@ -506,9 +480,6 @@ class BoardManager {
countElement.textContent = filteredTasks.length.toString();
}
});
// Check if dynamic layout adjustment is needed
setTimeout(() => this.checkAndApplyDynamicLayout(), 100);
}
// =====================
@@ -1496,102 +1467,6 @@ class BoardManager {
return div.innerHTML;
}
// =====================
// LAYOUT PREFERENCES
// =====================
loadLayoutPreference() {
const stored = localStorage.getItem('taskmate:boardLayout');
return stored === 'multiColumn';
}
saveLayoutPreference(multiColumn) {
localStorage.setItem('taskmate:boardLayout', multiColumn ? 'multiColumn' : 'single');
}
toggleLayout() {
this.multiColumnLayout = !this.multiColumnLayout;
this.saveLayoutPreference(this.multiColumnLayout);
this.applyLayoutClass();
this.checkAndApplyDynamicLayout();
this.showSuccess(this.multiColumnLayout
? 'Mehrspalten-Layout aktiviert'
: 'Einspalten-Layout aktiviert'
);
}
applyLayoutClass() {
const toggleBtn = $('#btn-toggle-layout');
if (this.multiColumnLayout) {
this.boardElement?.classList.add('multi-column-layout');
toggleBtn?.classList.add('active');
} else {
this.boardElement?.classList.remove('multi-column-layout');
toggleBtn?.classList.remove('active');
// Remove all dynamic classes when disabled
const columns = this.boardElement?.querySelectorAll('.column');
columns?.forEach(column => {
const columnBody = column.querySelector('.column-body');
columnBody?.classList.remove('dynamic-2-columns', 'dynamic-3-columns');
column.classList.remove('expanded-2x', 'expanded-3x');
});
}
}
checkAndApplyDynamicLayout() {
if (!this.multiColumnLayout || !this.boardElement) return;
// Debug logging
console.log('[Layout Check] Checking dynamic layout...');
// Check each column to see if scrolling is needed
const columns = this.boardElement.querySelectorAll('.column');
columns.forEach(column => {
const columnBody = column.querySelector('.column-body');
if (!columnBody) return;
// Remove dynamic classes first
columnBody.classList.remove('dynamic-2-columns', 'dynamic-3-columns');
column.classList.remove('expanded-2x', 'expanded-3x');
// Force reflow to get accurate measurements
columnBody.offsetHeight;
const scrollHeight = columnBody.scrollHeight;
const clientHeight = columnBody.clientHeight;
const hasOverflow = scrollHeight > clientHeight;
console.log('[Layout Check]', {
column: column.dataset.columnId,
scrollHeight,
clientHeight,
hasOverflow,
ratio: scrollHeight / clientHeight
});
// Check if content overflows
if (hasOverflow && clientHeight > 0) {
// Calculate how many columns we need based on content height
const ratio = scrollHeight / clientHeight;
if (ratio > 2.5 && window.innerWidth >= 1800) {
// Need 3 columns
console.log('[Layout] Applying 3 columns');
columnBody.classList.add('dynamic-3-columns');
column.classList.add('expanded-3x');
} else if (ratio > 1.1 && window.innerWidth >= 1400) {
// Need 2 columns (reduced threshold to 1.1)
console.log('[Layout] Applying 2 columns');
columnBody.classList.add('dynamic-2-columns');
column.classList.add('expanded-2x');
}
}
});
}
}
// Create and export singleton

Datei anzeigen

@@ -712,7 +712,11 @@ class CalendarViewManager {
filteredTasks = filteredTasks.filter(task => {
const filterCategory = columnFilterMap[task.columnId];
// If no filter state exists for this category, default to showing "in_progress"
// If column has 'in_progress' filter category, check if 'in_progress' filter is enabled
if (filterCategory === 'in_progress') {
return this.enabledFilters['in_progress'] !== false;
}
// For other categories, check their specific filter state
if (this.enabledFilters[filterCategory] === undefined) {
// Default: show in_progress, hide open and completed
return filterCategory === 'in_progress';

Datei-Diff unterdrückt, da er zu groß ist Diff laden

Datei anzeigen

@@ -5,6 +5,7 @@
*/
import { $, $$ } from './utils.js';
import { enhanceMobileSwipe } from './mobile-swipe.js';
class MobileManager {
constructor() {
@@ -21,6 +22,8 @@ class MobileManager {
this.touchStartTime = 0;
this.isSwiping = false;
this.swipeDirection = null;
this.currentColumnIndex = 0;
this.swipeTarget = null; // 'board' or 'header'
// Touch drag & drop state
this.touchDraggedElement = null;
@@ -87,6 +90,9 @@ class MobileManager {
this.updateUserInfo();
});
// Enhance with new swipe functionality
enhanceMobileSwipe(this);
console.log('[Mobile] Initialized');
}
@@ -220,8 +226,18 @@ class MobileManager {
$$('.view').forEach(v => {
const viewName = v.id.replace('view-', '');
const isActive = viewName === view;
v.classList.toggle('active', isActive);
v.classList.toggle('hidden', !isActive);
if (isActive) {
v.classList.add('active');
v.classList.remove('hidden');
// Force display for mobile
if (this.isMobile) {
v.style.display = '';
}
} else {
v.classList.remove('active');
v.classList.add('hidden');
}
});
// Update mobile nav
@@ -300,41 +316,42 @@ class MobileManager {
bindSwipeEvents() {
if (!this.mainContent) return;
this.mainContent.addEventListener('touchstart', (e) => this.handleSwipeStart(e), { passive: true });
this.mainContent.addEventListener('touchmove', (e) => this.handleSwipeMove(e), { passive: false });
this.mainContent.addEventListener('touchend', (e) => this.handleSwipeEnd(e), { passive: true });
this.mainContent.addEventListener('touchcancel', () => this.resetSwipe(), { passive: true });
// Swipe für Board-Container (Spalten-Navigation)
const boardContainer = document.querySelector('.board-container');
if (boardContainer) {
boardContainer.addEventListener('touchstart', (e) => this.handleBoardSwipeStart(e), { passive: true });
boardContainer.addEventListener('touchmove', (e) => this.handleBoardSwipeMove(e), { passive: false });
boardContainer.addEventListener('touchend', (e) => this.handleBoardSwipeEnd(e), { passive: true });
boardContainer.addEventListener('touchcancel', () => this.resetSwipe(), { passive: true });
}
// Swipe für Header (View-Navigation)
const header = document.querySelector('.header');
if (header) {
header.addEventListener('touchstart', (e) => this.handleHeaderSwipeStart(e), { passive: true });
header.addEventListener('touchmove', (e) => this.handleHeaderSwipeMove(e), { passive: false });
header.addEventListener('touchend', (e) => this.handleHeaderSwipeEnd(e), { passive: true });
header.addEventListener('touchcancel', () => this.resetSwipe(), { passive: true });
}
}
/**
* Handle swipe start
* Handle board swipe start (für Spalten-Navigation)
*/
handleSwipeStart(e) {
if (!this.isMobile) return;
handleBoardSwipeStart(e) {
if (!this.isMobile || this.currentView !== 'board') return;
if (this.isMenuOpen || $('.modal-overlay:not(.hidden)')) return;
// Don't swipe if menu is open
if (this.isMenuOpen) return;
// Don't swipe if modal is open
if ($('.modal-overlay:not(.hidden)')) return;
// Don't swipe on specific interactive elements, but allow swipe in column-body
// Don't swipe on interactive elements
const target = e.target;
if (target.closest('.modal') ||
target.closest('.calendar-grid') ||
target.closest('.knowledge-entry-list') ||
target.closest('.list-table') ||
target.closest('input') ||
target.closest('textarea') ||
if (target.closest('button') ||
target.closest('input') ||
target.closest('textarea') ||
target.closest('select') ||
target.closest('button') ||
target.closest('a[href]') ||
target.closest('.task-card .priority-stars') ||
target.closest('.task-card .task-counts')) {
target.closest('.task-card')) {
return;
}
// Only single touch
if (e.touches.length !== 1) return;
this.touchStartX = e.touches[0].clientX;
@@ -342,12 +359,79 @@ class MobileManager {
this.touchStartTime = Date.now();
this.isSwiping = false;
this.swipeDirection = null;
this.swipeTarget = 'board';
}
/**
* Handle swipe move
* Handle header swipe start (für View-Navigation)
*/
handleSwipeMove(e) {
handleHeaderSwipeStart(e) {
if (!this.isMobile) return;
if (this.isMenuOpen || $('.modal-overlay:not(.hidden)')) return;
// Nur in der Header-Region swipen
if (!e.target.closest('.header')) return;
// Nicht auf Buttons swipen
if (e.target.closest('button') || e.target.closest('.view-tab')) return;
if (e.touches.length !== 1) return;
this.touchStartX = e.touches[0].clientX;
this.touchStartY = e.touches[0].clientY;
this.touchStartTime = Date.now();
this.isSwiping = false;
this.swipeDirection = null;
this.swipeTarget = 'header';
}
/**
* Handle board swipe move
*/
handleBoardSwipeMove(e) {
if (!this.isMobile || this.touchStartX === 0 || this.swipeTarget !== 'board') return;
const touch = e.touches[0];
this.touchCurrentX = touch.clientX;
this.touchCurrentY = touch.clientY;
const deltaX = this.touchCurrentX - this.touchStartX;
const deltaY = this.touchCurrentY - this.touchStartY;
// Determine direction
if (!this.swipeDirection && (Math.abs(deltaX) > 10 || Math.abs(deltaY) > 10)) {
if (Math.abs(deltaX) > Math.abs(deltaY) * 1.5) {
this.swipeDirection = 'horizontal';
this.isSwiping = true;
document.body.classList.add('is-swiping');
} else {
this.swipeDirection = 'vertical';
this.resetSwipe();
return;
}
}
if (this.swipeDirection !== 'horizontal') return;
e.preventDefault();
// Visual feedback für Spalten-Navigation
const columns = $$('.column');
if (deltaX > this.SWIPE_THRESHOLD && this.currentColumnIndex > 0) {
this.swipeIndicatorLeft?.classList.add('visible');
this.swipeIndicatorRight?.classList.remove('visible');
} else if (deltaX < -this.SWIPE_THRESHOLD && this.currentColumnIndex < columns.length - 1) {
this.swipeIndicatorRight?.classList.add('visible');
this.swipeIndicatorLeft?.classList.remove('visible');
} else {
this.swipeIndicatorLeft?.classList.remove('visible');
this.swipeIndicatorRight?.classList.remove('visible');
}
}
/**
* Handle header swipe move
*/
handleHeaderSwipeMove(e) {
if (!this.isMobile || this.touchStartX === 0) return;
const touch = e.touches[0];
@@ -412,10 +496,11 @@ class MobileManager {
* Handle swipe end
*/
handleSwipeEnd() {
if (!this.isSwiping || this.swipeDirection !== 'horizontal') {
this.resetSwipe();
return;
}
if (!this.isMobile || this.touchStartX === 0 || this.swipeTarget !== 'header') return;
const touch = e.touches[0];
this.touchCurrentX = touch.clientX;
this.touchCurrentY = touch.clientY;
const deltaX = this.touchCurrentX - this.touchStartX;
const deltaTime = Date.now() - this.touchStartTime;

Datei anzeigen

@@ -32,10 +32,10 @@ class Store {
// Filters
filters: {
search: '',
priority: 'all',
assignee: 'all',
label: 'all',
dueDate: 'all',
priority: '',
assignee: '',
label: '',
dueDate: '',
archived: false
},
@@ -155,10 +155,10 @@ class Store {
labels: [],
filters: {
search: '',
priority: 'all',
assignee: 'all',
label: 'all',
dueDate: 'all',
priority: '',
assignee: '',
label: '',
dueDate: '',
archived: false
},
searchResultIds: [],
@@ -412,10 +412,10 @@ class Store {
this.setState({
filters: {
search: '',
priority: 'all',
assignee: 'all',
label: 'all',
dueDate: 'all',
priority: '',
assignee: '',
label: '',
dueDate: '',
archived: false
}
}, 'RESET_FILTERS');

Datei anzeigen

@@ -49,6 +49,16 @@ class SyncManager {
});
this.setupEventListeners();
// Update body offline class based on sync status
store.subscribe('syncStatus', () => {
const status = store.get('syncStatus');
if (status === 'offline' || status === 'error') {
document.body.classList.add('offline');
} else {
document.body.classList.remove('offline');
}
});
} catch (error) {
console.error('[Sync] Failed to connect:', error);
store.setSyncStatus('error');

Datei anzeigen

@@ -587,10 +587,38 @@ class TaskModalManager {
this.close();
this.showSuccess('Aufgabe wiederhergestellt');
} catch (error) {
this.showError('Fehler beim Wiederherstellen');
// Prüfen ob Spaltenauswahl erforderlich ist
if (error.data?.requiresColumn) {
this.showColumnSelectDialog();
} else {
this.showError('Fehler beim Wiederherstellen');
}
}
}
showColumnSelectDialog() {
const columns = store.get('columns');
// Modal für Spaltenauswahl erstellen
window.dispatchEvent(new CustomEvent('column-select:show', {
detail: {
message: 'Die ursprüngliche Spalte existiert nicht mehr. Bitte wählen Sie eine Spalte:',
columns: columns,
onSelect: async (columnId) => {
try {
const projectId = store.get('currentProjectId');
await api.restoreTask(projectId, this.taskId, columnId);
store.updateTask(this.taskId, { archived: false, columnId: columnId });
this.close();
this.showSuccess('Aufgabe wiederhergestellt');
} catch (error) {
this.showError('Fehler beim Wiederherstellen');
}
}
}
}));
}
async autoSaveDescription() {
// Deprecated - use autoSaveTask instead
await this.autoSaveTask();

Datei anzeigen

@@ -492,37 +492,55 @@ export function filterTasks(tasks, filters, searchResultIds = [], columns = [])
}
}
// Priority filter
if (filters.priority && filters.priority !== 'all') {
if (task.priority !== filters.priority) return false;
// Priority filter (string or array)
if (filters.priority && filters.priority.length > 0) {
const priorityFilter = Array.isArray(filters.priority) ? filters.priority : [filters.priority];
if (!priorityFilter.includes(task.priority)) return false;
}
// Assignee filter
if (filters.assignee && filters.assignee !== 'all') {
if (task.assignedTo !== parseInt(filters.assignee)) return false;
// Assignee filter (string or array)
if (filters.assignee && filters.assignee.length > 0) {
const assigneeFilter = Array.isArray(filters.assignee) ? filters.assignee : [filters.assignee];
const assigneeIds = assigneeFilter.map(id => parseInt(id));
const taskAssigneeId = task.assignedTo;
const taskAssignees = task.assignees?.map(a => a.id || a) || [];
const matches = assigneeIds.some(id => id === taskAssigneeId || taskAssignees.includes(id));
if (!matches) return false;
}
// Label filter
if (filters.label && filters.label !== 'all') {
const hasLabel = task.labels?.some(l => l.id === parseInt(filters.label));
// Label filter (string or array)
if (filters.label && filters.label.length > 0) {
const labelFilter = Array.isArray(filters.label) ? filters.label : [filters.label];
const labelIds = labelFilter.map(id => parseInt(id));
const hasLabel = task.labels?.some(l => labelIds.includes(l.id));
if (!hasLabel) return false;
}
// Due date filter
if (filters.dueDate && filters.dueDate !== 'all' && filters.dueDate !== '') {
const status = getDueDateStatus(task.dueDate);
// Due date filter (string or array)
if (filters.dueDate && filters.dueDate.length > 0) {
const dueDateFilter = Array.isArray(filters.dueDate) ? filters.dueDate : [filters.dueDate];
let matches = false;
// Bei "überfällig" erledigte Aufgaben ausschließen
if (filters.dueDate === 'overdue') {
if (status !== 'overdue' || isTaskCompleted(task)) return false;
}
if (filters.dueDate === 'today' && status !== 'today') return false;
if (filters.dueDate === 'week') {
const due = new Date(task.dueDate);
const weekFromNow = new Date();
weekFromNow.setDate(weekFromNow.getDate() + 7);
if (!task.dueDate || due > weekFromNow) return false;
for (const filterVal of dueDateFilter) {
if (filterVal === 'none') {
if (!task.dueDate) { matches = true; break; }
} else if (filterVal === 'overdue') {
const status = getDueDateStatus(task.dueDate);
if (status === 'overdue' && !isTaskCompleted(task)) { matches = true; break; }
} else if (filterVal === 'today') {
const status = getDueDateStatus(task.dueDate);
if (status === 'today') { matches = true; break; }
} else if (filterVal === 'week') {
if (task.dueDate) {
const due = new Date(task.dueDate);
const weekFromNow = new Date();
weekFromNow.setDate(weekFromNow.getDate() + 7);
if (due <= weekFromNow) { matches = true; break; }
}
}
}
if (!matches) return false;
}
return true;