@openmrs/esm-stock-management-app 3.0.1-pre.845 → 3.0.1-pre.853

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 (126) hide show
  1. package/.husky/pre-commit +4 -1
  2. package/__mocks__/react-i18next.js +8 -9
  3. package/dist/10.js +1 -1
  4. package/dist/10.js.map +1 -1
  5. package/dist/119.js +1 -1
  6. package/dist/119.js.map +1 -1
  7. package/dist/14.js +1 -1
  8. package/dist/14.js.map +1 -1
  9. package/dist/172.js +1 -1
  10. package/dist/172.js.map +1 -1
  11. package/dist/20.js +1 -1
  12. package/dist/20.js.map +1 -1
  13. package/dist/290.js +1 -1
  14. package/dist/290.js.map +1 -1
  15. package/dist/33.js +1 -0
  16. package/dist/33.js.map +1 -0
  17. package/dist/467.js +1 -1
  18. package/dist/467.js.map +1 -1
  19. package/dist/574.js +1 -1
  20. package/dist/606.js +1 -1
  21. package/dist/606.js.map +1 -1
  22. package/dist/642.js +1 -1
  23. package/dist/642.js.map +1 -1
  24. package/dist/675.js +1 -1
  25. package/dist/675.js.map +1 -1
  26. package/dist/727.js +1 -1
  27. package/dist/727.js.map +1 -1
  28. package/dist/842.js +1 -1
  29. package/dist/842.js.map +1 -1
  30. package/dist/93.js +1 -1
  31. package/dist/93.js.map +1 -1
  32. package/dist/main.js +1 -1
  33. package/dist/main.js.map +1 -1
  34. package/dist/openmrs-esm-stock-management-app.js.buildmanifest.json +70 -70
  35. package/dist/routes.json +1 -1
  36. package/jest.config.js +6 -3
  37. package/package.json +1 -1
  38. package/src/core/components/overlay/overlay.scss +1 -1
  39. package/src/core/components/privilages-component/privilages.scss +1 -1
  40. package/src/core/components/table/table.component.tsx +2 -2
  41. package/src/core/components/table/table.scss +1 -1
  42. package/src/core/components/tabs/vertical-tabs.scss +1 -1
  43. package/src/index.ts +5 -5
  44. package/src/stock-items/add-bulk-stock-item/add-stock-items-bulk-import-action-button.component.tsx +3 -3
  45. package/src/stock-items/add-bulk-stock-item/{stock-items-bulk-import.component.tsx → stock-items-bulk-import.modal.tsx} +20 -19
  46. package/src/stock-items/add-bulk-stock-item/stock-items-bulk-import.resource.ts +1 -1
  47. package/src/stock-items/add-bulk-stock-item/stock-items-bulk-import.test.tsx +59 -59
  48. package/src/stock-items/add-stock-item/add-stock-action-button.component.tsx +6 -6
  49. package/src/stock-items/add-stock-item/add-stock-item.component.tsx +6 -4
  50. package/src/stock-items/add-stock-item/add-stock-item.scss +5 -0
  51. package/src/stock-items/add-stock-item/add-stock-item.test.tsx +28 -43
  52. package/src/stock-items/add-stock-item/packaging-units/packaging-units-delete-modal.component.tsx +3 -4
  53. package/src/stock-items/add-stock-item/packaging-units/packaging-units.component.tsx +9 -10
  54. package/src/stock-items/add-stock-item/packaging-units/packaging-units.scss +4 -4
  55. package/src/stock-items/add-stock-item/stock-item-details/stock-item-details.component.tsx +27 -19
  56. package/src/stock-items/add-stock-item/stock-item-details/stock-item-details.scss +1 -1
  57. package/src/stock-items/add-stock-item/stock-item-references/stock-item-references.scss +4 -4
  58. package/src/stock-items/add-stock-item/stock-item-rules/add-stock-rules.component.tsx +15 -9
  59. package/src/stock-items/add-stock-item/stock-item-rules/add-stock-rules.scss +1 -0
  60. package/src/stock-items/add-stock-item/stock-item-rules/delete-stock-rule-modal.component.tsx +2 -1
  61. package/src/stock-items/add-stock-item/stock-item-rules/stock-item-rules.component.tsx +14 -16
  62. package/src/stock-items/add-stock-item/stock-item-rules/stock-item-rules.scss +7 -3
  63. package/src/stock-items/add-stock-item/transactions/printout/transactions-print-bincard-preview.modal.tsx +14 -6
  64. package/src/stock-items/add-stock-item/transactions/printout/transactions-print-stockcard-preview.modal.tsx +14 -8
  65. package/src/stock-items/edit-stock-item/edit-stock-item-action-menu.component.tsx +2 -2
  66. package/src/stock-items/stock-item.utils.tsx +3 -5
  67. package/src/stock-items/stock-items-table.component.tsx +47 -45
  68. package/src/stock-items/stock-items-table.resource.ts +2 -2
  69. package/src/stock-items/stock-items-table.scss +9 -8
  70. package/src/stock-items/stock-items-table.test.tsx +106 -65
  71. package/src/stock-items/stock-items.component.tsx +1 -1
  72. package/src/stock-locations/location-admin-form.component.tsx +5 -4
  73. package/src/stock-locations/stock-locations-table.component.tsx +10 -8
  74. package/src/stock-locations/stock-locations.component.tsx +1 -1
  75. package/src/stock-lookups/stock-lookups.resource.ts +3 -2
  76. package/src/stock-management-header/stock-management-header.scss +1 -1
  77. package/src/stock-operations/add-stock-operation/stock-operations-expanded-row/stock-operation-expanded-row.scss +1 -1
  78. package/src/stock-operations/stock-operations-dialog/stock-operations-dialog.component.tsx +2 -2
  79. package/src/stock-operations/stock-operations-filters.component.tsx +5 -5
  80. package/src/stock-operations/stock-operations-forms/input-components/batch-no-selector.component.tsx +11 -11
  81. package/src/stock-operations/stock-operations-forms/input-components/batch-no-selector.test.tsx +115 -25
  82. package/src/stock-operations/stock-operations-forms/input-components/qty-uim-selector.test.tsx +107 -65
  83. package/src/stock-operations/stock-operations-forms/input-components/quantity-uom-selector.component.tsx +9 -9
  84. package/src/stock-operations/stock-operations-forms/input-components/stock-operation-reason-selector.test.tsx +35 -153
  85. package/src/stock-operations/stock-operations-forms/input-components/user-selector.test.tsx +82 -29
  86. package/src/stock-operations/stock-operations-forms/step1.test.tsx +204 -69
  87. package/src/stock-operations/stock-operations-forms/step2.test.tsx +140 -63
  88. package/src/stock-operations/stock-operations-forms/step3.test.tsx +79 -60
  89. package/src/stock-operations/stock-operations-forms/steps/stock-operation-items-form-step.component.tsx +6 -5
  90. package/src/stock-operations/stock-operations-forms/steps/stock-operation-submission-form-step.component.tsx +12 -11
  91. package/src/stock-operations/stock-operations-forms/stock-item-form/stock-item-form.scss +1 -0
  92. package/src/stock-operations/stock-operations-forms/stock-item-form/stock-item-form.workspace.tsx +20 -12
  93. package/src/stock-operations/stock-operations-forms/stock-operation-form.scss +1 -0
  94. package/src/stock-operations/stock-operations-forms/stock-operation-stepper/stepper.scss +1 -3
  95. package/src/stock-operations/stock-operations-forms/stock-operation-stepper/stock-operation-stepper.component.tsx +2 -1
  96. package/src/stock-operations/stock-operations-table.component.tsx +66 -73
  97. package/src/stock-operations/stock-operations-table.scss +18 -9
  98. package/src/stock-operations/stock-operations.component.tsx +1 -1
  99. package/src/stock-reports/generate-report/create-stock-report.scss +3 -2
  100. package/src/stock-reports/generate-report/create-stock-report.workspace.tsx +32 -25
  101. package/src/stock-reports/report-list/stock-report-parameters.component.tsx +1 -1
  102. package/src/stock-reports/report-list/stock-report-status.component.tsx +1 -1
  103. package/src/stock-reports/report-list/stock-reports.component.tsx +24 -25
  104. package/src/stock-reports/report-list/stock-reports.scss +11 -3
  105. package/src/stock-sources/add-stock-sources/add-stock-sources.scss +12 -5
  106. package/src/stock-sources/add-stock-sources/add-stock-sources.test.tsx +38 -36
  107. package/src/stock-sources/add-stock-sources/add-stock-sources.workspace.tsx +35 -30
  108. package/src/stock-sources/delete-stock-modal.component.tsx +2 -1
  109. package/src/stock-sources/delete-stock-modal.scss +1 -1
  110. package/src/stock-sources/stock-sources-delete/stock-sources-delete.test.tsx +27 -36
  111. package/src/stock-sources/stock-sources-filter/stock-sources-filter.component.tsx +33 -21
  112. package/src/stock-sources/stock-sources-items-table.component.tsx +16 -17
  113. package/src/stock-sources/stock-sources-items-table.resource.ts +8 -6
  114. package/src/stock-sources/stock-sources-items-table.test.tsx +60 -37
  115. package/src/stock-sources/stock-sources.component.tsx +1 -1
  116. package/src/stock-sources/stock-sources.scss +6 -2
  117. package/src/stock-user-role-scopes/add-stock-user-scope/add-stock-user-role-scope.scss +5 -13
  118. package/src/stock-user-role-scopes/add-stock-user-scope/add-stock-user-role-scope.workspace.tsx +2 -2
  119. package/src/stock-user-role-scopes/delete-stock-user-scope-modal.component.tsx +2 -1
  120. package/src/stock-user-role-scopes/delete-stock-user-scope-modal.scss +1 -1
  121. package/src/stock-user-role-scopes/stock-user-role-scopes.component.tsx +1 -1
  122. package/src/stock-user-role-scopes/stock-user-role-scopes.scss +1 -1
  123. package/translations/en.json +5 -6
  124. package/tsconfig.json +4 -0
  125. package/dist/627.js +0 -1
  126. package/dist/627.js.map +0 -1
