Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,4 @@ playwright-report/

# Misc
.DS_Store
js/
64 changes: 64 additions & 0 deletions e2e/app.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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('');
});
});
});
1 change: 1 addition & 0 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,7 @@ <h2>Tasks</h2>
</select>
<input type="text" id="searchTasks" class="filter-input" placeholder="Search tasks...">
<button class="btn btn-secondary" id="hideCompletedBtn">👁 Hide Completed</button>
<button class="btn btn-secondary" id="resetFiltersBtn">↺ Reset Filters</button>
</div>

<!-- Task List -->
Expand Down
55 changes: 52 additions & 3 deletions src/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -59,6 +61,7 @@ class TaskManager {
this.setupEventListeners();
this.initializeFinanceDateFilter();
this.updateDateNavigator();
this.loadFilterSettings();
this.render();
this.processRecurringTasks();
}
Expand All @@ -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());
Expand Down Expand Up @@ -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');
});

Expand Down Expand Up @@ -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();
}

Expand Down
Loading