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/catalog | |
| 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/catalog')
| -rw-r--r-- | src/routes/catalog/+page.server.ts | 49 | ||||
| -rw-r--r-- | src/routes/catalog/+page.svelte | 210 |
2 files changed, 0 insertions, 259 deletions
diff --git a/src/routes/catalog/+page.server.ts b/src/routes/catalog/+page.server.ts deleted file mode 100644 index 22464dc..0000000 --- a/src/routes/catalog/+page.server.ts +++ /dev/null @@ -1,49 +0,0 @@ -import { fail } from '@sveltejs/kit'; -import { - loadAll, - toggle, - bulkUpdateGroceryCategory, -} from '$lib/server/services/groceries'; -import { - numericId, - parseOptionalNumber, -} from '$lib/server/services/validation'; -import type { EnrichedGrocery } from '$lib/server/services/groceries'; -import type { Category } from '$lib/server/services/types'; -import type { Actions, PageServerLoad } from './$types'; - -export interface CatalogOutput { - items: EnrichedGrocery[]; - categories: Category[]; -} - -export const load: PageServerLoad = async () => { - return loadAll(); -}; - -export const actions: Actions = { - toggle: async ({ request }) => { - const data = await request.formData(); - try { - const id = numericId(data.get('id')); - await toggle(id); - } catch { - return fail(400, { error: 'ID invalide' }); - } - return { success: true }; - }, - - bulkUpdateCategory: async ({ request }) => { - const data = await request.formData(); - const ids = data.getAll('ids').map(numericId); - const rawCategoryId = parseOptionalNumber(data.get('category_id')); - const categoryId = rawCategoryId === -1 ? null : rawCategoryId; - - if (ids.length === 0) { - return fail(400, { error: 'Aucun article sélectionné' }); - } - - await bulkUpdateGroceryCategory(ids, categoryId); - return { success: true, type: 'bulkUpdate' as const }; - }, -}; diff --git a/src/routes/catalog/+page.svelte b/src/routes/catalog/+page.svelte deleted file mode 100644 index ebc1819..0000000 --- a/src/routes/catalog/+page.svelte +++ /dev/null @@ -1,210 +0,0 @@ -<script lang="ts"> - import { enhance } from '$app/forms'; - import { SvelteSet } from 'svelte/reactivity'; - - let { data, form } = $props(); - - let catalogItems = $derived(data?.items ?? []); - let selectedIds = new SvelteSet<number>(); - let searchQuery = $state(''); - - let filteredItems = $derived( - searchQuery.trim() === '' - ? catalogItems - : catalogItems.filter((item) => - item.name.toLowerCase().includes(searchQuery.toLowerCase()), - ), - ); - - function toggleId(id: number) { - if (selectedIds.has(id)) selectedIds.delete(id); - else selectedIds.add(id); - } - - function groupByCategory(items: typeof catalogItems) { - const groups: Record<string, typeof catalogItems> = {}; - for (const item of items) { - const key = item.categoryName ?? 'Non catégorisé'; - (groups[key] ??= []).push(item); - } - const result: [string, typeof catalogItems][] = []; - for (const cat of data.categories) { - if (groups[cat.name]?.length > 0) { - result.push([cat.name, groups[cat.name]]); - } - } - if (groups['Non catégorisé']?.length > 0) { - result.unshift(['Non catégorisé', groups['Non catégorisé']]); - } - return result; - } -</script> - -{#if catalogItems.length > 0} - <div class="bulk-bar"> - <form - method="POST" - action="?/bulkUpdateCategory" - use:enhance={() => { - return async ({ update }) => { - await update({ reset: false }); - selectedIds.clear(); - }; - }} - > - {#each Array.from(selectedIds) as id (id)} - <input type="hidden" name="ids" value={id} /> - {/each} - <select name="category_id" class="form-select"> - <option value={-1}>Non catégorisé</option> - {#each data.categories as cat (cat.id)} - <option value={cat.id}>{cat.name}</option> - {/each} - </select> - <button - class="btn-primary" - type="submit" - disabled={selectedIds.size === 0} - > - Mettre à jour ({selectedIds.size}) - </button> - </form> - </div> -{/if} - -<main> - {#if form?.error} - <p class="error">{form.error}</p> - {/if} - - {#if catalogItems.length === 0} - <p class="empty">Aucun article</p> - {:else} - <input - type="search" - bind:value={searchQuery} - placeholder="Rechercher un article..." - class="search-input" - /> - - {#if filteredItems.length === 0} - <p class="empty">Aucun résultat</p> - {:else} - <form method="POST" action="?/toggle" use:enhance> - {#each groupByCategory(filteredItems) 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"> - <input - type="checkbox" - class="item-check" - checked={selectedIds.has(item.id)} - onchange={() => toggleId(item.id)} - /> - <button - class="item-btn item-btn--grow" - name="id" - value={item.id} - type="submit" - aria-pressed={item.checked} - > - <span class="item-text"> - <span class:checked={item.checked}>{item.name}</span> - {#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=/catalog" - >→</a - > - </li> - {/each} - </ul> - {/each} - </form> - {/if} - {/if} -</main> - -<style> - .bulk-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; - } - - .bulk-bar form { - display: flex; - gap: 0.5rem; - align-items: center; - width: 100%; - } - - .bulk-bar .form-select { - flex: 1; - min-width: 0; - } - - .bulk-bar .btn-primary { - padding: 0.75rem 1rem; - align-self: center; - white-space: nowrap; - } - - .bulk-bar .btn-primary:disabled { - opacity: 0.5; - cursor: not-allowed; - transform: none; - box-shadow: none; - } - - .item-check { - width: 1.25rem; - height: 1.25rem; - accent-color: var(--accent); - cursor: pointer; - flex-shrink: 0; - } - - .checked { - text-decoration: line-through; - color: var(--text-secondary); - font-weight: 400; - } - - .error { - margin-bottom: 1rem; - } - - .empty { - margin-top: 1rem; - padding: 2rem; - } - - .search-input { - width: 100%; - padding: 0.75rem 1rem; - border: 1px solid var(--border); - border-radius: 0.5rem; - background: var(--surface); - color: var(--text); - font-size: 1rem; - margin-top: 1rem; - } -</style> |