@@ -1,10 +1,13 @@
1
1
  import React from 'react';
2
- import { render, screen, waitFor, within } from '@testing-library/react';
3
2
  import userEvent from '@testing-library/user-event';
4
- import StockItemsTableComponent from './stock-items-table.component';
5
- import { useStockItemsPages } from './stock-items-table.resource';
3
+ import { render, screen, waitFor } from '@testing-library/react';
4
+ import { type StockItemDTO } from '../core/api/types/stockItem/StockItem';
6
5
  import { handleMutate } from '../utils';
7
- import { launchAddOrStockItemWorkspace } from './stock-item.utils';
6
+ import { launchAddOrEditStockItemWorkspace } from './stock-item.utils';
7
+ import { useStockItemsPages } from './stock-items-table.resource';
8
+ import StockItemsTableComponent from './stock-items-table.component';
9
+
10
+ const mockUseStockItemsPages = jest.mocked(useStockItemsPages);
8
11
 
9
12
  jest.mock('./stock-items-table.resource', () => ({
10
13
  useStockItemsPages: jest.fn(),
@@ -15,109 +18,146 @@ jest.mock('../utils', () => ({
15
18
  }));
16
19
 
17
20
  jest.mock('./stock-item.utils', () => ({
18
- launchAddOrStockItemWorkspace: jest.fn(),
19
- }));
20
-
21
- jest.mock('react-i18next', () => ({
22
- useTranslation: () => ({
23
- t: (key: string) => key,
24
- }),
21
+ launchAddOrEditStockItemWorkspace: jest.fn(),
25
22
  }));
26
23
 
27
24
  describe('StockItemsTableComponent', () => {
28
- const mockUseStockItemsPages = {
29
- isLoading: false,
30
- items: Array(25)
31
- .fill(null)
32
- .map((_, index) => ({
33
- uuid: `item-${index}`,
34
- commonName: `Test Item ${index}`,
35
- drugUuid: index % 2 === 0 ? `drug-${index}` : null,
36
- conceptName: `Concept ${index}`,
37
- dispensingUnitName: `Unit ${index}`,
38
- defaultStockOperationsUoMName: `UoM ${index}`,
39
- reorderLevel: index * 10,
40
- reorderLevelUoMName: 'Units',
41
- })),
42
- totalCount: 25,
43
- currentPageSize: 10,
44
- setPageSize: jest.fn(),
45
- pageSizes: [10, 20, 30],
46
- currentPage: 1,
47
- setCurrentPage: jest.fn(),
48
- isDrug: '',
49
- setDrug: jest.fn(),
50
- setSearchString: jest.fn(),
51
- };
52
-
53
25
  beforeEach(() => {
54
- jest.clearAllMocks();
55
- (useStockItemsPages as jest.Mock).mockReturnValue(mockUseStockItemsPages);
26
+ mockUseStockItemsPages.mockReturnValue({
27
+ isLoading: false,
28
+ items: Array(25)
29
+ .fill(null)
30
+ .map((_, index) => ({
31
+ uuid: `item-${index}`,
32
+ commonName: `Test Item ${index}`,
33
+ drugUuid: index % 2 === 0 ? `drug-${index}` : null,
34
+ conceptName: `Concept ${index}`,
35
+ dispensingUnitName: `Unit ${index}`,
36
+ defaultStockOperationsUoMName: `UoM ${index}`,
37
+ reorderLevel: index * 10,
38
+ reorderLevelUoMName: 'Units',
39
+ })) as StockItemDTO[],
40
+ pagination: {
41
+ results: [],
42
+ totalPages: 3,
43
+ currentPage: 1,
44
+ paginated: true,
45
+ showNextButton: true,
46
+ showPreviousButton: false,
47
+ goTo: jest.fn(),
48
+ goToNext: jest.fn(),
49
+ goToPrevious: jest.fn(),
50
+ },
51
+ error: null,
52
+ totalCount: 25,
53
+ currentPageSize: 10,
54
+ setPageSize: jest.fn(),
55
+ pageSizes: [10, 20, 30],
56
+ currentPage: 1,
57
+ setCurrentPage: jest.fn(),
58
+ isDrug: '',
59
+ setDrug: jest.fn(),
60
+ setSearchString: jest.fn(),
61
+ });
56
62
  });
57
63
 
58
64
  it('renders initial state and UI elements correctly', async () => {
59
- render(<StockItemsTableComponent />);
60
- expect(screen.getByText('panelDescription')).toBeInTheDocument();
61
- expect(screen.getByRole('searchbox')).toBeInTheDocument();
62
-
63
65
  const user = userEvent.setup();
64
- const menuButton = screen.getByTestId('stock-items-menu');
65
- await user.click(menuButton);
66
66
 
67
- await screen.findByText('Refresh');
67
+ render(<StockItemsTableComponent />);
68
68
 
69
- expect(screen.getByText('type')).toBeInTheDocument();
70
- expect(screen.getByText('genericName')).toBeInTheDocument();
71
- expect(screen.getByText('commonName')).toBeInTheDocument();
69
+ expect(screen.getByRole('radio', { name: /all/i })).toBeInTheDocument();
70
+ expect(screen.getByRole('radio', { name: /^pharmaceuticals$/i })).toBeInTheDocument();
71
+ expect(screen.getByRole('radio', { name: /non pharmaceuticals/i })).toBeInTheDocument();
72
+ expect(screen.getByRole('button', { name: /import/i })).toBeInTheDocument();
73
+ expect(screen.getByRole('button', { name: /settings/i })).toBeInTheDocument();
74
+ expect(screen.getByRole('searchbox', { name: /filter table/i })).toBeInTheDocument();
75
+
76
+ const settingsMenuButton = screen.getByRole('button', { name: /settings/i });
77
+ await user.click(settingsMenuButton);
78
+ await screen.findByText(/refresh/i);
79
+
80
+ expect(screen.getByRole('button', { name: /generic name/i })).toBeInTheDocument();
81
+ expect(screen.getByRole('button', { name: /common name/i })).toBeInTheDocument();
82
+ expect(screen.getByRole('button', { name: /dispensing uom/i })).toBeInTheDocument();
83
+ expect(screen.getByRole('button', { name: /bulk packaging/i })).toBeInTheDocument();
84
+ expect(screen.getByRole('button', { name: /reorder level/i })).toBeInTheDocument();
72
85
  });
73
86
 
74
87
  it('displays skeleton loader when isLoading is true', () => {
75
- (useStockItemsPages as jest.Mock).mockReturnValue({ ...mockUseStockItemsPages, isLoading: true });
88
+ mockUseStockItemsPages.mockReturnValue({
89
+ ...mockUseStockItemsPages,
90
+ isLoading: true,
91
+ items: [],
92
+ pagination: undefined,
93
+ totalCount: 0,
94
+ currentPageSize: 0,
95
+ currentPage: 0,
96
+ setCurrentPage: function (value: React.SetStateAction<number>): void {
97
+ throw new Error('Function not implemented.');
98
+ },
99
+ setPageSize: function (value: React.SetStateAction<number>): void {
100
+ throw new Error('Function not implemented.');
101
+ },
102
+ pageSizes: [],
103
+ error: undefined,
104
+ isDrug: '',
105
+ setDrug: undefined,
106
+ setSearchString: function (value: any): void {
107
+ throw new Error('Function not implemented.');
108
+ },
109
+ });
110
+
76
111
  render(<StockItemsTableComponent />);
112
+
77
113
  expect(screen.getByRole('progressbar')).toBeInTheDocument();
78
114
  });
79
115
 
80
116
  it('renders table rows correctly based on items prop', () => {
81
117
  render(<StockItemsTableComponent />);
82
- expect(screen.getByText('Test Item 0')).toBeInTheDocument();
83
- expect(screen.getAllByText('drug').length).toBeGreaterThan(0);
84
- expect(screen.getAllByText('other').length).toBeGreaterThan(0);
118
+
119
+ expect(screen.getByRole('cell', { name: /test item 0/i })).toBeInTheDocument();
120
+ expect(screen.getAllByRole('cell', { name: /drug/i }).length).toBeGreaterThan(0);
121
+ expect(screen.getAllByRole('cell', { name: /other/i }).length).toBeGreaterThan(0);
85
122
  });
86
123
 
87
124
  it('updates search input and triggers search function', async () => {
88
- render(<StockItemsTableComponent />);
89
125
  const user = userEvent.setup();
126
+
127
+ render(<StockItemsTableComponent />);
128
+
90
129
  const searchInput = screen.getByRole('searchbox');
91
130
  await user.type(searchInput, 'test search');
92
131
  await waitFor(
93
132
  () => {
94
- expect(mockUseStockItemsPages.setSearchString).toHaveBeenCalledWith('test search');
133
+ expect(mockUseStockItemsPages().setSearchString).toHaveBeenCalledWith('test search');
95
134
  },
96
135
  { timeout: 2000 },
97
136
  );
98
137
  });
99
138
 
100
139
  it('updates pagination when page or page size changes', async () => {
101
- render(<StockItemsTableComponent />);
102
140
  const user = userEvent.setup();
103
- const nextPageButton = screen.getByLabelText('Next page');
141
+ render(<StockItemsTableComponent />);
142
+
143
+ const nextPageButton = screen.getByLabelText(/next page/i);
104
144
  await user.click(nextPageButton);
105
- expect(mockUseStockItemsPages.setCurrentPage).toHaveBeenCalled();
145
+ expect(mockUseStockItemsPages().setCurrentPage).toHaveBeenCalled();
106
146
 
107
- const pageSizeSelect = screen.getByLabelText('Items per page:');
147
+ const pageSizeSelect = screen.getByLabelText(/items per page/i);
108
148
  await user.selectOptions(pageSizeSelect, '20');
109
- expect(mockUseStockItemsPages.setPageSize).toHaveBeenCalledWith(20);
149
+ expect(mockUseStockItemsPages().setPageSize).toHaveBeenCalledWith(20);
110
150
  });
111
151
 
112
152
  it('triggers handleRefresh when refresh button is clicked', async () => {
153
+ const user = userEvent.setup();
113
154
  render(<StockItemsTableComponent />);
114
155
 
115
- const user = userEvent.setup();
116
156
  const menuButton = screen.getByTestId('stock-items-menu');
117
157
  expect(menuButton).toBeInTheDocument();
118
158
  await user.click(menuButton);
119
159
 
120
- const refreshButton = await screen.findByText('Refresh');
160
+ const refreshButton = await screen.findByText(/refresh/i);
121
161
  expect(refreshButton).toBeInTheDocument();
122
162
  await user.click(refreshButton);
123
163
  await waitFor(() => {
@@ -125,13 +165,14 @@ describe('StockItemsTableComponent', () => {
125
165
  });
126
166
  });
127
167
 
128
- it('triggers launchAddOrEditDialog when edit button is clicked', async () => {
129
- render(<StockItemsTableComponent />);
168
+ it('triggers launchAddOrEditStockItemWorkspace when edit button is clicked', async () => {
130
169
  const user = userEvent.setup();
131
- const editButtons = screen.getAllByLabelText('Edit Stock Item');
170
+ render(<StockItemsTableComponent />);
171
+
172
+ const editButtons = screen.getAllByLabelText(/edit stock item/i);
132
173
  await user.click(editButtons[0]);
133
174
 
134
- expect(launchAddOrStockItemWorkspace).toHaveBeenCalledWith(
175
+ expect(launchAddOrEditStockItemWorkspace).toHaveBeenCalledWith(
135
176
  expect.any(Function),
136
177
  expect.objectContaining({ uuid: 'item-0' }),
137
178
  );
@@ -3,7 +3,7 @@ import StockItemsTableComponent from './stock-items-table.component';
3
3
 
4
4
  const StockItems = () => {
5
5
  return (
6
- <div style={{ margin: '5px' }}>
6
+ <div style={{ margin: '1rem' }}>
7
7
  <StockItemsTableComponent />
8
8
  </div>
9
9
  );
@@ -5,19 +5,20 @@ import { zodResolver } from '@hookform/resolvers/zod';
5
5
  import {
6
6
  Button,
7
7
  ComposedModal,
8
+ FilterableMultiSelect,
8
9
  FormGroup,
10
+ InlineNotification,
9
11
  ModalBody,
10
12
  ModalFooter,
11
13
  ModalHeader,
12
14
  Stack,
13
15
  TextInput,
14
- InlineNotification,
15
- FilterableMultiSelect,
16
16
  } from '@carbon/react';
17
+ import { getCoreTranslation } from '@openmrs/esm-framework';
17
18
  import { useTranslation } from 'react-i18next';
18
19
  import { type locationData } from '../stock-items/types';
19
- import styles from './stock-locations-table.scss';
20
20
  import { useLocationTags } from './stock-locations-table.resource';
21
+ import styles from './stock-locations-table.scss';
21
22
 
22
23
  const LocationAdministrationSchema = z.object({
23
24
  name: z.string().max(255),
@@ -146,7 +147,7 @@ const LocationAdministrationForm: React.FC<LocationAdministrationFormProps> = ({
146
147
  </ModalBody>
147
148
  <ModalFooter>
148
149
  <Button onClick={() => onModalChange(false)} kind="secondary">
149
- {t('cancel', 'Cancel')}
150
+ {getCoreTranslation('cancel')}
150
151
  </Button>
151
152
  <Button disabled={!isDirty} type="submit">
152
153
  <span>{t('save', 'Save')}</span>
@@ -1,5 +1,4 @@
1
1
  import React, { useState } from 'react';
2
- import { useStockLocationPages } from './stock-locations-table.resource';
3
2
  import {
4
3
  Button,
5
4
  DataTableSkeleton,
@@ -8,15 +7,16 @@ import {
8
7
  TableToolbarSearch,
9
8
  Tile,
10
9
  } from '@carbon/react';
11
- import styles from '../stock-items/stock-items-table.scss';
12
- import { ResourceRepresentation } from '../core/api/api';
13
- import DataList from '../core/components/table/table.component';
10
+ import { Add } from '@carbon/react/icons';
14
11
  import { useTranslation } from 'react-i18next';
15
12
  import { mutate } from 'swr';
16
- import NewLocationForm from './add-locations-form.workspace';
17
- import { Add } from '@carbon/react/icons';
18
- import { handleMutate } from '../utils';
19
13
  import { restBaseUrl } from '@openmrs/esm-framework';
14
+ import { handleMutate } from '../utils';
15
+ import { ResourceRepresentation } from '../core/api/api';
16
+ import { useStockLocationPages } from './stock-locations-table.resource';
17
+ import DataList from '../core/components/table/table.component';
18
+ import NewLocationForm from './add-locations-form.workspace';
19
+ import styles from '../stock-items/stock-items-table.scss';
20
20
 
21
21
  interface StockLocationsTableProps {
22
22
  status?: string;
@@ -46,7 +46,9 @@ const StockLocationsItems: React.FC<StockLocationsTableProps> = () => {
46
46
  <>
47
47
  <TableToolbarSearch persistent onChange={onInputChange} />
48
48
  <TableToolbarMenu>
49
- <TableToolbarAction onClick={handleRefresh}>Refresh</TableToolbarAction>
49
+ <TableToolbarAction className={styles.toolbarMenuAction} onClick={handleRefresh}>
50
+ {t('refresh', 'Refresh')}
51
+ </TableToolbarAction>
50
52
  </TableToolbarMenu>
51
53
  {showLocationModal ? (
52
54
  <NewLocationForm onModalChange={setAddLocationModal} showModal={showLocationModal} mutate={mutate} />
@@ -3,7 +3,7 @@ import StockLocationsItems from './stock-locations-table.component';
3
3
 
4
4
  function StockLocations() {
5
5
  return (
6
- <div style={{ margin: '5px' }}>
6
+ <div style={{ margin: '1rem' }}>
7
7
  <StockLocationsItems />
8
8
  </div>
9
9
  );
@@ -190,9 +190,10 @@ export function useConcept(conceptUuid: string) {
190
190
  data: Concept;
191
191
  },
192
192
  Error
193
- >(apiUrl, openmrsFetch);
193
+ >(conceptUuid ? apiUrl : null, openmrsFetch);
194
+
194
195
  return {
195
- items: data?.data || <Concept>{},
196
+ items: data?.data,
196
197
  isLoading,
197
198
  error,
198
199
  };
@@ -1,7 +1,7 @@
1
1
  @use '@carbon/colors';
2
2
  @use '@carbon/layout';
3
3
  @use '@carbon/type';
4
- @use '~@openmrs/esm-styleguide/src/vars' as *;
4
+ @use '@openmrs/esm-styleguide/src/vars' as *;
5
5
 
6
6
  .header {
7
7
  @include type.type-style('body-compact-02');
@@ -1,6 +1,6 @@
1
1
  @use '@carbon/layout';
2
2
  @use '@carbon/type';
3
- @use '~@openmrs/esm-styleguide/src/vars' as *;
3
+ @use '@openmrs/esm-styleguide/src/vars' as *;
4
4
 
5
5
  .textHeading {
6
6
  font-weight: bold;
@@ -8,7 +8,7 @@ import {
8
8
  type StopOperationActionType,
9
9
  } from '../../core/api/types/stockOperation/StockOperationAction';
10
10
  import { executeStockOperationAction } from '../stock-operations.resource';
11
- import { restBaseUrl, showSnackbar } from '@openmrs/esm-framework';
11
+ import { getCoreTranslation, restBaseUrl, showSnackbar } from '@openmrs/esm-framework';
12
12
  import { closeOverlay } from '../../core/components/overlay/hook';
13
13
  import { extractErrorMessagesFromResponse } from '../../constants';
14
14
  import { handleMutate } from '../../utils';
@@ -135,7 +135,7 @@ const StockOperationDialog: React.FC<StockOperationDialogProps> = ({ title, requ
135
135
  </ModalBody>
136
136
  <ModalFooter>
137
137
  <Button kind="secondary" onClick={closeModal}>
138
- {t('cancel', 'Cancel')}
138
+ {getCoreTranslation('cancel')}
139
139
  </Button>
140
140
  {isApproving ? <InlineLoading /> : <Button type="submit">{t('submit', 'Submit')}</Button>}
141
141
  </ModalFooter>
@@ -1,14 +1,14 @@
1
1
  import React, { useEffect, useState } from 'react';
2
- import { getStockOperationTypes, useConcept } from '../stock-lookups/stock-lookups.resource';
3
2
  import { DropdownSkeleton, MultiSelect } from '@carbon/react';
3
+ import { getStockOperationTypes, useConcept } from '../stock-lookups/stock-lookups.resource';
4
4
  import { StockFilters } from '../constants';
5
5
  import { StockOperationStatusTypes } from '../core/api/types/stockOperation/StockOperationStatus';
6
6
  import styles from '../stock-items/stock-items-table.scss';
7
7
 
8
8
  interface StockOperationFiltersProps {
9
9
  conceptUuid?: string;
10
- onFilterChange: (selectedItems: any[], filterType: string) => void;
11
10
  filterName: string;
11
+ onFilterChange: (selectedItems: any[], filterType: string) => void;
12
12
  }
13
13
 
14
14
  const StockOperationsFilters: React.FC<StockOperationFiltersProps> = ({ conceptUuid, onFilterChange, filterName }) => {
@@ -58,11 +58,12 @@ const StockOperationsFilters: React.FC<StockOperationFiltersProps> = ({ conceptU
58
58
 
59
59
  return (
60
60
  <MultiSelect
61
+ autoAlign
61
62
  className={styles.filtersAlign}
63
+ disabled={!dataItems.length}
62
64
  id="multiSelect"
63
65
  label={filterName}
64
- size="md"
65
- labelInline={true}
66
+ labelInline
66
67
  items={dataItems}
67
68
  itemToString={(item) => (item ? item.display : 'Not Set')}
68
69
  onChange={({ selectedItems }) => {
@@ -74,7 +75,6 @@ const StockOperationsFilters: React.FC<StockOperationFiltersProps> = ({ conceptU
74
75
  }
75
76
  }}
76
77
  placeholder={`Filter by ${filterName}`}
77
- style={{ minWidth: 'auto', width: 'auto' }}
78
78
  />
79
79
  );
80
80
  };
@@ -1,10 +1,10 @@
1
- import { ComboBox, SelectSkeleton } from '@carbon/react';
2
1
  import React, { useEffect, useMemo } from 'react';
3
2
  import { useTranslation } from 'react-i18next';
3
+ import { ComboBox, SelectSkeleton } from '@carbon/react';
4
4
  import { type StockBatchDTO } from '../../../core/api/types/stockItem/StockBatchDTO';
5
+ import { formatForDatePicker } from '../../../constants';
5
6
  import { useStockItemBatchInformationHook } from '../../../stock-items/add-stock-item/batch-information/batch-information.resource';
6
7
  import { useStockItemBatchNumbers } from '../hooks/useStockItemBatchNumbers';
7
- import { formatForDatePicker } from '../../../constants';
8
8
 
9
9
  interface BatchNoSelectorProps {
10
10
  stockItemUuid: string;
@@ -48,23 +48,23 @@ const BatchNoSelector: React.FC<BatchNoSelectorProps> = ({ stockItemUuid, error,
48
48
 
49
49
  return (
50
50
  <ComboBox
51
- style={{ flexGrow: '1' }}
52
- titleText={t('batchNo', 'Batch')}
53
- name={'stockBatchUuid'}
54
51
  id={'stockBatchUuid'}
52
+ invalid={!!error}
53
+ invalidText={error}
55
54
  items={filteredBatches || []}
56
- onChange={(data: { selectedItem?: StockBatchDTO }) => {
57
- onValueChange(data.selectedItem?.uuid);
58
- }}
59
- selectedItem={initialSelectedItem}
60
55
  itemToString={(s: StockBatchDTO) =>
61
56
  s?.batchNo
62
57
  ? `${s?.batchNo} | Qty: ${s?.quantity ?? 'Unknown'} | Expiry: ${formatForDatePicker(s.expiration)}`
63
58
  : ''
64
59
  }
60
+ name={'stockBatchUuid'}
61
+ onChange={(data: { selectedItem?: StockBatchDTO }) => {
62
+ onValueChange(data.selectedItem?.uuid);
63
+ }}
65
64
  placeholder={t('filter', "'Filter") + '...'}
66
- invalid={error}
67
- invalidText={error}
65
+ selectedItem={initialSelectedItem}
66
+ style={{ flexGrow: '1' }}
67
+ titleText={t('batchNo', 'Batch no.')}
68
68
  />
69
69
  );
70
70
  };
@@ -1,64 +1,127 @@
1
- import { render, screen } from '@testing-library/react';
2
- import userEvent from '@testing-library/user-event';
3
1
  import React from 'react';
2
+ import userEvent from '@testing-library/user-event';
3
+ import { render, screen } from '@testing-library/react';
4
+ import { type StockBatchDTO } from '../../../core/api/types/stockItem/StockBatchDTO';
4
5
  import { type StockItemInventory } from '../../../core/api/types/stockItem/StockItemInventory';
6
+ import { formatForDatePicker } from '../../../constants';
5
7
  import { useStockItemBatchInformationHook } from '../../../stock-items/add-stock-item/batch-information/batch-information.resource';
6
8
  import { useStockItemBatchNumbers } from '../hooks/useStockItemBatchNumbers';
7
9
  import BatchNoSelector from './batch-no-selector.component';
8
- import { formatForDatePicker } from '../../../constants';
9
10
 
10
- jest.mock('../hooks/useStockItemBatchNumbers');
11
- jest.mock('../../../stock-items/add-stock-item/batch-information/batch-information.resource');
12
- jest.mock('react-i18next', () => ({
13
- useTranslation: () => ({ t: (key: string) => key }),
11
+ jest.mock('../hooks/useStockItemBatchNumbers', () => ({
12
+ useStockItemBatchNumbers: jest.fn(),
13
+ }));
14
+
15
+ jest.mock('../../../stock-items/add-stock-item/batch-information/batch-information.resource', () => ({
16
+ useStockItemBatchInformationHook: jest.fn(),
14
17
  }));
15
18
 
16
- const mockUseStockItemBatchNumbers = useStockItemBatchNumbers as jest.Mock;
17
- const mockUseStockItemBatchInformationHook = useStockItemBatchInformationHook as jest.Mock;
19
+ const mockUseStockItemBatchNumbers = jest.mocked(useStockItemBatchNumbers);
20
+ const mockUseStockItemBatchInformationHook = jest.mocked(useStockItemBatchInformationHook);
18
21
 
19
22
  describe('BatchNoSelector', () => {
20
23
  const mockOnValueChange = jest.fn();
21
24
  const mockStockItemUuid = 'test-uuid';
22
25
  const mockExpiration = new Date();
23
- beforeEach(() => {
24
- jest.clearAllMocks();
25
26
 
27
+ beforeEach(() => {
26
28
  mockUseStockItemBatchNumbers.mockReturnValue({
27
29
  isLoading: false,
28
30
  stockItemBatchNos: [
29
- { uuid: '1', batchNo: 'BATCH-001', quantity: 10, expiration: mockExpiration },
30
- { uuid: '2', batchNo: 'BATCH-002', quantity: 20, expiration: mockExpiration },
31
+ {
32
+ uuid: '1',
33
+ batchNo: 'BATCH-001',
34
+ quantity: '10',
35
+ expiration: mockExpiration,
36
+ stockItemUuid: 'item1',
37
+ voided: false,
38
+ },
39
+ {
40
+ uuid: '2',
41
+ batchNo: 'BATCH-002',
42
+ quantity: '20',
43
+ expiration: mockExpiration,
44
+ stockItemUuid: 'item2',
45
+ voided: false,
46
+ },
31
47
  ],
48
+ setLimit: jest.fn(),
49
+ setRepresentation: jest.fn(),
50
+ setSearchString: jest.fn(),
32
51
  });
52
+
33
53
  mockUseStockItemBatchInformationHook.mockReturnValue({
34
54
  items: [
35
55
  { batchNumber: 'BATCH-001', quantity: 10 },
36
56
  { batchNumber: 'BATCH-002', quantity: 20 },
37
57
  ] as StockItemInventory[],
58
+ totalCount: 2,
59
+ currentPage: 1,
60
+ currentPageSize: 10,
61
+ setCurrentPage: jest.fn(),
62
+ setStockBatchUuid: jest.fn(),
38
63
  setStockItemUuid: jest.fn(),
64
+ setPageSize: jest.fn(),
65
+ pageSizes: [10, 20, 50],
66
+ isLoading: false,
67
+ error: null,
68
+ setLocationUuid: jest.fn(),
69
+ setPartyUuid: jest.fn(),
70
+ setSearchString: jest.fn(),
39
71
  });
40
72
  });
41
73
 
42
- it('should render loading skeleton when isLoading is true', () => {
43
- mockUseStockItemBatchNumbers.mockReturnValue({ isLoading: true, stockItemBatchNos: [] });
44
- mockUseStockItemBatchInformationHook.mockReturnValue({ isLoading: true, items: [], setStockItemUuid: jest.fn() });
74
+ it('should render a loading skeleton when "isLoading" is true', () => {
75
+ mockUseStockItemBatchNumbers.mockReturnValue({
76
+ isLoading: true,
77
+ stockItemBatchNos: [] as StockBatchDTO[],
78
+ setLimit: jest.fn(),
79
+ setRepresentation: jest.fn(),
80
+ setSearchString: jest.fn(),
81
+ });
82
+
83
+ mockUseStockItemBatchInformationHook.mockReturnValue({
84
+ isLoading: true,
85
+ items: [],
86
+ setStockItemUuid: jest.fn(),
87
+ totalCount: 0,
88
+ currentPage: 1,
89
+ currentPageSize: 10,
90
+ setCurrentPage: jest.fn(),
91
+ setStockBatchUuid: jest.fn(),
92
+ setPageSize: jest.fn(),
93
+ pageSizes: [10, 20, 50],
94
+ error: null,
95
+ setLocationUuid: jest.fn(),
96
+ setPartyUuid: jest.fn(),
97
+ setSearchString: jest.fn(),
98
+ });
99
+
45
100
  render(<BatchNoSelector stockItemUuid={mockStockItemUuid} onValueChange={mockOnValueChange} />);
101
+
46
102
  expect(screen.getByRole('progressbar')).toBeInTheDocument();
47
103
  });
48
104
 
49
- it('should render combobox with batch numbers', async () => {
105
+ it('should render a combobox with batch numbers', async () => {
50
106
  render(<BatchNoSelector stockItemUuid={mockStockItemUuid} onValueChange={mockOnValueChange} />);
107
+
51
108
  expect(screen.getByRole('combobox')).toBeInTheDocument();
52
- expect(screen.getByText('batchNo')).toBeInTheDocument();
109
+ expect(screen.getByText(/batch no./i)).toBeInTheDocument();
53
110
  });
54
111
 
55
112
  it('should handle batch selection', async () => {
113
+ const user = userEvent.setup();
114
+
56
115
  render(<BatchNoSelector stockItemUuid={mockStockItemUuid} onValueChange={mockOnValueChange} />);
116
+
57
117
  const combobox = screen.getByRole('combobox');
58
- await userEvent.click(combobox);
59
- await userEvent.type(combobox, 'BATCH-001');
118
+ await user.click(combobox);
119
+ await user.type(combobox, 'BATCH-001');
120
+
60
121
  const option = screen.getByText(`BATCH-001 | Qty: 10 | Expiry: ${formatForDatePicker(mockExpiration)}`);
61
- await userEvent.click(option);
122
+
123
+ await user.click(option);
124
+
62
125
  expect(mockOnValueChange).toHaveBeenCalledWith('1');
63
126
  });
64
127
 
@@ -71,18 +134,45 @@ describe('BatchNoSelector', () => {
71
134
  });
72
135
 
73
136
  it('should filter out batches with zero or undefined quantity', async () => {
137
+ const user = userEvent.setup();
138
+
74
139
  mockUseStockItemBatchNumbers.mockReturnValue({
75
140
  isLoading: false,
76
141
  stockItemBatchNos: [
77
- { uuid: '1', batchNo: 'BATCH-001', quantity: 10 },
78
- { uuid: '2', batchNo: 'BATCH-002', quantity: 0 },
79
- { uuid: '3', batchNo: 'BATCH-003', quantity: undefined },
142
+ {
143
+ uuid: '1',
144
+ batchNo: 'BATCH-001',
145
+ quantity: '10',
146
+ expiration: mockExpiration,
147
+ stockItemUuid: 'item1',
148
+ voided: false,
149
+ },
150
+ {
151
+ uuid: '2',
152
+ batchNo: 'BATCH-002',
153
+ quantity: '0',
154
+ expiration: mockExpiration,
155
+ stockItemUuid: 'item2',
156
+ voided: false,
157
+ },
158
+ {
159
+ uuid: '3',
160
+ batchNo: 'BATCH-003',
161
+ quantity: undefined,
162
+ expiration: mockExpiration,
163
+ stockItemUuid: 'item3',
164
+ voided: false,
165
+ },
80
166
  ],
167
+ setLimit: jest.fn(),
168
+ setRepresentation: jest.fn(),
169
+ setSearchString: jest.fn(),
81
170
  });
82
171
 
83
172
  render(<BatchNoSelector stockItemUuid={mockStockItemUuid} onValueChange={mockOnValueChange} />);
173
+
84
174
  const combobox = screen.getByRole('combobox');
85
- await userEvent.click(combobox);
175
+ await user.click(combobox);
86
176
 
87
177
  expect(screen.queryByText('BATCH-002')).not.toBeInTheDocument();
88
178
  expect(screen.queryByText('BATCH-003')).not.toBeInTheDocument();