@kenyaemr/esm-admin-app 5.4.3 → 5.4.4-pre.100

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 (296) hide show
  1. package/.turbo/turbo-build.log +5 -12
  2. package/dist/1074.js +1 -0
  3. package/dist/1074.js.map +1 -0
  4. package/dist/12.js +17 -0
  5. package/dist/12.js.map +1 -0
  6. package/dist/1201.js +1 -0
  7. package/dist/1201.js.map +1 -0
  8. package/dist/1242.js +1 -0
  9. package/dist/1242.js.map +1 -0
  10. package/dist/1311.js +1 -0
  11. package/dist/1311.js.map +1 -0
  12. package/dist/1462.js +1 -0
  13. package/dist/1462.js.map +1 -0
  14. package/dist/1469.js +1 -0
  15. package/dist/1469.js.map +1 -0
  16. package/dist/1506.js +13 -0
  17. package/dist/1506.js.map +1 -0
  18. package/dist/1718.js +1 -0
  19. package/dist/1718.js.map +1 -0
  20. package/dist/1722.js +1 -0
  21. package/dist/1722.js.map +1 -0
  22. package/dist/1772.js +1 -0
  23. package/dist/1772.js.map +1 -0
  24. package/dist/1889.js +1 -0
  25. package/dist/1889.js.map +1 -0
  26. package/dist/1972.js +1 -0
  27. package/dist/1972.js.map +1 -0
  28. package/dist/1990.js +1 -0
  29. package/dist/1990.js.map +1 -0
  30. package/dist/2016.js +1 -0
  31. package/dist/2016.js.map +1 -0
  32. package/dist/2080.js +1 -0
  33. package/dist/2080.js.map +1 -0
  34. package/dist/2096.js +1 -0
  35. package/dist/2096.js.map +1 -0
  36. package/dist/2153.js +1 -0
  37. package/dist/2153.js.map +1 -0
  38. package/dist/216.js +1 -0
  39. package/dist/216.js.map +1 -0
  40. package/dist/2270.js +1 -0
  41. package/dist/2270.js.map +1 -0
  42. package/dist/2294.js +1 -0
  43. package/dist/2294.js.map +1 -0
  44. package/dist/2345.js +1 -0
  45. package/dist/2345.js.map +1 -0
  46. package/dist/2402.js +1 -0
  47. package/dist/2402.js.map +1 -0
  48. package/dist/2500.js +1 -0
  49. package/dist/2500.js.map +1 -0
  50. package/dist/251.js +1 -0
  51. package/dist/251.js.map +1 -0
  52. package/dist/257.js +1 -0
  53. package/dist/257.js.map +1 -0
  54. package/dist/2586.js +1 -0
  55. package/dist/2586.js.map +1 -0
  56. package/dist/2625.js +1 -0
  57. package/dist/2625.js.map +1 -0
  58. package/dist/2652.js +1 -0
  59. package/dist/2652.js.map +1 -0
  60. package/dist/2685.js +1 -0
  61. package/dist/2685.js.map +1 -0
  62. package/dist/2948.js +1 -0
  63. package/dist/2948.js.map +1 -0
  64. package/dist/3089.js +1 -0
  65. package/dist/3089.js.map +1 -0
  66. package/dist/3190.js +1 -0
  67. package/dist/3190.js.map +1 -0
  68. package/dist/3224.js +1 -0
  69. package/dist/3224.js.map +1 -0
  70. package/dist/3366.js +1 -0
  71. package/dist/3366.js.map +1 -0
  72. package/dist/349.js +1 -0
  73. package/dist/349.js.map +1 -0
  74. package/dist/3548.js +1 -0
  75. package/dist/3548.js.map +1 -0
  76. package/dist/3571.js +1 -0
  77. package/dist/3571.js.map +1 -0
  78. package/dist/3691.js +1 -0
  79. package/dist/3691.js.map +1 -0
  80. package/dist/3775.js +1 -0
  81. package/dist/3775.js.map +1 -0
  82. package/dist/3816.js +1 -0
  83. package/dist/3816.js.map +1 -0
  84. package/dist/3852.js +1 -0
  85. package/dist/3852.js.map +1 -0
  86. package/dist/3906.js +1 -0
  87. package/dist/3906.js.map +1 -0
  88. package/dist/3963.js +1 -0
  89. package/dist/3963.js.map +1 -0
  90. package/dist/405.js +1 -0
  91. package/dist/405.js.map +1 -0
  92. package/dist/4296.js +1 -0
  93. package/dist/4296.js.map +1 -0
  94. package/dist/4337.js +1 -0
  95. package/dist/4337.js.map +1 -0
  96. package/dist/4735.js +1 -0
  97. package/dist/4735.js.map +1 -0
  98. package/dist/4744.js +1 -0
  99. package/dist/4744.js.map +1 -0
  100. package/dist/4813.js +2 -0
  101. package/dist/4813.js.map +1 -0
  102. package/dist/4858.js +1 -0
  103. package/dist/4858.js.map +1 -0
  104. package/dist/487.js +1 -0
  105. package/dist/487.js.map +1 -0
  106. package/dist/4970.js +1 -0
  107. package/dist/4970.js.map +1 -0
  108. package/dist/5202.js +1 -0
  109. package/dist/5202.js.map +1 -0
  110. package/dist/5294.js +1 -0
  111. package/dist/5294.js.map +1 -0
  112. package/dist/545.js +1 -0
  113. package/dist/545.js.map +1 -0
  114. package/dist/552.js +1 -0
  115. package/dist/552.js.map +1 -0
  116. package/dist/5592.js +1 -0
  117. package/dist/5592.js.map +1 -0
  118. package/dist/5669.js +1 -0
  119. package/dist/5669.js.map +1 -0
  120. package/dist/5884.js +1 -0
  121. package/dist/5884.js.map +1 -0
  122. package/dist/5940.js +1 -0
  123. package/dist/5940.js.map +1 -0
  124. package/dist/6092.js +1 -0
  125. package/dist/6092.js.map +1 -0
  126. package/dist/6155.js +1 -0
  127. package/dist/6155.js.map +1 -0
  128. package/dist/6178.js +1 -0
  129. package/dist/6178.js.map +1 -0
  130. package/dist/6399.js +1 -0
  131. package/dist/6399.js.map +1 -0
  132. package/dist/6456.js +1 -0
  133. package/dist/6466.js +3 -0
  134. package/dist/6466.js.map +1 -0
  135. package/dist/6492.js +1 -0
  136. package/dist/6492.js.map +1 -0
  137. package/dist/6676.js +1 -0
  138. package/dist/6676.js.map +1 -0
  139. package/dist/6800.js +1 -0
  140. package/dist/6800.js.map +1 -0
  141. package/dist/6976.js +1 -0
  142. package/dist/6976.js.map +1 -0
  143. package/dist/7005.js +1 -0
  144. package/dist/7005.js.map +1 -0
  145. package/dist/7201.js +1 -0
  146. package/dist/7201.js.map +1 -0
  147. package/dist/7210.js +1 -0
  148. package/dist/7210.js.map +1 -0
  149. package/dist/7234.js +1 -0
  150. package/dist/7234.js.map +1 -0
  151. package/dist/7261.js +1 -0
  152. package/dist/7261.js.map +1 -0
  153. package/dist/7326.js +1 -0
  154. package/dist/7463.js +1 -0
  155. package/dist/7463.js.map +1 -0
  156. package/dist/7528.js +1 -0
  157. package/dist/7528.js.map +1 -0
  158. package/dist/7584.js +1 -0
  159. package/dist/7584.js.map +1 -0
  160. package/dist/7607.js +1 -0
  161. package/dist/7717.js +1 -0
  162. package/dist/7717.js.map +1 -0
  163. package/dist/7737.js +1 -0
  164. package/dist/7737.js.map +1 -0
  165. package/dist/7739.js +1 -0
  166. package/dist/7739.js.map +1 -0
  167. package/dist/7765.js +1 -0
  168. package/dist/7765.js.map +1 -0
  169. package/dist/7820.js +1 -0
  170. package/dist/7820.js.map +1 -0
  171. package/dist/7844.js +1 -0
  172. package/dist/7844.js.map +1 -0
  173. package/dist/7866.js +1 -0
  174. package/dist/7866.js.map +1 -0
  175. package/dist/7916.js +1 -0
  176. package/dist/7916.js.map +1 -0
  177. package/dist/7971.js +1 -0
  178. package/dist/7971.js.map +1 -0
  179. package/dist/8159.js +7 -0
  180. package/dist/8159.js.map +1 -0
  181. package/dist/8244.js +1 -0
  182. package/dist/8244.js.map +1 -0
  183. package/dist/8262.js +1 -0
  184. package/dist/8262.js.map +1 -0
  185. package/dist/834.js +1 -0
  186. package/dist/834.js.map +1 -0
  187. package/dist/8376.js +1 -0
  188. package/dist/8376.js.map +1 -0
  189. package/dist/845.js +1 -0
  190. package/dist/845.js.map +1 -0
  191. package/dist/8546.js +1 -0
  192. package/dist/8546.js.map +1 -0
  193. package/dist/8570.js +1 -0
  194. package/dist/8570.js.map +1 -0
  195. package/dist/87.js +1 -0
  196. package/dist/87.js.map +1 -0
  197. package/dist/8727.js +1 -0
  198. package/dist/8828.js +1 -0
  199. package/dist/8828.js.map +1 -0
  200. package/dist/8860.js +1 -0
  201. package/dist/8860.js.map +1 -0
  202. package/dist/9036.js +1 -0
  203. package/dist/9036.js.map +1 -0
  204. package/dist/9124.js +1 -0
  205. package/dist/9124.js.map +1 -0
  206. package/dist/9182.js +1 -0
  207. package/dist/921.js +1 -0
  208. package/dist/921.js.map +1 -0
  209. package/dist/9404.js +1 -0
  210. package/dist/9404.js.map +1 -0
  211. package/dist/9406.js +1 -0
  212. package/dist/9406.js.map +1 -0
  213. package/dist/9446.js +1 -0
  214. package/dist/9446.js.map +1 -0
  215. package/dist/9449.js +1 -0
  216. package/dist/9449.js.map +1 -0
  217. package/dist/9566.js +5 -0
  218. package/dist/9566.js.map +1 -0
  219. package/dist/9641.js +1 -0
  220. package/dist/9641.js.map +1 -0
  221. package/dist/9711.js +1 -0
  222. package/dist/9711.js.map +1 -0
  223. package/dist/9801.js +1 -0
  224. package/dist/9801.js.map +1 -0
  225. package/dist/9835.js +11 -0
  226. package/dist/9835.js.map +1 -0
  227. package/dist/kenyaemr-esm-admin-app.js +5 -5
  228. package/dist/kenyaemr-esm-admin-app.js.buildmanifest.json +2704 -155
  229. package/dist/kenyaemr-esm-admin-app.js.map +1 -1
  230. package/dist/main.js +5 -31
  231. package/dist/main.js.map +1 -1
  232. package/dist/routes.json +1 -1
  233. package/package.json +6 -7
  234. package/rspack.config.js +1 -1
  235. package/src/components/facility-setup/facility-info.component.tsx +163 -94
  236. package/src/components/facility-setup/facility-info.scss +98 -55
  237. package/src/components/facility-setup/header/header.component.tsx +4 -10
  238. package/src/components/facility-setup/header/header.scss +3 -9
  239. package/src/components/facility-setup/shared/custom-info.component.tsx +9 -0
  240. package/src/components/facility-setup/shared/custom-section-card.component.tsx +10 -0
  241. package/src/components/facility-setup/shared/custom-status-tag.component.tsx +22 -0
  242. package/src/components/locations/forms/add-location/add-location.workspace.tsx +96 -95
  243. package/src/components/locations/forms/search-location/search-location.workspace.tsx +90 -85
  244. package/src/components/locations/tables/locations-table.component.tsx +117 -121
  245. package/src/components/users/manage-users/manage-user-role-scope/user-role-scope-workspace/user-role-scope.workspace.tsx +15 -15
  246. package/src/components/users/manage-users/user-list/user-list.component.tsx +22 -9
  247. package/src/components/users/manage-users/user-management.workspace.tsx +538 -531
  248. package/src/index.ts +51 -28
  249. package/src/root.component.tsx +11 -13
  250. package/src/routes.json +40 -40
  251. package/src/types/index.ts +29 -1
  252. package/src/user-management.resources.ts +5 -11
  253. package/translations/am.json +26 -8
  254. package/translations/en.json +26 -8
  255. package/translations/sw.json +26 -8
  256. package/tsconfig.json +1 -1
  257. package/dist/127.js +0 -1
  258. package/dist/267.js +0 -1
  259. package/dist/267.js.map +0 -1
  260. package/dist/281.js +0 -15
  261. package/dist/281.js.map +0 -1
  262. package/dist/329.js +0 -1
  263. package/dist/329.js.map +0 -1
  264. package/dist/40.js +0 -1
  265. package/dist/466.js +0 -1
  266. package/dist/466.js.map +0 -1
  267. package/dist/472.js +0 -1
  268. package/dist/472.js.map +0 -1
  269. package/dist/478.js +0 -1
  270. package/dist/478.js.map +0 -1
  271. package/dist/585.js +0 -1
  272. package/dist/585.js.map +0 -1
  273. package/dist/630.js +0 -1
  274. package/dist/630.js.map +0 -1
  275. package/dist/675.js +0 -1
  276. package/dist/675.js.map +0 -1
  277. package/dist/689.js +0 -1
  278. package/dist/689.js.map +0 -1
  279. package/dist/706.js +0 -27
  280. package/dist/706.js.map +0 -1
  281. package/dist/729.js +0 -17
  282. package/dist/729.js.map +0 -1
  283. package/dist/774.js +0 -1
  284. package/dist/774.js.map +0 -1
  285. package/dist/847.js +0 -1
  286. package/dist/847.js.map +0 -1
  287. package/dist/85.js +0 -1
  288. package/dist/85.js.map +0 -1
  289. package/dist/882.js +0 -1
  290. package/dist/91.js +0 -1
  291. package/dist/91.js.map +0 -1
  292. package/dist/916.js +0 -1
  293. package/dist/998.js +0 -1
  294. package/dist/998.js.map +0 -1
  295. package/jest.config.js +0 -8
  296. package/src/components/facility-setup/card.component.tsx +0 -16
