From 20a7a08e97e75c3a45b3430a74c2931554498f1c Mon Sep 17 00:00:00 2001 From: Martin Kagamino Lehoux Date: Mon, 7 Sep 2026 19:30:50 +0200 Subject: Extract shared groceries helpers and BestRef component --- src/lib/components/BestRef.svelte | 16 ++++++++++ src/lib/server/services/grocery-actions.ts | 14 +++++++++ src/lib/shared/group-by-category.test.ts | 50 ++++++++++++++++++++++++++++++ src/lib/shared/group-by-category.ts | 23 ++++++++++++++ 4 files changed, 103 insertions(+) create mode 100644 src/lib/components/BestRef.svelte create mode 100644 src/lib/server/services/grocery-actions.ts create mode 100644 src/lib/shared/group-by-category.test.ts create mode 100644 src/lib/shared/group-by-category.ts (limited to 'src/lib') diff --git a/src/lib/components/BestRef.svelte b/src/lib/components/BestRef.svelte new file mode 100644 index 0000000..e194876 --- /dev/null +++ b/src/lib/components/BestRef.svelte @@ -0,0 +1,16 @@ + + + + {ref.reference} + {#if ref.price && ref.unit} +  {ref.price.toFixed(2)} + {ref.unit} + {/if} + {#if ref.qualityScore != null} +  ({ref.qualityScore}/100) + {/if} + diff --git a/src/lib/server/services/grocery-actions.ts b/src/lib/server/services/grocery-actions.ts new file mode 100644 index 0000000..0966c92 --- /dev/null +++ b/src/lib/server/services/grocery-actions.ts @@ -0,0 +1,14 @@ +import { fail } from '@sveltejs/kit'; +import { toggle } from './groceries'; +import { numericId } from './validation'; + +export async function toggleGroceryAction(request: 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 }; +} diff --git a/src/lib/shared/group-by-category.test.ts b/src/lib/shared/group-by-category.test.ts new file mode 100644 index 0000000..d9bb379 --- /dev/null +++ b/src/lib/shared/group-by-category.test.ts @@ -0,0 +1,50 @@ +import { describe, it, expect } from 'vitest'; +import { groupByCategory } from './group-by-category'; + +type Item = { id: number; name: string; categoryName: string | null }; +type Cat = { id: number; name: string }; + +describe('groupByCategory', () => { + const categories: Cat[] = [ + { id: 1, name: 'Laitier' }, + { id: 2, name: 'Fruits' }, + { id: 3, name: 'Boulangerie' }, + ]; + + it('groups items by category, ordered by category order', () => { + const items: Item[] = [ + { id: 1, name: 'banane', categoryName: 'Fruits' }, + { id: 2, name: 'lait', categoryName: 'Laitier' }, + { id: 3, name: 'pomme', categoryName: 'Fruits' }, + ]; + + const result = groupByCategory(items, categories); + + expect(result.map(([cat]) => cat)).toEqual(['Laitier', 'Fruits']); + expect(result[0][1].map((i) => i.name)).toEqual(['lait']); + expect(result[1][1].map((i) => i.name)).toEqual(['banane', 'pomme']); + }); + + it('prepends a non-catégorisé group for items without a category', () => { + const items: Item[] = [ + { id: 1, name: 'miel', categoryName: null }, + { id: 2, name: 'lait', categoryName: 'Laitier' }, + ]; + + const result = groupByCategory(items, categories); + + expect(result.map(([cat]) => cat)).toEqual(['Non catégorisé', 'Laitier']); + }); + + it('omits categories without any item', () => { + const items: Item[] = [{ id: 1, name: 'lait', categoryName: 'Laitier' }]; + + const result = groupByCategory(items, categories); + + expect(result.map(([cat]) => cat)).toEqual(['Laitier']); + }); + + it('returns no groups for empty items', () => { + expect(groupByCategory([], categories)).toEqual([]); + }); +}); diff --git a/src/lib/shared/group-by-category.ts b/src/lib/shared/group-by-category.ts new file mode 100644 index 0000000..e9b0acd --- /dev/null +++ b/src/lib/shared/group-by-category.ts @@ -0,0 +1,23 @@ +export function groupByCategory< + TItem extends { categoryName: string | null }, + TCategory extends { name: string }, +>(items: TItem[], categories: TCategory[]): [string, TItem[]][] { + const groups = new Map(); + for (const item of items) { + const key = item.categoryName ?? 'Non catégorisé'; + const list = groups.get(key); + if (list) list.push(item); + else groups.set(key, [item]); + } + + const result: [string, TItem[]][] = []; + for (const cat of categories) { + const list = groups.get(cat.name); + if (list?.length) result.push([cat.name, list]); + } + const uncategorized = groups.get('Non catégorisé'); + if (uncategorized?.length) { + result.unshift(['Non catégorisé', uncategorized]); + } + return result; +} -- cgit v1.3