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();
}