"use client"; import { useState } from "react"; import { useTranslations } from "next-intl"; import { Minus, Plus, Sparkles, X } from "lucide-react"; import { Button } from "@/components/ui/button"; import { formatIngredientQuantity, type UnitPref } from "@/lib/unit-conversion"; import { SubstituteIngredientPopover } from "@/components/recipe/substitute-ingredient-popover"; type Ingredient = { id: string; rawName: string; quantity: string | null; unit: string | null; note: string | null; order: number; }; export type ScaledIngredient = { rawName: string; quantity: string; unit: string | null; note?: string; }; export function ServingScaler({ baseServings, ingredients, recipeTitle, recipeId, onAiScale, unitPref = "metric", }: { baseServings: number; ingredients: Ingredient[]; recipeTitle?: string; recipeId?: string; onAiScale?: (ingredients: ScaledIngredient[] | null) => void; unitPref?: UnitPref; }) { const t = useTranslations("servingScaler"); const [servings, setServings] = useState(baseServings); const [aiScaledIngredients, setAiScaledIngredients] = useState(null); const [aiScaling, setAiScaling] = useState(false); const min = 1; const max = 100; async function handleAiScale() { if (!recipeId) return; setAiScaling(true); try { const res = await fetch("/api/v1/ai/scale", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ recipeId, targetServings: servings }), }); if (!res.ok) return; const data = await res.json() as { ingredients: ScaledIngredient[] }; setAiScaledIngredients(data.ingredients); onAiScale?.(data.ingredients); } finally { setAiScaling(false); } } function dismissAiScale() { setAiScaledIngredients(null); onAiScale?.(null); } return (
{t("servings")}
{servings}
{servings !== baseServings && ( )} {recipeId && servings !== baseServings && ( )}
{aiScaledIngredients && (
{t("aiScaledNote")}
)} {/* grid + `contents` on each
  • , not flex — a flex child's quantity column only has a *minimum* width, so it drifts row-to-row once any value's text (e.g. an appended "(~2 tbsp)" conversion) exceeds that minimum. A shared grid track sizes to the widest cell across every row, so the name column lines up regardless of quantity length. */}
      {ingredients .sort((a, b) => a.order - b.order) .map((ing) => { const aiIng = aiScaledIngredients?.find((s) => s.rawName === ing.rawName); return (
    • {aiIng ? formatIngredientQuantity(aiIng.quantity, aiIng.unit, unitPref) : formatIngredientQuantity(ing.quantity, ing.unit, unitPref, { base: baseServings, desired: servings, })} {ing.rawName} {(aiIng?.note ?? ing.note) && ( , {aiIng?.note ?? ing.note} )} {recipeTitle && }
    • ); })}
  • ); }