Spaces:
Running
Running
| import { type ClassValue, clsx } from "clsx"; | |
| import { twMerge } from "tailwind-merge"; | |
| /** shadcn/ui's class merger: conditional classes with Tailwind conflict resolution. */ | |
| export function cn(...inputs: ClassValue[]) { | |
| return twMerge(clsx(inputs)); | |
| } | |
| const MONEY = new Intl.NumberFormat("en-US", { | |
| minimumFractionDigits: 2, | |
| maximumFractionDigits: 2, | |
| }); | |
| const COMPACT = new Intl.NumberFormat("en-US", { | |
| notation: "compact", | |
| maximumFractionDigits: 1, | |
| }); | |
| export function formatMoney(value: number | null | undefined, currency?: string | null): string { | |
| if (value === null || value === undefined) return "—"; | |
| return currency ? `${currency} ${MONEY.format(value)}` : MONEY.format(value); | |
| } | |
| export function formatCompact(value: number): string { | |
| return COMPACT.format(value); | |
| } | |
| /** Sub-dollar figures need more precision than a currency formatter gives. */ | |
| export function formatCost(value: number): string { | |
| if (value === 0) return "$0.00"; | |
| if (value < 0.01) return `$${value.toFixed(4)}`; | |
| return `$${value.toFixed(2)}`; | |
| } | |
| export function formatLatency(ms: number | null | undefined): string { | |
| if (ms === null || ms === undefined) return "—"; | |
| if (ms < 1000) return `${Math.round(ms)} ms`; | |
| return `${(ms / 1000).toFixed(ms < 10_000 ? 2 : 1)} s`; | |
| } | |
| export function formatBytes(bytes: number): string { | |
| if (bytes < 1024) return `${bytes} B`; | |
| if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`; | |
| return `${(bytes / 1024 / 1024).toFixed(1)} MB`; | |
| } | |
| export function formatDate(value: string | null | undefined): string { | |
| if (!value) return "—"; | |
| const parsed = new Date(value); | |
| if (Number.isNaN(parsed.getTime())) return value; | |
| return parsed.toLocaleDateString("en-GB", { | |
| day: "2-digit", | |
| month: "short", | |
| year: "numeric", | |
| }); | |
| } | |
| export function formatTime(value: string | null | undefined): string { | |
| if (!value) return "—"; | |
| const parsed = new Date(value); | |
| if (Number.isNaN(parsed.getTime())) return value; | |
| return parsed.toLocaleTimeString("en-GB", { | |
| hour: "2-digit", | |
| minute: "2-digit", | |
| second: "2-digit", | |
| }); | |
| } | |
| export function relativeTime(value: string): string { | |
| const then = new Date(value).getTime(); | |
| if (Number.isNaN(then)) return value; | |
| const seconds = Math.round((Date.now() - then) / 1000); | |
| if (seconds < 60) return "just now"; | |
| if (seconds < 3600) return `${Math.floor(seconds / 60)}m ago`; | |
| if (seconds < 86_400) return `${Math.floor(seconds / 3600)}h ago`; | |
| return `${Math.floor(seconds / 86_400)}d ago`; | |
| } | |
| /** Turn `pipeline.extraction_succeeded` into `Extraction succeeded`. */ | |
| export function humaniseEvent(event: string): string { | |
| const tail = event.includes(".") ? event.slice(event.indexOf(".") + 1) : event; | |
| const words = tail.replace(/_/g, " "); | |
| return words.charAt(0).toUpperCase() + words.slice(1); | |
| } | |
| export function titleCase(value: string): string { | |
| return value.charAt(0).toUpperCase() + value.slice(1).toLowerCase(); | |
| } | |