diff --git a/src/lib/api/presets.ts b/src/lib/api/presets.ts index f1b8367..2bfe0bd 100644 --- a/src/lib/api/presets.ts +++ b/src/lib/api/presets.ts @@ -2,26 +2,30 @@ import { apiGet, apiDelete, apiPatch, apiPost } from './client'; import type { Preset, PresetEntry } from '$lib/types/api'; export function listPresets(limit = 20, offset = 0): Promise { - const params = new URLSearchParams({ limit: String(limit), offset: String(offset) }); - return apiGet(`/preset?${params}`); + const params = new URLSearchParams({ limit: String(limit), offset: String(offset) }); + return apiGet(`/preset?${params}`); +} + +export function createPreset(name: string): Promise { + return apiPost(`/preset/`, { name }); } export function renamePreset(id: number, name: string): Promise { - return apiPatch(`/preset/${id}`, { name }); + return apiPatch(`/preset/${id}`, { name }); } export function deletePreset(id: number): Promise { - return apiDelete(`/preset/${id}`); + return apiDelete(`/preset/${id}`); } export function createPresetEntry(presetId: number, productId: number, grams: number): Promise { - return apiPost(`/preset/${presetId}/entry`, { product_id: productId, grams }); + return apiPost(`/preset/${presetId}/entry`, { product_id: productId, grams }); } export function updatePresetEntry(presetId: number, entryId: number, grams: number): Promise { - return apiPatch(`/preset/${presetId}/entry/${entryId}`, { grams }); + return apiPatch(`/preset/${presetId}/entry/${entryId}`, { grams }); } export function deletePresetEntry(presetId: number, entryId: number): Promise { - return apiDelete(`/preset/${presetId}/entry/${entryId}`); + return apiDelete(`/preset/${presetId}/entry/${entryId}`); } diff --git a/src/routes/(app)/diary/[date]/add-entry/+page.svelte b/src/routes/(app)/diary/[date]/add-entry/+page.svelte index 3eb07c0..36e9355 100644 --- a/src/routes/(app)/diary/[date]/add-entry/+page.svelte +++ b/src/routes/(app)/diary/[date]/add-entry/+page.svelte @@ -1,273 +1,344 @@ {#if scannerOpen} - scannerOpen = false} - /> + (scannerOpen = false)} + /> {/if}
- + - -
-
-
- - - - handleSearch(e.currentTarget.value)} - onkeydown={(e) => { - if (e.key === 'Enter') { - const first = productsQuery.data?.[0]; - if (first) selectProduct(first); - } - }} - class="w-full bg-zinc-900 border border-zinc-700 rounded-xl pl-9 pr-4 py-2.5 text-sm text-zinc-100 placeholder-zinc-500 focus:outline-none focus:border-green-500 transition-colors" - /> -
+ +
+
+
+ + + + handleSearch(e.currentTarget.value)} + onkeydown={(e) => { + if (e.key === "Enter") { + const first = productsQuery.data?.[0]; + if (first) selectProduct(first); + } + }} + class="w-full bg-zinc-900 border border-zinc-700 rounded-xl pl-9 pr-4 py-2.5 text-sm text-zinc-100 placeholder-zinc-500 focus:outline-none focus:border-green-500 transition-colors" + /> +
- - {#if network.online} - - {/if} -
+ + {#if network.online} + + {/if} +
- {#if scanError} -

{scanError}

- {/if} -
+ {#if scanError} +

{scanError}

+ {/if} +
- -
- {#if productsQuery.isPending} -
- {#each Array(6) as _} -
- {/each} -
- {:else if productsQuery.data?.length === 0} -
- {#if !network.online} -

No cached products match "{q}"

-

Connect to internet to search all products

- {:else} -

No products found

-

Try a different name or

- - Create "{q || 'new product'}" - - {/if} -
- {:else} -
    - {#each productsQuery.data ?? [] as product (product.id)} -
  • - -
  • - {/each} -
- {/if} -
+ +
+ {#if productsQuery.isPending} +
+ {#each Array(6) as _} +
+ {/each} +
+ {:else if productsQuery.data?.length === 0} +
+ {#if !network.online} +

No cached products match "{q}"

+

Connect to internet to search all products

+ {:else} +

No products found

+

Try a different name or

+ + Create "{q || "new product"}" + + {/if} +
+ {:else} +
    + {#each productsQuery.data ?? [] as product (product.id)} +
  • + +
  • + {/each} +
+ {/if} +
- - { selectedProduct = null; error = ''; }} - title={selectedProduct?.name ?? ''} - > - {#if selectedProduct} - {#if preview} -
- {#each [ - { label: 'kcal', value: preview.calories }, - { label: 'protein', value: preview.protein + 'g' }, - { label: 'carbs', value: preview.carb + 'g' }, - { label: 'fat', value: preview.fat + 'g' }, - ] as m} -
-

{m.value}

-

{m.label}

-
- {/each} -
- {/if} + + { + selectedProduct = null; + error = ""; + }} + title={selectedProduct?.name ?? ""} + > + {#if selectedProduct} + {#if preview} +
+ {#each [{ label: "kcal", value: preview.calories }, { label: "protein", value: preview.protein + "g" }, { label: "carbs", value: preview.carb + "g" }, { label: "fat", value: preview.fat + "g" }] as m} +
+

{m.value}

+

{m.label}

+
+ {/each} +
+ {/if} - -
- - e.currentTarget.select()} - onkeydown={(e) => { if (e.key === 'Enter') handleAddEntry(); }} - class="flex-1 bg-zinc-800 border border-zinc-700 rounded-xl px-4 py-2.5 text-center text-xl font-semibold focus:outline-none focus:border-green-500 transition-colors" - /> - -
+ +
+ + e.currentTarget.select()} + onkeydown={(e) => { + if (e.key === "Enter") handleAddEntry(); + }} + class="flex-1 bg-zinc-800 border border-zinc-700 rounded-xl px-4 py-2.5 text-center text-xl font-semibold focus:outline-none focus:border-green-500 transition-colors" + /> + +
- {#if error} -

{error}

- {/if} + {#if error} +

{error}

+ {/if} - - {/if} -
+ + {/if} +
diff --git a/src/routes/(app)/presets/+page.svelte b/src/routes/(app)/presets/+page.svelte index 388c56b..e369bb5 100644 --- a/src/routes/(app)/presets/+page.svelte +++ b/src/routes/(app)/presets/+page.svelte @@ -1,470 +1,704 @@
- + - -
-
- - - - handleSearch(e.currentTarget.value)} - class="w-full bg-zinc-900 border border-zinc-700 rounded-xl pl-9 pr-4 py-2.5 text-sm text-zinc-100 placeholder-zinc-500 focus:outline-none focus:border-green-500 transition-colors" - /> -
-
+ +
+
+ + + + handleSearch(e.currentTarget.value)} + class="w-full bg-zinc-900 border border-zinc-700 rounded-xl pl-9 pr-4 py-2.5 text-sm text-zinc-100 placeholder-zinc-500 focus:outline-none focus:border-green-500 transition-colors" + /> +
+
-
- {#if presetsQuery.isPending} -
- {#each Array(5) as _} -
- {/each} -
- {:else if presetsQuery.isError} -

Could not load presets

- {:else if !filteredPresets.length} -
-

No presets yet

-

Save a meal as preset from the diary view

-
- {:else} -
    - {#each filteredPresets as preset (preset.id)} -
  • - -
    - - +
    + {#if presetsQuery.isPending} +
    + {#each Array(5) as _} +
    + {/each} +
    + {:else if presetsQuery.isError} +

    Could not load presets

    + {:else if !filteredPresets.length} +
    +

    No presets yet

    +

    Save a meal as preset from the diary view or

    +
    + + {:else} +
      + {#each filteredPresets as preset (preset.id)} +
    • + +
      + + - -
      - - + ? 'bg-green-600/20 text-green-400' + : 'bg-zinc-800 hover:bg-zinc-700 text-zinc-300 disabled:opacity-50'}" + > + {#if addingId === preset.id} + + + + {:else if addedId === preset.id} + + + + Added + {:else} + + + + Today + {/if} + - - + + - - -
      -
      + + +
    +
- - {#if expandedId === preset.id} -
- {#if preset.entries.length === 0} -

No entries yet

- {:else} -
    - {#each preset.entries as entry (entry.id)} -
  • - - -
  • - {/each} -
- {/if} + + {#if expandedId === preset.id} +
+ {#if preset.entries.length === 0} +

+ No entries yet +

+ {:else} +
    + {#each preset.entries as entry (entry.id)} +
  • + + +
  • + {/each} +
+ {/if} - - -
- {/if} - - {/each} - - {/if} - + + +
+ {/if} + + {/each} +
  • + +
  • + + {/if} + + + { + creating = false; + creatingName = ""; + }} + title="Create new preset" +> +
    + + +
    +
    + - renamePreset_ = null} title="Rename preset"> -
    - - -
    + (renamePreset_ = null)} + title="Rename preset" +> +
    + + +
    - editingEntry = null} title="Edit entry"> - {#if editingEntry} -
    -

    {editingEntry.entry.product.name}

    -
    - -
    - - e.currentTarget.select()} - class="flex-1 bg-zinc-800 border border-zinc-700 rounded-xl px-4 py-2.5 text-center text-xl font-semibold focus:outline-none focus:border-green-500 transition-colors" - /> - -
    -
    - -
    - {/if} + (editingEntry = null)} + title="Edit entry" +> + {#if editingEntry} +
    +

    {editingEntry.entry.product.name}

    +
    + +
    + + e.currentTarget.select()} + class="flex-1 bg-zinc-800 border border-zinc-700 rounded-xl px-4 py-2.5 text-center text-xl font-semibold focus:outline-none focus:border-green-500 transition-colors" + /> + +
    +
    + +
    + {/if}
    - { addEntryPresetId = null; selectedProduct = null; }} title="Add food to preset"> - {#if addEntryPresetId !== null} - {#if selectedProduct === null} - -
    - handleProductSearch(e.currentTarget.value)} - autofocus - class="w-full bg-zinc-800 border border-zinc-700 rounded-xl px-4 py-2.5 text-sm text-zinc-100 placeholder-zinc-500 focus:outline-none focus:border-green-500 transition-colors" - /> - {#if productsQuery.isPending && productDebouncedQ} -

    Searching…

    - {:else} -
      - {#each productsQuery.data ?? [] as product (product.id)} -
    • - -
    • - {/each} -
    - {/if} -
    - {:else} - -
    -
    -

    {selectedProduct.name}

    -

    {kcal(selectedProduct.calories)} kcal / 100g

    -
    -
    - -
    - - e.currentTarget.select()} - class="flex-1 bg-zinc-800 border border-zinc-700 rounded-xl px-4 py-2.5 text-center text-xl font-semibold focus:outline-none focus:border-green-500 transition-colors" - /> - -
    -
    -
    - - -
    -
    - {/if} - {/if} + { + addEntryPresetId = null; + selectedProduct = null; + }} + title="Add food to preset" +> + {#if addEntryPresetId !== null} + {#if selectedProduct === null} + +
    + handleProductSearch(e.currentTarget.value)} + autofocus + class="w-full bg-zinc-800 border border-zinc-700 rounded-xl px-4 py-2.5 text-sm text-zinc-100 placeholder-zinc-500 focus:outline-none focus:border-green-500 transition-colors" + /> + {#if productsQuery.isPending && productDebouncedQ} +

    Searching…

    + {:else} +
      + {#each productsQuery.data ?? [] as product (product.id)} +
    • + +
    • + {/each} +
    + {/if} +
    + {:else} + +
    +
    +

    + {selectedProduct.name} +

    +

    + {kcal(selectedProduct.calories)} kcal / 100g +

    +
    +
    + +
    + + e.currentTarget.select()} + class="flex-1 bg-zinc-800 border border-zinc-700 rounded-xl px-4 py-2.5 text-center text-xl font-semibold focus:outline-none focus:border-green-500 transition-colors" + /> + +
    +
    +
    + + +
    +
    + {/if} + {/if}
    diff --git a/src/routes/(auth)/register/+page.svelte b/src/routes/(auth)/register/+page.svelte index 009659c..a10ea8a 100644 --- a/src/routes/(auth)/register/+page.svelte +++ b/src/routes/(auth)/register/+page.svelte @@ -1,7 +1,6 @@