@@ -1,35 +1,36 @@
1
- import React, { useEffect, useMemo } from 'react';
1
+ import React, { useEffect, useMemo, useState } from 'react';
2
2
  import {
3
- type DefaultWorkspaceProps,
4
3
  ResponsiveWrapper,
5
- useLayoutType,
6
- showSnackbar,
7
- useConfig,
4
+ Workspace2,
8
5
  restBaseUrl,
6
+ showSnackbar,
7
+ useLayoutType,
8
+ type Workspace2DefinitionProps,
9
9
  } from '@openmrs/esm-framework';
10
10
  import { useTranslation } from 'react-i18next';
11
11
  import { Controller, useForm } from 'react-hook-form';
12
12
  import {
13
- ButtonSet,
14
13
  Button,
15
- InlineLoading,
16
- TextInput,
14
+ ButtonSet,
15
+ FilterableMultiSelect,
16
+ Form,
17
17
  FormGroup,
18
+ InlineLoading,
18
19
  Stack,
19
- Form,
20
- FilterableMultiSelect,
20
+ TextInput,
21
21
  } from '@carbon/react';
22
- import classNames from 'classnames';
23
- import { z } from 'zod';
24
22
  import { zodResolver } from '@hookform/resolvers/zod';
23
+ import { z } from 'zod';
24
+ import classNames from 'classnames';
25
+ import { mutate } from 'swr';
26
+
25
27
  import styles from './add-location.workspace.scss';
