refactor: move license validity management to behavior tab and add capital threshold requirement

This commit is contained in:
estifanos
2026-08-29 08:22:09 +00:00
parent 92390ed573
commit 53c0ec98af
6 changed files with 102 additions and 73 deletions

View File

@@ -36,6 +36,8 @@ interface Draft {
requiresSeafarerRegistration: boolean;
requiresValidMedical: boolean;
minSeaTimeDays: number | null;
capitalThreshold: number | null;
validityMonths: number;
renewalWindowDays: number;
expiryReminderDays: number[];
requiresOperatorMode: boolean;
@@ -59,6 +61,11 @@ function toDraft(licenseType: LicenseType): Draft {
licenseType.requiresSeafarerRegistration ?? false,
requiresValidMedical: licenseType.requiresValidMedical ?? false,
minSeaTimeDays: licenseType.minSeaTimeDays ?? null,
capitalThreshold:
licenseType.capitalThreshold == null
? null
: Number(licenseType.capitalThreshold),
validityMonths: licenseType.validityMonths ?? 12,
renewalWindowDays: licenseType.renewalWindowDays ?? 60,
expiryReminderDays: licenseType.expiryReminderDays ?? [60, 30, 7],
requiresOperatorMode: licenseType.requiresOperatorMode ?? true,
@@ -194,10 +201,25 @@ export function BehaviorTab({ licenseType }: { licenseType: LicenseType }) {
<Text size="xs" c="dimmed">
{t(
'certReq.behavior.eligibilityHint',
'Checked when an applicant submits. Tightening a gate can stop someone mid-way from submitting a draft they have already started.',
'Checked when an applicant submits, and the capital requirement again when an officer approves. Tightening a gate can stop someone mid-way from submitting a draft they have already started.',
)}
</Text>
<NullableNumber
label={t('certReq.behavior.capitalThreshold', 'Minimum paid-up capital')}
switchLabel={t('certReq.behavior.capitalOn', 'Require a minimum paid-up capital')}
description={t(
'certReq.behavior.capitalHint',
'An officer cannot approve until they have verified capital at or above this. Applies to applications already in the queue, not just new ones.',
)}
value={draft.capitalThreshold}
onChange={(v) => set('capitalThreshold', v)}
disabled={!canEdit}
min={0}
defaultValue={1_000_000}
thousandSeparator
/>
<Switch
checked={draft.requiresSeafarerRegistration}
onChange={(e) => set('requiresSeafarerRegistration', e.currentTarget.checked)}
@@ -243,7 +265,27 @@ export function BehaviorTab({ licenseType }: { licenseType: LicenseType }) {
/>
</Section>
<Section title={t('certReq.behavior.renewal', 'Renewal')}>
<Section title={t('certReq.behavior.renewal', 'Validity and renewal')}>
{/* Stored in months, edited in years: a licence term is a number of
years to everyone who works with one. Half-years stay expressible. */}
<NumberInput
label={t('certReq.behavior.validityYears', 'Valid for (years)')}
description={t(
'certReq.behavior.validityHint',
'Applied when a licence is issued. Licences already issued keep the expiry date they were given.',
)}
value={Number((draft.validityMonths / 12).toFixed(2))}
onChange={(v) =>
set('validityMonths', Math.round(Number(v || 0) * 12) || draft.validityMonths)
}
min={0.5}
max={20}
step={0.5}
decimalScale={1}
allowNegative={false}
disabled={!canEdit}
/>
<NumberInput
label={t('certReq.behavior.renewalWindow', 'Renewal opens (days before expiry)')}
value={draft.renewalWindowDays}
@@ -389,6 +431,8 @@ function NullableNumber({
onChange,
disabled,
min,
defaultValue,
thousandSeparator,
}: {
label: string;
switchLabel: string;
@@ -397,12 +441,17 @@ function NullableNumber({
onChange: (value: number | null) => void;
disabled: boolean;
min: number;
/** Seeded when the switch is turned on. Defaults to the minimum. */
defaultValue?: number;
thousandSeparator?: boolean;
}) {
return (
<Stack gap="xs">
<Switch
checked={value !== null}
onChange={(e) => onChange(e.currentTarget.checked ? min || 1 : null)}
onChange={(e) =>
onChange(e.currentTarget.checked ? (defaultValue ?? min ?? 1) : null)
}
label={switchLabel}
description={description}
disabled={disabled}
@@ -414,6 +463,8 @@ function NullableNumber({
onChange={(v) => onChange(typeof v === 'number' ? v : value)}
min={min}
allowNegative={false}
thousandSeparator={thousandSeparator ? ',' : undefined}
decimalScale={thousandSeparator ? 2 : undefined}
disabled={disabled}
/>
)}