feat: add currency input field and enhance currency formatting utilities

- Introduced `FieldCurrencyInput` component for handling currency input in forms, supporting formatted display while maintaining numeric values.
- Implemented `RenderCurrency` component for displaying currency values with proper formatting.
- Enhanced `CurrencyUtils` to include parsing and formatting functions for Rupiah, ensuring accurate representation in UI.
- Updated various components and forms to utilize the new currency input and rendering capabilities, improving user experience in financial data entry.
- Added unit tests for currency input and formatting functionalities to ensure reliability and correctness.

These changes enhance the application's handling of currency inputs and displays, providing a more user-friendly experience for financial transactions.
This commit is contained in:
shancheas
2026-08-31 15:36:53 +07:00
parent efe321ca2f
commit 71046973ae
34 changed files with 439 additions and 80 deletions
@@ -1,20 +1,53 @@
import { z } from 'zod';
import { CURRENCY_DATA_SCALE } from '@repo/utils';
const DECIMAL_PATTERN = /^\d+(\.\d{1,4})?$/;
export const PRICE_DECIMAL_SCALE = CURRENCY_DATA_SCALE;
export const QUANTITY_DECIMAL_SCALE = 4;
function emptyToUndefined(value: unknown) {
function coerceDecimalInput(value: unknown) {
if (value === '' || value === null || value === undefined) {
return undefined;
}
if (typeof value === 'number') {
return Number.isFinite(value) ? String(value) : value;
}
return value;
}
export function decimalStringSchema(t: (key: string) => string, fieldKey = 'common:fields.price') {
return z.string().regex(DECIMAL_PATTERN, {
message: JSON.stringify({ key: 'validation:invalid_format', values: { field: t(fieldKey) } }),
});
export function toDecimalStringValue(value: unknown): string | undefined {
const next = coerceDecimalInput(value);
return typeof next === 'string' ? next : undefined;
}
export function optionalDecimalStringSchema(t: (key: string) => string, fieldKey = 'common:fields.price') {
return z.preprocess(emptyToUndefined, decimalStringSchema(t, fieldKey).optional());
function decimalPattern(maxDecimals: number) {
return new RegExp(`^\\d+(\\.\\d{1,${maxDecimals}})?$`);
}
export function decimalStringSchema(
t: (key: string) => string,
fieldKey = 'common:fields.price',
maxDecimals = QUANTITY_DECIMAL_SCALE,
) {
return z.preprocess(
coerceDecimalInput,
z.string().regex(decimalPattern(maxDecimals), {
message: JSON.stringify({ key: 'validation:invalid_format', values: { field: t(fieldKey) } }),
}),
);
}
export function optionalDecimalStringSchema(
t: (key: string) => string,
fieldKey = 'common:fields.price',
maxDecimals = PRICE_DECIMAL_SCALE,
) {
return z.preprocess(
coerceDecimalInput,
z
.string()
.regex(decimalPattern(maxDecimals), {
message: JSON.stringify({ key: 'validation:invalid_format', values: { field: t(fieldKey) } }),
})
.optional(),
);
}