@pih/esm-referrals-queue-app 1.0.5-pre.7

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.
Files changed (46) hide show
  1. package/.babelrc +10 -0
  2. package/.editorconfig +11 -0
  3. package/.eslintignore +1 -0
  4. package/.eslintrc +5 -0
  5. package/.gitattributes +1 -0
  6. package/.github/workflows/node.js.yml +80 -0
  7. package/.husky/pre-commit +6 -0
  8. package/.prettierignore +4 -0
  9. package/.tx/config +10 -0
  10. package/.vscode/launch.json +35 -0
  11. package/LICENSE +401 -0
  12. package/README.md +43 -0
  13. package/__mocks__/openmrs-esm-framework.mock.tsx +64 -0
  14. package/design-mockup.png +0 -0
  15. package/dist/264.pih-esm-referrals-queue-app.js +2 -0
  16. package/dist/264.pih-esm-referrals-queue-app.js.map +1 -0
  17. package/dist/319.pih-esm-referrals-queue-app.js +1 -0
  18. package/dist/574.pih-esm-referrals-queue-app.js +1 -0
  19. package/dist/757.pih-esm-referrals-queue-app.js +1 -0
  20. package/dist/874.pih-esm-referrals-queue-app.js +3 -0
  21. package/dist/874.pih-esm-referrals-queue-app.js.LICENSE.txt +27 -0
  22. package/dist/874.pih-esm-referrals-queue-app.js.map +1 -0
  23. package/dist/pih-esm-referrals-queue-app.js +2 -0
  24. package/dist/pih-esm-referrals-queue-app.js.buildmanifest.json +142 -0
  25. package/dist/pih-esm-referrals-queue-app.js.map +1 -0
  26. package/jest.config.js +12 -0
  27. package/package.json +87 -0
  28. package/src/declarations.d.tsx +20 -0
  29. package/src/index.ts +78 -0
  30. package/src/referrals-queue/react-dates-overrides.css +39 -0
  31. package/src/referrals-queue/referrals-queue.component.tsx +185 -0
  32. package/src/referrals-queue/referrals-queue.css +63 -0
  33. package/src/referrals-queue/referrals-queue.resource.ts +12 -0
  34. package/src/referrals-queue/referrals-queue.test.tsx +198 -0
  35. package/src/root.component.test.tsx +9 -0
  36. package/src/root.component.tsx +14 -0
  37. package/src/root.css +3 -0
  38. package/src/setup-tests.js +13 -0
  39. package/src/table/table.component.tsx +95 -0
  40. package/src/table/table.css +16 -0
  41. package/src/util.ts +20 -0
  42. package/translations/en.json +13 -0
  43. package/translations/es.json +13 -0
  44. package/translations/fr.json +13 -0
  45. package/tsconfig.json +23 -0
  46. package/webpack.config.js +1 -0
