From 1c85216ada768e12f15873b0961129405715b509 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 25 Mar 2026 22:59:17 +0000 Subject: [PATCH 1/2] Initial plan From efaf4746cc647a54985b1467e5e1ae3595c87336 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 25 Mar 2026 23:20:44 +0000 Subject: [PATCH 2/2] Changes before error encountered Co-authored-by: JoeProgrammer88 <7156063+JoeProgrammer88@users.noreply.github.com> Agent-Logs-Url: https://github.com/SpeakingInBits/TaskManagerWeb/sessions/a789990f-9e99-4c42-95a4-eb79206db0d0 --- css/styles.css | 40 +++++++++- index.html | 36 ++++++++- src/app.ts | 173 ++++++++++++++++++++++++++++++++++++++---- src/storage.ts | 70 ++++++++++++++++- tests/storage.test.ts | 83 ++++++++++++++++++++ 5 files changed, 381 insertions(+), 21 deletions(-) diff --git a/css/styles.css b/css/styles.css index cec25fd..b88a499 100644 --- a/css/styles.css +++ b/css/styles.css @@ -1520,12 +1520,50 @@ html, body { margin-bottom: 1.5rem; } -.wish-list { +.wishlist-header-actions { + display: flex; + gap: 0.5rem; +} + +.wish-list-group { + margin-bottom: 1.5rem; + border: 1px solid var(--border-color, #e0e0e0); + border-radius: 10px; + overflow: hidden; +} + +.wish-list-group-header { + display: flex; + justify-content: space-between; + align-items: center; + padding: 0.75rem 1rem; + background: var(--bg-secondary, #f5f5f5); + border-bottom: 1px solid var(--border-color, #e0e0e0); +} + +.wish-list-group-name { + font-weight: 700; + font-size: 1rem; + color: var(--text-primary, #333); +} + +.wish-list-group-actions { + display: flex; + gap: 0.5rem; +} + +.wish-list-group .wish-list { + padding: 0.75rem; display: flex; flex-direction: column; gap: 0.75rem; } +.wish-list-empty { + margin: 0; + padding: 0.5rem 0; +} + .wish-item { display: flex; align-items: center; diff --git a/index.html b/index.html index 0e31231..4342858 100644 --- a/index.html +++ b/index.html @@ -554,11 +554,35 @@

Add Finance Item

Wish List

- +
+ + +
-
-

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

+
+

No items in your wish list. Add a list or item to get started!

+
+
+ + + @@ -574,6 +598,12 @@

Add Wish List Item

+
+ + +
diff --git a/src/app.ts b/src/app.ts index 18ac418..cd25aca 100644 --- a/src/app.ts +++ b/src/app.ts @@ -2,7 +2,7 @@ // Main Application Logic // ======================== -import { StorageManager, storage, STORAGE_VERSION, Task, Habit, FinanceItem, WishItem, Note, getDaysUntilDueText } from './storage.js'; +import { StorageManager, storage, STORAGE_VERSION, Task, Habit, FinanceItem, WishItem, WishList, Note, getDaysUntilDueText } from './storage.js'; const FILTER_SETTINGS_KEY = 'taskManagerFilterSettings'; @@ -24,6 +24,7 @@ class TaskManager { currentEditingFinanceId: string | null = null; currentEditingFinanceType: string | null = null; currentEditingWishItemId: string | null = null; + currentEditingWishListId: string | null = null; currentEditingNoteId: string | null = null; dragSrcWishId: string | null = null; selectedDate: Date = new Date(); @@ -133,9 +134,13 @@ class TaskManager { // Wish List section document.getElementById('addWishItemBtn')!.addEventListener('click', () => this.openWishItemModal()); + document.getElementById('addWishListBtn')!.addEventListener('click', () => this.openWishListModal()); document.getElementById('wishItemForm')!.addEventListener('submit', (e) => this.saveWishItem(e)); document.getElementById('cancelWishItemBtn')!.addEventListener('click', () => this.closeWishItemModal()); document.getElementById('deleteWishItemBtn')!.addEventListener('click', () => this.deleteWishItem()); + document.getElementById('wishListForm')!.addEventListener('submit', (e) => this.saveWishList(e)); + document.getElementById('cancelWishListBtn')!.addEventListener('click', () => this.closeWishListModal()); + document.getElementById('deleteWishListBtn')!.addEventListener('click', () => this.deleteWishListFromModal()); // Notes section document.getElementById('addNoteBtn')!.addEventListener('click', () => this.openNoteModal()); @@ -1728,16 +1733,85 @@ class TaskManager { // Wish List // ======================== renderWishList(): void { - const items = storage.getWishItems(); - const container = document.getElementById('wishList')!; + const lists = storage.getWishLists(); + const allItems = storage.getWishItems(); + const container = document.getElementById('wishListContent')!; - if (items.length === 0) { - container.innerHTML = '

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

