@kenyaemr/esm-patient-clinical-view-app 5.4.2-pre.2724 → 5.4.2-pre.2730
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/.turbo/turbo-build.log +3 -3
- package/dist/127.js +1 -1
- package/dist/40.js +1 -1
- package/dist/433.js +1 -0
- package/dist/433.js.map +1 -0
- package/dist/805.js +1 -1
- package/dist/805.js.map +1 -1
- package/dist/916.js +1 -1
- package/dist/kenyaemr-esm-patient-clinical-view-app.js.buildmanifest.json +39 -15
- package/dist/main.js +1 -1
- package/dist/main.js.map +1 -1
- package/dist/routes.json +1 -1
- package/package.json +1 -1
- package/src/case-management/wrap/wrap.component.tsx +3 -1
- package/src/complaints/complaints.resource.tsx +109 -0
- package/src/complaints/complaints.test.tsx +512 -0
- package/src/complaints/patient-complaints.component.tsx +121 -0
- package/src/complaints/patient-complaints.scss +11 -0
- package/src/config-schema.ts +26 -0
- package/src/index.ts +2 -1
- package/src/routes.json +8 -0
- package/translations/am.json +7 -0
- package/translations/en.json +7 -0
- package/translations/sw.json +7 -0
package/dist/routes.json
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"$schema":"https://json.openmrs.org/routes.schema.json","backendDependencies":{"kenyaemr":"^19.0.0"},"pages":[{"component":"wrapComponent","route":"case-management"}],"extensions":[{"name":"hiv-care-and-treatment-dashboard-link","component":"hivCareAndTreatmentLink","slot":"patient-chart-dashboard-slot","meta":{"columns":1,"columnSpan":1,"slot":"patient-chart-hiv-care-and-treatment-dashboard-slot","path":"hiv-care-and-treatment-dashboard","layoutMode":"anchored"}},{"name":"hiv-care-and-treatment-dashboard","slot":"patient-chart-hiv-care-and-treatment-dashboard-slot","component":"hivCareAndTreatment","order":0,"online":true,"offline":false},{"name":"relationship-dashboard-link","component":"relationshipsLink","order":24,"online":true,"offline":false,"meta":{"columns":1,"columnSpan":1,"slot":"patient-chart-relationship-slot","path":"relationships","layoutMode":"anchored"}},{"name":"relationship-dashboard","slot":"patient-chart-relationship-slot","component":"relationships","order":0,"online":true,"offline":false},{"name":"clinical-encounter-dashboard-link","component":"clinicalEncounterLink","order":25,"meta":{"columns":1,"columnSpan":1,"slot":"patient-chart-clinical-encounter-slot","path":"clinical-encounter","layoutMode":"anchored"}},{"name":"clinical-encounter-dashboard","slot":"patient-chart-clinical-encounter-slot","component":"clinicalEncounter","order":0,"online":true,"offline":false},{"name":"maternal-and-child-health-dashboard-link","component":"maternalAndChildHealthDashboardLink","order":26,"meta":{"columns":1,"columnSpan":1,"slot":"patient-chart-maternal-and-child-health-slot","path":"maternal-and-child-health","layoutMode":"anchored"}},{"name":"maternal-and-child-health-dashboard","slot":"patient-chart-maternal-and-child-health-slot","component":"maternalAndChildHealthDashboard","order":0,"online":true,"offline":false},{"name":"maternal-and-child-health-partograph","slot":"maternal-and-child-health-partograph-slot","component":"partograph","order":0,"online":true,"offline":false},{"name":"contact-list-dashboard-link","component":"contactListLink","order":27,"meta":{"columns":1,"columnSpan":1,"slot":"patient-chart-contact-list-slot","path":"contact-list","layoutMode":"anchored"}},{"name":"contact-list-dashboard","slot":"patient-chart-contact-list-slot","component":"contactList","order":0,"online":true,"offline":false},{"component":"caseManagementDashboardLink","name":"case-management-dashboard-link","meta":{"name":"case-management","title":"Case Management","slot":"case-management-dashboard-slot","path":"/case-management"}},{"name":"wrap-component-view","slot":"case-management-dashboard-slot","component":"wrapComponent","order":2,"online":true,"offline":false},{"name":"case-encounter-link","component":"caseEncounterDashboardLink","order":14,"meta":{"columns":1,"columnSpan":1,"slot":"patient-chart-case-encounter-slot","path":"case-management-encounters","layoutMode":"anchored"}},{"name":"case-encounter-table","slot":"patient-chart-case-encounter-slot","component":"caseEncounterTable","order":0,"online":true,"offline":false},{"component":"peerCalendarDashboardLink","name":"peer-calendar-dashboard-link","meta":{"name":"peer-calendar","title":"Peer Calendar","slot":"peer-calendar-dashboard-slot","path":"peer-management"}},{"name":"peer-calendar","slot":"peer-calendar-dashboard-slot","component":"peerCalendar","order":0,"online":true,"offline":false},{"name":"special-clinics-dashboard-link","component":"specialClinicsDashboardLink","order":15,"meta":{"columns":1,"columnSpan":1,"slot":"patient-chart-special-clinics-slot","path":"special-clinics","layoutMode":"anchored"}},{"name":"special-clinics-dashboard","slot":"patient-chart-special-clinics-slot","component":"specialClinicsDashboard","order":0,"online":true,"offline":false}],"modals":[{"name":"birth-date-calculator","component":"birthDateCalculator"},{"name":"relationship-delete-confirm-dialog","component":"relationshipDeleteConfirmialog"},{"name":"end-relationship-dialog","component":"endRelationshipModal"}],"workspaces":[{"name":"contact-list-form","component":"contactListForm","title":"Contact List Form","type":"form"},{"name":"case-management-form","component":"caseManagementForm","title":"Case Management Form","type":"form"},{"name":"add-patient-case-form","component":"addPatientCaseForm","title":"Add patient case Form","type":"form"},{"name":"family-relationship-form","component":"familyRelationshipForm","title":"Family Relationship Form","type":"form"},{"name":"peers-form","component":"peersForm","title":"Add New Peer","type":"form"},{"name":"kenyaemr-cusom-form-entry-workspace","component":"peerCalendarFormEntry","title":"KVP Peer Educator Outreach Calendar","type":"form","width":"extra-wide","canMaximize":true,"canHide":true},{"name":"contact-list-update-form","component":"contactListUpdateForm","title":"Contact List Update Form","type":"form"},{"name":"other-relationship-form","component":"otherRelationshipsForm","title":"Other Relationships Form","type":"form"},{"name":"end-relationship-form","component":"endRelationshipWorkspace","title":"Discontinue relationship form","type":"form"}],"version":"5.4.2-pre.
|
|
1
|
+
{"$schema":"https://json.openmrs.org/routes.schema.json","backendDependencies":{"kenyaemr":"^19.0.0"},"pages":[{"component":"wrapComponent","route":"case-management"}],"extensions":[{"name":"hiv-care-and-treatment-dashboard-link","component":"hivCareAndTreatmentLink","slot":"patient-chart-dashboard-slot","meta":{"columns":1,"columnSpan":1,"slot":"patient-chart-hiv-care-and-treatment-dashboard-slot","path":"hiv-care-and-treatment-dashboard","layoutMode":"anchored"}},{"name":"hiv-care-and-treatment-dashboard","slot":"patient-chart-hiv-care-and-treatment-dashboard-slot","component":"hivCareAndTreatment","order":0,"online":true,"offline":false},{"name":"relationship-dashboard-link","component":"relationshipsLink","order":24,"online":true,"offline":false,"meta":{"columns":1,"columnSpan":1,"slot":"patient-chart-relationship-slot","path":"relationships","layoutMode":"anchored"}},{"name":"relationship-dashboard","slot":"patient-chart-relationship-slot","component":"relationships","order":0,"online":true,"offline":false},{"name":"clinical-encounter-dashboard-link","component":"clinicalEncounterLink","order":25,"meta":{"columns":1,"columnSpan":1,"slot":"patient-chart-clinical-encounter-slot","path":"clinical-encounter","layoutMode":"anchored"}},{"name":"clinical-encounter-dashboard","slot":"patient-chart-clinical-encounter-slot","component":"clinicalEncounter","order":0,"online":true,"offline":false},{"name":"maternal-and-child-health-dashboard-link","component":"maternalAndChildHealthDashboardLink","order":26,"meta":{"columns":1,"columnSpan":1,"slot":"patient-chart-maternal-and-child-health-slot","path":"maternal-and-child-health","layoutMode":"anchored"}},{"name":"maternal-and-child-health-dashboard","slot":"patient-chart-maternal-and-child-health-slot","component":"maternalAndChildHealthDashboard","order":0,"online":true,"offline":false},{"name":"maternal-and-child-health-partograph","slot":"maternal-and-child-health-partograph-slot","component":"partograph","order":0,"online":true,"offline":false},{"name":"contact-list-dashboard-link","component":"contactListLink","order":27,"meta":{"columns":1,"columnSpan":1,"slot":"patient-chart-contact-list-slot","path":"contact-list","layoutMode":"anchored"}},{"name":"contact-list-dashboard","slot":"patient-chart-contact-list-slot","component":"contactList","order":0,"online":true,"offline":false},{"component":"caseManagementDashboardLink","name":"case-management-dashboard-link","meta":{"name":"case-management","title":"Case Management","slot":"case-management-dashboard-slot","path":"/case-management"}},{"name":"wrap-component-view","slot":"case-management-dashboard-slot","component":"wrapComponent","order":2,"online":true,"offline":false},{"name":"case-encounter-link","component":"caseEncounterDashboardLink","order":14,"meta":{"columns":1,"columnSpan":1,"slot":"patient-chart-case-encounter-slot","path":"case-management-encounters","layoutMode":"anchored"}},{"name":"case-encounter-table","slot":"patient-chart-case-encounter-slot","component":"caseEncounterTable","order":0,"online":true,"offline":false},{"component":"peerCalendarDashboardLink","name":"peer-calendar-dashboard-link","meta":{"name":"peer-calendar","title":"Peer Calendar","slot":"peer-calendar-dashboard-slot","path":"peer-management"}},{"name":"peer-calendar","slot":"peer-calendar-dashboard-slot","component":"peerCalendar","order":0,"online":true,"offline":false},{"name":"special-clinics-dashboard-link","component":"specialClinicsDashboardLink","order":15,"meta":{"columns":1,"columnSpan":1,"slot":"patient-chart-special-clinics-slot","path":"special-clinics","layoutMode":"anchored"}},{"name":"special-clinics-dashboard","slot":"patient-chart-special-clinics-slot","component":"specialClinicsDashboard","order":0,"online":true,"offline":false},{"name":"patient-complaints","slot":"ewf-patient-summary-slot","component":"patientComplaints","order":0,"online":true,"offline":false}],"modals":[{"name":"birth-date-calculator","component":"birthDateCalculator"},{"name":"relationship-delete-confirm-dialog","component":"relationshipDeleteConfirmialog"},{"name":"end-relationship-dialog","component":"endRelationshipModal"}],"workspaces":[{"name":"contact-list-form","component":"contactListForm","title":"Contact List Form","type":"form"},{"name":"case-management-form","component":"caseManagementForm","title":"Case Management Form","type":"form"},{"name":"add-patient-case-form","component":"addPatientCaseForm","title":"Add patient case Form","type":"form"},{"name":"family-relationship-form","component":"familyRelationshipForm","title":"Family Relationship Form","type":"form"},{"name":"peers-form","component":"peersForm","title":"Add New Peer","type":"form"},{"name":"kenyaemr-cusom-form-entry-workspace","component":"peerCalendarFormEntry","title":"KVP Peer Educator Outreach Calendar","type":"form","width":"extra-wide","canMaximize":true,"canHide":true},{"name":"contact-list-update-form","component":"contactListUpdateForm","title":"Contact List Update Form","type":"form"},{"name":"other-relationship-form","component":"otherRelationshipsForm","title":"Other Relationships Form","type":"form"},{"name":"end-relationship-form","component":"endRelationshipWorkspace","title":"Discontinue relationship form","type":"form"}],"version":"5.4.2-pre.2730"}
|
package/package.json
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import React, { useState } from 'react';
|
|
2
|
+
import { WorkspaceContainer } from '@openmrs/esm-framework';
|
|
3
|
+
|
|
2
4
|
import { ClaimManagementHeader } from '../header/case-management-header';
|
|
3
5
|
import CaseManagementTabs from '../tabs/case-management-tabs.component';
|
|
4
6
|
import MetricsHeader from '../metrics/case-management-header.component';
|
|
5
|
-
|
|
6
7
|
const WrapComponent: React.FC = () => {
|
|
7
8
|
const [activeTabIndex, setActiveTabIndex] = useState<number>(0);
|
|
8
9
|
|
|
@@ -11,6 +12,7 @@ const WrapComponent: React.FC = () => {
|
|
|
11
12
|
<ClaimManagementHeader title={'Home'} />
|
|
12
13
|
<MetricsHeader activeTabIndex={activeTabIndex} />
|
|
13
14
|
<CaseManagementTabs setActiveTabIndex={setActiveTabIndex} />
|
|
15
|
+
<WorkspaceContainer key="case-management" contextKey="case-management" />
|
|
14
16
|
</div>
|
|
15
17
|
);
|
|
16
18
|
};
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import useSWR from 'swr';
|
|
2
|
+
import { useMemo } from 'react';
|
|
3
|
+
import {
|
|
4
|
+
Encounter,
|
|
5
|
+
makeUrl,
|
|
6
|
+
openmrsFetch,
|
|
7
|
+
restBaseUrl,
|
|
8
|
+
useOpenmrsPagination,
|
|
9
|
+
formatDate,
|
|
10
|
+
parseDate,
|
|
11
|
+
} from '@openmrs/esm-framework';
|
|
12
|
+
|
|
13
|
+
export function usePaginatedEncounters(patientUuid: string, encounterType: string, pageSize: number) {
|
|
14
|
+
const customRep = `custom:(uuid,display,diagnoses:(uuid,display,rank,diagnosis,certainty,voided),encounterDatetime,form:(uuid,display,name,description,encounterType,version,resources:(uuid,display,name,valueReference)),encounterType,visit,patient,obs:(uuid,concept:(uuid,display,conceptClass:(uuid,display)),display,groupMembers:(uuid,concept:(uuid,display),value:(uuid,display),display),value,obsDatetime),encounterProviders:(provider:(person)))`;
|
|
15
|
+
const url = new URL(makeUrl(`${restBaseUrl}/encounter`), window.location.toString());
|
|
16
|
+
url.searchParams.set('patient', patientUuid);
|
|
17
|
+
url.searchParams.set('v', customRep);
|
|
18
|
+
url.searchParams.set('order', 'desc');
|
|
19
|
+
encounterType && url.searchParams.set('encounterType', encounterType);
|
|
20
|
+
return useOpenmrsPagination<Encounter>(patientUuid ? url : null, pageSize);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const extractConceptLabels = (questions = []): Record<string, string> => {
|
|
24
|
+
const map: Record<string, string> = {};
|
|
25
|
+
|
|
26
|
+
questions.forEach((question) => {
|
|
27
|
+
// Extract labels from answers in questionOptions
|
|
28
|
+
if (question.questionOptions?.answers) {
|
|
29
|
+
question.questionOptions.answers.forEach((answer) => {
|
|
30
|
+
map[answer.concept] = answer.label;
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Recursively process nested questions (for obsGroup questions)
|
|
35
|
+
if (question.questions) {
|
|
36
|
+
Object.assign(map, extractConceptLabels(question.questions));
|
|
37
|
+
}
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
return map;
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
export const useForm = (formUuid: string) => {
|
|
44
|
+
const url = `${restBaseUrl}/o3/forms/${formUuid}`;
|
|
45
|
+
const { data, isLoading, error, mutate } = useSWR<{ data: Record<string, any> }>(url, openmrsFetch);
|
|
46
|
+
|
|
47
|
+
const conceptLabelMap = useMemo(() => {
|
|
48
|
+
if (!data?.data?.pages) {
|
|
49
|
+
return {};
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const map: Record<string, string> = {};
|
|
53
|
+
|
|
54
|
+
// Traverse pages -> sections -> questions
|
|
55
|
+
data.data.pages.forEach((page) => {
|
|
56
|
+
page.sections?.forEach((section) => {
|
|
57
|
+
if (section.questions) {
|
|
58
|
+
Object.assign(map, extractConceptLabels(section.questions));
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
return map;
|
|
64
|
+
}, [data]);
|
|
65
|
+
|
|
66
|
+
return { data, isLoading, error, mutate, conceptLabelMap };
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
export const extractComplaintsFromObservations = (
|
|
70
|
+
observations: any[],
|
|
71
|
+
config: {
|
|
72
|
+
complaints: {
|
|
73
|
+
chiefComplaintConceptUuid: string;
|
|
74
|
+
complaintMemberConceptUuid: string;
|
|
75
|
+
durationConceptUuid: string;
|
|
76
|
+
onsetConceptUuid: string;
|
|
77
|
+
};
|
|
78
|
+
},
|
|
79
|
+
conceptLabelMap: Record<string, string>,
|
|
80
|
+
) => {
|
|
81
|
+
return observations
|
|
82
|
+
.flatMap((observation) => observation)
|
|
83
|
+
.filter((obs) => obs.concept.uuid === config.complaints.chiefComplaintConceptUuid)
|
|
84
|
+
.map((obs) => {
|
|
85
|
+
const complaintMember = obs.groupMembers?.find(
|
|
86
|
+
(member) => member.concept.uuid === config.complaints.complaintMemberConceptUuid,
|
|
87
|
+
);
|
|
88
|
+
const durationMember = obs.groupMembers?.find(
|
|
89
|
+
(member) => member.concept.uuid === config.complaints.durationConceptUuid,
|
|
90
|
+
);
|
|
91
|
+
const onsetMember = obs.groupMembers?.find(
|
|
92
|
+
(member) => member.concept.uuid === config.complaints.onsetConceptUuid,
|
|
93
|
+
);
|
|
94
|
+
const onsetValue =
|
|
95
|
+
typeof onsetMember?.value === 'object' ? conceptLabelMap[onsetMember?.value?.['uuid']] : onsetMember?.value;
|
|
96
|
+
const onsetDate = obs.obsDatetime ? formatDate(parseDate(obs.obsDatetime), { mode: 'wide', noToday: true }) : '';
|
|
97
|
+
const onsetDisplay = [onsetDate, onsetValue].filter(Boolean).join(' - ') || '--';
|
|
98
|
+
|
|
99
|
+
return {
|
|
100
|
+
id: obs.uuid,
|
|
101
|
+
complaint:
|
|
102
|
+
(typeof complaintMember?.value === 'object' && complaintMember?.value?.display) ||
|
|
103
|
+
complaintMember?.display ||
|
|
104
|
+
'--',
|
|
105
|
+
duration: durationMember?.value || '--',
|
|
106
|
+
onset: onsetDisplay,
|
|
107
|
+
};
|
|
108
|
+
});
|
|
109
|
+
};
|
|
@@ -0,0 +1,512 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { render, screen, waitFor } from '@testing-library/react';
|
|
3
|
+
import userEvent from '@testing-library/user-event';
|
|
4
|
+
import '@testing-library/jest-dom';
|
|
5
|
+
import PatientComplaintsComponent from './patient-complaints.component';
|
|
6
|
+
import { usePaginatedEncounters, useForm, extractComplaintsFromObservations } from './complaints.resource';
|
|
7
|
+
import { useConfig, isDesktop } from '@openmrs/esm-framework';
|
|
8
|
+
import { usePaginationInfo } from '@openmrs/esm-patient-common-lib';
|
|
9
|
+
|
|
10
|
+
// Mock the dependencies
|
|
11
|
+
jest.mock('./complaints.resource', () => ({
|
|
12
|
+
usePaginatedEncounters: jest.fn(),
|
|
13
|
+
useForm: jest.fn(),
|
|
14
|
+
extractComplaintsFromObservations: jest.fn(),
|
|
15
|
+
}));
|
|
16
|
+
jest.mock('@openmrs/esm-framework', () => ({
|
|
17
|
+
...jest.requireActual('@openmrs/esm-framework'),
|
|
18
|
+
useConfig: jest.fn(),
|
|
19
|
+
isDesktop: jest.fn(),
|
|
20
|
+
ErrorState: ({ headerTitle }) => <div data-testid="error-state">{headerTitle}</div>,
|
|
21
|
+
}));
|
|
22
|
+
jest.mock('@openmrs/esm-patient-common-lib', () => ({
|
|
23
|
+
CardHeader: ({ title, children }) => (
|
|
24
|
+
<div>
|
|
25
|
+
{title}
|
|
26
|
+
{children}
|
|
27
|
+
</div>
|
|
28
|
+
),
|
|
29
|
+
EmptyState: ({ displayText }) => <div data-testid="empty-state">{displayText}</div>,
|
|
30
|
+
usePaginationInfo: jest.fn(),
|
|
31
|
+
}));
|
|
32
|
+
|
|
33
|
+
const mockUsePaginatedEncounters = usePaginatedEncounters as jest.Mock;
|
|
34
|
+
const mockUseForm = useForm as jest.Mock;
|
|
35
|
+
const mockUseConfig = useConfig as jest.Mock;
|
|
36
|
+
const mockIsDesktop = isDesktop as unknown as jest.Mock;
|
|
37
|
+
const mockUsePaginationInfo = usePaginationInfo as jest.Mock;
|
|
38
|
+
const mockExtractComplaintsFromObservations = extractComplaintsFromObservations as jest.Mock;
|
|
39
|
+
|
|
40
|
+
const mockConfig = {
|
|
41
|
+
encounterTypes: {
|
|
42
|
+
triage: 'd1059fb9-a079-4feb-a749-eedd709ae542',
|
|
43
|
+
},
|
|
44
|
+
formsList: {
|
|
45
|
+
complaintsFormUuid: '37f6bd8d-586a-4169-95fa-5781f987fe62',
|
|
46
|
+
},
|
|
47
|
+
complaints: {
|
|
48
|
+
chiefComplaintConceptUuid: '160531AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
|
|
49
|
+
complaintMemberConceptUuid: '5219AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
|
|
50
|
+
durationConceptUuid: '159368AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
|
|
51
|
+
onsetConceptUuid: 'd7a3441d-6aeb-49be-b7d6-b2a3bb39e78d',
|
|
52
|
+
},
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
const mockEncounterData = [
|
|
56
|
+
{
|
|
57
|
+
uuid: 'encounter-1',
|
|
58
|
+
encounterDatetime: '2023-10-01T10:30:00',
|
|
59
|
+
obs: [
|
|
60
|
+
{
|
|
61
|
+
uuid: 'obs-1',
|
|
62
|
+
concept: {
|
|
63
|
+
uuid: '160531AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
|
|
64
|
+
},
|
|
65
|
+
obsDatetime: '2023-10-01T10:30:00',
|
|
66
|
+
groupMembers: [
|
|
67
|
+
{
|
|
68
|
+
uuid: 'obs-1-1',
|
|
69
|
+
concept: {
|
|
70
|
+
uuid: '5219AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
|
|
71
|
+
},
|
|
72
|
+
value: {
|
|
73
|
+
display: 'Headache',
|
|
74
|
+
},
|
|
75
|
+
display: 'Chief Complaint: Headache',
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
uuid: 'obs-1-2',
|
|
79
|
+
concept: {
|
|
80
|
+
uuid: '159368AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
|
|
81
|
+
},
|
|
82
|
+
value: '2 days',
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
uuid: 'obs-1-3',
|
|
86
|
+
concept: {
|
|
87
|
+
uuid: 'd7a3441d-6aeb-49be-b7d6-b2a3bb39e78d',
|
|
88
|
+
},
|
|
89
|
+
value: {
|
|
90
|
+
uuid: 'onset-uuid-1',
|
|
91
|
+
},
|
|
92
|
+
},
|
|
93
|
+
],
|
|
94
|
+
},
|
|
95
|
+
],
|
|
96
|
+
},
|
|
97
|
+
{
|
|
98
|
+
uuid: 'encounter-2',
|
|
99
|
+
encounterDatetime: '2023-09-25T14:15:00',
|
|
100
|
+
obs: [
|
|
101
|
+
{
|
|
102
|
+
uuid: 'obs-2',
|
|
103
|
+
concept: {
|
|
104
|
+
uuid: '160531AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
|
|
105
|
+
},
|
|
106
|
+
obsDatetime: '2023-09-25T14:15:00',
|
|
107
|
+
groupMembers: [
|
|
108
|
+
{
|
|
109
|
+
uuid: 'obs-2-1',
|
|
110
|
+
concept: {
|
|
111
|
+
uuid: '5219AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
|
|
112
|
+
},
|
|
113
|
+
value: {
|
|
114
|
+
display: 'Fever',
|
|
115
|
+
},
|
|
116
|
+
display: 'Chief Complaint: Fever',
|
|
117
|
+
},
|
|
118
|
+
{
|
|
119
|
+
uuid: 'obs-2-2',
|
|
120
|
+
concept: {
|
|
121
|
+
uuid: '159368AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
|
|
122
|
+
},
|
|
123
|
+
value: '5 days',
|
|
124
|
+
},
|
|
125
|
+
{
|
|
126
|
+
uuid: 'obs-2-3',
|
|
127
|
+
concept: {
|
|
128
|
+
uuid: 'd7a3441d-6aeb-49be-b7d6-b2a3bb39e78d',
|
|
129
|
+
},
|
|
130
|
+
value: {
|
|
131
|
+
uuid: 'onset-uuid-2',
|
|
132
|
+
},
|
|
133
|
+
},
|
|
134
|
+
],
|
|
135
|
+
},
|
|
136
|
+
],
|
|
137
|
+
},
|
|
138
|
+
];
|
|
139
|
+
|
|
140
|
+
const mockConceptLabelMap = {
|
|
141
|
+
'onset-uuid-1': 'Sudden',
|
|
142
|
+
'onset-uuid-2': 'Gradual',
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
const mockComplaints = [
|
|
146
|
+
{
|
|
147
|
+
id: 'obs-1',
|
|
148
|
+
complaint: 'Headache',
|
|
149
|
+
duration: '2 days',
|
|
150
|
+
onset: '01-Oct-2023 - Sudden',
|
|
151
|
+
},
|
|
152
|
+
{
|
|
153
|
+
id: 'obs-2',
|
|
154
|
+
complaint: 'Fever',
|
|
155
|
+
duration: '5 days',
|
|
156
|
+
onset: '25-Sep-2023 - Gradual',
|
|
157
|
+
},
|
|
158
|
+
];
|
|
159
|
+
|
|
160
|
+
describe('PatientComplaintsComponent', () => {
|
|
161
|
+
beforeEach(() => {
|
|
162
|
+
jest.clearAllMocks();
|
|
163
|
+
mockUseConfig.mockReturnValue(mockConfig);
|
|
164
|
+
mockIsDesktop.mockReturnValue(true);
|
|
165
|
+
mockUsePaginationInfo.mockReturnValue({
|
|
166
|
+
pageSizes: [5, 10, 15, 20],
|
|
167
|
+
});
|
|
168
|
+
// Default mock for extractComplaintsFromObservations
|
|
169
|
+
mockExtractComplaintsFromObservations.mockReturnValue([]);
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
it('should render loading skeleton when data is loading', () => {
|
|
173
|
+
mockUsePaginatedEncounters.mockReturnValue({
|
|
174
|
+
data: [],
|
|
175
|
+
isLoading: true,
|
|
176
|
+
error: null,
|
|
177
|
+
currentPage: 1,
|
|
178
|
+
goTo: jest.fn(),
|
|
179
|
+
totalPages: 1,
|
|
180
|
+
});
|
|
181
|
+
mockUseForm.mockReturnValue({
|
|
182
|
+
conceptLabelMap: {},
|
|
183
|
+
isLoading: true,
|
|
184
|
+
error: null,
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
render(<PatientComplaintsComponent patientUuid="test-patient-uuid" />);
|
|
188
|
+
|
|
189
|
+
expect(screen.getByRole('table')).toBeInTheDocument();
|
|
190
|
+
expect(screen.getByRole('table')).toHaveClass('cds--skeleton');
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
it('should render error state when encounters fail to load', () => {
|
|
194
|
+
const mockError = new Error('Failed to load encounters');
|
|
195
|
+
mockUsePaginatedEncounters.mockReturnValue({
|
|
196
|
+
data: [],
|
|
197
|
+
isLoading: false,
|
|
198
|
+
error: mockError,
|
|
199
|
+
currentPage: 1,
|
|
200
|
+
goTo: jest.fn(),
|
|
201
|
+
totalPages: 1,
|
|
202
|
+
});
|
|
203
|
+
mockUseForm.mockReturnValue({
|
|
204
|
+
conceptLabelMap: {},
|
|
205
|
+
isLoading: false,
|
|
206
|
+
error: null,
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
render(<PatientComplaintsComponent patientUuid="test-patient-uuid" />);
|
|
210
|
+
|
|
211
|
+
expect(screen.getByTestId('error-state')).toBeInTheDocument();
|
|
212
|
+
expect(screen.getByText('Complaints')).toBeInTheDocument();
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
it('should render error state when form fails to load', () => {
|
|
216
|
+
const mockError = new Error('Failed to load form');
|
|
217
|
+
mockUsePaginatedEncounters.mockReturnValue({
|
|
218
|
+
data: [],
|
|
219
|
+
isLoading: false,
|
|
220
|
+
error: null,
|
|
221
|
+
currentPage: 1,
|
|
222
|
+
goTo: jest.fn(),
|
|
223
|
+
totalPages: 1,
|
|
224
|
+
});
|
|
225
|
+
mockUseForm.mockReturnValue({
|
|
226
|
+
conceptLabelMap: {},
|
|
227
|
+
isLoading: false,
|
|
228
|
+
error: mockError,
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
render(<PatientComplaintsComponent patientUuid="test-patient-uuid" />);
|
|
232
|
+
|
|
233
|
+
expect(screen.getByTestId('error-state')).toBeInTheDocument();
|
|
234
|
+
expect(screen.getByText('Complaints')).toBeInTheDocument();
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
it('should render empty state when there are no complaints', () => {
|
|
238
|
+
mockUsePaginatedEncounters.mockReturnValue({
|
|
239
|
+
data: [],
|
|
240
|
+
isLoading: false,
|
|
241
|
+
error: null,
|
|
242
|
+
currentPage: 1,
|
|
243
|
+
goTo: jest.fn(),
|
|
244
|
+
totalPages: 1,
|
|
245
|
+
});
|
|
246
|
+
mockUseForm.mockReturnValue({
|
|
247
|
+
conceptLabelMap: {},
|
|
248
|
+
isLoading: false,
|
|
249
|
+
error: null,
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
render(<PatientComplaintsComponent patientUuid="test-patient-uuid" />);
|
|
253
|
+
|
|
254
|
+
expect(screen.getByTestId('empty-state')).toBeInTheDocument();
|
|
255
|
+
expect(screen.getByText('Complaints')).toBeInTheDocument();
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
it('should render complaints table with data', () => {
|
|
259
|
+
mockUsePaginatedEncounters.mockReturnValue({
|
|
260
|
+
data: mockEncounterData,
|
|
261
|
+
isLoading: false,
|
|
262
|
+
error: null,
|
|
263
|
+
currentPage: 1,
|
|
264
|
+
goTo: jest.fn(),
|
|
265
|
+
totalPages: 1,
|
|
266
|
+
});
|
|
267
|
+
mockUseForm.mockReturnValue({
|
|
268
|
+
conceptLabelMap: mockConceptLabelMap,
|
|
269
|
+
isLoading: false,
|
|
270
|
+
error: null,
|
|
271
|
+
});
|
|
272
|
+
mockExtractComplaintsFromObservations.mockReturnValue(mockComplaints);
|
|
273
|
+
|
|
274
|
+
render(<PatientComplaintsComponent patientUuid="test-patient-uuid" />);
|
|
275
|
+
|
|
276
|
+
// Check table headers
|
|
277
|
+
expect(screen.getByText('Complaint')).toBeInTheDocument();
|
|
278
|
+
expect(screen.getByText('Duration')).toBeInTheDocument();
|
|
279
|
+
expect(screen.getByText('Onset')).toBeInTheDocument();
|
|
280
|
+
|
|
281
|
+
// Check table data
|
|
282
|
+
expect(screen.getByText('Headache')).toBeInTheDocument();
|
|
283
|
+
expect(screen.getByText('2 days')).toBeInTheDocument();
|
|
284
|
+
expect(screen.getByText('Fever')).toBeInTheDocument();
|
|
285
|
+
expect(screen.getByText('5 days')).toBeInTheDocument();
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
it('should handle pagination correctly', async () => {
|
|
289
|
+
const user = userEvent.setup();
|
|
290
|
+
const mockGoTo = jest.fn();
|
|
291
|
+
|
|
292
|
+
// Create enough complaints data to enable pagination (more than 5 items for page size 5)
|
|
293
|
+
const paginationComplaints = [
|
|
294
|
+
{ id: 'obs-1', complaint: 'Headache', duration: '2 days', onset: '01-Oct-2023 - Sudden' },
|
|
295
|
+
{ id: 'obs-2', complaint: 'Fever', duration: '5 days', onset: '25-Sep-2023 - Gradual' },
|
|
296
|
+
{ id: 'obs-3', complaint: 'Cough', duration: '3 days', onset: '28-Sep-2023 - Sudden' },
|
|
297
|
+
{ id: 'obs-4', complaint: 'Fatigue', duration: '7 days', onset: '20-Sep-2023 - Gradual' },
|
|
298
|
+
{ id: 'obs-5', complaint: 'Nausea', duration: '1 day', onset: '30-Sep-2023 - Sudden' },
|
|
299
|
+
{ id: 'obs-6', complaint: 'Dizziness', duration: '4 days', onset: '24-Sep-2023 - Gradual' },
|
|
300
|
+
];
|
|
301
|
+
|
|
302
|
+
mockUsePaginatedEncounters.mockReturnValue({
|
|
303
|
+
data: mockEncounterData,
|
|
304
|
+
isLoading: false,
|
|
305
|
+
error: null,
|
|
306
|
+
currentPage: 1,
|
|
307
|
+
goTo: mockGoTo,
|
|
308
|
+
totalPages: 2,
|
|
309
|
+
});
|
|
310
|
+
mockUseForm.mockReturnValue({
|
|
311
|
+
conceptLabelMap: mockConceptLabelMap,
|
|
312
|
+
isLoading: false,
|
|
313
|
+
error: null,
|
|
314
|
+
});
|
|
315
|
+
mockExtractComplaintsFromObservations.mockReturnValue(paginationComplaints);
|
|
316
|
+
|
|
317
|
+
render(<PatientComplaintsComponent patientUuid="test-patient-uuid" />);
|
|
318
|
+
|
|
319
|
+
// Find and click the next page button
|
|
320
|
+
const nextPageButton = screen.getByRole('button', { name: /next page/i });
|
|
321
|
+
await user.click(nextPageButton);
|
|
322
|
+
|
|
323
|
+
// Verify goTo was called with page 2
|
|
324
|
+
await waitFor(() => {
|
|
325
|
+
expect(mockGoTo).toHaveBeenCalledWith(2);
|
|
326
|
+
});
|
|
327
|
+
});
|
|
328
|
+
|
|
329
|
+
it('should display "sm" size table on desktop layout', () => {
|
|
330
|
+
mockIsDesktop.mockReturnValue(true);
|
|
331
|
+
mockUsePaginatedEncounters.mockReturnValue({
|
|
332
|
+
data: mockEncounterData,
|
|
333
|
+
isLoading: false,
|
|
334
|
+
error: null,
|
|
335
|
+
currentPage: 1,
|
|
336
|
+
goTo: jest.fn(),
|
|
337
|
+
totalPages: 1,
|
|
338
|
+
});
|
|
339
|
+
mockUseForm.mockReturnValue({
|
|
340
|
+
conceptLabelMap: mockConceptLabelMap,
|
|
341
|
+
isLoading: false,
|
|
342
|
+
error: null,
|
|
343
|
+
});
|
|
344
|
+
mockExtractComplaintsFromObservations.mockReturnValue(mockComplaints);
|
|
345
|
+
|
|
346
|
+
render(<PatientComplaintsComponent patientUuid="test-patient-uuid" />);
|
|
347
|
+
|
|
348
|
+
const table = screen.getByRole('table');
|
|
349
|
+
expect(table).toHaveClass('cds--data-table--sm');
|
|
350
|
+
});
|
|
351
|
+
|
|
352
|
+
it('should display "lg" size table on tablet layout', () => {
|
|
353
|
+
mockIsDesktop.mockReturnValue(false);
|
|
354
|
+
mockUsePaginatedEncounters.mockReturnValue({
|
|
355
|
+
data: mockEncounterData,
|
|
356
|
+
isLoading: false,
|
|
357
|
+
error: null,
|
|
358
|
+
currentPage: 1,
|
|
359
|
+
goTo: jest.fn(),
|
|
360
|
+
totalPages: 1,
|
|
361
|
+
});
|
|
362
|
+
mockUseForm.mockReturnValue({
|
|
363
|
+
conceptLabelMap: mockConceptLabelMap,
|
|
364
|
+
isLoading: false,
|
|
365
|
+
error: null,
|
|
366
|
+
});
|
|
367
|
+
mockExtractComplaintsFromObservations.mockReturnValue(mockComplaints);
|
|
368
|
+
|
|
369
|
+
render(<PatientComplaintsComponent patientUuid="test-patient-uuid" />);
|
|
370
|
+
|
|
371
|
+
const table = screen.getByRole('table');
|
|
372
|
+
expect(table).toHaveClass('cds--data-table--lg');
|
|
373
|
+
});
|
|
374
|
+
|
|
375
|
+
it('should display placeholder when complaint data is missing', () => {
|
|
376
|
+
const encounterWithMissingData = [
|
|
377
|
+
{
|
|
378
|
+
uuid: 'encounter-3',
|
|
379
|
+
encounterDatetime: '2023-10-01T10:30:00',
|
|
380
|
+
obs: [
|
|
381
|
+
{
|
|
382
|
+
uuid: 'obs-3',
|
|
383
|
+
concept: {
|
|
384
|
+
uuid: '160531AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
|
|
385
|
+
},
|
|
386
|
+
obsDatetime: '2023-10-01T10:30:00',
|
|
387
|
+
groupMembers: [],
|
|
388
|
+
},
|
|
389
|
+
],
|
|
390
|
+
},
|
|
391
|
+
];
|
|
392
|
+
|
|
393
|
+
const mockComplaintsWithPlaceholders = [
|
|
394
|
+
{
|
|
395
|
+
id: 'obs-3',
|
|
396
|
+
complaint: '--',
|
|
397
|
+
duration: '--',
|
|
398
|
+
onset: '--',
|
|
399
|
+
},
|
|
400
|
+
];
|
|
401
|
+
|
|
402
|
+
mockUsePaginatedEncounters.mockReturnValue({
|
|
403
|
+
data: encounterWithMissingData,
|
|
404
|
+
isLoading: false,
|
|
405
|
+
error: null,
|
|
406
|
+
currentPage: 1,
|
|
407
|
+
goTo: jest.fn(),
|
|
408
|
+
totalPages: 1,
|
|
409
|
+
});
|
|
410
|
+
mockUseForm.mockReturnValue({
|
|
411
|
+
conceptLabelMap: {},
|
|
412
|
+
isLoading: false,
|
|
413
|
+
error: null,
|
|
414
|
+
});
|
|
415
|
+
mockExtractComplaintsFromObservations.mockReturnValue(mockComplaintsWithPlaceholders);
|
|
416
|
+
|
|
417
|
+
render(<PatientComplaintsComponent patientUuid="test-patient-uuid" />);
|
|
418
|
+
|
|
419
|
+
// Should display placeholder "--" for missing data
|
|
420
|
+
const placeholders = screen.getAllByText('--');
|
|
421
|
+
expect(placeholders.length).toBeGreaterThan(0);
|
|
422
|
+
});
|
|
423
|
+
|
|
424
|
+
it('should have zebra striped rows', () => {
|
|
425
|
+
mockUsePaginatedEncounters.mockReturnValue({
|
|
426
|
+
data: mockEncounterData,
|
|
427
|
+
isLoading: false,
|
|
428
|
+
error: null,
|
|
429
|
+
currentPage: 1,
|
|
430
|
+
goTo: jest.fn(),
|
|
431
|
+
totalPages: 1,
|
|
432
|
+
});
|
|
433
|
+
mockUseForm.mockReturnValue({
|
|
434
|
+
conceptLabelMap: mockConceptLabelMap,
|
|
435
|
+
isLoading: false,
|
|
436
|
+
error: null,
|
|
437
|
+
});
|
|
438
|
+
mockExtractComplaintsFromObservations.mockReturnValue(mockComplaints);
|
|
439
|
+
|
|
440
|
+
render(<PatientComplaintsComponent patientUuid="test-patient-uuid" />);
|
|
441
|
+
|
|
442
|
+
const table = screen.getByRole('table');
|
|
443
|
+
expect(table).toHaveClass('cds--data-table--zebra');
|
|
444
|
+
});
|
|
445
|
+
|
|
446
|
+
it('should call usePaginatedEncounters with correct parameters', () => {
|
|
447
|
+
mockUsePaginatedEncounters.mockReturnValue({
|
|
448
|
+
data: [],
|
|
449
|
+
isLoading: false,
|
|
450
|
+
error: null,
|
|
451
|
+
currentPage: 1,
|
|
452
|
+
goTo: jest.fn(),
|
|
453
|
+
totalPages: 1,
|
|
454
|
+
});
|
|
455
|
+
mockUseForm.mockReturnValue({
|
|
456
|
+
conceptLabelMap: {},
|
|
457
|
+
isLoading: false,
|
|
458
|
+
error: null,
|
|
459
|
+
});
|
|
460
|
+
|
|
461
|
+
render(<PatientComplaintsComponent patientUuid="test-patient-uuid" />);
|
|
462
|
+
|
|
463
|
+
expect(mockUsePaginatedEncounters).toHaveBeenCalledWith(
|
|
464
|
+
'test-patient-uuid',
|
|
465
|
+
'd1059fb9-a079-4feb-a749-eedd709ae542',
|
|
466
|
+
5,
|
|
467
|
+
);
|
|
468
|
+
});
|
|
469
|
+
|
|
470
|
+
it('should call useForm with correct form UUID', () => {
|
|
471
|
+
mockUsePaginatedEncounters.mockReturnValue({
|
|
472
|
+
data: [],
|
|
473
|
+
isLoading: false,
|
|
474
|
+
error: null,
|
|
475
|
+
currentPage: 1,
|
|
476
|
+
goTo: jest.fn(),
|
|
477
|
+
totalPages: 1,
|
|
478
|
+
});
|
|
479
|
+
mockUseForm.mockReturnValue({
|
|
480
|
+
conceptLabelMap: {},
|
|
481
|
+
isLoading: false,
|
|
482
|
+
error: null,
|
|
483
|
+
});
|
|
484
|
+
|
|
485
|
+
render(<PatientComplaintsComponent patientUuid="test-patient-uuid" />);
|
|
486
|
+
|
|
487
|
+
expect(mockUseForm).toHaveBeenCalledWith('37f6bd8d-586a-4169-95fa-5781f987fe62');
|
|
488
|
+
});
|
|
489
|
+
|
|
490
|
+
it('should have pagination controls with correct props', () => {
|
|
491
|
+
mockUsePaginatedEncounters.mockReturnValue({
|
|
492
|
+
data: mockEncounterData,
|
|
493
|
+
isLoading: false,
|
|
494
|
+
error: null,
|
|
495
|
+
currentPage: 1,
|
|
496
|
+
goTo: jest.fn(),
|
|
497
|
+
totalPages: 2,
|
|
498
|
+
});
|
|
499
|
+
mockUseForm.mockReturnValue({
|
|
500
|
+
conceptLabelMap: mockConceptLabelMap,
|
|
501
|
+
isLoading: false,
|
|
502
|
+
error: null,
|
|
503
|
+
});
|
|
504
|
+
mockExtractComplaintsFromObservations.mockReturnValue(mockComplaints);
|
|
505
|
+
|
|
506
|
+
render(<PatientComplaintsComponent patientUuid="test-patient-uuid" />);
|
|
507
|
+
|
|
508
|
+
// Pagination should be present
|
|
509
|
+
expect(screen.getByRole('button', { name: /previous page/i })).toBeInTheDocument();
|
|
510
|
+
expect(screen.getByRole('button', { name: /next page/i })).toBeInTheDocument();
|
|
511
|
+
});
|
|
512
|
+
});
|