From 6822458dd561589be4ed3b808b04c838aa508b5d Mon Sep 17 00:00:00 2001 From: chibongho Date: Wed, 18 Sep 2024 10:24:07 -0400 Subject: [PATCH] (feat) O3-3861 - ward app - add tooltip to obs to show encounter date (#1312) * (feat) O3-3861 - ward app - add tooltip to obs to show encounter date * address PR comments --- .../src/hooks/useMotherAndChildren.ts | 10 +-- .../card-rows/mother-child-row.extension.tsx | 2 +- .../card-rows/mother-child-row.scss | 1 - .../ward-patient-coded-obs-tags.tsx | 61 ++++++++----- .../ward-patient-coded-obs-tags.tsx.orig | 87 ------------------- .../row-elements/ward-patient-obs.resource.ts | 12 ++- .../row-elements/ward-patient-obs.tsx | 17 ++-- .../ward-patient-responsive-tooltip.tsx | 32 +++++++ .../ward-patient-card/ward-patient-card.scss | 8 ++ packages/esm-ward-app/translations/en.json | 1 + .../esm-ward-app/translations/en.json.orig | 84 ------------------ 11 files changed, 106 insertions(+), 209 deletions(-) delete mode 100644 packages/esm-ward-app/src/ward-patient-card/row-elements/ward-patient-coded-obs-tags.tsx.orig create mode 100644 packages/esm-ward-app/src/ward-patient-card/row-elements/ward-patient-responsive-tooltip.tsx delete mode 100644 packages/esm-ward-app/translations/en.json.orig diff --git a/packages/esm-ward-app/src/hooks/useMotherAndChildren.ts b/packages/esm-ward-app/src/hooks/useMotherAndChildren.ts index 56967ff97..8ecbce4b2 100644 --- a/packages/esm-ward-app/src/hooks/useMotherAndChildren.ts +++ b/packages/esm-ward-app/src/hooks/useMotherAndChildren.ts @@ -1,10 +1,4 @@ -import { - makeUrl, - restBaseUrl, - useOpenmrsFetchAll, - useOpenmrsInfinite, - useOpenmrsPagination, -} from '@openmrs/esm-framework'; +import { makeUrl, restBaseUrl, useOpenmrsFetchAll } from '@openmrs/esm-framework'; import { type MotherAndChildren } from '../types'; export interface MothersAndChildrenSearchCriteria { @@ -44,7 +38,7 @@ export function useMotherAndChildren( requireChildBornDuringMothersActiveVisit?.toString() ?? 'false', ); rep && url.searchParams.append('v', rep); - return useOpenmrsPagination(fetch ? url : null, 50); + return useOpenmrsFetchAll(fetch ? url : null); } function makeUrlUrl(path: string) { diff --git a/packages/esm-ward-app/src/ward-patient-card/card-rows/mother-child-row.extension.tsx b/packages/esm-ward-app/src/ward-patient-card/card-rows/mother-child-row.extension.tsx index dcd2f65c3..bf5a36169 100644 --- a/packages/esm-ward-app/src/ward-patient-card/card-rows/mother-child-row.extension.tsx +++ b/packages/esm-ward-app/src/ward-patient-card/card-rows/mother-child-row.extension.tsx @@ -52,7 +52,7 @@ const MotherChildRowExtension: WardPatientCard = ({ patient }) => { if (isLoadingChildrenData || isLoadingMotherData) { return ( -
+
); diff --git a/packages/esm-ward-app/src/ward-patient-card/card-rows/mother-child-row.scss b/packages/esm-ward-app/src/ward-patient-card/card-rows/mother-child-row.scss index 501eab9eb..c8ca3f53c 100644 --- a/packages/esm-ward-app/src/ward-patient-card/card-rows/mother-child-row.scss +++ b/packages/esm-ward-app/src/ward-patient-card/card-rows/mother-child-row.scss @@ -14,7 +14,6 @@ .motherOrBabyIcon { padding: layout.$spacing-02; border-radius: 50%; - fill: $ui-03; background-color: $grey-2; margin-right: layout.$spacing-03; } diff --git a/packages/esm-ward-app/src/ward-patient-card/row-elements/ward-patient-coded-obs-tags.tsx b/packages/esm-ward-app/src/ward-patient-card/row-elements/ward-patient-coded-obs-tags.tsx index 1232a7ad5..98f5526b4 100644 --- a/packages/esm-ward-app/src/ward-patient-card/row-elements/ward-patient-coded-obs-tags.tsx +++ b/packages/esm-ward-app/src/ward-patient-card/row-elements/ward-patient-coded-obs-tags.tsx @@ -1,12 +1,13 @@ import { Tag } from '@carbon/react'; import { type OpenmrsResource, type Patient, type Visit } from '@openmrs/esm-framework'; -import React from 'react'; +import React, { type ReactNode } from 'react'; import { useTranslation } from 'react-i18next'; import { type ColoredObsTagsCardRowConfigObject } from '../../config-schema-extension-colored-obs-tags'; import { useObs } from '../../hooks/useObs'; import styles from '../ward-patient-card.scss'; import WardPatientSkeletonText from './ward-pateint-skeleton-text'; -import { obsCustomRepresentation, useConceptToTagColorMap } from './ward-patient-obs.resource'; +import { getObsEncounterString, obsCustomRepresentation, useConceptToTagColorMap } from './ward-patient-obs.resource'; +import WardPatientResponsiveTooltip from './ward-patient-responsive-tooltip'; interface WardPatientCodedObsTagsProps { config: ColoredObsTagsCardRowConfigObject; @@ -44,31 +45,47 @@ const WardPatientCodedObsTags: React.FC = ({ confi const summaryLabelToDisplay = summaryLabel != null ? t(summaryLabel) : obsToDisplay?.[0]?.concept?.display; - const obsNodes = obsToDisplay?.map((o) => { - const { display, uuid } = o.value as OpenmrsResource; + // for each obs configured to be displayed with a color, we create a tag for it + // for other obs not configured, we create a single summary tag for all of them. + const summaryTagTooltipText: ReactNode[] = []; + const coloredOpsTags = obsToDisplay + ?.map((o) => { + const { display, uuid } = o.value as OpenmrsResource; - const color = conceptToTagColorMap?.get(uuid); - if (color) { - return ( - - {display} - - ); - } else { - return null; - } - }); + const color = conceptToTagColorMap?.get(uuid); + if (color) { + return ( + + + {display} + + + ); + } else { + summaryTagTooltipText.push( +
+ {display} ({getObsEncounterString(o, t)}) +
, + ); + return null; + } + }) + .filter((o) => o != null); - const obsWithNoTagCount = obsNodes.filter((o) => o == null).length; - if (obsNodes?.length > 0 || obsWithNoTagCount > 0) { + if (coloredOpsTags?.length > 0 || summaryTagTooltipText.length > 0) { return (
- {obsNodes} - {obsWithNoTagCount > 0 ? ( - - {t('countItems', '{{count}} {{item}}', { count: obsWithNoTagCount, item: summaryLabelToDisplay })} - + {coloredOpsTags} + {summaryTagTooltipText.length > 0 ? ( + + + {t('countItems', '{{count}} {{item}}', { + count: summaryTagTooltipText.length, + item: summaryLabelToDisplay, + })} + + ) : null}
diff --git a/packages/esm-ward-app/src/ward-patient-card/row-elements/ward-patient-coded-obs-tags.tsx.orig b/packages/esm-ward-app/src/ward-patient-card/row-elements/ward-patient-coded-obs-tags.tsx.orig deleted file mode 100644 index bc8773e84..000000000 --- a/packages/esm-ward-app/src/ward-patient-card/row-elements/ward-patient-coded-obs-tags.tsx.orig +++ /dev/null @@ -1,87 +0,0 @@ -<<<<<<< HEAD -import { SkeletonText, Tag } from '@carbon/react'; -import { type Patient, translateFrom, type Visit, type OpenmrsResource } from '@openmrs/esm-framework'; -import React from 'react'; -======= -import { SkeletonText, Tag, Tooltip } from '@carbon/react'; -import { isDesktop, type OpenmrsResource, type Patient, useLayoutType, type Visit } from '@openmrs/esm-framework'; -import React, { ReactNode } from 'react'; ->>>>>>> b5d19494 ((fix) O3-3709 - ward app - handle pagination) -import { useTranslation } from 'react-i18next'; -import { type ColoredObsTagsCardRowConfigObject } from '../../config-schema-extension-colored-obs-tags'; -import { useObs } from '../../hooks/useObs'; -import styles from '../ward-patient-card.scss'; -import { obsCustomRepresentation, useConceptToTagColorMap } from './ward-patient-obs.resource'; -<<<<<<< HEAD -import { type ColoredObsTagsCardRowConfigObject } from '../../config-schema-extension-colored-obs-tags'; -======= ->>>>>>> b5d19494 ((fix) O3-3709 - ward app - handle pagination) - -interface WardPatientCodedObsTagsProps { - config: ColoredObsTagsCardRowConfigObject; - patient: Patient; - visit: Visit; -} - -/** - * The WardPatientCodedObsTags displays observations of coded values of a particular concept in the active visit as tags. - * Typically, these are taken from checkbox fields from a form. Each answer value can either be configured - * to show as its own tag, or collapsed into a summary tag show the number of these values present. - * - * This is a rather specialized element; - * for a more general display of obs value, use WardPatientObs instead. - * @param config - * @returns - */ -const WardPatientCodedObsTags: React.FC = ({ config, patient, visit }) => { - const { conceptUuid, summaryLabel, summaryLabelColor } = config; - const { data, isLoading } = useObs({ patient: patient.uuid, concept: conceptUuid }, obsCustomRepresentation); - const { t } = useTranslation(); - const { data: conceptToTagColorMap } = useConceptToTagColorMap(config.tags); - - if (isLoading) { - return ; - } else { - const obsToDisplay = data?.filter((o) => { - const matchVisit = o.encounter.visit?.uuid == visit?.uuid; - return matchVisit || visit == null; // TODO: remove visit == null hack when server API supports returning visit - }); - - const summaryLabelToDisplay = summaryLabel != null ? t(summaryLabel) : obsToDisplay?.[0]?.concept?.display; - - const obsNodes = obsToDisplay?.map((o) => { - const { display, uuid } = o.value as OpenmrsResource; - - const color = conceptToTagColorMap?.get(uuid); - if (color) { - return ( - - {display} - - ); - } else { - return null; - } - }); - - const obsWithNoTagCount = obsNodes.filter((o) => o == null).length; - if (obsNodes?.length > 0 || obsWithNoTagCount > 0) { - return ( -
- - {obsNodes} - {obsWithNoTagCount > 0 ? ( - - {t('countItems', '{{count}} {{item}}', { count: obsWithNoTagCount, item: summaryLabelToDisplay })} - - ) : null} - -
- ); - } else { - return null; - } - } -}; - -export default WardPatientCodedObsTags; diff --git a/packages/esm-ward-app/src/ward-patient-card/row-elements/ward-patient-obs.resource.ts b/packages/esm-ward-app/src/ward-patient-card/row-elements/ward-patient-obs.resource.ts index a8bcec17f..4a96e90dc 100644 --- a/packages/esm-ward-app/src/ward-patient-card/row-elements/ward-patient-obs.resource.ts +++ b/packages/esm-ward-app/src/ward-patient-card/row-elements/ward-patient-obs.resource.ts @@ -1,11 +1,13 @@ import { restBaseUrl, useOpenmrsFetchAll, type Concept } from '@openmrs/esm-framework'; import { type TagConfigObject } from '../../config-schema-extension-colored-obs-tags'; +import { type Observation } from '../../types'; +import { type TFunction } from 'i18next'; // prettier-ignore export const obsCustomRepresentation = 'custom:(uuid,display,obsDatetime,value,' + 'concept:(uuid,display),' + - 'encounter:(uuid,display,' + + 'encounter:(uuid,display,encounterType,encounterDatetime,' + 'visit:(uuid,display)))'; // get the setMembers of a concept set @@ -44,3 +46,11 @@ export function useConceptToTagColorMap(tags: Array) { return conceptToTagColorMap; } + +export function getObsEncounterString(obs: Observation, t: TFunction) { + return t('encounterDisplay', '{{encounterType}} {{encounterDate}}', { + encounterType: obs.encounter.encounterType.display, + encounterDate: new Date(obs.encounter.encounterDatetime).toLocaleDateString(), + interpolation: { escapeValue: false }, + }); +} diff --git a/packages/esm-ward-app/src/ward-patient-card/row-elements/ward-patient-obs.tsx b/packages/esm-ward-app/src/ward-patient-card/row-elements/ward-patient-obs.tsx index 45432e36c..546ba2622 100644 --- a/packages/esm-ward-app/src/ward-patient-card/row-elements/ward-patient-obs.tsx +++ b/packages/esm-ward-app/src/ward-patient-card/row-elements/ward-patient-obs.tsx @@ -1,12 +1,13 @@ -import { SkeletonText } from '@carbon/react'; -import { type OpenmrsResource, type Patient, translateFrom, type Visit } from '@openmrs/esm-framework'; +import { SkeletonText, Toggletip, ToggletipButton, ToggletipContent } from '@carbon/react'; +import { Information } from '@carbon/react/icons'; +import { type OpenmrsResource, type Patient, type Visit } from '@openmrs/esm-framework'; import React from 'react'; import { useTranslation } from 'react-i18next'; import { type ObsElementDefinition } from '../../config-schema'; import { useObs } from '../../hooks/useObs'; import styles from '../ward-patient-card.scss'; -import { moduleName } from '../../constant'; -import { obsCustomRepresentation } from './ward-patient-obs.resource'; +import { getObsEncounterString, obsCustomRepresentation } from './ward-patient-obs.resource'; +import WardPatientResponsiveTooltip from './ward-patient-responsive-tooltip'; export interface WardPatientObsProps { config: ObsElementDefinition; @@ -37,7 +38,13 @@ const WardPatientObs: React.FC = ({ config, patient, visit const obsNodes = obsToDisplay?.map((o) => { const { value } = o; const display: any = (value as OpenmrsResource)?.display ?? o.value; - return {display} ; + + const tooltipContent = getObsEncounterString(o, t); + return ( + + {display} + + ); }); if (obsNodes?.length > 0) { diff --git a/packages/esm-ward-app/src/ward-patient-card/row-elements/ward-patient-responsive-tooltip.tsx b/packages/esm-ward-app/src/ward-patient-card/row-elements/ward-patient-responsive-tooltip.tsx new file mode 100644 index 000000000..b4b116f30 --- /dev/null +++ b/packages/esm-ward-app/src/ward-patient-card/row-elements/ward-patient-responsive-tooltip.tsx @@ -0,0 +1,32 @@ +import { Toggletip, ToggletipButton, ToggletipContent, Tooltip } from '@carbon/react'; +import { isDesktop, useLayoutType } from '@openmrs/esm-framework'; +import React, { type ReactNode } from 'react'; +import styles from '../ward-patient-card.scss'; + +interface WardPatientResponsiveTooltipProps { + children: ReactNode; + tooltipContent: ReactNode; +} + +/** + * This component acts as a Tooltip on desktop and a Toggletip on mobile. + */ +const WardPatientResponsiveTooltip: React.FC = ({ children, tooltipContent }) => { + const layout = useLayoutType(); + if (isDesktop(layout)) { + return ( + + {children} + + ); + } else { + return ( + + {children} + {tooltipContent} + + ); + } +}; + +export default WardPatientResponsiveTooltip; diff --git a/packages/esm-ward-app/src/ward-patient-card/ward-patient-card.scss b/packages/esm-ward-app/src/ward-patient-card/ward-patient-card.scss index b43f70b04..f7455d8f1 100644 --- a/packages/esm-ward-app/src/ward-patient-card/ward-patient-card.scss +++ b/packages/esm-ward-app/src/ward-patient-card/ward-patient-card.scss @@ -70,6 +70,7 @@ .wardPatientCardExtensionSlot { display: none; + width: 100%; &:has(div:not(:empty)) { display: block; @@ -157,6 +158,13 @@ padding-right: layout.$spacing-02; } +.responsiveTooltip { + z-index: 2; + & :global(.cds--tag) { + border: 0; + } +} + .dotSeparatedChildren { display: flex; flex-wrap: wrap; diff --git a/packages/esm-ward-app/translations/en.json b/packages/esm-ward-app/translations/en.json index 0b23feefe..2e229e58f 100644 --- a/packages/esm-ward-app/translations/en.json +++ b/packages/esm-ward-app/translations/en.json @@ -15,6 +15,7 @@ "empty": "Empty", "emptyBed": "Empty bed", "emptyText": "Empty", + "encounterDisplay": "{{encounterType}} {{encounterDate}}", "errorAssigningBedToPatient": "Error assigning bed to patient", "errorCreatingEncounter": "Failed to admit patient", "errorCreatingTransferRequest": "Error creating transfer request", diff --git a/packages/esm-ward-app/translations/en.json.orig b/packages/esm-ward-app/translations/en.json.orig deleted file mode 100644 index 3a9854c81..000000000 --- a/packages/esm-ward-app/translations/en.json.orig +++ /dev/null @@ -1,84 +0,0 @@ -{ - "admissionRequests": "Admission requests", - "admissionRequestsCount_one": "{{count}} admission request", - "admissionRequestsCount_other": "{{count}} admission requests", - "admit": "Admit", - "admitPatient": "Admit patient", - "admitting": "Admitting...", - "bedManagementModuleNotInstalled": "Bed management module is not present to allow bed selection", - "bedShare": "Bed share", - "bedSwap": "Bed swap", - "cancel": "Cancel", - "chooseAnOption": "Choose an option", - "clinicalNoteLabel": "Write your notes", - "discharge": "Discharge", - "empty": "Empty", - "emptyBed": "Empty bed", - "emptyText": "Empty", - "errorAssigningBedToPatient": "Error assigning bed to patient", - "errorCreatingEncounter": "Failed to admit patient", - "errorCreatingTransferRequest": "Error creating transfer request", - "errorDischargingPatient": "Error discharging patient", -<<<<<<< HEAD - "errorLoadingBedDetails": "Error loading bed details", - "errorLoadingPatientAdmissionRequests": "Error loading patient admission requests", -======= - "errorLoadingBedDetails": "Error Loading Bed Details", - "errorLoadingPatientAdmissionRequests": "Error Loading patient admission requests", ->>>>>>> 6dfbe106 (remove orig files) - "errorLoadingPatients": "Error loading admitted patients", - "errorLoadingWardLocation": "Error loading ward location", - "female": "Female", - "fetchingEmrConfigurationFailed": "Fetching EMR configuration failed. Try refreshing the page or contact your system administrator.", - "fetchingPatientNotesFailed": "Fetching patient notes failed. Try refreshing the page or contact your system administrator.", - "inpatientNotesWorkspaceTitle": "In-patient notes", - "invalidElementIdCopy": "The configuration provided is invalid. It contains the following unknown element ID:", - "invalidLocationSpecified": "Invalid location specified", - "invalidWardLocation": "Invalid ward location: {{location}}", - "male": "Male", - "manage": "Manage", - "nextPage": "Next page", - "noBedsConfigured": "No beds configured for this location", - "noBedsConfiguredForLocation": "No beds configured for {{location}} location", - "noLocationsFound": "No locations found", - "note": "Note", - "notes": "Notes", - "Orders": "Orders", - "other": "Other", - "patientAdmittedButBedNotAssigned": "Patient admitted successfully but fail to assign bed to patient", - "patientAdmittedSuccessfully": "Patient admitted successfully", - "patientAdmittedSuccessfullySubtitle": "{{patientName}} has been successfully admitted and assigned to bed {{bedNumber}}", - "patientAdmittedWoBed": "Patient admitted successfully to {{location}}", - "patientAssignedNewbed": "Patient assigned to new bed", - "patientAssignedToBed": "{{patientName}} assigned to bed {{bedNumber}}", - "patientNoteNowVisible": "It should be now visible in the notes history", - "patientNoteSaveError": "Error saving patient note", - "patientNotesDidntLoad": "Patient notes didn't load", - "patientTransferRequestCreated": "Patient transfer request created", - "patientWasDischarged": "Patient was discharged", - "pleaseSelectBed": "Please select a bed", - "pleaseSelectTransferLocation": "Please select transfer location", - "pleaseSelectTransferType": "Please select transfer type", - "previousPage": "Previous page", - "proceedWithPatientDischarge": "Proceed with patient discharge", - "save": "Save", - "saving": "Saving...", - "searchForPatient": "Search for a patient", - "searchLocations": "Search locations", - "selectABed": "Select a bed", - "selectALocation": "Select a location", - "showingLocations_one": "{{start}}-{{end}} of {{count}} locations", - "showingLocations_other": "{{start}}-{{end}} of {{count}} locations", - "somePartsOfTheFormDidntLoad": "Some parts of the form didn't load", - "transfer": "Transfer", - "transferElsewhere": "Transfer elsewhere", - "transferRequest": "Transfer request", - "transfers": "Transfers", - "transferType": "Transfer type", - "typeOfTransfer": "Type of transfer", - "unableToSelectBeds": "Unable to select beds", - "unknown": "Unknown", - "visitNoteSaved": "Patient note saved", - "wardClinicalNotePlaceholder": "Write any notes here", - "wards": "Wards" -}