@openmrs/esm-fast-data-entry-app 1.0.1-pre.10 → 1.0.1-pre.17

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 (61) hide show
  1. package/dist/132.js +1 -0
  2. package/dist/247.js +1 -0
  3. package/dist/255.js +1 -0
  4. package/dist/294.js +2 -0
  5. package/dist/294.js.LICENSE.txt +9 -0
  6. package/dist/32.js +1 -0
  7. package/dist/327.js +1 -0
  8. package/dist/403.js +2 -0
  9. package/dist/403.js.LICENSE.txt +14 -0
  10. package/dist/553.js +2 -0
  11. package/dist/553.js.LICENSE.txt +14 -0
  12. package/dist/569.js +2 -0
  13. package/dist/569.js.LICENSE.txt +27 -0
  14. package/dist/574.js +1 -0
  15. package/dist/595.js +2 -0
  16. package/dist/595.js.LICENSE.txt +1 -0
  17. package/dist/617.js +1 -0
  18. package/dist/68.js +2 -0
  19. package/dist/68.js.LICENSE.txt +21 -0
  20. package/dist/776.js +1 -0
  21. package/dist/804.js +1 -0
  22. package/dist/820.js +1 -0
  23. package/dist/906.js +1 -0
  24. package/dist/935.js +2 -0
  25. package/dist/935.js.LICENSE.txt +19 -0
  26. package/dist/main.js +1 -0
  27. package/dist/openmrs-esm-fast-data-entry-app.js +1 -1
  28. package/dist/openmrs-esm-fast-data-entry-app.js.buildmanifest.json +544 -0
  29. package/dist/openmrs-esm-fast-data-entry-app.old +1 -0
  30. package/package.json +5 -3
  31. package/src/add-group-modal/AddGroupModal.tsx +80 -27
  32. package/src/add-group-modal/styles.scss +14 -4
  33. package/src/context/GroupFormWorkflowContext.tsx +2 -0
  34. package/src/context/GroupFormWorkflowReducer.ts +26 -2
  35. package/src/group-form-entry-workflow/GroupFormEntryWorkflow.tsx +2 -2
  36. package/src/group-form-entry-workflow/{group-banner/GroupBanner.test.tsx → group-display-header/GroupDisplayHeader.test.tsx} +2 -2
  37. package/src/group-form-entry-workflow/{group-banner/GroupBanner.tsx → group-display-header/GroupDisplayHeader.tsx} +31 -5
  38. package/src/group-form-entry-workflow/group-display-header/index.ts +3 -0
  39. package/src/group-form-entry-workflow/{group-banner → group-display-header}/styles.scss +0 -0
  40. package/src/group-form-entry-workflow/group-search/CompactGroupResults.tsx +61 -28
  41. package/src/group-form-entry-workflow/group-search/CompactGroupSearch.tsx +5 -0
  42. package/src/group-form-entry-workflow/group-search/GroupSearch.tsx +65 -8
  43. package/src/group-form-entry-workflow/group-search/group-search.scss +8 -6
  44. package/src/group-form-entry-workflow/group-search-header/GroupSearchHeader.tsx +14 -7
  45. package/src/group-form-entry-workflow/styles.scss +10 -0
  46. package/src/hooks/index.ts +1 -0
  47. package/src/hooks/usePostEndpoint.ts +70 -0
  48. package/src/hooks/useSearchEndpoint.ts +120 -0
  49. package/translations/en.json +6 -1
  50. package/.editorconfig +0 -12
  51. package/.eslintignore +0 -2
  52. package/.eslintrc.js +0 -10
  53. package/.husky/pre-push +0 -1
  54. package/.prettierignore +0 -14
  55. package/.tx/config +0 -9
  56. package/.yarn/plugins/@yarnpkg/plugin-version.cjs +0 -550
  57. package/.yarn/versions/45b499b6.yml +0 -0
  58. package/src/group-form-entry-workflow/group-banner/index.ts +0 -3
  59. package/src/group-form-entry-workflow/group-search/mock-group-data.ts +0 -79
  60. package/src/group-form-entry-workflow/group-search/useGroupSearch.ts +0 -14
  61. package/src/hooks/usePostCohort.ts +0 -18
