From 84a1f7b47cd7757173d1e7b9839dd802176f70d2 Mon Sep 17 00:00:00 2001 From: estifanos Date: Fri, 21 Aug 2026 06:57:19 +0000 Subject: [PATCH] feat: add minDate and maxDate constraints to AmharicDatePicker and restrict form inputs to prevent future and invalid dates --- .../seafarer/pages/SeaRecords/index.tsx | 48 ++++++++++++++----- libs/ui/src/lib/input/AmharicDatePicker.tsx | 27 ++++++++++- 2 files changed, 63 insertions(+), 12 deletions(-) diff --git a/apps/portal/src/app/features/seafarer/pages/SeaRecords/index.tsx b/apps/portal/src/app/features/seafarer/pages/SeaRecords/index.tsx index 01e419ddc..bdf96eef2 100644 --- a/apps/portal/src/app/features/seafarer/pages/SeaRecords/index.tsx +++ b/apps/portal/src/app/features/seafarer/pages/SeaRecords/index.tsx @@ -164,6 +164,13 @@ function EvidenceField({ // ---------------------------------------------------------------- sea service +/** Today as a `yyyy-mm-dd` key — same shape the pickers emit, so plain + * string comparison is a valid date comparison. Taken in the authority's + * timezone, matching the server's check, so a seafarer logging in from a + * zone ahead of Addis isn't offered a day the server then rejects. */ +const todayKey = () => + new Date().toLocaleDateString('en-CA', { timeZone: 'Africa/Addis_Ababa' }); + const EMPTY_SEA_SERVICE = { vesselName: '', imoNumber: '', @@ -276,12 +283,27 @@ function SeaServiceTab() { } }; + // Service already served — neither end of an engagement can be in the future. + const today = todayKey(); + const dateError = + form.engagementDate > today || form.dischargeDate > today + ? t('seaRecords.seaService.dateFuture', { + defaultValue: 'Engagement and discharge dates cannot be in the future.', + }) + : form.engagementDate && + form.dischargeDate && + form.engagementDate >= form.dischargeDate + ? t('seaRecords.seaService.dateOrder', { + defaultValue: 'Discharge date must be after the engagement date.', + }) + : null; + const valid = form.vesselName.trim().length > 1 && form.rank.trim().length > 1 && form.engagementDate && form.dischargeDate && - form.engagementDate < form.dischargeDate; + !dateError; // Shown under the date pickers as they are filled: the seafarer sees what // the engagement is worth before saving it. @@ -408,6 +430,7 @@ function SeaServiceTab() { onChange={(val) => setForm({ ...form, engagementDate: val }) } + maxDate={form.dischargeDate || today} dateFormat="date" /> setForm({ ...form, dischargeDate: val }) } + minDate={form.engagementDate || undefined} + maxDate={today} dateFormat="date" /> - {form.engagementDate && form.dischargeDate && ( + {(dateError || (form.engagementDate && form.dischargeDate)) && ( } py={6} > - {formDays === null - ? t('seaRecords.seaService.dateOrder', { - defaultValue: 'Discharge date must be after the engagement date.', - }) - : t('seaRecords.seaService.daysServed', { - days: formDays, - defaultValue: 'Days served on this engagement: {{days}} (both days counted)', - })} + {dateError ?? + t('seaRecords.seaService.daysServed', { + days: formDays, + defaultValue: 'Days served on this engagement: {{days}} (both days counted)', + })} )}