From 113415ce357a9cea1759d0452ebae6352f8fe36b Mon Sep 17 00:00:00 2001 From: Martin Kagamino Lehoux Date: Mon, 7 Sep 2026 10:55:06 +0200 Subject: 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. --- src/lib/components/Nav.svelte | 15 +- src/routes/+layout.svelte | 2 +- src/routes/+page.server.ts | 71 -------- src/routes/+page.svelte | 209 +---------------------- src/routes/catalog/+page.server.ts | 49 ------ src/routes/catalog/+page.svelte | 210 ----------------------- src/routes/categories/+page.server.ts | 53 ------ src/routes/categories/+page.svelte | 164 ------------------ src/routes/groceries/+page.server.ts | 71 ++++++++ src/routes/groceries/+page.svelte | 207 +++++++++++++++++++++++ src/routes/groceries/catalog/+page.server.ts | 49 ++++++ src/routes/groceries/catalog/+page.svelte | 211 ++++++++++++++++++++++++ src/routes/groceries/categories/+page.server.ts | 53 ++++++ src/routes/groceries/categories/+page.svelte | 164 ++++++++++++++++++ src/routes/login/+page.server.ts | 4 +- 15 files changed, 773 insertions(+), 759 deletions(-) delete mode 100644 src/routes/+page.server.ts delete mode 100644 src/routes/catalog/+page.server.ts delete mode 100644 src/routes/catalog/+page.svelte delete mode 100644 src/routes/categories/+page.server.ts delete mode 100644 src/routes/categories/+page.svelte create mode 100644 src/routes/groceries/+page.server.ts create mode 100644 src/routes/groceries/+page.svelte create mode 100644 src/routes/groceries/catalog/+page.server.ts create mode 100644 src/routes/groceries/catalog/+page.svelte create mode 100644 src/routes/groceries/categories/+page.server.ts create mode 100644 src/routes/groceries/categories/+page.svelte (limited to 'src') diff --git a/src/lib/components/Nav.svelte b/src/lib/components/Nav.svelte index 8f2e5c2..aa42ace 100644 --- a/src/lib/components/Nav.svelte +++ b/src/lib/components/Nav.svelte @@ -3,12 +3,17 @@ diff --git a/src/routes/+layout.svelte b/src/routes/+layout.svelte index b2803cc..add22ee 100644 --- a/src/routes/+layout.svelte +++ b/src/routes/+layout.svelte @@ -7,7 +7,7 @@ - Courses + Maison
diff --git a/src/routes/+page.server.ts b/src/routes/+page.server.ts deleted file mode 100644 index 1e6f60b..0000000 --- a/src/routes/+page.server.ts +++ /dev/null @@ -1,71 +0,0 @@ -import { fail, redirect, error } from '@sveltejs/kit'; -import { - loadUnchecked, - loadAll, - toggle, - addOrUncheck, -} from '$lib/server/services/groceries'; -import { - numericId, - parseFormString, - 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 HomeOutput { - items: EnrichedGrocery[]; - categories: Category[]; - catalog: EnrichedGrocery[]; -} - -export const load: PageServerLoad = async () => { - const unchecked = await loadUnchecked(); - const all = await loadAll(); - return { - items: unchecked.items, - categories: unchecked.categories, - catalog: all.items, - }; -}; - -export const actions: Actions = { - add: async ({ request }) => { - const data = await request.formData(); - const name = parseFormString(data.get('name')); - const categoryId = parseOptionalNumber(data.get('category_id')); - - if (!name) { - return fail(400, { error: 'Le nom est requis' }); - } - - await addOrUncheck(name, categoryId); - const { items } = await loadUnchecked(); - return { success: true, items }; - }, - - 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 }; - }, - - addAndEdit: async ({ request }) => { - const data = await request.formData(); - const name = parseFormString(data.get('name')); - const categoryId = parseOptionalNumber(data.get('category_id')); - - if (!name) { - return fail(400, { error: 'Le nom est requis' }); - } - - const { item, created } = await addOrUncheck(name, categoryId); - throw redirect(303, `/grocery/${item.id}`); - }, -}; 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 @@ - - -
- { - name = item.name; - handleAdd(false); - }} - /> - - -
- -
- {#if addError || form?.error} -

{addError || form?.error}

- {/if} - - {#if localItems.length === 0} -

Rien à acheter

- {:else} -
- {#each groupByCategory(localItems) as [cat, items] (cat)} -

{cat}

-
    - {#each items as item (item.id)} -
  • - - → -
  • - {/each} -
- {/each} -
- {/if} -
- - +

Accueil — à venir : projets et routines.

+ 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 @@ - - -{#if catalogItems.length > 0} -
-
{ - return async ({ update }) => { - await update({ reset: false }); - selectedIds.clear(); - }; - }} - > - {#each Array.from(selectedIds) as id (id)} - - {/each} - - -
-
-{/if} - -
- {#if form?.error} -

{form.error}

- {/if} - - {#if catalogItems.length === 0} -

Aucun article

- {:else} - - - {#if filteredItems.length === 0} -

Aucun résultat

- {:else} -
- {#each groupByCategory(filteredItems) as [cat, items] (cat)} -

{cat}

-
    - {#each items as item (item.id)} -
  • - toggleId(item.id)} - /> - - → -
  • - {/each} -
- {/each} -
- {/if} - {/if} -
- - diff --git a/src/routes/categories/+page.server.ts b/src/routes/categories/+page.server.ts deleted file mode 100644 index c97b619..0000000 --- a/src/routes/categories/+page.server.ts +++ /dev/null @@ -1,53 +0,0 @@ -import { fail } from '@sveltejs/kit'; -import { - loadAll, - add, - deleteCategory, - reorder, -} from '$lib/server/services/categories'; -import { numericId, parseFormString } from '$lib/server/services/validation'; -import type { Category } from '$lib/server/services/types'; -import type { Actions, PageServerLoad } from './$types'; - -export interface CategoriesOutput { - categories: Category[]; -} - -export const load: PageServerLoad = async () => { - return { categories: await loadAll() }; -}; - -export const actions: Actions = { - add: async ({ request }) => { - const data = await request.formData(); - const name = parseFormString(data.get('name')); - - if (!name) { - return fail(400, { error: 'Le nom est requis' }); - } - - await add(name); - return { success: true }; - }, - - delete: async ({ request }) => { - const data = await request.formData(); - try { - const id = numericId(data.get('id')); - await deleteCategory(id); - } catch { - return fail(400, { error: 'ID invalide' }); - } - return { success: true }; - }, - - reorder: async ({ request }) => { - const data = await request.formData(); - const ids = data.getAll('orderedIds').map(numericId); - if (ids.length === 0) { - return fail(400, { error: 'Aucune catégorie' }); - } - await reorder(ids); - return { success: true }; - }, -}; diff --git a/src/routes/categories/+page.svelte b/src/routes/categories/+page.svelte deleted file mode 100644 index 267a287..0000000 --- a/src/routes/categories/+page.svelte +++ /dev/null @@ -1,164 +0,0 @@ - - -
-
- - -
- - {#if form?.error} -

{form.error}

- {/if} - -
{ - return async ({ update }) => { - await update({ reset: false }); - }; - }} - > - {#each localCategories as cat (cat.id)} - - {/each} -
- -
-
    - {#each localCategories as cat, index (cat.id)} -
  • handleDragStart(index)} - ondragover={(e) => handleDragOver(e, index)} - ondrop={handleDrop} - ondragend={() => (draggedIndex = null)} - > - ⋮⋮ - {cat.name} - -
  • - {/each} -
-
- - {#if (localCategories.length ?? 0) === 0} -

Aucune catégorie

- {/if} -
- - diff --git a/src/routes/groceries/+page.server.ts b/src/routes/groceries/+page.server.ts new file mode 100644 index 0000000..e462ab2 --- /dev/null +++ b/src/routes/groceries/+page.server.ts @@ -0,0 +1,71 @@ +import { fail, redirect, error } from '@sveltejs/kit'; +import { + loadUnchecked, + loadAll, + toggle, + addOrUncheck, +} from '$lib/server/services/groceries'; +import { + numericId, + parseFormString, + 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 GroceriesOutput { + items: EnrichedGrocery[]; + categories: Category[]; + catalog: EnrichedGrocery[]; +} + +export const load: PageServerLoad = async () => { + const unchecked = await loadUnchecked(); + const all = await loadAll(); + return { + items: unchecked.items, + categories: unchecked.categories, + catalog: all.items, + }; +}; + +export const actions: Actions = { + add: async ({ request }) => { + const data = await request.formData(); + const name = parseFormString(data.get('name')); + const categoryId = parseOptionalNumber(data.get('category_id')); + + if (!name) { + return fail(400, { error: 'Le nom est requis' }); + } + + await addOrUncheck(name, categoryId); + const { items } = await loadUnchecked(); + return { success: true, items }; + }, + + 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 }; + }, + + addAndEdit: async ({ request }) => { + const data = await request.formData(); + const name = parseFormString(data.get('name')); + const categoryId = parseOptionalNumber(data.get('category_id')); + + if (!name) { + return fail(400, { error: 'Le nom est requis' }); + } + + const { item, created } = await addOrUncheck(name, categoryId); + throw redirect(303, `/grocery/${item.id}`); + }, +}; diff --git a/src/routes/groceries/+page.svelte b/src/routes/groceries/+page.svelte new file mode 100644 index 0000000..4471738 --- /dev/null +++ b/src/routes/groceries/+page.svelte @@ -0,0 +1,207 @@ + + +
+ { + name = item.name; + handleAdd(false); + }} + /> + + +
+ +
+ {#if addError || form?.error} +

{addError || form?.error}

+ {/if} + + {#if localItems.length === 0} +

Rien à acheter

+ {:else} +
+ {#each groupByCategory(localItems) as [cat, items] (cat)} +

{cat}

+
    + {#each items as item (item.id)} +
  • + + → +
  • + {/each} +
+ {/each} +
+ {/if} +
+ + diff --git a/src/routes/groceries/catalog/+page.server.ts b/src/routes/groceries/catalog/+page.server.ts new file mode 100644 index 0000000..22464dc --- /dev/null +++ b/src/routes/groceries/catalog/+page.server.ts @@ -0,0 +1,49 @@ +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/groceries/catalog/+page.svelte b/src/routes/groceries/catalog/+page.svelte new file mode 100644 index 0000000..4cbcc27 --- /dev/null +++ b/src/routes/groceries/catalog/+page.svelte @@ -0,0 +1,211 @@ + + +{#if catalogItems.length > 0} +
+
{ + return async ({ update }) => { + await update({ reset: false }); + selectedIds.clear(); + }; + }} + > + {#each Array.from(selectedIds) as id (id)} + + {/each} + + +
+
+{/if} + +
+ {#if form?.error} +

{form.error}

+ {/if} + + {#if catalogItems.length === 0} +

Aucun article

+ {:else} + + + {#if filteredItems.length === 0} +

Aucun résultat

+ {:else} +
+ {#each groupByCategory(filteredItems) as [cat, items] (cat)} +

{cat}

+
    + {#each items as item (item.id)} +
  • + toggleId(item.id)} + /> + + → +
  • + {/each} +
+ {/each} +
+ {/if} + {/if} +
+ + diff --git a/src/routes/groceries/categories/+page.server.ts b/src/routes/groceries/categories/+page.server.ts new file mode 100644 index 0000000..c97b619 --- /dev/null +++ b/src/routes/groceries/categories/+page.server.ts @@ -0,0 +1,53 @@ +import { fail } from '@sveltejs/kit'; +import { + loadAll, + add, + deleteCategory, + reorder, +} from '$lib/server/services/categories'; +import { numericId, parseFormString } from '$lib/server/services/validation'; +import type { Category } from '$lib/server/services/types'; +import type { Actions, PageServerLoad } from './$types'; + +export interface CategoriesOutput { + categories: Category[]; +} + +export const load: PageServerLoad = async () => { + return { categories: await loadAll() }; +}; + +export const actions: Actions = { + add: async ({ request }) => { + const data = await request.formData(); + const name = parseFormString(data.get('name')); + + if (!name) { + return fail(400, { error: 'Le nom est requis' }); + } + + await add(name); + return { success: true }; + }, + + delete: async ({ request }) => { + const data = await request.formData(); + try { + const id = numericId(data.get('id')); + await deleteCategory(id); + } catch { + return fail(400, { error: 'ID invalide' }); + } + return { success: true }; + }, + + reorder: async ({ request }) => { + const data = await request.formData(); + const ids = data.getAll('orderedIds').map(numericId); + if (ids.length === 0) { + return fail(400, { error: 'Aucune catégorie' }); + } + await reorder(ids); + return { success: true }; + }, +}; diff --git a/src/routes/groceries/categories/+page.svelte b/src/routes/groceries/categories/+page.svelte new file mode 100644 index 0000000..267a287 --- /dev/null +++ b/src/routes/groceries/categories/+page.svelte @@ -0,0 +1,164 @@ + + +
+
+ + +
+ + {#if form?.error} +

{form.error}

+ {/if} + +
{ + return async ({ update }) => { + await update({ reset: false }); + }; + }} + > + {#each localCategories as cat (cat.id)} + + {/each} +
+ +
+
    + {#each localCategories as cat, index (cat.id)} +
  • handleDragStart(index)} + ondragover={(e) => handleDragOver(e, index)} + ondrop={handleDrop} + ondragend={() => (draggedIndex = null)} + > + ⋮⋮ + {cat.name} + +
  • + {/each} +
+
+ + {#if (localCategories.length ?? 0) === 0} +

Aucune catégorie

+ {/if} +
+ + diff --git a/src/routes/login/+page.server.ts b/src/routes/login/+page.server.ts index 88c5f2d..b43c97e 100644 --- a/src/routes/login/+page.server.ts +++ b/src/routes/login/+page.server.ts @@ -3,7 +3,9 @@ import { setSession, verifyPassword } from '$lib/server/auth'; import type { Actions, PageServerLoad } from './$types'; function safeNext(value: string | null): string { - return value?.startsWith('/') && !value.startsWith('//') ? value : '/'; + return value?.startsWith('/') && !value.startsWith('//') + ? value + : '/groceries'; } export const load: PageServerLoad = ({ url }) => ({ -- cgit v1.3