Files
pocket-pascal/client/src/components/home/SuggestionCard.tsx
Steffi Müller a482bf5b09 Add Phase 1 Pocket Pascal UI: home, pantry, week plan, and plate.
Replace the hello-world shell with the elevated design system, bilingual screens, recipe/extras data, and the interactive Mein Teller view.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-04 12:22:32 +02:00

129 lines
4.3 KiB
TypeScript

import { useMemo, useState } from "react"
import { useNavigate } from "react-router-dom"
import { Pin, RefreshCw } from "lucide-react"
import { Button } from "@/components/ui-pp/Button"
import { GlassCard } from "@/components/ui-pp/GlassCard"
import { t, tr, ui } from "@/i18n/ui"
import { useLanguage } from "@/lib/language"
import { cx } from "@/lib/cx"
import { buildSuggestion, reshuffleSuggestion } from "@/lib/suggestion"
import { formatClock, getTimeContext } from "@/lib/timeContext"
import type { FoodItem, Recipe, TimeSlot } from "@/types/domain"
const CAT_DOT: Record<string, string> = {
protein: "var(--color-protein)",
obst: "var(--color-obstgem)",
gemuese: "var(--color-obstgem)",
fett: "var(--color-fett)",
kh: "var(--color-kh)",
snack: "var(--color-protein)",
}
function slotLabel(slot: TimeSlot, lang: "de" | "en"): string {
return t(ui.suggestion[slot], lang)
}
export function SuggestionCard() {
const { lang } = useLanguage()
const navigate = useNavigate()
const context = useMemo(() => getTimeContext(), [])
const [suggestion, setSuggestion] = useState(() => buildSuggestion(context.slot))
const [pinnedIds, setPinnedIds] = useState<Set<string>>(() => new Set())
const recipe: Recipe | null = suggestion.recipe
const items: FoodItem[] = suggestion.foods
const clock = formatClock(context.hour, context.minute, lang)
const header =
lang === "de"
? `${clock} ${t(ui.suggestion.timeSuffix, lang)} · ${slotLabel(context.slot, lang)}`
: `${clock} · ${slotLabel(context.slot, lang)}`
const title = recipe ? tr(recipe.name, lang) : t(ui.suggestion.fallbackName, lang)
function togglePin(id: string) {
setPinnedIds((current) => {
const next = new Set(current)
if (next.has(id)) next.delete(id)
else next.add(id)
return next
})
}
function reroll() {
if (pinnedIds.size === 0) {
setSuggestion(buildSuggestion(context.slot, recipe?.id))
return
}
setSuggestion({
recipe: null,
foods: reshuffleSuggestion(items, pinnedIds, context.slot),
})
}
function sendToBuilder() {
navigate("/builder", {
state: {
ingredientIds: items.map((item) => item.id),
source: "suggestion" as const,
},
})
}
return (
<GlassCard className="p-4">
<div className="flex flex-col gap-4">
<div className="flex flex-col gap-1">
<p className="type-label text-[var(--ink-faint)]">{header}</p>
<h2 className="type-title text-[var(--ink)]">{title}</h2>
</div>
<div className="flex flex-wrap gap-2">
{items.map((item) => {
const pinned = pinnedIds.has(item.id)
return (
<button
key={item.id}
type="button"
onClick={() => togglePin(item.id)}
aria-pressed={pinned}
aria-label={`${tr(item.name, lang)}${pinned ? t(ui.suggestion.unpin, lang) : t(ui.suggestion.pin, lang)}`}
className={cx(
"elevation-transition inline-flex min-h-[48px] items-center gap-2 rounded-[var(--radius-pill)] px-3",
"type-body-emphasis text-[var(--ink)]",
"ring-1 ring-[var(--border)]",
"active:translate-y-px active:brightness-95",
pinned && "ring-2 ring-[var(--color-pick)]/50",
)}
style={{ background: "var(--glass-fill)" }}
>
<span
aria-hidden
className="h-2.5 w-2.5 rounded-full"
style={{ background: CAT_DOT[item.cat] ?? "var(--ink-faint)" }}
/>
<span>{tr(item.name, lang)}</span>
{pinned ? (
<Pin className="h-3.5 w-3.5 text-[var(--color-pick)]" aria-hidden />
) : null}
</button>
)
})}
</div>
<div className="flex flex-col gap-2 sm:flex-row">
<Button variant="secondary" className="flex-1" onClick={reroll}>
<RefreshCw className="h-4 w-4" aria-hidden />
{t(ui.suggestion.reroll, lang)}
</Button>
<Button className="flex-1" onClick={sendToBuilder}>
{t(ui.suggestion.toBuilder, lang)}
</Button>
</div>
</div>
</GlassCard>
)
}