feat: implement CurrencyService for formatting and parsing currency values, update InputCurrency component, and add tests

This commit is contained in:
Firman Ramdhani
2026-01-26 15:52:20 +07:00
parent 0a7e3f081a
commit 0deaff6e18
6 changed files with 297 additions and 25 deletions
@@ -1,40 +1,55 @@
import { CurrencyService } from '@repo/utils';
import { InputCurrencyProps } from '../types';
import { InputNumber } from './input-number.component';
/**
* InputCurrency
*
* A controlled InputNumber component with currency formatting.
* Delegates all formatting/parsing to CurrencyService.
*
* Features:
* - Display currency prefix (e.g., Rp, $)
* - Thousand separator formatting
* - Optional decimal rounding
* - Global prefix support via CurrencyService
*/
export const InputCurrency = (props: InputCurrencyProps) => {
const { prefix = 'Rp ', ...restProps } = props;
const {
prefix, // Optional override for instance prefix
decimalSeparator, // Optional override for separator
decimalScale, // Optional rounding
...restProps
} = props;
// Change format from 10000 to "Rp 10.000"
const formatCurrency = (value: number | string | undefined) => {
if (value === undefined || value === null || value === '') {
return '';
}
// Convert value to string for processing
const valStr = `${value}`;
const formatted = valStr.replace(/\B(?=(\d{3})+(?!\d))/g, '.');
return `${prefix}${formatted}`;
// Create a CurrencyService instance for this input
const currencyService = new CurrencyService({
prefix,
decimalSeparator,
decimalScale,
});
/**
* Formatter: converts numeric value to formatted string for display
*/
const formatter = (value: number | string | undefined) => {
return currencyService.format(value);
};
// Change parse from "Rp 10.000" to 10000
const parseCurrency = (displayValue: string | undefined) => {
if (!displayValue) return '';
// Remove prefix and all non-digit characters (except comma if decimal support is needed)
// Here we assume pure integer input, so dots (.) are removed.
const cleanValue = displayValue.replace(prefix, '').replace(/\./g, '');
// If you need decimal support (comma), use this regex instead:
// return displayValue.replace(prefix, '').replace(/\./g, '').replace(/,/g, '.');
return cleanValue;
/**
* Parser: converts formatted string back to raw numeric string
* Note: never rounds or modifies the underlying value
*/
const parser = (displayValue: string | undefined) => {
return currencyService.parse(displayValue);
};
return (
<InputNumber
{...restProps}
formatter={formatCurrency}
parser={parseCurrency}
// Ensure minimum value is 0 for currency input
formatter={formatter}
parser={parser}
// Default min value to 0 for currency
min={props.min !== undefined ? props.min : 0}
/>
);
@@ -53,6 +53,8 @@ export interface InputPasswordProps extends InputProps {
export interface InputNumberProps extends RcInputNumberProps, BaseComponentProps {}
export interface InputCurrencyProps extends InputNumberProps, BaseComponentProps {
prefix?: string; // Optional: Custom prefix, default 'Rp '
decimalSeparator?: ',' | '.';
decimalScale?: number; // optional
}
/**