Spaces:
Running
Running
| class TodoApp { | |
| constructor() { | |
| this.tasks = []; | |
| this.currentFilter = 'all'; | |
| this.initElements(); | |
| this.initEventListeners(); | |
| this.loadTasks(); | |
| this.initFireworks(); | |
| } | |
| initElements() { | |
| this.taskInput = document.getElementById('taskInput'); | |
| this.addBtn = document.getElementById('addBtn'); | |
| this.taskList = document.getElementById('taskList'); | |
| this.emptyState = document.getElementById('emptyState'); | |
| this.clearCompletedBtn = document.getElementById('clearCompleted'); | |
| this.totalTasksEl = document.getElementById('totalTasks'); | |
| this.completedTasksEl = document.getElementById('completedTasks'); | |
| this.filterBtns = document.querySelectorAll('.filter-btn'); | |
| } | |
| initEventListeners() { | |
| this.addBtn.addEventListener('click', () => this.addTask()); | |
| this.taskInput.addEventListener('keypress', (e) => { | |
| if (e.key === 'Enter') this.addTask(); | |
| }); | |
| this.clearCompletedBtn.addEventListener('click', () => this.clearCompleted()); | |
| this.filterBtns.forEach(btn => { | |
| btn.addEventListener('click', () => { | |
| this.filterBtns.forEach(b => b.classList.remove('active')); | |
| btn.classList.add('active'); | |
| this.currentFilter = btn.dataset.filter; | |
| this.render(); | |
| }); | |
| }); | |
| } | |
| initFireworks() { | |
| const canvas = document.getElementById('fireworks'); | |
| this.fireworks = new Fireworks(canvas); | |
| } | |
| loadTasks() { | |
| const stored = localStorage.getItem('tasks'); | |
| if (stored) { | |
| this.tasks = JSON.parse(stored); | |
| } | |
| this.render(); | |
| } | |
| saveTasks() { | |
| localStorage.setItem('tasks', JSON.stringify(this.tasks)); | |
| } | |
| addTask() { | |
| const text = this.taskInput.value.trim(); | |
| if (!text) return; | |
| const task = { | |
| id: Date.now(), | |
| text: text, | |
| createdAt: new Date().toISOString(), | |
| completedAt: null, | |
| completed: false | |
| }; | |
| this.tasks.unshift(task); | |
| this.taskInput.value = ''; | |
| this.saveTasks(); | |
| this.render(); | |
| } | |
| toggleTask(id) { | |
| const task = this.tasks.find(t => t.id === id); | |
| if (!task) return; | |
| task.completed = !task.completed; | |
| task.completedAt = task.completed ? new Date().toISOString() : null; | |
| if (task.completed) { | |
| this.fireworks.start(); | |
| } | |
| this.saveTasks(); | |
| this.render(); | |
| } | |
| deleteTask(id) { | |
| this.tasks = this.tasks.filter(t => t.id !== id); | |
| this.saveTasks(); | |
| this.render(); | |
| } | |
| clearCompleted() { | |
| this.tasks = this.tasks.filter(t => !t.completed); | |
| this.saveTasks(); | |
| this.render(); | |
| } | |
| formatDate(dateString) { | |
| const date = new Date(dateString); | |
| const now = new Date(); | |
| const diff = now - date; | |
| const minutes = Math.floor(diff / 60000); | |
| const hours = Math.floor(diff / 3600000); | |
| const days = Math.floor(diff / 86400000); | |
| if (minutes < 1) return 'just now'; | |
| if (minutes < 60) return `${minutes}m ago`; | |
| if (hours < 24) return `${hours}h ago`; | |
| if (days < 7) return `${days}d ago`; | |
| return date.toLocaleDateString(); | |
| } | |
| getFilteredTasks() { | |
| switch (this.currentFilter) { | |
| case 'active': | |
| return this.tasks.filter(t => !t.completed); | |
| case 'completed': | |
| return this.tasks.filter(t => t.completed); | |
| default: | |
| return this.tasks; | |
| } | |
| } | |
| updateStats() { | |
| const total = this.tasks.length; | |
| const completed = this.tasks.filter(t => t.completed).length; | |
| this.totalTasksEl.textContent = `${total} ${total === 1 ? 'task' : 'tasks'}`; | |
| this.completedTasksEl.textContent = `${completed} completed`; | |
| } | |
| render() { | |
| const filteredTasks = this.getFilteredTasks(); | |
| this.taskList.innerHTML = ''; | |
| this.emptyState.classList.toggle('show', filteredTasks.length === 0); | |
| filteredTasks.forEach(task => { | |
| const taskEl = document.createElement('div'); | |
| taskEl.className = `task-item ${task.completed ? 'completed' : ''}`; | |
| taskEl.innerHTML = ` | |
| <div class="task-checkbox ${task.completed ? 'checked' : ''}" | |
| onclick="app.toggleTask(${task.id})"></div> | |
| <div class="task-content"> | |
| <div class="task-text">${this.escapeHtml(task.text)}</div> | |
| <div class="task-timestamps"> | |
| <div class="timestamp"> | |
| 📅 Created: ${this.formatDate(task.createdAt)} | |
| </div> | |
| ${task.completedAt ? ` | |
| <div class="timestamp"> | |
| ✅ Completed: ${this.formatDate(task.completedAt)} | |
| </div> | |
| ` : ''} | |
| </div> | |
| </div> | |
| <button class="delete-btn" onclick="app.deleteTask(${task.id})">Delete</button> | |
| `; | |
| this.taskList.appendChild(taskEl); | |
| }); | |
| this.updateStats(); | |
| } | |
| escapeHtml(text) { | |
| const div = document.createElement('div'); | |
| div.textContent = text; | |
| return div.innerHTML; | |
| } | |
| } | |
| // Initialize app | |
| const app = new TodoApp(); |