feat(shopping): Zutaten-Rows mit Abhaken
All checks were successful
Build & Publish Docker Image / build-and-push (push) Successful in 2m17s
All checks were successful
Build & Publish Docker Image / build-and-push (push) Successful in 2m17s
This commit is contained in:
57
src/lib/components/ShoppingListRow.svelte
Normal file
57
src/lib/components/ShoppingListRow.svelte
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import type { ShoppingListRow } from '$lib/server/shopping/repository';
|
||||||
|
import { formatQuantity } from '$lib/quantity-format';
|
||||||
|
|
||||||
|
let { row, onToggle }: {
|
||||||
|
row: ShoppingListRow;
|
||||||
|
onToggle: (row: ShoppingListRow, next: boolean) => void;
|
||||||
|
} = $props();
|
||||||
|
|
||||||
|
const qtyStr = $derived(formatQuantity(row.total_quantity));
|
||||||
|
const hasUnit = $derived(!!row.display_unit && row.display_unit.trim().length > 0);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<label class="row" class:checked={row.checked}>
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
checked={row.checked === 1}
|
||||||
|
onchange={(e) => onToggle(row, (e.currentTarget as HTMLInputElement).checked)}
|
||||||
|
/>
|
||||||
|
<span class="text">
|
||||||
|
<span class="name">
|
||||||
|
{#if qtyStr}
|
||||||
|
<span class="qty">{qtyStr}{hasUnit ? ` ${row.display_unit}` : ''}</span>
|
||||||
|
{/if}
|
||||||
|
{row.display_name}
|
||||||
|
</span>
|
||||||
|
<span class="src">aus {row.from_recipes}</span>
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.row {
|
||||||
|
display: flex;
|
||||||
|
gap: 0.75rem;
|
||||||
|
align-items: flex-start;
|
||||||
|
padding: 0.75rem;
|
||||||
|
border: 1px solid #e4eae7;
|
||||||
|
border-radius: 10px;
|
||||||
|
background: white;
|
||||||
|
cursor: pointer;
|
||||||
|
min-height: 60px;
|
||||||
|
}
|
||||||
|
.row input {
|
||||||
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
margin-top: 0.1rem;
|
||||||
|
flex-shrink: 0;
|
||||||
|
accent-color: #2b6a3d;
|
||||||
|
}
|
||||||
|
.text { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 0.2rem; }
|
||||||
|
.name { font-size: 1rem; }
|
||||||
|
.qty { font-weight: 600; margin-right: 0.3rem; }
|
||||||
|
.src { color: #888; font-size: 0.82rem; }
|
||||||
|
.row.checked { background: #f6f8f7; }
|
||||||
|
.row.checked .name,
|
||||||
|
.row.checked .qty { text-decoration: line-through; color: #888; }
|
||||||
|
</style>
|
||||||
@@ -2,6 +2,9 @@
|
|||||||
import { onMount } from 'svelte';
|
import { onMount } from 'svelte';
|
||||||
import { ShoppingCart } from 'lucide-svelte';
|
import { ShoppingCart } from 'lucide-svelte';
|
||||||
import type { ShoppingListSnapshot } from '$lib/server/shopping/repository';
|
import type { ShoppingListSnapshot } from '$lib/server/shopping/repository';
|
||||||
|
import ShoppingListRow from '$lib/components/ShoppingListRow.svelte';
|
||||||
|
import type { ShoppingListRow as Row } from '$lib/server/shopping/repository';
|
||||||
|
import { shoppingCartStore } from '$lib/client/shopping-cart.svelte';
|
||||||
|
|
||||||
let snapshot = $state<ShoppingListSnapshot>({ recipes: [], rows: [], uncheckedCount: 0 });
|
let snapshot = $state<ShoppingListSnapshot>({ recipes: [], rows: [], uncheckedCount: 0 });
|
||||||
let loading = $state(true);
|
let loading = $state(true);
|
||||||
@@ -16,6 +19,17 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function onToggleRow(row: Row, next: boolean) {
|
||||||
|
const method = next ? 'POST' : 'DELETE';
|
||||||
|
await fetch('/api/shopping-list/check', {
|
||||||
|
method,
|
||||||
|
headers: { 'content-type': 'application/json' },
|
||||||
|
body: JSON.stringify({ name_key: row.name_key, unit_key: row.unit_key })
|
||||||
|
});
|
||||||
|
await load();
|
||||||
|
void shoppingCartStore.refresh();
|
||||||
|
}
|
||||||
|
|
||||||
onMount(load);
|
onMount(load);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -36,6 +50,14 @@
|
|||||||
<p>Einkaufswagen ist leer.</p>
|
<p>Einkaufswagen ist leer.</p>
|
||||||
<p class="hint">Lege Rezepte auf der Wunschliste in den Wagen, um sie hier zu sehen.</p>
|
<p class="hint">Lege Rezepte auf der Wunschliste in den Wagen, um sie hier zu sehen.</p>
|
||||||
</section>
|
</section>
|
||||||
|
{:else}
|
||||||
|
<ul class="list">
|
||||||
|
{#each snapshot.rows as row (row.name_key + '|' + row.unit_key)}
|
||||||
|
<li>
|
||||||
|
<ShoppingListRow {row} onToggle={onToggleRow} />
|
||||||
|
</li>
|
||||||
|
{/each}
|
||||||
|
</ul>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
@@ -46,4 +68,12 @@
|
|||||||
.empty { text-align: center; padding: 3rem 1rem; }
|
.empty { text-align: center; padding: 3rem 1rem; }
|
||||||
.big { color: #8fb097; display: inline-flex; margin: 0 0 0.5rem; }
|
.big { color: #8fb097; display: inline-flex; margin: 0 0 0.5rem; }
|
||||||
.hint { color: #888; font-size: 0.9rem; }
|
.hint { color: #888; font-size: 0.9rem; }
|
||||||
|
.list {
|
||||||
|
list-style: none;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0.75rem 0;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0.5rem;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
Reference in New Issue
Block a user