From 017d5e81e13a533b39fed7fc0696cac90405a56a Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 21 Mar 2026 01:04:43 +0000 Subject: [PATCH 1/2] Initial plan From 5091f729f5fa5208cb63c608634a5f62f99be845 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 21 Mar 2026 01:07:54 +0000 Subject: [PATCH 2/2] chore: stop tracking compiled js/ output; fix deploy trigger to watch src/ Co-authored-by: JoeProgrammer88 <7156063+JoeProgrammer88@users.noreply.github.com> Agent-Logs-Url: https://github.com/SpeakingInBits/TaskManagerWeb/sessions/65db2c5c-fc58-487e-ac20-723720bddb2e --- .github/workflows/update-cache-version.yml | 2 +- .gitignore | 6 +- js/app.d.ts.map | 1 - js/app.js | 2020 -------------------- js/storage.d.ts.map | 1 - js/storage.js | 710 ------- 6 files changed, 3 insertions(+), 2737 deletions(-) delete mode 100644 js/app.d.ts.map delete mode 100644 js/app.js delete mode 100644 js/storage.d.ts.map delete mode 100644 js/storage.js diff --git a/.github/workflows/update-cache-version.yml b/.github/workflows/update-cache-version.yml index 0d8995b..3cebab9 100644 --- a/.github/workflows/update-cache-version.yml +++ b/.github/workflows/update-cache-version.yml @@ -7,7 +7,7 @@ on: paths: - 'index.html' - 'css/**' - - 'js/**' + - 'src/**' - 'manifest.json' jobs: diff --git a/.gitignore b/.gitignore index 95c7104..af83842 100644 --- a/.gitignore +++ b/.gitignore @@ -1,10 +1,8 @@ # Dependencies node_modules/ -# TypeScript build output -js/*.js -js/*.js.map -js/*.d.ts +# TypeScript build output (compiled at deploy time, not committed) +js/ # Playwright test-results/ diff --git a/js/app.d.ts.map b/js/app.d.ts.map deleted file mode 100644 index b683dd2..0000000 --- a/js/app.d.ts.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"app.d.ts","sourceRoot":"","sources":["../src/app.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/js/app.js b/js/app.js deleted file mode 100644 index 1556341..0000000 --- a/js/app.js +++ /dev/null @@ -1,2020 +0,0 @@ -// ======================== -// Main Application Logic -// ======================== -import { storage, STORAGE_VERSION, getDaysUntilDueText } from './storage.js'; -class TaskManager { - constructor() { - this.currentEditingTaskId = null; - this.currentEditingProjectId = null; - this.currentEditingHabitId = null; - this.currentEditingFinanceId = null; - this.currentEditingFinanceType = null; - this.currentEditingRewardId = null; - this.currentEditingWishItemId = null; - this.currentEditingNoteId = null; - this.dragSrcWishId = null; - this.selectedDate = new Date(); - this.tasksExpanded = false; - this.hideCompleted = false; - this.emojis = [ - // Activity - '๐Ÿ’ช', '๐Ÿƒ', '๐Ÿšด', '๐ŸŠ', '๐Ÿง˜', '๐Ÿ’ƒ', '๐Ÿ•บ', 'โ›น๏ธ', - // Food & Health - '๐Ÿฅ—', '๐ŸŽ', '๐Ÿฅ•', '๐Ÿ’Š', '๐Ÿฅ', '๐Ÿง„', '๐Ÿฅค', 'โ˜•', - // Work & Productivity - '๐Ÿ“š', 'โœ๏ธ', '๐Ÿ’ผ', '๐ŸŽฏ', '๐Ÿ“Š', '๐Ÿ’ป', '๐Ÿ“ฑ', 'โŒจ๏ธ', - // Learning & Mind - '๐Ÿง ', '๐Ÿ“–', '๐ŸŽ“', '๐Ÿ’ก', '๐Ÿ”ฌ', '๐ŸŽจ', '๐ŸŽต', '๐ŸŽญ', - // Nature & Outdoors - '๐ŸŒฟ', '๐ŸŒณ', '๐ŸŒž', '๐ŸŒ™', '๐ŸŒŠ', 'โ›ฐ๏ธ', '๐Ÿž๏ธ', '๐Ÿฆ‹', - // Sleep & Rest - '๐Ÿ˜ด', '๐Ÿ›๏ธ', '๐Ÿ˜Œ', '๐Ÿ•ฏ๏ธ', '๐ŸŒ™', '๐Ÿ’ค', '๐Ÿง–', '๐Ÿ›€', - // Social & Fun - '๐Ÿ‘จโ€๐Ÿ‘ฉโ€๐Ÿ‘งโ€๐Ÿ‘ฆ', '๐Ÿค', '๐ŸŽ‰', '๐Ÿ˜Š', 'โค๏ธ', '๐Ÿค—', '๐Ÿ˜‚', '๐Ÿ‘', - // Sports & Games - 'โšฝ', '๐Ÿ€', '๐ŸŽพ', '๐Ÿ', '๐ŸŽฏ', 'โ™Ÿ๏ธ', '๐ŸŽฒ', '๐Ÿƒ', - // Habits & Goals - 'โญ', '๐ŸŽฏ', '๐Ÿ†', '๐Ÿฅ‡', '๐Ÿ”ฅ', '๐Ÿ’Ž', 'โœจ', '๐ŸŒŸ', - // More Emojis - '๐Ÿš€', '๐ŸŒˆ', '๐ŸŽ', '๐Ÿ“…', 'โฐ', '๐Ÿ’ฐ', '๐ŸŽช', '๐ŸŽข' - ]; - this.init(); - } - init() { - this.setupEventListeners(); - this.initializeFinanceDateFilter(); - this.updateDateNavigator(); - this.render(); - this.processRecurringTasks(); - } - // ======================== - // Event Listeners Setup - // ======================== - setupEventListeners() { - // Navigation tabs - document.querySelectorAll('.nav-tab').forEach(tab => { - tab.addEventListener('click', (e) => this.switchTab(e.target.dataset.tab)); - }); - // Tasks section - document.getElementById('addTaskBtn').addEventListener('click', () => this.openTaskModal()); - document.getElementById('toggleTaskViewBtn').addEventListener('click', () => this.toggleTaskView()); - document.getElementById('taskForm').addEventListener('submit', (e) => this.saveTask(e)); - document.getElementById('cancelTaskBtn').addEventListener('click', () => this.closeTaskModal()); - document.getElementById('deleteTaskBtn').addEventListener('click', () => this.deleteTask()); - document.getElementById('taskRepeatType').addEventListener('change', (e) => this.updateRepeatTypeUI(e.target.value)); - document.getElementById('taskCategory').addEventListener('change', (e) => this.handleCategoryChange('task', e.target.value)); - document.getElementById('taskCategorySave').addEventListener('click', () => this.handleAddCategory('task')); - document.getElementById('taskCategoryCancel').addEventListener('click', () => this.cancelAddCategory('task')); - document.getElementById('categoryFilter').addEventListener('change', () => this.filterTasks()); - document.getElementById('statusFilter').addEventListener('change', () => this.filterTasks()); - document.getElementById('groupBySelect').addEventListener('change', () => this.filterTasks()); - document.getElementById('searchTasks').addEventListener('input', () => this.filterTasks()); - document.getElementById('hideCompletedBtn').addEventListener('click', () => this.toggleHideCompleted()); - // Projects section - document.getElementById('addProjectBtn').addEventListener('click', () => this.openProjectModal()); - document.getElementById('projectForm').addEventListener('submit', (e) => this.saveProject(e)); - document.getElementById('cancelProjectBtn').addEventListener('click', () => this.closeProjectModal()); - document.getElementById('deleteProjectBtn').addEventListener('click', () => this.deleteProject()); - document.getElementById('closeProjectDetailBtn').addEventListener('click', () => this.closeProjectDetailModal()); - document.getElementById('editProjectFromDetailBtn').addEventListener('click', () => this.editProjectFromDetail()); - // Habits section - document.getElementById('addHabitBtn').addEventListener('click', () => this.openHabitModal()); - document.getElementById('habitForm').addEventListener('submit', (e) => this.saveHabit(e)); - document.getElementById('cancelHabitBtn').addEventListener('click', () => this.closeHabitModal()); - document.getElementById('deleteHabitBtn').addEventListener('click', () => this.deleteHabit()); - document.getElementById('habitEmojiBtn').addEventListener('click', () => this.openEmojiPicker()); - document.getElementById('closeEmojiBtn').addEventListener('click', () => this.closeEmojiPicker()); - document.getElementById('habitCategory').addEventListener('change', (e) => this.handleCategoryChange('habit', e.target.value)); - document.getElementById('habitCategorySave').addEventListener('click', () => this.handleAddCategory('habit')); - document.getElementById('habitCategoryCancel').addEventListener('click', () => this.cancelAddCategory('habit')); - // Finances section - document.getElementById('addExpenseBtn').addEventListener('click', () => this.openFinanceModal('expense')); - document.getElementById('addRevenueBtn').addEventListener('click', () => this.openFinanceModal('revenue')); - document.getElementById('addChargeBtn').addEventListener('click', () => this.openFinanceModal('charge')); - document.getElementById('financeForm').addEventListener('submit', (e) => this.saveFinance(e)); - document.getElementById('cancelFinanceBtn').addEventListener('click', () => this.closeFinanceModal()); - document.getElementById('deleteFinanceBtn').addEventListener('click', () => this.deleteFinance()); - document.getElementById('financeCategory').addEventListener('change', (e) => this.handleCategoryChange('finance', e.target.value)); - document.getElementById('financeCategorySave').addEventListener('click', () => this.handleAddCategory('finance')); - document.getElementById('financeCategoryCancel').addEventListener('click', () => this.cancelAddCategory('finance')); - document.querySelectorAll('.finance-tab').forEach(tab => { - tab.addEventListener('click', (e) => this.switchFinanceTab(e.target.dataset.financeTab)); - }); - // Finance filters - document.getElementById('filterFinancesBtn').addEventListener('click', () => this.renderFinances()); - document.getElementById('resetFinanceFilterBtn').addEventListener('click', () => this.resetFinanceFilter()); - document.getElementById('prevMonthBtn').addEventListener('click', () => this.navigateToPrevMonth()); - document.getElementById('nextMonthBtn').addEventListener('click', () => this.navigateToNextMonth()); - // Shop section - document.getElementById('addRewardBtn').addEventListener('click', () => this.openRewardModal()); - document.getElementById('rewardForm').addEventListener('submit', (e) => this.saveReward(e)); - document.getElementById('cancelRewardBtn').addEventListener('click', () => this.closeRewardModal()); - document.getElementById('deleteRewardBtn').addEventListener('click', () => this.deleteReward()); - // Wish List section - document.getElementById('addWishItemBtn').addEventListener('click', () => this.openWishItemModal()); - document.getElementById('wishItemForm').addEventListener('submit', (e) => this.saveWishItem(e)); - document.getElementById('cancelWishItemBtn').addEventListener('click', () => this.closeWishItemModal()); - document.getElementById('deleteWishItemBtn').addEventListener('click', () => this.deleteWishItem()); - // Notes section - document.getElementById('addNoteBtn').addEventListener('click', () => this.openNoteModal()); - document.getElementById('noteForm').addEventListener('submit', (e) => this.saveNote(e)); - document.getElementById('cancelNoteBtn').addEventListener('click', () => this.closeNoteModal()); - document.getElementById('deleteNoteBtn').addEventListener('click', () => this.deleteNote()); - // Modal close buttons - document.querySelectorAll('.close-btn').forEach(btn => { - btn.addEventListener('click', (e) => { - e.target.closest('.modal').classList.remove('active'); - }); - }); - // Settings - document.getElementById('saveTasksPerLevel').addEventListener('click', () => this.saveTasksPerLevel()); - document.getElementById('exportBtn').addEventListener('click', () => this.exportData()); - document.getElementById('importBtn').addEventListener('click', () => document.getElementById('importFile').click()); - document.getElementById('importFile').addEventListener('change', (e) => this.importData(e)); - document.getElementById('clearDataBtn').addEventListener('click', () => { - if (storage.clearAllData()) { - location.reload(); - } - }); - // Modal backdrop click - document.querySelectorAll('.modal').forEach(modal => { - modal.addEventListener('click', (e) => { - if (e.target === modal) { - modal.classList.remove('active'); - } - }); - }); - // Hamburger menu toggle - document.getElementById('hamburgerMenu').addEventListener('click', () => { - const navTabs = document.getElementById('navTabs'); - const hamburger = document.getElementById('hamburgerMenu'); - navTabs.classList.toggle('show'); - hamburger.classList.toggle('active'); - }); - // Close mobile menu when a tab is clicked - document.querySelectorAll('.nav-tab').forEach(tab => { - tab.addEventListener('click', () => { - if (window.innerWidth <= 768) { - const navTabs = document.getElementById('navTabs'); - const hamburger = document.getElementById('hamburgerMenu'); - navTabs.classList.remove('show'); - hamburger.classList.remove('active'); - } - }); - }); - // Close mobile menu when clicking outside - document.addEventListener('click', (e) => { - if (window.innerWidth <= 768) { - const navTabs = document.getElementById('navTabs'); - const hamburger = document.getElementById('hamburgerMenu'); - if (!navTabs.contains(e.target) && !hamburger.contains(e.target)) { - navTabs.classList.remove('show'); - hamburger.classList.remove('active'); - } - } - }); - // Dashboard overview click navigation - document.getElementById('todayTasksItem').addEventListener('click', () => this.switchTab('tasks')); - document.getElementById('overdueTasksItem').addEventListener('click', () => { - document.getElementById('statusFilter').value = 'overdue'; - this.switchTab('tasks'); - }); - // Date navigator - document.getElementById('prevDayBtn').addEventListener('click', () => this.navigateDate(-1)); - document.getElementById('nextDayBtn').addEventListener('click', () => this.navigateDate(1)); - document.getElementById('goTodayBtn').addEventListener('click', () => { - this.selectedDate = new Date(); - this.updateDateNavigator(); - const activeTabName = document.querySelector('.nav-tab.active')?.dataset.tab; - if (activeTabName) { - this.switchTab(activeTabName); - } - else { - this.renderDashboard(); - } - }); - } - // ======================== - // Tab Navigation - // ======================== - switchTab(tabName) { - // Hide all tabs - document.querySelectorAll('.tab-content').forEach(tab => { - tab.classList.remove('active'); - }); - // Remove active from all nav tabs - document.querySelectorAll('.nav-tab').forEach(tab => { - tab.classList.remove('active'); - }); - // Show selected tab - document.getElementById(`${tabName}-tab`).classList.add('active'); - document.querySelector(`[data-tab="${tabName}"]`).classList.add('active'); - // Re-render based on tab - if (tabName === 'dashboard') { - this.renderDashboard(); - } - else if (tabName === 'tasks') { - this.renderTasks(); - } - else if (tabName === 'projects') { - this.renderProjects(); - } - else if (tabName === 'habits') { - this.renderHabits(); - } - else if (tabName === 'finances') { - this.renderFinances(); - } - else if (tabName === 'shop') { - this.renderShop(); - } - else if (tabName === 'wishlist') { - this.renderWishList(); - } - else if (tabName === 'notes') { - this.renderNotes(); - } - else if (tabName === 'settings') { - this.renderSettings(); - } - } - switchFinanceTab(tabName) { - document.querySelectorAll('.finance-content').forEach(tab => { - tab.classList.remove('active'); - }); - document.querySelectorAll('.finance-tab').forEach(tab => { - tab.classList.remove('active'); - }); - document.getElementById(`${tabName}-content`).classList.add('active'); - document.querySelector(`[data-finance-tab="${tabName}"]`).classList.add('active'); - } - // ======================== - // Dashboard Rendering - // ======================== - renderDashboard() { - const today = this.getSelectedDateStr(); - const tasks = storage.getTasks(); - const habits = storage.getHabits(); - const userStats = storage.getUserStats(); - // Update header stats - document.getElementById('totalPoints').textContent = String(userStats.totalPoints); - document.getElementById('userLevel').textContent = String(userStats.level); - document.getElementById('dailyStreak').textContent = String(userStats.dailyStreak); - // Update level progress - const settings = storage.getSettings(); - const completedTasksCount = tasks.filter(t => t.completed).length; - const tasksInCurrentLevel = completedTasksCount % settings.tasksPerLevel; - const tasksNeeded = settings.tasksPerLevel; - document.getElementById('levelProgress').textContent = `${tasksInCurrentLevel}/${tasksNeeded} tasks`; - // Selected day's overview - const todayTasks = tasks.filter(t => t.dueDate === today && !t.completed); - const overdueTasks = tasks.filter(t => !t.completed && !!t.dueDate && t.dueDate < today); - const completedToday = tasks.filter(t => t.completedDate === today); - const todayDay = this.selectedDate.getDay(); - // Find incomplete habits for selected day - const incompleteHabits = habits.filter(habit => { - const isValidDay = !habit.daysOfWeek || habit.daysOfWeek.includes(todayDay); - if (!isValidDay) - return false; - const todaysCompletions = storage.countHabitCompletionsForDate(habit.id, today); - const targetGoal = habit.targetGoal || 1; - return todaysCompletions < targetGoal; - }); - document.getElementById('todayTasksCount').textContent = String(todayTasks.length); - document.getElementById('overdueTasksCount').textContent = String(overdueTasks.length); - document.getElementById('completedTodayCount').textContent = String(completedToday.length); - document.getElementById('incompleteHabitsCount').textContent = String(incompleteHabits.length); - // Render incomplete habits list - const incompleteHabitsList = document.getElementById('incompleteHabitsList'); - if (incompleteHabits.length === 0) { - incompleteHabitsList.innerHTML = '

