mirror of
https://github.com/Tria-plc/emaui.git
synced 2026-08-26 19:12:50 +00:00
feat: add payment functionality for passed exam certificates and update related statuses
This commit is contained in:
@@ -36,6 +36,7 @@ import {
|
|||||||
useGetMyMedicalCertificatesQuery,
|
useGetMyMedicalCertificatesQuery,
|
||||||
} from '@ema-platform/api';
|
} from '@ema-platform/api';
|
||||||
import { PdfPreviewModal } from '@ema-platform/ui';
|
import { PdfPreviewModal } from '@ema-platform/ui';
|
||||||
|
import { useApplicationPayment } from '../../payments/hooks/useApplicationPayment';
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
// Mock data
|
// Mock data
|
||||||
@@ -55,6 +56,9 @@ interface CertificatesOverview {
|
|||||||
type: string;
|
type: string;
|
||||||
submitted: string;
|
submitted: string;
|
||||||
status: string;
|
status: string;
|
||||||
|
/** The fee owed at the current status, or null when nothing is due. */
|
||||||
|
feeAmount: number | null;
|
||||||
|
feeCurrency: string | null;
|
||||||
}[];
|
}[];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -141,6 +145,7 @@ export function CertificatesPage() {
|
|||||||
const [previewUrl, setPreviewUrl] = useState<string | null>(null);
|
const [previewUrl, setPreviewUrl] = useState<string | null>(null);
|
||||||
const [previewTitle, setPreviewTitle] = useState('');
|
const [previewTitle, setPreviewTitle] = useState('');
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
|
const { pay, isPaying } = useApplicationPayment();
|
||||||
|
|
||||||
const { data } = useApiQuery<CertificatesOverview>({
|
const { data } = useApiQuery<CertificatesOverview>({
|
||||||
url: '/certificates/my',
|
url: '/certificates/my',
|
||||||
@@ -313,14 +318,28 @@ export function CertificatesPage() {
|
|||||||
</Badge>
|
</Badge>
|
||||||
</Table.Td>
|
</Table.Td>
|
||||||
<Table.Td>
|
<Table.Td>
|
||||||
<Text
|
<Group gap="xs" justify="flex-end" wrap="nowrap">
|
||||||
fz="xs"
|
{/* feeAmount is non-null exactly when the server would
|
||||||
c="blue"
|
accept a payment, so the button and the API agree. */}
|
||||||
style={{ cursor: 'pointer' }}
|
{app.feeAmount !== null && (
|
||||||
onClick={() => navigate(`/applications/${app.applicationId}`)}
|
<Button
|
||||||
>
|
size="xs"
|
||||||
Details
|
color="yellow"
|
||||||
</Text>
|
loading={isPaying}
|
||||||
|
onClick={() => pay(app.applicationId)}
|
||||||
|
>
|
||||||
|
Pay {app.feeAmount.toLocaleString()} {app.feeCurrency}
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
<Text
|
||||||
|
fz="xs"
|
||||||
|
c="blue"
|
||||||
|
style={{ cursor: 'pointer' }}
|
||||||
|
onClick={() => navigate(`/applications/${app.applicationId}`)}
|
||||||
|
>
|
||||||
|
Details
|
||||||
|
</Text>
|
||||||
|
</Group>
|
||||||
</Table.Td>
|
</Table.Td>
|
||||||
</Table.Tr>
|
</Table.Tr>
|
||||||
))}
|
))}
|
||||||
|
|||||||
@@ -93,6 +93,25 @@ export function ExamStageActions({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Passed: the certificate itself is the last fee on the application.
|
||||||
|
if (app.status === 'EXAM_PASSED') {
|
||||||
|
return (
|
||||||
|
<Button
|
||||||
|
size="xs"
|
||||||
|
variant="filled"
|
||||||
|
color="yellow"
|
||||||
|
loading={paying}
|
||||||
|
onClick={() => onPay(app)}
|
||||||
|
>
|
||||||
|
{t('applications.actions.payCertificateFee', {
|
||||||
|
defaultValue: 'Pay certificate fee ({{amount}} {{currency}})',
|
||||||
|
amount: Number(app.feeAmount ?? 0).toLocaleString(),
|
||||||
|
currency: app.feeCurrency,
|
||||||
|
})}
|
||||||
|
</Button>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// Paid and scheduled are both waiting states — nothing for the candidate to
|
// Paid and scheduled are both waiting states — nothing for the candidate to
|
||||||
// do, so say so rather than offering a button that does nothing.
|
// do, so say so rather than offering a button that does nothing.
|
||||||
if (app.status === 'EXAM_PAID' || app.status === 'EXAM_SCHEDULED') {
|
if (app.status === 'EXAM_PAID' || app.status === 'EXAM_SCHEDULED') {
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ const EXAM_STAGE_STATUSES = [
|
|||||||
'EXAM_PAID',
|
'EXAM_PAID',
|
||||||
'EXAM_SCHEDULED',
|
'EXAM_SCHEDULED',
|
||||||
'EXAM_FAILED',
|
'EXAM_FAILED',
|
||||||
|
'EXAM_PASSED',
|
||||||
];
|
];
|
||||||
|
|
||||||
export function applicationActionsColumn(
|
export function applicationActionsColumn(
|
||||||
|
|||||||
@@ -159,6 +159,8 @@ export const APPLICANT_ACTION_STATUSES: LicenseStatus[] = [
|
|||||||
// sit again after a failure.
|
// sit again after a failure.
|
||||||
'EXAM_PAYMENT_PENDING',
|
'EXAM_PAYMENT_PENDING',
|
||||||
'EXAM_FAILED',
|
'EXAM_FAILED',
|
||||||
|
// Passed: the certificate fee falls due, and only the candidate can pay it.
|
||||||
|
'EXAM_PASSED',
|
||||||
];
|
];
|
||||||
|
|
||||||
/** Statuses that are finished, whichever way they went. */
|
/** Statuses that are finished, whichever way they went. */
|
||||||
|
|||||||
Reference in New Issue
Block a user