@@ -0,0 +1,63 @@
1
+ .container {
2
+ margin-right: auto;
3
+ margin-left: auto;
4
+ max-width: 1500px;
5
+ }
6
+
7
+ /* Hack inputs smaller than usual MFE */
8
+ .container input {
9
+ max-height: 2.5rem;
10
+ }
11
+ .container select {
12
+ max-height: 2.5rem;
13
+ }
14
+ /* End inputs hack */
15
+
16
+ .tableContainer {
17
+ height: 500px;
18
+ overflow-x: auto;
19
+ margin: 0 1rem;
20
+ border: 2px solid lightgrey;
21
+ border-radius: 0.25rem;
22
+ }
23
+
24
+ .controlsContainer {
25
+ display: flex;
26
+ flex-wrap: wrap;
27
+ }
28
+
29
+ .inputContainer {
30
+ display: flex;
31
+ width: 50%;
32
+ min-width: 300px;
33
+ text-align: left;
34
+ padding: 0 1rem;
35
+ }
36
+
37
+ .inputContainer label {
38
+ display: inline-block;
39
+ margin: 0 0.5rem 0.5rem 0;
40
+ }
41
+
42
+ .dateInputContainer {
43
+ margin: 2rem 2rem 1rem 0;
44
+ }
45
+
46
+ .dropdown {
47
+ width: 100%;
48
+ height: 3.5rem;
49
+ background: transparent;
50
+ border: 1px solid var(--omrs-color-ink-lowest-contrast);
51
+ background-clip: padding-box;
52
+ border-radius: 2px;
53
+ margin: 0;
54
+ }
55
+ .dropdown:disabled {
56
+ opacity: 0.8;
57
+ background: lightgrey;
58
+ cursor: not-allowed;
59
+ }
60
+ .dropdown:focus {
61
+ border: 2px solid var(--omrs-color-interaction);
62
+ outline: 0;
63
+ }
@@ -0,0 +1,12 @@
1
+ import { openmrsObservableFetch } from "@openmrs/esm-framework";
2
+ import { map } from "rxjs/operators";
3
+
4
+ export function getReferrals({
5
+ fromDate,
6
+ toDate,
7
+ locale,
8
+ }): Observable<Object[]> {
9
+ return openmrsObservableFetch(
10
+ `/ws/rest/v1/reportingrest/reportdata/cd7dfde7-764a-4da6-81c2-d5887ed1df51?startDate=${fromDate}&endDate=${toDate}&locale=${locale}`
11
+ ).pipe(map(({ data }) => data["dataSets"][0]["rows"]));
12
+ }
@@ -0,0 +1,198 @@
1
+ import React from "react";
2
+ import { of } from "rxjs";
3
+ import {
4
+ render,
5
+ fireEvent,
6
+ screen,
7
+ within,
8
+ waitFor,
9
+ } from "@testing-library/react";
10
+ import MockDate from "mockdate";
11
+ import ReferralsQueue from "./referrals-queue.component";
12
+ import { getReferrals } from "./referrals-queue.resource";
13
+
14
+ jest.mock("./referrals-queue.resource");
15
+ const mockedGetReferrals = getReferrals as jest.Mock;
16
+
17
+ const referrals = [
18
+ {
19
+ patient_uuid: "ptaaaaaa-2869-41a4-86af-811fa2ee65b8",
20
+ zl_emr_id: "PTID1",
21
+ patient_name: "Patient Commcare",
22
+ referral_date: "2020-03-25T00:00:00.000-0400",
23
+ details: null,
24
+ visit_uuid: "visitaaa-091a-46fd-a442-2a9778d91a52",
25
+ encounter_id: 12,
26
+ referral_type: "Test Referral Type",
27
+ encounter_uuid: "encounter-0fe1-4318-95a4-641d31745e09",
28
+ fulfillment_status: null,
29
+ },
30
+ {
31
+ patient_uuid: "ptbbbbbb-de0e-489f-b4c7-fe4579e07aa9",
32
+ zl_emr_id: "PTID2",
33
+ patient_name: "Davina Mother",
34
+ referral_date: "2020-03-23T00:00:00.000-0400",
35
+ details: "Suspected depression",
36
+ visit_uuid: "visitbbb-64ca-4d53-9024-574a9214f76e",
37
+ encounter_id: 13,
38
+ referral_type: "Mental Health",
39
+ encounter_uuid: "encounter-d5cb-4f7e-a1aa-bd6741a96381",
40
+ fulfillment_status: "Pending status",
41
+ },
42
+ {
43
+ person_uuid: "ptcccccc-de0e-489f-b4c7-fe4579e07aa9",
44
+ zl_emr_id: "PTID3",
45
+ patient_name: "Patient Prenatal",
46
+ referral_date: "2020-03-28T00:00:00.000-0400",
47
+ details: "Urgent",
48
+ visit_uuid: "visitccc-64ca-4d53-9024-574a9214f76e",
49
+ encounter_id: 14,
50
+ referral_type: "Family Member",
51
+ encounter_uuid: "encounter-67bc-435d-9929-670a98cefe4e",
52
+ fulfillment_status: "Test Complete",
53
+ },
54
+ ];
55
+
56
+ (window as any).openmrsBase = "/openmrs";
57
+ // hack around js-dom to make navigation testable
58
+ delete window.location;
59
+ //@ts-ignore
60
+ window.location = { href: "/referrals-queue" };
61
+
62
+ describe("referrals queue", () => {
63
+ const todayString = "2020-10-31";
64
+ beforeEach(() => {
65
+ MockDate.set(todayString + "T10:00:00.000-0400");
66
+ mockedGetReferrals.mockReset();
67
+ mockedGetReferrals.mockReturnValue(of(referrals));
68
+ render(<ReferralsQueue />);
69
+ });
70
+
71
+ afterEach(() => {
72
+ MockDate.reset();
73
+ });
74
+
75
+ it("renders without failing", () => {
76
+ // in beforeEach
77
+ });
78
+
79
+ it("renders the expected fields", () => {
80
+ for (let referral of referrals) {
81
+ const row = screen.getByText(referral.zl_emr_id).closest("tr");
82
+ within(row).getByText(referral.patient_name);
83
+ within(row).getByText(referral.referral_type, { selector: "td" });
84
+ if (referral.details) {
85
+ within(row).getByText(referral.details);
86
+ }
87
+ if (referral.fulfillment_status) {
88
+ within(row).getByText(referral.fulfillment_status);
89
+ }
90
+ }
91
+ screen.getByText("25 Mar");
92
+ screen.getByText("23 Mar");
93
+ screen.getByText("28 Mar");
94
+ });
95
+
96
+ it("navigates to the links", async () => {
97
+ const table = screen.getByRole("table");
98
+ const pt0DashLink = within(table).getByText(referrals[0].zl_emr_id);
99
+ fireEvent.click(pt0DashLink);
100
+ await waitFor(() => {
101
+ expect(window.location.href).toBe(
102
+ "/openmrs/pt-dash/" + referrals[0].patient_uuid
103
+ );
104
+ });
105
+ const pt1VisitLink = within(table).getByText("23 Mar");
106
+ fireEvent.click(pt1VisitLink);
107
+ await waitFor(() => {
108
+ expect(window.location.href).toBe(
109
+ `/openmrs/visit/${referrals[1].patient_uuid}/${referrals[1].visit_uuid}`
110
+ );
111
+ });
112
+ const pendingStatusLink = within(table).getByText("Pending status");
113
+ fireEvent.click(pendingStatusLink);
114
+ await waitFor(() => {
115
+ expect(window.location.href).toBe(
116
+ `/openmrs/home-visit-form/${referrals[1].patient_uuid}/${referrals[1].visit_uuid}/${referrals[1].encounter_uuid}`
117
+ );
118
+ });
119
+ });
120
+
121
+ it("doesn't make non-pending statuses into links", () => {
122
+ const table = screen.getByRole("table");
123
+ expect(within(table).getByText("Pending status").tagName).toBe("A"); // sanity check
124
+ expect(within(table).getByText("Test Complete").tagName).not.toBe("A"); // the actual test
125
+ });
126
+
127
+ it("filters by referral type", () => {
128
+ const dropdown = screen.getByLabelText("Referral Type", {
129
+ selector: "select",
130
+ });
131
+ fireEvent.change(dropdown, { target: { value: "Mental Health" } });
132
+ expect(screen.queryByText("PTID1")).toBeNull();
133
+ expect(screen.queryByText("PTID2")).not.toBeNull();
134
+ expect(screen.queryByText("PTID3")).toBeNull();
135
+ });
136
+
137
+ it("infers list of referral types from data", () => {
138
+ const dropdown = screen.getByLabelText("Referral Type", {
139
+ selector: "select",
140
+ });
141
+ fireEvent.change(dropdown, { target: { value: "Test Referral Type" } });
142
+ expect(screen.queryByDisplayValue("Test Referral Type")).not.toBeNull();
143
+ });
144
+
145
+ it("filters by statuses, with dropdown inferred from data", () => {
146
+ const dropdown = screen.getByLabelText("Status", { selector: "select" });
147
+ fireEvent.change(dropdown, { target: { value: "Test Complete" } });
148
+ expect(screen.queryByText("PTID1")).toBeNull();
149
+ expect(screen.queryByText("PTID2")).toBeNull();
150
+ expect(screen.queryByText("PTID3")).not.toBeNull();
151
+ });
152
+
153
+ it("calls getReferrals with the correct date arguments", () => {
154
+ const monthAgoString = "2020-09-30";
155
+ expect(mockedGetReferrals).lastCalledWith({
156
+ fromDate: monthAgoString,
157
+ toDate: todayString,
158
+ locale: "en",
159
+ });
160
+ const fromDateInput = screen.getByLabelText("From");
161
+ fireEvent.change(fromDateInput, { target: { value: "2020-01-01" } });
162
+ expect(mockedGetReferrals).lastCalledWith({
163
+ fromDate: "2020-01-01",
164
+ toDate: todayString,
165
+ locale: "en",
166
+ });
167
+ const toDateInput = screen.getByLabelText("To");
168
+ fireEvent.change(toDateInput, { target: { value: "2020-03-01" } });
169
+ expect(mockedGetReferrals).lastCalledWith({
170
+ fromDate: "2020-01-01",
171
+ toDate: "2020-03-01",
172
+ locale: "en",
173
+ });
174
+ });
175
+
176
+ it("filters the list of results by patient name query", () => {
177
+ const queryBox = screen.getByLabelText("Filter by patient");
178
+ fireEvent.change(queryBox, { target: { value: "Mother" } });
179
+ expect(screen.queryByText("PTID2")).not.toBeNull();
180
+ expect(screen.queryByText("PTID1")).toBeNull();
181
+ });
182
+
183
+ it("filters the list of results by patient name partial token matches", () => {
184
+ const queryBox = screen.getByLabelText("Filter by patient");
185
+ fireEvent.change(queryBox, { target: { value: "dav mo" } });
186
+ expect(screen.queryByText("PTID2")).not.toBeNull();
187
+ expect(screen.queryByText("PTID1")).toBeNull();
188
+ });
189
+
190
+ it("filters the list of results by patient id", () => {
191
+ const queryBox = screen.getByLabelText("Filter by patient");
192
+ fireEvent.change(queryBox, { target: { value: "ptid1" } });
193
+ expect(screen.queryByText("PTID1")).not.toBeNull();
194
+ expect(screen.queryByText("PTID2")).toBeNull();
195
+ });
196
+
197
+ test.todo("makes the API call with the locale from the user session");
198
+ });
@@ -0,0 +1,9 @@
1
+ import React from "react";
2
+ import { render } from "@testing-library/react";
3
+ import Root from "./root.component";
4
+
5
+ describe(`<Root />`, () => {
6
+ it(`renders without dying`, () => {
7
+ render(<Root />);
8
+ });
9
+ });
@@ -0,0 +1,14 @@
1
+ import React from "react";
2
+ import { BrowserRouter, Route } from "react-router-dom";
3
+ import ReferralsQueue from "./referrals-queue/referrals-queue.component";
4
+ import styles from "./root.css";
5
+
6
+ export default function Root(props) {
7
+ return (
8
+ <div className={`omrs-main-content ${styles.overflowAuto}`}>
9
+ <BrowserRouter basename={window["getOpenmrsSpaBase"]()}>
10
+ <Route path="/referrals-queue" component={ReferralsQueue} />
11
+ </BrowserRouter>
12
+ </div>
13
+ );
14
+ }
package/src/root.css ADDED
@@ -0,0 +1,3 @@
1
+ .overflowAuto {
2
+ overflow: auto;
3
+ }
@@ -0,0 +1,13 @@
1
+ import i18n from "i18next";
2
+ import { initReactI18next } from "react-i18next";
3
+
4
+ i18n.use(initReactI18next).init({
5
+ lng: "en",
6
+ resources: {
7
+ en: {},
8
+ fr: {},
9
+ ht: {},
10
+ },
11
+ });
12
+
13
+ window.getOpenmrsSpaBase = () => "openmrs/spa";
@@ -0,0 +1,95 @@
1
+ import React from "react";
2
+ import {
3
+ ConfigurableLink,
4
+ interpolateString,
5
+ useConfig,
6
+ } from "@openmrs/esm-framework";
7
+ import { Trans } from "react-i18next";
8
+ import styles from "./table.css";
9
+ import { formatDate } from "../util";
10
+
11
+ export interface TableProps {
12
+ referrals: Referral[];
13
+ }
14
+
15
+ export default function Table(props: TableProps) {
16
+ const config = useConfig();
17
+
18
+ return (
19
+ <table className={styles.table}>
20
+ <thead>
21
+ <tr className={`omrs-bold ${styles.tr}`}>
22
+ <td>
23
+ <Trans i18nKey="emr-id">EMR ID</Trans>
24
+ </td>
25
+ <td>
26
+ <Trans i18nKey="name">Name</Trans>
27
+ </td>
28
+ <td>
29
+ <Trans i18nKey="referral-date">Referral Date</Trans>
30
+ </td>
31
+ <td>
32
+ <Trans i18nKey="referral-type">Referral Type</Trans>
33
+ </td>
34
+ <td>
35
+ <Trans i18nKey="details">Details</Trans>
36
+ </td>
37
+ <td>
38
+ <Trans i18nKey="status">Status</Trans>
39
+ </td>
40
+ </tr>
41
+ </thead>
42
+ <tbody>
43
+ {props.referrals &&
44
+ props.referrals.map((referral, index) => (
45
+ <React.Fragment key={index}>
46
+ <tr className={styles.tr}>
47
+ <td>
48
+ <ConfigurableLink
49
+ to={interpolateString(config.links.patientDash, {
50
+ patientUuid:
51
+ referral.patient_uuid || referral.person_uuid,
52
+ })}
53
+ >
54
+ {referral.zl_emr_id}
55
+ </ConfigurableLink>
56
+ </td>
57
+ <td>{referral.patient_name}</td>
58
+ <td>
59
+ <ConfigurableLink
60
+ to={interpolateString(config.links.visitPage, {
61
+ patientUuid:
62
+ referral.patient_uuid || referral.person_uuid,
63
+ visitUuid: referral.visit_uuid,
64
+ })}
65
+ >
66
+ {formatDate(referral.referral_date)}
67
+ </ConfigurableLink>
68
+ </td>
69
+ <td>{referral.referral_type}</td>
70
+ <td>{referral.details}</td>
71
+ <td>
72
+ {config.pendingStatuses.includes(
73
+ referral.fulfillment_status
74
+ ) ? (
75
+ <ConfigurableLink
76
+ to={interpolateString(config.links.homeVisitForm, {
77
+ patientUuid:
78
+ referral.patient_uuid || referral.person_uuid,
79
+ visitUuid: referral.visit_uuid,
80
+ encounterUuid: referral.encounter_uuid,
81
+ })}
82
+ >
83
+ {referral.fulfillment_status}
84
+ </ConfigurableLink>
85
+ ) : (
86
+ referral.fulfillment_status
87
+ )}
88
+ </td>
89
+ </tr>
90
+ </React.Fragment>
91
+ ))}
92
+ </tbody>
93
+ </table>
94
+ );
95
+ }
@@ -0,0 +1,16 @@
1
+ .table {
2
+ width: 100%;
3
+ }
4
+
5
+ .tr {
6
+ border-bottom: 1px solid lightgrey;
7
+ line-height: 2rem;
8
+ }
9
+
10
+ .tr:nth-child(even) {
11
+ background-color: var(--omrs-color-bg-medium-contrast);
12
+ }
13
+
14
+ .table td {
15
+ padding: 0.25rem 0.5rem;
16
+ }
package/src/util.ts ADDED
@@ -0,0 +1,20 @@
1
+ import moment from "moment";
2
+
3
+ // We use UTC for the present TZ because the dates in the data
4
+ // are all midnight Eastern Time, which lines up with UTC
5
+ // but not with, e.g. Pacific Time.
6
+ export function formatDate(strDate: string) {
7
+ const date = moment.utc(strDate);
8
+ const today = moment.utc(new Date());
9
+ if (
10
+ date.date() === today.date() &&
11
+ date.month() === today.month() &&
12
+ date.year() === today.year()
13
+ ) {
14
+ return moment().calendar().split(" ")[0]; // locale-specific 'Today'
15
+ } else if (date.year() === today.year()) {
16
+ return date.format("DD MMM");
17
+ } else {
18
+ return date.format("DD MMM YYYY");
19
+ }
20
+ }
@@ -0,0 +1,13 @@
1
+ {
2
+ "any": "Any",
3
+ "details": "Details",
4
+ "emr-id": "EMR ID",
5
+ "filter-by-patient": "Filter by patient",
6
+ "from": "From",
7
+ "name": "Name",
8
+ "referral-date": "Referral Date",
9
+ "referral-type": "Referral Type",
10
+ "referrals-queue": "Referrals Queue",
11
+ "status": "Status",
12
+ "to": "To"
13
+ }
@@ -0,0 +1,13 @@
1
+ {
2
+ "any": "Any",
3
+ "details": "Detalles",
4
+ "emr-id": "EMR ID",
5
+ "filter-by-patient": "Filtrar por paciente",
6
+ "from": "Desde",
7
+ "name": "Nombre",
8
+ "referral-date": "Fecha de referencia",
9
+ "referral-type": "Tipo de referencia",
10
+ "referrals-queue": "Cola de referencias",
11
+ "status": "Estado",
12
+ "to": "Hasta"
13
+ }
@@ -0,0 +1,13 @@
1
+ {
2
+ "any": "Tout",
3
+ "details": "Détails",
4
+ "emr-id": "EMR ID",
5
+ "filter-by-patient": "Filtrer avec patient",
6
+ "from": "de",
7
+ "name": "Nom",
8
+ "referral-date": "Date de Référé",
9
+ "referral-type": "Type Référé",
10
+ "referrals-queue": "List de Référé",
11
+ "status": "État",
12
+ "to": "à"
13
+ }
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");