110 lines
3.2 KiB
TypeScript
110 lines
3.2 KiB
TypeScript
/**
|
|
* Format a date string or Date to a long readable date: "Jan 6, 2026"
|
|
*/
|
|
export function formatDate(value?: string | Date | null): string {
|
|
if (!value) return "—"
|
|
const date = value instanceof Date ? value : new Date(value)
|
|
if (isNaN(date.getTime())) return "—"
|
|
return date.toLocaleDateString(undefined, { year: "numeric", month: "short", day: "numeric" })
|
|
}
|
|
|
|
/**
|
|
* Format a date string or Date to date + time: "Jan 6, 2026, 2:30 PM"
|
|
*/
|
|
export function formatDateTime(value?: string | Date | null): string {
|
|
if (!value) return "—"
|
|
const date = value instanceof Date ? value : new Date(value)
|
|
if (isNaN(date.getTime())) return "—"
|
|
return date.toLocaleDateString(undefined, {
|
|
year: "numeric",
|
|
month: "short",
|
|
day: "numeric",
|
|
hour: "numeric",
|
|
minute: "2-digit",
|
|
})
|
|
}
|
|
|
|
/**
|
|
* Format a date string or Date to a short numeric date: "04/06/2026"
|
|
*/
|
|
export function formatDateShort(value?: string | Date | null): string {
|
|
if (!value) return "—"
|
|
const date = value instanceof Date ? value : new Date(value)
|
|
if (isNaN(date.getTime())) return "—"
|
|
return date.toLocaleDateString(undefined, { year: "numeric", month: "2-digit", day: "2-digit" })
|
|
}
|
|
|
|
/**
|
|
* Format a time string or Date to a readable time: "2:30 PM"
|
|
*/
|
|
export function formatTime(value?: string | Date | null): string {
|
|
if (!value) return "—"
|
|
const date = value instanceof Date ? value : new Date(value)
|
|
if (isNaN(date.getTime())) return "—"
|
|
return date.toLocaleTimeString(undefined, { hour: "numeric", minute: "2-digit" })
|
|
}
|
|
|
|
/**
|
|
* Format a snake_case or underscore-separated string to Title Case words.
|
|
* e.g. "in_progress" → "In Progress"
|
|
*/
|
|
export function formatEnum(value?: string | null): string {
|
|
if (!value) return "—"
|
|
return value
|
|
.split("_")
|
|
.map((w) => w.charAt(0).toUpperCase() + w.slice(1))
|
|
.join(" ")
|
|
}
|
|
|
|
type TaxLabelValue = {
|
|
name?: string | null
|
|
title?: string | null
|
|
rate?: string | number | null
|
|
}
|
|
|
|
export function formatTaxLabel(
|
|
value?: TaxLabelValue | null,
|
|
emptyFallback = "—",
|
|
): string {
|
|
const label = value?.name ?? value?.title
|
|
const rate = value?.rate
|
|
|
|
if (!label) return emptyFallback
|
|
if (rate == null || rate === "") return label
|
|
|
|
return `${label} (${rate}%)`
|
|
}
|
|
|
|
/**
|
|
* Format a number with locale-aware thousand separators.
|
|
* e.g. 150000 → "150,000"
|
|
*/
|
|
export function formatNumber(value?: number | string | null): string {
|
|
if (value == null || value === "") return "—"
|
|
const num = typeof value === "string" ? Number(value) : value
|
|
if (isNaN(num)) return "—"
|
|
return num.toLocaleString()
|
|
}
|
|
|
|
/**
|
|
* Format a numeric value as currency: "$1,500.00"
|
|
*/
|
|
export function formatCurrency(
|
|
value?: number | string | null,
|
|
currency = "USD",
|
|
locale?: string,
|
|
): string {
|
|
if (value == null || value === "") return "—"
|
|
const num = typeof value === "string" ? Number(value) : value
|
|
if (isNaN(num)) return "—"
|
|
return new Intl.NumberFormat(locale, { style: "currency", currency }).format(num)
|
|
}
|
|
|
|
/**
|
|
* Format text to uppercase.
|
|
*/
|
|
export function formatUppercase(value?: string | null): string {
|
|
if (value == null) return ""
|
|
return value.toUpperCase()
|
|
}
|