All habits completed for today! ๐ŸŽ‰

'; - } - else { - incompleteHabitsList.innerHTML = incompleteHabits.map(habit => { - const todaysCompletions = storage.countHabitCompletionsForDate(habit.id, today); - const targetGoal = habit.targetGoal || 1; - const percentage = Math.min(100, Math.round((todaysCompletions / targetGoal) * 100)); - return ` -
-
-
- ${habit.icon} - ${habit.name} -
- ${todaysCompletions}/${targetGoal} (${percentage}%) -
-
- `; - }).join(''); - // Add click handlers to navigate to habits - document.querySelectorAll('.habit-item').forEach(item => { - item.addEventListener('click', () => { - this.switchTab('habits'); - }); - }); - } - // Recent activity - this.renderRecentActivity(); - // Active projects - this.renderProjectsSummary(); - } - renderRecentActivity() { - const data = storage.getData(); - const activities = []; - // Collect activities from tasks - data.tasks.filter(t => t.completedDate).forEach(t => { - activities.push({ - type: 'task', - message: `Completed task: ${t.title}`, - date: t.completedDate, - icon: 'โœ“' - }); - }); - // Collect activities from habits - if (data.dailyHabitLogs) { - data.dailyHabitLogs.slice(-10).forEach(log => { - const habit = data.habits.find(h => h.id === log.habitId); - if (habit) { - activities.push({ - type: 'habit', - message: `Completed habit: ${habit.name}`, - date: log.date, - icon: 'โญ' - }); - } - }); - } - // Sort by date - activities.sort((a, b) => new Date(b.date).getTime() - new Date(a.date).getTime()); - const activityList = document.getElementById('recentActivity'); - if (activities.length === 0) { - activityList.innerHTML = '

No activity yet. Start completing tasks!

'; - } - else { - activityList.innerHTML = activities.slice(0, 5).map(activity => ` -
-
${activity.icon} ${activity.message}
-
${activity.date}
-
- `).join(''); - } - } - renderProjectsSummary() { - const projects = storage.getProjects(); - const tasks = storage.getTasks(); - const container = document.getElementById('projectsSummary'); - if (projects.length === 0) { - container.innerHTML = '

No active projects. Create one to organize your tasks!

'; - return; - } - container.innerHTML = projects.map(project => { - const projectTasks = tasks.filter(t => t.projectId === project.id); - const completedTasks = projectTasks.filter(t => t.completed); - const percentage = projectTasks.length === 0 ? 0 : Math.round((completedTasks.length / projectTasks.length) * 100); - return ` -
-
${project.name}
-
-
-
-
${completedTasks.length}/${projectTasks.length} tasks (${percentage}%)
-
- `; - }).join(''); - } - // ======================== - // Tasks Management - toggleHideCompleted() { - this.hideCompleted = !this.hideCompleted; - const btn = document.getElementById('hideCompletedBtn'); - btn.textContent = this.hideCompleted ? '๐Ÿ‘ Show Completed' : '๐Ÿ‘ Hide Completed'; - btn.classList.toggle('active', this.hideCompleted); - this.filterTasks(); - } - // ======================== - toggleTaskView() { - this.tasksExpanded = !this.tasksExpanded; - const btn = document.getElementById('toggleTaskViewBtn'); - btn.textContent = this.tasksExpanded ? 'โŠŸ Collapse Details' : 'โŠž Expand Details'; - document.getElementById('taskList').classList.toggle('expanded', this.tasksExpanded); - } - renderTasks() { - this.updateCategoryFilter(); - this.filterTasks(); - } - filterTasks() { - const tasks = storage.getTasks(); - const categoryFilter = document.getElementById('categoryFilter').value; - const statusFilter = document.getElementById('statusFilter').value; - const searchTerm = document.getElementById('searchTasks').value.toLowerCase(); - const groupBy = document.getElementById('groupBySelect').value; - const today = this.getSelectedDateStr(); - const filtersActive = statusFilter || searchTerm; - let filtered = tasks.filter(task => { - // Hide completed filter - if (this.hideCompleted && task.completed) - return false; - // Category filter - if (categoryFilter && task.category !== categoryFilter) - return false; - // Status filter - if (statusFilter) { - if (statusFilter === 'completed' && !task.completed) - return false; - if (statusFilter === 'pending' && task.completed) - return false; - if (statusFilter === 'overdue' && (!task.dueDate || task.completed || task.dueDate >= today)) - return false; - } - // Search filter - if (searchTerm && !task.title.toLowerCase().includes(searchTerm) && !task.description?.toLowerCase().includes(searchTerm)) - return false; - return true; - }); - const taskList = document.getElementById('taskList'); - if (filtered.length === 0) { - taskList.innerHTML = '

No tasks found.

'; - return; - } - let html = ''; - if (groupBy === 'priority') { - const priorityLabels = { high: 'High Priority', medium: 'Medium Priority', low: 'Low Priority' }; - const grouped = { high: [], medium: [], low: [], ungrouped: [] }; - filtered.forEach(task => { - if (task.priority && grouped[task.priority]) { - grouped[task.priority].push(task); - } - else { - grouped['ungrouped'].push(task); - } - }); - ['high', 'medium', 'low'].forEach(p => { - if (grouped[p].length > 0) { - html += `

${priorityLabels[p]}

`; - html += grouped[p].map(task => this.renderTaskItem(task)).join(''); - } - }); - if (grouped['ungrouped'].length > 0) { - html += `

Ungrouped

`; - html += grouped['ungrouped'].map(task => this.renderTaskItem(task)).join(''); - } - } - else if (groupBy === 'category') { - const withCategory = filtered.filter(task => task.category); - const withoutCategory = filtered.filter(task => !task.category); - const categories = [...new Set(withCategory.map(task => task.category))].sort(); - categories.forEach(cat => { - const group = withCategory.filter(task => task.category === cat); - if (group.length > 0) { - html += `

${cat}

`; - html += group.map(task => this.renderTaskItem(task)).join(''); - } - }); - if (withoutCategory.length > 0) { - html += `

Ungrouped

