@kenyaemr/esm-patient-clinical-view-app 5.4.2-pre.2128 → 5.4.2-pre.2135
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 +67 -80
- package/dist/574.js +1 -1
- package/dist/{551.js → 825.js} +1 -1
- package/dist/825.js.map +1 -0
- package/dist/kenyaemr-esm-patient-clinical-view-app.js +1 -1
- package/dist/kenyaemr-esm-patient-clinical-view-app.js.buildmanifest.json +32 -32
- 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/config-schema.ts +21 -5
- package/src/contact-list/contact-list.component.tsx +3 -1
- package/src/contact-list/contact-list.resource.tsx +15 -13
- package/src/contact-list/contact-list.workspace.tsx +10 -249
- package/src/contact-list/contact-tracing-history.component.tsx +1 -1
- package/src/family-partner-history/family-history.component.tsx +40 -9
- package/src/family-partner-history/family-relationship.workspace.tsx +10 -4
- package/src/family-partner-history/relationships.resource.tsx +24 -98
- package/src/hooks/useContacts.ts +1 -1
- package/src/hooks/usePersonAttributes.ts +15 -0
- package/src/index.ts +0 -4
- package/src/relationships/forms/baseline-info-form-section.component.tsx +315 -0
- package/src/relationships/forms/patient-search-create-form.tsx +38 -23
- package/src/relationships/relationship.resources.ts +15 -5
- package/src/relationships/tabs/relationships-tabs-component.tsx +0 -5
- package/src/routes.json +0 -16
- package/translations/en.json +13 -3
- package/dist/551.js.map +0 -1
- package/src/other-relationships/other-relationships.component.tsx +0 -229
- package/src/other-relationships/other-relationships.scss +0 -125
- package/src/other-relationships/other-relationships.workspace.tsx +0 -155
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Button, ButtonSet, Column, ComboBox, DatePicker, DatePickerInput, Form, Stack
|
|
1
|
+
import { Button, ButtonSet, Column, ComboBox, DatePicker, DatePickerInput, Form, Stack } from '@carbon/react';
|
|
2
2
|
import { zodResolver } from '@hookform/resolvers/zod';
|
|
3
3
|
import { useConfig, useSession } from '@openmrs/esm-framework';
|
|
4
4
|
import React, { useMemo } from 'react';
|
|
@@ -6,8 +6,11 @@ import { Controller, FormProvider, SubmitHandler, useForm } from 'react-hook-for
|
|
|
6
6
|
import { useTranslation } from 'react-i18next';
|
|
7
7
|
import { z } from 'zod';
|
|
8
8
|
import { ConfigObject } from '../config-schema';
|
|
9
|
+
import { saveContact } from '../contact-list/contact-list.resource';
|
|
10
|
+
import usePersonAttributes from '../hooks/usePersonAttributes';
|
|
11
|
+
import RelationshipBaselineInfoFormSection from '../relationships/forms/baseline-info-form-section.component';
|
|
9
12
|
import PatientSearchCreate from '../relationships/forms/patient-search-create-form';
|
|
10
|
-
import { relationshipFormSchema
|
|
13
|
+
import { relationshipFormSchema } from '../relationships/relationship.resources';
|
|
11
14
|
import { uppercaseText } from '../utils/expression-helper';
|
|
12
15
|
import styles from './family-relationship.scss';
|
|
13
16
|
import { useMappedRelationshipTypes } from './relationships.resource';
|
|
@@ -57,12 +60,14 @@ const FamilyRelationshipForm: React.FC<RelationshipFormProps> = ({ closeWorkspac
|
|
|
57
60
|
},
|
|
58
61
|
resolver: zodResolver(schema),
|
|
59
62
|
});
|
|
63
|
+
const personUuid = form.watch('personB');
|
|
64
|
+
const { attributes } = usePersonAttributes(personUuid);
|
|
60
65
|
|
|
61
66
|
const { control, handleSubmit } = form;
|
|
62
67
|
|
|
63
68
|
const onSubmit: SubmitHandler<FormData> = async (data) => {
|
|
64
69
|
try {
|
|
65
|
-
await
|
|
70
|
+
await saveContact(data, config, session, attributes); /// Remove notes from payload since endpoint doesn't expect it to avoid 400 error
|
|
66
71
|
closeWorkspace();
|
|
67
72
|
} catch (error) {}
|
|
68
73
|
};
|
|
@@ -83,7 +88,7 @@ const FamilyRelationshipForm: React.FC<RelationshipFormProps> = ({ closeWorkspac
|
|
|
83
88
|
titleText={t('relationship', 'Relationship')}
|
|
84
89
|
placeholder="Relationship to patient"
|
|
85
90
|
items={relationshipTypes}
|
|
86
|
-
itemToString={(item) =>
|
|
91
|
+
itemToString={(item) => item?.text ?? ''}
|
|
87
92
|
onChange={(e) => field.onChange(e.selectedItem?.id)}
|
|
88
93
|
invalid={!!fieldState.error}
|
|
89
94
|
invalidText={fieldState.error?.message}
|
|
@@ -142,6 +147,7 @@ const FamilyRelationshipForm: React.FC<RelationshipFormProps> = ({ closeWorkspac
|
|
|
142
147
|
)}
|
|
143
148
|
/>
|
|
144
149
|
</Column>
|
|
150
|
+
<RelationshipBaselineInfoFormSection />
|
|
145
151
|
</Stack>
|
|
146
152
|
|
|
147
153
|
<ButtonSet className={styles.buttonSet}>
|
|
@@ -4,45 +4,8 @@ import { type FetchResponse, openmrsFetch, FHIRResource, restBaseUrl, useConfig
|
|
|
4
4
|
import useSWRImmutable from 'swr/immutable';
|
|
5
5
|
import { RelationshipTypeResponse } from '../case-management/workspace/case-management.resource';
|
|
6
6
|
import { ConfigObject } from '../config-schema';
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
results: Array<Relationship>;
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
interface ExtractedRelationship {
|
|
13
|
-
uuid: string;
|
|
14
|
-
display: string;
|
|
15
|
-
relativeAge: number;
|
|
16
|
-
name: string;
|
|
17
|
-
dead: boolean;
|
|
18
|
-
causeOfDeath: string;
|
|
19
|
-
relativeUuid: string;
|
|
20
|
-
relationshipType: string;
|
|
21
|
-
relationshipTypeDisplay: string;
|
|
22
|
-
relationshipTypeUUID: string;
|
|
23
|
-
patientUuid: string;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
export interface Relationship {
|
|
27
|
-
display: string;
|
|
28
|
-
uuid: string;
|
|
29
|
-
personA: Person;
|
|
30
|
-
personB: Person;
|
|
31
|
-
relationshipType: {
|
|
32
|
-
uuid: string;
|
|
33
|
-
display: string;
|
|
34
|
-
aIsToB: string;
|
|
35
|
-
bIsToA: string;
|
|
36
|
-
};
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
interface Person {
|
|
40
|
-
uuid: string;
|
|
41
|
-
age: number;
|
|
42
|
-
dead: boolean;
|
|
43
|
-
display: string;
|
|
44
|
-
causeOfDeath: string;
|
|
45
|
-
}
|
|
7
|
+
import { extractContactData } from '../hooks/useContacts';
|
|
8
|
+
import { Relationship } from '../types';
|
|
46
9
|
|
|
47
10
|
type FHIRResourceResponse = {
|
|
48
11
|
total: number;
|
|
@@ -124,78 +87,41 @@ export const useMappedRelationshipTypes = () => {
|
|
|
124
87
|
};
|
|
125
88
|
|
|
126
89
|
export function usePatientRelationships(patientUuid: string) {
|
|
127
|
-
const
|
|
128
|
-
'custom:(display,uuid,personA:(uuid,age,display,dead,causeOfDeath),personB:(uuid,age,display,dead,causeOfDeath),relationshipType:(uuid,display,description,aIsToB,bIsToA))';
|
|
90
|
+
const customeRepresentation =
|
|
91
|
+
'custom:(display,uuid,personA:(uuid,age,display,dead,causeOfDeath,gender,attributes:(uuid,display,value,attributeType:(uuid,display))),personB:(uuid,age,display,dead,causeOfDeath,gender,attributes:(uuid,display,value,attributeType:(uuid,display))),relationshipType:(uuid,display,description,aIsToB,bIsToA),startDate)';
|
|
92
|
+
const url = patientUuid ? `/ws/rest/v1/relationship?v=${customeRepresentation}&person=${patientUuid}` : null;
|
|
129
93
|
|
|
130
|
-
const
|
|
131
|
-
? `/ws/rest/v1/relationship?person=${patientUuid}&v=${customRepresentation}`
|
|
132
|
-
: null;
|
|
94
|
+
const config = useConfig<ConfigObject>();
|
|
133
95
|
|
|
134
|
-
const { data, error, isLoading, isValidating } = useSWR<FetchResponse<
|
|
135
|
-
|
|
96
|
+
const { data, error, isLoading, isValidating } = useSWR<FetchResponse<{ results: Array<Relationship> }>, Error>(
|
|
97
|
+
url,
|
|
136
98
|
openmrsFetch,
|
|
137
99
|
{
|
|
138
100
|
revalidateOnFocus: false,
|
|
139
101
|
},
|
|
140
102
|
);
|
|
103
|
+
const familyRelationships = useMemo(
|
|
104
|
+
() => config.relationshipTypesList.filter((rl) => rl.category.some((c) => c === 'family')),
|
|
105
|
+
[config],
|
|
106
|
+
);
|
|
141
107
|
|
|
142
108
|
const relationships = useMemo(() => {
|
|
143
|
-
return data?.data?.results?.length
|
|
144
|
-
|
|
109
|
+
return data?.data?.results?.length
|
|
110
|
+
? extractContactData(
|
|
111
|
+
patientUuid,
|
|
112
|
+
data?.data?.results.filter((rel) =>
|
|
113
|
+
familyRelationships.some((famRel) => famRel.uuid === rel.relationshipType.uuid),
|
|
114
|
+
),
|
|
115
|
+
config,
|
|
116
|
+
)
|
|
117
|
+
: [];
|
|
118
|
+
}, [data?.data?.results, patientUuid, config, familyRelationships]);
|
|
145
119
|
|
|
146
120
|
return {
|
|
147
|
-
relationships,
|
|
121
|
+
relationships: relationships ?? [],
|
|
148
122
|
error,
|
|
149
123
|
isLoading,
|
|
150
124
|
isValidating,
|
|
151
|
-
relationshipsUrl,
|
|
125
|
+
relationshipsUrl: url,
|
|
152
126
|
};
|
|
153
127
|
}
|
|
154
|
-
|
|
155
|
-
function extractRelationshipData(
|
|
156
|
-
patientIdentifier: string,
|
|
157
|
-
relationships: Array<Relationship>,
|
|
158
|
-
): Array<ExtractedRelationship> {
|
|
159
|
-
const relationshipsData = [];
|
|
160
|
-
for (const r of relationships) {
|
|
161
|
-
if (patientIdentifier === r.personA.uuid) {
|
|
162
|
-
relationshipsData.push({
|
|
163
|
-
uuid: r.uuid,
|
|
164
|
-
name: extractName(r.personB.display),
|
|
165
|
-
display: r.personB.display,
|
|
166
|
-
relativeAge: r.personB.age,
|
|
167
|
-
dead: r.personB.dead,
|
|
168
|
-
causeOfDeath: r.personB.causeOfDeath,
|
|
169
|
-
relativeUuid: r.personB.uuid,
|
|
170
|
-
relationshipType: r.relationshipType.bIsToA,
|
|
171
|
-
relationshipTypeDisplay: r.relationshipType.display,
|
|
172
|
-
relationshipTypeUUID: r.relationshipType.uuid,
|
|
173
|
-
patientUuid: r.personB.uuid,
|
|
174
|
-
});
|
|
175
|
-
} else {
|
|
176
|
-
relationshipsData.push({
|
|
177
|
-
uuid: r.uuid,
|
|
178
|
-
name: extractName(r.personA.display),
|
|
179
|
-
display: r.personA.display,
|
|
180
|
-
relativeAge: r.personA.age,
|
|
181
|
-
causeOfDeath: r.personA.causeOfDeath,
|
|
182
|
-
relativeUuid: r.personA.uuid,
|
|
183
|
-
dead: r.personA.dead,
|
|
184
|
-
relationshipType: r.relationshipType.aIsToB,
|
|
185
|
-
relationshipTypeDisplay: r.relationshipType.display,
|
|
186
|
-
relationshipTypeUUID: r.relationshipType.uuid,
|
|
187
|
-
patientUuid: r.personA.uuid,
|
|
188
|
-
});
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
return relationshipsData;
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
function extractName(display: string) {
|
|
195
|
-
const pattern = /-\s*(.*)$/;
|
|
196
|
-
const match = display.match(pattern);
|
|
197
|
-
if (match && match.length > 1) {
|
|
198
|
-
return match[1].trim();
|
|
199
|
-
}
|
|
200
|
-
return display.trim();
|
|
201
|
-
}
|
package/src/hooks/useContacts.ts
CHANGED
|
@@ -84,7 +84,7 @@ function getContact(relationship: Relationship, config: ConfigObject, person: 'p
|
|
|
84
84
|
endDate: !relationship.endDate ? null : formatDate(parseDate(relationship.endDate)),
|
|
85
85
|
} as Contact;
|
|
86
86
|
}
|
|
87
|
-
function extractContactData(
|
|
87
|
+
export function extractContactData(
|
|
88
88
|
patientIdentifier: string,
|
|
89
89
|
relationships: Array<Relationship>,
|
|
90
90
|
config: ConfigObject,
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { FetchResponse, openmrsFetch, restBaseUrl } from '@openmrs/esm-framework';
|
|
2
|
+
import useSWR from 'swr';
|
|
3
|
+
import { Person } from '../types';
|
|
4
|
+
|
|
5
|
+
const usePersonAttributes = (personUuid?: string) => {
|
|
6
|
+
const url = `${restBaseUrl}/person/${personUuid}/attribute`;
|
|
7
|
+
const { data, error, isLoading, mutate } = useSWR<
|
|
8
|
+
FetchResponse<{
|
|
9
|
+
results: Person['attributes'];
|
|
10
|
+
}>
|
|
11
|
+
>(personUuid ? url : null, openmrsFetch);
|
|
12
|
+
return { error, isLoading, mutate, attributes: data?.data?.results ?? [] };
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export default usePersonAttributes;
|
package/src/index.ts
CHANGED
|
@@ -44,8 +44,6 @@ import CaseManagementForm from './case-management/workspace/case-management.work
|
|
|
44
44
|
import Relationships from './relationships/relationships.component';
|
|
45
45
|
import CaseEncounterOverviewComponent from './case-management/encounters/case-encounter-overview.component';
|
|
46
46
|
import FamilyRelationshipForm from './family-partner-history/family-relationship.workspace';
|
|
47
|
-
import { OtherRelationships } from './other-relationships/other-relationships.component';
|
|
48
|
-
import { OtherRelationshipsForm } from './other-relationships/other-relationships.workspace';
|
|
49
47
|
import InPatient from './in-patient/in-patient.component';
|
|
50
48
|
import { inPatientMeta } from './in-patient/in-patient.meta';
|
|
51
49
|
import PeerCalendar from './peer-calendar/peer-calendar.component';
|
|
@@ -98,9 +96,7 @@ export const familyHistoryLink = getSyncLifecycle(createDashboardLink(familyHist
|
|
|
98
96
|
export const familyRelationshipForm = getSyncLifecycle(FamilyRelationshipForm, options);
|
|
99
97
|
|
|
100
98
|
// Dashboard links for Other relationships and the corresponding view in the patient chart
|
|
101
|
-
export const otherRelationships = getSyncLifecycle(OtherRelationships, options);
|
|
102
99
|
export const otherRelationshipsLink = getSyncLifecycle(createDashboardLink(otherRelationshipsDashboardMeta), options);
|
|
103
|
-
export const otherRelationshipsForm = getSyncLifecycle(OtherRelationshipsForm, options);
|
|
104
100
|
|
|
105
101
|
// Relationships links for Family History and the corresponding view in the patient chart
|
|
106
102
|
export const relationshipsLink = getSyncLifecycle(createDashboardLink(relationshipsDashboardMeta), options);
|
|
@@ -0,0 +1,315 @@
|
|
|
1
|
+
import { Column, Dropdown, RadioButton, RadioButtonGroup, SelectSkeleton } from '@carbon/react';
|
|
2
|
+
import { useConfig } from '@openmrs/esm-framework';
|
|
3
|
+
import React, { useEffect, useMemo } from 'react';
|
|
4
|
+
import { Controller, useFormContext } from 'react-hook-form';
|
|
5
|
+
import { useTranslation } from 'react-i18next';
|
|
6
|
+
import { z } from 'zod';
|
|
7
|
+
import { ConfigObject } from '../../config-schema';
|
|
8
|
+
import { contactListConceptMap } from '../../contact-list/contact-list-concept-map';
|
|
9
|
+
import { contactIPVOutcomeOptions } from '../../contact-list/contact-list.resource';
|
|
10
|
+
import usePersonAttributes from '../../hooks/usePersonAttributes';
|
|
11
|
+
import { BOOLEAN_NO, BOOLEAN_YES, relationshipFormSchema } from '../relationship.resources';
|
|
12
|
+
import {
|
|
13
|
+
LIVING_WITH_PATIENT_CONCEPT_UUID,
|
|
14
|
+
PARTNER_HIV_STATUS_CONCEPT_UUID,
|
|
15
|
+
PNS_APROACH_CONCEPT_UUID,
|
|
16
|
+
} from '../relationships-constants';
|
|
17
|
+
import styles from './form.scss';
|
|
18
|
+
|
|
19
|
+
const RelationshipBaselineInfoFormSection = () => {
|
|
20
|
+
const form = useFormContext<z.infer<typeof relationshipFormSchema>>();
|
|
21
|
+
const { t } = useTranslation();
|
|
22
|
+
const personUuid = form.watch('personB');
|
|
23
|
+
const config = useConfig<ConfigObject>();
|
|
24
|
+
const { setValue } = form;
|
|
25
|
+
const { attributes, isLoading } = usePersonAttributes(personUuid);
|
|
26
|
+
|
|
27
|
+
const hivStatus = useMemo(
|
|
28
|
+
() =>
|
|
29
|
+
Object.entries(contactListConceptMap[PARTNER_HIV_STATUS_CONCEPT_UUID].answers).map(([uuid, display]) => ({
|
|
30
|
+
label: display,
|
|
31
|
+
value: uuid,
|
|
32
|
+
})),
|
|
33
|
+
[],
|
|
34
|
+
);
|
|
35
|
+
|
|
36
|
+
const pnsAproach = useMemo(
|
|
37
|
+
() =>
|
|
38
|
+
Object.entries(contactListConceptMap[PNS_APROACH_CONCEPT_UUID].answers).map(([uuid, display]) => ({
|
|
39
|
+
label: display,
|
|
40
|
+
value: uuid,
|
|
41
|
+
})),
|
|
42
|
+
[],
|
|
43
|
+
);
|
|
44
|
+
|
|
45
|
+
const contactLivingWithPatient = useMemo(
|
|
46
|
+
() =>
|
|
47
|
+
Object.entries(contactListConceptMap[LIVING_WITH_PATIENT_CONCEPT_UUID].answers).map(([uuid, display]) => ({
|
|
48
|
+
label: display,
|
|
49
|
+
value: uuid,
|
|
50
|
+
})),
|
|
51
|
+
[],
|
|
52
|
+
);
|
|
53
|
+
|
|
54
|
+
const observableRelationship = form.watch('relationshipType');
|
|
55
|
+
const observablePhysicalAssault = form.watch('physicalAssault');
|
|
56
|
+
const observableThreatened = form.watch('threatened');
|
|
57
|
+
const observableSexualAssault = form.watch('sexualAssault');
|
|
58
|
+
const showIPVRelatedFields =
|
|
59
|
+
config.relationshipTypesList.findIndex(
|
|
60
|
+
(r) => r.uuid === observableRelationship && r.category.some((c) => c === 'sexual'),
|
|
61
|
+
) !== -1;
|
|
62
|
+
|
|
63
|
+
useEffect(() => {
|
|
64
|
+
if ([observablePhysicalAssault, observableThreatened, observableSexualAssault].includes(BOOLEAN_YES)) {
|
|
65
|
+
form.setValue('ipvOutCome', 'True');
|
|
66
|
+
} else if (
|
|
67
|
+
[observablePhysicalAssault, observableThreatened, observableSexualAssault].every((v) => v === BOOLEAN_NO)
|
|
68
|
+
) {
|
|
69
|
+
form.setValue('ipvOutCome', 'False');
|
|
70
|
+
}
|
|
71
|
+
if (!showIPVRelatedFields) {
|
|
72
|
+
form.setValue('ipvOutCome', undefined);
|
|
73
|
+
}
|
|
74
|
+
}, [
|
|
75
|
+
observablePhysicalAssault,
|
|
76
|
+
observableThreatened,
|
|
77
|
+
observableSexualAssault,
|
|
78
|
+
observableRelationship,
|
|
79
|
+
form,
|
|
80
|
+
showIPVRelatedFields,
|
|
81
|
+
]);
|
|
82
|
+
|
|
83
|
+
useEffect(() => {
|
|
84
|
+
if (attributes.length) {
|
|
85
|
+
// HIV Status
|
|
86
|
+
const hivStatusAttribute = attributes.find(
|
|
87
|
+
(a) => a.attributeType.uuid === config.contactPersonAttributesUuid.baselineHIVStatus,
|
|
88
|
+
);
|
|
89
|
+
if (hivStatusAttribute) {
|
|
90
|
+
const value = hivStatus.find((r) => r.value.startsWith(hivStatusAttribute.value))?.value;
|
|
91
|
+
if (value) {
|
|
92
|
+
setValue('baselineStatus', value);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const pnsAproachAttribute = attributes.find(
|
|
97
|
+
(a) => a.attributeType.uuid === config.contactPersonAttributesUuid.preferedPnsAproach,
|
|
98
|
+
);
|
|
99
|
+
if (pnsAproachAttribute) {
|
|
100
|
+
const value = pnsAproach.find((r) => r.value.startsWith(pnsAproachAttribute.value))?.value;
|
|
101
|
+
if (value) {
|
|
102
|
+
setValue('preferedPNSAproach', value);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const livingWithPatientAttribute = attributes.find(
|
|
107
|
+
(a) => a.attributeType.uuid === config.contactPersonAttributesUuid.livingWithContact,
|
|
108
|
+
);
|
|
109
|
+
if (livingWithPatientAttribute) {
|
|
110
|
+
const value = contactLivingWithPatient.find((r) => r.value.startsWith(livingWithPatientAttribute.value))?.value;
|
|
111
|
+
if (value) {
|
|
112
|
+
setValue('livingWithClient', value);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const ipvAttr = attributes.find(
|
|
117
|
+
(a) => a.attributeType.uuid === config.contactPersonAttributesUuid.contactIPVOutcome,
|
|
118
|
+
);
|
|
119
|
+
if (ipvAttr) {
|
|
120
|
+
const value = contactIPVOutcomeOptions.find((r) => r.value.startsWith(ipvAttr.value))?.value;
|
|
121
|
+
if (value) {
|
|
122
|
+
setValue('ipvOutCome', value as any);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}, [attributes, setValue, config, hivStatus, pnsAproach, contactLivingWithPatient]);
|
|
127
|
+
|
|
128
|
+
return (
|
|
129
|
+
<>
|
|
130
|
+
{showIPVRelatedFields && (
|
|
131
|
+
<>
|
|
132
|
+
<span className={styles.sectionHeader}>{t('ipvQuestions', 'IPV Questions')}</span>
|
|
133
|
+
<Column>
|
|
134
|
+
<Controller
|
|
135
|
+
control={form.control}
|
|
136
|
+
name="physicalAssault"
|
|
137
|
+
render={({ field, fieldState: { error } }) => (
|
|
138
|
+
<RadioButtonGroup
|
|
139
|
+
id="physicalAssault"
|
|
140
|
+
legendText={t(
|
|
141
|
+
'physicalAssault',
|
|
142
|
+
'1. Has he/she ever hit, kicked, slapped, or otherwise physically hurt you?',
|
|
143
|
+
)}
|
|
144
|
+
{...field}
|
|
145
|
+
invalid={error?.message}
|
|
146
|
+
invalidText={error?.message}
|
|
147
|
+
className={styles.billingItem}>
|
|
148
|
+
<RadioButton labelText={t('yes', 'Yes')} value={BOOLEAN_YES} id="physicalAssault_yes" />
|
|
149
|
+
<RadioButton labelText={t('no', 'No')} value={BOOLEAN_NO} id="physicalAssault_no" />
|
|
150
|
+
</RadioButtonGroup>
|
|
151
|
+
)}
|
|
152
|
+
/>
|
|
153
|
+
</Column>
|
|
154
|
+
<Column>
|
|
155
|
+
<Controller
|
|
156
|
+
control={form.control}
|
|
157
|
+
name="threatened"
|
|
158
|
+
render={({ field, fieldState: { error } }) => (
|
|
159
|
+
<RadioButtonGroup
|
|
160
|
+
id="threatened"
|
|
161
|
+
legendText={t('threatened', '2. Has he/she ever threatened to hurt you?')}
|
|
162
|
+
{...field}
|
|
163
|
+
invalid={error?.message}
|
|
164
|
+
invalidText={error?.message}
|
|
165
|
+
className={styles.billingItem}>
|
|
166
|
+
<RadioButton labelText={t('yes', 'Yes')} value={BOOLEAN_YES} id="threatened_yes" />
|
|
167
|
+
<RadioButton labelText={t('no', 'No')} value={BOOLEAN_NO} id="threatened_no" />
|
|
168
|
+
</RadioButtonGroup>
|
|
169
|
+
)}
|
|
170
|
+
/>
|
|
171
|
+
</Column>
|
|
172
|
+
<Column>
|
|
173
|
+
<Controller
|
|
174
|
+
control={form.control}
|
|
175
|
+
name="sexualAssault"
|
|
176
|
+
render={({ field, fieldState: { error } }) => (
|
|
177
|
+
<RadioButtonGroup
|
|
178
|
+
id="sexualAssault"
|
|
179
|
+
legendText={t(
|
|
180
|
+
'sexualAssault',
|
|
181
|
+
'3.Has he/she ever forced you to do something sexually that made you feel uncomfortable?',
|
|
182
|
+
)}
|
|
183
|
+
{...field}
|
|
184
|
+
invalid={error?.message}
|
|
185
|
+
invalidText={error?.message}
|
|
186
|
+
className={styles.billingItem}>
|
|
187
|
+
<RadioButton labelText={t('yes', 'Yes')} value={BOOLEAN_YES} id="sexualAssault_yes" />
|
|
188
|
+
<RadioButton labelText={t('no', 'No')} value={BOOLEAN_NO} id="sexualAssault_no" />
|
|
189
|
+
</RadioButtonGroup>
|
|
190
|
+
)}
|
|
191
|
+
/>
|
|
192
|
+
</Column>
|
|
193
|
+
<span className={styles.sectionHeader}>{t('ipvOutcome', 'IPV Outcome')}</span>
|
|
194
|
+
<Column>
|
|
195
|
+
<Controller
|
|
196
|
+
control={form.control}
|
|
197
|
+
name="ipvOutCome"
|
|
198
|
+
render={({ field, fieldState: { error } }) => (
|
|
199
|
+
<>
|
|
200
|
+
{isLoading ? (
|
|
201
|
+
<SelectSkeleton />
|
|
202
|
+
) : (
|
|
203
|
+
<Dropdown
|
|
204
|
+
ref={field.ref}
|
|
205
|
+
invalid={error?.message}
|
|
206
|
+
invalidText={error?.message}
|
|
207
|
+
id="ipvOutCome"
|
|
208
|
+
titleText={t('ipvOutCome', 'IPV Outcome')}
|
|
209
|
+
onChange={(e) => {
|
|
210
|
+
field.onChange(e.selectedItem);
|
|
211
|
+
}}
|
|
212
|
+
selectedItem={field.value}
|
|
213
|
+
label="Choose option"
|
|
214
|
+
items={contactIPVOutcomeOptions.map((r) => r.value)}
|
|
215
|
+
itemToString={(item) => {
|
|
216
|
+
return contactIPVOutcomeOptions.find((r) => r.value === item)?.label ?? '';
|
|
217
|
+
}}
|
|
218
|
+
/>
|
|
219
|
+
)}
|
|
220
|
+
</>
|
|
221
|
+
)}
|
|
222
|
+
/>
|
|
223
|
+
</Column>
|
|
224
|
+
</>
|
|
225
|
+
)}
|
|
226
|
+
<Column>
|
|
227
|
+
<Controller
|
|
228
|
+
control={form.control}
|
|
229
|
+
name="livingWithClient"
|
|
230
|
+
render={({ field, fieldState: { error } }) => (
|
|
231
|
+
<>
|
|
232
|
+
{isLoading ? (
|
|
233
|
+
<SelectSkeleton />
|
|
234
|
+
) : (
|
|
235
|
+
<Dropdown
|
|
236
|
+
ref={field.ref}
|
|
237
|
+
invalid={!!error?.message}
|
|
238
|
+
invalidText={error?.message}
|
|
239
|
+
id="livingWithClient"
|
|
240
|
+
titleText={t('livingWithClient', 'Living with client')}
|
|
241
|
+
onChange={(e: { selectedItem: string }) => {
|
|
242
|
+
field.onChange(e.selectedItem);
|
|
243
|
+
}}
|
|
244
|
+
selectedItem={field.value}
|
|
245
|
+
label="Select"
|
|
246
|
+
items={contactLivingWithPatient.map((r) => r.value)}
|
|
247
|
+
itemToString={(item: string) => contactLivingWithPatient.find((r) => r.value === item)?.label ?? ''}
|
|
248
|
+
/>
|
|
249
|
+
)}
|
|
250
|
+
</>
|
|
251
|
+
)}
|
|
252
|
+
/>
|
|
253
|
+
</Column>
|
|
254
|
+
<span className={styles.sectionHeader}>{t('baselineInformation', 'Baseline Information')}</span>
|
|
255
|
+
<Column>
|
|
256
|
+
<Controller
|
|
257
|
+
control={form.control}
|
|
258
|
+
name="baselineStatus"
|
|
259
|
+
render={({ field, fieldState: { error } }) => (
|
|
260
|
+
<>
|
|
261
|
+
{isLoading ? (
|
|
262
|
+
<SelectSkeleton />
|
|
263
|
+
) : (
|
|
264
|
+
<Dropdown
|
|
265
|
+
ref={field.ref}
|
|
266
|
+
invalid={!!error?.message}
|
|
267
|
+
invalidText={error?.message}
|
|
268
|
+
id="baselineStatus"
|
|
269
|
+
titleText={t('baselineStatus', 'HIV Status')}
|
|
270
|
+
onChange={(e: { selectedItem: string }) => {
|
|
271
|
+
field.onChange(e.selectedItem);
|
|
272
|
+
}}
|
|
273
|
+
selectedItem={field.value}
|
|
274
|
+
label="Select HIV Status"
|
|
275
|
+
items={hivStatus.map((r) => r.value)}
|
|
276
|
+
itemToString={(item: string) => hivStatus.find((r) => r.value === item)?.label ?? ''}
|
|
277
|
+
/>
|
|
278
|
+
)}
|
|
279
|
+
</>
|
|
280
|
+
)}
|
|
281
|
+
/>
|
|
282
|
+
</Column>
|
|
283
|
+
<Column>
|
|
284
|
+
<Controller
|
|
285
|
+
control={form.control}
|
|
286
|
+
name="preferedPNSAproach"
|
|
287
|
+
render={({ field, fieldState: { error } }) => (
|
|
288
|
+
<>
|
|
289
|
+
{isLoading ? (
|
|
290
|
+
<SelectSkeleton />
|
|
291
|
+
) : (
|
|
292
|
+
<Dropdown
|
|
293
|
+
ref={field.ref}
|
|
294
|
+
invalid={!!error?.message}
|
|
295
|
+
invalidText={error?.message}
|
|
296
|
+
id="preferedPNSAproach"
|
|
297
|
+
titleText={t('preferedPNSAproach', 'Prefered PNS Aproach')}
|
|
298
|
+
onChange={(e: { selectedItem: string }) => {
|
|
299
|
+
field.onChange(e.selectedItem);
|
|
300
|
+
}}
|
|
301
|
+
selectedItem={field.value}
|
|
302
|
+
label="Select Aproach"
|
|
303
|
+
items={pnsAproach.map((r) => r.value)}
|
|
304
|
+
itemToString={(item: string) => pnsAproach.find((r) => r.value === item)?.label ?? ''}
|
|
305
|
+
/>
|
|
306
|
+
)}
|
|
307
|
+
</>
|
|
308
|
+
)}
|
|
309
|
+
/>
|
|
310
|
+
</Column>
|
|
311
|
+
</>
|
|
312
|
+
);
|
|
313
|
+
};
|
|
314
|
+
|
|
315
|
+
export default RelationshipBaselineInfoFormSection;
|
|
@@ -11,7 +11,7 @@ import {
|
|
|
11
11
|
TextInput,
|
|
12
12
|
} from '@carbon/react';
|
|
13
13
|
import { Calculator } from '@carbon/react/icons';
|
|
14
|
-
import { showModal } from '@openmrs/esm-framework';
|
|
14
|
+
import { showModal, useConfig } from '@openmrs/esm-framework';
|
|
15
15
|
import React, { useMemo } from 'react';
|
|
16
16
|
import { Controller, useFormContext } from 'react-hook-form';
|
|
17
17
|
import { useTranslation } from 'react-i18next';
|
|
@@ -23,6 +23,7 @@ import { fetchPerson, relationshipFormSchema } from '../relationship.resources';
|
|
|
23
23
|
import styles from './form.scss';
|
|
24
24
|
import { MARITAL_STATUS_CONCEPT_UUID } from '../relationships-constants';
|
|
25
25
|
import PatientSearchInfo from '../../autosuggest/patient-search-info.component';
|
|
26
|
+
import { ConfigObject } from '../../config-schema';
|
|
26
27
|
|
|
27
28
|
type PatientSearchCreateProps = {};
|
|
28
29
|
|
|
@@ -33,6 +34,7 @@ const PatientSearchCreate: React.FC<PatientSearchCreateProps> = () => {
|
|
|
33
34
|
const abortController = new AbortController();
|
|
34
35
|
return await fetchPerson(query, abortController);
|
|
35
36
|
};
|
|
37
|
+
const { requireMaritalStatusOnAgeGreaterThanOrEqualTo } = useConfig<ConfigObject>();
|
|
36
38
|
|
|
37
39
|
const handleAdd = () => form.setValue('mode', 'create');
|
|
38
40
|
const maritalStatus = useMemo(
|
|
@@ -52,6 +54,17 @@ const PatientSearchCreate: React.FC<PatientSearchCreateProps> = () => {
|
|
|
52
54
|
};
|
|
53
55
|
|
|
54
56
|
const mode = form.watch('mode');
|
|
57
|
+
const dobObservable = form.watch('personBInfo.birthdate');
|
|
58
|
+
const age = useMemo(() => {
|
|
59
|
+
if (!dobObservable) {
|
|
60
|
+
return '';
|
|
61
|
+
}
|
|
62
|
+
const dob = new Date(dobObservable);
|
|
63
|
+
const today = new Date();
|
|
64
|
+
const ageInMs = today.getTime() - dob.getTime();
|
|
65
|
+
const ageInYears = Math.floor(ageInMs / (1000 * 60 * 60 * 24 * 365.25));
|
|
66
|
+
return ageInYears.toString();
|
|
67
|
+
}, [dobObservable]);
|
|
55
68
|
|
|
56
69
|
return (
|
|
57
70
|
<>
|
|
@@ -198,28 +211,30 @@ const PatientSearchCreate: React.FC<PatientSearchCreateProps> = () => {
|
|
|
198
211
|
{t('fromAge', 'From Age')}
|
|
199
212
|
</Button>
|
|
200
213
|
</Column>
|
|
201
|
-
|
|
202
|
-
<
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
214
|
+
{age && Number(age) >= requireMaritalStatusOnAgeGreaterThanOrEqualTo && (
|
|
215
|
+
<Column>
|
|
216
|
+
<Controller
|
|
217
|
+
control={form.control}
|
|
218
|
+
name="personBInfo.maritalStatus"
|
|
219
|
+
render={({ field, fieldState: { error } }) => (
|
|
220
|
+
<Dropdown
|
|
221
|
+
ref={field.ref}
|
|
222
|
+
invalid={error?.message}
|
|
223
|
+
invalidText={error?.message}
|
|
224
|
+
id="maritalStatus"
|
|
225
|
+
titleText={t('maritalStatus', 'Marital status')}
|
|
226
|
+
onChange={(e) => {
|
|
227
|
+
field.onChange(e.selectedItem);
|
|
228
|
+
}}
|
|
229
|
+
initialSelectedItem={field.value}
|
|
230
|
+
label="Choose option"
|
|
231
|
+
items={maritalStatus.map((r) => r.value)}
|
|
232
|
+
itemToString={(item) => maritalStatus.find((r) => r.value === item)?.label ?? ''}
|
|
233
|
+
/>
|
|
234
|
+
)}
|
|
235
|
+
/>
|
|
236
|
+
</Column>
|
|
237
|
+
)}
|
|
223
238
|
<span className={styles.sectionHeader}>{t('contact', 'Contact')}</span>
|
|
224
239
|
<Column>
|
|
225
240
|
<Controller
|