122 lines
4.1 KiB
TypeScript
122 lines
4.1 KiB
TypeScript
import { weightStandardData } from '../mockData/weightStandardData.ts';
|
|
|
|
// Using the 43g Day Old chick weight standard as default for this app
|
|
const standardTargetData = weightStandardData.data.find((row) => row.day === 43)?.values;
|
|
|
|
/** Converts grams to kg via string math to avoid floating-point artifacts. */
|
|
function formatGramsAsKgExact(grams: number): string {
|
|
const isNegative = grams < 0;
|
|
const absValue = Math.abs(grams);
|
|
const parts = String(absValue).split('.');
|
|
const intPart = parts[0] ?? '0';
|
|
const fracPart = parts[1] ?? '';
|
|
const kgDecimalIndex = intPart.length - 3;
|
|
const allDigits = intPart + fracPart;
|
|
|
|
let kgStr: string;
|
|
if (kgDecimalIndex <= 0) {
|
|
kgStr = `0.${'0'.repeat(-kgDecimalIndex)}${allDigits}`;
|
|
} else {
|
|
kgStr = `${allDigits.slice(0, kgDecimalIndex)}.${allDigits.slice(kgDecimalIndex)}`;
|
|
}
|
|
|
|
const [kgInt, kgFrac = ''] = kgStr.split('.');
|
|
const formatted = kgFrac
|
|
? `${Number(kgInt).toLocaleString('id-ID')},${kgFrac}`
|
|
: Number(kgInt).toLocaleString('id-ID');
|
|
return isNegative ? `-${formatted}` : formatted;
|
|
}
|
|
|
|
/** Formats a number exactly while keeping locale-friendly separators. */
|
|
function formatExactNumber(value: number): string {
|
|
const isNegative = value < 0;
|
|
const absValue = Math.abs(value);
|
|
const parts = String(absValue).split('.');
|
|
const intPart = parts[0] ?? '0';
|
|
const fracPart = parts[1] ?? '';
|
|
const formatted = fracPart
|
|
? `${Number(intPart).toLocaleString('id-ID')},${fracPart}`
|
|
: Number(intPart).toLocaleString('id-ID');
|
|
return isNegative ? `-${formatted}` : formatted;
|
|
}
|
|
|
|
/**
|
|
* Gets the target weight for a specific day in the cycle.
|
|
* @param day The day of the cycle (e.g., 1 to 35).
|
|
* @returns The target weight in grams, or null if not available.
|
|
*/
|
|
export const getTargetWeightForDay = (day: number): number | null => {
|
|
if (!standardTargetData) {
|
|
return null;
|
|
}
|
|
// day is 1-based, array is 0-based
|
|
if (day > 0 && day <= standardTargetData.length) {
|
|
return standardTargetData[day - 1] ?? null;
|
|
}
|
|
return null;
|
|
};
|
|
|
|
/**
|
|
* Formats weight values with intelligent unit conversion.
|
|
* Converts grams to kilograms when value exceeds 999g.
|
|
* Uses smart decimals: whole numbers show no decimals, fractional show 1 decimal.
|
|
*
|
|
* @param grams - The weight value in grams
|
|
* @param options - Optional formatting configuration
|
|
* @returns Formatted weight string with unit (e.g., "750 g", "1,6 kg", "450 kg")
|
|
*
|
|
* @example
|
|
* formatWeight(450) // "450 g"
|
|
* formatWeight(1583) // "1,6 kg"
|
|
* formatWeight(450000) // "450 kg"
|
|
* formatWeight(750, { suffix: '/ekor' }) // "750 g/ekor"
|
|
* formatWeight(1500.19, { preservePrecision: true }) // "1,50019 kg"
|
|
* formatWeight(187855, { preservePrecision: true }) // "187,855 kg"
|
|
*/
|
|
export function formatWeight(
|
|
grams: number | null | undefined,
|
|
options?: {
|
|
suffix?: string; // Optional suffix like "/ekor"
|
|
forceDecimals?: boolean; // Force 1 decimal even for whole numbers
|
|
maxDecimals?: number; // Override decimal precision (default: 1)
|
|
preservePrecision?: boolean; // Show value as-is without rounding
|
|
}
|
|
): string {
|
|
// Handle edge cases
|
|
if (grams == null || grams < 0) {
|
|
return `0 g${options?.suffix || ''}`;
|
|
}
|
|
|
|
const suffix = options?.suffix || '';
|
|
|
|
if (options?.preservePrecision) {
|
|
if (grams <= 999) {
|
|
return `${formatExactNumber(grams)} g${suffix}`;
|
|
}
|
|
return `${formatGramsAsKgExact(grams)} kg${suffix}`;
|
|
}
|
|
|
|
// For values <= 999g, display as grams
|
|
if (grams <= 999) {
|
|
const formatted = Math.round(grams).toLocaleString('id-ID');
|
|
return `${formatted} g${suffix}`;
|
|
}
|
|
|
|
// For values > 999g, convert to kg
|
|
const kg = grams / 1000;
|
|
const maxDecimals = options?.maxDecimals ?? 1;
|
|
|
|
// Smart decimals: hide decimals for whole numbers unless forced
|
|
const isWholeNumber = kg % 1 === 0;
|
|
const shouldShowDecimals = options?.forceDecimals || !isWholeNumber;
|
|
|
|
const minDecimals = shouldShowDecimals ? maxDecimals : 0;
|
|
|
|
const formatted = kg.toLocaleString('id-ID', {
|
|
minimumFractionDigits: minDecimals,
|
|
maximumFractionDigits: maxDecimals,
|
|
});
|
|
|
|
return `${formatted} kg${suffix}`;
|
|
}
|