From def6218dbfe52db4155f09b7667dd40b1a10daef Mon Sep 17 00:00:00 2001 From: estifanos Date: Thu, 27 Aug 2026 07:50:43 +0000 Subject: [PATCH 1/4] fix: show inspection-failed alert at page level, not inside the inspection tab A license type configured without an inspection detail section (e.g. vessel ownership transfer) would hide the "approval blocked" explanation entirely if it ever reached INSPECTION_FAILED. The alert now renders beside the other page-level status notices, so the reason is visible regardless of which detail sections the type declares. Co-Authored-By: Claude Fable 5 --- .../pages/LicenseReviewPage/index.tsx | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/apps/backoffice/src/app/features/license-review/pages/LicenseReviewPage/index.tsx b/apps/backoffice/src/app/features/license-review/pages/LicenseReviewPage/index.tsx index 71a13403a..23311a189 100644 --- a/apps/backoffice/src/app/features/license-review/pages/LicenseReviewPage/index.tsx +++ b/apps/backoffice/src/app/features/license-review/pages/LicenseReviewPage/index.tsx @@ -1077,18 +1077,6 @@ export function LicenseReviewPage() { - {status === "INSPECTION_FAILED" && ( - } - > - {t( - "review.inspectionFailedBlocked", - "Approval is unavailable because the inspection failed. Schedule a re-inspection, request corrections, or reject the application.", - )} - - )} {inspections.length === 0 ? ( @@ -1136,6 +1124,18 @@ export function LicenseReviewPage() { + {/* Page-level, not inside the inspection tab: a license type + configured without an inspection detail section must still show + why approval is blocked if it ever lands here. */} + {status === "INSPECTION_FAILED" && ( + }> + {t( + "review.inspectionFailedBlocked", + "Approval is unavailable because the inspection failed. Schedule a re-inspection, request corrections, or reject the application.", + )} + + )} + {status === "PAYMENT_PENDING" && ( Date: Thu, 27 Aug 2026 08:16:20 +0000 Subject: [PATCH 2/4] style: update component background and icon colors to use semantic theme variables --- .../certificate-requirements/components/ConditionBuilder.tsx | 2 +- .../components/DocumentRequirementsTab.tsx | 2 +- .../certificate-requirements/components/FormSchemaTab.tsx | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/backoffice/src/app/features/certificate-requirements/components/ConditionBuilder.tsx b/apps/backoffice/src/app/features/certificate-requirements/components/ConditionBuilder.tsx index b1192fca2..7c016bd76 100644 --- a/apps/backoffice/src/app/features/certificate-requirements/components/ConditionBuilder.tsx +++ b/apps/backoffice/src/app/features/certificate-requirements/components/ConditionBuilder.tsx @@ -198,7 +198,7 @@ function ConditionArmFields({ fz="xs" px={6} py={2} - bg="var(--mantine-color-gray-1)" + bg="var(--mantine-color-default-hover)" style={{ borderRadius: 4, cursor: 'pointer' }} onClick={() => setInValues((value.in ?? []).filter((_, idx) => idx !== i).map(String))} title={t('certReq.condition.removeValue', 'Click to remove')} diff --git a/apps/backoffice/src/app/features/certificate-requirements/components/DocumentRequirementsTab.tsx b/apps/backoffice/src/app/features/certificate-requirements/components/DocumentRequirementsTab.tsx index 05680009d..033795186 100644 --- a/apps/backoffice/src/app/features/certificate-requirements/components/DocumentRequirementsTab.tsx +++ b/apps/backoffice/src/app/features/certificate-requirements/components/DocumentRequirementsTab.tsx @@ -127,7 +127,7 @@ export function DocumentRequirementsTab({ licenseType }: { licenseType: LicenseT ) : ( {rows.map((req) => ( - +
diff --git a/apps/backoffice/src/app/features/certificate-requirements/components/FormSchemaTab.tsx b/apps/backoffice/src/app/features/certificate-requirements/components/FormSchemaTab.tsx index ddeb712f1..f103b43d4 100644 --- a/apps/backoffice/src/app/features/certificate-requirements/components/FormSchemaTab.tsx +++ b/apps/backoffice/src/app/features/certificate-requirements/components/FormSchemaTab.tsx @@ -209,7 +209,7 @@ export function FormSchemaTab({ licenseType }: { licenseType: LicenseType }) { - +
{localized(section.title) || section.key} @@ -237,7 +237,7 @@ export function FormSchemaTab({ licenseType }: { licenseType: LicenseType }) { {section.fields.map((field, fIndex) => ( - +
From 88a1caa52b6dea89e1c0761e457380a16e0af14d Mon Sep 17 00:00:00 2001 From: estifanos Date: Thu, 27 Aug 2026 12:29:54 +0000 Subject: [PATCH 3/4] feat: add sorting support to seafarer registration query and default queue to newest first --- .../pages/SeafarerRegistrationQueuePage.tsx | 4 +++- .../seafarer-registration/seafarer-registration-api.ts | 8 ++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/apps/backoffice/src/app/features/seafarer-registration-review/pages/SeafarerRegistrationQueuePage.tsx b/apps/backoffice/src/app/features/seafarer-registration-review/pages/SeafarerRegistrationQueuePage.tsx index 4a3787697..7c978f38d 100644 --- a/apps/backoffice/src/app/features/seafarer-registration-review/pages/SeafarerRegistrationQueuePage.tsx +++ b/apps/backoffice/src/app/features/seafarer-registration-review/pages/SeafarerRegistrationQueuePage.tsx @@ -26,7 +26,7 @@ export function applicantName(r: Pick ({ type: TAG, id }) as const; +export type SeafarerRegistrationSortField = + | 'submittedAt' + | 'registrationNumber' + | 'lastName' + | 'status'; + export interface SeafarerRegistrationListFilter { status?: SeafarerRegistrationStatus; search?: string; + sortBy?: SeafarerRegistrationSortField; + sortDir?: 'ASC' | 'DESC'; take?: number; skip?: number; } From e86375e7619908f20f367753802287e32742b2c4 Mon Sep 17 00:00:00 2001 From: estifanos Date: Thu, 27 Aug 2026 12:37:04 +0000 Subject: [PATCH 4/4] refactor: update application locking logic to handle staff remarks and itemized rounds --- .../pages/LicenseApplicationPage.tsx | 24 +++++++++++-------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/apps/portal/src/app/features/licensing/pages/LicenseApplicationPage.tsx b/apps/portal/src/app/features/licensing/pages/LicenseApplicationPage.tsx index 30b801ab3..ac497f5f7 100644 --- a/apps/portal/src/app/features/licensing/pages/LicenseApplicationPage.tsx +++ b/apps/portal/src/app/features/licensing/pages/LicenseApplicationPage.tsx @@ -352,14 +352,18 @@ export function LicenseApplicationPage() { ), [roundRemarks], ); - const hasSectionRemarks = Object.keys(flaggedSections).length > 0; - const hasDocRemarks = Object.keys(flaggedDocuments).length > 0; + const hasStaffRemarks = roundRemarks.some((r) => r.targetType === "STAFF"); + // Nothing at all came back for this round (a detail response that predates + // the remarks, say) — lock nothing rather than freeze the whole application + // with no way forward. Any remark present means the round is itemised, so + // only what the officer flagged opens: a documents-only round leaves every + // form section frozen, and a sections-only round leaves every document as + // filed. + const roundIsItemised = isAdjusting && roundRemarks.length > 0; - // A round that flagged no form sections carries no section locks — mirror of - // the server's fallback, without which a documents-only correction round - // froze every field and the applicant could not edit anything at all. const isSectionLocked = (sectionKey: string) => - isAdjusting && hasSectionRemarks && !flaggedSections[sectionKey]; + roundIsItemised && !flaggedSections[sectionKey]; + const staffLocked = roundIsItemised && !hasStaffRemarks; // Sections that share a group collapse onto one step, so the stepper stays // short instead of showing a page per section. @@ -862,7 +866,7 @@ export function LicenseApplicationPage() { complete )} - {!readOnly && ( + {!readOnly && !staffLocked && (
- {!readOnly && ( + {!readOnly && !staffLocked && (
@@ -937,7 +941,7 @@ export function LicenseApplicationPage() { ownerType="APPLICATION" ownerId={appId} flagged={flaggedDocuments} - restrictToFlagged={isAdjusting && hasDocRemarks} + restrictToFlagged={roundIsItemised} readOnly={readOnly} onUploaded={() => { refetchAttachments();