From f4522e873f359802ff43b76530bc47602b037288 Mon Sep 17 00:00:00 2001 From: Usama Idriss Kakumba Date: Thu, 18 Jul 2024 16:37:28 +0300 Subject: [PATCH] feat create patient notes workspace --- packages/esm-ward-app/src/index.ts | 13 ++++- packages/esm-ward-app/src/routes.json | 57 ++++++++++++------- packages/esm-ward-app/src/store.ts | 18 ++++++ .../ward-patient-card-row.resources.tsx | 5 +- .../ward-patient-card/ward-patient-card.scss | 7 +++ .../ward-patient-card/ward-patient-card.tsx | 19 +++++-- .../ward-patient.workspace.tsx | 24 ++++---- .../patient-banner.component.tsx | 42 ++++++++++++++ .../ward-workspace/patient-banner/style.scss | 23 ++++++++ .../notes-action-button.extension.tsx | 17 ++++++ .../ward-patient-notes/notes.style.scss | 6 ++ .../ward-patient-notes/notes.workspace.tsx | 39 +++++++++++++ 12 files changed, 231 insertions(+), 39 deletions(-) create mode 100644 packages/esm-ward-app/src/store.ts create mode 100644 packages/esm-ward-app/src/ward-workspace/patient-banner/patient-banner.component.tsx create mode 100644 packages/esm-ward-app/src/ward-workspace/patient-banner/style.scss create mode 100644 packages/esm-ward-app/src/ward-workspace/ward-patient-notes/notes-action-button.extension.tsx create mode 100644 packages/esm-ward-app/src/ward-workspace/ward-patient-notes/notes.style.scss create mode 100644 packages/esm-ward-app/src/ward-workspace/ward-patient-notes/notes.workspace.tsx diff --git a/packages/esm-ward-app/src/index.ts b/packages/esm-ward-app/src/index.ts index 932466d99e..f1d046f2fd 100644 --- a/packages/esm-ward-app/src/index.ts +++ b/packages/esm-ward-app/src/index.ts @@ -9,6 +9,7 @@ import { configSchema } from './config-schema'; import rootComponent from './root.component'; import { moduleName } from './constant'; import WardPatientActionButton from './ward-patient-workspace/ward-patient-action-button.extension'; +import WardPatientNotesActionButton from './ward-workspace/ward-patient-notes/notes-action-button.extension'; export const importTranslation = require.context('../translations', false, /.json$/, 'lazy'); @@ -24,10 +25,20 @@ export const admissionRequestWorkspace = getAsyncLifecycle( options, ); -export const wardPatientWorkspace = getAsyncLifecycle(() => import('./ward-patient-workspace/ward-patient.workspace'), options); +export const wardPatientWorkspace = getAsyncLifecycle( + () => import('./ward-patient-workspace/ward-patient.workspace'), + options, +); + +export const wardPatientNotesWorkspace = getAsyncLifecycle( + () => import('./ward-workspace/ward-patient-notes/notes.workspace'), + options, +); export const wardPatientActionButtonExtension = getSyncLifecycle(WardPatientActionButton, options); +export const wardPatientNotesActionButtonExtension = getSyncLifecycle(WardPatientNotesActionButton, options); + export function startupApp() { registerBreadcrumbs([]); defineConfigSchema(moduleName, configSchema); diff --git a/packages/esm-ward-app/src/routes.json b/packages/esm-ward-app/src/routes.json index 2e9976cc88..73dd233e93 100644 --- a/packages/esm-ward-app/src/routes.json +++ b/packages/esm-ward-app/src/routes.json @@ -4,15 +4,15 @@ "webservices.rest": "^2.2.0", "emrapi": "^2.0.0 || 2.0.0-SNAPSHOT" }, - "optionalBackendDependencies":{ - "bedmanagement":{ + "optionalBackendDependencies": { + "bedmanagement": { "version": "^6.0.0 || 6.0.0-SNAPSHOT", "feature": { "flagName": "bedmanagement-module", - "label":"Ward App Patient Service", + "label": "Ward App Patient Service", "description": "This module, if installed, provides services for managing patients admitted to the ward." } - } + } }, "pages": [ { @@ -20,25 +20,42 @@ "route": "ward" } ], - "extensions": [{ - "component": "wardPatientActionButtonExtension", - "name": "ward-patient-action-button", - "slot": "action-menu-ward-patient-items-slot" - }], + "extensions": [ + { + "component": "wardPatientActionButtonExtension", + "name": "ward-patient-action-button", + "slot": "action-menu-ward-patient-items-slot" + }, + { + "component": "wardPatientNotesActionButtonExtension", + "name": "ward-in-patient-notes-action-button", + "slot": "action-menu-ward-patient-items-slot" + } + ], "workspaces": [ { - "name":"admission-requests-workspace", + "name": "admission-requests-workspace", "component": "admissionRequestWorkspace", - "title":"admissionRequests", - "type":"admission-requests" + "title": "admissionRequests", + "type": "admission-requests" + }, + { + "name": "ward-patient-notes-workspace", + "component": "wardPatientNotesWorkspace", + "type": "ward-patient-notes", + "title": "In-patient notes", + "sidebarFamily": "ward-patient", + "width": "wider", + "hasOwnSidebar": true }, { - "name": "ward-patient-workspace", - "component": "wardPatientWorkspace", - "type": "ward", - "title": "Ward Patient", - "width": "extra-wide", - "hasOwnSidebar": true, - "sidebarFamily": "ward-patient" - }] + "name": "ward-patient-workspace", + "component": "wardPatientWorkspace", + "type": "ward", + "title": "Ward Patient", + "width": "extra-wide", + "hasOwnSidebar": true, + "sidebarFamily": "ward-patient" + } + ] } diff --git a/packages/esm-ward-app/src/store.ts b/packages/esm-ward-app/src/store.ts new file mode 100644 index 0000000000..52929fe880 --- /dev/null +++ b/packages/esm-ward-app/src/store.ts @@ -0,0 +1,18 @@ +import { createGlobalStore, getGlobalStore } from '@openmrs/esm-framework'; +import { type WardPatientCardProps } from './types'; + +type ActiveBedSelection = WardPatientCardProps; + +interface WardStoreState { + activeBedSelection: ActiveBedSelection | null; +} + +const initialState: WardStoreState = { + activeBedSelection: null, +}; + +export const wardStore = createGlobalStore('ward', initialState); + +export function getWardStore() { + return getGlobalStore('ward', initialState); +} diff --git a/packages/esm-ward-app/src/ward-patient-card/ward-patient-card-row.resources.tsx b/packages/esm-ward-app/src/ward-patient-card/ward-patient-card-row.resources.tsx index 1697f54662..280698a226 100644 --- a/packages/esm-ward-app/src/ward-patient-card/ward-patient-card-row.resources.tsx +++ b/packages/esm-ward-app/src/ward-patient-card/ward-patient-card-row.resources.tsx @@ -1,5 +1,5 @@ import { useConfig } from '@openmrs/esm-framework'; -import { useMemo } from 'react'; +import React, { useMemo } from 'react'; import { builtInPatientCardElements, defaultPatientCardElementConfig, @@ -11,7 +11,6 @@ import WardPatientAge from './row-elements/ward-patient-age'; import WardPatientBedNumber from './row-elements/ward-patient-bed-number'; import wardPatientAddress from './row-elements/ward-patient-header-address'; import WardPatientName from './row-elements/ward-patient-name'; -import React from 'react'; import styles from './ward-patient-card.scss'; import wardPatientObs from './row-elements/ward-patient-obs'; import wardPatientCodedObsTags from './row-elements/ward-patient-coded-obs-tags'; @@ -68,7 +67,7 @@ export function usePatientCardRows(location: string) { return patientCardRows; } -function getPatientCardElementFromDefinition( +export function getPatientCardElementFromDefinition( patientCardElementDef: PatientCardElementDefinition, ): WardPatientCardElement { const { elementType, config } = patientCardElementDef; 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 ef931affbd..a12b46698d 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 @@ -44,6 +44,10 @@ } } +.activeWardPatientCardButton { + outline: 2px solid $interactive-01; +} + .wardPatientCardRow { width: 100%; padding: spacing.$spacing-04; @@ -62,6 +66,7 @@ .wardPatientName { @include type.type-style('heading-compact-02'); color: $text-02; + &::before { content: '' !important; } @@ -78,6 +83,7 @@ display: flex; justify-content: center; align-items: center; + &.empty { background-color: $color-blue-10; color: $color-blue-60-2; @@ -106,6 +112,7 @@ > div:not(div:first-of-type) { display: flex; align-items: center; + &::before { content: '·'; padding: 0 spacing.$spacing-02; diff --git a/packages/esm-ward-app/src/ward-patient-card/ward-patient-card.tsx b/packages/esm-ward-app/src/ward-patient-card/ward-patient-card.tsx index 2ca4e2cb5b..c83d91f564 100644 --- a/packages/esm-ward-app/src/ward-patient-card/ward-patient-card.tsx +++ b/packages/esm-ward-app/src/ward-patient-card/ward-patient-card.tsx @@ -4,9 +4,14 @@ import { type WardPatientCardProps } from '../types'; import { usePatientCardRows } from './ward-patient-card-row.resources'; import styles from './ward-patient-card.scss'; import { getPatientName, launchWorkspace } from '@openmrs/esm-framework'; -import { type WardPatientWorkspaceProps } from '../ward-patient-workspace/ward-patient.workspace'; +import { getWardStore } from '../store'; +import classNames from 'classnames'; + +const spaRoot = window['getOpenmrsSpaBase']; const WardPatientCard: React.FC = (props) => { + const wardStore = getWardStore(); + const activeBedSelection = wardStore.getState().activeBedSelection; const { locationUuid } = useParams(); const patientCardRows = usePatientCardRows(locationUuid); @@ -16,10 +21,14 @@ const WardPatientCard: React.FC = (props) => { ))} diff --git a/packages/esm-ward-app/src/ward-patient-workspace/ward-patient.workspace.tsx b/packages/esm-ward-app/src/ward-patient-workspace/ward-patient.workspace.tsx index ff9e6ff2f0..5a14824511 100644 --- a/packages/esm-ward-app/src/ward-patient-workspace/ward-patient.workspace.tsx +++ b/packages/esm-ward-app/src/ward-patient-workspace/ward-patient.workspace.tsx @@ -1,26 +1,30 @@ import React, { useEffect, useMemo } from 'react'; import { useTranslation } from 'react-i18next'; -import { InlineNotification } from '@carbon/react'; -import { InlineLoading } from '@carbon/react'; +import { InlineLoading, InlineNotification } from '@carbon/react'; import { + age, + attach, type DefaultWorkspaceProps, ExtensionSlot, - attach, getPatientName, usePatient, - age, } from '@openmrs/esm-framework'; import styles from './ward-patient.style.scss'; +import { getWardStore } from '../store'; attach('ward-patient-workspace-header-slot', 'patient-vitals-info'); -export interface WardPatientWorkspaceProps extends DefaultWorkspaceProps { - patientUuid: string; -} - -export default function WardPatientWorkspace({ patientUuid, setTitle }: WardPatientWorkspaceProps) { +export default function WardPatientWorkspace({ setTitle, setOnCloseCallback }: DefaultWorkspaceProps) { const { t } = useTranslation(); - const { patient, isLoading, error } = usePatient(patientUuid); + const wardStore = getWardStore(); + const { + patient: { uuid }, + } = wardStore.getState().activeBedSelection; + const { patient, isLoading, error } = usePatient(uuid); + + setOnCloseCallback(() => { + wardStore.setState({ activeBedSelection: null }); + }); useEffect(() => { if (isLoading) { diff --git a/packages/esm-ward-app/src/ward-workspace/patient-banner/patient-banner.component.tsx b/packages/esm-ward-app/src/ward-workspace/patient-banner/patient-banner.component.tsx new file mode 100644 index 0000000000..71582eff36 --- /dev/null +++ b/packages/esm-ward-app/src/ward-workspace/patient-banner/patient-banner.component.tsx @@ -0,0 +1,42 @@ +import React, { useMemo } from 'react'; +import { useConfig } from '@openmrs/esm-framework'; +import { defaultPatientCardElementConfig, type WardConfigObject } from '../../config-schema'; +import { useParams } from 'react-router-dom'; +import { getPatientCardElementFromDefinition } from '../../ward-patient-card/ward-patient-card-row.resources'; +import type { PatientCardElementType, WardPatientCardProps } from '../../types'; +import styles from './style.scss'; + +const WardPatientWorkspaceBanner = ({ bed, patient, visit }: WardPatientCardProps) => { + const { locationUuid } = useParams(); + const { wardPatientCards } = useConfig(); + const { cardDefinitions } = wardPatientCards; + + // extract configured elements for the patient card header to use for the banner section + const bannerElements = useMemo(() => { + const cardDefinition = cardDefinitions.find((cardDef) => { + const appliedTo = cardDef.appliedTo; + + return appliedTo == null || appliedTo.some((criteria) => criteria.location == locationUuid); + }); + + const headerRow = cardDefinition.rows.find((cardDef) => cardDef.rowType === 'header'); + + return headerRow.elements.map((elementType: PatientCardElementType) => + getPatientCardElementFromDefinition({ + id: elementType, + elementType, + config: defaultPatientCardElementConfig, + }), + ); + }, [cardDefinitions]); + + return ( +
+ {bannerElements.map((BannerElement) => ( + + ))} +
+ ); +}; + +export default WardPatientWorkspaceBanner; diff --git a/packages/esm-ward-app/src/ward-workspace/patient-banner/style.scss b/packages/esm-ward-app/src/ward-workspace/patient-banner/style.scss new file mode 100644 index 0000000000..1a2239fc19 --- /dev/null +++ b/packages/esm-ward-app/src/ward-workspace/patient-banner/style.scss @@ -0,0 +1,23 @@ +@use '@carbon/styles/scss/spacing'; +@import '~@openmrs/esm-styleguide/src/vars'; + +.patientBanner { + @extend .dotSeparatedChildren; + display: flex; + flex-wrap: wrap; + width: 100%; + padding: spacing.$spacing-04; + background: $ui-01; +} + +.dotSeparatedChildren { + > div:not(div:first-of-type) { + display: flex; + align-items: center; + + &::before { + content: '·'; + padding: 0 spacing.$spacing-02; + } + } +} diff --git a/packages/esm-ward-app/src/ward-workspace/ward-patient-notes/notes-action-button.extension.tsx b/packages/esm-ward-app/src/ward-workspace/ward-patient-notes/notes-action-button.extension.tsx new file mode 100644 index 0000000000..bba31cdf9b --- /dev/null +++ b/packages/esm-ward-app/src/ward-workspace/ward-patient-notes/notes-action-button.extension.tsx @@ -0,0 +1,17 @@ +import React from 'react'; +import { useTranslation } from 'react-i18next'; +import { ActionMenuButton, launchWorkspace, StickyNoteAddIcon } from '@openmrs/esm-framework'; + +export default function WardPatientNotesActionButton({ workspaceProps }: { workspaceProps: any }) { + const { t } = useTranslation(); + + return ( + } + label={t('PatientNote', 'Patient Note')} + iconDescription={t('PatientNote', 'Patient Note')} + handler={() => launchWorkspace('ward-patient-notes-workspace')} + type={'ward-patient-notes'} + /> + ); +} diff --git a/packages/esm-ward-app/src/ward-workspace/ward-patient-notes/notes.style.scss b/packages/esm-ward-app/src/ward-workspace/ward-patient-notes/notes.style.scss new file mode 100644 index 0000000000..8cd90a1364 --- /dev/null +++ b/packages/esm-ward-app/src/ward-workspace/ward-patient-notes/notes.style.scss @@ -0,0 +1,6 @@ +@import '~@openmrs/esm-styleguide/src/vars'; + +.workspaceContainer { + width: calc(100% - 2rem); + min-height: var(--desktop-workspace-window-height); +} diff --git a/packages/esm-ward-app/src/ward-workspace/ward-patient-notes/notes.workspace.tsx b/packages/esm-ward-app/src/ward-workspace/ward-patient-notes/notes.workspace.tsx new file mode 100644 index 0000000000..542c1eec87 --- /dev/null +++ b/packages/esm-ward-app/src/ward-workspace/ward-patient-notes/notes.workspace.tsx @@ -0,0 +1,39 @@ +import React, { useMemo } from 'react'; +import styles from './notes.style.scss'; +import { type DefaultWorkspaceProps, ExtensionSlot } from '@openmrs/esm-framework'; +import { getWardStore } from '../../store'; +import WardPatientWorkspaceBanner from '../patient-banner/patient-banner.component'; + +const WardPatientNotesWorkspace = ({ + promptBeforeClosing, + closeWorkspace, + closeWorkspaceWithSavedChanges, + setOnCloseCallback, +}: DefaultWorkspaceProps) => { + const wardStore = getWardStore(); + const { patient, visit, bed } = wardStore.getState().activeBedSelection; + + setOnCloseCallback(() => { + wardStore.setState({ activeBedSelection: null }); + }); + + const notesFormExtensionState = useMemo( + () => ({ + patient, + patientUuid: patient.uuid, + promptBeforeClosing, + closeWorkspace, + closeWorkspaceWithSavedChanges, + }), + [patient, closeWorkspace, closeWorkspaceWithSavedChanges, promptBeforeClosing], + ); + + return ( +
+ + +
+ ); +}; + +export default WardPatientNotesWorkspace;