=> {
- if (!versionHistoryRef.current) {
- versionHistoryRef.current = true;
- try {
- const response = await getDocumentVersionHistoryResponse({
- nhsNumber,
- baseUrl,
- baseHeaders,
- documentReferenceId: documentReference.id,
- });
- setVersionHistory(response);
- } catch {
- navigate(routes.PATIENT_DOCUMENTS);
- } finally {
- setLoading(false);
- }
- }
- };
- void fetchVersionHistory();
- }, [documentReference, nhsNumber, baseUrl, baseHeaders, navigate]);
-
- if (loading) {
- return ;
- }
-
- if (!documentReference) {
- navigate(routes.PATIENT_DOCUMENTS);
- return <>>;
- }
-
- const renderVersionHistoryTimeline = (): React.JSX.Element => {
- if (!versionHistory?.entry || versionHistory.entry.length === 0) {
- return No version history available for this document.
;
- }
-
- const sortedEntries = [...versionHistory.entry].sort(
- (a, b) => Number(getVersionId(b.resource)) - Number(getVersionId(a.resource)),
- );
-
- return (
-
- {sortedEntries.map((entry, index) => {
- const isCurrentVersion = index === 0;
- const status = isCurrentVersion
- ? TimelineStatus.Active
- : TimelineStatus.Inactive;
- const isLastItem = index === versionHistory.entry!.length - 1;
- const doc = entry.resource;
- const version = getVersionId(doc);
- const heading = `${docTypeLabel}: version ${version}`;
-
- return (
-
-
- {heading}
-
-
- {isCurrentVersion && (
-
- This is the current version shown in this patient's record
-
- )}
-
-
-
- {isCurrentVersion ? (
-
- View
-
- ) : (
-
-
-
- Restore version
-
-
- )}
-
- );
- })}
-
- );
- };
-
- return (
-
-
-
-
{pageHeader}
-
- {renderVersionHistoryTimeline()}
-
- );
-};
-
-export default DocumentVersionHistoryPage;
diff --git a/app/src/pages/documentVersionPage/DocumentVersionRestorePage.test.tsx b/app/src/pages/documentVersionPage/DocumentVersionRestorePage.test.tsx
new file mode 100644
index 000000000..057dfbc3d
--- /dev/null
+++ b/app/src/pages/documentVersionPage/DocumentVersionRestorePage.test.tsx
@@ -0,0 +1,168 @@
+import { render, RenderResult, screen, waitFor } from '@testing-library/react';
+import { createMemoryRouter, RouterProvider } from 'react-router-dom';
+import { Mock } from 'vitest';
+import useConfig from '../../helpers/hooks/useConfig';
+import { routes } from '../../types/generic/routes';
+import { DocumentReference } from '../../types/pages/documentSearchResultsPage/types';
+import DocumentVersionRestorePage from './DocumentVersionRestorePage';
+
+const mockNavigate = vi.fn();
+
+vi.mock('react-router-dom', async () => {
+ const actual = await vi.importActual('react-router-dom');
+ return {
+ ...actual,
+ useNavigate: (): Mock => mockNavigate,
+ };
+});
+
+vi.mock(
+ '../../components/blocks/_documentVersion/documentVersionRestoreHistoryStage/DocumentVersionRestoreHistoryStage',
+ () => ({
+ default: (props: {
+ documentReference: DocumentReference | null;
+ setDocumentReferenceToRestore: (docRef: DocumentReference) => void;
+ setDocumentReference: (docRef: DocumentReference) => void;
+ setLatestVersion: (version: string) => void;
+ }): React.JSX.Element => (
+
+ {props.documentReference && (
+ {props.documentReference.id}
+ )}
+
+ ),
+ }),
+);
+
+vi.mock('../../components/blocks/_patientDocuments/documentView/DocumentView', () => ({
+ default: (props: {
+ documentReference: DocumentReference | null;
+ viewState?: string;
+ isActiveVersion?: boolean;
+ }): React.JSX.Element => (
+
+ {props.viewState && {props.viewState}}
+ {props.isActiveVersion !== undefined && (
+ {String(props.isActiveVersion)}
+ )}
+ {props.documentReference && (
+ {props.documentReference.id}
+ )}
+
+ ),
+ DOCUMENT_VIEW_STATE: {
+ DOCUMENT: 'DOCUMENT',
+ VERSION_HISTORY: 'VERSION_HISTORY',
+ },
+}));
+
+vi.mock('../../helpers/hooks/useConfig');
+
+const mockedUseConfig = useConfig as Mock;
+
+describe('DocumentVersionRestorePage', () => {
+ beforeEach(() => {
+ import.meta.env.VITE_ENVIRONMENT = 'vitest';
+ mockedUseConfig.mockReturnValue({
+ featureFlags: {
+ versionHistoryEnabled: true,
+ },
+ });
+ });
+
+ afterEach(() => {
+ vi.clearAllMocks();
+ });
+
+ describe('Feature flag guard', () => {
+ it('navigates to home when versionHistoryEnabled is false', async () => {
+ mockedUseConfig.mockReturnValue({
+ featureFlags: {
+ versionHistoryEnabled: false,
+ },
+ });
+
+ renderPage();
+
+ await waitFor(() => {
+ expect(mockNavigate).toHaveBeenCalledWith(routes.HOME);
+ });
+ });
+
+ it('renders empty fragment when versionHistoryEnabled is false', () => {
+ mockedUseConfig.mockReturnValue({
+ featureFlags: {
+ versionHistoryEnabled: false,
+ },
+ });
+
+ const { container } = renderPage();
+
+ expect(container.innerHTML).toBe('');
+ });
+
+ it('does not navigate to home when versionHistoryEnabled is true', () => {
+ renderPage();
+
+ expect(mockNavigate).not.toHaveBeenCalledWith(routes.HOME);
+ });
+
+ it('navigates to home when featureFlags is undefined', async () => {
+ mockedUseConfig.mockReturnValue({
+ featureFlags: undefined,
+ });
+
+ renderPage();
+
+ await waitFor(() => {
+ expect(mockNavigate).toHaveBeenCalledWith(routes.HOME);
+ });
+ });
+ });
+
+ describe('Rendering', () => {
+ it('renders the history stage at the index route', () => {
+ renderPage();
+
+ expect(screen.getByTestId('history-stage')).toBeInTheDocument();
+ });
+
+ it('renders the document view at the view route', () => {
+ renderPage('/patient/documents/version-history/view');
+
+ expect(screen.getByTestId('document-view')).toBeInTheDocument();
+ });
+
+ it('passes VERSION_HISTORY view state to DocumentView', () => {
+ renderPage('/patient/documents/version-history/view');
+
+ expect(screen.getByTestId('view-state')).toHaveTextContent('VERSION_HISTORY');
+ });
+ });
+
+ describe('State management', () => {
+ it('passes null documentReference to history stage initially', () => {
+ renderPage();
+
+ expect(screen.queryByTestId('history-doc-ref')).not.toBeInTheDocument();
+ });
+ });
+
+ const renderPage = (
+ initialPath: string = '/patient/documents/version-history',
+ ): RenderResult => {
+ const router = createMemoryRouter(
+ [
+ {
+ path: '/patient/documents/version-history/*',
+ element: ,
+ },
+ ],
+ {
+ initialEntries: [initialPath],
+ },
+ );
+
+ return render();
+ };
+});
diff --git a/app/src/pages/documentVersionPage/DocumentVersionRestorePage.tsx b/app/src/pages/documentVersionPage/DocumentVersionRestorePage.tsx
new file mode 100644
index 000000000..c4d7c7575
--- /dev/null
+++ b/app/src/pages/documentVersionPage/DocumentVersionRestorePage.tsx
@@ -0,0 +1,57 @@
+import { useEffect, useState } from 'react';
+import { Route, Routes, useNavigate } from 'react-router-dom';
+import DocumentVersionRestoreHistoryStage from '../../components/blocks/_documentVersion/documentVersionRestoreHistoryStage/DocumentVersionRestoreHistoryStage';
+import DocumentView, {
+ DOCUMENT_VIEW_STATE,
+} from '../../components/blocks/_patientDocuments/documentView/DocumentView';
+import useConfig from '../../helpers/hooks/useConfig';
+import { getLastURLPath } from '../../helpers/utils/urlManipulations';
+import { routeChildren, routes } from '../../types/generic/routes';
+import { DocumentReference } from '../../types/pages/documentSearchResultsPage/types';
+
+const DocumentVersionRestorePage = (): React.JSX.Element => {
+ const [documentReferenceToRestore, setDocumentReferenceToRestore] =
+ useState(null);
+ const [documentReference, setDocumentReference] = useState(null);
+ const [latestVersion, setLatestVersion] = useState('');
+ const config = useConfig();
+ const navigate = useNavigate();
+
+ useEffect(() => {
+ if (!config.featureFlags?.versionHistoryEnabled) {
+ navigate(routes.HOME);
+ }
+ }, [config.featureFlags, navigate]);
+
+ if (!config.featureFlags?.versionHistoryEnabled) {
+ return <>>;
+ }
+
+ return (
+
+
+ }
+ />
+
+ }
+ />
+
+ );
+};
+
+export default DocumentVersionRestorePage;
diff --git a/app/src/router/AppRouter.tsx b/app/src/router/AppRouter.tsx
index 786d64c20..9c788d785 100644
--- a/app/src/router/AppRouter.tsx
+++ b/app/src/router/AppRouter.tsx
@@ -33,6 +33,7 @@ import CookiePolicyPage from '../pages/cookiePolicyPage/CookiePolicyPage';
import DocumentCorrectPage from '../pages/documentCorrectPage/DocumentCorrectPage';
import { AdminPage } from '../pages/adminPage/AdminPage';
import UserPatientRestrictionsPage from '../pages/userPatientRestrictionsPage/UserPatientRestrictionsPage';
+import DocumentVersionRestorePage from '../pages/documentVersionPage/DocumentVersionRestorePage';
const {
START,
@@ -69,6 +70,8 @@ const {
COOKIES_POLICY_WILDCARD,
DOCUMENT_REASSIGN_PAGES,
DOCUMENT_REASSIGN_PAGES_WILDCARD,
+ DOCUMENT_VERSION_HISTORY,
+ DOCUMENT_VERSION_HISTORY_WILDCARD,
USER_PATIENT_RESTRICTIONS,
USER_PATIENT_RESTRICTIONS_WILDCARD,
} = routes;
@@ -427,6 +430,14 @@ export const routeMap: Routes = {
page: ,
type: ROUTE_TYPE.PATIENT,
},
+ [DOCUMENT_VERSION_HISTORY]: {
+ page: ,
+ type: ROUTE_TYPE.PATIENT,
+ },
+ [DOCUMENT_VERSION_HISTORY_WILDCARD]: {
+ page: ,
+ type: ROUTE_TYPE.PATIENT,
+ },
[USER_PATIENT_RESTRICTIONS]: {
page: ,
type: ROUTE_TYPE.PRIVATE,
diff --git a/app/src/types/blocks/lloydGeorgeActions.ts b/app/src/types/blocks/lloydGeorgeActions.ts
index 08637e2c2..3a79221db 100644
--- a/app/src/types/blocks/lloydGeorgeActions.ts
+++ b/app/src/types/blocks/lloydGeorgeActions.ts
@@ -84,7 +84,7 @@ export const VersionHistoryAction = (
index: 4,
label: label,
key: ACTION_LINK_KEY.HISTORY,
- type: RECORD_ACTION.UPDATE, // This could be a different type if needed
+ type: RECORD_ACTION.UPDATE,
unauthorised: [],
onClick,
showIfRecordInStorage: true,
diff --git a/app/src/types/fhirR4/bundle.ts b/app/src/types/fhirR4/bundle.ts
index 87be6e27b..5413cb3bd 100644
--- a/app/src/types/fhirR4/bundle.ts
+++ b/app/src/types/fhirR4/bundle.ts
@@ -131,12 +131,6 @@ export interface BundleEntry {
fullUrl?: string;
/** A resource in the bundle */
resource: T;
- /** Search related information */
- search?: BundleEntrySearch;
- /** Additional execution information (transaction/batch/history) */
- request?: BundleEntryRequest;
- /** Results of execution (transaction/batch/history) */
- response?: BundleEntryResponse;
}
// ─── Bundle Resource ─────────────────────────────────────────────────────────
diff --git a/app/src/types/generic/routes.ts b/app/src/types/generic/routes.ts
index f92f50c9b..26548f3da 100644
--- a/app/src/types/generic/routes.ts
+++ b/app/src/types/generic/routes.ts
@@ -30,6 +30,9 @@ export enum routes {
DOCUMENT_REASSIGN_PAGES = '/patient/document-reassign-pages',
DOCUMENT_REASSIGN_PAGES_WILDCARD = '/patient/document-reassign-pages/*',
+ DOCUMENT_VERSION_HISTORY = '/patient/documents/version-history',
+ DOCUMENT_VERSION_HISTORY_WILDCARD = '/patient/documents/version-history/*',
+
MOCK_LOGIN = 'Auth/MockLogin',
ADMIN_ROUTE = '/admin',
@@ -75,7 +78,7 @@ export enum routeChildren {
DOCUMENT_REASSIGN_UPLOADING = '/patient/document-reassign-pages/uploading',
DOCUMENT_REASSIGN_COMPLETE = '/patient/document-reassign-pages/complete',
- DOCUMENT_VIEW_VERSION_HISTORY = '/patient/documents/version-history-view',
+ DOCUMENT_VIEW_VERSION_HISTORY = '/patient/documents/version-history/view',
DOCUMENT_VERSION_HISTORY = '/patient/documents/version-history',
DOCUMENT_VIEW = '/patient/documents/view',