// ===== Task Manager Frontend Application ===== const API_BASE = '/api'; let tasks = []; let currentFilter = 'all'; // ===== DOM Elements ===== const taskList = document.getElementById('task-list'); const emptyState = document.getElementById('empty-state'); const loadingState = document.getElementById('loading-state'); const addTaskForm = document.getElementById('add-task-form'); const taskTitleInput = document.getElementById('task-title'); const taskDescInput = document.getElementById('task-description'); const filterTabs = document.querySelectorAll('.filter-tab'); const toast = document.getElementById('toast'); // ===== API Functions ===== async function apiRequest(url, options = {}) { try { const response = await fetch(`${API_BASE}${url}`, { headers: { 'Content-Type': 'application/json' }, ...options, }); if (!response.ok) { const err = await response.json().catch(() => ({})); throw new Error(err.error || `HTTP ${response.status}`); } return await response.json(); } catch (err) { if (err.name === 'TypeError') { throw new Error('Cannot connect to server. Is the API running?'); } throw err; } } async function fetchTasks() { const data = await apiRequest('/tasks'); tasks = data.tasks || []; // Update source indicator const sourceEl = document.querySelector('#stat-source .stat-value'); if (sourceEl) { sourceEl.textContent = data.source === 'cache' ? '⚡ Cache' : '🗄️ DB'; } return tasks; } async function createTask(title, description) { const data = await apiRequest('/tasks', { method: 'POST', body: JSON.stringify({ title, description }), }); return data.task; } async function updateTask(id, updates) { const data = await apiRequest(`/tasks/${id}`, { method: 'PUT', body: JSON.stringify(updates), }); return data.task; } async function deleteTask(id) { await apiRequest(`/tasks/${id}`, { method: 'DELETE' }); } // ===== UI Rendering ===== function getFilteredTasks() { switch (currentFilter) { case 'active': return tasks.filter(t => !t.completed); case 'completed': return tasks.filter(t => t.completed); default: return tasks; } } function formatDate(dateStr) { const date = new Date(dateStr); return date.toLocaleDateString('en-US', { month: 'short', day: 'numeric', hour: '2-digit', minute: '2-digit' }); } function renderTasks() { const filtered = getFilteredTasks(); // Update stats const total = tasks.length; const completed = tasks.filter(t => t.completed).length; const active = total - completed; document.querySelector('#stat-total .stat-value').textContent = total; document.querySelector('#stat-active .stat-value').textContent = active; document.querySelector('#stat-completed .stat-value').textContent = completed; // Show/hide states loadingState.style.display = 'none'; if (filtered.length === 0) { taskList.innerHTML = ''; emptyState.style.display = 'block'; if (tasks.length > 0) { emptyState.querySelector('p').textContent = `No ${currentFilter} tasks.`; } else { emptyState.querySelector('p').textContent = 'No tasks yet. Add your first task above!'; } return; } emptyState.style.display = 'none'; taskList.innerHTML = filtered.map(task => `
${escapeHtml(task.title)}
${task.description ? `
${escapeHtml(task.description)}
` : ''}
${formatDate(task.created_at)}
`).join(''); } function escapeHtml(text) { const div = document.createElement('div'); div.textContent = text; return div.innerHTML; } // ===== Toast Notifications ===== let toastTimeout; function showToast(message, type = 'info') { clearTimeout(toastTimeout); toast.textContent = message; toast.className = `toast ${type} show`; toastTimeout = setTimeout(() => { toast.classList.remove('show'); }, 3000); } // ===== Event Handlers ===== async function handleToggle(id, completed) { try { await updateTask(id, { completed }); await fetchTasks(); renderTasks(); showToast(completed ? '✅ Task completed!' : '🔄 Task reopened', 'success'); } catch (err) { showToast(err.message, 'error'); await fetchTasks(); renderTasks(); } } async function handleDelete(id) { try { await deleteTask(id); const el = document.querySelector(`.task-item[data-id="${id}"]`); if (el) { el.style.transform = 'translateX(100px)'; el.style.opacity = '0'; await new Promise(r => setTimeout(r, 250)); } await fetchTasks(); renderTasks(); showToast('🗑️ Task deleted', 'success'); } catch (err) { showToast(err.message, 'error'); } } // ===== Form Submit ===== addTaskForm.addEventListener('submit', async (e) => { e.preventDefault(); const title = taskTitleInput.value.trim(); const description = taskDescInput.value.trim(); if (!title) return; try { await createTask(title, description); taskTitleInput.value = ''; taskDescInput.value = ''; taskTitleInput.focus(); await fetchTasks(); renderTasks(); showToast('✨ Task created!', 'success'); } catch (err) { showToast(err.message, 'error'); } }); // ===== Filter Tabs ===== filterTabs.forEach(tab => { tab.addEventListener('click', () => { filterTabs.forEach(t => t.classList.remove('active')); tab.classList.add('active'); currentFilter = tab.dataset.filter; renderTasks(); }); }); // ===== Initialize ===== async function init() { loadingState.style.display = 'block'; emptyState.style.display = 'none'; try { await fetchTasks(); renderTasks(); } catch (err) { loadingState.style.display = 'none'; showToast('Failed to load tasks: ' + err.message, 'error'); emptyState.style.display = 'block'; emptyState.querySelector('p').textContent = 'Unable to connect to the server.'; } } // Start the application init(); // Auto-refresh every 30 seconds setInterval(async () => { try { await fetchTasks(); renderTasks(); } catch (err) { // Silent fail on auto-refresh } }, 30000);