@openmrs/esm-cohort-builder-app 3.0.0

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 (103) hide show
  1. package/LICENSE +401 -0
  2. package/README.md +16 -0
  3. package/dist/130.js +2 -0
  4. package/dist/130.js.LICENSE.txt +9 -0
  5. package/dist/139.js +1 -0
  6. package/dist/247.js +1 -0
  7. package/dist/294.js +2 -0
  8. package/dist/294.js.LICENSE.txt +9 -0
  9. package/dist/434.js +2 -0
  10. package/dist/434.js.LICENSE.txt +12 -0
  11. package/dist/480.js +2 -0
  12. package/dist/480.js.LICENSE.txt +12 -0
  13. package/dist/484.js +1 -0
  14. package/dist/574.js +1 -0
  15. package/dist/595.js +2 -0
  16. package/dist/595.js.LICENSE.txt +3 -0
  17. package/dist/690.js +1 -0
  18. package/dist/873.js +1 -0
  19. package/dist/935.js +2 -0
  20. package/dist/935.js.LICENSE.txt +19 -0
  21. package/dist/964.js +2 -0
  22. package/dist/964.js.LICENSE.txt +14 -0
  23. package/dist/main.js +1 -0
  24. package/dist/openmrs-esm-cohort-builder-app.js +1 -0
  25. package/dist/openmrs-esm-cohort-builder-app.js.buildmanifest.json +426 -0
  26. package/dist/openmrs-esm-cohort-builder-app.old +1 -0
  27. package/package.json +99 -0
  28. package/src/cohort-builder-app-menu-link.tsx +14 -0
  29. package/src/cohort-builder.resources.ts +84 -0
  30. package/src/cohort-builder.scss +126 -0
  31. package/src/cohort-builder.test.tsx +12 -0
  32. package/src/cohort-builder.tsx +213 -0
  33. package/src/cohort-builder.utils.ts +197 -0
  34. package/src/components/composition/composition.component.tsx +109 -0
  35. package/src/components/composition/composition.style.css +3 -0
  36. package/src/components/composition/composition.test.tsx +112 -0
  37. package/src/components/composition/composition.utils.ts +53 -0
  38. package/src/components/empty-data/empty-data.component.tsx +25 -0
  39. package/src/components/empty-data/empty-data.style.scss +19 -0
  40. package/src/components/saved-cohorts/saved-cohorts-options/saved-cohort-options.test.tsx +49 -0
  41. package/src/components/saved-cohorts/saved-cohorts-options/saved-cohorts-options.component.tsx +112 -0
  42. package/src/components/saved-cohorts/saved-cohorts.component.tsx +139 -0
  43. package/src/components/saved-cohorts/saved-cohorts.resources.ts +39 -0
  44. package/src/components/saved-cohorts/saved-cohorts.scss +15 -0
  45. package/src/components/saved-cohorts/saved-cohorts.test.tsx +51 -0
  46. package/src/components/saved-queries/saved-queries-options/saved-queries-options.component.tsx +112 -0
  47. package/src/components/saved-queries/saved-queries-options/saved-queries-options.test.tsx +47 -0
  48. package/src/components/saved-queries/saved-queries.component.tsx +139 -0
  49. package/src/components/saved-queries/saved-queries.resources.ts +39 -0
  50. package/src/components/saved-queries/saved-queries.scss +23 -0
  51. package/src/components/saved-queries/saved-queries.test.tsx +50 -0
  52. package/src/components/search-button-set/search-button-set.css +9 -0
  53. package/src/components/search-button-set/search-button-set.test.tsx +26 -0
  54. package/src/components/search-button-set/search-button-set.tsx +47 -0
  55. package/src/components/search-by-concepts/search-by-concepts.component.tsx +344 -0
  56. package/src/components/search-by-concepts/search-by-concepts.style.scss +48 -0
  57. package/src/components/search-by-concepts/search-by-concepts.test.tsx +129 -0
  58. package/src/components/search-by-concepts/search-concept/search-concept.component.tsx +130 -0
  59. package/src/components/search-by-concepts/search-concept/search-concept.resource.ts +53 -0
  60. package/src/components/search-by-concepts/search-concept/search-concept.style.css +25 -0
  61. package/src/components/search-by-concepts/search-concept/search-concept.test.tsx +89 -0
  62. package/src/components/search-by-demographics/search-by-demographics.component.tsx +209 -0
  63. package/src/components/search-by-demographics/search-by-demographics.style.scss +41 -0
  64. package/src/components/search-by-demographics/search-by-demographics.test.tsx +92 -0
  65. package/src/components/search-by-demographics/search-by-demographics.utils.ts +129 -0
  66. package/src/components/search-by-drug-orders/search-by-drug-orders.component.tsx +185 -0
  67. package/src/components/search-by-drug-orders/search-by-drug-orders.resources.ts +60 -0
  68. package/src/components/search-by-drug-orders/search-by-drug-orders.style.scss +4 -0
  69. package/src/components/search-by-drug-orders/search-by-drug-orders.test.tsx +159 -0
  70. package/src/components/search-by-drug-orders/search-by-drug-orders.utils.ts +118 -0
  71. package/src/components/search-by-encounters/search-by-encounters.component.tsx +188 -0
  72. package/src/components/search-by-encounters/search-by-encounters.resources.ts +51 -0
  73. package/src/components/search-by-encounters/search-by-encounters.style.scss +12 -0
  74. package/src/components/search-by-encounters/search-by-encounters.test.tsx +202 -0
  75. package/src/components/search-by-encounters/search-by-encounters.utils.ts +113 -0
  76. package/src/components/search-by-enrollments/search-by-enrollments.component.tsx +183 -0
  77. package/src/components/search-by-enrollments/search-by-enrollments.resources.ts +31 -0
  78. package/src/components/search-by-enrollments/search-by-enrollments.style.scss +4 -0
  79. package/src/components/search-by-enrollments/search-by-enrollments.test.tsx +158 -0
  80. package/src/components/search-by-enrollments/search-by-enrollments.utils.ts +69 -0
  81. package/src/components/search-by-location/search-by-location.component.tsx +97 -0
  82. package/src/components/search-by-location/search-by-location.style.scss +4 -0
  83. package/src/components/search-by-location/search-by-location.test.tsx +110 -0
  84. package/src/components/search-by-location/search-by-location.utils.ts +40 -0
  85. package/src/components/search-by-person-attributes/search-by-person-attributes.component.tsx +90 -0
  86. package/src/components/search-by-person-attributes/search-by-person-attributes.resource.ts +27 -0
  87. package/src/components/search-by-person-attributes/search-by-person-attributes.style.scss +4 -0
  88. package/src/components/search-by-person-attributes/search-by-person-attributes.test.tsx +133 -0
  89. package/src/components/search-by-person-attributes/search-by-person-attributes.utils.ts +42 -0
  90. package/src/components/search-history/search-history-options/search-history-options.component.tsx +290 -0
  91. package/src/components/search-history/search-history-options/search-history-options.resources.ts +29 -0
  92. package/src/components/search-history/search-history-options/search-history-options.test.tsx +144 -0
  93. package/src/components/search-history/search-history.component.tsx +174 -0
  94. package/src/components/search-history/search-history.style.scss +12 -0
  95. package/src/components/search-history/search-history.test.tsx +106 -0
  96. package/src/components/search-history/search-history.utils.ts +14 -0
  97. package/src/components/search-results-table/search-results-table.component.tsx +105 -0
  98. package/src/components/search-results-table/search-results-table.scss +4 -0
  99. package/src/components/search-results-table/search-results-table.test.tsx +47 -0
  100. package/src/declarations.d.tsx +2 -0
  101. package/src/index.ts +47 -0
  102. package/src/setup-tests.ts +1 -0
  103. package/src/types/index.ts +120 -0
