mirror of
https://github.com/Tria-plc/emaui.git
synced 2026-08-26 19:12:50 +00:00
feat: Refactor seafarer document application flow
- Remove SeamanBookApplicationPage from router and redirect to Seaman Book page. - Add new API endpoints for managing seafarer documents, including listing, reviewing, and issuing documents. - Introduce new SeafarerDocumentQueuePage and SeafarerDocumentReviewPage components for document management. - Update licensing types to accommodate optional applicationId and documentId in ApplicationPayment. - Remove unused claimSeafarerRegistration mutation and related constants. - Update seafarer registration status labels and types to remove 'UNDER_REVIEW'. - Create new constants and types for seafarer documents, including status labels and colors. - Implement document payment initiation and confirmation functionalities. - Enhance UI components for better user experience in document management.
This commit is contained in:
@@ -0,0 +1,161 @@
|
||||
import { useMemo, useState } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { Badge, Container, Group, Select, Text, TextInput, Title } from '@mantine/core';
|
||||
import { IconSearch } from '@tabler/icons-react';
|
||||
import { useDebouncedValue } from '@mantine/hooks';
|
||||
import {
|
||||
SEAFARER_DOCUMENT_KIND_LABELS,
|
||||
SEAFARER_DOCUMENT_STATUS_COLORS,
|
||||
SEAFARER_DOCUMENT_STATUS_LABELS,
|
||||
useListSeafarerDocumentsQuery,
|
||||
type SeafarerDocumentKind,
|
||||
type SeafarerDocumentRow,
|
||||
type SeafarerDocumentStatus,
|
||||
} from '@ema-platform/api';
|
||||
import { AdvancedTable, type AdvancedColumn } from '@ema-platform/ui';
|
||||
import { useDateDisplayer } from '@ema-platform/shared';
|
||||
|
||||
const PAGE_SIZE = 10;
|
||||
|
||||
/** Statuses an officer filters by — held and withdrawn requests are not work. */
|
||||
const STATUS_FILTERS = (
|
||||
['PAYMENT_PENDING', 'PAID', 'PAYMENT_CONFIRMED', 'SCHEDULED', 'ISSUED', 'REJECTED'] as SeafarerDocumentStatus[]
|
||||
).map((value) => ({ value, label: SEAFARER_DOCUMENT_STATUS_LABELS[value] }));
|
||||
|
||||
/**
|
||||
* The Seaman Book queue and the BTC queue — one page, keyed by kind. Requests
|
||||
* appear here once the seafarer registration that opened them is approved.
|
||||
*/
|
||||
export function SeafarerDocumentQueuePage({ kind }: { kind: SeafarerDocumentKind }) {
|
||||
const navigate = useNavigate();
|
||||
const showDate = useDateDisplayer();
|
||||
const [status, setStatus] = useState<SeafarerDocumentStatus | null>(null);
|
||||
const [search, setSearch] = useState('');
|
||||
const [debouncedSearch] = useDebouncedValue(search, 300);
|
||||
const [page, setPage] = useState(0);
|
||||
const [pageSize, setPageSize] = useState(PAGE_SIZE);
|
||||
|
||||
const { data, isLoading, isFetching, refetch } = useListSeafarerDocumentsQuery({
|
||||
kind,
|
||||
status: status ?? undefined,
|
||||
search: debouncedSearch || undefined,
|
||||
take: pageSize,
|
||||
skip: page * pageSize,
|
||||
});
|
||||
|
||||
const columns: AdvancedColumn<SeafarerDocumentRow>[] = useMemo(
|
||||
() => [
|
||||
{
|
||||
header: 'Request №',
|
||||
accessorKey: 'requestNumber',
|
||||
cell: ({ row }) => (
|
||||
<div>
|
||||
<Text size="sm" ff="monospace">
|
||||
{row.original.requestNumber}
|
||||
</Text>
|
||||
{row.original.documentNumber && (
|
||||
<Text size="xs" c="teal" ff="monospace">
|
||||
{row.original.documentNumber}
|
||||
</Text>
|
||||
)}
|
||||
</div>
|
||||
),
|
||||
},
|
||||
{
|
||||
header: 'Seafarer',
|
||||
accessorKey: 'applicant.name',
|
||||
cell: ({ row }) => (
|
||||
<div>
|
||||
<Text size="sm" fw={500}>
|
||||
{row.original.applicant?.name ?? '—'}
|
||||
</Text>
|
||||
<Text size="xs" c="dimmed" ff="monospace">
|
||||
{row.original.applicant?.seafarerNumber ?? '—'}
|
||||
</Text>
|
||||
</div>
|
||||
),
|
||||
},
|
||||
{
|
||||
header: 'Fee',
|
||||
accessorKey: 'feeAmount',
|
||||
cell: ({ row }) => (
|
||||
<Text size="sm">
|
||||
{row.original.feeAmount !== null ? `${row.original.feeAmount} ${row.original.feeCurrency}` : '—'}
|
||||
</Text>
|
||||
),
|
||||
},
|
||||
{
|
||||
header: 'Released',
|
||||
accessorKey: 'submittedAt',
|
||||
cell: ({ row }) => (
|
||||
<Text size="sm">{row.original.submittedAt ? showDate(row.original.submittedAt) : '—'}</Text>
|
||||
),
|
||||
},
|
||||
{
|
||||
header: 'Status',
|
||||
accessorKey: 'status',
|
||||
cell: ({ row }) => (
|
||||
<Badge size="sm" variant="light" color={SEAFARER_DOCUMENT_STATUS_COLORS[row.original.status]}>
|
||||
{SEAFARER_DOCUMENT_STATUS_LABELS[row.original.status]}
|
||||
</Badge>
|
||||
),
|
||||
},
|
||||
],
|
||||
[showDate],
|
||||
);
|
||||
|
||||
return (
|
||||
<Container size="xl" py="md">
|
||||
<Title order={3} mb={4}>
|
||||
{SEAFARER_DOCUMENT_KIND_LABELS[kind]} Queue
|
||||
</Title>
|
||||
<Text size="sm" c="dimmed" mb="md">
|
||||
Requests released by an approved seafarer registration: confirm payment, schedule the
|
||||
collection date, then issue.
|
||||
</Text>
|
||||
<Group mb="md" gap="sm">
|
||||
<TextInput
|
||||
placeholder="Search number, name or seafarer №…"
|
||||
leftSection={<IconSearch size={14} />}
|
||||
value={search}
|
||||
onChange={(e) => {
|
||||
setSearch(e.currentTarget.value);
|
||||
setPage(0);
|
||||
}}
|
||||
w={280}
|
||||
/>
|
||||
<Select
|
||||
placeholder="All statuses"
|
||||
data={STATUS_FILTERS}
|
||||
value={status}
|
||||
onChange={(v) => {
|
||||
setStatus(v as SeafarerDocumentStatus | null);
|
||||
setPage(0);
|
||||
}}
|
||||
clearable
|
||||
w={220}
|
||||
/>
|
||||
</Group>
|
||||
<AdvancedTable
|
||||
columns={columns}
|
||||
data={data?.items ?? []}
|
||||
tableName={`${SEAFARER_DOCUMENT_KIND_LABELS[kind]} requests`}
|
||||
itemCount={data?.total ?? 0}
|
||||
pageIndex={page}
|
||||
onPageChange={setPage}
|
||||
pageSize={pageSize}
|
||||
onPageSizeChange={(size) => {
|
||||
setPageSize(size);
|
||||
setPage(0);
|
||||
}}
|
||||
refresh={refetch}
|
||||
isLoading={isLoading || isFetching}
|
||||
emptyText="No requests match."
|
||||
onRowClick={(row) => navigate(`/seafarer-documents/${row.id}`)}
|
||||
/>
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
|
||||
export const SeamanBookQueuePage = () => <SeafarerDocumentQueuePage kind="SEAMAN_BOOK" />;
|
||||
export const BtcQueuePage = () => <SeafarerDocumentQueuePage kind="BTC_BASIC_TRAINING" />;
|
||||
@@ -0,0 +1,276 @@
|
||||
import { useState } from 'react';
|
||||
import { Link, useNavigate, useParams } from 'react-router-dom';
|
||||
import {
|
||||
Alert,
|
||||
Badge,
|
||||
Button,
|
||||
Center,
|
||||
Container,
|
||||
Group,
|
||||
Loader,
|
||||
Modal,
|
||||
Paper,
|
||||
Stack,
|
||||
Table,
|
||||
Text,
|
||||
Textarea,
|
||||
Title,
|
||||
} from '@mantine/core';
|
||||
import { IconAlertTriangle, IconArrowLeft, IconCheck, IconDownload } from '@tabler/icons-react';
|
||||
import {
|
||||
SEAFARER_DOCUMENT_KIND_LABELS,
|
||||
SEAFARER_DOCUMENT_STATUS_COLORS,
|
||||
SEAFARER_DOCUMENT_STATUS_LABELS,
|
||||
extractErrorMessage,
|
||||
useConfirmSeafarerDocumentPaymentMutation,
|
||||
useGetSeafarerDocumentReviewQuery,
|
||||
useIssueSeafarerDocumentMutation,
|
||||
useLazyGetSeafarerDocumentReviewDownloadQuery,
|
||||
useRejectSeafarerDocumentMutation,
|
||||
useScheduleSeafarerDocumentMutation,
|
||||
} from '@ema-platform/api';
|
||||
import { AmharicDatePicker, notify } from '@ema-platform/ui';
|
||||
import { useDateDisplayer } from '@ema-platform/shared';
|
||||
import { LICENSE_PERMISSIONS, RequirePermission } from '@ema-platform/auth';
|
||||
|
||||
const QUEUE_PATH = { SEAMAN_BOOK: '/seaman-book-queue', BTC_BASIC_TRAINING: '/btc-queue' } as const;
|
||||
|
||||
function Row({ label, value }: { label: string; value: React.ReactNode }) {
|
||||
return (
|
||||
<Table.Tr>
|
||||
<Table.Td w="40%">
|
||||
<Text size="xs" c="dimmed">
|
||||
{label}
|
||||
</Text>
|
||||
</Table.Td>
|
||||
<Table.Td>
|
||||
<Text size="sm" component="div">
|
||||
{value ?? '—'}
|
||||
</Text>
|
||||
</Table.Td>
|
||||
</Table.Tr>
|
||||
);
|
||||
}
|
||||
|
||||
/** One Seaman Book / BTC request: payment → collection date → issue, or reject. */
|
||||
export function SeafarerDocumentReviewPage() {
|
||||
const { id = '' } = useParams();
|
||||
const navigate = useNavigate();
|
||||
const showDate = useDateDisplayer();
|
||||
const { data, isLoading, error } = useGetSeafarerDocumentReviewQuery(id, { skip: !id });
|
||||
|
||||
const [confirmPayment, { isLoading: confirming }] = useConfirmSeafarerDocumentPaymentMutation();
|
||||
const [schedule, { isLoading: scheduling }] = useScheduleSeafarerDocumentMutation();
|
||||
const [issue, { isLoading: issuing }] = useIssueSeafarerDocumentMutation();
|
||||
const [reject, { isLoading: rejecting }] = useRejectSeafarerDocumentMutation();
|
||||
const [getDownload, { isFetching: downloading }] = useLazyGetSeafarerDocumentReviewDownloadQuery();
|
||||
|
||||
const [scheduleOpen, setScheduleOpen] = useState(false);
|
||||
const [pickupDate, setPickupDate] = useState('');
|
||||
const [rejectOpen, setRejectOpen] = useState(false);
|
||||
const [reason, setReason] = useState('');
|
||||
|
||||
if (isLoading) {
|
||||
return (
|
||||
<Center h={300}>
|
||||
<Loader />
|
||||
</Center>
|
||||
);
|
||||
}
|
||||
if (error || !data) {
|
||||
return (
|
||||
<Container size="md" py="xl">
|
||||
<Alert color="red" icon={<IconAlertTriangle size={16} />}>
|
||||
{extractErrorMessage(error, 'Could not load this request.')}
|
||||
</Alert>
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
|
||||
const { document, applicant, payment } = data;
|
||||
const kindLabel = SEAFARER_DOCUMENT_KIND_LABELS[document.kind];
|
||||
const terminal = ['ISSUED', 'REJECTED', 'CANCELLED'].includes(document.status);
|
||||
|
||||
async function run(action: () => Promise<unknown>, done: string) {
|
||||
try {
|
||||
await action();
|
||||
notify.success(done);
|
||||
setScheduleOpen(false);
|
||||
setRejectOpen(false);
|
||||
setReason('');
|
||||
} catch (err) {
|
||||
notify.error(extractErrorMessage(err, 'Could not record the action'));
|
||||
}
|
||||
}
|
||||
|
||||
async function download() {
|
||||
try {
|
||||
const { url } = await getDownload(id).unwrap();
|
||||
window.open(url, '_blank', 'noopener');
|
||||
} catch (err) {
|
||||
notify.error(extractErrorMessage(err, 'Could not fetch the PDF'));
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<Container size="lg" py="md">
|
||||
<Button
|
||||
variant="subtle"
|
||||
size="xs"
|
||||
leftSection={<IconArrowLeft size={14} />}
|
||||
onClick={() => navigate(QUEUE_PATH[document.kind])}
|
||||
mb="xs"
|
||||
>
|
||||
Back to queue
|
||||
</Button>
|
||||
<Group justify="space-between" align="flex-start" mb="md">
|
||||
<div>
|
||||
<Title order={3}>
|
||||
{kindLabel} — {applicant?.name ?? '—'}
|
||||
</Title>
|
||||
<Group gap="xs" mt={4}>
|
||||
<Text size="sm" c="dimmed" ff="monospace">
|
||||
{document.requestNumber}
|
||||
</Text>
|
||||
<Badge size="sm" variant="light" color={SEAFARER_DOCUMENT_STATUS_COLORS[document.status]}>
|
||||
{SEAFARER_DOCUMENT_STATUS_LABELS[document.status]}
|
||||
</Badge>
|
||||
{document.documentNumber && (
|
||||
<Badge size="sm" color="teal" leftSection={<IconCheck size={10} />}>
|
||||
{document.documentNumber}
|
||||
</Badge>
|
||||
)}
|
||||
</Group>
|
||||
</div>
|
||||
<Group gap="xs">
|
||||
{(document.status === 'PAYMENT_PENDING' || document.status === 'PAID') && (
|
||||
<RequirePermission anyOf={[LICENSE_PERMISSIONS.CONFIRM_PAYMENT]} hideOnly>
|
||||
<Button loading={confirming} onClick={() => run(() => confirmPayment(id).unwrap(), 'Payment confirmed')}>
|
||||
Confirm payment
|
||||
</Button>
|
||||
</RequirePermission>
|
||||
)}
|
||||
{document.status === 'PAYMENT_CONFIRMED' && (
|
||||
<RequirePermission anyOf={[LICENSE_PERMISSIONS.SCHEDULE_ISSUANCE]} hideOnly>
|
||||
<Button onClick={() => setScheduleOpen(true)}>Schedule pickup</Button>
|
||||
</RequirePermission>
|
||||
)}
|
||||
{(document.status === 'SCHEDULED' || document.status === 'PAYMENT_CONFIRMED') && (
|
||||
<RequirePermission anyOf={[LICENSE_PERMISSIONS.ISSUE_CERTIFICATE]} hideOnly>
|
||||
<Button color="teal" loading={issuing} onClick={() => run(() => issue(id).unwrap(), `${kindLabel} issued`)}>
|
||||
Issue
|
||||
</Button>
|
||||
</RequirePermission>
|
||||
)}
|
||||
{document.status === 'ISSUED' && (
|
||||
<Button variant="light" leftSection={<IconDownload size={14} />} loading={downloading} onClick={download}>
|
||||
Download PDF
|
||||
</Button>
|
||||
)}
|
||||
{!terminal && (
|
||||
<RequirePermission anyOf={[LICENSE_PERMISSIONS.REJECT_APPLICATION]} hideOnly>
|
||||
<Button color="red" variant="light" onClick={() => setRejectOpen(true)}>
|
||||
Reject
|
||||
</Button>
|
||||
</RequirePermission>
|
||||
)}
|
||||
</Group>
|
||||
</Group>
|
||||
|
||||
{document.status === 'REJECTED' && (
|
||||
<Alert color="red" icon={<IconAlertTriangle size={16} />} title="Rejected" mb="md">
|
||||
{document.rejectionReason}
|
||||
</Alert>
|
||||
)}
|
||||
|
||||
<Paper withBorder p="lg" radius="md">
|
||||
<Stack gap="md">
|
||||
<div>
|
||||
<Text fw={600} size="sm" mb={4}>
|
||||
Seafarer
|
||||
</Text>
|
||||
<Table withTableBorder withColumnBorders>
|
||||
<Table.Tbody>
|
||||
<Row label="Name" value={applicant?.name} />
|
||||
<Row label="Seafarer №" value={applicant?.seafarerNumber} />
|
||||
<Row
|
||||
label="Registration"
|
||||
value={
|
||||
applicant?.registrationId ? (
|
||||
<Link to={`/seafarer-registrations/${applicant.registrationId}`}>
|
||||
{applicant.registrationNumber}
|
||||
</Link>
|
||||
) : (
|
||||
applicant?.registrationNumber
|
||||
)
|
||||
}
|
||||
/>
|
||||
</Table.Tbody>
|
||||
</Table>
|
||||
</div>
|
||||
<div>
|
||||
<Text fw={600} size="sm" mb={4}>
|
||||
Payment
|
||||
</Text>
|
||||
<Table withTableBorder withColumnBorders>
|
||||
<Table.Tbody>
|
||||
<Row label="Fee" value={document.feeAmount !== null ? `${document.feeAmount} ${document.feeCurrency}` : null} />
|
||||
<Row label="Released to payment" value={document.submittedAt ? showDate(document.submittedAt) : null} />
|
||||
<Row label="Paid" value={document.paidAt ? showDate(document.paidAt) : null} />
|
||||
<Row label="Provider" value={payment?.provider} />
|
||||
<Row label="Reference" value={document.paymentReference ?? payment?.providerRef} />
|
||||
</Table.Tbody>
|
||||
</Table>
|
||||
</div>
|
||||
<div>
|
||||
<Text fw={600} size="sm" mb={4}>
|
||||
Issuance
|
||||
</Text>
|
||||
<Table withTableBorder withColumnBorders>
|
||||
<Table.Tbody>
|
||||
<Row label="Pickup date" value={document.scheduledIssuanceDate ? showDate(document.scheduledIssuanceDate) : null} />
|
||||
<Row label="Document №" value={document.documentNumber} />
|
||||
<Row label="Issued" value={document.issueDate ? showDate(document.issueDate) : null} />
|
||||
<Row label="Valid until" value={document.expiryDate ? showDate(document.expiryDate) : null} />
|
||||
</Table.Tbody>
|
||||
</Table>
|
||||
</div>
|
||||
</Stack>
|
||||
</Paper>
|
||||
|
||||
<Modal opened={scheduleOpen} onClose={() => setScheduleOpen(false)} title="Schedule pickup" centered>
|
||||
<Stack>
|
||||
<AmharicDatePicker label="Pickup date" dateFormat="date" value={pickupDate} onChange={setPickupDate} required />
|
||||
<Group justify="flex-end">
|
||||
<Button variant="default" onClick={() => setScheduleOpen(false)}>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
disabled={!pickupDate}
|
||||
loading={scheduling}
|
||||
onClick={() => run(() => schedule({ id, scheduledDate: pickupDate }).unwrap(), 'Pickup scheduled')}
|
||||
>
|
||||
Schedule
|
||||
</Button>
|
||||
</Group>
|
||||
</Stack>
|
||||
</Modal>
|
||||
|
||||
<Modal opened={rejectOpen} onClose={() => setRejectOpen(false)} title={`Reject ${kindLabel}`} centered>
|
||||
<Stack>
|
||||
<Textarea label="Reason (shown to the seafarer)" required minRows={3} value={reason} onChange={(e) => setReason(e.currentTarget.value)} data-autofocus />
|
||||
<Group justify="flex-end">
|
||||
<Button variant="default" onClick={() => setRejectOpen(false)}>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button color="red" disabled={reason.trim().length < 3} loading={rejecting} onClick={() => run(() => reject({ id, reason: reason.trim() }).unwrap(), 'Request rejected')}>
|
||||
Confirm
|
||||
</Button>
|
||||
</Group>
|
||||
</Stack>
|
||||
</Modal>
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
|
||||
export default SeafarerDocumentReviewPage;
|
||||
@@ -17,7 +17,7 @@ import {
|
||||
Textarea,
|
||||
Title,
|
||||
} from '@mantine/core';
|
||||
import { IconAlertTriangle, IconArrowLeft, IconCheck, IconInfoCircle } from '@tabler/icons-react';
|
||||
import { IconAlertTriangle, IconArrowLeft, IconCheck } from '@tabler/icons-react';
|
||||
import {
|
||||
SEAFARER_REGISTRATION_DOCUMENTS,
|
||||
SEAFARER_REGISTRATION_FIELD_LABELS,
|
||||
@@ -27,14 +27,12 @@ import {
|
||||
displaySeafarerAnswer,
|
||||
extractErrorMessage,
|
||||
useApproveSeafarerRegistrationMutation,
|
||||
useClaimSeafarerRegistrationMutation,
|
||||
useGetSeafarerRegistrationReviewQuery,
|
||||
useRejectSeafarerRegistrationMutation,
|
||||
useRequestSeafarerRegistrationChangesMutation,
|
||||
} from '@ema-platform/api';
|
||||
import { notify } from '@ema-platform/ui';
|
||||
import { LICENSE_PERMISSIONS, RequirePermission } from '@ema-platform/auth';
|
||||
import { useAppSelector } from '../../../store/hooks';
|
||||
import { applicantName } from './SeafarerRegistrationQueuePage';
|
||||
|
||||
type Decision = 'approve' | 'reject' | 'changes';
|
||||
@@ -49,10 +47,8 @@ const DECISION_COPY: Record<Decision, { title: string; label: string; color: str
|
||||
export function SeafarerRegistrationReviewPage() {
|
||||
const { id = '' } = useParams();
|
||||
const navigate = useNavigate();
|
||||
const me = useAppSelector((state) => state.auth.user);
|
||||
const { data, isLoading, error } = useGetSeafarerRegistrationReviewQuery(id, { skip: !id });
|
||||
|
||||
const [claim, { isLoading: claiming }] = useClaimSeafarerRegistrationMutation();
|
||||
const [approve, { isLoading: approving }] = useApproveSeafarerRegistrationMutation();
|
||||
const [reject, { isLoading: rejecting }] = useRejectSeafarerRegistrationMutation();
|
||||
const [requestChanges, { isLoading: requesting }] = useRequestSeafarerRegistrationChangesMutation();
|
||||
@@ -78,8 +74,8 @@ export function SeafarerRegistrationReviewPage() {
|
||||
}
|
||||
|
||||
const { registration, attachments } = data;
|
||||
const mine = !registration.assignedOfficerId || registration.assignedOfficerId === me?.id;
|
||||
const canDecide = registration.status === 'UNDER_REVIEW' && mine;
|
||||
// Decided straight off the queue — no claim step.
|
||||
const canDecide = registration.status === 'SUBMITTED';
|
||||
const busy = approving || rejecting || requesting;
|
||||
|
||||
async function run(action: () => Promise<unknown>, done: string) {
|
||||
@@ -131,13 +127,6 @@ export function SeafarerRegistrationReviewPage() {
|
||||
</Group>
|
||||
</div>
|
||||
<Group gap="xs">
|
||||
{registration.status === 'SUBMITTED' && (
|
||||
<RequirePermission anyOf={[LICENSE_PERMISSIONS.CLAIM_APPLICATION]} hideOnly>
|
||||
<Button loading={claiming} onClick={() => run(() => claim(id).unwrap(), 'Claimed — it is yours to review.')}>
|
||||
Claim
|
||||
</Button>
|
||||
</RequirePermission>
|
||||
)}
|
||||
{canDecide && (
|
||||
<>
|
||||
<RequirePermission anyOf={[LICENSE_PERMISSIONS.REQUEST_ADJUSTMENT]} hideOnly>
|
||||
@@ -160,11 +149,6 @@ export function SeafarerRegistrationReviewPage() {
|
||||
</Group>
|
||||
</Group>
|
||||
|
||||
{registration.status === 'UNDER_REVIEW' && !mine && (
|
||||
<Alert color="gray" icon={<IconInfoCircle size={16} />} mb="md">
|
||||
Assigned to another officer — only they can decide it.
|
||||
</Alert>
|
||||
)}
|
||||
{registration.status === 'RESUBMIT_REQUIRED' && (
|
||||
<Alert color="orange" icon={<IconAlertTriangle size={16} />} title="Awaiting the applicant's corrections" mb="md">
|
||||
{registration.reviewRemark}
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
import { Container } from '@mantine/core';
|
||||
import { FeatureUnavailable } from '@ema-platform/ui';
|
||||
|
||||
/**
|
||||
* Placeholder until this feature has a backend.
|
||||
*
|
||||
* This page previously rendered hardcoded sample records, which were
|
||||
* indistinguishable from real ones.
|
||||
*/
|
||||
export function SeamanBookQueuePage() {
|
||||
return (
|
||||
<Container size="lg" py="xl">
|
||||
<FeatureUnavailable
|
||||
title="Seaman Book queue"
|
||||
description="Seaman Book applications are not connected to the backend yet."
|
||||
/>
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
|
||||
export default SeamanBookQueuePage;
|
||||
@@ -98,8 +98,8 @@ export const NAV_SECTIONS: NavSection[] = [
|
||||
{ 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 },
|
||||
{ to: '/licence-review/type/SEAMAN_BOOK', label: 'nav.seamanBookQueue', icon: IconBook2, permissions: APPLICATION_QUEUE },
|
||||
{ to: '/licence-review/type/BTC_BASIC_TRAINING', label: 'nav.btcQueue', icon: IconShieldCheck, permissions: APPLICATION_QUEUE },
|
||||
{ to: '/seaman-book-queue', label: 'nav.seamanBookQueue', icon: IconBook2, permissions: APPLICATION_QUEUE },
|
||||
{ to: '/btc-queue', label: 'nav.btcQueue', icon: IconShieldCheck, permissions: APPLICATION_QUEUE },
|
||||
{ to: '/licence-review/type/ENDORSEMENT_COC', label: 'nav.endorsementCocQueue', icon: IconRubberStamp, permissions: APPLICATION_QUEUE },
|
||||
{ to: '/licence-review/type/ENDORSEMENT_GOC', label: 'nav.endorsementGocQueue', icon: IconRubberStamp, permissions: APPLICATION_QUEUE },
|
||||
{ to: '/medical-verification', label: 'nav.medicalVerification', icon: IconHeart, permissions: [P.VERIFY_SEAFARER_RECORDS] },
|
||||
|
||||
@@ -27,7 +27,8 @@ import { PaymentConfigPage } from '../features/payment-config/pages/PaymentConfi
|
||||
import { SeafarerRegistryPage } from '../features/seafarer-registry/pages/SeafarerRegistryPage';
|
||||
import { SeafarerRegistrationQueuePage } from '../features/seafarer-registration-review/pages/SeafarerRegistrationQueuePage';
|
||||
import { SeafarerRegistrationReviewPage } from '../features/seafarer-registration-review/pages/SeafarerRegistrationReviewPage';
|
||||
import { SeamanBookQueuePage } from '../features/seaman-book-queue/pages/SeamanBookQueuePage';
|
||||
import { BtcQueuePage, SeamanBookQueuePage } from '../features/seafarer-document-review/pages/SeafarerDocumentQueuePage';
|
||||
import { SeafarerDocumentReviewPage } from '../features/seafarer-document-review/pages/SeafarerDocumentReviewPage';
|
||||
import { QuestionPage } from '../features/question/pages/QuestionPage';
|
||||
import { ExamPage } from '../features/exam/pages/ExamPage';
|
||||
import { ExamDetailPage } from '../features/exam/pages/ExamDetailPage';
|
||||
@@ -90,7 +91,12 @@ const router = createBrowserRouter([
|
||||
{ path: 'seafarer-registrations', element: guard(APPLICATION_QUEUE, <SeafarerRegistrationQueuePage />) },
|
||||
{ path: 'seafarer-registrations/:id', element: guard(APPLICATION_QUEUE, <SeafarerRegistrationReviewPage />) },
|
||||
{ path: 'licence-review/type/SEAFARER_REGISTRATION', element: <Navigate to="/seafarer-registrations" replace /> },
|
||||
// Seaman Book and BTC are not licences: own queues, own review.
|
||||
{ path: 'seaman-book-queue', element: guard(APPLICATION_QUEUE, <SeamanBookQueuePage />) },
|
||||
{ path: 'btc-queue', element: guard(APPLICATION_QUEUE, <BtcQueuePage />) },
|
||||
{ path: 'seafarer-documents/:id', element: guard(APPLICATION_QUEUE, <SeafarerDocumentReviewPage />) },
|
||||
{ path: 'licence-review/type/SEAMAN_BOOK', element: <Navigate to="/seaman-book-queue" replace /> },
|
||||
{ path: 'licence-review/type/BTC_BASIC_TRAINING', element: <Navigate to="/btc-queue" replace /> },
|
||||
{ path: 'questions', element: guard([P.APPROVE_QUESTION, P.AUTHOR_QUESTION], <QuestionPage />) },
|
||||
{ path: 'exams', element: guard([P.MANAGE_EXAMS, P.RECORD_EXAM_ATTENDANCE, P.MANAGE_EXAM_INCIDENTS, P.PUBLISH_EXAM_RESULT], <ExamPage />) },
|
||||
{ path: 'exams/:id', element: guard([P.MANAGE_EXAMS, P.RECORD_EXAM_ATTENDANCE, P.MANAGE_EXAM_INCIDENTS, P.PUBLISH_EXAM_RESULT], <ExamDetailPage />) },
|
||||
|
||||
Reference in New Issue
Block a user