planmymeal/app/lib/display.ts

59 lines
2.2 KiB
TypeScript
Raw Normal View History

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". */
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";
}
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;
}
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));
}