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 ( +
+ {t('formTitle', 'Fill in the form details')} + + {relatedPersonUuid && } + {!relatedPersonUuid && ( + + + + )} + + ( + (item ? uppercaseText(item.text) : '')} + onChange={(e) => field.onChange(e.selectedItem?.id)} + invalid={!!fieldState.error} + invalidText={fieldState.error?.message} + /> + )} + /> + + + + ( + field.onChange(e[0])} + className={styles.datePickerInput}> + + + )} + /> + + + + ( + field.onChange(e[0])} + className={styles.datePickerInput}> + + + )} + /> + + + + ( +