aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorMartin Kagamino Lehoux <martin@lehoux.net>2026-07-28 22:22:12 +0200
committerMartin Kagamino Lehoux <martin@lehoux.net>2026-07-28 22:25:37 +0200
commit7e5535f9834fca2594c5bade425ecee00c32bdb8 (patch)
treeef0d22ef1e3926250e96e28c6add377593e43add /src
parent7d55b5d859e8339205494f12f4b34b6e44a44f50 (diff)
feat: autocomplete search with datalist to prevent duplicate items
Diffstat (limited to 'src')
-rw-r--r--src/routes/+page.server.ts10
-rw-r--r--src/routes/+page.svelte28
-rw-r--r--src/routes/catalog/+page.svelte3
3 files changed, 33 insertions, 8 deletions
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)