mirror of
https://github.com/Tria-plc/emaui.git
synced 2026-08-30 09:58:13 +00:00
feat: implement date display utility across various pages
- Added `useDateDisplayer` hook and `dateDisplayer` function to format dates consistently based on the user's language. - Updated multiple components and pages in both backoffice and portal applications to utilize the new date display functionality, ensuring proper formatting for dates in lists, tables, and detail views. - Introduced Ethiopian date formatting for Amharic language support. - Refactored date handling in components such as ExamAppealsPage, ResultPage, SeafarerRegistryPage, and others to improve localization and user experience.
This commit is contained in:
@@ -16,6 +16,7 @@ import {
|
||||
} from '@mantine/core';
|
||||
import { IconAlertTriangle, IconInfoCircle, IconPlus } from '@tabler/icons-react';
|
||||
import { notify } from '@ema-platform/ui';
|
||||
import { useDateDisplayer } from '@ema-platform/shared';
|
||||
import { extractErrorMessage } from '@ema-platform/api';
|
||||
import {
|
||||
useGetExamIncidentsQuery,
|
||||
@@ -51,6 +52,7 @@ const STATUS_COLOR: Record<ExamIncidentStatus, string> = {
|
||||
*/
|
||||
export function ExamIncidentsPanel({ examId }: { examId: string }) {
|
||||
const { t } = useTranslation();
|
||||
const showDate = useDateDisplayer();
|
||||
const { data: incidents, isError } = useGetExamIncidentsQuery(examId);
|
||||
const { data: registrations } = useGetExamRegistrationsQuery(examId);
|
||||
const [recordIncident, { isLoading: isFiling }] = useRecordIncidentMutation();
|
||||
@@ -175,7 +177,7 @@ export function ExamIncidentsPanel({ examId }: { examId: string }) {
|
||||
)}
|
||||
</Table.Td>
|
||||
<Table.Td>
|
||||
<Text fz="xs">{incident.occurredAt?.slice(0, 10)}</Text>
|
||||
<Text fz="xs">{showDate(incident.occurredAt)}</Text>
|
||||
</Table.Td>
|
||||
<Table.Td>
|
||||
<Badge
|
||||
|
||||
@@ -2,6 +2,7 @@ import { Table, Badge, ActionIcon, Text } from '@mantine/core';
|
||||
import { IconTrash } from '@tabler/icons-react';
|
||||
import { useGetItemsQuery, useDeleteItemMutation, type Item } from '../api/item-api';
|
||||
import { notify, useErrorHandler } from '@ema-platform/ui';
|
||||
import { useDateDisplayer } from '@ema-platform/shared';
|
||||
|
||||
const STATUS_COLORS: Record<Item['status'], string> = {
|
||||
DRAFT: 'gray',
|
||||
@@ -13,6 +14,7 @@ export function ItemTable() {
|
||||
const { data, isLoading } = useGetItemsQuery({});
|
||||
const [deleteItem] = useDeleteItemMutation();
|
||||
const { handleError } = useErrorHandler();
|
||||
const showDate = useDateDisplayer();
|
||||
|
||||
const handleDelete = async (id: string) => {
|
||||
try {
|
||||
@@ -43,7 +45,7 @@ export function ItemTable() {
|
||||
<Table.Td>
|
||||
<Badge color={STATUS_COLORS[item.status]}>{item.status}</Badge>
|
||||
</Table.Td>
|
||||
<Table.Td>{new Date(item.createdAt).toLocaleDateString()}</Table.Td>
|
||||
<Table.Td>{showDate(item.createdAt)}</Table.Td>
|
||||
<Table.Td>
|
||||
<ActionIcon
|
||||
color="red"
|
||||
|
||||
@@ -19,6 +19,7 @@ import {
|
||||
} from '@mantine/core';
|
||||
import { IconSearch, IconShieldCog } from '@tabler/icons-react';
|
||||
import { notify } from '@ema-platform/ui';
|
||||
import { useDateDisplayer } from '@ema-platform/shared';
|
||||
import {
|
||||
extractErrorMessage,
|
||||
localized,
|
||||
@@ -166,6 +167,7 @@ export function LicenseRegisterPage() {
|
||||
const [target, setTarget] = useState<IssuedLicense | null>(null);
|
||||
|
||||
const items = data?.items ?? [];
|
||||
const showDate = useDateDisplayer();
|
||||
|
||||
return (
|
||||
<Container size="xl" py="md">
|
||||
@@ -227,12 +229,12 @@ export function LicenseRegisterPage() {
|
||||
</Table.Td>
|
||||
<Table.Td>
|
||||
<Text size="sm" c="dimmed">
|
||||
{license.issueDate?.slice(0, 10)}
|
||||
{showDate(license.issueDate)}
|
||||
</Text>
|
||||
</Table.Td>
|
||||
<Table.Td>
|
||||
<Text size="sm" c="dimmed">
|
||||
{license.expiryDate?.slice(0, 10)}
|
||||
{showDate(license.expiryDate)}
|
||||
</Text>
|
||||
</Table.Td>
|
||||
<Table.Td>
|
||||
|
||||
@@ -20,6 +20,7 @@ import {
|
||||
STATUS_LABELS,
|
||||
type ApplicationDetail,
|
||||
} from '@ema-platform/api';
|
||||
import { useDateDisplayer } from '@ema-platform/shared';
|
||||
|
||||
type EntryKind = 'status' | 'remark' | 'upload' | 'assignment';
|
||||
|
||||
@@ -50,7 +51,8 @@ const ICONS: Record<EntryKind, typeof IconArrowRight> = {
|
||||
* notifications sent to the applicant are not among them.
|
||||
*/
|
||||
export function ActivityRail({ detail }: { detail: ApplicationDetail }) {
|
||||
const { t, i18n } = useTranslation();
|
||||
const { t } = useTranslation();
|
||||
const showDate = useDateDisplayer();
|
||||
|
||||
const entries = useMemo<ActivityEntry[]>(() => {
|
||||
const merged: ActivityEntry[] = [];
|
||||
@@ -159,12 +161,9 @@ export function ActivityRail({ detail }: { detail: ApplicationDetail }) {
|
||||
<Text size="xs" c="dimmed">
|
||||
·
|
||||
</Text>
|
||||
<Tooltip
|
||||
label={new Date(entry.at).toLocaleString(i18n.language)}
|
||||
withArrow
|
||||
>
|
||||
<Tooltip label={showDate(entry.at)} withArrow>
|
||||
<Text size="xs" c="dimmed">
|
||||
{new Date(entry.at).toLocaleDateString(i18n.language)}
|
||||
{showDate(entry.at.slice(0, 10))}
|
||||
</Text>
|
||||
</Tooltip>
|
||||
</Group>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { STATUS_LABELS, type LicenseApplication } from '@ema-platform/api';
|
||||
import { dateDisplayer } from '@ema-platform/shared';
|
||||
import { computeSla } from './sla';
|
||||
|
||||
/**
|
||||
@@ -27,15 +28,13 @@ const COLUMNS: Array<{
|
||||
{ header: 'Assigned officer', value: (a) => a.assignedOfficerId },
|
||||
{
|
||||
header: 'Submitted',
|
||||
value: (a, locale) =>
|
||||
a.submittedAt ? new Date(a.submittedAt).toLocaleString(locale) : '',
|
||||
value: (a, locale) => (a.submittedAt ? dateDisplayer(a.submittedAt, locale) : ''),
|
||||
},
|
||||
{
|
||||
header: 'Decided',
|
||||
value: (a, locale) =>
|
||||
a.decidedAt ? new Date(a.decidedAt).toLocaleString(locale) : '',
|
||||
value: (a, locale) => (a.decidedAt ? dateDisplayer(a.decidedAt, locale) : ''),
|
||||
},
|
||||
{ header: 'SLA', value: (a) => computeSla(a).label },
|
||||
{ header: 'SLA', value: (a, locale) => computeSla(a, undefined, locale).label },
|
||||
{ header: 'Adjustment rounds', value: (a) => a.adjustmentRound },
|
||||
{ header: 'Declared capital', value: (a) => a.capitalAmountDeclared },
|
||||
{ header: 'Verified capital', value: (a) => a.capitalAmountVerified },
|
||||
|
||||
@@ -56,6 +56,7 @@ import {
|
||||
AmharicDatePicker,
|
||||
type AdvancedColumn,
|
||||
} from "@ema-platform/ui";
|
||||
import { dateDisplayer } from "@ema-platform/shared";
|
||||
import { computeSla } from "../sla";
|
||||
import {
|
||||
DEFAULT_VIEW,
|
||||
@@ -428,18 +429,14 @@ export function LicenseQueuePage() {
|
||||
label: t("queue.submitted", "Submitted"),
|
||||
cell: ({ row }) => (
|
||||
<Text size="sm" c="dimmed">
|
||||
{row.original.submittedAt
|
||||
? new Date(row.original.submittedAt).toLocaleDateString(
|
||||
i18n.language,
|
||||
)
|
||||
: "—"}
|
||||
{dateDisplayer(row.original.submittedAt, i18n.language)}
|
||||
</Text>
|
||||
),
|
||||
},
|
||||
{
|
||||
header: t("queue.sla", "Age / SLA"),
|
||||
cell: ({ row }) => {
|
||||
const sla = computeSla(row.original);
|
||||
const sla = computeSla(row.original, undefined, i18n.language);
|
||||
return (
|
||||
// Colour is never the only signal — the label says the same thing.
|
||||
<Tooltip label={sla.tooltip} withArrow>
|
||||
|
||||
@@ -59,6 +59,7 @@ import {
|
||||
type RemarkTargetType,
|
||||
} from '@ema-platform/api';
|
||||
import { ErrorState, ModalFooter, AmharicDatePicker } from '@ema-platform/ui';
|
||||
import { useDateDisplayer } from '@ema-platform/shared';
|
||||
import { usePermissions } from '@ema-platform/auth';
|
||||
import { useAppSelector } from '../../../store/hooks';
|
||||
import { DecisionBar } from '../components/DecisionBar';
|
||||
@@ -109,6 +110,7 @@ function buildChecklist(
|
||||
*/
|
||||
export function LicenseReviewPage() {
|
||||
const { t, i18n } = useTranslation();
|
||||
const showDate = useDateDisplayer();
|
||||
const { id = '' } = useParams();
|
||||
const { can } = usePermissions();
|
||||
const currentUserId = useAppSelector((state) => state.auth.user?.id) ?? '';
|
||||
@@ -268,7 +270,7 @@ export function LicenseReviewPage() {
|
||||
const app = data.application;
|
||||
const status = app.status;
|
||||
const presentation = presentationFor(app.licenseType?.key);
|
||||
const sla = computeSla(app);
|
||||
const sla = computeSla(app, undefined, i18n.language);
|
||||
const eligibility = evaluateEligibility(app, app.licenseType, i18n.language);
|
||||
|
||||
const rawThreshold = app.licenseType?.capitalThreshold;
|
||||
@@ -536,11 +538,7 @@ export function LicenseReviewPage() {
|
||||
<SummaryRow label={t('review.kind', 'Kind')} value={app.kind} />
|
||||
<SummaryRow
|
||||
label={t('review.submitted', 'Submitted')}
|
||||
value={
|
||||
app.submittedAt
|
||||
? new Date(app.submittedAt).toLocaleDateString(i18n.language)
|
||||
: undefined
|
||||
}
|
||||
value={showDate(app.submittedAt)}
|
||||
/>
|
||||
<SummaryRow label={t('review.slaLabel', 'SLA')} value={sla.label} />
|
||||
</Stack>
|
||||
@@ -604,7 +602,7 @@ export function LicenseReviewPage() {
|
||||
}
|
||||
>
|
||||
<Text size="xs" c="dimmed">
|
||||
{new Date(entry.createdAt).toLocaleDateString(i18n.language)}
|
||||
{showDate(entry.createdAt)}
|
||||
</Text>
|
||||
</Timeline.Item>
|
||||
))}
|
||||
@@ -848,7 +846,7 @@ export function LicenseReviewPage() {
|
||||
<div>
|
||||
<Text size="sm">
|
||||
{inspection.scheduledDate
|
||||
? new Date(inspection.scheduledDate).toLocaleString(i18n.language)
|
||||
? showDate(inspection.scheduledDate)
|
||||
: t('review.unscheduled', 'Not scheduled')}
|
||||
</Text>
|
||||
{inspection.findings && (
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import type { LicenseApplication } from '@ema-platform/api';
|
||||
import { dateDisplayer } from '@ema-platform/shared';
|
||||
|
||||
/** Amber once this much of the window has been consumed. */
|
||||
const WARNING_RATIO = 0.7;
|
||||
@@ -35,6 +36,7 @@ function formatDuration(ms: number): string {
|
||||
export function computeSla(
|
||||
application: LicenseApplication,
|
||||
now: number = Date.now(),
|
||||
language = 'en',
|
||||
): SlaState {
|
||||
const slaHours = application.licenseType?.slaHours;
|
||||
const submittedAt = application.submittedAt;
|
||||
@@ -54,7 +56,7 @@ export function computeSla(
|
||||
const elapsed = (application.decidedAt ? new Date(application.decidedAt).getTime() : now) - submitted;
|
||||
const window = slaHours * HOUR_MS;
|
||||
const ratio = Math.min(Math.max(elapsed / window, 0), 1);
|
||||
const targetText = `Target ${slaHours}h from submission (${new Date(target).toLocaleString()})`;
|
||||
const targetText = `Target ${slaHours}h from submission (${dateDisplayer(target, language)})`;
|
||||
|
||||
if (application.decidedAt) {
|
||||
const met = elapsed <= window;
|
||||
|
||||
@@ -24,6 +24,7 @@ import {
|
||||
IconX,
|
||||
} from '@tabler/icons-react';
|
||||
import { AdvancedTable, notify, type AdvancedColumn } from '@ema-platform/ui';
|
||||
import { useDateDisplayer } from '@ema-platform/shared';
|
||||
import {
|
||||
extractErrorMessage,
|
||||
useGetAttachmentsQuery,
|
||||
@@ -216,6 +217,8 @@ export function MedicalVerificationPage() {
|
||||
title: string;
|
||||
} | null>(null);
|
||||
|
||||
const showDate = useDateDisplayer();
|
||||
|
||||
const [medicalPage, setMedicalPage] = useState(0);
|
||||
const [seaServicePage, setSeaServicePage] = useState(0);
|
||||
const [medicalPageSize, setMedicalPageSize] = useState(PAGE_SIZE);
|
||||
@@ -301,7 +304,7 @@ export function MedicalVerificationPage() {
|
||||
label: 'Validity',
|
||||
cell: ({ row }) => (
|
||||
<Text size="sm">
|
||||
{row.original.issueDate} → {row.original.expiryDate}
|
||||
{showDate(row.original.issueDate)} → {showDate(row.original.expiryDate)}
|
||||
</Text>
|
||||
),
|
||||
},
|
||||
@@ -367,7 +370,7 @@ export function MedicalVerificationPage() {
|
||||
),
|
||||
},
|
||||
],
|
||||
[rulingMedical, rule, verifyMedical],
|
||||
[rulingMedical, rule, verifyMedical, showDate],
|
||||
);
|
||||
|
||||
const seaServiceColumns: AdvancedColumn<SeaServiceRecord>[] = useMemo(
|
||||
@@ -415,7 +418,7 @@ export function MedicalVerificationPage() {
|
||||
label: 'Period',
|
||||
cell: ({ row }) => (
|
||||
<Text size="sm">
|
||||
{row.original.engagementDate} → {row.original.dischargeDate}
|
||||
{showDate(row.original.engagementDate)} → {showDate(row.original.dischargeDate)}
|
||||
</Text>
|
||||
),
|
||||
},
|
||||
@@ -471,7 +474,7 @@ export function MedicalVerificationPage() {
|
||||
),
|
||||
},
|
||||
],
|
||||
[rulingSeaService, rule, verifySeaService],
|
||||
[rulingSeaService, rule, verifySeaService, showDate],
|
||||
);
|
||||
|
||||
return (
|
||||
|
||||
@@ -18,6 +18,7 @@ import {
|
||||
} from '@mantine/core';
|
||||
import { IconGavel, IconInfoCircle } from '@tabler/icons-react';
|
||||
import { notify } from '@ema-platform/ui';
|
||||
import { useDateDisplayer } from '@ema-platform/shared';
|
||||
import { extractErrorMessage } from '@ema-platform/api';
|
||||
import {
|
||||
useGetPendingAppealsQuery,
|
||||
@@ -35,6 +36,7 @@ import type { ExamAppeal } from '../types/result';
|
||||
export function ExamAppealsPage() {
|
||||
const { t, i18n } = useTranslation();
|
||||
const locale = i18n.language as 'en' | 'am';
|
||||
const showDate = useDateDisplayer();
|
||||
const { data: appeals, isLoading, isError } = useGetPendingAppealsQuery();
|
||||
const [decideAppeal, { isLoading: isDeciding }] = useDecideAppealMutation();
|
||||
|
||||
@@ -128,7 +130,7 @@ export function ExamAppealsPage() {
|
||||
</Text>
|
||||
</Table.Td>
|
||||
<Table.Td>
|
||||
<Text fz="xs">{appeal.createdAt?.slice(0, 10)}</Text>
|
||||
<Text fz="xs">{showDate(appeal.createdAt)}</Text>
|
||||
</Table.Td>
|
||||
<Table.Td>
|
||||
<Button
|
||||
|
||||
@@ -38,6 +38,7 @@ import {
|
||||
IconSend,
|
||||
} from '@tabler/icons-react';
|
||||
import { notify, BilingualInput, useErrorHandler, AdvancedTable, useServerTable, ModalFooter, type AdvancedColumn } from '@ema-platform/ui';
|
||||
import { useDateDisplayer } from '@ema-platform/shared';
|
||||
import type { BilingualValue } from '@ema-platform/ui';
|
||||
import { extractErrorMessage } from '@ema-platform/api';
|
||||
import {
|
||||
@@ -116,6 +117,7 @@ function InfoRow({ label, value }: { label: string; value: string }) {
|
||||
|
||||
export function ResultPage() {
|
||||
const { t, i18n } = useTranslation();
|
||||
const showDate = useDateDisplayer();
|
||||
const locale = i18n.language as 'en' | 'am';
|
||||
const { handleError } = useErrorHandler();
|
||||
const { data: examRes } = useGetExamsQuery();
|
||||
@@ -344,7 +346,7 @@ export function ResultPage() {
|
||||
},
|
||||
{
|
||||
header: t('result.columns.date'),
|
||||
cell: ({ row }) => <Text fz="sm">{new Date(row.original.createdAt).toLocaleDateString()}</Text>,
|
||||
cell: ({ row }) => <Text fz="sm">{showDate(row.original.createdAt)}</Text>,
|
||||
},
|
||||
{
|
||||
header: '',
|
||||
@@ -481,7 +483,7 @@ export function ResultPage() {
|
||||
<SimpleGrid cols={{ base: 2, sm: 4 }} spacing="sm">
|
||||
<InfoRow label={t('result.detail.fullName')} value={detailResult.profile ? `${detailResult.profile.firstName} ${detailResult.profile.middleName ?? ''} ${detailResult.profile.lastName}` : detailResult.seafarerId} />
|
||||
<InfoRow label={t('result.detail.gender')} value={detailResult.profile?.gender ?? '—'} />
|
||||
<InfoRow label={t('result.detail.dateOfBirth')} value={detailResult.profile?.dob ? new Date(detailResult.profile.dob).toLocaleDateString() : '—'} />
|
||||
<InfoRow label={t('result.detail.dateOfBirth')} value={showDate(detailResult.profile?.dob)} />
|
||||
<InfoRow label={t('result.detail.maritalStatus')} value={detailResult.profile?.maritalStatus ?? '—'} />
|
||||
</SimpleGrid>
|
||||
</Paper>
|
||||
@@ -499,7 +501,7 @@ export function ResultPage() {
|
||||
<InfoRow label={t('result.detail.examTitle')} value={detailResult.exam.title?.[locale] ?? '—'} />
|
||||
<InfoRow label={t('result.detail.type')} value={detailResult.exam.type ?? '—'} />
|
||||
<InfoRow label={t('result.detail.venue')} value={detailResult.exam.venue ?? '—'} />
|
||||
<InfoRow label={t('result.detail.date')} value={detailResult.exam.date ? new Date(detailResult.exam.date).toLocaleDateString() : '—'} />
|
||||
<InfoRow label={t('result.detail.date')} value={showDate(detailResult.exam.date)} />
|
||||
<InfoRow label={t('result.detail.passMark')} value={String(detailResult.exam.cuttingPoint ?? 0)} />
|
||||
</SimpleGrid>
|
||||
</Paper>
|
||||
|
||||
@@ -25,6 +25,7 @@ import {
|
||||
IconStethoscope,
|
||||
} from '@tabler/icons-react';
|
||||
import { AdvancedTable, notify, useServerTable, type AdvancedColumn } from '@ema-platform/ui';
|
||||
import { useDateDisplayer } from '@ema-platform/shared';
|
||||
import {
|
||||
extractErrorMessage,
|
||||
useApiQuery,
|
||||
@@ -81,6 +82,7 @@ function SeafarerDetailDrawer({
|
||||
useGetSeaServiceForProfileQuery(profileId, { skip: !profileId });
|
||||
const { data: medical, isLoading: loadingMedical } =
|
||||
useGetMedicalForProfileQuery(profileId, { skip: !profileId });
|
||||
const showDate = useDateDisplayer();
|
||||
|
||||
return (
|
||||
<Drawer
|
||||
@@ -177,7 +179,7 @@ function SeafarerDetailDrawer({
|
||||
</Table.Td>
|
||||
<Table.Td>{record.rank}</Table.Td>
|
||||
<Table.Td>
|
||||
{record.engagementDate} → {record.dischargeDate}
|
||||
{showDate(record.engagementDate)} → {showDate(record.dischargeDate)}
|
||||
</Table.Td>
|
||||
<Table.Td>
|
||||
<Badge size="sm" color={RECORD_STATUS_COLORS[record.status]}>
|
||||
@@ -213,7 +215,7 @@ function SeafarerDetailDrawer({
|
||||
<Table.Tr key={certificate.id}>
|
||||
<Table.Td>{certificate.issuerName}</Table.Td>
|
||||
<Table.Td>
|
||||
{certificate.issueDate} → {certificate.expiryDate}
|
||||
{showDate(certificate.issueDate)} → {showDate(certificate.expiryDate)}
|
||||
</Table.Td>
|
||||
<Table.Td>{certificate.fitnessStatus}</Table.Td>
|
||||
<Table.Td>
|
||||
|
||||
@@ -27,6 +27,7 @@ import {
|
||||
IconShieldCog,
|
||||
} from '@tabler/icons-react';
|
||||
import { notify } from '@ema-platform/ui';
|
||||
import { useDateDisplayer } from '@ema-platform/shared';
|
||||
import {
|
||||
extractErrorMessage,
|
||||
useGetVesselIncidentsQuery,
|
||||
@@ -56,6 +57,7 @@ function VesselDetailDrawer({
|
||||
}) {
|
||||
const { data: incidents, isLoading: loadingIncidents } =
|
||||
useGetVesselIncidentsQuery(vessel?.id ?? '', { skip: !vessel });
|
||||
const showDate = useDateDisplayer();
|
||||
|
||||
const particulars: [string, string | number | null][] = vessel
|
||||
? [
|
||||
@@ -75,7 +77,7 @@ function VesselDetailDrawer({
|
||||
['Engines', vessel.numberOfEngines],
|
||||
['Hull material', vessel.hullMaterial],
|
||||
['Owner', vessel.ownerName],
|
||||
['Registered', vessel.registeredAt?.slice(0, 10) ?? null],
|
||||
['Registered', vessel.registeredAt ? showDate(vessel.registeredAt) : null],
|
||||
]
|
||||
: [];
|
||||
|
||||
@@ -127,7 +129,7 @@ function VesselDetailDrawer({
|
||||
<Card key={incident.id} withBorder radius="md" p="sm">
|
||||
<Group justify="space-between">
|
||||
<Text size="sm" fw={600}>
|
||||
{incident.occurredAt}
|
||||
{showDate(incident.occurredAt)}
|
||||
{incident.location ? ` — ${incident.location}` : ''}
|
||||
</Text>
|
||||
<Badge size="sm" variant="light">
|
||||
@@ -240,6 +242,7 @@ export function VesselRegistrationQueuePage() {
|
||||
const [statusTarget, setStatusTarget] = useState<Vessel | null>(null);
|
||||
|
||||
const items = data?.items ?? [];
|
||||
const showDate = useDateDisplayer();
|
||||
|
||||
return (
|
||||
<Container size="xl" py="md">
|
||||
@@ -319,7 +322,7 @@ export function VesselRegistrationQueuePage() {
|
||||
</Table.Td>
|
||||
<Table.Td>
|
||||
<Text size="sm" c="dimmed">
|
||||
{vessel.registeredAt?.slice(0, 10)}
|
||||
{showDate(vessel.registeredAt)}
|
||||
</Text>
|
||||
</Table.Td>
|
||||
<Table.Td>
|
||||
|
||||
Reference in New Issue
Block a user