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) => ( - +
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/features/license-review/pages/LicenseReviewPage/index.tsx b/apps/backoffice/src/app/features/license-review/pages/LicenseReviewPage/index.tsx index 1977689ae..3c5b8f2f1 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 @@ -1214,18 +1214,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 ? ( @@ -1281,6 +1269,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" && ( 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(); 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/libs/api/src/lib/features/seafarer-registration/seafarer-registration-api.ts b/libs/api/src/lib/features/seafarer-registration/seafarer-registration-api.ts index 4b42681eb..87f5fef15 100644 --- a/libs/api/src/lib/features/seafarer-registration/seafarer-registration-api.ts +++ b/libs/api/src/lib/features/seafarer-registration/seafarer-registration-api.ts @@ -10,9 +10,17 @@ const TAG = 'SeafarerRegistration' as const; const LIST = { type: TAG, id: 'LIST' } as const; const item = (id: string) => ({ 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; } 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",