From f736e4ddac6fb7ba3b857c88444b6df8045b05d8 Mon Sep 17 00:00:00 2001 From: estifanos Date: Fri, 31 Jul 2026 10:37:04 +0000 Subject: [PATCH] feat: add toGregorianDateLabel function and update date displays across multiple pages --- .../src/app/components/AmharicDatePicker.tsx | 29 ++++++++++++------- .../pages/SeafarerRegistrationPage.tsx | 6 ++-- .../pages/SeamanBookApplicationPage.tsx | 8 ++--- .../ShippingAgentLicenseApplicationPage.tsx | 6 ++-- .../waiver/pages/WaiverApplicationPage.tsx | 6 ++-- 5 files changed, 31 insertions(+), 24 deletions(-) diff --git a/apps/portal/src/app/components/AmharicDatePicker.tsx b/apps/portal/src/app/components/AmharicDatePicker.tsx index 5ef8c4f37..717d97b6a 100644 --- a/apps/portal/src/app/components/AmharicDatePicker.tsx +++ b/apps/portal/src/app/components/AmharicDatePicker.tsx @@ -59,22 +59,23 @@ const ETH_FORMATTERS = { formatMonthDropdown: (month: Date) => ethMonthName(month), }; -// Local-date parse/format for the "YYYY-MM-DD" wire format (matches native -// semantics). Deliberately NOT `new Date(iso)` (parses as -// UTC midnight, off-by-one in negative-offset zones) and NOT -// `.toISOString()` (shifts by the local offset when formatting) — same class -// of bug the UTC-noon workaround above already had to fix once in this file. +// Wire format is a full ISO-8601 instant string (e.g. +// "2026-07-31T00:00:00.000Z"), what the backend expects for date fields. +// The picker only selects a calendar day, so the time is pinned to UTC +// midnight — reading back with getUTC*() (not local get*()) keeps the +// calendar day stable regardless of the runtime's timezone, avoiding the +// same off-by-one class of bug the UTC-noon workaround above exists for. function parseISO(value?: string | null): Date | null { if (!value) return null; - const [y, m, d] = value.split('-').map(Number); - return y && m && d ? new Date(y, m - 1, d) : null; + const d = new Date(value); + if (isNaN(d.getTime())) return null; + return new Date(d.getUTCFullYear(), d.getUTCMonth(), d.getUTCDate()); } function formatISO(date: Date): string { - const y = date.getFullYear(); - const m = String(date.getMonth() + 1).padStart(2, '0'); - const d = String(date.getDate()).padStart(2, '0'); - return `${y}-${m}-${d}`; + return new Date( + Date.UTC(date.getFullYear(), date.getMonth(), date.getDate()), + ).toISOString(); } export function toEthiopicDateLabel(date: Date | string): string { @@ -88,6 +89,12 @@ export function toEthiopicDateLabel(date: Date | string): string { } } +export function toGregorianDateLabel(date: Date | string): string { + const d = typeof date === 'string' ? parseISO(date) : date; + if (!d) return ''; + return d.toLocaleDateString('en-US', { year: 'numeric', month: 'long', day: 'numeric' }); +} + const YEAR_DROPDOWN_END = new Date(new Date().getFullYear() + 50, 11, 31); export interface AmharicDatePickerProps { diff --git a/apps/portal/src/app/features/seafarer/pages/SeafarerRegistrationPage.tsx b/apps/portal/src/app/features/seafarer/pages/SeafarerRegistrationPage.tsx index e849275af..e32e7678c 100644 --- a/apps/portal/src/app/features/seafarer/pages/SeafarerRegistrationPage.tsx +++ b/apps/portal/src/app/features/seafarer/pages/SeafarerRegistrationPage.tsx @@ -35,7 +35,7 @@ import { import { useNavigate } from 'react-router-dom'; import { notify, BilingualInput, useErrorHandler } from '@ema-platform/ui'; import type { BilingualValue } from '@ema-platform/ui'; -import { AmharicDatePicker, toEthiopicDateLabel } from '../../../components/AmharicDatePicker'; +import { AmharicDatePicker, toEthiopicDateLabel, toGregorianDateLabel } from '../../../components/AmharicDatePicker'; import { LocationPicker } from '../../location/components/LocationPicker'; // --------------------------------------------------------------------------- @@ -458,13 +458,13 @@ export function SeafarerRegistrationPage() { - + - + diff --git a/apps/portal/src/app/features/seaman-book/pages/SeamanBookApplicationPage.tsx b/apps/portal/src/app/features/seaman-book/pages/SeamanBookApplicationPage.tsx index afde671f6..8ec9b81cd 100644 --- a/apps/portal/src/app/features/seaman-book/pages/SeamanBookApplicationPage.tsx +++ b/apps/portal/src/app/features/seaman-book/pages/SeamanBookApplicationPage.tsx @@ -1,5 +1,5 @@ import { useRef, useState } from "react"; -import { AmharicDatePicker } from "../../../components/AmharicDatePicker"; +import { AmharicDatePicker, toGregorianDateLabel } from "../../../components/AmharicDatePicker"; import { Alert, Badge, @@ -1228,8 +1228,8 @@ export function SeamanBookApplicationPage() { - - + + @@ -1254,7 +1254,7 @@ export function SeamanBookApplicationPage() { : paymentRef } /> - + - - + + diff --git a/apps/portal/src/app/features/waiver/pages/WaiverApplicationPage.tsx b/apps/portal/src/app/features/waiver/pages/WaiverApplicationPage.tsx index 396a10a87..2dbfe1ab9 100644 --- a/apps/portal/src/app/features/waiver/pages/WaiverApplicationPage.tsx +++ b/apps/portal/src/app/features/waiver/pages/WaiverApplicationPage.tsx @@ -1,7 +1,7 @@ import { useRef, useState } from 'react'; import { useNavigate } from 'react-router-dom'; import { useApiMutation } from '@ema-platform/api'; -import { AmharicDatePicker } from '../../../components/AmharicDatePicker'; +import { AmharicDatePicker, toGregorianDateLabel } from '../../../components/AmharicDatePicker'; import { Alert, Badge, @@ -454,8 +454,8 @@ export function WaiverApplicationPage() { - {isPreWaiver && } - {isPostWaiver && } + {isPreWaiver && } + {isPostWaiver && }