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
40 changes: 39 additions & 1 deletion css/styles.css
Original file line number Diff line number Diff line change
Expand Up @@ -1603,12 +1603,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;
Expand Down
36 changes: 33 additions & 3 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -561,11 +561,35 @@ <h3 id="financeModalTitle">Add Finance Item</h3>
<div class="wishlist-container">
<div class="wishlist-header">
<h2>Wish List</h2>
<button class="btn btn-primary" id="addWishItemBtn">+ Add Item</button>
<div class="wishlist-header-actions">
<button class="btn btn-secondary" id="addWishListBtn">+ Add List</button>
<button class="btn btn-primary" id="addWishItemBtn">+ Add Item</button>
</div>
</div>

<div class="wish-list" id="wishList">
<p class="empty-state">No items in your wish list. Add one to get started!</p>
<div id="wishListContent">
<p class="empty-state">No items in your wish list. Add a list or item to get started!</p>
</div>
</div>

<!-- Wish List Modal (for creating/editing named lists) -->
<div class="modal" id="wishListModal">
<div class="modal-content">
<div class="modal-header">
<h3 id="wishListModalTitle">Add List</h3>
<button class="close-btn">&times;</button>
</div>
<form id="wishListForm">
<div class="form-group">
<label for="wishListName">List Name *</label>
<input type="text" id="wishListName" required>
</div>
<div class="form-actions">
<button type="submit" class="btn btn-primary">Save</button>
<button type="button" class="btn btn-danger" id="deleteWishListBtn" style="display: none;">Delete</button>
<button type="button" class="btn btn-secondary" id="cancelWishListBtn">Cancel</button>
</div>
</form>
</div>
</div>

Expand All @@ -581,6 +605,12 @@ <h3 id="wishItemModalTitle">Add Wish List Item</h3>
<label for="wishItemTitle">Title *</label>
<input type="text" id="wishItemTitle" required>
</div>
<div class="form-group">
<label for="wishItemList">List</label>
<select id="wishItemList">
<option value="">Uncategorized</option>
</select>
</div>
<div class="form-group">
<label for="wishItemUrl">URL (optional)</label>
<input type="url" id="wishItemUrl" placeholder="https://...">
Expand Down
173 changes: 157 additions & 16 deletions src/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
// Main Application Logic
// ========================

import { StorageManager, storage, STORAGE_VERSION, Task, Habit, FinanceItem, WishItem, Note, ShoppingItem, getDaysUntilDueText } from './storage.js';
import { StorageManager, storage, STORAGE_VERSION, Task, Habit, FinanceItem, WishItem, WishList, Note, ShoppingItem, getDaysUntilDueText } from './storage.js';

const FILTER_SETTINGS_KEY = 'taskManagerFilterSettings';

Expand All @@ -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;
currentEditingShoppingItemId: string | null = null;
dragSrcWishId: string | null = null;
Expand Down Expand Up @@ -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());

// Shopping List section
document.getElementById('addShoppingItemBtn')!.addEventListener('click', () => this.openShoppingItemModal());
Expand Down Expand Up @@ -1870,16 +1875,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 = '<p class="empty-state">No items in your wish list. Add one to get started!</p>';
if (lists.length === 0 && allItems.length === 0) {
container.innerHTML = '<p class="empty-state">No items in your wish list. Add a list or item to get started!</p>';
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<HTMLElement>(`.wish-list-group[data-list-id="${list.id}"]`);
if (groupEl) {
const itemsContainer = groupEl.querySelector<HTMLElement>('.wish-list')!;
this.setupWishItemDragDrop(itemsContainer, list.id);
const addBtn = groupEl.querySelector<HTMLElement>('.wish-list-group-add-btn');
if (addBtn) {
addBtn.addEventListener('click', () => this.openWishItemModal(null, list.id));
}
const editBtn = groupEl.querySelector<HTMLElement>('.wish-list-edit-btn');
if (editBtn) {
editBtn.addEventListener('click', () => this.openWishListModal(list.id));
}
}
});

// Set up drag-drop for uncategorized section
const uncategorizedGroup = container.querySelector<HTMLElement>('.wish-list-group[data-list-id="uncategorized"]');
if (uncategorizedGroup) {
const itemsContainer = uncategorizedGroup.querySelector<HTMLElement>('.wish-list')!;
this.setupWishItemDragDrop(itemsContainer, null);
const addBtn = uncategorizedGroup.querySelector<HTMLElement>('.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
? `<button class="btn btn-secondary wish-list-edit-btn" title="Edit list">Edit</button>`
: '';
const itemsHtml = items.length > 0
? items.map(item => this.renderWishItem(item)).join('')
: `<p class="empty-state wish-list-empty">No items yet. Click "+ Add" to add one.</p>`;
return `
<div class="wish-list-group" ${dataAttr}>
<div class="wish-list-group-header">
<span class="wish-list-group-name">${name}</span>
<div class="wish-list-group-actions">
${editBtnHtml}
<button class="btn btn-primary wish-list-group-add-btn">+ Add</button>
</div>
</div>
<div class="wish-list">
${itemsHtml}
</div>
</div>
`;
}

setupWishItemDragDrop(container: HTMLElement, listId: string | null): void {
container.querySelectorAll<HTMLElement>('.wish-item').forEach(el => {
const handle = el.querySelector<HTMLElement>('.wish-drag-handle');
if (!handle) return;
Expand Down Expand Up @@ -1916,11 +1990,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));
Expand Down Expand Up @@ -1981,11 +2057,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));
Expand Down Expand Up @@ -2034,15 +2112,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 = '<option value="">Uncategorized</option>' +
lists.map(l => `<option value="${l.id}">${l.name}</option>`).join('');

document.getElementById('wishItemModalTitle')!.textContent = itemId ? 'Edit Wish List Item' : 'Add Wish List Item';

if (itemId) {
Expand All @@ -2052,8 +2136,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');
Expand All @@ -2071,8 +2158,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);
Expand All @@ -2094,6 +2183,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();
}
}
}

// ========================
// Shopping List
// ========================
Expand Down
Loading
Loading