-
Notifications
You must be signed in to change notification settings - Fork 0
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
(feat) UHM-8240 Create O2 Visit Summary workspace for Ward App #1
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
This ESM is not really an app (for now). It is a place to define extensions to be slotted into other apps that are not PIH specific (e.g. the ward app). |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
/** At present, this entire mock is boilerplate. */ | ||
|
||
const React = require('react'); | ||
const reactI18next = require('react-i18next'); | ||
|
||
const hasChildren = (node) => node && (node.children || (node.props && node.props.children)); | ||
|
||
const getChildren = (node) => (node && node.children ? node.children : node.props && node.props.children); | ||
|
||
const renderNodes = (reactNodes) => { | ||
if (typeof reactNodes === 'string') { | ||
return reactNodes; | ||
} | ||
|
||
return Object.keys(reactNodes).map((key, i) => { | ||
const child = reactNodes[key]; | ||
const isElement = React.isValidElement(child); | ||
|
||
if (typeof child === 'string') { | ||
return child; | ||
} | ||
if (hasChildren(child)) { | ||
const inner = renderNodes(getChildren(child)); | ||
return React.cloneElement(child, { ...child.props, key: i }, inner); | ||
} | ||
if (typeof child === 'object' && !isElement) { | ||
return Object.keys(child).reduce((str, childKey) => `${str}${child[childKey]}`, ''); | ||
} | ||
|
||
return child; | ||
}); | ||
}; | ||
|
||
const useMock = [(k) => k, {}]; | ||
useMock.t = (k, o) => (o && o.defaultValue) || (typeof o === 'string' ? o : k); | ||
useMock.i18n = {}; | ||
|
||
module.exports = { | ||
// this mock makes sure any components using the translate HoC receive the t function as a prop | ||
Trans: ({ children }) => renderNodes(children), | ||
Translation: ({ children }) => children((k) => k, { i18n: {} }), | ||
useTranslation: () => useMock, | ||
|
||
// mock if needed | ||
I18nextProvider: reactI18next.I18nextProvider, | ||
initReactI18next: reactI18next.initReactI18next, | ||
setDefaults: reactI18next.setDefaults, | ||
getDefaults: reactI18next.getDefaults, | ||
setI18n: reactI18next.setI18n, | ||
getI18n: reactI18next.getI18n, | ||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
const rootConfig = require('../../jest.config.js'); | ||
|
||
module.exports = rootConfig; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
{ | ||
"name": "@pih/esm-commons-app", | ||
"version": "1.0.0", | ||
"description": "An app to define extensions to be slotted into other apps that are not PIH specific.", | ||
"browser": "dist/pih-esm-commons-app.js", | ||
"main": "src/index.ts", | ||
"source": true, | ||
"license": "MPL-2.0", | ||
"homepage": "https://github.com/PIH/openmrs-esm-pihemr#readme", | ||
"scripts": { | ||
"start": "openmrs develop", | ||
"serve": "webpack serve --mode=development", | ||
"debug": "npm run serve", | ||
"build": "webpack --mode production", | ||
"analyze": "webpack --mode=production --env.analyze=true", | ||
"lint": "cross-env eslint src --ext ts,tsx", | ||
"test": "cross-env TZ=UTC jest --config jest.config.js --verbose false --passWithNoTests --color", | ||
"test:watch": "cross-env TZ=UTC jest --watch --config jest.config.js --color", | ||
"coverage": "yarn test --coverage", | ||
"typescript": "tsc", | ||
"extract-translations": "i18next 'src/**/*.component.tsx'" | ||
}, | ||
"browserslist": [ | ||
"extends browserslist-config-openmrs" | ||
], | ||
"keywords": [ | ||
"openmrs" | ||
], | ||
"publishConfig": { | ||
"access": "public" | ||
}, | ||
"repository": { | ||
"type": "git", | ||
"url": "git+https://github.com/PIH/openmrs-esm-pihemr.git" | ||
}, | ||
"bugs": { | ||
"url": "https://github.com/PIH/openmrs-esm-pihemr/issues" | ||
}, | ||
"dependencies": { | ||
"@babel/runtime": "^7.24.0", | ||
"@carbon/react": "~1.37.0", | ||
"moment": "^2.30.1", | ||
"react-dates": "^21.8.0", | ||
"webpack": "^5.74.0" | ||
}, | ||
"peerDependencies": { | ||
"@openmrs/esm-framework": "5.x", | ||
"i18next": "^19.0.0", | ||
"react": "18.x", | ||
"react-i18next": "11.x", | ||
"react-router-dom": "^6.x", | ||
"swr": "2.x" | ||
}, | ||
"devDependencies": { | ||
"webpack": "^5.74.0" | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
/** | ||
* This is the entrypoint file of the application. It communicates the | ||
* important features of this microfrontend to the app shell. It | ||
* connects the app shell to the React application(s) that make up this | ||
* microfrontend. | ||
*/ | ||
|
||
import { getAsyncLifecycle } from '@openmrs/esm-framework'; | ||
|
||
const moduleName = '@pih/esm-commons-app'; | ||
const options = { | ||
featureName: 'commons', | ||
moduleName, | ||
}; | ||
|
||
export const importTranslation = require.context('../translations', false, /.json$/, 'lazy'); | ||
|
||
// export const root = getAsyncLifecycle(() => import('./root.component'), options); | ||
|
||
export const o2VisitSummaryWorkspaceSideRailIcon = getAsyncLifecycle( | ||
() => import('./ward-app/o2-visit-summary-action-button.extension'), | ||
options, | ||
); | ||
|
||
export const o2VisitSummaryWorkspace = getAsyncLifecycle( | ||
() => import('./ward-app/o2-visit-summary-workspace.component'), | ||
options, | ||
); | ||
|
||
export function startupApp() {} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
{ | ||
"$schema": "https://json.openmrs.org/routes.schema.json", | ||
"pages": [ | ||
], | ||
"extensions": [ | ||
{ | ||
"name": "o2-visit-summary-workspace-siderail-button", | ||
"component": "o2VisitSummaryWorkspaceSideRailIcon", | ||
"slot": "action-menu-ward-patient-items-slot" | ||
} | ||
], | ||
"workspaces": [ | ||
{ | ||
"name": "o2-visit-summary-workspace", | ||
"component": "o2VisitSummaryWorkspace", | ||
"title": "visitSummary", | ||
"type": "o2-visit-summary", | ||
"sidebarFamily": "ward-patient", | ||
"hasOwnSidebar": true, | ||
"width": "extra-wide" | ||
} | ||
] | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
import { ActionMenuButton, launchWorkspace, UserAvatarIcon } from '@openmrs/esm-framework'; | ||
import React from 'react'; | ||
import { useTranslation } from 'react-i18next'; | ||
import type { WardPatientWorkspaceProps } from './types'; | ||
|
||
export default function WardPatientActionButton() { | ||
const { t } = useTranslation(); | ||
|
||
return ( | ||
<ActionMenuButton | ||
getIcon={(props) => <UserAvatarIcon {...props} />} | ||
label={t('visitSummary', 'Visit summary')} | ||
iconDescription={t('patientVisitSummary', 'Patient visit summary')} | ||
handler={() => launchWorkspace<WardPatientWorkspaceProps>('o2-visit-summary-workspace')} | ||
type={'o2-visit-summary'} | ||
/> | ||
); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
import React, { useCallback, useRef } from 'react'; | ||
import { type WardPatientWorkspaceProps } from './types'; | ||
import styles from './o2-visit-summary-workspace.scss'; | ||
|
||
const O2VisitSummaryWorkspace: React.FC<WardPatientWorkspaceProps> = ({ wardPatient }) => { | ||
const { patient, visit } = wardPatient ?? {}; | ||
const iframeRef = useRef<HTMLIFrameElement>(); | ||
|
||
// hide the headers breadcrumbs and visit actions from | ||
const onLoad = useCallback(() => { | ||
const dashboard = iframeRef.current.contentDocument; | ||
const elementsToHide = ['header', '.patient-header', '#breadcrumbs', '.visit-actions', '#choose-another-visit']; | ||
|
||
const styleTag = dashboard.createElement('style'); | ||
styleTag.innerHTML = elementsToHide.map((e) => `${e} {display: none;}`).join('\n'); | ||
dashboard.head.appendChild(styleTag); | ||
}, []); | ||
|
||
if (patient && visit) { | ||
const src = `/openmrs/pihcore/visit/visit.page?patient=${patient.uuid}&visit=${visit.uuid}`; | ||
return ( | ||
<div className={styles.iframeWrapper}> | ||
<iframe ref={iframeRef} src={src} onLoad={onLoad} className={styles.o2Iframe} /> | ||
</div> | ||
); | ||
} else { | ||
return; | ||
} | ||
}; | ||
|
||
export default O2VisitSummaryWorkspace; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
.iframeWrapper { | ||
height: 100%; | ||
width: 100%; | ||
} | ||
|
||
.o2Iframe { | ||
height: 100%; | ||
width: 100%; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
import type { DefaultWorkspaceProps, Patient, Visit } from '@openmrs/esm-framework'; | ||
|
||
// a stripped down version of the same type defined in esm-ward-app | ||
export type WardPatient = { | ||
patient: Patient; | ||
visit: Visit; | ||
}; | ||
|
||
export interface WardPatientWorkspaceProps extends DefaultWorkspaceProps { | ||
wardPatient: WardPatient; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
{ | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not sure why, but I had to manually add these translations in, even though there is an There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Would be good to see if we can get an answer to why that is. |
||
"patientVisitSummary": "Patient visit summary", | ||
"visitSummary": "Visit summary" | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
{ | ||
"patientVisitSummary": "Patient visit summary", | ||
"visitSummary": "Visit summary" | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
{ | ||
"patientVisitSummary": "Patient visit summary", | ||
"visitSummary": "Visit summary" | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
{ | ||
"extends": "../../tsconfig.json", | ||
"include": ["src/**/*"], | ||
"exclude": ["src/**/*.test.tsx"] | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
module.exports = require('openmrs/default-webpack-config'); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,3 @@ | ||
@use '@carbon/react'; | ||
|
||
.container { | ||
margin-right: auto; | ||
margin-left: auto; | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,28 +1,23 @@ | ||
{ | ||
"compilerOptions": { | ||
"esModuleInterop": true, | ||
"module": "esnext", | ||
"allowSyntheticDefaultImports": true, | ||
"esModuleInterop": true, | ||
"jsx": "react", | ||
"skipLibCheck": true, | ||
"moduleResolution": "node", | ||
"lib": [ | ||
"dom", | ||
"es5", | ||
"scripthost", | ||
"es2015", | ||
"es2015.promise", | ||
"es2016.array.include", | ||
"es2018", | ||
"es2020" | ||
"es2022" | ||
], | ||
"module": "esnext", | ||
"moduleResolution": "node", | ||
"resolveJsonModule": true, | ||
"noEmit": true, | ||
"target": "esnext", | ||
"paths": { | ||
"@openmrs/*": ["./node_modules/@openmrs/*"], | ||
"__mocks__": ["./__mocks__"], | ||
"tools": ["./tools"], | ||
} | ||
}, | ||
"resolveJsonModule": true, | ||
"skipLibCheck": true, | ||
"target": "esnext", | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Minor, but, annoyingly, in Mirebalais the web app is called "mirebalais", not "openmrs". I think we have a variable with the webapp name? Otherwise, we can cross this bridge when we come to it (we've wanted to rename the Mirebalais web app for years).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I can't find a variable for it. I'll leave it as is for now.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm sure there must be a variable for this.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Found
openmrsBase
. I just pushed a change to make it use that.