@@ -0,0 +1,144 @@
1
+ import React from "react";
2
+
3
+ import { screen, render, cleanup, waitFor } from "@testing-library/react";
4
+ import userEvent from "@testing-library/user-event";
5
+
6
+ import { Cohort, Query } from "../../../types";
7
+ import SearchHistoryOptions from "./search-history-options.component";
8
+ import * as apis from "./search-history-options.resources";
9
+
10
+ jest.mock("./search-history-options.resources");
11
+
12
+ const searchHistoryItem = {
13
+ description: "Patients with NO Chronic viral hepatitis",
14
+ patients: [
15
+ {
16
+ firstname: "Horatio",
17
+ gender: "M",
18
+ patientId: 2,
19
+ age: 81,
20
+ lastname: "Hornblower",
21
+ id: "2",
22
+ name: "Horatio Hornblower",
23
+ },
24
+ {
25
+ firstname: "John",
26
+ gender: "M",
27
+ patientId: 3,
28
+ age: 47,
29
+ lastname: "Patient",
30
+ id: "3",
31
+ name: "John Patient",
32
+ },
33
+ ],
34
+ parameters: {
35
+ type: "org.openmrs.module.reporting.dataset.definition.PatientDataSetDefinition",
36
+ columns: [
37
+ {
38
+ name: "firstname",
39
+ key: "reporting.library.patientDataDefinition.builtIn.preferredName.givenName",
40
+ type: "org.openmrs.module.reporting.data.patient.definition.PatientDataDefinition",
41
+ },
42
+ {
43
+ name: "lastname",
44
+ key: "reporting.library.patientDataDefinition.builtIn.preferredName.familyName",
45
+ type: "org.openmrs.module.reporting.data.patient.definition.PatientDataDefinition",
46
+ },
47
+ {
48
+ name: "gender",
49
+ key: "reporting.library.patientDataDefinition.builtIn.gender",
50
+ type: "org.openmrs.module.reporting.data.patient.definition.PatientDataDefinition",
51
+ },
52
+ {
53
+ name: "age",
54
+ key: "reporting.library.patientDataDefinition.builtIn.ageOnDate.fullYears",
55
+ type: "org.openmrs.module.reporting.data.patient.definition.PatientDataDefinition",
56
+ },
57
+ {
58
+ name: "patientId",
59
+ key: "reporting.library.patientDataDefinition.builtIn.patientId",
60
+ type: "org.openmrs.module.reporting.data.patient.definition.PatientDataDefinition",
61
+ },
62
+ ],
63
+ rowFilters: [
64
+ {
65
+ key: "reporting.library.cohortDefinition.builtIn.codedObsSearchAdvanced",
66
+ parameterValues: {
67
+ operator1: "LESS_THAN",
68
+ question: "145131AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA",
69
+ timeModifier: "NO",
70
+ },
71
+ type: "org.openmrs.module.reporting.dataset.definition.PatientDataSetDefinition",
72
+ },
73
+ ],
74
+ customRowFilterCombination: "1",
75
+ },
76
+ id: "1",
77
+ results: "2",
78
+ };
79
+
80
+ const testProps = {
81
+ searchItem: searchHistoryItem,
82
+ updateSearchHistory: jest.fn(),
83
+ };
84
+
85
+ describe("Test the search history options", () => {
86
+ afterEach(cleanup);
87
+ it("should be able to save the search history item as a cohort", async () => {
88
+ const user = userEvent.setup();
89
+ const cohort: Cohort = {
90
+ memberIds: [2, 3],
91
+ description: "Patients with NO Chronic viral hepatitis",
92
+ name: "Chronic viral hepatitis cohort",
93
+ display: "Chronic viral hepatitis cohort",
94
+ };
95
+
96
+ render(<SearchHistoryOptions {...testProps} />);
97
+
98
+ await waitFor(() => user.click(screen.getByTestId("options")));
99
+ await waitFor(() => user.click(screen.getByTestId("save-cohort")));
100
+ await waitFor(() =>
101
+ user.type(
102
+ screen.getByTestId("cohort-name"),
103
+ "Chronic viral hepatitis cohort"
104
+ )
105
+ );
106
+ await waitFor(() => user.click(screen.getByTestId("cohort-save-button")));
107
+ await waitFor(() =>
108
+ expect(jest.spyOn(apis, "createCohort")).toBeCalledWith(cohort)
109
+ );
110
+ });
111
+
112
+ it("should be able to save the search history item as a query", async () => {
113
+ const user = userEvent.setup();
114
+ const query: Query = searchHistoryItem.parameters;
115
+ render(<SearchHistoryOptions {...testProps} />);
116
+
117
+ await waitFor(() => user.click(screen.getByTestId("options")));
118
+ await waitFor(() => user.click(screen.getByTestId("save-query")));
119
+ await waitFor(() =>
120
+ user.type(
121
+ screen.getByTestId("query-name"),
122
+ "Chronic viral hepatitis query"
123
+ )
124
+ );
125
+ await waitFor(() => user.click(screen.getByTestId("query-save-button")));
126
+ expect(jest.spyOn(apis, "createQuery")).toBeCalledWith(query);
127
+ });
128
+
129
+ it("should be able delete search history item", async () => {
130
+ const user = userEvent.setup();
131
+ const updateSearchHistory = jest.fn();
132
+ render(
133
+ <SearchHistoryOptions
134
+ searchItem={searchHistoryItem}
135
+ updateSearchHistory={updateSearchHistory}
136
+ />
137
+ );
138
+
139
+ await waitFor(() => user.click(screen.getByTestId("options")));
140
+ await waitFor(() => user.click(screen.getByTestId("deleteFromHistory")));
141
+ await waitFor(() => user.click(screen.getByText("Delete")));
142
+ expect(updateSearchHistory).toBeCalledWith(searchHistoryItem);
143
+ });
144
+ });
@@ -0,0 +1,174 @@
1
+ import React, { Dispatch, SetStateAction, useEffect, useState } from "react";
2
+
3
+ import {
4
+ Button,
5
+ ComposedModal,
6
+ DataTable,
7
+ ModalFooter,
8
+ ModalHeader,
9
+ Pagination,
10
+ Table,
11
+ TableBody,
12
+ TableCell,
13
+ TableHead,
14
+ TableHeader,
15
+ TableRow,
16
+ } from "@carbon/react";
17
+ import { useTranslation } from "react-i18next";
18
+
19
+ import mainStyles from "../../cohort-builder.scss";
20
+ import { PaginationData, SearchHistoryItem } from "../../types";
21
+ import EmptyData from "../empty-data/empty-data.component";
22
+ import SearchHistoryOptions from "./search-history-options/search-history-options.component";
23
+ import styles from "./search-history.style.scss";
24
+ import { getSearchHistory } from "./search-history.utils";
25
+
26
+ interface SearchHistoryProps {
27
+ isHistoryUpdated: boolean;
28
+ setIsHistoryUpdated: Dispatch<SetStateAction<boolean>>;
29
+ }
30
+
31
+ const SearchHistory: React.FC<SearchHistoryProps> = ({
32
+ isHistoryUpdated,
33
+ setIsHistoryUpdated,
34
+ }) => {
35
+ const { t } = useTranslation();
36
+ const [searchResults, setSearchResults] = useState<SearchHistoryItem[]>([]);
37
+ const [page, setPage] = useState(1);
38
+ const [pageSize, setPageSize] = useState(10);
39
+ const [isClearHistoryModalVisible, setIsClearHistoryModalVisible] =
40
+ useState(false);
41
+
42
+ useEffect(() => {
43
+ if (isHistoryUpdated) {
44
+ setSearchResults(getSearchHistory());
45
+ setIsHistoryUpdated(false);
46
+ }
47
+ }, [isHistoryUpdated, setIsHistoryUpdated]);
48
+
49
+ const handlePagination = ({ page, pageSize }: PaginationData) => {
50
+ setPage(page);
51
+ setPageSize(pageSize);
52
+ };
53
+
54
+ const headers = [
55
+ {
56
+ key: "id",
57
+ header: "#",
58
+ },
59
+ {
60
+ key: "description",
61
+ header: t("query", "Query"),
62
+ },
63
+ {
64
+ key: "results",
65
+ header: t("results", "Results"),
66
+ },
67
+ ];
68
+
69
+ const clearHistory = () => {
70
+ window.sessionStorage.removeItem("openmrsHistory");
71
+ setSearchResults([]);
72
+ setIsClearHistoryModalVisible(false);
73
+ };
74
+
75
+ const updateSearchHistory = (selectedSearchItem: SearchHistoryItem) => {
76
+ const updatedSearchResults = [...searchResults].filter(
77
+ (searchResult, index) =>
78
+ index != searchResults.indexOf(selectedSearchItem)
79
+ );
80
+ setSearchResults(updatedSearchResults);
81
+ window.sessionStorage.setItem(
82
+ "openmrsHistory",
83
+ JSON.stringify(updatedSearchResults)
84
+ );
85
+ };
86
+
87
+ return (
88
+ <div className={styles.container}>
89
+ <div className={styles.header}>
90
+ <p className={mainStyles.heading}>
91
+ {t("searchHistory", "Search History")}
92
+ </p>
93
+ {searchResults.length > 0 && (
94
+ <Button
95
+ kind="danger--tertiary"
96
+ onClick={() => setIsClearHistoryModalVisible(true)}
97
+ >
98
+ {t("clearHistory", "Clear Search History")}
99
+ </Button>
100
+ )}
101
+ </div>
102
+ <DataTable rows={searchResults} headers={headers} useZebraStyles>
103
+ {({ rows, headers, getTableProps, getHeaderProps, getRowProps }) => (
104
+ <Table {...getTableProps()}>
105
+ <TableHead>
106
+ <TableRow>
107
+ {headers.map((header) => (
108
+ <TableHeader {...getHeaderProps({ header })}>
109
+ {header.header}
110
+ </TableHeader>
111
+ ))}
112
+ <TableHeader className={mainStyles.optionHeader}></TableHeader>
113
+ </TableRow>
114
+ </TableHead>
115
+ <TableBody>
116
+ {rows
117
+ .slice((page - 1) * pageSize)
118
+ .slice(0, pageSize)
119
+ .map((row, index: number) => (
120
+ <TableRow {...getRowProps({ row })}>
121
+ {row.cells.map((cell) => (
122
+ <TableCell key={cell.id}>{cell.value}</TableCell>
123
+ ))}
124
+ <TableCell className={mainStyles.optionCell}>
125
+ <SearchHistoryOptions
126
+ searchItem={searchResults[index]}
127
+ updateSearchHistory={updateSearchHistory}
128
+ />
129
+ </TableCell>
130
+ </TableRow>
131
+ ))}
132
+ </TableBody>
133
+ </Table>
134
+ )}
135
+ </DataTable>
136
+ {searchResults.length > 10 && (
137
+ <Pagination
138
+ backwardText={t("previousPage", "Previous page")}
139
+ forwardText={t("nextPage", "Next page")}
140
+ itemsPerPageText={t("itemsPerPage:", "Items per page:")}
141
+ onChange={handlePagination}
142
+ page={1}
143
+ pageSize={10}
144
+ pageSizes={[10, 20, 30, 40, 50]}
145
+ size="md"
146
+ totalItems={searchResults.length}
147
+ />
148
+ )}
149
+ {!searchResults.length && <EmptyData displayText={t("data", "data")} />}
150
+ <ComposedModal
151
+ size={"sm"}
152
+ open={isClearHistoryModalVisible}
153
+ onClose={() => setIsClearHistoryModalVisible(false)}
154
+ >
155
+ <ModalHeader>
156
+ <p>
157
+ {t(
158
+ "clearHistoryMsg",
159
+ "Are you sure you want to clear the search history?"
160
+ )}
161
+ </p>
162
+ </ModalHeader>
163
+ <ModalFooter
164
+ danger
165
+ onRequestSubmit={clearHistory}
166
+ primaryButtonText={t("clear", "Clear")}
167
+ secondaryButtonText={t("cancel", "Cancel")}
168
+ />
169
+ </ComposedModal>
170
+ </div>
171
+ );
172
+ };
173
+
174
+ export default SearchHistory;
@@ -0,0 +1,12 @@
1
+ .header {
2
+ display: flex;
3
+ align-items: center;
4
+ justify-content: space-between;
5
+ padding: 0.5rem
6
+ }
7
+
8
+ .container {
9
+ margin-top: 1rem;
10
+ background: white;
11
+ padding: 1rem;
12
+ }
@@ -0,0 +1,106 @@
1
+ import React from "react";
2
+
3
+ import { render, cleanup, screen } from "@testing-library/react";
4
+
5
+ import translations from "../../../translations/en.json";
6
+ import SearchHistory from "./search-history.component";
7
+ import * as utils from "./search-history.utils";
8
+
9
+ jest.mock("./search-history.utils.ts");
10
+
11
+ const mockSearchHistory = [
12
+ {
13
+ description: "Patients with NO Chronic viral hepatitis",
14
+ patients: [
15
+ {
16
+ firstname: "Horatio",
17
+ gender: "M",
18
+ patientId: 2,
19
+ age: 81,
20
+ lastname: "Hornblower",
21
+ id: "2",
22
+ name: "Horatio Hornblower",
23
+ },
24
+ {
25
+ firstname: "John",
26
+ gender: "M",
27
+ patientId: 3,
28
+ age: 47,
29
+ lastname: "Patient",
30
+ id: "3",
31
+ name: "John Patient",
32
+ },
33
+ ],
34
+ parameters: {
35
+ type: "org.openmrs.module.reporting.dataset.definition.PatientDataSetDefinition",
36
+ columns: [
37
+ {
38
+ name: "firstname",
39
+ key: "reporting.library.patientDataDefinition.builtIn.preferredName.givenName",
40
+ type: "org.openmrs.module.reporting.data.patient.definition.PatientDataDefinition",
41
+ },
42
+ {
43
+ name: "lastname",
44
+ key: "reporting.library.patientDataDefinition.builtIn.preferredName.familyName",
45
+ type: "org.openmrs.module.reporting.data.patient.definition.PatientDataDefinition",
46
+ },
47
+ {
48
+ name: "gender",
49
+ key: "reporting.library.patientDataDefinition.builtIn.gender",
50
+ type: "org.openmrs.module.reporting.data.patient.definition.PatientDataDefinition",
51
+ },
52
+ {
53
+ name: "age",
54
+ key: "reporting.library.patientDataDefinition.builtIn.ageOnDate.fullYears",
55
+ type: "org.openmrs.module.reporting.data.patient.definition.PatientDataDefinition",
56
+ },
57
+ {
58
+ name: "patientId",
59
+ key: "reporting.library.patientDataDefinition.builtIn.patientId",
60
+ type: "org.openmrs.module.reporting.data.patient.definition.PatientDataDefinition",
61
+ },
62
+ ],
63
+ rowFilters: [
64
+ {
65
+ key: "reporting.library.cohortDefinition.builtIn.codedObsSearchAdvanced",
66
+ parameterValues: {
67
+ operator1: "LESS_THAN",
68
+ question: "145131AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA",
69
+ timeModifier: "NO",
70
+ },
71
+ type: "org.openmrs.module.reporting.dataset.definition.PatientDataSetDefinition",
72
+ },
73
+ ],
74
+ customRowFilterCombination: "1",
75
+ },
76
+ id: "1",
77
+ results: "2",
78
+ },
79
+ ];
80
+
81
+ describe("Test the search history component", () => {
82
+ afterEach(cleanup);
83
+ it("should render a message when there's no history to display", async () => {
84
+ render(
85
+ <SearchHistory isHistoryUpdated={false} setIsHistoryUpdated={jest.fn()} />
86
+ );
87
+
88
+ expect(
89
+ screen.getByText("There are no data to display")
90
+ ).toBeInTheDocument();
91
+ });
92
+
93
+ it("should display the search history", async () => {
94
+ jest.spyOn(utils, "getSearchHistory").mockReturnValue(mockSearchHistory);
95
+
96
+ render(
97
+ <SearchHistory isHistoryUpdated={true} setIsHistoryUpdated={jest.fn()} />
98
+ );
99
+
100
+ expect(screen.getByText(translations.clearHistory)).toBeInTheDocument();
101
+ expect(screen.getByText("2")).toBeInTheDocument();
102
+ expect(
103
+ screen.getByText(mockSearchHistory[0].description)
104
+ ).toBeInTheDocument();
105
+ });
106
+ });
@@ -0,0 +1,14 @@
1
+ import { SearchHistoryItem } from "../../types";
2
+
3
+ export const getSearchHistory = () => {
4
+ const history = JSON.parse(window.sessionStorage.getItem("openmrsHistory"));
5
+ let searchHistory: SearchHistoryItem[] = [];
6
+ history?.map((historyItem, index) =>
7
+ searchHistory.push({
8
+ ...historyItem,
9
+ id: (index + 1).toString(),
10
+ results: historyItem.patients.length,
11
+ })
12
+ );
13
+ return searchHistory;
14
+ };
@@ -0,0 +1,105 @@
1
+ import React, { useState } from "react";
2
+
3
+ import {
4
+ DataTable,
5
+ Table,
6
+ TableHead,
7
+ TableRow,
8
+ TableHeader,
9
+ TableBody,
10
+ TableCell,
11
+ Pagination,
12
+ } from "@carbon/react";
13
+ import { useTranslation } from "react-i18next";
14
+
15
+ import mainStyle from "../../cohort-builder.scss";
16
+ import { PaginationData, Patient } from "../../types";
17
+ import EmptyData from "../empty-data/empty-data.component";
18
+ import styles from "./search-results-table.scss";
19
+
20
+ interface SearchResultsTableProps {
21
+ patients: Patient[];
22
+ }
23
+
24
+ const SearchResultsTable: React.FC<SearchResultsTableProps> = ({
25
+ patients,
26
+ }) => {
27
+ const [page, setPage] = useState(1);
28
+ const [pageSize, setPageSize] = useState(10);
29
+ const { t } = useTranslation();
30
+
31
+ const headers = [
32
+ {
33
+ key: "id",
34
+ header: t("openmrsId", "OpenMRS ID"),
35
+ },
36
+ {
37
+ key: "name",
38
+ header: t("name", "Name"),
39
+ },
40
+ {
41
+ key: "age",
42
+ header: t("age", "Age"),
43
+ },
44
+ {
45
+ key: "gender",
46
+ header: t("gender", "Gender"),
47
+ },
48
+ ];
49
+
50
+ const handlePagination = ({ page, pageSize }: PaginationData) => {
51
+ setPage(page);
52
+ setPageSize(pageSize);
53
+ };
54
+
55
+ return (
56
+ <div className={styles.container}>
57
+ <p className={mainStyle.heading}>
58
+ {t("searchResults", "Search Results")}
59
+ </p>
60
+ <DataTable rows={patients} headers={headers} useZebraStyles>
61
+ {({ rows, headers, getTableProps, getHeaderProps, getRowProps }) => (
62
+ <Table {...getTableProps()}>
63
+ <TableHead>
64
+ <TableRow>
65
+ {headers.map((header) => (
66
+ <TableHeader {...getHeaderProps({ header })}>
67
+ {header.header}
68
+ </TableHeader>
69
+ ))}
70
+ </TableRow>
71
+ </TableHead>
72
+ <TableBody>
73
+ {rows
74
+ .slice((page - 1) * pageSize)
75
+ .slice(0, pageSize)
76
+ .map((row, index) => (
77
+ <TableRow {...getRowProps({ row })} key={index}>
78
+ {row.cells.map((cell, index) => (
79
+ <TableCell key={index}>{cell.value}</TableCell>
80
+ ))}
81
+ </TableRow>
82
+ ))}
83
+ </TableBody>
84
+ </Table>
85
+ )}
86
+ </DataTable>
87
+ {patients.length > 10 && (
88
+ <Pagination
89
+ backwardText={t("previousPage", "Previous page")}
90
+ forwardText={t("nextPage", "Next page")}
91
+ itemsPerPageText={t("itemsPerPage:", "Items per page:")}
92
+ onChange={handlePagination}
93
+ page={1}
94
+ pageSize={10}
95
+ pageSizes={[10, 20, 30, 40, 50]}
96
+ size="md"
97
+ totalItems={patients.length}
98
+ />
99
+ )}
100
+ {!patients.length && <EmptyData displayText={t("data", "data")} />}
101
+ </div>
102
+ );
103
+ };
104
+
105
+ export default SearchResultsTable;
@@ -0,0 +1,4 @@
1
+ .container {
2
+ background: white;
3
+ padding: 1rem;
4
+ }
@@ -0,0 +1,47 @@
1
+ import React from "react";
2
+
3
+ import { render, cleanup } from "@testing-library/react";
4
+
5
+ import SearchResultsTable from "./search-results-table.component";
6
+
7
+ const mockPatients = [
8
+ {
9
+ firstname: "Horatio",
10
+ gender: "M",
11
+ patientId: 2,
12
+ age: 81,
13
+ lastname: "Hornblower",
14
+ id: "2",
15
+ name: "Horatio Hornblower",
16
+ },
17
+ {
18
+ firstname: "John",
19
+ gender: "M",
20
+ patientId: 3,
21
+ age: 47,
22
+ lastname: "Patient",
23
+ id: "3",
24
+ name: "John Patient",
25
+ },
26
+ ];
27
+
28
+ describe("Test the search results component", () => {
29
+ afterEach(cleanup);
30
+ it("should render a message when there's no results to display", async () => {
31
+ const { getByText } = render(<SearchResultsTable patients={[]} />);
32
+ expect(getByText("There are no data to display")).toBeInTheDocument();
33
+ });
34
+
35
+ it("should display the search results", () => {
36
+ const { getAllByRole } = render(
37
+ <SearchResultsTable patients={mockPatients} />
38
+ );
39
+ const rows = getAllByRole("row");
40
+ const cells = getAllByRole("cell");
41
+ expect(rows).toHaveLength(mockPatients.length + 1);
42
+ expect(cells[1].textContent).toBe(mockPatients[0].name);
43
+ expect(cells[5].textContent).toBe(mockPatients[1].name);
44
+ expect(cells[0].textContent).toBe(mockPatients[0].id);
45
+ expect(cells[4].textContent).toBe(mockPatients[1].id);
46
+ });
47
+ });
@@ -0,0 +1,2 @@
1
+ declare module "*.css";
2
+ declare module "*.scss";
package/src/index.ts ADDED
@@ -0,0 +1,47 @@
1
+ import { getAsyncLifecycle } from "@openmrs/esm-framework";
2
+
3
+ const importTranslation = require.context(
4
+ "../translations",
5
+ false,
6
+ /.json$/,
7
+ "lazy"
8
+ );
9
+
10
+ const backendDependencies = {
11
+ fhir2: "^1.2.0",
12
+ "webservices.rest": "^2.2.0",
13
+ reportingrest: "^1.0.0",
14
+ reporting: "^1.0.0",
15
+ };
16
+
17
+ function setupOpenMRS() {
18
+ const moduleName = "@openmrs/esm-cohort-builder";
19
+
20
+ const options = {
21
+ featureName: "cohort-builder",
22
+ moduleName,
23
+ };
24
+
25
+ return {
26
+ pages: [
27
+ {
28
+ load: getAsyncLifecycle(() => import("./cohort-builder"), options),
29
+ route: "cohort-builder",
30
+ },
31
+ ],
32
+ extensions: [
33
+ {
34
+ id: "cohort-builder-app-link",
35
+ slot: "app-menu-slot",
36
+ load: getAsyncLifecycle(
37
+ () => import("./cohort-builder-app-menu-link"),
38
+ options
39
+ ),
40
+ online: true,
41
+ offline: false,
42
+ },
43
+ ],
44
+ };
45
+ }
46
+
47
+ export { backendDependencies, importTranslation, setupOpenMRS };
@@ -0,0 +1 @@
1
+ import "@testing-library/jest-dom/extend-expect";