diff options
| author | Martin Kagamino Lehoux <martin@lehoux.net> | 2026-07-27 17:18:40 +0200 |
|---|---|---|
| committer | Martin Kagamino Lehoux <martin@lehoux.net> | 2026-07-27 17:18:40 +0200 |
| commit | 6722589b515ccee267212cf783fc0ebbaa20cd90 (patch) | |
| tree | 1220bf320a47dc5fe8a438da8443c788555f8edb /src/routes | |
| parent | 6cde16e66ddcb95f55aa2195910260d9b1faa88b (diff) | |
feat: catalog section + deduplicate items by name
- Show all items split into List (unchecked) and Catalog (checked) sections
- Tapping a catalog item unchecks it (returns to list)
- Add action now upserts: unchecks existing item instead of creating duplicate
- UNIQUE constraint on groceries.name as defense-in-depth
- Tests for dedup behavior (insert, re-add from catalog, no-op on list)
Diffstat (limited to 'src/routes')
| -rw-r--r-- | src/routes/+page.server.ts | 22 | ||||
| -rw-r--r-- | src/routes/+page.svelte | 35 |
2 files changed, 51 insertions, 6 deletions
diff --git a/src/routes/+page.server.ts b/src/routes/+page.server.ts index e6f3b0f..947cc3b 100644 --- a/src/routes/+page.server.ts +++ b/src/routes/+page.server.ts @@ -8,7 +8,6 @@ export const load: PageServerLoad = async () => { const items = await db .select() .from(groceries) - .where(eq(groceries.checked, false)) .orderBy(desc(groceries.createdAt)); return { items }; @@ -23,10 +22,23 @@ export const actions: Actions = { return fail(400, { error: 'Name is required' }); } - await db.insert(groceries).values({ - name, - createdAt: new Date().toISOString(), - }); + const [existing] = await db + .select() + .from(groceries) + .where(eq(groceries.name, name)) + .limit(1); + + if (existing) { + await db + .update(groceries) + .set({ checked: false }) + .where(eq(groceries.id, existing.id)); + } else { + await db.insert(groceries).values({ + name, + createdAt: new Date().toISOString(), + }); + } return { success: true }; }, diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte index 0e9e1ac..325d15a 100644 --- a/src/routes/+page.svelte +++ b/src/routes/+page.svelte @@ -4,6 +4,9 @@ let { data, form } = $props(); let name = $state(''); + + let listItems = $derived(data?.items?.filter((i) => !i.checked) ?? []); + let catalogItems = $derived(data?.items?.filter((i) => i.checked) ?? []); </script> <h1>Groceries</h1> @@ -23,11 +26,31 @@ <p class="error">{form.error}</p> {/if} +<h2>List</h2> + +<form method="POST" action="?/toggle" use:enhance> + <ul class="item-list"> + {#each listItems as item (item.id)} + <li> + <button name="id" value={item.id} type="submit"> + {item.name} + </button> + </li> + {/each} + </ul> +</form> + +{#if listItems.length === 0} + <p class="empty">Nothing to buy</p> +{/if} + <hr /> +<h2>Catalog</h2> + <form method="POST" action="?/toggle" use:enhance> <ul class="item-list"> - {#each data?.items ?? [] as item (item.id)} + {#each catalogItems as item (item.id)} <li> <button name="id" value={item.id} type="submit"> {item.name} @@ -37,6 +60,10 @@ </ul> </form> +{#if catalogItems.length === 0} + <p class="empty">No items yet</p> +{/if} + <style> .add-form { display: flex; @@ -94,4 +121,10 @@ .item-list li button:hover { background: #f5f5f5; } + + .empty { + color: #999; + font-size: 0.875rem; + margin-top: 0.5rem; + } </style> |
