/** * Format number with thousand separators * @param value - Number to format * @param decimals - Number of decimal places (default: 0) * @param decimalSeparator - Decimal separator (default: ',') * @param thousandSeparator - Thousand separator (default: '.') * @returns Formatted number string * * @example * numberFormat(1234567.89) // "1.234.567" * numberFormat(1234567.89, 2) // "1.234.567,89" * numberFormat(1234.5, 2, '.', ',') // "1,234.50" */ export function numberFormat( value: number | string | null | undefined, decimals: number = 0, decimalSeparator: string = ',', thousandSeparator: string = '.' ): string { if (value === null || value === undefined || value === '') { return '0'; } const num = typeof value === 'string' ? parseFloat(value) : value; if (isNaN(num)) { return '0'; } const fixedNum = num.toFixed(decimals); const parts = fixedNum.split('.'); // Format integer part with thousand separator parts[0] = parts[0].replace(/\B(?=(\d{3})+(?!\d))/g, thousandSeparator); // Join with decimal separator if decimals exist if (decimals > 0 && parts[1]) { return parts.join(decimalSeparator); } return parts[0]; } /** * Format number as currency (Indonesian Rupiah) * @param value - Number to format * @param decimals - Number of decimal places (default: 0) * @param prefix - Currency prefix (default: 'Rp ') * @returns Formatted currency string * * @example * currencyFormat(1234567) // "Rp 1.234.567" * currencyFormat(1234567.89, 2) // "Rp 1.234.567,89" */ export function currencyFormat( value: number | string | null | undefined, decimals: number = 0, prefix: string = 'Rp ' ): string { return prefix + numberFormat(value, decimals); } /** * Format number as percentage * @param value - Number to format (0-100 or 0-1) * @param decimals - Number of decimal places (default: 2) * @param isDecimal - Whether value is in decimal form (0-1) or percentage form (0-100) * @returns Formatted percentage string * * @example * percentageFormat(45.5) // "45,50%" * percentageFormat(0.455, 2, true) // "45,50%" */ export function percentageFormat( value: number | string | null | undefined, decimals: number = 2, isDecimal: boolean = false ): string { if (value === null || value === undefined || value === '') { return '0%'; } const num = typeof value === 'string' ? parseFloat(value) : value; if (isNaN(num)) { return '0%'; } const percentage = isDecimal ? num * 100 : num; return numberFormat(percentage, decimals) + '%'; } /** * Format file size * @param bytes - File size in bytes * @param decimals - Number of decimal places (default: 2) * @returns Formatted file size string * * @example * fileSizeFormat(1024) // "1,00 KB" * fileSizeFormat(1048576) // "1,00 MB" */ export function fileSizeFormat( bytes: number | string | null | undefined, decimals: number = 2 ): string { if (bytes === null || bytes === undefined || bytes === '') { return '0 Bytes'; } const num = typeof bytes === 'string' ? parseFloat(bytes) : bytes; if (num === 0) return '0 Bytes'; if (isNaN(num)) return '0 Bytes'; const k = 1024; const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB']; const i = Math.floor(Math.log(Math.abs(num)) / Math.log(k)); return numberFormat(num / Math.pow(k, i), decimals) + ' ' + sizes[i]; } /** * Abbreviate large numbers * @param value - Number to abbreviate * @param decimals - Number of decimal places (default: 1) * @returns Abbreviated number string * * @example * abbreviateNumber(1234) // "1,2 K" * abbreviateNumber(1234567) // "1,2 M" * abbreviateNumber(1234567890) // "1,2 B" */ export function abbreviateNumber( value: number | string | null | undefined, decimals: number = 1 ): string { if (value === null || value === undefined || value === '') { return '0'; } const num = typeof value === 'string' ? parseFloat(value) : value; if (isNaN(num)) { return '0'; } if (Math.abs(num) < 1000) { return numberFormat(num, 0); } const units = ['K', 'M', 'B', 'T']; const unit = Math.floor((Math.abs(num).toString().length - 1) / 3); const unitValue = Math.pow(1000, unit); const shortValue = num / unitValue; return numberFormat(shortValue, decimals) + ' ' + units[unit - 1]; } /** * Parse formatted number string back to number * @param value - Formatted number string * @param decimalSeparator - Decimal separator used in the string * @param thousandSeparator - Thousand separator used in the string * @returns Parsed number * * @example * parseNumber("1.234.567,89") // 1234567.89 * parseNumber("1,234.50", '.', ',') // 1234.50 */ export function parseNumber( value: string | null | undefined, decimalSeparator: string = ',', thousandSeparator: string = '.' ): number { if (!value) return 0; // Remove thousand separators and replace decimal separator with dot const cleanValue = value .replace(new RegExp('\\' + thousandSeparator, 'g'), '') .replace(decimalSeparator, '.'); const num = parseFloat(cleanValue); return isNaN(num) ? 0 : num; } /** * Format phone number to Indonesian format * @param value - Phone number to format * @returns Formatted phone number * * @example * phoneFormat("081234567890") // "0812-3456-7890" * phoneFormat("628123456789") // "62-812-3456-7890" */ export function phoneFormat(value: string | null | undefined): string { if (!value) return ''; const cleaned = value.replace(/\D/g, ''); if (cleaned.startsWith('62')) { // International format const match = cleaned.match(/^(\d{2})(\d{3})(\d{4})(\d+)$/); if (match) { return `${match[1]}-${match[2]}-${match[3]}-${match[4]}`; } } else if (cleaned.startsWith('0')) { // Local format const match = cleaned.match(/^(\d{4})(\d{4})(\d+)$/); if (match) { return `${match[1]}-${match[2]}-${match[3]}`; } } return cleaned; } /** * Capitalize first letter of each word * @param value - String to capitalize * @returns Capitalized string * * @example * capitalize("hello world") // "Hello World" */ export function capitalize(value: string | null | undefined): string { if (!value) return ''; return value .toLowerCase() .split(' ') .map(word => word.charAt(0).toUpperCase() + word.slice(1)) .join(' '); } /** * Truncate string with ellipsis * @param value - String to truncate * @param length - Maximum length * @param suffix - Suffix to add (default: '...') * @returns Truncated string * * @example * truncate("Hello World", 8) // "Hello..." */ export function truncate( value: string | null | undefined, length: number, suffix: string = '...' ): string { if (!value) return ''; if (value.length <= length) return value; return value.substring(0, length) + suffix; } export function formatDate(date: string | Date | null | undefined, options?: Intl.DateTimeFormatOptions): string { if (!date) return '-'; return new Date(date).toLocaleDateString('id-ID', { year: 'numeric', month: 'short', day: 'numeric' }); };