'; + if (lists.length === 0 && allItems.length === 0) { + container.innerHTML = '

No items in your wish list. Add a list or item to get started!

'; return; } - container.innerHTML = items.map(item => this.renderWishItem(item)).join(''); + let html = ''; + + // Render each named list group + lists.forEach(list => { + const listItems = allItems.filter(i => i.listId === list.id); + html += this.renderWishListGroup(list.id, list.name, listItems, true); + }); + + // Render uncategorized items (no listId or listId is null) + const uncategorized = allItems.filter(i => !i.listId); + if (uncategorized.length > 0 || lists.length === 0) { + html += this.renderWishListGroup(null, 'Uncategorized', uncategorized, false); + } + + container.innerHTML = html; + + // Set up drag-drop for each named list group + lists.forEach(list => { + const groupEl = container.querySelector(`.wish-list-group[data-list-id="${list.id}"]`); + if (groupEl) { + const itemsContainer = groupEl.querySelector('.wish-list')!; + this.setupWishItemDragDrop(itemsContainer, list.id); + const addBtn = groupEl.querySelector('.wish-list-group-add-btn'); + if (addBtn) { + addBtn.addEventListener('click', () => this.openWishItemModal(null, list.id)); + } + const editBtn = groupEl.querySelector('.wish-list-edit-btn'); + if (editBtn) { + editBtn.addEventListener('click', () => this.openWishListModal(list.id)); + } + } + }); + // Set up drag-drop for uncategorized section + const uncategorizedGroup = container.querySelector('.wish-list-group[data-list-id="uncategorized"]'); + if (uncategorizedGroup) { + const itemsContainer = uncategorizedGroup.querySelector('.wish-list')!; + this.setupWishItemDragDrop(itemsContainer, null); + const addBtn = uncategorizedGroup.querySelector('.wish-list-group-add-btn'); + if (addBtn) { + addBtn.addEventListener('click', () => this.openWishItemModal(null, null)); + } + } + } + + renderWishListGroup(listId: string | null, name: string, items: WishItem[], hasEditBtn: boolean): string { + const dataAttr = listId ? `data-list-id="${listId}"` : 'data-list-id="uncategorized"'; + const editBtnHtml = hasEditBtn + ? `` + : ''; + const itemsHtml = items.length > 0 + ? items.map(item => this.renderWishItem(item)).join('') + : `

No items yet. Click "+ Add" to add one.

