'use strict'; const STORAGE_KEY = 'focusdesk_tasks'; function loadTasks() { try { const raw = localStorage.getItem(STORAGE_KEY); if (!raw) return []; const parsed = JSON.parse(raw); if (!Array.isArray(parsed)) return []; return parsed.filter(t => t && typeof t.id === 'string' && typeof t.title === 'string' && typeof t.completed === 'boolean' ); } catch { return []; } } function saveTasks(tasks) { localStorage.setItem(STORAGE_KEY, JSON.stringify(tasks)); } let tasks = loadTasks(); let searchQuery = ''; let statusFilter = 'all'; let priorityFilter = 'all'; const createForm = document.getElementById('create-form'); const titleInput = document.querySelector('[data-testid="task-title"]'); const notesInput = document.querySelector('[data-testid="task-notes"]'); const prioritySelect = document.querySelector('[data-testid="task-priority"]'); const dueInput = document.querySelector('[data-testid="task-due"]'); const formError = document.getElementById('form-error'); const searchInput = document.querySelector('[data-testid="search"]'); const statusFilterSel = document.querySelector('[data-testid="status-filter"]'); const priorityFilterSel= document.querySelector('[data-testid="priority-filter"]'); const taskList = document.querySelector('[data-testid="task-list"]'); const emptyState = document.querySelector('[data-testid="empty-state"]'); const countTotal = document.querySelector('[data-testid="count-total"]'); const countActive = document.querySelector('[data-testid="count-active"]'); const countCompleted = document.querySelector('[data-testid="count-completed"]'); const editModal = document.getElementById('edit-modal'); const editForm = document.getElementById('edit-form'); const editTaskId = document.getElementById('edit-task-id'); const editTitleInput = document.querySelector('[data-testid="edit-title"]'); const editPrioritySel = document.getElementById('edit-priority-select'); const editDueInput = document.getElementById('edit-due-input'); const editNotesInput = document.getElementById('edit-notes-input'); const editError = document.getElementById('edit-error'); const cancelEdit = document.getElementById('cancel-edit'); const modalBackdrop = document.querySelector('.modal-backdrop'); function uid() { return Date.now().toString(36) + Math.random().toString(36).slice(2, 7); } function escText(str) { const d = document.createElement('div'); d.appendChild(document.createTextNode(str)); return d.innerHTML; } function formatDate(iso) { if (!iso) return ''; const [y, m, d] = iso.split('-'); return `${y}-${m}-${d}`; } function isOverdue(iso) { if (!iso) return false; const today = new Date(); today.setHours(0, 0, 0, 0); const due = new Date(iso + 'T00:00:00'); return due < today; } function getFiltered() { const q = searchQuery.trim().toLowerCase(); return tasks.filter(t => { if (statusFilter === 'active' && t.completed) return false; if (statusFilter === 'completed' && !t.completed) return false; if (priorityFilter !== 'all' && t.priority !== priorityFilter) return false; if (q) { const inTitle = t.title.toLowerCase().includes(q); const inNotes = (t.notes || '').toLowerCase().includes(q); if (!inTitle && !inNotes) return false; } return true; }); } function render() { const filtered = getFiltered(); countTotal.textContent = tasks.length; countActive.textContent = tasks.filter(t => !t.completed).length; countCompleted.textContent = tasks.filter(t => t.completed).length; if (filtered.length === 0) { taskList.innerHTML = ''; emptyState.hidden = false; } else { emptyState.hidden = true; const frag = document.createDocumentFragment(); filtered.forEach(task => { const li = document.createElement('li'); li.className = 'task-item' + (task.completed ? ' completed' : ''); li.setAttribute('data-testid', 'task-item'); li.setAttribute('data-id', task.id); const chk = document.createElement('input'); chk.type = 'checkbox'; chk.className = 'task-toggle'; chk.setAttribute('data-testid', 'task-toggle'); chk.checked = task.completed; chk.setAttribute('aria-label', `Mark "${task.title}" as ${task.completed ? 'incomplete' : 'complete'}`); const body = document.createElement('div'); body.className = 'task-body'; const titleEl = document.createElement('div'); titleEl.className = 'task-title-text'; titleEl.textContent = task.title; body.appendChild(titleEl); if (task.notes) { const notesEl = document.createElement('div'); notesEl.className = 'task-notes-text'; notesEl.textContent = task.notes; body.appendChild(notesEl); } const meta = document.createElement('div'); meta.className = 'task-meta'; const badge = document.createElement('span'); badge.className = `badge badge--${task.priority}`; badge.textContent = task.priority; meta.appendChild(badge); if (task.due) { const dueEl = document.createElement('span'); dueEl.className = 'task-due-text' + (!task.completed && isOverdue(task.due) ? ' overdue' : ''); dueEl.textContent = formatDate(task.due); meta.appendChild(dueEl); } body.appendChild(meta); const actions = document.createElement('div'); actions.className = 'task-actions'; const editBtn = document.createElement('button'); editBtn.type = 'button'; editBtn.className = 'btn btn--icon'; editBtn.setAttribute('data-testid', 'task-edit'); editBtn.setAttribute('aria-label', `Edit task: ${task.title}`); editBtn.innerHTML = '✎'; const delBtn = document.createElement('button'); delBtn.type = 'button'; delBtn.className = 'btn btn--icon delete'; delBtn.setAttribute('data-testid', 'task-delete'); delBtn.setAttribute('aria-label', `Delete task: ${task.title}`); delBtn.innerHTML = '✕'; actions.appendChild(editBtn); actions.appendChild(delBtn); li.appendChild(chk); li.appendChild(body); li.appendChild(actions); frag.appendChild(li); }); taskList.innerHTML = ''; taskList.appendChild(frag); } } createForm.addEventListener('submit', e => { e.preventDefault(); const title = titleInput.value; if (!title.trim()) { formError.textContent = 'Title is required.'; titleInput.focus(); return; } formError.textContent = ''; const task = { id: uid(), title: title.trim(), notes: notesInput.value.trim(), priority: prioritySelect.value, due: dueInput.value || '', completed: false, createdAt: Date.now() }; tasks.unshift(task); saveTasks(tasks); titleInput.value = ''; notesInput.value = ''; prioritySelect.value = 'medium'; dueInput.value = ''; titleInput.focus(); render(); }); titleInput.addEventListener('input', () => { formError.textContent = ''; }); taskList.addEventListener('change', e => { const chk = e.target.closest('[data-testid="task-toggle"]'); if (!chk) return; const id = chk.closest('[data-id]').dataset.id; const task = tasks.find(t => t.id === id); if (task) { task.completed = chk.checked; saveTasks(tasks); render(); } }); taskList.addEventListener('click', e => { const editBtn = e.target.closest('[data-testid="task-edit"]'); if (editBtn) { const id = editBtn.closest('[data-id]').dataset.id; openEdit(id); return; } const delBtn = e.target.closest('[data-testid="task-delete"]'); if (delBtn) { const id = delBtn.closest('[data-id]').dataset.id; tasks = tasks.filter(t => t.id !== id); saveTasks(tasks); render(); } }); searchInput.addEventListener('input', () => { searchQuery = searchInput.value; render(); }); statusFilterSel.addEventListener('change', () => { statusFilter = statusFilterSel.value; render(); }); priorityFilterSel.addEventListener('change', () => { priorityFilter = priorityFilterSel.value; render(); }); function openEdit(id) { const task = tasks.find(t => t.id === id); if (!task) return; editTaskId.value = id; editTitleInput.value = task.title; editNotesInput.value = task.notes || ''; editPrioritySel.value = task.priority; editDueInput.value = task.due || ''; editError.textContent = ''; editModal.hidden = false; editTitleInput.focus(); } function closeEdit() { editModal.hidden = true; editError.textContent = ''; } editForm.addEventListener('submit', e => { e.preventDefault(); const newTitle = editTitleInput.value; if (!newTitle.trim()) { editError.textContent = 'Title is required.'; editTitleInput.focus(); return; } const id = editTaskId.value; const task = tasks.find(t => t.id === id); if (task) { task.title = newTitle.trim(); task.notes = editNotesInput.value.trim(); task.priority = editPrioritySel.value; task.due = editDueInput.value || ''; saveTasks(tasks); render(); } closeEdit(); }); cancelEdit.addEventListener('click', closeEdit); modalBackdrop.addEventListener('click', closeEdit); document.addEventListener('keydown', e => { if (e.key === 'Escape' && !editModal.hidden) closeEdit(); }); render();