`; - html += withoutCategory.map(task => this.renderTaskItem(task)).join(''); - } - } - else if (filtersActive) { - html = filtered.map(task => this.renderTaskItem(task)).join(''); - } - else { - const priorityOrder = { high: 0, medium: 1, low: 2 }; - const overdue = filtered.filter(task => !task.completed && task.dueDate && task.dueDate < today); - const dueToday = filtered.filter(task => task.dueDate && task.dueDate === today); - const upcoming = filtered - .filter(task => !task.completed && - task.dueDate && - task.dueDate > today && - task.repeatType !== 'daily') - .sort((a, b) => { - if (a.dueDate < b.dueDate) - return -1; - if (a.dueDate > b.dueDate) - return 1; - return (priorityOrder[a.priority] ?? 1) - (priorityOrder[b.priority] ?? 1); - }); - const noDueDate = filtered.filter(task => !task.dueDate); - if (overdue.length > 0) { - html += `

Overdue

`; - html += overdue.map(task => this.renderTaskItem(task)).join(''); - } - if (dueToday.length > 0) { - html += `

Due Today

`; - html += dueToday.map(task => this.renderTaskItem(task)).join(''); - } - if (upcoming.length > 0) { - html += `

Upcoming Tasks

`; - html += upcoming.map(task => this.renderTaskItem(task)).join(''); - } - if (noDueDate.length > 0) { - html += `

No Due Date

