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
50 changes: 50 additions & 0 deletions e2e/app.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -374,6 +374,56 @@ test.describe('Task Manager App', () => {
});
});

// ========================
// Task Group By
// ========================
test.describe('task group by', () => {
test.beforeEach(async ({ page }) => {
// Add tasks with different priorities and categories via localStorage
await page.evaluate(() => {
const data = JSON.parse(localStorage.getItem('taskManagerData') || '{}');
data.tasks = [
{ id: 'task-high', title: 'High Task', priority: 'high', points: 10, repeatType: 'none', completed: false, createdDate: new Date().toISOString(), category: 'Work' },
{ id: 'task-medium', title: 'Medium Task', priority: 'medium', points: 10, repeatType: 'none', completed: false, createdDate: new Date().toISOString(), category: 'Personal' },
{ id: 'task-low', title: 'Low Task', priority: 'low', points: 10, repeatType: 'none', completed: false, createdDate: new Date().toISOString() },
];
localStorage.setItem('taskManagerData', JSON.stringify(data));
});
await page.reload();
await page.waitForSelector('.header');
await page.click('[data-tab="tasks"]');
});

test('should show group by dropdown', async ({ page }) => {
await expect(page.locator('#groupBySelect')).toBeVisible();
});

test('should group tasks by priority', async ({ page }) => {
await page.selectOption('#groupBySelect', 'priority');
const headers = page.locator('.task-section-header');
await expect(headers).toHaveCount(3);
await expect(headers.nth(0)).toHaveText('High Priority');
await expect(headers.nth(1)).toHaveText('Medium Priority');
await expect(headers.nth(2)).toHaveText('Low Priority');
});

test('should group tasks by category', async ({ page }) => {
await page.selectOption('#groupBySelect', 'category');
const headers = page.locator('.task-section-header');
await expect(headers).toHaveCount(3);
await expect(headers.nth(0)).toHaveText('Personal');
await expect(headers.nth(1)).toHaveText('Work');
await expect(headers.nth(2)).toHaveText('Ungrouped');
});

test('should revert to default grouping when no grouping selected', async ({ page }) => {
await page.selectOption('#groupBySelect', 'priority');
await page.selectOption('#groupBySelect', '');
// Default view shows no task-section-header for tasks with no due date
await expect(page.locator('.task-item')).toHaveCount(3);
});
});

// ========================
// Settings
// ========================
Expand Down
5 changes: 5 additions & 0 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,11 @@ <h2>Tasks</h2>
<option value="completed">Completed</option>
<option value="overdue">Overdue</option>
</select>
<select id="groupBySelect" class="filter-select">
<option value="">No Grouping</option>
<option value="priority">Group by Priority</option>
<option value="category">Group by Category</option>
</select>
<input type="text" id="searchTasks" class="filter-input" placeholder="Search tasks...">
</div>

Expand Down
2 changes: 1 addition & 1 deletion js/app.d.ts.map

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

42 changes: 41 additions & 1 deletion js/app.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

39 changes: 38 additions & 1 deletion src/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ class TaskManager {
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());

// Projects section
Expand Down Expand Up @@ -436,6 +437,7 @@ class TaskManager {
const categoryFilter = (document.getElementById('categoryFilter') as HTMLSelectElement).value;
const statusFilter = (document.getElementById('statusFilter') as HTMLSelectElement).value;
const searchTerm = (document.getElementById('searchTasks') as HTMLInputElement).value.toLowerCase();
const groupBy = (document.getElementById('groupBySelect') as HTMLSelectElement).value;
const today = this.getSelectedDateStr();
const filtersActive = statusFilter || searchTerm;

Expand Down Expand Up @@ -464,7 +466,42 @@ class TaskManager {

let html = '';

if (filtersActive) {
if (groupBy === 'priority') {
const priorityLabels: Record<string, string> = { high: 'High Priority', medium: 'Medium Priority', low: 'Low Priority' };
const grouped: Record<string, typeof filtered> = { 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'] as const).forEach(p => {
if (grouped[p].length > 0) {
html += `<h3 class="task-section-header">${priorityLabels[p]}</h3>`;
html += grouped[p].map(task => this.renderTaskItem(task)).join('');
}
});
if (grouped['ungrouped'].length > 0) {
html += `<h3 class="task-section-header">Ungrouped</h3>`;
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 as string))].sort();
categories.forEach(cat => {
const group = withCategory.filter(task => task.category === cat);
if (group.length > 0) {
html += `<h3 class="task-section-header">${cat}</h3>`;
html += group.map(task => this.renderTaskItem(task)).join('');
}
});
if (withoutCategory.length > 0) {
html += `<h3 class="task-section-header">Ungrouped</h3>`;
html += withoutCategory.map(task => this.renderTaskItem(task)).join('');
}
} else if (filtersActive) {
html = filtered.map(task => this.renderTaskItem(task)).join('');
} else {
const priorityOrder: Record<string, number> = { high: 0, medium: 1, low: 2 };
Expand Down