diff options
| author | Martin Kagamino Lehoux <martin@lehoux.net> | 2026-09-07 10:55:06 +0200 |
|---|---|---|
| committer | Martin Kagamino Lehoux <martin@lehoux.net> | 2026-09-07 10:55:06 +0200 |
| commit | 113415ce357a9cea1759d0452ebae6352f8fe36b (patch) | |
| tree | 109d55552e7007ba1a8c2752692e85eeb0cc6168 /src/routes/+page.svelte | |
| parent | 8cd634e51b79200848068ef45bd3fb700758c394 (diff) | |
Split groceries domain out of the app root
Move the grocery list, catalog, and categories routes under /groceries/
so the app home can host the projects/routines overview next. Update
nav to exact-path matching, login redirect default, and layout title.
Diffstat (limited to 'src/routes/+page.svelte')
| -rw-r--r-- | src/routes/+page.svelte | 209 |
1 files changed, 4 insertions, 205 deletions
diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte index d07b055..5bdee46 100644 --- a/src/routes/+page.svelte +++ b/src/routes/+page.svelte @@ -1,205 +1,4 @@ -<script lang="ts"> - import { deserialize } from '$app/forms'; - import { goto } from '$app/navigation'; - import { enhance } from '$app/forms'; - import FuzzyAutocomplete from '$lib/components/FuzzyAutocomplete.svelte'; - import type { EnrichedGrocery } from '$lib/server/services/groceries'; - - let { data, form } = $props(); - let name = $state(''); - // eslint-disable-next-line svelte/prefer-writable-derived - let localItems = $state<EnrichedGrocery[]>([]); - let addError = $state(''); - // Sync local list with server data when it changes externally (toggle, navigation) - $effect(() => { - localItems = data?.items ?? []; - }); - - function groupByCategory(items: typeof localItems) { - const groups: Record<string, typeof localItems> = {}; - for (const item of items) { - const key = item.categoryName ?? 'Non catégorisé'; - (groups[key] ??= []).push(item); - } - const result: [string, typeof localItems][] = data.categories - .filter((cat) => groups[cat.name]?.length > 0) - .map((cat) => [cat.name, groups[cat.name]]); - if (groups['Non catégorisé']?.length > 0) { - result.unshift(['Non catégorisé', groups['Non catégorisé']]); - } - return result; - } - - async function handleAdd(redirectToEdit: boolean) { - if (!name.trim()) { - addError = 'Le nom est requis'; - return; - } - addError = ''; - - const formData = new FormData(); - formData.append('name', name); - - const action = redirectToEdit ? '?/addAndEdit' : '?/add'; - name = ''; - - try { - const response = await fetch(action, { - method: 'POST', - headers: { - accept: 'application/json', - 'x-sveltekit-action': 'true', - }, - body: formData, - }); - - const result = deserialize(await response.text()); - - if (result.type === 'error') { - addError = - typeof result.error === 'string' - ? result.error - : result.error?.message || 'Erreur'; - return; - } - - if (result.type === 'redirect') { - goto(result.location); - return; - } - - if (result.type === 'success' && result.data?.items) { - localItems = result.data.items as EnrichedGrocery[]; - } - } catch (err) { - addError = 'Erreur réseau'; - console.error(err); - } - } -</script> - -<div class="add-bar"> - <FuzzyAutocomplete - items={data.catalog} - bind:value={name} - placeholder="Ajouter un article..." - onselect={(item) => { - name = item.name; - handleAdd(false); - }} - /> - <button type="button" aria-label="Ajouter" onclick={() => handleAdd(false)} - >+</button - > - <button type="button" aria-label="Modifier" onclick={() => handleAdd(true)} - >→</button - > -</div> - -<main> - {#if addError || form?.error} - <p class="error">{addError || form?.error}</p> - {/if} - - {#if localItems.length === 0} - <p class="empty">Rien à acheter</p> - {:else} - <form method="POST" action="?/toggle" use:enhance> - {#each groupByCategory(localItems) as [cat, items] (cat)} - <h3 class="category-name">{cat}</h3> - <ul class="item-list"> - {#each items as item (item.id)} - <li class="item-row item-row--fixed"> - <button - class="item-btn item-btn--grow" - name="id" - value={item.id} - type="submit" - aria-pressed={item.checked} - > - <span class="item-text"> - {item.name} - {#if item.bestRef} - <span class="best-ref" - >{item.bestRef.reference} - {#if item.bestRef.price && item.bestRef.unit} - {item.bestRef.price.toFixed(2)} - {item.bestRef.unit} - {/if} - {#if item.bestRef.qualityScore != null} - ({item.bestRef.qualityScore}/100) - {/if} - </span> - {/if} - </span> - </button> - <a class="ref-link" href="/grocery/{item.id}?from=/">→</a> - </li> - {/each} - </ul> - {/each} - </form> - {/if} -</main> - -<style> - .add-bar { - display: flex; - gap: 0.5rem; - padding: 0.5rem 1rem; - position: sticky; - top: var(--nav-height); - background: var(--bg); - border-bottom: 1px solid var(--border); - z-index: 10; - } - - .add-bar button { - width: 3rem; - height: 3rem; - flex-shrink: 0; - padding: 0; - border: none; - border-radius: 0.75rem; - font-size: 1.25rem; - font-weight: 500; - font-family: inherit; - cursor: pointer; - display: flex; - align-items: center; - justify-content: center; - transition: all 0.2s ease; - } - - .add-bar > button:nth-of-type(1) { - background: var(--accent); - color: var(--white); - box-shadow: 0 1px 2px var(--shadow); - } - - .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-of-type(2) { - background: var(--surface); - color: var(--text-secondary); - border: 1.5px solid var(--border); - } - - .add-bar > button:nth-of-type(2):hover { - border-color: var(--accent); - color: var(--text); - } - - .error { - margin-bottom: 1rem; - } - - .empty { - margin-top: 1rem; - padding: 2rem; - } -</style> +<p>Accueil — à venir : projets et routines.</p> +<ul> + <li><a href="/groceries">Courses</a></li> +</ul> |
