feat(planner): implement C1 weekly planner home screen (#26)
Three-breakpoint layout (mobile/tablet/desktop) with VarietyScoreCard, WeekStrip, DayMealCard components. Server loads week plan and variety score via API; read-only role behavior derived from benutzer.rolle. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
70
frontend/src/lib/planner/WeekStrip.svelte
Normal file
70
frontend/src/lib/planner/WeekStrip.svelte
Normal file
@@ -0,0 +1,70 @@
|
||||
<script lang="ts">
|
||||
import { weekDays, formatDayAbbr } from './week';
|
||||
|
||||
interface Slot {
|
||||
id?: string;
|
||||
slotDate?: string;
|
||||
recipe?: { id?: string; name?: string } | null;
|
||||
}
|
||||
|
||||
let {
|
||||
weekStart,
|
||||
slots = [],
|
||||
selectedDay,
|
||||
today,
|
||||
onselectDay
|
||||
}: {
|
||||
weekStart: string;
|
||||
slots?: Slot[];
|
||||
selectedDay: string;
|
||||
today: string;
|
||||
onselectDay?: (day: string) => void;
|
||||
} = $props();
|
||||
|
||||
let days = $derived(weekDays(weekStart));
|
||||
let slotMap = $derived(
|
||||
Object.fromEntries(slots.map((s) => [s.slotDate!, s]))
|
||||
);
|
||||
|
||||
function selectDay(day: string) {
|
||||
onselectDay?.(day);
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="grid grid-cols-7 gap-[2px] md:gap-[6px]">
|
||||
{#each days as day}
|
||||
{@const isSelected = day === selectedDay}
|
||||
{@const isTodayDay = day === today}
|
||||
{@const hasMeal = !!slotMap[day]?.recipe}
|
||||
{@const dateNum = day.slice(-2).replace(/^0/, '')}
|
||||
{@const abbr = formatDayAbbr(day, 'narrow')}
|
||||
|
||||
<button
|
||||
type="button"
|
||||
data-testid="day-chip-{day}"
|
||||
data-selected={isSelected}
|
||||
data-today={isTodayDay}
|
||||
onclick={() => selectDay(day)}
|
||||
class="flex flex-col items-center rounded-[10px] px-1 py-2 transition-colors
|
||||
{isTodayDay ? 'border border-[var(--yellow-light)] bg-[var(--yellow-tint)]' : ''}
|
||||
{isSelected && !isTodayDay ? 'border border-[var(--green-light)] bg-[var(--green-tint)]' : ''}
|
||||
{!isTodayDay && !isSelected ? 'border border-transparent' : ''}"
|
||||
>
|
||||
<span class="font-[var(--font-sans)] text-[7px] uppercase tracking-wide text-[var(--color-text-muted)] md:text-[10px]">
|
||||
{abbr}
|
||||
</span>
|
||||
<span class="font-[var(--font-sans)] text-[11px] font-medium text-[var(--color-text)] md:text-[14px]">
|
||||
{dateNum}
|
||||
</span>
|
||||
<!-- Dot indicator -->
|
||||
<span
|
||||
data-testid="dot-{day}"
|
||||
data-has-meal={hasMeal}
|
||||
class="mt-1 h-[3px] w-[3px] rounded-full
|
||||
{hasMeal ? 'bg-[var(--green)]' : ''}
|
||||
{!hasMeal && isTodayDay ? 'bg-[var(--yellow-text)]' : ''}
|
||||
{!hasMeal && !isTodayDay ? 'bg-[var(--color-border)]' : ''}"
|
||||
></span>
|
||||
</button>
|
||||
{/each}
|
||||
</div>
|
||||
Reference in New Issue
Block a user