- Fix logic bug `{#if !isPlanner === false}` - view/cook buttons now visible for all roles, swap only for planner
- Convert Tauschen from dead button to link with suggestions href
- Add week.ts unit tests (23 tests covering getWeekStart Sunday edge case, prevWeek/nextWeek, weekDays, isToday, formatWeekRange)
- Fix isToday to use UTC consistently (.toISOString().slice(0,10)) instead of local date
- Add server-side role guard to createPlan action (403 for members)
- Add weekStart format validation in createPlan action
- Add isSelected prop to DayMealCard with green treatment
- Make variety banner sticky on mobile (always visible per spec)
- Add day name abbreviation above date badge in desktop column headers
- Remove placeholder Navigation text from desktop sidebar
- Add aria-label to desktop empty tile buttons
- Add variety score partial failure test, multiple overlaps test, WeekStrip today+selected test
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
87 lines
2.2 KiB
Svelte
87 lines
2.2 KiB
Svelte
<script lang="ts">
|
|
interface SlotRecipe {
|
|
id?: string;
|
|
name?: string;
|
|
effort?: string;
|
|
cookTimeMin?: number;
|
|
}
|
|
|
|
interface Slot {
|
|
id?: string;
|
|
slotDate?: string;
|
|
recipe?: SlotRecipe | null;
|
|
}
|
|
|
|
let {
|
|
slot,
|
|
isToday = false,
|
|
isSelected = false,
|
|
readonly = false
|
|
}: {
|
|
slot: Slot;
|
|
isToday?: boolean;
|
|
isSelected?: boolean;
|
|
readonly?: boolean;
|
|
} = $props();
|
|
|
|
let metadata = $derived(
|
|
[
|
|
slot.recipe?.cookTimeMin != null ? `${slot.recipe.cookTimeMin} Min` : null,
|
|
slot.recipe?.effort ?? null
|
|
]
|
|
.filter(Boolean)
|
|
.join(' · ')
|
|
);
|
|
|
|
let borderClass = $derived(
|
|
isToday
|
|
? 'border-[var(--yellow)] bg-[var(--yellow-tint)]'
|
|
: isSelected
|
|
? 'border-[var(--green)] bg-[var(--green-tint)]'
|
|
: 'border-[var(--color-border)] bg-[var(--color-surface)]'
|
|
);
|
|
</script>
|
|
|
|
<div
|
|
data-testid="day-meal-card"
|
|
data-today={isToday}
|
|
data-selected={isSelected}
|
|
class="rounded-[var(--radius-lg)] border-2 p-4 transition-colors {borderClass}"
|
|
>
|
|
{#if slot.recipe}
|
|
<h3 class="font-[var(--font-display)] text-[20px] font-[300] leading-tight text-[var(--color-text)]">
|
|
{slot.recipe.name}
|
|
</h3>
|
|
{#if metadata}
|
|
<p class="mt-1 font-[var(--font-sans)] text-[13px] text-[var(--color-text-muted)]">{metadata}</p>
|
|
{/if}
|
|
|
|
{#if !readonly}
|
|
<div class="mt-3 flex gap-2">
|
|
<a
|
|
href="/recipes/{slot.recipe.id}/cook"
|
|
class="rounded-[var(--radius-md)] bg-[var(--green-dark)] px-3 py-2 text-[13px] font-medium tracking-[0.04em] font-[var(--font-sans)] text-white"
|
|
>
|
|
Jetzt kochen
|
|
</a>
|
|
<a
|
|
href="/planner/suggestions?day={slot.slotDate}"
|
|
class="rounded-[var(--radius-md)] border border-[var(--color-border)] px-3 py-2 text-[13px] font-medium tracking-[0.04em] font-[var(--font-sans)] text-[var(--color-text)]"
|
|
>
|
|
Tauschen
|
|
</a>
|
|
</div>
|
|
{/if}
|
|
{:else}
|
|
<p class="font-[var(--font-sans)] text-[14px] text-[var(--color-text-muted)]">Kein Gericht geplant</p>
|
|
{#if !readonly}
|
|
<a
|
|
href="/planner/suggestions?day={slot.slotDate}"
|
|
class="mt-2 inline-block rounded-[var(--radius-md)] border border-dashed border-[var(--color-border)] px-3 py-2 text-[13px] font-medium tracking-[0.04em] font-[var(--font-sans)] text-[var(--color-text-muted)]"
|
|
>
|
|
+ Gericht hinzufügen
|
|
</a>
|
|
{/if}
|
|
{/if}
|
|
</div>
|