@bathiran212/esm-patient-notes-app 2.0.0
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/dist/1076.js +1 -0
- package/dist/1076.js.map +1 -0
- package/dist/1339.js +1 -0
- package/dist/1339.js.map +1 -0
- package/dist/1480.js +1 -0
- package/dist/1480.js.map +1 -0
- package/dist/1646.js +1 -0
- package/dist/1646.js.map +1 -0
- package/dist/1789.js +1 -0
- package/dist/1789.js.map +1 -0
- package/dist/1869.js +1 -0
- package/dist/1869.js.map +1 -0
- package/dist/1871.js +1 -0
- package/dist/1871.js.map +1 -0
- package/dist/1877.js +1 -0
- package/dist/1877.js.map +1 -0
- package/dist/2153.js +1 -0
- package/dist/2153.js.map +1 -0
- package/dist/2317.js +1 -0
- package/dist/2317.js.map +1 -0
- package/dist/2416.js +1 -0
- package/dist/2416.js.map +1 -0
- package/dist/2544.js +27 -0
- package/dist/2544.js.map +1 -0
- package/dist/282.js +1 -0
- package/dist/282.js.map +1 -0
- package/dist/2824.js +1 -0
- package/dist/2824.js.map +1 -0
- package/dist/2842.js +1 -0
- package/dist/2842.js.map +1 -0
- package/dist/2881.js +1 -0
- package/dist/2881.js.map +1 -0
- package/dist/3378.js +1 -0
- package/dist/3378.js.map +1 -0
- package/dist/3720.js +1 -0
- package/dist/3720.js.map +1 -0
- package/dist/3963.js +1 -0
- package/dist/3963.js.map +1 -0
- package/dist/3989.js +1 -0
- package/dist/3989.js.map +1 -0
- package/dist/4106.js +1 -0
- package/dist/4106.js.map +1 -0
- package/dist/4111.js +1 -0
- package/dist/4111.js.map +1 -0
- package/dist/434.js +1 -0
- package/dist/434.js.map +1 -0
- package/dist/4348.js +1 -0
- package/dist/4348.js.map +1 -0
- package/dist/4383.js +1 -0
- package/dist/4383.js.map +1 -0
- package/dist/4658.js +1 -0
- package/dist/4658.js.map +1 -0
- package/dist/466.js +1 -0
- package/dist/466.js.map +1 -0
- package/dist/4928.js +1 -0
- package/dist/4928.js.map +1 -0
- package/dist/5117.js +1 -0
- package/dist/5117.js.map +1 -0
- package/dist/5132.js +1 -0
- package/dist/5132.js.map +1 -0
- package/dist/5145.js +1 -0
- package/dist/5145.js.map +1 -0
- package/dist/5503.js +1 -0
- package/dist/5503.js.map +1 -0
- package/dist/556.js +1 -0
- package/dist/556.js.map +1 -0
- package/dist/5644.js +1 -0
- package/dist/5644.js.map +1 -0
- package/dist/5697.js +1 -0
- package/dist/5697.js.map +1 -0
- package/dist/5861.js +1 -0
- package/dist/5861.js.map +1 -0
- package/dist/5940.js +1 -0
- package/dist/5940.js.map +1 -0
- package/dist/6047.js +1 -0
- package/dist/6047.js.map +1 -0
- package/dist/6371.js +1 -0
- package/dist/6371.js.map +1 -0
- package/dist/6377.js +1 -0
- package/dist/6377.js.map +1 -0
- package/dist/6444.js +1 -0
- package/dist/6444.js.map +1 -0
- package/dist/6508.js +1 -0
- package/dist/6508.js.map +1 -0
- package/dist/6724.js +1 -0
- package/dist/6724.js.map +1 -0
- package/dist/6904.js +1 -0
- package/dist/6904.js.map +1 -0
- package/dist/7045.js +1 -0
- package/dist/7045.js.map +1 -0
- package/dist/7103.js +1 -0
- package/dist/7103.js.map +1 -0
- package/dist/7175.js +1 -0
- package/dist/7175.js.map +1 -0
- package/dist/7182.js +1 -0
- package/dist/7182.js.map +1 -0
- package/dist/7205.js +11 -0
- package/dist/7205.js.map +1 -0
- package/dist/7646.js +17 -0
- package/dist/7646.js.map +1 -0
- package/dist/7742.js +1 -0
- package/dist/7742.js.map +1 -0
- package/dist/7912.js +1 -0
- package/dist/7912.js.map +1 -0
- package/dist/8358.js +1 -0
- package/dist/8358.js.map +1 -0
- package/dist/8359.js +1 -0
- package/dist/8359.js.map +1 -0
- package/dist/8369.js +1 -0
- package/dist/8369.js.map +1 -0
- package/dist/8695.js +1 -0
- package/dist/8695.js.map +1 -0
- package/dist/8722.js +1 -0
- package/dist/8722.js.map +1 -0
- package/dist/903.js +1 -0
- package/dist/903.js.map +1 -0
- package/dist/9061.js +1 -0
- package/dist/9061.js.map +1 -0
- package/dist/9072.js +1 -0
- package/dist/9072.js.map +1 -0
- package/dist/9105.js +1 -0
- package/dist/9105.js.map +1 -0
- package/dist/9712.js +1 -0
- package/dist/9712.js.map +1 -0
- package/dist/9771.js +1 -0
- package/dist/9771.js.map +1 -0
- package/dist/9806.js +1 -0
- package/dist/9806.js.map +1 -0
- package/dist/main.js +16 -0
- package/dist/main.js.map +1 -0
- package/dist/openmrs-esm-patient-notes-app.js +6 -0
- package/dist/openmrs-esm-patient-notes-app.js.buildmanifest.json +1760 -0
- package/dist/openmrs-esm-patient-notes-app.js.map +1 -0
- package/dist/routes.json +1 -0
- package/package.json +58 -0
- package/rspack.config.js +1 -0
- package/src/config-schema.ts +28 -0
- package/src/dashboard.meta.ts +7 -0
- package/src/declarations.d.ts +4 -0
- package/src/index.ts +45 -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 +101 -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 +219 -0
- package/src/notes/visit-notes-form.test.tsx +523 -0
- package/src/notes/visit-notes-form.workspace.tsx +853 -0
- package/src/notes/visit-notes.resource.ts +113 -0
- package/src/routes.json +48 -0
- package/src/sticky-notes/delete-sticky-note-button.component.tsx +39 -0
- package/src/sticky-notes/delete-sticky-note-button.scss +13 -0
- package/src/sticky-notes/delete-sticky-note.modal.test.tsx +72 -0
- package/src/sticky-notes/delete-sticky-note.modal.tsx +62 -0
- package/src/sticky-notes/edit-sticky-note-button.component.tsx +20 -0
- package/src/sticky-notes/sticky-note-header-button.component.tsx +100 -0
- package/src/sticky-notes/sticky-note-header-button.scss +38 -0
- package/src/sticky-notes/sticky-note-header-button.test.tsx +182 -0
- package/src/sticky-notes/sticky-note-panel.component.tsx +88 -0
- package/src/sticky-notes/sticky-note-panel.scss +54 -0
- package/src/sticky-notes/sticky-note-panel.test.tsx +66 -0
- package/src/sticky-notes/sticky-note.modal.scss +3 -0
- package/src/sticky-notes/sticky-note.modal.test.tsx +115 -0
- package/src/sticky-notes/sticky-note.modal.tsx +93 -0
- package/src/sticky-notes/sticky-note.resource.test.ts +24 -0
- package/src/sticky-notes/sticky-note.resource.ts +82 -0
- package/src/sticky-notes/utils.test.ts +36 -0
- package/src/sticky-notes/utils.ts +9 -0
- package/src/types/index.ts +203 -0
- package/src/visit-note-action-button.extension.tsx +28 -0
- package/src/visit-note-action-button.test.tsx +42 -0
- package/translations/am.json +55 -0
- package/translations/ar.json +55 -0
- package/translations/ar_SY.json +55 -0
- package/translations/bn.json +55 -0
- package/translations/cs.json +55 -0
- package/translations/de.json +55 -0
- package/translations/en.json +55 -0
- package/translations/en_US.json +55 -0
- package/translations/es.json +55 -0
- package/translations/es_MX.json +55 -0
- package/translations/fr.json +55 -0
- package/translations/he.json +55 -0
- package/translations/hi.json +55 -0
- package/translations/hi_IN.json +55 -0
- package/translations/id.json +55 -0
- package/translations/it.json +55 -0
- package/translations/ka.json +55 -0
- package/translations/km.json +55 -0
- package/translations/ku.json +55 -0
- package/translations/ky.json +55 -0
- package/translations/lg.json +55 -0
- package/translations/ne.json +55 -0
- package/translations/pl.json +55 -0
- package/translations/pt.json +55 -0
- package/translations/pt_BR.json +55 -0
- package/translations/qu.json +55 -0
- package/translations/ro_RO.json +55 -0
- package/translations/ru_RU.json +55 -0
- package/translations/si.json +55 -0
- package/translations/sq.json +55 -0
- package/translations/sw.json +55 -0
- package/translations/sw_KE.json +55 -0
- package/translations/tr.json +55 -0
- package/translations/tr_TR.json +55 -0
- package/translations/uk.json +55 -0
- package/translations/uz.json +55 -0
- package/translations/uz@Latn.json +55 -0
- package/translations/uz_UZ.json +55 -0
- package/translations/vi.json +55 -0
- package/translations/zh.json +55 -0
- package/translations/zh_CN.json +55 -0
- package/translations/zh_TW.json +55 -0
- package/tsconfig.json +4 -0
- package/vitest.config.ts +4 -0
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { useMemo } from 'react';
|
|
2
|
+
import useSWR from 'swr';
|
|
3
|
+
import { openmrsFetch, restBaseUrl, useConfig } from '@openmrs/esm-framework';
|
|
4
|
+
import { type ConfigObject } from '../config-schema';
|
|
5
|
+
|
|
6
|
+
const stickyNoteRepresentation =
|
|
7
|
+
'custom:(uuid,value,obsDatetime,encounter:(uuid),formNamespaceAndPath,auditInfo:(creator:(display)))';
|
|
8
|
+
|
|
9
|
+
export interface StickyNoteObs {
|
|
10
|
+
uuid: string;
|
|
11
|
+
value: string;
|
|
12
|
+
obsDatetime: string;
|
|
13
|
+
encounter?: { uuid: string } | null;
|
|
14
|
+
formNamespaceAndPath?: string | null;
|
|
15
|
+
auditInfo?: {
|
|
16
|
+
creator?: {
|
|
17
|
+
display?: string;
|
|
18
|
+
};
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
interface StickyNoteResponse {
|
|
23
|
+
results: Array<StickyNoteObs>;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Exclude obs attached to an encounter or created by a form engine. The sticky-note UI only
|
|
27
|
+
// writes standalone obs, so this guards against another feature (e.g. the visit note) writing
|
|
28
|
+
// to the same concept UUID.
|
|
29
|
+
export const isStandaloneStickyNote = (obs: StickyNoteObs) => !obs.encounter && !obs.formNamespaceAndPath;
|
|
30
|
+
|
|
31
|
+
export const useStickyNote = (patientUuid: string) => {
|
|
32
|
+
const { stickyNoteConceptUuid } = useConfig<ConfigObject>();
|
|
33
|
+
const url =
|
|
34
|
+
patientUuid && stickyNoteConceptUuid
|
|
35
|
+
? `${restBaseUrl}/obs?patient=${patientUuid}&concept=${stickyNoteConceptUuid}&v=${stickyNoteRepresentation}`
|
|
36
|
+
: null;
|
|
37
|
+
|
|
38
|
+
const { data, error, isLoading, mutate } = useSWR<{ data: StickyNoteResponse }, Error>(url, openmrsFetch);
|
|
39
|
+
|
|
40
|
+
const note = useMemo(
|
|
41
|
+
() =>
|
|
42
|
+
data?.data?.results
|
|
43
|
+
?.filter(isStandaloneStickyNote)
|
|
44
|
+
.slice()
|
|
45
|
+
.sort((a, b) => new Date(b.obsDatetime).getTime() - new Date(a.obsDatetime).getTime())[0],
|
|
46
|
+
[data?.data?.results],
|
|
47
|
+
);
|
|
48
|
+
|
|
49
|
+
return {
|
|
50
|
+
note,
|
|
51
|
+
isLoading,
|
|
52
|
+
error,
|
|
53
|
+
mutate,
|
|
54
|
+
};
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
export const createStickyNote = (patientUuid: string, value: string, conceptUuid: string) => {
|
|
58
|
+
return openmrsFetch(`${restBaseUrl}/obs`, {
|
|
59
|
+
method: 'POST',
|
|
60
|
+
headers: { 'Content-Type': 'application/json' },
|
|
61
|
+
body: JSON.stringify({
|
|
62
|
+
person: patientUuid,
|
|
63
|
+
concept: conceptUuid,
|
|
64
|
+
value,
|
|
65
|
+
obsDatetime: new Date().toISOString(),
|
|
66
|
+
}),
|
|
67
|
+
});
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
export const updateStickyNote = (obsUuid: string, value: string) => {
|
|
71
|
+
return openmrsFetch(`${restBaseUrl}/obs/${obsUuid}`, {
|
|
72
|
+
method: 'POST',
|
|
73
|
+
headers: { 'Content-Type': 'application/json' },
|
|
74
|
+
body: JSON.stringify({ value }),
|
|
75
|
+
});
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
export const deleteStickyNote = (obsUuid: string) => {
|
|
79
|
+
return openmrsFetch(`${restBaseUrl}/obs/${obsUuid}`, {
|
|
80
|
+
method: 'DELETE',
|
|
81
|
+
});
|
|
82
|
+
};
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { decodeHtmlEntities } from './utils';
|
|
3
|
+
|
|
4
|
+
describe('decodeHtmlEntities', () => {
|
|
5
|
+
it('decodes < and > to < and >', () => {
|
|
6
|
+
expect(decodeHtmlEntities('<b>bold</b>')).toBe('<b>bold</b>');
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
it('decodes & to &', () => {
|
|
10
|
+
expect(decodeHtmlEntities('Tom & Jerry')).toBe('Tom & Jerry');
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
it('decodes " and ' to quote characters', () => {
|
|
14
|
+
expect(decodeHtmlEntities('"hello" 'world'')).toBe(`"hello" 'world'`);
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
it('decodes numeric entities', () => {
|
|
18
|
+
expect(decodeHtmlEntities('<div>')).toBe('<div>');
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it('decodes hex entities', () => {
|
|
22
|
+
expect(decodeHtmlEntities('<span>')).toBe('<span>');
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it('returns plain text unchanged', () => {
|
|
26
|
+
expect(decodeHtmlEntities('no entities here')).toBe('no entities here');
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it('handles mixed encoded and plain text', () => {
|
|
30
|
+
expect(decodeHtmlEntities('BP < 120 & HR > 60')).toBe('BP < 120 & HR > 60');
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it('returns an empty string for empty input', () => {
|
|
34
|
+
expect(decodeHtmlEntities('')).toBe('');
|
|
35
|
+
});
|
|
36
|
+
});
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { decode } from 'html-entities';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Decodes HTML entities (e.g. < > &) that the REST API may return
|
|
5
|
+
* in obs values back into their original characters.
|
|
6
|
+
*/
|
|
7
|
+
export function decodeHtmlEntities(text: string): string {
|
|
8
|
+
return decode(text);
|
|
9
|
+
}
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
export interface EncountersFetchResponse {
|
|
2
|
+
results: Array<RESTPatientNote>;
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
export interface RESTPatientNote {
|
|
6
|
+
uuid: string;
|
|
7
|
+
display: string;
|
|
8
|
+
encounterDatetime: string;
|
|
9
|
+
encounterType: { name: string; uuid: string };
|
|
10
|
+
encounterProviders: [
|
|
11
|
+
{
|
|
12
|
+
encounterRole: { uuid: string; display: string };
|
|
13
|
+
provider: { uuid?: string; person: { uuid?: string; display: string } };
|
|
14
|
+
},
|
|
15
|
+
];
|
|
16
|
+
location: { uuid: string; display: string; name: string };
|
|
17
|
+
auditInfo: {
|
|
18
|
+
creator: any;
|
|
19
|
+
uuid: string;
|
|
20
|
+
display: string;
|
|
21
|
+
links: any;
|
|
22
|
+
dateCreated: Date;
|
|
23
|
+
changedBy?: any;
|
|
24
|
+
dateChanged?: Date;
|
|
25
|
+
};
|
|
26
|
+
obs: Array<ObsData>;
|
|
27
|
+
diagnoses: Array<DiagnosisData>;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export interface PatientNote {
|
|
31
|
+
id: string;
|
|
32
|
+
diagnoses: string;
|
|
33
|
+
encounterDate: string;
|
|
34
|
+
encounterNote: string;
|
|
35
|
+
encounterNoteRecordedAt: string;
|
|
36
|
+
encounterProvider: string;
|
|
37
|
+
encounterProviderUuid?: string;
|
|
38
|
+
encounterProviderPersonUuid?: string;
|
|
39
|
+
encounterProviderRole: string;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export interface Concept {
|
|
43
|
+
display: string;
|
|
44
|
+
uuid: string;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export interface DiagnosisData {
|
|
48
|
+
uuid: string;
|
|
49
|
+
display: string;
|
|
50
|
+
conceptClass: {
|
|
51
|
+
uuid: string;
|
|
52
|
+
name: string;
|
|
53
|
+
description: string;
|
|
54
|
+
};
|
|
55
|
+
names: Array<ConceptNames>;
|
|
56
|
+
mappings: Array<ConceptMapping>;
|
|
57
|
+
voided: boolean;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export interface ConceptNames {
|
|
61
|
+
uuid: string;
|
|
62
|
+
name: string;
|
|
63
|
+
conceptNameType: string;
|
|
64
|
+
}
|
|
65
|
+
export interface ConceptMapping {
|
|
66
|
+
conceptMapType: {
|
|
67
|
+
uuid: string;
|
|
68
|
+
display: string;
|
|
69
|
+
};
|
|
70
|
+
conceptReferenceTerm: {
|
|
71
|
+
display: string;
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export interface DisplayMetadata {
|
|
76
|
+
display?: string;
|
|
77
|
+
links?: Links;
|
|
78
|
+
uuid?: string;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export interface SessionData {
|
|
82
|
+
authenticated: boolean;
|
|
83
|
+
locale: string;
|
|
84
|
+
currentProvider: {
|
|
85
|
+
uuid: string;
|
|
86
|
+
display: string;
|
|
87
|
+
person: DisplayMetadata;
|
|
88
|
+
identifier: string;
|
|
89
|
+
attributes: Array<{}>;
|
|
90
|
+
retired: boolean;
|
|
91
|
+
links: Links;
|
|
92
|
+
resourceVersion: string;
|
|
93
|
+
};
|
|
94
|
+
sessionLocation: {
|
|
95
|
+
uuid: string;
|
|
96
|
+
display: string;
|
|
97
|
+
name: string;
|
|
98
|
+
description?: string;
|
|
99
|
+
};
|
|
100
|
+
user: {
|
|
101
|
+
uuid: string;
|
|
102
|
+
display: string;
|
|
103
|
+
username: string;
|
|
104
|
+
};
|
|
105
|
+
privileges: Array<DisplayMetadata>;
|
|
106
|
+
roles: Array<DisplayMetadata>;
|
|
107
|
+
retired: false;
|
|
108
|
+
links: Links;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export type Links = Array<{
|
|
112
|
+
rel: string;
|
|
113
|
+
uri: string;
|
|
114
|
+
}>;
|
|
115
|
+
|
|
116
|
+
export interface Provider {
|
|
117
|
+
uuid: string;
|
|
118
|
+
display: string;
|
|
119
|
+
person: {
|
|
120
|
+
uuid: string;
|
|
121
|
+
display: string;
|
|
122
|
+
links: Links;
|
|
123
|
+
};
|
|
124
|
+
identifier: string;
|
|
125
|
+
attributes: Array<any>;
|
|
126
|
+
retired: boolean;
|
|
127
|
+
links: Links;
|
|
128
|
+
resourceVersion: string;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
export interface Location {
|
|
132
|
+
uuid: string;
|
|
133
|
+
display: string;
|
|
134
|
+
name: string;
|
|
135
|
+
description?: string;
|
|
136
|
+
address1?: string;
|
|
137
|
+
address2?: string;
|
|
138
|
+
cityVillage?: string;
|
|
139
|
+
stateProvince?: string;
|
|
140
|
+
country?: string;
|
|
141
|
+
postalCode?: string;
|
|
142
|
+
latitude?: string;
|
|
143
|
+
longitude?: string;
|
|
144
|
+
countryDistrict?: string;
|
|
145
|
+
address3?: string;
|
|
146
|
+
address4?: string;
|
|
147
|
+
address5?: string;
|
|
148
|
+
address6?: string;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
export interface ObsData {
|
|
152
|
+
uuid?: string;
|
|
153
|
+
concept: Concept;
|
|
154
|
+
value?: string | any;
|
|
155
|
+
groupMembers?: Array<{
|
|
156
|
+
concept: Concept;
|
|
157
|
+
value?: string | any;
|
|
158
|
+
}>;
|
|
159
|
+
obsDatetime: string;
|
|
160
|
+
voided?: boolean;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
export interface Diagnosis {
|
|
164
|
+
patient: string;
|
|
165
|
+
diagnosis: {
|
|
166
|
+
coded: string;
|
|
167
|
+
};
|
|
168
|
+
certainty: string;
|
|
169
|
+
rank: number;
|
|
170
|
+
display: string;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
export interface DiagnosisPayload {
|
|
174
|
+
encounter: string;
|
|
175
|
+
patient: string;
|
|
176
|
+
condition: null;
|
|
177
|
+
diagnosis: {
|
|
178
|
+
coded: string;
|
|
179
|
+
};
|
|
180
|
+
certainty: string;
|
|
181
|
+
rank: number;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
export interface VisitNotePayload {
|
|
185
|
+
encounterDatetime: string; // date and time the encounter was created (ISO8601 Long) (REQUIRED)
|
|
186
|
+
encounterType: string; // uuid of the encounter type - initial visit, return visit etc. (REQUIRED)
|
|
187
|
+
patient: string; // the patient to whom the encounter applies
|
|
188
|
+
location: string; // the location at which the encounter occurred (REQUIRED)
|
|
189
|
+
encounterProviders: Array<{ encounterRole: string; provider: string }>; // array of providers and their role within the encounter. At least 1 provider is required
|
|
190
|
+
obs: Array<ObsPayload>; // array of observations and values for the encounter
|
|
191
|
+
form: string; // target form uuid to be filled for the encounter
|
|
192
|
+
orders?: Array<any>; // list of orders created during the encounter
|
|
193
|
+
visit?: string; // when creating an encounter for a specific visit, this specifies the visit
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
export interface ObsPayload {
|
|
197
|
+
concept: Concept;
|
|
198
|
+
value?: string;
|
|
199
|
+
groupMembers?: Array<{
|
|
200
|
+
concept: Concept;
|
|
201
|
+
value: string;
|
|
202
|
+
}>;
|
|
203
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import React, { type ComponentProps } from 'react';
|
|
2
|
+
import { useTranslation } from 'react-i18next';
|
|
3
|
+
import { ActionMenuButton2, PenIcon } from '@openmrs/esm-framework';
|
|
4
|
+
import { useStartVisitIfNeeded, type PatientChartWorkspaceActionButtonProps } from '@openmrs/esm-patient-common-lib';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* This button uses the patient chart store and MUST only be used
|
|
8
|
+
* within the patient chart
|
|
9
|
+
*/
|
|
10
|
+
const VisitNoteActionButton: React.FC<PatientChartWorkspaceActionButtonProps> = ({ groupProps: { patientUuid } }) => {
|
|
11
|
+
const { t } = useTranslation();
|
|
12
|
+
|
|
13
|
+
const startVisitIfNeeded = useStartVisitIfNeeded(patientUuid);
|
|
14
|
+
|
|
15
|
+
return (
|
|
16
|
+
<ActionMenuButton2
|
|
17
|
+
icon={(props: ComponentProps<typeof PenIcon>) => <PenIcon {...props} />}
|
|
18
|
+
label={t('visitNote', 'Visit note')}
|
|
19
|
+
workspaceToLaunch={{
|
|
20
|
+
workspaceName: 'visit-notes-form-workspace',
|
|
21
|
+
workspaceProps: {},
|
|
22
|
+
}}
|
|
23
|
+
onBeforeWorkspaceLaunch={startVisitIfNeeded}
|
|
24
|
+
/>
|
|
25
|
+
);
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
export default VisitNoteActionButton;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { vi, describe, it, expect } from 'vitest';
|
|
3
|
+
import { screen, render } from '@testing-library/react';
|
|
4
|
+
import { type LayoutType, useLayoutType } from '@openmrs/esm-framework';
|
|
5
|
+
import VisitNoteActionButton from './visit-note-action-button.extension';
|
|
6
|
+
import { mockPatient } from 'tools';
|
|
7
|
+
|
|
8
|
+
const mockUseLayoutType = vi.mocked(useLayoutType);
|
|
9
|
+
|
|
10
|
+
vi.mock('@openmrs/esm-patient-common-lib', async () => {
|
|
11
|
+
const originalModule = (await vi.importActual('@openmrs/esm-patient-common-lib')) as object;
|
|
12
|
+
|
|
13
|
+
return {
|
|
14
|
+
...originalModule,
|
|
15
|
+
useStartVisitIfNeeded: vi.fn(() => () => Promise.resolve(true)),
|
|
16
|
+
};
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
describe('VisitNoteActionButton', () => {
|
|
20
|
+
it('should display tablet view', async () => {
|
|
21
|
+
mockUseLayoutType.mockReturnValue('tablet');
|
|
22
|
+
|
|
23
|
+
render(
|
|
24
|
+
<VisitNoteActionButton
|
|
25
|
+
groupProps={{ patientUuid: 'patient-uuid', mutateVisitContext: null, patient: null, visitContext: null }}
|
|
26
|
+
/>,
|
|
27
|
+
);
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it('should display desktop view', async () => {
|
|
31
|
+
mockUseLayoutType.mockReturnValue('desktop' as LayoutType);
|
|
32
|
+
|
|
33
|
+
render(
|
|
34
|
+
<VisitNoteActionButton
|
|
35
|
+
groupProps={{ patientUuid: mockPatient.id, patient: mockPatient, visitContext: null, mutateVisitContext: null }}
|
|
36
|
+
/>,
|
|
37
|
+
);
|
|
38
|
+
|
|
39
|
+
const visitNoteButton = screen.getByRole('button', { name: /Note/i });
|
|
40
|
+
expect(visitNoteButton).toBeInTheDocument();
|
|
41
|
+
});
|
|
42
|
+
});
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
{
|
|
2
|
+
"add": "Add",
|
|
3
|
+
"addImage": "Add image",
|
|
4
|
+
"addVisitNote": "Add a visit note",
|
|
5
|
+
"clinicalNoteLabel": "Write your notes",
|
|
6
|
+
"clinicalNotePlaceholder": "Write any notes here",
|
|
7
|
+
"confirmDeleteStickyNote": "Confirm delete sticky note",
|
|
8
|
+
"confirmDeleteStickyNoteMessage": "Are you sure you want to delete this sticky note?",
|
|
9
|
+
"date": "Date",
|
|
10
|
+
"deleteStickyNote": "Delete sticky note",
|
|
11
|
+
"deleting": "Deleting",
|
|
12
|
+
"diagnoses": "Diagnoses",
|
|
13
|
+
"discard": "Discard",
|
|
14
|
+
"editStickyNote": "Edit sticky note",
|
|
15
|
+
"emptyDiagnosisText": "No diagnosis selected — Enter a diagnosis below",
|
|
16
|
+
"enterNotePlaceholder": "Enter your sticky note here...",
|
|
17
|
+
"enterPrimaryDiagnoses": "Enter Primary diagnoses",
|
|
18
|
+
"enterSecondaryDiagnoses": "Enter Secondary diagnoses",
|
|
19
|
+
"error": "Error",
|
|
20
|
+
"errorDeletingStickyNote": "Error deleting sticky note",
|
|
21
|
+
"errorDeletingStickyNoteMessage": "An error occurred while deleting the sticky note",
|
|
22
|
+
"errorFetchingConcepts": "There was a problem fetching concepts",
|
|
23
|
+
"errorLoadingStickyNote": "Couldn't load sticky note.",
|
|
24
|
+
"errorSavingStickyNote": "Error saving sticky note",
|
|
25
|
+
"errorTransformingDiagnoses": "Error transforming diagnoses",
|
|
26
|
+
"image": "Image",
|
|
27
|
+
"imageRemoved": "Image removed",
|
|
28
|
+
"imageUploadHelperText": "Upload an image or use this device's camera to capture an image",
|
|
29
|
+
"noMatchingDiagnoses": "No diagnoses found matching",
|
|
30
|
+
"note": "Note",
|
|
31
|
+
"noVisitNoteToDisplay": "No visit note to display",
|
|
32
|
+
"primaryDiagnosis": "Primary diagnosis",
|
|
33
|
+
"primaryDiagnosisInputPlaceholder": "Choose a primary diagnosis",
|
|
34
|
+
"primaryDiagnosisRequired": "Choose at least one primary diagnosis",
|
|
35
|
+
"retry": "Retry",
|
|
36
|
+
"saveAndClose": "Save and close",
|
|
37
|
+
"saving": "Saving",
|
|
38
|
+
"searchForPrimaryDiagnosis": "Search for a primary diagnosis",
|
|
39
|
+
"searchForSecondaryDiagnosis": "Search for a secondary diagnosis",
|
|
40
|
+
"secondaryDiagnosis": "Secondary diagnosis",
|
|
41
|
+
"secondaryDiagnosisInputPlaceholder": "Choose a secondary diagnosis",
|
|
42
|
+
"seeAll": "See all",
|
|
43
|
+
"stickyNote": "Sticky note",
|
|
44
|
+
"stickyNoteCreated": "Sticky note created",
|
|
45
|
+
"stickyNoteDeleted": "Sticky note deleted",
|
|
46
|
+
"stickyNoteDeletedSuccessfully": "The sticky note was deleted successfully",
|
|
47
|
+
"stickyNoteUpdated": "Sticky note updated",
|
|
48
|
+
"visitDate": "Visit date",
|
|
49
|
+
"visitNote": "Visit note",
|
|
50
|
+
"visitNoteNowVisible": "It is now visible on the Encounters page",
|
|
51
|
+
"visitNotes": "Visit notes",
|
|
52
|
+
"visitNoteSaved": "Visit note saved",
|
|
53
|
+
"visitNoteSaveError": "Error saving visit note",
|
|
54
|
+
"visitNoteWorkspaceTitle": "Visit Note"
|
|
55
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
{
|
|
2
|
+
"add": "أضف",
|
|
3
|
+
"addImage": "أضف صورة",
|
|
4
|
+
"addVisitNote": "أضف ملاحظة للزيارة",
|
|
5
|
+
"clinicalNoteLabel": "اكتب ملاحظاتك",
|
|
6
|
+
"clinicalNotePlaceholder": "اكتب أي ملاحظات هنا",
|
|
7
|
+
"confirmDeleteStickyNote": "Confirm delete sticky note",
|
|
8
|
+
"confirmDeleteStickyNoteMessage": "Are you sure you want to delete this sticky note?",
|
|
9
|
+
"date": "تاريخ",
|
|
10
|
+
"deleteStickyNote": "Delete sticky note",
|
|
11
|
+
"deleting": "Deleting",
|
|
12
|
+
"diagnoses": "التشخيصات",
|
|
13
|
+
"discard": "تجاهل",
|
|
14
|
+
"editStickyNote": "Edit sticky note",
|
|
15
|
+
"emptyDiagnosisText": "لم يتم اختيار تشخيص — أدخل تشخيصًا أدناه",
|
|
16
|
+
"enterNotePlaceholder": "Enter your sticky note here...",
|
|
17
|
+
"enterPrimaryDiagnoses": "أدخل التشخيص الرئيسي",
|
|
18
|
+
"enterSecondaryDiagnoses": "أدخل التشخيص الثانوي",
|
|
19
|
+
"error": "Error",
|
|
20
|
+
"errorDeletingStickyNote": "Error deleting sticky note",
|
|
21
|
+
"errorDeletingStickyNoteMessage": "An error occurred while deleting the sticky note",
|
|
22
|
+
"errorFetchingConcepts": "There was a problem fetching concepts",
|
|
23
|
+
"errorLoadingStickyNote": "Couldn't load sticky note.",
|
|
24
|
+
"errorSavingStickyNote": "Error saving sticky note",
|
|
25
|
+
"errorTransformingDiagnoses": "Error transforming diagnoses",
|
|
26
|
+
"image": "صورة",
|
|
27
|
+
"imageRemoved": "Image removed",
|
|
28
|
+
"imageUploadHelperText": "قم بتحميل صورة أو استخدم كاميرا هذا الجهاز لالتقاط صورة",
|
|
29
|
+
"noMatchingDiagnoses": "لم يتم العثور على تشخيصات مطابقة",
|
|
30
|
+
"note": "ملاحظة",
|
|
31
|
+
"noVisitNoteToDisplay": "لا توجد ملاحظة للزيارة لعرضها",
|
|
32
|
+
"primaryDiagnosis": "التشخيص الرئيسي",
|
|
33
|
+
"primaryDiagnosisInputPlaceholder": "اختر التشخيص الرئيسي",
|
|
34
|
+
"primaryDiagnosisRequired": "Choose at least one primary diagnosis",
|
|
35
|
+
"retry": "Retry",
|
|
36
|
+
"saveAndClose": "حفظ وإغلاق",
|
|
37
|
+
"saving": "Saving",
|
|
38
|
+
"searchForPrimaryDiagnosis": "ابحث عن التشخيص الرئيسي",
|
|
39
|
+
"searchForSecondaryDiagnosis": "ابحث عن التشخيص الثانوي",
|
|
40
|
+
"secondaryDiagnosis": "التشخيص الثانوي",
|
|
41
|
+
"secondaryDiagnosisInputPlaceholder": "اختر التشخيص الثانوي",
|
|
42
|
+
"seeAll": "عرض الكل",
|
|
43
|
+
"stickyNote": "Sticky note",
|
|
44
|
+
"stickyNoteCreated": "Sticky note created",
|
|
45
|
+
"stickyNoteDeleted": "Sticky note deleted",
|
|
46
|
+
"stickyNoteDeletedSuccessfully": "The sticky note was deleted successfully",
|
|
47
|
+
"stickyNoteUpdated": "Sticky note updated",
|
|
48
|
+
"visitDate": "تاريخ الزيارة",
|
|
49
|
+
"visitNote": "ملاحظة الزيارة",
|
|
50
|
+
"visitNoteNowVisible": "أصبحت مرئية الآن في صفحة اللقاءات",
|
|
51
|
+
"visitNotes": "ملاحظات الزيارة",
|
|
52
|
+
"visitNoteSaved": "تم حفظ ملاحظة الزيارة",
|
|
53
|
+
"visitNoteSaveError": "خطأ في حفظ ملاحظة الزيارة",
|
|
54
|
+
"visitNoteWorkspaceTitle": "ملاحظة الزيارة"
|
|
55
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
{
|
|
2
|
+
"add": "Add",
|
|
3
|
+
"addImage": "Add image",
|
|
4
|
+
"addVisitNote": "Add a visit note",
|
|
5
|
+
"clinicalNoteLabel": "Write your notes",
|
|
6
|
+
"clinicalNotePlaceholder": "Write any notes here",
|
|
7
|
+
"confirmDeleteStickyNote": "Confirm delete sticky note",
|
|
8
|
+
"confirmDeleteStickyNoteMessage": "Are you sure you want to delete this sticky note?",
|
|
9
|
+
"date": "Date",
|
|
10
|
+
"deleteStickyNote": "Delete sticky note",
|
|
11
|
+
"deleting": "Deleting",
|
|
12
|
+
"diagnoses": "Diagnoses",
|
|
13
|
+
"discard": "Discard",
|
|
14
|
+
"editStickyNote": "Edit sticky note",
|
|
15
|
+
"emptyDiagnosisText": "No diagnosis selected — Enter a diagnosis below",
|
|
16
|
+
"enterNotePlaceholder": "Enter your sticky note here...",
|
|
17
|
+
"enterPrimaryDiagnoses": "Enter Primary diagnoses",
|
|
18
|
+
"enterSecondaryDiagnoses": "Enter Secondary diagnoses",
|
|
19
|
+
"error": "Error",
|
|
20
|
+
"errorDeletingStickyNote": "Error deleting sticky note",
|
|
21
|
+
"errorDeletingStickyNoteMessage": "An error occurred while deleting the sticky note",
|
|
22
|
+
"errorFetchingConcepts": "There was a problem fetching concepts",
|
|
23
|
+
"errorLoadingStickyNote": "Couldn't load sticky note.",
|
|
24
|
+
"errorSavingStickyNote": "Error saving sticky note",
|
|
25
|
+
"errorTransformingDiagnoses": "Error transforming diagnoses",
|
|
26
|
+
"image": "Image",
|
|
27
|
+
"imageRemoved": "Image removed",
|
|
28
|
+
"imageUploadHelperText": "Upload an image or use this device's camera to capture an image",
|
|
29
|
+
"noMatchingDiagnoses": "No diagnoses found matching",
|
|
30
|
+
"note": "Note",
|
|
31
|
+
"noVisitNoteToDisplay": "No visit note to display",
|
|
32
|
+
"primaryDiagnosis": "Primary diagnosis",
|
|
33
|
+
"primaryDiagnosisInputPlaceholder": "Choose a primary diagnosis",
|
|
34
|
+
"primaryDiagnosisRequired": "Choose at least one primary diagnosis",
|
|
35
|
+
"retry": "Retry",
|
|
36
|
+
"saveAndClose": "Save and close",
|
|
37
|
+
"saving": "Saving",
|
|
38
|
+
"searchForPrimaryDiagnosis": "Search for a primary diagnosis",
|
|
39
|
+
"searchForSecondaryDiagnosis": "Search for a secondary diagnosis",
|
|
40
|
+
"secondaryDiagnosis": "Secondary diagnosis",
|
|
41
|
+
"secondaryDiagnosisInputPlaceholder": "Choose a secondary diagnosis",
|
|
42
|
+
"seeAll": "See all",
|
|
43
|
+
"stickyNote": "Sticky note",
|
|
44
|
+
"stickyNoteCreated": "Sticky note created",
|
|
45
|
+
"stickyNoteDeleted": "Sticky note deleted",
|
|
46
|
+
"stickyNoteDeletedSuccessfully": "The sticky note was deleted successfully",
|
|
47
|
+
"stickyNoteUpdated": "Sticky note updated",
|
|
48
|
+
"visitDate": "Visit date",
|
|
49
|
+
"visitNote": "Visit note",
|
|
50
|
+
"visitNoteNowVisible": "It is now visible on the Encounters page",
|
|
51
|
+
"visitNotes": "Visit notes",
|
|
52
|
+
"visitNoteSaved": "Visit note saved",
|
|
53
|
+
"visitNoteSaveError": "Error saving visit note",
|
|
54
|
+
"visitNoteWorkspaceTitle": "Visit Note"
|
|
55
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
{
|
|
2
|
+
"add": "Add",
|
|
3
|
+
"addImage": "Add image",
|
|
4
|
+
"addVisitNote": "Add a visit note",
|
|
5
|
+
"clinicalNoteLabel": "Write your notes",
|
|
6
|
+
"clinicalNotePlaceholder": "Write any notes here",
|
|
7
|
+
"confirmDeleteStickyNote": "Confirm delete sticky note",
|
|
8
|
+
"confirmDeleteStickyNoteMessage": "Are you sure you want to delete this sticky note?",
|
|
9
|
+
"date": "Date",
|
|
10
|
+
"deleteStickyNote": "Delete sticky note",
|
|
11
|
+
"deleting": "Deleting",
|
|
12
|
+
"diagnoses": "Diagnoses",
|
|
13
|
+
"discard": "Discard",
|
|
14
|
+
"editStickyNote": "Edit sticky note",
|
|
15
|
+
"emptyDiagnosisText": "No diagnosis selected — Enter a diagnosis below",
|
|
16
|
+
"enterNotePlaceholder": "Enter your sticky note here...",
|
|
17
|
+
"enterPrimaryDiagnoses": "Enter Primary diagnoses",
|
|
18
|
+
"enterSecondaryDiagnoses": "Enter Secondary diagnoses",
|
|
19
|
+
"error": "Error",
|
|
20
|
+
"errorDeletingStickyNote": "Error deleting sticky note",
|
|
21
|
+
"errorDeletingStickyNoteMessage": "An error occurred while deleting the sticky note",
|
|
22
|
+
"errorFetchingConcepts": "There was a problem fetching concepts",
|
|
23
|
+
"errorLoadingStickyNote": "Couldn't load sticky note.",
|
|
24
|
+
"errorSavingStickyNote": "Error saving sticky note",
|
|
25
|
+
"errorTransformingDiagnoses": "Error transforming diagnoses",
|
|
26
|
+
"image": "Image",
|
|
27
|
+
"imageRemoved": "Image removed",
|
|
28
|
+
"imageUploadHelperText": "Upload an image or use this device's camera to capture an image",
|
|
29
|
+
"noMatchingDiagnoses": "No diagnoses found matching",
|
|
30
|
+
"note": "Note",
|
|
31
|
+
"noVisitNoteToDisplay": "No visit note to display",
|
|
32
|
+
"primaryDiagnosis": "Primary diagnosis",
|
|
33
|
+
"primaryDiagnosisInputPlaceholder": "Choose a primary diagnosis",
|
|
34
|
+
"primaryDiagnosisRequired": "Choose at least one primary diagnosis",
|
|
35
|
+
"retry": "Retry",
|
|
36
|
+
"saveAndClose": "Save and close",
|
|
37
|
+
"saving": "Saving",
|
|
38
|
+
"searchForPrimaryDiagnosis": "Search for a primary diagnosis",
|
|
39
|
+
"searchForSecondaryDiagnosis": "Search for a secondary diagnosis",
|
|
40
|
+
"secondaryDiagnosis": "Secondary diagnosis",
|
|
41
|
+
"secondaryDiagnosisInputPlaceholder": "Choose a secondary diagnosis",
|
|
42
|
+
"seeAll": "See all",
|
|
43
|
+
"stickyNote": "Sticky note",
|
|
44
|
+
"stickyNoteCreated": "Sticky note created",
|
|
45
|
+
"stickyNoteDeleted": "Sticky note deleted",
|
|
46
|
+
"stickyNoteDeletedSuccessfully": "The sticky note was deleted successfully",
|
|
47
|
+
"stickyNoteUpdated": "Sticky note updated",
|
|
48
|
+
"visitDate": "Visit date",
|
|
49
|
+
"visitNote": "Visit note",
|
|
50
|
+
"visitNoteNowVisible": "It is now visible on the Encounters page",
|
|
51
|
+
"visitNotes": "Visit notes",
|
|
52
|
+
"visitNoteSaved": "Visit note saved",
|
|
53
|
+
"visitNoteSaveError": "Error saving visit note",
|
|
54
|
+
"visitNoteWorkspaceTitle": "Visit Note"
|
|
55
|
+
}
|