mirror of
https://github.com/Tria-plc/emaui.git
synced 2026-08-26 19:12:50 +00:00
feat: implement date display utility across various pages
- 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.
This commit is contained in:
@@ -17,77 +17,20 @@ import { useDisclosure } from '@mantine/hooks';
|
||||
import { DayPicker as EthiopicDayPicker } from '@daypicker/ethiopic';
|
||||
import { DayPicker as GregorianDayPicker } from '@daypicker/react';
|
||||
import { IconCalendarEvent } from '@tabler/icons-react';
|
||||
import { EthDateTime } from 'ethiopian-calendar-date-converter';
|
||||
import '@daypicker/react/dist/style.css';
|
||||
import './AmharicDatePicker.css';
|
||||
import {
|
||||
type EthPeriod,
|
||||
ETH_PERIODS,
|
||||
ethMonthName,
|
||||
ethTimeLabel,
|
||||
fromEthTime,
|
||||
toAmharicDisplay,
|
||||
toEthDateTime,
|
||||
toEthTime,
|
||||
} from '@ema-platform/shared';
|
||||
|
||||
const EC_MONTHS_AM = [
|
||||
'መስከረም', 'ጥቅምት', 'ኅዳር', 'ታህሳስ', 'ጥር', 'የካቲት',
|
||||
'መጋቢት', 'ሚያዝያ', 'ግንቦት', 'ሰኔ', 'ሐምሌ', 'ነሐሴ', 'ጳጉሜ',
|
||||
];
|
||||
|
||||
// EthDateTime.fromEuropeanDate() computes the day from a raw UTC-epoch
|
||||
// difference. A local-midnight Date in any positive-UTC-offset timezone
|
||||
// (e.g. Ethiopia, UTC+3) lands in the previous UTC day and converts to
|
||||
// yesterday's Ethiopian date. Re-embedding the same Y/M/D at UTC noon fixes
|
||||
// the day regardless of the runtime's timezone.
|
||||
function toEthDateTime(date: Date): EthDateTime {
|
||||
const utcNoon = new Date(
|
||||
Date.UTC(date.getFullYear(), date.getMonth(), date.getDate(), 12),
|
||||
);
|
||||
return EthDateTime.fromEuropeanDate(utcNoon);
|
||||
}
|
||||
|
||||
function ethMonthName(date: Date): string {
|
||||
try {
|
||||
return EC_MONTHS_AM[toEthDateTime(date).month - 1] ?? '';
|
||||
} catch {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
function toAmharicDisplay(date: Date): string {
|
||||
try {
|
||||
const eth = toEthDateTime(date);
|
||||
return `${ethMonthName(date)} ${eth.date}/${eth.year}`;
|
||||
} catch {
|
||||
return date.toLocaleDateString('en-US');
|
||||
}
|
||||
}
|
||||
|
||||
export type EthPeriod = 'lelit' | 'tewat' | 'ken' | 'mata';
|
||||
|
||||
// Ethiopian day starts at 6am. Period is picked from the 24h hour; the
|
||||
// displayed hour is the Western hour shifted 6, wrapped onto a 12-hour dial.
|
||||
// Each period only spans 6 hours (not 12): ጠዋት/ማታ show 12,1..5, ቀን/ሌሊት show
|
||||
// 6..11 — offering all 12 hours in every period would let a user pick e.g.
|
||||
// "ጠዋት 7", which has no 06:00–11:59 preimage.
|
||||
const ETH_PERIODS: { value: EthPeriod; label: string; hours: number[] }[] = [
|
||||
{ value: 'tewat', label: 'ጠዋት', hours: [12, 1, 2, 3, 4, 5] }, // 06:00–11:59
|
||||
{ value: 'ken', label: 'ቀን', hours: [6, 7, 8, 9, 10, 11] }, // 12:00–17:59
|
||||
{ value: 'mata', label: 'ማታ', hours: [12, 1, 2, 3, 4, 5] }, // 18:00–23:59
|
||||
{ value: 'lelit', label: 'ሌሊት', hours: [6, 7, 8, 9, 10, 11] }, // 00:00–05:59
|
||||
];
|
||||
|
||||
export function toEthTime(h24: number): { period: EthPeriod; hour: number } {
|
||||
const period: EthPeriod =
|
||||
h24 < 6 ? 'lelit' : h24 < 12 ? 'tewat' : h24 < 18 ? 'ken' : 'mata';
|
||||
return { period, hour: ((h24 + 6) % 12) || 12 };
|
||||
}
|
||||
|
||||
export function fromEthTime(period: EthPeriod, hour: number): number {
|
||||
// Inverse of the shift, then re-add the 12h that %12 discarded for the
|
||||
// afternoon/night pair of periods.
|
||||
const pm = period === 'ken' || period === 'mata';
|
||||
return ((hour + 6) % 12) + (pm ? 12 : 0);
|
||||
}
|
||||
|
||||
function ethTimeLabel(date: Date): string {
|
||||
const { period, hour } = toEthTime(date.getHours());
|
||||
const minutes = String(date.getMinutes()).padStart(2, '0');
|
||||
const label = ETH_PERIODS.find((p) => p.value === period)?.label ?? '';
|
||||
return `${hour}:${minutes} ${label}`;
|
||||
}
|
||||
export type { EthPeriod };
|
||||
|
||||
// react-day-picker calls these with the Gregorian Date it tracks internally;
|
||||
// override so the caption/dropdown show Amharic month names instead of the
|
||||
@@ -168,30 +111,6 @@ function mergeDateTime(
|
||||
return result;
|
||||
}
|
||||
|
||||
// Accepts either wire shape for display-only formatting — tries the plain
|
||||
// yyyy-MM-dd shape first, falls back to ISO — so these keep working
|
||||
// regardless of which `dateFormat` produced the stored string.
|
||||
function parseAnyDateString(value: string): Date | null {
|
||||
return parsePlainDate(value) ?? parseWireValue(value, 'iso', false);
|
||||
}
|
||||
|
||||
export function toEthiopicDateLabel(date: Date | string): string {
|
||||
const d = typeof date === 'string' ? parseAnyDateString(date) : date;
|
||||
if (!d) return '';
|
||||
try {
|
||||
const eth = toEthDateTime(d);
|
||||
return `EC ${eth.year}-${String(eth.month).padStart(2, '0')}-${String(eth.date).padStart(2, '0')}`;
|
||||
} catch {
|
||||
return d.toLocaleDateString('en-US');
|
||||
}
|
||||
}
|
||||
|
||||
export function toGregorianDateLabel(date: Date | string): string {
|
||||
const d = typeof date === 'string' ? parseAnyDateString(date) : date;
|
||||
if (!d) return '';
|
||||
return d.toLocaleDateString('en-US', { year: 'numeric', month: 'long', day: 'numeric' });
|
||||
}
|
||||
|
||||
const YEAR_DROPDOWN_START = new Date(new Date().getFullYear() - 100, 0, 1);
|
||||
const YEAR_DROPDOWN_END = new Date(new Date().getFullYear() + 50, 11, 31);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user