@tebokaroa/openmrs-esm-patient-notes-app 12.1.0-custom.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -0
- package/jest.config.js +3 -0
- package/package.json +56 -0
- package/rspack.config.js +1 -0
- package/src/config-schema.ts +22 -0
- package/src/dashboard.meta.ts +7 -0
- package/src/declarations.d.ts +4 -0
- package/src/index.ts +34 -0
- package/src/notes/notes-overview.extension.tsx +74 -0
- package/src/notes/notes-overview.scss +40 -0
- package/src/notes/notes-overview.test.tsx +100 -0
- package/src/notes/paginated-notes.component.tsx +182 -0
- package/src/notes/visit-note-config-schema.ts +38 -0
- package/src/notes/visit-notes-form.scss +228 -0
- package/src/notes/visit-notes-form.test.tsx +494 -0
- package/src/notes/visit-notes-form.workspace.tsx +943 -0
- package/src/notes/visit-notes.resource.ts +113 -0
- package/src/routes.json +32 -0
- package/src/types/index.ts +202 -0
- package/src/visit-note-action-button.extension.tsx +28 -0
- package/src/visit-note-action-button.test.tsx +41 -0
- package/translations/am.json +39 -0
- package/translations/ar.json +39 -0
- package/translations/ar_SY.json +39 -0
- package/translations/bn.json +39 -0
- package/translations/cs.json +39 -0
- package/translations/de.json +39 -0
- package/translations/en.json +41 -0
- package/translations/en_US.json +39 -0
- package/translations/es.json +39 -0
- package/translations/es_MX.json +39 -0
- package/translations/fr.json +39 -0
- package/translations/he.json +39 -0
- package/translations/hi.json +39 -0
- package/translations/hi_IN.json +39 -0
- package/translations/id.json +39 -0
- package/translations/it.json +39 -0
- package/translations/ka.json +39 -0
- package/translations/km.json +39 -0
- package/translations/ku.json +39 -0
- package/translations/ky.json +39 -0
- package/translations/lg.json +39 -0
- package/translations/ne.json +39 -0
- package/translations/pl.json +39 -0
- package/translations/pt.json +39 -0
- package/translations/pt_BR.json +39 -0
- package/translations/qu.json +39 -0
- package/translations/ro_RO.json +39 -0
- package/translations/ru_RU.json +39 -0
- package/translations/si.json +39 -0
- package/translations/sq.json +39 -0
- package/translations/sw.json +39 -0
- package/translations/sw_KE.json +39 -0
- package/translations/tr.json +39 -0
- package/translations/tr_TR.json +39 -0
- package/translations/uk.json +39 -0
- package/translations/uz.json +39 -0
- package/translations/uz@Latn.json +39 -0
- package/translations/uz_UZ.json +39 -0
- package/translations/vi.json +39 -0
- package/translations/zh.json +39 -0
- package/translations/zh_CN.json +39 -0
- package/translations/zh_TW.json +39 -0
- package/tsconfig.json +4 -0
package/README.md
ADDED
package/jest.config.js
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@tebokaroa/openmrs-esm-patient-notes-app",
|
|
3
|
+
"version": "12.1.0-custom.1",
|
|
4
|
+
"license": "MPL-2.0",
|
|
5
|
+
"description": "Patient notes microfrontend for the OpenMRS SPA",
|
|
6
|
+
"browser": "dist/openmrs-esm-patient-notes-app.js",
|
|
7
|
+
"main": "src/index.ts",
|
|
8
|
+
"source": true,
|
|
9
|
+
"scripts": {
|
|
10
|
+
"start": "openmrs develop",
|
|
11
|
+
"serve": "rspack serve --mode=development",
|
|
12
|
+
"debug": "npm run serve",
|
|
13
|
+
"build": "rspack --mode=production",
|
|
14
|
+
"analyze": "rspack --mode=production --env analyze=true",
|
|
15
|
+
"lint": "cross-env eslint src --ext tsx,ts --fix --max-warnings=0",
|
|
16
|
+
"test": "cross-env TZ=UTC jest --config jest.config.js --verbose false --passWithNoTests --color",
|
|
17
|
+
"test:watch": "cross-env TZ=UTC jest --watch --config jest.config.js --color",
|
|
18
|
+
"coverage": "yarn test --coverage",
|
|
19
|
+
"typescript": "tsc",
|
|
20
|
+
"extract-translations": "i18next 'src/**/*.component.tsx' 'src/**/*.modal.tsx' 'src/**/*.extension.tsx' 'src/**/*.workspace.tsx' 'src/**/*.hook.tsx' 'src/index.ts' --config ../../tools/i18next-parser.config.js"
|
|
21
|
+
},
|
|
22
|
+
"browserslist": [
|
|
23
|
+
"extends browserslist-config-openmrs"
|
|
24
|
+
],
|
|
25
|
+
"keywords": [
|
|
26
|
+
"openmrs"
|
|
27
|
+
],
|
|
28
|
+
"homepage": "https://github.com/openmrs/openmrs-esm-patient-chart#readme",
|
|
29
|
+
"publishConfig": {
|
|
30
|
+
"access": "public"
|
|
31
|
+
},
|
|
32
|
+
"repository": {
|
|
33
|
+
"type": "git",
|
|
34
|
+
"url": "git+https://github.com/openmrs/openmrs-esm-patient-chart.git"
|
|
35
|
+
},
|
|
36
|
+
"bugs": {
|
|
37
|
+
"url": "https://github.com/openmrs/openmrs-esm-patient-chart/issues"
|
|
38
|
+
},
|
|
39
|
+
"dependencies": {
|
|
40
|
+
"lodash-es": "^4.17.23"
|
|
41
|
+
},
|
|
42
|
+
"peerDependencies": {
|
|
43
|
+
"@openmrs/esm-framework": "9.x",
|
|
44
|
+
"@openmrs/esm-patient-common-lib": "12.x",
|
|
45
|
+
"dayjs": "1.x",
|
|
46
|
+
"react": "18.x",
|
|
47
|
+
"react-i18next": "16.x",
|
|
48
|
+
"react-router-dom": "6.x",
|
|
49
|
+
"rxjs": "6.x",
|
|
50
|
+
"swr": "2.x"
|
|
51
|
+
},
|
|
52
|
+
"devDependencies": {
|
|
53
|
+
"@openmrs/esm-patient-common-lib": "workspace:*",
|
|
54
|
+
"openmrs": "next"
|
|
55
|
+
}
|
|
56
|
+
}
|
package/rspack.config.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
module.exports = require('openmrs/default-rspack-config');
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { Type } from '@openmrs/esm-framework';
|
|
2
|
+
import notesConfigSchema, { type VisitNoteConfigObject } from './notes/visit-note-config-schema';
|
|
3
|
+
|
|
4
|
+
export const configSchema = {
|
|
5
|
+
diagnosisConceptClass: {
|
|
6
|
+
_type: Type.UUID,
|
|
7
|
+
_default: '8d4918b0-c2cc-11de-8d13-0010c6dffd0f',
|
|
8
|
+
_description: 'The concept class UUID for diagnoses',
|
|
9
|
+
},
|
|
10
|
+
isPrimaryDiagnosisRequired: {
|
|
11
|
+
_type: Type.Boolean,
|
|
12
|
+
_default: true,
|
|
13
|
+
_description: 'Indicates whether a primary diagnosis is required when submitting a visit note',
|
|
14
|
+
},
|
|
15
|
+
visitNoteConfig: notesConfigSchema,
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export interface ConfigObject {
|
|
19
|
+
diagnosisConceptClass: string;
|
|
20
|
+
isPrimaryDiagnosisRequired: boolean;
|
|
21
|
+
visitNoteConfig: VisitNoteConfigObject;
|
|
22
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import {
|
|
2
|
+
defineConfigSchema,
|
|
3
|
+
getAsyncLifecycle,
|
|
4
|
+
getSyncLifecycle,
|
|
5
|
+
messageOmrsServiceWorker,
|
|
6
|
+
restBaseUrl,
|
|
7
|
+
} from '@openmrs/esm-framework';
|
|
8
|
+
import { configSchema } from './config-schema';
|
|
9
|
+
import notesOverviewExtension from './notes/notes-overview.extension';
|
|
10
|
+
import visitNotesActionButtonExtension from './visit-note-action-button.extension';
|
|
11
|
+
|
|
12
|
+
const moduleName = '@openmrs/esm-patient-notes-app';
|
|
13
|
+
|
|
14
|
+
const options = {
|
|
15
|
+
featureName: 'patient-notes',
|
|
16
|
+
moduleName,
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export const importTranslation = require.context('../translations', false, /.json$/, 'lazy');
|
|
20
|
+
|
|
21
|
+
export function startupApp() {
|
|
22
|
+
messageOmrsServiceWorker({
|
|
23
|
+
type: 'registerDynamicRoute',
|
|
24
|
+
pattern: `.+${restBaseUrl}/encounter.+`,
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
defineConfigSchema(moduleName, configSchema);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export const notesOverview = getSyncLifecycle(notesOverviewExtension, options);
|
|
31
|
+
export const visitNotesActionButton = getSyncLifecycle(visitNotesActionButtonExtension, options);
|
|
32
|
+
|
|
33
|
+
// t('visitNoteWorkspaceTitle', 'Visit Note')
|
|
34
|
+
export const visitNotesFormWorkspace = getAsyncLifecycle(() => import('./notes/visit-notes-form.workspace'), options);
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import React, { type ComponentProps } from 'react';
|
|
2
|
+
import { useTranslation } from 'react-i18next';
|
|
3
|
+
import { Button, DataTableSkeleton, InlineLoading } from '@carbon/react';
|
|
4
|
+
import { AddIcon, launchWorkspace, useLayoutType } from '@openmrs/esm-framework';
|
|
5
|
+
import {
|
|
6
|
+
CardHeader,
|
|
7
|
+
EmptyState,
|
|
8
|
+
ErrorState,
|
|
9
|
+
launchStartVisitPrompt,
|
|
10
|
+
usePatientChartStore,
|
|
11
|
+
} from '@openmrs/esm-patient-common-lib';
|
|
12
|
+
import { useVisitNotes } from './visit-notes.resource';
|
|
13
|
+
import PaginatedNotes from './paginated-notes.component';
|
|
14
|
+
import styles from './notes-overview.scss';
|
|
15
|
+
|
|
16
|
+
interface NotesOverviewProps {
|
|
17
|
+
patientUuid: string;
|
|
18
|
+
patient: fhir.Patient;
|
|
19
|
+
basePath: string;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* This extension uses the patient chart store and MUST only be mounted within the patient chart
|
|
24
|
+
*/
|
|
25
|
+
const NotesOverview: React.FC<NotesOverviewProps> = ({ patientUuid, patient, basePath }) => {
|
|
26
|
+
const pageSize = 5;
|
|
27
|
+
const { t } = useTranslation();
|
|
28
|
+
const pageUrl = `\${openmrsSpaBase}/patient/${patient.id}/chart/Forms & Notes`;
|
|
29
|
+
const urlLabel = t('seeAll', 'See all');
|
|
30
|
+
|
|
31
|
+
const { visitContext } = usePatientChartStore(patientUuid);
|
|
32
|
+
const displayText = t('visitNotes', 'Visit notes');
|
|
33
|
+
const headerTitle = t('visitNotes', 'Visit notes');
|
|
34
|
+
const { visitNotes, error, isLoading, isValidating } = useVisitNotes(patientUuid);
|
|
35
|
+
const layout = useLayoutType();
|
|
36
|
+
const isDesktop = layout === 'large-desktop' || layout === 'small-desktop';
|
|
37
|
+
|
|
38
|
+
const launchVisitNoteForm = React.useCallback(() => {
|
|
39
|
+
if (visitContext) {
|
|
40
|
+
launchWorkspace('visit-notes-form-workspace');
|
|
41
|
+
} else {
|
|
42
|
+
launchStartVisitPrompt();
|
|
43
|
+
}
|
|
44
|
+
}, [visitContext]);
|
|
45
|
+
|
|
46
|
+
if (isLoading) {
|
|
47
|
+
return <DataTableSkeleton role="progressbar" compact={isDesktop} zebra />;
|
|
48
|
+
}
|
|
49
|
+
if (error) {
|
|
50
|
+
return <ErrorState error={error} headerTitle={headerTitle} />;
|
|
51
|
+
}
|
|
52
|
+
if (!visitNotes?.length) {
|
|
53
|
+
return <EmptyState displayText={displayText} headerTitle={headerTitle} launchForm={launchVisitNoteForm} />;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return (
|
|
57
|
+
<div className={styles.widgetCard}>
|
|
58
|
+
<CardHeader title={headerTitle}>
|
|
59
|
+
<span>{isValidating ? <InlineLoading /> : null}</span>
|
|
60
|
+
<Button
|
|
61
|
+
kind="ghost"
|
|
62
|
+
renderIcon={(props: ComponentProps<typeof AddIcon>) => <AddIcon size={16} {...props} />}
|
|
63
|
+
iconDescription="Add visit note"
|
|
64
|
+
onClick={launchVisitNoteForm}
|
|
65
|
+
>
|
|
66
|
+
{t('add', 'Add')}
|
|
67
|
+
</Button>
|
|
68
|
+
</CardHeader>
|
|
69
|
+
<PaginatedNotes notes={visitNotes} pageSize={pageSize} urlLabel={urlLabel} pageUrl={pageUrl} />
|
|
70
|
+
</div>
|
|
71
|
+
);
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
export default NotesOverview;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
@use '@carbon/layout';
|
|
2
|
+
@use '@carbon/type';
|
|
3
|
+
@use '@openmrs/esm-styleguide/src/vars' as *;
|
|
4
|
+
|
|
5
|
+
.widgetCard {
|
|
6
|
+
border: 1px solid $ui-03;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
.expandedRow > td {
|
|
10
|
+
padding-left: layout.$spacing-08 !important;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
.expandedRow > td > div {
|
|
14
|
+
max-height: max-content !important;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
.expandedRow th[colspan] td[colspan] > div:first-child {
|
|
18
|
+
padding: 0 layout.$spacing-05;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
.copy {
|
|
22
|
+
@include type.type-style('body-01');
|
|
23
|
+
|
|
24
|
+
display: flex;
|
|
25
|
+
flex-direction: column;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
.container {
|
|
29
|
+
margin: layout.$spacing-03;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
.metadata {
|
|
33
|
+
@include type.type-style('label-01');
|
|
34
|
+
color: $text-02;
|
|
35
|
+
margin: layout.$spacing-03 0;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
.hiddenRow {
|
|
39
|
+
display: none;
|
|
40
|
+
}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { screen, within } from '@testing-library/react';
|
|
3
|
+
import { useConfig } from '@openmrs/esm-framework';
|
|
4
|
+
import { ErrorState } from '@openmrs/esm-patient-common-lib';
|
|
5
|
+
import { mockVisitNotes, ConfigMock } from '__mocks__';
|
|
6
|
+
import { mockPatient, patientChartBasePath, renderWithSwr } from 'tools';
|
|
7
|
+
import { useVisitNotes } from './visit-notes.resource';
|
|
8
|
+
import NotesOverview from './notes-overview.extension';
|
|
9
|
+
|
|
10
|
+
const testProps = {
|
|
11
|
+
basePath: patientChartBasePath,
|
|
12
|
+
patient: mockPatient,
|
|
13
|
+
patientUuid: mockPatient.id,
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
const mockUseVisitNotes = jest.mocked(useVisitNotes);
|
|
17
|
+
const mockUseConfig = jest.mocked(useConfig);
|
|
18
|
+
|
|
19
|
+
jest.mock('./visit-notes.resource', () => {
|
|
20
|
+
return { useVisitNotes: jest.fn().mockReturnValue([{}]) };
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
describe('NotesOverview', () => {
|
|
24
|
+
beforeEach(() => {
|
|
25
|
+
mockUseConfig.mockReturnValue(ConfigMock);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
test('renders an empty state view if visit note data is unavailable', async () => {
|
|
29
|
+
mockUseVisitNotes.mockReturnValueOnce({
|
|
30
|
+
visitNotes: [],
|
|
31
|
+
error: null,
|
|
32
|
+
isLoading: false,
|
|
33
|
+
isValidating: false,
|
|
34
|
+
mutateVisitNotes: jest.fn(),
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
renderWithSwr(<NotesOverview {...testProps} />);
|
|
38
|
+
|
|
39
|
+
expect(screen.queryByRole('table')).not.toBeInTheDocument();
|
|
40
|
+
expect(screen.getByRole('heading', { name: /notes/i })).toBeInTheDocument();
|
|
41
|
+
expect(screen.getByText(/There are no visit notes to display for this patient/i)).toBeInTheDocument();
|
|
42
|
+
expect(screen.getByText(/Record visit notes/i)).toBeInTheDocument();
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
test('renders an error state view if there is a problem fetching visit note data', async () => {
|
|
46
|
+
const error = {
|
|
47
|
+
name: 'Error',
|
|
48
|
+
message: 'You are not logged in',
|
|
49
|
+
response: {
|
|
50
|
+
status: 401,
|
|
51
|
+
statusText: 'Unauthorized',
|
|
52
|
+
},
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
mockUseVisitNotes.mockReturnValueOnce({
|
|
56
|
+
visitNotes: [],
|
|
57
|
+
error: error,
|
|
58
|
+
isLoading: false,
|
|
59
|
+
isValidating: false,
|
|
60
|
+
mutateVisitNotes: jest.fn(),
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
renderWithSwr(<NotesOverview {...testProps} />);
|
|
64
|
+
|
|
65
|
+
expect(screen.queryByRole('table')).not.toBeInTheDocument();
|
|
66
|
+
expect(ErrorState).toHaveBeenCalledWith(expect.objectContaining({ error, headerTitle: 'Visit notes' }), {});
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
test("renders a tabular overview of the patient's visit notes when present", async () => {
|
|
70
|
+
mockUseVisitNotes.mockReturnValueOnce({
|
|
71
|
+
visitNotes: mockVisitNotes,
|
|
72
|
+
error: null,
|
|
73
|
+
isLoading: false,
|
|
74
|
+
isValidating: false,
|
|
75
|
+
mutateVisitNotes: jest.fn(),
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
renderWithSwr(<NotesOverview {...testProps} />);
|
|
79
|
+
|
|
80
|
+
expect(screen.queryByRole('progressbar')).not.toBeInTheDocument();
|
|
81
|
+
expect(screen.getByRole('heading', { name: /notes/i })).toBeInTheDocument();
|
|
82
|
+
|
|
83
|
+
const table = screen.getByRole('table');
|
|
84
|
+
|
|
85
|
+
const expectedColumnHeaders = [/Date/, /Diagnoses/];
|
|
86
|
+
expectedColumnHeaders.forEach((header) => {
|
|
87
|
+
expect(screen.getByRole('columnheader', { name: new RegExp(header, 'i') })).toBeInTheDocument();
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
const expectedTableRows = [
|
|
91
|
+
/27 — Jan — 2022 Malaria, Primary respiratory tuberculosis, confirmed/,
|
|
92
|
+
/14 — Jan — 2022 Visit Diagnoses: Presumed diagnosis, Malaria, Primary/,
|
|
93
|
+
/14 — Jan — 2022 Visit Diagnoses: Presumed diagnosis, Hemorrhage in early pregnancy, Primary/,
|
|
94
|
+
];
|
|
95
|
+
|
|
96
|
+
expectedTableRows.map((row) =>
|
|
97
|
+
expect(within(table).getByRole('row', { name: new RegExp(row, 'i') })).toBeInTheDocument(),
|
|
98
|
+
);
|
|
99
|
+
});
|
|
100
|
+
});
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
import React, { useState } from 'react';
|
|
2
|
+
import { useTranslation } from 'react-i18next';
|
|
3
|
+
import classNames from 'classnames';
|
|
4
|
+
import {
|
|
5
|
+
DataTable,
|
|
6
|
+
type DataTableCell,
|
|
7
|
+
type DataTableSortState,
|
|
8
|
+
Table,
|
|
9
|
+
TableCell,
|
|
10
|
+
TableContainer,
|
|
11
|
+
TableBody,
|
|
12
|
+
TableHead,
|
|
13
|
+
TableHeader,
|
|
14
|
+
TableRow,
|
|
15
|
+
TableExpandHeader,
|
|
16
|
+
TableExpandRow,
|
|
17
|
+
TableExpandedRow,
|
|
18
|
+
} from '@carbon/react';
|
|
19
|
+
import { orderBy } from 'lodash-es';
|
|
20
|
+
import { formatDate, formatTime, parseDate, useLayoutType, usePagination } from '@openmrs/esm-framework';
|
|
21
|
+
import { PatientChartPagination } from '@openmrs/esm-patient-common-lib';
|
|
22
|
+
import type { PatientNote } from '../types';
|
|
23
|
+
import styles from './notes-overview.scss';
|
|
24
|
+
|
|
25
|
+
interface PaginatedNotes {
|
|
26
|
+
notes: Array<PatientNote>;
|
|
27
|
+
pageSize: number;
|
|
28
|
+
pageUrl: string;
|
|
29
|
+
urlLabel: string;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const PaginatedNotes: React.FC<PaginatedNotes> = ({ notes, pageSize, pageUrl, urlLabel }) => {
|
|
33
|
+
const { t } = useTranslation();
|
|
34
|
+
const layout = useLayoutType();
|
|
35
|
+
const isTablet = layout === 'tablet';
|
|
36
|
+
|
|
37
|
+
const [sortParams, setSortParams] = useState({ key: '', order: 'none' });
|
|
38
|
+
|
|
39
|
+
const tableHeaders = [
|
|
40
|
+
{
|
|
41
|
+
key: 'encounterDate',
|
|
42
|
+
header: t('date', 'Date'),
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
key: 'diagnoses',
|
|
46
|
+
header: t('diagnoses', 'Diagnoses'),
|
|
47
|
+
},
|
|
48
|
+
];
|
|
49
|
+
|
|
50
|
+
const sortDate = (myArray, order) =>
|
|
51
|
+
order === 'ASC'
|
|
52
|
+
? orderBy(myArray, [(obj) => new Date(obj.encounterDate).getTime()], ['desc'])
|
|
53
|
+
: orderBy(myArray, [(obj) => new Date(obj.encounterDate).getTime()], ['asc']);
|
|
54
|
+
|
|
55
|
+
const { key, order } = sortParams;
|
|
56
|
+
|
|
57
|
+
const sortedData =
|
|
58
|
+
key === 'encounterDate'
|
|
59
|
+
? sortDate(notes, order)
|
|
60
|
+
: order === 'DESC'
|
|
61
|
+
? orderBy(notes, [key], ['desc'])
|
|
62
|
+
: orderBy(notes, [key], ['asc']);
|
|
63
|
+
|
|
64
|
+
function customSortRow(
|
|
65
|
+
cellA,
|
|
66
|
+
cellB,
|
|
67
|
+
{
|
|
68
|
+
sortDirection,
|
|
69
|
+
sortStates,
|
|
70
|
+
}: {
|
|
71
|
+
sortDirection: string;
|
|
72
|
+
sortStates: any;
|
|
73
|
+
locale: string;
|
|
74
|
+
},
|
|
75
|
+
) {
|
|
76
|
+
const key = Object.keys(sortStates).find((k) => sortStates[k] === sortDirection);
|
|
77
|
+
setSortParams({ key: key ?? '', order: sortDirection });
|
|
78
|
+
return 0;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const { results: paginatedNotes, goTo, currentPage } = usePagination(sortedData, pageSize);
|
|
82
|
+
const tableRows = React.useMemo(
|
|
83
|
+
() =>
|
|
84
|
+
paginatedNotes?.map((note) => ({
|
|
85
|
+
...note,
|
|
86
|
+
id: `${note.id}`,
|
|
87
|
+
encounterDate: formatDate(parseDate(note.encounterDate), { mode: 'wide' }),
|
|
88
|
+
diagnoses: note.diagnoses ? note.diagnoses : '--',
|
|
89
|
+
})),
|
|
90
|
+
[paginatedNotes],
|
|
91
|
+
);
|
|
92
|
+
|
|
93
|
+
return (
|
|
94
|
+
<>
|
|
95
|
+
<DataTable
|
|
96
|
+
rows={tableRows}
|
|
97
|
+
sortRow={customSortRow}
|
|
98
|
+
headers={tableHeaders}
|
|
99
|
+
isSortable
|
|
100
|
+
size={isTablet ? 'lg' : 'sm'}
|
|
101
|
+
useZebraStyles
|
|
102
|
+
>
|
|
103
|
+
{({
|
|
104
|
+
getExpandedRowProps,
|
|
105
|
+
getExpandHeaderProps,
|
|
106
|
+
getHeaderProps,
|
|
107
|
+
getRowProps,
|
|
108
|
+
getTableContainerProps,
|
|
109
|
+
getTableProps,
|
|
110
|
+
headers,
|
|
111
|
+
rows,
|
|
112
|
+
}) => (
|
|
113
|
+
<TableContainer {...getTableContainerProps()}>
|
|
114
|
+
<Table {...getTableProps()}>
|
|
115
|
+
<TableHead>
|
|
116
|
+
<TableRow>
|
|
117
|
+
<TableExpandHeader enableToggle {...getExpandHeaderProps()} />
|
|
118
|
+
{headers.map((header, i) => (
|
|
119
|
+
<TableHeader
|
|
120
|
+
key={i}
|
|
121
|
+
className={classNames(styles.productiveHeading01, styles.text02)}
|
|
122
|
+
{...getHeaderProps({
|
|
123
|
+
header,
|
|
124
|
+
})}
|
|
125
|
+
>
|
|
126
|
+
{header.header}
|
|
127
|
+
</TableHeader>
|
|
128
|
+
))}
|
|
129
|
+
</TableRow>
|
|
130
|
+
</TableHead>
|
|
131
|
+
<TableBody>
|
|
132
|
+
{rows.map((row, i) => (
|
|
133
|
+
<React.Fragment key={row.id}>
|
|
134
|
+
<TableExpandRow {...getRowProps({ row })}>
|
|
135
|
+
{row.cells.map((cell) => (
|
|
136
|
+
<TableCell key={cell.id}>{cell.value}</TableCell>
|
|
137
|
+
))}
|
|
138
|
+
</TableExpandRow>
|
|
139
|
+
{row.isExpanded ? (
|
|
140
|
+
<TableExpandedRow
|
|
141
|
+
className={styles.expandedRow}
|
|
142
|
+
colSpan={headers.length + 1}
|
|
143
|
+
{...getExpandedRowProps({ row })}
|
|
144
|
+
>
|
|
145
|
+
<div className={styles.container} key={i}>
|
|
146
|
+
{tableRows?.[i]?.encounterNote ? (
|
|
147
|
+
<div className={styles.copy}>
|
|
148
|
+
<span className={styles.content}>{tableRows?.[i]?.encounterNote}</span>
|
|
149
|
+
<span className={styles.metadata}>
|
|
150
|
+
{formatTime(new Date(tableRows?.[i]?.encounterNoteRecordedAt))} ·{' '}
|
|
151
|
+
{tableRows?.[i]?.encounterProvider}, {tableRows?.[i]?.encounterProviderRole}
|
|
152
|
+
</span>
|
|
153
|
+
</div>
|
|
154
|
+
) : (
|
|
155
|
+
<span className={styles.copy}>{t('noVisitNoteToDisplay', 'No visit note to display')}</span>
|
|
156
|
+
)}
|
|
157
|
+
</div>
|
|
158
|
+
</TableExpandedRow>
|
|
159
|
+
) : (
|
|
160
|
+
<TableExpandedRow className={styles.hiddenRow} colSpan={headers.length + 2} />
|
|
161
|
+
)}
|
|
162
|
+
</React.Fragment>
|
|
163
|
+
))}
|
|
164
|
+
</TableBody>
|
|
165
|
+
</Table>
|
|
166
|
+
</TableContainer>
|
|
167
|
+
)}
|
|
168
|
+
</DataTable>
|
|
169
|
+
<PatientChartPagination
|
|
170
|
+
pageNumber={currentPage}
|
|
171
|
+
totalItems={notes.length}
|
|
172
|
+
currentItems={paginatedNotes.length}
|
|
173
|
+
pageSize={pageSize}
|
|
174
|
+
onPageNumberChange={({ page }) => goTo(page)}
|
|
175
|
+
dashboardLinkUrl={pageUrl}
|
|
176
|
+
dashboardLinkLabel={urlLabel}
|
|
177
|
+
/>
|
|
178
|
+
</>
|
|
179
|
+
);
|
|
180
|
+
};
|
|
181
|
+
|
|
182
|
+
export default PaginatedNotes;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { Type } from '@openmrs/esm-framework';
|
|
2
|
+
|
|
3
|
+
export default {
|
|
4
|
+
clinicianEncounterRole: {
|
|
5
|
+
_type: Type.UUID,
|
|
6
|
+
_default: '240b26f9-dd88-4172-823d-4a8bfeb7841f',
|
|
7
|
+
_description: 'Doctor or Nurse who is the primary provider for an encounter, and will sign the note',
|
|
8
|
+
},
|
|
9
|
+
visitDiagnosesConceptUuid: {
|
|
10
|
+
_type: Type.ConceptUuid,
|
|
11
|
+
_default: '159947AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
|
|
12
|
+
_description: 'The set of diagnoses that were either addressed or diagnosed during the current visit',
|
|
13
|
+
},
|
|
14
|
+
encounterNoteTextConceptUuid: {
|
|
15
|
+
_type: Type.ConceptUuid,
|
|
16
|
+
_default: '162169AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
|
|
17
|
+
_description: 'Free text note field intended to capture unstructured description of the patient encounter',
|
|
18
|
+
},
|
|
19
|
+
encounterTypeUuid: {
|
|
20
|
+
_type: Type.UUID,
|
|
21
|
+
_default: 'd7151f82-c1f3-4152-a605-2f9ea7414a79',
|
|
22
|
+
_description:
|
|
23
|
+
'Encounter where a full or abbreviated examination is done, usually leading to a presumptive or confirmed diagnosis, recorded by the examining clinician.',
|
|
24
|
+
},
|
|
25
|
+
formConceptUuid: {
|
|
26
|
+
_type: Type.UUID,
|
|
27
|
+
_default: 'c75f120a-04ec-11e3-8780-2b40bef9a44b',
|
|
28
|
+
_description: 'The UUID of the Visit Note form to be associated with visit note encounters',
|
|
29
|
+
},
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
export interface VisitNoteConfigObject {
|
|
33
|
+
clinicianEncounterRole: string;
|
|
34
|
+
encounterNoteTextConceptUuid: string;
|
|
35
|
+
encounterTypeUuid: string;
|
|
36
|
+
formConceptUuid: string;
|
|
37
|
+
visitDiagnosesConceptUuid: string;
|
|
38
|
+
}
|