class TaskManager {
constructor() {
this.tasks = [];
this.load();
this.render();
this.attachEventListeners();
}
load() {
try {
const stored = localStorage.getItem('focusdeskTasks');
this.tasks = stored ? JSON.parse(stored) : [];
} catch {
this.tasks = [];
localStorage.removeItem('focusdeskTasks');
}
}
save() {
localStorage.setItem('focusdeskTasks', JSON.stringify(this.tasks));
}
generateId() {
return Date.now() + Math.random().toString(36).substr(2, 9);
}
addTask(title, notes, priority, due) {
const trimmed = title.trim();
if (!trimmed) return false;
this.tasks.unshift({
id: this.generateId(),
title: trimmed,
notes: notes.trim(),
priority,
due,
completed: false,
created: new Date().toISOString()
});
this.save();
return true;
}
deleteTask(id) {
this.tasks = this.tasks.filter(t => t.id !== id);
this.save();
}
toggleTask(id) {
const task = this.tasks.find(t => t.id === id);
if (task) {
task.completed = !task.completed;
this.save();
}
}
updateTask(id, updates) {
const task = this.tasks.find(t => t.id === id);
if (task) {
Object.assign(task, updates);
this.save();
}
}
getFilteredTasks() {
const search = document.getElementById('searchInput').value.toLowerCase();
const statusFilter = document.getElementById('statusFilter').value;
const priorityFilter = document.getElementById('priorityFilter').value;
return this.tasks.filter(task => {
const matchesSearch =
task.title.toLowerCase().includes(search) ||
task.notes.toLowerCase().includes(search);
const matchesStatus =
statusFilter === 'all' ||
(statusFilter === 'active' && !task.completed) ||
(statusFilter === 'completed' && task.completed);
const matchesPriority =
priorityFilter === 'all' ||
task.priority === priorityFilter;
return matchesSearch && matchesStatus && matchesPriority;
});
}
updateCounts() {
const total = this.tasks.length;
const active = this.tasks.filter(t => !t.completed).length;
const completed = this.tasks.filter(t => t.completed).length;
document.querySelector('[data-testid="count-total"]').textContent = total;
document.querySelector('[data-testid="count-active"]').textContent = active;
document.querySelector('[data-testid="count-completed"]').textContent = completed;
}
formatDate(dateStr) {
if (!dateStr) return '';
const d = new Date(dateStr);
return d.toLocaleDateString('en-US', { month: 'short', day: 'numeric', year: 'numeric' });
}
renderTaskItem(task) {
const div = document.createElement('div');
div.className = `task-item ${task.completed ? 'completed' : ''}`;
div.data-testid = 'task-item';
div.dataset.taskId = task.id;
const dueHtml = task.due ? `
📅 ${this.formatDate(task.due)}
` : '';
const notesHtml = task.notes ? `${this.escapeHtml(task.notes)}
` : '';
div.innerHTML = `
${notesHtml}
${dueHtml}
`;
const checkbox = div.querySelector('[data-testid="task-toggle"]');
checkbox.addEventListener('change', () => {
this.toggleTask(task.id);
this.render();
});
const editBtn = div.querySelector('[data-testid="task-edit"]');
editBtn.addEventListener('click', () => this.showEditForm(task));
const deleteBtn = div.querySelector('[data-testid="task-delete"]');
deleteBtn.addEventListener('click', () => {
this.deleteTask(task.id);
this.render();
});
return div;
}
showEditForm(task) {
const backdrop = document.createElement('div');
backdrop.style.cssText = `
position: fixed; top: 0; left: 0; right: 0; bottom: 0;
background: rgba(0,0,0,0.3); display: flex; align-items: center;
justify-content: center; z-index: 1000;
`;
const modal = document.createElement('div');
modal.style.cssText = `
background: white; padding: 24px; border-radius: 8px;
max-width: 90%; width: 100%; max-height: 90vh; overflow-y: auto;
box-shadow: 0 10px 25px rgba(0,0,0,0.2);
`;
modal.innerHTML = `
Edit Task
`;
backdrop.appendChild(modal);
document.body.appendChild(backdrop);
const titleInput = modal.querySelector('#editTitle');
titleInput.focus();
modal.querySelector('#saveBtn').addEventListener('click', () => {
const newTitle = titleInput.value.trim();
if (!newTitle) {
titleInput.focus();
return;
}
this.updateTask(task.id, {
title: newTitle,
notes: modal.querySelector('#editNotes').value.trim(),
priority: modal.querySelector('#editPriority').value,
due: modal.querySelector('#editDue').value
});
document.body.removeChild(backdrop);
this.render();
});
modal.querySelector('#cancelBtn').addEventListener('click', () => {
document.body.removeChild(backdrop);
});
backdrop.addEventListener('click', (e) => {
if (e.target === backdrop) {
document.body.removeChild(backdrop);
}
});
}
escapeHtml(text) {
const div = document.createElement('div');
div.textContent = text;
return div.innerHTML;
}
render() {
this.updateCounts();
const filtered = this.getFilteredTasks();
const taskList = document.querySelector('[data-testid="task-list"]');
const emptyState = document.querySelector('[data-testid="empty-state"]');
taskList.innerHTML = '';
if (filtered.length === 0) {
emptyState.hidden = false;
} else {
emptyState.hidden = true;
filtered.forEach(task => {
taskList.appendChild(this.renderTaskItem(task));
});
}
}
attachEventListeners() {
document.getElementById('createForm').addEventListener('submit', (e) => {
e.preventDefault();
const title = document.getElementById('taskTitle').value;
const notes = document.getElementById('taskNotes').value;
const priority = document.getElementById('taskPriority').value;
const due = document.getElementById('taskDue').value;
if (this.addTask(title, notes, priority, due)) {
document.getElementById('taskTitle').value = '';
document.getElementById('taskNotes').value = '';
document.getElementById('taskPriority').value = 'medium';
document.getElementById('taskDue').value = '';
this.render();
}
});
document.getElementById('searchInput').addEventListener('input', () => this.render());
document.getElementById('statusFilter').addEventListener('change', () => this.render());
document.getElementById('priorityFilter').addEventListener('change', () => this.render());
}
}
document.addEventListener('DOMContentLoaded', () => {
new TaskManager();
});