From f4648cc38273ac524332c0819f43905c8f22447a Mon Sep 17 00:00:00 2001 From: Marcel Raddatz Date: Thu, 9 Apr 2026 13:03:10 +0200 Subject: [PATCH] feat(planner): show score badges for all recipes in RecipePicker - +server.ts: pass topN=100 so all recipes are scored in one request - RecipePicker: Empfohlen keeps top 5 with scoreDelta > 0; builds a scoreMap from all suggestions; shows green/yellow/red delta badge on every recipe in Alle Rezepte that has a score entry - Extracted scoreBadge snippet to avoid duplication between sections Co-Authored-By: Claude Sonnet 4.6 --- frontend/src/lib/planner/RecipePicker.svelte | 206 ++++++++++-------- frontend/src/lib/planner/RecipePicker.test.ts | 78 ++++++- frontend/src/routes/(app)/planner/+server.ts | 2 +- .../src/routes/(app)/planner/server.test.ts | 2 +- 4 files changed, 181 insertions(+), 107 deletions(-) diff --git a/frontend/src/lib/planner/RecipePicker.svelte b/frontend/src/lib/planner/RecipePicker.svelte index 1b73409..47546b0 100644 --- a/frontend/src/lib/planner/RecipePicker.svelte +++ b/frontend/src/lib/planner/RecipePicker.svelte @@ -21,6 +21,14 @@ let searchQuery = $state(''); + let topRecommendations = $derived( + suggestions.filter((s) => s.scoreDelta > 0).slice(0, 5) + ); + + let scoreMap = $derived( + new Map(suggestions.map((s) => [s.recipe.id, s])) + ); + let filteredRecipes = $derived( searchQuery.trim() === '' ? allRecipes @@ -39,6 +47,34 @@ } +{#snippet scoreBadge(recipeId: string, delta: number, hasConflict: boolean)} + {#if delta > 0} + + ↑ +{delta.toFixed(1)} Punkte + + {:else if hasConflict} + + ↓ {delta.toFixed(1)} Punkte + + {:else} + + = {delta.toFixed(1)} Punkte + + {/if} +{/snippet} +
@@ -81,107 +117,91 @@
{/each}
- {:else if suggestions.length > 0} -
- Empfohlen · Beste Abwechslung -
- - {#each suggestions as suggestion (suggestion.recipe.id)} - {@const meta = recipeMetadata(suggestion.recipe)} + {:else if topRecommendations.length > 0} +
-
-

- {suggestion.recipe.name} -

- {#if meta} -

- {meta} -

- {/if} - {#if (suggestion.scoreDelta ?? 0) > 0} - - ↑ +{(suggestion.scoreDelta ?? 0).toFixed(1)} Punkte - - {:else if suggestion.hasConflict} - - ↓ {(suggestion.scoreDelta ?? 0).toFixed(1)} Punkte - - {:else} - - = {(suggestion.scoreDelta ?? 0).toFixed(1)} Punkte - - {/if} -
- + Empfohlen · Beste Abwechslung
- {/each} + + {#each topRecommendations as suggestion (suggestion.recipe.id)} + {@const meta = recipeMetadata(suggestion.recipe)} +
+
+

+ {suggestion.recipe.name} +

+ {#if meta} +

+ {meta} +

+ {/if} + {@render scoreBadge(suggestion.recipe.id, suggestion.scoreDelta ?? 0, suggestion.hasConflict)} +
+ +
+ {/each} +
{/if} -
- Alle Rezepte -
+
+
+ Alle Rezepte +
- {#if filteredRecipes.length === 0} -

- Keine Treffer -

- {:else} - {#each filteredRecipes as recipe (recipe.id)} - {@const meta = recipeMetadata(recipe)} -
-
-

- {recipe.name} -

- {#if meta} -

- {meta} -

- {/if} -
- -
- {/each} - {/if} +
+

+ {recipe.name} +

+ {#if meta} +

+ {meta} +

+ {/if} + {#if score} + {@render scoreBadge(recipe.id, score.scoreDelta ?? 0, score.hasConflict)} + {/if} +
+ +
+ {/each} + {/if} +