@kenyaemr/esm-patient-clinical-view-app 5.4.2-pre.2189 → 5.4.2-pre.2191
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 +91 -84
- package/dist/40.js +1 -0
- package/dist/40.js.map +1 -0
- package/dist/574.js +1 -1
- package/dist/685.js +2 -0
- package/dist/685.js.map +1 -0
- package/dist/{156.js → 700.js} +2 -2
- package/dist/{156.js.map → 700.js.map} +1 -1
- package/dist/kenyaemr-esm-patient-clinical-view-app.js +1 -1
- package/dist/kenyaemr-esm-patient-clinical-view-app.js.buildmanifest.json +67 -67
- package/dist/kenyaemr-esm-patient-clinical-view-app.js.map +1 -1
- 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/contact-list/contact-list.component.tsx +12 -6
- package/src/contact-list/contact-list.resource.tsx +88 -2
- package/src/contact-list/forms/contact-list-update.scss +79 -0
- package/src/contact-list/forms/contact-list-update.workspace.tsx +554 -0
- package/src/family-partner-history/family-history.component.tsx +17 -20
- package/src/index.ts +2 -2
- package/src/relationships/forms/baseline-info-form-section.component.tsx +61 -34
- package/src/routes.json +3 -3
- package/src/types/index.ts +7 -0
- package/translations/en.json +1 -0
- package/dist/66.js +0 -2
- package/dist/66.js.map +0 -1
- package/dist/825.js +0 -1
- package/dist/825.js.map +0 -1
- package/src/relationships/forms/relationships-update-form.workspace.tsx +0 -183
- /package/dist/{66.js.LICENSE.txt → 685.js.LICENSE.txt} +0 -0
- /package/dist/{156.js.LICENSE.txt → 700.js.LICENSE.txt} +0 -0
|
@@ -0,0 +1,554 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Button,
|
|
3
|
+
ButtonSet,
|
|
4
|
+
Column,
|
|
5
|
+
DatePicker,
|
|
6
|
+
DatePickerInput,
|
|
7
|
+
Dropdown,
|
|
8
|
+
Form,
|
|
9
|
+
InlineLoading,
|
|
10
|
+
Stack,
|
|
11
|
+
Tile,
|
|
12
|
+
RadioButtonGroup,
|
|
13
|
+
RadioButton,
|
|
14
|
+
} from '@carbon/react';
|
|
15
|
+
import { zodResolver } from '@hookform/resolvers/zod';
|
|
16
|
+
import { DefaultWorkspaceProps, restBaseUrl, showSnackbar, useConfig } from '@openmrs/esm-framework';
|
|
17
|
+
import React, { useEffect, useMemo } from 'react';
|
|
18
|
+
import { Controller, FormProvider, useForm } from 'react-hook-form';
|
|
19
|
+
import { useTranslation } from 'react-i18next';
|
|
20
|
+
import { mutate } from 'swr';
|
|
21
|
+
import { z } from 'zod';
|
|
22
|
+
import useRelationship from '../../hooks/useRelationship';
|
|
23
|
+
import useRelationshipTypes from '../../hooks/useRelationshipTypes';
|
|
24
|
+
import styles from './contact-list-update.scss';
|
|
25
|
+
import PatientInfo from '../../case-management/workspace/patient-info.component';
|
|
26
|
+
import usePerson, { contactIPVOutcomeOptions, updateContactAttributes } from '../../contact-list/contact-list.resource';
|
|
27
|
+
import {
|
|
28
|
+
BOOLEAN_NO,
|
|
29
|
+
BOOLEAN_YES,
|
|
30
|
+
relationshipFormSchema,
|
|
31
|
+
relationshipUpdateFormSchema,
|
|
32
|
+
updateRelationship,
|
|
33
|
+
} from '../../relationships/relationship.resources';
|
|
34
|
+
import { type Contact } from '../../types';
|
|
35
|
+
import { ConfigObject } from '../../config-schema';
|
|
36
|
+
import { contactListConceptMap } from '../../contact-list/contact-list-concept-map';
|
|
37
|
+
import {
|
|
38
|
+
LIVING_WITH_PATIENT_CONCEPT_UUID,
|
|
39
|
+
PARTNER_HIV_STATUS_CONCEPT_UUID,
|
|
40
|
+
PNS_APROACH_CONCEPT_UUID,
|
|
41
|
+
} from '../../relationships/relationships-constants';
|
|
42
|
+
|
|
43
|
+
interface ContactListUpdateFormProps extends DefaultWorkspaceProps {
|
|
44
|
+
relation: Contact;
|
|
45
|
+
closeWorkspace: () => void;
|
|
46
|
+
patientUuid: string;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
type ContactListUpdateFormType = z.infer<typeof relationshipFormSchema>;
|
|
50
|
+
|
|
51
|
+
const ContactListUpdateForm: React.FC<ContactListUpdateFormProps> = ({ closeWorkspace, relation, patientUuid }) => {
|
|
52
|
+
const { error, isLoading, relationship } = useRelationship(relation?.uuid);
|
|
53
|
+
const { isLoading: typesLoading, error: typesError, relationshipTypes } = useRelationshipTypes();
|
|
54
|
+
const { person } = usePerson(relationship?.personB?.uuid);
|
|
55
|
+
const config = useConfig<ConfigObject>();
|
|
56
|
+
|
|
57
|
+
const { t } = useTranslation();
|
|
58
|
+
|
|
59
|
+
const hivStatus = useMemo(
|
|
60
|
+
() =>
|
|
61
|
+
Object.entries(contactListConceptMap[PARTNER_HIV_STATUS_CONCEPT_UUID].answers).map(([uuid, display]) => ({
|
|
62
|
+
label: display,
|
|
63
|
+
value: uuid,
|
|
64
|
+
})),
|
|
65
|
+
[],
|
|
66
|
+
);
|
|
67
|
+
|
|
68
|
+
const pnsAproach = useMemo(
|
|
69
|
+
() =>
|
|
70
|
+
Object.entries(contactListConceptMap[PNS_APROACH_CONCEPT_UUID].answers).map(([uuid, display]) => ({
|
|
71
|
+
label: display,
|
|
72
|
+
value: uuid,
|
|
73
|
+
})),
|
|
74
|
+
[],
|
|
75
|
+
);
|
|
76
|
+
|
|
77
|
+
const contactLivingWithPatient = useMemo(
|
|
78
|
+
() =>
|
|
79
|
+
Object.entries(contactListConceptMap[LIVING_WITH_PATIENT_CONCEPT_UUID].answers).map(([uuid, display]) => ({
|
|
80
|
+
label: display,
|
|
81
|
+
value: uuid,
|
|
82
|
+
})),
|
|
83
|
+
[],
|
|
84
|
+
);
|
|
85
|
+
|
|
86
|
+
const form = useForm<ContactListUpdateFormType>({
|
|
87
|
+
defaultValues: {
|
|
88
|
+
endDate: undefined,
|
|
89
|
+
startDate: undefined,
|
|
90
|
+
relationshipType: undefined,
|
|
91
|
+
baselineStatus: undefined,
|
|
92
|
+
preferedPNSAproach: undefined,
|
|
93
|
+
livingWithClient: undefined,
|
|
94
|
+
ipvOutCome: undefined,
|
|
95
|
+
physicalAssault: undefined,
|
|
96
|
+
threatened: undefined,
|
|
97
|
+
sexualAssault: undefined,
|
|
98
|
+
},
|
|
99
|
+
resolver: zodResolver(relationshipUpdateFormSchema),
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
const observableRelationship = form.watch('relationshipType');
|
|
103
|
+
const showIPVRelatedFields = useMemo(() => {
|
|
104
|
+
if (!observableRelationship || !config.relationshipTypesList) {
|
|
105
|
+
return false;
|
|
106
|
+
}
|
|
107
|
+
return (
|
|
108
|
+
config.relationshipTypesList.findIndex(
|
|
109
|
+
(r) => r.uuid === observableRelationship && r.category.some((c) => c === 'sexual'),
|
|
110
|
+
) !== -1
|
|
111
|
+
);
|
|
112
|
+
}, [observableRelationship, config.relationshipTypesList]);
|
|
113
|
+
|
|
114
|
+
useEffect(() => {
|
|
115
|
+
if (relationship && relationshipTypes.length > 0) {
|
|
116
|
+
if (relationship.endDate) {
|
|
117
|
+
form.setValue('endDate', new Date(relationship.endDate));
|
|
118
|
+
}
|
|
119
|
+
if (relationship.startDate) {
|
|
120
|
+
form.setValue('startDate', new Date(relationship.startDate));
|
|
121
|
+
}
|
|
122
|
+
if (relationship.relationshipType) {
|
|
123
|
+
form.setValue('relationshipType', relationship.relationshipType.uuid);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}, [relationship, relationshipTypes, form]);
|
|
127
|
+
|
|
128
|
+
useEffect(() => {
|
|
129
|
+
if (relation && hivStatus.length > 0 && pnsAproach.length > 0 && contactLivingWithPatient.length > 0) {
|
|
130
|
+
if (relation.baselineHIVStatus) {
|
|
131
|
+
const hivStatusValue = hivStatus.find(
|
|
132
|
+
(status) =>
|
|
133
|
+
typeof status.label === 'string' &&
|
|
134
|
+
status.label.toLowerCase().includes(relation.baselineHIVStatus.toLowerCase()),
|
|
135
|
+
)?.value;
|
|
136
|
+
if (hivStatusValue) {
|
|
137
|
+
form.setValue('baselineStatus', hivStatusValue);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
if (relation.pnsAproach) {
|
|
142
|
+
const pnsValue = pnsAproach.find((approach) =>
|
|
143
|
+
(approach.label as string).toLowerCase().includes((relation.pnsAproach as string).toLowerCase()),
|
|
144
|
+
)?.value;
|
|
145
|
+
if (pnsValue) {
|
|
146
|
+
form.setValue('preferedPNSAproach', pnsValue);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
if (relation.livingWithClient) {
|
|
151
|
+
const livingValue = contactLivingWithPatient.find((living) => {
|
|
152
|
+
const relationLiving = (relation.livingWithClient as string).toLowerCase();
|
|
153
|
+
const livingLabel = (living.label as string).toLowerCase();
|
|
154
|
+
|
|
155
|
+
return (
|
|
156
|
+
livingLabel === relationLiving ||
|
|
157
|
+
livingLabel.includes(relationLiving) ||
|
|
158
|
+
relationLiving.includes(livingLabel)
|
|
159
|
+
);
|
|
160
|
+
})?.value;
|
|
161
|
+
|
|
162
|
+
if (livingValue) {
|
|
163
|
+
form.setValue('livingWithClient', livingValue);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
if (relation.ipvOutcome) {
|
|
168
|
+
const ipvValue = contactIPVOutcomeOptions.find(
|
|
169
|
+
(outcome) =>
|
|
170
|
+
outcome.value === relation.ipvOutcome ||
|
|
171
|
+
outcome.label.toLowerCase().includes(relation.ipvOutcome.toLowerCase()),
|
|
172
|
+
)?.value;
|
|
173
|
+
if (ipvValue) {
|
|
174
|
+
form.setValue('ipvOutCome', ipvValue as any);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
if (relation.startDate) {
|
|
179
|
+
try {
|
|
180
|
+
const startDate = new Date(relation.startDate);
|
|
181
|
+
form.setValue('startDate', startDate);
|
|
182
|
+
} catch (e) {
|
|
183
|
+
console.warn('Invalid start date format:', relation.startDate);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
if (relation.endDate) {
|
|
188
|
+
try {
|
|
189
|
+
const endDate = new Date(relation.endDate);
|
|
190
|
+
form.setValue('endDate', endDate);
|
|
191
|
+
} catch (e) {
|
|
192
|
+
console.warn('Invalid end date format:', relation.endDate);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
if (relation.relationshipType && relationshipTypes.length > 0) {
|
|
197
|
+
const relType = relationshipTypes.find(
|
|
198
|
+
(type) =>
|
|
199
|
+
type.displayBIsToA === relation.relationshipType || type.displayAIsToB === relation.relationshipType,
|
|
200
|
+
);
|
|
201
|
+
if (relType) {
|
|
202
|
+
form.setValue('relationshipType', relType.uuid);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
}, [relation, hivStatus, pnsAproach, contactLivingWithPatient, relationshipTypes, form]);
|
|
207
|
+
|
|
208
|
+
const observablePhysicalAssault = form.watch('physicalAssault');
|
|
209
|
+
const observableThreatened = form.watch('threatened');
|
|
210
|
+
const observableSexualAssault = form.watch('sexualAssault');
|
|
211
|
+
|
|
212
|
+
useEffect(() => {
|
|
213
|
+
if ([observablePhysicalAssault, observableThreatened, observableSexualAssault].includes(BOOLEAN_YES)) {
|
|
214
|
+
form.setValue('ipvOutCome', 'True');
|
|
215
|
+
} else if (
|
|
216
|
+
[observablePhysicalAssault, observableThreatened, observableSexualAssault].every((v) => v === BOOLEAN_NO)
|
|
217
|
+
) {
|
|
218
|
+
form.setValue('ipvOutCome', 'False');
|
|
219
|
+
}
|
|
220
|
+
if (!showIPVRelatedFields) {
|
|
221
|
+
form.setValue('ipvOutCome', undefined);
|
|
222
|
+
}
|
|
223
|
+
}, [observablePhysicalAssault, observableThreatened, observableSexualAssault, showIPVRelatedFields, form]);
|
|
224
|
+
|
|
225
|
+
const onSubmit = async (values: ContactListUpdateFormType) => {
|
|
226
|
+
try {
|
|
227
|
+
const data = form.getValues();
|
|
228
|
+
|
|
229
|
+
await updateRelationship(relationship.uuid, values);
|
|
230
|
+
|
|
231
|
+
await updateContactAttributes(
|
|
232
|
+
person?.uuid,
|
|
233
|
+
{
|
|
234
|
+
baselineStatus: data?.baselineStatus,
|
|
235
|
+
preferedPNSAproach: data?.preferedPNSAproach,
|
|
236
|
+
livingWithClient: data?.livingWithClient,
|
|
237
|
+
ipvOutCome: data?.ipvOutCome,
|
|
238
|
+
},
|
|
239
|
+
config,
|
|
240
|
+
person?.attributes?.map((attr) => ({
|
|
241
|
+
uuid: attr.uuid,
|
|
242
|
+
display: attr.display ?? '',
|
|
243
|
+
value: attr.value,
|
|
244
|
+
attributeType: {
|
|
245
|
+
uuid: attr.attributeType.uuid,
|
|
246
|
+
display: attr.attributeType.display ?? '',
|
|
247
|
+
},
|
|
248
|
+
})),
|
|
249
|
+
);
|
|
250
|
+
|
|
251
|
+
showSnackbar({
|
|
252
|
+
title: 'Success',
|
|
253
|
+
kind: 'success',
|
|
254
|
+
subtitle: t('relationshipUpdated', ' Relationship updated successfully'),
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
mutate((key) => {
|
|
258
|
+
return typeof key === 'string' && key.startsWith(`${restBaseUrl}/relationship`);
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
closeWorkspace();
|
|
262
|
+
} catch (error) {
|
|
263
|
+
showSnackbar({
|
|
264
|
+
title: 'Error',
|
|
265
|
+
subtitle: 'Failure updating relationship! ' + JSON.stringify(error),
|
|
266
|
+
kind: 'error',
|
|
267
|
+
});
|
|
268
|
+
}
|
|
269
|
+
};
|
|
270
|
+
|
|
271
|
+
if (isLoading || typesLoading) {
|
|
272
|
+
return (
|
|
273
|
+
<div className={styles.loading}>
|
|
274
|
+
<InlineLoading status="active" iconDescription="Loading" description="Loading form..." />
|
|
275
|
+
</div>
|
|
276
|
+
);
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
if (error || typesError) {
|
|
280
|
+
return (
|
|
281
|
+
<div className={styles.error}>
|
|
282
|
+
<Tile id="error">
|
|
283
|
+
<strong>Error:</strong>
|
|
284
|
+
<p>{error?.message ?? typesError?.message ?? t('errorLoadingForm', 'Failed to load form')}</p>
|
|
285
|
+
</Tile>
|
|
286
|
+
</div>
|
|
287
|
+
);
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
return (
|
|
291
|
+
<FormProvider {...form}>
|
|
292
|
+
<Form onSubmit={form.handleSubmit(onSubmit)}>
|
|
293
|
+
<Stack gap={4} className={styles.grid}>
|
|
294
|
+
<Column>
|
|
295
|
+
<PatientInfo patientUuid={relationship?.personB?.uuid || relation?.relativeUuid} />
|
|
296
|
+
</Column>
|
|
297
|
+
|
|
298
|
+
<span className={styles.sectionHeader}>{t('relationship', 'Relationship')}</span>
|
|
299
|
+
<Column>
|
|
300
|
+
<Controller
|
|
301
|
+
control={form.control}
|
|
302
|
+
name="startDate"
|
|
303
|
+
render={({ field, fieldState: { error } }) => (
|
|
304
|
+
<DatePicker
|
|
305
|
+
className={styles.datePickerInput}
|
|
306
|
+
dateFormat="d/m/Y"
|
|
307
|
+
id="startDate"
|
|
308
|
+
datePickerType="single"
|
|
309
|
+
{...field}
|
|
310
|
+
ref={undefined}
|
|
311
|
+
invalid={!!error?.message}
|
|
312
|
+
invalidText={error?.message}>
|
|
313
|
+
<DatePickerInput
|
|
314
|
+
id={`startdate-input`}
|
|
315
|
+
name="startdate-input"
|
|
316
|
+
invalid={!!error?.message}
|
|
317
|
+
invalidText={error?.message}
|
|
318
|
+
placeholder="mm/dd/yyyy"
|
|
319
|
+
labelText={t('startDate', 'Start Date')}
|
|
320
|
+
size="xl"
|
|
321
|
+
/>
|
|
322
|
+
</DatePicker>
|
|
323
|
+
)}
|
|
324
|
+
/>
|
|
325
|
+
</Column>
|
|
326
|
+
|
|
327
|
+
<Column>
|
|
328
|
+
<Controller
|
|
329
|
+
control={form.control}
|
|
330
|
+
name="endDate"
|
|
331
|
+
render={({ field, fieldState: { error } }) => (
|
|
332
|
+
<DatePicker
|
|
333
|
+
className={styles.datePickerInput}
|
|
334
|
+
dateFormat="d/m/Y"
|
|
335
|
+
id="endDate"
|
|
336
|
+
datePickerType="single"
|
|
337
|
+
{...field}
|
|
338
|
+
ref={undefined}
|
|
339
|
+
invalid={!!error?.message}
|
|
340
|
+
invalidText={error?.message}>
|
|
341
|
+
<DatePickerInput
|
|
342
|
+
id="enddate-input"
|
|
343
|
+
name="enddate-input"
|
|
344
|
+
invalid={!!error?.message}
|
|
345
|
+
invalidText={error?.message}
|
|
346
|
+
placeholder="mm/dd/yyyy"
|
|
347
|
+
labelText={t('endDate', 'End Date')}
|
|
348
|
+
size="xl"
|
|
349
|
+
/>
|
|
350
|
+
</DatePicker>
|
|
351
|
+
)}
|
|
352
|
+
/>
|
|
353
|
+
</Column>
|
|
354
|
+
|
|
355
|
+
<Column>
|
|
356
|
+
<Controller
|
|
357
|
+
control={form.control}
|
|
358
|
+
name="relationshipType"
|
|
359
|
+
render={({ field, fieldState: { error } }) => (
|
|
360
|
+
<Dropdown
|
|
361
|
+
ref={field.ref}
|
|
362
|
+
invalid={!!error?.message}
|
|
363
|
+
invalidText={error?.message}
|
|
364
|
+
id="relationshipToPatient"
|
|
365
|
+
titleText={t('relationToPatient', 'Relation to patient')}
|
|
366
|
+
onChange={(e) => {
|
|
367
|
+
field.onChange(e.selectedItem);
|
|
368
|
+
}}
|
|
369
|
+
selectedItem={field.value}
|
|
370
|
+
label="Select Relationship"
|
|
371
|
+
items={relationshipTypes.map((r) => r.uuid)}
|
|
372
|
+
itemToString={(item) => relationshipTypes.find((r) => r.uuid === item)?.displayBIsToA ?? ''}
|
|
373
|
+
/>
|
|
374
|
+
)}
|
|
375
|
+
/>
|
|
376
|
+
</Column>
|
|
377
|
+
|
|
378
|
+
<Column>
|
|
379
|
+
<Controller
|
|
380
|
+
control={form.control}
|
|
381
|
+
name="livingWithClient"
|
|
382
|
+
render={({ field, fieldState: { error } }) => (
|
|
383
|
+
<Dropdown
|
|
384
|
+
ref={field.ref}
|
|
385
|
+
invalid={!!error?.message}
|
|
386
|
+
invalidText={error?.message}
|
|
387
|
+
id="livingWithClient"
|
|
388
|
+
titleText={t('livingWithClient', 'Living with client')}
|
|
389
|
+
onChange={(e: { selectedItem: string }) => {
|
|
390
|
+
field.onChange(e.selectedItem);
|
|
391
|
+
}}
|
|
392
|
+
selectedItem={field.value}
|
|
393
|
+
label="Select"
|
|
394
|
+
items={contactLivingWithPatient.map((r) => r.value)}
|
|
395
|
+
itemToString={(item: string) => contactLivingWithPatient.find((r) => r.value === item)?.label ?? ''}
|
|
396
|
+
/>
|
|
397
|
+
)}
|
|
398
|
+
/>
|
|
399
|
+
</Column>
|
|
400
|
+
|
|
401
|
+
{showIPVRelatedFields && (
|
|
402
|
+
<>
|
|
403
|
+
<span className={styles.sectionHeader}>{t('ipvQuestions', 'IPV Questions')}</span>
|
|
404
|
+
<Column>
|
|
405
|
+
<Controller
|
|
406
|
+
control={form.control}
|
|
407
|
+
name="physicalAssault"
|
|
408
|
+
render={({ field, fieldState: { error } }) => (
|
|
409
|
+
<RadioButtonGroup
|
|
410
|
+
id="physicalAssault"
|
|
411
|
+
legendText={t(
|
|
412
|
+
'physicalAssault',
|
|
413
|
+
'1. Has he/she ever hit, kicked, slapped, or otherwise physically hurt you?',
|
|
414
|
+
)}
|
|
415
|
+
{...field}
|
|
416
|
+
invalid={!!error?.message}
|
|
417
|
+
invalidText={error?.message}
|
|
418
|
+
className={styles.billingItem}>
|
|
419
|
+
<RadioButton labelText={t('yes', 'Yes')} value={BOOLEAN_YES} id="physicalAssault_yes" />
|
|
420
|
+
<RadioButton labelText={t('no', 'No')} value={BOOLEAN_NO} id="physicalAssault_no" />
|
|
421
|
+
</RadioButtonGroup>
|
|
422
|
+
)}
|
|
423
|
+
/>
|
|
424
|
+
</Column>
|
|
425
|
+
<Column>
|
|
426
|
+
<Controller
|
|
427
|
+
control={form.control}
|
|
428
|
+
name="threatened"
|
|
429
|
+
render={({ field, fieldState: { error } }) => (
|
|
430
|
+
<RadioButtonGroup
|
|
431
|
+
id="threatened"
|
|
432
|
+
legendText={t('threatened', '2. Has he/she ever threatened to hurt you?')}
|
|
433
|
+
{...field}
|
|
434
|
+
invalid={!!error?.message}
|
|
435
|
+
invalidText={error?.message}
|
|
436
|
+
className={styles.billingItem}>
|
|
437
|
+
<RadioButton labelText={t('yes', 'Yes')} value={BOOLEAN_YES} id="threatened_yes" />
|
|
438
|
+
<RadioButton labelText={t('no', 'No')} value={BOOLEAN_NO} id="threatened_no" />
|
|
439
|
+
</RadioButtonGroup>
|
|
440
|
+
)}
|
|
441
|
+
/>
|
|
442
|
+
</Column>
|
|
443
|
+
<Column>
|
|
444
|
+
<Controller
|
|
445
|
+
control={form.control}
|
|
446
|
+
name="sexualAssault"
|
|
447
|
+
render={({ field, fieldState: { error } }) => (
|
|
448
|
+
<RadioButtonGroup
|
|
449
|
+
id="sexualAssault"
|
|
450
|
+
legendText={t(
|
|
451
|
+
'sexualAssault',
|
|
452
|
+
'3.Has he/she ever forced you to do something sexually that made you feel uncomfortable?',
|
|
453
|
+
)}
|
|
454
|
+
{...field}
|
|
455
|
+
invalid={!!error?.message}
|
|
456
|
+
invalidText={error?.message}
|
|
457
|
+
className={styles.billingItem}>
|
|
458
|
+
<RadioButton labelText={t('yes', 'Yes')} value={BOOLEAN_YES} id="sexualAssault_yes" />
|
|
459
|
+
<RadioButton labelText={t('no', 'No')} value={BOOLEAN_NO} id="sexualAssault_no" />
|
|
460
|
+
</RadioButtonGroup>
|
|
461
|
+
)}
|
|
462
|
+
/>
|
|
463
|
+
</Column>
|
|
464
|
+
<span className={styles.sectionHeader}>{t('ipvOutcome', 'IPV Outcome')}</span>
|
|
465
|
+
<Column>
|
|
466
|
+
<Controller
|
|
467
|
+
control={form.control}
|
|
468
|
+
name="ipvOutCome"
|
|
469
|
+
render={({ field, fieldState: { error } }) => (
|
|
470
|
+
<Dropdown
|
|
471
|
+
ref={field.ref}
|
|
472
|
+
invalid={!!error?.message}
|
|
473
|
+
invalidText={error?.message}
|
|
474
|
+
id="ipvOutCome"
|
|
475
|
+
titleText={t('ipvOutCome', 'IPV Outcome')}
|
|
476
|
+
onChange={(e) => {
|
|
477
|
+
field.onChange(e.selectedItem);
|
|
478
|
+
}}
|
|
479
|
+
selectedItem={field.value}
|
|
480
|
+
label="Choose option"
|
|
481
|
+
items={contactIPVOutcomeOptions.map((r) => r.value)}
|
|
482
|
+
itemToString={(item) => {
|
|
483
|
+
return contactIPVOutcomeOptions.find((r) => r.value === item)?.label ?? '';
|
|
484
|
+
}}
|
|
485
|
+
/>
|
|
486
|
+
)}
|
|
487
|
+
/>
|
|
488
|
+
</Column>
|
|
489
|
+
</>
|
|
490
|
+
)}
|
|
491
|
+
|
|
492
|
+
<span className={styles.sectionHeader}>{t('baselineInformation', 'Baseline Information')}</span>
|
|
493
|
+
<Column>
|
|
494
|
+
<Controller
|
|
495
|
+
control={form.control}
|
|
496
|
+
name="baselineStatus"
|
|
497
|
+
render={({ field, fieldState: { error } }) => (
|
|
498
|
+
<Dropdown
|
|
499
|
+
ref={field.ref}
|
|
500
|
+
invalid={!!error?.message}
|
|
501
|
+
invalidText={error?.message}
|
|
502
|
+
id="baselineStatus"
|
|
503
|
+
titleText={t('baselineStatus', 'HIV Status')}
|
|
504
|
+
onChange={(e: { selectedItem: string }) => {
|
|
505
|
+
field.onChange(e.selectedItem);
|
|
506
|
+
}}
|
|
507
|
+
selectedItem={field.value}
|
|
508
|
+
label="Select HIV Status"
|
|
509
|
+
items={hivStatus.map((r) => r.value)}
|
|
510
|
+
itemToString={(item: string) => hivStatus.find((r) => r.value === item)?.label ?? ''}
|
|
511
|
+
/>
|
|
512
|
+
)}
|
|
513
|
+
/>
|
|
514
|
+
</Column>
|
|
515
|
+
|
|
516
|
+
<Column>
|
|
517
|
+
<Controller
|
|
518
|
+
control={form.control}
|
|
519
|
+
name="preferedPNSAproach"
|
|
520
|
+
render={({ field, fieldState: { error } }) => (
|
|
521
|
+
<Dropdown
|
|
522
|
+
ref={field.ref}
|
|
523
|
+
invalid={!!error?.message}
|
|
524
|
+
invalidText={error?.message}
|
|
525
|
+
id="preferedPNSAproach"
|
|
526
|
+
titleText={t('preferedPNSAproach', 'Preferred PNS Approach')}
|
|
527
|
+
onChange={(e: { selectedItem: string }) => {
|
|
528
|
+
field.onChange(e.selectedItem);
|
|
529
|
+
}}
|
|
530
|
+
selectedItem={field.value}
|
|
531
|
+
className={styles.preferredPnsApproach}
|
|
532
|
+
label="Select Approach"
|
|
533
|
+
items={pnsAproach.map((r) => r.value)}
|
|
534
|
+
itemToString={(item: string) => pnsAproach.find((r) => r.value === item)?.label ?? ''}
|
|
535
|
+
/>
|
|
536
|
+
)}
|
|
537
|
+
/>
|
|
538
|
+
</Column>
|
|
539
|
+
</Stack>
|
|
540
|
+
|
|
541
|
+
<ButtonSet className={styles.buttonSet}>
|
|
542
|
+
<Button className={styles.button} kind="secondary" onClick={closeWorkspace}>
|
|
543
|
+
{t('discard', 'Discard')}
|
|
544
|
+
</Button>
|
|
545
|
+
<Button className={styles.button} kind="primary" type="submit" disabled={form.formState.isSubmitting}>
|
|
546
|
+
{t('submit', 'Submit')}
|
|
547
|
+
</Button>
|
|
548
|
+
</ButtonSet>
|
|
549
|
+
</Form>
|
|
550
|
+
</FormProvider>
|
|
551
|
+
);
|
|
552
|
+
};
|
|
553
|
+
|
|
554
|
+
export default ContactListUpdateForm;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React, {
|
|
1
|
+
import React, { useState } from 'react';
|
|
2
2
|
import { useTranslation } from 'react-i18next';
|
|
3
3
|
import {
|
|
4
4
|
DataTable,
|
|
@@ -14,8 +14,10 @@ import {
|
|
|
14
14
|
TableRow,
|
|
15
15
|
Tile,
|
|
16
16
|
Button,
|
|
17
|
+
OverflowMenu,
|
|
18
|
+
OverflowMenuItem,
|
|
17
19
|
} from '@carbon/react';
|
|
18
|
-
import { Add
|
|
20
|
+
import { Add } from '@carbon/react/icons';
|
|
19
21
|
import { EmptyDataIllustration, ErrorState, CardHeader, usePaginationInfo } from '@openmrs/esm-patient-common-lib';
|
|
20
22
|
import {
|
|
21
23
|
ConfigurableLink,
|
|
@@ -31,6 +33,8 @@ import type { ConfigObject } from '../config-schema';
|
|
|
31
33
|
import styles from './family-history.scss';
|
|
32
34
|
import { deleteRelationship } from '../relationships/relationship.resources';
|
|
33
35
|
import HIVStatus from '../contact-list/hiv-status.component';
|
|
36
|
+
import { type Contact } from '../types';
|
|
37
|
+
import { extractNameString, uppercaseText } from '../utils/expression-helper';
|
|
34
38
|
|
|
35
39
|
interface FamilyHistoryProps {
|
|
36
40
|
patientUuid: string;
|
|
@@ -42,6 +46,7 @@ const FamilyHistory: React.FC<FamilyHistoryProps> = ({ patientUuid }) => {
|
|
|
42
46
|
const { concepts } = config;
|
|
43
47
|
const layout = useLayoutType();
|
|
44
48
|
const [pageSize, setPageSize] = useState(10);
|
|
49
|
+
const size = layout === 'tablet' ? 'lg' : 'md';
|
|
45
50
|
const { relationships, error, isLoading, isValidating } = usePatientRelationships(patientUuid);
|
|
46
51
|
const headerTitle = t('familyContacts', 'Family contacts');
|
|
47
52
|
const { results, totalPages, currentPage, goTo } = usePagination(relationships, pageSize);
|
|
@@ -109,9 +114,11 @@ const FamilyHistory: React.FC<FamilyHistoryProps> = ({ patientUuid }) => {
|
|
|
109
114
|
patientUuid,
|
|
110
115
|
});
|
|
111
116
|
};
|
|
112
|
-
const handleEditRelationship = (
|
|
113
|
-
launchWorkspace('
|
|
114
|
-
|
|
117
|
+
const handleEditRelationship = (relation: Contact) => {
|
|
118
|
+
launchWorkspace('contact-list-update-form', {
|
|
119
|
+
relation,
|
|
120
|
+
workspaceTitle: t('editContactList', 'Edit contact list'),
|
|
121
|
+
patientUuid,
|
|
115
122
|
});
|
|
116
123
|
};
|
|
117
124
|
|
|
@@ -126,7 +133,7 @@ const FamilyHistory: React.FC<FamilyHistoryProps> = ({ patientUuid }) => {
|
|
|
126
133
|
<ConfigurableLink
|
|
127
134
|
style={{ textDecoration: 'none' }}
|
|
128
135
|
to={window.getOpenmrsSpaBase() + `patient/${relation.relativeUuid}/chart/Patient Summary`}>
|
|
129
|
-
{relation.name}
|
|
136
|
+
{extractNameString(uppercaseText(relation.name))}
|
|
130
137
|
</ConfigurableLink>
|
|
131
138
|
),
|
|
132
139
|
relation: relation?.relationshipType,
|
|
@@ -147,20 +154,10 @@ const FamilyHistory: React.FC<FamilyHistoryProps> = ({ patientUuid }) => {
|
|
|
147
154
|
|
|
148
155
|
actions: (
|
|
149
156
|
<>
|
|
150
|
-
<
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
iconDescription="Edit"
|
|
155
|
-
onClick={() => handleEditRelationship(relation.uuid)}
|
|
156
|
-
/>
|
|
157
|
-
<Button
|
|
158
|
-
renderIcon={TrashCan}
|
|
159
|
-
hasIconOnly
|
|
160
|
-
kind="ghost"
|
|
161
|
-
iconDescription="Delete"
|
|
162
|
-
onClick={() => deleteRelationship(relation.uuid)}
|
|
163
|
-
/>
|
|
157
|
+
<OverflowMenu size={size} flipped>
|
|
158
|
+
<OverflowMenuItem itemText={t('edit', 'Edit')} onClick={() => handleEditRelationship(relation)} />
|
|
159
|
+
<OverflowMenuItem itemText={t('delete', 'Delete')} onClick={() => deleteRelationship(relation.uuid)} />
|
|
160
|
+
</OverflowMenu>
|
|
164
161
|
</>
|
|
165
162
|
),
|
|
166
163
|
};
|
package/src/index.ts
CHANGED
|
@@ -49,12 +49,12 @@ import { inPatientMeta } from './in-patient/in-patient.meta';
|
|
|
49
49
|
import PeerCalendar from './peer-calendar/peer-calendar.component';
|
|
50
50
|
import PeerForm from './peer-calendar/forms/peer-form.workspace';
|
|
51
51
|
import FormEntryWorkspace from './peer-calendar/forms/form-entry.workspace';
|
|
52
|
-
import RelationshipUpdateForm from './relationships/forms/relationships-update-form.workspace';
|
|
53
52
|
import DeleteRelationshipConfirmDialog from './relationships/modals/delete-relationship-dialog.modal';
|
|
54
53
|
import DeceasedDetailsView from './deceased-panel/tabs/tabs.component';
|
|
55
54
|
import DeceasedPanelDashboardLink from './deceased-panel/dashboard-link/dashboard-link.component';
|
|
56
55
|
import EndRelationshipWorkspace from './case-management/workspace/case-management-workspace.component';
|
|
57
56
|
import Partograph from './maternal-and-child-health/partography/partograph.component';
|
|
57
|
+
import ContactListUpdateForm from './contact-list/forms/contact-list-update.workspace';
|
|
58
58
|
|
|
59
59
|
const moduleName = '@kenyaemr/esm-patient-clinical-view-app';
|
|
60
60
|
|
|
@@ -101,7 +101,6 @@ export const otherRelationshipsLink = getSyncLifecycle(createDashboardLink(other
|
|
|
101
101
|
// Relationships links for Family History and the corresponding view in the patient chart
|
|
102
102
|
export const relationshipsLink = getSyncLifecycle(createDashboardLink(relationshipsDashboardMeta), options);
|
|
103
103
|
export const relationships = getSyncLifecycle(Relationships, options);
|
|
104
|
-
export const relationshipUpdateForm = getSyncLifecycle(RelationshipUpdateForm, options);
|
|
105
104
|
export const relationshipDeleteConfirmialog = getSyncLifecycle(DeleteRelationshipConfirmDialog, options);
|
|
106
105
|
|
|
107
106
|
// Contacts
|
|
@@ -109,6 +108,7 @@ export const contactList = getSyncLifecycle(ContactList, options);
|
|
|
109
108
|
export const contactListLink = getSyncLifecycle(createDashboardLink(contactListDashboardMeta), options);
|
|
110
109
|
export const contactListForm = getSyncLifecycle(ContactListForm, options);
|
|
111
110
|
export const birthDateCalculator = getSyncLifecycle(BirthDateCalculator, options);
|
|
111
|
+
export const contactListUpdateForm = getSyncLifecycle(ContactListUpdateForm, options);
|
|
112
112
|
|
|
113
113
|
// Peer Clendar
|
|
114
114
|
export const peerCalendar = getSyncLifecycle(PeerCalendar, options);
|