= fileArray.map((file) => ({
- id: uuidv4(),
- file,
- state: DOCUMENT_UPLOAD_STATE.SELECTED,
- progress: 0,
- docType: docType,
- attempts: 0,
- }));
- const updatedDocList = [...newlyAddedDocuments, ...documents];
- arfController.field.onChange(updatedDocList);
- setDocuments(updatedDocList);
- };
-
- const onRemove = (index: number, _docType: DOCUMENT_TYPE): void => {
- const updatedDocList: UploadDocument[] = [
- ...documents.slice(0, index),
- ...documents.slice(index + 1),
- ];
-
- if (arfInputRef.current) {
- arfInputRef.current.files = toFileList(updatedDocList);
- arfController.field.onChange(updatedDocList);
- }
-
- setDocuments(updatedDocList);
- };
-
- return (
- <>
-
-
- >
- );
-};
-
-export default SelectStage;
diff --git a/app/src/components/blocks/_arf/uploadConfirmationFailed/uploadConfirmationFailed.test.tsx b/app/src/components/blocks/_arf/uploadConfirmationFailed/uploadConfirmationFailed.test.tsx
deleted file mode 100644
index 38c40cfe8..000000000
--- a/app/src/components/blocks/_arf/uploadConfirmationFailed/uploadConfirmationFailed.test.tsx
+++ /dev/null
@@ -1,21 +0,0 @@
-import UploadConfirmationFailed from './uploadConfirmationFailed';
-import { render, screen } from '@testing-library/react';
-import { runAxeTest } from '../../../../helpers/test/axeTestHelper';
-import { describe, expect, it } from 'vitest';
-
-describe('UploadConfirmationFailed', () => {
- it('renders the page', () => {
- render( );
-
- expect(
- screen.getByRole('heading', { name: "We couldn't confirm the upload" }),
- ).toBeInTheDocument();
- });
-
- it('pass accessibility checks at page entry point', async () => {
- render( );
-
- const results = await runAxeTest(document.body);
- expect(results).toHaveNoViolations();
- });
-});
diff --git a/app/src/components/blocks/_arf/uploadConfirmationFailed/uploadConfirmationFailed.tsx b/app/src/components/blocks/_arf/uploadConfirmationFailed/uploadConfirmationFailed.tsx
deleted file mode 100644
index 8143bcea4..000000000
--- a/app/src/components/blocks/_arf/uploadConfirmationFailed/uploadConfirmationFailed.tsx
+++ /dev/null
@@ -1,23 +0,0 @@
-import React from 'react';
-import useTitle from '../../../../helpers/hooks/useTitle';
-
-const UploadConfirmationFailed = (): React.JSX.Element => {
- const pageHeader = "We couldn't confirm the upload";
- useTitle({ pageTitle: pageHeader });
-
- return (
- <>
- {pageHeader}
-
- The electronic health record was not uploaded for this patient. Please try uploading
- the record again in a few minutes.
-
-
- Make sure to safely store the electronic health record until it's completely
- uploaded to this storage.
-
- >
- );
-};
-
-export default UploadConfirmationFailed;
diff --git a/app/src/components/blocks/_arf/uploadFailedStage/uploadFailedStage.test.tsx b/app/src/components/blocks/_arf/uploadFailedStage/uploadFailedStage.test.tsx
deleted file mode 100644
index b25bf0a7e..000000000
--- a/app/src/components/blocks/_arf/uploadFailedStage/uploadFailedStage.test.tsx
+++ /dev/null
@@ -1,21 +0,0 @@
-import { render, screen } from '@testing-library/react';
-import { runAxeTest } from '../../../../helpers/test/axeTestHelper';
-import UploadFailedStage from './uploadFailedStage';
-import { describe, expect, it } from 'vitest';
-
-describe('UploadFailedStage', () => {
- it('renders the page', () => {
- render( );
-
- expect(
- screen.getByRole('heading', { name: 'All files failed to upload' }),
- ).toBeInTheDocument();
- });
-
- it('pass accessibility checks at page entry point', async () => {
- render( );
-
- const results = await runAxeTest(document.body);
- expect(results).toHaveNoViolations();
- });
-});
diff --git a/app/src/components/blocks/_arf/uploadFailedStage/uploadFailedStage.tsx b/app/src/components/blocks/_arf/uploadFailedStage/uploadFailedStage.tsx
deleted file mode 100644
index 1025eeceb..000000000
--- a/app/src/components/blocks/_arf/uploadFailedStage/uploadFailedStage.tsx
+++ /dev/null
@@ -1,23 +0,0 @@
-import React from 'react';
-import useTitle from '../../../../helpers/hooks/useTitle';
-
-const UploadFailedStage = (): React.JSX.Element => {
- const pageHeader = 'All files failed to upload';
- useTitle({ pageTitle: pageHeader });
-
- return (
- <>
- {pageHeader}
-
- The electronic health record was not uploaded for this patient. You will need to
- check your files and try again.
-
-
- Make sure to safely store the electronic health record until it's completely
- uploaded to this storage.
-
- >
- );
-};
-
-export default UploadFailedStage;
diff --git a/app/src/components/blocks/_arf/uploadSummary/UploadSummary.test.tsx b/app/src/components/blocks/_arf/uploadSummary/UploadSummary.test.tsx
deleted file mode 100644
index 937d4d722..000000000
--- a/app/src/components/blocks/_arf/uploadSummary/UploadSummary.test.tsx
+++ /dev/null
@@ -1,242 +0,0 @@
-import { render, screen, within } from '@testing-library/react';
-import userEvent from '@testing-library/user-event';
-import UploadSummary, { Props } from './UploadSummary';
-import {
- DOCUMENT_UPLOAD_STATE as documentUploadStates,
- UploadDocument,
-} from '../../../../types/pages/UploadDocumentsPage/types';
-import { formatFileSize as formatSize } from '../../../../helpers/utils/formatFileSize';
-import { getFormattedDate } from '../../../../helpers/utils/formatDate';
-import {
- buildDocument,
- buildPatientDetails,
- buildTextFile,
-} from '../../../../helpers/test/testBuilders';
-import usePatient from '../../../../helpers/hooks/usePatient';
-import { runAxeTest } from '../../../../helpers/test/axeTestHelper';
-import { afterEach, beforeEach, describe, expect, it, vi, Mock } from 'vitest';
-
-vi.mock('../../../../helpers/hooks/usePatient');
-const mockedUsePatient = usePatient as Mock;
-const mockPatient = buildPatientDetails();
-
-describe('UploadSummary', () => {
- beforeEach(() => {
- import.meta.env.VITE_ENVIRONMENT = 'vitest';
- mockedUsePatient.mockReturnValue(mockPatient);
- });
- afterEach(() => {
- vi.clearAllMocks();
- });
-
- it('renders the page', () => {
- renderUploadSummary({ documents: [] });
-
- expect(screen.getByRole('heading', { name: 'Upload Summary' })).toBeInTheDocument();
- expect(
- screen.getByRole('heading', {
- name: /All documents have been successfully uploaded on/,
- }),
- ).toBeInTheDocument();
- expect(screen.getByText('NHS number')).toBeInTheDocument();
- expect(screen.getByText('Surname')).toBeInTheDocument();
- expect(screen.getByText('First name')).toBeInTheDocument();
- expect(screen.getByText('Date of birth')).toBeInTheDocument();
- expect(screen.getByText('Postcode')).toBeInTheDocument();
- expect(screen.getByText('Before you close this page')).toBeInTheDocument();
- expect(
- screen.queryByText('Some of your documents failed to upload'),
- ).not.toBeInTheDocument();
- expect(screen.queryByText('View successfully uploaded documents')).not.toBeInTheDocument();
- });
-
- it('displays successfully uploaded docs and callout message', () => {
- const files = [buildTextFile('one', 100), buildTextFile('two', 101)];
- const documents = files.map((file) => buildDocument(file, documentUploadStates.SUCCEEDED));
-
- renderUploadSummary({ documents });
-
- expect(
- screen.getByText(/All documents have been successfully uploaded on/),
- ).toBeInTheDocument();
- expect(screen.getByText('View successfully uploaded documents')).toBeInTheDocument();
- const uploadedDocsTable = screen.getByRole('table', {
- name: 'Successfully uploaded documents',
- });
- files.forEach(({ name, size }) => {
- expect(within(uploadedDocsTable).getByText(name)).toBeInTheDocument();
- expect(within(uploadedDocsTable).getByText(formatSize(size))).toBeInTheDocument();
- });
- expect(screen.getByText('Before you close this page')).toBeInTheDocument();
- });
-
- it('displays a collapsible list of successfully uploaded docs', async () => {
- const files = [buildTextFile('test1'), buildTextFile('test2')];
- const documents = files.map((file) => buildDocument(file, documentUploadStates.SUCCEEDED));
-
- renderUploadSummary({ documents });
-
- expect(
- screen.queryByRole('table', { name: 'Successfully uploaded documents' }),
- ).not.toBeVisible();
-
- await userEvent.click(screen.getByText('View successfully uploaded documents'));
-
- expect(
- screen.getByRole('table', { name: 'Successfully uploaded documents' }),
- ).toBeVisible();
-
- await userEvent.click(screen.getByText('View successfully uploaded documents'));
-
- expect(
- screen.queryByRole('table', { name: 'Successfully uploaded documents' }),
- ).not.toBeVisible();
- });
-
- it('does not include docs that failed to upload in the successfully uploaded docs list', () => {
- const uploadedFileName = 'one';
- const failedToUploadFileName = 'two';
- const documents = [
- buildDocument(buildTextFile(uploadedFileName, 100), documentUploadStates.SUCCEEDED),
- buildDocument(buildTextFile(failedToUploadFileName, 101), documentUploadStates.FAILED),
- ];
-
- renderUploadSummary({ documents });
-
- expect(
- screen.queryByRole('heading', {
- name: /All documents have been successfully uploaded on/,
- }),
- ).not.toBeInTheDocument();
- const uploadedDocsTable = screen.getByRole('table', {
- name: 'Successfully uploaded documents',
- });
- expect(within(uploadedDocsTable).getByText(`${uploadedFileName}.txt`)).toBeInTheDocument();
- expect(
- within(uploadedDocsTable).queryByText(`${failedToUploadFileName}.txt`),
- ).not.toBeInTheDocument();
- });
-
- it('does not display the successfully uploads docs list when all of the docs failed to upload', () => {
- const documents = [buildDocument(buildTextFile('test1'), documentUploadStates.FAILED)];
-
- renderUploadSummary({ documents });
-
- expect(
- screen.queryByRole('heading', {
- name: /All documents have been successfully uploaded on/,
- }),
- ).not.toBeInTheDocument();
- expect(screen.queryByText('View succe0ssfully uploaded documents')).not.toBeInTheDocument();
- });
-
- it('displays message and does not display an alert if all the docs were uploaded successfully', () => {
- const files = [buildTextFile('one', 100)];
- const documents = [buildDocument(files[0], documentUploadStates.SUCCEEDED)];
-
- renderUploadSummary({ documents });
-
- expect(
- screen.getByRole('heading', {
- name:
- 'All documents have been successfully uploaded on ' +
- getFormattedDate(new Date()),
- }),
- ).toBeInTheDocument();
- expect(
- screen.queryByRole('alert', {
- name: 'Some of your documents failed to upload',
- }),
- ).not.toBeInTheDocument();
- expect(
- screen.queryByText(`${documents.length} of ${files.length} files failed to upload`),
- ).not.toBeInTheDocument();
- });
-
- it('displays an alert if some of the docs failed to upload', () => {
- const documents = [
- buildDocument(buildTextFile('test1'), documentUploadStates.SUCCEEDED),
- buildDocument(buildTextFile('test2'), documentUploadStates.FAILED),
- ];
-
- renderUploadSummary({ documents });
-
- expect(screen.getByRole('alert', { name: 'There is a problem' })).toBeInTheDocument();
- expect(
- screen.getByText(
- 'Some documents failed to upload. You can try to upload the documents again if you wish, or they must be printed and sent via PCSE',
- ),
- ).toBeInTheDocument();
- expect(screen.getAllByText('Documents that have failed to upload')).toHaveLength(2);
- expect(
- screen.getByRole('link', { name: 'Documents that have failed to upload' }),
- ).toHaveAttribute('href', '#failed-uploads');
- });
-
- it('displays each doc that failed to upload in a table', () => {
- const files = [buildTextFile('one', 100), buildTextFile('two', 101)];
- const documents = files.map((file) => buildDocument(file, documentUploadStates.FAILED));
-
- renderUploadSummary({ documents });
-
- const failedToUploadDocsTable = screen.getByRole('table', {
- name: /failed to upload/,
- });
- files.forEach(({ name, size }) => {
- expect(within(failedToUploadDocsTable).getByText(name)).toBeInTheDocument();
- expect(within(failedToUploadDocsTable).getByText(formatSize(size))).toBeInTheDocument();
- });
- });
-
- it('displays number of failed uploads and total uploads when there is at least 1 failed upload', () => {
- const files = [buildTextFile('one', 100), buildTextFile('two', 101)];
- const documents: Array = files.map((file) =>
- buildDocument(file, documentUploadStates.FAILED),
- );
-
- renderUploadSummary({ documents });
-
- expect(
- screen.getByText(`${documents.length} of ${files.length} files failed to upload`),
- ).toBeInTheDocument();
- });
-
- describe('Accessibility', () => {
- it('pass accessibility checks when upload result are all successful', async () => {
- const files = [buildTextFile('one', 100), buildTextFile('two', 101)];
- const documents = files.map((file) =>
- buildDocument(file, documentUploadStates.SUCCEEDED),
- );
- renderUploadSummary({ documents });
-
- await screen.findByText(/All documents have been successfully uploaded/);
-
- const results = await runAxeTest(document.body);
- expect(results).toHaveNoViolations();
- });
-
- it('pass accessibility checks when result contain both successful and unsuccessful uploads', async () => {
- const files = [buildTextFile('one', 100), buildTextFile('two', 101)];
- const documents = [
- buildDocument(files[0], documentUploadStates.FAILED),
- buildDocument(files[1], documentUploadStates.SUCCEEDED),
- ];
- renderUploadSummary({ documents });
-
- await screen.findByText(/files failed to upload/);
- await screen.findByText('View successfully uploaded documents');
-
- const results = await runAxeTest(document.body);
- expect(results).toHaveNoViolations();
- });
- });
-});
-
-const renderUploadSummary = (propsOverride: Partial) => {
- const props: Props = {
- documents: [],
- ...propsOverride,
- };
-
- render( );
-};
diff --git a/app/src/components/blocks/_arf/uploadSummary/UploadSummary.tsx b/app/src/components/blocks/_arf/uploadSummary/UploadSummary.tsx
deleted file mode 100644
index 4598b2fc3..000000000
--- a/app/src/components/blocks/_arf/uploadSummary/UploadSummary.tsx
+++ /dev/null
@@ -1,140 +0,0 @@
-import { Details, Table, WarningCallout } from 'nhsuk-react-components';
-import React from 'react';
-import {
- DOCUMENT_UPLOAD_STATE,
- UploadDocument,
-} from '../../../../types/pages/UploadDocumentsPage/types';
-import formatFileSize from '../../../../helpers/utils/formatFileSize';
-import { getFormattedDate } from '../../../../helpers/utils/formatDate';
-import ErrorBox from '../../../layout/errorBox/ErrorBox';
-import useTitle from '../../../../helpers/hooks/useTitle';
-import PatientSummary from '../../../generic/patientSummary/PatientSummary';
-
-export interface Props {
- documents: Array;
-}
-const UploadSummary = ({ documents }: Props): React.JSX.Element => {
- const successfulUploads = documents.filter((document) => {
- return document.state === DOCUMENT_UPLOAD_STATE.SUCCEEDED;
- });
-
- const failedUploads = documents.filter((document) => {
- return [DOCUMENT_UPLOAD_STATE.FAILED, DOCUMENT_UPLOAD_STATE.INFECTED].includes(
- document.state,
- );
- });
-
- const tableCaption = (
- <>
-
- {failedUploads.length} of {documents.length} files failed to upload
-
-
- Error: Documents that have failed
- to upload
-
- >
- );
- const pageHeader = 'Upload Summary';
- useTitle({ pageTitle: pageHeader });
-
- return (
-
- {failedUploads.length > 0 && (
-
- )}
-
- {failedUploads.length > 0 && (
-
-
-
- {failedUploads.map((document) => {
- return (
-
- {document.file.name}
-
- {formatFileSize(document.file.size)}
-
-
- );
- })}
-
-
-
- )}
- {failedUploads.length === 0 && (
-
- All documents have been successfully uploaded on {getFormattedDate(new Date())}
-
- )}
- {successfulUploads.length > 0 && (
-
-
- View successfully uploaded documents
-
-
-
-
-
- File Name
- File Size
-
-
-
- {successfulUploads.map((document) => {
- return (
-
- {document.file.name}
-
- {formatFileSize(document.file.size)}
-
-
- );
- })}
-
-
-
-
- )}
-
-
-
- Before you close this page
-
-
- You could take a screenshot of this summary page and attach it to the
- patient's record
-
-
- When you have finished uploading, and the patient is deducted from your
- practice, delete all temporary files created for upload on your computer
-
-
- If you have accidentally uploaded incorrect documents, please contact
- Primary Care Support England (PSCE)
-
-
-
-
- );
-};
-
-export default UploadSummary;
diff --git a/app/src/components/blocks/_arf/uploadingStage/UploadingStage.test.tsx b/app/src/components/blocks/_arf/uploadingStage/UploadingStage.test.tsx
deleted file mode 100644
index 7d169edf7..000000000
--- a/app/src/components/blocks/_arf/uploadingStage/UploadingStage.test.tsx
+++ /dev/null
@@ -1,157 +0,0 @@
-import { render, screen } from '@testing-library/react';
-import {
- DOCUMENT_TYPE,
- DOCUMENT_UPLOAD_STATE,
- DOCUMENT_UPLOAD_STATE as documentUploadStates,
- UploadDocument,
-} from '../../../../types/pages/UploadDocumentsPage/types';
-import { buildTextFile } from '../../../../helpers/test/testBuilders';
-import UploadingStage from './UploadingStage';
-import { runAxeTest } from '../../../../helpers/test/axeTestHelper';
-import { describe, expect, it } from 'vitest';
-
-describe(' ', () => {
- describe('with NHS number', () => {
- const triggerUploadStateChange = (
- document: UploadDocument,
- state: DOCUMENT_UPLOAD_STATE,
- progress?: number,
- ) => {
- document.state = state;
- document.progress = progress;
- };
-
- it('uploads documents and displays the progress', async () => {
- const documentOne = {
- file: buildTextFile('one', 100),
- state: documentUploadStates.SELECTED,
- id: '1',
- progress: 0,
- docType: DOCUMENT_TYPE.ARF,
- attempts: 0,
- };
- const documentTwo = {
- file: buildTextFile('two', 200),
- state: documentUploadStates.SELECTED,
- id: '2',
- progress: 0,
- docType: DOCUMENT_TYPE.ARF,
- attempts: 0,
- };
- const documentThree = {
- file: buildTextFile('three', 100),
- state: documentUploadStates.SELECTED,
- id: '3',
- progress: 0,
- docType: DOCUMENT_TYPE.ARF,
- attempts: 0,
- };
- render( );
-
- triggerUploadStateChange(documentOne, documentUploadStates.UPLOADING, 0);
-
- expect(screen.queryByTestId('upload-document-form')).not.toBeInTheDocument();
- expect(
- screen.getByText(
- 'Do not close or navigate away from this browser until upload is complete.',
- ),
- ).toBeInTheDocument();
- });
-
- it('progress bar reflect the upload progress', async () => {
- const documentOne = {
- file: buildTextFile('one', 100),
- state: documentUploadStates.SELECTED,
- id: '1',
- progress: 0,
- docType: DOCUMENT_TYPE.ARF,
- attempts: 0,
- };
- const documentTwo = {
- file: buildTextFile('two', 200),
- state: documentUploadStates.SELECTED,
- id: '2',
- progress: 0,
- docType: DOCUMENT_TYPE.ARF,
- attempts: 0,
- };
- const documentThree = {
- file: buildTextFile('three', 100),
- state: documentUploadStates.SELECTED,
- id: '3',
- progress: 0,
- docType: DOCUMENT_TYPE.ARF,
- attempts: 0,
- };
-
- const { rerender } = render(
- ,
- );
- const getProgressBarValue = (document: UploadDocument) => {
- const progressBar: HTMLProgressElement = screen.getByRole('progressbar', {
- name: `Uploading ${document.file.name}`,
- });
- return progressBar.value;
- };
- const getProgressText = (document: UploadDocument) => {
- return screen.getByRole('status', {
- name: `${document.file.name} upload status`,
- }).textContent;
- };
-
- triggerUploadStateChange(documentOne, documentUploadStates.UPLOADING, 10);
- rerender( );
- expect(getProgressBarValue(documentOne)).toEqual(10);
- expect(getProgressText(documentOne)).toContain('10% uploaded...');
-
- triggerUploadStateChange(documentOne, documentUploadStates.UPLOADING, 70);
- rerender( );
- expect(getProgressBarValue(documentOne)).toEqual(70);
- expect(getProgressText(documentOne)).toContain('70% uploaded...');
-
- triggerUploadStateChange(documentTwo, documentUploadStates.UPLOADING, 20);
- rerender( );
- expect(getProgressBarValue(documentTwo)).toEqual(20);
- expect(getProgressText(documentTwo)).toContain('20% uploaded...');
-
- triggerUploadStateChange(documentTwo, documentUploadStates.SUCCEEDED, 100);
- rerender( );
- expect(getProgressBarValue(documentTwo)).toEqual(100);
- expect(getProgressText(documentTwo)).toContain('Upload succeeded');
-
- triggerUploadStateChange(documentOne, documentUploadStates.FAILED, 0);
- rerender( );
- expect(getProgressBarValue(documentOne)).toEqual(0);
- expect(getProgressText(documentOne)).toContain('Upload failed');
-
- triggerUploadStateChange(documentTwo, documentUploadStates.SCANNING);
- rerender( );
- expect(getProgressText(documentTwo)).toContain('Virus scan in progress');
-
- triggerUploadStateChange(documentTwo, documentUploadStates.CLEAN);
- rerender( );
- expect(getProgressText(documentTwo)).toContain('Virus scan complete');
-
- triggerUploadStateChange(documentTwo, documentUploadStates.SUCCEEDED);
- rerender( );
- expect(getProgressText(documentTwo)).toContain('Upload succeeded');
- });
- });
-
- it('pass accessibility check', async () => {
- const documentOne = {
- file: buildTextFile('one', 100),
- state: documentUploadStates.UPLOADING,
- id: '1',
- progress: 10,
- docType: DOCUMENT_TYPE.ARF,
- attempts: 0,
- };
- render( );
-
- await screen.findByText(documentOne.file.name);
-
- const results = await runAxeTest(document.body);
- expect(results).toHaveNoViolations();
- });
-});
diff --git a/app/src/components/blocks/_arf/uploadingStage/UploadingStage.tsx b/app/src/components/blocks/_arf/uploadingStage/UploadingStage.tsx
deleted file mode 100644
index 22244de3e..000000000
--- a/app/src/components/blocks/_arf/uploadingStage/UploadingStage.tsx
+++ /dev/null
@@ -1,68 +0,0 @@
-import React from 'react';
-import {
- DOCUMENT_UPLOAD_STATE,
- UploadDocument,
-} from '../../../../types/pages/UploadDocumentsPage/types';
-import { Table, WarningCallout } from 'nhsuk-react-components';
-import formatFileSize from '../../../../helpers/utils/formatFileSize';
-import useTitle from '../../../../helpers/hooks/useTitle';
-import { getUploadMessage } from '../../../../helpers/utils/uploadDocumentHelpers';
-
-interface Props {
- documents: Array;
-}
-
-const UploadingStage = ({ documents }: Props): React.JSX.Element => {
- const pageHeader = 'Your documents are uploading';
- useTitle({ pageTitle: 'Uploading documents' });
-
- return (
- <>
- {pageHeader}
-
- Stay on this page
- Do not close or navigate away from this browser until upload is complete.
-
-
-
-
- File Name
- File Size
- File Upload Progress
-
-
-
- {documents.map((document) => {
- const isScanning = document.state === DOCUMENT_UPLOAD_STATE.SCANNING;
- return (
-
- {document.file.name}
-
- {formatFileSize(document.file.size)}
-
-
-
-
- {getUploadMessage(document)}
-
-
-
- );
- })}
-
-
- >
- );
-};
-
-export default UploadingStage;
diff --git a/app/src/components/blocks/_delete/deleteResultStage/DeleteResultStage.test.tsx b/app/src/components/blocks/_delete/deleteResultStage/DeleteResultStage.test.tsx
index 27109902b..b53f0d95f 100644
--- a/app/src/components/blocks/_delete/deleteResultStage/DeleteResultStage.test.tsx
+++ b/app/src/components/blocks/_delete/deleteResultStage/DeleteResultStage.test.tsx
@@ -10,27 +10,34 @@ import usePatient from '../../../../helpers/hooks/usePatient';
import { DOWNLOAD_STAGE } from '../../../../types/generic/downloadStage';
import { runAxeTest } from '../../../../helpers/test/axeTestHelper';
import { afterEach, beforeEach, describe, expect, it, vi, Mock } from 'vitest';
+import useConfig from '../../../../helpers/hooks/useConfig';
const mockNavigate = vi.fn();
vi.mock('../../../../helpers/hooks/useRole');
vi.mock('../../../../helpers/hooks/usePatient');
+vi.mock('../../../../helpers/hooks/useConfig');
vi.mock('react-router-dom', () => ({
- Link: (props: LinkProps) => ,
- useNavigate: () => mockNavigate,
+ Link: (props: LinkProps): React.JSX.Element => ,
+ useNavigate: (): Mock => mockNavigate,
}));
const mockedUseRole = useRole as Mock;
const mockedUsePatient = usePatient as Mock;
+const mockedUseConfig = useConfig as Mock;
const mockPatientDetails = buildPatientDetails();
-const mockLgSearchResult = buildLgSearchResult();
const mockSetDownloadStage = vi.fn();
describe('DeleteResultStage', () => {
beforeEach(() => {
import.meta.env.VITE_ENVIRONMENT = 'vitest';
mockedUsePatient.mockReturnValue(mockPatientDetails);
+ mockedUseConfig.mockReturnValue({
+ featureFlags: {
+ uploadDocumentIteration3Enabled: true,
+ },
+ });
});
afterEach(() => {
vi.clearAllMocks();
@@ -41,19 +48,13 @@ describe('DeleteResultStage', () => {
"renders the page with Lloyd George patient details when user role is '%s'",
async (role) => {
const patientName = `${mockPatientDetails.givenName} ${mockPatientDetails.familyName}`;
- const numberOfFiles = mockLgSearchResult.numberOfFiles;
mockedUseRole.mockReturnValue(role);
- render(
- ,
- );
+ render( );
await waitFor(async () => {
expect(
- screen.getByText('You have permanently removed the record of:'),
+ screen.getByText(`You have permanently removed the records of:`),
).toBeInTheDocument();
});
@@ -71,19 +72,13 @@ describe('DeleteResultStage', () => {
);
it('renders the page with ARF patient details, when user role is PCSE', async () => {
const patientName = `${mockPatientDetails.givenName} ${mockPatientDetails.familyName}`;
- const numberOfFiles = 1;
mockedUseRole.mockReturnValue(REPOSITORY_ROLE.PCSE);
- render(
- ,
- );
+ render( );
await waitFor(async () => {
expect(
- screen.getByText('You have permanently removed the record of:'),
+ screen.getByText('You have permanently removed the records of:'),
).toBeInTheDocument();
});
@@ -97,19 +92,13 @@ describe('DeleteResultStage', () => {
it.each([REPOSITORY_ROLE.GP_ADMIN, REPOSITORY_ROLE.GP_CLINICAL])(
"renders the return to Lloyd George Record button, when user role is '%s'",
async (role) => {
- const numberOfFiles = mockLgSearchResult.numberOfFiles;
mockedUseRole.mockReturnValue(role);
- render(
- ,
- );
+ render( );
await waitFor(async () => {
expect(
- screen.getByText('You have permanently removed the record of:'),
+ screen.getByText('You have permanently removed the records of:'),
).toBeInTheDocument();
});
@@ -122,19 +111,13 @@ describe('DeleteResultStage', () => {
);
it('does not render the return to Lloyd George Record button, when user role is PCSE', async () => {
- const numberOfFiles = mockLgSearchResult.numberOfFiles;
mockedUseRole.mockReturnValue(REPOSITORY_ROLE.PCSE);
- render(
- ,
- );
+ render( );
await waitFor(async () => {
expect(
- screen.getByText('You have permanently removed the record of:'),
+ screen.getByText('You have permanently removed the records of:'),
).toBeInTheDocument();
});
@@ -146,19 +129,13 @@ describe('DeleteResultStage', () => {
});
it('renders the Start Again button, when user role is PCSE', async () => {
- const numberOfFiles = 7;
mockedUseRole.mockReturnValue(REPOSITORY_ROLE.PCSE);
- render(
- ,
- );
+ render( );
await waitFor(async () => {
expect(
- screen.getByText('You have permanently removed the record of:'),
+ screen.getByText('You have permanently removed the records of:'),
).toBeInTheDocument();
});
@@ -172,19 +149,13 @@ describe('DeleteResultStage', () => {
it.each([REPOSITORY_ROLE.GP_ADMIN, REPOSITORY_ROLE.GP_CLINICAL])(
"does not render the Start Again button, when user role is '%s'",
async (role) => {
- const numberOfFiles = 7;
mockedUseRole.mockReturnValue(role);
- render(
- ,
- );
+ render( );
await waitFor(async () => {
expect(
- screen.getByText('You have permanently removed the record of:'),
+ screen.getByText('You have permanently removed the records of:'),
).toBeInTheDocument();
});
@@ -201,7 +172,7 @@ describe('DeleteResultStage', () => {
const roles = [REPOSITORY_ROLE.GP_ADMIN, REPOSITORY_ROLE.PCSE];
it.each(roles)('pass accessibility checks for role %s', async (role) => {
mockedUseRole.mockReturnValue(role);
- render( );
+ render( );
const results = await runAxeTest(document.body);
expect(results).toHaveNoViolations();
@@ -212,19 +183,13 @@ describe('DeleteResultStage', () => {
it.each([REPOSITORY_ROLE.GP_ADMIN, REPOSITORY_ROLE.GP_CLINICAL])(
"navigates to the Lloyd George view page when return button is clicked, when user role is '%s'",
async (role) => {
- const numberOfFiles = mockLgSearchResult.numberOfFiles;
mockedUseRole.mockReturnValue(role);
- render(
- ,
- );
+ render( );
await waitFor(async () => {
expect(
- screen.getByText('You have permanently removed the record of:'),
+ screen.getByText('You have permanently removed the records of:'),
).toBeInTheDocument();
});
@@ -235,26 +200,20 @@ describe('DeleteResultStage', () => {
);
await waitFor(() => {
- expect(mockNavigate).toHaveBeenCalledWith(routes.LLOYD_GEORGE);
+ expect(mockNavigate).toHaveBeenCalledWith(routes.PATIENT_DOCUMENTS);
});
expect(mockSetDownloadStage).toHaveBeenCalledWith(DOWNLOAD_STAGE.REFRESH);
},
);
it('navigates to Home page when link is clicked when user role is PCSE', async () => {
- const numberOfFiles = 7;
mockedUseRole.mockReturnValue(REPOSITORY_ROLE.PCSE);
- render(
- ,
- );
+ render( );
await waitFor(async () => {
expect(
- screen.getByText('You have permanently removed the record of:'),
+ screen.getByText('You have permanently removed the records of:'),
).toBeInTheDocument();
});
diff --git a/app/src/components/blocks/_delete/deleteResultStage/DeleteResultStage.tsx b/app/src/components/blocks/_delete/deleteResultStage/DeleteResultStage.tsx
index 5ee391c6f..c21de3137 100644
--- a/app/src/components/blocks/_delete/deleteResultStage/DeleteResultStage.tsx
+++ b/app/src/components/blocks/_delete/deleteResultStage/DeleteResultStage.tsx
@@ -7,26 +7,35 @@ import { REPOSITORY_ROLE } from '../../../../types/generic/authRole';
import ReducedPatientInfo from '../../../generic/reducedPatientInfo/ReducedPatientInfo';
import useTitle from '../../../../helpers/hooks/useTitle';
import { DOWNLOAD_STAGE } from '../../../../types/generic/downloadStage';
+import useConfig from '../../../../helpers/hooks/useConfig';
+import { DOCUMENT_TYPE, getDocumentTypeLabel } from '../../../../helpers/utils/documentType';
export type Props = {
- numberOfFiles: number;
+ docType?: DOCUMENT_TYPE;
setDownloadStage?: Dispatch>;
};
-const DeleteResultStage = ({ numberOfFiles, setDownloadStage }: Props): React.JSX.Element => {
+const DeleteResultStage = ({ docType, setDownloadStage }: Props): React.JSX.Element => {
const navigate = useNavigate();
const role = useRole();
+ const config = useConfig();
const handleClick = (e: MouseEvent): void => {
e.preventDefault();
if (setDownloadStage) {
setDownloadStage(DOWNLOAD_STAGE.REFRESH);
}
- navigate(routes.LLOYD_GEORGE);
+ navigate(
+ config.featureFlags.uploadDocumentIteration3Enabled
+ ? routes.PATIENT_DOCUMENTS
+ : routes.LLOYD_GEORGE,
+ );
};
+ const recordLabel = docType ? getDocumentTypeLabel(docType) : '';
+
const isGP = role === REPOSITORY_ROLE.GP_ADMIN || role === REPOSITORY_ROLE.GP_CLINICAL;
- const pageHeader = 'You have permanently removed the record of:';
+ const pageHeader = `You have permanently removed the ${recordLabel ? recordLabel : 'records'} of:`;
useTitle({ pageTitle: pageHeader });
return (
diff --git a/app/src/components/blocks/_delete/deleteSubmitStage/DeleteSubmitStage.test.tsx b/app/src/components/blocks/_delete/deleteSubmitStage/DeleteSubmitStage.test.tsx
index cd9ad5679..aa1c28778 100644
--- a/app/src/components/blocks/_delete/deleteSubmitStage/DeleteSubmitStage.test.tsx
+++ b/app/src/components/blocks/_delete/deleteSubmitStage/DeleteSubmitStage.test.tsx
@@ -3,7 +3,6 @@ import { buildLgSearchResult, buildPatientDetails } from '../../../../helpers/te
import DeleteSubmitStage, { Props } from './DeleteSubmitStage';
import { getFormattedDate } from '../../../../helpers/utils/formatDate';
import userEvent from '@testing-library/user-event';
-import { DOCUMENT_TYPE } from '../../../../types/pages/UploadDocumentsPage/types';
import axios from 'axios';
import useRole from '../../../../helpers/hooks/useRole';
import { REPOSITORY_ROLE, authorisedRoles } from '../../../../types/generic/authRole';
@@ -15,6 +14,8 @@ import * as ReactRouter from 'react-router-dom';
import waitForSeconds from '../../../../helpers/utils/waitForSeconds';
import { afterEach, beforeEach, describe, expect, it, vi, Mock, Mocked } from 'vitest';
import { formatNhsNumber } from '../../../../helpers/utils/formatNhsNumber';
+import { DOCUMENT_TYPE } from '../../../../helpers/utils/documentType';
+import useConfig from '../../../../helpers/hooks/useConfig';
vi.mock('../../../../helpers/hooks/useConfig');
vi.mock('../../../../helpers/hooks/useBaseAPIHeaders');
@@ -28,10 +29,10 @@ vi.mock('react-router-dom', async () => {
const actual = await vi.importActual('react-router-dom');
return {
...actual,
- useNavigate: () => mockedUseNavigate,
+ useNavigate: (): Mock => mockedUseNavigate,
};
});
-Date.now = () => new Date('2020-01-01T00:00:00.000Z').getTime();
+Date.now = (): number => new Date('2020-01-01T00:00:00.000Z').getTime();
let history: MemoryHistory = createMemoryHistory({
initialEntries: ['/'],
@@ -43,7 +44,7 @@ const mockedAxios = axios as Mocked;
const mockedUsePatient = usePatient as Mock;
const mockResetDocState = vi.fn();
const mockPatientDetails = buildPatientDetails();
-const mockLgSearchResult = buildLgSearchResult();
+const mockuseConfig = useConfig as Mock;
const mockSetStage = vi.fn();
@@ -55,6 +56,11 @@ describe('DeleteSubmitStage', () => {
});
import.meta.env.VITE_ENVIRONMENT = 'vitest';
mockedUsePatient.mockReturnValue(mockPatientDetails);
+ mockuseConfig.mockReturnValue({
+ featureFlags: {
+ uploadDocumentIteration3Enabled: false,
+ },
+ });
});
afterEach(() => {
@@ -124,7 +130,7 @@ describe('DeleteSubmitStage', () => {
await userEvent.click(screen.getByRole('button', { name: 'Continue' }));
await waitFor(() => {
- expect(mockedUseNavigate).toHaveBeenCalledWith(routes.ARF_OVERVIEW);
+ expect(mockedUseNavigate).toHaveBeenCalledWith(routes.PATIENT_DOCUMENTS);
});
});
@@ -156,7 +162,7 @@ describe('DeleteSubmitStage', () => {
await waitFor(() => {
expect(mockedUseNavigate).toHaveBeenCalledWith(
- routeChildren.LLOYD_GEORGE_DELETE_COMPLETE,
+ routeChildren.DOCUMENT_DELETE_COMPLETE,
);
});
});
@@ -193,7 +199,9 @@ describe('DeleteSubmitStage', () => {
await userEvent.click(screen.getByRole('button', { name: 'Continue' }));
await waitFor(() => {
- expect(mockedUseNavigate).toHaveBeenCalledWith(routeChildren.ARF_DELETE_COMPLETE);
+ expect(mockedUseNavigate).toHaveBeenCalledWith(
+ routeChildren.DOCUMENT_DELETE_COMPLETE,
+ );
});
});
@@ -331,42 +339,40 @@ describe('DeleteSubmitStage', () => {
expect(results).toHaveNoViolations();
});
});
-});
-describe('Navigation', () => {
- it('navigates to session expire page when API call returns 403', async () => {
- const errorResponse = {
- response: {
- status: 403,
- message: 'Forbidden',
- },
- };
- mockedAxios.delete.mockImplementation(() => Promise.reject(errorResponse));
- mockedUseRole.mockReturnValue(REPOSITORY_ROLE.PCSE);
+ describe('Navigation', () => {
+ it('navigates to session expire page when API call returns 403', async () => {
+ const errorResponse = {
+ response: {
+ status: 403,
+ message: 'Forbidden',
+ },
+ };
+ mockedAxios.delete.mockImplementation(() => Promise.reject(errorResponse));
+ mockedUseRole.mockReturnValue(REPOSITORY_ROLE.PCSE);
- renderComponent(DOCUMENT_TYPE.ALL, history);
+ renderComponent(DOCUMENT_TYPE.ALL, history);
- expect(screen.getByRole('radio', { name: 'Yes' })).toBeInTheDocument();
- expect(screen.getByRole('button', { name: 'Continue' })).toBeInTheDocument();
+ expect(screen.getByRole('radio', { name: 'Yes' })).toBeInTheDocument();
+ expect(screen.getByRole('button', { name: 'Continue' })).toBeInTheDocument();
- await userEvent.click(screen.getByRole('radio', { name: 'Yes' }));
- await userEvent.click(screen.getByRole('button', { name: 'Continue' }));
+ await userEvent.click(screen.getByRole('radio', { name: 'Yes' }));
+ await userEvent.click(screen.getByRole('button', { name: 'Continue' }));
- await waitFor(() => {
- expect(mockedUseNavigate).toHaveBeenCalledWith(routes.SESSION_EXPIRED);
+ await waitFor(() => {
+ expect(mockedUseNavigate).toHaveBeenCalledWith(routes.SESSION_EXPIRED);
+ });
});
});
});
-const renderComponent = (docType: DOCUMENT_TYPE, history: MemoryHistory) => {
+const renderComponent = (docType: DOCUMENT_TYPE, history: MemoryHistory): void => {
const props: Omit = {
- numberOfFiles: mockLgSearchResult.numberOfFiles,
docType,
- recordType: docType.toString(),
resetDocState: mockResetDocState,
};
- return render(
+ render(
,
,
diff --git a/app/src/components/blocks/_delete/deleteSubmitStage/DeleteSubmitStage.tsx b/app/src/components/blocks/_delete/deleteSubmitStage/DeleteSubmitStage.tsx
index 3d397d0ec..75b0b5b7d 100644
--- a/app/src/components/blocks/_delete/deleteSubmitStage/DeleteSubmitStage.tsx
+++ b/app/src/components/blocks/_delete/deleteSubmitStage/DeleteSubmitStage.tsx
@@ -1,11 +1,10 @@
-import React, { Dispatch, SetStateAction, useState } from 'react';
+import { Dispatch, SetStateAction, useState } from 'react';
import { FieldValues, useForm } from 'react-hook-form';
import { Button, Fieldset, Radios, WarningCallout } from 'nhsuk-react-components';
import deleteAllDocuments, {
DeleteResponse,
} from '../../../../helpers/requests/deleteAllDocuments';
import useBaseAPIHeaders from '../../../../helpers/hooks/useBaseAPIHeaders';
-import { DOCUMENT_TYPE } from '../../../../types/pages/UploadDocumentsPage/types';
import { DOWNLOAD_STAGE } from '../../../../types/generic/downloadStage';
import SpinnerButton from '../../../generic/spinnerButton/SpinnerButton';
import ServiceError from '../../../layout/serviceErrorBox/ServiceErrorBox';
@@ -22,16 +21,15 @@ import { isMock } from '../../../../helpers/utils/isLocal';
import useConfig from '../../../../helpers/hooks/useConfig';
import useTitle from '../../../../helpers/hooks/useTitle';
import ErrorBox from '../../../layout/errorBox/ErrorBox';
-import { getLastURLPath } from '../../../../helpers/utils/urlManipulations';
-import DeleteResultStage from '../deleteResultStage/DeleteResultStage';
import BackButton from '../../../generic/backButton/BackButton';
import PatientSummary, { PatientInfo } from '../../../generic/patientSummary/PatientSummary';
+import { DOCUMENT_TYPE, getDocumentTypeLabel } from '../../../../helpers/utils/documentType';
+import { getLastURLPath } from '../../../../helpers/utils/urlManipulations';
+import DeleteResultStage from '../deleteResultStage/DeleteResultStage';
export type Props = {
docType: DOCUMENT_TYPE;
- numberOfFiles: number;
setDownloadStage?: Dispatch>;
- recordType: string;
resetDocState: () => void;
};
@@ -42,13 +40,11 @@ enum DELETE_DOCUMENTS_OPTION {
type IndexViewProps = {
docType: DOCUMENT_TYPE;
- recordType: string;
resetDocState: () => void;
};
const DeleteSubmitStageIndexView = ({
docType,
- recordType,
resetDocState,
}: IndexViewProps): React.JSX.Element => {
const patientDetails = usePatient();
@@ -70,11 +66,7 @@ const DeleteSubmitStageIndexView = ({
const onSuccess = (): void => {
resetDocState();
setDeletionStage(SUBMISSION_STATE.SUCCEEDED);
- if (userIsGP) {
- navigate(routeChildren.LLOYD_GEORGE_DELETE_COMPLETE);
- } else {
- navigate(routeChildren.ARF_DELETE_COMPLETE);
- }
+ navigate(routeChildren.DOCUMENT_DELETE_COMPLETE);
};
try {
setDeletionStage(SUBMISSION_STATE.PENDING);
@@ -107,7 +99,7 @@ const DeleteSubmitStageIndexView = ({
if (role === REPOSITORY_ROLE.GP_ADMIN) {
navigate(routes.LLOYD_GEORGE);
} else if (role === REPOSITORY_ROLE.PCSE) {
- navigate(routes.ARF_OVERVIEW);
+ navigate(routes.PATIENT_DOCUMENTS);
}
};
@@ -124,14 +116,16 @@ const DeleteSubmitStageIndexView = ({
}
};
- const pageTitle = `You are removing the ${recordType} record of`;
+ const pageTitle = `You are removing the ${getDocumentTypeLabel(docType) ?? 'records'} of`;
useTitle({ pageTitle });
return (
<>
@@ -230,14 +224,9 @@ const DeleteSubmitStageIndexView = ({
const DeleteSubmitStage = ({
docType,
- numberOfFiles,
setDownloadStage,
- recordType,
resetDocState,
}: Props): React.JSX.Element => {
- const pageTitle = `You are removing the ${recordType} record of:`;
- useTitle({ pageTitle });
-
return (
<>
@@ -246,34 +235,30 @@ const DeleteSubmitStage = ({
element={
}
/>
}
- >
+ />
+
}
- >
+ />
>
);
};
+
export default DeleteSubmitStage;
diff --git a/app/src/components/blocks/_delete/removeRecordStage/RemoveRecordStage.test.tsx b/app/src/components/blocks/_delete/removeRecordStage/RemoveRecordStage.test.tsx
index c7ee4eeda..628d402fd 100644
--- a/app/src/components/blocks/_delete/removeRecordStage/RemoveRecordStage.test.tsx
+++ b/app/src/components/blocks/_delete/removeRecordStage/RemoveRecordStage.test.tsx
@@ -8,17 +8,20 @@ import { MemoryHistory, createMemoryHistory } from 'history';
import * as ReactRouter from 'react-router-dom';
import waitForSeconds from '../../../../helpers/utils/waitForSeconds';
import { afterEach, beforeEach, describe, expect, it, vi, Mock, Mocked } from 'vitest';
+import { DOCUMENT_TYPE, getDocumentTypeLabel } from '../../../../helpers/utils/documentType';
+import useConfig from '../../../../helpers/hooks/useConfig';
vi.mock('axios');
-Date.now = () => new Date('2020-01-01T00:00:00.000Z').getTime();
+Date.now = (): number => new Date('2020-01-01T00:00:00.000Z').getTime();
vi.mock('react-router-dom', async () => ({
...(await vi.importActual('react-router-dom')),
- useNavigate: () => mockUseNavigate,
+ useNavigate: (): Mock => mockUseNavigate,
}));
vi.mock('../../../../helpers/hooks/useBaseAPIHeaders');
vi.mock('../../../../helpers/hooks/useBaseAPIUrl');
vi.mock('../../../../helpers/hooks/usePatient');
vi.mock('../../../../helpers/hooks/useRole');
+vi.mock('../../../../helpers/hooks/useConfig');
const mockedAxios = axios as Mocked;
const mockUseNavigate = vi.fn();
@@ -26,10 +29,10 @@ const mockUsePatient = usePatient as Mock;
const mockPatientDetails = buildPatientDetails();
const mockDownloadStage = vi.fn();
const mockResetDocState = vi.fn();
+const mockUseConfig = useConfig as Mock;
const testFileName1 = 'John_1';
const testFileName2 = 'John_2';
-const numberOfFiles = 7;
const searchResults = [
buildSearchResult({ fileName: testFileName1 }),
buildSearchResult({ fileName: testFileName2 }),
@@ -48,17 +51,26 @@ describe('RemoveRecordStage', () => {
});
import.meta.env.VITE_ENVIRONMENT = 'vitest';
mockUsePatient.mockReturnValue(mockPatientDetails);
+ mockUseConfig.mockReturnValue({
+ featureFlags: {
+ uploadDocumentIteration3Enabled: true,
+ },
+ });
});
afterEach(() => {
vi.clearAllMocks();
});
describe('Render', () => {
- it('renders the component', () => {
+ it.each([
+ DOCUMENT_TYPE.LLOYD_GEORGE,
+ DOCUMENT_TYPE.EHR,
+ DOCUMENT_TYPE.EHR_ATTACHMENTS,
+ DOCUMENT_TYPE.LETTERS_AND_DOCS,
+ ])('renders the component for %s', (docType) => {
mockedAxios.get.mockImplementation(() => waitForSeconds(0));
- const recordType = 'Test Record';
- renderComponent(history, numberOfFiles, recordType);
+ renderComponent(history, docType);
expect(
- screen.getByRole('heading', { name: 'Remove this ' + recordType + ' record' }),
+ screen.getByRole('heading', { name: 'Remove ' + getDocumentTypeLabel(docType) }),
).toBeInTheDocument();
expect(
screen.getByText(
@@ -70,13 +82,13 @@ describe('RemoveRecordStage', () => {
it('show progress bar when file search pending', () => {
mockedAxios.get.mockImplementation(() => waitForSeconds(0));
- const recordType = 'Test Record';
- renderComponent(history, numberOfFiles, recordType);
+ const recordType = DOCUMENT_TYPE.LLOYD_GEORGE;
+ renderComponent(history, recordType);
expect(screen.getByRole('progressbar', { name: 'Loading...' })).toBeInTheDocument();
});
it('show service error when file search failed', async () => {
- const recordType = 'Test Record';
+ const recordType = DOCUMENT_TYPE.LLOYD_GEORGE;
const errorResponse = {
response: {
status: 400,
@@ -84,7 +96,7 @@ describe('RemoveRecordStage', () => {
},
};
mockedAxios.get.mockImplementation(() => Promise.reject(errorResponse));
- renderComponent(history, numberOfFiles, recordType);
+ renderComponent(history, recordType);
expect(screen.getByRole('progressbar', { name: 'Loading...' })).toBeInTheDocument();
await waitFor(() => {
expect(
@@ -97,9 +109,9 @@ describe('RemoveRecordStage', () => {
});
it('show results when when file search succeeded', async () => {
- const recordType = 'Test Record';
+ const recordType = DOCUMENT_TYPE.LLOYD_GEORGE;
mockedAxios.get.mockImplementation(() => Promise.resolve({ data: searchResults }));
- renderComponent(history, numberOfFiles, recordType);
+ renderComponent(history, recordType);
expect(screen.getByRole('progressbar', { name: 'Loading...' })).toBeInTheDocument();
await waitFor(() => {
expect(
@@ -114,7 +126,7 @@ describe('RemoveRecordStage', () => {
describe('Navigate', () => {
it('navigates to server error page when search 500', async () => {
- const recordType = 'Test Record';
+ const recordType = DOCUMENT_TYPE.LLOYD_GEORGE;
const errorResponse = {
response: {
status: 500,
@@ -122,7 +134,7 @@ describe('RemoveRecordStage', () => {
},
};
mockedAxios.get.mockImplementation(() => Promise.reject(errorResponse));
- renderComponent(history, numberOfFiles, recordType);
+ renderComponent(history, recordType);
expect(screen.getByRole('progressbar', { name: 'Loading...' })).toBeInTheDocument();
const mockedShortcode = '?encodedError=WyJTUF8xMDAxIiwiMTU3NzgzNjgwMCJd';
await waitFor(() => {
@@ -131,7 +143,7 @@ describe('RemoveRecordStage', () => {
});
it('navigates to session expired page when search 403', async () => {
- const recordType = 'Test Record';
+ const recordType = DOCUMENT_TYPE.LLOYD_GEORGE;
const errorResponse = {
response: {
status: 403,
@@ -139,7 +151,7 @@ describe('RemoveRecordStage', () => {
},
};
mockedAxios.get.mockImplementation(() => Promise.reject(errorResponse));
- renderComponent(history, numberOfFiles, recordType);
+ renderComponent(history, recordType);
expect(screen.getByRole('progressbar', { name: 'Loading...' })).toBeInTheDocument();
await waitFor(() => {
expect(mockUseNavigate).toBeCalledWith(routes.SESSION_EXPIRED);
@@ -148,12 +160,11 @@ describe('RemoveRecordStage', () => {
});
});
-const renderComponent = (history: MemoryHistory, numberOfFiles: number, recordType: string) => {
- return render(
+const renderComponent = (history: MemoryHistory, recordType: DOCUMENT_TYPE): void => {
+ render(
diff --git a/app/src/components/blocks/_delete/removeRecordStage/RemoveRecordStage.tsx b/app/src/components/blocks/_delete/removeRecordStage/RemoveRecordStage.tsx
index 8248610c2..8b0fb660a 100644
--- a/app/src/components/blocks/_delete/removeRecordStage/RemoveRecordStage.tsx
+++ b/app/src/components/blocks/_delete/removeRecordStage/RemoveRecordStage.tsx
@@ -18,30 +18,30 @@ import { isMock } from '../../../../helpers/utils/isLocal';
import { buildSearchResult } from '../../../../helpers/test/testBuilders';
import { getLastURLPath } from '../../../../helpers/utils/urlManipulations';
import DeleteSubmitStage from '../deleteSubmitStage/DeleteSubmitStage';
-import { DOCUMENT_TYPE } from '../../../../types/pages/UploadDocumentsPage/types';
import DeleteResultStage from '../deleteResultStage/DeleteResultStage';
import { DOWNLOAD_STAGE } from '../../../../types/generic/downloadStage';
import PatientSummary from '../../../generic/patientSummary/PatientSummary';
import BackButton from '../../../generic/backButton/BackButton';
import useRole from '../../../../helpers/hooks/useRole';
import { REPOSITORY_ROLE } from '../../../../types/generic/authRole';
+import { DOCUMENT_TYPE, getDocumentTypeLabel } from '../../../../helpers/utils/documentType';
+import useConfig from '../../../../helpers/hooks/useConfig';
export type Props = {
- numberOfFiles: number;
- recordType: string;
+ docType: DOCUMENT_TYPE;
setDownloadStage: Dispatch>;
resetDocState: () => void;
};
const RemoveRecordStage = ({
- numberOfFiles,
- recordType,
+ docType,
setDownloadStage,
resetDocState,
}: Props): React.JSX.Element => {
useTitle({ pageTitle: 'Remove record' });
const patientDetails = usePatient();
const [submissionState, setSubmissionState] = useState(SUBMISSION_STATE.PENDING);
+ const config = useConfig();
const role = useRole();
@@ -65,6 +65,7 @@ const RemoveRecordStage = ({
nhsNumber,
baseUrl,
baseHeaders,
+ docType,
});
onSuccess(results ?? []);
} catch (e) {
@@ -97,10 +98,24 @@ const RemoveRecordStage = ({
const hasDocuments = !!searchResults.length && !!patientDetails;
+ const pageHeader = (): string => {
+ if (config.featureFlags.uploadDocumentIteration3Enabled) {
+ return getDocumentTypeLabel(docType) || 'patient record';
+ }
+
+ return 'Lloyd George record';
+ };
+
+ const getDeleteConfirmationPath = (): string => {
+ return config.featureFlags.uploadDocumentIteration3Enabled
+ ? routeChildren.DOCUMENT_DELETE_CONFIRMATION
+ : routeChildren.LLOYD_GEORGE_DELETE_CONFIRMATION;
+ };
+
const PageIndexView = (): React.JSX.Element => (
<>
- Remove this {recordType} record
+ Remove {pageHeader()}
{role !== REPOSITORY_ROLE.PCSE && (
@@ -131,7 +146,7 @@ const RemoveRecordStage = ({
<>
{hasDocuments ? (
<>
-
+
Filename
@@ -172,7 +187,7 @@ const RemoveRecordStage = ({
{
- navigate(routeChildren.LLOYD_GEORGE_DELETE_CONFIRMATION);
+ navigate(getDeleteConfirmationPath());
}}
>
Remove all files
@@ -188,34 +203,13 @@ const RemoveRecordStage = ({
- }
- >
-
- }
+ path={getLastURLPath(routeChildren.LLOYD_GEORGE_DELETE_CONFIRMATION) + '/*'}
+ element={ }
>
+
}
>
diff --git a/app/src/components/blocks/_documentUpload/documentSelectFileErrorsPage/DocumentSelectFileErrorsPage.test.tsx b/app/src/components/blocks/_documentUpload/documentSelectFileErrorsPage/DocumentSelectFileErrorsPage.test.tsx
index 3f01f59b5..bcc00b3b7 100644
--- a/app/src/components/blocks/_documentUpload/documentSelectFileErrorsPage/DocumentSelectFileErrorsPage.test.tsx
+++ b/app/src/components/blocks/_documentUpload/documentSelectFileErrorsPage/DocumentSelectFileErrorsPage.test.tsx
@@ -7,13 +7,13 @@ import DocumentSelectFileErrorsPage from './DocumentSelectFileErrorsPage';
import {
UploadDocument,
DOCUMENT_UPLOAD_STATE,
- DOCUMENT_TYPE,
} from '../../../../types/pages/UploadDocumentsPage/types';
import {
fileUploadErrorMessages,
UPLOAD_FILE_ERROR_TYPE,
} from '../../../../helpers/utils/fileUploadErrorMessages';
import { routes } from '../../../../types/generic/routes';
+import { DOCUMENT_TYPE } from '../../../../helpers/utils/documentType';
const createFailedDocument = (name: string, error: UPLOAD_FILE_ERROR_TYPE): UploadDocument => ({
id: `${name}-id`,
diff --git a/app/src/components/blocks/_documentUpload/documentSelectOrderStage/DocumentSelectOrderStage.test.tsx b/app/src/components/blocks/_documentUpload/documentSelectOrderStage/DocumentSelectOrderStage.test.tsx
index 9cb99b87e..0239367db 100644
--- a/app/src/components/blocks/_documentUpload/documentSelectOrderStage/DocumentSelectOrderStage.test.tsx
+++ b/app/src/components/blocks/_documentUpload/documentSelectOrderStage/DocumentSelectOrderStage.test.tsx
@@ -2,7 +2,6 @@ import { render, waitFor, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import DocumentSelectOrderStage from './DocumentSelectOrderStage';
import {
- DOCUMENT_TYPE,
DOCUMENT_UPLOAD_STATE,
UploadDocument,
} from '../../../../types/pages/UploadDocumentsPage/types';
@@ -10,6 +9,7 @@ import { MemoryRouter } from 'react-router-dom';
import { fileUploadErrorMessages } from '../../../../helpers/utils/fileUploadErrorMessages';
import { buildLgFile } from '../../../../helpers/test/testBuilders';
import { Mock } from 'vitest';
+import { DOCUMENT_TYPE } from '../../../../helpers/utils/documentType';
const mockNavigate = vi.fn();
const mockSetDocuments = vi.fn();
diff --git a/app/src/components/blocks/_documentUpload/documentSelectOrderStage/DocumentSelectOrderStage.tsx b/app/src/components/blocks/_documentUpload/documentSelectOrderStage/DocumentSelectOrderStage.tsx
index 09abb2109..146fa1d14 100644
--- a/app/src/components/blocks/_documentUpload/documentSelectOrderStage/DocumentSelectOrderStage.tsx
+++ b/app/src/components/blocks/_documentUpload/documentSelectOrderStage/DocumentSelectOrderStage.tsx
@@ -13,7 +13,6 @@ import { routeChildren, routes } from '../../../../types/generic/routes';
import { SelectRef } from '../../../../types/generic/selectRef';
import { ErrorMessageListItem } from '../../../../types/pages/genericPageErrors';
import {
- DOCUMENT_TYPE,
SetUploadDocuments,
UploadDocument,
} from '../../../../types/pages/UploadDocumentsPage/types';
@@ -22,6 +21,7 @@ import PatientSummary, { PatientInfo } from '../../../generic/patientSummary/Pat
import ErrorBox from '../../../layout/errorBox/ErrorBox';
import DocumentUploadLloydGeorgePreview from '../documentUploadLloydGeorgePreview/DocumentUploadLloydGeorgePreview';
import SpinnerButton from '../../../generic/spinnerButton/SpinnerButton';
+import { DOCUMENT_TYPE } from '../../../../helpers/utils/documentType';
type Props = {
documents: UploadDocument[];
diff --git a/app/src/components/blocks/_documentUpload/documentSelectStage/DocumentSelectStage.test.tsx b/app/src/components/blocks/_documentUpload/documentSelectStage/DocumentSelectStage.test.tsx
index ace3fbc81..dddb3c1fe 100644
--- a/app/src/components/blocks/_documentUpload/documentSelectStage/DocumentSelectStage.test.tsx
+++ b/app/src/components/blocks/_documentUpload/documentSelectStage/DocumentSelectStage.test.tsx
@@ -12,9 +12,10 @@ import { PDF_PARSING_ERROR_TYPE } from '../../../../helpers/utils/fileUploadErro
import { getFormattedDate } from '../../../../helpers/utils/formatDate';
import { formatNhsNumber } from '../../../../helpers/utils/formatNhsNumber';
import { routeChildren, routes } from '../../../../types/generic/routes';
-import { DOCUMENT_TYPE, UploadDocument } from '../../../../types/pages/UploadDocumentsPage/types';
+import { UploadDocument } from '../../../../types/pages/UploadDocumentsPage/types';
import DocumentSelectStage, { Props } from './DocumentSelectStage';
import { getFormattedPatientFullName } from '../../../../helpers/utils/formatPatientFullName';
+import { DOCUMENT_TYPE } from '../../../../helpers/utils/documentType';
vi.mock('../../../../helpers/hooks/usePatient');
vi.mock('react-router-dom', async () => {
diff --git a/app/src/components/blocks/_documentUpload/documentSelectStage/DocumentSelectStage.tsx b/app/src/components/blocks/_documentUpload/documentSelectStage/DocumentSelectStage.tsx
index 12c28fb76..8dc256065 100644
--- a/app/src/components/blocks/_documentUpload/documentSelectStage/DocumentSelectStage.tsx
+++ b/app/src/components/blocks/_documentUpload/documentSelectStage/DocumentSelectStage.tsx
@@ -12,7 +12,6 @@ import {
import formatFileSize from '../../../../helpers/utils/formatFileSize';
import { routeChildren, routes } from '../../../../types/generic/routes';
import {
- DOCUMENT_TYPE,
DOCUMENT_UPLOAD_STATE,
FileInputEvent,
SetUploadDocuments,
@@ -24,6 +23,7 @@ import PatientSummary, { PatientInfo } from '../../../generic/patientSummary/Pat
import ErrorBox from '../../../layout/errorBox/ErrorBox';
import { ErrorMessageListItem } from '../../../../types/pages/genericPageErrors';
import { getJourney, useEnhancedNavigate } from '../../../../helpers/utils/urlManipulations';
+import { DOCUMENT_TYPE } from '../../../../helpers/utils/documentType';
export type Props = {
setDocuments: SetUploadDocuments;
diff --git a/app/src/components/blocks/_documentUpload/documentUploadCompleteStage/DocumentUploadCompleteStage.test.tsx b/app/src/components/blocks/_documentUpload/documentUploadCompleteStage/DocumentUploadCompleteStage.test.tsx
index eed6065bf..a05775821 100644
--- a/app/src/components/blocks/_documentUpload/documentUploadCompleteStage/DocumentUploadCompleteStage.test.tsx
+++ b/app/src/components/blocks/_documentUpload/documentUploadCompleteStage/DocumentUploadCompleteStage.test.tsx
@@ -9,10 +9,10 @@ import { formatNhsNumber } from '../../../../helpers/utils/formatNhsNumber';
import usePatient from '../../../../helpers/hooks/usePatient';
import { getFormattedPatientFullName } from '../../../../helpers/utils/formatPatientFullName';
import {
- DOCUMENT_TYPE,
DOCUMENT_UPLOAD_STATE,
UploadDocument,
} from '../../../../types/pages/UploadDocumentsPage/types';
+import { DOCUMENT_TYPE } from '../../../../helpers/utils/documentType';
const mockNavigate = vi.fn();
vi.mock('../../../../helpers/hooks/usePatient');
diff --git a/app/src/components/blocks/_documentUpload/documentUploadConfirmStage/DocumentUploadConfirmStage.test.tsx b/app/src/components/blocks/_documentUpload/documentUploadConfirmStage/DocumentUploadConfirmStage.test.tsx
index 0bc4e96c2..27797cbc4 100644
--- a/app/src/components/blocks/_documentUpload/documentUploadConfirmStage/DocumentUploadConfirmStage.test.tsx
+++ b/app/src/components/blocks/_documentUpload/documentUploadConfirmStage/DocumentUploadConfirmStage.test.tsx
@@ -5,7 +5,6 @@ import { getFormattedDate } from '../../../../helpers/utils/formatDate';
import { buildPatientDetails } from '../../../../helpers/test/testBuilders';
import usePatient from '../../../../helpers/hooks/usePatient';
import {
- DOCUMENT_TYPE,
DOCUMENT_UPLOAD_STATE,
UploadDocument,
} from '../../../../types/pages/UploadDocumentsPage/types';
@@ -14,6 +13,7 @@ import { MemoryHistory, createMemoryHistory } from 'history';
import userEvent from '@testing-library/user-event';
import { routeChildren, routes } from '../../../../types/generic/routes';
import { getFormattedPatientFullName } from '../../../../helpers/utils/formatPatientFullName';
+import { DOCUMENT_TYPE } from '../../../../helpers/utils/documentType';
const mockedUseNavigate = vi.fn();
vi.mock('../../../../helpers/hooks/usePatient');
diff --git a/app/src/components/blocks/_documentUpload/documentUploadConfirmStage/DocumentUploadConfirmStage.tsx b/app/src/components/blocks/_documentUpload/documentUploadConfirmStage/DocumentUploadConfirmStage.tsx
index dd281dddb..5e0ce2090 100644
--- a/app/src/components/blocks/_documentUpload/documentUploadConfirmStage/DocumentUploadConfirmStage.tsx
+++ b/app/src/components/blocks/_documentUpload/documentUploadConfirmStage/DocumentUploadConfirmStage.tsx
@@ -1,12 +1,13 @@
import { Button, Table } from 'nhsuk-react-components';
import useTitle from '../../../../helpers/hooks/useTitle';
-import { DOCUMENT_TYPE, UploadDocument } from '../../../../types/pages/UploadDocumentsPage/types';
+import { UploadDocument } from '../../../../types/pages/UploadDocumentsPage/types';
import BackButton from '../../../generic/backButton/BackButton';
import { routeChildren, routes } from '../../../../types/generic/routes';
import { useState } from 'react';
import Pagination from '../../../generic/pagination/Pagination';
import PatientSummary, { PatientInfo } from '../../../generic/patientSummary/PatientSummary';
import { getJourney, useEnhancedNavigate } from '../../../../helpers/utils/urlManipulations';
+import { DOCUMENT_TYPE } from '../../../../helpers/utils/documentType';
type Props = {
documents: UploadDocument[];
diff --git a/app/src/components/blocks/_documentUpload/documentUploadLloydGeorgePreview/DocumentUploadLloydGeorgePreview.test.tsx b/app/src/components/blocks/_documentUpload/documentUploadLloydGeorgePreview/DocumentUploadLloydGeorgePreview.test.tsx
index 1d671bd2b..5fc8fccfb 100644
--- a/app/src/components/blocks/_documentUpload/documentUploadLloydGeorgePreview/DocumentUploadLloydGeorgePreview.test.tsx
+++ b/app/src/components/blocks/_documentUpload/documentUploadLloydGeorgePreview/DocumentUploadLloydGeorgePreview.test.tsx
@@ -1,11 +1,11 @@
import { act, render, screen, waitFor } from '@testing-library/react';
import {
UploadDocument,
- DOCUMENT_TYPE,
DOCUMENT_UPLOAD_STATE,
} from '../../../../types/pages/UploadDocumentsPage/types';
import DocumentUploadLloydGeorgePreview from './DocumentUploadLloydGeorgePreview';
import getMergedPdfBlob from '../../../../helpers/utils/pdfMerger';
+import { DOCUMENT_TYPE } from '../../../../helpers/utils/documentType';
const mockNavigate = vi.fn();
diff --git a/app/src/components/blocks/_documentUpload/documentUploadRemoveFilesStage/DocumentUploadRemoveFilesStage.test.tsx b/app/src/components/blocks/_documentUpload/documentUploadRemoveFilesStage/DocumentUploadRemoveFilesStage.test.tsx
index 3351252a7..64cfc7093 100644
--- a/app/src/components/blocks/_documentUpload/documentUploadRemoveFilesStage/DocumentUploadRemoveFilesStage.test.tsx
+++ b/app/src/components/blocks/_documentUpload/documentUploadRemoveFilesStage/DocumentUploadRemoveFilesStage.test.tsx
@@ -1,6 +1,7 @@
import { render, waitFor, screen } from '@testing-library/react';
-import { DOCUMENT_TYPE, UploadDocument } from '../../../../types/pages/UploadDocumentsPage/types';
+import { UploadDocument } from '../../../../types/pages/UploadDocumentsPage/types';
import DocumentUploadRemoveFilesStage from './DocumentUploadRemoveFilesStage';
+import { DOCUMENT_TYPE } from '../../../../helpers/utils/documentType';
const mockNavigate = vi.fn();
vi.mock('../../../../helpers/hooks/usePatient');
diff --git a/app/src/components/blocks/_documentUpload/documentUploadRemoveFilesStage/DocumentUploadRemoveFilesStage.tsx b/app/src/components/blocks/_documentUpload/documentUploadRemoveFilesStage/DocumentUploadRemoveFilesStage.tsx
index 79a4cec4a..fb0aef9ee 100644
--- a/app/src/components/blocks/_documentUpload/documentUploadRemoveFilesStage/DocumentUploadRemoveFilesStage.tsx
+++ b/app/src/components/blocks/_documentUpload/documentUploadRemoveFilesStage/DocumentUploadRemoveFilesStage.tsx
@@ -1,5 +1,4 @@
import {
- DOCUMENT_TYPE,
SetUploadDocuments,
UploadDocument,
} from '../../../../types/pages/UploadDocumentsPage/types';
@@ -8,6 +7,7 @@ import { Button } from 'nhsuk-react-components';
import useTitle from '../../../../helpers/hooks/useTitle';
import LinkButton from '../../../generic/linkButton/LinkButton';
import { useEnhancedNavigate } from '../../../../helpers/utils/urlManipulations';
+import { DOCUMENT_TYPE } from '../../../../helpers/utils/documentType';
type Props = {
documents: UploadDocument[];
diff --git a/app/src/components/blocks/_documentUpload/documentUploadingStage/DocumentUploadingStage.test.tsx b/app/src/components/blocks/_documentUpload/documentUploadingStage/DocumentUploadingStage.test.tsx
index 613bab9da..2c957ff46 100644
--- a/app/src/components/blocks/_documentUpload/documentUploadingStage/DocumentUploadingStage.test.tsx
+++ b/app/src/components/blocks/_documentUpload/documentUploadingStage/DocumentUploadingStage.test.tsx
@@ -1,6 +1,5 @@
import { render, waitFor, screen } from '@testing-library/react';
import {
- DOCUMENT_TYPE,
DOCUMENT_UPLOAD_STATE,
UploadDocument,
} from '../../../../types/pages/UploadDocumentsPage/types';
@@ -8,6 +7,7 @@ import DocumentUploadingStage from './DocumentUploadingStage';
import { buildLgFile } from '../../../../helpers/test/testBuilders';
import { MemoryRouter } from 'react-router-dom';
import { routes } from '../../../../types/generic/routes';
+import { DOCUMENT_TYPE } from '../../../../helpers/utils/documentType';
const mockStartUpload = vi.fn();
const mockedNavigate = vi.fn();
diff --git a/app/src/components/blocks/_documentUpload/documentUploadingStage/DocumentUploadingStage.tsx b/app/src/components/blocks/_documentUpload/documentUploadingStage/DocumentUploadingStage.tsx
index 3d2af055e..993a098b6 100644
--- a/app/src/components/blocks/_documentUpload/documentUploadingStage/DocumentUploadingStage.tsx
+++ b/app/src/components/blocks/_documentUpload/documentUploadingStage/DocumentUploadingStage.tsx
@@ -1,7 +1,6 @@
import { Table, WarningCallout } from 'nhsuk-react-components';
import useTitle from '../../../../helpers/hooks/useTitle';
import {
- DOCUMENT_TYPE,
DOCUMENT_UPLOAD_STATE,
UploadDocument,
} from '../../../../types/pages/UploadDocumentsPage/types';
@@ -10,6 +9,7 @@ import { useNavigate } from 'react-router-dom';
import { routes } from '../../../../types/generic/routes';
import { allDocsHaveState } from '../../../../helpers/utils/uploadDocumentHelpers';
import { getJourney } from '../../../../helpers/utils/urlManipulations';
+import { DOCUMENT_TYPE } from '../../../../helpers/utils/documentType';
type Props = {
documents: UploadDocument[];
diff --git a/app/src/components/blocks/_lloydGeorge/lloydGeorgeDownloadStage/LloydGeorgeDownloadStage.tsx b/app/src/components/blocks/_lloydGeorge/lloydGeorgeDownloadStage/LloydGeorgeDownloadStage.tsx
index 2540e38f0..f1f37eb17 100644
--- a/app/src/components/blocks/_lloydGeorge/lloydGeorgeDownloadStage/LloydGeorgeDownloadStage.tsx
+++ b/app/src/components/blocks/_lloydGeorge/lloydGeorgeDownloadStage/LloydGeorgeDownloadStage.tsx
@@ -2,7 +2,6 @@ import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'
import { Card } from 'nhsuk-react-components';
import useBaseAPIHeaders from '../../../../helpers/hooks/useBaseAPIHeaders';
import getPresignedUrlForZip from '../../../../helpers/requests/getPresignedUrlForZip';
-import { DOCUMENT_TYPE } from '../../../../types/pages/UploadDocumentsPage/types';
import useBaseAPIUrl from '../../../../helpers/hooks/useBaseAPIUrl';
import usePatient from '../../../../helpers/hooks/usePatient';
import { routeChildren, routes } from '../../../../types/generic/routes';
@@ -12,6 +11,7 @@ import { AxiosError } from 'axios/index';
import { isMock } from '../../../../helpers/utils/isLocal';
import useConfig from '../../../../helpers/hooks/useConfig';
import useTitle from '../../../../helpers/hooks/useTitle';
+import { DOCUMENT_TYPE } from '../../../../helpers/utils/documentType';
const FakeProgress = require('fake-progress');
diff --git a/app/src/components/blocks/_lloydGeorge/lloydGeorgeSelectDownloadStage/LloydGeorgeSelectDownloadStage.tsx b/app/src/components/blocks/_lloydGeorge/lloydGeorgeSelectDownloadStage/LloydGeorgeSelectDownloadStage.tsx
index 00398f5a8..1ab929180 100644
--- a/app/src/components/blocks/_lloydGeorge/lloydGeorgeSelectDownloadStage/LloydGeorgeSelectDownloadStage.tsx
+++ b/app/src/components/blocks/_lloydGeorge/lloydGeorgeSelectDownloadStage/LloydGeorgeSelectDownloadStage.tsx
@@ -11,7 +11,6 @@ import { AxiosError } from 'axios';
import { routeChildren, routes } from '../../../../types/generic/routes';
import { errorToParams } from '../../../../helpers/utils/errorToParams';
import ProgressBar from '../../../generic/progressBar/ProgressBar';
-import { DOCUMENT_TYPE } from '../../../../types/pages/UploadDocumentsPage/types';
import { isMock } from '../../../../helpers/utils/isLocal';
import LloydGeorgeSelectSearchResults from '../lloydGeorgeSelectSearchResults/LloydGeorgeSelectSearchResults';
import PatientSummary from '../../../generic/patientSummary/PatientSummary';
@@ -20,6 +19,7 @@ import { buildSearchResult } from '../../../../helpers/test/testBuilders';
import { getLastURLPath } from '../../../../helpers/utils/urlManipulations';
import LgDownloadComplete from '../lloydGeorgeDownloadComplete/LloydGeorgeDownloadComplete';
import { DOWNLOAD_STAGE } from '../../../../types/generic/downloadStage';
+import { DOCUMENT_TYPE } from '../../../../helpers/utils/documentType';
export type Props = {
numberOfFiles: number;
diff --git a/app/src/components/blocks/_lloydGeorge/lloydGeorgeViewRecordStage/LloydGeorgeViewRecordStage.tsx b/app/src/components/blocks/_lloydGeorge/lloydGeorgeViewRecordStage/LloydGeorgeViewRecordStage.tsx
index 9a030261c..685375962 100644
--- a/app/src/components/blocks/_lloydGeorge/lloydGeorgeViewRecordStage/LloydGeorgeViewRecordStage.tsx
+++ b/app/src/components/blocks/_lloydGeorge/lloydGeorgeViewRecordStage/LloydGeorgeViewRecordStage.tsx
@@ -25,6 +25,7 @@ import useBaseAPIHeaders from '../../../../helpers/hooks/useBaseAPIHeaders';
import { AxiosError } from 'axios';
import { SearchResult } from '../../../../types/generic/searchResult';
import { isMock } from '../../../../helpers/utils/isLocal';
+import { DOCUMENT_TYPE } from '../../../../helpers/utils/documentType';
export type Props = {
downloadStage: DOWNLOAD_STAGE;
@@ -57,20 +58,21 @@ const LloydGeorgeViewRecordStage = ({
const hasRecordInStorage = downloadStage === DOWNLOAD_STAGE.SUCCEEDED;
- const setFullScreen = (isFullscreen: boolean): void => {
- if (isFullscreen) {
- if (document.fullscreenEnabled) {
- document.documentElement.requestFullscreen?.();
- }
- } else if (document.fullscreenElement !== null) {
+ const enableFullscreen = (): void => {
+ if (document.fullscreenEnabled) {
+ document.documentElement.requestFullscreen?.();
+ }
+ };
+
+ const disableFullscreen = (): void => {
+ if (document.fullscreenElement !== null) {
document.exitFullscreen?.();
}
- // Note: Let the fullscreen event handlers manage the session state to avoid race conditions
};
let recordLinksToShow = getUserRecordActionLinks({ role, hasRecordInStorage }).map((link) => {
link.onClick = (): void => {
- setFullScreen(false);
+ disableFullscreen();
};
return link;
@@ -166,6 +168,8 @@ const LloydGeorgeViewRecordStage = ({
created: new Date().toISOString(),
fileSize: 12345,
virusScannerResult: 'clean',
+ documentSnomedCodeType: DOCUMENT_TYPE.LLOYD_GEORGE,
+ contentType: 'application/pdf',
},
]);
} else if (error.response?.status === 403) {
@@ -185,9 +189,7 @@ const LloydGeorgeViewRecordStage = ({
reverse
data-testid="back-link"
className="exit-fullscreen-button"
- onClick={(): void => {
- setFullScreen(false);
- }}
+ onClick={disableFullscreen}
>
Exit full screen
@@ -196,9 +198,7 @@ const LloydGeorgeViewRecordStage = ({
{
- setFullScreen(false);
- }}
+ onClick={disableFullscreen}
>
Sign out
@@ -257,7 +257,7 @@ const LloydGeorgeViewRecordStage = ({
>
}
isFullScreen={session.isFullscreen!}
pdfObjectUrl={hasRecordInStorage ? pdfObjectUrl : ''}
@@ -270,7 +270,7 @@ const LloydGeorgeViewRecordStage = ({
) : (
}
isFullScreen={session.isFullscreen!}
pdfObjectUrl={hasRecordInStorage ? pdfObjectUrl : ''}
diff --git a/app/src/components/blocks/_patientDocuments/documentSearchResults/DocumentSearchResults.scss b/app/src/components/blocks/_patientDocuments/documentSearchResults/DocumentSearchResults.scss
new file mode 100644
index 000000000..0ffc1a623
--- /dev/null
+++ b/app/src/components/blocks/_patientDocuments/documentSearchResults/DocumentSearchResults.scss
@@ -0,0 +1,11 @@
+.document-search-results {
+ #available-files-table-title {
+ .table-column-header, .nhsuk-table-responsive__heading {
+ word-break: keep-all;
+ }
+
+ .filename-value {
+ word-break: break-word;
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/components/blocks/_patientDocuments/documentSearchResults/DocumentSearchResults.test.tsx b/app/src/components/blocks/_patientDocuments/documentSearchResults/DocumentSearchResults.test.tsx
new file mode 100644
index 000000000..43f63b217
--- /dev/null
+++ b/app/src/components/blocks/_patientDocuments/documentSearchResults/DocumentSearchResults.test.tsx
@@ -0,0 +1,59 @@
+import { buildSearchResult, buildUserAuth } from '../../../../helpers/test/testBuilders';
+import { SearchResult } from '../../../../types/generic/searchResult';
+import DocumentSearchResults from './DocumentSearchResults';
+import { render, screen, waitFor, within } from '@testing-library/react';
+import { runAxeTest } from '../../../../helpers/test/axeTestHelper';
+import { describe, expect, it } from 'vitest';
+import SessionProvider, { Session } from '../../../../providers/sessionProvider/SessionProvider';
+import { getFormattedDate } from '../../../../helpers/utils/formatDate';
+
+describe('DocumentSearchResults', () => {
+ const mockDetails = buildSearchResult();
+
+ const mockSearchResults: Array = [mockDetails];
+
+ it('renders provided search results information', async () => {
+ renderPage(mockSearchResults);
+
+ expect(
+ screen.getByText('Records and documents stored for this patient'),
+ ).toBeInTheDocument();
+
+ let searchResults: HTMLElement[] = [];
+ await waitFor(() => {
+ searchResults = screen.getAllByTestId('search-result');
+ });
+
+ const mappedResults = searchResults.map((result) => ({
+ filename: within(result).getByTestId('filename').textContent,
+ created: within(result).getByTestId('created').textContent,
+ }));
+
+ expect(mappedResults).toEqual([
+ {
+ created: `Date uploaded ${getFormattedDate(new Date(mockDetails.created))}`,
+ filename: `Filename ${mockDetails.fileName}`,
+ },
+ ]);
+ });
+
+ it('pass accessibility checks', async () => {
+ renderPage(mockSearchResults);
+ await screen.findByText(mockDetails.fileName);
+
+ const results = await runAxeTest(document.body);
+ expect(results).toHaveNoViolations();
+ });
+});
+
+const renderPage = (searchResults: Array): void => {
+ const session: Session = {
+ auth: buildUserAuth(),
+ isLoggedIn: true,
+ };
+ render(
+
+
+ ,
+ );
+};
diff --git a/app/src/components/blocks/_patientDocuments/documentSearchResults/DocumentSearchResults.tsx b/app/src/components/blocks/_patientDocuments/documentSearchResults/DocumentSearchResults.tsx
new file mode 100644
index 000000000..6e1bd4a5f
--- /dev/null
+++ b/app/src/components/blocks/_patientDocuments/documentSearchResults/DocumentSearchResults.tsx
@@ -0,0 +1,85 @@
+import { Table } from 'nhsuk-react-components';
+import { SearchResult } from '../../../../types/generic/searchResult';
+import { useSessionContext } from '../../../../providers/sessionProvider/SessionProvider';
+import { REPOSITORY_ROLE } from '../../../../types/generic/authRole';
+import { getFormattedDate } from '../../../../helpers/utils/formatDate';
+import { getDocumentTypeLabel } from '../../../../helpers/utils/documentType';
+import LinkButton from '../../../generic/linkButton/LinkButton';
+
+type Props = {
+ searchResults: Array;
+ onViewDocument?: (document: SearchResult) => void;
+};
+
+const DocumentSearchResults = (props: Props) => {
+ const [session] = useSessionContext();
+
+ const canViewFiles =
+ session.auth!.role === REPOSITORY_ROLE.GP_ADMIN ||
+ session.auth!.role === REPOSITORY_ROLE.GP_CLINICAL;
+
+ return (
+
+
Records and documents stored for this patient
+
+
+
+
+ Document type
+ Filename
+ Date uploaded
+ View
+
+
+
+ {props.searchResults?.map((result, index) => (
+
+
+ {getDocumentTypeLabel(result.documentSnomedCodeType) ?? 'Other'}
+
+
+ {result.fileName}
+
+
+ {getFormattedDate(new Date(result.created))}
+
+
+ {canViewFiles && props.onViewDocument && (
+ props.onViewDocument!(result)}
+ id={`available-files-row-${index}-view-link`}
+ data-testid={`view-${index}-link`}
+ href="#"
+ >
+ View
+
+ )}
+
+
+ ))}
+
+
+
+
+ );
+};
+
+export default DocumentSearchResults;
diff --git a/app/src/components/blocks/_arf/documentSearchResultsOptions/DocumentSearchResultsOptions.test.tsx b/app/src/components/blocks/_patientDocuments/documentSearchResultsOptions/DocumentSearchResultsOptions.test.tsx
similarity index 98%
rename from app/src/components/blocks/_arf/documentSearchResultsOptions/DocumentSearchResultsOptions.test.tsx
rename to app/src/components/blocks/_patientDocuments/documentSearchResultsOptions/DocumentSearchResultsOptions.test.tsx
index 29a9fc8bf..debeaad64 100644
--- a/app/src/components/blocks/_arf/documentSearchResultsOptions/DocumentSearchResultsOptions.test.tsx
+++ b/app/src/components/blocks/_patientDocuments/documentSearchResultsOptions/DocumentSearchResultsOptions.test.tsx
@@ -127,9 +127,7 @@ describe('DocumentSearchResultsOptions', () => {
await userEvent.click(screen.getByRole('button', { name: 'Remove all documents' }));
await waitFor(() => {
- expect(mockedUseNavigate).toHaveBeenCalledWith(
- routeChildren.ARF_DELETE_CONFIRMATION,
- );
+ expect(mockedUseNavigate).toHaveBeenCalledWith(routeChildren.DOCUMENT_DELETE);
});
});
});
diff --git a/app/src/components/blocks/_arf/documentSearchResultsOptions/DocumentSearchResultsOptions.tsx b/app/src/components/blocks/_patientDocuments/documentSearchResultsOptions/DocumentSearchResultsOptions.tsx
similarity index 92%
rename from app/src/components/blocks/_arf/documentSearchResultsOptions/DocumentSearchResultsOptions.tsx
rename to app/src/components/blocks/_patientDocuments/documentSearchResultsOptions/DocumentSearchResultsOptions.tsx
index d6ae7de9f..0053eb460 100644
--- a/app/src/components/blocks/_arf/documentSearchResultsOptions/DocumentSearchResultsOptions.tsx
+++ b/app/src/components/blocks/_patientDocuments/documentSearchResultsOptions/DocumentSearchResultsOptions.tsx
@@ -7,9 +7,9 @@ import getPresignedUrlForZip from '../../../../helpers/requests/getPresignedUrlF
import { AxiosError } from 'axios';
import { useEffect, useRef, useState } from 'react';
import useBaseAPIHeaders from '../../../../helpers/hooks/useBaseAPIHeaders';
-import { DOCUMENT_TYPE } from '../../../../types/pages/UploadDocumentsPage/types';
import useBaseAPIUrl from '../../../../helpers/hooks/useBaseAPIUrl';
import { errorToParams } from '../../../../helpers/utils/errorToParams';
+import { DOCUMENT_TYPE } from '../../../../helpers/utils/documentType';
type Props = {
nhsNumber: string;
@@ -81,18 +81,13 @@ const DocumentSearchResultsOptions = (props: Props): React.JSX.Element => {
};
const deleteAllDocuments = (): void => {
- navigate(routeChildren.ARF_DELETE_CONFIRMATION);
+ navigate(routeChildren.DOCUMENT_DELETE);
};
return (
<>
-
- {statusMessage}
+
+ {statusMessage}
{props.downloadState === SUBMISSION_STATE.PENDING ? (
@@ -102,7 +97,7 @@ const DocumentSearchResultsOptions = (props: Props): React.JSX.Element => {
disabled={true}
/>
) : (
-
+
Download all documents
)}
diff --git a/app/src/components/blocks/_patientDocuments/documentView/DocumentView.test.tsx b/app/src/components/blocks/_patientDocuments/documentView/DocumentView.test.tsx
new file mode 100644
index 000000000..b310431dc
--- /dev/null
+++ b/app/src/components/blocks/_patientDocuments/documentView/DocumentView.test.tsx
@@ -0,0 +1,308 @@
+import { render, screen, fireEvent, waitFor, act } from '@testing-library/react';
+import { describe, it, expect, vi, beforeEach, Mock } from 'vitest';
+import DocumentView from './DocumentView';
+import usePatient from '../../../../helpers/hooks/usePatient';
+import useTitle from '../../../../helpers/hooks/useTitle';
+import { DOCUMENT_TYPE, getDocumentTypeLabel } from '../../../../helpers/utils/documentType';
+import { DocumentReference } from '../../../../types/pages/documentSearchResultsPage/types';
+import { routes } from '../../../../types/generic/routes';
+import { buildPatientDetails } from '../../../../helpers/test/testBuilders';
+import userEvent from '@testing-library/user-event';
+import { getFormattedDate } from '../../../../helpers/utils/formatDate';
+import { lloydGeorgeRecordLinks } from '../../../../types/blocks/lloydGeorgeActions';
+import SessionProvider from '../../../../providers/sessionProvider/SessionProvider';
+import { createMemoryHistory } from 'history';
+import * as ReactRouter from 'react-router-dom';
+import { REPOSITORY_ROLE } from '../../../../types/generic/authRole';
+import useRole from '../../../../helpers/hooks/useRole';
+
+// Mock dependencies
+vi.mock('../../../../helpers/hooks/usePatient');
+vi.mock('../../../../helpers/hooks/useTitle');
+vi.mock('../../../../helpers/hooks/useRole');
+vi.mock('react-router-dom', async () => {
+ const actual = await vi.importActual('react-router-dom');
+ return {
+ ...actual,
+ useNavigate: () => mockUseNavigate,
+ createSearchParams: () => mockCreateSearchParams,
+ };
+});
+
+const mockUsePatient = usePatient as Mock;
+const mockUseTitle = useTitle as Mock;
+const mockUseRole = useRole as Mock;
+const mockUseNavigate = vi.fn();
+const mockRemoveDocuments = vi.fn();
+const mockCreateSearchParams = vi.fn();
+
+const EMBEDDED_PDF_VIEWER_TITLE = 'Embedded PDF Viewer';
+
+const mockDocumentReference: DocumentReference = {
+ id: 'test-id',
+ fileName: 'test-document.pdf',
+ created: '2023-01-01T10:00:00Z',
+ url: 'https://example.com/document.pdf',
+ contentType: 'application/pdf',
+ documentSnomedCodeType: DOCUMENT_TYPE.LLOYD_GEORGE,
+ version: '1',
+ virusScannerResult: 'clean',
+ fileSize: 1024,
+ isPdf: true,
+};
+
+const mockPatientDetails = buildPatientDetails();
+
+const simulateFullscreenChange = (isFullscreen: boolean) => {
+ act(() => {
+ // Update the fullscreenElement property to simulate browser state
+ Object.defineProperty(document, 'fullscreenElement', {
+ writable: true,
+ configurable: true,
+ value: isFullscreen ? document.documentElement : null,
+ });
+
+ // Dispatch the fullscreenchange event
+ document.dispatchEvent(new Event('fullscreenchange'));
+ });
+};
+
+type Props = {
+ documentReference: DocumentReference | null;
+};
+
+const TestApp = ({ documentReference }: Props) => {
+ const history = createMemoryHistory();
+ return (
+
+
+
+ );
+};
+
+const renderComponent = (documentReference: DocumentReference | null = mockDocumentReference) => {
+ render(
+
+
+ ,
+ );
+};
+
+describe('DocumentView', () => {
+ beforeEach(() => {
+ import.meta.env.VITE_ENVIRONMENT = 'vitest';
+ mockUsePatient.mockReturnValue(mockPatientDetails);
+ mockUseRole.mockReturnValue(REPOSITORY_ROLE.GP_ADMIN);
+
+ // Mock fullscreen API
+ Object.defineProperty(document, 'fullscreenEnabled', {
+ writable: true,
+ configurable: true,
+ value: true,
+ });
+
+ Object.defineProperty(document, 'fullscreenElement', {
+ writable: true,
+ configurable: true,
+ value: null,
+ });
+
+ // Mock fetch
+ global.fetch = vi.fn().mockResolvedValue({
+ blob: () => Promise.resolve(new Blob()),
+ });
+ });
+
+ describe('Component rendering', () => {
+ it('renders the document view with all components', () => {
+ renderComponent();
+
+ expect(screen.getByText('Lloyd George records')).toBeInTheDocument();
+ expect(screen.getByTestId('patient-summary')).toBeInTheDocument();
+ expect(screen.getByTestId('go-back-button')).toBeInTheDocument();
+ });
+
+ it('sets the page title correctly', () => {
+ renderComponent();
+
+ expect(mockUseTitle).toHaveBeenCalledWith({ pageTitle: 'Lloyd George records' });
+ });
+
+ it('navigates to patient documents when documentReference is null', () => {
+ renderComponent(null);
+
+ expect(mockUseNavigate).toHaveBeenCalledWith(routes.PATIENT_DOCUMENTS);
+ });
+ });
+
+ describe('Fullscreen mode', () => {
+ it('shows full screen mode with patient info', async () => {
+ const patientName = `${mockPatientDetails.familyName}, ${mockPatientDetails.givenName}`;
+ const dob = getFormattedDate(new Date(mockPatientDetails.birthDate));
+
+ renderComponent();
+
+ await screen.findByTitle(EMBEDDED_PDF_VIEWER_TITLE);
+ await userEvent.click(screen.getByText('View in full screen'));
+
+ // Simulate the browser entering fullscreen
+ simulateFullscreenChange(true);
+
+ await screen.findByText('Exit full screen');
+
+ expect(screen.getByText(patientName)).toBeInTheDocument();
+ expect(screen.getByText(new RegExp(dob))).toBeInTheDocument();
+ expect(screen.getByText(/NHS number/)).toBeInTheDocument();
+ });
+
+ it('shows deceased tag for deceased patients', async () => {
+ mockUsePatient.mockReturnValue(buildPatientDetails({ deceased: true }));
+ renderComponent();
+
+ expect(screen.getByTestId('deceased-patient-tag')).toBeInTheDocument();
+ });
+
+ it('returns to regular view when exiting full screen', async () => {
+ renderComponent();
+
+ await userEvent.click(await screen.findByText('View in full screen'));
+ // Simulate entering fullscreen
+ simulateFullscreenChange(true);
+
+ await userEvent.click(await screen.findByText('Exit full screen'));
+ // Simulate exiting fullscreen
+ simulateFullscreenChange(false);
+
+ expect(screen.getByText('View in full screen')).toBeInTheDocument();
+ });
+ });
+
+ describe('Document details', () => {
+ it('displays formatted creation date', () => {
+ renderComponent();
+
+ expect(screen.getByText(/Last updated:/)).toBeInTheDocument();
+ });
+
+ it.each(
+ Array.from(Object.values(DOCUMENT_TYPE)).filter((type) => type !== DOCUMENT_TYPE.ALL),
+ )('displays document type label in record card when doc type is %s', (documentType) => {
+ renderComponent({
+ ...mockDocumentReference,
+ documentSnomedCodeType: documentType,
+ });
+
+ expect(screen.getByTestId('record-card-container')).toHaveTextContent(
+ getDocumentTypeLabel(documentType),
+ );
+ });
+ });
+
+ describe('Add Files functionality', () => {
+ it('shows add files section for Lloyd George documents when not in fullscreen', () => {
+ renderComponent();
+
+ expect(screen.getByTestId('add-files-btn')).toBeInTheDocument();
+ });
+
+ it('does not show add files section when in fullscreen', async () => {
+ renderComponent();
+
+ await screen.findByTitle(EMBEDDED_PDF_VIEWER_TITLE);
+ await userEvent.click(screen.getByText('View in full screen'));
+
+ // Simulate the browser entering fullscreen
+ simulateFullscreenChange(true);
+
+ expect(screen.queryByText('Add Files')).not.toBeInTheDocument();
+ });
+
+ it('does not show add files section for non-Lloyd George documents', () => {
+ const nonLGDocument = {
+ ...mockDocumentReference,
+ documentSnomedCodeType: DOCUMENT_TYPE.EHR,
+ };
+
+ renderComponent(nonLGDocument);
+
+ expect(screen.queryByText('Add Files')).not.toBeInTheDocument();
+ });
+
+ it('navigates to upload page when add files is clicked', async () => {
+ renderComponent();
+
+ const addFilesButton = screen.getByTestId('add-files-btn');
+ fireEvent.click(addFilesButton);
+
+ await waitFor(() => {
+ expect(mockUseNavigate).toHaveBeenCalledWith(
+ expect.objectContaining({
+ pathname: routes.DOCUMENT_UPLOAD,
+ }),
+ expect.objectContaining({
+ state: expect.objectContaining({
+ journey: 'update',
+ existingDocuments: expect.arrayContaining([
+ expect.objectContaining({
+ fileName: mockDocumentReference.fileName,
+ }),
+ ]),
+ }),
+ }),
+ );
+ });
+ });
+
+ it('navigates to server error when patient has no NHS number', async () => {
+ mockUsePatient.mockReturnValue({ nhsNumber: null });
+
+ renderComponent();
+
+ const addFilesButton = screen.getByTestId('add-files-btn');
+ fireEvent.click(addFilesButton);
+
+ await waitFor(() => {
+ expect(mockUseNavigate).toHaveBeenCalledWith(routes.SERVER_ERROR);
+ });
+ });
+ });
+
+ describe('Document actions', () => {
+ it('calls removeDocuments when remove action is triggered', () => {
+ renderComponent();
+
+ // Assuming the first record link is remove action
+ const removeRecordLink = screen.getByTestId(lloydGeorgeRecordLinks[0].key);
+ fireEvent.click(removeRecordLink);
+ expect(mockRemoveDocuments).toHaveBeenCalledWith(
+ mockDocumentReference.documentSnomedCodeType,
+ );
+ });
+ });
+
+ describe('Role-based rendering', () => {
+ it('shows menu for GP_ADMIN role when not in fullscreen', () => {
+ renderComponent();
+
+ // Menu should be available for GP_ADMIN
+ expect(screen.getByTestId('record-menu-card')).toBeInTheDocument();
+ });
+
+ it('does not show menu when in fullscreen mode', async () => {
+ renderComponent();
+
+ await screen.findByTitle(EMBEDDED_PDF_VIEWER_TITLE);
+ await userEvent.click(screen.getByText('View in full screen'));
+
+ // Simulate the browser entering fullscreen
+ simulateFullscreenChange(true);
+
+ // Check that fullscreen layout is different
+ expect(screen.getByText('Exit full screen')).toBeInTheDocument();
+ expect(screen.queryByTestId('record-menu-card')).not.toBeInTheDocument();
+ });
+ });
+});
diff --git a/app/src/components/blocks/_patientDocuments/documentView/DocumentView.tsx b/app/src/components/blocks/_patientDocuments/documentView/DocumentView.tsx
new file mode 100644
index 000000000..39d02de23
--- /dev/null
+++ b/app/src/components/blocks/_patientDocuments/documentView/DocumentView.tsx
@@ -0,0 +1,262 @@
+import { routeChildren, routes } from '../../../../types/generic/routes';
+import useTitle from '../../../../helpers/hooks/useTitle';
+import { useSessionContext } from '../../../../providers/sessionProvider/SessionProvider';
+import { DOCUMENT_TYPE, getDocumentTypeLabel } from '../../../../helpers/utils/documentType';
+import { getFormattedDate } from '../../../../helpers/utils/formatDate';
+import { DocumentReference } from '../../../../types/pages/documentSearchResultsPage/types';
+import {
+ getRecordActionLinksAllowedForRole,
+ LGRecordActionLink,
+ lloydGeorgeRecordLinks,
+ RECORD_ACTION,
+} from '../../../../types/blocks/lloydGeorgeActions';
+import { createSearchParams, NavigateOptions, To, useNavigate } from 'react-router-dom';
+import { REPOSITORY_ROLE } from '../../../../types/generic/authRole';
+import RecordCard from '../../../generic/recordCard/RecordCard';
+import PatientSummary, { PatientInfo } from '../../../generic/patientSummary/PatientSummary';
+import RecordMenuCard from '../../../generic/recordMenuCard/RecordMenuCard';
+import { Button, ChevronLeftIcon } from 'nhsuk-react-components';
+import BackButton from '../../../generic/backButton/BackButton';
+import usePatient from '../../../../helpers/hooks/usePatient';
+import { useEffect } from 'react';
+import useRole from '../../../../helpers/hooks/useRole';
+
+type Props = {
+ documentReference: DocumentReference | null;
+ removeDocuments: (docType: DOCUMENT_TYPE) => void;
+};
+
+const DocumentView = ({
+ documentReference,
+ removeDocuments,
+}: Readonly): React.JSX.Element => {
+ const [session, setUserSession] = useSessionContext();
+ const role = useRole();
+ const navigate = useNavigate();
+ const showMenu = role === REPOSITORY_ROLE.GP_ADMIN && !session.isFullscreen;
+ const patientDetails = usePatient();
+
+ const pageHeader = 'Lloyd George records';
+ useTitle({ pageTitle: pageHeader });
+
+ // Handle fullscreen changes from browser events
+ useEffect(() => {
+ const handleFullscreenChange = (): void => {
+ const isCurrentlyFullscreen = document.fullscreenElement !== null;
+ // Only update if the state has actually changed to avoid unnecessary re-renders
+ if (session.isFullscreen !== isCurrentlyFullscreen) {
+ setUserSession({
+ ...session,
+ isFullscreen: isCurrentlyFullscreen,
+ });
+ }
+ };
+
+ document.addEventListener('fullscreenchange', handleFullscreenChange);
+
+ return () => {
+ document.removeEventListener('fullscreenchange', handleFullscreenChange);
+ };
+ }, [session, setUserSession]);
+
+ if (!documentReference) {
+ navigate(routes.PATIENT_DOCUMENTS);
+ return <>>;
+ }
+
+ const details = (): React.JSX.Element => {
+ return (
+
+
+
+ Filename: {documentReference.fileName}
+
+
+ Last updated: {getFormattedDate(new Date(documentReference.created))}
+
+
+
+ );
+ };
+
+ const downloadClicked = (): void => {
+ if (documentReference.url) {
+ const anchor = document.createElement('a');
+ anchor.href = documentReference.url;
+ anchor.download = documentReference.fileName;
+ document.body.appendChild(anchor);
+ anchor.click();
+ anchor.remove();
+ }
+ };
+
+ const removeClicked = (): void => {
+ disableFullscreen();
+ removeDocuments(documentReference.documentSnomedCodeType);
+ };
+
+ const getCardLinks = (): Array => {
+ if (session.isFullscreen) {
+ return [];
+ }
+
+ const links = getRecordActionLinksAllowedForRole({
+ role,
+ hasRecordInStorage: true,
+ inputLinks: lloydGeorgeRecordLinks,
+ });
+
+ return links.map((link) => {
+ return {
+ ...link,
+ href:
+ link.type === RECORD_ACTION.DELETE ? routeChildren.DOCUMENT_DELETE : undefined,
+ onClick: link.type === RECORD_ACTION.DOWNLOAD ? downloadClicked : removeClicked,
+ };
+ });
+ };
+
+ const getPdfObjectUrl = (): string => {
+ if (documentReference.contentType !== 'application/pdf') {
+ return '';
+ }
+
+ return documentReference.url ? documentReference.url : 'loading';
+ };
+
+ const enableFullscreen = (): void => {
+ if (document.fullscreenEnabled) {
+ document.documentElement.requestFullscreen?.();
+ }
+ };
+
+ const disableFullscreen = (): void => {
+ if (document.fullscreenElement !== null) {
+ document.exitFullscreen?.();
+ }
+ };
+
+ const handleAddFilesClick = async (): Promise => {
+ if (!patientDetails?.nhsNumber) {
+ navigate(routes.SERVER_ERROR);
+ return;
+ }
+
+ const fileName = documentReference.fileName;
+ const documentId = documentReference.id;
+ const versionId = documentReference.version;
+
+ const response = await fetch(documentReference.url!);
+ const blob = await response.blob();
+
+ const to: To = {
+ pathname: routes.DOCUMENT_UPLOAD,
+ search: createSearchParams({ journey: 'update' }).toString(),
+ };
+ const options: NavigateOptions = {
+ state: {
+ journey: 'update',
+ existingDocuments: [{ fileName, blob, documentId, versionId }],
+ },
+ };
+ navigate(to, options);
+ };
+
+ const getRecordCard = (): React.JSX.Element => {
+ const card = (
+
+ );
+ return session.isFullscreen ? (
+ card
+ ) : (
+
+ );
+ };
+
+ return (
+
+ {session.isFullscreen && (
+
+ )}
+
+
+
+ {!session.isFullscreen && (
+ <>
+
+
{pageHeader}
+ >
+ )}
+
+
+
+
+
+
+
+ {session.isFullscreen && (
+
{}}
+ showMenu={showMenu}
+ />
+ )}
+
+ {!session.isFullscreen &&
+ documentReference.documentSnomedCodeType === DOCUMENT_TYPE.LLOYD_GEORGE && (
+ <>
+ Add Files
+ You can add more files to this patient's record.
+
+ Add Files
+
+ >
+ )}
+
+
+ {getRecordCard()}
+
+
+ );
+};
+
+export default DocumentView;
diff --git a/app/src/components/generic/recordCard/RecordCard.test.tsx b/app/src/components/generic/recordCard/RecordCard.test.tsx
index b1c608cbb..3da59d032 100644
--- a/app/src/components/generic/recordCard/RecordCard.test.tsx
+++ b/app/src/components/generic/recordCard/RecordCard.test.tsx
@@ -152,7 +152,7 @@ describe('RecordCard Component', () => {
await userEvent.click(screen.getByTestId('full-screen-btn'));
- expect(mockFullScreenHandler).toHaveBeenCalledWith(true);
+ expect(mockFullScreenHandler).toHaveBeenCalled();
});
});
});
diff --git a/app/src/components/generic/recordCard/RecordCard.tsx b/app/src/components/generic/recordCard/RecordCard.tsx
index 64f972320..bd6f6d053 100644
--- a/app/src/components/generic/recordCard/RecordCard.tsx
+++ b/app/src/components/generic/recordCard/RecordCard.tsx
@@ -4,10 +4,11 @@ import PdfViewer from '../pdfViewer/PdfViewer';
import { LGRecordActionLink } from '../../../types/blocks/lloydGeorgeActions';
import { LG_RECORD_STAGE } from '../../../types/blocks/lloydGeorgeStages';
import RecordMenuCard from '../recordMenuCard/RecordMenuCard';
+import Spinner from '../spinner/Spinner';
export type Props = {
heading: string;
- fullScreenHandler: (clicked: true) => void;
+ fullScreenHandler: () => void;
detailsElement: ReactNode;
isFullScreen: boolean;
pdfObjectUrl: string;
@@ -27,7 +28,18 @@ const RecordCard = ({
showMenu = false,
}: Props): React.JSX.Element => {
const Record = (): React.JSX.Element => {
- return pdfObjectUrl ? : <>>;
+ switch (pdfObjectUrl) {
+ case '':
+ case null:
+ case undefined:
+ return <>>;
+
+ case 'loading':
+ return ;
+
+ default:
+ return ;
+ }
};
const RecordLayout = ({ children }: { children: ReactNode }): React.JSX.Element => {
@@ -56,9 +68,7 @@ const RecordCard = ({
{
- fullScreenHandler(true);
- }}
+ onClick={fullScreenHandler}
>
View in full screen
diff --git a/app/src/components/generic/recordMenuCard/RecordMenuCard.tsx b/app/src/components/generic/recordMenuCard/RecordMenuCard.tsx
index dd91d27b2..6acd14ee6 100644
--- a/app/src/components/generic/recordMenuCard/RecordMenuCard.tsx
+++ b/app/src/components/generic/recordMenuCard/RecordMenuCard.tsx
@@ -60,7 +60,13 @@ const LinkSection = ({ actionLinks, setStage }: SubSectionProps): React.JSX.Elem
return (
<>
{actionLinks.map((link) => (
-
+
))}
>
);
diff --git a/app/src/helpers/requests/deleteAllDocuments.test.ts b/app/src/helpers/requests/deleteAllDocuments.test.ts
index 00c3301ce..3c5d40da9 100644
--- a/app/src/helpers/requests/deleteAllDocuments.test.ts
+++ b/app/src/helpers/requests/deleteAllDocuments.test.ts
@@ -1,7 +1,7 @@
import axios, { AxiosError } from 'axios';
import deleteAllDocuments, { DeleteResponse } from './deleteAllDocuments';
-import { DOCUMENT_TYPE } from '../../types/pages/UploadDocumentsPage/types';
import { describe, expect, test, vi, Mocked } from 'vitest';
+import { DOCUMENT_TYPE } from '../utils/documentType';
vi.mock('axios');
const mockedAxios = axios as Mocked;
@@ -12,7 +12,7 @@ describe('[DELETE] deleteAllDocuments', () => {
test('Delete all documents handles a 2XX response', async () => {
mockedAxios.delete.mockImplementation(() => Promise.resolve({ status: 200, data: '' }));
const args = {
- docType: DOCUMENT_TYPE.ARF,
+ docType: DOCUMENT_TYPE.LLOYD_GEORGE,
nhsNumber: '90000000009',
baseUrl: '/test',
baseHeaders: { 'Content-Type': 'application/json', test: 'test' },
@@ -31,7 +31,7 @@ describe('[DELETE] deleteAllDocuments', () => {
test('Delete all documents catches a 4XX response', async () => {
mockedAxios.delete.mockImplementation(() => Promise.reject({ status: 403, data: '' }));
const args = {
- docType: DOCUMENT_TYPE.ARF,
+ docType: DOCUMENT_TYPE.LLOYD_GEORGE,
nhsNumber: '',
baseUrl: '/test',
baseHeaders: { 'Content-Type': 'application/json', test: 'test' },
@@ -50,7 +50,7 @@ describe('[DELETE] deleteAllDocuments', () => {
test('Delete all documents catches a 5XX response', async () => {
mockedAxios.delete.mockImplementation(() => Promise.reject({ status: 500, data: '' }));
const args = {
- docType: DOCUMENT_TYPE.ARF,
+ docType: DOCUMENT_TYPE.LLOYD_GEORGE,
nhsNumber: '',
baseUrl: '/test',
baseHeaders: { 'Content-Type': 'application/json', test: 'test' },
diff --git a/app/src/helpers/requests/deleteAllDocuments.ts b/app/src/helpers/requests/deleteAllDocuments.ts
index 8c587caf6..17259634b 100644
--- a/app/src/helpers/requests/deleteAllDocuments.ts
+++ b/app/src/helpers/requests/deleteAllDocuments.ts
@@ -1,6 +1,6 @@
import axios from 'axios';
import { AuthHeaders } from '../../types/blocks/authHeaders';
-import { DOCUMENT_TYPE } from '../../types/pages/UploadDocumentsPage/types';
+import { DOCUMENT_TYPE } from '../utils/documentType';
type Args = {
docType: DOCUMENT_TYPE;
diff --git a/app/src/helpers/requests/getDocument.test.ts b/app/src/helpers/requests/getDocument.test.ts
new file mode 100644
index 000000000..debd24e0a
--- /dev/null
+++ b/app/src/helpers/requests/getDocument.test.ts
@@ -0,0 +1,76 @@
+import { describe, it, expect, vi, beforeEach, Mocked } from 'vitest';
+import axios from 'axios';
+import getDocument, { GetDocumentResponse } from './getDocument';
+import { AuthHeaders } from '../../types/blocks/authHeaders';
+import { endpoints } from '../../types/generic/endpoints';
+
+vi.mock('axios');
+const mockedAxios = axios as Mocked;
+
+describe('getDocument', () => {
+ const mockArgs = {
+ nhsNumber: '1234567890',
+ baseUrl: 'https://api.example.com',
+ baseHeaders: { 'Content-Type': 'application/json', test: 'test' } as AuthHeaders,
+ documentId: 'doc-123',
+ };
+
+ const mockResponse: GetDocumentResponse = {
+ url: 'https://example.com/document.pdf',
+ contentType: 'application/pdf',
+ };
+
+ beforeEach(() => {
+ vi.clearAllMocks();
+ });
+
+ it('should successfully fetch document and return response', async () => {
+ mockedAxios.get.mockResolvedValueOnce({ data: mockResponse });
+
+ const result = await getDocument(mockArgs);
+
+ expect(mockedAxios.get).toHaveBeenCalledWith(
+ `${mockArgs.baseUrl}${endpoints.DOCUMENT_REFERENCE}/${mockArgs.documentId}`,
+ {
+ headers: mockArgs.baseHeaders,
+ params: {
+ patientId: mockArgs.nhsNumber,
+ },
+ },
+ );
+ expect(result).toEqual(mockResponse);
+ });
+
+ it('should throw AxiosError when request fails', async () => {
+ const mockError = new Error('Network Error');
+ mockedAxios.get.mockRejectedValueOnce(mockError);
+
+ await expect(getDocument(mockArgs)).rejects.toThrow(mockError);
+ });
+
+ it('should construct correct URL with documentId', async () => {
+ mockedAxios.get.mockResolvedValueOnce({ data: mockResponse });
+
+ await getDocument(mockArgs);
+
+ expect(mockedAxios.get).toHaveBeenCalledWith(
+ expect.stringContaining(`/${mockArgs.documentId}`),
+ expect.any(Object),
+ );
+ });
+
+ it('should pass correct parameters including patientId', async () => {
+ mockedAxios.get.mockResolvedValueOnce({ data: mockResponse });
+
+ await getDocument(mockArgs);
+
+ expect(mockedAxios.get).toHaveBeenCalledWith(
+ expect.any(String),
+ expect.objectContaining({
+ params: {
+ patientId: mockArgs.nhsNumber,
+ },
+ }),
+ );
+ });
+});
diff --git a/app/src/helpers/requests/getDocument.ts b/app/src/helpers/requests/getDocument.ts
new file mode 100644
index 000000000..d40b60041
--- /dev/null
+++ b/app/src/helpers/requests/getDocument.ts
@@ -0,0 +1,42 @@
+import { AuthHeaders } from '../../types/blocks/authHeaders';
+import { endpoints } from '../../types/generic/endpoints';
+import axios, { AxiosError } from 'axios';
+
+type Args = {
+ nhsNumber: string;
+ baseUrl: string;
+ baseHeaders: AuthHeaders;
+ documentId: string;
+};
+
+export type GetDocumentResponse = {
+ url: string;
+ contentType: string;
+};
+
+const getDocument = async ({
+ nhsNumber,
+ baseUrl,
+ baseHeaders,
+ documentId,
+}: Args): Promise => {
+ const gatewayUrl = baseUrl + endpoints.DOCUMENT_REFERENCE + `/${documentId}`;
+
+ try {
+ const { data } = await axios.get(gatewayUrl, {
+ headers: {
+ ...baseHeaders,
+ },
+ params: {
+ patientId: nhsNumber,
+ },
+ });
+
+ return data;
+ } catch (e) {
+ const error = e as AxiosError;
+ throw error;
+ }
+};
+
+export default getDocument;
diff --git a/app/src/helpers/requests/getDocumentSearchResults.ts b/app/src/helpers/requests/getDocumentSearchResults.ts
index 6956fc3b2..9fba2272f 100644
--- a/app/src/helpers/requests/getDocumentSearchResults.ts
+++ b/app/src/helpers/requests/getDocumentSearchResults.ts
@@ -3,7 +3,7 @@ import { endpoints } from '../../types/generic/endpoints';
import { SearchResult } from '../../types/generic/searchResult';
import axios, { AxiosError } from 'axios';
-import { DOCUMENT_TYPE } from '../../types/pages/UploadDocumentsPage/types';
+import { DOCUMENT_TYPE } from '../utils/documentType';
type Args = {
nhsNumber: string;
diff --git a/app/src/helpers/requests/getPresignedUrlForZip.test.ts b/app/src/helpers/requests/getPresignedUrlForZip.test.ts
index a5481adde..269f5b876 100644
--- a/app/src/helpers/requests/getPresignedUrlForZip.test.ts
+++ b/app/src/helpers/requests/getPresignedUrlForZip.test.ts
@@ -4,9 +4,9 @@ import getPresignedUrlForZip, { pollForPresignedUrl, requestJobId } from './getP
import { endpoints } from '../../types/generic/endpoints';
import { JOB_STATUS, PollingResponse } from '../../types/generic/downloadManifestJobStatus';
import waitForSeconds from '../utils/waitForSeconds';
-import { DOCUMENT_TYPE } from '../../types/pages/UploadDocumentsPage/types';
import { DownloadManifestError } from '../../types/generic/errors';
import { describe, expect, it, vi, Mocked, MockedFunction, afterEach } from 'vitest';
+import { DOCUMENT_TYPE } from '../utils/documentType';
vi.mock('axios');
vi.mock('../utils/waitForSeconds', () => ({
@@ -190,7 +190,7 @@ describe('requestJobId', () => {
const postRequestParams = mockedAxios.post.mock.calls[0][2]?.params;
expect(postRequestParams).toEqual({
- docType: 'LG',
+ docType: '16521000000101',
docReferences,
patientId: nhsNumber,
});
diff --git a/app/src/helpers/requests/getPresignedUrlForZip.ts b/app/src/helpers/requests/getPresignedUrlForZip.ts
index a93bb7e56..011e684f4 100644
--- a/app/src/helpers/requests/getPresignedUrlForZip.ts
+++ b/app/src/helpers/requests/getPresignedUrlForZip.ts
@@ -1,11 +1,11 @@
import axios from 'axios';
import { endpoints } from '../../types/generic/endpoints';
import { AuthHeaders } from '../../types/blocks/authHeaders';
-import { DOCUMENT_TYPE } from '../../types/pages/UploadDocumentsPage/types';
import { JOB_STATUS, PollingResponse } from '../../types/generic/downloadManifestJobStatus';
import waitForSeconds from '../utils/waitForSeconds';
import { DownloadManifestError } from '../../types/generic/errors';
import { isRunningInCypress } from '../utils/isLocal';
+import { DOCUMENT_TYPE } from '../utils/documentType';
export const DELAY_BETWEEN_POLLING_IN_SECONDS = isRunningInCypress() ? 0 : 3;
diff --git a/app/src/helpers/requests/uploadDocument.test.ts b/app/src/helpers/requests/uploadDocument.test.ts
index 692cf2af8..2ba8c9b5c 100644
--- a/app/src/helpers/requests/uploadDocument.test.ts
+++ b/app/src/helpers/requests/uploadDocument.test.ts
@@ -7,7 +7,6 @@ import {
} from '../test/testBuilders';
import {
DOCUMENT_STATUS,
- DOCUMENT_TYPE,
DOCUMENT_UPLOAD_STATE,
} from '../../types/pages/UploadDocumentsPage/types';
import uploadDocuments, {
@@ -19,6 +18,7 @@ import { describe, expect, it, Mocked, vi, beforeEach } from 'vitest';
import { DocumentStatusResult } from '../../types/generic/uploadResult';
import { endpoints } from '../../types/generic/endpoints';
import { v4 as uuidv4 } from 'uuid';
+import { DOCUMENT_TYPE } from '../utils/documentType';
vi.mock('axios');
@@ -59,7 +59,7 @@ describe('uploadDocuments', () => {
expect(mockedAxios.post).toHaveBeenCalledTimes(1);
expect(mockedAxios.post).toHaveBeenCalledWith(
- baseUrl + endpoints.DOCUMENT_UPLOAD,
+ baseUrl + endpoints.DOCUMENT_REFERENCE,
expect.any(String),
{
headers: baseHeaders,
@@ -100,7 +100,7 @@ describe('uploadDocuments', () => {
expect(mockedAxios.put).toHaveBeenCalledTimes(1);
expect(mockedAxios.put).toHaveBeenCalledWith(
- baseUrl + endpoints.DOCUMENT_UPLOAD + `/${documentReferenceId}`,
+ baseUrl + endpoints.DOCUMENT_REFERENCE + `/${documentReferenceId}`,
expect.any(String),
{
headers: baseHeaders,
@@ -330,7 +330,7 @@ describe('uploadDocuments', () => {
});
expect(mockedAxios.post).toHaveBeenCalledWith(
- baseUrl + endpoints.DOCUMENT_UPLOAD,
+ baseUrl + endpoints.DOCUMENT_REFERENCE,
expect.any(String),
expect.any(Object),
);
diff --git a/app/src/helpers/requests/uploadDocuments.ts b/app/src/helpers/requests/uploadDocuments.ts
index 7123dc5a8..be3fd71b2 100644
--- a/app/src/helpers/requests/uploadDocuments.ts
+++ b/app/src/helpers/requests/uploadDocuments.ts
@@ -115,7 +115,7 @@ const uploadDocuments = async ({
const gatewayUrl =
baseUrl +
- endpoints.DOCUMENT_UPLOAD +
+ endpoints.DOCUMENT_REFERENCE +
(documentReferenceId ? `/${documentReferenceId}` : '');
try {
diff --git a/app/src/helpers/test/axeTestHelper.ts b/app/src/helpers/test/axeTestHelper.ts
index 72f6dd3ce..a174dcdd4 100644
--- a/app/src/helpers/test/axeTestHelper.ts
+++ b/app/src/helpers/test/axeTestHelper.ts
@@ -18,6 +18,7 @@ const suppressRules = {
'nested-interactive': { enabled: false },
'duplicate-id-active': { enabled: false },
'duplicate-id': { enabled: false },
+ 'heading-order': { enabled: false },
};
const SUPPRESS_ACCESSIBILITY_TEST = true;
diff --git a/app/src/helpers/test/testBuilders.ts b/app/src/helpers/test/testBuilders.ts
index f4eba69d9..ba88d56f1 100644
--- a/app/src/helpers/test/testBuilders.ts
+++ b/app/src/helpers/test/testBuilders.ts
@@ -1,5 +1,4 @@
import {
- DOCUMENT_TYPE,
DOCUMENT_UPLOAD_STATE,
UploadDocument,
DOCUMENT_UPLOAD_STATE as documentUploadStates,
@@ -19,6 +18,7 @@ import {
DeceasedAccessAuditReasons,
PatientAccessAudit,
} from '../../types/generic/accessAudit';
+import { DOCUMENT_TYPE } from '../utils/documentType';
const buildUserAuth = (userAuthOverride?: Partial): UserAuth => {
const auth: UserAuth = {
@@ -78,7 +78,7 @@ const buildDocument = (
state: uploadStatus ?? documentUploadStates.SUCCEEDED,
progress: 0,
id: uuidv4(),
- docType: docType ?? DOCUMENT_TYPE.ARF,
+ docType: docType ?? DOCUMENT_TYPE.LLOYD_GEORGE,
attempts: 0,
versionId: '1',
};
@@ -114,6 +114,8 @@ const buildSearchResult = (searchResultOverride?: Partial): Search
id: '1234qwer-241ewewr',
fileSize: 224,
version: '1',
+ documentSnomedCodeType: DOCUMENT_TYPE.LLOYD_GEORGE,
+ contentType: 'application/pdf',
...searchResultOverride,
};
return result;
diff --git a/app/src/helpers/test/testDataForPdsNameValidation.ts b/app/src/helpers/test/testDataForPdsNameValidation.ts
index 7aa88df1c..97401433f 100644
--- a/app/src/helpers/test/testDataForPdsNameValidation.ts
+++ b/app/src/helpers/test/testDataForPdsNameValidation.ts
@@ -1,11 +1,11 @@
import { PatientDetails } from '../../types/generic/patientDetails';
import { buildPatientDetails } from './testBuilders';
import {
- DOCUMENT_TYPE,
DOCUMENT_UPLOAD_STATE as documentUploadStates,
UploadDocument,
} from '../../types/pages/UploadDocumentsPage/types';
import { v4 as uuidv4 } from 'uuid';
+import { DOCUMENT_TYPE } from '../utils/documentType';
type PdsNameMatchingTestCase = {
patientDetails: PatientDetails;
diff --git a/app/src/pages/documentSearchResultsPage/DocumentSearchResultsPage.test.tsx b/app/src/pages/documentSearchResultsPage/DocumentSearchResultsPage.test.tsx
index bfcfd6292..5e859634c 100644
--- a/app/src/pages/documentSearchResultsPage/DocumentSearchResultsPage.test.tsx
+++ b/app/src/pages/documentSearchResultsPage/DocumentSearchResultsPage.test.tsx
@@ -1,40 +1,55 @@
import { render, screen, waitFor } from '@testing-library/react';
-import { act } from 'react';
+import React, { act } from 'react';
import DocumentSearchResultsPage from './DocumentSearchResultsPage';
import userEvent from '@testing-library/user-event';
-import { buildPatientDetails, buildSearchResult } from '../../helpers/test/testBuilders';
-import { routes } from '../../types/generic/routes';
+import {
+ buildPatientDetails,
+ buildSearchResult,
+ buildUserAuth,
+} from '../../helpers/test/testBuilders';
+import { routeChildren, routes } from '../../types/generic/routes';
import axios from 'axios';
import usePatient from '../../helpers/hooks/usePatient';
import * as ReactRouter from 'react-router-dom';
import { History, createMemoryHistory } from 'history';
import { runAxeTest } from '../../helpers/test/axeTestHelper';
import { afterEach, beforeEach, describe, expect, it, vi, Mock, Mocked } from 'vitest';
+import SessionProvider, { Session } from '../../providers/sessionProvider/SessionProvider';
+import { REPOSITORY_ROLE } from '../../types/generic/authRole';
+import getDocumentSearchResults from '../../helpers/requests/getDocumentSearchResults';
+import getDocument from '../../helpers/requests/getDocument';
+import useConfig from '../../helpers/hooks/useConfig';
const mockedUseNavigate = vi.fn();
vi.mock('react-router-dom', async () => ({
...(await vi.importActual('react-router-dom')),
- useNavigate: () => mockedUseNavigate,
- Link: (props: ReactRouter.LinkProps) => ,
+ useNavigate: (): Mock => mockedUseNavigate,
+ Link: (props: ReactRouter.LinkProps): React.JSX.Element => ,
}));
vi.mock('axios');
-Date.now = () => new Date('2020-01-01T00:00:00.000Z').getTime();
+Date.now = (): number => new Date('2020-01-01T00:00:00.000Z').getTime();
vi.mock('../../helpers/hooks/useBaseAPIHeaders');
vi.mock('../../helpers/hooks/usePatient');
vi.mock('../../helpers/hooks/useConfig');
+vi.mock('../../helpers/requests/getDocumentSearchResults');
+vi.mock('../../helpers/requests/getDocument');
const mockedAxios = axios as Mocked;
const mockedUsePatient = usePatient as Mock;
+const mockedGetSearchResults = getDocumentSearchResults as Mock;
+const mockedGetDocument = getDocument as Mock;
+const mockedUseConfig = useConfig as Mock;
const mockPatient = buildPatientDetails();
let history = createMemoryHistory({
- initialEntries: ['/'],
+ initialEntries: ['/patient/documents'],
initialIndex: 0,
});
describe(' ', () => {
beforeEach(() => {
+ sessionStorage.setItem('UserSession', '');
history = createMemoryHistory({
initialEntries: ['/'],
initialIndex: 0,
@@ -42,41 +57,50 @@ describe(' ', () => {
import.meta.env.VITE_ENVIRONMENT = 'vitest';
mockedUsePatient.mockReturnValue(mockPatient);
+ mockedUseConfig.mockReturnValue({
+ featureFlags: {
+ uploadDocumentIteration3Enabled: true,
+ },
+ });
});
afterEach(() => {
vi.clearAllMocks();
+ vi.restoreAllMocks();
});
describe('Rendering', () => {
- it('renders the page after a successful response from api', async () => {
- mockedAxios.get.mockResolvedValue(async () => {
- return Promise.resolve({ data: [buildSearchResult()] });
- });
-
- renderPage(history);
-
- expect(
- screen.getByRole('heading', {
- name: 'Manage this Lloyd George record',
- }),
- ).toBeInTheDocument();
-
- await waitFor(() => {
- expect(
- screen.queryByRole('progressbar', { name: 'Loading...' }),
- ).not.toBeInTheDocument();
- });
- });
+ it.each([
+ { role: REPOSITORY_ROLE.GP_ADMIN, title: 'Lloyd George records' },
+ { role: REPOSITORY_ROLE.GP_CLINICAL, title: 'Lloyd George records' },
+ { role: REPOSITORY_ROLE.PCSE, title: 'Manage Lloyd George records' },
+ ])(
+ 'renders the page after a successful response from api when role is $role',
+ async ({ role, title }) => {
+ mockedGetSearchResults.mockResolvedValue([buildSearchResult()]);
+
+ renderPage(history, role);
+
+ const pageTitle = screen.getByTestId('page-title');
+ expect(pageTitle).toBeInTheDocument();
+ expect(pageTitle).toHaveTextContent(title);
+
+ await waitFor(() => {
+ expect(
+ screen.queryByRole('progressbar', { name: 'Loading...' }),
+ ).not.toBeInTheDocument();
+ });
+ },
+ );
it('displays a progress bar when the document search results are being requested', async () => {
- mockedAxios.get.mockImplementation(async () => {
+ mockedGetSearchResults.mockImplementationOnce(async () => {
await new Promise((resolve) => {
setTimeout(() => {
// To delay the mock request, and give a chance for the progress bar to appear
resolve(null);
}, 500);
});
- return Promise.resolve({ data: [buildSearchResult()] });
+ return Promise.resolve([buildSearchResult()]);
});
renderPage(history);
@@ -85,7 +109,7 @@ describe(' ', () => {
});
it('displays a message when a document search returns no results', async () => {
- mockedAxios.get.mockResolvedValue(async () => {
+ mockedGetSearchResults.mockImplementation(async () => {
return Promise.resolve({ data: [] });
});
@@ -103,86 +127,27 @@ describe(' ', () => {
expect(screen.queryByTestId('delete-all-documents-btn')).not.toBeInTheDocument();
});
- it('displays a error messages when the call to document manifest fails', async () => {
- mockedAxios.get.mockResolvedValue({ data: [buildSearchResult()] });
-
- const errorResponse = {
- response: {
- status: 403,
- message: 'An error occurred',
- },
- };
-
- renderPage(history);
-
- mockedAxios.get.mockImplementation(() => Promise.reject(errorResponse));
-
- await waitFor(() => {
- screen.getByRole('button', { name: 'Download all documents' });
- });
- await userEvent.click(screen.getByRole('button', { name: 'Download all documents' }));
-
- expect(
- await screen.findByText('An error has occurred while preparing your download'),
- ).toBeInTheDocument();
- expect(
- screen.getByRole('button', { name: 'Download all documents' }),
- ).toBeInTheDocument();
- expect(
- screen.getByRole('button', { name: 'Remove all documents' }),
- ).toBeInTheDocument();
- });
-
- it('displays a error messages when the call to document manifest return 400', async () => {
- mockedAxios.get.mockResolvedValue({ data: [buildSearchResult()] });
-
+ it('displays a service error when document search fails with bad request', async () => {
const errorResponse = {
response: {
status: 400,
- data: { message: 'An error occurred', err_code: 'SP_1001' },
+ message: 'bad request',
},
};
+ mockedGetSearchResults.mockRejectedValueOnce(errorResponse);
renderPage(history);
- mockedAxios.get.mockImplementation(() => Promise.reject(errorResponse));
-
await waitFor(() => {
- screen.getByRole('button', { name: 'Download all documents' });
+ expect(screen.getByTestId('service-error')).toBeInTheDocument();
});
- await userEvent.click(screen.getByRole('button', { name: 'Download all documents' }));
- expect(
- await screen.findByText('An error has occurred while preparing your download'),
- ).toBeInTheDocument();
- expect(
- screen.getByRole('button', { name: 'Download all documents' }),
- ).toBeInTheDocument();
- expect(screen.getByTestId('delete-all-documents-btn')).toBeInTheDocument();
- });
-
- it('displays a message when a document search return 423 locked error', async () => {
- const errorResponse = {
- response: {
- status: 423,
- message: 'An error occurred',
- },
- };
- mockedAxios.get.mockImplementation(() => Promise.reject(errorResponse));
-
- renderPage(history);
-
- expect(
- await screen.findByText(
- 'There are already files being uploaded for this patient, please try again in a few minutes.',
- ),
- ).toBeInTheDocument();
});
});
describe('Accessibility', () => {
it('pass accessibility checks at loading screen', async () => {
- mockedAxios.get.mockReturnValueOnce(
- new Promise((resolve) => setTimeout(resolve, 100000)),
+ mockedGetSearchResults.mockImplementation(() =>
+ new Promise((resolve) => setTimeout(resolve, 20000)),
);
renderPage(history);
@@ -193,30 +158,33 @@ describe(' ', () => {
});
it('pass accessibility checks when displaying search result', async () => {
- mockedAxios.get.mockResolvedValue({ data: [buildSearchResult()] });
+ mockedGetSearchResults.mockImplementation(() => Promise.resolve([buildSearchResult()]));
renderPage(history);
- expect(await screen.findByText('List of documents available')).toBeInTheDocument();
+ await waitFor(() => {
+ expect(screen.getByTestId('subtitle')).toBeInTheDocument();
+ });
const results = await runAxeTest(document.body);
expect(results).toHaveNoViolations();
});
it('pass accessibility checks when error boxes are showing up', async () => {
- mockedAxios.get.mockResolvedValue({ data: [buildSearchResult()] });
+ mockedGetSearchResults.mockImplementation(() => Promise.resolve([buildSearchResult()]));
const errorResponse = {
response: {
status: 400,
data: { message: 'An error occurred', err_code: 'SP_1001' },
},
};
- renderPage(history);
+ renderPage(history, REPOSITORY_ROLE.PCSE);
const downloadButton = await screen.findByRole('button', {
name: 'Download all documents',
});
- mockedAxios.get.mockImplementation(() => Promise.reject(errorResponse));
+
+ vi.spyOn(mockedAxios, 'get').mockRejectedValueOnce(errorResponse);
await userEvent.click(downloadButton);
expect(
@@ -232,48 +200,131 @@ describe(' ', () => {
});
describe('Navigation', () => {
- it('navigates to Error page when call to doc manifest return 500', async () => {
- mockedAxios.get.mockResolvedValue({ data: [buildSearchResult()] });
+ it('navigates to session expire page when a document search return 403 unauthorised error', async () => {
+ const errorResponse = {
+ response: {
+ status: 403,
+ message: 'An error occurred',
+ },
+ };
+ mockedGetSearchResults.mockRejectedValueOnce(errorResponse);
+
+ renderPage(history);
+
+ await waitFor(() => {
+ expect(mockedUseNavigate).toHaveBeenCalledWith(routes.SESSION_EXPIRED);
+ });
+ });
+
+ it('navigates to server error page when document search return 500 server error', async () => {
const errorResponse = {
response: {
status: 500,
- data: { message: 'An error occurred', err_code: 'SP_1001' },
+ message: 'An error occurred',
},
};
- mockedAxios.get.mockImplementation(() => Promise.reject(errorResponse));
+ mockedGetSearchResults.mockRejectedValueOnce(errorResponse);
+
+ renderPage(history);
+
+ await waitFor(() => {
+ expect(mockedUseNavigate).toHaveBeenCalledWith(expect.stringContaining(routes.SERVER_ERROR));
+ });
+ });
+
+ it('loads the document and navigates to view screen on view link clicked', async () => {
+ mockedGetSearchResults.mockResolvedValue([buildSearchResult()]);
+
+ renderPage(history);
+
+ await waitFor(() => {
+ expect(
+ screen.queryByRole('progressbar', { name: 'Loading...' }),
+ ).not.toBeInTheDocument();
+ });
- act(() => {
- renderPage(history);
+ const viewLink = screen.getByTestId('view-0-link');
+ await act(async () => {
+ await userEvent.click(viewLink);
});
+ expect(mockedGetDocument).toHaveBeenCalledTimes(1);
+ expect(mockedUseNavigate).toHaveBeenCalledWith(routeChildren.DOCUMENT_VIEW);
+ });
+
+ it('navigates to server error when load document fails with 500', async () => {
+ mockedGetSearchResults.mockResolvedValue([buildSearchResult()]);
+
+ const errorResponse = {
+ response: {
+ status: 500,
+ message: 'server error',
+ },
+ };
+ mockedGetDocument.mockRejectedValue(errorResponse);
+
+ renderPage(history);
+
await waitFor(() => {
- expect(mockedUseNavigate).toHaveBeenCalledWith(
- routes.SERVER_ERROR + '?encodedError=WyJTUF8xMDAxIiwiMTU3NzgzNjgwMCJd',
- );
+ expect(
+ screen.queryByRole('progressbar', { name: 'Loading...' }),
+ ).not.toBeInTheDocument();
});
+
+ const viewLink = screen.getByTestId('view-0-link');
+ await act(async () => {
+ await userEvent.click(viewLink);
+ });
+
+ expect(mockedGetDocument).toHaveBeenCalledTimes(1);
+ expect(mockedUseNavigate).toHaveBeenCalledWith(routeChildren.DOCUMENT_VIEW);
+ expect(mockedUseNavigate).toHaveBeenCalledWith(
+ expect.stringContaining(routes.SERVER_ERROR),
+ );
});
- it('navigates to session expire page when a document search return 403 unauthorised error', async () => {
+
+ it('navigates to session expired when load document fails with 403', async () => {
+ mockedGetSearchResults.mockResolvedValue([buildSearchResult()]);
+
const errorResponse = {
response: {
status: 403,
- message: 'An error occurred',
+ message: 'forbidden',
},
};
- mockedAxios.get.mockImplementation(() => Promise.reject(errorResponse));
+ mockedGetDocument.mockRejectedValue(errorResponse);
renderPage(history);
await waitFor(() => {
- expect(mockedUseNavigate).toHaveBeenCalledWith(routes.SESSION_EXPIRED);
+ expect(
+ screen.queryByRole('progressbar', { name: 'Loading...' }),
+ ).not.toBeInTheDocument();
});
+
+ const viewLink = screen.getByTestId('view-0-link');
+ await act(async () => {
+ await userEvent.click(viewLink);
+ });
+
+ expect(mockedGetDocument).toHaveBeenCalledTimes(1);
+ expect(mockedUseNavigate).toHaveBeenCalledWith(routeChildren.DOCUMENT_VIEW);
+ expect(mockedUseNavigate).toHaveBeenCalledWith(routes.SESSION_EXPIRED);
});
});
- const renderPage = (history: History) => {
- return render(
-
-
- ,
+ const renderPage = (history: History, role?: REPOSITORY_ROLE): void => {
+ const auth: Session = {
+ auth: buildUserAuth({ role: role ?? REPOSITORY_ROLE.GP_ADMIN }),
+ isLoggedIn: true,
+ };
+ render(
+
+
+
+
+ ,
+ ,
);
};
});
diff --git a/app/src/pages/documentSearchResultsPage/DocumentSearchResultsPage.tsx b/app/src/pages/documentSearchResultsPage/DocumentSearchResultsPage.tsx
index 35fb3a02a..79d013311 100644
--- a/app/src/pages/documentSearchResultsPage/DocumentSearchResultsPage.tsx
+++ b/app/src/pages/documentSearchResultsPage/DocumentSearchResultsPage.tsx
@@ -1,27 +1,37 @@
import { Dispatch, SetStateAction, useEffect, useRef, useState } from 'react';
import { SearchResult } from '../../types/generic/searchResult';
-import DocumentSearchResults from '../../components/blocks/_arf/documentSearchResults/DocumentSearchResults';
+import DocumentSearchResults from '../../components/blocks/_patientDocuments/documentSearchResults/DocumentSearchResults';
import { Outlet, Route, Routes, useNavigate } from 'react-router-dom';
import { routeChildren, routes } from '../../types/generic/routes';
-import { SUBMISSION_STATE } from '../../types/pages/documentSearchResultsPage/types';
-import ProgressBar from '../../components/generic/progressBar/ProgressBar';
+import {
+ DocumentReference,
+ SUBMISSION_STATE,
+} from '../../types/pages/documentSearchResultsPage/types';
import ServiceError from '../../components/layout/serviceErrorBox/ServiceErrorBox';
-import DocumentSearchResultsOptions from '../../components/blocks/_arf/documentSearchResultsOptions/DocumentSearchResultsOptions';
-import { AxiosError } from 'axios';
+import DocumentSearchResultsOptions from '../../components/blocks/_patientDocuments/documentSearchResultsOptions/DocumentSearchResultsOptions';
+import axios, { AxiosError } from 'axios';
import getDocumentSearchResults from '../../helpers/requests/getDocumentSearchResults';
import useBaseAPIHeaders from '../../helpers/hooks/useBaseAPIHeaders';
-import DeleteSubmitStage from '../../components/blocks/_delete/deleteSubmitStage/DeleteSubmitStage';
-import { DOCUMENT_TYPE } from '../../types/pages/UploadDocumentsPage/types';
import usePatient from '../../helpers/hooks/usePatient';
import useBaseAPIUrl from '../../helpers/hooks/useBaseAPIUrl';
import ErrorBox from '../../components/layout/errorBox/ErrorBox';
import { errorToParams } from '../../helpers/utils/errorToParams';
import useTitle from '../../helpers/hooks/useTitle';
import { getLastURLPath } from '../../helpers/utils/urlManipulations';
-import PatientSummary from '../../components/generic/patientSummary/PatientSummary';
+import PatientSummary, {
+ PatientInfo,
+} from '../../components/generic/patientSummary/PatientSummary';
import { isMock } from '../../helpers/utils/isLocal';
import useConfig from '../../helpers/hooks/useConfig';
import { buildSearchResult } from '../../helpers/test/testBuilders';
+import { useSessionContext } from '../../providers/sessionProvider/SessionProvider';
+import { REPOSITORY_ROLE } from '../../types/generic/authRole';
+import DocumentView from '../../components/blocks/_patientDocuments/documentView/DocumentView';
+import getDocument, { GetDocumentResponse } from '../../helpers/requests/getDocument';
+import { DOCUMENT_TYPE } from '../../helpers/utils/documentType';
+import BackButton from '../../components/generic/backButton/BackButton';
+import ProgressBar from '../../components/generic/progressBar/ProgressBar';
+import DeleteSubmitStage from '../../components/blocks/_delete/deleteSubmitStage/DeleteSubmitStage';
const DocumentSearchResultsPage = (): React.JSX.Element => {
const patientDetails = usePatient();
@@ -30,11 +40,14 @@ const DocumentSearchResultsPage = (): React.JSX.Element => {
const [searchResults, setSearchResults] = useState>([]);
const [submissionState, setSubmissionState] = useState(SUBMISSION_STATE.INITIAL);
const [downloadState, setDownloadState] = useState(SUBMISSION_STATE.INITIAL);
+ const [documentReference, setDocumentReference] = useState(null);
const navigate = useNavigate();
const baseUrl = useBaseAPIUrl();
const baseHeaders = useBaseAPIHeaders();
const config = useConfig();
const mounted = useRef(false);
+ const activeSearchResult = useRef(null);
+ const [removeDocType, setRemoveDocType] = useState(undefined);
useEffect(() => {
const onPageLoad = async (): Promise => {
@@ -52,18 +65,31 @@ const DocumentSearchResultsPage = (): React.JSX.Element => {
} catch (e) {
const error = e as AxiosError;
if (isMock(error)) {
- if (config.mockLocal.uploading) {
- setSubmissionState(SUBMISSION_STATE.BLOCKED);
- } else if (config.mockLocal.recordUploaded) {
- setSearchResults([buildSearchResult(), buildSearchResult()]);
+ if (config.mockLocal.recordUploaded) {
+ setSearchResults([
+ buildSearchResult({
+ documentSnomedCodeType: DOCUMENT_TYPE.LLOYD_GEORGE,
+ fileName: 'Scanned paper notes.pdf',
+ }),
+ buildSearchResult({
+ documentSnomedCodeType: DOCUMENT_TYPE.EHR,
+ fileName: 'Electronic health record.pdf',
+ }),
+ buildSearchResult({
+ documentSnomedCodeType: DOCUMENT_TYPE.EHR_ATTACHMENTS,
+ fileName: 'EHR Attachments.zip',
+ contentType: 'application/zip',
+ }),
+ ]);
+ setSubmissionState(SUBMISSION_STATE.SUCCEEDED);
+ } else {
+ setSearchResults([]);
setSubmissionState(SUBMISSION_STATE.SUCCEEDED);
}
} else if (error.response?.status === 403) {
navigate(routes.SESSION_EXPIRED);
} else if (error.response?.status && error.response?.status >= 500) {
navigate(routes.SERVER_ERROR + errorToParams(error));
- } else if (error.response?.status === 423) {
- setSubmissionState(SUBMISSION_STATE.BLOCKED);
} else {
setSubmissionState(SUBMISSION_STATE.FAILED);
}
@@ -74,8 +100,63 @@ const DocumentSearchResultsPage = (): React.JSX.Element => {
void onPageLoad();
}
}, [nhsNumber, navigate, baseUrl, baseHeaders, config]);
- const pageHeader = 'Manage this Lloyd George record';
- useTitle({ pageTitle: pageHeader });
+
+ const onViewDocument = (documentItem: SearchResult): void => {
+ activeSearchResult.current = documentItem;
+ setDocumentReference({
+ isPdf: documentItem.contentType === 'application/pdf',
+ ...documentItem,
+ });
+ navigate(routeChildren.DOCUMENT_VIEW);
+
+ void loadDocument(documentItem.id);
+ };
+
+ const loadDocument = async (documentId: string): Promise => {
+ try {
+ const documentResponse = await getDocument({
+ nhsNumber: patientDetails!.nhsNumber,
+ baseUrl,
+ baseHeaders,
+ documentId,
+ });
+
+ await handleViewDocSuccess(documentResponse);
+ } catch (e) {
+ const error = e as AxiosError;
+ if (isMock(error)) {
+ await handleViewDocSuccess({
+ url: '/dev/testFile.pdf',
+ contentType: activeSearchResult.current?.contentType || 'application/pdf',
+ });
+ } else if (error.response?.status === 403) {
+ navigate(routes.SESSION_EXPIRED);
+ } else {
+ navigate(routes.SERVER_ERROR + errorToParams(error));
+ }
+ }
+ };
+
+ const handleViewDocSuccess = async (documentResponse: GetDocumentResponse): Promise => {
+ setDocumentReference({
+ url: await getObjectUrl(documentResponse.url),
+ isPdf: documentResponse.contentType === 'application/pdf',
+ ...activeSearchResult.current,
+ } as DocumentReference);
+ };
+
+ const getObjectUrl = async (cloudFrontUrl: string): Promise => {
+ const { data } = await axios.get(cloudFrontUrl, {
+ responseType: 'blob',
+ });
+
+ return URL.createObjectURL(data);
+ };
+
+ const removeDocuments = (docType: DOCUMENT_TYPE): void => {
+ setRemoveDocType(docType);
+ navigate(routeChildren.DOCUMENT_DELETE);
+ };
return (
<>
@@ -90,18 +171,27 @@ const DocumentSearchResultsPage = (): React.JSX.Element => {
downloadState={downloadState}
setDownloadState={setDownloadState}
searchResults={searchResults}
- pageHeader={pageHeader}
+ onViewDocument={onViewDocument}
+ />
+ }
+ />
+
}
/>
null}
+ docType={removeDocType ?? DOCUMENT_TYPE.ALL}
+ resetDocState={(): void => {
+ mounted.current = false;
+ }}
/>
}
/>
@@ -118,8 +208,8 @@ type PageIndexArgs = {
downloadState: SUBMISSION_STATE;
setDownloadState: Dispatch>;
searchResults: SearchResult[];
- pageHeader: string;
nhsNumber: string;
+ onViewDocument: (document: SearchResult) => void;
};
const DocumentSearchResultsPageIndex = ({
submissionState,
@@ -127,49 +217,88 @@ const DocumentSearchResultsPageIndex = ({
searchResults,
nhsNumber,
setDownloadState,
+ onViewDocument,
}: PageIndexArgs): React.JSX.Element => {
- const pageHeader = 'Manage this Lloyd George record';
+ const [session] = useSessionContext();
+ const patientDetails = usePatient();
+ const navigate = useNavigate();
+
+ const role = session.auth?.role;
+
+ const canViewFiles =
+ session.auth?.role === REPOSITORY_ROLE.GP_ADMIN ||
+ session.auth?.role === REPOSITORY_ROLE.GP_CLINICAL;
+
+ const pageHeader = canViewFiles ? 'Lloyd George records' : 'Manage Lloyd George records';
useTitle({ pageTitle: pageHeader });
+ const SearchResults = (): React.JSX.Element => {
+ if (
+ submissionState === SUBMISSION_STATE.INITIAL ||
+ submissionState === SUBMISSION_STATE.PENDING
+ ) {
+ return ;
+ }
+
+ if (searchResults.length && nhsNumber) {
+ return (
+ <>
+
+
+ {role === REPOSITORY_ROLE.PCSE && (
+
+ )}
+ >
+ );
+ }
+
+ return (
+
+
+ There are no documents available for this patient.
+
+
+ );
+ };
+
+ if (!session.auth) {
+ navigate(routes.UNAUTHORISED);
+ return <>>;
+ }
+
return (
<>
- {pageHeader}
+
+
+
+ {pageHeader}
+
{(submissionState === SUBMISSION_STATE.FAILED ||
downloadState === SUBMISSION_STATE.FAILED) && }
-
-
- {submissionState === SUBMISSION_STATE.PENDING && (
-
- )}
- {submissionState === SUBMISSION_STATE.BLOCKED && (
-
- There are already files being uploaded for this patient, please try again in a
- few minutes.
-
- )}
+
+
+
+
+
- {submissionState === SUBMISSION_STATE.SUCCEEDED && (
- <>
- {searchResults.length && nhsNumber ? (
- <>
-
-
- >
- ) : (
-
-
- There are no documents available for this patient.
-
-
- )}
- >
- )}
+
{downloadState === SUBMISSION_STATE.FAILED && (
{
journey: 'update',
existingDocuments: [
{
- docType: OLD_DOCUMENT_TYPE.LLOYD_GEORGE,
+ docType: DOCUMENT_TYPE.LLOYD_GEORGE,
blob: mockBlob,
fileName: 'test.pdf',
documentId: 'doc-123',
@@ -280,7 +279,7 @@ describe('DocumentUploadPage', (): void => {
const testDocument = buildDocument(
testFile,
DOCUMENT_UPLOAD_STATE.SELECTED,
- OLD_DOCUMENT_TYPE.LLOYD_GEORGE,
+ DOCUMENT_TYPE.LLOYD_GEORGE,
);
const mockUploadSession = buildUploadSession([testDocument]);
@@ -297,7 +296,7 @@ describe('DocumentUploadPage', (): void => {
const testDocument = buildDocument(
testFile,
DOCUMENT_UPLOAD_STATE.UPLOADING,
- OLD_DOCUMENT_TYPE.LLOYD_GEORGE,
+ DOCUMENT_TYPE.LLOYD_GEORGE,
);
testDocument.ref = 'test-ref-123';
diff --git a/app/src/pages/documentUploadPage/DocumentUploadPage.tsx b/app/src/pages/documentUploadPage/DocumentUploadPage.tsx
index 89828a768..27f2cd09f 100644
--- a/app/src/pages/documentUploadPage/DocumentUploadPage.tsx
+++ b/app/src/pages/documentUploadPage/DocumentUploadPage.tsx
@@ -32,14 +32,9 @@ import {
useEnhancedNavigate,
} from '../../helpers/utils/urlManipulations';
import { routeChildren, routes } from '../../types/generic/routes';
-import {
- DocumentStatusResult,
- S3UploadFields,
- UploadSession,
-} from '../../types/generic/uploadResult';
+import { DocumentStatusResult, UploadSession } from '../../types/generic/uploadResult';
import {
DOCUMENT_STATUS,
- DOCUMENT_TYPE,
DOCUMENT_UPLOAD_STATE,
UploadDocument,
} from '../../types/pages/UploadDocumentsPage/types';
@@ -47,6 +42,7 @@ import documentTypesConfig from '../../config/documentTypesConfig.json';
import { Card } from 'nhsuk-react-components';
import { ReactComponent as RightCircleIcon } from '../../styles/right-chevron-circle.svg';
import PatientSummary from '../../components/generic/patientSummary/PatientSummary';
+import { DOCUMENT_TYPE } from '../../helpers/utils/documentType';
type LocationState = {
journey?: JourneyType;
@@ -208,11 +204,8 @@ const DocumentUploadPage = (): React.JSX.Element => {
const session: UploadSession = {};
documents.forEach((doc) => {
session[doc.id] = {
- url: 'https://example.com',
- fields: {
- key: `https://example.com/${uuidv4()}`,
- } as S3UploadFields,
- };
+ url: 'https://dusafgdswgfew4-staging-bulk-store.s3.eu-west-2.amazonaws.com/user_upload/9730153817/91b73c0f-b5b0-49f1-acbe-b0a5752dc3df?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=ASIAXYSUA44V5SE2IC6U%2F20251028%2Feu-west-2%2Fs3%2Faws4_request&X-Amz-Date=20251028T162320Z&X-Amz-Expires=1800&X-Amz-SignedHeaders=host&X-Amz-Security-Token=FwoGZXIvYXdzEBoaDCqX56UT2MdBQk7ztCLIAWXO7781OXoLLc3gJN9UQcAZlaoEhwJl5FQfKuJvn32DAPwYhbS80rb0JGIYmF8rIqj7TKbNOfaw4t%2Bq5NUO%2FEDQLxRbSpl8%2B078%2Ba9d2pY5XbPH3u6D0nW9mzNVREwg1%2Bt02HnWp9YLdREyDO4is9Fj5P3SQRh6DydzLx3in%2BZzzwVK8prxGG%2BBYRn5cQVOKcQCtAR7NMhHhTz9GeFQxU6X5YNalZdZdRJoFmdkxkpdoFeoIozs2Kg6plZhnqbWpFIrV3GvmYTDKPfbg8gGMi2c6f%2F9IJpIscXn0RfQZYA8lr02VHjBtez0LgzKcGVXYsE666uclkspOgBxpgo%3D&X-Amz-Signature=fdf6e3d7522ab4fe80156510d1318c430d4a44170fb98924cdc231117b5eafb8',
+ } as any;
});
return session;
diff --git a/app/src/pages/lloydGeorgeRecordPage/LloydGeorgeRecordPage.tsx b/app/src/pages/lloydGeorgeRecordPage/LloydGeorgeRecordPage.tsx
index e0279262b..68d8fb91e 100644
--- a/app/src/pages/lloydGeorgeRecordPage/LloydGeorgeRecordPage.tsx
+++ b/app/src/pages/lloydGeorgeRecordPage/LloydGeorgeRecordPage.tsx
@@ -21,6 +21,7 @@ import getLloydGeorgeRecord from '../../helpers/requests/getLloydGeorgeRecord';
import useBaseAPIHeaders from '../../helpers/hooks/useBaseAPIHeaders';
import useBaseAPIUrl from '../../helpers/hooks/useBaseAPIUrl';
import { getFormattedDatetime } from '../../helpers/utils/formatDatetime';
+import { DOCUMENT_TYPE } from '../../helpers/utils/documentType';
const LloydGeorgeRecordPage = (): React.JSX.Element => {
const [downloadStage, setDownloadStage] = useState(DOWNLOAD_STAGE.INITIAL);
@@ -138,8 +139,7 @@ const LloydGeorgeRecordPage = (): React.JSX.Element => {
element={
}
diff --git a/app/src/pages/patientResultPage/PatientResultPage.test.tsx b/app/src/pages/patientResultPage/PatientResultPage.test.tsx
index 1d2b5417d..aa80bd310 100644
--- a/app/src/pages/patientResultPage/PatientResultPage.test.tsx
+++ b/app/src/pages/patientResultPage/PatientResultPage.test.tsx
@@ -8,6 +8,7 @@ import useRole from '../../helpers/hooks/useRole';
import usePatient from '../../helpers/hooks/usePatient';
import { runAxeTest } from '../../helpers/test/axeTestHelper';
import { afterEach, beforeEach, describe, expect, it, vi, Mock } from 'vitest';
+import useConfig from '../../helpers/hooks/useConfig';
const mockedUseNavigate = vi.fn();
vi.mock('react-router-dom', () => ({
@@ -16,9 +17,11 @@ vi.mock('react-router-dom', () => ({
}));
vi.mock('../../helpers/hooks/useRole');
vi.mock('../../helpers/hooks/usePatient');
+vi.mock('../../helpers/hooks/useConfig');
const mockedUseRole = useRole as Mock;
const mockedUsePatient = usePatient as Mock;
+const mockedUseConfig = useConfig as Mock;
const PAGE_HEADER_TEXT = 'Patient details';
const PAGE_TEXT =
@@ -29,6 +32,15 @@ describe('PatientResultPage', () => {
beforeEach(() => {
import.meta.env.VITE_ENVIRONMENT = 'vitest';
mockedUseRole.mockReturnValue(REPOSITORY_ROLE.PCSE);
+ vi.mocked(useConfig).mockReturnValue({
+ featureFlags: {
+ uploadLambdaEnabled: true,
+ uploadArfWorkflowEnabled: false,
+ uploadLloydGeorgeWorkflowEnabled: true,
+ uploadDocumentIteration3Enabled: false,
+ },
+ mockLocal: {},
+ });
});
afterEach(() => {
vi.clearAllMocks();
@@ -247,7 +259,7 @@ describe('PatientResultPage', () => {
});
it.each([REPOSITORY_ROLE.GP_ADMIN, REPOSITORY_ROLE.GP_CLINICAL])(
- "navigates to Lloyd George Record page after user selects Active patient, when role is '%s'",
+ "navigates to Lloyd George Record page after user selects Active patient, when role is '%s' and uploadDocumentIteration3Enabled is false",
async (role) => {
const patient = buildPatientDetails({ active: true });
mockedUseRole.mockReturnValue(role);
@@ -263,6 +275,32 @@ describe('PatientResultPage', () => {
},
);
+ it.each([REPOSITORY_ROLE.GP_ADMIN, REPOSITORY_ROLE.GP_CLINICAL])(
+ "navigates to patient documents page after user selects Active patient, when role is '%s' and uploadDocumentIteration3Enabled is true",
+ async (role) => {
+ const patient = buildPatientDetails({ active: true });
+ mockedUseRole.mockReturnValue(role);
+ mockedUsePatient.mockReturnValue(patient);
+ mockedUseConfig.mockReturnValue({
+ featureFlags: {
+ uploadLambdaEnabled: true,
+ uploadArfWorkflowEnabled: false,
+ uploadLloydGeorgeWorkflowEnabled: true,
+ uploadDocumentIteration3Enabled: true,
+ },
+ mockLocal: {},
+ });
+
+ render( );
+
+ await userEvent.click(screen.getByRole('button', { name: CONFIRM_BUTTON_TEXT }));
+
+ await waitFor(() => {
+ expect(mockedUseNavigate).toHaveBeenCalledWith(routes.PATIENT_DOCUMENTS);
+ });
+ },
+ );
+
it('navigates to ARF Download page when user selects Verify patient, when role is PCSE', async () => {
const role = REPOSITORY_ROLE.PCSE;
mockedUseRole.mockReturnValue(role);
@@ -272,7 +310,7 @@ describe('PatientResultPage', () => {
await userEvent.click(screen.getByRole('button', { name: CONFIRM_BUTTON_TEXT }));
await waitFor(() => {
- expect(mockedUseNavigate).toHaveBeenCalledWith(routes.ARF_OVERVIEW);
+ expect(mockedUseNavigate).toHaveBeenCalledWith(routes.PATIENT_DOCUMENTS);
});
});
diff --git a/app/src/pages/patientResultPage/PatientResultPage.tsx b/app/src/pages/patientResultPage/PatientResultPage.tsx
index 6aaf8ce63..0709b302e 100644
--- a/app/src/pages/patientResultPage/PatientResultPage.tsx
+++ b/app/src/pages/patientResultPage/PatientResultPage.tsx
@@ -10,6 +10,7 @@ import useRole from '../../helpers/hooks/useRole';
import usePatient from '../../helpers/hooks/usePatient';
import useTitle from '../../helpers/hooks/useTitle';
import PatientSummary from '../../components/generic/patientSummary/PatientSummary';
+import useConfig from '../../helpers/hooks/useConfig';
const PatientResultPage = (): React.JSX.Element => {
const role = useRole();
@@ -18,11 +19,12 @@ const PatientResultPage = (): React.JSX.Element => {
const navigate = useNavigate();
const [inputError, setInputError] = useState('');
const { handleSubmit } = useForm();
+ const { featureFlags } = useConfig();
const submit = (): void => {
if (userIsPCSE) {
// Make PDS and Dynamo document store search request to download documents from patient
- navigate(routes.ARF_OVERVIEW);
+ navigate(routes.PATIENT_DOCUMENTS);
} else {
// Make PDS patient search request to upload documents to patient
if (typeof patientDetails?.active === 'undefined') {
@@ -36,13 +38,18 @@ const PatientResultPage = (): React.JSX.Element => {
}
if (patientDetails?.active) {
- navigate(routes.LLOYD_GEORGE);
+ navigate(
+ featureFlags.uploadDocumentIteration3Enabled
+ ? routes.PATIENT_DOCUMENTS
+ : routes.LLOYD_GEORGE,
+ );
return;
}
navigate(routes.SEARCH_PATIENT);
}
};
+
const showDeceasedWarning = patientDetails?.deceased && !userIsPCSE;
const showWarning =
patientDetails?.superseded || patientDetails?.restricted || showDeceasedWarning;
diff --git a/app/src/router/AppRouter.tsx b/app/src/router/AppRouter.tsx
index 4b72c0c51..4e0e73adc 100644
--- a/app/src/router/AppRouter.tsx
+++ b/app/src/router/AppRouter.tsx
@@ -9,7 +9,7 @@ import UnauthorisedPage from '../pages/unauthorisedPage/UnauthorisedPage';
import LogoutPage from '../pages/logoutPage/LogoutPage';
import PatientSearchPage from '../pages/patientSearchPage/PatientSearchPage';
import PatientResultPage from '../pages/patientResultPage/PatientResultPage';
-import ArfSearchResultsPage from '../pages/documentSearchResultsPage/DocumentSearchResultsPage';
+import PatientDocumentSearchResultsPage from '../pages/documentSearchResultsPage/DocumentSearchResultsPage';
import LloydGeorgeRecordPage from '../pages/lloydGeorgeRecordPage/LloydGeorgeRecordPage';
import AuthGuard from './guards/authGuard/AuthGuard';
import PatientGuard from './guards/patientGuard/PatientGuard';
@@ -46,8 +46,8 @@ const {
PRIVACY_POLICY,
LLOYD_GEORGE,
LLOYD_GEORGE_WILDCARD,
- ARF_OVERVIEW,
- ARF_OVERVIEW_WILDCARD,
+ PATIENT_DOCUMENTS,
+ PATIENT_DOCUMENTS_WILDCARD,
REPORT_DOWNLOAD,
REPORT_DOWNLOAD_WILDCARD,
PATIENT_ACCESS_AUDIT,
@@ -91,16 +91,20 @@ export const childRoutes = [
parent: LLOYD_GEORGE,
},
{
- route: routeChildren.ARF_DELETE,
- parent: ARF_OVERVIEW,
+ route: routeChildren.DOCUMENT_VIEW,
+ parent: PATIENT_DOCUMENTS,
},
{
- route: routeChildren.ARF_DELETE_CONFIRMATION,
- parent: ARF_OVERVIEW,
+ route: routeChildren.DOCUMENT_DELETE,
+ parent: PATIENT_DOCUMENTS,
},
{
- route: routeChildren.ARF_DELETE_COMPLETE,
- parent: ARF_OVERVIEW,
+ route: routeChildren.DOCUMENT_DELETE_CONFIRMATION,
+ parent: PATIENT_DOCUMENTS,
+ },
+ {
+ route: routeChildren.DOCUMENT_DELETE_COMPLETE,
+ parent: PATIENT_DOCUMENTS,
},
{
route: routeChildren.REPORT_DOWNLOAD_COMPLETE,
@@ -239,15 +243,13 @@ export const routeMap: Routes = {
type: ROUTE_TYPE.PATIENT,
unauthorized: [REPOSITORY_ROLE.PCSE],
},
- [ARF_OVERVIEW]: {
- page: ,
+ [PATIENT_DOCUMENTS]: {
+ page: ,
type: ROUTE_TYPE.PATIENT,
- unauthorized: [REPOSITORY_ROLE.GP_ADMIN, REPOSITORY_ROLE.GP_CLINICAL],
},
- [ARF_OVERVIEW_WILDCARD]: {
- page: ,
+ [PATIENT_DOCUMENTS_WILDCARD]: {
+ page: ,
type: ROUTE_TYPE.PATIENT,
- unauthorized: [REPOSITORY_ROLE.GP_ADMIN, REPOSITORY_ROLE.GP_CLINICAL],
},
[PATIENT_ACCESS_AUDIT]: {
page: ,
diff --git a/app/src/styles/App.scss b/app/src/styles/App.scss
index 2743eb059..83f7d9873 100644
--- a/app/src/styles/App.scss
+++ b/app/src/styles/App.scss
@@ -301,9 +301,6 @@ $hunit: '%';
margin-bottom: 0;
&-content {
- @extend .nhsuk-body-s;
- position: relative;
-
&-label {
font-weight: 700;
font-size: 1.5rem;
@@ -1311,3 +1308,5 @@ progress:not(.continuous-progress-bar) {
}
}
}
+
+@import '../components/blocks/_patientDocuments/documentSearchResults/DocumentSearchResults.scss'
\ No newline at end of file
diff --git a/app/src/types/blocks/lloydGeorgeActions.test.ts b/app/src/types/blocks/lloydGeorgeActions.test.ts
index 654f332bd..29c070d5b 100644
--- a/app/src/types/blocks/lloydGeorgeActions.test.ts
+++ b/app/src/types/blocks/lloydGeorgeActions.test.ts
@@ -10,12 +10,12 @@ describe('getUserRecordActionLinks', () => {
const expectedOutput = expect.arrayContaining([
expect.objectContaining({
label: 'Remove record',
- key: 'delete-all-files-link',
+ key: 'delete-files-link',
type: RECORD_ACTION.UPDATE,
}),
expect.objectContaining({
label: 'Download record',
- key: 'download-all-files-link',
+ key: 'download-files-link',
type: RECORD_ACTION.DOWNLOAD,
}),
]);
diff --git a/app/src/types/blocks/lloydGeorgeActions.ts b/app/src/types/blocks/lloydGeorgeActions.ts
index 342c64142..94780e773 100644
--- a/app/src/types/blocks/lloydGeorgeActions.ts
+++ b/app/src/types/blocks/lloydGeorgeActions.ts
@@ -5,6 +5,7 @@ import { LG_RECORD_STAGE } from './lloydGeorgeStages';
export enum RECORD_ACTION {
UPDATE = 0,
DOWNLOAD = 1,
+ DELETE = 2,
}
type ActionRoute = routeChildren | routes;
@@ -23,7 +24,7 @@ export type LGRecordActionLink = {
export const lloydGeorgeRecordLinks: Array = [
{
label: 'Remove record',
- key: 'delete-all-files-link',
+ key: 'delete-files-link',
type: RECORD_ACTION.UPDATE,
unauthorised: [REPOSITORY_ROLE.GP_CLINICAL],
href: routeChildren.LLOYD_GEORGE_DELETE,
@@ -31,7 +32,7 @@ export const lloydGeorgeRecordLinks: Array = [
},
{
label: 'Download record',
- key: 'download-all-files-link',
+ key: 'download-files-link',
type: RECORD_ACTION.DOWNLOAD,
unauthorised: [REPOSITORY_ROLE.GP_CLINICAL],
href: routeChildren.LLOYD_GEORGE_DOWNLOAD,
diff --git a/app/src/types/generic/endpoints.ts b/app/src/types/generic/endpoints.ts
index 38d653731..37c311398 100644
--- a/app/src/types/generic/endpoints.ts
+++ b/app/src/types/generic/endpoints.ts
@@ -6,7 +6,7 @@ export enum endpoints {
PATIENT_SEARCH = '/SearchPatient',
DOCUMENT_SEARCH = '/SearchDocumentReferences',
- DOCUMENT_UPLOAD = '/DocumentReference',
+ DOCUMENT_REFERENCE = '/DocumentReference',
DOCUMENT_PRESIGN = '/DocumentManifest',
LLOYDGEORGE_STITCH = '/LloydGeorgeStitch',
diff --git a/app/src/types/generic/routes.ts b/app/src/types/generic/routes.ts
index 668dae40d..157916363 100644
--- a/app/src/types/generic/routes.ts
+++ b/app/src/types/generic/routes.ts
@@ -17,8 +17,8 @@ export enum routes {
VERIFY_PATIENT = '/patient/verify',
LLOYD_GEORGE = '/patient/lloyd-george-record',
LLOYD_GEORGE_WILDCARD = '/patient/lloyd-george-record/*',
- ARF_OVERVIEW = '/patient/arf',
- ARF_OVERVIEW_WILDCARD = '/patient/arf/*',
+ PATIENT_DOCUMENTS = '/patient/documents',
+ PATIENT_DOCUMENTS_WILDCARD = '/patient/documents/*',
FEEDBACK_CONFIRMATION = '/feedback/confirmation',
REPORT_DOWNLOAD = '/create-report',
REPORT_DOWNLOAD_WILDCARD = '/create-report/*',
@@ -27,6 +27,7 @@ export enum routes {
DOCUMENT_UPLOAD = '/patient/document-upload',
DOCUMENT_UPLOAD_WILDCARD = '/patient/document-upload/*',
+
MOCK_LOGIN = 'Auth/MockLogin',
}
@@ -38,9 +39,6 @@ export enum routeChildren {
LLOYD_GEORGE_DELETE = '/patient/lloyd-george-record/delete',
LLOYD_GEORGE_DELETE_CONFIRMATION = '/patient/lloyd-george-record/delete/confirmation',
LLOYD_GEORGE_DELETE_COMPLETE = '/patient/lloyd-george-record/delete/complete',
- ARF_DELETE = '/patient/arf/delete',
- ARF_DELETE_CONFIRMATION = '/patient/arf/delete/confirmation',
- ARF_DELETE_COMPLETE = '/patient/arf/delete/complete',
REPORT_DOWNLOAD_COMPLETE = '/create-report/complete',
PATIENT_ACCESS_AUDIT_DECEASED = '/patient/access-audit/deceased',
@@ -52,6 +50,11 @@ export enum routeChildren {
DOCUMENT_UPLOAD_COMPLETED = '/patient/document-upload/completed',
DOCUMENT_UPLOAD_INFECTED = '/patient/document-upload/infected',
DOCUMENT_UPLOAD_FILE_ERRORS = '/patient/document-upload/file-errors',
+
+ DOCUMENT_VIEW = '/patient/documents/view',
+ DOCUMENT_DELETE = '/patient/documents/delete',
+ DOCUMENT_DELETE_CONFIRMATION = '/patient/documents/delete/confirmation',
+ DOCUMENT_DELETE_COMPLETE = '/patient/documents/delete/complete',
}
export enum ROUTE_TYPE {
diff --git a/app/src/types/generic/searchResult.ts b/app/src/types/generic/searchResult.ts
index d9a6d593c..20c6a7dc6 100644
--- a/app/src/types/generic/searchResult.ts
+++ b/app/src/types/generic/searchResult.ts
@@ -1,3 +1,5 @@
+import { DOCUMENT_TYPE } from '../../helpers/utils/documentType';
+
export type SearchResult = {
fileName: string;
created: string;
@@ -5,4 +7,6 @@ export type SearchResult = {
id: string;
fileSize: number;
version: string;
+ documentSnomedCodeType: DOCUMENT_TYPE;
+ contentType: string;
};
diff --git a/app/src/types/pages/UploadDocumentsPage/types.ts b/app/src/types/pages/UploadDocumentsPage/types.ts
index 1ee793639..58b232f2b 100644
--- a/app/src/types/pages/UploadDocumentsPage/types.ts
+++ b/app/src/types/pages/UploadDocumentsPage/types.ts
@@ -1,5 +1,6 @@
import type { Dispatch, FormEvent, SetStateAction } from 'react';
import { UPLOAD_FILE_ERROR_TYPE } from '../../../helpers/utils/fileUploadErrorMessages';
+import { DOCUMENT_TYPE } from '../../../helpers/utils/documentType';
export type SetUploadStage = Dispatch>;
export type SetUploadDocuments = Dispatch>>;
@@ -9,12 +10,6 @@ export enum UPLOAD_STAGE {
Complete = 2,
}
-export enum DOCUMENT_TYPE {
- LLOYD_GEORGE = 'LG',
- ARF = 'ARF',
- ALL = 'LG,ARF',
-}
-
export enum DOCUMENT_UPLOAD_STATE {
SELECTED = 'SELECTED',
UPLOADING = 'UPLOADING',
diff --git a/app/src/types/pages/documentSearchResultsPage/types.ts b/app/src/types/pages/documentSearchResultsPage/types.ts
index 298be815a..74407a0d3 100644
--- a/app/src/types/pages/documentSearchResultsPage/types.ts
+++ b/app/src/types/pages/documentSearchResultsPage/types.ts
@@ -1,3 +1,5 @@
+import { SearchResult } from '../../generic/searchResult';
+
export enum SUBMISSION_STATE {
INITIAL = 'INITIAL',
PENDING = 'PENDING',
@@ -12,3 +14,8 @@ export enum SEARCH_AND_DOWNLOAD_STATE {
SEARCH_SUCCEEDED = 'SEARCH_SUCCEEDED',
DOWNLOAD_SELECTED = 'DOWNLOAD_SELECTED',
}
+
+export type DocumentReference = SearchResult & {
+ url?: string | null;
+ isPdf?: boolean;
+};
diff --git a/lambdas/enums/supported_document_types.py b/lambdas/enums/supported_document_types.py
index 5eb393620..38412c770 100644
--- a/lambdas/enums/supported_document_types.py
+++ b/lambdas/enums/supported_document_types.py
@@ -1,6 +1,7 @@
import os
from enum import StrEnum
+from enums.snomed_codes import SnomedCodes
from utils.audit_logging_setup import LoggingService
logger = LoggingService(__name__)
@@ -8,7 +9,7 @@
class SupportedDocumentTypes(StrEnum):
ARF = "ARF"
- LG = "LG"
+ LG = SnomedCodes.LLOYD_GEORGE.value.code
@staticmethod
def list():
@@ -19,11 +20,11 @@ def get_dynamodb_table_name(self) -> str:
Get the dynamodb table name related to a specific doc_type
example usage:
- SupportedDocumentTypes.ARF.get_dynamodb_table_name()
- (returns "ndr*_DocumentReferenceMetadata")
+ SupportedDocumentTypes.LG.get_dynamodb_table_name()
+ (returns "ndr*_LloydGeorgeDocumentReferenceMetadata")
result:
- "ndr*_DocumentReferenceMetadata"
+ "ndr*_LloydGeorgeDocumentReferenceMetadata"
Eventually we could replace all os.environ["XXX_DYNAMODB_NAME"] calls with this method,
so that the logic of resolving table names are controlled in one place.
diff --git a/lambdas/services/document_reference_search_service.py b/lambdas/services/document_reference_search_service.py
index e3ea9ab75..e039ad834 100644
--- a/lambdas/services/document_reference_search_service.py
+++ b/lambdas/services/document_reference_search_service.py
@@ -9,6 +9,7 @@
from enums.metadata_field_names import DocumentReferenceMetadataFields
from enums.mtls import MtlsCommonNames
from enums.snomed_codes import SnomedCodes
+from enums.supported_document_types import SupportedDocumentTypes
from models.document_reference import DocumentReference
from models.fhir.R4.bundle import Bundle, BundleEntry
from models.fhir.R4.fhir_document_reference import Attachment, DocumentReferenceInfo
@@ -183,6 +184,8 @@ def _build_document_model(self, document: DocumentReference) -> dict:
"virus_scanner_result",
"file_size",
"version",
+ "content_type",
+ "document_snomed_code_type",
},
)
return document_formatted
diff --git a/lambdas/tests/unit/handlers/conftest.py b/lambdas/tests/unit/handlers/conftest.py
index f5f29ab3d..32f659f10 100755
--- a/lambdas/tests/unit/handlers/conftest.py
+++ b/lambdas/tests/unit/handlers/conftest.py
@@ -37,7 +37,7 @@ def valid_id_post_event_with_auth_header():
def valid_id_and_both_doctype_event():
api_gateway_proxy_event = {
"httpMethod": "GET",
- "queryStringParameters": {"patientId": "9000000009", "docType": "LG,ARF"},
+ "queryStringParameters": {"patientId": "9000000009", "docType": "16521000000101,ARF"},
}
return api_gateway_proxy_event
@@ -55,7 +55,7 @@ def valid_id_and_arf_doctype_event():
def valid_id_and_lg_doctype_event():
api_gateway_proxy_event = {
"httpMethod": "GET",
- "queryStringParameters": {"patientId": "9000000009", "docType": "LG"},
+ "queryStringParameters": {"patientId": "9000000009", "docType": "16521000000101"},
}
return api_gateway_proxy_event
@@ -64,7 +64,7 @@ def valid_id_and_lg_doctype_event():
def valid_id_and_lg_doctype_delete_event():
api_gateway_proxy_event = {
"httpMethod": "DELETE",
- "queryStringParameters": {"patientId": "9000000009", "docType": "LG"},
+ "queryStringParameters": {"patientId": "9000000009", "docType": "16521000000101"},
}
return api_gateway_proxy_event
@@ -131,6 +131,24 @@ def mock_upload_document_iteration2_disabled(mocker):
]
yield mock_function
+@pytest.fixture
+def mock_upload_document_iteration3_enabled(mocker):
+ mock_function = mocker.patch.object(FeatureFlagService, "get_feature_flags_by_flag")
+ mock_function.side_effect = [
+ {"uploadLambdaEnabled": True},
+ {"uploadDocumentIteration3Enabled": True},
+ ]
+ yield mock_function
+
+@pytest.fixture
+def mock_upload_document_iteration3_disabled(mocker):
+ mock_function = mocker.patch.object(FeatureFlagService, "get_feature_flags_by_flag")
+ mock_function.side_effect = [
+ {"uploadLambdaEnabled": True},
+ {"uploadDocumentIteration3Enabled": False},
+ ]
+ yield mock_function
+
@pytest.fixture
def mock_validation_strict_disabled(mocker):
diff --git a/lambdas/tests/unit/handlers/test_delete_document_reference_handler.py b/lambdas/tests/unit/handlers/test_delete_document_reference_handler.py
index 0933842da..520060a61 100644
--- a/lambdas/tests/unit/handlers/test_delete_document_reference_handler.py
+++ b/lambdas/tests/unit/handlers/test_delete_document_reference_handler.py
@@ -26,19 +26,19 @@ def mock_handle_delete(mocker):
[
{
"httpMethod": "GET",
- "queryStringParameters": {"patientId": "9000000009", "docType": "LG,ARF"},
+ "queryStringParameters": {"patientId": "9000000009", "docType": "16521000000101,ARF"},
},
{
"httpMethod": "GET",
- "queryStringParameters": {"patientId": "9000000009", "docType": "ARF,LG"},
+ "queryStringParameters": {"patientId": "9000000009", "docType": "ARF,16521000000101"},
},
{
"httpMethod": "GET",
- "queryStringParameters": {"patientId": "9000000009", "docType": "LG , ARF"},
+ "queryStringParameters": {"patientId": "9000000009", "docType": "16521000000101 , ARF"},
},
{
"httpMethod": "GET",
- "queryStringParameters": {"patientId": "9000000009", "docType": "ARF, LG"},
+ "queryStringParameters": {"patientId": "9000000009", "docType": "ARF, 16521000000101"},
},
],
)
diff --git a/lambdas/tests/unit/handlers/test_document_manifest_job_handler.py b/lambdas/tests/unit/handlers/test_document_manifest_job_handler.py
index 6691f99f2..7c97d3d3b 100644
--- a/lambdas/tests/unit/handlers/test_document_manifest_job_handler.py
+++ b/lambdas/tests/unit/handlers/test_document_manifest_job_handler.py
@@ -21,7 +21,7 @@
def valid_id_and_both_doctype_post_event():
api_gateway_proxy_event = {
"httpMethod": "POST",
- "queryStringParameters": {"patientId": TEST_NHS_NUMBER, "docType": "LG,ARF"},
+ "queryStringParameters": {"patientId": TEST_NHS_NUMBER, "docType": "16521000000101,ARF"},
"multiValueQueryStringParameters": {},
}
return api_gateway_proxy_event
@@ -41,7 +41,7 @@ def valid_id_and_arf_doctype_post_event():
def valid_id_and_lg_doctype_post_event():
api_gateway_proxy_event = {
"httpMethod": "POST",
- "queryStringParameters": {"patientId": TEST_NHS_NUMBER, "docType": "LG"},
+ "queryStringParameters": {"patientId": TEST_NHS_NUMBER, "docType": "16521000000101"},
"multiValueQueryStringParameters": {},
}
return api_gateway_proxy_event
@@ -63,7 +63,7 @@ def valid_id_and_lg_doctype_post_event_with_doc_references():
"httpMethod": "POST",
"queryStringParameters": {
"patientId": TEST_NHS_NUMBER,
- "docType": "LG",
+ "docType": "16521000000101",
},
"multiValueQueryStringParameters": {
"docReferences": ["test-doc-ref", "test-doc-ref2"],
diff --git a/lambdas/tests/unit/helpers/data/create_document_reference.py b/lambdas/tests/unit/helpers/data/create_document_reference.py
index 7abcfe915..cfdd20548 100644
--- a/lambdas/tests/unit/helpers/data/create_document_reference.py
+++ b/lambdas/tests/unit/helpers/data/create_document_reference.py
@@ -29,19 +29,19 @@
{
"fileName": f"1of3_Lloyd_George_Record_[Joe Bloggs]_[{TEST_NHS_NUMBER}]_[25-12-2019].pdf",
"contentType": "application/pdf",
- "docType": "LG",
+ "docType": "16521000000101",
"clientId": "uuid4",
},
{
"fileName": f"2of3_Lloyd_George_Record_[Joe Bloggs]_[{TEST_NHS_NUMBER}]_[25-12-2019].pdf",
"contentType": "application/pdf",
- "docType": "LG",
+ "docType": "16521000000101",
"clientId": "uuid5",
},
{
"fileName": f"3of3_Lloyd_George_Record_[Joe Bloggs]_[{TEST_NHS_NUMBER}]_[25-12-2019].pdf",
"contentType": "application/pdf",
- "docType": "LG",
+ "docType": "16521000000101",
"clientId": "uuid6",
},
]
@@ -53,21 +53,21 @@
{
"fileName": f"1of3_Lloyd_George_Record_[Joe Bloggs]_[{TEST_NHS_NUMBER}]_[25-12-2019].pdf",
"contentType": "application/pdf",
- "docType": "LG",
+ "docType": "16521000000101",
"clientId": "uuid1",
"versionId": "1"
},
{
"fileName": f"2of3_Lloyd_George_Record_[Joe Bloggs]_[{TEST_NHS_NUMBER}]_[25-12-2019].pdf",
"contentType": "application/pdf",
- "docType": "LG",
+ "docType": "16521000000101",
"clientId": "uuid2",
"versionId": "2"
},
{
"fileName": f"3of3_Lloyd_George_Record_[Joe Bloggs]_[{TEST_NHS_NUMBER}]_[25-12-2019].pdf",
"contentType": "application/pdf",
- "docType": "LG",
+ "docType": "16521000000101",
"clientId": "uuid3",
"versionId": "3"
},
@@ -76,7 +76,7 @@
LG_FILE = {
"fileName": f"1of1_Lloyd_George_Record_[Joe Bloggs]_[{TEST_NHS_NUMBER}]_[25-12-2019].pdf",
"contentType": "application/pdf",
- "docType": "LG",
+ "docType": "16521000000101",
"clientId": "uuid1",
"versionId": "1"
}
@@ -85,7 +85,7 @@
UploadRequestDocument(
file_name=f"{i}of3_Lloyd_George_Record_[Joe Bloggs]_[{TEST_NHS_NUMBER}]_[25-12-2019].pdf",
content_type="application/pdf",
- doc_type="LG",
+ doc_type="16521000000101",
client_id=f"uuid{i}",
version_id=f"{i}"
)
@@ -115,7 +115,7 @@
{
"fileName": f"1of1_Lloyd_George_Record_[Joe Bloggs]_[{TEST_NHS_NUMBER}]_[25-12-2019].pdf",
"contentType": "text/plain",
- "docType": "LG",
+ "docType": "16521000000101",
"clientId": "uuid1",
}
]
@@ -134,7 +134,7 @@
{
"fileName": f"1of1_BAD_NAME_[Joe Bloggs]_[{TEST_NHS_NUMBER}]_[25-12-2019].pdf",
"contentType": "application/pdf",
- "docType": "LG",
+ "docType": "16521000000101",
"clientId": "uuid1",
}
]
@@ -153,7 +153,7 @@
{
"fileName": f"1of3_Lloyd_George_Record_[Joe Bloggs]_[{TEST_NHS_NUMBER}]_[25-12-2019].pdf",
"contentType": "application/pdf",
- "docType": "LG",
+ "docType": "16521000000101",
"clientId": "uuid1",
}
]
@@ -172,13 +172,13 @@
{
"fileName": f"1of2_Lloyd_George_Record_[Joe Bloggs]_[{TEST_NHS_NUMBER}]_[25-12-2019].pdf",
"contentType": "application/pdf",
- "docType": "LG",
+ "docType": "16521000000101",
"clientId": "uuid1",
},
{
"fileName": f"1of2_Lloyd_George_Record_[Joe Bloggs]_[{TEST_NHS_NUMBER}]_[25-12-2019].pdf",
"contentType": "application/pdf",
- "docType": "LG",
+ "docType": "16521000000101",
"clientId": "uuid2",
},
]
diff --git a/lambdas/tests/unit/helpers/data/dynamo/dynamo_responses.py b/lambdas/tests/unit/helpers/data/dynamo/dynamo_responses.py
index 423669fee..b957c819f 100755
--- a/lambdas/tests/unit/helpers/data/dynamo/dynamo_responses.py
+++ b/lambdas/tests/unit/helpers/data/dynamo/dynamo_responses.py
@@ -13,6 +13,7 @@
"Uploaded": "True",
"Uploading": "False",
"LastUpdated": 1704110400, # Timestamp: 2024-01-01T12:00:00
+ "DocumentSnomedCodeType": "16521000000101",
},
{
"ID": "4d8683b9-1665-40d2-8499-6e8302d507ff",
@@ -27,6 +28,7 @@
"Uploaded": "True",
"Uploading": "False",
"LastUpdated": 1704110400, # Timestamp: 2024-01-01T12:00:00
+ "DocumentSnomedCodeType": "16521000000101",
},
{
"ID": "5d8683b9-1665-40d2-8499-6e8302d507ff",
@@ -41,6 +43,7 @@
"Uploaded": "True",
"Uploading": "False",
"LastUpdated": 1704110400, # Timestamp: 2024-01-01T12:00:00
+ "DocumentSnomedCodeType": "16521000000101",
},
],
"Count": 3,
diff --git a/lambdas/tests/unit/helpers/data/upload_confirm_result.py b/lambdas/tests/unit/helpers/data/upload_confirm_result.py
index 48e993f5f..658a35dbe 100644
--- a/lambdas/tests/unit/helpers/data/upload_confirm_result.py
+++ b/lambdas/tests/unit/helpers/data/upload_confirm_result.py
@@ -4,11 +4,11 @@
MOCK_ARF_DOCUMENTS = {"ARF": [TEST_FILE_KEY, TEST_FILE_KEY]}
MOCK_ARF_DOCUMENT_REFERENCES = [TEST_FILE_KEY, TEST_FILE_KEY]
-MOCK_LG_SINGLE_DOCUMENT = {"LG": [TEST_FILE_KEY]}
+MOCK_LG_SINGLE_DOCUMENT = {"16521000000101": [TEST_FILE_KEY]}
MOCK_LG_SINGLE_DOCUMENT_REFERENCES = [TEST_FILE_KEY]
-MOCK_LG_DOCUMENTS = {"LG": [TEST_FILE_KEY, TEST_FILE_KEY]}
+MOCK_LG_DOCUMENTS = {"16521000000101": [TEST_FILE_KEY, TEST_FILE_KEY]}
MOCK_LG_DOCUMENT_REFERENCES = [TEST_FILE_KEY, TEST_FILE_KEY]
-MOCK_BOTH_DOC_TYPES = {"ARF": [TEST_FILE_KEY, TEST_FILE_KEY], "LG": [TEST_FILE_KEY]}
+MOCK_BOTH_DOC_TYPES = {"ARF": [TEST_FILE_KEY, TEST_FILE_KEY], "16521000000101": [TEST_FILE_KEY]}
MOCK_NO_DOC_TYPE = {"": [TEST_FILE_KEY]}
MOCK_VALID_LG_EVENT_BODY = {
diff --git a/lambdas/tests/unit/services/test_document_reference_search_service.py b/lambdas/tests/unit/services/test_document_reference_search_service.py
index 4a63f2cd7..096d3f83c 100644
--- a/lambdas/tests/unit/services/test_document_reference_search_service.py
+++ b/lambdas/tests/unit/services/test_document_reference_search_service.py
@@ -32,6 +32,8 @@
"id": "3d8683b9-1665-40d2-8499-6e8302d507ff",
"fileSize": MOCK_FILE_SIZE,
"version": "1",
+ "contentType": "application/pdf",
+ "documentSnomedCodeType": SnomedCodes.LLOYD_GEORGE.value.code,
}
diff --git a/lambdas/tests/unit/utils/decorators/conftest.py b/lambdas/tests/unit/utils/decorators/conftest.py
index 15f820120..6f26a4efd 100644
--- a/lambdas/tests/unit/utils/decorators/conftest.py
+++ b/lambdas/tests/unit/utils/decorators/conftest.py
@@ -49,7 +49,7 @@ def valid_id_and_arf_doctype_event():
def valid_id_and_lg_doctype_event():
api_gateway_proxy_event = {
"httpMethod": "GET",
- "queryStringParameters": {"patientId": "9000000009", "docType": "LG"},
+ "queryStringParameters": {"patientId": "9000000009", "docType": "16521000000101"},
}
return api_gateway_proxy_event
@@ -58,7 +58,7 @@ def valid_id_and_lg_doctype_event():
def valid_id_and_both_doctype_event():
api_gateway_proxy_event = {
"httpMethod": "GET",
- "queryStringParameters": {"patientId": "9000000009", "docType": "LG,ARF"},
+ "queryStringParameters": {"patientId": "9000000009", "docType": "16521000000101,ARF"},
}
return api_gateway_proxy_event
diff --git a/lambdas/tests/unit/utils/test_document_type_utils.py b/lambdas/tests/unit/utils/test_document_type_utils.py
index f0f337cb3..864ca1982 100644
--- a/lambdas/tests/unit/utils/test_document_type_utils.py
+++ b/lambdas/tests/unit/utils/test_document_type_utils.py
@@ -6,10 +6,10 @@
@pytest.mark.parametrize(
"value",
[
- "LG, ARF",
- "ARF,LG",
- " ARF, LG",
- "LG , ARF",
+ "16521000000101, ARF",
+ "ARF,16521000000101",
+ " ARF, 16521000000101",
+ "16521000000101 , ARF",
],
)
def test_extract_document_type_both(value):
@@ -23,8 +23,8 @@ def test_extract_document_type_both(value):
@pytest.mark.parametrize(
"value",
[
- "LG ",
- " LG",
+ "16521000000101 ",
+ " 16521000000101",
],
)
def test_extract_document_type_lg(value):
@@ -56,11 +56,11 @@ def test_extract_document_type_arf(value):
("ARF", [SupportedDocumentTypes.ARF]),
("ARF ", [SupportedDocumentTypes.ARF]),
(" ARF", [SupportedDocumentTypes.ARF]),
- ("LG", [SupportedDocumentTypes.LG]),
- ("LG ", [SupportedDocumentTypes.LG]),
- (" LG", [SupportedDocumentTypes.LG]),
- (" ARF, LG ", [SupportedDocumentTypes.ARF, SupportedDocumentTypes.LG]),
- (" LG , ARF ", [SupportedDocumentTypes.LG, SupportedDocumentTypes.ARF]),
+ ("16521000000101", [SupportedDocumentTypes.LG]),
+ ("16521000000101 ", [SupportedDocumentTypes.LG]),
+ (" 16521000000101", [SupportedDocumentTypes.LG]),
+ (" ARF, 16521000000101 ", [SupportedDocumentTypes.ARF, SupportedDocumentTypes.LG]),
+ (" 16521000000101 , ARF ", [SupportedDocumentTypes.LG, SupportedDocumentTypes.ARF]),
],
)
def test_extract_document_type_as_enum(value, expected):