aboutsummaryrefslogtreecommitdiff
path: root/src/routes
diff options
context:
space:
mode:
Diffstat (limited to 'src/routes')
-rw-r--r--src/routes/+layout.svelte2
-rw-r--r--src/routes/+page.svelte209
-rw-r--r--src/routes/groceries/+page.server.ts (renamed from src/routes/+page.server.ts)2
-rw-r--r--src/routes/groceries/+page.svelte207
-rw-r--r--src/routes/groceries/catalog/+page.server.ts (renamed from src/routes/catalog/+page.server.ts)0
-rw-r--r--src/routes/groceries/catalog/+page.svelte (renamed from src/routes/catalog/+page.svelte)5
-rw-r--r--src/routes/groceries/categories/+page.server.ts (renamed from src/routes/categories/+page.server.ts)0
-rw-r--r--src/routes/groceries/categories/+page.svelte (renamed from src/routes/categories/+page.svelte)0
-rw-r--r--src/routes/login/+page.server.ts4
9 files changed, 219 insertions, 210 deletions
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 @@
</script>
<svelte:head>
- <title>Courses</title>
+ <title>Maison</title>
</svelte:head>
<div class="app">
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)}
- >&rarr;</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}
- &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=/">&rarr;</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>
diff --git a/src/routes/+page.server.ts b/src/routes/groceries/+page.server.ts
index 1e6f60b..e462ab2 100644
--- a/src/routes/+page.server.ts
+++ b/src/routes/groceries/+page.server.ts
@@ -14,7 +14,7 @@ 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 {
+export interface GroceriesOutput {
items: EnrichedGrocery[];
categories: Category[];
catalog: EnrichedGrocery[];
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 @@
+<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)}
+ >&rarr;</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}
+ &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=/groceries"
+ >&rarr;</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>
diff --git a/src/routes/catalog/+page.server.ts b/src/routes/groceries/catalog/+page.server.ts
index 22464dc..22464dc 100644
--- a/src/routes/catalog/+page.server.ts
+++ b/src/routes/groceries/catalog/+page.server.ts
diff --git a/src/routes/catalog/+page.svelte b/src/routes/groceries/catalog/+page.svelte
index ebc1819..4cbcc27 100644
--- a/src/routes/catalog/+page.svelte
+++ b/src/routes/groceries/catalog/+page.svelte
@@ -125,8 +125,9 @@
{/if}
</span>
</button>
- <a class="ref-link" href="/grocery/{item.id}?from=/catalog"
- >&rarr;</a
+ <a
+ class="ref-link"
+ href="/grocery/{item.id}?from=/groceries/catalog">&rarr;</a
>
</li>
{/each}
diff --git a/src/routes/categories/+page.server.ts b/src/routes/groceries/categories/+page.server.ts
index c97b619..c97b619 100644
--- a/src/routes/categories/+page.server.ts
+++ b/src/routes/groceries/categories/+page.server.ts
diff --git a/src/routes/categories/+page.svelte b/src/routes/groceries/categories/+page.svelte
index 267a287..267a287 100644
--- a/src/routes/categories/+page.svelte
+++ b/src/routes/groceries/categories/+page.svelte
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 }) => ({