/** * KeepItGoing - Offline mini task app * Renders and edits tasks straight from IndexedDB while there's no network. * Reuses the .task-item/.task-checkbox/.priority-badge classes from * templates/tasks/_task_item.html for visual consistency with the real app. */ function formatDueDate(dateStr) { if (!dateStr) return ''; const date = new Date(dateStr + 'T00:00:00'); return date.toLocaleDateString(undefined, { month: 'short', day: 'numeric', year: 'numeric' }); } function renderTaskItem(task) { const item = document.createElement('div'); item.className = `task-item priority-${task.priority}${task.status === 'completed' ? ' completed' : ''}`; const toggleForm = document.createElement('button'); toggleForm.type = 'button'; toggleForm.className = `task-checkbox${task.status === 'completed' ? ' checked' : ''}`; toggleForm.setAttribute('aria-label', 'Toggle complete'); if (task.status === 'completed') { toggleForm.innerHTML = ''; } toggleForm.addEventListener('click', () => toggleTaskComplete(task.sync_id)); const content = document.createElement('div'); content.className = 'task-content'; const title = document.createElement('div'); title.className = 'task-title'; title.textContent = task.title; content.appendChild(title); if (task.due_date) { const meta = document.createElement('div'); meta.className = 'task-meta'; meta.textContent = `📅 ${formatDueDate(task.due_date)}`; content.appendChild(meta); } const priorityBadge = document.createElement('span'); priorityBadge.className = `priority-badge ${task.priority}`; priorityBadge.textContent = task.priority; item.appendChild(toggleForm); item.appendChild(content); item.appendChild(priorityBadge); return item; } async function toggleTaskComplete(syncId) { const tasks = await getAllTasks(); const task = tasks.find((t) => t.sync_id === syncId); if (!task) return; task.status = task.status === 'completed' ? 'pending' : 'completed'; task.updated_at = new Date().toISOString(); task._dirty = true; await putLocalTask(task); renderOfflineTasks(); } async function addOfflineTask(title) { const now = new Date().toISOString(); const syncId = crypto.randomUUID(); const task = { id: syncId, sync_id: syncId, parent: null, parent_sync_id: null, title: title, description: '', status: 'pending', priority: 'medium', due_date: null, due_time: null, reminder_at: null, recurrence: 'none', recurrence_rule: '', recurrence_end_date: null, tag_sync_ids: [], sort_order: 0, is_deleted: false, created_at: now, updated_at: now, _dirty: true, _pending_conflict_id: null, }; await putLocalTask(task); renderOfflineTasks(); } async function renderOfflineTasks() { const listEl = document.getElementById('offline-task-list'); const emptyEl = document.getElementById('offline-empty-state'); if (!listEl) return; const allTasks = await getAllTasks(); const visibleTasks = allTasks .filter((t) => !t.is_deleted) .sort((a, b) => (a.due_date || '9999').localeCompare(b.due_date || '9999')); listEl.innerHTML = ''; if (visibleTasks.length === 0) { const lastSyncToken = await getMeta('last_sync_token'); emptyEl.textContent = lastSyncToken ? 'No tasks yet.' : "No offline data yet — connect once while online to sync your tasks."; emptyEl.hidden = false; listEl.hidden = true; return; } emptyEl.hidden = true; listEl.hidden = false; for (const task of visibleTasks) { listEl.appendChild(renderTaskItem(task)); } } document.addEventListener('DOMContentLoaded', () => { renderOfflineTasks(); const form = document.getElementById('offline-add-task-form'); if (form) { form.addEventListener('submit', (event) => { event.preventDefault(); const input = document.getElementById('offline-task-title'); const title = input.value.trim(); if (title) { addOfflineTask(title); input.value = ''; } }); } });