/** * Formatting utilities for various data types */ // Number formatting export const formatNumber = ( value: number, options: Intl.NumberFormatOptions = {} ): string => { if (typeof value !== 'number' || isNaN(value)) { return '0'; } const defaultOptions: Intl.NumberFormatOptions = { minimumFractionDigits: 0, maximumFractionDigits: 2, }; return new Intl.NumberFormat('es-ES', { ...defaultOptions, ...options }).format(value); }; // Currency formatting export const formatCurrency = ( amount: number, currency: string = 'EUR', locale: string = 'es-ES' ): string => { if (typeof amount !== 'number' || isNaN(amount)) { return new Intl.NumberFormat(locale, { style: 'currency', currency, }).format(0); } return new Intl.NumberFormat(locale, { style: 'currency', currency, }).format(amount); }; // Percentage formatting export const formatPercentage = ( value: number, decimals: number = 1, locale: string = 'es-ES' ): string => { if (typeof value !== 'number' || isNaN(value)) { return '0%'; } return new Intl.NumberFormat(locale, { style: 'percent', minimumFractionDigits: decimals, maximumFractionDigits: decimals, }).format(value / 100); }; // Compact number formatting (1K, 1M, etc.) export const formatCompactNumber = ( value: number, locale: string = 'es-ES' ): string => { if (typeof value !== 'number' || isNaN(value)) { return '0'; } return new Intl.NumberFormat(locale, { notation: 'compact', compactDisplay: 'short', }).format(value); }; // Decimal formatting with specific precision export const formatDecimal = ( value: number, decimals: number = 2, locale: string = 'es-ES' ): string => { if (typeof value !== 'number' || isNaN(value)) { return '0'; } return new Intl.NumberFormat(locale, { minimumFractionDigits: decimals, maximumFractionDigits: decimals, }).format(value); }; // File size formatting export const formatFileSize = (bytes: number): string => { if (typeof bytes !== 'number' || isNaN(bytes)) { return '0 B'; } const sizes = ['B', 'KB', 'MB', 'GB', 'TB']; if (bytes === 0) return '0 B'; const i = Math.floor(Math.log(bytes) / Math.log(1024)); const size = bytes / Math.pow(1024, i); return `${formatDecimal(size, i === 0 ? 0 : 1)} ${sizes[i]}`; }; // Duration formatting (milliseconds to human readable) export const formatDuration = (milliseconds: number): string => { if (typeof milliseconds !== 'number' || isNaN(milliseconds) || milliseconds < 0) { return '0s'; } const seconds = Math.floor(milliseconds / 1000); const minutes = Math.floor(seconds / 60); const hours = Math.floor(minutes / 60); const days = Math.floor(hours / 24); if (days > 0) { return `${days}d ${hours % 24}h`; } else if (hours > 0) { return `${hours}h ${minutes % 60}m`; } else if (minutes > 0) { return `${minutes}m ${seconds % 60}s`; } else { return `${seconds}s`; } }; // Short duration formatting (for performance metrics) export const formatShortDuration = (milliseconds: number): string => { if (typeof milliseconds !== 'number' || isNaN(milliseconds)) { return '0ms'; } if (milliseconds < 1000) { return `${Math.round(milliseconds)}ms`; } const seconds = milliseconds / 1000; if (seconds < 60) { return `${formatDecimal(seconds, 1)}s`; } const minutes = seconds / 60; return `${formatDecimal(minutes, 1)}m`; }; // Text formatting export const truncateText = ( text: string, maxLength: number, suffix: string = '...' ): string => { if (!text || typeof text !== 'string') { return ''; } if (text.length <= maxLength) { return text; } return text.slice(0, maxLength - suffix.length) + suffix; }; // Capitalize first letter export const capitalize = (text: string): string => { if (!text || typeof text !== 'string') { return ''; } return text.charAt(0).toUpperCase() + text.slice(1).toLowerCase(); }; // Title case formatting export const titleCase = (text: string): string => { if (!text || typeof text !== 'string') { return ''; } return text .toLowerCase() .split(' ') .map(word => word.charAt(0).toUpperCase() + word.slice(1)) .join(' '); }; // Kebab case to title case export const kebabToTitle = (text: string): string => { if (!text || typeof text !== 'string') { return ''; } return text .split('-') .map(word => word.charAt(0).toUpperCase() + word.slice(1)) .join(' '); }; // Snake case to title case export const snakeToTitle = (text: string): string => { if (!text || typeof text !== 'string') { return ''; } return text .split('_') .map(word => word.charAt(0).toUpperCase() + word.slice(1)) .join(' '); }; // Camel case to title case export const camelToTitle = (text: string): string => { if (!text || typeof text !== 'string') { return ''; } return text .replace(/([A-Z])/g, ' $1') .replace(/^./, str => str.toUpperCase()) .trim(); }; // Phone number formatting export const formatPhoneNumber = (phone: string): string => { if (!phone || typeof phone !== 'string') { return ''; } // Remove all non-digit characters const digits = phone.replace(/\D/g, ''); // Spanish phone number format if (digits.length === 9) { return `${digits.slice(0, 3)} ${digits.slice(3, 6)} ${digits.slice(6)}`; } // International format if (digits.length === 11 && digits.startsWith('34')) { return `+34 ${digits.slice(2, 5)} ${digits.slice(5, 8)} ${digits.slice(8)}`; } // Return original if doesn't match expected formats return phone; }; // Unit of measure formatting export const formatUnit = (value: number, unit: string): string => { if (typeof value !== 'number' || isNaN(value)) { return `0 ${unit}`; } const formattedValue = value % 1 === 0 ? value.toString() : formatDecimal(value); return `${formattedValue} ${unit}`; }; // Weight formatting with unit conversion export const formatWeight = (grams: number): string => { if (typeof grams !== 'number' || isNaN(grams)) { return '0 g'; } if (grams >= 1000) { const kg = grams / 1000; return `${formatDecimal(kg)} kg`; } return `${formatDecimal(grams, 0)} g`; }; // Volume formatting with unit conversion export const formatVolume = (milliliters: number): string => { if (typeof milliliters !== 'number' || isNaN(milliliters)) { return '0 ml'; } if (milliliters >= 1000) { const liters = milliliters / 1000; return `${formatDecimal(liters)} l`; } return `${formatDecimal(milliliters, 0)} ml`; }; // Temperature formatting export const formatTemperature = ( celsius: number, unit: 'C' | 'F' = 'C' ): string => { if (typeof celsius !== 'number' || isNaN(celsius)) { return `0°${unit}`; } if (unit === 'F') { const fahrenheit = (celsius * 9/5) + 32; return `${formatDecimal(fahrenheit, 0)}°F`; } return `${formatDecimal(celsius, 0)}°C`; }; // List formatting (join with commas and "and") export const formatList = ( items: string[], conjunction: string = 'y' ): string => { if (!Array.isArray(items) || items.length === 0) { return ''; } if (items.length === 1) { return items[0]; } if (items.length === 2) { return `${items[0]} ${conjunction} ${items[1]}`; } const lastItem = items[items.length - 1]; const otherItems = items.slice(0, -1); return `${otherItems.join(', ')} ${conjunction} ${lastItem}`; }; // Address formatting export const formatAddress = (address: { street?: string; city?: string; postal_code?: string; country?: string; }): string => { const parts = []; if (address.street) parts.push(address.street); if (address.postal_code && address.city) { parts.push(`${address.postal_code} ${address.city}`); } else if (address.city) { parts.push(address.city); } else if (address.postal_code) { parts.push(address.postal_code); } if (address.country) parts.push(address.country); return parts.join(', '); }; // HTML stripping (for displaying rich text as plain text) export const stripHtml = (html: string): string => { if (!html || typeof html !== 'string') { return ''; } // Create a temporary div element to strip HTML tags const tmp = document.createElement('div'); tmp.innerHTML = html; return tmp.textContent || tmp.innerText || ''; }; // URL slug generation export const slugify = (text: string): string => { if (!text || typeof text !== 'string') { return ''; } return text .toLowerCase() .replace(/[^\w ]+/g, '') .replace(/ +/g, '-'); }; // Name formatting (last name, first name) export const formatName = ( firstName: string, lastName: string, format: 'first_last' | 'last_first' | 'initials' = 'first_last' ): string => { const first = firstName?.trim() || ''; const last = lastName?.trim() || ''; if (!first && !last) return ''; if (!first) return last; if (!last) return first; switch (format) { case 'last_first': return `${last}, ${first}`; case 'initials': return `${first.charAt(0)}.${last.charAt(0)}.`; case 'first_last': default: return `${first} ${last}`; } };