diff --git a/.gitignore b/.gitignore index af83842..16eca4e 100644 --- a/.gitignore +++ b/.gitignore @@ -10,3 +10,4 @@ playwright-report/ # Misc .DS_Store +js/ diff --git a/e2e/app.spec.ts b/e2e/app.spec.ts index eae98c6..9cfde54 100644 --- a/e2e/app.spec.ts +++ b/e2e/app.spec.ts @@ -451,4 +451,68 @@ test.describe('Task Manager App', () => { expect(value).toBe('50'); }); }); + + // ======================== + // Filter Settings Persistence + // ======================== + test.describe('filter settings persistence', () => { + test.beforeEach(async ({ page }) => { + await page.click('[data-tab="tasks"]'); + }); + + test('should show the Reset Filters button', async ({ page }) => { + await expect(page.locator('#resetFiltersBtn')).toBeVisible(); + }); + + test('should persist status filter across page reloads', async ({ page }) => { + await page.selectOption('#statusFilter', 'completed'); + await page.reload(); + await page.waitForSelector('.header'); + await page.click('[data-tab="tasks"]'); + const value = await page.locator('#statusFilter').inputValue(); + expect(value).toBe('completed'); + }); + + test('should persist groupBy filter across page reloads', async ({ page }) => { + await page.selectOption('#groupBySelect', 'priority'); + await page.reload(); + await page.waitForSelector('.header'); + await page.click('[data-tab="tasks"]'); + const value = await page.locator('#groupBySelect').inputValue(); + expect(value).toBe('priority'); + }); + + test('should persist hideCompleted state across page reloads', async ({ page }) => { + await page.click('#hideCompletedBtn'); + await expect(page.locator('#hideCompletedBtn')).toContainText('Show Completed'); + await page.reload(); + await page.waitForSelector('.header'); + await page.click('[data-tab="tasks"]'); + await expect(page.locator('#hideCompletedBtn')).toContainText('Show Completed'); + }); + + test('should reset all filters when Reset Filters is clicked', async ({ page }) => { + await page.selectOption('#statusFilter', 'pending'); + await page.selectOption('#groupBySelect', 'category'); + await page.click('#hideCompletedBtn'); + + await page.click('#resetFiltersBtn'); + + const statusValue = await page.locator('#statusFilter').inputValue(); + const groupByValue = await page.locator('#groupBySelect').inputValue(); + expect(statusValue).toBe(''); + expect(groupByValue).toBe(''); + await expect(page.locator('#hideCompletedBtn')).toContainText('Hide Completed'); + }); + + test('should not restore filters after reset and reload', async ({ page }) => { + await page.selectOption('#statusFilter', 'pending'); + await page.click('#resetFiltersBtn'); + await page.reload(); + await page.waitForSelector('.header'); + await page.click('[data-tab="tasks"]'); + const value = await page.locator('#statusFilter').inputValue(); + expect(value).toBe(''); + }); + }); }); diff --git a/index.html b/index.html index 377beaf..327a3f4 100644 --- a/index.html +++ b/index.html @@ -149,6 +149,7 @@

Tasks

+ diff --git a/src/app.ts b/src/app.ts index 83b5dbf..a7b7187 100644 --- a/src/app.ts +++ b/src/app.ts @@ -4,6 +4,8 @@ import { StorageManager, storage, STORAGE_VERSION, Task, Habit, FinanceItem, WishItem, Note, getDaysUntilDueText } from './storage.js'; +const FILTER_SETTINGS_KEY = 'taskManagerFilterSettings'; + interface Activity { type: string; message: string; @@ -59,6 +61,7 @@ class TaskManager { this.setupEventListeners(); this.initializeFinanceDateFilter(); this.updateDateNavigator(); + this.loadFilterSettings(); this.render(); this.processRecurringTasks(); } @@ -82,11 +85,12 @@ class TaskManager { document.getElementById('taskCategory')!.addEventListener('change', (e) => this.handleCategoryChange('task', (e.target as HTMLSelectElement).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('categoryFilter')!.addEventListener('change', () => { this.filterTasks(); this.saveFilterSettings(); }); + document.getElementById('statusFilter')!.addEventListener('change', () => { this.filterTasks(); this.saveFilterSettings(); }); + document.getElementById('groupBySelect')!.addEventListener('change', () => { this.filterTasks(); this.saveFilterSettings(); }); document.getElementById('searchTasks')!.addEventListener('input', () => this.filterTasks()); document.getElementById('hideCompletedBtn')!.addEventListener('click', () => this.toggleHideCompleted()); + document.getElementById('resetFiltersBtn')!.addEventListener('click', () => this.resetFilters()); // Projects section document.getElementById('addProjectBtn')!.addEventListener('click', () => this.openProjectModal()); @@ -209,6 +213,7 @@ class TaskManager { document.getElementById('todayTasksItem')!.addEventListener('click', () => this.switchTab('tasks')); document.getElementById('overdueTasksItem')!.addEventListener('click', () => { (document.getElementById('statusFilter') as HTMLSelectElement).value = 'overdue'; + this.saveFilterSettings(); this.switchTab('tasks'); }); @@ -434,9 +439,53 @@ class TaskManager { // Tasks Management toggleHideCompleted(): void { this.hideCompleted = !this.hideCompleted; + this.updateHideCompletedBtn(); + this.filterTasks(); + this.saveFilterSettings(); + } + + updateHideCompletedBtn(): void { const btn = document.getElementById('hideCompletedBtn')!; btn.textContent = this.hideCompleted ? '👁 Show Completed' : '👁 Hide Completed'; btn.classList.toggle('active', this.hideCompleted); + } + + saveFilterSettings(): void { + const categoryFilter = (document.getElementById('categoryFilter') as HTMLSelectElement).value; + const statusFilter = (document.getElementById('statusFilter') as HTMLSelectElement).value; + const groupBy = (document.getElementById('groupBySelect') as HTMLSelectElement).value; + const settings = { categoryFilter, statusFilter, groupBy, hideCompleted: this.hideCompleted }; + localStorage.setItem(FILTER_SETTINGS_KEY, JSON.stringify(settings)); + } + + loadFilterSettings(): void { + const raw = localStorage.getItem(FILTER_SETTINGS_KEY); + if (!raw) return; + try { + const settings = JSON.parse(raw); + const categoryFilter = document.getElementById('categoryFilter') as HTMLSelectElement; + const statusFilter = document.getElementById('statusFilter') as HTMLSelectElement; + const groupBySelect = document.getElementById('groupBySelect') as HTMLSelectElement; + if (settings.categoryFilter !== undefined) categoryFilter.value = settings.categoryFilter; + if (settings.statusFilter !== undefined) statusFilter.value = settings.statusFilter; + if (settings.groupBy !== undefined) groupBySelect.value = settings.groupBy; + if (settings.hideCompleted) { + this.hideCompleted = true; + this.updateHideCompletedBtn(); + } + } catch { + localStorage.removeItem(FILTER_SETTINGS_KEY); + } + } + + resetFilters(): void { + (document.getElementById('categoryFilter') as HTMLSelectElement).value = ''; + (document.getElementById('statusFilter') as HTMLSelectElement).value = ''; + (document.getElementById('groupBySelect') as HTMLSelectElement).value = ''; + (document.getElementById('searchTasks') as HTMLInputElement).value = ''; + this.hideCompleted = false; + this.updateHideCompletedBtn(); + localStorage.removeItem(FILTER_SETTINGS_KEY); this.filterTasks(); }