planmymeal/app/lib/display.ts
Cayden Schwechten c19b910c36 init
2026-10-31 20:44:29 -05:00

58 lines
2.2 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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". */
export function formatQuantity(value: number): string {
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";
}
export interface IngredientLine {
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;
}
export function ingredientLine(ingredient: RecipeIngredient, scale: number): IngredientLine {
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));
}