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

@@ -1,4 +1,4 @@
import { Button, Group, NumberInput, Select, Tooltip } from '@mantine/core';
import { Button, Group, Select, Stack, Text } from '@mantine/core';
import { IconPlus } from '@tabler/icons-react';
import { useTranslation } from 'react-i18next';
import { useLocalized, type LicenseType, type Rank } from '@ema-platform/api';
@@ -12,12 +12,9 @@ interface Props {
ranks: Rank[];
rankId: string | null;
onRankChange: (id: string | null) => void;
/** Shown for context only; edited on Certificate Requirements → Behaviour. */
validityMonths: number;
onValidityChange: (months: number) => void;
currentValidityMonths?: number | null;
canEdit: boolean;
savingValidity: boolean;
onSaveValidity: () => void;
onNewVersion: () => void;
}
@@ -30,11 +27,7 @@ export function DesignerToolbar({
rankId,
onRankChange,
validityMonths,
onValidityChange,
currentValidityMonths,
canEdit,
savingValidity,
onSaveValidity,
onNewVersion,
}: Props) {
const { t } = useTranslation();
@@ -73,38 +66,27 @@ export function DesignerToolbar({
/>
)}
{/* Validity lives beside the design because it is the other half of
what a certificate promises. */}
<NumberInput
label={t('designer.validityYears', 'Valid for (years)')}
description={t('designer.validityHint', 'Applied when a licence is issued')}
value={Number((validityMonths / 12).toFixed(2))}
onChange={(value) => onValidityChange(Math.round(Number(value || 0) * 12))}
min={0.5}
max={20}
step={0.5}
decimalScale={1}
w={190}
disabled={!canEdit}
/>
<Tooltip
label={
canEdit
? t('designer.saveValidity', 'Save validity')
: t('designer.noPermission', 'You do not have permission')
}
>
<span>
<Button
variant="light"
loading={savingValidity}
disabled={!canEdit || !typeId || validityMonths === currentValidityMonths}
onClick={onSaveValidity}
>
{t('designer.saveValidity', 'Save validity')}
</Button>
</span>
</Tooltip>
{/* Read-only here. Validity is one policy decision with the renewal
window and the expiry reminders, so it is edited in one place —
Certificate Requirements → Behaviour — rather than from two screens
behind two different permissions. Still shown, because a designer
laying out a certificate that prints an expiry needs to see the term
it promises. */}
{typeId && (
<Stack gap={2}>
<Text size="xs" c="dimmed" fw={500}>
{t('designer.validityYears', 'Valid for (years)')}
</Text>
<Group gap={6} align="baseline">
<Text size="sm" fw={600}>
{Number((validityMonths / 12).toFixed(2))}
</Text>
<Text size="xs" c="dimmed">
{t('designer.validityEditedOn', '— set on Certificate Requirements → Behaviour')}
</Text>
</Group>
</Stack>
)}
<div style={{ flex: 1 }} />

View File

@@ -29,7 +29,6 @@ import {
useGetRanksQuery,
useGetTemplateVariablesQuery,
usePublishLicenseTemplateMutation,
useUpdateLicenseValidityMutation,
useUpdateLicenseTemplateMutation,
} from '@ema-platform/api';
import { EmptyState, ErrorState, PageHeader, PdfPreviewModal } from '@ema-platform/ui';
@@ -88,7 +87,6 @@ export function CertificateDesignerPage() {
const [publishTemplate, { isLoading: publishing }] = usePublishLicenseTemplateMutation();
const [archiveTemplate] = useArchiveLicenseTemplateMutation();
const [deleteTemplate] = useDeleteLicenseTemplateMutation();
const [updateValidity, { isLoading: savingValidity }] = useUpdateLicenseValidityMutation();
const draft = useTemplateDraft(templates);
const run = useDesignerActions();
@@ -96,7 +94,6 @@ export function CertificateDesignerPage() {
const [newOpen, setNewOpen] = useState(false);
const [newName, setNewName] = useState('');
const [validityMonths, setValidityMonths] = useState<number>(12);
const [mode, setMode] = useState<'canvas' | 'source'>('canvas');
const selectedType = licenseTypes?.items?.find((type) => type.id === typeId);
@@ -127,10 +124,6 @@ export function CertificateDesignerPage() {
if (!typeId && licenseTypes?.items?.length) setTypeId(licenseTypes.items[0].id);
}, [licenseTypes, typeId]);
useEffect(() => {
if (selectedType) setValidityMonths(selectedType.validityMonths ?? 12);
}, [selectedType]);
// Switching licence type leaves a stale rank selected from the previous
// type's ladder — reset to the type's default design.
useEffect(() => {
@@ -169,17 +162,8 @@ export function CertificateDesignerPage() {
setRankId(value);
draft.setSelectedId(null);
}}
validityMonths={validityMonths}
onValidityChange={setValidityMonths}
currentValidityMonths={selectedType?.validityMonths}
validityMonths={selectedType?.validityMonths ?? 12}
canEdit={canEdit}
savingValidity={savingValidity}
onSaveValidity={() =>
run(
() => updateValidity({ id: typeId as string, validityMonths }).unwrap(),
t('designer.validitySaved', 'Validity updated'),
)
}
onNewVersion={startNewVersion}
/>