204 lines
6.0 KiB
TypeScript
204 lines
6.0 KiB
TypeScript
/**
|
|
* Centralized number formatting utilities.
|
|
*
|
|
* All formatters use id-ID locale to provide period (.) as thousands separator
|
|
* and comma (,) as decimal separator for consistency across the application.
|
|
*/
|
|
|
|
import { FORMATTING_CONFIG } from '../config/formatting.config.ts';
|
|
|
|
export type NullableNumber = number | null | undefined;
|
|
|
|
interface FormatOptions {
|
|
/** Maximum number of decimal places */
|
|
maxDecimals?: number;
|
|
/** Minimum number of decimal places */
|
|
minDecimals?: number;
|
|
/** Enable thousands grouping (default: true) */
|
|
useGrouping?: boolean;
|
|
}
|
|
|
|
/**
|
|
* Formats an integer with period as thousands separator (Indonesian format).
|
|
*
|
|
* @param value - The number to format (can be null/undefined)
|
|
* @param options - Formatting options
|
|
* @returns Formatted string (e.g., "1.234" or "-" for null)
|
|
*
|
|
* @example
|
|
* formatNumber(1234) // "1.234"
|
|
* formatNumber(1234567) // "1.234.567"
|
|
* formatNumber(123) // "123"
|
|
* formatNumber(null) // "-"
|
|
*/
|
|
export function formatNumber(value: NullableNumber, options?: FormatOptions): string {
|
|
if (value == null || isNaN(value)) {
|
|
return FORMATTING_CONFIG.FALLBACK.NULL_VALUE;
|
|
}
|
|
|
|
const { maxDecimals = 0, minDecimals = 0, useGrouping = true } = options || {};
|
|
|
|
return value.toLocaleString(FORMATTING_CONFIG.NUMBER_LOCALE, {
|
|
minimumFractionDigits: minDecimals,
|
|
maximumFractionDigits: maxDecimals,
|
|
useGrouping,
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Formats a number with decimal places (Indonesian format).
|
|
*
|
|
* @param value - The number to format
|
|
* @param options - Formatting options
|
|
* @returns Formatted string with comma as decimal separator
|
|
*
|
|
* @example
|
|
* formatDecimal(1234.56) // "1.234,56"
|
|
* formatDecimal(1.5) // "1,5"
|
|
* formatDecimal(1.567, { maxDecimals: 2 }) // "1,57"
|
|
* formatDecimal(null) // "-"
|
|
*/
|
|
export function formatDecimal(value: NullableNumber, options?: FormatOptions): string {
|
|
if (value == null || isNaN(value)) {
|
|
return FORMATTING_CONFIG.FALLBACK.NULL_VALUE;
|
|
}
|
|
|
|
const { maxDecimals = 2, minDecimals = 0, useGrouping = true } = options || {};
|
|
|
|
return value.toLocaleString(FORMATTING_CONFIG.NUMBER_LOCALE, {
|
|
minimumFractionDigits: minDecimals,
|
|
maximumFractionDigits: maxDecimals,
|
|
useGrouping,
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Formats a percentage value (Indonesian format with comma decimal).
|
|
*
|
|
* @param value - The percentage value (0-100 scale, not 0-1)
|
|
* @param decimals - Number of decimal places (default: 1)
|
|
* @returns Formatted percentage string with comma as decimal separator
|
|
*
|
|
* @example
|
|
* formatPercentage(85.345) // "85,3%"
|
|
* formatPercentage(85.345, 2) // "85,35%"
|
|
* formatPercentage(null) // "-"
|
|
*/
|
|
export function formatPercentage(
|
|
value: NullableNumber,
|
|
decimals: number = FORMATTING_CONFIG.DECIMALS.PERCENTAGE
|
|
): string {
|
|
if (value == null || isNaN(value)) {
|
|
return FORMATTING_CONFIG.FALLBACK.NULL_VALUE;
|
|
}
|
|
|
|
return `${value.toFixed(decimals).replace('.', ',')}%`;
|
|
}
|
|
|
|
/**
|
|
* Formats FCR/EEF ratio values with fixed 3 decimal places (Indonesian format).
|
|
*
|
|
* @param value - The ratio value
|
|
* @returns Formatted ratio string with 3 decimals and comma as decimal separator
|
|
*
|
|
* @example
|
|
* formatRatio(1.567) // "1,567"
|
|
* formatRatio(1.5) // "1,500"
|
|
* formatRatio(null) // "-"
|
|
*/
|
|
export function formatRatio(value: NullableNumber): string {
|
|
if (value == null || isNaN(value)) {
|
|
return FORMATTING_CONFIG.FALLBACK.NULL_VALUE;
|
|
}
|
|
|
|
return value.toFixed(FORMATTING_CONFIG.DECIMALS.FCR).replace('.', ',');
|
|
}
|
|
|
|
/**
|
|
* Formats a ratio value with fixed decimals, truncating extra digits instead of rounding.
|
|
*
|
|
* This is useful for KPI cards where the UI must preserve the first N decimals
|
|
* without nudging the displayed value upward.
|
|
*/
|
|
export function formatTruncatedRatio(value: NullableNumber, decimals = 3): string {
|
|
if (value == null || isNaN(value)) {
|
|
return FORMATTING_CONFIG.FALLBACK.NULL_VALUE;
|
|
}
|
|
|
|
const factor = 10 ** decimals;
|
|
const truncated = Math.trunc(value * factor) / factor;
|
|
|
|
return truncated.toLocaleString(FORMATTING_CONFIG.NUMBER_LOCALE, {
|
|
minimumFractionDigits: decimals,
|
|
maximumFractionDigits: decimals,
|
|
useGrouping: false,
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Formats a nullable number with custom fallback text.
|
|
*
|
|
* @param value - The number to format
|
|
* @param formatter - Custom formatter function
|
|
* @param fallback - Custom fallback text (default: "-")
|
|
* @returns Formatted string or fallback
|
|
*
|
|
* @example
|
|
* formatNullable(1234, (v) => formatNumber(v)) // "1.234"
|
|
* formatNullable(null, (v) => formatNumber(v)) // "-"
|
|
* formatNullable(null, (v) => formatNumber(v), "N/A") // "N/A"
|
|
*/
|
|
export function formatNullable(
|
|
value: NullableNumber,
|
|
formatter: (value: number) => string,
|
|
fallback: string = FORMATTING_CONFIG.FALLBACK.NULL_VALUE
|
|
): string {
|
|
if (value == null || isNaN(value)) {
|
|
return fallback;
|
|
}
|
|
return formatter(value);
|
|
}
|
|
|
|
/**
|
|
* Standard tooltip formatters for Recharts components.
|
|
* These provide consistent formatting across all charts.
|
|
*/
|
|
export const tooltipFormatters = {
|
|
/** Format as integer with comma separator */
|
|
number: (value: number): [string, string] => [formatNumber(value), ''],
|
|
|
|
/** Format as decimal with specified precision */
|
|
decimal: (value: number, decimals = 1): [string, string] => [
|
|
formatDecimal(value, { maxDecimals: decimals }),
|
|
'',
|
|
],
|
|
|
|
/** Format as percentage */
|
|
percentage: (value: number, decimals = 1): [string, string] => [
|
|
formatPercentage(value, decimals),
|
|
'',
|
|
],
|
|
|
|
/** Format as FCR/EEF ratio */
|
|
ratio: (value: number): [string, string] => [formatRatio(value), ''],
|
|
};
|
|
|
|
/**
|
|
* Creates a YAxis tick formatter function.
|
|
*
|
|
* @param decimals - Number of decimal places (default: 0 for integers)
|
|
* @returns Formatter function for YAxis ticks
|
|
*
|
|
* @example
|
|
* <YAxis tickFormatter={createYAxisFormatter()} />
|
|
* <YAxis tickFormatter={createYAxisFormatter(1)} />
|
|
*/
|
|
export function createYAxisFormatter(decimals = 0): (value: number) => string {
|
|
return (value: number) => {
|
|
if (decimals === 0) {
|
|
return formatNumber(value);
|
|
}
|
|
return formatDecimal(value, { maxDecimals: decimals });
|
|
};
|
|
}
|