`; + return ` +
+
+ ${name} +
+ ${editBtnHtml} + +
+
+
+ ${itemsHtml} +
+
+ `; + } + + setupWishItemDragDrop(container: HTMLElement, listId: string | null): void { container.querySelectorAll('.wish-item').forEach(el => { const handle = el.querySelector('.wish-drag-handle'); if (!handle) return; @@ -1774,11 +1848,13 @@ class TaskManager { 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); + const groupItems = storage.getWishItems().filter(i => + listId ? i.listId === listId : !i.listId + ); + const srcIdx = groupItems.findIndex(i => i.id === this.dragSrcWishId); + const tgtIdx = groupItems.findIndex(i => i.id === targetId); if (srcIdx !== -1 && tgtIdx !== -1) { - const reordered = [...allItems]; + const reordered = [...groupItems]; const [moved] = reordered.splice(srcIdx, 1); reordered.splice(tgtIdx, 0, moved); storage.reorderWishItems(reordered.map(i => i.id)); @@ -1839,11 +1915,13 @@ class TaskManager { touchDragOverItem = null; touchDragActive = false; 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); + const groupItems = storage.getWishItems().filter(i => + listId ? i.listId === listId : !i.listId + ); + const srcIdx = groupItems.findIndex(i => i.id === this.dragSrcWishId); + const tgtIdx = groupItems.findIndex(i => i.id === targetId); if (srcIdx !== -1 && tgtIdx !== -1) { - const reordered = [...allItems]; + const reordered = [...groupItems]; const [moved] = reordered.splice(srcIdx, 1); reordered.splice(tgtIdx, 0, moved); storage.reorderWishItems(reordered.map(i => i.id)); @@ -1892,15 +1970,21 @@ class TaskManager { `; } - openWishItemModal(itemId: string | null = null): void { + openWishItemModal(itemId: string | null = null, preselectedListId: string | null = null): void { this.currentEditingWishItemId = itemId; const modal = document.getElementById('wishItemModal')!; const form = document.getElementById('wishItemForm') as HTMLFormElement; const deleteBtn = document.getElementById('deleteWishItemBtn') as HTMLElement; + const listSelect = document.getElementById('wishItemList') as HTMLSelectElement; form.reset(); deleteBtn.style.display = 'none'; + // Populate list selector + const lists = storage.getWishLists(); + listSelect.innerHTML = '' + + lists.map(l => ``).join(''); + document.getElementById('wishItemModalTitle')!.textContent = itemId ? 'Edit Wish List Item' : 'Add Wish List Item'; if (itemId) { @@ -1910,8 +1994,11 @@ class TaskManager { (document.getElementById('wishItemUrl') as HTMLInputElement).value = item.url || ''; (document.getElementById('wishItemPrice') as HTMLInputElement).value = item.price !== undefined && item.price !== null ? String(item.price) : ''; + listSelect.value = item.listId || ''; deleteBtn.style.display = 'block'; } + } else if (preselectedListId) { + listSelect.value = preselectedListId; } modal.classList.add('active'); @@ -1929,8 +2016,10 @@ class TaskManager { const url = (document.getElementById('wishItemUrl') as HTMLInputElement).value.trim() || undefined; const priceVal = (document.getElementById('wishItemPrice') as HTMLInputElement).value; const price = priceVal !== '' ? parseFloat(priceVal) : undefined; + const listIdVal = (document.getElementById('wishItemList') as HTMLSelectElement).value; + const listId = listIdVal || null; - const item = { title, url, price }; + const item = { title, url, price, listId }; if (this.currentEditingWishItemId) { storage.updateWishItem(this.currentEditingWishItemId, item); @@ -1952,6 +2041,58 @@ class TaskManager { } } + openWishListModal(listId: string | null = null): void { + this.currentEditingWishListId = listId; + const modal = document.getElementById('wishListModal')!; + const form = document.getElementById('wishListForm') as HTMLFormElement; + const deleteBtn = document.getElementById('deleteWishListBtn') as HTMLElement; + + form.reset(); + deleteBtn.style.display = 'none'; + + document.getElementById('wishListModalTitle')!.textContent = listId ? 'Edit List' : 'Add List'; + + if (listId) { + const list = storage.getWishLists().find(l => l.id === listId); + if (list) { + (document.getElementById('wishListName') as HTMLInputElement).value = list.name; + deleteBtn.style.display = 'block'; + } + } + + modal.classList.add('active'); + } + + closeWishListModal(): void { + document.getElementById('wishListModal')!.classList.remove('active'); + this.currentEditingWishListId = null; + } + + saveWishList(e: Event): void { + e.preventDefault(); + const name = (document.getElementById('wishListName') as HTMLInputElement).value.trim(); + if (!name) return; + + if (this.currentEditingWishListId) { + storage.updateWishList(this.currentEditingWishListId, { name }); + } else { + storage.addWishList({ name }); + } + + this.closeWishListModal(); + this.renderWishList(); + } + + deleteWishListFromModal(): void { + if (this.currentEditingWishListId) { + if (confirm('Are you sure you want to delete this list? Items in this list will become uncategorized.')) { + storage.deleteWishList(this.currentEditingWishListId); + this.closeWishListModal(); + this.renderWishList(); + } + } + } + // ======================== // Notes // ======================== diff --git a/src/storage.ts b/src/storage.ts index ef0fa53..6b5681f 100644 --- a/src/storage.ts +++ b/src/storage.ts @@ -4,7 +4,7 @@ const STORAGE_VERSION = '1.0.0'; const STORAGE_KEY = 'taskManagerData'; -const DATA_SCHEMA_VERSION = 2; +const DATA_SCHEMA_VERSION = 3; // ======================== // Type Definitions @@ -74,6 +74,14 @@ export interface WishItem { order: number; createdDate: string; completed?: boolean; + listId?: string | null; +} + +export interface WishList { + id: string; + name: string; + order: number; + createdDate: string; } export interface Note { @@ -114,6 +122,7 @@ export interface AppData { userStats: UserStats; settings: Settings; wishList: WishItem[]; + wishLists: WishList[]; notes: Note[]; } @@ -168,6 +177,7 @@ export class StorageManager { tasksPerLevel: 30 }, wishList: [], + wishLists: [], notes: [] }; @@ -552,6 +562,63 @@ export class StorageManager { this.saveData(data); } + // Named Wish Lists Management + getWishLists(): WishList[] { + const data = this.getData(); + if (!data.wishLists) return []; + return data.wishLists.slice().sort((a, b) => a.order - b.order); + } + + addWishList(list: Partial): WishList { + const data = this.getData(); + if (!data.wishLists) data.wishLists = []; + const newList: WishList = { + id: this.generateId(), + name: list.name || '', + order: data.wishLists.length, + createdDate: new Date().toISOString(), + }; + data.wishLists.push(newList); + this.saveData(data); + return newList; + } + + updateWishList(listId: string, updates: Partial): WishList | undefined { + const data = this.getData(); + if (!data.wishLists) data.wishLists = []; + const list = data.wishLists.find(l => l.id === listId); + if (list) { + Object.assign(list, updates); + this.saveData(data); + } + return list; + } + + deleteWishList(listId: string): void { + const data = this.getData(); + if (!data.wishLists) data.wishLists = []; + data.wishLists = data.wishLists.filter(l => l.id !== listId); + // Re-index order values + data.wishLists.forEach((l, idx) => { l.order = idx; }); + // Move items in the deleted list to uncategorized + if (data.wishList) { + data.wishList = data.wishList.map(item => + item.listId === listId ? { ...item, listId: null } : item + ); + } + this.saveData(data); + } + + reorderWishLists(orderedIds: string[]): void { + const data = this.getData(); + if (!data.wishLists) return; + orderedIds.forEach((id, idx) => { + const list = data.wishLists.find(l => l.id === id); + if (list) list.order = idx; + }); + this.saveData(data); + } + // Note Management addNote(note: Partial): Note { const data = this.getData(); @@ -719,6 +786,7 @@ export class StorageManager { }, settings: data.settings || { tasksPerLevel: 30 }, wishList: Array.isArray(data.wishList) ? data.wishList : [], + wishLists: Array.isArray(data.wishLists) ? data.wishLists : [], notes: Array.isArray(data.notes) ? data.notes : [], }; } diff --git a/tests/storage.test.ts b/tests/storage.test.ts index 3681e0c..dbf7119 100644 --- a/tests/storage.test.ts +++ b/tests/storage.test.ts @@ -686,6 +686,89 @@ describe('StorageManager', () => { }); }); + // ======================== + // Named Wish Lists Management + // ======================== + describe('named wish lists management', () => { + it('should add a wish list', () => { + const list = storage.addWishList({ name: 'Electronics' }); + expect(list.id).toBeDefined(); + expect(list.name).toBe('Electronics'); + expect(list.order).toBe(0); + expect(list.createdDate).toBeDefined(); + }); + + it('should get all wish lists sorted by order', () => { + storage.addWishList({ name: 'List A' }); + storage.addWishList({ name: 'List B' }); + storage.addWishList({ name: 'List C' }); + const lists = storage.getWishLists(); + expect(lists.length).toBe(3); + expect(lists[0].order).toBeLessThanOrEqual(lists[1].order); + expect(lists[1].order).toBeLessThanOrEqual(lists[2].order); + }); + + it('should update a wish list', () => { + const list = storage.addWishList({ name: 'Old Name' }); + const updated = storage.updateWishList(list.id, { name: 'New Name' }); + expect(updated?.name).toBe('New Name'); + }); + + it('should return undefined when updating non-existent wish list', () => { + const result = storage.updateWishList('nonexistent', { name: 'Test' }); + expect(result).toBeUndefined(); + }); + + it('should delete a wish list and re-index order', () => { + storage.addWishList({ name: 'List A' }); + const listB = storage.addWishList({ name: 'List B' }); + storage.addWishList({ name: 'List C' }); + storage.deleteWishList(listB.id); + const lists = storage.getWishLists(); + expect(lists.length).toBe(2); + expect(lists[0].order).toBe(0); + expect(lists[1].order).toBe(1); + }); + + it('should move items to uncategorized when their list is deleted', () => { + const list = storage.addWishList({ name: 'To Delete' }); + const item = storage.addWishItem({ title: 'Item in list', listId: list.id }); + storage.deleteWishList(list.id); + const items = storage.getWishItems(); + const found = items.find(i => i.id === item.id); + expect(found).toBeDefined(); + expect(found?.listId).toBeNull(); + }); + + it('should reorder wish lists', () => { + const a = storage.addWishList({ name: 'List A' }); + const b = storage.addWishList({ name: 'List B' }); + const c = storage.addWishList({ name: 'List C' }); + storage.reorderWishLists([c.id, a.id, b.id]); + const lists = storage.getWishLists(); + expect(lists[0].name).toBe('List C'); + expect(lists[1].name).toBe('List A'); + expect(lists[2].name).toBe('List B'); + }); + + it('should associate a wish item with a list', () => { + const list = storage.addWishList({ name: 'Tech' }); + const item = storage.addWishItem({ title: 'Laptop', listId: list.id }); + expect(item.listId).toBe(list.id); + const items = storage.getWishItems(); + const found = items.find(i => i.id === item.id); + expect(found?.listId).toBe(list.id); + }); + + it('should update listId on a wish item', () => { + const list = storage.addWishList({ name: 'Tech' }); + const item = storage.addWishItem({ title: 'Laptop' }); + expect(item.listId).toBeUndefined(); + const updated = storage.updateWishItem(item.id, { listId: list.id }); + expect(updated?.listId).toBe(list.id); + }); + }); + // ======================== // Clear All Data // ========================