mirror of
https://github.com/Tria-plc/emaui.git
synced 2026-09-01 18:13:28 +00:00
feat: add support for license validity defined in days alongside months
This commit is contained in:
@@ -14,6 +14,8 @@ interface Props {
|
||||
onRankChange: (id: string | null) => void;
|
||||
/** Shown for context only; edited on Certificate Requirements → Behaviour. */
|
||||
validityMonths: number;
|
||||
/** Non-null when the type is configured in days rather than months. */
|
||||
validityDays?: number | null;
|
||||
canEdit: boolean;
|
||||
onNewVersion: () => void;
|
||||
}
|
||||
@@ -27,6 +29,7 @@ export function DesignerToolbar({
|
||||
rankId,
|
||||
onRankChange,
|
||||
validityMonths,
|
||||
validityDays,
|
||||
canEdit,
|
||||
onNewVersion,
|
||||
}: Props) {
|
||||
@@ -75,11 +78,15 @@ export function DesignerToolbar({
|
||||
{typeId && (
|
||||
<Stack gap={2}>
|
||||
<Text size="xs" c="dimmed" fw={500}>
|
||||
{t('designer.validityYears', 'Valid for (years)')}
|
||||
{t('designer.validity', 'Valid for')}
|
||||
</Text>
|
||||
<Group gap={6} align="baseline">
|
||||
<Text size="sm" fw={600}>
|
||||
{Number((validityMonths / 12).toFixed(2))}
|
||||
{validityDays != null
|
||||
? t('designer.validityDays', '{{count}} days', { count: validityDays })
|
||||
: t('designer.validityMonths', '{{count}} months', {
|
||||
count: validityMonths,
|
||||
})}
|
||||
</Text>
|
||||
<Text size="xs" c="dimmed">
|
||||
{t('designer.validityEditedOn', '— set on Certificate Requirements → Behaviour')}
|
||||
|
||||
@@ -163,6 +163,7 @@ export function CertificateDesignerPage() {
|
||||
draft.setSelectedId(null);
|
||||
}}
|
||||
validityMonths={selectedType?.validityMonths ?? 12}
|
||||
validityDays={selectedType?.validityDays ?? null}
|
||||
canEdit={canEdit}
|
||||
onNewVersion={startNewVersion}
|
||||
/>
|
||||
|
||||
@@ -38,6 +38,7 @@ interface Draft {
|
||||
minSeaTimeDays: number | null;
|
||||
capitalThreshold: number | null;
|
||||
validityMonths: number;
|
||||
validityDays: number | null;
|
||||
renewalWindowDays: number;
|
||||
expiryReminderDays: number[];
|
||||
requiresOperatorMode: boolean;
|
||||
@@ -66,6 +67,7 @@ function toDraft(licenseType: LicenseType): Draft {
|
||||
? null
|
||||
: Number(licenseType.capitalThreshold),
|
||||
validityMonths: licenseType.validityMonths ?? 12,
|
||||
validityDays: licenseType.validityDays ?? null,
|
||||
renewalWindowDays: licenseType.renewalWindowDays ?? 60,
|
||||
expiryReminderDays: licenseType.expiryReminderDays ?? [60, 30, 7],
|
||||
requiresOperatorMode: licenseType.requiresOperatorMode ?? true,
|
||||
@@ -266,25 +268,52 @@ export function BehaviorTab({ licenseType }: { licenseType: LicenseType }) {
|
||||
</Section>
|
||||
|
||||
<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}
|
||||
/>
|
||||
{/* Amount + unit rather than a years box that silently divides by 12:
|
||||
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. */}
|
||||
<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}
|
||||
/>
|
||||
<Select
|
||||
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') },
|
||||
]}
|
||||
value={draft.validityDays !== null ? 'DAYS' : 'MONTHS'}
|
||||
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);
|
||||
}}
|
||||
allowDeselect={false}
|
||||
disabled={!canEdit}
|
||||
w={130}
|
||||
/>
|
||||
</Group>
|
||||
|
||||
<NumberInput
|
||||
label={t('certReq.behavior.renewalWindow', 'Renewal opens (days before expiry)')}
|
||||
|
||||
@@ -1225,7 +1225,11 @@ export const am: Translations = {
|
||||
title: "የምስክር ወረቀት ንድፍ",
|
||||
subtitle: "ለፈቃድ ባለቤቶች የሚሰጠውን የምስክር ወረቀት ይንደፉ።",
|
||||
licenceType: "የፈቃድ ዓይነት",
|
||||
validityYears: "የሚቆይበት (ዓመታት)",
|
||||
validity: "የሚቆይበት",
|
||||
validityMonths_one: "{{count}} ወር",
|
||||
validityMonths_other: "{{count}} ወራት",
|
||||
validityDays_one: "{{count}} ቀን",
|
||||
validityDays_other: "{{count}} ቀናት",
|
||||
validityEditedOn: "— በምስክር ወረቀት መስፈርቶች → ባህሪ ውስጥ ይዘጋጃል",
|
||||
newVersion: "አዲስ ስሪት",
|
||||
versions: "ስሪቶች",
|
||||
@@ -1299,7 +1303,10 @@ export const am: Translations = {
|
||||
capitalOn: "አነስተኛ የተከፈለ ካፒታል ይጠየቅ",
|
||||
capitalHint:
|
||||
"ኃላፊው ከዚህ እኩል ወይም በላይ የሆነ ካፒታል እስኪያረጋግጥ ድረስ ማጽደቅ አይችልም። ለአዲሶቹ ብቻ ሳይሆን ቀደም ብለው በወረፋ ላይ ላሉ ማመልከቻዎችም ይሠራል።",
|
||||
validityYears: "የሚቆይበት (ዓመታት)",
|
||||
validity: "የሚቆይበት",
|
||||
validityUnit: "የሚቆይበት መለኪያ",
|
||||
unitMonths: "ወራት",
|
||||
unitDays: "ቀናት",
|
||||
validityHint:
|
||||
"ፈቃድ ሲሰጥ ተግባራዊ ይሆናል። ቀደም ብለው የተሰጡ ፈቃዶች የተሰጣቸውን የማብቂያ ቀን ይይዛሉ።",
|
||||
requiresSeafarer: "የጸና የመርከበኛ ምዝገባ ያስፈልገዋል",
|
||||
|
||||
@@ -1231,7 +1231,11 @@ export const en = {
|
||||
title: 'Certificate designer',
|
||||
subtitle: 'Design the certificate issued to licence holders.',
|
||||
licenceType: 'Licence type',
|
||||
validityYears: 'Valid for (years)',
|
||||
validity: 'Valid for',
|
||||
validityMonths_one: '{{count}} month',
|
||||
validityMonths_other: '{{count}} months',
|
||||
validityDays_one: '{{count}} day',
|
||||
validityDays_other: '{{count}} days',
|
||||
validityEditedOn: '— set on Certificate Requirements → Behaviour',
|
||||
newVersion: 'New version',
|
||||
versions: 'Versions',
|
||||
@@ -1304,7 +1308,10 @@ export const en = {
|
||||
capitalOn: 'Require a minimum paid-up capital',
|
||||
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.',
|
||||
validityYears: 'Valid for (years)',
|
||||
validity: 'Valid for',
|
||||
validityUnit: 'Validity unit',
|
||||
unitMonths: 'Months',
|
||||
unitDays: 'Days',
|
||||
validityHint:
|
||||
'Applied when a licence is issued. Licences already issued keep the expiry date they were given.',
|
||||
requiresSeafarer: 'Requires an active seafarer registration',
|
||||
|
||||
@@ -227,6 +227,7 @@ export const licensingApi = baseApi
|
||||
requiresValidMedical?: boolean;
|
||||
minSeaTimeDays?: number | null;
|
||||
validityMonths?: number;
|
||||
validityDays?: number | null;
|
||||
capitalThreshold?: number | null;
|
||||
renewalWindowDays?: number;
|
||||
expiryReminderDays?: number[];
|
||||
|
||||
@@ -187,6 +187,11 @@ export interface LicenseType {
|
||||
feeCurrency: string;
|
||||
capitalThreshold: string | number | null;
|
||||
validityMonths: number;
|
||||
/**
|
||||
* A term in days instead of months, for licences shorter than a month can
|
||||
* express. Wins over `validityMonths` when set; null keeps calendar months.
|
||||
*/
|
||||
validityDays?: number | null;
|
||||
/**
|
||||
* Target turnaround in hours. Null means this type is not tracked against
|
||||
* an SLA, which the grid renders as "—" rather than as instantly overdue.
|
||||
|
||||
Reference in New Issue
Block a user