26
28
  import { type LocationResponse } from '../../types';
27
29
  import { extractErrorMessagesFromResponse } from '../../helpers';
28
30
  import { useLocationTags } from '../../hooks/useLocationTags';
29
- import { mutate } from 'swr';
30
31
  import { saveOrUpdateLocation } from '../../hooks/useLocation';
31
32
 
32
- type AddLocationWorkspaceProps = DefaultWorkspaceProps & {
33
+ type AddLocationWorkspaceProps = {
33
34
  location?: LocationResponse;
34
35
  };
35
36
 
@@ -46,16 +47,14 @@ const locationFormSchema = z.object({
46
47
 
47
48
  type LocationFormType = z.infer<typeof locationFormSchema>;
48
49
 
49
- const AddLocationWorkspace: React.FC<AddLocationWorkspaceProps> = ({
50
+ const AddLocationWorkspace: React.FC<Workspace2DefinitionProps<AddLocationWorkspaceProps, {}, {}>> = ({
50
51
  closeWorkspace,
51
- closeWorkspaceWithSavedChanges,
52
- promptBeforeClosing,
53
- location,
52
+ workspaceProps: { location = {} as LocationResponse },
54
53
  }) => {
55
54
  const { t } = useTranslation();
56
55
  const isTablet = useLayoutType() === 'tablet';
57
56
  const { locationTagList: Tags } = useLocationTags();
58
-
57
+ const [hasUnsavedChanges, setHasUnsavedChanges] = useState(false);
59
58
  const hasLocationAttributes = useMemo(() => {
60
59
  return location?.attributes && location.attributes.length > 0;
61
60
  }, [location?.attributes]);
@@ -105,7 +104,7 @@ const AddLocationWorkspace: React.FC<AddLocationWorkspaceProps> = ({
105
104
  }),
106
105
  });
107
106
  handleMutation();
108
- closeWorkspaceWithSavedChanges();
107
+ closeWorkspace({ discardUnsavedChanges: true });
109
108
  } catch (error: any) {
110
109
  const errorMessages = extractErrorMessagesFromResponse(error);
111
110
  showSnackbar({
@@ -117,83 +116,85 @@ const AddLocationWorkspace: React.FC<AddLocationWorkspaceProps> = ({
117
116
  };
118
117
 
119
118
  useEffect(() => {
120
- promptBeforeClosing(() => isDirty);
121
- }, [isDirty, promptBeforeClosing]);
119
+ setHasUnsavedChanges(true);
120
+ }, [isDirty, setHasUnsavedChanges]);
122
121
 
123
122
  return (
124
- <Form onSubmit={handleSubmit(onSubmit)} className={styles.form}>
125
- <div className={styles.formContainer}>
126
- <Stack gap={3}>
127
- <ResponsiveWrapper>
128
- <FormGroup legendText="">
129
- <Controller
130
- control={control}
131
- name="name"
132
- render={({ field }) => (
133
- <TextInput
134
- id="locationName"
135
- placeholder={t('locationPlaceholder', 'Add a location')}
136
- labelText={t('locationName', 'Location Name')}
137
- value={field.value}
138
- onChange={field.onChange}
139
- invalid={!!errors.name?.message}
140
- invalidText={errors.name?.message}
141
- disabled={hasLocationAttributes}
142
- />
143
- )}
144
- />
145
- </FormGroup>
146
- </ResponsiveWrapper>
147
-
148
- <ResponsiveWrapper>
149
- <FormGroup legendText="">
150
- <Controller
151
- control={control}
152
- name="tags"
153
- render={({ field: { onChange, value, ref } }) => (
154
- <FilterableMultiSelect
155
- id="locationTags"
156
- titleText={t('selectTags', 'Select tag(s)')}
157
- placeholder={t('selectTagPlaceholder', 'Select a tag')}
158
- items={Tags || []}
159
- selectedItems={(value || []).map(
160
- (selected) => Tags?.find((tag) => tag.uuid === selected.uuid) || selected,
161
- )}
162
- onChange={({ selectedItems }) => onChange(selectedItems || [])}
163
- itemToString={(item) => (item && typeof item === 'object' ? item.display : '')}
164
- selectionFeedback="top-after-reopen"
165
- invalid={!!errors.tags?.message}
166
- invalidText={errors.tags?.message}
167
- disabled={!Tags?.length}
168
- ref={ref}
169
- />
170
- )}
171
- />
172
- </FormGroup>
173
- </ResponsiveWrapper>
174
- </Stack>
175
- </div>
176
-
177
- <ButtonSet
178
- className={classNames({
179
- [styles.tablet]: isTablet,
180
- [styles.desktop]: !isTablet,
181
- })}>
182
- <Button className={styles.buttonContainer} kind="secondary" onClick={() => closeWorkspace()}>
183
- {t('cancel', 'Cancel')}
184
- </Button>
185
- <Button className={styles.buttonContainer} disabled={isSubmitting || !isDirty} kind="primary" type="submit">
186
- {isSubmitting ? (
187
- <span className={styles.inlineLoading}>
188
- {t('submitting', 'Submitting' + '...')}
189
- <InlineLoading status="active" iconDescription="Loading" />
190
- </span>
191
- ) : (
192
- t('saveAndClose', 'Save & close')
193
- )}
194
- </Button>
195
- </ButtonSet>
196
- </Form>
123
+ <Workspace2 title={t('addLocation', 'Add Location')} hasUnsavedChanges={hasUnsavedChanges}>
124
+ <Form onSubmit={handleSubmit(onSubmit)} className={styles.form}>
125
+ <div className={styles.formContainer}>
126
+ <Stack gap={3}>
127
+ <ResponsiveWrapper>
128
+ <FormGroup legendText="">
129
+ <Controller
130
+ control={control}
131
+ name="name"
132
+ render={({ field }) => (
133
+ <TextInput
134
+ id="locationName"
135
+ placeholder={t('locationPlaceholder', 'Add a location')}
136
+ labelText={t('locationName', 'Location Name')}
137
+ value={field.value}
138
+ onChange={field.onChange}
139
+ invalid={!!errors.name?.message}
140
+ invalidText={errors.name?.message}
141
+ disabled={hasLocationAttributes}
142
+ />
143
+ )}
144
+ />
145
+ </FormGroup>
146
+ </ResponsiveWrapper>
147
+
148
+ <ResponsiveWrapper>
149
+ <FormGroup legendText="">
150
+ <Controller
151
+ control={control}
152
+ name="tags"
153
+ render={({ field: { onChange, value, ref } }) => (
154
+ <FilterableMultiSelect
155
+ id="locationTags"
156
+ titleText={t('selectTags', 'Select tag(s)')}
157
+ placeholder={t('selectTagPlaceholder', 'Select a tag')}
158
+ items={Tags || []}
159
+ selectedItems={(value || []).map(
160
+ (selected) => Tags?.find((tag) => tag.uuid === selected.uuid) || selected,
161
+ )}
162
+ onChange={({ selectedItems }) => onChange(selectedItems || [])}
163
+ itemToString={(item) => (item && typeof item === 'object' ? item.display : '')}
164
+ selectionFeedback="top-after-reopen"
165
+ invalid={!!errors.tags?.message}
166
+ invalidText={errors.tags?.message}
167
+ disabled={!Tags?.length}
168
+ ref={ref}
169
+ />
170
+ )}
171
+ />
172
+ </FormGroup>
173
+ </ResponsiveWrapper>
174
+ </Stack>
175
+ </div>
176
+
177
+ <ButtonSet
178
+ className={classNames({
179
+ [styles.tablet]: isTablet,
180
+ [styles.desktop]: !isTablet,
181
+ })}>
182
+ <Button className={styles.buttonContainer} kind="secondary" onClick={() => closeWorkspace()}>
183
+ {t('cancel', 'Cancel')}
184
+ </Button>
185
+ <Button className={styles.buttonContainer} disabled={isSubmitting || !isDirty} kind="primary" type="submit">
186
+ {isSubmitting ? (
187
+ <span className={styles.inlineLoading}>
188
+ {t('submitting', 'Submitting' + '...')}
189
+ <InlineLoading status="active" iconDescription="Loading" />
190
+ </span>
191
+ ) : (
192
+ t('saveAndClose', 'Save & close')
193
+ )}
194
+ </Button>
195
+ </ButtonSet>
196
+ </Form>
197
+ </Workspace2>
197
198
  );
198
199
  };
199
200
 
@@ -1,27 +1,29 @@
1
- import { Button, ButtonSet, FilterableMultiSelect, Form, FormGroup, InlineLoading, Stack } from '@carbon/react';
1
+ import React, { useEffect, useState } from 'react';
2
+ import { useTranslation } from 'react-i18next';
3
+ import { mutate } from 'swr';
4
+ import classNames from 'classnames';
5
+ import { z } from 'zod';
6
+ import { Controller, useForm } from 'react-hook-form';
2
7
  import { zodResolver } from '@hookform/resolvers/zod';
3
8
  import {
4
- type DefaultWorkspaceProps,
9
+ Workspace2,
10
+ Workspace2DefinitionProps,
11
+ useLayoutType,
5
12
  ResponsiveWrapper,
6
13
  restBaseUrl,
7
14
  showSnackbar,
8
- useLayoutType,
9
15
  } from '@openmrs/esm-framework';
10
- import classNames from 'classnames';
11
- import React, { useEffect, useState } from 'react';
12
- import { Controller, useForm } from 'react-hook-form';
13
- import { useTranslation } from 'react-i18next';
14
- import { mutate } from 'swr';
15
- import { z } from 'zod';
16
+ import { Button, ButtonSet, FilterableMultiSelect, Form, FormGroup, InlineLoading, Stack } from '@carbon/react';
17
+
16
18
  import { LocationAutosuggest } from '../../auto-suggest/location-autosuggest.component';
17
19
  import ResultsTile from '../../common/results-tile.component';
18
- import { extractErrorMessagesFromResponse } from '../../helpers';
19
- import { saveOrUpdateLocation } from '../../hooks/useLocation';
20
20
  import { useLocationTags } from '../../hooks/useLocationTags';
21
+ import { saveOrUpdateLocation } from '../../hooks/useLocation';
22
+ import { extractErrorMessagesFromResponse } from '../../helpers';
21
23
  import { LocationResponse } from '../../types';
22
24
  import styles from './search-location.workspace.scss';
23
25
 
24
- type AddLocationWorkspaceProps = DefaultWorkspaceProps & {
26
+ type AddLocationWorkspaceProps = {
25
27
  location?: LocationResponse;
26
28
  };
27
29
 
@@ -37,17 +39,15 @@ const locationFormSchema = z.object({
37
39
 
38
40
  type LocationFormType = z.infer<typeof locationFormSchema>;
39
41
 
40
- const SearchLocationWorkspace: React.FC<AddLocationWorkspaceProps> = ({
42
+ const SearchLocationWorkspace: React.FC<Workspace2DefinitionProps<AddLocationWorkspaceProps, {}, {}>> = ({
41
43
  closeWorkspace,
42
- closeWorkspaceWithSavedChanges,
43
- promptBeforeClosing,
44
- location,
44
+ workspaceProps: { location = {} as LocationResponse },
45
45
  }) => {
46
46
  const { t } = useTranslation();
47
47
  const isTablet = useLayoutType() === 'tablet';
48
48
  const { locationTagList: Tags } = useLocationTags();
49
49
  const [selectedLocation, setSelectedLocation] = useState<LocationResponse | null>(null);
50
-
50
+ const [hasUnsavedChanges, setHasUnsavedChanges] = useState(false);
51
51
  const handleMutation = () => {
52
52
  const url = `${restBaseUrl}/location`;
53
53
  mutate((key) => typeof key === 'string' && key.startsWith(url), undefined, { revalidate: true });
@@ -56,7 +56,6 @@ const SearchLocationWorkspace: React.FC<AddLocationWorkspaceProps> = ({
56
56
  const {
57
57
  handleSubmit,
58
58
  control,
59
- getValues,
60
59
  setValue,
61
60
  reset,
62
61
  formState: { isSubmitting, isDirty, errors },
@@ -89,7 +88,7 @@ const SearchLocationWorkspace: React.FC<AddLocationWorkspaceProps> = ({
89
88
  });
90
89
 
91
90
  handleMutation();
92
- closeWorkspaceWithSavedChanges();
91
+ closeWorkspace({ discardUnsavedChanges: true });
93
92
  } catch (error: any) {
94
93
  console.error('Error saving location:', error);
95
94
  const errorMessages = extractErrorMessagesFromResponse(error);
@@ -137,78 +136,84 @@ const SearchLocationWorkspace: React.FC<AddLocationWorkspaceProps> = ({
137
136
  };
138
137
 
139
138
  useEffect(() => {
140
- promptBeforeClosing(() => isDirty);
141
- }, [isDirty, promptBeforeClosing]);
139
+ setHasUnsavedChanges(true);
140
+ }, [isDirty, setHasUnsavedChanges]);
142
141
 
143
142
  const isFormReady = (selectedLocation || location) && isDirty;
144
143
 
145
144
  return (
146
- <Form onSubmit={handleSubmit(onSubmit)} className={styles.form}>
147
- <div className={styles.formContainer}>
148
- <Stack gap={3}>
149
- <ResponsiveWrapper>
150
- <FormGroup legendText="">
151
- {!selectedLocation ? (
152
- <LocationAutosuggest
153
- onLocationSelected={handleLocationSelected}
154
- labelText={t('searchForLocation', 'Search for location')}
155
- placeholder={t('searchParentLocation', 'Search for location...')}
156
- />
157
- ) : (
158
- renderSelectedLocationTile()
159
- )}
160
- </FormGroup>
161
- </ResponsiveWrapper>
162
-
163
- <ResponsiveWrapper>
164
- <FormGroup legendText="">
165
- <Controller
166
- control={control}
167
- name="tags"
168
- render={({ field: { onChange, value, ref } }) => (
169
- <FilterableMultiSelect
170
- id="locationTags"
171
- titleText={t('selectTags', 'Select tag(s)')}
172
- placeholder={t('selectTagPlaceholder', 'Select a tag')}
173
- items={Tags || []}
174
- selectedItems={(value || []).map(
175
- (selected) => Tags?.find((tag) => tag.uuid === selected.uuid) || selected,
176
- )}
177
- onChange={({ selectedItems }) => onChange(selectedItems || [])}
178
- itemToString={(item) => (item && typeof item === 'object' ? item.display : '')}
179
- selectionFeedback="top-after-reopen"
180
- invalid={!!errors.tags?.message}
181
- invalidText={errors.tags?.message}
182
- disabled={!Tags?.length}
183
- ref={ref}
145
+ <Workspace2 title={t('searchLocation', 'Search Location')} hasUnsavedChanges={hasUnsavedChanges}>
146
+ <Form onSubmit={handleSubmit(onSubmit)} className={styles.form}>
147
+ <div className={styles.formContainer}>
148
+ <Stack gap={3}>
149
+ <ResponsiveWrapper>
150
+ <FormGroup legendText="">
151
+ {!selectedLocation ? (
152
+ <LocationAutosuggest
153
+ onLocationSelected={handleLocationSelected}
154
+ labelText={t('searchForLocation', 'Search for location')}
155
+ placeholder={t('searchParentLocation', 'Search for location...')}
184
156
  />
157
+ ) : (
158
+ renderSelectedLocationTile()
185
159
  )}
186
- />
187
- </FormGroup>
188
- </ResponsiveWrapper>
189
- </Stack>
190
- </div>
191
-
192
- <ButtonSet
193
- className={classNames({
194
- [styles.tablet]: isTablet,
195
- [styles.desktop]: !isTablet,
196
- })}>
197
- <Button className={styles.buttonContainer} kind="secondary" onClick={() => closeWorkspace()}>
198
- {t('cancel', 'Cancel')}
199
- </Button>
200
- <Button className={styles.buttonContainer} disabled={isSubmitting || !isFormReady} kind="primary" type="submit">
201
- {isSubmitting ? (
202
- <span className={styles.inlineLoading}>
203
- {t('submitting', 'Submitting' + '...')}
204
- <InlineLoading status="active" iconDescription="Loading" />
205
- </span>
206
- ) : (
207
- t('saveAndClose', 'Save & close')
208
- )}
209
- </Button>
210
- </ButtonSet>
211
- </Form>
160
+ </FormGroup>
161
+ </ResponsiveWrapper>
162
+
163
+ <ResponsiveWrapper>
164
+ <FormGroup legendText="">
165
+ <Controller
166
+ control={control}
167
+ name="tags"
168
+ render={({ field: { onChange, value, ref } }) => (
169
+ <FilterableMultiSelect
170
+ id="locationTags"
171
+ titleText={t('selectTags', 'Select tag(s)')}
172
+ placeholder={t('selectTagPlaceholder', 'Select a tag')}
173
+ items={Tags || []}
174
+ selectedItems={(value || []).map(
175
+ (selected) => Tags?.find((tag) => tag.uuid === selected.uuid) || selected,
176
+ )}
177
+ onChange={({ selectedItems }) => onChange(selectedItems || [])}
178
+ itemToString={(item) => (item && typeof item === 'object' ? item.display : '')}
179
+ selectionFeedback="top-after-reopen"
180
+ invalid={!!errors.tags?.message}
181
+ invalidText={errors.tags?.message}
182
+ disabled={!Tags?.length}
183
+ ref={ref}
184
+ />
185
+ )}
186
+ />
187
+ </FormGroup>
188
+ </ResponsiveWrapper>
189
+ </Stack>
190
+ </div>
191
+
192
+ <ButtonSet
193
+ className={classNames({
194
+ [styles.tablet]: isTablet,
195
+ [styles.desktop]: !isTablet,
196
+ })}>
197
+ <Button className={styles.buttonContainer} kind="secondary" onClick={() => closeWorkspace()}>
198
+ {t('cancel', 'Cancel')}
199
+ </Button>
200
+ <Button
201
+ className={styles.buttonContainer}
202
+ disabled={isSubmitting || !isFormReady}
203
+ kind="primary"
204
+ type="submit">
205
+ {isSubmitting ? (
206
+ <span className={styles.inlineLoading}>
207
+ {t('submitting', 'Submitting' + '...')}
208
+ <InlineLoading status="active" iconDescription="Loading" />
209
+ </span>
210
+ ) : (
211
+ t('saveAndClose', 'Save & close')
212
+ )}
213
+ </Button>
214
+ </ButtonSet>
215
+ </Form>
216
+ </Workspace2>
212
217
  );
213
218
  };
214
219