95 lines
3.3 KiB
TypeScript
95 lines
3.3 KiB
TypeScript
import type { UnitPrice } from '../types';
|
|
|
|
const METRIC_UNITS = ['g', 'kg', 'ml', 'l'];
|
|
const IMPERIAL_UNITS = ['oz', 'lb', 'fl oz'];
|
|
|
|
const CONVERSIONS: Record<string, { to: string; factor: number }> = {
|
|
// metric to imperial
|
|
g: { to: 'oz', factor: 0.035274 },
|
|
kg: { to: 'lb', factor: 2.20462 },
|
|
ml: { to: 'fl oz', factor: 0.033814 },
|
|
l: { to: 'fl oz', factor: 33.814 },
|
|
|
|
// imperial to metric
|
|
oz: { to: 'g', factor: 28.3495 },
|
|
lb: { to: 'kg', factor: 0.453592 },
|
|
'fl oz': { to: 'ml', factor: 29.5735 },
|
|
};
|
|
|
|
interface FormattedPrice {
|
|
price: string;
|
|
unit: string | null;
|
|
}
|
|
|
|
/**
|
|
* Converts a unit price to the target system and formats it for display.
|
|
* @param unitPrice The structured unit price object from the database.
|
|
* @param system The target system ('metric' or 'imperial').
|
|
* @returns An object with formatted price and unit strings.
|
|
*/
|
|
export const formatUnitPrice = (unitPrice: UnitPrice | null | undefined, system: 'metric' | 'imperial'): FormattedPrice => {
|
|
if (!unitPrice || typeof unitPrice.value !== 'number' || !unitPrice.unit) {
|
|
return { price: '—', unit: null };
|
|
}
|
|
|
|
const { value, unit } = unitPrice;
|
|
const isMetric = METRIC_UNITS.includes(unit);
|
|
const isImperial = IMPERIAL_UNITS.includes(unit);
|
|
|
|
let displayValue = value;
|
|
let displayUnit = unit;
|
|
|
|
if (system === 'imperial' && isMetric) {
|
|
const conversion = CONVERSIONS[unit];
|
|
if (conversion) {
|
|
displayValue = value * conversion.factor;
|
|
displayUnit = conversion.to;
|
|
}
|
|
} else if (system === 'metric' && isImperial) {
|
|
const conversion = CONVERSIONS[unit];
|
|
if (conversion) {
|
|
displayValue = value * conversion.factor;
|
|
displayUnit = conversion.to;
|
|
}
|
|
}
|
|
|
|
// Smart formatting for price
|
|
const formattedPrice = displayValue < 0.10
|
|
? displayValue.toLocaleString('en-US', { style: 'currency', currency: 'USD', minimumFractionDigits: 3, maximumFractionDigits: 3 })
|
|
: displayValue.toLocaleString('en-US', { style: 'currency', currency: 'USD', minimumFractionDigits: 2, maximumFractionDigits: 2 });
|
|
|
|
// Always show a unit if one exists for clarity
|
|
if (displayUnit === 'each') {
|
|
return { price: formattedPrice, unit: '/each' };
|
|
}
|
|
|
|
return { price: formattedPrice, unit: `/${displayUnit}` };
|
|
};
|
|
|
|
|
|
/**
|
|
* Converts an imperial unit price to its metric equivalent for database storage.
|
|
* @param unitPrice The structured unit price object, potentially in imperial units.
|
|
* @returns A unit price object with metric units, or the original if already metric or not applicable.
|
|
*/
|
|
export const convertToMetric = (unitPrice: UnitPrice | null | undefined): UnitPrice | null | undefined => {
|
|
if (!unitPrice || typeof unitPrice.value !== 'number' || !unitPrice.unit) {
|
|
return unitPrice;
|
|
}
|
|
|
|
const { value, unit } = unitPrice;
|
|
const isImperial = IMPERIAL_UNITS.includes(unit);
|
|
|
|
if (isImperial) {
|
|
const conversion = CONVERSIONS[unit];
|
|
if (conversion) {
|
|
return {
|
|
value: value * conversion.factor,
|
|
unit: conversion.to,
|
|
};
|
|
}
|
|
}
|
|
|
|
// Return original if it's already metric or not a weight/volume unit (like 'each')
|
|
return unitPrice;
|
|
}; |