- {headerName}
+ {headerName}
{app.applicationNumber}
diff --git a/apps/backoffice/src/app/features/location/pages/LocationPage.tsx b/apps/backoffice/src/app/features/location/pages/LocationPage.tsx
index b2d4f9e3c..e22a16b20 100644
--- a/apps/backoffice/src/app/features/location/pages/LocationPage.tsx
+++ b/apps/backoffice/src/app/features/location/pages/LocationPage.tsx
@@ -1,23 +1,9 @@
import { useState, useCallback } from 'react';
-import {
- Stack,
- Title,
- Group,
- Button,
- Paper,
- Text,
- Grid,
- Modal,
- ActionIcon,
- Tooltip,
- Loader,
- Center,
- Alert,
-} from '@mantine/core';
+import {Stack, Group, Button, Paper, Text, Grid, Modal, ActionIcon, Tooltip, Loader, Center, Alert} from '@mantine/core';
import { useDisclosure } from '@mantine/hooks';
import { IconSettings, IconMap, IconInfoCircle, IconPlus } from '@tabler/icons-react';
import { useTranslation } from 'react-i18next';
-import { notify, useErrorHandler, ModalFooter, PageLoader } from '@ema-platform/ui';
+import { ModalFooter, notify, PageHeader, PageLoader, useErrorHandler } from '@ema-platform/ui';
import { LocationTree } from '../components/LocationTree';
import { LocationDetail } from '../components/LocationDetail';
import { LocationForm } from '../components/LocationForm';
@@ -108,9 +94,11 @@ export function LocationPage() {
return (
-
- {t('location.title')}
-
+
{locationTypes.length > 0 && (
-
-
+
+ }
+ />
{locationTypes.length === 0 && (
-
- Logistics overview
-
-
- Licence applications currently in the department.
-
+
{stats.map((stat) => (
diff --git a/apps/backoffice/src/app/features/medical-verification/pages/MedicalVerificationPage/index.tsx b/apps/backoffice/src/app/features/medical-verification/pages/MedicalVerificationPage/index.tsx
index 9d79075a7..0ea1f9900 100644
--- a/apps/backoffice/src/app/features/medical-verification/pages/MedicalVerificationPage/index.tsx
+++ b/apps/backoffice/src/app/features/medical-verification/pages/MedicalVerificationPage/index.tsx
@@ -1,27 +1,8 @@
import { useCallback, useMemo, useState } from 'react';
import { useTranslation } from 'react-i18next';
-import {
- Badge,
- Button,
- Center,
- Container,
- Group,
- Loader,
- Modal,
- Paper,
- SegmentedControl,
- Stack,
- Text,
- Textarea,
- Title,
-} from '@mantine/core';
+import {Badge, Button, Center, Container, Group, Loader, Modal, Paper, SegmentedControl, Stack, Text, Textarea} from '@mantine/core';
import { IconEye, IconInbox, IconPaperclip } from '@tabler/icons-react';
-import {
- AdvancedTable,
- notify,
- PdfPreviewModal,
- type AdvancedColumn,
-} from '@ema-platform/ui';
+import { AdvancedTable, notify, PageHeader, PdfPreviewModal, type AdvancedColumn } from '@ema-platform/ui';
import { useDateDisplayer } from '@ema-platform/shared';
import {
extractErrorMessage,
@@ -376,22 +357,24 @@ export function MedicalVerificationPage({ kind = 'medical' }: { kind?: Verificat
return (
-
- {isMedical
- ? t('recordVerification.medicalTitle', 'Medical Certificate Verification')
- : t('recordVerification.seaServiceTitle', 'Sea Service Verification')}
-
-
- {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.',
- )}
-
+
{isMedical ? (
-
-
- {t('paymentConfig.title', 'Payment configuration')}
-
- {t(
- 'paymentConfig.subtitle',
- 'What each licence costs. Applicants are charged after approval, and the amount is fixed onto the application at that moment.',
- )}
-
-
- Vessel registration report
-
- {report
- ? `Register-wide totals with a ${showDate(report.filters.from)} – ${showDate(report.filters.to)} window on the trends.`
- : 'The national vessel register at a glance.'}
-
-
-
+
+ —
+
+ );
+ }
+
+ const days = daysSince(since);
+
+ // A decided item keeps its elapsed time visible — useful when reviewing how
+ // long something took — but never coloured, because nothing is pending.
+ const tone = done
+ ? 'neutral'
+ : days >= slaDays
+ ? 'danger'
+ : days >= slaDays * WARNING_RATIO
+ ? 'pending'
+ : 'neutral';
+
+ const label = days === 0 ? 'today' : `${days}d`;
+
+ return (
+
+
+ {label}
+
+
+ );
+}
diff --git a/libs/ui/src/lib/layout/PageHeader.tsx b/libs/ui/src/lib/layout/PageHeader.tsx
index 0e3d9d6de..b544a29cf 100644
--- a/libs/ui/src/lib/layout/PageHeader.tsx
+++ b/libs/ui/src/lib/layout/PageHeader.tsx
@@ -2,17 +2,54 @@ import { Group, Stack, Text, Title } from '@mantine/core';
import type { ReactNode } from 'react';
interface PageHeaderProps {
- title: string;
- subtitle?: string;
+ /**
+ * The page's name. A `ReactNode` rather than a string because detail pages
+ * title themselves with the record they are showing, which is often a
+ * localised or composed value rather than a literal.
+ */
+ title: ReactNode;
+ subtitle?: ReactNode;
+ /**
+ * Identifiers that belong beside the title rather than under it — a
+ * reference number, a status badge. Rendered inline, small and dimmed.
+ */
+ meta?: ReactNode;
/** Right-aligned actions (e.g. a primary button). */
action?: ReactNode;
+ /**
+ * Set when the header sits directly inside a gapped ``, which already
+ * spaces it from what follows — the built-in margin would double that gap.
+ */
+ noMargin?: boolean;
}
-export function PageHeader({ title, subtitle, action }: PageHeaderProps) {
+/**
+ * The heading every backoffice page starts with.
+ *
+ * It exists because 25 pages each rolled their own: eight at `order={2}`,
+ * seventeen at `order={3}`, with `mb="xs"`, `mb={4}`, `mb="lg"` and nothing at
+ * all between them. Clicking between two screens moved the title, which is the
+ * single loudest signal that a product was assembled rather than designed.
+ *
+ * `order={2}` is deliberate — it is the page's only `h2`, sitting under the
+ * app-level `h1`, so the heading outline reads correctly for a screen reader.
+ * Section headings inside cards stay at `order={4}`/`{5}` and are not this
+ * component's business.
+ */
+export function PageHeader({ title, subtitle, meta, action, noMargin }: PageHeaderProps) {
return (
-
+
- {title}
+
+ {title}
+ {meta}
+
{subtitle && (
{subtitle}