mirror of
https://github.com/Tria-plc/emaui.git
synced 2026-09-07 14:15:45 +00:00
- Added `useDateDisplayer` hook and `dateDisplayer` function to format dates consistently based on the user's language. - Updated multiple components and pages in both backoffice and portal applications to utilize the new date display functionality, ensuring proper formatting for dates in lists, tables, and detail views. - Introduced Ethiopian date formatting for Amharic language support. - Refactored date handling in components such as ExamAppealsPage, ResultPage, SeafarerRegistryPage, and others to improve localization and user experience.
58 lines
2.1 KiB
TypeScript
58 lines
2.1 KiB
TypeScript
import { ethTimeLabel, toAmharicDisplay } from './ethiopic';
|
|
|
|
/** Wire values carrying no time of day, e.g. a date column serialised as `2026-08-10`. */
|
|
const DATE_ONLY = /^\d{4}-\d{2}-\d{2}$/;
|
|
|
|
/** Midnight UTC — how backends commonly serialise a plain date column. */
|
|
const UTC_MIDNIGHT = /T00:00:00(\.0+)?Z$/;
|
|
|
|
/**
|
|
* The one way a date is shown to a user.
|
|
*
|
|
* Timestamps render in the viewer's local timezone — `2026-08-10T06:57:34.507Z`
|
|
* reads as `Aug 10, 2026 9:57am` in Addis. Values with no real time of day
|
|
* render as a bare date: a birth date or a licence expiry stamped `12:00am`
|
|
* reads as precision the data does not have. Those are also formatted in UTC,
|
|
* because parsing `2026-08-10` yields UTC midnight, and converting that to a
|
|
* behind-UTC local timezone would show the previous day.
|
|
*
|
|
* `language` is a parameter rather than read from i18next because each app runs
|
|
* a DEDICATED i18n instance, not the global singleton (see `app/i18n/config.ts`)
|
|
* — the same reason `localized()` in licensing.helpers.ts takes one. Components
|
|
* should not call this directly; use `useDateDisplayer()` so the text actually
|
|
* re-renders when the language changes.
|
|
*/
|
|
export function dateDisplayer(
|
|
value: string | number | Date | null | undefined,
|
|
language = 'en',
|
|
): string {
|
|
if (value === null || value === undefined || value === '') return '—';
|
|
|
|
const date = new Date(value);
|
|
if (Number.isNaN(date.getTime())) return '—';
|
|
|
|
const dateOnly =
|
|
typeof value === 'string' && (DATE_ONLY.test(value) || UTC_MIDNIGHT.test(value));
|
|
|
|
if (language.startsWith('am')) {
|
|
const day = toAmharicDisplay(date); // ሐምሌ 22/2018
|
|
return dateOnly ? day : `${day} - ${ethTimeLabel(date)}`;
|
|
}
|
|
|
|
const day = date.toLocaleDateString('en-US', {
|
|
month: 'short',
|
|
day: 'numeric',
|
|
year: 'numeric',
|
|
...(dateOnly ? { timeZone: 'UTC' } : {}),
|
|
});
|
|
if (dateOnly) return day;
|
|
|
|
// Intl gives "9:57 AM"; the house format is "9:57am".
|
|
const time = date
|
|
.toLocaleTimeString('en-US', { hour: 'numeric', minute: '2-digit' })
|
|
.replace(' ', '')
|
|
.toLowerCase();
|
|
|
|
return `${day} ${time}`;
|
|
}
|