@openmrs/esm-fast-data-entry-app 1.0.0-pre.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.editorconfig +12 -0
- package/.eslintignore +2 -0
- package/.eslintrc +4 -0
- package/.github/workflows/node.js.yml +79 -0
- package/.husky/pre-commit +6 -0
- package/.husky/pre-push +6 -0
- package/.prettierignore +14 -0
- package/LICENSE +401 -0
- package/README.md +12 -0
- package/__mocks__/react-i18next.js +56 -0
- package/babel.config.json +8 -0
- package/dist/24.js +3 -0
- package/dist/24.js.LICENSE.txt +16 -0
- package/dist/24.js.map +1 -0
- package/dist/294.js +3 -0
- package/dist/294.js.LICENSE.txt +14 -0
- package/dist/294.js.map +1 -0
- package/dist/296.js +2 -0
- package/dist/296.js.map +1 -0
- package/dist/299.js +2 -0
- package/dist/299.js.map +1 -0
- package/dist/382.js +3 -0
- package/dist/382.js.LICENSE.txt +8 -0
- package/dist/382.js.map +1 -0
- package/dist/415.js +2 -0
- package/dist/415.js.map +1 -0
- package/dist/574.js +1 -0
- package/dist/595.js +3 -0
- package/dist/595.js.LICENSE.txt +1 -0
- package/dist/595.js.map +1 -0
- package/dist/69.js +2 -0
- package/dist/69.js.map +1 -0
- package/dist/735.js +3 -0
- package/dist/735.js.LICENSE.txt +29 -0
- package/dist/735.js.map +1 -0
- package/dist/777.js +2 -0
- package/dist/777.js.map +1 -0
- package/dist/860.js +2 -0
- package/dist/860.js.map +1 -0
- package/dist/906.js +2 -0
- package/dist/906.js.map +1 -0
- package/dist/openmrs-esm-fast-data-entry-app.js +1 -0
- package/dist/openmrs-esm-fast-data-entry-app.js.buildmanifest.json +369 -0
- package/dist/openmrs-esm-fast-data-entry-app.js.map +1 -0
- package/dist/openmrs-esm-fast-data-entry-app.old +2 -0
- package/jest.config.json +18 -0
- package/package.json +106 -0
- package/src/Root.tsx +16 -0
- package/src/boxes/extensions/blue-box.tsx +15 -0
- package/src/boxes/extensions/box.scss +23 -0
- package/src/boxes/extensions/brand-box.tsx +15 -0
- package/src/boxes/extensions/red-box.tsx +15 -0
- package/src/boxes/slot/boxes.css +23 -0
- package/src/boxes/slot/boxes.tsx +19 -0
- package/src/config-schema.ts +46 -0
- package/src/constant.ts +7 -0
- package/src/declarations.d.tsx +2 -0
- package/src/forms/FormsRoot.tsx +32 -0
- package/src/forms/FormsTable.tsx +64 -0
- package/src/forms/mockData.ts +43 -0
- package/src/forms-app-menu-link.tsx +12 -0
- package/src/greeter/greeter.css +4 -0
- package/src/greeter/greeter.test.tsx +29 -0
- package/src/greeter/greeter.tsx +25 -0
- package/src/hello.css +3 -0
- package/src/hello.test.tsx +45 -0
- package/src/hello.tsx +30 -0
- package/src/index.ts +75 -0
- package/src/patient-getter/patient-getter.resource.ts +31 -0
- package/src/patient-getter/patient-getter.test.tsx +28 -0
- package/src/patient-getter/patient-getter.tsx +28 -0
- package/src/setup-tests.ts +1 -0
- package/translations/en.json +4 -0
- package/tsconfig.json +23 -0
- package/webpack.config.js +1 -0
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/* Extensions should supply the minimum amount of styling
|
|
2
|
+
* necessary. Here, the extensions set only their colors.
|
|
3
|
+
* Their sizes and other general features of their display
|
|
4
|
+
* is controlled by the slot. */
|
|
5
|
+
|
|
6
|
+
@import "~@openmrs/esm-styleguide/src/vars";
|
|
7
|
+
|
|
8
|
+
.blue {
|
|
9
|
+
background-color: darkblue;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
.red {
|
|
13
|
+
background-color: darkred;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/* Brand colors are special. They must be included using a
|
|
17
|
+
* SASS mix-in (shown here) or using a CSS variable like
|
|
18
|
+
* `var(--brand-01)`. */
|
|
19
|
+
.brand {
|
|
20
|
+
@include brand-01(background-color);
|
|
21
|
+
color: white;
|
|
22
|
+
padding: 8px;
|
|
23
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This component demonstrates the creation of an extension.
|
|
3
|
+
*
|
|
4
|
+
* Check out the Extension System docs:
|
|
5
|
+
* https://openmrs.github.io/openmrs-esm-core/#/main/extensions
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import React from "react";
|
|
9
|
+
import styles from "./box.scss";
|
|
10
|
+
|
|
11
|
+
const RedBox: React.FC = () => {
|
|
12
|
+
return <div className={styles.brand}></div>;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export default RedBox;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This component demonstrates the creation of an extension.
|
|
3
|
+
*
|
|
4
|
+
* Check out the Extension System docs:
|
|
5
|
+
* https://openmrs.github.io/openmrs-esm-core/#/main/extensions
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import React from "react";
|
|
9
|
+
import styles from "./box.scss";
|
|
10
|
+
|
|
11
|
+
const RedBox: React.FC = () => {
|
|
12
|
+
return <div className={styles.red}></div>;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export default RedBox;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/* General features of extension styling are controlled
|
|
2
|
+
* at the slot level. The boxes should know as little as
|
|
3
|
+
* possible about the context where they are used, so
|
|
4
|
+
* they do not set their own sizes.
|
|
5
|
+
*
|
|
6
|
+
* `> * > *` is used to target the outermost DOM node of
|
|
7
|
+
* the extension.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
.box > * > * {
|
|
11
|
+
height: 4em;
|
|
12
|
+
width: 4em;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
.boxes {
|
|
16
|
+
margin: 2em 0 0 1em;
|
|
17
|
+
display: flex;
|
|
18
|
+
gap: 1em;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
.container {
|
|
22
|
+
max-width: 40em;
|
|
23
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import styles from "./boxes.css";
|
|
3
|
+
import { Extension, ExtensionSlot } from "@openmrs/esm-framework";
|
|
4
|
+
|
|
5
|
+
export const Boxes: React.FC = () => {
|
|
6
|
+
return (
|
|
7
|
+
<div className={styles.container}>
|
|
8
|
+
Here are some colored boxes. Because they are attached as extensions
|
|
9
|
+
within a slot, an admin can change what boxes are shown using
|
|
10
|
+
configuration. These boxes happen to be defined in this module, but they
|
|
11
|
+
could attach to this slot even if they were in a different module.
|
|
12
|
+
<ExtensionSlot extensionSlotName="Boxes" className={styles.boxes}>
|
|
13
|
+
<div className={styles.box}>
|
|
14
|
+
<Extension />
|
|
15
|
+
</div>
|
|
16
|
+
</ExtensionSlot>
|
|
17
|
+
</div>
|
|
18
|
+
);
|
|
19
|
+
};
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { Type, validator } from "@openmrs/esm-framework";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* This is the config schema. It expects a configuration object which
|
|
5
|
+
* looks like this:
|
|
6
|
+
*
|
|
7
|
+
* ```json
|
|
8
|
+
* { "casualGreeting": true, "whoToGreet": ["Mom"] }
|
|
9
|
+
* ```
|
|
10
|
+
*
|
|
11
|
+
* In OpenMRS Microfrontends, all config parameters are optional. Thus,
|
|
12
|
+
* all elements must have a reasonable default. A good default is one
|
|
13
|
+
* that works well with the reference application.
|
|
14
|
+
*
|
|
15
|
+
* To understand the schema below, please read the configuration system
|
|
16
|
+
* documentation:
|
|
17
|
+
* https://openmrs.github.io/openmrs-esm-core/#/main/config
|
|
18
|
+
* Note especially the section "How do I make my module configurable?"
|
|
19
|
+
* https://openmrs.github.io/openmrs-esm-core/#/main/config?id=im-developing-an-esm-module-how-do-i-make-it-configurable
|
|
20
|
+
* and the Schema Reference
|
|
21
|
+
* https://openmrs.github.io/openmrs-esm-core/#/main/config?id=schema-reference
|
|
22
|
+
*/
|
|
23
|
+
export const configSchema = {
|
|
24
|
+
casualGreeting: {
|
|
25
|
+
_type: Type.Boolean,
|
|
26
|
+
_default: false,
|
|
27
|
+
_description: "Whether to use a casual greeting (or a formal one).",
|
|
28
|
+
},
|
|
29
|
+
whoToGreet: {
|
|
30
|
+
_type: Type.Array,
|
|
31
|
+
_default: ["World"],
|
|
32
|
+
_description:
|
|
33
|
+
"Who should be greeted. Names will be separated by a comma and space.",
|
|
34
|
+
_elements: {
|
|
35
|
+
_type: Type.String,
|
|
36
|
+
},
|
|
37
|
+
_validators: [
|
|
38
|
+
validator((v) => v.length > 0, "At least one person must be greeted."),
|
|
39
|
+
],
|
|
40
|
+
},
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
export type Config = {
|
|
44
|
+
casualGreeting: boolean;
|
|
45
|
+
whoToGreet: Array<String>;
|
|
46
|
+
};
|
package/src/constant.ts
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { Tab, Tabs } from "carbon-components-react";
|
|
2
|
+
import React from "react";
|
|
3
|
+
import FormsTable from "./FormsTable";
|
|
4
|
+
|
|
5
|
+
const FormsRoot = ({ match }) => {
|
|
6
|
+
const { tab } = match?.params;
|
|
7
|
+
return (
|
|
8
|
+
<div style={{ padding: "2rem" }}>
|
|
9
|
+
<h3 style={{ marginBottom: "1.5rem" }}>Forms</h3>
|
|
10
|
+
<Tabs type="container">
|
|
11
|
+
<Tab label="All Forms">
|
|
12
|
+
<FormsTable />
|
|
13
|
+
</Tab>
|
|
14
|
+
<Tab label="ICRC Forms">
|
|
15
|
+
<FormsTable />
|
|
16
|
+
</Tab>
|
|
17
|
+
<Tab label="Distress Scales">
|
|
18
|
+
<FormsTable />
|
|
19
|
+
</Tab>
|
|
20
|
+
<Tab label="Functioning Scales">
|
|
21
|
+
<FormsTable />
|
|
22
|
+
</Tab>
|
|
23
|
+
<Tab label="Coping Scales">
|
|
24
|
+
<FormsTable />
|
|
25
|
+
</Tab>
|
|
26
|
+
</Tabs>
|
|
27
|
+
</div>
|
|
28
|
+
);
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
export default FormsRoot;
|
|
32
|
+
export { FormsRoot };
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import {
|
|
2
|
+
DataTable,
|
|
3
|
+
Table,
|
|
4
|
+
TableBody,
|
|
5
|
+
TableCell,
|
|
6
|
+
TableContainer,
|
|
7
|
+
TableHead,
|
|
8
|
+
TableHeader,
|
|
9
|
+
TableRow,
|
|
10
|
+
TableToolbar,
|
|
11
|
+
TableToolbarContent,
|
|
12
|
+
TableToolbarSearch,
|
|
13
|
+
} from "carbon-components-react";
|
|
14
|
+
import React from "react";
|
|
15
|
+
import { tableData } from "./mockData";
|
|
16
|
+
|
|
17
|
+
const FormsTable = () => {
|
|
18
|
+
const { rows, headers } = tableData;
|
|
19
|
+
return (
|
|
20
|
+
<DataTable rows={rows} headers={headers} isSortable>
|
|
21
|
+
{({
|
|
22
|
+
rows,
|
|
23
|
+
headers,
|
|
24
|
+
getTableProps,
|
|
25
|
+
getHeaderProps,
|
|
26
|
+
getRowProps,
|
|
27
|
+
onInputChange,
|
|
28
|
+
}) => {
|
|
29
|
+
return (
|
|
30
|
+
<TableContainer>
|
|
31
|
+
<TableToolbar style={{ position: "relative" }}>
|
|
32
|
+
<TableToolbarContent>
|
|
33
|
+
<TableToolbarSearch onChange={onInputChange} />
|
|
34
|
+
</TableToolbarContent>
|
|
35
|
+
</TableToolbar>
|
|
36
|
+
<Table {...getTableProps()}>
|
|
37
|
+
<TableHead>
|
|
38
|
+
<TableRow>
|
|
39
|
+
{headers.map((header) => (
|
|
40
|
+
<TableHeader {...getHeaderProps({ header })}>
|
|
41
|
+
{header.header}
|
|
42
|
+
</TableHeader>
|
|
43
|
+
))}
|
|
44
|
+
</TableRow>
|
|
45
|
+
</TableHead>
|
|
46
|
+
<TableBody>
|
|
47
|
+
{rows.map((row) => (
|
|
48
|
+
<TableRow {...getRowProps({ row })}>
|
|
49
|
+
{row.cells.map((cell) => (
|
|
50
|
+
<TableCell key={cell.id}>{cell.value}</TableCell>
|
|
51
|
+
))}
|
|
52
|
+
</TableRow>
|
|
53
|
+
))}
|
|
54
|
+
</TableBody>
|
|
55
|
+
</Table>
|
|
56
|
+
</TableContainer>
|
|
57
|
+
);
|
|
58
|
+
}}
|
|
59
|
+
</DataTable>
|
|
60
|
+
);
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
export default FormsTable;
|
|
64
|
+
export { FormsTable };
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
const rows = [
|
|
2
|
+
{
|
|
3
|
+
id: "admission-form",
|
|
4
|
+
name: "Admission form",
|
|
5
|
+
region: "All regions",
|
|
6
|
+
actions: "Fill Form",
|
|
7
|
+
},
|
|
8
|
+
{
|
|
9
|
+
id: "dass-form",
|
|
10
|
+
name: "DASS 21",
|
|
11
|
+
region: "All regions",
|
|
12
|
+
actions: "Fill form",
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
id: "follow-up-form",
|
|
16
|
+
name: "Follow-up form",
|
|
17
|
+
region: "All regions",
|
|
18
|
+
actions: "Fill form",
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
id: "closure-form",
|
|
22
|
+
name: "Closure form",
|
|
23
|
+
region: "All regions",
|
|
24
|
+
actions: "Fill form",
|
|
25
|
+
},
|
|
26
|
+
];
|
|
27
|
+
|
|
28
|
+
const headers = [
|
|
29
|
+
{
|
|
30
|
+
key: "name",
|
|
31
|
+
header: "Form name",
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
key: "region",
|
|
35
|
+
header: "Region",
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
key: "actions",
|
|
39
|
+
header: "Actions",
|
|
40
|
+
},
|
|
41
|
+
];
|
|
42
|
+
|
|
43
|
+
export const tableData = { ...{ rows, headers } };
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { useTranslation } from "react-i18next";
|
|
3
|
+
import { ConfigurableLink } from "@openmrs/esm-framework";
|
|
4
|
+
|
|
5
|
+
export default function OfflineToolsAppMenuLink() {
|
|
6
|
+
const { t } = useTranslation();
|
|
7
|
+
return (
|
|
8
|
+
<ConfigurableLink to="${openmrsSpaBase}/forms">
|
|
9
|
+
{t("formsAppMenuLink", "Forms")}
|
|
10
|
+
</ConfigurableLink>
|
|
11
|
+
);
|
|
12
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { render, cleanup, screen, getByText } from "@testing-library/react";
|
|
3
|
+
import { useConfig } from "@openmrs/esm-framework";
|
|
4
|
+
import { Greeter } from "./greeter";
|
|
5
|
+
import { Config } from "../config-schema";
|
|
6
|
+
|
|
7
|
+
const mockUseConfig = useConfig as jest.Mock;
|
|
8
|
+
|
|
9
|
+
describe(`<Greeter />`, () => {
|
|
10
|
+
afterEach(cleanup);
|
|
11
|
+
it(`displays the expected default text`, () => {
|
|
12
|
+
const config: Config = { casualGreeting: false, whoToGreet: ["World"] };
|
|
13
|
+
mockUseConfig.mockReturnValue(config);
|
|
14
|
+
render(<Greeter />);
|
|
15
|
+
expect(screen.getByText(/world/i)).toHaveTextContent("hello World!");
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
it(`will casually greet my friends`, () => {
|
|
19
|
+
const config: Config = {
|
|
20
|
+
casualGreeting: true,
|
|
21
|
+
whoToGreet: ["Ariel", "Barak", "Callum"],
|
|
22
|
+
};
|
|
23
|
+
mockUseConfig.mockReturnValue(config);
|
|
24
|
+
render(<Greeter />);
|
|
25
|
+
expect(screen.getByText(/ariel/i)).toHaveTextContent(
|
|
26
|
+
"hey Ariel, Barak, Callum!"
|
|
27
|
+
);
|
|
28
|
+
});
|
|
29
|
+
});
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This component demonstrates usage of the config object. Its structure
|
|
3
|
+
* comes from `../config-schema.ts`. For more information about the
|
|
4
|
+
* configuration system, please see that file.
|
|
5
|
+
*/
|
|
6
|
+
import { useConfig } from "@openmrs/esm-framework";
|
|
7
|
+
import React from "react";
|
|
8
|
+
import { Trans } from "react-i18next";
|
|
9
|
+
import { Config } from "../config-schema";
|
|
10
|
+
import styles from "./greeter.css";
|
|
11
|
+
|
|
12
|
+
export const Greeter: React.FC = () => {
|
|
13
|
+
const config = useConfig() as Config;
|
|
14
|
+
|
|
15
|
+
return (
|
|
16
|
+
<div className={styles.greeting}>
|
|
17
|
+
{config.casualGreeting ? (
|
|
18
|
+
<Trans key="casualGreeting">hey</Trans>
|
|
19
|
+
) : (
|
|
20
|
+
<Trans key="formalGreeting">hello</Trans>
|
|
21
|
+
)}{" "}
|
|
22
|
+
{config.whoToGreet.join(", ")}!
|
|
23
|
+
</div>
|
|
24
|
+
);
|
|
25
|
+
};
|
package/src/hello.css
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This is the root test for this page. It simply checks that the page
|
|
3
|
+
* renders. If the components of your page are highly interdependent,
|
|
4
|
+
* (e.g., if the `Hello` component had state that communicated
|
|
5
|
+
* information between `Greeter` and `PatientGetter`) then you might
|
|
6
|
+
* want to do most of your testing here. If those components are
|
|
7
|
+
* instead quite independent (as is the case in this example), then
|
|
8
|
+
* it would make more sense to test those components independently.
|
|
9
|
+
*
|
|
10
|
+
* The key thing to remember, always, is: write tests that behave like
|
|
11
|
+
* users. They should *look* for elements by their visual
|
|
12
|
+
* characteristics, *interact* with them, and (mostly) *assert* based
|
|
13
|
+
* on things that would be visually apparent to a user.
|
|
14
|
+
*
|
|
15
|
+
* To learn more about how we do testing, see the following resources:
|
|
16
|
+
* https://kentcdodds.com/blog/how-to-know-what-to-test
|
|
17
|
+
* https://kentcdodds.com/blog/testing-implementation-details
|
|
18
|
+
* https://kentcdodds.com/blog/common-mistakes-with-react-testing-library
|
|
19
|
+
*
|
|
20
|
+
* Kent C. Dodds is the inventor of `@testing-library`:
|
|
21
|
+
* https://testing-library.com/docs/guiding-principles
|
|
22
|
+
*/
|
|
23
|
+
import React from "react";
|
|
24
|
+
import { render, cleanup } from "@testing-library/react";
|
|
25
|
+
import Hello from "./hello";
|
|
26
|
+
import { useConfig } from "@openmrs/esm-framework";
|
|
27
|
+
import { Config } from "./config-schema";
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* This is an idiomatic way of dealing with mocked files. Note that
|
|
31
|
+
* `useConfig` is already mocked; the Jest moduleNameMapper (see the
|
|
32
|
+
* Jest config) has mapped the `@openmrs/esm-framework` import to a
|
|
33
|
+
* mock file. This line just tells TypeScript that the object is, in
|
|
34
|
+
* fact, a mock, and so will have methods like `mockReturnValue`.
|
|
35
|
+
*/
|
|
36
|
+
const mockUseConfig = useConfig as jest.Mock;
|
|
37
|
+
|
|
38
|
+
describe(`<Hello />`, () => {
|
|
39
|
+
afterEach(cleanup);
|
|
40
|
+
it(`renders without dying`, () => {
|
|
41
|
+
const config: Config = { casualGreeting: false, whoToGreet: ["World"] };
|
|
42
|
+
mockUseConfig.mockReturnValue(config);
|
|
43
|
+
render(<Hello />);
|
|
44
|
+
});
|
|
45
|
+
});
|
package/src/hello.tsx
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* From here, the application is pretty typical React, but with lots of
|
|
3
|
+
* support from `@openmrs/esm-framework`. Check out `Greeter` to see
|
|
4
|
+
* usage of the configuration system, and check out `PatientGetter` to
|
|
5
|
+
* see data fetching using the OpenMRS FHIR API.
|
|
6
|
+
*
|
|
7
|
+
* Check out the Config docs:
|
|
8
|
+
* https://openmrs.github.io/openmrs-esm-core/#/main/config
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import React from "react";
|
|
12
|
+
import styles from "./hello.css";
|
|
13
|
+
import { Greeter } from "./greeter/greeter";
|
|
14
|
+
import { PatientGetter } from "./patient-getter/patient-getter";
|
|
15
|
+
import { Boxes } from "./boxes/slot/boxes";
|
|
16
|
+
|
|
17
|
+
const Hello: React.FC = () => {
|
|
18
|
+
return (
|
|
19
|
+
<div className={`omrs-main-content ${styles.container}`}>
|
|
20
|
+
{/* Greeter: demonstrates the configuration system */}
|
|
21
|
+
<Greeter />
|
|
22
|
+
{/* PatientGetter: demonstrates data fetching */}
|
|
23
|
+
<PatientGetter />
|
|
24
|
+
{/* Boxes: demonstrates the extension system */}
|
|
25
|
+
<Boxes />
|
|
26
|
+
</div>
|
|
27
|
+
);
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
export default Hello;
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This is the entrypoint file of the application. It communicates the
|
|
3
|
+
* important features of this microfrontend to the app shell. It
|
|
4
|
+
* connects the app shell to the React application(s) that make up this
|
|
5
|
+
* microfrontend.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { getAsyncLifecycle, defineConfigSchema } from "@openmrs/esm-framework";
|
|
9
|
+
import { configSchema } from "./config-schema";
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* This tells the app shell how to obtain translation files: that they
|
|
13
|
+
* are JSON files in the directory `../translations` (which you should
|
|
14
|
+
* see in the directory structure).
|
|
15
|
+
*/
|
|
16
|
+
const importTranslation = require.context(
|
|
17
|
+
"../translations",
|
|
18
|
+
false,
|
|
19
|
+
/.json$/,
|
|
20
|
+
"lazy"
|
|
21
|
+
);
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* This tells the app shell what versions of what OpenMRS backend modules
|
|
25
|
+
* are expected. Warnings will appear if suitable modules are not
|
|
26
|
+
* installed. The keys are the part of the module name after
|
|
27
|
+
* `openmrs-module-`; e.g., `openmrs-module-fhir2` becomes `fhir2`.
|
|
28
|
+
*/
|
|
29
|
+
const backendDependencies = {
|
|
30
|
+
fhir2: "^1.2.0",
|
|
31
|
+
"webservices.rest": "^2.2.0",
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* This function performs any setup that should happen at microfrontend
|
|
36
|
+
* load-time (such as defining the config schema) and then returns an
|
|
37
|
+
* object which describes how the React application(s) should be
|
|
38
|
+
* rendered.
|
|
39
|
+
*
|
|
40
|
+
* In this example, our return object contains a single page definition.
|
|
41
|
+
* It tells the app shell that the default export of `greeter.tsx`
|
|
42
|
+
* should be rendered when the route matches `hello`. The full route
|
|
43
|
+
* will be `openmrsSpaBase() + 'hello'`, which is usually
|
|
44
|
+
* `/openmrs/spa/hello`.
|
|
45
|
+
*/
|
|
46
|
+
function setupOpenMRS() {
|
|
47
|
+
const moduleName = "@openmrs/esm-fast-data-entry";
|
|
48
|
+
|
|
49
|
+
const options = {
|
|
50
|
+
featureName: "fast-data-entry",
|
|
51
|
+
moduleName,
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
defineConfigSchema(moduleName, configSchema);
|
|
55
|
+
|
|
56
|
+
return {
|
|
57
|
+
pages: [
|
|
58
|
+
{
|
|
59
|
+
load: getAsyncLifecycle(() => import("./Root"), options),
|
|
60
|
+
route: "forms",
|
|
61
|
+
},
|
|
62
|
+
],
|
|
63
|
+
extensions: [
|
|
64
|
+
{
|
|
65
|
+
name: "forms-app-link",
|
|
66
|
+
slot: "app-menu-slot",
|
|
67
|
+
load: getAsyncLifecycle(() => import("./forms-app-menu-link"), options),
|
|
68
|
+
online: true,
|
|
69
|
+
offline: true,
|
|
70
|
+
},
|
|
71
|
+
],
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export { backendDependencies, importTranslation, setupOpenMRS };
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { openmrsFetch, fhir } from "@openmrs/esm-framework";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* This is a somewhat silly resource function. It searches for a patient
|
|
5
|
+
* using the REST API, and then immediately gets the data using the FHIR
|
|
6
|
+
* API for the first patient found. OpenMRS API endpoints are generally
|
|
7
|
+
* hit using `openmrsFetch`. For FHIR endpoints we use the FHIR API
|
|
8
|
+
* object.
|
|
9
|
+
*
|
|
10
|
+
* See the `fhir` object API docs: https://github.com/openmrs/openmrs-esm-core/blob/master/packages/framework/esm-api/docs/API.md#fhir
|
|
11
|
+
* See the docs for the underlying fhir.js Client object: https://github.com/FHIR/fhir.js#api
|
|
12
|
+
* See the OpenMRS FHIR Module docs: https://wiki.openmrs.org/display/projects/OpenMRS+FHIR+Module
|
|
13
|
+
* See the OpenMRS REST API docs: https://rest.openmrs.org/#openmrs-rest-api
|
|
14
|
+
*
|
|
15
|
+
* @param query A patient name or ID
|
|
16
|
+
* @returns The first matching patient
|
|
17
|
+
*/
|
|
18
|
+
export async function getPatient(query) {
|
|
19
|
+
const searchResult = await openmrsFetch(
|
|
20
|
+
`/ws/rest/v1/patient?q=${query}&limit=1`,
|
|
21
|
+
{
|
|
22
|
+
method: "GET",
|
|
23
|
+
}
|
|
24
|
+
);
|
|
25
|
+
return (
|
|
26
|
+
await fhir.read<fhir.Patient>({
|
|
27
|
+
type: "Patient",
|
|
28
|
+
patient: searchResult.data.results[0].uuid,
|
|
29
|
+
})
|
|
30
|
+
).data;
|
|
31
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { render, screen } from "@testing-library/react";
|
|
3
|
+
import userEvent from "@testing-library/user-event";
|
|
4
|
+
import { PatientGetter } from "./patient-getter";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* This is an idiomatic mock of a backend resource. We generally mock
|
|
8
|
+
* resource fetching functions like `getPatient`, rather than mocking
|
|
9
|
+
* `fetch` or anything lower-level.
|
|
10
|
+
*/
|
|
11
|
+
jest.mock("./patient-getter.resource.ts", () => ({
|
|
12
|
+
getPatient: jest.fn(() =>
|
|
13
|
+
Promise.resolve({
|
|
14
|
+
name: [{ id: "abc123", given: "Joeboy", family: "Testguy" }],
|
|
15
|
+
gender: "male",
|
|
16
|
+
birthDate: "1997-05-21",
|
|
17
|
+
})
|
|
18
|
+
),
|
|
19
|
+
}));
|
|
20
|
+
|
|
21
|
+
describe(`<PatientGetter />`, () => {
|
|
22
|
+
it("gets a patient when the button is clicked", async () => {
|
|
23
|
+
render(<PatientGetter />);
|
|
24
|
+
userEvent.click(screen.getByRole("button"));
|
|
25
|
+
const resultText = await screen.findByText(/joeboy/i);
|
|
26
|
+
expect(resultText).toHaveTextContent("Joeboy Testguy / male / 1997-05-21");
|
|
27
|
+
});
|
|
28
|
+
});
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Components that make queries delegate the query-making logic to a
|
|
3
|
+
* `.resource.ts` function. This component simply calls `getPatient`
|
|
4
|
+
* and sets a state variable using the result.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import React, { useState } from "react";
|
|
8
|
+
import { Trans } from "react-i18next";
|
|
9
|
+
import Button from "carbon-components-react/es/components/Button";
|
|
10
|
+
import { Tile } from "carbon-components-react/es/components/Tile";
|
|
11
|
+
import { getPatient } from "./patient-getter.resource";
|
|
12
|
+
|
|
13
|
+
export function PatientGetter() {
|
|
14
|
+
const [patient, setPatient] = useState<fhir.Patient>();
|
|
15
|
+
const patientName = "test";
|
|
16
|
+
return (
|
|
17
|
+
<div>
|
|
18
|
+
<Button onClick={() => getPatient(patientName).then(setPatient)}>
|
|
19
|
+
<Trans key="getPatient">Get a patient named</Trans> 'test'
|
|
20
|
+
</Button>
|
|
21
|
+
<Tile>
|
|
22
|
+
{patient
|
|
23
|
+
? `${patient.name[0].given} ${patient.name[0].family} / ${patient.gender} / ${patient.birthDate}`
|
|
24
|
+
: null}
|
|
25
|
+
</Tile>
|
|
26
|
+
</div>
|
|
27
|
+
);
|
|
28
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import "@testing-library/jest-dom/extend-expect";
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"esModuleInterop": true,
|
|
4
|
+
"module": "esnext",
|
|
5
|
+
"allowSyntheticDefaultImports": true,
|
|
6
|
+
"jsx": "react",
|
|
7
|
+
"skipLibCheck": true,
|
|
8
|
+
"moduleResolution": "node",
|
|
9
|
+
"lib": [
|
|
10
|
+
"dom",
|
|
11
|
+
"es5",
|
|
12
|
+
"scripthost",
|
|
13
|
+
"es2015",
|
|
14
|
+
"es2015.promise",
|
|
15
|
+
"es2016.array.include",
|
|
16
|
+
"es2018",
|
|
17
|
+
"es2020"
|
|
18
|
+
],
|
|
19
|
+
"resolveJsonModule": true,
|
|
20
|
+
"noEmit": true,
|
|
21
|
+
"target": "esnext"
|
|
22
|
+
}
|
|
23
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
module.exports = require("openmrs/default-webpack-config");
|