/**
* 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 = `
|
|
${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 ? `
` : '-'}
|
|
`;
}
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 `
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;
}
}