From 358f38660a4daa43365853f86c048222697d563a Mon Sep 17 00:00:00 2001 From: busya Date: Wed, 17 Jun 2026 14:05:37 +0300 Subject: [PATCH] feat(frontend): add shared Badge component and dateFormat utility MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Consolidation infrastructure: - Badge.svelte: compact inline badge/chip with variant (success/warning/destructive/info/primary/muted), size, and dot mode - dateFormat.ts: formatDate, formatDateTime, formatRelativeTime, formatFileSize — replaces 6+ local definitions - Export Badge from $lib/ui index --- frontend/src/lib/ui/Badge.svelte | 51 ++++++++++++++++++++ frontend/src/lib/ui/index.ts | 1 + frontend/src/lib/utils/dateFormat.ts | 72 ++++++++++++++++++++++++++++ 3 files changed, 124 insertions(+) create mode 100644 frontend/src/lib/ui/Badge.svelte create mode 100644 frontend/src/lib/utils/dateFormat.ts diff --git a/frontend/src/lib/ui/Badge.svelte b/frontend/src/lib/ui/Badge.svelte new file mode 100644 index 00000000..17c80d71 --- /dev/null +++ b/frontend/src/lib/ui/Badge.svelte @@ -0,0 +1,51 @@ + + + + + + + + + + + + + {#if dot} + + {/if} + + {@render children?.()} + + + diff --git a/frontend/src/lib/ui/index.ts b/frontend/src/lib/ui/index.ts index 94a63c6b..6bb71f1a 100644 --- a/frontend/src/lib/ui/index.ts +++ b/frontend/src/lib/ui/index.ts @@ -16,6 +16,7 @@ export { default as EmptyState } from './EmptyState.svelte'; export { default as LanguageSwitcher } from './LanguageSwitcher.svelte'; export { default as Icon } from './Icon.svelte'; export { default as HelpTooltip } from './HelpTooltip.svelte'; +export { default as Badge } from './Badge.svelte'; // [/SECTION: EXPORTS] // #endregion ui:Module \ No newline at end of file diff --git a/frontend/src/lib/utils/dateFormat.ts b/frontend/src/lib/utils/dateFormat.ts new file mode 100644 index 00000000..c2c1a307 --- /dev/null +++ b/frontend/src/lib/utils/dateFormat.ts @@ -0,0 +1,72 @@ +// #region Utils.DateFormat [C:2] [TYPE Module] [SEMANTICS date,format,utility,helper] +// @ingroup Utils +// @BRIEF Centralized date/time formatting utilities. Replaces 6+ local formatDate definitions. +// @LAYER Infra +// @RATIONALE 6 files defined independent formatDate functions and 20+ files use inline new Date().toLocaleDateString(). +// Consolidating into one module ensures consistent locale-aware formatting and reduces duplication. + +// #region formatDate:Function [TYPE Function] +// @BRIEF Format ISO date string to localized short date (e.g. "15 Jan 2025"). +export function formatDate(value: string | Date | null | undefined): string { + if (!value) return ""; + const d = typeof value === "string" ? new Date(value) : value; + if (isNaN(d.getTime())) return ""; + return d.toLocaleDateString(undefined, { + year: "numeric", + month: "short", + day: "numeric", + }); +} +// #endregion formatDate:Function + +// #region formatDateTime:Function [TYPE Function] +// @BRIEF Format ISO date string to localized date+time (e.g. "15 Jan 2025, 14:30"). +export function formatDateTime(value: string | Date | null | undefined): string { + if (!value) return ""; + const d = typeof value === "string" ? new Date(value) : value; + if (isNaN(d.getTime())) return ""; + return d.toLocaleDateString(undefined, { + year: "numeric", + month: "short", + day: "numeric", + hour: "2-digit", + minute: "2-digit", + }); +} +// #endregion formatDateTime:Function + +// #region formatRelativeTime:Function [TYPE Function] +// @BRIEF Format date to relative time string (e.g. "2 hours ago", "just now"). +export function formatRelativeTime(value: string | Date | null | undefined): string { + if (!value) return ""; + const d = typeof value === "string" ? new Date(value) : value; + if (isNaN(d.getTime())) return ""; + + const now = Date.now(); + const diffMs = now - d.getTime(); + const diffSec = Math.floor(diffMs / 1000); + const diffMin = Math.floor(diffSec / 60); + const diffHour = Math.floor(diffMin / 60); + const diffDay = Math.floor(diffHour / 24); + + if (diffSec < 60) return "just now"; + if (diffMin < 60) return `${diffMin}m ago`; + if (diffHour < 24) return `${diffHour}h ago`; + if (diffDay < 30) return `${diffDay}d ago`; + return formatDate(d); +} +// #endregion formatRelativeTime:Function + +// #region formatFileSize:Function [TYPE Function] +// @BRIEF Format bytes to human-readable size (e.g. "1.5 MB"). +export function formatFileSize(bytes: number | null | undefined): string { + if (bytes == null || bytes < 0) return ""; + if (bytes === 0) return "0 B"; + const units = ["B", "KB", "MB", "GB", "TB"]; + const i = Math.floor(Math.log(bytes) / Math.log(1024)); + const val = bytes / Math.pow(1024, i); + return `${val.toFixed(i === 0 ? 0 : 1)} ${units[i]}`; +} +// #endregion formatFileSize:Function + +// #endregion Utils.DateFormat