mirror of
https://github.com/Tria-plc/emaui.git
synced 2026-08-26 19:12:50 +00:00
feat: Refactor medical and sea service verification pages
- Split MedicalVerificationPage into MedicalVerificationPage and SeaServiceVerificationPage for better separation of concerns. - Update navigation to include separate entries for Sea Service Verification and Medical Verification. - Enhance sea service columns to display additional vessel information and days served. - Add translations for new and updated labels in both English and Amharic. - Introduce seaServiceDays helper function to calculate days served based on engagement and discharge dates. - Remove deprecated MedicalCertificatePage from the portal. - Update routing to direct to the new sea service and medical pages.
This commit is contained in:
@@ -1,11 +1,12 @@
|
||||
import { Badge, Text } from '@mantine/core';
|
||||
import type { TFunction } from 'i18next';
|
||||
import type { AdvancedColumn } from '@ema-platform/ui';
|
||||
import type {
|
||||
MedicalCertificate,
|
||||
SeaServiceRecord,
|
||||
SeafarerProfileSummary,
|
||||
SeafarerRecordStatus,
|
||||
import {
|
||||
seaServiceDays,
|
||||
type MedicalCertificate,
|
||||
type SeaServiceRecord,
|
||||
type SeafarerProfileSummary,
|
||||
type SeafarerRecordStatus,
|
||||
} from '@ema-platform/api';
|
||||
|
||||
export function ownerName(profile?: SeafarerProfileSummary): string {
|
||||
@@ -133,6 +134,17 @@ export function seaServiceColumns(
|
||||
IMO {row.original.imoNumber}
|
||||
</Text>
|
||||
)}
|
||||
{(row.original.vesselType || row.original.flagState || row.original.grossTonnage) && (
|
||||
<Text size="xs" c="dimmed">
|
||||
{[
|
||||
row.original.vesselType,
|
||||
row.original.flagState,
|
||||
row.original.grossTonnage ? `${row.original.grossTonnage} GT` : null,
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join(' · ')}
|
||||
</Text>
|
||||
)}
|
||||
</div>
|
||||
),
|
||||
},
|
||||
@@ -151,6 +163,16 @@ export function seaServiceColumns(
|
||||
</Text>
|
||||
),
|
||||
},
|
||||
{
|
||||
header: t('recordVerification.columns.days', 'Days'),
|
||||
label: t('recordVerification.columns.days', 'Days'),
|
||||
align: 'right',
|
||||
cell: ({ row }) => (
|
||||
<Text size="sm" fw={600}>
|
||||
{seaServiceDays(row.original.engagementDate, row.original.dischargeDate) ?? '—'}
|
||||
</Text>
|
||||
),
|
||||
},
|
||||
statusColumn<SeaServiceRecord>(t),
|
||||
];
|
||||
}
|
||||
|
||||
@@ -11,18 +11,11 @@ import {
|
||||
Paper,
|
||||
SegmentedControl,
|
||||
Stack,
|
||||
Tabs,
|
||||
Text,
|
||||
Textarea,
|
||||
Title,
|
||||
} from '@mantine/core';
|
||||
import {
|
||||
IconAnchor,
|
||||
IconEye,
|
||||
IconInbox,
|
||||
IconPaperclip,
|
||||
IconStethoscope,
|
||||
} from '@tabler/icons-react';
|
||||
import { IconEye, IconInbox, IconPaperclip } from '@tabler/icons-react';
|
||||
import {
|
||||
AdvancedTable,
|
||||
notify,
|
||||
@@ -196,7 +189,14 @@ function AttachmentsModal({
|
||||
* freezes a record — sea service starts counting toward sea time, a medical
|
||||
* certificate starts satisfying the submission gate.
|
||||
*/
|
||||
export function MedicalVerificationPage() {
|
||||
export type VerificationKind = 'medical' | 'sea-service';
|
||||
|
||||
/**
|
||||
* One kind per page — the sidebar lists "Sea Service Verification" and
|
||||
* "Medical Verification" separately, so an officer lands on the queue they
|
||||
* came for rather than on a tab.
|
||||
*/
|
||||
export function MedicalVerificationPage({ kind = 'medical' }: { kind?: VerificationKind }) {
|
||||
const { t } = useTranslation();
|
||||
const [filter, setFilter] = useState<RecordQueueFilter>('SUBMITTED');
|
||||
const {
|
||||
@@ -372,74 +372,62 @@ export function MedicalVerificationPage() {
|
||||
[rulingSeaService, rule, verifySeaService, showDate, t],
|
||||
);
|
||||
|
||||
const isMedical = kind === 'medical';
|
||||
|
||||
return (
|
||||
<Container size="xl" py="md">
|
||||
<Title order={3} mb={4}>
|
||||
{t('recordVerification.title', 'Record verification')}
|
||||
{isMedical
|
||||
? t('recordVerification.medicalTitle', 'Medical Certificate Verification')
|
||||
: t('recordVerification.seaServiceTitle', 'Sea Service Verification')}
|
||||
</Title>
|
||||
<Text size="sm" c="dimmed" mb="md">
|
||||
{t(
|
||||
'recordVerification.subtitle',
|
||||
'Submitted sea-service records and medical certificates awaiting a ruling. Verified records are frozen; rejections return to the seafarer with your remark.',
|
||||
)}
|
||||
{isMedical
|
||||
? t(
|
||||
'recordVerification.medicalSubtitle',
|
||||
'Submitted medical certificates awaiting a ruling. Verified certificates are frozen; rejections return to the seafarer with your remark.',
|
||||
)
|
||||
: t(
|
||||
'recordVerification.seaServiceSubtitle',
|
||||
'Submitted sea-service records awaiting a ruling. Verified records are frozen and count toward sea time; rejections return to the seafarer with your remark.',
|
||||
)}
|
||||
</Text>
|
||||
|
||||
<Tabs defaultValue="medical" keepMounted={false}>
|
||||
<Tabs.List>
|
||||
<Tabs.Tab value="medical" leftSection={<IconStethoscope size={16} />}>
|
||||
{t('recordVerification.tabs.medical', {
|
||||
count: pendingMedicalList.length,
|
||||
defaultValue: 'Medical ({{count}})',
|
||||
})}
|
||||
</Tabs.Tab>
|
||||
<Tabs.Tab value="sea-service" leftSection={<IconAnchor size={16} />}>
|
||||
{t('recordVerification.tabs.seaService', {
|
||||
count: pendingSeaServiceList.length,
|
||||
defaultValue: 'Sea Service ({{count}})',
|
||||
})}
|
||||
</Tabs.Tab>
|
||||
</Tabs.List>
|
||||
|
||||
<Tabs.Panel value="medical" pt="md">
|
||||
{statusFilter}
|
||||
<AdvancedTable
|
||||
columns={medicalTableColumns}
|
||||
data={pagedMedical}
|
||||
tableName={t('recordVerification.tabs.medical', {
|
||||
count: pendingMedicalList.length,
|
||||
defaultValue: 'Medical ({{count}})',
|
||||
})}
|
||||
itemCount={pendingMedicalList.length}
|
||||
pageIndex={medicalPage}
|
||||
onPageChange={setMedicalPage}
|
||||
pageSize={medicalPageSize}
|
||||
onPageSizeChange={handleMedicalPageSizeChange}
|
||||
refresh={refetchMedical}
|
||||
isLoading={loadingMedical || fetchingMedical}
|
||||
emptyText={emptyText}
|
||||
/>
|
||||
</Tabs.Panel>
|
||||
|
||||
<Tabs.Panel value="sea-service" pt="md">
|
||||
{statusFilter}
|
||||
<AdvancedTable
|
||||
columns={seaServiceTableColumns}
|
||||
data={pagedSeaService}
|
||||
tableName={t('recordVerification.tabs.seaService', {
|
||||
count: pendingSeaServiceList.length,
|
||||
defaultValue: 'Sea Service ({{count}})',
|
||||
})}
|
||||
itemCount={pendingSeaServiceList.length}
|
||||
pageIndex={seaServicePage}
|
||||
onPageChange={setSeaServicePage}
|
||||
pageSize={seaServicePageSize}
|
||||
onPageSizeChange={handleSeaServicePageSizeChange}
|
||||
refresh={refetchSeaService}
|
||||
isLoading={loadingSeaService || fetchingSeaService}
|
||||
emptyText={emptyText}
|
||||
/>
|
||||
</Tabs.Panel>
|
||||
</Tabs>
|
||||
{isMedical ? (
|
||||
<AdvancedTable
|
||||
columns={medicalTableColumns}
|
||||
data={pagedMedical}
|
||||
tableName={t('recordVerification.tabs.medical', {
|
||||
count: pendingMedicalList.length,
|
||||
defaultValue: 'Medical ({{count}})',
|
||||
})}
|
||||
itemCount={pendingMedicalList.length}
|
||||
pageIndex={medicalPage}
|
||||
onPageChange={setMedicalPage}
|
||||
pageSize={medicalPageSize}
|
||||
onPageSizeChange={handleMedicalPageSizeChange}
|
||||
refresh={refetchMedical}
|
||||
isLoading={loadingMedical || fetchingMedical}
|
||||
emptyText={t('recordVerification.emptyText', 'Nothing awaiting verification.')}
|
||||
/>
|
||||
) : (
|
||||
<AdvancedTable
|
||||
columns={seaServiceTableColumns}
|
||||
data={pagedSeaService}
|
||||
tableName={t('recordVerification.tabs.seaService', {
|
||||
count: pendingSeaServiceList.length,
|
||||
defaultValue: 'Sea Service ({{count}})',
|
||||
})}
|
||||
itemCount={pendingSeaServiceList.length}
|
||||
pageIndex={seaServicePage}
|
||||
onPageChange={setSeaServicePage}
|
||||
pageSize={seaServicePageSize}
|
||||
onPageSizeChange={handleSeaServicePageSizeChange}
|
||||
refresh={refetchSeaService}
|
||||
isLoading={loadingSeaService || fetchingSeaService}
|
||||
emptyText={t('recordVerification.emptyText', 'Nothing awaiting verification.')}
|
||||
/>
|
||||
)}
|
||||
|
||||
<AttachmentsModal
|
||||
opened={Boolean(attachmentModal)}
|
||||
@@ -491,4 +479,6 @@ export function MedicalVerificationPage() {
|
||||
);
|
||||
}
|
||||
|
||||
export const SeaServiceVerificationPage = () => <MedicalVerificationPage kind="sea-service" />;
|
||||
|
||||
export default MedicalVerificationPage;
|
||||
|
||||
@@ -92,7 +92,8 @@ export const am: Translations = {
|
||||
applications: "ማመልከቻዎች",
|
||||
paymentConfig: "የክፍያ ውቅረት",
|
||||
analytics: "ትንታኔ",
|
||||
medicalVerification: "የሕክምና እና የባህር አገልግሎት ማረጋገጫ",
|
||||
seaServiceVerification: "የባህር አገልግሎት ማረጋገጫ",
|
||||
medicalVerification: "የሕክምና ማረጋገጫ",
|
||||
locations: "አካባቢዎች",
|
||||
configuration: "ውቅረት",
|
||||
profile: "መገለጫ",
|
||||
|
||||
@@ -90,7 +90,8 @@ export const en = {
|
||||
applications: 'Applications',
|
||||
paymentConfig: 'Payment Config',
|
||||
analytics: 'Analytics',
|
||||
medicalVerification: 'Medical and Sea Service Verification',
|
||||
seaServiceVerification: 'Sea Service Verification',
|
||||
medicalVerification: 'Medical Verification',
|
||||
locations: 'Locations',
|
||||
configuration: 'Configuration',
|
||||
profile: 'Profile',
|
||||
|
||||
@@ -102,6 +102,7 @@ export const NAV_SECTIONS: NavSection[] = [
|
||||
{ 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: '/sea-service-verification', label: 'nav.seaServiceVerification', icon: IconAnchor, permissions: [P.VERIFY_SEAFARER_RECORDS] },
|
||||
{ to: '/medical-verification', label: 'nav.medicalVerification', icon: IconHeart, permissions: [P.VERIFY_SEAFARER_RECORDS] },
|
||||
],
|
||||
},
|
||||
|
||||
@@ -22,7 +22,10 @@ import { ProfilePage } from '../features/profile/pages/ProfilePage';
|
||||
import { ConfigurationPage } from '../features/configuration/pages/ConfigurationPage';
|
||||
import { AnalyticsPage } from '../features/analytics/pages/AnalyticsPage';
|
||||
import { ApplicationReviewPage } from '../features/applications/pages/ApplicationReviewPage';
|
||||
import { MedicalVerificationPage } from '../features/medical-verification/pages/MedicalVerificationPage';
|
||||
import {
|
||||
MedicalVerificationPage,
|
||||
SeaServiceVerificationPage,
|
||||
} from '../features/medical-verification/pages/MedicalVerificationPage';
|
||||
import { PaymentConfigPage } from '../features/payment-config/pages/PaymentConfigPage';
|
||||
import { SeafarerRegistryPage } from '../features/seafarer-registry/pages/SeafarerRegistryPage';
|
||||
import { SeafarerRegistrationQueuePage } from '../features/seafarer-registration-review/pages/SeafarerRegistrationQueuePage';
|
||||
@@ -85,6 +88,7 @@ const router = createBrowserRouter([
|
||||
{ path: 'endorsement-queue', element: <Navigate to="/licence-review/type/ENDORSEMENT_COC" replace /> },
|
||||
{ path: 'endorsement-queue/:id', element: <Navigate to="/licence-review" replace /> },
|
||||
{ path: 'medical-verification', element: guard([P.VERIFY_SEAFARER_RECORDS], <MedicalVerificationPage />) },
|
||||
{ path: 'sea-service-verification', element: guard([P.VERIFY_SEAFARER_RECORDS], <SeaServiceVerificationPage />) },
|
||||
{ path: 'payment-config', element: guard([P.VIEW_PAYMENTS], <PaymentConfigPage />) },
|
||||
{ path: 'seafarer-registry', element: guard([P.VIEW_SEAFARER_REGISTRY], <SeafarerRegistryPage />) },
|
||||
// Seafarer registration is not a licence: own queue, own review.
|
||||
|
||||
Reference in New Issue
Block a user