From 33583c72a7c0844e7f0bf63674087e97132df632 Mon Sep 17 00:00:00 2001
From: Amos Machora
Date: Fri, 26 Jul 2024 16:34:04 +0300
Subject: [PATCH] Refactor: Relationships UI improvements (#278)
* feat: created relationship workspace and revalidating functionality
* feat: translations and filtering relationship types
* Update packages/esm-patient-clinical-view-app/src/family-partner-history/family-relationship.workspace.tsx
Co-authored-by: Makombe Kennedy
* Apply suggestions from code review from @Makombe
Co-authored-by: Makombe Kennedy
* feat: other relationships tab on relationships page
* fix: correctly filtering relationships based on tab
* refactor: filter relationship types from useConfig object
* refactor: misplaced aria label
---------
Co-authored-by: Makombe Kennedy
---
.../workspace/case-management.resource.ts | 5 +-
.../src/config-schema.ts | 20 +-
.../contact-list/contact-list.component.tsx | 2 +-
.../contact-list/contact-list.workspace.tsx | 4 +-
.../src/dashboard.meta.tsx | 10 +
.../family-history.component.tsx | 44 +++-
.../family-partner-dashboard.meta.tsx | 8 -
.../family-relationship.scss | 104 +++++++++
.../family-relationship.workspace.tsx | 195 +++++++++++++++++
.../relationships.resource.tsx | 22 +-
.../src/index.ts | 15 +-
.../other-relationships.component.tsx | 200 ++++++++++++++++++
.../other-relationships.scss | 150 +++++++++++++
.../other-relationships.workspace.tsx | 199 +++++++++++++++++
.../tabs/relationships-tabs-component.tsx | 5 +
.../src/routes.json | 34 ++-
.../translations/en.json | 7 +-
17 files changed, 975 insertions(+), 49 deletions(-)
delete mode 100644 packages/esm-patient-clinical-view-app/src/family-partner-history/family-partner-dashboard.meta.tsx
create mode 100644 packages/esm-patient-clinical-view-app/src/family-partner-history/family-relationship.scss
create mode 100644 packages/esm-patient-clinical-view-app/src/family-partner-history/family-relationship.workspace.tsx
create mode 100644 packages/esm-patient-clinical-view-app/src/other-relationships/other-relationships.component.tsx
create mode 100644 packages/esm-patient-clinical-view-app/src/other-relationships/other-relationships.scss
create mode 100644 packages/esm-patient-clinical-view-app/src/other-relationships/other-relationships.workspace.tsx
diff --git a/packages/esm-patient-clinical-view-app/src/case-management/workspace/case-management.resource.ts b/packages/esm-patient-clinical-view-app/src/case-management/workspace/case-management.resource.ts
index 259e043c..3498bef0 100644
--- a/packages/esm-patient-clinical-view-app/src/case-management/workspace/case-management.resource.ts
+++ b/packages/esm-patient-clinical-view-app/src/case-management/workspace/case-management.resource.ts
@@ -19,10 +19,7 @@ interface RelationshipType {
display: string;
}
-interface RelationshipTypeResponse {
- results: RelationshipType[];
-}
-interface RelationshipTypeResponse {
+export interface RelationshipTypeResponse {
results: RelationshipType[];
}
diff --git a/packages/esm-patient-clinical-view-app/src/config-schema.ts b/packages/esm-patient-clinical-view-app/src/config-schema.ts
index 4023ba48..0f512ace 100644
--- a/packages/esm-patient-clinical-view-app/src/config-schema.ts
+++ b/packages/esm-patient-clinical-view-app/src/config-schema.ts
@@ -138,39 +138,39 @@ export const configSchema = {
_default: [
{
uuid: '8d91a01c-c2cc-11de-8d13-0010c6dffd0f',
- displayAIsToB: 'Sibling',
+ display: 'Sibling/Sibling',
},
{
uuid: '8d91a210-c2cc-11de-8d13-0010c6dffd0f',
- displayAIsToB: 'Parent',
+ display: 'Parent/Child',
},
{
uuid: '8d91a3dc-c2cc-11de-8d13-0010c6dffd0f',
- displayAIsToB: 'Aunt/Uncle',
+ display: 'Aunt/Uncle/Niece/Nephew',
},
{
uuid: '5f115f62-68b7-11e3-94ee-6bef9086de92',
- displayAIsToB: 'Guardian',
+ display: 'Guardian/Dependant',
},
{
uuid: 'd6895098-5d8d-11e3-94ee-b35a4132a5e3',
- displayAIsToB: 'Spouse',
+ display: 'Spouse/Spouse',
},
{
uuid: '007b765f-6725-4ae9-afee-9966302bace4',
- displayAIsToB: 'Partner',
+ display: 'Partner/Partner',
},
{
uuid: '2ac0d501-eadc-4624-b982-563c70035d46',
- displayAIsToB: 'Co-wife',
+ display: 'Co-wife/Co-wife',
},
{
uuid: '58da0d1e-9c89-42e9-9412-275cef1e0429',
- displayAIsToB: 'Injectable-drug-user',
+ display: 'Injectable-drug-user/Injectable-druguser',
},
{
uuid: '76edc1fe-c5ce-4608-b326-c8ecd1020a73',
- displayAIsToB: 'SNS',
+ display: 'SNS/SNS',
},
],
},
@@ -205,7 +205,7 @@ export interface ConfigObject {
preferedPnsAproach: string;
livingWithContact: string;
};
- familyRelationshipsTypeList: Array<{ uuid: string; displayAIsToB: string }>;
+ familyRelationshipsTypeList: Array<{ uuid: string; display: string }>;
}
export interface PartograpyComponents {
diff --git a/packages/esm-patient-clinical-view-app/src/contact-list/contact-list.component.tsx b/packages/esm-patient-clinical-view-app/src/contact-list/contact-list.component.tsx
index 9da89c82..cd38d2fe 100644
--- a/packages/esm-patient-clinical-view-app/src/contact-list/contact-list.component.tsx
+++ b/packages/esm-patient-clinical-view-app/src/contact-list/contact-list.component.tsx
@@ -141,7 +141,7 @@ const ContactList: React.FC = ({ patientUuid }) => {
{t('noContactToDisplay', 'There is no contact data to display for this patient.')}
diff --git a/packages/esm-patient-clinical-view-app/src/contact-list/contact-list.workspace.tsx b/packages/esm-patient-clinical-view-app/src/contact-list/contact-list.workspace.tsx
index 53a9d659..3fa2dbd3 100644
--- a/packages/esm-patient-clinical-view-app/src/contact-list/contact-list.workspace.tsx
+++ b/packages/esm-patient-clinical-view-app/src/contact-list/contact-list.workspace.tsx
@@ -317,9 +317,7 @@ const ContactListForm: React.FC = ({
initialSelectedItem={field.value}
label="Select Realtionship"
items={config.familyRelationshipsTypeList.map((r) => r.uuid)}
- itemToString={(item) =>
- config.familyRelationshipsTypeList.find((r) => r.uuid === item)?.displayAIsToB ?? ''
- }
+ itemToString={(item) => config.familyRelationshipsTypeList.find((r) => r.uuid === item)?.display ?? ''}
/>
)}
/>
diff --git a/packages/esm-patient-clinical-view-app/src/dashboard.meta.tsx b/packages/esm-patient-clinical-view-app/src/dashboard.meta.tsx
index 0ccfbd7a..e74ca4fa 100644
--- a/packages/esm-patient-clinical-view-app/src/dashboard.meta.tsx
+++ b/packages/esm-patient-clinical-view-app/src/dashboard.meta.tsx
@@ -57,6 +57,15 @@ export const contactListDashboardMeta = {
config: {},
};
+export const otherRelationshipsDashboardMeta = {
+ slot: 'patient-chart-relationships-slot',
+ columns: 1,
+ title: 'Other Relationships',
+ path: 'other-relationships',
+ moduleName: '@kenyaemr/esm-patient-clinical-view-app',
+ config: {},
+};
+
export const relationshipsDashboardMeta = {
slot: 'patient-chart-relationships-slot',
columns: 1,
@@ -65,6 +74,7 @@ export const relationshipsDashboardMeta = {
moduleName: '@kenyaemr/esm-patient-clinical-view-app',
config: {},
};
+
export const labourDeliveryDashboardMeta = {
slot: 'patient-chart-labour-delivery-slot',
columns: 1,
diff --git a/packages/esm-patient-clinical-view-app/src/family-partner-history/family-history.component.tsx b/packages/esm-patient-clinical-view-app/src/family-partner-history/family-history.component.tsx
index a7cb366a..123810ab 100644
--- a/packages/esm-patient-clinical-view-app/src/family-partner-history/family-history.component.tsx
+++ b/packages/esm-patient-clinical-view-app/src/family-partner-history/family-history.component.tsx
@@ -17,27 +17,35 @@ import {
} from '@carbon/react';
import { Add } from '@carbon/react/icons';
import { EmptyDataIllustration, ErrorState, CardHeader, usePaginationInfo } from '@openmrs/esm-patient-common-lib';
-import { ConfigurableLink, isDesktop, navigate, useConfig, useLayoutType, usePagination } from '@openmrs/esm-framework';
+import {
+ ConfigurableLink,
+ isDesktop,
+ launchWorkspace,
+ useConfig,
+ useLayoutType,
+ usePagination,
+} from '@openmrs/esm-framework';
import { useRelationships } from './relationships.resource';
import ConceptObservations from './concept-obs.component';
import type { ConfigObject } from '../config-schema';
import styles from './family-history.scss';
interface FamilyHistoryProps {
- encounterTypeUuid?: string;
- formEntrySub?: any;
patientUuid: string;
}
const FamilyHistory: React.FC = ({ patientUuid }) => {
const { t } = useTranslation();
const config = useConfig();
+ const { concepts, familyRelationshipsTypeList } = config;
const layout = useLayoutType();
- const { concepts } = config;
const [pageSize, setPageSize] = useState(10);
const { relationships, error, isLoading, isValidating } = useRelationships(patientUuid);
- const headerTitle = t('familyHistory', 'Family history');
- const { results, totalPages, currentPage, goTo } = usePagination(relationships, pageSize);
+ const familyRelationshipTypeUUIDs = new Set(familyRelationshipsTypeList.map((type) => type.uuid));
+ const familyRelationships = relationships.filter((r) => familyRelationshipTypeUUIDs.has(r.relationshipTypeUUID));
+
+ const headerTitle = t('familyContacts', 'Family contacts');
+ const { results, totalPages, currentPage, goTo } = usePagination(familyRelationships, pageSize);
const { pageSizes } = usePaginationInfo(pageSize, totalPages, currentPage, results.length);
const headers = [
@@ -68,7 +76,10 @@ const FamilyHistory: React.FC = ({ patientUuid }) => {
];
const handleAddHistory = () => {
- navigate({ to: `\${openmrsSpaBase}/patient/${patientUuid}/edit` });
+ launchWorkspace('family-relationship-form', {
+ workspaceTitle: 'Family Relationship Form',
+ rootPersonUuid: patientUuid,
+ });
};
const tableRows =
@@ -95,15 +106,26 @@ const FamilyHistory: React.FC = ({ patientUuid }) => {
};
}) ?? [];
- if (isLoading) {
- return ;
+ if (isLoading || isValidating) {
+ return (
+
+ );
}
if (error) {
return ;
}
- if (relationships.length === 0) {
+ if (familyRelationships.length === 0) {
return (
@@ -113,7 +135,7 @@ const FamilyHistory: React.FC = ({ patientUuid }) => {
There is no family history data to display for this patient.
diff --git a/packages/esm-patient-clinical-view-app/src/family-partner-history/family-partner-dashboard.meta.tsx b/packages/esm-patient-clinical-view-app/src/family-partner-history/family-partner-dashboard.meta.tsx
deleted file mode 100644
index 75103022..00000000
--- a/packages/esm-patient-clinical-view-app/src/family-partner-history/family-partner-dashboard.meta.tsx
+++ /dev/null
@@ -1,8 +0,0 @@
-export const familyHistoryDashboardMeta = {
- slot: 'patient-chart-family-history-slot',
- columns: 1,
- title: 'Family History',
- path: 'family-history',
- moduleName: '@kenyaemr/esm-patient-clinical-view-app',
- config: {},
-};
diff --git a/packages/esm-patient-clinical-view-app/src/family-partner-history/family-relationship.scss b/packages/esm-patient-clinical-view-app/src/family-partner-history/family-relationship.scss
new file mode 100644
index 00000000..f6d76c51
--- /dev/null
+++ b/packages/esm-patient-clinical-view-app/src/family-partner-history/family-relationship.scss
@@ -0,0 +1,104 @@
+@use '@carbon/styles/scss/spacing';
+@use '@carbon/styles/scss/type';
+@use '@carbon/layout';
+@import '~@openmrs/esm-styleguide/src/vars';
+
+.heading {
+ @include type.type-style('heading-compact-01');
+ margin: spacing.$spacing-05 0 spacing.$spacing-05;
+}
+
+.warningContainer {
+ background-color: $carbon--red-50;
+ padding: spacing.$spacing-04;
+ margin: spacing.$spacing-03 0 spacing.$spacing-03;
+ display: flex;
+ justify-content: space-between;
+ .warning {
+ @include type.type-style('heading-compact-01');
+ color: $ui-05;
+ }
+}
+
+.datePickerInput span,
+.datePickerInput div,
+.datePickerInput input,
+.datePickerInput {
+ width: 100% !important;
+}
+.form {
+ display: flex;
+ flex-direction: column;
+ height: 100%;
+ padding-bottom: 20px;
+}
+
+.buttonSet {
+ margin-top: auto;
+ display: flex;
+}
+
+.button {
+ height: spacing.$spacing-10;
+ display: flex;
+ align-content: flex-start;
+ align-items: center;
+ max-width: 50%;
+ min-width: 50%;
+ width: 50%;
+}
+
+.grid {
+ margin: 0 spacing.$spacing-05;
+ padding: 0rem;
+}
+.textbox {
+ margin-bottom: spacing.$spacing-08;
+}
+
+.caseFormTitle {
+ @include type.type-style('heading-02');
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ margin: spacing.$spacing-05;
+ margin-bottom: spacing.$spacing-07;
+ row-gap: 1.5rem;
+ position: relative;
+
+ &::after {
+ content: '';
+ display: block;
+ width: 2rem;
+ border-bottom: 0.375rem solid var(--brand-03);
+ position: absolute;
+ bottom: -0.75rem;
+ left: 0;
+ }
+
+ & > span {
+ @include type.type-style('body-01');
+ }
+}
+
+.sectionHeader {
+ @include type.type-style('heading-02');
+ margin-top: spacing.$spacing-05;
+}
+
+:global(.omrs-breakpoint-lt-desktop) {
+ .form {
+ height: var(--tablet-workspace-window-height);
+ }
+
+ .buttonSet {
+ padding: spacing.$spacing-06 spacing.$spacing-05;
+ background-color: $ui-02;
+ justify-content: flex-end;
+ gap: spacing.$spacing-05;
+ padding: 0;
+ margin-top: auto;
+ display: flex;
+ justify-content: end;
+ }
+}
diff --git a/packages/esm-patient-clinical-view-app/src/family-partner-history/family-relationship.workspace.tsx b/packages/esm-patient-clinical-view-app/src/family-partner-history/family-relationship.workspace.tsx
new file mode 100644
index 00000000..0477bda6
--- /dev/null
+++ b/packages/esm-patient-clinical-view-app/src/family-partner-history/family-relationship.workspace.tsx
@@ -0,0 +1,195 @@
+import React, { useMemo, useState } from 'react';
+import { useTranslation } from 'react-i18next';
+import { Column, TextArea, Form, Stack, ButtonSet, ComboBox, Button, DatePicker, DatePickerInput } from '@carbon/react';
+import { useForm, Controller, SubmitHandler } from 'react-hook-form';
+import { zodResolver } from '@hookform/resolvers/zod';
+import { z } from 'zod';
+import styles from './family-relationship.scss';
+import { ExtensionSlot, showSnackbar, useConfig } from '@openmrs/esm-framework';
+import { uppercaseText } from '../utils/expression-helper';
+import { saveRelationship } from '../case-management/workspace/case-management.resource';
+import PatientInfo from '../case-management/workspace/patient-info.component';
+import { mutate } from 'swr';
+import { useAllRelationshipTypes, useRelationships } from './relationships.resource';
+import { ConfigObject } from '../config-schema';
+
+const schema = z.object({
+ relationship: z.string({ required_error: 'Relationship is required' }),
+ startDate: z.date({ required_error: 'Start date is required' }),
+ endDate: z.date().optional(),
+ notes: z.string().optional(),
+});
+type FormData = z.infer;
+
+type RelationshipFormProps = {
+ closeWorkspace: () => void;
+ rootPersonUuid: string;
+};
+
+const FamilyRelationshipForm: React.FC = ({ closeWorkspace, rootPersonUuid }) => {
+ const { t } = useTranslation();
+ const [relatedPersonUuid, setRelatedPersonUuid] = useState(undefined);
+ const { relationshipsUrl } = useRelationships(rootPersonUuid);
+ const { familyRelationshipsTypeList } = useConfig();
+
+ const relationshipTypes = familyRelationshipsTypeList.map((relationship) => ({
+ id: relationship.uuid,
+ text: relationship.display,
+ }));
+
+ const {
+ control,
+ handleSubmit,
+ formState: { isValid },
+ } = useForm({
+ mode: 'all',
+ resolver: zodResolver(schema),
+ });
+
+ const onSubmit: SubmitHandler = async (data) => {
+ const payload = {
+ personA: rootPersonUuid,
+ personB: relatedPersonUuid,
+ relationshipType: data.relationship,
+ startDate: data.startDate.toISOString(),
+ endDate: data.endDate ? data.endDate.toISOString() : null,
+ };
+ try {
+ await saveRelationship(payload);
+ mutate(relationshipsUrl);
+ showSnackbar({
+ kind: 'success',
+ title: t('saveRelationship', 'Save Relationship'),
+ subtitle: t('savedRlship', 'Relationship saved successfully'),
+ timeoutInMs: 3000,
+ isLowContrast: true,
+ });
+
+ closeWorkspace();
+ } catch (err) {
+ showSnackbar({
+ kind: 'error',
+ title: t('relationshpError', 'Relationship Error'),
+ subtitle: t('RlshipError', 'Request Failed.......'),
+ timeoutInMs: 2500,
+ isLowContrast: true,
+ });
+ }
+ };
+
+ const selectPatient = (relatedPersonUuid: string) => {
+ setRelatedPersonUuid(relatedPersonUuid);
+ };
+
+ return (
+
+ );
+};
+
+export default FamilyRelationshipForm;
diff --git a/packages/esm-patient-clinical-view-app/src/family-partner-history/relationships.resource.tsx b/packages/esm-patient-clinical-view-app/src/family-partner-history/relationships.resource.tsx
index 50e0b1fd..6794173d 100644
--- a/packages/esm-patient-clinical-view-app/src/family-partner-history/relationships.resource.tsx
+++ b/packages/esm-patient-clinical-view-app/src/family-partner-history/relationships.resource.tsx
@@ -1,6 +1,8 @@
import { useMemo } from 'react';
import useSWR from 'swr';
-import { type FetchResponse, openmrsFetch, FHIRResource } from '@openmrs/esm-framework';
+import { type FetchResponse, openmrsFetch, FHIRResource, restBaseUrl } from '@openmrs/esm-framework';
+import useSWRImmutable from 'swr/immutable';
+import { RelationshipTypeResponse } from '../case-management/workspace/case-management.resource';
interface RelationshipsResponse {
results: Array;
@@ -15,6 +17,8 @@ interface ExtractedRelationship {
causeOfDeath: string;
relativeUuid: string;
relationshipType: string;
+ relationshipTypeDisplay: string;
+ relationshipTypeUUID: string;
patientUuid: string;
}
@@ -74,6 +78,14 @@ function mapObservations(obsData) {
}
}
+export const useAllRelationshipTypes = () => {
+ const customRepresentation = 'custom:(uuid,display)';
+ const url = `${restBaseUrl}/relationshiptype?v=${customRepresentation}`;
+ const { data, error } = useSWRImmutable<{ data: RelationshipTypeResponse }>(url, openmrsFetch);
+
+ return { data, error };
+};
+
export function useRelationships(patientUuid: string) {
const customRepresentation =
'custom:(display,uuid,personA:(uuid,age,display,dead,causeOfDeath),personB:(uuid,age,display,dead,causeOfDeath),relationshipType:(uuid,display,description,aIsToB,bIsToA))';
@@ -83,8 +95,11 @@ export function useRelationships(patientUuid: string) {
: null;
const { data, error, isLoading, isValidating } = useSWR, Error>(
- patientUuid ? relationshipsUrl : null,
+ relationshipsUrl,
openmrsFetch,
+ {
+ revalidateOnFocus: false,
+ },
);
const relationships = useMemo(() => {
@@ -96,6 +111,7 @@ export function useRelationships(patientUuid: string) {
error,
isLoading,
isValidating,
+ relationshipsUrl,
};
}
@@ -115,6 +131,8 @@ function extractRelationshipData(
causeOfDeath: r.personB.causeOfDeath,
relativeUuid: r.personB.uuid,
relationshipType: r.relationshipType.bIsToA,
+ relationshipTypeDisplay: r.relationshipType.display,
+ relationshipTypeUUID: r.relationshipType.uuid,
patientUuid: r.personB.uuid,
});
} else {
diff --git a/packages/esm-patient-clinical-view-app/src/index.ts b/packages/esm-patient-clinical-view-app/src/index.ts
index da39193e..d60944c4 100644
--- a/packages/esm-patient-clinical-view-app/src/index.ts
+++ b/packages/esm-patient-clinical-view-app/src/index.ts
@@ -12,10 +12,11 @@ import {
caseEncounterDashboardMeta,
caseManagementDashboardMeta,
contactListDashboardMeta,
+ familyHistoryDashboardMeta,
+ otherRelationshipsDashboardMeta,
relationshipsDashboardMeta,
} from './dashboard.meta';
import FamilyHistory from './family-partner-history/family-history.component';
-import { familyHistoryDashboardMeta } from './family-partner-history/family-partner-dashboard.meta';
import AntenatalCare from './maternal-and-child-health/antenatal-care.component';
import LabourDelivery from './maternal-and-child-health/labour-delivery.component';
import {
@@ -40,6 +41,9 @@ import WrapComponent from './case-management/wrap/wrap.component';
import CaseManagementForm from './case-management/workspace/case-management.workspace';
import Relationships from './relationships/relationships.component';
import CaseEncounterOverviewComponent from './case-management/encounters/case-encounter-overview.component';
+import FamilyRelationshipForm from './family-partner-history/family-relationship.workspace';
+import { OtherRelationships } from './other-relationships/other-relationships.component';
+import { OtherRelationshipsForm } from './other-relationships/other-relationships.workspace';
const moduleName = '@kenyaemr/esm-patient-clinical-view-app';
@@ -78,11 +82,18 @@ export const defaulterTracing = getSyncLifecycle(DefaulterTracing, options);
// Dashboard links for Family History and the corresponding view in the patient chart
export const familyHistory = getSyncLifecycle(FamilyHistory, options);
export const familyHistoryLink = getSyncLifecycle(createDashboardLink(familyHistoryDashboardMeta), options);
+export const familyRelationshipForm = getSyncLifecycle(FamilyRelationshipForm, options);
-// RElationships links for Family History and the corresponding view in the patient chart
+// Dashboard links for Other relationships and the corresponding view in the patient chart
+export const otherRelationships = getSyncLifecycle(OtherRelationships, options);
+export const otherRelationshipsLink = getSyncLifecycle(createDashboardLink(otherRelationshipsDashboardMeta), options);
+export const otherRelationshipsForm = getSyncLifecycle(OtherRelationshipsForm, options);
+
+// Relationships links for Family History and the corresponding view in the patient chart
export const relationshipsLink = getSyncLifecycle(createDashboardLink(relationshipsDashboardMeta), options);
export const relationships = getSyncLifecycle(Relationships, options);
+// Contacts
export const contactList = getSyncLifecycle(ContactList, options);
export const contactListLink = getSyncLifecycle(createDashboardLink(contactListDashboardMeta), options);
export const contactListForm = getSyncLifecycle(ContactListForm, options);
diff --git a/packages/esm-patient-clinical-view-app/src/other-relationships/other-relationships.component.tsx b/packages/esm-patient-clinical-view-app/src/other-relationships/other-relationships.component.tsx
new file mode 100644
index 00000000..b1a3fb02
--- /dev/null
+++ b/packages/esm-patient-clinical-view-app/src/other-relationships/other-relationships.component.tsx
@@ -0,0 +1,200 @@
+import React, { useState } from 'react';
+import { useTranslation } from 'react-i18next';
+import {
+ DataTable,
+ DataTableSkeleton,
+ Layer,
+ Pagination,
+ Table,
+ TableBody,
+ TableCell,
+ TableContainer,
+ TableHead,
+ TableHeader,
+ TableRow,
+ Tile,
+ Button,
+} from '@carbon/react';
+import { Add } from '@carbon/react/icons';
+import { EmptyDataIllustration, ErrorState, CardHeader, usePaginationInfo } from '@openmrs/esm-patient-common-lib';
+import {
+ ConfigurableLink,
+ isDesktop,
+ launchWorkspace,
+ useConfig,
+ useLayoutType,
+ usePagination,
+} from '@openmrs/esm-framework';
+import type { ConfigObject } from '../config-schema';
+import styles from './other-relationships.scss';
+import { useRelationships } from '../family-partner-history/relationships.resource';
+import ConceptObservations from '../family-partner-history/concept-obs.component';
+
+interface OtherRelationshipsProps {
+ patientUuid: string;
+}
+
+export const OtherRelationships: React.FC = ({ patientUuid }) => {
+ const { t } = useTranslation();
+ const config = useConfig();
+ const layout = useLayoutType();
+ const { concepts, familyRelationshipsTypeList } = config;
+ const [pageSize, setPageSize] = useState(10);
+
+ const { relationships, error, isLoading, isValidating } = useRelationships(patientUuid);
+ const familyRelationshipTypeUUIDs = new Set(familyRelationshipsTypeList.map((type) => type.uuid));
+ const nonFamilyRelationships = relationships.filter((r) => !familyRelationshipTypeUUIDs.has(r.relationshipTypeUUID));
+
+ const headerTitle = t('otherRelationships', 'Other Relationships');
+ const { results, totalPages, currentPage, goTo } = usePagination(nonFamilyRelationships, pageSize);
+ const { pageSizes } = usePaginationInfo(pageSize, totalPages, currentPage, results.length);
+
+ const headers = [
+ {
+ header: t('name', 'Name'),
+ key: 'name',
+ },
+ {
+ header: t('relation', 'Relation'),
+ key: 'relation',
+ },
+ {
+ header: t('age', 'Age'),
+ key: 'age',
+ },
+ {
+ header: t('alive', 'Alive'),
+ key: 'alive',
+ },
+ {
+ header: t('causeOfDeath', 'Cause of Death'),
+ key: 'causeOfDeath',
+ },
+ {
+ header: t('chronicDisease', 'Chronic Disease'),
+ key: 'chronicDisease',
+ },
+ ];
+
+ const handleAddHistory = () => {
+ launchWorkspace('other-relationship-form', {
+ workspaceTitle: 'Other Relationship Form',
+ rootPersonUuid: patientUuid,
+ });
+ };
+
+ const tableRows =
+ results?.map((relation) => {
+ const patientUuid = relation.patientUuid;
+
+ return {
+ id: `${relation.uuid}`,
+ name: (
+
+ {relation.name}
+
+ ),
+ relation: relation?.relationshipType,
+ age: relation?.relativeAge ?? '--',
+ alive: relation?.dead ? t('dead', 'Dead') : t('alive', 'Alive'),
+ causeOfDeath: (
+
+ ),
+ patientUuid: relation,
+ chronicDisease: ,
+ };
+ }) ?? [];
+
+ if (isLoading || isValidating) {
+ return (
+
+ );
+ }
+
+ if (error) {
+ return ;
+ }
+
+ if (nonFamilyRelationships.length === 0) {
+ return (
+
+
+
+
{headerTitle}
+
+
+ There is no other relationships data to display for this patient.
+
+
+
+ );
+ }
+
+ return (
+
+
+ {isLoading && }{' '}
+
+
+
(
+
+
+
+
+ {headers.map((header) => (
+
+ {header.header?.content ?? header.header}
+
+ ))}
+
+
+
+ {rows.map((row) => (
+
+ {row.cells.map((cell) => (
+ {cell.value}
+ ))}
+
+ ))}
+
+
+
+ )}
+ />
+ {
+ goTo(page);
+ setPageSize(pageSize);
+ }}
+ />
+
+ );
+};
diff --git a/packages/esm-patient-clinical-view-app/src/other-relationships/other-relationships.scss b/packages/esm-patient-clinical-view-app/src/other-relationships/other-relationships.scss
new file mode 100644
index 00000000..3b07292f
--- /dev/null
+++ b/packages/esm-patient-clinical-view-app/src/other-relationships/other-relationships.scss
@@ -0,0 +1,150 @@
+@use '@carbon/styles/scss/spacing';
+@use '@carbon/styles/scss/type';
+@use '@carbon/styles/scss/colors';
+@use '@carbon/layout';
+@import '~@openmrs/esm-styleguide/src/vars';
+
+.heading {
+ @include type.type-style('heading-compact-01');
+ margin: spacing.$spacing-05 0 spacing.$spacing-05;
+}
+
+.warningContainer {
+ background-color: $carbon--red-50;
+ padding: spacing.$spacing-04;
+ margin: spacing.$spacing-03 0 spacing.$spacing-03;
+ display: flex;
+ justify-content: space-between;
+ .warning {
+ @include type.type-style('heading-compact-01');
+ color: $ui-05;
+ }
+}
+
+.datePickerInput span,
+.datePickerInput div,
+.datePickerInput input,
+.datePickerInput {
+ width: 100% !important;
+}
+.form {
+ display: flex;
+ flex-direction: column;
+ height: 100%;
+ padding-bottom: 20px;
+}
+
+.buttonSet {
+ margin-top: auto;
+ display: flex;
+}
+
+.button {
+ height: spacing.$spacing-10;
+ display: flex;
+ align-content: flex-start;
+ align-items: center;
+ max-width: 50%;
+ min-width: 50%;
+ width: 50%;
+}
+
+.grid {
+ margin: 0 spacing.$spacing-05;
+ padding: 0rem;
+}
+.textbox {
+ margin-bottom: spacing.$spacing-08;
+}
+
+.caseFormTitle {
+ @include type.type-style('heading-02');
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ margin: spacing.$spacing-05;
+ margin-bottom: spacing.$spacing-07;
+ row-gap: 1.5rem;
+ position: relative;
+
+ &::after {
+ content: '';
+ display: block;
+ width: 2rem;
+ border-bottom: 0.375rem solid var(--brand-03);
+ position: absolute;
+ bottom: -0.75rem;
+ left: 0;
+ }
+
+ & > span {
+ @include type.type-style('body-01');
+ }
+}
+
+.sectionHeader {
+ @include type.type-style('heading-02');
+ margin-top: spacing.$spacing-05;
+}
+
+:global(.omrs-breakpoint-lt-desktop) {
+ .form {
+ height: var(--tablet-workspace-window-height);
+ }
+
+ .buttonSet {
+ padding: spacing.$spacing-06 spacing.$spacing-05;
+ background-color: $ui-02;
+ justify-content: flex-end;
+ gap: spacing.$spacing-05;
+ padding: 0;
+ margin-top: auto;
+ display: flex;
+ justify-content: end;
+ }
+}
+
+.widgetContainer {
+ background-color: colors.$white-0;
+ border: 1px solid colors.$gray-20;
+ margin-bottom: 1rem;
+}
+
+.widgetContainer :global(.cds--data-table) thead th button span {
+ height: unset !important;
+}
+
+.tile {
+ text-align: center;
+}
+
+.emptyStateContainer {
+ margin: 2rem 0;
+}
+
+.content {
+ @include type.type-style('heading-compact-01');
+ color: colors.$gray-70;
+ margin-top: layout.$layout-05;
+ margin-bottom: layout.$spacing-03;
+}
+
+.desktopHeading,
+.tabletHeading {
+ text-align: left;
+ text-transform: capitalize;
+
+ h4 {
+ @include type.type-style('heading-compact-02');
+ color: colors.$gray-70;
+
+ &:after {
+ content: '';
+ display: block;
+ width: 2rem;
+ padding-top: 3px;
+ border-bottom: 0.375rem solid;
+ @include brand-03(border-bottom-color);
+ }
+ }
+}
diff --git a/packages/esm-patient-clinical-view-app/src/other-relationships/other-relationships.workspace.tsx b/packages/esm-patient-clinical-view-app/src/other-relationships/other-relationships.workspace.tsx
new file mode 100644
index 00000000..8dc403f0
--- /dev/null
+++ b/packages/esm-patient-clinical-view-app/src/other-relationships/other-relationships.workspace.tsx
@@ -0,0 +1,199 @@
+import React, { useState } from 'react';
+import { useTranslation } from 'react-i18next';
+import { Column, TextArea, Form, Stack, ButtonSet, ComboBox, Button, DatePicker, DatePickerInput } from '@carbon/react';
+import { useForm, Controller, SubmitHandler } from 'react-hook-form';
+import { zodResolver } from '@hookform/resolvers/zod';
+import { z } from 'zod';
+import styles from './other-relationships.scss';
+import { ExtensionSlot, showSnackbar, useConfig } from '@openmrs/esm-framework';
+import { uppercaseText } from '../utils/expression-helper';
+import { saveRelationship } from '../case-management/workspace/case-management.resource';
+import PatientInfo from '../case-management/workspace/patient-info.component';
+import { mutate } from 'swr';
+import { useAllRelationshipTypes, useRelationships } from '../family-partner-history/relationships.resource';
+import { ConfigObject } from '../config-schema';
+
+const schema = z.object({
+ relationship: z.string({ required_error: 'Relationship is required' }),
+ startDate: z.date({ required_error: 'Start date is required' }),
+ endDate: z.date().optional(),
+ notes: z.string().optional(),
+});
+type FormData = z.infer;
+
+type OtherRelationshipsFormProps = {
+ closeWorkspace: () => void;
+ rootPersonUuid: string;
+};
+
+export const OtherRelationshipsForm: React.FC = ({ closeWorkspace, rootPersonUuid }) => {
+ const { t } = useTranslation();
+ const [relatedPersonUuid, setRelatedPersonUuid] = useState(undefined);
+ const { relationshipsUrl } = useRelationships(rootPersonUuid);
+ const { data: relationshipTypesData } = useAllRelationshipTypes();
+ const { familyRelationshipsTypeList } = useConfig();
+ const familyRelationshipTypeUUIDs = new Set(familyRelationshipsTypeList.map((type) => type.uuid));
+
+ const relationshipTypes =
+ relationshipTypesData?.data.results
+ .map((relationship) => ({
+ id: relationship.uuid,
+ text: relationship.display,
+ }))
+ .filter((r) => !familyRelationshipTypeUUIDs.has(r.id)) || [];
+
+ const {
+ control,
+ handleSubmit,
+ formState: { isValid },
+ } = useForm({
+ mode: 'all',
+ resolver: zodResolver(schema),
+ });
+
+ const onSubmit: SubmitHandler = async (data) => {
+ const payload = {
+ personA: rootPersonUuid,
+ personB: relatedPersonUuid,
+ relationshipType: data.relationship,
+ startDate: data.startDate.toISOString(),
+ endDate: data.endDate ? data.endDate.toISOString() : null,
+ };
+
+ try {
+ await saveRelationship(payload);
+ mutate(relationshipsUrl);
+ showSnackbar({
+ kind: 'success',
+ title: t('saveRelationship', 'Save Relationship'),
+ subtitle: t('savedRlship', 'Relationship saved successfully'),
+ timeoutInMs: 3000,
+ isLowContrast: true,
+ });
+
+ closeWorkspace();
+ } catch (err) {
+ showSnackbar({
+ kind: 'error',
+ title: t('relationshpError', 'Relationship Error'),
+ subtitle: t('RlshipError', 'Request Failed.......'),
+ timeoutInMs: 2500,
+ isLowContrast: true,
+ });
+ }
+ };
+
+ const selectPatient = (relatedPersonUuid: string) => {
+ setRelatedPersonUuid(relatedPersonUuid);
+ };
+
+ return (
+
+ );
+};
diff --git a/packages/esm-patient-clinical-view-app/src/relationships/tabs/relationships-tabs-component.tsx b/packages/esm-patient-clinical-view-app/src/relationships/tabs/relationships-tabs-component.tsx
index cd4ec6a3..d2238e88 100644
--- a/packages/esm-patient-clinical-view-app/src/relationships/tabs/relationships-tabs-component.tsx
+++ b/packages/esm-patient-clinical-view-app/src/relationships/tabs/relationships-tabs-component.tsx
@@ -4,6 +4,7 @@ import { useTranslation } from 'react-i18next';
import styles from './relationships-tabs.scss';
import ContactList from '../../contact-list/contact-list.component';
import FamilyHistory from '../../family-partner-history/family-history.component';
+import { OtherRelationships } from '../../other-relationships/other-relationships.component';
interface RelationshipsTabProps {
patientUuid: string;
@@ -17,6 +18,7 @@ export const RelationshipsTab: React.FC = ({ patientUuid
{t('family', 'Family')}
{t('pnsContacts', 'PNS Contacts')}
+ {t('other', 'Other')}
@@ -25,6 +27,9 @@ export const RelationshipsTab: React.FC = ({ patientUuid
+
+
+
diff --git a/packages/esm-patient-clinical-view-app/src/routes.json b/packages/esm-patient-clinical-view-app/src/routes.json
index 33569878..a673ec83 100644
--- a/packages/esm-patient-clinical-view-app/src/routes.json
+++ b/packages/esm-patient-clinical-view-app/src/routes.json
@@ -18,6 +18,14 @@
"online": true,
"offline": false
},
+ {
+ "name": "other-relationships",
+ "slot": "patient-chart-family-history-slot",
+ "component": "otherRelationships",
+ "order": 0,
+ "online": true,
+ "offline": false
+ },
{
"name": "relationships-link",
"component": "relationshipsLink",
@@ -206,11 +214,7 @@
"online": true,
"offline": false
},
- {
- "name": "case-management-form",
- "component": "caseManagementForm"
- },
- {
+ {
"name": "case-encounter-link",
"component": "caseEncounterDashboardLink",
"slot": "patient-chart-dashboard-slot",
@@ -238,5 +242,25 @@
"name": "birth-date-calculator",
"component": "birthDateCalculator"
}
+],
+"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": "other-relationship-form",
+ "component": "otherRelationshipsForm",
+ "title": "Other Relationships Form",
+ "type": "form"
+ }
]
}
diff --git a/packages/esm-patient-clinical-view-app/translations/en.json b/packages/esm-patient-clinical-view-app/translations/en.json
index abaf2dad..b031ccb5 100644
--- a/packages/esm-patient-clinical-view-app/translations/en.json
+++ b/packages/esm-patient-clinical-view-app/translations/en.json
@@ -5,6 +5,8 @@
"activeCases": "Active cases",
"add": "Add",
"addCase": "Add case",
+ "addPNSContact": "Add PNS Contact",
+ "addRelationship": "Add relationship",
"admissionDate": "Admission Date",
"admissionWard": "Admission Ward",
"admittingDoctor": "Admitting Doctor",
@@ -60,7 +62,7 @@
"error": "Error",
"facility": "Facility",
"failedDeleting": "couldn't be deleted",
- "familyHistory": "Family history",
+ "familyContacts": "Family contacts",
"feedingOrders": "Feeding Orders",
"fetalHeartRate": "Fetal Heart Rate",
"filterByForm": "Filter by form",
@@ -97,6 +99,7 @@
"noObservationsFound": "No observations found",
"onDate": "On Date",
"operatingDoctor": "Operating Doctor",
+ "otherRelationships": "Other Relationships",
"otherSubstanceAbuse": "Other Substance Abuse",
"partnerStatus": "HIV status of partner)",
"partograph": "Partograph",
@@ -111,8 +114,6 @@
"primaryDiagnosis": "Primary Diagnosis",
"priorityOfAdmission": "Priority Of Admission",
"recommendedProcedure": "Recommended Procedure",
- "recordContact": "Record Contact",
- "recordHistory": "Record History",
"recordLabourDetails": "Record labour details",
"referrals": "Referrals",
"relation": "Relation",