2026-10-31 20:44:29 -05:00
|
|
|
|
import type { Recipe, RecipeIngredient } from "@/app/types";
|
|
|
|
|
|
import { CATALOG_BY_ID } from "./catalog";
|
|
|
|
|
|
|
|
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
|
|
// Human-friendly ingredient lines, scaled to a serving count.
|
|
|
|
|
|
//
|
|
|
|
|
|
// ing("white_rice", 1.5, "cup") for 4 → shown for 2 as "¾ cup Long Grain White Rice"
|
|
|
|
|
|
// ing("black_beans", 1, "each") → "1 × Black Beans, canned (15 oz can)"
|
|
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
|
|
const FRACTIONS: [number, string][] = [
|
|
|
|
|
|
[0, ""],
|
|
|
|
|
|
[1 / 8, "⅛"],
|
|
|
|
|
|
[1 / 4, "¼"],
|
|
|
|
|
|
[1 / 3, "⅓"],
|
|
|
|
|
|
[1 / 2, "½"],
|
|
|
|
|
|
[2 / 3, "⅔"],
|
|
|
|
|
|
[3 / 4, "¾"],
|
|
|
|
|
|
[1, ""],
|
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
|
|
/** 0.75 → "¾", 1.5 → "1½", 2.33 → "2⅓", 13.2 → "13". */
|
Move UI to BoardUI with slate + salmon theme; add PDF plan export
- Adopt BoardUI (Pro init): theme tokens, Inter, .dark mode, Button,
SegmentedControl, Meter, Skeleton and ThemeToggle; remap the app's
color aliases onto BoardUI tokens and swap lucide for Remix icons
- Retint BoardUI's neutral ramp to cool slate with a salmon accent;
drop the dotted background; raise muted text contrast
- Simplify the landing page and remove the overlap explorer
- Planner: explicit "I have this" toggle on grocery lines, pantry panel
above the menu with the staples list spelled out, sticky aisle
header fix, and a "Download PDF" plan export (jsPDF, lazy-loaded)
- Recipes: replace the family cabbage soup with Cade's Cabbage Soup
(adds Bone-In Ham Steak to the catalog); remove Ham, Bean & Cabbage Soup
- Remove unused BoardUI components, dependencies and exports
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-06 16:36:33 -06:00
|
|
|
|
function formatQuantity(value: number): string {
|
2026-10-31 20:44:29 -05:00
|
|
|
|
if (value >= 10) return String(Math.round(value));
|
|
|
|
|
|
const whole = Math.floor(value);
|
|
|
|
|
|
const fraction = value - whole;
|
|
|
|
|
|
const [nearest, glyph] = FRACTIONS.reduce((best, f) => (Math.abs(f[0] - fraction) < Math.abs(best[0] - fraction) ? f : best));
|
|
|
|
|
|
const total = whole + (nearest === 1 ? 1 : 0);
|
|
|
|
|
|
if (total === 0 && glyph === "") return "⅛"; // never show "0" for a real ingredient
|
|
|
|
|
|
return `${total > 0 ? total : ""}${glyph}` || "0";
|
|
|
|
|
|
}
|
|
|
|
|
|
|
Move UI to BoardUI with slate + salmon theme; add PDF plan export
- Adopt BoardUI (Pro init): theme tokens, Inter, .dark mode, Button,
SegmentedControl, Meter, Skeleton and ThemeToggle; remap the app's
color aliases onto BoardUI tokens and swap lucide for Remix icons
- Retint BoardUI's neutral ramp to cool slate with a salmon accent;
drop the dotted background; raise muted text contrast
- Simplify the landing page and remove the overlap explorer
- Planner: explicit "I have this" toggle on grocery lines, pantry panel
above the menu with the staples list spelled out, sticky aisle
header fix, and a "Download PDF" plan export (jsPDF, lazy-loaded)
- Recipes: replace the family cabbage soup with Cade's Cabbage Soup
(adds Bone-In Ham Steak to the catalog); remove Ham, Bean & Cabbage Soup
- Remove unused BoardUI components, dependencies and exports
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-06 16:36:33 -06:00
|
|
|
|
interface IngredientLine {
|
2026-10-31 20:44:29 -05:00
|
|
|
|
quantity: string;
|
|
|
|
|
|
/** e.g. "cup", "lb", or "×" for counted items. */
|
|
|
|
|
|
unit: string;
|
|
|
|
|
|
name: string;
|
|
|
|
|
|
/** Package hint for counted items, e.g. "15 oz can". */
|
|
|
|
|
|
detail?: string;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
Move UI to BoardUI with slate + salmon theme; add PDF plan export
- Adopt BoardUI (Pro init): theme tokens, Inter, .dark mode, Button,
SegmentedControl, Meter, Skeleton and ThemeToggle; remap the app's
color aliases onto BoardUI tokens and swap lucide for Remix icons
- Retint BoardUI's neutral ramp to cool slate with a salmon accent;
drop the dotted background; raise muted text contrast
- Simplify the landing page and remove the overlap explorer
- Planner: explicit "I have this" toggle on grocery lines, pantry panel
above the menu with the staples list spelled out, sticky aisle
header fix, and a "Download PDF" plan export (jsPDF, lazy-loaded)
- Recipes: replace the family cabbage soup with Cade's Cabbage Soup
(adds Bone-In Ham Steak to the catalog); remove Ham, Bean & Cabbage Soup
- Remove unused BoardUI components, dependencies and exports
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-06 16:36:33 -06:00
|
|
|
|
function ingredientLine(ingredient: RecipeIngredient, scale: number): IngredientLine {
|
2026-10-31 20:44:29 -05:00
|
|
|
|
const item = CATALOG_BY_ID.get(ingredient.itemId);
|
|
|
|
|
|
const name = item?.name ?? ingredient.itemId;
|
|
|
|
|
|
const quantity = formatQuantity(ingredient.amount * scale);
|
|
|
|
|
|
|
|
|
|
|
|
if (ingredient.unit === "each") {
|
|
|
|
|
|
// Single-unit packages (a can, a jar) read better with the package size.
|
|
|
|
|
|
const detail = item && item.packageSize === 1 && item.packageLabel !== "each" ? item.packageLabel : undefined;
|
|
|
|
|
|
return { quantity, unit: "×", name, detail };
|
|
|
|
|
|
}
|
|
|
|
|
|
return { quantity, unit: ingredient.unit, name };
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export function ingredientLines(recipe: Recipe, servings: number): IngredientLine[] {
|
|
|
|
|
|
const scale = servings / recipe.servings;
|
|
|
|
|
|
return recipe.ingredients.map((i) => ingredientLine(i, scale));
|
|
|
|
|
|
}
|