@@ -1,4 +1,4 @@
1
- import React, { useCallback, useContext, useState } from "react";
1
+ import React, { useCallback, useContext, useEffect, useState } from "react";
2
2
  import {
3
3
  ComposedModal,
4
4
  Button,
@@ -7,12 +7,14 @@ import {
7
7
  ModalBody,
8
8
  TextInput,
9
9
  FormLabel,
10
+ Loading,
10
11
  } from "@carbon/react";
11
- import { Add, Close } from "@carbon/react/icons";
12
+ import { Add, TrashCan } from "@carbon/react/icons";
12
13
  import { useTranslation } from "react-i18next";
13
- import { ExtensionSlot } from "@openmrs/esm-framework";
14
+ import { ExtensionSlot, showToast } from "@openmrs/esm-framework";
14
15
  import styles from "./styles.scss";
15
16
  import GroupFormWorkflowContext from "../context/GroupFormWorkflowContext";
17
+ import { usePostCohort } from "../hooks";
16
18
 
17
19
  const MemExtension = React.memo(ExtensionSlot);
18
20
 
@@ -20,18 +22,19 @@ const PatientRow = ({ patient, removePatient }) => {
20
22
  const { t } = useTranslation();
21
23
  return (
22
24
  <li key={patient.uuid} className={styles.patientRow}>
23
- <span className={styles.patientName}>{patient?.display}</span>
24
25
  <span>
25
26
  <Button
26
27
  kind="tertiary"
27
28
  size="sm"
29
+ hasIconOnly
28
30
  onClick={() => removePatient(patient.uuid)}
29
- renderIcon={Close}
30
- tooltipPosition="right"
31
- >
32
- {t("remove", "Remove")}
33
- </Button>
31
+ renderIcon={TrashCan}
32
+ tooltipAlignment="start"
33
+ tooltipPosition="top"
34
+ iconDescription={t("remove", "Remove")}
35
+ />
34
36
  </span>
37
+ <span className={styles.patientName}>{patient?.display}</span>
35
38
  </li>
36
39
  );
37
40
  };
@@ -64,17 +67,21 @@ const NewGroupForm = (props) => {
64
67
  />
65
68
  {errors?.name && (
66
69
  <p className={styles.formError}>
67
- {t("groupNameError", "Please enter a group name.")}
70
+ {errors.name === "required"
71
+ ? t("groupNameError", "Please enter a group name.")
72
+ : errors.name}
68
73
  </p>
69
74
  )}
70
- <FormLabel>Patients in group</FormLabel>
75
+ <FormLabel>
76
+ {patientList.length} {t("patientsInGroup", "Patients in group")}
77
+ </FormLabel>
71
78
  {errors?.patientList && (
72
79
  <p className={styles.formError}>
73
80
  {t("noPatientError", "Please enter at least one patient.")}
74
81
  </p>
75
82
  )}
76
83
  {!errors?.patientList && (
77
- <ul>
84
+ <ul className={styles.patientList}>
78
85
  {patientList?.map((patient, index) => (
79
86
  <PatientRow
80
87
  patient={patient}
@@ -92,7 +99,7 @@ const NewGroupForm = (props) => {
92
99
  state={{
93
100
  selectPatientAction: updatePatientList,
94
101
  buttonProps: {
95
- kind: "primary",
102
+ kind: "secondary",
96
103
  },
97
104
  }}
98
105
  />
@@ -108,6 +115,7 @@ const AddGroupModal = () => {
108
115
  const [errors, setErrors] = useState({});
109
116
  const [name, setName] = useState("");
110
117
  const [patientList, setPatientList] = useState([]);
118
+ const { post, result, isPosting, error } = usePostCohort();
111
119
 
112
120
  const handleCancel = () => {
113
121
  setOpen(false);
@@ -165,10 +173,46 @@ const AddGroupModal = () => {
165
173
 
166
174
  const handleSubmit = () => {
167
175
  if (validate()) {
168
- setGroup({ id: "1234", name: name, members: patientList });
176
+ post({
177
+ name: name,
178
+ cohortMembers: patientList.map((p) => ({ patient: p.uuid })),
179
+ });
169
180
  }
170
181
  };
171
182
 
183
+ useEffect(() => {
184
+ if (result) {
185
+ setGroup({
186
+ ...result,
187
+ // the result doesn't come with cohortMembers.
188
+ // need to add it in based on our local state
189
+ cohortMembers: patientList.map((p) => ({ patient: { uuid: p.uuid } })),
190
+ });
191
+ }
192
+ }, [result, setGroup, patientList]);
193
+
194
+ useEffect(() => {
195
+ if (error) {
196
+ showToast({
197
+ kind: "error",
198
+ title: t("postError", "POST Error"),
199
+ description:
200
+ error.message ??
201
+ t("unknownPostError", "An unknown error occured while saving data"),
202
+ });
203
+ if (error.fieldErrors) {
204
+ setErrors(
205
+ Object.fromEntries(
206
+ Object.entries(error.fieldErrors).map(([key, value]) => [
207
+ key,
208
+ value?.[0]?.message,
209
+ ])
210
+ )
211
+ );
212
+ }
213
+ }
214
+ }, [error, t]);
215
+
172
216
  return (
173
217
  <div className={styles.modal}>
174
218
  <Button
@@ -181,23 +225,32 @@ const AddGroupModal = () => {
181
225
  <ComposedModal open={open} onClose={() => setOpen(false)}>
182
226
  <ModalHeader>{t("createNewGroup", "Create New Group")}</ModalHeader>
183
227
  <ModalBody>
184
- <NewGroupForm
185
- {...{
186
- name,
187
- setName,
188
- patientList,
189
- updatePatientList,
190
- errors,
191
- validate,
192
- removePatient,
193
- }}
194
- />
228
+ {result ? (
229
+ <p>Group saved succesfully</p>
230
+ ) : isPosting ? (
231
+ <div className={styles.loading}>
232
+ <Loading withOverlay={false} />
233
+ <span>Saving new group...</span>
234
+ </div>
235
+ ) : (
236
+ <NewGroupForm
237
+ {...{
238
+ name,
239
+ setName,
240
+ patientList,
241
+ updatePatientList,
242
+ errors,
243
+ validate,
244
+ removePatient,
245
+ }}
246
+ />
247
+ )}
195
248
  </ModalBody>
196
249
  <ModalFooter>
197
- <Button kind="secondary" onClick={handleCancel}>
250
+ <Button kind="secondary" onClick={handleCancel} disabled={isPosting}>
198
251
  {t("cancel", "Cancel")}
199
252
  </Button>
200
- <Button kind="primary" onClick={handleSubmit}>
253
+ <Button kind="primary" onClick={handleSubmit} disabled={isPosting}>
201
254
  {t("createGroup", "Create Group")}
202
255
  </Button>
203
256
  </ModalFooter>
@@ -23,13 +23,23 @@
23
23
 
24
24
  .patientRow {
25
25
  display: flex;
26
+ align-items: center;
26
27
  width: "100%";
28
+ &:nth-child(odd) {
29
+ background-color: colors.$gray-20;
30
+ }
27
31
  }
28
32
 
29
33
  .patientName {
30
34
  flex-grow: 1;
31
- padding: spacing.$spacing-02;
32
- &:hover {
33
- background-color: colors.$gray-20;
34
- }
35
+ padding-left: spacing.$spacing-05;
36
+ }
37
+
38
+ .loading {
39
+ display: flex;
40
+ height: 100%;
41
+ flex-direction: column;
42
+ justify-content: center;
43
+ align-items: center;
44
+ row-gap: spacing.$spacing-05;
35
45
  }
@@ -20,6 +20,7 @@ export interface MetaType {
20
20
 
21
21
  const initialActions = {
22
22
  setGroup: (group: GroupType) => undefined,
23
+ unsetGroup: () => undefined,
23
24
  setSessionMeta: (meta: MetaType) => undefined,
24
25
  openPatientSearch: () => undefined,
25
26
  saveEncounter: (encounterUuid: string | number) => undefined,
@@ -75,6 +76,7 @@ const GroupFormWorkflowProvider = ({ children }) => {
75
76
  activeFormUuid,
76
77
  }),
77
78
  setGroup: (group) => dispatch({ type: "SET_GROUP", group }),
79
+ unsetGroup: () => dispatch({ type: "UNSET_GROUP" }),
78
80
  setSessionMeta: (meta) => dispatch({ type: "SET_SESSION_META", meta }),
79
81
  openPatientSearch: () => dispatch({ type: "OPEN_PATIENT_SEARCH" }),
80
82
  saveEncounter: (encounterUuid) =>
@@ -75,9 +75,33 @@ const reducer = (state, action) => {
75
75
  ...state.forms,
76
76
  [state.activeFormUuid]: {
77
77
  ...state.forms[state.activeFormUuid],
78
- groupUuid: action.group.id,
78
+ groupUuid: action.group.uuid,
79
79
  groupName: action.group.name,
80
- patientUuids: action.group.members.map((member) => member.uuid),
80
+ patientUuids:
81
+ // this translation is not preferred
82
+ // the only reason we tollerate it here is beause it should be the only time
83
+ // we add cohort information to state
84
+ action.group.cohortMembers?.map(
85
+ (member) => member?.patient?.uuid
86
+ ) ?? [],
87
+ activePatientUuid: null,
88
+ activeEncounterUuid: null,
89
+ },
90
+ },
91
+ };
92
+ persistData(newState);
93
+ return newState;
94
+ }
95
+ case "UNSET_GROUP": {
96
+ const newState = {
97
+ ...state,
98
+ forms: {
99
+ ...state.forms,
100
+ [state.activeFormUuid]: {
101
+ ...state.forms[state.activeFormUuid],
102
+ groupUuid: null,
103
+ groupName: null,
104
+ patientUuids: [],
81
105
  activePatientUuid: null,
82
106
  activeEncounterUuid: null,
83
107
  },
@@ -19,7 +19,7 @@ import {
19
19
  import React, { useContext, useEffect, useState } from "react";
20
20
  import { useNavigate } from "react-router-dom";
21
21
  import PatientCard from "../patient-card/PatientCard";
22
- import GroupBanner from "./group-banner";
22
+ import GroupDisplayHeader from "./group-display-header";
23
23
  import styles from "./styles.scss";
24
24
  import { useTranslation } from "react-i18next";
25
25
  import GroupFormWorkflowContext, {
@@ -387,7 +387,7 @@ const GroupFormEntryWorkflow = () => {
387
387
  <ExtensionSlot extensionSlotName="breadcrumbs-slot" />
388
388
  </div>
389
389
  <GroupSearchHeader />
390
- <GroupBanner />
390
+ <GroupDisplayHeader />
391
391
  {workflowState === "NEW_GROUP_SESSION" && (
392
392
  <div className={styles.workspaceWrapper}>
393
393
  <SessionMetaWorkspace />
@@ -1,9 +1,9 @@
1
1
  import React from "react";
2
2
  import { render } from "@testing-library/react";
3
- import GroupBanner from "./GroupBanner";
3
+ import GroupDisplayHeader from "./GroupDisplayHeader";
4
4
 
5
5
  describe("PatientBanner", () => {
6
6
  it("renders placeholder information when no data is present", () => {
7
- render(<GroupBanner />);
7
+ render(<GroupDisplayHeader />);
8
8
  });
9
9
  });
@@ -1,12 +1,20 @@
1
1
  import React, { useContext } from "react";
2
- import { Events } from "@carbon/react/icons";
2
+ import { Button } from "@carbon/react";
3
+ import { Events, Close } from "@carbon/react/icons";
3
4
  import styles from "./styles.scss";
4
5
  import { useTranslation } from "react-i18next";
5
6
  import GroupFormWorkflowContext from "../../context/GroupFormWorkflowContext";
7
+ import { navigate } from "@openmrs/esm-framework";
6
8
 
7
- const GroupBanner = () => {
8
- const { activeGroupName, activeGroupUuid, patientUuids, activeSessionMeta } =
9
- useContext(GroupFormWorkflowContext);
9
+ const GroupDisplayHeader = () => {
10
+ const {
11
+ activeGroupName,
12
+ activeGroupUuid,
13
+ patientUuids,
14
+ activeSessionMeta,
15
+ unsetGroup,
16
+ destroySession,
17
+ } = useContext(GroupFormWorkflowContext);
10
18
  const { t } = useTranslation();
11
19
 
12
20
  if (!activeGroupUuid) {
@@ -38,8 +46,26 @@ const GroupBanner = () => {
38
46
  </div>
39
47
  </div>
40
48
  )}
49
+ <span style={{ flexGrow: 1 }} />
50
+ <span>
51
+ <Button kind="ghost" onClick={() => unsetGroup()}>
52
+ {t("changeGroup", "Choose a different group")} <Close size={20} />
53
+ </Button>
54
+ </span>
55
+ <span>
56
+ <Button
57
+ kind="ghost"
58
+ onClick={() => {
59
+ destroySession();
60
+ // eslint-disable-next-line
61
+ navigate({ to: "${openmrsSpaBase}/forms" });
62
+ }}
63
+ >
64
+ {t("cancel", "Cancel")} <Close size={20} />
65
+ </Button>
66
+ </span>
41
67
  </div>
42
68
  );
43
69
  };
44
70
 
45
- export default GroupBanner;
71
+ export default GroupDisplayHeader;
@@ -0,0 +1,3 @@
1
+ import GroupDisplayHeader from "./GroupDisplayHeader";
2
+
3
+ export default GroupDisplayHeader;
@@ -1,4 +1,4 @@
1
- import React, { useEffect, useReducer } from "react";
1
+ import React, { useEffect, useReducer, useRef } from "react";
2
2
  import { SkeletonIcon, SkeletonText } from "@carbon/react";
3
3
  import { Events } from "@carbon/react/icons";
4
4
  import styles from "./compact-group-result.scss";
@@ -20,11 +20,64 @@ const reducer = (state, action) => {
20
20
  }
21
21
  };
22
22
 
23
- const CompactGroupResults = ({ groups, selectGroupAction }) => {
23
+ const scrollingOptions = {
24
+ behavior: "smooth",
25
+ block: "nearest",
26
+ };
27
+
28
+ const ResultItem = ({
29
+ index,
30
+ selectGroupAction,
31
+ group,
32
+ dispatch,
33
+ state,
34
+ totalGroups,
35
+ lastRef,
36
+ }) => {
37
+ const ref = useRef(null);
38
+ const { t } = useTranslation();
39
+
40
+ useEffect(() => {
41
+ if (state.selectedIndex === totalGroups - 1) {
42
+ lastRef.current.scrollIntoView(scrollingOptions);
43
+ } else if (state.selectedIndex === index) {
44
+ ref.current.scrollIntoView(scrollingOptions);
45
+ }
46
+ }, [state, index, totalGroups, lastRef]);
47
+
48
+ return (
49
+ <div
50
+ onClick={() => {
51
+ dispatch({ type: "select", payload: index });
52
+ selectGroupAction(group);
53
+ }}
54
+ className={`${styles.patientSearchResult} ${
55
+ index === state.selectedIndex && styles.patientSearchResultSelected
56
+ }`}
57
+ role="button"
58
+ aria-pressed={index === state.selectedIndex}
59
+ tabIndex={0}
60
+ ref={ref}
61
+ >
62
+ <div className={styles.patientAvatar} role="img">
63
+ <Events size={24} />
64
+ </div>
65
+ <div>
66
+ <h2 className={styles.patientName}>{group.name}</h2>
67
+ <p className={styles.demographics}>
68
+ {group.cohortMembers?.length ?? 0} {t("members", "members")}
69
+ <span className={styles.middot}>&middot;</span> {group.description}
70
+ </p>
71
+ </div>
72
+ </div>
73
+ );
74
+ };
75
+
76
+ const CompactGroupResults = ({ groups, selectGroupAction, lastRef }) => {
24
77
  const arrowUpPressed = useKeyPress("ArrowUp");
25
78
  const arrowDownPressed = useKeyPress("ArrowDown");
26
79
  const enterPressed = useKeyPress("Enter");
27
- const { t } = useTranslation();
80
+
28
81
  const [state, dispatch] = useReducer(reducer, { selectedIndex: 0 });
29
82
 
30
83
  useEffect(() => {
@@ -48,31 +101,11 @@ const CompactGroupResults = ({ groups, selectGroupAction }) => {
48
101
  return (
49
102
  <>
50
103
  {groups.map((group, index) => (
51
- <div
52
- onClick={() => {
53
- dispatch({ type: "select", payload: index });
54
- selectGroupAction(group);
55
- }}
56
- key={group.id}
57
- className={`${styles.patientSearchResult} ${
58
- index === state.selectedIndex && styles.patientSearchResultSelected
59
- }`}
60
- role="button"
61
- aria-pressed={index === state.selectedIndex}
62
- tabIndex={0}
63
- >
64
- <div className={styles.patientAvatar} role="img">
65
- <Events size={24} />
66
- </div>
67
- <div>
68
- <h2 className={styles.patientName}>{group.name}</h2>
69
- <p className={styles.demographics}>
70
- {group.members.length} {t("members", "members")}
71
- <span className={styles.middot}>&middot;</span>{" "}
72
- {group.description}
73
- </p>
74
- </div>
75
- </div>
104
+ <ResultItem
105
+ key={index}
106
+ totalGroups={groups.length}
107
+ {...{ lastRef, index, selectGroupAction, group, dispatch, state }}
108
+ />
76
109
  ))}
77
110
  </>
78
111
  );
@@ -4,6 +4,7 @@ import styles from "./compact-group-search.scss";
4
4
  import GroupSearch from "./GroupSearch";
5
5
  import { Button, Search } from "@carbon/react";
6
6
  import { useTranslation } from "react-i18next";
7
+ import debounce from "lodash-es/debounce";
7
8
 
8
9
  interface CompactGroupSearchProps {
9
10
  selectGroupAction?: (group: GroupType) => void;
@@ -23,6 +24,10 @@ const CompactGroupSearch: React.FC<CompactGroupSearchProps> = ({
23
24
  };
24
25
 
25
26
  const handleSearchChange = (e) => {
27
+ debounce((q) => {
28
+ setDropdownShown(!!e.length);
29
+ setQuery(q);
30
+ }, 300);
26
31
  setQuery(e);
27
32
  if (e.length) {
28
33
  setDropdownShown(true);
@@ -1,13 +1,13 @@
1
- import React from "react";
1
+ import React, { useCallback, useRef } from "react";
2
2
  import { useTranslation } from "react-i18next";
3
- import { Layer, Tile } from "@carbon/react";
3
+ import { Layer, Tile, Loading } from "@carbon/react";
4
4
  import styles from "./group-search.scss";
5
5
  import { EmptyDataIllustration } from "../../empty-state/EmptyDataIllustration";
6
- import { useGroupSearch } from "./useGroupSearch";
7
6
  import CompactGroupResults, {
8
7
  SearchResultSkeleton,
9
8
  } from "./CompactGroupResults";
10
9
  import { GroupType } from "../../context/GroupFormWorkflowContext";
10
+ import { useSearchCohortInfinite } from "../../hooks/useSearchEndpoint";
11
11
 
12
12
  interface GroupSearchProps {
13
13
  query: string;
@@ -19,8 +19,48 @@ const GroupSearch: React.FC<GroupSearchProps> = ({
19
19
  selectGroupAction,
20
20
  }) => {
21
21
  const { t } = useTranslation();
22
- const results = useGroupSearch(query);
23
- const error = false;
22
+ const {
23
+ isLoading,
24
+ data: results,
25
+ error,
26
+ loadingNewData,
27
+ setPage,
28
+ hasMore,
29
+ totalResults,
30
+ } = useSearchCohortInfinite({
31
+ searchTerm: query,
32
+ searching: !!query,
33
+ parameters: {
34
+ v: "full",
35
+ },
36
+ });
37
+
38
+ const lastItem = useRef(null);
39
+ const observer = useRef(null);
40
+ const loadingRef = useCallback(
41
+ (node) => {
42
+ if (loadingNewData) {
43
+ return;
44
+ }
45
+ if (observer.current) {
46
+ observer.current.disconnect();
47
+ }
48
+ observer.current = new IntersectionObserver(
49
+ (entries) => {
50
+ if (entries[0].isIntersecting && hasMore) {
51
+ setPage((page) => page + 1);
52
+ }
53
+ },
54
+ {
55
+ threshold: 0.75,
56
+ }
57
+ );
58
+ if (node) {
59
+ observer.current.observe(node);
60
+ }
61
+ },
62
+ [loadingNewData, hasMore, setPage]
63
+ );
24
64
 
25
65
  if (error) {
26
66
  return (
@@ -43,9 +83,19 @@ const GroupSearch: React.FC<GroupSearchProps> = ({
43
83
  );
44
84
  }
45
85
 
46
- if (query.length <= 2) return <SearchResultSkeleton />;
86
+ if (isLoading) {
87
+ return (
88
+ <div className={styles.searchResultsContainer}>
89
+ <SearchResultSkeleton />
90
+ <SearchResultSkeleton />
91
+ <SearchResultSkeleton />
92
+ <SearchResultSkeleton />
93
+ <SearchResultSkeleton />
94
+ </div>
95
+ );
96
+ }
47
97
 
48
- if (results.length === 0) {
98
+ if (results?.length === 0) {
49
99
  return (
50
100
  <div className={styles.searchResults}>
51
101
  <Layer>
@@ -79,12 +129,19 @@ const GroupSearch: React.FC<GroupSearchProps> = ({
79
129
  }}
80
130
  >
81
131
  <p className={styles.resultsText}>
82
- {results.length} {t("searchResultsText", "search result(s)")}
132
+ {totalResults} {t("searchResultsText", "search result(s)")}
83
133
  </p>
84
134
  <CompactGroupResults
85
135
  groups={results}
86
136
  selectGroupAction={selectGroupAction}
137
+ lastRef={lastItem}
87
138
  />
139
+ <div ref={lastItem}>
140
+ <div className={styles.lastItem} ref={loadingRef}>
141
+ {hasMore && <Loading withOverlay={false} small />}
142
+ {!hasMore && <p>{t("noMoreResults", "End of search results")}</p>}
143
+ </div>
144
+ </div>
88
145
  </div>
89
146
  </div>
90
147
  );
@@ -29,12 +29,7 @@
29
29
  width: 100%;
30
30
  }
31
31
 
32
- .loadingIcon {
33
- padding: spacing.$spacing-05 0;
34
- display: flex;
35
- justify-content: center;
36
- align-items: center;
37
- }
32
+
38
33
 
39
34
  .searchTerm {
40
35
  @include type.type-style('heading-03');
@@ -92,3 +87,10 @@
92
87
  @include type.type-style('body-01');
93
88
  color: $text-02;
94
89
  }
90
+
91
+ .lastItem {
92
+ padding: spacing.$spacing-05;
93
+ display: flex;
94
+ justify-content: center;
95
+ align-items: center;
96
+ }
@@ -1,16 +1,18 @@
1
1
  import { Close } from "@carbon/react/icons";
2
2
  import { Button } from "@carbon/react";
3
3
  import React, { useContext } from "react";
4
- import { Link } from "react-router-dom";
5
4
  import GroupFormWorkflowContext from "../../context/GroupFormWorkflowContext";
6
5
  import styles from "./styles.scss";
7
6
  import { useTranslation } from "react-i18next";
8
7
  import CompactGroupSearch from "../group-search/CompactGroupSearch";
9
8
  import AddGroupModal from "../../add-group-modal/AddGroupModal";
9
+ import { navigate } from "@openmrs/esm-framework";
10
10
 
11
11
  const GroupSearchHeader = () => {
12
12
  const { t } = useTranslation();
13
- const { activeGroupUuid, setGroup } = useContext(GroupFormWorkflowContext);
13
+ const { activeGroupUuid, setGroup, destroySession } = useContext(
14
+ GroupFormWorkflowContext
15
+ );
14
16
  const handleSelectGroup = (group) => {
15
17
  setGroup(group);
16
18
  };
@@ -29,11 +31,16 @@ const GroupSearchHeader = () => {
29
31
  </span>
30
32
  <span style={{ flexGrow: 1 }} />
31
33
  <span>
32
- <Link to="..">
33
- <Button kind="ghost">
34
- {t("cancel", "Cancel")} <Close size={20} />
35
- </Button>
36
- </Link>
34
+ <Button
35
+ kind="ghost"
36
+ onClick={() => {
37
+ destroySession();
38
+ // eslint-disable-next-line
39
+ navigate({ to: "${openmrsSpaBase}/forms" });
40
+ }}
41
+ >
42
+ {t("cancel", "Cancel")} <Close size={20} />
43
+ </Button>
37
44
  </span>
38
45
  </div>
39
46
  );
@@ -19,6 +19,16 @@
19
19
  width: 1100px;
20
20
  }
21
21
 
22
+ :global(.omrs-breakpoint-lt-large-desktop) .workspace {
23
+ width: 1000px;
24
+ }
25
+
26
+ :global(.omrs-breakpoint-lt-small-desktop) .workspace {
27
+ // there's only so much we can do here. Currenlty the design does not support tablet
28
+ width: 100vw;
29
+ padding: 0 spacing.$spacing-04;
30
+ }
31
+
22
32
  .selectPatientMessage {
23
33
  @include type.type-style('productive-heading-03');
24
34
  margin: spacing.$spacing-07;