mirror of
https://github.com/Tria-plc/emaui.git
synced 2026-09-06 04:13:38 +00:00
@@ -84,9 +84,11 @@ export function DesignerToolbar({
|
||||
<Text size="sm" fw={600}>
|
||||
{validityDays != null
|
||||
? t('designer.validityDays', '{{count}} days', { count: validityDays })
|
||||
: t('designer.validityMonths', '{{count}} months', {
|
||||
count: validityMonths,
|
||||
})}
|
||||
: validityMonths
|
||||
? t('designer.validityMonths', '{{count}} months', {
|
||||
count: validityMonths,
|
||||
})
|
||||
: t('designer.validityNone', 'Does not expire')}
|
||||
</Text>
|
||||
<Text size="xs" c="dimmed">
|
||||
{t('designer.validityEditedOn', '— set on Certificate Requirements → Behaviour')}
|
||||
|
||||
@@ -72,9 +72,13 @@ function toDraft(licenseType: LicenseType): Draft {
|
||||
licenseType.capitalThreshold == null
|
||||
? null
|
||||
: Number(licenseType.capitalThreshold),
|
||||
// Zero is a real stored state for `validityMonths` (a type that issues
|
||||
// nothing with an expiry) and is kept. Zero in the other two is not a
|
||||
// policy anyone set — it is an unfilled column — and seeding a box with a
|
||||
// value below its own floor only produces a save the server rejects.
|
||||
validityMonths: licenseType.validityMonths ?? 12,
|
||||
validityDays: licenseType.validityDays ?? null,
|
||||
renewalWindowDays: licenseType.renewalWindowDays ?? 60,
|
||||
validityDays: licenseType.validityDays || null,
|
||||
renewalWindowDays: licenseType.renewalWindowDays || 60,
|
||||
expiryReminderDays: licenseType.expiryReminderDays ?? [60, 30, 7],
|
||||
requiresOperatorMode: licenseType.requiresOperatorMode ?? true,
|
||||
allowMultipleOpenDrafts: licenseType.allowMultipleOpenDrafts ?? false,
|
||||
@@ -114,14 +118,52 @@ export function BehaviorTab({ licenseType }: { licenseType: LicenseType }) {
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [licenseType.id]);
|
||||
|
||||
function set<K extends keyof Draft>(key: K, value: Draft[K]) {
|
||||
setDraft((current) => ({ ...current, [key]: value }));
|
||||
function patch(values: Partial<Draft>) {
|
||||
setDraft((current) => ({ ...current, ...values }));
|
||||
setDirty(true);
|
||||
}
|
||||
|
||||
function set<K extends keyof Draft>(key: K, value: Draft[K]) {
|
||||
patch({ [key]: value } as Partial<Draft>);
|
||||
}
|
||||
|
||||
// Nothing this type issues carries an expiry date — a transfer, or any
|
||||
// one-off record. Renewal, its window and its reminders are all measured
|
||||
// against an expiry that never arrives, so none of them are asked for.
|
||||
const expires = draft.validityDays !== null || draft.validityMonths > 0;
|
||||
|
||||
// The server's floor for a stated term is 6 months, so no-expiry is a state
|
||||
// this form can hold and edit around but cannot switch a type into. Offered
|
||||
// only where it is already what the type is, rather than as an option whose
|
||||
// save would be refused.
|
||||
const noExpiryAvailable =
|
||||
(licenseType.validityMonths ?? 12) === 0 && !licenseType.validityDays;
|
||||
|
||||
async function onSave() {
|
||||
// Only the settings this form actually asked for. The endpoint patches, so
|
||||
// an omitted field keeps its stored value — and the fields hidden above are
|
||||
// hidden precisely because the type has no such policy, which the server
|
||||
// stores as a zero its own validators then refuse (`validityMonths` has a
|
||||
// floor of 6, `renewalWindowDays` of 1). Echoing those back is what made
|
||||
// saving an ownership transfer fail outright.
|
||||
const {
|
||||
validityMonths,
|
||||
validityDays,
|
||||
renewalWindowDays,
|
||||
expiryReminderDays,
|
||||
...rest
|
||||
} = draft;
|
||||
|
||||
const ok = await run(
|
||||
() => save({ id: licenseType.id, ...draft }).unwrap(),
|
||||
() =>
|
||||
save({
|
||||
id: licenseType.id,
|
||||
...rest,
|
||||
...(expires ? { validityMonths, validityDays } : {}),
|
||||
...(expires && draft.renewalEnabled
|
||||
? { renewalWindowDays, expiryReminderDays }
|
||||
: {}),
|
||||
}).unwrap(),
|
||||
t('certReq.behavior.saved', 'Configuration saved.'),
|
||||
);
|
||||
if (ok) setDirty(false);
|
||||
@@ -289,94 +331,142 @@ export function BehaviorTab({ licenseType }: { licenseType: LicenseType }) {
|
||||
the seed says `validityMonths: 12` and this now says "12 Months",
|
||||
so the two read the same. Months advance the calendar (issued on
|
||||
the 31st, expires on the 31st); days are for terms shorter than a
|
||||
month can express. */}
|
||||
month can express. The third unit is no term at all, which the
|
||||
server stores as `validityMonths: 0`. */}
|
||||
<Group align="flex-end" gap="sm" wrap="nowrap">
|
||||
<NumberInput
|
||||
label={t('certReq.behavior.validity', 'Valid for')}
|
||||
description={t(
|
||||
'certReq.behavior.validityHint',
|
||||
'Applied when a licence is issued. Licences already issued keep the expiry date they were given.',
|
||||
)}
|
||||
value={draft.validityDays ?? draft.validityMonths}
|
||||
onChange={(v) => {
|
||||
const next = typeof v === 'number' ? v : 0;
|
||||
if (!next) return;
|
||||
if (draft.validityDays !== null) set('validityDays', next);
|
||||
else set('validityMonths', next);
|
||||
}}
|
||||
// Matches the server's ranges, so the box cannot offer a value the
|
||||
// save would reject: 1–3650 days, or 6–240 months.
|
||||
min={draft.validityDays !== null ? 1 : 6}
|
||||
max={draft.validityDays !== null ? 3650 : 240}
|
||||
allowNegative={false}
|
||||
disabled={!canEdit}
|
||||
flex={1}
|
||||
/>
|
||||
{expires && (
|
||||
<NumberInput
|
||||
label={t('certReq.behavior.validity', 'Valid for')}
|
||||
description={t(
|
||||
'certReq.behavior.validityHint',
|
||||
'Applied when a licence is issued. Licences already issued keep the expiry date they were given.',
|
||||
)}
|
||||
value={draft.validityDays ?? draft.validityMonths}
|
||||
onChange={(v) => {
|
||||
const next = typeof v === 'number' ? v : 0;
|
||||
if (!next) return;
|
||||
if (draft.validityDays !== null) set('validityDays', next);
|
||||
else set('validityMonths', next);
|
||||
}}
|
||||
// Matches the server's ranges, so the box cannot offer a value the
|
||||
// save would reject: 1–3650 days, or 6–240 months.
|
||||
min={draft.validityDays !== null ? 1 : 6}
|
||||
max={draft.validityDays !== null ? 3650 : 240}
|
||||
allowNegative={false}
|
||||
disabled={!canEdit}
|
||||
flex={1}
|
||||
/>
|
||||
)}
|
||||
<Select
|
||||
label={
|
||||
expires
|
||||
? undefined
|
||||
: t('certReq.behavior.validityUnit', 'Validity unit')
|
||||
}
|
||||
aria-label={t('certReq.behavior.validityUnit', 'Validity unit')}
|
||||
data={[
|
||||
{ value: 'MONTHS', label: t('certReq.behavior.unitMonths', 'Months') },
|
||||
{ value: 'DAYS', label: t('certReq.behavior.unitDays', 'Days') },
|
||||
...(noExpiryAvailable
|
||||
? [
|
||||
{
|
||||
value: 'NONE',
|
||||
label: t('certReq.behavior.unitNone', 'Does not expire'),
|
||||
},
|
||||
]
|
||||
: []),
|
||||
]}
|
||||
value={draft.validityDays !== null ? 'DAYS' : 'MONTHS'}
|
||||
value={
|
||||
draft.validityDays !== null
|
||||
? 'DAYS'
|
||||
: draft.validityMonths > 0
|
||||
? 'MONTHS'
|
||||
: 'NONE'
|
||||
}
|
||||
onChange={(unit) => {
|
||||
// Switching unit is a change of policy, not a conversion: 12
|
||||
// calendar months is not 365 days, so carry no arithmetic across
|
||||
// and let the administrator state the new term outright.
|
||||
if (unit === 'DAYS') set('validityDays', draft.validityDays ?? 90);
|
||||
else set('validityDays', null);
|
||||
else if (unit === 'MONTHS')
|
||||
patch({
|
||||
validityDays: null,
|
||||
validityMonths:
|
||||
draft.validityMonths >= 6 ? draft.validityMonths : 12,
|
||||
});
|
||||
// No expiry means no renewal policy: clear it here rather than
|
||||
// save renewal settings that could never fire.
|
||||
else
|
||||
patch({
|
||||
validityDays: null,
|
||||
validityMonths: 0,
|
||||
renewalEnabled: false,
|
||||
});
|
||||
}}
|
||||
allowDeselect={false}
|
||||
disabled={!canEdit}
|
||||
w={130}
|
||||
w={expires ? 130 : 220}
|
||||
/>
|
||||
</Group>
|
||||
|
||||
<Switch
|
||||
checked={draft.renewalEnabled}
|
||||
onChange={(e) => set('renewalEnabled', e.currentTarget.checked)}
|
||||
label={t('certReq.behavior.renewalEnabled', 'Holders may renew this licence')}
|
||||
description={t(
|
||||
'certReq.behavior.renewalEnabledHint',
|
||||
'Off for one-off issues — a registration that does not lapse, or a waiver written for a single shipment.',
|
||||
)}
|
||||
disabled={!canEdit}
|
||||
/>
|
||||
{!expires && (
|
||||
<Text size="xs" c="dimmed">
|
||||
{t(
|
||||
'certReq.behavior.noExpiryHint',
|
||||
'What this type issues never expires — an ownership transfer, or any one-off record. There is no renewal policy to set.',
|
||||
)}
|
||||
</Text>
|
||||
)}
|
||||
|
||||
{/* The window and the reminders are both measured against an expiry a
|
||||
non-renewing licence never reaches, so they are hidden rather than
|
||||
shown as settings that quietly do nothing. */}
|
||||
{draft.renewalEnabled && (
|
||||
{expires && (
|
||||
<>
|
||||
<NumberInput
|
||||
label={t('certReq.behavior.renewalWindow', 'Renewal opens (days before expiry)')}
|
||||
value={draft.renewalWindowDays}
|
||||
onChange={(v) =>
|
||||
set('renewalWindowDays', typeof v === 'number' ? v : draft.renewalWindowDays)
|
||||
}
|
||||
min={1}
|
||||
max={365}
|
||||
allowNegative={false}
|
||||
<Switch
|
||||
checked={draft.renewalEnabled}
|
||||
onChange={(e) => set('renewalEnabled', e.currentTarget.checked)}
|
||||
label={t('certReq.behavior.renewalEnabled', 'Holders may renew this licence')}
|
||||
description={t(
|
||||
'certReq.behavior.renewalEnabledHint',
|
||||
'Off for one-off issues — a registration that does not lapse, or a waiver written for a single shipment.',
|
||||
)}
|
||||
disabled={!canEdit}
|
||||
/>
|
||||
|
||||
<MultiSelect
|
||||
label={t('certReq.behavior.reminders', 'Expiry reminders (days before)')}
|
||||
description={t(
|
||||
'certReq.behavior.remindersHint',
|
||||
'The holder is reminded at each of these offsets.',
|
||||
)}
|
||||
data={REMINDER_OFFSETS}
|
||||
value={draft.expiryReminderDays.map(String)}
|
||||
onChange={(values) =>
|
||||
set(
|
||||
'expiryReminderDays',
|
||||
values.map(Number).sort((a, b) => b - a),
|
||||
)
|
||||
}
|
||||
disabled={!canEdit}
|
||||
clearable
|
||||
/>
|
||||
{/* The window and the reminders are both measured against an expiry
|
||||
a non-renewing licence never reaches, so they are hidden rather
|
||||
than shown as settings that quietly do nothing. */}
|
||||
{draft.renewalEnabled && (
|
||||
<>
|
||||
<NumberInput
|
||||
label={t('certReq.behavior.renewalWindow', 'Renewal opens (days before expiry)')}
|
||||
value={draft.renewalWindowDays}
|
||||
onChange={(v) =>
|
||||
set('renewalWindowDays', typeof v === 'number' ? v : draft.renewalWindowDays)
|
||||
}
|
||||
min={1}
|
||||
max={365}
|
||||
allowNegative={false}
|
||||
disabled={!canEdit}
|
||||
/>
|
||||
|
||||
<MultiSelect
|
||||
label={t('certReq.behavior.reminders', 'Expiry reminders (days before)')}
|
||||
description={t(
|
||||
'certReq.behavior.remindersHint',
|
||||
'The holder is reminded at each of these offsets.',
|
||||
)}
|
||||
data={REMINDER_OFFSETS}
|
||||
value={draft.expiryReminderDays.map(String)}
|
||||
onChange={(values) =>
|
||||
set(
|
||||
'expiryReminderDays',
|
||||
values.map(Number).sort((a, b) => b - a),
|
||||
)
|
||||
}
|
||||
disabled={!canEdit}
|
||||
clearable
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</Section>
|
||||
|
||||
@@ -88,8 +88,6 @@ export const am: Translations = {
|
||||
btcQueue: "የBTC ወረፋ",
|
||||
cocQueue: "የCoC ወረፋ",
|
||||
copQueue: "የCoP ወረፋ",
|
||||
endorsementCocQueue: "የCoC እውቅና ወረፋ",
|
||||
endorsementGocQueue: "የGOC እውቅና ወረፋ",
|
||||
endorsementQueue: "የማስተያየት ወረፋ",
|
||||
vesselRegistrations: "የመርከብ ምዝገባ",
|
||||
// vesselRegistrationReport: 'የምዝገባ ሪፖርት',
|
||||
@@ -1274,6 +1272,7 @@ export const am: Translations = {
|
||||
validityMonths_other: "{{count}} ወራት",
|
||||
validityDays_one: "{{count}} ቀን",
|
||||
validityDays_other: "{{count}} ቀናት",
|
||||
validityNone: "ጊዜው አያልፍም",
|
||||
validityEditedOn: "— በምስክር ወረቀት መስፈርቶች → ባህሪ ውስጥ ይዘጋጃል",
|
||||
newVersion: "አዲስ ስሪት",
|
||||
versions: "ስሪቶች",
|
||||
@@ -1360,8 +1359,11 @@ export const am: Translations = {
|
||||
validityUnit: "የሚቆይበት መለኪያ",
|
||||
unitMonths: "ወራት",
|
||||
unitDays: "ቀናት",
|
||||
unitNone: "ጊዜው አያልፍም",
|
||||
validityHint:
|
||||
"ፈቃድ ሲሰጥ ተግባራዊ ይሆናል። ቀደም ብለው የተሰጡ ፈቃዶች የተሰጣቸውን የማብቂያ ቀን ይይዛሉ።",
|
||||
noExpiryHint:
|
||||
"ይህ ዓይነት የሚሰጠው ሰነድ ጊዜው አያልፍም — የባለቤትነት ዝውውር ወይም አንድ ጊዜ ብቻ የሚሰጥ መዝገብ። የሚቀመጥ የዕድሳት መመሪያ የለም።",
|
||||
requiresSeafarer: "የጸና የመርከበኛ ምዝገባ ያስፈልገዋል",
|
||||
requiresSeafarerHint:
|
||||
"ይህንን ዓይነት እንደ የመርከበኛ የምስክር ወረቀት ያመለክታል፤ በመርከበኛው ፖርታል ላይ እንዲታይ የሚያደርገው ይኸው ነው።",
|
||||
|
||||
@@ -88,8 +88,6 @@ export const en = {
|
||||
postWaiverQueue: 'Post-Waiver Queue',
|
||||
cocQueue: 'CoC Queue',
|
||||
copQueue: 'CoP Queue',
|
||||
endorsementCocQueue: 'CoC Endorsement Queue',
|
||||
endorsementGocQueue: 'GOC Endorsement Queue',
|
||||
endorsementQueue: 'Endorsement Queue',
|
||||
vesselRegistrations: 'Vessel Registration',
|
||||
vesselTransfers: 'Vessel Ownership Transfer',
|
||||
@@ -1281,6 +1279,7 @@ export const en = {
|
||||
validityMonths_other: '{{count}} months',
|
||||
validityDays_one: '{{count}} day',
|
||||
validityDays_other: '{{count}} days',
|
||||
validityNone: 'Does not expire',
|
||||
validityEditedOn: '— set on Certificate Requirements → Behaviour',
|
||||
newVersion: 'New version',
|
||||
versions: 'Versions',
|
||||
@@ -1366,8 +1365,11 @@ export const en = {
|
||||
validityUnit: 'Validity unit',
|
||||
unitMonths: 'Months',
|
||||
unitDays: 'Days',
|
||||
unitNone: 'Does not expire',
|
||||
validityHint:
|
||||
'Applied when a licence is issued. Licences already issued keep the expiry date they were given.',
|
||||
noExpiryHint:
|
||||
'What this type issues never expires — an ownership transfer, or any one-off record. There is no renewal policy to set.',
|
||||
requiresSeafarer: 'Requires an active seafarer registration',
|
||||
requiresSeafarerHint:
|
||||
'Also marks this type as a seafarer certificate, which is what makes it appear in the seafarer portal.',
|
||||
|
||||
@@ -189,18 +189,6 @@ export const NAV_SECTIONS: NavSection[] = [
|
||||
icon: IconShieldCheck,
|
||||
permissions: SEAFARER_QUEUE,
|
||||
},
|
||||
{
|
||||
to: "/licence-review/type/ENDORSEMENT_COC",
|
||||
label: "nav.endorsementCocQueue",
|
||||
icon: IconRubberStamp,
|
||||
permissions: [P.VIEW_SEAFARER_REGISTRY],
|
||||
},
|
||||
{
|
||||
to: "/licence-review/type/ENDORSEMENT_GOC",
|
||||
label: "nav.endorsementGocQueue",
|
||||
icon: IconRubberStamp,
|
||||
permissions: [P.VIEW_SEAFARER_REGISTRY],
|
||||
},
|
||||
{
|
||||
to: "/licence-review/type/ENDORSEMENT_SEAFARER",
|
||||
label: "nav.endorsementQueue",
|
||||
|
||||
@@ -12,16 +12,13 @@ export default defineConfig({
|
||||
server: {
|
||||
port: 4201,
|
||||
host: 'localhost',
|
||||
proxy: {
|
||||
'/api': {
|
||||
target: 'https://ema-api-dev.triaplc.com',
|
||||
changeOrigin: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
// server: {
|
||||
// port: 4201,
|
||||
// proxy: {
|
||||
// '/api': {
|
||||
// target: 'http://localhost:3000', // change from 'https://ema-api-dev.triaplc.com'
|
||||
// changeOrigin: true,
|
||||
// },
|
||||
// },
|
||||
// },
|
||||
preview: { port: 4201, host: 'localhost' },
|
||||
plugins: [react(), nxViteTsPaths()],
|
||||
resolve: {
|
||||
|
||||
@@ -322,8 +322,16 @@ function LicenseTypeCard({
|
||||
</Tooltip>
|
||||
)}
|
||||
{type.issuesCertificate ? (
|
||||
// A term in days wins over the months column, and a type with
|
||||
// neither issues something that simply does not expire — a
|
||||
// transfer, or any one-off record. Reading `validityMonths`
|
||||
// alone badged those "0 months".
|
||||
<Badge size="sm" variant="light" color="teal">
|
||||
{t('licensing.catalogue.validityBadge', { months: type.validityMonths })}
|
||||
{type.validityDays
|
||||
? t('licensing.catalogue.validityDaysBadge', { count: type.validityDays })
|
||||
: type.validityMonths
|
||||
? t('licensing.catalogue.validityBadge', { months: type.validityMonths })
|
||||
: t('licensing.catalogue.noExpiryBadge')}
|
||||
</Badge>
|
||||
) : (
|
||||
<Tooltip label={t('licensing.catalogue.evaluationTooltip')}>
|
||||
|
||||
@@ -61,7 +61,7 @@ export function SelectField(p: FieldProps & { options: { value: string; label: s
|
||||
);
|
||||
}
|
||||
|
||||
export function DateField(p: FieldProps) {
|
||||
export function DateField(p: FieldProps & { minDate?: Date | string; maxDate?: Date | string }) {
|
||||
return (
|
||||
<Col span={p.span}>
|
||||
<AmharicDatePicker
|
||||
@@ -70,6 +70,8 @@ export function DateField(p: FieldProps) {
|
||||
disabled={p.disabled}
|
||||
required={p.required}
|
||||
dateFormat="date"
|
||||
minDate={p.minDate}
|
||||
maxDate={p.maxDate}
|
||||
value={(p.form[p.name] as string) ?? ''}
|
||||
onChange={(v) => p.set(p.name, v)}
|
||||
/>
|
||||
|
||||
@@ -220,6 +220,10 @@ export function EmergencyContactStep(p: StepProps) {
|
||||
name="medicalIssueDate"
|
||||
label="Issue Date"
|
||||
required
|
||||
// Bounded here rather than only at submit: the calendar is the one
|
||||
// place the applicant can see why a day is refused, and the server's
|
||||
// rejection otherwise only surfaces five steps later.
|
||||
maxDate={new Date()}
|
||||
description="Cannot be a future date. Validity is calculated from this: two years, or one year if you are under 18."
|
||||
/>
|
||||
</Grid>
|
||||
|
||||
@@ -80,6 +80,11 @@ function answersOf(registration: SeafarerRegistration): SaveSeafarerRegistration
|
||||
return Object.fromEntries(ANSWER_KEYS.map((k) => [k, registration[k]])) as SaveSeafarerRegistration;
|
||||
}
|
||||
|
||||
/** Today as `yyyy-mm-dd` in the browser's own zone — en-CA is that format. */
|
||||
function todayDate(): string {
|
||||
return new Date().toLocaleDateString('en-CA');
|
||||
}
|
||||
|
||||
function blank(value: unknown): boolean {
|
||||
return value === null || value === undefined || value === '' || value === false;
|
||||
}
|
||||
@@ -258,6 +263,10 @@ export function SeafarerRegistrationPage() {
|
||||
|
||||
function set(key: AnswerKey, value: unknown) {
|
||||
setForm((prev) => ({ ...prev, [key]: value }));
|
||||
// The submit refusal names what was wrong at the time it was refused.
|
||||
// Leaving it on screen while the applicant corrects it reads as the
|
||||
// correction having been ignored.
|
||||
if (issues.length) setIssues([]);
|
||||
setErrors((prev) => {
|
||||
if (!prev[key]) return prev;
|
||||
const next = { ...prev };
|
||||
@@ -280,6 +289,9 @@ export function SeafarerRegistrationPage() {
|
||||
found.weightKg = `Enter a weight between ${weightKg.min} and ${weightKg.max} kg.`;
|
||||
}
|
||||
}
|
||||
if (index === 2 && (form.medicalIssueDate ?? '').slice(0, 10) > todayDate()) {
|
||||
found.medicalIssueDate = 'The issue date cannot be in the future.';
|
||||
}
|
||||
setErrors(found);
|
||||
const missingKeys = Object.keys(found) as AnswerKey[];
|
||||
if (missingKeys.length) {
|
||||
@@ -323,19 +335,23 @@ export function SeafarerRegistrationPage() {
|
||||
}
|
||||
|
||||
async function goToStep(target: number) {
|
||||
if (target <= active) {
|
||||
setActive(target);
|
||||
return;
|
||||
}
|
||||
// Going forward validates every step passed over, so a jump cannot skip a
|
||||
// required field; the walk stops on the first step that fails.
|
||||
for (let step = active; step < target; step++) {
|
||||
if (!readOnly && !validateStep(step)) {
|
||||
setActive(step);
|
||||
return;
|
||||
// Saved before anything can turn the navigation around. `form` is the only
|
||||
// copy of what was typed, so validating first — as this used to — threw the
|
||||
// edit away on every blocked step and every step back: an applicant fixing
|
||||
// a field the submit check rejected watched the correction vanish. A draft
|
||||
// takes any subset of the answers, so persisting an incomplete one is safe.
|
||||
const saved = await saveAnswers();
|
||||
if (target > active) {
|
||||
if (!saved) return;
|
||||
// Going forward validates every step passed over, so a jump cannot skip a
|
||||
// required field; the walk stops on the first step that fails.
|
||||
for (let step = active; step < target; step++) {
|
||||
if (!readOnly && !validateStep(step)) {
|
||||
setActive(step);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!(await saveAnswers())) return;
|
||||
setErrors({});
|
||||
setActive(target);
|
||||
}
|
||||
@@ -343,8 +359,8 @@ export function SeafarerRegistrationPage() {
|
||||
async function handleSubmit() {
|
||||
if (!registration) return;
|
||||
setIssues([]);
|
||||
if (!readOnly && !validateStep(4)) return;
|
||||
if (!(await saveAnswers())) return;
|
||||
if (!readOnly && !validateStep(4)) return;
|
||||
try {
|
||||
await submit(registration.id).unwrap();
|
||||
notifications.show({
|
||||
@@ -510,7 +526,7 @@ export function SeafarerRegistrationPage() {
|
||||
)}
|
||||
|
||||
<Group justify="space-between" mt="xl">
|
||||
<Button variant="default" onClick={() => setActive((s) => Math.max(0, s - 1))} disabled={active === 0}>
|
||||
<Button variant="default" onClick={() => goToStep(Math.max(0, active - 1))} disabled={active === 0}>
|
||||
Back
|
||||
</Button>
|
||||
{active < STEPS.length - 1 ? (
|
||||
|
||||
@@ -893,6 +893,9 @@ export const am: Translations = {
|
||||
capitalTooltip: 'በባንክ ደብዳቤ መረጋገጥ ያለበት ዝቅተኛ ካፒታል',
|
||||
capitalBadge: 'ካፒታል {{amount}}',
|
||||
validityBadge: '{{months}} ወራት',
|
||||
validityDaysBadge_one: '{{count}} ቀን',
|
||||
validityDaysBadge_other: '{{count}} ቀናት',
|
||||
noExpiryBadge: 'ጊዜው አያልፍም',
|
||||
evaluationTooltip: 'በምስክር ወረቀት ፈንታ በባለሥልጣኑ ውሳኔ የሚጠናቀቅ',
|
||||
evaluationOnly: 'ግምገማ ብቻ',
|
||||
startApplication: 'ማመልከቻ ጀምር',
|
||||
|
||||
@@ -899,6 +899,9 @@ export const en = {
|
||||
capitalTooltip: 'Minimum capital that must be evidenced by a bank letter',
|
||||
capitalBadge: 'Capital {{amount}}',
|
||||
validityBadge: '{{months}} months',
|
||||
validityDaysBadge_one: '{{count}} day',
|
||||
validityDaysBadge_other: '{{count}} days',
|
||||
noExpiryBadge: 'No expiry',
|
||||
evaluationTooltip: 'Concludes with an EMA decision rather than a certificate',
|
||||
evaluationOnly: 'Evaluation only',
|
||||
startApplication: 'Start application',
|
||||
|
||||
@@ -9,16 +9,16 @@ export default defineConfig({
|
||||
// built-in default.
|
||||
envDir: "../../",
|
||||
cacheDir: "../../node_modules/.vite/apps/portal",
|
||||
server: { port: 4200, host: "localhost" },
|
||||
// server: {
|
||||
// port: 4200,
|
||||
// proxy: {
|
||||
// '/api': {
|
||||
// target: 'http://localhost:3000', // change from 'https://ema-api-dev.triaplc.com'
|
||||
// changeOrigin: true,
|
||||
// },
|
||||
// },
|
||||
// },
|
||||
server: {
|
||||
port: 4200,
|
||||
host: "localhost",
|
||||
proxy: {
|
||||
'/api': {
|
||||
target: 'https://ema-api-dev.triaplc.com',
|
||||
changeOrigin: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
preview: { port: 4200, host: "localhost" },
|
||||
plugins: [react(), nxViteTsPaths()],
|
||||
resolve: {
|
||||
|
||||
Reference in New Issue
Block a user