Files
fusion/packages/dashboard/app/i18n/format.ts
gsxdsm 03cfc2dfc8 FN-8561: add lifecycle dates to task cards
Show localized creation and completion dates directly on task cards.

- Render compact, locale-aware lifecycle timestamps with accessible full-date titles.
- Persist archive timestamps for completed archived-task fallbacks and add coverage.
- Add translated labels, documentation, and a minor release changeset.

Files changed:
 .changeset/fn-8561-task-card-dates.md              |  7 +++
 docs/dashboard-guide.md                            |  1 +
 .../__tests__/archive-entry-serialization.test.ts  | 35 ++++++++++++++
 packages/core/src/task-store/serialization.ts      |  8 ++++
 packages/core/src/types/task-core.ts               |  5 ++
 packages/dashboard/app/components/TaskCard.css     | 27 +++++++++++
 packages/dashboard/app/components/TaskCard.tsx     | 55 ++++++++++++++++++++++
 .../__tests__/TaskCard.host-inventory.test.tsx     | 21 +++++++++
 .../app/components/__tests__/TaskCard.test.tsx     | 17 +++++++
 .../dashboard/app/i18n/__tests__/format.test.ts    | 19 +++++++-
 packages/dashboard/app/i18n/format.ts              | 38 +++++++++++++++
 packages/i18n/locales/en/app.json                  |  4 ++
 packages/i18n/locales/es/app.json                  |  4 ++
 packages/i18n/locales/fr/app.json                  |  4 ++
 packages/i18n/locales/ko/app.json                  |  4 ++
 packages/i18n/locales/zh-CN/app.json               |  4 ++
 packages/i18n/locales/zh-TW/app.json               |  4 ++
 17 files changed, 256 insertions(+), 1 deletion(-)

Fusion-Task-Id: FN-8561

Fusion-Task-Lineage: 474b949b-d8e7-469a-b564-00acae66e58a

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
2026-07-24 11:15:43 -07:00

70 lines
2.5 KiB
TypeScript

import { useMemo } from "react";
import { useTranslation } from "react-i18next";
export interface CompactLifecycleDate {
compact: string;
full: string;
dateTime: string;
}
/**
* Formats lifecycle timestamps against the viewer's local calendar, rather than
* UTC date boundaries. Invalid and missing legacy values intentionally produce
* no model so callers never render an Invalid Date shell.
*/
export function formatCompactLifecycleDate(
value: string | undefined,
locale: string,
now: Date = new Date(),
): CompactLifecycleDate | null {
if (!value) return null;
const date = new Date(value);
if (Number.isNaN(date.getTime()) || Number.isNaN(now.getTime())) return null;
const sameDay = date.getFullYear() === now.getFullYear()
&& date.getMonth() === now.getMonth()
&& date.getDate() === now.getDate();
const compact = sameDay
? new Intl.DateTimeFormat(locale, { hour: "numeric", minute: "2-digit" }).format(date)
: new Intl.DateTimeFormat(locale, {
month: "short",
day: "numeric",
...(date.getFullYear() !== now.getFullYear() ? { year: "numeric" } : {}),
}).format(date);
return {
compact,
full: new Intl.DateTimeFormat(locale, { dateStyle: "medium", timeStyle: "short" }).format(date),
dateTime: date.toISOString(),
};
}
/**
* Locale-aware date/number formatting bound to the active i18n locale.
*
* Replaces the ~45 `toLocale*(undefined, …)` call sites that previously used
* the implicit browser locale. Routing them through this hook threads the
* user's chosen language into all date/number formatting (R8).
*/
export function useLocaleFormat() {
const { i18n } = useTranslation();
const locale = i18n.resolvedLanguage || i18n.language || "en";
// Memoized per locale so the formatter identities stay stable across
// renders — consumers can safely put them in hook dependency arrays.
return useMemo(
() => ({
locale,
formatDate: (value: number | string | Date, options?: Intl.DateTimeFormatOptions) =>
new Date(value).toLocaleDateString(locale, options),
formatTime: (value: number | string | Date, options?: Intl.DateTimeFormatOptions) =>
new Date(value).toLocaleTimeString(locale, options),
formatDateTime: (value: number | string | Date, options?: Intl.DateTimeFormatOptions) =>
new Date(value).toLocaleString(locale, options),
formatNumber: (value: number, options?: Intl.NumberFormatOptions) =>
value.toLocaleString(locale, options),
}),
[locale],
);
}