From def6218dbfe52db4155f09b7667dd40b1a10daef Mon Sep 17 00:00:00 2001 From: estifanos Date: Thu, 27 Aug 2026 07:50:43 +0000 Subject: [PATCH 1/5] 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/5] 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 14508635c58f8e67ea2c5c4875906330e2b0a226 Mon Sep 17 00:00:00 2001 From: nati14575 Date: Thu, 27 Aug 2026 12:07:08 +0300 Subject: [PATCH 3/5] refactor(actions): update action eligibility conditions and remove unused actions refactor(i18n): remove 'assignReviewer' translation key from Amharic and English locales refactor(nav-config): clean up navigation configuration by removing redundant items refactor(licensing-api): remove assignReviewer mutation from licensing API feat(package): add build script for parallel building of portal and backoffice --- .../features/license-review/config/actions.ts | 16 +++++++++------- apps/backoffice/src/app/i18n/locales/am.ts | 1 - apps/backoffice/src/app/i18n/locales/en.ts | 1 - apps/backoffice/src/app/layouts/nav-config.ts | 7 ------- .../src/lib/features/licensing/licensing-api.ts | 13 ------------- package.json | 1 + 6 files changed, 10 insertions(+), 29 deletions(-) diff --git a/apps/backoffice/src/app/features/license-review/config/actions.ts b/apps/backoffice/src/app/features/license-review/config/actions.ts index 475c7fb9b..8511b7b29 100644 --- a/apps/backoffice/src/app/features/license-review/config/actions.ts +++ b/apps/backoffice/src/app/features/license-review/config/actions.ts @@ -212,12 +212,15 @@ export const ACTIONS: ActionDefinition[] = [ id: 'final-approve', tier: 'primary', labelKey: 'review.actions.finalApprove', - from: ['INSPECTION_COMPLETED', - 'REVIEW_REPORTED', - 'INSPECTION_REPORTED', 'UNDER_EVALUATION'], // ELIGIBILITY_PAID: an examined certificate (CoC/CoP) is decided straight // off the eligibility queue — no assignment step. - from: ['INSPECTION_COMPLETED', 'UNDER_EVALUATION', 'ELIGIBILITY_PAID'], + from: [ + 'INSPECTION_COMPLETED', + 'REVIEW_REPORTED', + 'INSPECTION_REPORTED', + 'UNDER_EVALUATION', + 'ELIGIBILITY_PAID', + ], permissions: ['can:approve:license-application'], emphasis: 'filled', color: 'teal', @@ -227,13 +230,12 @@ export const ACTIONS: ActionDefinition[] = [ id: 'request-adjustment', tier: 'primary', labelKey: 'review.actions.requestAdjustment', - from: ['UNDER_REVIEW', 'UNDER_EVALUATION', 'INSPECTION_COMPLETED', - 'REVIEW_REPORTED', - 'INSPECTION_REPORTED'], from: [ 'UNDER_REVIEW', 'UNDER_EVALUATION', 'INSPECTION_COMPLETED', + 'REVIEW_REPORTED', + 'INSPECTION_REPORTED', 'INSPECTION_FAILED', 'ELIGIBILITY_PAID', ], diff --git a/apps/backoffice/src/app/i18n/locales/am.ts b/apps/backoffice/src/app/i18n/locales/am.ts index 94622fa47..feb26b9e8 100644 --- a/apps/backoffice/src/app/i18n/locales/am.ts +++ b/apps/backoffice/src/app/i18n/locales/am.ts @@ -1012,7 +1012,6 @@ export const am: Translations = { actions: { claim: "ውሰድ", assign: "መድብ", - assignReviewer: "ግምገማ መድብ", reportReview: "ለቡድን መሪ አሳውቅ", assignInspector: "ምርመራ መድብ", reportInspection: "የምርመራ ውጤት አሳውቅ", diff --git a/apps/backoffice/src/app/i18n/locales/en.ts b/apps/backoffice/src/app/i18n/locales/en.ts index e98459f58..79edd53d7 100644 --- a/apps/backoffice/src/app/i18n/locales/en.ts +++ b/apps/backoffice/src/app/i18n/locales/en.ts @@ -1021,7 +1021,6 @@ export const en = { actions: { claim: 'Claim', assign: 'Assign', - assignReviewer: 'Assign review', reportReview: 'Report to team leader', assignInspector: 'Assign inspection', reportInspection: 'Report inspection result', diff --git a/apps/backoffice/src/app/layouts/nav-config.ts b/apps/backoffice/src/app/layouts/nav-config.ts index 969e7d0f9..d9ba3abee 100644 --- a/apps/backoffice/src/app/layouts/nav-config.ts +++ b/apps/backoffice/src/app/layouts/nav-config.ts @@ -96,13 +96,6 @@ export const NAV_SECTIONS: NavSection[] = [ label: 'nav.groupSeafarer', items: [ { to: '/seafarer-registry', label: 'nav.seafarerRegistry', icon: IconUsers, permissions: [P.VIEW_SEAFARER_REGISTRY] }, - { to: '/seafarer-registrations', label: 'nav.seafarerRegistrationQueue', icon: IconId, permissions: [P.VIEW_SEAFARER_REGISTRY] }, - { to: '/licence-review/type/CERTIFICATE_OF_COMPETENCY', label: 'nav.cocQueue', icon: IconShieldCheck, permissions: [P.VIEW_SEAFARER_REGISTRY] }, - { to: '/licence-review/type/CERTIFICATE_OF_PROFICIENCY', label: 'nav.copQueue', icon: IconShieldCheck, permissions: [P.VIEW_SEAFARER_REGISTRY] }, - { to: '/seaman-book-queue', label: 'nav.seamanBookQueue', icon: IconBook2, permissions: [P.VIEW_SEAFARER_REGISTRY] }, - { to: '/btc-queue', label: 'nav.btcQueue', icon: IconShieldCheck, permissions: [P.VIEW_SEAFARER_REGISTRY] }, - { to: '/licence-review/type/ENDORSEMENT_COC', label: 'nav.endorsementCocQueue', icon: IconRubberStamp, permissions: [P.VIEW_SEAFARER_REGISTRY] }, - { to: '/licence-review/type/ENDORSEMENT_GOC', label: 'nav.endorsementGocQueue', icon: IconRubberStamp, permissions: [P.VIEW_SEAFARER_REGISTRY] }, { to: '/seafarer-registrations', label: 'nav.seafarerRegistrationQueue', icon: IconId, permissions: APPLICATION_QUEUE }, { to: '/licence-review/type/CERTIFICATE_OF_COMPETENCY', label: 'nav.cocQueue', icon: IconShieldCheck, permissions: APPLICATION_QUEUE }, { to: '/licence-review/type/CERTIFICATE_OF_PROFICIENCY', label: 'nav.copQueue', icon: IconShieldCheck, permissions: APPLICATION_QUEUE }, diff --git a/libs/api/src/lib/features/licensing/licensing-api.ts b/libs/api/src/lib/features/licensing/licensing-api.ts index 2467979fa..d6c518fd2 100644 --- a/libs/api/src/lib/features/licensing/licensing-api.ts +++ b/libs/api/src/lib/features/licensing/licensing-api.ts @@ -860,19 +860,6 @@ export const licensingApi = baseApi * flight; these two *start* a stage, because under the push model * assignment is how work begins — nothing is claimed from a queue. */ - assignReviewer: builder.mutation< - LicenseApplication, - { id: string; officerId: string; remark?: string } - >({ - query: ({ id, ...body }) => ({ - url: `/license-application-review/${id}/assign-reviewer`, - method: 'POST', - body, - }), - invalidatesTags: (_r, error, { id }) => - error ? [] : [itemTag('LicenseApplication', id), listTag('ApplicationQueue')], - }), - assignInspector: builder.mutation< LicenseApplication, { id: string; inspectorId: string; remark?: string } diff --git a/package.json b/package.json index de6d7a558..b8cf8ec94 100644 --- a/package.json +++ b/package.json @@ -6,6 +6,7 @@ "backoffice": "nx serve @ema-platform/backoffice", "portal": "nx serve @ema-platform/portal", "dev:all": "nx run-many -t serve -p @ema-platform/portal @ema-platform/backoffice --parallel=2", + "build": "nx run-many -t build -p @ema-platform/portal @ema-platform/backoffice", "build:backoffice": "nx build @ema-platform/backoffice", "build:portal": "nx build @ema-platform/portal", "lint": "nx run-many -t lint", From 88a1caa52b6dea89e1c0761e457380a16e0af14d Mon Sep 17 00:00:00 2001 From: estifanos Date: Thu, 27 Aug 2026 12:29:54 +0000 Subject: [PATCH 4/5] 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 5/5] 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();