@openmrs/esm-metadataexport-app 4.4.1-pre.877 → 4.4.1-pre.879
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.
- package/.turbo/turbo-build.log +1 -1
- package/dist/2173.js +1 -1
- package/dist/2173.js.map +1 -1
- package/dist/5065.js +1 -1
- package/dist/5065.js.map +1 -1
- package/dist/5117.js +1 -1
- package/dist/5117.js.map +1 -1
- package/dist/7121.js +1 -1
- package/dist/7121.js.map +1 -1
- package/dist/9503.js +1 -1
- package/dist/9503.js.map +1 -1
- package/dist/openmrs-esm-metadataexport-app.js +1 -1
- package/dist/openmrs-esm-metadataexport-app.js.buildmanifest.json +16 -16
- package/dist/routes.json +1 -1
- package/package.json +1 -1
- package/src/admin-card-link.component.tsx +1 -1
- package/src/components/dashboard/home-dashboard.component.tsx +1 -2
- package/src/components/metadata-package-form/add-package-action-button/add-package-action-button.component.tsx +3 -3
- package/src/components/metadata-package-form/add-package-action-button/add-package-action-button.test.tsx +5 -5
- package/src/components/metadata-package-form/metadata-package-form-utils.ts +3 -7
- package/src/components/metadata-package-form/metadata-package-form.workspace.test.tsx +58 -30
- package/src/components/metadata-package-form/metadata-package-form.workspace.tsx +104 -106
- package/src/components/packages-table/packages-table.component.tsx +1 -1
- package/src/components/view-metadata-package/view-metadata-package-utils.ts +2 -5
- package/src/components/view-metadata-package/view-metadata-package.workspace.test.tsx +31 -6
- package/src/components/view-metadata-package/view-metadata-package.workspace.tsx +103 -93
- package/src/root.component.tsx +1 -1
- package/src/routes.json +25 -10
- package/translations/en.json +2 -1
|
@@ -71,7 +71,7 @@
|
|
|
71
71
|
"auxiliaryFiles": [
|
|
72
72
|
"openmrs-esm-metadataexport-app.js.map"
|
|
73
73
|
],
|
|
74
|
-
"hash": "
|
|
74
|
+
"hash": "6234fa494254ef21",
|
|
75
75
|
"childrenByOrder": {}
|
|
76
76
|
},
|
|
77
77
|
{
|
|
@@ -225,10 +225,10 @@
|
|
|
225
225
|
"rendered": true,
|
|
226
226
|
"initial": false,
|
|
227
227
|
"entry": false,
|
|
228
|
-
"size":
|
|
228
|
+
"size": 28540,
|
|
229
229
|
"sizes": {
|
|
230
230
|
"css/mini-extract": 1647,
|
|
231
|
-
"javascript":
|
|
231
|
+
"javascript": 26893
|
|
232
232
|
},
|
|
233
233
|
"names": [],
|
|
234
234
|
"idHints": [],
|
|
@@ -243,7 +243,7 @@
|
|
|
243
243
|
"2173.css.map",
|
|
244
244
|
"2173.js.map"
|
|
245
245
|
],
|
|
246
|
-
"hash": "
|
|
246
|
+
"hash": "c226db14bbdbbf59",
|
|
247
247
|
"childrenByOrder": {}
|
|
248
248
|
},
|
|
249
249
|
{
|
|
@@ -763,10 +763,10 @@
|
|
|
763
763
|
"rendered": true,
|
|
764
764
|
"initial": false,
|
|
765
765
|
"entry": false,
|
|
766
|
-
"size":
|
|
766
|
+
"size": 17618,
|
|
767
767
|
"sizes": {
|
|
768
768
|
"css/mini-extract": 1915,
|
|
769
|
-
"javascript":
|
|
769
|
+
"javascript": 15703
|
|
770
770
|
},
|
|
771
771
|
"names": [],
|
|
772
772
|
"idHints": [],
|
|
@@ -781,7 +781,7 @@
|
|
|
781
781
|
"5065.css.map",
|
|
782
782
|
"5065.js.map"
|
|
783
783
|
],
|
|
784
|
-
"hash": "
|
|
784
|
+
"hash": "c013e3ec5d865d0a",
|
|
785
785
|
"childrenByOrder": {}
|
|
786
786
|
},
|
|
787
787
|
{
|
|
@@ -789,9 +789,9 @@
|
|
|
789
789
|
"rendered": true,
|
|
790
790
|
"initial": false,
|
|
791
791
|
"entry": false,
|
|
792
|
-
"size":
|
|
792
|
+
"size": 2092,
|
|
793
793
|
"sizes": {
|
|
794
|
-
"javascript":
|
|
794
|
+
"javascript": 2092
|
|
795
795
|
},
|
|
796
796
|
"names": [],
|
|
797
797
|
"idHints": [],
|
|
@@ -804,7 +804,7 @@
|
|
|
804
804
|
"auxiliaryFiles": [
|
|
805
805
|
"5117.js.map"
|
|
806
806
|
],
|
|
807
|
-
"hash": "
|
|
807
|
+
"hash": "b94bb6b8ac5d28e2",
|
|
808
808
|
"childrenByOrder": {}
|
|
809
809
|
},
|
|
810
810
|
{
|
|
@@ -1323,9 +1323,9 @@
|
|
|
1323
1323
|
"rendered": true,
|
|
1324
1324
|
"initial": false,
|
|
1325
1325
|
"entry": false,
|
|
1326
|
-
"size":
|
|
1326
|
+
"size": 1376,
|
|
1327
1327
|
"sizes": {
|
|
1328
|
-
"javascript":
|
|
1328
|
+
"javascript": 1376
|
|
1329
1329
|
},
|
|
1330
1330
|
"names": [],
|
|
1331
1331
|
"idHints": [],
|
|
@@ -1338,7 +1338,7 @@
|
|
|
1338
1338
|
"auxiliaryFiles": [
|
|
1339
1339
|
"7121.js.map"
|
|
1340
1340
|
],
|
|
1341
|
-
"hash": "
|
|
1341
|
+
"hash": "75c22d2c49b9924c",
|
|
1342
1342
|
"childrenByOrder": {}
|
|
1343
1343
|
},
|
|
1344
1344
|
{
|
|
@@ -1625,10 +1625,10 @@
|
|
|
1625
1625
|
"rendered": true,
|
|
1626
1626
|
"initial": false,
|
|
1627
1627
|
"entry": false,
|
|
1628
|
-
"size":
|
|
1628
|
+
"size": 19275,
|
|
1629
1629
|
"sizes": {
|
|
1630
1630
|
"css/mini-extract": 4201,
|
|
1631
|
-
"javascript":
|
|
1631
|
+
"javascript": 15074
|
|
1632
1632
|
},
|
|
1633
1633
|
"names": [],
|
|
1634
1634
|
"idHints": [],
|
|
@@ -1643,7 +1643,7 @@
|
|
|
1643
1643
|
"9503.css.map",
|
|
1644
1644
|
"9503.js.map"
|
|
1645
1645
|
],
|
|
1646
|
-
"hash": "
|
|
1646
|
+
"hash": "10d839d0fb1604b5",
|
|
1647
1647
|
"childrenByOrder": {}
|
|
1648
1648
|
},
|
|
1649
1649
|
{
|
package/dist/routes.json
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"$schema":"https://json.openmrs.org/routes.schema.json","backendDependencies":{"metadataexport":">=1.0.0-SNAPSHOT"},"pages":[{"route":"
|
|
1
|
+
{"$schema":"https://json.openmrs.org/routes.schema.json","backendDependencies":{"metadataexport":">=1.0.0-SNAPSHOT"},"pages":[{"route":"metadata-export","component":"root","online":true}],"extensions":[{"name":"metadata-export-card-link","slot":"system-admin-page-card-link-slot","component":"metadataExportCardLink","privileges":["Get Metadata Export Packages"]}],"modals":[{"name":"delete-metadata-package-modal","component":"deleteMetadataPackageModal"}],"workspaceGroups2":[{"name":"metadata-export-group","scopePattern":"^/metadata-export","overlay":true}],"workspaceWindows2":[{"name":"metadata-package-form-window","group":"metadata-export-group","canMaximize":true,"width":"narrow"},{"name":"view-metadata-package-window","group":"metadata-export-group","canMaximize":true,"width":"narrow"}],"workspaces2":[{"name":"metadata-package-form-workspace","component":"metadataPackageFormWorkspace","window":"metadata-package-form-window"},{"name":"view-metadata-package-workspace","component":"viewMetadataPackageWorkspace","window":"view-metadata-package-window"}],"version":"4.4.1-pre.879"}
|
package/package.json
CHANGED
|
@@ -3,7 +3,7 @@ import { useTranslation } from 'react-i18next';
|
|
|
3
3
|
import { Layer, ClickableTile } from '@carbon/react';
|
|
4
4
|
import { ArrowRightIcon, navigate } from '@openmrs/esm-framework';
|
|
5
5
|
|
|
6
|
-
const metadataExportUrl = `${window.spaBase}/
|
|
6
|
+
const metadataExportUrl = `${window.spaBase}/metadata-export`;
|
|
7
7
|
|
|
8
8
|
// Navigate client-side on a plain left click; modified clicks fall through
|
|
9
9
|
// to the anchor so they open a new tab or window as usual.
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
import { useLayoutType, isDesktop
|
|
2
|
+
import { useLayoutType, isDesktop } from '@openmrs/esm-framework';
|
|
3
3
|
import DashboardView from './dashboard-view.component';
|
|
4
4
|
import styles from './home-dashboard.scss';
|
|
5
5
|
|
|
@@ -11,7 +11,6 @@ export default function Dashboard() {
|
|
|
11
11
|
<section className={isDesktop(layout) ? styles.dashboardContainer : styles.dashboardContainerTablet}>
|
|
12
12
|
<DashboardView />
|
|
13
13
|
</section>
|
|
14
|
-
<WorkspaceContainer overlay contextKey="metadata-export" />
|
|
15
14
|
</div>
|
|
16
15
|
);
|
|
17
16
|
}
|
|
@@ -12,8 +12,8 @@ const AddPackageActionButton: React.FC = () => {
|
|
|
12
12
|
const canManage = session.user ? userHasAccess('Manage Metadata Export Packages', session.user) : false;
|
|
13
13
|
|
|
14
14
|
const handleLaunchPackageForm = useCallback(() => {
|
|
15
|
-
launchPackageFormWorkspace(
|
|
16
|
-
}, [
|
|
15
|
+
launchPackageFormWorkspace();
|
|
16
|
+
}, []);
|
|
17
17
|
|
|
18
18
|
if (!canManage) {
|
|
19
19
|
return null;
|
|
@@ -26,7 +26,7 @@ const AddPackageActionButton: React.FC = () => {
|
|
|
26
26
|
kind="primary"
|
|
27
27
|
renderIcon={(props) => <Add size={16} {...props} />}
|
|
28
28
|
>
|
|
29
|
-
{t('
|
|
29
|
+
{t('newPackage', 'New package')}
|
|
30
30
|
</Button>
|
|
31
31
|
);
|
|
32
32
|
};
|
|
@@ -23,17 +23,17 @@ describe('AddPackageActionButton', () => {
|
|
|
23
23
|
mockUserHasAccess.mockReturnValue(true);
|
|
24
24
|
});
|
|
25
25
|
|
|
26
|
-
it('renders a "New
|
|
26
|
+
it('renders a "New package" button', () => {
|
|
27
27
|
render(<AddPackageActionButton />);
|
|
28
28
|
|
|
29
|
-
expect(screen.getByRole('button', { name: 'New
|
|
29
|
+
expect(screen.getByRole('button', { name: 'New package' })).toBeInTheDocument();
|
|
30
30
|
});
|
|
31
31
|
|
|
32
32
|
it('launches the new package workspace when a privileged user clicks', async () => {
|
|
33
33
|
const user = userEvent.setup();
|
|
34
34
|
render(<AddPackageActionButton />);
|
|
35
35
|
|
|
36
|
-
await user.click(screen.getByRole('button', { name: 'New
|
|
36
|
+
await user.click(screen.getByRole('button', { name: 'New package' }));
|
|
37
37
|
|
|
38
38
|
expect(mockUserHasAccess).toHaveBeenCalledWith('Manage Metadata Export Packages', expect.anything());
|
|
39
39
|
expect(mockLaunchPackageFormWorkspace).toHaveBeenCalledTimes(1);
|
|
@@ -43,7 +43,7 @@ describe('AddPackageActionButton', () => {
|
|
|
43
43
|
mockUserHasAccess.mockReturnValue(false);
|
|
44
44
|
render(<AddPackageActionButton />);
|
|
45
45
|
|
|
46
|
-
expect(screen.queryByRole('button', { name: 'New
|
|
46
|
+
expect(screen.queryByRole('button', { name: 'New package' })).not.toBeInTheDocument();
|
|
47
47
|
expect(mockLaunchPackageFormWorkspace).not.toHaveBeenCalled();
|
|
48
48
|
});
|
|
49
49
|
|
|
@@ -51,7 +51,7 @@ describe('AddPackageActionButton', () => {
|
|
|
51
51
|
mockUseSession.mockReturnValue({ authenticated: false } as unknown as Session);
|
|
52
52
|
render(<AddPackageActionButton />);
|
|
53
53
|
|
|
54
|
-
expect(screen.queryByRole('button', { name: 'New
|
|
54
|
+
expect(screen.queryByRole('button', { name: 'New package' })).not.toBeInTheDocument();
|
|
55
55
|
expect(mockUserHasAccess).not.toHaveBeenCalled();
|
|
56
56
|
});
|
|
57
57
|
});
|
|
@@ -1,9 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { launchWorkspace } from '@openmrs/esm-framework';
|
|
1
|
+
import { launchWorkspace2 } from '@openmrs/esm-framework';
|
|
3
2
|
|
|
4
|
-
export const launchPackageFormWorkspace = (
|
|
5
|
-
|
|
6
|
-
workspaceTitle: uuid ? t('editpackage', 'Edit package') : t('newpackage', 'New package'),
|
|
7
|
-
uuid,
|
|
8
|
-
});
|
|
3
|
+
export const launchPackageFormWorkspace = (uuid?: string) => {
|
|
4
|
+
launchWorkspace2('metadata-package-form-workspace', { uuid });
|
|
9
5
|
};
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import { render, screen } from '@testing-library/react';
|
|
3
3
|
import userEvent from '@testing-library/user-event';
|
|
4
|
-
import { OpenmrsFetchError, showSnackbar } from '@openmrs/esm-framework';
|
|
4
|
+
import { OpenmrsFetchError, showSnackbar, Workspace2, type Workspace2DefinitionProps } from '@openmrs/esm-framework';
|
|
5
5
|
import { describe, it, expect, vi, beforeEach, type Mock } from 'vitest';
|
|
6
6
|
import { useDomains } from '../../domain-lookups/domain-lookups.resource';
|
|
7
7
|
import { createPackage, editPackage, usePackage } from '../../packages/packages.resource';
|
|
@@ -25,8 +25,7 @@ const mockEditPackage = editPackage as Mock;
|
|
|
25
25
|
const mockUsePackage = usePackage as Mock;
|
|
26
26
|
const mockShowSnackbar = showSnackbar as Mock;
|
|
27
27
|
const mockCloseWorkspace = vi.fn();
|
|
28
|
-
const
|
|
29
|
-
const mockPromptBeforeClosing = vi.fn();
|
|
28
|
+
const mockWorkspace2 = vi.mocked(Workspace2);
|
|
30
29
|
|
|
31
30
|
const domains = ['ATTRIBUTE_TYPES', 'CONCEPTS', 'ENCOUNTER_TYPES'];
|
|
32
31
|
|
|
@@ -40,18 +39,22 @@ const fetchError = (responseBody: unknown) =>
|
|
|
40
39
|
new Error(),
|
|
41
40
|
);
|
|
42
41
|
|
|
42
|
+
function workspaceProps(uuid?: string): Workspace2DefinitionProps<{ uuid?: string }> {
|
|
43
|
+
return {
|
|
44
|
+
workspaceProps: { uuid },
|
|
45
|
+
windowProps: null,
|
|
46
|
+
groupProps: null,
|
|
47
|
+
closeWorkspace: mockCloseWorkspace,
|
|
48
|
+
launchChildWorkspace: vi.fn(),
|
|
49
|
+
workspaceName: 'metadata-package-form-workspace',
|
|
50
|
+
windowName: 'metadata-package-form-window',
|
|
51
|
+
isRootWorkspace: true,
|
|
52
|
+
showActionMenu: false,
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
|
|
43
56
|
function renderWorkspace(uuid?: string) {
|
|
44
|
-
return render(
|
|
45
|
-
<MetadataPackageFormWorkspace
|
|
46
|
-
uuid={uuid}
|
|
47
|
-
closeWorkspace={mockCloseWorkspace}
|
|
48
|
-
closeWorkspaceWithSavedChanges={mockCloseWorkspaceWithSavedChanges}
|
|
49
|
-
promptBeforeClosing={mockPromptBeforeClosing}
|
|
50
|
-
setTitle={vi.fn()}
|
|
51
|
-
// @ts-expect-error - the workspace only uses closeWorkspace and promptBeforeClosing from the default props
|
|
52
|
-
additionalProps={{}}
|
|
53
|
-
/>,
|
|
54
|
-
);
|
|
57
|
+
return render(<MetadataPackageFormWorkspace {...workspaceProps(uuid)} />);
|
|
55
58
|
}
|
|
56
59
|
|
|
57
60
|
describe('PackageFormWorkspace', () => {
|
|
@@ -135,8 +138,7 @@ describe('PackageFormWorkspace', () => {
|
|
|
135
138
|
expect.objectContaining({ title: 'Package created', kind: 'success' }),
|
|
136
139
|
);
|
|
137
140
|
// Closes without triggering the "unsaved changes" prompt.
|
|
138
|
-
expect(
|
|
139
|
-
expect(mockCloseWorkspace).not.toHaveBeenCalled();
|
|
141
|
+
expect(mockCloseWorkspace).toHaveBeenCalledWith({ discardUnsavedChanges: true });
|
|
140
142
|
});
|
|
141
143
|
|
|
142
144
|
it('sends an empty entries array when every domain is selected', async () => {
|
|
@@ -209,7 +211,24 @@ describe('PackageFormWorkspace', () => {
|
|
|
209
211
|
|
|
210
212
|
await user.click(screen.getByRole('button', { name: 'Cancel' }));
|
|
211
213
|
|
|
212
|
-
expect(mockCloseWorkspace).
|
|
214
|
+
expect(mockCloseWorkspace).toHaveBeenCalledWith();
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
it('marks the workspace as having unsaved changes once the user starts filling the form', async () => {
|
|
218
|
+
const user = userEvent.setup();
|
|
219
|
+
renderWorkspace();
|
|
220
|
+
|
|
221
|
+
expect(mockWorkspace2).toHaveBeenLastCalledWith(
|
|
222
|
+
expect.objectContaining({ title: 'New package', hasUnsavedChanges: false }),
|
|
223
|
+
expect.anything(),
|
|
224
|
+
);
|
|
225
|
+
|
|
226
|
+
await user.type(screen.getByRole('textbox', { name: 'Package name' }), 'Core reference data');
|
|
227
|
+
|
|
228
|
+
expect(mockWorkspace2).toHaveBeenLastCalledWith(
|
|
229
|
+
expect.objectContaining({ hasUnsavedChanges: true }),
|
|
230
|
+
expect.anything(),
|
|
231
|
+
);
|
|
213
232
|
});
|
|
214
233
|
|
|
215
234
|
describe('edit mode', () => {
|
|
@@ -231,6 +250,25 @@ describe('PackageFormWorkspace', () => {
|
|
|
231
250
|
expect(screen.getByText('Not found')).toBeInTheDocument();
|
|
232
251
|
});
|
|
233
252
|
|
|
253
|
+
it('still reports unsaved changes when a refetch fails after the user has edited the package', async () => {
|
|
254
|
+
const exportPackage = { name: 'Original name', description: '', entries: [{ domain: 'CONCEPTS' }] };
|
|
255
|
+
mockUsePackage.mockReturnValue({ exportPackage, isLoading: false, error: undefined });
|
|
256
|
+
const user = userEvent.setup();
|
|
257
|
+
const { rerender } = renderWorkspace(uuid);
|
|
258
|
+
|
|
259
|
+
await user.type(screen.getByRole('textbox', { name: 'Package name' }), ' edited');
|
|
260
|
+
|
|
261
|
+
// SWR keeps the cached package but reports the error from a failed background revalidation.
|
|
262
|
+
mockUsePackage.mockReturnValue({ exportPackage, isLoading: false, error: new Error('Network error') });
|
|
263
|
+
rerender(<MetadataPackageFormWorkspace {...workspaceProps(uuid)} />);
|
|
264
|
+
|
|
265
|
+
expect(screen.getByText('Error loading package')).toBeInTheDocument();
|
|
266
|
+
expect(mockWorkspace2).toHaveBeenLastCalledWith(
|
|
267
|
+
expect.objectContaining({ hasUnsavedChanges: true }),
|
|
268
|
+
expect.anything(),
|
|
269
|
+
);
|
|
270
|
+
});
|
|
271
|
+
|
|
234
272
|
it('seeds the form from the fetched package', () => {
|
|
235
273
|
mockUsePackage.mockReturnValue({
|
|
236
274
|
exportPackage: {
|
|
@@ -267,17 +305,7 @@ describe('PackageFormWorkspace', () => {
|
|
|
267
305
|
|
|
268
306
|
// Simulate a background revalidation returning the original server values again.
|
|
269
307
|
mockUsePackage.mockReturnValue(serverPackage());
|
|
270
|
-
rerender(
|
|
271
|
-
<MetadataPackageFormWorkspace
|
|
272
|
-
uuid={uuid}
|
|
273
|
-
closeWorkspace={mockCloseWorkspace}
|
|
274
|
-
closeWorkspaceWithSavedChanges={mockCloseWorkspaceWithSavedChanges}
|
|
275
|
-
promptBeforeClosing={mockPromptBeforeClosing}
|
|
276
|
-
setTitle={vi.fn()}
|
|
277
|
-
// @ts-expect-error - the workspace only uses closeWorkspace and promptBeforeClosing from the default props
|
|
278
|
-
additionalProps={{}}
|
|
279
|
-
/>,
|
|
280
|
-
);
|
|
308
|
+
rerender(<MetadataPackageFormWorkspace {...workspaceProps(uuid)} />);
|
|
281
309
|
|
|
282
310
|
expect(screen.getByRole('textbox', { name: 'Package name' })).toHaveValue('My edited name');
|
|
283
311
|
});
|
|
@@ -320,7 +348,7 @@ describe('PackageFormWorkspace', () => {
|
|
|
320
348
|
expect(mockShowSnackbar).toHaveBeenCalledWith(
|
|
321
349
|
expect.objectContaining({ title: 'Package updated', kind: 'success' }),
|
|
322
350
|
);
|
|
323
|
-
expect(
|
|
351
|
+
expect(mockCloseWorkspace).toHaveBeenCalledWith({ discardUnsavedChanges: true });
|
|
324
352
|
});
|
|
325
353
|
|
|
326
354
|
it('shows an update-failure snackbar and keeps the workspace open on error', async () => {
|
|
@@ -342,7 +370,7 @@ describe('PackageFormWorkspace', () => {
|
|
|
342
370
|
kind: 'error',
|
|
343
371
|
}),
|
|
344
372
|
);
|
|
345
|
-
expect(
|
|
373
|
+
expect(mockCloseWorkspace).not.toHaveBeenCalled();
|
|
346
374
|
});
|
|
347
375
|
});
|
|
348
376
|
});
|
|
@@ -13,21 +13,26 @@ import {
|
|
|
13
13
|
TextInput,
|
|
14
14
|
} from '@carbon/react';
|
|
15
15
|
import { useSWRConfig } from 'swr';
|
|
16
|
-
import {
|
|
16
|
+
import {
|
|
17
|
+
showSnackbar,
|
|
18
|
+
useLayoutType,
|
|
19
|
+
OpenmrsFetchError,
|
|
20
|
+
Workspace2,
|
|
21
|
+
type Workspace2DefinitionProps,
|
|
22
|
+
} from '@openmrs/esm-framework';
|
|
17
23
|
import { formatDomainLabel, useDomains } from '../../domain-lookups/domain-lookups.resource';
|
|
18
24
|
import { createPackage, editPackage, isPackagesCacheKey, usePackage } from '../../packages/packages.resource';
|
|
19
25
|
import styles from './metadata-package-form.workspace.scss';
|
|
20
26
|
|
|
21
|
-
interface PackageFormWorkspaceProps
|
|
27
|
+
interface PackageFormWorkspaceProps {
|
|
22
28
|
uuid?: string;
|
|
23
29
|
}
|
|
24
30
|
|
|
25
|
-
const MetadataPackageFormWorkspace: React.FC<PackageFormWorkspaceProps
|
|
26
|
-
|
|
31
|
+
const MetadataPackageFormWorkspace: React.FC<Workspace2DefinitionProps<PackageFormWorkspaceProps>> = ({
|
|
32
|
+
workspaceProps,
|
|
27
33
|
closeWorkspace,
|
|
28
|
-
closeWorkspaceWithSavedChanges,
|
|
29
|
-
promptBeforeClosing,
|
|
30
34
|
}) => {
|
|
35
|
+
const uuid = workspaceProps?.uuid;
|
|
31
36
|
const { t } = useTranslation();
|
|
32
37
|
const { mutate } = useSWRConfig();
|
|
33
38
|
const isTablet = useLayoutType() === 'tablet';
|
|
@@ -65,10 +70,6 @@ const MetadataPackageFormWorkspace: React.FC<PackageFormWorkspaceProps> = ({
|
|
|
65
70
|
|
|
66
71
|
const hasUnsavedChanges = packageName.trim().length > 0 || description.trim().length > 0 || selectedDomains.size > 0;
|
|
67
72
|
|
|
68
|
-
useEffect(() => {
|
|
69
|
-
promptBeforeClosing(() => hasUnsavedChanges);
|
|
70
|
-
}, [hasUnsavedChanges, promptBeforeClosing]);
|
|
71
|
-
|
|
72
73
|
// Seed the form from the fetched package exactly once, so a later SWR revalidation
|
|
73
74
|
// of the package or domain list doesn't clobber the user's in-progress edits.
|
|
74
75
|
// An empty entries list means the package includes every registered domain.
|
|
@@ -125,7 +126,7 @@ const MetadataPackageFormWorkspace: React.FC<PackageFormWorkspaceProps> = ({
|
|
|
125
126
|
});
|
|
126
127
|
}
|
|
127
128
|
// Bypass the "unsaved changes" prompt now that the package is persisted.
|
|
128
|
-
|
|
129
|
+
closeWorkspace({ discardUnsavedChanges: true });
|
|
129
130
|
} catch (submitError) {
|
|
130
131
|
const responseBody = submitError instanceof OpenmrsFetchError ? submitError.responseBody : null;
|
|
131
132
|
const reason =
|
|
@@ -153,124 +154,121 @@ const MetadataPackageFormWorkspace: React.FC<PackageFormWorkspaceProps> = ({
|
|
|
153
154
|
setIsSubmitting(false);
|
|
154
155
|
}
|
|
155
156
|
},
|
|
156
|
-
[
|
|
157
|
-
allSelected,
|
|
158
|
-
closeWorkspaceWithSavedChanges,
|
|
159
|
-
description,
|
|
160
|
-
isEditMode,
|
|
161
|
-
mutate,
|
|
162
|
-
packageName,
|
|
163
|
-
selectedDomains,
|
|
164
|
-
t,
|
|
165
|
-
uuid,
|
|
166
|
-
],
|
|
157
|
+
[allSelected, closeWorkspace, description, isEditMode, mutate, packageName, selectedDomains, t, uuid],
|
|
167
158
|
);
|
|
168
159
|
|
|
169
160
|
const isSubmitDisabled = packageName.trim().length === 0 || selectedDomains.size === 0 || isSubmitting;
|
|
161
|
+
const title = isEditMode ? t('editPackage', 'Edit package') : t('newPackage', 'New package');
|
|
170
162
|
|
|
171
163
|
if (isEditMode && isLoadingPackage) {
|
|
172
164
|
return (
|
|
173
|
-
<
|
|
174
|
-
<
|
|
175
|
-
|
|
165
|
+
<Workspace2 title={title}>
|
|
166
|
+
<div className={styles.form}>
|
|
167
|
+
<InlineLoading description={t('loadingPackage', 'Loading package…')} />
|
|
168
|
+
</div>
|
|
169
|
+
</Workspace2>
|
|
176
170
|
);
|
|
177
171
|
}
|
|
178
172
|
|
|
179
173
|
if (packageError) {
|
|
180
174
|
return (
|
|
181
|
-
<
|
|
182
|
-
<
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
175
|
+
<Workspace2 title={title} hasUnsavedChanges={hasUnsavedChanges}>
|
|
176
|
+
<div className={styles.form}>
|
|
177
|
+
<InlineNotification
|
|
178
|
+
kind="error"
|
|
179
|
+
lowContrast
|
|
180
|
+
title={t('errorLoadingPackage', 'Error loading package')}
|
|
181
|
+
subtitle={packageError.message}
|
|
182
|
+
/>
|
|
183
|
+
</div>
|
|
184
|
+
</Workspace2>
|
|
189
185
|
);
|
|
190
186
|
}
|
|
191
187
|
|
|
192
188
|
return (
|
|
193
|
-
<
|
|
194
|
-
<
|
|
195
|
-
<
|
|
196
|
-
<
|
|
197
|
-
<
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
189
|
+
<Workspace2 title={title} hasUnsavedChanges={hasUnsavedChanges}>
|
|
190
|
+
<Form className={styles.form} onSubmit={handleSubmit}>
|
|
191
|
+
<div className={styles.grid}>
|
|
192
|
+
<Stack gap={6}>
|
|
193
|
+
<section className={styles.section}>
|
|
194
|
+
<TextInput
|
|
195
|
+
id="packageName"
|
|
196
|
+
labelText={t('packageName', 'Package name')}
|
|
197
|
+
placeholder={t('packageNamePlaceholder', 'e.g. Core reference data')}
|
|
198
|
+
value={packageName}
|
|
199
|
+
onChange={(event) => setPackageName(event.target.value)}
|
|
200
|
+
/>
|
|
201
|
+
</section>
|
|
205
202
|
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
203
|
+
<section className={styles.section}>
|
|
204
|
+
<TextArea
|
|
205
|
+
id="packageDescription"
|
|
206
|
+
labelText={t('description', 'Description')}
|
|
207
|
+
placeholder={t('descriptionPlaceholder', 'Briefly describe what this package contains')}
|
|
208
|
+
value={description}
|
|
209
|
+
onChange={(event) => setDescription(event.target.value)}
|
|
210
|
+
/>
|
|
211
|
+
</section>
|
|
215
212
|
|
|
216
|
-
|
|
217
|
-
|
|
213
|
+
<section className={styles.section}>
|
|
214
|
+
<span className={styles.sectionLabel}>{t('domains', 'Domains')}</span>
|
|
218
215
|
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
/>
|
|
226
|
-
) : isLoading ? (
|
|
227
|
-
<div className={styles.domainList}>
|
|
228
|
-
{Array.from({ length: 8 }, (_, index) => (
|
|
229
|
-
<CheckboxSkeleton key={index} />
|
|
230
|
-
))}
|
|
231
|
-
</div>
|
|
232
|
-
) : (
|
|
233
|
-
<>
|
|
234
|
-
<Checkbox
|
|
235
|
-
id="select-all-domains"
|
|
236
|
-
className={styles.selectAll}
|
|
237
|
-
labelText={t('selectAll', 'Select all')}
|
|
238
|
-
checked={allSelected}
|
|
239
|
-
indeterminate={someSelected}
|
|
240
|
-
onChange={(_event, { checked }) => toggleSelectAll(checked)}
|
|
216
|
+
{error ? (
|
|
217
|
+
<InlineNotification
|
|
218
|
+
kind="error"
|
|
219
|
+
lowContrast
|
|
220
|
+
title={t('errorLoadingDomains', 'Error loading domains')}
|
|
221
|
+
subtitle={error?.message}
|
|
241
222
|
/>
|
|
223
|
+
) : isLoading ? (
|
|
242
224
|
<div className={styles.domainList}>
|
|
243
|
-
{
|
|
244
|
-
<
|
|
245
|
-
key={domain}
|
|
246
|
-
id={`domain-${domain}`}
|
|
247
|
-
labelText={formatDomainLabel(domain)}
|
|
248
|
-
checked={selectedDomains.has(domain)}
|
|
249
|
-
onChange={(_event, { checked }) => toggleDomain(domain, checked)}
|
|
250
|
-
/>
|
|
225
|
+
{Array.from({ length: 8 }, (_, index) => (
|
|
226
|
+
<CheckboxSkeleton key={index} />
|
|
251
227
|
))}
|
|
252
228
|
</div>
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
229
|
+
) : (
|
|
230
|
+
<>
|
|
231
|
+
<Checkbox
|
|
232
|
+
id="select-all-domains"
|
|
233
|
+
className={styles.selectAll}
|
|
234
|
+
labelText={t('selectAll', 'Select all')}
|
|
235
|
+
checked={allSelected}
|
|
236
|
+
indeterminate={someSelected}
|
|
237
|
+
onChange={(_event, { checked }) => toggleSelectAll(checked)}
|
|
238
|
+
/>
|
|
239
|
+
<div className={styles.domainList}>
|
|
240
|
+
{domains.map((domain) => (
|
|
241
|
+
<Checkbox
|
|
242
|
+
key={domain}
|
|
243
|
+
id={`domain-${domain}`}
|
|
244
|
+
labelText={formatDomainLabel(domain)}
|
|
245
|
+
checked={selectedDomains.has(domain)}
|
|
246
|
+
onChange={(_event, { checked }) => toggleDomain(domain, checked)}
|
|
247
|
+
/>
|
|
248
|
+
))}
|
|
249
|
+
</div>
|
|
250
|
+
</>
|
|
251
|
+
)}
|
|
252
|
+
</section>
|
|
253
|
+
</Stack>
|
|
254
|
+
</div>
|
|
258
255
|
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
256
|
+
<ButtonSet className={isTablet ? styles.tabletButtonSet : styles.desktopButtonSet}>
|
|
257
|
+
<Button className={styles.button} kind="secondary" onClick={() => closeWorkspace()}>
|
|
258
|
+
{t('cancel', 'Cancel')}
|
|
259
|
+
</Button>
|
|
260
|
+
<Button className={styles.button} kind="primary" type="submit" disabled={isSubmitDisabled}>
|
|
261
|
+
{isSubmitting ? (
|
|
262
|
+
<InlineLoading description={(isEditMode ? t('saving', 'Saving') : t('creating', 'Creating')) + '…'} />
|
|
263
|
+
) : isEditMode ? (
|
|
264
|
+
t('saveChanges', 'Save changes')
|
|
265
|
+
) : (
|
|
266
|
+
t('createPackage', 'Create package')
|
|
267
|
+
)}
|
|
268
|
+
</Button>
|
|
269
|
+
</ButtonSet>
|
|
270
|
+
</Form>
|
|
271
|
+
</Workspace2>
|
|
274
272
|
);
|
|
275
273
|
};
|
|
276
274
|
|
|
@@ -101,7 +101,7 @@ const PackagesTable: React.FC = () => {
|
|
|
101
101
|
<EmptyCard
|
|
102
102
|
displayText={t('packages__lower', 'packages')}
|
|
103
103
|
headerTitle={headerTitle}
|
|
104
|
-
launchForm={canManage ? () => launchPackageFormWorkspace(
|
|
104
|
+
launchForm={canManage ? () => launchPackageFormWorkspace() : undefined}
|
|
105
105
|
/>
|
|
106
106
|
</div>
|
|
107
107
|
);
|