aboutsummaryrefslogtreecommitdiff
path: root/src/routes/catalog/+page.svelte
diff options
context:
space:
mode:
authorMartin Kagamino Lehoux <martin@lehoux.net>2026-09-07 10:55:06 +0200
committerMartin Kagamino Lehoux <martin@lehoux.net>2026-09-07 10:55:06 +0200
commit113415ce357a9cea1759d0452ebae6352f8fe36b (patch)
tree109d55552e7007ba1a8c2752692e85eeb0cc6168 /src/routes/catalog/+page.svelte
parent8cd634e51b79200848068ef45bd3fb700758c394 (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/+page.svelte')
-rw-r--r--src/routes/catalog/+page.svelte210
1 files changed, 0 insertions, 210 deletions
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}
- &nbsp;{item.bestRef.price.toFixed(2)}
- {item.bestRef.unit}
- {/if}
- {#if item.bestRef.qualityScore != null}
- &nbsp;({item.bestRef.qualityScore}/100)
- {/if}
- </span>
- {/if}
- </span>
- </button>
- <a class="ref-link" href="/grocery/{item.id}?from=/catalog"
- >&rarr;</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>