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>
This commit is contained in:
139
client/src/lib/catalog.ts
Normal file
139
client/src/lib/catalog.ts
Normal file
@@ -0,0 +1,139 @@
|
||||
import type {
|
||||
CategoryId,
|
||||
CatalogItem,
|
||||
ExtraItem,
|
||||
FoodCategory,
|
||||
FoodItem,
|
||||
LocalizedString,
|
||||
PlanRecipeType,
|
||||
Recipe,
|
||||
TagType,
|
||||
TimeSlot,
|
||||
} from "@/types/domain"
|
||||
import extrasRaw from "@/data/extras.json"
|
||||
import foodsRaw from "@/data/foods.json"
|
||||
import recipesRaw from "@/data/recipes.json"
|
||||
import stepsRaw from "@/data/steps.json"
|
||||
|
||||
interface RawFoodItem {
|
||||
id: string
|
||||
name: LocalizedString
|
||||
v: LocalizedString
|
||||
f?: TagType
|
||||
approx?: boolean
|
||||
}
|
||||
|
||||
interface RawFoodCategory {
|
||||
id: CategoryId
|
||||
cls: string
|
||||
title: LocalizedString
|
||||
thumb: LocalizedString
|
||||
items: RawFoodItem[]
|
||||
}
|
||||
|
||||
export interface PlateStep {
|
||||
n: number
|
||||
cls: string
|
||||
title: LocalizedString
|
||||
qty: LocalizedString
|
||||
text: LocalizedString
|
||||
}
|
||||
|
||||
function mapItem(cat: CategoryId, item: RawFoodItem): FoodItem {
|
||||
return {
|
||||
id: item.id,
|
||||
cat,
|
||||
name: item.name,
|
||||
valueInfo: item.v,
|
||||
tag: item.f,
|
||||
isApprox: item.approx,
|
||||
}
|
||||
}
|
||||
|
||||
export const foodCategories: FoodCategory[] = (foodsRaw as RawFoodCategory[]).map(
|
||||
(category) => ({
|
||||
id: category.id,
|
||||
cls: category.cls,
|
||||
title: category.title,
|
||||
thumb: category.thumb,
|
||||
items: category.items.map((item) => mapItem(category.id, item)),
|
||||
}),
|
||||
)
|
||||
|
||||
export const foods: FoodItem[] = foodCategories.flatMap((category) => category.items)
|
||||
|
||||
export const foodById: Record<string, FoodItem> = Object.fromEntries(
|
||||
foods.map((item) => [item.id, item]),
|
||||
)
|
||||
|
||||
export const extras: ExtraItem[] = extrasRaw as ExtraItem[]
|
||||
|
||||
export const extraById: Record<string, ExtraItem> = Object.fromEntries(
|
||||
extras.map((item) => [item.id, item]),
|
||||
)
|
||||
|
||||
export const recipes: Recipe[] = recipesRaw as Recipe[]
|
||||
|
||||
export const plateSteps: PlateStep[] = stepsRaw as PlateStep[]
|
||||
|
||||
export const PLAN_TYPE_ORDER: PlanRecipeType[] = [
|
||||
"breakfast",
|
||||
"snack",
|
||||
"full",
|
||||
"recipe",
|
||||
]
|
||||
|
||||
export function foodsInCategory(cat: CategoryId): FoodItem[] {
|
||||
return foodCategories.find((category) => category.id === cat)?.items ?? []
|
||||
}
|
||||
|
||||
export function resolveCatalogId(id: string): CatalogItem | null {
|
||||
const food = foodById[id]
|
||||
if (food) {
|
||||
return {
|
||||
id: food.id,
|
||||
name: food.name,
|
||||
source: "cat",
|
||||
cat: food.cat,
|
||||
valueInfo: food.valueInfo,
|
||||
tag: food.tag,
|
||||
}
|
||||
}
|
||||
const extra = extraById[id]
|
||||
if (extra) {
|
||||
return {
|
||||
id: extra.id,
|
||||
name: extra.name,
|
||||
source: "extra",
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
export function resolveRecipeUses(recipe: Recipe): CatalogItem[] {
|
||||
return recipe.uses
|
||||
.map((id) => resolveCatalogId(id))
|
||||
.filter((item): item is CatalogItem => Boolean(item))
|
||||
}
|
||||
|
||||
export function recipesByType(type: PlanRecipeType): Recipe[] {
|
||||
return recipes.filter((recipe) => recipe.type === type)
|
||||
}
|
||||
|
||||
/** Map plan recipe types onto suggestion time slots. */
|
||||
export function timeSlotsForRecipe(recipe: Recipe): TimeSlot[] {
|
||||
switch (recipe.type) {
|
||||
case "breakfast":
|
||||
return ["morning"]
|
||||
case "snack":
|
||||
return ["afternoon"]
|
||||
case "full":
|
||||
return ["midday", "evening"]
|
||||
case "recipe":
|
||||
return ["morning", "afternoon", "midday", "evening"]
|
||||
}
|
||||
}
|
||||
|
||||
export function mealTypeForRecipe(recipe: Recipe): "full" | "half" {
|
||||
return recipe.type === "full" ? "full" : "half"
|
||||
}
|
||||
Reference in New Issue
Block a user