Files
dashboard/utils/weightUtils.ts
T
Alberto-Audrix 32a36cceff
CI / lint-and-test (push) Canceled after 0s
first commit
2026-07-28 08:55:05 +07:00

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}`;
}