9d9dfb46c6
Cooking mode step-by-step view. OpenAPI 3.1 spec auto-generated from Zod schemas. Email lib (resend). Redis client. S3 storage helper. Photo upload endpoint. Landing page. Short recipe URL redirect /r/[id]. API docs page.
46 lines
1.1 KiB
TypeScript
46 lines
1.1 KiB
TypeScript
const FRACTIONS: [number, string][] = [
|
|
[1 / 8, "⅛"],
|
|
[1 / 4, "¼"],
|
|
[1 / 3, "⅓"],
|
|
[3 / 8, "⅜"],
|
|
[1 / 2, "½"],
|
|
[5 / 8, "⅝"],
|
|
[2 / 3, "⅔"],
|
|
[3 / 4, "¾"],
|
|
[7 / 8, "⅞"],
|
|
];
|
|
|
|
export function formatQuantity(value: number): string {
|
|
if (value === 0) return "0";
|
|
|
|
const whole = Math.floor(value);
|
|
const decimal = value - whole;
|
|
|
|
if (decimal < 0.05) return whole === 0 ? "0" : String(whole);
|
|
if (decimal > 0.95) return String(whole + 1);
|
|
|
|
let bestFraction = "";
|
|
let bestDiff = Infinity;
|
|
for (const [frac, symbol] of FRACTIONS) {
|
|
const diff = Math.abs(decimal - frac);
|
|
if (diff < bestDiff) {
|
|
bestDiff = diff;
|
|
bestFraction = symbol;
|
|
}
|
|
}
|
|
|
|
if (bestDiff > 0.06) {
|
|
return value.toFixed(1);
|
|
}
|
|
|
|
return whole === 0 ? bestFraction : `${whole} ${bestFraction}`;
|
|
}
|
|
|
|
export function scaleQuantity(baseQuantity: string | null, base: number, desired: number): string {
|
|
if (!baseQuantity) return "";
|
|
const raw = parseFloat(baseQuantity);
|
|
if (isNaN(raw)) return baseQuantity;
|
|
const scale = base === 0 ? 1 : desired / base;
|
|
return formatQuantity(raw * scale);
|
|
}
|