feat: add certificate regeneration functionality to LicenseReviewPage

This commit is contained in:
nati
2026-09-07 14:25:35 +00:00
parent 4c86b65e81
commit e807839d74
2 changed files with 70 additions and 0 deletions

View File

@@ -34,6 +34,7 @@ import {
IconPaperclip,
IconPencil,
IconQuestionMark,
IconRefresh,
IconX,
} from "@tabler/icons-react";
import { notifications } from "@mantine/notifications";
@@ -71,6 +72,7 @@ import {
useScheduleInspectionMutation,
useRescheduleInspectionMutation,
useGetCertificateUrlForOfficerMutation,
useRegenerateCertificateMutation,
uploadDocument,
type ApplicationRemark,
type RemarkTargetType,
@@ -236,6 +238,8 @@ export function LicenseReviewPage() {
const [scheduleIssuance] = useScheduleIssuanceMutation();
const [issueCertificate] = useIssueCertificateMutation();
const [getCertificateUrlForOfficer] = useGetCertificateUrlForOfficerMutation();
const [regenerateCertificate, { isLoading: regeneratingCertificate }] =
useRegenerateCertificateMutation();
const [certificateBusy, setCertificateBusy] = useState<"view" | "download" | null>(
null,
);
@@ -720,6 +724,37 @@ export function LicenseReviewPage() {
}
}
/**
* Re-renders the stored certificate from whatever design is published now.
* Unlike View/Download above, this overwrites the stored PDF even though
* one already exists — the fix for a certificate printed from a design
* that turned out to be wrong (e.g. a placeholder published for a rank),
* where View/Download would otherwise keep handing back the same bad file.
*/
async function handleRegenerateCertificate() {
if (!app.issuedLicenseId) return;
try {
await regenerateCertificate(app.issuedLicenseId).unwrap();
notifications.show({
color: "teal",
title: t("review.certificateRegenerated", "Certificate regenerated"),
message: t(
"review.certificateRegeneratedBody",
"Re-rendered from the currently published design.",
),
});
} catch (err) {
notifications.show({
color: "red",
title: t(
"review.certificateRegenerateError",
"Could not regenerate the certificate",
),
message: extractErrorMessage(err),
});
}
}
/** Opens the booking modal seeded with the visit already on the books. */
function openReschedule() {
if (!pendingInspection) return;
@@ -1156,6 +1191,29 @@ export function LicenseReviewPage() {
>
{t("review.download", "Download")}
</Button>
{/* Same authority as issuing in the first place — reprints
the stored PDF from whatever design is published now,
for when the one on file turns out to be wrong. */}
{can(["can:issue:license-certificate"]) && (
<Tooltip
label={t(
"review.regenerateCertificateHint",
"Re-render from the currently published design",
)}
>
<Button
size="xs"
variant="subtle"
color="gray"
leftSection={<IconRefresh size={14} />}
loading={regeneratingCertificate}
disabled={Boolean(certificateBusy) || regeneratingCertificate}
onClick={handleRegenerateCertificate}
>
{t("review.regenerateCertificate", "Regenerate")}
</Button>
</Tooltip>
)}
</Group>
</Paper>
)}