fix(web): consolidate inline currency formatters into shared lib (GOO-205)

Remove 8 inline formatPrice/formatVND/formatPriceM2 functions scattered
across components and pages, replacing them with imports from
@/lib/currency. Add formatVNDFull (full locale, no compact notation) for
chuyen-nhuong pages. Fix price-history-chart off-by-1000 bug caused by
double-dividing through priceToMillions then formatMillions. Add k/m²
branch to formatPricePerM2 for sub-million values.

Co-Authored-By: Claude Opus 4 <noreply@anthropic.com>
This commit is contained in:
Ho Ngoc Hai
2026-04-24 14:17:32 +07:00
parent dfb398131d
commit e850ac48d7
10 changed files with 90 additions and 87 deletions

View File

@@ -28,12 +28,12 @@ export function formatPrice(amount: string | number): string {
if (num >= 1_000_000_000) {
const billions = num / 1_000_000_000;
return `${stripTrailingZero(billions.toFixed(1))} t\u1ef7`;
return `${stripTrailingZero(billions.toFixed(1))} t`;
}
if (num >= 1_000_000) {
const millions = num / 1_000_000;
return `${stripTrailingZero(millions.toFixed(1))} tri\u1ec7u`;
return `${stripTrailingZero(millions.toFixed(1))} triu`;
}
return num.toLocaleString('vi-VN');
@@ -44,8 +44,8 @@ export function formatPrice(amount: string | number): string {
// ---------------------------------------------------------------------------
/**
* Format a VND amount with a " \u0111" currency suffix.
* Returns "Mi\u1ec5n ph\u00ed" for zero amounts.
* Format a VND amount with a " đ" currency suffix.
* Returns "Miễn phí" for zero amounts.
*
* @example
* formatVND(4_990_000) // "4.99 trieu d"
@@ -53,20 +53,20 @@ export function formatPrice(amount: string | number): string {
*/
export function formatVND(amount: string | number): string {
const num = typeof amount === 'string' ? Number(amount) : amount;
if (!Number.isFinite(num) || num < 0) return '0 \u0111';
if (num === 0) return 'Mi\u1ec5n ph\u00ed';
if (!Number.isFinite(num) || num < 0) return '0 đ';
if (num === 0) return 'Miễn phí';
if (num >= 1_000_000_000) {
const billions = num / 1_000_000_000;
return `${stripTrailingZero(billions.toFixed(1))} t\u1ef7 \u0111`;
return `${stripTrailingZero(billions.toFixed(1))} tỷ đ`;
}
if (num >= 1_000_000) {
const millions = num / 1_000_000;
return `${stripTrailingZero(millions.toFixed(1))} tri\u1ec7u \u0111`;
return `${stripTrailingZero(millions.toFixed(1))} triệu đ`;
}
return num.toLocaleString('vi-VN') + ' \u0111';
return num.toLocaleString('vi-VN') + ' đ';
}
// ---------------------------------------------------------------------------
@@ -74,27 +74,49 @@ export function formatVND(amount: string | number): string {
// ---------------------------------------------------------------------------
/**
* Format a VND/m\u00b2 value.
* Format a VND/m² value.
*
* @example
* formatPricePerM2(50_500_000) // "50.5 tr/m\u00b2"
* formatPricePerM2(500_000) // "500.000 \u0111/m\u00b2"
* formatPricePerM2(50_500_000) // "50.5 tr/m²"
* formatPricePerM2(500_000) // "500k/m²"
*/
export function formatPricePerM2(price: string | number): string {
const num = typeof price === 'string' ? Number(price) : price;
if (!Number.isFinite(num) || num < 0) return '0 \u0111/m\u00b2';
if (!Number.isFinite(num) || num < 0) return '0 đ/m²';
if (num >= 1_000_000_000) {
const billions = num / 1_000_000_000;
return `${stripTrailingZero(billions.toFixed(1))} t\u1ef7/m\u00b2`;
return `${stripTrailingZero(billions.toFixed(1))} tỷ/m²`;
}
if (num >= 1_000_000) {
const millions = num / 1_000_000;
return `${stripTrailingZero(millions.toFixed(1))} tr/m\u00b2`;
return `${stripTrailingZero(millions.toFixed(1))} tr/m²`;
}
return `${num.toLocaleString('vi-VN')} \u0111/m\u00b2`;
if (num >= 1_000) {
return `${Math.round(num / 1_000)}k/m²`;
}
return `${num.toLocaleString('vi-VN')} đ/m²`;
}
// ---------------------------------------------------------------------------
// Variant: full locale format (no compact notation)
// ---------------------------------------------------------------------------
/**
* Format a VND amount as a full locale number with " đ" suffix.
* Unlike formatVND, this never uses compact notation.
*
* @example
* formatVNDFull(4_990_000) // "4.990.000 đ"
* formatVNDFull(0) // "0 đ"
*/
export function formatVNDFull(amount: string | number): string {
const num = typeof amount === 'string' ? Number(amount) : amount;
if (!Number.isFinite(num) || num < 0) return '0 đ';
return new Intl.NumberFormat('vi-VN').format(num) + ' đ';
}
// ---------------------------------------------------------------------------
@@ -103,7 +125,7 @@ export function formatPricePerM2(price: string | number): string {
/**
* Parse a formatted Vietnamese price string back into a number.
* Returns `null` if the input cannot be parsed.
* Returns null if the input cannot be parsed.
*/
export function parseVND(formatted: string): number | null {
const cleaned = formatted.replace(/[^\d]/g, '');