aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--README.md10
-rw-r--r--src/routes/+page.server.ts10
-rw-r--r--src/routes/+page.svelte28
-rw-r--r--src/routes/catalog/+page.svelte3
4 files changed, 38 insertions, 13 deletions
diff --git a/README.md b/README.md
index 04d4525..1eb4985 100644
--- a/README.md
+++ b/README.md
@@ -30,8 +30,9 @@ on the list for the next shopping trip — there is only one persistent list.
**Three modes of use:**
1. **Quick add** — during the week, you run out of milk or need an ingredient.
- Open the app, type the name, tap to add. As frictionless as possible so you
- build the habit.
+ Open the app, type the name, tap to add. An autocomplete dropdown surfaces
+ existing catalog items as you type, so "citron" does not become "citrons".
+ Tapping a suggestion re-adds the existing item; typing a new name creates it.
2. **Planning** — before shopping, sit down with your partner and browse the
catalog. Tap unchecked items to add them back to the list. The page
@@ -60,13 +61,12 @@ worker precaches assets; works offline after first visit.
### Bugs / Polish
-- [ ] Live updates from server
-- [ ] Search/filter in list and catalog
- [ ] Fixed category order in both lists (drag to reorder aisles)
### Features
-- [ ] From catalog, multi-select and add in batch to the list
+- [ ] Live updates from server
+- [ ] From catalog, multi-select and bulk-update category
- [ ] Update item name/category on blur, no explicit save button
- [ ] Auto-categorize new items (LLM-assisted)
- [ ] Chores module (intro already promises "groceries, chores")
diff --git a/src/routes/+page.server.ts b/src/routes/+page.server.ts
index b0ddff0..1e6f60b 100644
--- a/src/routes/+page.server.ts
+++ b/src/routes/+page.server.ts
@@ -1,6 +1,7 @@
import { fail, redirect, error } from '@sveltejs/kit';
import {
loadUnchecked,
+ loadAll,
toggle,
addOrUncheck,
} from '$lib/server/services/groceries';
@@ -16,10 +17,17 @@ import type { Actions, PageServerLoad } from './$types';
export interface HomeOutput {
items: EnrichedGrocery[];
categories: Category[];
+ catalog: EnrichedGrocery[];
}
export const load: PageServerLoad = async () => {
- return loadUnchecked();
+ const unchecked = await loadUnchecked();
+ const all = await loadAll();
+ return {
+ items: unchecked.items,
+ categories: unchecked.categories,
+ catalog: all.items,
+ };
};
export const actions: Actions = {
diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte
index e4f78c5..a151a9b 100644
--- a/src/routes/+page.svelte
+++ b/src/routes/+page.svelte
@@ -9,7 +9,6 @@
// eslint-disable-next-line svelte/prefer-writable-derived
let localItems = $state(data?.items ?? []);
let addError = $state('');
-
// Sync local list with server data when it changes externally (toggle, navigation)
$effect(() => {
localItems = data?.items ?? [];
@@ -81,13 +80,29 @@
bind:value={name}
placeholder="Ajouter un article..."
autocomplete="off"
+ list="item-suggestions"
onkeydown={(e) => {
if (e.key === 'Enter') {
e.preventDefault();
handleAdd(false);
}
}}
+ oninput={(e) => {
+ const val = e.currentTarget.value.trim().toLowerCase();
+ const match = data.catalog.find(
+ (item) => item.name.toLowerCase() === val,
+ );
+ if (match) {
+ name = match.name;
+ handleAdd(false);
+ }
+ }}
/>
+ <datalist id="item-suggestions">
+ {#each data.catalog as item (item.id)}
+ <option value={item.name}></option>
+ {/each}
+ </datalist>
<button type="button" aria-label="Ajouter" onclick={() => handleAdd(false)}
>+</button
>
@@ -123,7 +138,8 @@
<span class="best-ref"
>{item.bestRef.reference}
{#if item.bestRef.price && item.bestRef.unit}
- &nbsp;{item.bestRef.price.toFixed(2)} {item.bestRef.unit}
+ &nbsp;{item.bestRef.price.toFixed(2)}
+ {item.bestRef.unit}
{/if}
{#if item.bestRef.qualityScore != null}
&nbsp;({item.bestRef.qualityScore}/100)
@@ -175,25 +191,25 @@
transition: all 0.2s ease;
}
- .add-bar button:nth-child(2) {
+ .add-bar > button:nth-of-type(1) {
background: var(--accent);
color: var(--white);
box-shadow: 0 1px 2px var(--shadow);
}
- .add-bar button:nth-child(2):hover {
+ .add-bar > button:nth-of-type(1):hover {
background: var(--accent-hover);
transform: translateY(-1px);
box-shadow: 0 4px 8px var(--shadow-accent-lg);
}
- .add-bar button:nth-child(3) {
+ .add-bar > button:nth-of-type(2) {
background: var(--surface);
color: var(--text-secondary);
border: 1.5px solid var(--border);
}
- .add-bar button:nth-child(3):hover {
+ .add-bar > button:nth-of-type(2):hover {
border-color: var(--accent);
color: var(--text);
}
diff --git a/src/routes/catalog/+page.svelte b/src/routes/catalog/+page.svelte
index 525c2ed..7d3ebbe 100644
--- a/src/routes/catalog/+page.svelte
+++ b/src/routes/catalog/+page.svelte
@@ -38,7 +38,8 @@
<span class="best-ref"
>{item.bestRef.reference}
{#if item.bestRef.price && item.bestRef.unit}
- &nbsp;{item.bestRef.price.toFixed(2)} {item.bestRef.unit}
+ &nbsp;{item.bestRef.price.toFixed(2)}
+ {item.bestRef.unit}
{/if}
{#if item.bestRef.qualityScore != null}
&nbsp;({item.bestRef.qualityScore}/100)