feat(planner): add SwapSuggestionList component for J4 swap context

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-04-09 10:10:44 +02:00
parent 1b2a02881d
commit c8c2605f31
2 changed files with 204 additions and 0 deletions

View File

@@ -0,0 +1,116 @@
<script lang="ts">
interface Recipe {
id: string;
name: string;
effort?: string | null;
cookTimeMin?: number | null;
}
let {
replacingName,
replacingMeta,
recipes,
currentWeekRecipeIds,
onpick,
oncancel
}: {
replacingName: string;
replacingMeta?: string;
recipes: Recipe[];
currentWeekRecipeIds: Set<string>;
onpick: (recipeId: string, recipeName: string) => void;
oncancel?: () => void;
} = $props();
function recipeMeta(recipe: Recipe): string {
return [
recipe.cookTimeMin != null ? `${recipe.cookTimeMin} min` : null,
recipe.effort ?? null
]
.filter(Boolean)
.join(' · ');
}
</script>
<!-- Replacing banner -->
<div
style="background: var(--orange-tint); border: 1px solid #FBCDA4; border-radius: var(--radius-lg); padding: 10px 12px; margin-bottom: 14px;"
>
<p
style="font-size: 10px; font-weight: 500; letter-spacing: .08em; text-transform: uppercase; color: var(--orange-dark); margin: 0 0 4px 0; font-family: var(--font-sans);"
>
Replacing
</p>
<span
data-testid="replacing-name"
style="font-family: var(--font-display); font-size: 14px; text-decoration: line-through; opacity: 0.6; color: var(--color-text);"
>
{replacingName}{#if replacingMeta}<br />{replacingMeta}{/if}
</span>
</div>
<!-- Eyebrow label -->
<p
style="font-size: 10px; font-weight: 500; letter-spacing: .08em; text-transform: uppercase; color: var(--color-text-muted); margin: 0 0 6px 0; font-family: var(--font-sans);"
>
Swap to (easiest first)
</p>
<!-- Recipe list -->
{#if recipes.length === 0}
<p
data-testid="swap-empty-state"
style="text-align: center; color: var(--color-text-muted); font-family: var(--font-sans); margin: 0;"
>
Keine Rezepte verfügbar.
</p>
{:else}
{#each recipes as recipe (recipe.id)}
{@const meta = recipeMeta(recipe)}
{@const alreadyPlanned = currentWeekRecipeIds.has(recipe.id)}
<div
style="background: var(--color-surface); border: 1px solid var(--color-border); border-radius: var(--radius-lg); padding: 10px 12px; margin-bottom: 6px; display: flex; align-items: center; gap: 8px;"
>
<div style="flex: 1; min-width: 0;">
<p
style="font-family: var(--font-display); font-size: 13px; color: var(--color-text); margin: 0;"
>
{recipe.name}
</p>
{#if meta}
<p
style="font-size: 9px; color: var(--color-text-muted); font-family: var(--font-sans); margin: 1px 0 0;"
>
{meta}
</p>
{/if}
{#if alreadyPlanned}
<p
data-testid="already-planned-{recipe.id}"
style="font-size: 9px; color: var(--yellow-text); font-family: var(--font-sans); margin: 1px 0 0;"
>
⚠ Bereits diese Woche
</p>
{/if}
</div>
<button
type="button"
onclick={() => onpick(recipe.id, recipe.name)}
style="background: none; border: none; cursor: pointer; font-size: 11px; font-weight: 500; color: var(--green); font-family: var(--font-sans); flex-shrink: 0;"
>
Pick
</button>
</div>
{/each}
{/if}
<!-- Cancel button (optional) -->
{#if oncancel}
<button
type="button"
onclick={oncancel}
style="width: 100%; background: none; border: none; cursor: pointer; color: var(--color-text-muted); font-size: 13px; text-align: center; padding: 8px 0; font-family: var(--font-sans);"
>
Cancel
</button>
{/if}