init
This commit is contained in:
commit
c19b910c36
80 changed files with 16134 additions and 0 deletions
181
app/types/index.ts
Normal file
181
app/types/index.ts
Normal file
|
|
@ -0,0 +1,181 @@
|
|||
// ---------------------------------------------------------------------------
|
||||
// Shared types for the whole app (client + server).
|
||||
// Keep this file dependency-free so it can be imported anywhere.
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export type Appliance = "stovetop" | "oven" | "air_fryer" | "slow_cooker" | "microwave" | "blender" | "rice_cooker";
|
||||
|
||||
export type Cuisine =
|
||||
| "american"
|
||||
| "southern"
|
||||
| "mexican"
|
||||
| "caribbean"
|
||||
| "italian"
|
||||
| "mediterranean"
|
||||
| "middle_eastern"
|
||||
| "indian"
|
||||
| "chinese"
|
||||
| "japanese"
|
||||
| "korean"
|
||||
| "thai";
|
||||
|
||||
/** "caydens_corner" marks the family recipes collection. */
|
||||
export type CookingStyle = "comfort" | "quick" | "healthy" | "meal_prep" | "vegetarian" | "seafood" | "caydens_corner";
|
||||
|
||||
/**
|
||||
* Breakfast slots only get breakfast recipes; lunch/dinner slots get mains.
|
||||
* Sides and desserts live in the library and can be added to a slot by hand,
|
||||
* but auto-fill never picks them.
|
||||
*/
|
||||
export type MealKind = "breakfast" | "main" | "side" | "dessert";
|
||||
|
||||
// --- Units -----------------------------------------------------------------
|
||||
|
||||
/** Units recipes are written in (what a human would type). */
|
||||
export type RecipeUnit = "g" | "oz" | "lb" | "ml" | "tsp" | "tbsp" | "cup" | "each";
|
||||
|
||||
/** Units the store catalog is priced in. Every recipe unit converts to one of these. */
|
||||
export type BaseUnit = "g" | "ml" | "each";
|
||||
|
||||
// --- Store catalog ---------------------------------------------------------
|
||||
|
||||
export type ItemId = string;
|
||||
|
||||
export type Aisle = "Meat & Eggs" | "Seafood" | "Dairy" | "Produce" | "Frozen" | "Pantry" | "International" | "Spices & Sauces" | "Bakery";
|
||||
|
||||
/** One purchasable product, e.g. "Long Grain White Rice, 2 lb bag". */
|
||||
export interface StoreItem {
|
||||
id: ItemId;
|
||||
name: string;
|
||||
aisle: Aisle;
|
||||
/** How much is in one package, in `baseUnit`. */
|
||||
packageSize: number;
|
||||
baseUnit: BaseUnit;
|
||||
/** Human label for one package, e.g. "2 lb bag". */
|
||||
packageLabel: string;
|
||||
/** Price of one package, USD. */
|
||||
price: number;
|
||||
/**
|
||||
* Lets weight-priced items be measured by volume in recipes
|
||||
* (e.g. rice: 1 cup ≈ 185 g → 0.78 g/ml). Only needed for `g` items.
|
||||
*/
|
||||
gramsPerMl?: number;
|
||||
/** Salt, oil, etc. Marked "have it" by default for new users. */
|
||||
pantryStaple?: boolean;
|
||||
}
|
||||
|
||||
// --- Recipes ---------------------------------------------------------------
|
||||
|
||||
export interface RecipeIngredient {
|
||||
/** Points at a StoreItem. Cost is derived from the catalog, never hard-coded per recipe. */
|
||||
itemId: ItemId;
|
||||
amount: number;
|
||||
unit: RecipeUnit;
|
||||
/** Optional display note, e.g. "diced". */
|
||||
note?: string;
|
||||
}
|
||||
|
||||
export interface Recipe {
|
||||
id: string;
|
||||
title: string;
|
||||
description: string;
|
||||
cuisine: Cuisine;
|
||||
styles: CookingStyle[];
|
||||
kind: MealKind;
|
||||
/** Minutes, hands-on + cook. */
|
||||
prepTime: number;
|
||||
/** ALL of these are needed. An empty array means no-cook. */
|
||||
requiredAppliances: Appliance[];
|
||||
/** How many people one batch feeds. Ingredients scale to household size. */
|
||||
servings: number;
|
||||
ingredients: RecipeIngredient[];
|
||||
/** Cooking instructions, in order. Kept in recipes/steps/ and joined in by recipes/index.ts. */
|
||||
steps: string[];
|
||||
/** Optional cook's tip shown under the steps. */
|
||||
tip?: string;
|
||||
}
|
||||
|
||||
/** A recipe as written in the data files, before its steps are attached. */
|
||||
export type RecipeData = Omit<Recipe, "steps">;
|
||||
|
||||
// --- Plan state --------------------------------------------------------------
|
||||
|
||||
export interface MealSlot {
|
||||
dayIndex: number; // 0-6
|
||||
mealNumber: number; // 1-3
|
||||
recipeId: string | null;
|
||||
}
|
||||
|
||||
export interface MealPlanState {
|
||||
targetBudget: number;
|
||||
daysCount: number; // 1-7
|
||||
mealsPerDay: number; // 1-3
|
||||
householdSize: number; // 1-6
|
||||
selectedAppliances: Appliance[];
|
||||
/** Empty = any cuisine. */
|
||||
preferredCuisines: Cuisine[];
|
||||
/** Empty = any style. */
|
||||
preferredStyles: CookingStyle[];
|
||||
/** Never plan recipes that use these store items. */
|
||||
avoidedItems: ItemId[];
|
||||
/** Already in the kitchen: listed on the grocery list but cost $0. */
|
||||
onHandItems: ItemId[];
|
||||
/**
|
||||
* Keyed "<day>-<meal>" (day 0 = Mon; meal 1/2/3 = Breakfast/Lunch/Dinner) → recipe id.
|
||||
* Only filled slots are stored. Slots outside daysCount/mealsPerDay are kept but hidden.
|
||||
*/
|
||||
slots: Record<string, string>;
|
||||
}
|
||||
|
||||
// --- Grocery output ----------------------------------------------------------
|
||||
|
||||
export interface GroceryLine {
|
||||
item: StoreItem;
|
||||
/** Total amount all recipes need, in the item's base unit. */
|
||||
neededAmount: number;
|
||||
/** Whole packages to buy. */
|
||||
packages: number;
|
||||
/** What you pay at checkout for this line. */
|
||||
checkoutCost: number;
|
||||
/** Value of what actually gets eaten this week. */
|
||||
usedCost: number;
|
||||
/** Left over after the week, in base unit (goes to your pantry). */
|
||||
leftoverAmount: number;
|
||||
/** User marked this item as already in their kitchen. */
|
||||
onHand: boolean;
|
||||
/** Recipe titles that use this item. Length > 1 means ingredient overlap. */
|
||||
usedBy: string[];
|
||||
}
|
||||
|
||||
export interface GrocerySummary {
|
||||
lines: GroceryLine[];
|
||||
/** Sum of checkout costs (excludes on-hand lines). */
|
||||
checkoutTotal: number;
|
||||
/** Value of food actually eaten this plan. */
|
||||
usedTotal: number;
|
||||
/** checkoutTotal - usedTotal: what you carry into next week. */
|
||||
carryoverValue: number;
|
||||
/** How many store items are shared by 2+ recipes. */
|
||||
sharedItemCount: number;
|
||||
}
|
||||
|
||||
/** Per-ingredient setting in the Ingredients panel. */
|
||||
export type ItemPreference = "avoid" | "use" | "have";
|
||||
|
||||
// --- Server action contract -------------------------------------------------
|
||||
|
||||
export type FillMode = "empty" | "all";
|
||||
|
||||
export interface GeneratePlanInput {
|
||||
state: MealPlanState;
|
||||
fillMode: FillMode;
|
||||
/** Changing the seed gives a different (but still cost-optimized) plan. */
|
||||
seed: number;
|
||||
}
|
||||
|
||||
export interface GeneratePlanResult {
|
||||
slots: Record<string, string>;
|
||||
summary: GrocerySummary;
|
||||
/** Human-readable notes, e.g. when filters had to be relaxed. */
|
||||
notes: string[];
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue