diff --git a/packages/esm-ward-app/src/config-schema.ts b/packages/esm-ward-app/src/config-schema.ts index 6d9a959ff..f9504bab0 100644 --- a/packages/esm-ward-app/src/config-schema.ts +++ b/packages/esm-ward-app/src/config-schema.ts @@ -74,30 +74,45 @@ export const configSchema: ConfigSchema = { }, }, pendingItems: { - orders: { - orderTypes: { - _type: Type.Array, - _description: 'Configures pending orders and transfers to display.', - _default: [{ label: 'Labs', uuid: '52a447d3-a64a-11e3-9aeb-50e549534c5e' }], - _elements: { - uuid: { - _type: Type.UUID, - _description: 'Identifies the order type.', - }, - label: { - _type: Type.String, - _description: - "The label or i18n key to the translated label to display. If not provided, defaults to 'Orders'", - _default: null, + _type: Type.Array, + _description: 'Configures pending orders and transfers to display.', + _default: [ + { + id: 'pending-items', + orders: { + orderTypes: [{ label: 'Labs', uuid: '52a447d3-a64a-11e3-9aeb-50e549534c5e' }], + }, + showPendingItems: true, + }, + ], + _elements: { + id: { + _type: Type.String, + _description: 'The unique identifier for this patient card element', + }, + orders: { + orderTypes: { + _type: Type.Array, + _description: 'Configures pending orders and transfers to display.', + _elements: { + uuid: { + _type: Type.UUID, + _description: 'Identifies the order type.', + }, + label: { + _type: Type.String, + _description: + "The label or i18n key to the translated label to display. If not provided, defaults to 'Orders'", + _default: null, + }, }, }, }, - }, - showPendingItems: { - _type: Type.Boolean, - _description: - 'Optional. If true, pending items (e.g., number of pending orders) will be displayed on the patient card.', - _default: true, + showPendingItems: { + _type: Type.Boolean, + _description: + 'Optional. If true, pending items (e.g., number of pending orders) will be displayed on the patient card.', + }, }, }, patientIdentifier: { @@ -158,7 +173,7 @@ export const configSchema: ConfigSchema = { _description: 'Configures admission request notes to display.', _default: [ { - id: 'admission-note', + id: 'admission-request-note', conceptUuid: '161011AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA', }, ], diff --git a/packages/esm-ward-app/src/ward-patient-card/card-rows/admission-request-note-row.component.tsx b/packages/esm-ward-app/src/ward-patient-card/card-rows/admission-request-note-row.component.tsx index 1a9b1ff55..c44918280 100644 --- a/packages/esm-ward-app/src/ward-patient-card/card-rows/admission-request-note-row.component.tsx +++ b/packages/esm-ward-app/src/ward-patient-card/card-rows/admission-request-note-row.component.tsx @@ -1,10 +1,9 @@ -import { useConfig } from '@openmrs/esm-framework'; import React from 'react'; import { type ObsElementConfig } from '../../config-schema'; import { type WardPatient } from '../../types'; +import { useElementConfig } from '../../ward-view/ward-view.resource'; import WardPatientObs from '../row-elements/ward-patient-obs'; import styles from '../ward-patient-card.scss'; -import { useElementConfig } from '../../ward-view/ward-view.resource'; interface AdmissionRequestNoteRowProps { wardPatient: WardPatient; @@ -13,7 +12,7 @@ interface AdmissionRequestNoteRowProps { const AdmissionRequestNoteRow: React.FC = ({ id, wardPatient }) => { const { patient, visit, inpatientAdmission } = wardPatient; - const { conceptUuid } = useElementConfig('admissionRequestNote', id); + const { conceptUuid } = useElementConfig('admissionRequestNote', id) ?? {}; const config: ObsElementConfig = { conceptUuid, limit: 0, 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 584aae6c4..95b21005b 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 @@ -60,7 +60,7 @@ const WardPatientObs: React.FC = ({ id, configOverride, pat {labelToDisplay ? t('labelColon', '{{label}}:', { label: labelToDisplay }) : ''} - {obsNodes} +
{obsNodes}
); } else { 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 0a003280f..3e25d3b29 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 @@ -56,6 +56,7 @@ width: 100%; padding: layout.$spacing-04; border-top: 1px colors.$gray-20 solid; + background-color: colors.$white; &:empty { border-top: 0px; @@ -174,7 +175,7 @@ display: flex; flex-wrap: wrap; - > div:not(div:first-of-type):not(:empty) { + > *:not(*:first-of-type):not(:empty) { display: flex; align-items: center; diff --git a/packages/esm-ward-app/src/ward-view/default-ward/default-ward-patient-card-header.component.tsx b/packages/esm-ward-app/src/ward-view/default-ward/default-ward-patient-card-header.component.tsx index efacbc380..a2c0c3952 100644 --- a/packages/esm-ward-app/src/ward-view/default-ward/default-ward-patient-card-header.component.tsx +++ b/packages/esm-ward-app/src/ward-view/default-ward/default-ward-patient-card-header.component.tsx @@ -7,9 +7,10 @@ import WardPatientName from '../../ward-patient-card/row-elements/ward-patient-n import WardPatientTimeOnWard from '../../ward-patient-card/row-elements/ward-patient-time-on-ward'; import WardPatientTimeSinceAdmission from '../../ward-patient-card/row-elements/ward-patient-time-since-admission'; import styles from '../../ward-patient-card/ward-patient-card.scss'; -import { WardPatientCardType } from '../../types'; +import { type WardPatientCardType } from '../../types'; +import WardPatientGender from '../../ward-patient-card/row-elements/ward-patient-gender.component'; -const DefaultWardPatientCardHeader : WardPatientCardType = (wardPatient) => { +const DefaultWardPatientCardHeader: WardPatientCardType = (wardPatient) => { const { patient, bed, inpatientAdmission } = wardPatient; const { encounterAssigningToCurrentInpatientLocation, firstAdmissionOrTransferEncounter } = inpatientAdmission ?? {}; @@ -18,6 +19,7 @@ const DefaultWardPatientCardHeader : WardPatientCardType = (wardPatient) => { {bed ? : null} + { /> ); -} +}; -export default DefaultWardPatientCardHeader; \ No newline at end of file +export default DefaultWardPatientCardHeader; diff --git a/packages/esm-ward-app/src/ward-view/default-ward/default-ward-pending-patients.component.tsx b/packages/esm-ward-app/src/ward-view/default-ward/default-ward-pending-patients.component.tsx index d3dc97029..5d0513f68 100644 --- a/packages/esm-ward-app/src/ward-view/default-ward/default-ward-pending-patients.component.tsx +++ b/packages/esm-ward-app/src/ward-view/default-ward/default-ward-pending-patients.component.tsx @@ -4,6 +4,7 @@ import { useTranslation } from 'react-i18next'; import { type WardViewContext, type InpatientRequest } from '../../types'; import AdmissionRequestCard from '../../ward-workspace/admission-request-card/admission-request-card.component'; import WardPatientSkeletonText from '../../ward-patient-card/row-elements/ward-patient-skeleton-text'; +import AdmissionRequestNoteRow from '../../ward-patient-card/card-rows/admission-request-note-row.component'; function DefaultWardPendingPatients() { const { wardPatientGroupDetails } = useAppContext('ward-view-context') ?? {}; @@ -21,16 +22,29 @@ function DefaultWardPendingPatients() { ) : ( <> - {inpatientRequests?.map((request: InpatientRequest, i) => ( - - ))} + {inpatientRequests?.map((request: InpatientRequest, i) => { + const wardPatient = { + patient: request.patient, + visit: request.visit, + bed: null, + inpatientRequest: request, + inpatientAdmission: null, + }; + + return ( + + + + ); + })} ); } diff --git a/packages/esm-ward-app/src/ward-view/materal-ward/maternal-ward-beds.component.tsx b/packages/esm-ward-app/src/ward-view/materal-ward/maternal-ward-beds.component.tsx index 018b9c62e..1a6d6843c 100644 --- a/packages/esm-ward-app/src/ward-view/materal-ward/maternal-ward-beds.component.tsx +++ b/packages/esm-ward-app/src/ward-view/materal-ward/maternal-ward-beds.component.tsx @@ -1,7 +1,7 @@ import { useAppContext } from '@openmrs/esm-framework'; import React from 'react'; import WardBed from '../../beds/ward-bed.component'; -import { MotherAndChild, type WardPatient, type WardViewContext } from '../../types'; +import { type MotherAndChild, type WardPatient, type WardViewContext } from '../../types'; import { bedLayoutToBed } from '../ward-view.resource'; import MaternalWardPatientCard from './maternal-ward-patient-card.component'; @@ -9,52 +9,52 @@ interface MaternalWardBedsProps { motherChildrenRelationshipsByPatient: Map; } -const MaternalWardBeds : React.FC = ({motherChildrenRelationshipsByPatient}) => { - const {wardPatientGroupDetails} = useAppContext('ward-view-context') ?? {}; +const MaternalWardBeds: React.FC = ({ motherChildrenRelationshipsByPatient }) => { + const { wardPatientGroupDetails } = useAppContext('ward-view-context') ?? {}; const { bedLayouts, wardAdmittedPatientsWithBed } = wardPatientGroupDetails ?? {}; const wardBeds = bedLayouts?.map((bedLayout) => { const { patients: patientsInCurrentBed } = bedLayout; const bed = bedLayoutToBed(bedLayout); - const wardPatients: WardPatient[] = patientsInCurrentBed.map((patient): WardPatient => { - const inpatientAdmission = wardAdmittedPatientsWithBed?.get(patient.uuid); - if (inpatientAdmission) { - const { patient, visit, currentInpatientRequest } = inpatientAdmission; - return { patient, visit, bed, inpatientAdmission, inpatientRequest: currentInpatientRequest || null }; - } else { - // for some reason this patient is in a bed but not in the list of admitted patients, - // so we need to use the patient data from the bed endpoint - return { - patient: patient, - visit: null, - bed, - inpatientAdmission: null, - inpatientRequest: null, - }; - } - }).filter((wardPatient) => { - // filter out any child patient whose mother is also assigned to the same bed - // (the child patient will instead have a sub-card rendered in the mother's patient card) - const patientUuid = wardPatient.patient.uuid; - for(const relationship of motherChildrenRelationshipsByPatient?.get(patientUuid) ?? []) { - if(relationship.child.uuid == patientUuid) { - if(patientsInCurrentBed.some((patient) => patient.uuid == relationship.mother.uuid)) { - return false; + const wardPatients: WardPatient[] = patientsInCurrentBed + .map((patient): WardPatient => { + const inpatientAdmission = wardAdmittedPatientsWithBed?.get(patient.uuid); + if (inpatientAdmission) { + const { patient, visit, currentInpatientRequest } = inpatientAdmission; + return { patient, visit, bed, inpatientAdmission, inpatientRequest: currentInpatientRequest || null }; + } else { + // for some reason this patient is in a bed but not in the list of admitted patients, + // so we need to use the patient data from the bed endpoint + return { + patient: patient, + visit: null, + bed, + inpatientAdmission: null, + inpatientRequest: null, + }; + } + }) + .filter((wardPatient) => { + // filter out any child patient whose mother is also assigned to the same bed + // (the child patient will instead have a sub-card rendered in the mother's patient card) + const patientUuid = wardPatient.patient.uuid; + for (const relationship of motherChildrenRelationshipsByPatient?.get(patientUuid) ?? []) { + if (relationship.child.uuid == patientUuid) { + if (patientsInCurrentBed.some((patient) => patient.uuid == relationship.mother.uuid)) { + return false; + } } } - } - return true; - }); - const patientCards = wardPatients.map(wardPatient => ( - + return true; + }); + const patientCards = wardPatients.map((wardPatient) => ( + )); return ; }); return <>{wardBeds}; -} +}; export default MaternalWardBeds; diff --git a/packages/esm-ward-app/src/ward-view/materal-ward/maternal-ward-patient-card-header.component.tsx b/packages/esm-ward-app/src/ward-view/materal-ward/maternal-ward-patient-card-header.component.tsx index 0e270528b..f55fb2961 100644 --- a/packages/esm-ward-app/src/ward-view/materal-ward/maternal-ward-patient-card-header.component.tsx +++ b/packages/esm-ward-app/src/ward-view/materal-ward/maternal-ward-patient-card-header.component.tsx @@ -1,6 +1,6 @@ import classNames from 'classnames'; import React from 'react'; -import { WardPatientCardType } from '../../types'; +import { type WardPatientCardType } from '../../types'; import WardPatientAge from '../../ward-patient-card/row-elements/ward-patient-age'; import WardPatientBedNumber from '../../ward-patient-card/row-elements/ward-patient-bed-number'; import WardPatientAddress from '../../ward-patient-card/row-elements/ward-patient-header-address'; @@ -10,7 +10,7 @@ import WardPatientObs from '../../ward-patient-card/row-elements/ward-patient-ob import WardPatientTimeSinceAdmission from '../../ward-patient-card/row-elements/ward-patient-time-since-admission'; import styles from '../../ward-patient-card/ward-patient-card.scss'; -const MaternalWardPatientCardHeader : WardPatientCardType = (wardPatient) => { +const MaternalWardPatientCardHeader: WardPatientCardType = (wardPatient) => { const { patient, bed, visit, inpatientAdmission } = wardPatient; const { firstAdmissionOrTransferEncounter } = inpatientAdmission ?? {}; @@ -20,11 +20,11 @@ const MaternalWardPatientCardHeader : WardPatientCardType = (wardPatient) => { - - + + ); -} +}; -export default MaternalWardPatientCardHeader; \ No newline at end of file +export default MaternalWardPatientCardHeader; diff --git a/packages/esm-ward-app/src/ward-view/materal-ward/maternal-ward-patient-card.component.tsx b/packages/esm-ward-app/src/ward-view/materal-ward/maternal-ward-patient-card.component.tsx index d042b8bbd..c5740f9a4 100644 --- a/packages/esm-ward-app/src/ward-view/materal-ward/maternal-ward-patient-card.component.tsx +++ b/packages/esm-ward-app/src/ward-view/materal-ward/maternal-ward-patient-card.component.tsx @@ -27,7 +27,6 @@ const MaternalWardPatientCard: WardPatientCardType = (wardPatient) => { - ); diff --git a/packages/esm-ward-app/src/ward-view/materal-ward/maternal-ward-pending-patients.component.tsx b/packages/esm-ward-app/src/ward-view/materal-ward/maternal-ward-pending-patients.component.tsx new file mode 100644 index 000000000..6005fe671 --- /dev/null +++ b/packages/esm-ward-app/src/ward-view/materal-ward/maternal-ward-pending-patients.component.tsx @@ -0,0 +1,48 @@ +import { ErrorState, useAppContext } from '@openmrs/esm-framework'; +import React from 'react'; +import { useTranslation } from 'react-i18next'; +import { type InpatientRequest, type WardViewContext } from '../../types'; +import AdmissionRequestNoteRow from '../../ward-patient-card/card-rows/admission-request-note-row.component'; +import CodedObsTagsRow from '../../ward-patient-card/card-rows/coded-obs-tags-row.component'; +import MotherChildRowExtension from '../../ward-patient-card/card-rows/mother-child-row.component'; +import WardPatientSkeletonText from '../../ward-patient-card/row-elements/ward-patient-skeleton-text'; +import AdmissionRequestCard from '../../ward-workspace/admission-request-card/admission-request-card.component'; + +function MaternalWardPendingPatients() { + const { wardPatientGroupDetails } = useAppContext('ward-view-context') ?? {}; + const { t } = useTranslation(); + const { inpatientRequestResponse } = wardPatientGroupDetails ?? {}; + const { + inpatientRequests, + isLoading: isLoadingInpatientRequests, + error: errorFetchingInpatientRequests, + } = inpatientRequestResponse ?? {}; + + return isLoadingInpatientRequests ? ( + + ) : errorFetchingInpatientRequests ? ( + + ) : ( + <> + {inpatientRequests?.map((request: InpatientRequest, i) => { + const wardPatient = { + patient: request.patient, + visit: request.visit, + bed: null, + inpatientRequest: request, + inpatientAdmission: null, + }; + + return ( + + + + + + ); + })} + + ); +} + +export default MaternalWardPendingPatients; diff --git a/packages/esm-ward-app/src/ward-view/materal-ward/maternal-ward-view.component.tsx b/packages/esm-ward-app/src/ward-view/materal-ward/maternal-ward-view.component.tsx index 2d87fe153..aaada99f8 100644 --- a/packages/esm-ward-app/src/ward-view/materal-ward/maternal-ward-view.component.tsx +++ b/packages/esm-ward-app/src/ward-view/materal-ward/maternal-ward-view.component.tsx @@ -6,9 +6,9 @@ import WardViewHeader from '../../ward-view-header/ward-view-header.component'; import Ward from '../ward.component'; import MaternalWardBeds from './maternal-ward-beds.component'; import MaternalWardPatientCardHeader from './maternal-ward-patient-card-header.component'; +import MaternalWardPendingPatients from './maternal-ward-pending-patients.component'; import MaternalWardUnassignedPatients from './maternal-ward-unassigned-patients.component'; import { useMotherChildrenRelationshipsByPatient } from './maternal-ward-view.resource'; -import DefaultWardPendingPatients from '../default-ward/default-ward-pending-patients.component'; const MaternalWardView = () => { const wardPatientGroupDetails = useWardPatientGrouping(); @@ -28,7 +28,7 @@ const MaternalWardView = () => { const wardBeds = ; const wardUnassignedPatients = ; - const wardPendingPatients = ; + const wardPendingPatients = ; return ( <> diff --git a/packages/esm-ward-app/src/ward-view/ward-view.resource.ts b/packages/esm-ward-app/src/ward-view/ward-view.resource.ts index bf6dee1af..a7311392a 100644 --- a/packages/esm-ward-app/src/ward-view/ward-view.resource.ts +++ b/packages/esm-ward-app/src/ward-view/ward-view.resource.ts @@ -1,4 +1,4 @@ -import { useConfig, type Patient } from '@openmrs/esm-framework'; +import { showNotification, useConfig, type Patient } from '@openmrs/esm-framework'; import type { TFunction } from 'i18next'; import { useMemo } from 'react'; import { @@ -20,6 +20,7 @@ import type { WardMetrics, WardPatientGroupDetails, } from '../types'; +import { useTranslation } from 'react-i18next'; // the server side has 2 slightly incompatible types for Bed export function bedLayoutToBed(bedLayout: BedLayout): Bed { @@ -167,7 +168,26 @@ export function useElementConfig(elementType: 'pendingItems', id: string): Pendi export function useElementConfig(elementType: 'admissionRequestNote', id: string): AdmissionRequestNoteElementConfig; export function useElementConfig(elementType, id: string): object { const config = useConfig(); - return config?.patientCardElements?.[elementType]?.find((elementConfig) => elementConfig.id == id); + const { t } = useTranslation(); + + try { + return config?.patientCardElements?.[elementType]?.find((elementConfig) => elementConfig?.id == id); + } catch (e) { + showNotification({ + title: t('errorConfiguringPatientCard', 'Error configuring patient card'), + kind: 'error', + critical: true, + description: t( + 'errorConfiguringPatientCardMessage', + 'Unable to find configuration for {{elementType}}, id: {{id}}', + { + elementType, + id, + }, + ), + }); + return null; + } } export function useWardConfig(locationUuid: string): WardDefinition { diff --git a/packages/esm-ward-app/src/ward-workspace/admission-request-card/admission-request-card-header.component.tsx b/packages/esm-ward-app/src/ward-workspace/admission-request-card/admission-request-card-header.component.tsx index 426380e8b..169f85035 100644 --- a/packages/esm-ward-app/src/ward-workspace/admission-request-card/admission-request-card-header.component.tsx +++ b/packages/esm-ward-app/src/ward-workspace/admission-request-card/admission-request-card-header.component.tsx @@ -1,18 +1,25 @@ -import { formatDatetime, getLocale, useAppContext } from '@openmrs/esm-framework'; +import { formatDatetime, getLocale } from '@openmrs/esm-framework'; import classNames from 'classnames'; import React from 'react'; -import { WardViewContext, type WardPatientCardType } from '../../types'; +import { type WardPatientCardType } from '../../types'; +import WardPatientAge from '../../ward-patient-card/row-elements/ward-patient-age'; +import WardPatientGender from '../../ward-patient-card/row-elements/ward-patient-gender.component'; +import WardPatientIdentifier from '../../ward-patient-card/row-elements/ward-patient-identifier'; +import WardPatientName from '../../ward-patient-card/row-elements/ward-patient-name'; import styles from './admission-request-card.scss'; const AdmissionRequestCardHeader: WardPatientCardType = (wardPatient) => { const { inpatientRequest } = wardPatient; const { dispositionEncounter } = inpatientRequest; - const {WardPatientHeader} = useAppContext('ward-view-context') ?? {}; + const { patient } = wardPatient; return (
- {WardPatientHeader && } + + + +
diff --git a/packages/esm-ward-app/src/ward-workspace/admission-request-card/admission-request-card.component.tsx b/packages/esm-ward-app/src/ward-workspace/admission-request-card/admission-request-card.component.tsx index 6e7f6af27..81ab83edb 100644 --- a/packages/esm-ward-app/src/ward-workspace/admission-request-card/admission-request-card.component.tsx +++ b/packages/esm-ward-app/src/ward-workspace/admission-request-card/admission-request-card.component.tsx @@ -1,13 +1,19 @@ -import React from 'react'; -import type { WardPatientCardType } from '../../types'; +import React, { type ReactNode } from 'react'; +import type { WardPatient, WardPatientCardType } from '../../types'; import AdmissionRequestCardActions from './admission-request-card-actions.component'; import AdmissionRequestCardHeader from './admission-request-card-header.component'; import styles from './admission-request-card.scss'; -const AdmissionRequestCard: WardPatientCardType = (wardPatient) => { +interface AdmissionRequestCardProps { + wardPatient: WardPatient; + children?: ReactNode; +} + +const AdmissionRequestCard: React.FC = ({ wardPatient, children }) => { return (
+ {children}
); diff --git a/packages/esm-ward-app/src/ward-workspace/admission-request-card/admission-request-card.scss b/packages/esm-ward-app/src/ward-workspace/admission-request-card/admission-request-card.scss index b7496b63c..08620f49c 100644 --- a/packages/esm-ward-app/src/ward-workspace/admission-request-card/admission-request-card.scss +++ b/packages/esm-ward-app/src/ward-workspace/admission-request-card/admission-request-card.scss @@ -8,6 +8,11 @@ height: fit-content; color: $color-gray-70; margin-bottom: layout.$spacing-05; + + > div { + margin: layout.$spacing-04; + width: unset; + } } .admissionRequestCardHeaderContainer { @@ -29,7 +34,7 @@ display: none; &:has(div:not(:empty)) { - display: block; + display: block; margin: layout.$spacing-03 0; background-color: white; } diff --git a/packages/esm-ward-app/translations/en.json b/packages/esm-ward-app/translations/en.json index cf2d57e0d..3297a2c6f 100644 --- a/packages/esm-ward-app/translations/en.json +++ b/packages/esm-ward-app/translations/en.json @@ -22,6 +22,8 @@ "emptyText": "Empty", "encounterDisplay": "{{encounterType}} {{encounterDate}}", "errorAssigningBedToPatient": "Error assigning bed to patient", + "errorConfiguringPatientCard": "Error configuring patient card", + "errorConfiguringPatientCardMessage": "Unable to find configuration for {{elementType}}, id: {{id}}", "errorCreatingEncounter": "Failed to admit patient", "errorCreatingTransferRequest": "Error creating transfer request", "errorDischargingPatient": "Error discharging patient",