`; - html += noDueDate.map(task => this.renderTaskItem(task)).join(''); - } - if (!html) { - html = '

No tasks found.

'; - } - } - taskList.innerHTML = html; - // Add event listeners to task items - document.querySelectorAll('.task-checkbox').forEach(checkbox => { - checkbox.addEventListener('change', (e) => { - const taskId = e.target.dataset.taskId; - this.toggleTask(taskId); - }); - }); - document.querySelectorAll('.task-item').forEach(item => { - item.addEventListener('click', (e) => { - if (!e.target.classList.contains('task-checkbox')) { - this.openTaskModal(item.dataset.taskId); - } - }); - }); - } - renderTaskItem(task) { - const today = storage.formatDate(new Date()); - let status = 'pending'; - if (task.completed) { - status = 'completed'; - } - else if (task.dueDate && task.dueDate < today) { - status = 'overdue'; - } - const dueBadge = task.dueDate && !task.completed - ? `${getDaysUntilDueText(task.dueDate)}` - : ''; - return ` -
- -
-
${task.title}
- ${task.description ? `
${task.description}
` : ''} -
- ${task.category ? `๐Ÿ“‚ ${task.category}` : ''} - ${task.dueDate ? `๐Ÿ“… ${task.dueDate}` : ''} - ${task.priority} - ${task.repeatType !== 'none' ? `๐Ÿ” ${task.repeatType}` : ''} - ${task.points ? `โญ ${task.points} pts` : ''} -
-
-
${status}
- ${dueBadge} -
- `; - } - updateCategoryFilter() { - const tasks = storage.getTasks(); - const categories = [...new Set(tasks.map(t => t.category).filter((c) => !!c))]; - const select = document.getElementById('categoryFilter'); - const currentValue = select.value; - select.innerHTML = '' + - categories.map(cat => ``).join(''); - select.value = currentValue; - } - openTaskModal(taskId = null) { - this.currentEditingTaskId = taskId; - const modal = document.getElementById('taskModal'); - const form = document.getElementById('taskForm'); - const deleteBtn = document.getElementById('deleteTaskBtn'); - form.reset(); - deleteBtn.style.display = 'none'; - // Update project dropdown - this.updateProjectSelect(); - // Clear all task day checkboxes - document.querySelectorAll('input[name="taskDay"]').forEach(checkbox => { - checkbox.checked = false; - }); - if (taskId) { - const task = storage.getTasks().find(t => t.id === taskId); - if (task) { - document.getElementById('taskTitle').value = task.title; - document.getElementById('taskDescription').value = task.description || ''; - document.getElementById('taskDueDate').value = task.dueDate || ''; - document.getElementById('taskCategory').value = task.category || ''; - document.getElementById('taskPriority').value = task.priority || 'medium'; - document.getElementById('taskPoints').value = String(task.points || 10); - document.getElementById('taskRepeatType').value = task.repeatType || 'none'; - document.getElementById('taskProject').value = task.projectId || ''; - document.getElementById('taskRepeatUnit').value = String(task.repeatUnit || 1); - if (task.repeatType === 'custom') { - document.getElementById('customRepeatDays').value = String(task.customRepeatDays || ''); - } - if (task.repeatType === 'movable') { - document.getElementById('movableRepeatDays').value = String(task.movableRepeatDays || ''); - } - // Load daysOfWeek if available - if (task.daysOfWeek && Array.isArray(task.daysOfWeek)) { - task.daysOfWeek.forEach(day => { - const checkbox = document.querySelector(`input[name="taskDay"][value="${day}"]`); - if (checkbox) { - checkbox.checked = true; - } - }); - } - deleteBtn.style.display = 'block'; - this.updateRepeatTypeUI(task.repeatType); - } - } - else { - document.getElementById('taskRepeatUnit').value = '1'; - } - // Load categories - this.loadCategoryDropdown('task'); - modal.classList.add('active'); - } - closeTaskModal() { - document.getElementById('taskModal').classList.remove('active'); - this.currentEditingTaskId = null; - // If project detail modal is open, refresh it - if (document.getElementById('projectDetailModal').classList.contains('active')) { - this.openProjectDetailModal(this.currentEditingProjectId); - } - } - updateRepeatTypeUI(repeatType) { - const customGroup = document.getElementById('customRepeatGroup'); - const movableGroup = document.getElementById('movableRepeatGroup'); - const repeatUnitGroup = document.getElementById('repeatUnitGroup'); - const taskDaysGroup = document.getElementById('taskDaysGroup'); - const repeatUnitLabel = document.getElementById('repeatUnitLabel'); - customGroup.style.display = repeatType === 'custom' ? 'block' : 'none'; - movableGroup.style.display = repeatType === 'movable' ? 'block' : 'none'; - // Show repeat unit for daily, weekly, monthly, yearly - if (['daily', 'weekly', 'monthly', 'yearly'].includes(repeatType)) { - repeatUnitGroup.style.display = 'block'; - const labels = { - daily: 'day(s)', - weekly: 'week(s)', - monthly: 'month(s)', - yearly: 'year(s)' - }; - repeatUnitLabel.textContent = labels[repeatType] || 'unit(s)'; - } - else { - repeatUnitGroup.style.display = 'none'; - } - // Show days selector for weekly and daily - taskDaysGroup.style.display = (['weekly', 'daily'].includes(repeatType)) ? 'block' : 'none'; - } - updateProjectSelect() { - const projects = storage.getProjects(); - const select = document.getElementById('taskProject'); - select.innerHTML = '' + - projects.map(p => ``).join(''); - } - saveTask(e) { - e.preventDefault(); - // Get selected days for weekly/daily tasks - const selectedDays = Array.from(document.querySelectorAll('input[name="taskDay"]:checked')) - .map(checkbox => parseInt(checkbox.value)); - const task = { - title: document.getElementById('taskTitle').value, - description: document.getElementById('taskDescription').value, - dueDate: document.getElementById('taskDueDate').value, - category: document.getElementById('taskCategory').value, - priority: document.getElementById('taskPriority').value, - points: parseInt(document.getElementById('taskPoints').value), - repeatType: document.getElementById('taskRepeatType').value, - projectId: document.getElementById('taskProject').value || null - }; - // Add repeatUnit for daily, weekly, monthly, yearly tasks - if (['daily', 'weekly', 'monthly', 'yearly'].includes(task.repeatType)) { - task.repeatUnit = parseInt(document.getElementById('taskRepeatUnit').value) || 1; - if (selectedDays.length > 0) { - task.daysOfWeek = selectedDays; - } - } - if (task.repeatType === 'custom') { - task.customRepeatDays = parseInt(document.getElementById('customRepeatDays').value); - } - if (task.repeatType === 'movable') { - task.movableRepeatDays = parseInt(document.getElementById('movableRepeatDays').value); - } - if (this.currentEditingTaskId) { - storage.updateTask(this.currentEditingTaskId, task); - } - else { - storage.addTask(task); - } - this.closeTaskModal(); - this.renderTasks(); - this.renderProjects(); - } - deleteTask() { - if (this.currentEditingTaskId) { - if (confirm('Are you sure you want to delete this task?')) { - storage.deleteTask(this.currentEditingTaskId); - this.closeTaskModal(); - this.renderTasks(); - this.renderProjects(); - } - } - } - toggleTask(taskId) { - const task = storage.getTasks().find(t => t.id === taskId); - if (task) { - task.completed = !task.completed; - if (task.completed) { - task.completedDate = this.getSelectedDateStr(); - storage.addPoints(task.points, 'tasks'); - storage.updateDailyStreak(true); - // If repeatable, immediately create next task with recalculated due date - if (task.repeatType !== 'none') { - this.createNextRecurringTask(task); - } - } - else { - // If uncompleting, also recalculate level - task.completedDate = null; - } - storage.updateTask(taskId, task); - storage.updateLevel(); - this.renderTasks(); - this.renderDashboard(); - this.renderProjects(); - } - } - nextOccurrenceOfDays(fromDate, days) { - const next = new Date(fromDate); - for (let i = 1; i <= 7; i++) { - next.setDate(next.getDate() + 1); - if (days.includes(next.getDay())) { - return next; - } - } - return next; - } - createNextRecurringTask(completedTask) { - const [year, month, day] = completedTask.completedDate.split('-').map(Number); - const completionDate = new Date(year, month - 1, day); - const nextDueDate = new Date(completionDate); - const hasDaysOfWeek = completedTask.daysOfWeek && completedTask.daysOfWeek.length > 0; - switch (completedTask.repeatType) { - case 'daily': - if (hasDaysOfWeek) { - const next = this.nextOccurrenceOfDays(completionDate, completedTask.daysOfWeek); - nextDueDate.setTime(next.getTime()); - } - else { - nextDueDate.setDate(nextDueDate.getDate() + (completedTask.repeatUnit || 1)); - } - break; - case 'weekly': - if (hasDaysOfWeek) { - const next = this.nextOccurrenceOfDays(completionDate, completedTask.daysOfWeek); - next.setDate(next.getDate() + 7 * ((completedTask.repeatUnit || 1) - 1)); - nextDueDate.setTime(next.getTime()); - } - else { - nextDueDate.setDate(nextDueDate.getDate() + 7 * (completedTask.repeatUnit || 1)); - } - break; - case 'monthly': - nextDueDate.setMonth(nextDueDate.getMonth() + (completedTask.repeatUnit || 1)); - break; - case 'yearly': - nextDueDate.setFullYear(nextDueDate.getFullYear() + (completedTask.repeatUnit || 1)); - break; - case 'custom': - nextDueDate.setDate(nextDueDate.getDate() + (completedTask.customRepeatDays || 1)); - break; - case 'movable': - nextDueDate.setDate(nextDueDate.getDate() + (completedTask.movableRepeatDays || 1)); - break; - default: - return; - } - const newTask = { - title: completedTask.title, - description: completedTask.description, - category: completedTask.category, - priority: completedTask.priority, - points: completedTask.points, - projectId: completedTask.projectId, - repeatType: completedTask.repeatType, - repeatUnit: completedTask.repeatUnit, - customRepeatDays: completedTask.customRepeatDays, - movableRepeatDays: completedTask.movableRepeatDays, - daysOfWeek: completedTask.daysOfWeek, - dueDate: storage.formatDate(nextDueDate) - }; - storage.addTask(newTask); - } - // ======================== - // Projects Management - // ======================== - renderProjects() { - const projects = storage.getProjects(); - const container = document.getElementById('projectsList'); - if (projects.length === 0) { - container.innerHTML = '

No projects yet. Create one to organize your tasks!

'; - return; - } - container.innerHTML = projects.map(project => this.renderProjectCard(project)).join(''); - document.querySelectorAll('.project-card').forEach(card => { - card.addEventListener('click', () => { - this.openProjectDetailModal(card.dataset.projectId); - }); - }); - } - renderProjectCard(project) { - const tasks = storage.getTasks().filter(t => t.projectId === project.id); - const completed = tasks.filter(t => t.completed).length; - const percentage = tasks.length === 0 ? 0 : Math.round((completed / tasks.length) * 100); - return ` -
-
${project.name}
- ${project.description ? `
${project.description}
` : ''} -
-
- Tasks - ${tasks.length} -
-
- Completed - ${completed} -
-
- Progress - ${percentage}% -
-
-
-
-
-
- `; - } - openProjectModal(projectId = null) { - this.currentEditingProjectId = projectId; - const modal = document.getElementById('projectModal'); - const form = document.getElementById('projectForm'); - const deleteBtn = document.getElementById('deleteProjectBtn'); - form.reset(); - deleteBtn.style.display = 'none'; - if (projectId) { - const project = storage.getProjects().find(p => p.id === projectId); - if (project) { - document.getElementById('projectName').value = project.name; - document.getElementById('projectDescription').value = project.description || ''; - document.getElementById('projectColor').value = project.color || 'blue'; - deleteBtn.style.display = 'block'; - } - } - modal.classList.add('active'); - } - closeProjectModal() { - document.getElementById('projectModal').classList.remove('active'); - this.currentEditingProjectId = null; - } - saveProject(e) { - e.preventDefault(); - const project = { - name: document.getElementById('projectName').value, - description: document.getElementById('projectDescription').value, - color: document.getElementById('projectColor').value - }; - if (this.currentEditingProjectId) { - storage.updateProject(this.currentEditingProjectId, project); - } - else { - storage.addProject(project); - } - this.closeProjectModal(); - this.renderProjects(); - this.updateProjectSelect(); - } - deleteProject() { - if (this.currentEditingProjectId) { - if (confirm('Are you sure you want to delete this project? This will not delete the tasks in it.')) { - storage.deleteProject(this.currentEditingProjectId); - this.closeProjectModal(); - this.renderProjects(); - } - } - } - openProjectDetailModal(projectId) { - const project = storage.getProjects().find(p => p.id === projectId); - if (!project) - return; - this.currentEditingProjectId = projectId; - const modal = document.getElementById('projectDetailModal'); - document.getElementById('projectDetailTitle').textContent = project.name; - document.getElementById('projectDetailDescription').textContent = project.description || 'No description'; - const tasks = storage.getTasks().filter(t => t.projectId === projectId); - const completed = tasks.filter(t => t.completed).length; - const percentage = tasks.length === 0 ? 0 : Math.round((completed / tasks.length) * 100); - document.getElementById('projectDetailTotalTasks').textContent = String(tasks.length); - document.getElementById('projectDetailCompletedTasks').textContent = String(completed); - document.getElementById('projectDetailProgress').textContent = percentage + '%'; - this.renderProjectDetailTasks(tasks); - modal.classList.add('active'); - } - closeProjectDetailModal() { - document.getElementById('projectDetailModal').classList.remove('active'); - this.currentEditingProjectId = null; - } - editProjectFromDetail() { - const projectId = this.currentEditingProjectId; - this.closeProjectDetailModal(); - this.openProjectModal(projectId); - } - renderProjectDetailTasks(tasks) { - const container = document.getElementById('projectDetailTaskList'); - if (tasks.length === 0) { - container.innerHTML = '

No tasks in this project yet.

'; - return; - } - container.innerHTML = tasks.map(task => ` -
- -
-
${task.title}
-
- ${task.dueDate ? `๐Ÿ“… ${task.dueDate}` : ''} - ${task.priority ? `โšก ${task.priority}` : ''} - ${task.category ? `๐Ÿ“‚ ${task.category}` : ''} -
-
-
- `).join(''); - document.querySelectorAll('#projectDetailTaskList .task-item').forEach(item => { - const checkbox = item.querySelector('.task-checkbox'); - checkbox.addEventListener('click', (e) => { - e.stopPropagation(); - this.toggleTask(item.dataset.taskId); - this.openProjectDetailModal(this.currentEditingProjectId); - }); - item.addEventListener('click', (e) => { - if (!e.target.classList.contains('task-checkbox')) { - this.closeProjectDetailModal(); - this.switchTab('tasks'); - this.openTaskModal(item.dataset.taskId); - } - }); - }); - } - // ======================== - // Habits Management - // ======================== - renderHabits() { - const habits = storage.getHabits(); - const container = document.getElementById('habitsList'); - if (habits.length === 0) { - container.innerHTML = '

No habits yet. Create daily habits to build streaks!

'; - return; - } - container.innerHTML = habits.map(habit => this.renderHabitCard(habit)).join(''); - document.querySelectorAll('.habit-card').forEach(card => { - card.addEventListener('click', (e) => { - if (!e.target.classList.contains('habit-checkbox')) { - this.openHabitModal(card.dataset.habitId); - } - }); - }); - document.querySelectorAll('.habit-checkbox').forEach(btn => { - btn.addEventListener('click', (e) => { - e.stopPropagation(); - const habitId = e.target.dataset.habitId; - this.completeHabit(habitId); - }); - }); - } - renderHabitCard(habit) { - const selectedDayOfWeek = this.selectedDate.getDay(); - const isValidDay = !habit.daysOfWeek || habit.daysOfWeek.includes(selectedDayOfWeek); - const selectedDateStr = this.getSelectedDateStr(); - const isPastDay = !this.isSelectedDateToday(); - const todaysCompletions = storage.countHabitCompletionsForDate(habit.id, selectedDateStr); - const targetGoal = habit.targetGoal || 1; - const percentage = Math.min(100, Math.round((todaysCompletions / targetGoal) * 100)); - const isComplete = todaysCompletions >= targetGoal; - const btnLabel = !isValidDay - ? 'โœ— Not Scheduled' - : isComplete - ? (isPastDay ? 'โœ“ Logged' : 'โœ“ Done for Today') - : (isPastDay ? '+ Log Past Day' : '+ Complete'); - return ` -
-
${habit.icon}
-
${habit.name}
- ${habit.description ? `
${habit.description}
` : ''} -
-
- Streak - ${habit.streak || 0} -
-
- Points - ${habit.points} -
-
- Progress - ${todaysCompletions}/${targetGoal} -
-
-
-
-
-
-
- ${isComplete ? 'โœ“ Complete!' : `${percentage}% Complete`} -
-
- -
- `; - } - openHabitModal(habitId = null) { - this.currentEditingHabitId = habitId; - const modal = document.getElementById('habitModal'); - const form = document.getElementById('habitForm'); - const deleteBtn = document.getElementById('deleteHabitBtn'); - form.reset(); - deleteBtn.style.display = 'none'; - document.getElementById('habitIcon').value = 'โญ'; - document.getElementById('habitIconDisplay').textContent = 'โญ'; - document.querySelectorAll('input[name="habitDay"]').forEach(checkbox => { - checkbox.checked = false; - }); - if (habitId) { - const habit = storage.getHabits().find(h => h.id === habitId); - if (habit) { - document.getElementById('habitName').value = habit.name; - document.getElementById('habitDescription').value = habit.description || ''; - document.getElementById('habitIcon').value = habit.icon || 'โญ'; - document.getElementById('habitIconDisplay').textContent = habit.icon || 'โญ'; - document.getElementById('habitCategory').value = habit.category || ''; - document.getElementById('habitPoints').value = String(habit.points || 5); - document.getElementById('habitTargetGoal').value = String(habit.targetGoal || 1); - deleteBtn.style.display = 'block'; - if (habit.daysOfWeek && Array.isArray(habit.daysOfWeek)) { - habit.daysOfWeek.forEach(day => { - const checkbox = document.querySelector(`input[name="habitDay"][value="${day}"]`); - if (checkbox) { - checkbox.checked = true; - } - }); - } - else { - document.querySelectorAll('input[name="habitDay"]').forEach(checkbox => { - checkbox.checked = true; - }); - } - } - } - else { - document.querySelectorAll('input[name="habitDay"]').forEach(checkbox => { - checkbox.checked = true; - }); - } - this.loadCategoryDropdown('habit'); - modal.classList.add('active'); - } - closeHabitModal() { - document.getElementById('habitModal').classList.remove('active'); - this.currentEditingHabitId = null; - } - saveHabit(e) { - e.preventDefault(); - const selectedDays = Array.from(document.querySelectorAll('input[name="habitDay"]:checked')) - .map(checkbox => parseInt(checkbox.value)); - const habit = { - name: document.getElementById('habitName').value, - description: document.getElementById('habitDescription').value, - icon: document.getElementById('habitIcon').value, - category: document.getElementById('habitCategory').value || null, - points: parseInt(document.getElementById('habitPoints').value), - targetGoal: parseInt(document.getElementById('habitTargetGoal').value) || 1, - daysOfWeek: selectedDays.length > 0 ? selectedDays : [0, 1, 2, 3, 4, 5, 6] - }; - if (this.currentEditingHabitId) { - storage.updateHabit(this.currentEditingHabitId, habit); - } - else { - storage.addHabit(habit); - } - this.closeHabitModal(); - this.renderHabits(); - } - deleteHabit() { - if (this.currentEditingHabitId) { - if (confirm('Are you sure you want to delete this habit?')) { - storage.deleteHabit(this.currentEditingHabitId); - this.closeHabitModal(); - this.renderHabits(); - } - } - } - completeHabit(habitId) { - const habit = storage.getHabits().find(h => h.id === habitId); - if (habit) { - const selectedDayOfWeek = this.selectedDate.getDay(); - const isValidDay = !habit.daysOfWeek || habit.daysOfWeek.includes(selectedDayOfWeek); - if (isValidDay) { - storage.logHabitCompletion(habitId, this.selectedDate); - storage.addPoints(habit.points, 'habits'); - storage.updateDailyStreak(true); - this.renderHabits(); - this.renderDashboard(); - } - } - } - openEmojiPicker() { - const modal = document.getElementById('emojiModal'); - const emojiGrid = document.getElementById('emojiGrid'); - emojiGrid.innerHTML = this.emojis.map(emoji => ``).join(''); - document.querySelectorAll('.emoji-btn').forEach(btn => { - btn.addEventListener('click', (e) => { - e.preventDefault(); - this.selectEmoji(e.target.dataset.emoji); - }); - }); - modal.classList.add('active'); - } - closeEmojiPicker() { - document.getElementById('emojiModal').classList.remove('active'); - } - selectEmoji(emoji) { - document.getElementById('habitIcon').value = emoji; - document.getElementById('habitIconDisplay').textContent = emoji; - this.closeEmojiPicker(); - } - // ======================== - // Category Management - // ======================== - loadCategoryDropdown(type) { - const select = document.getElementById(`${type}Category`); - const categories = storage.getCategories(); - const currentValue = select.value; - const emptyOption = document.createElement('option'); - emptyOption.value = ''; - emptyOption.textContent = 'Select category...'; - const addNewOption = document.createElement('option'); - addNewOption.value = '__add_new__'; - addNewOption.textContent = '+ Add New Category'; - select.innerHTML = ''; - select.appendChild(emptyOption); - categories.forEach(cat => { - const option = document.createElement('option'); - option.value = cat; - option.textContent = cat; - select.appendChild(option); - }); - select.appendChild(addNewOption); - select.value = currentValue; - } - handleCategoryChange(type, value) { - const inputDiv = document.getElementById(`${type}CategoryInput`); - const textInput = document.getElementById(`${type}CategoryText`); - if (value === '__add_new__') { - inputDiv.style.display = 'block'; - textInput.value = ''; - textInput.focus(); - } - else { - inputDiv.style.display = 'none'; - } - } - handleAddCategory(type) { - const textInput = document.getElementById(`${type}CategoryText`); - const categoryName = textInput.value.trim(); - if (categoryName) { - if (storage.addCategory(categoryName)) { - this.loadCategoryDropdown(type); - const select = document.getElementById(`${type}Category`); - select.value = categoryName; - document.getElementById(`${type}CategoryInput`).style.display = 'none'; - textInput.value = ''; - } - else { - alert('This category already exists!'); - } - } - else { - alert('Please enter a category name'); - } - } - cancelAddCategory(type) { - document.getElementById(`${type}CategoryInput`).style.display = 'none'; - document.getElementById(`${type}CategoryText`).value = ''; - document.getElementById(`${type}Category`).value = ''; - } - // ======================== - // Settings Category Management - // ======================== - renderCategoryManagement() { - const list = document.getElementById('categoryList'); - if (!list) - return; - const categories = storage.getCategories(); - list.innerHTML = categories.length === 0 - ? '
  • No categories yet.
  • ' - : categories.map(cat => ` -
  • - ${this.escapeHtml(cat)} - - -
  • `).join(''); - } - escapeHtml(str) { - return String(str).replace(/&/g, '&').replace(/"/g, '"').replace(//g, '>'); - } - startEditCategory(name, btn) { - const li = btn.closest('li'); - const nameSpan = li.querySelector('.category-name'); - nameSpan.style.display = 'none'; - const input = document.createElement('input'); - input.type = 'text'; - input.className = 'category-edit-input'; - input.value = name; - li.insertBefore(input, nameSpan); - btn.textContent = 'Save'; - btn.onclick = () => this.saveEditCategory(name, input, btn); - const cancelBtn = document.createElement('button'); - cancelBtn.className = 'btn btn-secondary btn-sm'; - cancelBtn.textContent = 'Cancel'; - cancelBtn.onclick = () => this.renderCategoryManagement(); - btn.after(cancelBtn); - input.focus(); - } - saveEditCategory(oldName, input, _btn) { - const newName = input.value.trim(); - if (!newName) { - alert('Please enter a category name.'); - return; - } - if (newName === oldName) { - this.renderCategoryManagement(); - return; - } - if (!storage.updateCategory(oldName, newName)) { - alert('A category with that name already exists.'); - return; - } - this.renderCategoryManagement(); - } - deleteCategoryItem(name) { - if (!confirm(`Delete category "${name}"? All related items will have their category cleared.`)) - return; - storage.deleteCategory(name); - this.renderCategoryManagement(); - } - addCategoryFromSettings() { - const input = document.getElementById('newCategoryText'); - const name = input.value.trim(); - if (!name) { - alert('Please enter a category name.'); - return; - } - if (!storage.addCategory(name)) { - alert('This category already exists.'); - return; - } - input.value = ''; - this.renderCategoryManagement(); - } - // ======================== - // Finance Management - // ======================== - initializeFinanceDateFilter() { - const today = new Date(); - const firstDay = new Date(today.getFullYear(), today.getMonth(), 1); - const lastDay = new Date(today.getFullYear(), today.getMonth() + 1, 0); - document.getElementById('financeStartDate').valueAsDate = firstDay; - document.getElementById('financeEndDate').valueAsDate = lastDay; - } - resetFinanceFilter() { - this.initializeFinanceDateFilter(); - this.renderFinances(); - } - navigateToPrevMonth() { - const startInput = document.getElementById('financeStartDate'); - const base = startInput.value ? new Date(startInput.value + 'T00:00:00') : new Date(); - const firstDay = new Date(base.getFullYear(), base.getMonth() - 1, 1); - const lastDay = new Date(firstDay.getFullYear(), firstDay.getMonth() + 1, 0); - startInput.valueAsDate = firstDay; - document.getElementById('financeEndDate').valueAsDate = lastDay; - this.renderFinances(); - } - navigateToNextMonth() { - const startInput = document.getElementById('financeStartDate'); - const base = startInput.value ? new Date(startInput.value + 'T00:00:00') : new Date(); - const firstDay = new Date(base.getFullYear(), base.getMonth() + 1, 1); - const lastDay = new Date(firstDay.getFullYear(), firstDay.getMonth() + 1, 0); - startInput.valueAsDate = firstDay; - document.getElementById('financeEndDate').valueAsDate = lastDay; - this.renderFinances(); - } - getFinanceDateRange() { - const startDate = document.getElementById('financeStartDate').value; - const endDate = document.getElementById('financeEndDate').value; - return { startDate, endDate }; - } - filterFinanceItemsByDate(items) { - const { startDate, endDate } = this.getFinanceDateRange(); - if (!startDate && !endDate) { - return items.map(item => ({ ...item, monthlyAmount: item.amount })); - } - return items.filter(item => { - if (!item.date) - return false; - if (item.recurring === 'yearly' || item.recurring === 'monthly') { - if (endDate && item.date > endDate) - return false; - return true; - } - if (startDate && item.date < startDate) - return false; - if (endDate && item.date > endDate) - return false; - return true; - }).map(item => ({ - ...item, - monthlyAmount: item.recurring === 'yearly' ? item.amount / 12 : item.amount - })); - } - renderFinances() { - this.updateFinanceSummary(); - this.renderExpenses(); - this.renderRevenue(); - this.renderCharges(); - } - updateFinanceSummary() { - const expenses = this.filterFinanceItemsByDate(storage.getExpenses()); - const revenue = this.filterFinanceItemsByDate(storage.getRevenue()); - const charges = this.filterFinanceItemsByDate(storage.getCharges()); - const totalExpenses = expenses.reduce((sum, e) => sum + (e.monthlyAmount || 0), 0); - const totalCharges = charges.reduce((sum, c) => sum + (c.monthlyAmount || 0), 0); - const totalRevenue = revenue.reduce((sum, r) => sum + (r.monthlyAmount || 0), 0); - const net = totalRevenue - totalExpenses - totalCharges; - document.getElementById('totalIncome').textContent = '$' + totalRevenue.toFixed(2); - document.getElementById('totalExpenses').textContent = '$' + (totalExpenses + totalCharges).toFixed(2); - document.getElementById('netBalance').textContent = '$' + net.toFixed(2); - } - renderExpenses() { - const expenses = this.filterFinanceItemsByDate(storage.getExpenses()); - this.renderFinanceList(expenses, 'expensesList', 'expense'); - } - renderRevenue() { - const revenue = this.filterFinanceItemsByDate(storage.getRevenue()); - this.renderFinanceList(revenue, 'revenueList', 'revenue', true); - } - renderCharges() { - const charges = this.filterFinanceItemsByDate(storage.getCharges()); - this.renderFinanceList(charges, 'chargesList', 'charge'); - } - renderFinanceList(items, containerId, type, isIncome = false) { - const container = document.getElementById(containerId); - if (items.length === 0) { - container.innerHTML = '

    No items. Add one to get started!

    '; - return; - } - container.innerHTML = items.map(item => { - const displayAmount = (item.monthlyAmount !== undefined ? item.monthlyAmount : item.amount).toFixed(2); - const monthlyLabel = item.recurring === 'yearly' ? ' /mo' : ''; - return ` -
    -
    -
    ${item.description}
    -
    - ${item.category ? `๐Ÿ“‚ ${item.category}` : ''} - ๐Ÿ“… ${item.date || 'N/A'} - ${item.recurring ? `๐Ÿ” ${item.recurring}` : ''} -
    -
    -
    - ${isIncome ? '+' : '-'}$${displayAmount}${monthlyLabel} -
    -
    - `; - }).join(''); - document.querySelectorAll('.finance-item').forEach(item => { - item.addEventListener('click', () => { - this.openFinanceModal(item.dataset.financeType, item.dataset.financeId); - }); - }); - } - openFinanceModal(type, financeId = null) { - this.currentEditingFinanceType = type; - this.currentEditingFinanceId = financeId; - const modal = document.getElementById('financeModal'); - const form = document.getElementById('financeForm'); - const deleteBtn = document.getElementById('deleteFinanceBtn'); - const recurringGroup = document.getElementById('financeRecurringGroup'); - form.reset(); - deleteBtn.style.display = 'none'; - document.getElementById('financeDate').valueAsDate = new Date(); - this.loadCategoryDropdown('finance'); - recurringGroup.style.display = ['expense', 'revenue'].includes(type) ? 'block' : 'none'; - const titles = { expense: 'Add Expense', revenue: 'Add Revenue', charge: 'Add Other Charge' }; - document.getElementById('financeModalTitle').textContent = financeId ? `Edit ${type}` : titles[type]; - if (financeId) { - let item; - if (type === 'expense') - item = storage.getExpenses().find(e => e.id === financeId); - else if (type === 'revenue') - item = storage.getRevenue().find(r => r.id === financeId); - else if (type === 'charge') - item = storage.getCharges().find(c => c.id === financeId); - if (item) { - document.getElementById('financeDescription').value = item.description; - document.getElementById('financeAmount').value = String(item.amount); - document.getElementById('financeDate').value = item.date || ''; - document.getElementById('financeCategory').value = item.category || ''; - if (item.recurring) { - document.getElementById('financeRecurring').value = item.recurring; - } - deleteBtn.style.display = 'block'; - } - } - modal.classList.add('active'); - } - closeFinanceModal() { - document.getElementById('financeModal').classList.remove('active'); - this.currentEditingFinanceId = null; - this.currentEditingFinanceType = null; - } - saveFinance(e) { - e.preventDefault(); - const financeItem = { - description: document.getElementById('financeDescription').value, - amount: parseFloat(document.getElementById('financeAmount').value), - date: document.getElementById('financeDate').value, - category: document.getElementById('financeCategory').value - }; - if (['expense', 'revenue'].includes(this.currentEditingFinanceType)) { - financeItem.recurring = document.getElementById('financeRecurring').value; - } - if (this.currentEditingFinanceType === 'expense') { - if (this.currentEditingFinanceId) { - storage.updateExpense(this.currentEditingFinanceId, financeItem); - } - else { - storage.addExpense(financeItem); - } - } - else if (this.currentEditingFinanceType === 'revenue') { - if (this.currentEditingFinanceId) { - storage.updateRevenue(this.currentEditingFinanceId, financeItem); - } - else { - storage.addRevenue(financeItem); - } - } - else if (this.currentEditingFinanceType === 'charge') { - if (this.currentEditingFinanceId) { - storage.updateCharge(this.currentEditingFinanceId, financeItem); - } - else { - storage.addCharge(financeItem); - } - } - this.closeFinanceModal(); - this.renderFinances(); - } - deleteFinance() { - if (this.currentEditingFinanceId) { - if (confirm('Are you sure you want to delete this item?')) { - if (this.currentEditingFinanceType === 'expense') { - storage.deleteExpense(this.currentEditingFinanceId); - } - else if (this.currentEditingFinanceType === 'revenue') { - storage.deleteRevenue(this.currentEditingFinanceId); - } - else if (this.currentEditingFinanceType === 'charge') { - storage.deleteCharge(this.currentEditingFinanceId); - } - this.closeFinanceModal(); - this.renderFinances(); - } - } - } - // ======================== - // Shop/Rewards Management - // ======================== - renderShop() { - const rewards = storage.getRewards(); - const userStats = storage.getUserStats(); - const container = document.getElementById('rewardsList'); - document.getElementById('shopPointsDisplay').textContent = String(userStats.totalPoints); - if (rewards.length === 0) { - container.innerHTML = '

    No rewards yet. Add rewards to spend your points on!

    '; - return; - } - container.innerHTML = rewards.map(reward => { - let alreadyPurchased = false; - if (reward.repeatable === false) { - const purchaseHistory = storage.getData().purchaseHistory || []; - alreadyPurchased = purchaseHistory.some(ph => ph.rewardId === reward.id); - } - const disabled = userStats.totalPoints < reward.cost || alreadyPurchased; - let purchaseLabel = 'Purchase'; - if (userStats.totalPoints < reward.cost) - purchaseLabel = 'Not Enough Points'; - if (alreadyPurchased) - purchaseLabel = 'Purchased'; - return ` -
    -
    ${reward.name}
    - ${reward.description ? `
    ${reward.description}
    ` : ''} -
    -
    - Cost - ${reward.cost} pts -
    -
    - ${reward.repeatable === false ? 'One-time' : 'Repeatable'} -
    -
    -
    - - -
    -
    - `; - }).join(''); - document.querySelectorAll('.purchase-btn').forEach(btn => { - btn.addEventListener('click', (e) => { - e.stopPropagation(); - const card = e.target.closest('[data-reward-id]'); - this.purchaseReward(card.dataset.rewardId); - }); - }); - document.querySelectorAll('.edit-reward-btn').forEach(btn => { - btn.addEventListener('click', (e) => { - e.stopPropagation(); - const card = e.target.closest('[data-reward-id]'); - this.openRewardModal(card.dataset.rewardId); - }); - }); - } - openRewardModal(rewardId = null) { - this.currentEditingRewardId = rewardId; - const modal = document.getElementById('rewardModal'); - const form = document.getElementById('rewardForm'); - const deleteBtn = document.getElementById('deleteRewardBtn'); - form.reset(); - deleteBtn.style.display = 'none'; - document.getElementById('rewardModalTitle').textContent = rewardId ? 'Edit Reward' : 'Add Reward'; - if (rewardId) { - const reward = storage.getRewards().find(r => r.id === rewardId); - if (reward) { - document.getElementById('rewardName').value = reward.name; - document.getElementById('rewardDescription').value = reward.description || ''; - document.getElementById('rewardCost').value = String(reward.cost); - document.getElementById('rewardRepeatable').value = String(reward.repeatable === undefined ? true : reward.repeatable); - deleteBtn.style.display = 'block'; - } - } - modal.classList.add('active'); - } - closeRewardModal() { - document.getElementById('rewardModal').classList.remove('active'); - this.currentEditingRewardId = null; - } - saveReward(e) { - e.preventDefault(); - const reward = { - name: document.getElementById('rewardName').value, - description: document.getElementById('rewardDescription').value, - cost: parseInt(document.getElementById('rewardCost').value), - repeatable: document.getElementById('rewardRepeatable').value === 'true' - }; - if (this.currentEditingRewardId) { - storage.updateReward(this.currentEditingRewardId, reward); - } - else { - storage.addReward(reward); - } - this.closeRewardModal(); - this.renderShop(); - } - deleteReward() { - if (this.currentEditingRewardId) { - if (confirm('Are you sure you want to delete this reward?')) { - storage.deleteReward(this.currentEditingRewardId); - this.closeRewardModal(); - this.renderShop(); - } - } - } - purchaseReward(rewardId) { - const reward = storage.getRewards().find(r => r.id === rewardId); - if (!reward) - return; - if (confirm(`Purchase "${reward.name}" for ${reward.cost} points?`)) { - const result = storage.purchaseReward(rewardId); - if (result.success) { - alert(`Congratulations! You've purchased: ${reward.name}!\n\nEnjoy your reward! ๐ŸŽ‰`); - this.renderShop(); - this.renderDashboard(); - } - else { - alert(result.message); - } - } - } - // ======================== - // Wish List - // ======================== - renderWishList() { - const items = storage.getWishItems(); - const container = document.getElementById('wishList'); - if (items.length === 0) { - container.innerHTML = '

    No items in your wish list. Add one to get started!

    '; - return; - } - container.innerHTML = items.map(item => this.renderWishItem(item)).join(''); - container.querySelectorAll('.wish-item').forEach(el => { - el.addEventListener('dragstart', (e) => { - this.dragSrcWishId = el.dataset.wishId; - el.classList.add('dragging'); - e.dataTransfer.effectAllowed = 'move'; - }); - el.addEventListener('dragend', () => { - this.dragSrcWishId = null; - el.classList.remove('dragging'); - container.querySelectorAll('.wish-item').forEach(i => i.classList.remove('drag-over')); - }); - el.addEventListener('dragover', (e) => { - e.preventDefault(); - e.dataTransfer.dropEffect = 'move'; - container.querySelectorAll('.wish-item').forEach(i => i.classList.remove('drag-over')); - el.classList.add('drag-over'); - }); - el.addEventListener('drop', (e) => { - e.preventDefault(); - const targetId = el.dataset.wishId; - if (this.dragSrcWishId && this.dragSrcWishId !== targetId) { - const allItems = storage.getWishItems(); - const srcIdx = allItems.findIndex(i => i.id === this.dragSrcWishId); - const tgtIdx = allItems.findIndex(i => i.id === targetId); - if (srcIdx !== -1 && tgtIdx !== -1) { - const reordered = [...allItems]; - const [moved] = reordered.splice(srcIdx, 1); - reordered.splice(tgtIdx, 0, moved); - storage.reorderWishItems(reordered.map(i => i.id)); - this.renderWishList(); - } - } - }); - // Touch events for mobile drag and drop support - let touchDragOverItem = null; - el.addEventListener('touchstart', () => { - this.dragSrcWishId = el.dataset.wishId; - el.classList.add('dragging'); - }, { passive: false }); - el.addEventListener('touchmove', (e) => { - e.preventDefault(); - const touch = e.touches[0]; - // Temporarily hide the dragged element so elementFromPoint finds the element underneath - el.style.visibility = 'hidden'; - const target = document.elementFromPoint(touch.clientX, touch.clientY); - el.style.visibility = ''; - const targetItem = target?.closest('.wish-item') ?? null; - if (targetItem !== touchDragOverItem) { - touchDragOverItem?.classList.remove('drag-over'); - touchDragOverItem = targetItem !== el ? targetItem : null; - touchDragOverItem?.classList.add('drag-over'); - } - }, { passive: false }); - el.addEventListener('touchend', (e) => { - el.classList.remove('dragging'); - touchDragOverItem?.classList.remove('drag-over'); - const touch = e.changedTouches[0]; - el.style.visibility = 'hidden'; - const target = document.elementFromPoint(touch.clientX, touch.clientY); - el.style.visibility = ''; - const targetItem = target?.closest('.wish-item'); - const targetId = targetItem?.dataset.wishId; - touchDragOverItem = null; - if (this.dragSrcWishId && targetId && this.dragSrcWishId !== targetId) { - const allItems = storage.getWishItems(); - const srcIdx = allItems.findIndex(i => i.id === this.dragSrcWishId); - const tgtIdx = allItems.findIndex(i => i.id === targetId); - if (srcIdx !== -1 && tgtIdx !== -1) { - const reordered = [...allItems]; - const [moved] = reordered.splice(srcIdx, 1); - reordered.splice(tgtIdx, 0, moved); - storage.reorderWishItems(reordered.map(i => i.id)); - this.renderWishList(); - } - } - this.dragSrcWishId = null; - }); - el.querySelector('.wish-item-checkbox').addEventListener('change', (e) => { - e.stopPropagation(); - const checkbox = e.target; - storage.updateWishItem(el.dataset.wishId, { completed: checkbox.checked }); - el.classList.toggle('completed', checkbox.checked); - }); - el.querySelector('.edit-wish-btn').addEventListener('click', (e) => { - e.stopPropagation(); - this.openWishItemModal(el.dataset.wishId); - }); - }); - } - renderWishItem(item) { - const priceStr = item.price !== undefined && item.price !== null - ? `$${Number(item.price).toFixed(2)}` - : ''; - const urlStr = item.url - ? `๐Ÿ”— View Listing` - : ''; - const completedClass = item.completed ? ' completed' : ''; - const checkedAttr = item.completed ? ' checked' : ''; - return ` -
    - โ ฟ - -
    -
    ${item.title}
    -
    - ${priceStr} - ${urlStr} -
    -
    - -
    - `; - } - openWishItemModal(itemId = null) { - this.currentEditingWishItemId = itemId; - const modal = document.getElementById('wishItemModal'); - const form = document.getElementById('wishItemForm'); - const deleteBtn = document.getElementById('deleteWishItemBtn'); - form.reset(); - deleteBtn.style.display = 'none'; - document.getElementById('wishItemModalTitle').textContent = itemId ? 'Edit Wish List Item' : 'Add Wish List Item'; - if (itemId) { - const item = storage.getWishItems().find(w => w.id === itemId); - if (item) { - document.getElementById('wishItemTitle').value = item.title; - document.getElementById('wishItemUrl').value = item.url || ''; - document.getElementById('wishItemPrice').value = - item.price !== undefined && item.price !== null ? String(item.price) : ''; - deleteBtn.style.display = 'block'; - } - } - modal.classList.add('active'); - } - closeWishItemModal() { - document.getElementById('wishItemModal').classList.remove('active'); - this.currentEditingWishItemId = null; - } - saveWishItem(e) { - e.preventDefault(); - const title = document.getElementById('wishItemTitle').value; - const url = document.getElementById('wishItemUrl').value.trim() || undefined; - const priceVal = document.getElementById('wishItemPrice').value; - const price = priceVal !== '' ? parseFloat(priceVal) : undefined; - const item = { title, url, price }; - if (this.currentEditingWishItemId) { - storage.updateWishItem(this.currentEditingWishItemId, item); - } - else { - storage.addWishItem(item); - } - this.closeWishItemModal(); - this.renderWishList(); - } - deleteWishItem() { - if (this.currentEditingWishItemId) { - if (confirm('Are you sure you want to delete this wish list item?')) { - storage.deleteWishItem(this.currentEditingWishItemId); - this.closeWishItemModal(); - this.renderWishList(); - } - } - } - // ======================== - // Notes - // ======================== - renderNotes() { - const notes = storage.getNotes(); - const container = document.getElementById('notesList'); - if (notes.length === 0) { - container.innerHTML = '

    No notes yet. Add one to get started!

    '; - return; - } - container.innerHTML = notes.map(note => this.renderNoteItem(note)).join(''); - container.querySelectorAll('.note-item').forEach(el => { - el.addEventListener('click', () => { - this.openNoteModal(el.dataset.noteId); - }); - }); - } - renderNoteItem(note) { - const rawPreview = note.content.length > 120 - ? note.content.substring(0, 120) + 'โ€ฆ' - : note.content; - const title = this.escapeHtml(note.title || 'Untitled'); - const preview = rawPreview ? this.escapeHtml(rawPreview) : 'No content'; - const date = new Date(note.updatedDate ?? note.createdDate).toLocaleDateString(); - return ` -
    -
    ${title}
    -
    ${preview}
    -
    ${date}
    -
    - `; - } - openNoteModal(noteId = null) { - this.currentEditingNoteId = noteId; - const modal = document.getElementById('noteModal'); - const form = document.getElementById('noteForm'); - const deleteBtn = document.getElementById('deleteNoteBtn'); - form.reset(); - deleteBtn.style.display = 'none'; - document.getElementById('noteModalTitle').textContent = noteId ? 'Edit Note' : 'Add Note'; - if (noteId) { - const note = storage.getNotes().find(n => n.id === noteId); - if (note) { - document.getElementById('noteTitle').value = note.title; - document.getElementById('noteContent').value = note.content; - deleteBtn.style.display = 'block'; - } - } - modal.classList.add('active'); - } - closeNoteModal() { - document.getElementById('noteModal').classList.remove('active'); - this.currentEditingNoteId = null; - } - saveNote(e) { - e.preventDefault(); - const title = document.getElementById('noteTitle').value.trim(); - const content = document.getElementById('noteContent').value.trim(); - if (this.currentEditingNoteId) { - storage.updateNote(this.currentEditingNoteId, { title, content }); - } - else { - storage.addNote({ title, content }); - } - this.closeNoteModal(); - this.renderNotes(); - } - deleteNote() { - if (this.currentEditingNoteId) { - if (confirm('Are you sure you want to delete this note?')) { - storage.deleteNote(this.currentEditingNoteId); - this.closeNoteModal(); - this.renderNotes(); - } - } - } - // ======================== - // Settings - // ======================== - renderSettings() { - this.loadSettings(); - this.updateSettingsStatus(); - this.renderCategoryManagement(); - } - loadSettings() { - const settings = storage.getSettings(); - const tasksPerLevelInput = document.getElementById('tasksPerLevel'); - if (tasksPerLevelInput) { - tasksPerLevelInput.value = String(settings.tasksPerLevel || 30); - } - } - updateSettingsStatus() { - const settings = storage.getSettings(); - const tasks = storage.getTasks(); - const completedTasksCount = tasks.filter(t => t.completed).length; - const userStats = storage.getUserStats(); - const tasksInCurrentLevel = completedTasksCount % settings.tasksPerLevel; - const tasksToNext = settings.tasksPerLevel - tasksInCurrentLevel; - const currentLevelEl = document.getElementById('settingsCurrentLevel'); - const totalCompletedEl = document.getElementById('settingsTotalCompleted'); - const tasksToNextEl = document.getElementById('settingsTasksToNext'); - if (currentLevelEl) - currentLevelEl.textContent = String(userStats.level); - if (totalCompletedEl) - totalCompletedEl.textContent = String(completedTasksCount); - if (tasksToNextEl) - tasksToNextEl.textContent = String(tasksToNext); - } - saveTasksPerLevel() { - const tasksPerLevel = parseInt(document.getElementById('tasksPerLevel').value); - if (isNaN(tasksPerLevel) || tasksPerLevel < 1) { - alert('Please enter a valid number (minimum 1)'); - return; - } - storage.updateSettings({ tasksPerLevel }); - alert('Settings saved! Level has been recalculated.'); - this.updateSettingsStatus(); - this.renderDashboard(); - } - exportData() { - const data = storage.exportData(); - const blob = new Blob([data], { type: 'application/json' }); - const url = URL.createObjectURL(blob); - const a = document.createElement('a'); - a.href = url; - a.download = `task-manager-backup-${new Date().toISOString().split('T')[0]}.json`; - document.body.appendChild(a); - a.click(); - document.body.removeChild(a); - URL.revokeObjectURL(url); - } - importData(e) { - const file = e.target.files?.[0]; - if (!file) - return; - const reader = new FileReader(); - reader.onload = (event) => { - try { - if (storage.importData(event.target.result)) { - alert('Data imported successfully! Refreshing...'); - location.reload(); - } - else { - alert('Invalid file format. Please upload a valid Task Manager backup.'); - } - } - catch (error) { - alert('Error importing file: ' + error.message); - } - }; - reader.readAsText(file); - } - // ======================== - // Recurring Tasks Processing - // ======================== - processRecurringTasks() { - // New recurring tasks are created immediately when a repeatable task is completed - // via createNextRecurringTask(). The original completed task stays in history. - } - // ======================== - // General Rendering - // ======================== - render() { - document.getElementById('dataVersion').textContent = STORAGE_VERSION; - const lastUpdated = storage.getData().lastUpdated; - document.getElementById('lastUpdated').textContent = lastUpdated ? new Date(lastUpdated).toLocaleString() : 'Never'; - this.updateDateNavigator(); - this.renderDashboard(); - } - // ======================== - // Date Navigation - // ======================== - getSelectedDateStr() { - return storage.formatDate(this.selectedDate); - } - isSelectedDateToday() { - return this.getSelectedDateStr() === storage.formatDate(new Date()); - } - navigateDate(delta) { - const today = new Date(); - today.setHours(0, 0, 0, 0); - const minDate = new Date(today); - minDate.setDate(minDate.getDate() - 6); - const newDate = new Date(this.selectedDate); - newDate.setDate(newDate.getDate() + delta); - newDate.setHours(0, 0, 0, 0); - if (newDate >= minDate && newDate <= today) { - this.selectedDate = newDate; - this.updateDateNavigator(); - const activeTab = document.querySelector('.nav-tab.active'); - if (activeTab) { - this.switchTab(activeTab.dataset.tab); - } - else { - this.renderDashboard(); - } - } - } - formatDisplayDate(date) { - const days = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday']; - const months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; - return `${days[date.getDay()]}, ${months[date.getMonth()]} ${date.getDate()}`; - } - updateDateNavigator() { - const today = new Date(); - today.setHours(0, 0, 0, 0); - const minDate = new Date(today); - minDate.setDate(minDate.getDate() - 6); - const sel = new Date(this.selectedDate); - sel.setHours(0, 0, 0, 0); - const isToday = sel.getTime() === today.getTime(); - const isPastLimit = sel.getTime() <= minDate.getTime(); - const displayStr = isToday - ? `Today โ€” ${this.formatDisplayDate(this.selectedDate)}` - : this.formatDisplayDate(this.selectedDate); - document.getElementById('selectedDateDisplay').textContent = displayStr; - document.getElementById('prevDayBtn').disabled = isPastLimit; - document.getElementById('nextDayBtn').disabled = isToday; - document.getElementById('goTodayBtn').style.display = isToday ? 'none' : 'inline-block'; - } -} -// Initialize the app when DOM is ready -document.addEventListener('DOMContentLoaded', () => { - window.app = new TaskManager(); -}); -export { TaskManager }; -//# sourceMappingURL=app.js.map \ No newline at end of file diff --git a/js/storage.d.ts.map b/js/storage.d.ts.map deleted file mode 100644 index 2d3572c..0000000 --- a/js/storage.d.ts.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"storage.d.ts","sourceRoot":"","sources":["../src/storage.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/js/storage.js b/js/storage.js deleted file mode 100644 index 65bd7bb..0000000 --- a/js/storage.js +++ /dev/null @@ -1,710 +0,0 @@ -// ======================== -// Storage Management with Versioning -// ======================== -const STORAGE_VERSION = '1.0.0'; -const STORAGE_KEY = 'taskManagerData'; -const DATA_SCHEMA_VERSION = 1; -export class StorageManager { - constructor() { - this.initializeStorage(); - } - initializeStorage() { - const existingData = localStorage.getItem(STORAGE_KEY); - if (!existingData) { - this.createInitialData(); - } - } - createInitialData() { - const initialData = { - version: STORAGE_VERSION, - schemaVersion: DATA_SCHEMA_VERSION, - lastUpdated: new Date().toISOString(), - tasks: [], - projects: [], - habits: [], - dailyHabitLogs: [], - expenses: [], - revenue: [], - charges: [], - rewards: [], - purchaseHistory: [], - categories: ['Work', 'Personal', 'Home', 'Shopping', 'Health', 'Fitness', 'Learning', 'Productivity', 'Food', 'Transportation', 'Entertainment', 'Utilities', 'Income'], - userStats: { - totalPoints: 0, - level: 1, - dailyStreak: 0, - lastActivityDate: null, - pointsBreakdown: { - tasks: 0, - projects: 0, - habits: 0, - streakBonus: 0 - } - }, - settings: { - tasksPerLevel: 30 - }, - wishList: [], - notes: [] - }; - localStorage.setItem(STORAGE_KEY, JSON.stringify(initialData)); - } - getData() { - const data = localStorage.getItem(STORAGE_KEY); - return data ? JSON.parse(data) : this.getDefaultData(); - } - getDefaultData() { - this.createInitialData(); - return JSON.parse(localStorage.getItem(STORAGE_KEY)); - } - saveData(data) { - data.lastUpdated = new Date().toISOString(); - localStorage.setItem(STORAGE_KEY, JSON.stringify(data)); - } - // Task Management - addTask(task) { - const data = this.getData(); - const newTask = { - ...task, - id: this.generateId(), - createdDate: new Date().toISOString(), - completed: false, - title: task.title || '', - priority: task.priority || 'medium', - points: task.points || 10, - repeatType: task.repeatType || 'none', - }; - data.tasks.push(newTask); - this.saveData(data); - return newTask; - } - updateTask(taskId, updates) { - const data = this.getData(); - const task = data.tasks.find(t => t.id === taskId); - if (task) { - Object.assign(task, updates); - this.saveData(data); - } - return task; - } - deleteTask(taskId) { - const data = this.getData(); - data.tasks = data.tasks.filter(t => t.id !== taskId); - this.saveData(data); - } - getTasks() { - const data = this.getData(); - return data.tasks || []; - } - // Project Management - addProject(project) { - const data = this.getData(); - const newProject = { - ...project, - id: this.generateId(), - createdDate: new Date().toISOString(), - name: project.name || '', - }; - data.projects.push(newProject); - this.saveData(data); - return newProject; - } - updateProject(projectId, updates) { - const data = this.getData(); - const project = data.projects.find(p => p.id === projectId); - if (project) { - Object.assign(project, updates); - this.saveData(data); - } - return project; - } - deleteProject(projectId) { - const data = this.getData(); - data.projects = data.projects.filter(p => p.id !== projectId); - // Remove project from all tasks - data.tasks = data.tasks.map(t => { - if (t.projectId === projectId) { - t.projectId = null; - } - return t; - }); - this.saveData(data); - } - getProjects() { - const data = this.getData(); - return data.projects || []; - } - // Habit Management - addHabit(habit) { - const data = this.getData(); - const newHabit = { - ...habit, - id: this.generateId(), - createdDate: new Date().toISOString(), - streak: 0, - lastCompletedDate: null, - targetGoal: habit.targetGoal || 1, - name: habit.name || '', - icon: habit.icon || 'โญ', - points: habit.points || 10, - }; - data.habits.push(newHabit); - this.saveData(data); - return newHabit; - } - updateHabit(habitId, updates) { - const data = this.getData(); - const habit = data.habits.find(h => h.id === habitId); - if (habit) { - Object.assign(habit, updates); - if (!habit.targetGoal) - habit.targetGoal = 1; - this.saveData(data); - } - return habit; - } - deleteHabit(habitId) { - const data = this.getData(); - data.habits = data.habits.filter(h => h.id !== habitId); - this.saveData(data); - } - getHabits() { - const data = this.getData(); - return data.habits || []; - } - logHabitCompletion(habitId, date = new Date()) { - const data = this.getData(); - const dateStr = this.formatDate(date); - data.dailyHabitLogs.push({ - id: this.generateId(), - habitId, - date: dateStr, - timestamp: new Date().toISOString() - }); - // Update habit streak based on fully-completed consecutive days - const habit = data.habits.find(h => h.id === habitId); - if (habit) { - habit.lastCompletedDate = dateStr; - habit.streak = this.calculateHabitStreak(habitId, habit.targetGoal, data.dailyHabitLogs); - } - this.saveData(data); - } - calculateHabitStreak(habitId, targetGoal, logs) { - // Count completions per date for this habit - const habitLogs = logs.filter(l => l.habitId === habitId); - const countsByDate = {}; - for (const log of habitLogs) { - countsByDate[log.date] = (countsByDate[log.date] || 0) + 1; - } - // Get dates where fully completed (>= targetGoal), sorted most recent first - const completedDates = Object.keys(countsByDate) - .filter(date => countsByDate[date] >= targetGoal) - .sort() - .reverse(); - if (completedDates.length === 0) - return 0; - // Count consecutive days going backward from the most recent fully-completed day - let streak = 1; - let currentDate = completedDates[0]; - for (let i = 1; i < completedDates.length; i++) { - const [year, month, day] = currentDate.split('-').map(Number); - const prevDay = new Date(year, month - 1, day); - prevDay.setDate(prevDay.getDate() - 1); - const expectedDate = this.formatDate(prevDay); - if (completedDates[i] === expectedDate) { - streak++; - currentDate = completedDates[i]; - } - else { - break; - } - } - return streak; - } - isHabitCompletedToday(habitId) { - const data = this.getData(); - const todayStr = this.formatDate(new Date()); - return data.dailyHabitLogs.some(log => log.habitId === habitId && log.date === todayStr); - } - countHabitCompletionsToday(habitId) { - const data = this.getData(); - const todayStr = this.formatDate(new Date()); - return data.dailyHabitLogs.filter(log => log.habitId === habitId && log.date === todayStr).length; - } - countHabitCompletionsForDate(habitId, dateStr) { - const data = this.getData(); - return data.dailyHabitLogs.filter(log => log.habitId === habitId && log.date === dateStr).length; - } - // Finance Management - addExpense(expense) { - const data = this.getData(); - const newExpense = { - ...expense, - id: this.generateId(), - createdDate: new Date().toISOString(), - description: expense.description || '', - amount: expense.amount || 0, - }; - data.expenses.push(newExpense); - this.saveData(data); - return newExpense; - } - updateExpense(expenseId, updates) { - const data = this.getData(); - const expense = data.expenses.find(e => e.id === expenseId); - if (expense) { - Object.assign(expense, updates); - this.saveData(data); - } - return expense; - } - deleteExpense(expenseId) { - const data = this.getData(); - data.expenses = data.expenses.filter(e => e.id !== expenseId); - this.saveData(data); - } - getExpenses() { - const data = this.getData(); - return data.expenses || []; - } - addRevenue(revenue) { - const data = this.getData(); - const newRevenue = { - ...revenue, - id: this.generateId(), - createdDate: new Date().toISOString(), - description: revenue.description || '', - amount: revenue.amount || 0, - }; - data.revenue.push(newRevenue); - this.saveData(data); - return newRevenue; - } - updateRevenue(revenueId, updates) { - const data = this.getData(); - const item = data.revenue.find(r => r.id === revenueId); - if (item) { - Object.assign(item, updates); - this.saveData(data); - } - return item; - } - deleteRevenue(revenueId) { - const data = this.getData(); - data.revenue = data.revenue.filter(r => r.id !== revenueId); - this.saveData(data); - } - getRevenue() { - const data = this.getData(); - return data.revenue || []; - } - // Other Charges Management - addCharge(charge) { - const data = this.getData(); - if (!data.charges) { - data.charges = []; - } - const newCharge = { - ...charge, - id: this.generateId(), - createdDate: new Date().toISOString(), - description: charge.description || '', - amount: charge.amount || 0, - }; - data.charges.push(newCharge); - this.saveData(data); - return newCharge; - } - updateCharge(chargeId, updates) { - const data = this.getData(); - if (!data.charges) { - data.charges = []; - } - const charge = data.charges.find(c => c.id === chargeId); - if (charge) { - Object.assign(charge, updates); - this.saveData(data); - } - return charge; - } - deleteCharge(chargeId) { - const data = this.getData(); - if (!data.charges) { - data.charges = []; - } - data.charges = data.charges.filter(c => c.id !== chargeId); - this.saveData(data); - } - getCharges() { - const data = this.getData(); - return data.charges || []; - } - // Rewards Shop Management - addReward(reward) { - const data = this.getData(); - if (!data.rewards) { - data.rewards = []; - } - const newReward = { - ...reward, - id: this.generateId(), - createdDate: new Date().toISOString(), - purchased: false, - repeatable: typeof reward.repeatable === 'undefined' ? true : reward.repeatable, - name: reward.name || '', - cost: reward.cost || 0, - }; - data.rewards.push(newReward); - this.saveData(data); - return newReward; - } - updateReward(rewardId, updates) { - const data = this.getData(); - if (!data.rewards) { - data.rewards = []; - } - const reward = data.rewards.find(r => r.id === rewardId); - if (reward) { - Object.assign(reward, updates); - if (typeof reward.repeatable === 'undefined') - reward.repeatable = true; - this.saveData(data); - } - return reward; - } - deleteReward(rewardId) { - const data = this.getData(); - if (!data.rewards) { - data.rewards = []; - } - data.rewards = data.rewards.filter(r => r.id !== rewardId); - this.saveData(data); - } - getRewards() { - const data = this.getData(); - return data.rewards || []; - } - purchaseReward(rewardId) { - const data = this.getData(); - if (!data.rewards) { - data.rewards = []; - } - if (!data.purchaseHistory) { - data.purchaseHistory = []; - } - const reward = data.rewards.find(r => r.id === rewardId); - if (!reward) { - return { success: false, message: 'Reward not found' }; - } - if (data.userStats.totalPoints < reward.cost) { - return { success: false, message: 'Not enough points' }; - } - // If one-time and already purchased, block - if (reward.repeatable === false) { - const alreadyPurchased = data.purchaseHistory.some(ph => ph.rewardId === reward.id); - if (alreadyPurchased) { - return { success: false, message: 'This reward can only be purchased once.' }; - } - } - // Deduct points - data.userStats.totalPoints -= reward.cost; - // Add to purchase history - const purchase = { - id: this.generateId(), - rewardId: reward.id, - rewardName: reward.name, - rewardDescription: reward.description, - cost: reward.cost, - purchaseDate: new Date().toISOString() - }; - data.purchaseHistory.push(purchase); - this.saveData(data); - return { success: true, purchase }; - } - getPurchaseHistory() { - const data = this.getData(); - return data.purchaseHistory || []; - } - // Wish List Management - addWishItem(item) { - const data = this.getData(); - if (!data.wishList) - data.wishList = []; - const newItem = { - ...item, - id: this.generateId(), - createdDate: new Date().toISOString(), - title: item.title || '', - order: data.wishList.length, - }; - data.wishList.push(newItem); - this.saveData(data); - return newItem; - } - updateWishItem(itemId, updates) { - const data = this.getData(); - if (!data.wishList) - data.wishList = []; - const item = data.wishList.find(w => w.id === itemId); - if (item) { - Object.assign(item, updates); - this.saveData(data); - } - return item; - } - deleteWishItem(itemId) { - const data = this.getData(); - if (!data.wishList) - data.wishList = []; - data.wishList = data.wishList.filter(w => w.id !== itemId); - // Re-index order values - data.wishList.forEach((w, idx) => { w.order = idx; }); - this.saveData(data); - } - getWishItems() { - const data = this.getData(); - if (!data.wishList) - return []; - return data.wishList.slice().sort((a, b) => a.order - b.order); - } - reorderWishItems(orderedIds) { - const data = this.getData(); - if (!data.wishList) - return; - orderedIds.forEach((id, idx) => { - const item = data.wishList.find(w => w.id === id); - if (item) - item.order = idx; - }); - this.saveData(data); - } - // Note Management - addNote(note) { - const data = this.getData(); - if (!data.notes) - data.notes = []; - const newNote = { - id: this.generateId(), - title: note.title || '', - content: note.content || '', - createdDate: new Date().toISOString(), - }; - data.notes.push(newNote); - this.saveData(data); - return newNote; - } - updateNote(noteId, updates) { - const data = this.getData(); - if (!data.notes) - data.notes = []; - const note = data.notes.find(n => n.id === noteId); - if (note) { - Object.assign(note, updates); - note.updatedDate = new Date().toISOString(); - this.saveData(data); - } - return note; - } - deleteNote(noteId) { - const data = this.getData(); - if (!data.notes) - data.notes = []; - data.notes = data.notes.filter(n => n.id !== noteId); - this.saveData(data); - } - getNotes() { - const data = this.getData(); - if (!data.notes) - return []; - return data.notes.slice().sort((a, b) => new Date(b.updatedDate ?? b.createdDate).getTime() - new Date(a.updatedDate ?? a.createdDate).getTime()); - } - // Points Management - addPoints(amount, source) { - const data = this.getData(); - data.userStats.totalPoints += amount; - if (data.userStats.pointsBreakdown[source] !== undefined) { - data.userStats.pointsBreakdown[source] += amount; - } - // Level is now calculated based on completed tasks, not points - this.updateLevel(); - this.saveData(data); - } - updateLevel() { - const data = this.getData(); - const settings = this.getSettings(); - const completedTasksCount = data.tasks.filter(t => t.completed).length; - data.userStats.level = Math.floor(completedTasksCount / settings.tasksPerLevel) + 1; - this.saveData(data); - } - updateDailyStreak(increment = true) { - const data = this.getData(); - const today = this.formatDate(new Date()); - if (increment) { - if (data.userStats.lastActivityDate !== today) { - const yesterday = new Date(); - yesterday.setDate(yesterday.getDate() - 1); - if (data.userStats.lastActivityDate !== this.formatDate(yesterday)) { - data.userStats.dailyStreak = 1; - } - else { - data.userStats.dailyStreak += 1; - } - } - } - data.userStats.lastActivityDate = today; - this.saveData(data); - } - getUserStats() { - const data = this.getData(); - return data.userStats; - } - // Utility Methods - generateId() { - return Date.now().toString(36) + Math.random().toString(36).substr(2); - } - formatDate(date) { - const year = date.getFullYear(); - const month = String(date.getMonth() + 1).padStart(2, '0'); - const day = String(date.getDate()).padStart(2, '0'); - return `${year}-${month}-${day}`; - } - exportData() { - const data = this.getData(); - return JSON.stringify(data, null, 2); - } - importData(jsonString) { - try { - const data = JSON.parse(jsonString); - // Validate that it has the required structure - if (data.version && data.tasks !== undefined && data.projects !== undefined) { - localStorage.setItem(STORAGE_KEY, JSON.stringify(data)); - return true; - } - return false; - } - catch (e) { - console.error('Import error:', e); - return false; - } - } - clearAllData() { - if (confirm('Are you sure you want to clear all data? This cannot be undone.')) { - localStorage.removeItem(STORAGE_KEY); - this.createInitialData(); - return true; - } - return false; - } - // ======================== - // Category Management - // ======================== - getCategories() { - const data = this.getData(); - // Migrate old per-type structure to a single shared array - if (data.categories && !Array.isArray(data.categories)) { - const catObj = data.categories; - const merged = [...new Set([ - ...(catObj['tasks'] || []), - ...(catObj['habits'] || []), - ...(catObj['finance'] || []) - ])]; - data.categories = merged; - this.saveData(data); - } - return Array.isArray(data.categories) ? data.categories : []; - } - addCategory(categoryName) { - const data = this.getData(); - if (!Array.isArray(data.categories)) - data.categories = []; - const trimmedName = categoryName.trim(); - if (trimmedName && !data.categories.includes(trimmedName)) { - data.categories.push(trimmedName); - this.saveData(data); - return true; - } - return false; - } - updateCategory(oldName, newName) { - const data = this.getData(); - if (!Array.isArray(data.categories)) - return false; - const trimmedNew = newName.trim(); - if (!trimmedNew || data.categories.includes(trimmedNew)) - return false; - const idx = data.categories.indexOf(oldName); - if (idx === -1) - return false; - data.categories[idx] = trimmedNew; - // Propagate rename to all item types - data.tasks = data.tasks.map(t => t.category === oldName ? { ...t, category: trimmedNew } : t); - data.habits = data.habits.map(h => h.category === oldName ? { ...h, category: trimmedNew } : h); - data.expenses = data.expenses.map(e => e.category === oldName ? { ...e, category: trimmedNew } : e); - data.revenue = data.revenue.map(r => r.category === oldName ? { ...r, category: trimmedNew } : r); - if (data.charges) { - data.charges = data.charges.map(c => c.category === oldName ? { ...c, category: trimmedNew } : c); - } - this.saveData(data); - return true; - } - deleteCategory(categoryName) { - const data = this.getData(); - if (!Array.isArray(data.categories)) - return false; - const idx = data.categories.indexOf(categoryName); - if (idx === -1) - return false; - data.categories.splice(idx, 1); - // Clear category from all item types - data.tasks = data.tasks.map(t => t.category === categoryName ? { ...t, category: null } : t); - data.habits = data.habits.map(h => h.category === categoryName ? { ...h, category: null } : h); - data.expenses = data.expenses.map(e => e.category === categoryName ? { ...e, category: null } : e); - data.revenue = data.revenue.map(r => r.category === categoryName ? { ...r, category: null } : r); - if (data.charges) { - data.charges = data.charges.map(c => c.category === categoryName ? { ...c, category: null } : c); - } - this.saveData(data); - return true; - } - // ======================== - // Settings Management - // ======================== - getSettings() { - const data = this.getData(); - // Ensure settings exist with defaults - if (!data.settings) { - data.settings = { - tasksPerLevel: 30 - }; - this.saveData(data); - } - return data.settings; - } - updateSettings(settings) { - const data = this.getData(); - if (!data.settings) { - data.settings = { tasksPerLevel: 30 }; - } - Object.assign(data.settings, settings); - // Recalculate level with new settings - this.updateLevel(); - this.saveData(data); - } -} -// Initialize global storage manager -const storage = new StorageManager(); -export { storage, STORAGE_VERSION, STORAGE_KEY }; -export function getDaysUntilDueText(dueDate) { - const todayStr = storage.formatDate(new Date()); - const todayMs = new Date(todayStr + 'T00:00:00').getTime(); - const dueMs = new Date(dueDate + 'T00:00:00').getTime(); - const days = Math.round((dueMs - todayMs) / (1000 * 60 * 60 * 24)); - if (days < 0) - return 'Overdue'; - if (days === 0) - return 'Today'; - if (days === 1) - return '1 day'; - return `${days} days`; -} -//# sourceMappingURL=storage.js.map \ No newline at end of file