(function () { 'use strict'; var KEY = 'focus-desk.tasks.v1'; var PRIORITIES = ['low', 'medium', 'high']; var el = { form: document.getElementById('create-form'), title: document.getElementById('task-title'), notes: document.getElementById('task-notes'), priority: document.getElementById('task-priority'), due: document.getElementById('task-due'), titleError: document.getElementById('title-error'), search: document.getElementById('search'), status: document.getElementById('status-filter'), prioFilter: document.getElementById('priority-filter'), list: document.getElementById('task-list'), empty: document.getElementById('empty-state'), emptyDetail: document.getElementById('empty-detail'), total: document.getElementById('count-total'), active: document.getElementById('count-active'), done: document.getElementById('count-completed') }; var tasks = []; var editingId = null; function load() { var raw; try { raw = localStorage.getItem(KEY); } catch (e) { return []; } if (!raw) return []; var parsed; try { parsed = JSON.parse(raw); } catch (e) { return []; } if (!Array.isArray(parsed)) return []; return parsed.map(normalize).filter(function (t) { return t !== null; }); } function normalize(t) { if (!t || typeof t !== 'object') return null; var title = typeof t.title === 'string' ? t.title.trim() : ''; if (!title) return null; return { id: typeof t.id === 'string' && t.id ? t.id : uid(), title: title, notes: typeof t.notes === 'string' ? t.notes : '', priority: PRIORITIES.indexOf(t.priority) >= 0 ? t.priority : 'medium', due: typeof t.due === 'string' ? t.due : '', completed: t.completed === true }; } function save() { try { localStorage.setItem(KEY, JSON.stringify(tasks)); } catch (e) { /* storage unavailable */ } } function uid() { return 't' + Date.now().toString(36) + Math.random().toString(36).slice(2, 7); } function find(id) { for (var i = 0; i < tasks.length; i++) if (tasks[i].id === id) return tasks[i]; return null; } function visible() { var q = el.search.value.trim().toLowerCase(); var status = el.status.value; var prio = el.prioFilter.value; return tasks.filter(function (t) { if (status === 'active' && t.completed) return false; if (status === 'completed' && !t.completed) return false; if (prio !== 'all' && t.priority !== prio) return false; if (q) { var hay = (t.title + ' ' + t.notes).toLowerCase(); if (hay.indexOf(q) === -1) return false; } return true; }); } function dueLabel(t) { if (!t.due) return null; var parts = t.due.split('-'); if (parts.length !== 3) return null; var d = new Date(Number(parts[0]), Number(parts[1]) - 1, Number(parts[2])); if (isNaN(d.getTime())) return null; var text = d.toLocaleDateString(undefined, { month: 'short', day: 'numeric', year: 'numeric' }); var today = new Date(); today.setHours(0, 0, 0, 0); return { text: text, overdue: !t.completed && d.getTime() < today.getTime() }; } function tag(cls, text) { var s = document.createElement('span'); s.className = 'tag' + (cls ? ' ' + cls : ''); s.textContent = text; return s; } function renderTask(t) { var li = document.createElement('li'); li.className = 'task-item' + (t.completed ? ' done' : ''); li.dataset.testid = 'task-item'; li.dataset.priority = t.priority; li.dataset.id = t.id; if (editingId === t.id) { li.appendChild(editForm(t)); return li; } var box = document.createElement('input'); box.type = 'checkbox'; box.checked = t.completed; box.dataset.testid = 'task-toggle'; box.setAttribute('aria-label', (t.completed ? 'Mark "' : 'Mark "') + t.title + '" ' + (t.completed ? 'incomplete' : 'complete')); box.addEventListener('change', function () { t.completed = box.checked; save(); render(); }); li.appendChild(box); var body = document.createElement('div'); body.className = 'body'; var h = document.createElement('p'); h.className = 'title'; h.textContent = t.title; body.appendChild(h); if (t.notes) { var n = document.createElement('p'); n.className = 'notes'; n.textContent = t.notes; body.appendChild(n); } var meta = document.createElement('div'); meta.className = 'meta'; meta.appendChild(tag(t.priority, t.priority)); var d = dueLabel(t); if (d) meta.appendChild(tag(d.overdue ? 'due overdue' : 'due', d.overdue ? 'Overdue ' + d.text : 'Due ' + d.text)); body.appendChild(meta); li.appendChild(body); var actions = document.createElement('div'); actions.className = 'actions'; actions.appendChild(button('Edit', 'task-edit', 'ghost small', function () { editingId = t.id; render(); var input = el.list.querySelector('[data-testid="edit-title"]'); if (input) input.focus(); })); actions.appendChild(button('Delete', 'task-delete', 'ghost small danger', function () { tasks = tasks.filter(function (x) { return x.id !== t.id; }); if (editingId === t.id) editingId = null; save(); render(); })); li.appendChild(actions); return li; } function button(label, testid, cls, onClick) { var b = document.createElement('button'); b.type = 'button'; b.className = 'btn ' + cls; b.dataset.testid = testid; b.textContent = label; b.addEventListener('click', onClick); return b; } function field(id, labelText, node) { var wrap = document.createElement('div'); wrap.className = 'field'; var l = document.createElement('label'); l.setAttribute('for', id); l.textContent = labelText; node.id = id; wrap.appendChild(l); wrap.appendChild(node); return wrap; } function editForm(t) { var form = document.createElement('form'); form.className = 'edit-form'; form.noValidate = true; var title = document.createElement('input'); title.type = 'text'; title.value = t.title; title.dataset.testid = 'edit-title'; title.setAttribute('aria-label', 'Edit title'); var notes = document.createElement('textarea'); notes.rows = 2; notes.value = t.notes; notes.dataset.testid = 'edit-notes'; notes.setAttribute('aria-label', 'Edit notes'); var prio = document.createElement('select'); prio.dataset.testid = 'edit-priority'; prio.setAttribute('aria-label', 'Edit priority'); PRIORITIES.forEach(function (p) { var o = document.createElement('option'); o.value = p; o.textContent = p.charAt(0).toUpperCase() + p.slice(1); if (p === t.priority) o.selected = true; prio.appendChild(o); }); var due = document.createElement('input'); due.type = 'date'; due.value = t.due; due.dataset.testid = 'edit-due'; due.setAttribute('aria-label', 'Edit due date'); var row = document.createElement('div'); row.className = 'edit-row'; row.appendChild(field('edit-priority-' + t.id, 'Priority', prio)); row.appendChild(field('edit-due-' + t.id, 'Due date', due)); var actions = document.createElement('div'); actions.className = 'edit-actions'; var saveBtn = button('Save', 'save-edit', 'primary small', function () { commit(t, title, notes, prio, due); }); saveBtn.type = 'submit'; actions.appendChild(saveBtn); actions.appendChild(button('Cancel', 'cancel-edit', 'ghost small', function () { editingId = null; render(); })); form.appendChild(field('edit-title-' + t.id, 'Title', title)); form.appendChild(field('edit-notes-' + t.id, 'Notes', notes)); form.appendChild(row); form.appendChild(actions); form.addEventListener('submit', function (e) { e.preventDefault(); commit(t, title, notes, prio, due); }); return form; } function commit(t, title, notes, prio, due) { var v = title.value.trim(); if (!v) { title.focus(); return; } t.title = v; t.notes = notes.value; t.priority = PRIORITIES.indexOf(prio.value) >= 0 ? prio.value : t.priority; t.due = due.value; editingId = null; save(); render(); } function render() { var list = visible(); el.list.textContent = ''; list.forEach(function (t) { el.list.appendChild(renderTask(t)); }); var total = tasks.length; var done = tasks.filter(function (t) { return t.completed; }).length; el.total.textContent = String(total); el.active.textContent = String(total - done); el.done.textContent = String(done); var empty = list.length === 0; el.empty.hidden = !empty; if (empty) { el.emptyDetail.textContent = tasks.length === 0 ? 'Add your first task above to get started.' : 'No tasks match your current search and filters.'; } } el.form.addEventListener('submit', function (e) { e.preventDefault(); var title = el.title.value.trim(); if (!title) { el.titleError.hidden = false; el.title.focus(); return; } el.titleError.hidden = true; tasks.unshift({ id: uid(), title: title, notes: el.notes.value.trim(), priority: PRIORITIES.indexOf(el.priority.value) >= 0 ? el.priority.value : 'medium', due: el.due.value, completed: false }); save(); el.form.reset(); el.priority.value = 'medium'; el.title.focus(); render(); }); el.title.addEventListener('input', function () { if (el.title.value.trim()) el.titleError.hidden = true; }); ['input', 'change'].forEach(function (evt) { el.search.addEventListener(evt, render); el.status.addEventListener(evt, render); el.prioFilter.addEventListener(evt, render); }); tasks = load(); render(); })();