// 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, """)
|
.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);
|
}
|
};
|
}
|