mirror of
https://github.com/Tria-plc/emaui.git
synced 2026-08-26 19:12:50 +00:00
Merge branch 'estif-branch-1' of https://github.com/Tria-plc/emaui into estif-branch-1
This commit is contained in:
@@ -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"
|
||||
/>
|
||||
<AmharicDatePicker
|
||||
@@ -417,21 +440,20 @@ function SeaServiceTab() {
|
||||
onChange={(val) =>
|
||||
setForm({ ...form, dischargeDate: val })
|
||||
}
|
||||
minDate={form.engagementDate || undefined}
|
||||
maxDate={today}
|
||||
dateFormat="date"
|
||||
/>
|
||||
</Group>
|
||||
{form.engagementDate && form.dischargeDate && (
|
||||
{(dateError || (form.engagementDate && form.dischargeDate)) && (
|
||||
<Alert
|
||||
variant="light"
|
||||
color={formDays === null ? 'red' : 'teal'}
|
||||
color={dateError ? 'red' : 'teal'}
|
||||
icon={<IconInfoCircle size={16} />}
|
||||
py={6}
|
||||
>
|
||||
{formDays === null
|
||||
? t('seaRecords.seaService.dateOrder', {
|
||||
defaultValue: 'Discharge date must be after the engagement date.',
|
||||
})
|
||||
: t('seaRecords.seaService.daysServed', {
|
||||
{dateError ??
|
||||
t('seaRecords.seaService.daysServed', {
|
||||
days: formDays,
|
||||
defaultValue: 'Days served on this engagement: {{days}} (both days counted)',
|
||||
})}
|
||||
@@ -581,10 +603,12 @@ function MedicalTab() {
|
||||
}
|
||||
};
|
||||
|
||||
const today = todayKey();
|
||||
const valid =
|
||||
form.issuerName.trim().length > 1 &&
|
||||
form.issueDate &&
|
||||
form.expiryDate &&
|
||||
form.issueDate <= today &&
|
||||
form.issueDate < form.expiryDate;
|
||||
|
||||
const { setPageIndex, pageSize, setPageSize, paginate } = useServerTable({ pageSize: 10 });
|
||||
@@ -664,6 +688,7 @@ function MedicalTab() {
|
||||
required
|
||||
value={form.issueDate}
|
||||
onChange={(val) => setForm({ ...form, issueDate: val })}
|
||||
maxDate={today}
|
||||
dateFormat="date"
|
||||
/>
|
||||
<AmharicDatePicker
|
||||
@@ -671,6 +696,7 @@ function MedicalTab() {
|
||||
required
|
||||
value={form.expiryDate}
|
||||
onChange={(val) => setForm({ ...form, expiryDate: val })}
|
||||
minDate={form.issueDate || undefined}
|
||||
dateFormat="date"
|
||||
/>
|
||||
</Group>
|
||||
|
||||
@@ -15,7 +15,7 @@ import {
|
||||
import { TimeInput } from '@mantine/dates';
|
||||
import { useDisclosure } from '@mantine/hooks';
|
||||
import { DayPicker as EthiopicDayPicker } from '@daypicker/ethiopic';
|
||||
import { DayPicker as GregorianDayPicker } from '@daypicker/react';
|
||||
import { DayPicker as GregorianDayPicker, type Matcher } from '@daypicker/react';
|
||||
import { IconCalendarEvent } from '@tabler/icons-react';
|
||||
import '@daypicker/react/dist/style.css';
|
||||
import './AmharicDatePicker.css';
|
||||
@@ -131,6 +131,11 @@ export interface AmharicDatePickerProps {
|
||||
/** Show a time-of-day field alongside the calendar. Off by default —
|
||||
* most callers only need a calendar day. */
|
||||
withTime?: boolean;
|
||||
/** Earliest/latest selectable day. Accepts a Date or a value in the same
|
||||
* wire format as `value`. Days outside the range are disabled in both
|
||||
* calendars. */
|
||||
minDate?: Date | string;
|
||||
maxDate?: Date | string;
|
||||
/** Wire format for `value`/`onChange`: a full ISO-8601 instant (default,
|
||||
* what most backend date fields expect) or a bare `yyyy-mm-dd` calendar
|
||||
* date (what filter query params and plain `date: string` DTO fields
|
||||
@@ -151,6 +156,8 @@ export function AmharicDatePicker({
|
||||
onBlur,
|
||||
w,
|
||||
withTime = false,
|
||||
minDate,
|
||||
maxDate,
|
||||
dateFormat = 'iso',
|
||||
}: AmharicDatePickerProps) {
|
||||
const { t, i18n } = useTranslation();
|
||||
@@ -161,6 +168,21 @@ export function AmharicDatePicker({
|
||||
|
||||
const selected = parseWireValue(value, dateFormat, withTime);
|
||||
|
||||
const asDate = (limit: Date | string | undefined) =>
|
||||
limit instanceof Date ? limit : parseWireValue(limit, dateFormat, withTime);
|
||||
const min = asDate(minDate);
|
||||
const max = asDate(maxDate);
|
||||
const outOfRange: Matcher[] = [
|
||||
...(min ? [{ before: min }] : []),
|
||||
...(max ? [{ after: max }] : []),
|
||||
];
|
||||
// Compared as calendar days — the limits carry a midnight time-of-day, so
|
||||
// an instant comparison would call today "after" a max of today.
|
||||
const todayKey = formatPlainDate(new Date());
|
||||
const todayOutOfRange =
|
||||
(!!min && todayKey < formatPlainDate(min)) ||
|
||||
(!!max && todayKey > formatPlainDate(max));
|
||||
|
||||
const dateLabel = selected
|
||||
? calendarType === 'EN'
|
||||
? selected.toLocaleDateString('en-US', {
|
||||
@@ -266,6 +288,7 @@ export function AmharicDatePicker({
|
||||
endMonth={YEAR_DROPDOWN_END}
|
||||
numerals="latn"
|
||||
captionLayout="dropdown"
|
||||
disabled={outOfRange}
|
||||
formatters={ETH_FORMATTERS}
|
||||
onSelect={(date: Date | undefined) => {
|
||||
onChange?.(date ? formatWireValue(mergeDateTime(selected, date), dateFormat, withTime) : '');
|
||||
@@ -281,6 +304,7 @@ export function AmharicDatePicker({
|
||||
startMonth={YEAR_DROPDOWN_START}
|
||||
endMonth={YEAR_DROPDOWN_END}
|
||||
captionLayout="dropdown"
|
||||
disabled={outOfRange}
|
||||
onSelect={(date: Date | undefined) => {
|
||||
onChange?.(date ? formatWireValue(mergeDateTime(selected, date), dateFormat, withTime) : '');
|
||||
if (!withTime) close();
|
||||
@@ -389,6 +413,7 @@ export function AmharicDatePicker({
|
||||
<Button
|
||||
variant="light"
|
||||
size="xs"
|
||||
disabled={todayOutOfRange}
|
||||
onClick={() => {
|
||||
onChange?.(formatWireValue(new Date(), dateFormat, withTime));
|
||||
close();
|
||||
|
||||
Reference in New Issue
Block a user