Refactor SeaServiceTab and MedicalTab to use AdvancedTable for improved data handling and pagination

This commit is contained in:
estifanos
2026-08-10 08:21:53 +00:00
parent 02681fe9be
commit fc28ef9960

View File

@@ -4,6 +4,7 @@ import {
Anchor,
Badge,
Button,
Card,
FileButton,
Group,
Loader,
@@ -12,7 +13,6 @@ import {
Paper,
Select,
Stack,
Table,
Tabs,
Text,
TextInput,
@@ -31,7 +31,7 @@ import {
IconTrash,
} from '@tabler/icons-react';
import { useState } from 'react';
import { notify } from '@ema-platform/ui';
import { AdvancedTable, notify, useServerTable, type AdvancedColumn } from '@ema-platform/ui';
import {
extractErrorMessage,
uploadDocument,
@@ -237,7 +237,82 @@ function SeaServiceTab() {
form.dischargeDate &&
form.engagementDate < form.dischargeDate;
if (isLoading) return <Loader />;
const { setPageIndex, pageSize, paginate } = useServerTable({ pageSize: 10 });
const page = paginate(records ?? []);
const columns: AdvancedColumn<SeaServiceRecord>[] = [
{
header: 'Vessel',
cell: ({ row }) => (
<>
<Text fw={600} size="sm">
{row.original.vesselName}
</Text>
{row.original.imoNumber && (
<Text size="xs" c="dimmed">
IMO {row.original.imoNumber}
</Text>
)}
</>
),
},
{ header: 'Rank', accessorKey: 'rank' },
{ header: 'From', accessorKey: 'engagementDate' },
{ header: 'To', accessorKey: 'dischargeDate' },
{
header: 'Status',
cell: ({ row }) => (
<Tooltip
label={row.original.verificationRemark ?? ''}
disabled={!row.original.verificationRemark}
>
<Badge color={RECORD_STATUS_COLORS[row.original.status]}>
{row.original.status}
</Badge>
</Tooltip>
),
},
{
header: '',
label: 'Actions',
align: 'right',
cell: ({ row }) => {
const record = row.original;
const locked = record.status !== 'SUBMITTED';
return (
<Group gap="xs" justify="flex-end" wrap="nowrap">
<Tooltip label="Evidence">
<ActionIcon
variant="subtle"
onClick={() => setEvidenceFor(record.id)}
>
<IconPaperclip size={16} />
</ActionIcon>
</Tooltip>
<Tooltip label={locked ? 'Verified records are frozen' : 'Edit'}>
<ActionIcon
variant="subtle"
disabled={locked}
onClick={() => openEdit(record)}
>
<IconEdit size={16} />
</ActionIcon>
</Tooltip>
<Tooltip label={locked ? 'Verified records are frozen' : 'Delete'}>
<ActionIcon
variant="subtle"
color="red"
disabled={locked}
onClick={() => remove(record)}
>
<IconTrash size={16} />
</ActionIcon>
</Tooltip>
</Group>
);
},
},
];
return (
<Stack>
@@ -264,87 +339,18 @@ function SeaServiceTab() {
</Text>
</Paper>
) : (
<Table.ScrollContainer minWidth={720}>
<Table striped highlightOnHover>
<Table.Thead>
<Table.Tr>
<Table.Th>Vessel</Table.Th>
<Table.Th>Rank</Table.Th>
<Table.Th>From</Table.Th>
<Table.Th>To</Table.Th>
<Table.Th>Status</Table.Th>
<Table.Th />
</Table.Tr>
</Table.Thead>
<Table.Tbody>
{(records ?? []).map((record) => {
const locked = record.status !== 'SUBMITTED';
return (
<Table.Tr key={record.id}>
<Table.Td>
<Text fw={600} size="sm">
{record.vesselName}
</Text>
{record.imoNumber && (
<Text size="xs" c="dimmed">
IMO {record.imoNumber}
</Text>
)}
</Table.Td>
<Table.Td>{record.rank}</Table.Td>
<Table.Td>{record.engagementDate}</Table.Td>
<Table.Td>{record.dischargeDate}</Table.Td>
<Table.Td>
<Tooltip
label={record.verificationRemark ?? ''}
disabled={!record.verificationRemark}
>
<Badge color={RECORD_STATUS_COLORS[record.status]}>
{record.status}
</Badge>
</Tooltip>
</Table.Td>
<Table.Td>
<Group gap="xs" justify="flex-end" wrap="nowrap">
<Tooltip label="Evidence">
<ActionIcon
variant="subtle"
onClick={() => setEvidenceFor(record.id)}
>
<IconPaperclip size={16} />
</ActionIcon>
</Tooltip>
<Tooltip
label={locked ? 'Verified records are frozen' : 'Edit'}
>
<ActionIcon
variant="subtle"
disabled={locked}
onClick={() => openEdit(record)}
>
<IconEdit size={16} />
</ActionIcon>
</Tooltip>
<Tooltip
label={locked ? 'Verified records are frozen' : 'Delete'}
>
<ActionIcon
variant="subtle"
color="red"
disabled={locked}
onClick={() => remove(record)}
>
<IconTrash size={16} />
</ActionIcon>
</Tooltip>
</Group>
</Table.Td>
</Table.Tr>
);
})}
</Table.Tbody>
</Table>
</Table.ScrollContainer>
<Card withBorder padding={0}>
<AdvancedTable
columns={columns}
data={page.rows}
tableName="Sea service"
itemCount={page.itemCount}
pageIndex={page.pageIndex}
onPageChange={setPageIndex}
pageSize={pageSize}
isLoading={isLoading}
/>
</Card>
)}
<Modal
@@ -530,7 +536,95 @@ function MedicalTab() {
const today = new Date().toISOString().slice(0, 10);
if (isLoading) return <Loader />;
const { setPageIndex, pageSize, paginate } = useServerTable({ pageSize: 10 });
const page = paginate(certificates ?? []);
const columns: AdvancedColumn<MedicalCertificate>[] = [
{
header: 'Issuer',
cell: ({ row }) => (
<>
<Text fw={600} size="sm">
{row.original.issuerName}
</Text>
{row.original.certificateNumber && (
<Text size="xs" c="dimmed">
{row.original.certificateNumber}
</Text>
)}
</>
),
},
{ header: 'Issued', accessorKey: 'issueDate' },
{
header: 'Expires',
cell: ({ row }) => (
<Group gap={6} wrap="nowrap">
{row.original.expiryDate}
{row.original.expiryDate < today && <Badge color="red">Expired</Badge>}
</Group>
),
},
{
header: 'Fitness',
cell: ({ row }) =>
FITNESS_OPTIONS.find((o) => o.value === row.original.fitnessStatus)
?.label ?? row.original.fitnessStatus,
},
{
header: 'Status',
cell: ({ row }) => (
<Tooltip
label={row.original.verificationRemark ?? ''}
disabled={!row.original.verificationRemark}
>
<Badge color={RECORD_STATUS_COLORS[row.original.status]}>
{row.original.status}
</Badge>
</Tooltip>
),
},
{
header: '',
label: 'Actions',
align: 'right',
cell: ({ row }) => {
const certificate = row.original;
const locked = certificate.status !== 'SUBMITTED';
return (
<Group gap="xs" justify="flex-end" wrap="nowrap">
<Tooltip label="Scan / evidence">
<ActionIcon
variant="subtle"
onClick={() => setEvidenceFor(certificate.id)}
>
<IconPaperclip size={16} />
</ActionIcon>
</Tooltip>
<Tooltip label={locked ? 'Verified certificates are frozen' : 'Edit'}>
<ActionIcon
variant="subtle"
disabled={locked}
onClick={() => openEdit(certificate)}
>
<IconEdit size={16} />
</ActionIcon>
</Tooltip>
<Tooltip label={locked ? 'Verified certificates are frozen' : 'Delete'}>
<ActionIcon
variant="subtle"
color="red"
disabled={locked}
onClick={() => remove(certificate)}
>
<IconTrash size={16} />
</ActionIcon>
</Tooltip>
</Group>
);
},
},
];
return (
<Stack>
@@ -550,103 +644,18 @@ function MedicalTab() {
</Text>
</Paper>
) : (
<Table.ScrollContainer minWidth={720}>
<Table striped highlightOnHover>
<Table.Thead>
<Table.Tr>
<Table.Th>Issuer</Table.Th>
<Table.Th>Issued</Table.Th>
<Table.Th>Expires</Table.Th>
<Table.Th>Fitness</Table.Th>
<Table.Th>Status</Table.Th>
<Table.Th />
</Table.Tr>
</Table.Thead>
<Table.Tbody>
{(certificates ?? []).map((certificate) => {
const locked = certificate.status !== 'SUBMITTED';
const expired = certificate.expiryDate < today;
return (
<Table.Tr key={certificate.id}>
<Table.Td>
<Text fw={600} size="sm">
{certificate.issuerName}
</Text>
{certificate.certificateNumber && (
<Text size="xs" c="dimmed">
{certificate.certificateNumber}
</Text>
)}
</Table.Td>
<Table.Td>{certificate.issueDate}</Table.Td>
<Table.Td>
<Group gap={6} wrap="nowrap">
{certificate.expiryDate}
{expired && <Badge color="red">Expired</Badge>}
</Group>
</Table.Td>
<Table.Td>
{FITNESS_OPTIONS.find(
(o) => o.value === certificate.fitnessStatus,
)?.label ?? certificate.fitnessStatus}
</Table.Td>
<Table.Td>
<Tooltip
label={certificate.verificationRemark ?? ''}
disabled={!certificate.verificationRemark}
>
<Badge color={RECORD_STATUS_COLORS[certificate.status]}>
{certificate.status}
</Badge>
</Tooltip>
</Table.Td>
<Table.Td>
<Group gap="xs" justify="flex-end" wrap="nowrap">
<Tooltip label="Scan / evidence">
<ActionIcon
variant="subtle"
onClick={() => setEvidenceFor(certificate.id)}
>
<IconPaperclip size={16} />
</ActionIcon>
</Tooltip>
<Tooltip
label={
locked ? 'Verified certificates are frozen' : 'Edit'
}
>
<ActionIcon
variant="subtle"
disabled={locked}
onClick={() => openEdit(certificate)}
>
<IconEdit size={16} />
</ActionIcon>
</Tooltip>
<Tooltip
label={
locked
? 'Verified certificates are frozen'
: 'Delete'
}
>
<ActionIcon
variant="subtle"
color="red"
disabled={locked}
onClick={() => remove(certificate)}
>
<IconTrash size={16} />
</ActionIcon>
</Tooltip>
</Group>
</Table.Td>
</Table.Tr>
);
})}
</Table.Tbody>
</Table>
</Table.ScrollContainer>
<Card withBorder padding={0}>
<AdvancedTable
columns={columns}
data={page.rows}
tableName="Medical certificates"
itemCount={page.itemCount}
pageIndex={page.pageIndex}
onPageChange={setPageIndex}
pageSize={pageSize}
isLoading={isLoading}
/>
</Card>
)}
<Modal