// formatters.js - Utility functions for formatting data /** * Format a date in relative time (e.g., "2 days ago") * @param {string} dateStr - ISO date string * @returns {string} - Formatted relative time */ export function formatTimeAgo(dateStr) { const date = new Date(dateStr); const now = new Date(); const seconds = Math.floor((now - date) / 1000); const minutes = Math.floor(seconds / 60); const hours = Math.floor(minutes / 60); const days = Math.floor(hours / 24); const weeks = Math.floor(days / 7); const months = Math.floor(days / 30); // Within the last 24 hours - show hours ago if (hours < 24) { if (hours === 0) { return minutes === 0 ? 'Just now' : `${minutes} ${minutes === 1 ? 'minute' : 'minutes'} ago`; } return `${hours} ${hours === 1 ? 'hour' : 'hours'} ago`; } // Within the last 7 days - show days ago if (days < 7) { return `${days} ${days === 1 ? 'day' : 'days'} ago`; } // Within the last 4 weeks - show week-based format if (weeks < 4) { if (weeks === 1) return 'Last week'; return 'A few weeks ago'; } // Within the last 2 months - show "Last month" if (months < 2) { return 'Last month'; } // For anything older, show the full date return date.toLocaleDateString('en-US', { year: 'numeric', month: 'long', day: 'numeric' }); } /** * Format a number with comma separator (e.g., 1,234) * @param {number} num - Number to format * @returns {string} - Formatted number */ export function formatNumber(num) { return num.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ","); } /** * Format a price with currency symbol * @param {number} price - Price to format * @param {string} currency - Currency code (default: 'CAD') * @returns {string} - Formatted price */ export function formatPrice(price, currency = 'CAD') { return new Intl.NumberFormat('en-CA', { style: 'currency', currency: currency }).format(price); } /** * Escape HTML special characters to prevent XSS * @param {string} text - Text to escape * @returns {string} - Escaped text */ export function escapeHtml(text) { if (!text) return ''; return text .replace(/&/g, "&") .replace(//g, ">") .replace(/"/g, """) .replace(/'/g, "'"); } /** * Truncate text to a specific length with ellipsis * @param {string} text - Text to truncate * @param {number} length - Maximum length * @returns {string} - Truncated text */ export function truncateText(text, length = 100) { if (!text || text.length <= length) return text; return text.substring(0, length) + '...'; } /** * Format a date range (e.g., "Jan 1 - Jan 5, 2023") * @param {string} startDate - Start date ISO string * @param {string} endDate - End date ISO string * @returns {string} - Formatted date range */ export function formatDateRange(startDate, endDate) { const start = new Date(startDate); const end = new Date(endDate); // If same day, just show one date if (start.toDateString() === end.toDateString()) { return start.toLocaleDateString('en-US', { year: 'numeric', month: 'short', day: 'numeric' }); } // If same month and year, show range with month once if (start.getMonth() === end.getMonth() && start.getFullYear() === end.getFullYear()) { return `${start.toLocaleDateString('en-US', { month: 'short', day: 'numeric' })} - ${end.getDate()}, ${end.getFullYear()}`; } // If same year, show full range with year once if (start.getFullYear() === end.getFullYear()) { return `${start.toLocaleDateString('en-US', { month: 'short', day: 'numeric' })} - ${end.toLocaleDateString('en-US', { month: 'short', day: 'numeric' })}, ${end.getFullYear()}`; } // Different years, show full dates return `${start.toLocaleDateString('en-US', { month: 'short', day: 'numeric', year: 'numeric' })} - ${end.toLocaleDateString('en-US', { month: 'short', day: 'numeric', year: 'numeric' })}`; } /** * Debounce function to limit frequent calls * @param {Function} func - Function to debounce * @param {number} wait - Wait time in milliseconds * @returns {Function} - Debounced function */ export function debounce(func, wait = 300) { let timeout; return function(...args) { clearTimeout(timeout); timeout = setTimeout(() => func.apply(this, args), wait); }; } /** * Throttle function to limit call frequency * @param {Function} func - Function to throttle * @param {number} limit - Time limit in milliseconds * @returns {Function} - Throttled function */ export function throttle(func, limit = 300) { let inThrottle; return function(...args) { if (!inThrottle) { func.apply(this, args); inThrottle = true; setTimeout(() => inThrottle = false, limit); } }; }