From aaeffdbf4ca1f5014f2ffdde08e3b911cdef5f3e Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 27 Jun 2026 18:04:22 +0000 Subject: [PATCH 1/3] Add ability to edit a logged food entry Tapping a food entry in today's list loads it back into the add-form for editing (name/macros/quantity), with Save Changes/Cancel actions. Adds FoodStore.update(), backed by IndexedDB get+put preserving id and createdAt. Co-Authored-By: Claude Sonnet 4.6 Claude-Session: https://claude.ai/code/session_01K3Q361t53QdxgaV8nHSayT --- pwa/src/lib/foods/store.test.ts | 41 ++++++++++++++++++++++++++++++ pwa/src/lib/foods/store.ts | 19 ++++++++++++++ pwa/src/lib/foods/types.ts | 1 + pwa/src/routes/Food.module.css | 19 ++++++++++++++ pwa/src/routes/Food.tsx | 44 ++++++++++++++++++++++++++++----- 5 files changed, 118 insertions(+), 6 deletions(-) diff --git a/pwa/src/lib/foods/store.test.ts b/pwa/src/lib/foods/store.test.ts index 8c1ca98..6e6ac4a 100644 --- a/pwa/src/lib/foods/store.test.ts +++ b/pwa/src/lib/foods/store.test.ts @@ -65,6 +65,47 @@ describe('createIndexedDbFoodStore', () => { expect(entries.map((e) => e.id)).toEqual([b.id, a.id]); }); + it('update() overwrites fields while preserving id and createdAt', async () => { + const entry = await store.create({ + name: 'A', + quantity: 1, + protein: 1, + calories: 1, + carbs: 1, + date: '2026-06-27', + }); + + const updated = await store.update(entry.id, { + name: 'A (revised)', + quantity: 2, + protein: 5, + calories: 50, + carbs: 10, + date: '2026-06-27', + }); + + expect(updated.id).toBe(entry.id); + expect(updated.createdAt).toBe(entry.createdAt); + expect(updated.name).toBe('A (revised)'); + expect(updated.quantity).toBe(2); + + const [stored] = await store.list(); + expect(stored).toEqual(updated); + }); + + it('update() throws for an unknown id', async () => { + await expect( + store.update('missing-id', { + name: 'A', + quantity: 1, + protein: 1, + calories: 1, + carbs: 1, + date: '2026-06-27', + }), + ).rejects.toThrow(); + }); + it('remove() deletes a food entry', async () => { const entry = await store.create({ name: 'A', diff --git a/pwa/src/lib/foods/store.ts b/pwa/src/lib/foods/store.ts index 5ee0695..8b18e17 100644 --- a/pwa/src/lib/foods/store.ts +++ b/pwa/src/lib/foods/store.ts @@ -50,6 +50,24 @@ export function createIndexedDbFoodStore(): FoodStore { return record; } + async function update(id: string, entry: Omit): Promise { + const db = await getDb(); + const tx = db.transaction(FOODS_STORE, 'readwrite'); + const store = tx.objectStore(FOODS_STORE); + + const existing = await requestToPromise(store.get(id)); + if (!existing) { + tx.abort(); + throw new Error(`Food entry not found: ${id}`); + } + + const updated: FoodEntry = { ...entry, id: existing.id, createdAt: existing.createdAt }; + store.put(updated); + await transactionDone(tx); + + return updated; + } + async function remove(id: string): Promise { const db = await getDb(); const tx = db.transaction(FOODS_STORE, 'readwrite'); @@ -74,6 +92,7 @@ export function createIndexedDbFoodStore(): FoodStore { list, listByDate, create, + update, remove, listRecentDistinct, }; diff --git a/pwa/src/lib/foods/types.ts b/pwa/src/lib/foods/types.ts index 0f2128c..160ecc2 100644 --- a/pwa/src/lib/foods/types.ts +++ b/pwa/src/lib/foods/types.ts @@ -13,6 +13,7 @@ export interface FoodStore { list(): Promise; listByDate(date: string): Promise; create(entry: Omit): Promise; + update(id: string, entry: Omit): Promise; remove(id: string): Promise; /** Most recently logged entry for each distinct food name, newest first. */ listRecentDistinct(): Promise; diff --git a/pwa/src/routes/Food.module.css b/pwa/src/routes/Food.module.css index e99c739..6789bc2 100644 --- a/pwa/src/routes/Food.module.css +++ b/pwa/src/routes/Food.module.css @@ -107,6 +107,16 @@ border-radius: 8px; } +.cancelButton { + padding: 0.6rem 1rem; + font-size: 1rem; + font-weight: 600; + color: #007aff; + background: #fff; + border: 1px solid #d1d1d6; + border-radius: 8px; +} + .sectionTitle { font-size: 0.85rem; font-weight: 600; @@ -149,11 +159,20 @@ border-radius: 8px; } +.rowEditing { + background: #e5f1ff; +} + .rowMain { flex: 1; display: flex; flex-direction: column; + align-items: flex-start; gap: 0.2rem; + padding: 0; + background: none; + border: none; + text-align: left; } .rowName { diff --git a/pwa/src/routes/Food.tsx b/pwa/src/routes/Food.tsx index 5879acc..86ffdd7 100644 --- a/pwa/src/routes/Food.tsx +++ b/pwa/src/routes/Food.tsx @@ -20,6 +20,7 @@ function Food() { const [recentFoods, setRecentFoods] = createSignal([]); const [targets, setTargets] = createSignal(getFoodTargets()); const [form, setForm] = createSignal(EMPTY_FORM); + const [editingId, setEditingId] = createSignal(); const refresh = async () => { const [today, recent] = await Promise.all([ @@ -54,6 +55,7 @@ function Food() { }; const handleSelectRecent = (entry: FoodEntry) => { + setEditingId(undefined); setForm({ name: entry.name, protein: String(entry.protein), @@ -63,26 +65,51 @@ function Food() { }); }; + const handleEdit = (entry: FoodEntry) => { + setEditingId(entry.id); + setForm({ + name: entry.name, + protein: String(entry.protein), + calories: String(entry.calories), + carbs: String(entry.carbs), + quantity: String(entry.quantity), + }); + }; + + const handleCancelEdit = () => { + setEditingId(undefined); + setForm(EMPTY_FORM); + }; + const handleSubmit = async (event: Event) => { event.preventDefault(); const current = form(); const name = current.name.trim(); if (!name) return; - await foodStore.create({ + const record = { name, quantity: Number(current.quantity) || 1, protein: Number(current.protein) || 0, calories: Number(current.calories) || 0, carbs: Number(current.carbs) || 0, date: todayDateKey(), - }); + }; + + const id = editingId(); + if (id) { + await foodStore.update(id, record); + } else { + await foodStore.create(record); + } + setEditingId(undefined); setForm(EMPTY_FORM); await refresh(); }; const handleRemove = async (id: string) => { + if (editingId() === id) handleCancelEdit(); await foodStore.remove(id); await refresh(); }; @@ -156,8 +183,13 @@ function Food() { onInput={(event) => updateField('quantity', event.currentTarget.value)} /> + + + @@ -187,8 +219,8 @@ function Food() {
{(entry) => ( -
-
+
+
+