@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,25 @@
1
+ .search {
2
+ max-height: 400px;
3
+ overflow: scroll;
4
+ position: absolute;
5
+ z-index: 2;
6
+ width: 33%;
7
+ background: #f0eeee;
8
+ }
9
+
10
+ .concept {
11
+ font-weight: bold;
12
+ }
13
+
14
+ .text {
15
+ padding-top: 1.5rem;
16
+ }
17
+
18
+ .notification {
19
+ position: fixed;
20
+ left: 50%;
21
+ bottom: 20px;
22
+ transform: translate(-50%, -50%);
23
+ margin: 0 auto;
24
+ z-index: 1;
25
+ }
@@ -0,0 +1,89 @@
1
+ import React from "react";
2
+
3
+ import { render, cleanup, screen, waitFor } from "@testing-library/react";
4
+ import userEvent from "@testing-library/user-event";
5
+
6
+ import { Concept } from "../../../types";
7
+ import { SearchConcept } from "./search-concept.component";
8
+ import * as apis from "./search-concept.resource";
9
+
10
+ jest.mock("./search-concept.resource.ts");
11
+
12
+ const concepts: Concept[] = [
13
+ {
14
+ uuid: "1000AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA",
15
+ units: "",
16
+ answers: [],
17
+ hl7Abbrev: "ZZ",
18
+ name: "Whole blood sample",
19
+ description: "Blood samples not seperated into subtypes",
20
+ datatype: {
21
+ uuid: "8d4a4c94-c2cc-11de-8d13-0010c6dffd0f",
22
+ name: "N/A",
23
+ description: "Not associated with a datatype (e.g., term answers, sets)",
24
+ hl7Abbreviation: "ZZ",
25
+ },
26
+ },
27
+ {
28
+ uuid: "2a08da66-f326-4cac-b4cc-6efd68333847",
29
+ units: "mg/dl",
30
+ answers: [],
31
+ hl7Abbrev: "NM",
32
+ name: "BLOOD SUGAR",
33
+ description: "Laboratory measurement of the glucose level in the blood.",
34
+ datatype: {
35
+ uuid: "8d4a4488-c2cc-11de-8d13-0010c6dffd0f",
36
+ name: "Numeric",
37
+ description:
38
+ "Numeric value, including integer or float (e.g., creatinine, weight)",
39
+ hl7Abbreviation: "NM",
40
+ },
41
+ },
42
+ ];
43
+
44
+ describe("Test the concept search component", () => {
45
+ afterEach(cleanup);
46
+ it("should be able to search for a concept", async () => {
47
+ const user = userEvent.setup();
48
+ jest.spyOn(apis, "getConcepts").mockResolvedValue(concepts);
49
+ let searchText = "";
50
+ const setSearchText = jest
51
+ .fn()
52
+ .mockImplementation((search: string) => (searchText = search));
53
+ render(
54
+ <SearchConcept
55
+ concept={null}
56
+ setConcept={jest.fn()}
57
+ searchText={searchText}
58
+ setSearchText={setSearchText}
59
+ />
60
+ );
61
+ const searchInput = screen.getByPlaceholderText("Search Concepts");
62
+ await waitFor(() => user.click(searchInput));
63
+ await waitFor(() => user.type(searchInput, "blood s"));
64
+
65
+ await waitFor(() =>
66
+ expect(jest.spyOn(apis, "getConcepts")).toBeCalledWith(searchText)
67
+ );
68
+ expect(screen.getByText(concepts[0].name)).toBeInTheDocument();
69
+ expect(screen.getByText(concepts[1].name)).toBeInTheDocument();
70
+ });
71
+
72
+ it("should be able to clear the current search value", async () => {
73
+ const user = userEvent.setup();
74
+ const { getByLabelText, getByPlaceholderText } = render(
75
+ <SearchConcept
76
+ concept={null}
77
+ setConcept={jest.fn()}
78
+ searchText={""}
79
+ setSearchText={jest.fn()}
80
+ />
81
+ );
82
+ const searchInput = getByPlaceholderText("Search Concepts");
83
+ await waitFor(() => user.click(searchInput));
84
+ await waitFor(() => user.type(searchInput, "blood"));
85
+ const clearButton = getByLabelText("Clear search");
86
+ await waitFor(() => user.click(clearButton));
87
+ expect(searchInput.getAttribute("value")).toEqual("");
88
+ });
89
+ });
@@ -0,0 +1,209 @@
1
+ import React, { useState } from "react";
2
+
3
+ import {
4
+ DatePicker,
5
+ DatePickerInput,
6
+ Column,
7
+ NumberInput,
8
+ Switch,
9
+ ContentSwitcher,
10
+ } from "@carbon/react";
11
+ import dayjs from "dayjs";
12
+ import { useTranslation } from "react-i18next";
13
+
14
+ import { SearchByProps } from "../../types";
15
+ import SearchButtonSet from "../search-button-set/search-button-set";
16
+ import styles from "./search-by-demographics.style.scss";
17
+ import {
18
+ getDescription,
19
+ getQueryDetails,
20
+ } from "./search-by-demographics.utils";
21
+
22
+ const SearchByDemographics: React.FC<SearchByProps> = ({ onSubmit }) => {
23
+ const { t } = useTranslation();
24
+ const [livingStatus, setLivingStatus] = useState("alive");
25
+ const [gender, setGender] = useState("all");
26
+ const [birthDayStartDate, setBirthDayStartDate] = useState("");
27
+ const [birthDayEndDate, setBirthDayEndDate] = useState("");
28
+ const [minAge, setMinAge] = useState(0);
29
+ const [maxAge, setMaxAge] = useState(0);
30
+ const [isLoading, setIsLoading] = useState(false);
31
+
32
+ const genders = [
33
+ {
34
+ id: 0,
35
+ label: t("all", "All"),
36
+ value: "all",
37
+ },
38
+ {
39
+ id: 1,
40
+ label: t("males", "Male"),
41
+ value: "males",
42
+ },
43
+ {
44
+ id: 3,
45
+ label: t("females", "Female"),
46
+ value: "females",
47
+ },
48
+ ];
49
+
50
+ const livingStatuses = [
51
+ {
52
+ id: 0,
53
+ label: t("alive", "Alive"),
54
+ value: "alive",
55
+ },
56
+ {
57
+ id: 1,
58
+ label: t("dead", "Dead"),
59
+ value: "dead",
60
+ },
61
+ ];
62
+
63
+ const reset = () => {
64
+ setMaxAge(0);
65
+ setMinAge(0);
66
+ setBirthDayEndDate("");
67
+ setBirthDayStartDate("");
68
+ };
69
+
70
+ const submit = async () => {
71
+ setIsLoading(true);
72
+ const demographics = {
73
+ gender,
74
+ minAge,
75
+ maxAge,
76
+ birthDayStartDate,
77
+ birthDayEndDate,
78
+ livingStatus,
79
+ };
80
+ await onSubmit(getQueryDetails(demographics), getDescription(demographics));
81
+ setIsLoading(false);
82
+ };
83
+
84
+ return (
85
+ <>
86
+ <Column>
87
+ <p className={`${styles.text} ${styles.genderTitle}`}>
88
+ {t("gender", "Gender")}
89
+ </p>
90
+ <div className={styles.genderContainer}>
91
+ <div className={styles.switch}>
92
+ <ContentSwitcher
93
+ selectedIndex={genders[0].id}
94
+ className={styles.contentSwitcher}
95
+ size="lg"
96
+ onChange={({ index }) => setGender(genders[index].value)}
97
+ >
98
+ {genders.map((gender) => (
99
+ <Switch
100
+ data-testid={gender.label}
101
+ key={gender.id}
102
+ name={gender.value}
103
+ text={gender.label}
104
+ />
105
+ ))}
106
+ </ContentSwitcher>
107
+ </div>
108
+ <div className={styles.switch}>
109
+ <ContentSwitcher
110
+ selectedIndex={livingStatuses[0].id}
111
+ className={styles.contentSwitcher}
112
+ size="lg"
113
+ onChange={({ index }) =>
114
+ setLivingStatus(livingStatuses[index].value)
115
+ }
116
+ >
117
+ {livingStatuses.map((livingStatus) => (
118
+ <Switch
119
+ key={livingStatus.id}
120
+ name={livingStatus.value}
121
+ text={livingStatus.label}
122
+ />
123
+ ))}
124
+ </ContentSwitcher>
125
+ </div>
126
+ </div>
127
+ </Column>
128
+ <div className={styles.column}>
129
+ <Column className={styles.age}>
130
+ <Column>
131
+ <NumberInput
132
+ hideSteppers={true}
133
+ id="minAge"
134
+ data-testid="minAge"
135
+ label={t("ageBetween", "Age between")}
136
+ invalidText={t(
137
+ "minAgeIsNotValid",
138
+ "The age must be greater than 0"
139
+ )}
140
+ min={0}
141
+ value={minAge}
142
+ onChange={(event, { value }) => setMinAge(value)}
143
+ />
144
+ </Column>
145
+ <Column>
146
+ <NumberInput
147
+ id="maxAge"
148
+ hideSteppers={true}
149
+ data-testid="maxAge"
150
+ label={t("and", "and")}
151
+ invalidText={t(
152
+ "maxAgeIsNotValid",
153
+ "The age must be less than 200"
154
+ )}
155
+ min={0}
156
+ max={200}
157
+ value={maxAge}
158
+ onChange={(event, { value }) => setMaxAge(value)}
159
+ />
160
+ </Column>
161
+ </Column>
162
+ </div>
163
+ <div className={styles.column}>
164
+ <Column>
165
+ <DatePicker
166
+ datePickerType="single"
167
+ allowInput={false}
168
+ onChange={(date) => setBirthDayStartDate(dayjs(date[0]).format())}
169
+ >
170
+ <DatePickerInput
171
+ id="startDate"
172
+ labelText={t("birthDate", "Birth date between")}
173
+ value={
174
+ birthDayStartDate &&
175
+ dayjs(birthDayStartDate).format("DD-MM-YYYY")
176
+ }
177
+ placeholder="DD-MM-YYYY"
178
+ size="md"
179
+ />
180
+ </DatePicker>
181
+ </Column>
182
+ <Column>
183
+ <DatePicker
184
+ datePickerType="single"
185
+ allowInput={false}
186
+ onChange={(date) => setBirthDayEndDate(dayjs(date[0]).format())}
187
+ >
188
+ <DatePickerInput
189
+ id="endDate"
190
+ labelText={t("and", "and")}
191
+ value={
192
+ birthDayEndDate && dayjs(birthDayEndDate).format("DD-MM-YYYY")
193
+ }
194
+ placeholder="DD-MM-YYYY"
195
+ size="md"
196
+ />
197
+ </DatePicker>
198
+ </Column>
199
+ </div>
200
+ <SearchButtonSet
201
+ isLoading={isLoading}
202
+ onHandleSubmit={submit}
203
+ onHandleReset={reset}
204
+ />
205
+ </>
206
+ );
207
+ };
208
+
209
+ export default SearchByDemographics;
@@ -0,0 +1,41 @@
1
+ @use '@carbon/styles/scss/spacing';
2
+ @import "~@openmrs/esm-styleguide/src/vars";
3
+
4
+ .contentSwitcher {
5
+ height: spacing.$spacing-08;
6
+ }
7
+
8
+ .column {
9
+ padding-top: 1.5rem;
10
+ display: flex;
11
+ }
12
+
13
+ .text {
14
+ padding-right: 1rem;
15
+ font-size: 0.75rem;
16
+ color: #525252;
17
+ }
18
+
19
+ .switch {
20
+ width: 60%;
21
+ padding-right: 1rem;
22
+ }
23
+
24
+ .genderContainer{
25
+ display: flex;
26
+ align-items: end;
27
+ }
28
+
29
+ .ageContainer {
30
+ display: flex;
31
+ justify-content: space-between;
32
+ width: 34%;
33
+ }
34
+
35
+ .age {
36
+ display: flex;
37
+ }
38
+
39
+ .genderTitle {
40
+ padding-bottom: 0.5rem;
41
+ }
@@ -0,0 +1,92 @@
1
+ import React from "react";
2
+
3
+ import { render, cleanup, fireEvent, waitFor } from "@testing-library/react";
4
+ import userEvent from "@testing-library/user-event";
5
+ import dayjs from "dayjs";
6
+
7
+ import SearchByDemographics from "./search-by-demographics.component";
8
+
9
+ const expectedQuery = {
10
+ query: {
11
+ type: "org.openmrs.module.reporting.dataset.definition.PatientDataSetDefinition",
12
+ columns: [
13
+ {
14
+ name: "firstname",
15
+ key: "reporting.library.patientDataDefinition.builtIn.preferredName.givenName",
16
+ type: "org.openmrs.module.reporting.data.patient.definition.PatientDataDefinition",
17
+ },
18
+ {
19
+ name: "lastname",
20
+ key: "reporting.library.patientDataDefinition.builtIn.preferredName.familyName",
21
+ type: "org.openmrs.module.reporting.data.patient.definition.PatientDataDefinition",
22
+ },
23
+ {
24
+ name: "gender",
25
+ key: "reporting.library.patientDataDefinition.builtIn.gender",
26
+ type: "org.openmrs.module.reporting.data.patient.definition.PatientDataDefinition",
27
+ },
28
+ {
29
+ name: "age",
30
+ key: "reporting.library.patientDataDefinition.builtIn.ageOnDate.fullYears",
31
+ type: "org.openmrs.module.reporting.data.patient.definition.PatientDataDefinition",
32
+ },
33
+ {
34
+ name: "patientId",
35
+ key: "reporting.library.patientDataDefinition.builtIn.patientId",
36
+ type: "org.openmrs.module.reporting.data.patient.definition.PatientDataDefinition",
37
+ },
38
+ ],
39
+ rowFilters: [
40
+ {
41
+ key: "reporting.library.cohortDefinition.builtIn.males",
42
+ type: "org.openmrs.module.reporting.dataset.definition.PatientDataSetDefinition",
43
+ },
44
+ {
45
+ key: "reporting.library.cohortDefinition.builtIn.ageRangeOnDate",
46
+ parameterValues: {
47
+ minAge: "10",
48
+ maxAge: "20",
49
+ },
50
+ type: "org.openmrs.module.reporting.dataset.definition.PatientDataSetDefinition",
51
+ },
52
+ {
53
+ key: "reporting.library.cohortDefinition.builtIn.diedDuringPeriod",
54
+ parameterValues: {
55
+ endDate: "2022-07-09T17:12:47+05:30",
56
+ },
57
+ type: "org.openmrs.module.reporting.dataset.definition.PatientDataSetDefinition",
58
+ },
59
+ ],
60
+ customRowFilterCombination: "1 AND 2 AND NOT 3",
61
+ },
62
+ };
63
+
64
+ describe("Test the search by demographics component", () => {
65
+ afterEach(cleanup);
66
+
67
+ it("should be able to select input values", async () => {
68
+ const submit = jest.fn();
69
+ const { getByTestId } = render(<SearchByDemographics onSubmit={submit} />);
70
+
71
+ fireEvent.click(getByTestId("Male"));
72
+ const minAgeInput = getByTestId("minAge");
73
+ const maxAgeInput = getByTestId("maxAge");
74
+
75
+ fireEvent.click(minAgeInput);
76
+ await waitFor(() => userEvent.type(minAgeInput, "10"));
77
+
78
+ fireEvent.click(maxAgeInput);
79
+ await waitFor(() => userEvent.type(maxAgeInput, "20"));
80
+
81
+ expectedQuery.query.rowFilters[2].parameterValues.endDate =
82
+ dayjs().format();
83
+ fireEvent.click(getByTestId("search-btn"));
84
+
85
+ await waitFor(() => {
86
+ expect(submit).toBeCalledWith(
87
+ expectedQuery,
88
+ "Male Patients with ages between 10 and 20 years that are alive"
89
+ );
90
+ });
91
+ });
92
+ });
@@ -0,0 +1,129 @@
1
+ import dayjs from "dayjs";
2
+
3
+ import { composeJson } from "../../cohort-builder.utils";
4
+
5
+ interface DemographicsSearchParams {
6
+ gender: string;
7
+ ageRangeOnDate?: Value[];
8
+ atLeastAgeOnDate?: Value[];
9
+ upToAgeOnDate?: Value[];
10
+ bornDuringPeriod?: Value[];
11
+ diedDuringPeriod?: Value[];
12
+ }
13
+
14
+ interface Value {
15
+ name: string;
16
+ value: number | string;
17
+ dataType?: string;
18
+ livingStatus?: string;
19
+ }
20
+
21
+ interface Demographics {
22
+ gender: string;
23
+ minAge: number;
24
+ maxAge: number;
25
+ birthDayStartDate: string;
26
+ birthDayEndDate: string;
27
+ livingStatus: string;
28
+ }
29
+
30
+ export const getDescription = ({
31
+ gender,
32
+ minAge,
33
+ maxAge,
34
+ birthDayStartDate,
35
+ birthDayEndDate,
36
+ livingStatus,
37
+ }: Demographics) => {
38
+ let description =
39
+ gender != "all"
40
+ ? `${gender === "males" ? "Male" : "Female"} Patients`
41
+ : "All Patients";
42
+ if (minAge || maxAge) {
43
+ if (minAge && maxAge) {
44
+ description += ` with ages between ${minAge} and ${maxAge}`;
45
+ } else {
46
+ description += minAge
47
+ ? ` with minimum age of ${minAge}`
48
+ : ` with maximum age of ${maxAge}`;
49
+ }
50
+ description += " years";
51
+ }
52
+ if (birthDayStartDate != "" || birthDayEndDate != "") {
53
+ if (birthDayStartDate && birthDayEndDate) {
54
+ description += ` and birthdate between ${birthDayStartDate} and ${birthDayEndDate}`;
55
+ } else {
56
+ description += minAge
57
+ ? ` and born before ${birthDayStartDate}`
58
+ : ` and born before ${birthDayEndDate}`;
59
+ }
60
+ }
61
+ if (livingStatus) {
62
+ description += ` that are ${livingStatus}`;
63
+ }
64
+ return description;
65
+ };
66
+
67
+ export const getQueryDetails = ({
68
+ gender,
69
+ minAge,
70
+ maxAge,
71
+ birthDayStartDate,
72
+ birthDayEndDate,
73
+ livingStatus,
74
+ }: Demographics) => {
75
+ const searchParameters: DemographicsSearchParams = { gender };
76
+
77
+ if (minAge && maxAge) {
78
+ searchParameters.ageRangeOnDate = [
79
+ { name: "minAge", value: minAge },
80
+ { name: "maxAge", value: maxAge },
81
+ ];
82
+ } else {
83
+ if (minAge) {
84
+ searchParameters.atLeastAgeOnDate = [{ name: "minAge", value: minAge }];
85
+ } else {
86
+ searchParameters.upToAgeOnDate = [{ name: "maxAge", value: maxAge }];
87
+ }
88
+ }
89
+ if (birthDayStartDate && birthDayEndDate) {
90
+ searchParameters.bornDuringPeriod = [
91
+ { name: "startDate", dataType: "date", value: birthDayStartDate },
92
+ { name: "endDate", dataType: "date", value: birthDayEndDate },
93
+ ];
94
+ }
95
+
96
+ const today = dayjs().format();
97
+ searchParameters.diedDuringPeriod = [
98
+ { name: "endDate", dataType: "date", value: today, livingStatus },
99
+ ];
100
+
101
+ const queryDetails = composeJson(searchParameters);
102
+
103
+ if (searchParameters.gender === "all") {
104
+ const rowFilterWithAllGenders = ["males", "females", "unknownGender"].map(
105
+ (gender) => {
106
+ return {
107
+ type: "org.openmrs.module.reporting.dataset.definition.PatientDataSetDefinition",
108
+ key: `reporting.library.cohortDefinition.builtIn.${gender}`,
109
+ };
110
+ }
111
+ );
112
+ let {
113
+ query: { rowFilters, customRowFilterCombination },
114
+ } = queryDetails;
115
+ rowFilters =
116
+ rowFilters.length > 0
117
+ ? [...rowFilters, ...rowFilterWithAllGenders]
118
+ : rowFilterWithAllGenders;
119
+ const filters = rowFilters;
120
+ const filterCombination = customRowFilterCombination;
121
+ customRowFilterCombination = filterCombination
122
+ ? `(${filters.length - 2} OR ${filters.length - 1} OR ${
123
+ filters.length
124
+ }) AND ${filterCombination}`
125
+ : "(1 OR 2 OR 3)";
126
+ }
127
+
128
+ return queryDetails;
129
+ };