/** * TaskList View Component * Vollständige Task-Verwaltung mit Filtern, Sortierung und Drag & Drop */ import { store } from '../state.js'; import { Task } from '../models.js'; import { Route } from '../router.js'; export class TaskList { constructor(projectId = null) { this.projectId = projectId; this.viewMode = 'list'; // 'list' | 'board' | 'calendar' this.draggedTask = null; } render(container) { this.container = container; this.update(); // Subscriptions this.unsubscribers = [ store.subscribe('tasks:changed', () => this.update()), store.subscribe('projects:changed', () => this.update()), store.subscribe('ui:searchQuery:changed', () => this.update()) ]; } update() { let tasks = this.projectId ? store.getTasksByProject(this.projectId) : store.getFilteredTasks(); // Filter nach aktuellem User wenn "Meine Tasks" if (!this.projectId && window.location.pathname === Route.TASKS) { tasks = tasks.filter(t => t.assigneeId === store.state.currentUser?.id); } this.container.innerHTML = `

${this.projectId ? this.getProjectName() : 'Meine Tasks'}

${tasks.length} Tasks insgesamt

${['list', 'board'].map(mode => ` `).join('')}
${this.projectId ? '' : ` `}
${this.viewMode === 'board' ? this.renderBoardView(tasks) : this.renderListView(tasks)}
`; lucide.createIcons(); this.bindEvents(); } renderListView(tasks) { if (tasks.length === 0) { return this.renderEmptyState(); } const sortedTasks = this.sortTasks(tasks); return `
${sortedTasks.map(task => this.renderTaskRow(task)).join('')}
Task Status Priorität Projekt Fällig Assignee Aktionen
`; } renderBoardView(tasks) { const columns = [ { id: 'todo', title: 'To Do', color: 'gray' }, { id: 'in_progress', title: 'In Progress', color: 'blue' }, { id: 'review', title: 'Review', color: 'yellow' }, { id: 'done', title: 'Done', color: 'green' } ]; return `
${columns.map(col => { const colTasks = tasks.filter(t => t.status === col.id); return `

${col.title}

${colTasks.length}
${colTasks.map(task => this.renderTaskCard(task)).join('')} ${colTasks.length === 0 ? `
Drop tasks here
` : ''}
`; }).join('')}
`; } renderTaskRow(task) { const project = store.state.projects.find(p => p.id === task.projectId); const assignee = store.state.users.find(u => u.id === task.assigneeId); const priorityColors = { low: 'bg-gray-100 text-gray-800 dark:bg-gray-700 dark:text-gray-300', medium: 'bg-blue-100 text-blue-800 dark:bg-blue-900 dark:text-blue-200', high: 'bg-orange-100 text-orange-800 dark:bg-orange-900 dark:text-orange-200', urgent: 'bg-red-100 text-red-800 dark:bg-red-900 dark:text-red-200' }; const statusColors = { todo: 'bg-gray-100 text-gray-800 dark:bg-gray-700 dark:text-gray-300', in_progress: 'bg-blue-100 text-blue-800 dark:bg-blue-900 dark:text-blue-200', review: 'bg-yellow-100 text-yellow-800 dark:bg-yellow-900 dark:text-yellow-200', done: 'bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-200' }; return `
${task.title}
${task.description || 'Keine Beschreibung'}
${this.formatStatus(task.status)} ${this.capitalize(task.priority)} ${project ? `
${project.name}
` : '-'} ${task.dueDate ? `
${new Date(task.dueDate).toLocaleDateString('de-DE')} ${task.isOverdue ? '(überfällig)' : ''}
` : '-'} ${assignee ? ` ${assignee.name} ` : '-'} `; } renderTaskCard(task) { const project = store.state.projects.find(p => p.id === task.projectId); const assignee = store.state.users.find(u => u.id === task.assigneeId); const priorityColors = { low: 'border-l-4 border-gray-400', medium: 'border-l-4 border-blue-500', high: 'border-l-4 border-orange-500', urgent: 'border-l-4 border-red-500' }; return `

${task.title}

${task.isOverdue ? '!' : ''}
${assignee ? `` : ''} ${task.dueDate ? ` ${new Date(task.dueDate).toLocaleDateString('de-DE', {month:'short', day:'numeric'})} ` : ''}
${project ? `` : ''}
`; } renderEmptyState() { return `

Keine Tasks gefunden

Erstellen Sie einen neuen Task oder passen Sie die Filter an.

`; } getProjectName() { const project = store.state.projects.find(p => p.id === this.projectId); return project ? project.name : 'Unbekanntes Projekt'; } sortTasks(tasks) { return tasks.sort((a, b) => { // Überfällige zuerst if (a.isOverdue && !b.isOverdue) return -1; if (!a.isOverdue && b.isOverdue) return 1; // Dann nach Priorität const priorityOrder = { urgent: 0, high: 1, medium: 2, low: 3 }; if (priorityOrder[a.priority] !== priorityOrder[b.priority]) { return priorityOrder[a.priority] - priorityOrder[b.priority]; } // Dann nach Fälligkeitsdatum if (a.dueDate && b.dueDate) { return new Date(a.dueDate) - new Date(b.dueDate); } return 0; }); } capitalize(str) { return str.charAt(0).toUpperCase() + str.slice(1); } formatStatus(status) { const labels = { todo: 'To Do', in_progress: 'In Progress', review: 'Review', done: 'Done' }; return labels[status] || status; } bindEvents() { // View mode toggle window.setViewMode = (mode) => { this.viewMode = mode; this.update(); }; // Filters window.updateSearch = (value) => { store.setUISState('searchQuery', value); }; window.updateFilter = (type, value) => { const filters = { ...store.state.ui.filters, [type]: value || null }; store.setUISState('filters', filters); }; window.clearFilters = () => { store.setUISState('searchQuery', ''); store.setUISState('filters', { priority: null, status: null, assignee: null, project: null }); }; // Task CRUD window.openTaskModal = () => { const title = prompt('Task Titel:'); if (!title) return; const description = prompt('Beschreibung:') || ''; const priority = prompt('Priorität (low, medium, high, urgent):', 'medium') || 'medium'; store.addTask({ title, description, projectId: this.projectId || (store.state.projects[0]?.id || null), assigneeId: store.state.currentUser?.id, creatorId: store.state.currentUser?.id, priority: ['low', 'medium', 'high', 'urgent'].includes(priority) ? priority : 'medium', status: 'todo', dueDate: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000) // +7 days default }); }; window.editTask = (taskId) => { const task = store.state.tasks.find(t => t.id === taskId); if (!task) return; const title = prompt('Neuer Titel:', task.title); if (title === null) return; const status = prompt('Status (todo, in_progress, review, done):', task.status); if (status === null) return; const priority = prompt('Priorität (low, medium, high, urgent):', task.priority); if (priority === null) return; const updates = { title }; if (['todo', 'in_progress', 'review', 'done'].includes(status)) updates.status = status; if (['low', 'medium', 'high', 'urgent'].includes(priority)) updates.priority = priority; store.updateTask(taskId, updates); }; window.deleteTask = (taskId) => { if (confirm('Task wirklich löschen?')) { store.deleteTask(taskId); } }; window.toggleTaskStatus = (taskId) => { const task = store.state.tasks.find(t => t.id === taskId); if (task) { const newStatus = task.status === 'done' ? 'todo' : 'done'; store.updateTask(taskId, { status: newStatus }); } }; // Drag & Drop window.handleDragStart = (e, taskId) => { this.draggedTask = taskId; e.dataTransfer.effectAllowed = 'move'; e.target.style.opacity = '0.5'; }; window.handleDragOver = (e) => { e.preventDefault(); e.dataTransfer.dropEffect = 'move'; e.currentTarget.classList.add('bg-gray-100', 'dark:bg-slate-700/50'); }; window.handleDrop = (e, status) => { e.preventDefault(); e.currentTarget.classList.remove('bg-gray-100', 'dark:bg-slate-700/50'); const taskId = this.draggedTask; if (taskId) { store.moveTask(taskId, status); this.draggedTask = null; } // Reset opacity document.querySelectorAll('[draggable="true"]').forEach(el => el.style.opacity = '1'); }; } destroy() { this.unsubscribers.forEach(unsub => unsub()); // Cleanup global functions delete window.setViewMode; delete window.updateSearch; delete window.updateFilter; delete window.clearFilters; delete window.openTaskModal; delete window.editTask; delete window.deleteTask; delete window.toggleTaskStatus; delete window.handleDragStart; delete window.handleDragOver; delete window.handleDrop; } }