@kenyaemr/esm-patient-clinical-view-app 5.4.2-pre.2223 → 5.4.2-pre.2234

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/dist/routes.json CHANGED
@@ -1 +1 @@
1
- {"$schema":"https://json.openmrs.org/routes.schema.json","backendDependencies":{"kenyaemr":"^19.0.0"},"pages":[],"extensions":[{"name":"clinical-view-section","component":"clinicalViewPatientDashboard","slot":"patient-chart-dashboard-slot"},{"name":"family-history","slot":"patient-chart-family-history-slot","component":"familyHistory","order":0,"online":true,"offline":false},{"name":"relationships-link","component":"relationshipsLink","slot":"patient-chart-dashboard-slot","order":14,"meta":{"columns":1,"columnSpan":1,"slot":"patient-chart-relationships-slot","path":"relationships","layoutMode":"anchored"}},{"name":"relationships","slot":"patient-chart-relationships-slot","component":"relationships","order":0,"online":true,"offline":false},{"name":"contact-list-form","component":"contactListForm"},{"name":"maternal-and-child-health-dashboard-group-link","slot":"clinical-view-section","component":"maternalAndChildHealthSideNavGroup"},{"name":"antenatal-care-dashboard-link","component":"antenatalCareLink","slot":"maternal-and-child-health-slot","meta":{"columns":1,"columnSpan":1,"slot":"patient-chart-antenatal-care-dashboard-slot","path":"antenatal-care-dashboard","layoutMode":"anchored"}},{"name":"antenatal-care-dashboard","slot":"patient-chart-antenatal-care-dashboard-slot","component":"antenatalCare"},{"name":"postnatal-care-dashboard-link","component":"postnatalCareLink","slot":"maternal-and-child-health-slot","meta":{"columns":1,"columnSpan":1,"slot":"patient-chart-postnatal-care-dashboard-slot","path":"postnatal-care-dashboard","layoutMode":"anchored"}},{"name":"postnatal-care-dashboard","slot":"patient-chart-postnatal-care-dashboard-slot","component":"postnatalCare"},{"name":"labour-and-delivery-dashboard-link","component":"labourAndDeliveryLink","slot":"maternal-and-child-health-slot","meta":{"fullWidth":true,"slot":"patient-chart-labour-and-delivery-dashboard-slot","path":"labour-and-delivery-dashboard","layoutMode":"anchored"}},{"name":"labour-and-delivery-dashboard","slot":"patient-chart-labour-and-delivery-dashboard-slot","component":"labourAndDelivery","meta":{"fullWidth":true}},{"name":"hiv-care-and-treatment-dashboard-group-link","slot":"special-clinics-slot","component":"hivCareAndTreatMentSideNavGroup"},{"name":"genericNavLinks","slot":"special-clinics-slot","component":"genericNavLinks","meta":{"columns":1,"columnSpan":1,"slot":"patient-chart-special-clinic-dashboard-slot","path":"special-clinics-dashboard","layoutMode":"anchored"}},{"name":"patient-chart-special-clinic-dashboard-slot","slot":"patient-chart-special-clinic-dashboard-slot","component":"genericDashboard"},{"name":"hts-dashboard-link","component":"htsDashboardLink","slot":"hiv-care-and-treatment-slot","meta":{"columns":1,"columnSpan":1,"slot":"patient-chart-hts-dashboard-slot","path":"hts-dashboard","layoutMode":"anchored"}},{"name":"hts-clinical-view","slot":"patient-chart-hts-dashboard-slot","component":"htsClinicalView","order":2,"online":true,"offline":false},{"name":"defaulter-tracing-dashboard-link","component":"defaulterTracingLink","slot":"hiv-care-and-treatment-slot","meta":{"columns":1,"columnSpan":1,"slot":"patient-chart-defaulter-tracing-dashboard-slot","path":"defaulter-tracing-dashboard","layoutMode":"anchored"}},{"name":"defaulter-tracing-dashboard","slot":"patient-chart-defaulter-tracing-dashboard-slot","component":"defaulterTracing","order":3,"online":true,"offline":false},{"name":"special-clinics-dashboard-group-link","slot":"clinical-view-section","component":"specialClinicsSideNavGroup"},{"name":"clinical-encounter-link","component":"inPatientClinicalEncounterLink","slot":"clinical-view-section","order":40,"meta":{"columns":1,"columnSpan":1,"slot":"patient-chart-clinical-encounter-slot","path":"clinical-encounter","layoutMode":"anchored"}},{"name":"clinical-encounter","slot":"patient-chart-clinical-encounter-slot","component":"inPatientClinicalEncounter","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":"in-patient-dashboard-link","component":"inPatientChartLink","slot":"patient-chart-dashboard-slot","order":7,"meta":{"slot":"patient-chart-in-patient-dashboard-slot","path":"in-patient","layoutMode":"anchored","columns":1,"columnSpan":1}},{"name":"in-patient-dashboard","slot":"patient-chart-in-patient-dashboard-slot","component":"inPatientChartDashboard","meta":{"fullWidth":false}},{"name":"wrap-component-view","slot":"case-management-dashboard-slot","component":"wrapComponent","order":2,"online":true,"offline":false},{"name":"case-encounter-link","component":"caseEncounterDashboardLink","slot":"patient-chart-dashboard-slot","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":"deceased-panel-dashboard-link","component":"deceasedPanelDashboardLink","slot":"patient-chart-dashboard-slot","order":15,"meta":{"columns":1,"columnSpan":1,"slot":"patient-chart-deceased-panel-slot","path":"deceased-panel","layoutMode":"anchored"}},{"name":"deceased-details-tabs","slot":"patient-chart-deceased-panel-slot","component":"deceasedDetailsTabs","order":0,"online":true,"offline":false},{"name":"parto-graph-chart","slot":"patient-chart-labour-and-delivery-dashboard-slot","component":"partograph","meta":{"fullWidth":true}}],"modals":[{"name":"birth-date-calculator","component":"birthDateCalculator"},{"name":"relationship-delete-confirm-dialog","component":"relationshipDeleteConfirmialog"},{"name":"end-relationship-dialog","component":"endRelationshipModal"}],"workspaces":[{"name":"case-management-form","component":"caseManagementForm","title":"Case Management 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.2223"}
1
+ {"$schema":"https://json.openmrs.org/routes.schema.json","backendDependencies":{"kenyaemr":"^19.0.0"},"pages":[],"extensions":[{"name":"clinical-view-section","component":"clinicalViewPatientDashboard","slot":"patient-chart-dashboard-slot"},{"name":"family-history","slot":"patient-chart-family-history-slot","component":"familyHistory","order":0,"online":true,"offline":false},{"name":"relationships-link","component":"relationshipsLink","slot":"patient-chart-dashboard-slot","order":14,"meta":{"columns":1,"columnSpan":1,"slot":"patient-chart-relationships-slot","path":"relationships","layoutMode":"anchored"}},{"name":"relationships","slot":"patient-chart-relationships-slot","component":"relationships","order":0,"online":true,"offline":false},{"name":"contact-list-form","component":"contactListForm"},{"name":"maternal-and-child-health-dashboard-group-link","slot":"clinical-view-section","component":"maternalAndChildHealthSideNavGroup"},{"name":"antenatal-care-dashboard-link","component":"antenatalCareLink","slot":"maternal-and-child-health-slot","meta":{"columns":1,"columnSpan":1,"slot":"patient-chart-antenatal-care-dashboard-slot","path":"antenatal-care-dashboard","layoutMode":"anchored"}},{"name":"antenatal-care-dashboard","slot":"patient-chart-antenatal-care-dashboard-slot","component":"antenatalCare"},{"name":"postnatal-care-dashboard-link","component":"postnatalCareLink","slot":"maternal-and-child-health-slot","meta":{"columns":1,"columnSpan":1,"slot":"patient-chart-postnatal-care-dashboard-slot","path":"postnatal-care-dashboard","layoutMode":"anchored"}},{"name":"postnatal-care-dashboard","slot":"patient-chart-postnatal-care-dashboard-slot","component":"postnatalCare"},{"name":"labour-and-delivery-dashboard-link","component":"labourAndDeliveryLink","slot":"maternal-and-child-health-slot","meta":{"fullWidth":true,"slot":"patient-chart-labour-and-delivery-dashboard-slot","path":"labour-and-delivery-dashboard","layoutMode":"anchored"}},{"name":"labour-and-delivery-dashboard","slot":"patient-chart-labour-and-delivery-dashboard-slot","component":"labourAndDelivery","meta":{"fullWidth":true}},{"name":"hiv-care-and-treatment-dashboard-group-link","slot":"special-clinics-slot","component":"hivCareAndTreatMentSideNavGroup"},{"name":"genericNavLinks","slot":"special-clinics-slot","component":"genericNavLinks","meta":{"columns":1,"columnSpan":1,"slot":"patient-chart-special-clinic-dashboard-slot","path":"special-clinics-dashboard","layoutMode":"anchored"}},{"name":"patient-chart-special-clinic-dashboard-slot","slot":"patient-chart-special-clinic-dashboard-slot","component":"genericDashboard"},{"name":"hts-dashboard-link","component":"htsDashboardLink","slot":"hiv-care-and-treatment-slot","meta":{"columns":1,"columnSpan":1,"slot":"patient-chart-hts-dashboard-slot","path":"hts-dashboard","layoutMode":"anchored"}},{"name":"hts-clinical-view","slot":"patient-chart-hts-dashboard-slot","component":"htsClinicalView","order":2,"online":true,"offline":false},{"name":"defaulter-tracing-dashboard-link","component":"defaulterTracingLink","slot":"hiv-care-and-treatment-slot","meta":{"columns":1,"columnSpan":1,"slot":"patient-chart-defaulter-tracing-dashboard-slot","path":"defaulter-tracing-dashboard","layoutMode":"anchored"}},{"name":"defaulter-tracing-dashboard","slot":"patient-chart-defaulter-tracing-dashboard-slot","component":"defaulterTracing","order":3,"online":true,"offline":false},{"name":"special-clinics-dashboard-group-link","slot":"clinical-view-section","component":"specialClinicsSideNavGroup"},{"name":"clinical-encounter-link","component":"inPatientClinicalEncounterLink","slot":"clinical-view-section","order":40,"meta":{"columns":1,"columnSpan":1,"slot":"patient-chart-clinical-encounter-slot","path":"clinical-encounter","layoutMode":"anchored"}},{"name":"clinical-encounter","slot":"patient-chart-clinical-encounter-slot","component":"inPatientClinicalEncounter","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":"in-patient-dashboard-link","component":"inPatientChartLink","slot":"patient-chart-dashboard-slot","order":7,"meta":{"slot":"patient-chart-in-patient-dashboard-slot","path":"in-patient","layoutMode":"anchored","columns":1,"columnSpan":1}},{"name":"in-patient-dashboard","slot":"patient-chart-in-patient-dashboard-slot","component":"inPatientChartDashboard","meta":{"fullWidth":false}},{"name":"wrap-component-view","slot":"case-management-dashboard-slot","component":"wrapComponent","order":2,"online":true,"offline":false},{"name":"case-encounter-link","component":"caseEncounterDashboardLink","slot":"patient-chart-dashboard-slot","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":"deceased-panel-dashboard-link","component":"deceasedPanelDashboardLink","slot":"patient-chart-dashboard-slot","order":15,"meta":{"columns":1,"columnSpan":1,"slot":"patient-chart-deceased-panel-slot","path":"deceased-panel","layoutMode":"anchored"}},{"name":"deceased-details-tabs","slot":"patient-chart-deceased-panel-slot","component":"deceasedDetailsTabs","order":0,"online":true,"offline":false},{"name":"parto-graph-chart","slot":"patient-chart-labour-and-delivery-dashboard-slot","component":"partograph","meta":{"fullWidth":true}}],"modals":[{"name":"birth-date-calculator","component":"birthDateCalculator"},{"name":"relationship-delete-confirm-dialog","component":"relationshipDeleteConfirmialog"},{"name":"end-relationship-dialog","component":"endRelationshipModal"}],"workspaces":[{"name":"case-management-form","component":"caseManagementForm","title":"Case Management 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.2234"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kenyaemr/esm-patient-clinical-view-app",
3
- "version": "5.4.2-pre.2223",
3
+ "version": "5.4.2-pre.2234",
4
4
  "description": "Patient clinical view microfrontend for the OpenMRS SPA",
5
5
  "browser": "dist/kenyaemr-esm-patient-clinical-view-app.js",
6
6
  "main": "src/index.ts",
@@ -167,48 +167,73 @@ export const updatePersonAttributes = (payload: any, personUuid: string, attribu
167
167
  });
168
168
  };
169
169
 
170
+ const ATTRIBUTE_MAPPINGS = [
171
+ { key: 'baselineStatus', configPath: 'baselineHIVStatus', cleanValue: true },
172
+ { key: 'preferedPNSAproach', configPath: 'preferedPnsAproach', cleanValue: true },
173
+ { key: 'livingWithClient', configPath: 'livingWithContact', cleanValue: true },
174
+ { key: 'ipvOutCome', configPath: 'contactIPVOutcome', cleanValue: false },
175
+ ];
176
+
177
+ const createAttribute = (
178
+ attributeType: string,
179
+ value: string,
180
+ existingAttributes: Person['attributes'],
181
+ cleanValue: boolean = true,
182
+ ) => ({
183
+ attributeType,
184
+ value: cleanValue ? replaceAll(value, 'A', '') : value,
185
+ attribute: existingAttributes.find((a) => a.attributeType.uuid === attributeType)?.uuid,
186
+ });
187
+
188
+ const buildAttributes = (
189
+ attributes: ContactAttributeData,
190
+ config: ConfigObject,
191
+ existingAttributes: Person['attributes'] = [],
192
+ ) => {
193
+ if (!attributes) {
194
+ return [];
195
+ }
196
+
197
+ return ATTRIBUTE_MAPPINGS.filter((m) => attributes[m.key]).map((m) =>
198
+ createAttribute(
199
+ config.contactPersonAttributesUuid[m.configPath],
200
+ attributes[m.key],
201
+ existingAttributes,
202
+ m.cleanValue,
203
+ ),
204
+ );
205
+ };
206
+
170
207
  export const updateContactAttributes = async (
171
208
  personUuid: string,
172
- attributeData: ContactAttributeData,
209
+ attributes: ContactAttributeData,
173
210
  config: ConfigObject,
174
211
  existingAttributes: Person['attributes'] = [],
175
212
  ) => {
176
213
  try {
177
- const updatableAttributes = [
178
- {
179
- attributeType: config?.contactPersonAttributesUuid?.baselineHIVStatus,
180
- value: replaceAll(attributeData?.baselineStatus, 'A', ''),
181
- },
182
- {
183
- attributeType: config?.contactPersonAttributesUuid?.preferedPnsAproach,
184
- value: replaceAll(attributeData?.preferedPNSAproach, 'A', ''),
185
- },
186
- {
187
- attributeType: config?.contactPersonAttributesUuid?.livingWithContact,
188
- value: replaceAll(attributeData?.livingWithClient, 'A', ''),
189
- },
190
- {
191
- attributeType: config?.contactPersonAttributesUuid?.contactIPVOutcome,
192
- value: attributeData?.ipvOutCome,
193
- },
194
- ].filter((attr) => attr?.value !== undefined && attr?.value !== null && attr?.value !== '');
195
-
196
- await Promise.allSettled(
197
- updatableAttributes?.map((attr) => {
198
- const existingAttribute = existingAttributes?.find((at) => at?.attributeType?.uuid === attr?.attributeType);
199
-
200
- const payload = {
201
- attributeType: attr?.attributeType,
202
- value: attr?.value,
203
- };
204
-
205
- if (!existingAttribute?.uuid) {
206
- return createPersonAttribute(payload, personUuid);
207
- }
208
- return updatePersonAttributes(payload, personUuid, existingAttribute.uuid);
209
- }),
214
+ const attrs = buildAttributes(attributes, config, existingAttributes);
215
+
216
+ const results = await Promise.allSettled(
217
+ attrs.map((attr) =>
218
+ openmrsFetch(`${restBaseUrl}/person/${personUuid}/attribute/${attr.attribute ?? ''}`, {
219
+ method: 'POST',
220
+ headers: {
221
+ 'Content-Type': 'application/json',
222
+ },
223
+ body: JSON.stringify(omit(attr, ['attribute'])),
224
+ }),
225
+ ),
210
226
  );
211
227
 
228
+ if (results.length && results.every((r) => r.status === 'fulfilled')) {
229
+ showSnackbar({ title: 'Success ', kind: 'success', subtitle: 'Patient attributes updated succesfully' });
230
+ }
231
+ results.forEach((res) => {
232
+ if (res.status === 'rejected') {
233
+ showSnackbar({ title: 'Error updating patient attribute', kind: 'error', subtitle: res.reason?.message });
234
+ }
235
+ });
236
+
212
237
  mutate((key) => typeof key === 'string' && key.startsWith(`${restBaseUrl}/person`));
213
238
  } catch (error) {
214
239
  showSnackbar({
@@ -9,39 +9,30 @@ import {
9
9
  InlineLoading,
10
10
  Stack,
11
11
  Tile,
12
- RadioButtonGroup,
13
- RadioButton,
14
12
  } from '@carbon/react';
15
13
  import { zodResolver } from '@hookform/resolvers/zod';
16
14
  import { DefaultWorkspaceProps, parseDate, restBaseUrl, showSnackbar, useConfig } from '@openmrs/esm-framework';
17
- import React, { useEffect, useMemo } from 'react';
15
+ import dayjs from 'dayjs';
16
+ import React, { useEffect, useMemo, useRef } from 'react';
18
17
  import { Controller, FormProvider, useForm } from 'react-hook-form';
19
18
  import { useTranslation } from 'react-i18next';
20
19
  import { mutate } from 'swr';
21
20
  import { z } from 'zod';
21
+ import PatientInfo from '../../case-management/workspace/patient-info.component';
22
+ import { ConfigObject } from '../../config-schema';
23
+ import { updateContactAttributes } from '../../contact-list/contact-list.resource';
24
+ import usePersonAttributes from '../../hooks/usePersonAttributes';
22
25
  import useRelationship from '../../hooks/useRelationship';
23
26
  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 RelationshipBaselineInfoFormSection from '../../relationships/forms/baseline-info-form-section.component';
27
28
  import {
28
- BOOLEAN_NO,
29
- BOOLEAN_YES,
30
29
  relationshipFormSchema,
31
30
  relationshipUpdateFormSchema,
32
31
  updateRelationship,
33
32
  usePatientBirthdate,
34
33
  } from '../../relationships/relationship.resources';
35
34
  import { type Contact } from '../../types';
36
- import { ConfigObject } from '../../config-schema';
37
- import { contactListConceptMap } from '../../contact-list/contact-list-concept-map';
38
- import {
39
- LIVING_WITH_PATIENT_CONCEPT_UUID,
40
- PARTNER_HIV_STATUS_CONCEPT_UUID,
41
- PNS_APROACH_CONCEPT_UUID,
42
- } from '../../relationships/relationships-constants';
43
- import dayjs from 'dayjs';
44
- import RelationshipBaselineInfoFormSection from '../../relationships/forms/baseline-info-form-section.component';
35
+ import styles from './contact-list-update.scss';
45
36
 
46
37
  interface ContactListUpdateFormProps extends DefaultWorkspaceProps {
47
38
  relation: Contact;
@@ -54,35 +45,19 @@ type ContactListUpdateFormType = z.infer<typeof relationshipFormSchema>;
54
45
  const ContactListUpdateForm: React.FC<ContactListUpdateFormProps> = ({ closeWorkspace, relation, patientUuid }) => {
55
46
  const { error, isLoading, relationship } = useRelationship(relation?.uuid);
56
47
  const { isLoading: typesLoading, error: typesError, relationshipTypes } = useRelationshipTypes();
57
- const { person } = usePerson(relationship?.personB?.uuid);
48
+ const personUuid = relationship?.personB?.uuid;
49
+ const { attributes } = usePersonAttributes(personUuid);
58
50
  const config = useConfig<ConfigObject>();
59
-
60
51
  const { t } = useTranslation();
61
-
62
- const pnsAproach = useMemo(
63
- () =>
64
- Object.entries(contactListConceptMap[PNS_APROACH_CONCEPT_UUID].answers).map(([uuid, display]) => ({
65
- label: display,
66
- value: uuid,
67
- })),
68
- [],
69
- );
70
-
71
- const contactLivingWithPatient = useMemo(
72
- () =>
73
- Object.entries(contactListConceptMap[LIVING_WITH_PATIENT_CONCEPT_UUID].answers).map(([uuid, display]) => ({
74
- label: display,
75
- value: uuid,
76
- })),
77
- [],
78
- );
79
-
80
- const form = useForm<ContactListUpdateFormType>({
81
- defaultValues: {},
52
+ const form = useForm<z.infer<typeof relationshipUpdateFormSchema>>({
82
53
  resolver: zodResolver(relationshipUpdateFormSchema),
54
+ mode: 'all',
83
55
  });
56
+ const { setValue } = form;
57
+ const { isLoading: isPatientLoading, birthdate } = usePatientBirthdate(relationship?.personB?.uuid);
84
58
 
85
- const { isLoading: isPatientloading, birthdate } = usePatientBirthdate(relationship?.personB?.uuid);
59
+ // Use ref to track if initial values have been set
60
+ const initialValuesSet = useRef(false);
86
61
 
87
62
  const patientAgeMonths = useMemo(() => {
88
63
  let birthDate = birthdate ? parseDate(birthdate) : null;
@@ -93,81 +68,54 @@ const ContactListUpdateForm: React.FC<ContactListUpdateFormProps> = ({ closeWork
93
68
  }, [birthdate]);
94
69
 
95
70
  useEffect(() => {
96
- if (relationship && relationshipTypes.length > 0) {
97
- if (relationship.endDate) {
98
- form.setValue('endDate', new Date(relationship.endDate));
99
- }
71
+ // Only set initial values once when data is available and not already set
72
+ if (relationshipTypes.length > 0 && !initialValuesSet.current && relationship) {
100
73
  if (relationship.startDate) {
101
- form.setValue('startDate', new Date(relationship.startDate));
102
- }
103
- if (relationship.relationshipType) {
104
- form.setValue('relationshipType', relationship.relationshipType.uuid);
105
- }
106
- }
107
- }, [relationship, relationshipTypes, form]);
108
-
109
- useEffect(() => {
110
- if (relation) {
111
- if (relation.startDate) {
112
74
  try {
113
- const startDate = new Date(relation.startDate);
114
- form.setValue('startDate', startDate);
75
+ const startDate = parseDate(relationship.startDate);
76
+ setValue('startDate', startDate);
115
77
  } catch (e) {
116
- console.warn('Invalid start date format:', relation.startDate);
78
+ console.warn('Invalid start date format:', relationship.startDate);
117
79
  }
118
80
  }
119
81
 
120
- if (relation.endDate) {
82
+ if (relationship.endDate) {
121
83
  try {
122
- const endDate = new Date(relation.endDate);
123
- form.setValue('endDate', endDate);
84
+ const endDate = parseDate(relationship.endDate);
85
+ setValue('endDate', endDate);
124
86
  } catch (e) {
125
- console.warn('Invalid end date format:', relation.endDate);
87
+ console.warn('Invalid end date format:', relationship.endDate);
126
88
  }
127
89
  }
128
90
 
129
- if (relation.relationshipType && relationshipTypes.length > 0) {
130
- const relType = relationshipTypes.find(
131
- (type) =>
132
- type.displayBIsToA === relation.relationshipType || type.displayAIsToB === relation.relationshipType,
133
- );
134
- if (relType) {
135
- form.setValue('relationshipType', relType.uuid);
136
- }
91
+ if (relationship.relationshipType?.uuid) {
92
+ setValue('relationshipType', relationship.relationshipType.uuid);
137
93
  }
94
+
95
+ // Mark that initial values have been set
96
+ initialValuesSet.current = true;
138
97
  }
139
- }, [relation, relationshipTypes, form]);
98
+ }, [relationshipTypes, setValue, relationship]);
140
99
 
141
100
  const onSubmit = async (values: ContactListUpdateFormType) => {
142
101
  try {
143
- const data = form.getValues();
144
-
145
102
  await updateRelationship(relationship.uuid, values);
146
-
147
103
  await updateContactAttributes(
148
- person?.uuid,
104
+ personUuid,
149
105
  {
150
- baselineStatus: data?.baselineStatus,
151
- preferedPNSAproach: data?.preferedPNSAproach,
152
- livingWithClient: data?.livingWithClient,
153
- ipvOutCome: data?.ipvOutCome,
106
+ baselineStatus: values?.baselineStatus,
107
+ preferedPNSAproach: values?.preferedPNSAproach,
108
+ livingWithClient: values?.livingWithClient,
109
+ ipvOutCome: values?.ipvOutCome,
154
110
  },
155
111
  config,
156
- person?.attributes?.map((attr) => ({
157
- uuid: attr.uuid,
158
- display: attr.display ?? '',
159
- value: attr.value,
160
- attributeType: {
161
- uuid: attr.attributeType.uuid,
162
- display: attr.attributeType.display ?? '',
163
- },
164
- })),
112
+ attributes,
165
113
  );
166
114
 
167
115
  showSnackbar({
168
116
  title: 'Success',
169
117
  kind: 'success',
170
- subtitle: t('relationshipUpdated', ' Relationship updated successfully'),
118
+ subtitle: t('relationshipUpdated', 'Relationship updated successfully'),
171
119
  });
172
120
 
173
121
  mutate((key) => {
@@ -178,13 +126,13 @@ const ContactListUpdateForm: React.FC<ContactListUpdateFormProps> = ({ closeWork
178
126
  } catch (error) {
179
127
  showSnackbar({
180
128
  title: 'Error',
181
- subtitle: 'Failure updating relationship! ' + JSON.stringify(error),
129
+ subtitle: 'Failure updating relationship! ' + (error?.message || JSON.stringify(error)),
182
130
  kind: 'error',
183
131
  });
184
132
  }
185
133
  };
186
134
 
187
- if (isLoading || typesLoading) {
135
+ if (isLoading || typesLoading || isPatientLoading) {
188
136
  return (
189
137
  <div className={styles.loading}>
190
138
  <InlineLoading status="active" iconDescription="Loading" description="Loading form..." />
@@ -203,6 +151,17 @@ const ContactListUpdateForm: React.FC<ContactListUpdateFormProps> = ({ closeWork
203
151
  );
204
152
  }
205
153
 
154
+ if (!relationship) {
155
+ return (
156
+ <div className={styles.error}>
157
+ <Tile id="no-relationship">
158
+ <strong>Error:</strong>
159
+ <p>{t('noRelationshipFound', 'No relationship data found')}</p>
160
+ </Tile>
161
+ </div>
162
+ );
163
+ }
164
+
206
165
  return (
207
166
  <FormProvider {...form}>
208
167
  <Form onSubmit={form.handleSubmit(onSubmit)}>
@@ -210,7 +169,6 @@ const ContactListUpdateForm: React.FC<ContactListUpdateFormProps> = ({ closeWork
210
169
  <Column>
211
170
  <PatientInfo patientUuid={relationship?.personB?.uuid || relation?.relativeUuid} />
212
171
  </Column>
213
-
214
172
  <span className={styles.sectionHeader}>{t('relationship', 'Relationship')}</span>
215
173
  <Column>
216
174
  <Controller
@@ -221,7 +179,8 @@ const ContactListUpdateForm: React.FC<ContactListUpdateFormProps> = ({ closeWork
221
179
  className={styles.datePickerInput}
222
180
  dateFormat="d/m/Y"
223
181
  datePickerType="single"
224
- {...field}
182
+ value={field.value}
183
+ onChange={(v) => field.onChange(v[0])}
225
184
  ref={undefined}
226
185
  invalid={!!error?.message}
227
186
  invalidText={error?.message}>
@@ -247,15 +206,15 @@ const ContactListUpdateForm: React.FC<ContactListUpdateFormProps> = ({ closeWork
247
206
  className={styles.datePickerInput}
248
207
  dateFormat="d/m/Y"
249
208
  datePickerType="single"
250
- {...field}
251
- ref={undefined}
209
+ value={field.value}
210
+ onChange={(dates) => field.onChange(dates[0])}
252
211
  invalid={!!error?.message}
253
212
  invalidText={error?.message}>
254
213
  <DatePickerInput
255
214
  id="enddate-input"
256
215
  invalid={!!error?.message}
257
216
  invalidText={error?.message}
258
- placeholder="mm/dd/yyyy"
217
+ placeholder="dd/mm/yyyy"
259
218
  labelText={t('endDate', 'End Date')}
260
219
  size="lg"
261
220
  />
@@ -287,10 +246,7 @@ const ContactListUpdateForm: React.FC<ContactListUpdateFormProps> = ({ closeWork
287
246
  />
288
247
  </Column>
289
248
 
290
- <RelationshipBaselineInfoFormSection
291
- patientAgeMonths={patientAgeMonths}
292
- patientUuid={relationship?.personB?.uuid}
293
- />
249
+ <RelationshipBaselineInfoFormSection patientAgeMonths={patientAgeMonths} patientUuid={personUuid} />
294
250
  </Stack>
295
251
 
296
252
  <ButtonSet className={styles.buttonSet}>
@@ -298,7 +254,7 @@ const ContactListUpdateForm: React.FC<ContactListUpdateFormProps> = ({ closeWork
298
254
  {t('discard', 'Discard')}
299
255
  </Button>
300
256
  <Button className={styles.button} kind="primary" type="submit" disabled={form.formState.isSubmitting}>
301
- {t('submit', 'Submit')}
257
+ {form.formState.isSubmitting ? t('submitting', 'Submitting...') : t('submit', 'Submit')}
302
258
  </Button>
303
259
  </ButtonSet>
304
260
  </Form>
@@ -1,16 +1,19 @@
1
- import { openmrsFetch } from '@openmrs/esm-framework';
1
+ import { openmrsFetch, useConfig } from '@openmrs/esm-framework';
2
2
  import useSWR from 'swr';
3
3
  import { RelationShipType } from '../types';
4
+ import { ConfigObject } from '../config-schema';
4
5
 
5
6
  const useRelationshipTypes = () => {
6
7
  const customeRepresentation = 'custom:(uuid,displayAIsToB,displayBIsToA)';
7
8
  const url = `/ws/rest/v1/relationshiptype?v=${customeRepresentation}`;
8
-
9
+ const { relationshipTypesList } = useConfig<ConfigObject>();
9
10
  const { data, error, isLoading } = useSWR<{ data: { results: RelationShipType[] } }>(url, openmrsFetch);
10
11
  return {
11
12
  error,
12
13
  isLoading,
13
- relationshipTypes: data?.data?.results ?? [],
14
+ relationshipTypes: (data?.data?.results ?? []).filter(
15
+ (r) => relationshipTypesList.findIndex((rl) => rl.uuid === r.uuid) !== -1,
16
+ ),
14
17
  };
15
18
  };
16
19
 
@@ -45,6 +45,7 @@ const RelationshipBaselineInfoFormSection: FC<RelationshipBaselineInfoFormSectio
45
45
  const config = useConfig<ConfigObject>();
46
46
  const { setValue } = form;
47
47
  const { attributes, isLoading } = usePersonAttributes(patientUuid);
48
+
48
49
  const pnsAproach = useMemo(
49
50
  () =>
50
51
  Object.entries(contactListConceptMap[PNS_APROACH_CONCEPT_UUID].answers).map(([uuid, display]) => ({
@@ -92,6 +93,7 @@ const RelationshipBaselineInfoFormSection: FC<RelationshipBaselineInfoFormSectio
92
93
  [patientAgeMonths],
93
94
  );
94
95
 
96
+ // useEffect for IPV outcome logic
95
97
  useEffect(() => {
96
98
  if ([observablePhysicalAssault, observableThreatened, observableSexualAssault].includes(BOOLEAN_YES)) {
97
99
  form.setValue('ipvOutCome', 'True');
@@ -103,68 +105,79 @@ const RelationshipBaselineInfoFormSection: FC<RelationshipBaselineInfoFormSectio
103
105
  if (!showIPVRelatedFields) {
104
106
  form.setValue('ipvOutCome', undefined);
105
107
  }
106
- }, [
107
- observablePhysicalAssault,
108
- observableThreatened,
109
- observableSexualAssault,
110
- observableRelationship,
111
- form,
112
- showIPVRelatedFields,
113
- ]);
108
+ }, [observablePhysicalAssault, observableThreatened, observableSexualAssault, form, showIPVRelatedFields]);
114
109
 
110
+ // Set initial values from attributes only once
115
111
  useEffect(() => {
116
- if (attributes.length) {
117
- const hivStatusAttribute = attributes.find(
118
- (a) => a.attributeType.uuid === config.contactPersonAttributesUuid.baselineHIVStatus,
119
- );
120
- if (hivStatusAttribute) {
121
- const value = hivStatus.find((r) => r.value.startsWith(hivStatusAttribute.value))?.value;
122
- if (value) {
123
- setValue('baselineStatus', value);
112
+ // Only set values once when attributes are loaded and not already set
113
+ if (attributes.length > 0 || hivStatusPersonB) {
114
+ if (attributes.length > 0) {
115
+ const hivStatusAttribute = attributes.find(
116
+ (a) => a.attributeType.uuid === config.contactPersonAttributesUuid.baselineHIVStatus,
117
+ );
118
+ if (hivStatusAttribute) {
119
+ const value = hivStatus.find((r) => r.value.startsWith(hivStatusAttribute.value))?.value;
120
+ if (value) {
121
+ setValue('baselineStatus', value);
122
+ }
124
123
  }
125
- } else if (hivStatusPersonB) {
126
- const value = hivStatus.find((r) => r.label.toLowerCase().includes(hivStatusPersonB.toLowerCase()))?.value;
127
- if (value) {
128
- setValue('baselineStatus', value);
124
+
125
+ const pnsAproachAttribute = attributes.find(
126
+ (a) => a.attributeType.uuid === config.contactPersonAttributesUuid.preferedPnsAproach,
127
+ );
128
+ if (pnsAproachAttribute) {
129
+ const value = pnsAproach.find((r) => r.value.startsWith(pnsAproachAttribute.value))?.value;
130
+ if (value) {
131
+ setValue('preferedPNSAproach', value);
132
+ }
129
133
  }
130
- }
131
134
 
132
- const pnsAproachAttribute = attributes.find(
133
- (a) => a.attributeType.uuid === config.contactPersonAttributesUuid.preferedPnsAproach,
134
- );
135
- if (pnsAproachAttribute) {
136
- const value = pnsAproach.find((r) => r.value.startsWith(pnsAproachAttribute.value))?.value;
137
- if (value) {
138
- setValue('preferedPNSAproach', value);
135
+ const livingWithPatientAttribute = attributes.find(
136
+ (a) => a.attributeType.uuid === config.contactPersonAttributesUuid.livingWithContact,
137
+ );
138
+ if (livingWithPatientAttribute) {
139
+ const value = contactLivingWithPatient.find((r) =>
140
+ r.value.startsWith(livingWithPatientAttribute.value),
141
+ )?.value;
142
+ if (value) {
143
+ setValue('livingWithClient', value);
144
+ }
139
145
  }
140
- }
141
146
 
142
- const livingWithPatientAttribute = attributes.find(
143
- (a) => a.attributeType.uuid === config.contactPersonAttributesUuid.livingWithContact,
144
- );
145
- if (livingWithPatientAttribute) {
146
- const value = contactLivingWithPatient.find((r) => r.value.startsWith(livingWithPatientAttribute.value))?.value;
147
- if (value) {
148
- setValue('livingWithClient', value);
147
+ const ipvAttr = attributes.find(
148
+ (a) => a.attributeType.uuid === config.contactPersonAttributesUuid.contactIPVOutcome,
149
+ );
150
+ if (ipvAttr) {
151
+ const value = contactIPVOutcomeOptions.find((r) => r.value.startsWith(ipvAttr.value))?.value;
152
+ if (value) {
153
+ setValue('ipvOutCome', value as any);
154
+ }
149
155
  }
150
156
  }
151
157
 
152
- const ipvAttr = attributes.find(
153
- (a) => a.attributeType.uuid === config.contactPersonAttributesUuid.contactIPVOutcome,
154
- );
155
- if (ipvAttr) {
156
- const value = contactIPVOutcomeOptions.find((r) => r.value.startsWith(ipvAttr.value))?.value;
158
+ // Handle hivStatusPersonB when no attributes but HIV status is available
159
+ if (
160
+ hivStatusPersonB &&
161
+ !attributes.find((a) => a.attributeType.uuid === config.contactPersonAttributesUuid.baselineHIVStatus)
162
+ ) {
163
+ const value = hivStatus.find((r) => r.label.toLowerCase().includes(hivStatusPersonB.toLowerCase()))?.value;
157
164
  if (value) {
158
- setValue('ipvOutCome', value as any);
165
+ setValue('baselineStatus', value);
159
166
  }
160
167
  }
161
- } else if (hivStatusPersonB) {
162
- const value = hivStatus.find((r) => r.label.toLowerCase().includes(hivStatusPersonB.toLowerCase()))?.value;
163
- if (value) {
164
- setValue('baselineStatus', value);
165
- }
166
168
  }
167
- }, [attributes, setValue, config, hivStatus, pnsAproach, contactLivingWithPatient, hivStatusPersonB]);
169
+ }, [
170
+ attributes,
171
+ contactLivingWithPatient,
172
+ hivStatus,
173
+ hivStatusPersonB,
174
+ pnsAproach,
175
+ setValue,
176
+ config.contactPersonAttributesUuid.baselineHIVStatus,
177
+ config.contactPersonAttributesUuid.preferedPnsAproach,
178
+ config.contactPersonAttributesUuid.livingWithContact,
179
+ config.contactPersonAttributesUuid.contactIPVOutcome,
180
+ ]);
168
181
 
169
182
  return (
170
183
  <>