@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,90 @@
1
+ import React, { useState } from "react";
2
+
3
+ import { Column, Dropdown, TextInput } from "@carbon/react";
4
+ import { showToast } from "@openmrs/esm-framework";
5
+ import { useTranslation } from "react-i18next";
6
+
7
+ import { SearchByProps } from "../../types";
8
+ import SearchButtonSet from "../search-button-set/search-button-set";
9
+ import { usePersonAttributes } from "./search-by-person-attributes.resource";
10
+ import styles from "./search-by-person-attributes.style.scss";
11
+ import {
12
+ getQueryDetails,
13
+ getSearchByAttributesDescription,
14
+ } from "./search-by-person-attributes.utils";
15
+
16
+ const SearchByPersonAttributes: React.FC<SearchByProps> = ({ onSubmit }) => {
17
+ const { t } = useTranslation();
18
+ const { personAttributes, personAttributesError } = usePersonAttributes();
19
+ const [selectedAttributeValues, setSelectedAttributeValues] = useState([]);
20
+ const [selectedAttributeId, setSelectedAttributeId] = useState<string>(null);
21
+ const [isLoading, setIsLoading] = useState(false);
22
+
23
+ if (personAttributesError) {
24
+ showToast({
25
+ title: t("error", "Error"),
26
+ kind: "error",
27
+ critical: true,
28
+ description: personAttributesError?.message,
29
+ });
30
+ }
31
+
32
+ const handleResetInputs = () => {
33
+ setSelectedAttributeId(null);
34
+ setSelectedAttributeValues([]);
35
+ };
36
+
37
+ const submit = async () => {
38
+ setIsLoading(true);
39
+ const selectedPersonAttribute = personAttributes?.find(
40
+ (personAttribute) => personAttribute.value == selectedAttributeId
41
+ );
42
+ await onSubmit(
43
+ getQueryDetails(selectedAttributeId, selectedAttributeValues),
44
+ getSearchByAttributesDescription(
45
+ selectedPersonAttribute?.label,
46
+ selectedAttributeValues
47
+ )
48
+ );
49
+ setIsLoading(false);
50
+ };
51
+
52
+ return (
53
+ <>
54
+ <Column>
55
+ <div>
56
+ <Dropdown
57
+ id="personAttributes"
58
+ data-testid="personAttributes"
59
+ onChange={(data) => setSelectedAttributeId(data.selectedItem.value)}
60
+ items={personAttributes}
61
+ label={t("selectAttribute", "Select a person attribute")}
62
+ />
63
+ </div>
64
+ </Column>
65
+ <div className={styles.column}>
66
+ <Column>
67
+ <TextInput
68
+ id={"selectedAttributeValues"}
69
+ data-testid={"selectedAttributeValues"}
70
+ disabled={!selectedAttributeId}
71
+ labelText={t(
72
+ "selectedAttributeValues",
73
+ "Enter Comma Delimited Values"
74
+ )}
75
+ onChange={(e) =>
76
+ setSelectedAttributeValues(e.target.value.trim().split(","))
77
+ }
78
+ />
79
+ </Column>
80
+ </div>
81
+ <SearchButtonSet
82
+ onHandleReset={handleResetInputs}
83
+ onHandleSubmit={submit}
84
+ isLoading={isLoading}
85
+ />
86
+ </>
87
+ );
88
+ };
89
+
90
+ export default SearchByPersonAttributes;
@@ -0,0 +1,27 @@
1
+ import { openmrsFetch } from "@openmrs/esm-framework";
2
+ import useSWRImmutable from "swr/immutable";
3
+
4
+ import { DropdownValue, Response } from "../../types";
5
+
6
+ /**
7
+ * @returns PersonAttributes
8
+ */
9
+ export function usePersonAttributes() {
10
+ const { data, error } = useSWRImmutable<{
11
+ data: { results: Response[] };
12
+ }>("/ws/rest/v1/personattributetype", openmrsFetch);
13
+
14
+ const personAttributes: DropdownValue[] = [];
15
+ data?.data.results.map((personAttribute: Response, index: number) => {
16
+ personAttributes.push({
17
+ id: index,
18
+ label: personAttribute.display,
19
+ value: personAttribute.uuid,
20
+ });
21
+ });
22
+ return {
23
+ isLoading: !data && !error,
24
+ personAttributes,
25
+ personAttributesError: error,
26
+ };
27
+ }
@@ -0,0 +1,4 @@
1
+ .column {
2
+ padding-top: 1.5rem;
3
+ display: flex;
4
+ }
@@ -0,0 +1,133 @@
1
+ import React from "react";
2
+
3
+ import { render, screen, waitFor } from "@testing-library/react";
4
+ import userEvent from "@testing-library/user-event";
5
+
6
+ import SearchByPersonAttributes from "./search-by-person-attributes.component";
7
+ import * as apis from "./search-by-person-attributes.resource";
8
+
9
+ jest.mock("./search-by-person-attributes.resource.ts");
10
+
11
+ const personAttributes = [
12
+ {
13
+ id: 0,
14
+ label: "email",
15
+ value: "ac7d7773-fe9f-11ec-8b9b-0242ac1b0002",
16
+ },
17
+ {
18
+ id: 1,
19
+ label: "Birthplace",
20
+ value: "8d8718c2-c2cc-11de-8d13-0010c6dffd0f",
21
+ },
22
+ {
23
+ id: 2,
24
+ label: "Citizenship",
25
+ value: "8d871afc-c2cc-11de-8d13-0010c6dffd0f",
26
+ },
27
+ {
28
+ id: 3,
29
+ label: "Civil Status",
30
+ value: "8d871f2a-c2cc-11de-8d13-0010c6dffd0f",
31
+ },
32
+ {
33
+ id: 4,
34
+ label: "Health Center",
35
+ value: "8d87236c-c2cc-11de-8d13-0010c6dffd0f",
36
+ },
37
+ {
38
+ id: 5,
39
+ label: "Health District",
40
+ value: "8d872150-c2cc-11de-8d13-0010c6dffd0f",
41
+ },
42
+ {
43
+ id: 6,
44
+ label: "Mother's Name",
45
+ value: "8d871d18-c2cc-11de-8d13-0010c6dffd0f",
46
+ },
47
+ {
48
+ id: 7,
49
+ label: "Race",
50
+ value: "8d871386-c2cc-11de-8d13-0010c6dffd0f",
51
+ },
52
+ {
53
+ id: 8,
54
+ label: "Telephone Number",
55
+ value: "14d4f066-15f5-102d-96e4-000c29c2a5d7",
56
+ },
57
+ {
58
+ id: 9,
59
+ label: "Unknown patient",
60
+ value: "8b56eac7-5c76-4b9c-8c6f-1deab8d3fc47",
61
+ },
62
+ ];
63
+
64
+ const expectedQuery = {
65
+ query: {
66
+ columns: [
67
+ {
68
+ key: "reporting.library.patientDataDefinition.builtIn.preferredName.givenName",
69
+ name: "firstname",
70
+ type: "org.openmrs.module.reporting.data.patient.definition.PatientDataDefinition",
71
+ },
72
+ {
73
+ key: "reporting.library.patientDataDefinition.builtIn.preferredName.familyName",
74
+ name: "lastname",
75
+ type: "org.openmrs.module.reporting.data.patient.definition.PatientDataDefinition",
76
+ },
77
+ {
78
+ key: "reporting.library.patientDataDefinition.builtIn.gender",
79
+ name: "gender",
80
+ type: "org.openmrs.module.reporting.data.patient.definition.PatientDataDefinition",
81
+ },
82
+ {
83
+ key: "reporting.library.patientDataDefinition.builtIn.ageOnDate.fullYears",
84
+ name: "age",
85
+ type: "org.openmrs.module.reporting.data.patient.definition.PatientDataDefinition",
86
+ },
87
+ {
88
+ key: "reporting.library.patientDataDefinition.builtIn.patientId",
89
+ name: "patientId",
90
+ type: "org.openmrs.module.reporting.data.patient.definition.PatientDataDefinition",
91
+ },
92
+ ],
93
+ customRowFilterCombination: "1",
94
+ rowFilters: [
95
+ {
96
+ key: "reporting.library.cohortDefinition.builtIn.personWithAttribute",
97
+ parameterValues: {
98
+ attributeType: "8d871d18-c2cc-11de-8d13-0010c6dffd0f",
99
+ values: ["janet", "irina"],
100
+ },
101
+ type: "org.openmrs.module.reporting.dataset.definition.PatientDataSetDefinition",
102
+ },
103
+ ],
104
+ type: "org.openmrs.module.reporting.dataset.definition.PatientDataSetDefinition",
105
+ },
106
+ };
107
+
108
+ describe("Test the search by person attributes component", () => {
109
+ it("should be able to select input values", async () => {
110
+ const user = userEvent.setup();
111
+ jest.spyOn(apis, "usePersonAttributes").mockReturnValue({
112
+ personAttributes,
113
+ isLoading: false,
114
+ personAttributesError: undefined,
115
+ });
116
+ const submit = jest.fn();
117
+ render(<SearchByPersonAttributes onSubmit={submit} />);
118
+ expect(jest.spyOn(apis, "usePersonAttributes"));
119
+ await waitFor(() => user.click(screen.getByText("Open menu")));
120
+ await waitFor(() => user.click(screen.getByText("Mother's Name")));
121
+ await waitFor(() =>
122
+ user.click(screen.getByTestId("selectedAttributeValues"))
123
+ );
124
+ await waitFor(() =>
125
+ user.type(screen.getByTestId("selectedAttributeValues"), "janet,irina")
126
+ );
127
+ await waitFor(() => user.click(screen.getByTestId("search-btn")));
128
+ expect(submit).toBeCalledWith(
129
+ expectedQuery,
130
+ "Patients with Mother's Name equal to either janet or irina"
131
+ );
132
+ });
133
+ });
@@ -0,0 +1,42 @@
1
+ import { composeJson } from "../../cohort-builder.utils";
2
+
3
+ export const getQueryDetails = (
4
+ attributeId: string,
5
+ attributeValues: string[]
6
+ ) => {
7
+ const searchParameters = {
8
+ personWithAttribute: [
9
+ {
10
+ name: "attributeType",
11
+ value: attributeId,
12
+ },
13
+ {
14
+ name: "values",
15
+ value: attributeValues,
16
+ },
17
+ ],
18
+ };
19
+ const queryDetails = composeJson(searchParameters);
20
+
21
+ return queryDetails;
22
+ };
23
+
24
+ export const getSearchByAttributesDescription = (
25
+ attributeId: string,
26
+ selectedAttributeValues: string[]
27
+ ) => {
28
+ let description = "Patients";
29
+ const attributeValues =
30
+ selectedAttributeValues.length > 0
31
+ ? selectedAttributeValues.join(", ").replace(/,(?=[^,]*$)/, " or")
32
+ : "";
33
+
34
+ description += attributeId
35
+ ? ` with${attributeValues ? "" : " any"} ${attributeId}`
36
+ : attributeId;
37
+
38
+ description += attributeValues
39
+ ? ` equal to ${attributeValues.length > 1 && "either"} ${attributeValues}`
40
+ : "";
41
+ return description;
42
+ };
@@ -0,0 +1,290 @@
1
+ import React, { useState } from "react";
2
+
3
+ import {
4
+ Button,
5
+ ComposedModal,
6
+ ModalBody,
7
+ ModalFooter,
8
+ ModalHeader,
9
+ OverflowMenu,
10
+ OverflowMenuItem,
11
+ TextInput,
12
+ } from "@carbon/react";
13
+ import { showToast } from "@openmrs/esm-framework";
14
+ import { useTranslation } from "react-i18next";
15
+
16
+ import { downloadCSV } from "../../../cohort-builder.utils";
17
+ import { Cohort, Patient, SearchHistoryItem } from "../../../types";
18
+ import { createCohort, createQuery } from "./search-history-options.resources";
19
+
20
+ enum Option {
21
+ SAVE_COHORT,
22
+ SAVE_QUERY,
23
+ DOWNLOAD,
24
+ DELETE,
25
+ }
26
+
27
+ interface SearchHistoryOptions {
28
+ searchItem: SearchHistoryItem;
29
+ updateSearchHistory: (selectedSearchItem: SearchHistoryItem) => void;
30
+ }
31
+
32
+ const SearchHistoryOptions: React.FC<SearchHistoryOptions> = ({
33
+ searchItem,
34
+ updateSearchHistory,
35
+ }) => {
36
+ const { t } = useTranslation();
37
+ const [cohortName, setCohortName] = useState("");
38
+ const [cohortDescription, setCohortDescription] = useState("");
39
+ const [queryName, setQueryName] = useState("");
40
+ const [queryDescription, setQueryDescription] = useState("");
41
+ const [isDeleteCohortModalVisible, setIsDeleteCohortModalVisible] =
42
+ useState(false);
43
+ const [isSaveCohortModalVisible, setIsSaveCohortModalVisible] =
44
+ useState(false);
45
+ const [isSaveQueryModalVisible, setIsSaveQueryModalVisible] = useState(false);
46
+
47
+ const handleOption = async (option: Option) => {
48
+ const { patients, description } = searchItem;
49
+ switch (option) {
50
+ case Option.SAVE_COHORT:
51
+ setCohortDescription(description);
52
+ setIsSaveCohortModalVisible(true);
53
+ break;
54
+ case Option.SAVE_QUERY:
55
+ setQueryDescription(description);
56
+ setIsSaveQueryModalVisible(true);
57
+ break;
58
+ case Option.DOWNLOAD:
59
+ downloadCSV(patients, description);
60
+ break;
61
+ case Option.DELETE:
62
+ setIsDeleteCohortModalVisible(true);
63
+ break;
64
+ }
65
+ };
66
+
67
+ const saveCohort = async () => {
68
+ const cohortMembers: number[] = [];
69
+ const { patients } = searchItem;
70
+ patients.forEach((patient: Patient) =>
71
+ cohortMembers.push(parseInt(patient.id))
72
+ );
73
+ let cohort: Cohort = {
74
+ display: cohortName,
75
+ memberIds: cohortMembers,
76
+ description: cohortDescription,
77
+ name: cohortName,
78
+ };
79
+
80
+ try {
81
+ await createCohort(cohort);
82
+ setCohortName("");
83
+ setCohortDescription("");
84
+ setIsSaveCohortModalVisible(false);
85
+ showToast({
86
+ title: t("success", "Success"),
87
+ kind: "success",
88
+ critical: true,
89
+ description: "the cohort is saved",
90
+ });
91
+ } catch (error) {
92
+ showToast({
93
+ title: t("cohortCreateError", "Error creating the cohort"),
94
+ kind: "error",
95
+ critical: true,
96
+ description: error?.message,
97
+ });
98
+ }
99
+ };
100
+
101
+ const handleDeleteSearchItem = async () => {
102
+ try {
103
+ updateSearchHistory(searchItem);
104
+ setIsDeleteCohortModalVisible(false);
105
+ showToast({
106
+ title: t("success", "Success"),
107
+ kind: "success",
108
+ critical: true,
109
+ description: "the search item is deleted",
110
+ });
111
+ } catch (error) {
112
+ showToast({
113
+ title: t("searchItemDeleteError", "Error deleting the cohort"),
114
+ kind: "error",
115
+ critical: true,
116
+ description: error?.message,
117
+ });
118
+ }
119
+ };
120
+
121
+ const saveQuery = async () => {
122
+ try {
123
+ const { parameters } = searchItem;
124
+ parameters.name = queryName;
125
+ parameters.description = queryDescription;
126
+ await createQuery(parameters);
127
+ setQueryName("");
128
+ setQueryDescription("");
129
+ setIsSaveQueryModalVisible(false);
130
+ showToast({
131
+ title: t("success", "Success"),
132
+ kind: "success",
133
+ critical: true,
134
+ description: "the query is saved",
135
+ });
136
+ } catch (error) {
137
+ showToast({
138
+ title: t("queryDeleteError", "Error saving the query"),
139
+ kind: "error",
140
+ critical: true,
141
+ description: error?.message,
142
+ });
143
+ }
144
+ };
145
+
146
+ return (
147
+ <>
148
+ <OverflowMenu
149
+ ariaLabel="overflow-menu"
150
+ size="md"
151
+ flipped
152
+ direction="top"
153
+ data-testid="options"
154
+ >
155
+ <OverflowMenuItem
156
+ data-testid="save-cohort"
157
+ itemText={t("saveCohort", "Save Cohort")}
158
+ onClick={() => handleOption(Option.SAVE_COHORT)}
159
+ />
160
+ <OverflowMenuItem
161
+ data-testid="save-query"
162
+ itemText={t("saveQuery", "Save Query")}
163
+ onClick={() => handleOption(Option.SAVE_QUERY)}
164
+ />
165
+ <OverflowMenuItem
166
+ itemText={t("downloadResults", "Download Results")}
167
+ onClick={() => handleOption(Option.DOWNLOAD)}
168
+ />
169
+ <OverflowMenuItem
170
+ data-testid="deleteFromHistory"
171
+ itemText={t("deleteFromHistory", "Delete from history")}
172
+ onClick={() => handleOption(Option.DELETE)}
173
+ />
174
+ </OverflowMenu>
175
+
176
+ <ComposedModal
177
+ size={"sm"}
178
+ open={isSaveCohortModalVisible}
179
+ onClose={() => setIsSaveCohortModalVisible(false)}
180
+ >
181
+ <ModalHeader>
182
+ <p>Save Cohort</p>
183
+ </ModalHeader>
184
+ <ModalBody hasForm>
185
+ <TextInput
186
+ data-modal-primary-focus
187
+ required
188
+ labelText={t("saveName", "Enter a name")}
189
+ data-testid="cohort-name"
190
+ id="cohort-name"
191
+ onChange={(e) => setCohortName(e.target.value)}
192
+ value={cohortName}
193
+ />
194
+ <br />
195
+ <TextInput
196
+ data-modal-primary-focus
197
+ required
198
+ labelText={t("saveDescription", "Enter a description")}
199
+ data-testid="cohort-description"
200
+ id="cohort-description"
201
+ onChange={(e) => setCohortDescription(e.target.value)}
202
+ value={cohortDescription}
203
+ />
204
+ </ModalBody>
205
+ <ModalFooter>
206
+ <Button
207
+ kind="secondary"
208
+ onClick={() => setIsSaveCohortModalVisible(false)}
209
+ >
210
+ {t("cancel", "Cancel")}
211
+ </Button>
212
+ <Button
213
+ data-testid="cohort-save-button"
214
+ kind="primary"
215
+ onClick={saveCohort}
216
+ >
217
+ {t("save", "Save")}
218
+ </Button>
219
+ </ModalFooter>
220
+ </ComposedModal>
221
+ <ComposedModal
222
+ size={"sm"}
223
+ open={isSaveQueryModalVisible}
224
+ onClose={() => setIsSaveQueryModalVisible(false)}
225
+ >
226
+ <ModalHeader>
227
+ <p>Save Query</p>
228
+ </ModalHeader>
229
+ <ModalBody hasForm>
230
+ <TextInput
231
+ required
232
+ labelText={t("saveName", "Enter a name")}
233
+ id="query-name"
234
+ data-testid="query-name"
235
+ onChange={(e) => setQueryName(e.target.value)}
236
+ value={queryName}
237
+ />
238
+ <br />
239
+ <TextInput
240
+ required
241
+ labelText={t("saveDescription", "Enter a description")}
242
+ id="query-description"
243
+ onChange={(e) => setQueryDescription(e.target.value)}
244
+ value={queryDescription}
245
+ />
246
+ </ModalBody>
247
+ <ModalFooter>
248
+ <Button
249
+ kind="secondary"
250
+ onClick={() => setIsSaveQueryModalVisible(false)}
251
+ >
252
+ {t("cancel", "Cancel")}
253
+ </Button>
254
+ <Button
255
+ data-testid="query-save-button"
256
+ kind="primary"
257
+ onClick={saveQuery}
258
+ >
259
+ {t("save", "Save")}
260
+ </Button>
261
+ </ModalFooter>
262
+ </ComposedModal>
263
+ <ComposedModal
264
+ size={"sm"}
265
+ open={isDeleteCohortModalVisible}
266
+ onClose={() => setIsDeleteCohortModalVisible(false)}
267
+ >
268
+ <ModalHeader>
269
+ <p>
270
+ {t(
271
+ "deleteHistoryItem",
272
+ `Are you sure you want to delete ${searchItem?.description} from the search history?`,
273
+ {
274
+ searchItemName: searchItem?.description,
275
+ }
276
+ )}
277
+ </p>
278
+ </ModalHeader>
279
+ <ModalFooter
280
+ danger
281
+ onRequestSubmit={handleDeleteSearchItem}
282
+ primaryButtonText={t("delete", "Delete")}
283
+ secondaryButtonText={t("cancel", "Cancel")}
284
+ />
285
+ </ComposedModal>
286
+ </>
287
+ );
288
+ };
289
+
290
+ export default SearchHistoryOptions;
@@ -0,0 +1,29 @@
1
+ import { FetchResponse, openmrsFetch } from "@openmrs/esm-framework";
2
+
3
+ import { Cohort, Query } from "../../../types";
4
+
5
+ /**
6
+ * @returns Cohort
7
+ * @param cohort
8
+ */
9
+ export async function createCohort(
10
+ cohort: Cohort
11
+ ): Promise<FetchResponse<Cohort>> {
12
+ return await openmrsFetch("/ws/rest/v1/cohort", {
13
+ method: "POST",
14
+ headers: { "Content-Type": "application/json" },
15
+ body: cohort,
16
+ });
17
+ }
18
+
19
+ /**
20
+ * @returns Query
21
+ * @param query
22
+ */
23
+ export async function createQuery(query: Query): Promise<FetchResponse<Query>> {
24
+ return await openmrsFetch("/ws/rest/v1/reportingrest/adhocdataset", {
25
+ method: "POST",
26
+ headers: { "Content-Type": "application/json" },
27
+ body: query,
28
+ });
29
+ }