@openmrs/esm-stock-management-app 1.0.1-pre.666 → 1.0.1-pre.680

Sign up to get free protection for your applications and to get access to all the features.
Files changed (36) hide show
  1. package/dist/172.js +1 -1
  2. package/dist/20.js +1 -1
  3. package/dist/26.js +1 -1
  4. package/dist/26.js.LICENSE.txt +20 -28
  5. package/dist/26.js.map +1 -1
  6. package/dist/273.js +2 -0
  7. package/dist/273.js.LICENSE.txt +40 -0
  8. package/dist/273.js.map +1 -0
  9. package/dist/290.js +1 -1
  10. package/dist/606.js +1 -1
  11. package/dist/627.js +1 -1
  12. package/dist/690.js +1 -0
  13. package/dist/690.js.map +1 -0
  14. package/dist/main.js +1 -1
  15. package/dist/main.js.map +1 -1
  16. package/dist/openmrs-esm-stock-management-app.js +1 -1
  17. package/dist/openmrs-esm-stock-management-app.js.buildmanifest.json +57 -57
  18. package/dist/openmrs-esm-stock-management-app.js.map +1 -1
  19. package/dist/routes.json +1 -1
  20. package/package.json +1 -1
  21. package/src/stock-items/add-stock-item/transactions/transaction-filters/transaction-locations-filter.component.tsx +0 -1
  22. package/src/stock-items/add-stock-item/transactions/transactions.component.tsx +16 -47
  23. package/src/stock-operations/add-stock-operation/add-stock-operation.component.tsx +18 -174
  24. package/src/stock-operations/add-stock-operation/add-stock-operation.scss +19 -0
  25. package/src/stock-operations/add-stock-operation/stock-operation-reference.component.tsx +38 -0
  26. package/src/stock-operations/add-stock-operation/stock-operation-related-link.component.tsx +38 -0
  27. package/src/stock-operations/add-stock-operation/stock-operation-status.component.tsx +170 -0
  28. package/src/stock-operations/edit-stock-operation/edit-stock-operation-action-menu.component.tsx +1 -1
  29. package/src/stock-operations/stock-operations-table.component.tsx +17 -156
  30. package/src/stock-operations/stock-operations-table.scss +18 -0
  31. package/src/stock-sources/stock-sources-items-table.test.tsx +248 -0
  32. package/dist/241.js +0 -1
  33. package/dist/241.js.map +0 -1
  34. package/dist/764.js +0 -2
  35. package/dist/764.js.LICENSE.txt +0 -32
  36. package/dist/764.js.map +0 -1
@@ -20,26 +20,15 @@ import {
20
20
  TableToolbarContent,
21
21
  TableToolbarSearch,
22
22
  Tile,
23
- StructuredListHead,
24
- StructuredListRow,
25
- StructuredListCell,
26
- StructuredListBody,
27
23
  DatePickerInput,
28
24
  DatePicker,
29
25
  TableToolbarMenu,
30
26
  TableToolbarAction,
31
- Button,
32
27
  InlineLoading,
33
28
  } from '@carbon/react';
34
- import { ArrowRight, Edit } from '@carbon/react/icons';
29
+ import { ArrowRight } from '@carbon/react/icons';
35
30
  import { formatDisplayDate } from '../core/utils/datetimeUtils';
36
- import {
37
- StockOperationStatusCancelled,
38
- StockOperationStatusNew,
39
- StockOperationStatusRejected,
40
- StockOperationStatusReturned,
41
- } from '../core/api/types/stockOperation/StockOperationStatus';
42
- import { isDesktop, restBaseUrl, useConfig, showModal } from '@openmrs/esm-framework';
31
+ import { isDesktop, restBaseUrl } from '@openmrs/esm-framework';
43
32
  import StockOperationTypesSelector from './stock-operation-types-selector/stock-operation-types-selector.component';
44
33
  import { launchAddOrEditDialog } from './stock-operation.utils';
45
34
  import { initialStockOperationValue } from '../core/utils/utils';
@@ -51,6 +40,7 @@ import { DATE_PICKER_CONTROL_FORMAT, DATE_PICKER_FORMAT, StockFilters } from '..
51
40
  import { handleMutate } from '../utils';
52
41
 
53
42
  import styles from './stock-operations-table.scss';
43
+ import StockOperationStatus from './add-stock-operation/stock-operation-status.component';
54
44
 
55
45
  interface StockOperationsTableProps {
56
46
  status?: string;
@@ -164,94 +154,15 @@ const StockOperations: React.FC<StockOperationsTableProps> = () => {
164
154
  destination: `${stockOperation?.destinationName ?? ''}`,
165
155
  location: (
166
156
  <>
167
- {' '}
168
- {stockOperation?.sourceName ?? ''}{' '}
157
+ {stockOperation?.sourceName ?? ''}
169
158
  {stockOperation?.sourceName && stockOperation?.destinationName ? <ArrowRight size={16} /> : ''}{' '}
170
- {stockOperation?.destinationName ?? ''}{' '}
159
+ {stockOperation?.destinationName ?? ''}
171
160
  </>
172
161
  ),
173
162
  responsiblePerson: `${
174
163
  stockOperation?.responsiblePersonFamilyName ?? stockOperation?.responsiblePersonOther ?? ''
175
164
  } ${stockOperation?.responsiblePersonGivenName ?? ''}`,
176
165
  operationDate: formatDisplayDate(stockOperation?.operationDate),
177
- details: (
178
- <div className="tbl-expand-display-fields">
179
- <div className="field-label">
180
- <span className="field-title"> {t('created', 'Created')}</span>
181
- <span className="field-desc">
182
- <span className="action-date">{formatDisplayDate(stockOperation?.dateCreated)}</span> {t('by', 'By')}
183
- <span className="action-by">
184
- {stockOperation.creatorFamilyName ?? ''} {stockOperation.creatorGivenName ?? ''}
185
- </span>
186
- </span>
187
- </div>
188
- {stockOperation?.status !== StockOperationStatusNew &&
189
- stockOperation?.status !== StockOperationStatusReturned &&
190
- stockOperation?.submittedDate && (
191
- <div className="field-label">
192
- <span className="field-title">{t('submitted', 'Submitted')}</span>
193
- <span className="field-desc">
194
- <span className="action-date">{formatDisplayDate(stockOperation?.submittedDate)}</span>{' '}
195
- {t('by', 'By')}
196
- <span className="action-by">
197
- {stockOperation.submittedByFamilyName ?? ''} {stockOperation.submittedByGivenName ?? ''}
198
- </span>
199
- </span>
200
- </div>
201
- )}
202
-
203
- {stockOperation?.completedDate && (
204
- <div className="field-label">
205
- <span className="field-title">{t('completed', 'Completed')}</span>
206
- <span className="field-desc">
207
- <span className="action-date">{formatDisplayDate(stockOperation?.completedDate)}</span> {t('by', 'By')}
208
- <span className="action-by">
209
- {stockOperation.completedByFamilyName ?? ''} {stockOperation.completedByGivenName ?? ''}
210
- </span>
211
- </span>
212
- </div>
213
- )}
214
-
215
- {stockOperation?.status === StockOperationStatusCancelled && (
216
- <div className="field-label">
217
- <span className="field-title"> {t('cancelled', 'Cancelled')}</span>
218
- <span className="field-desc">
219
- <span className="action-date">{formatDisplayDate(stockOperation?.cancelledDate)}</span> {t('by', 'By')}
220
- <span className="action-by">
221
- {stockOperation.cancelledByFamilyName ?? ''} {stockOperation.cancelledByGivenName ?? ''}
222
- </span>
223
- <p>{stockOperation.cancelReason}</p>
224
- </span>
225
- </div>
226
- )}
227
-
228
- {stockOperation?.status === StockOperationStatusRejected && (
229
- <div className="field-label">
230
- <span className="field-title">Rejected</span>
231
- <span className="field-desc">
232
- <span className="action-date">{formatDisplayDate(stockOperation?.rejectedDate)}</span> {t('by', 'By')}
233
- <span className="action-by">
234
- {stockOperation.rejectedByFamilyName ?? ''} {stockOperation.rejectedByGivenName ?? ''}
235
- </span>
236
- <p>{stockOperation.rejectionReason}</p>
237
- </span>
238
- </div>
239
- )}
240
-
241
- {stockOperation?.status === StockOperationStatusReturned && (
242
- <div className="field-label">
243
- <span className="field-title">Returned</span>
244
- <span className="field-desc">
245
- <span className="action-date">{formatDisplayDate(stockOperation?.returnedDate)}</span> By
246
- <span className="action-by">
247
- {stockOperation.returnedByFamilyName ?? ''} {stockOperation.returnedByGivenName ?? ''}
248
- </span>
249
- <p>{stockOperation.returnReason}</p>
250
- </span>
251
- </div>
252
- )}
253
- </div>
254
- ),
255
166
  actions: (
256
167
  <EditStockOperationActionMenu
257
168
  model={stockOperation}
@@ -264,7 +175,7 @@ const StockOperations: React.FC<StockOperationsTableProps> = () => {
264
175
  />
265
176
  ),
266
177
  }));
267
- }, [items, operations, t, handleEditClick, operation]);
178
+ }, [items, operations, handleEditClick, operation]);
268
179
 
269
180
  if (isLoading && !filterApplied) {
270
181
  return (
@@ -358,74 +269,24 @@ const StockOperations: React.FC<StockOperationsTableProps> = () => {
358
269
  </TableRow>
359
270
  </TableHead>
360
271
  <TableBody>
361
- {rows.map((row: any, index) => {
272
+ {rows?.map((row: any, index) => {
362
273
  return (
363
274
  <React.Fragment key={row.id}>
364
275
  <TableExpandRow
365
276
  className={isDesktop ? styles.desktopRow : styles.tabletRow}
366
277
  {...getRowProps({ row })}
367
278
  >
368
- {row.cells.map(
369
- (cell: any) =>
370
- cell?.info?.header !== 'details' && <TableCell key={cell.id}>{cell.value}</TableCell>,
371
- )}
279
+ {row.cells.map((cell) => (
280
+ <TableCell key={cell.id}>{cell.value}</TableCell>
281
+ ))}
372
282
  </TableExpandRow>
373
- <TableExpandedRow colSpan={headers.length + 2}>
374
- <>
375
- <StructuredListHead>
376
- <StructuredListRow head>
377
- <StructuredListCell head>{t('dateCreated', 'Date Created')}</StructuredListCell>
378
- <StructuredListCell head>{t('dateCompleted', 'Date Completed')}</StructuredListCell>
379
- </StructuredListRow>
380
- </StructuredListHead>
381
- <StructuredListBody>
382
- <StructuredListRow>
383
- <StructuredListCell noWrap>
384
- {items[index]?.dateCreated ? formatDisplayDate(items[index]?.dateCreated) : ''}
385
- &nbsp;
386
- {items[index]?.dateCreated ? 'By' : ''}
387
- &nbsp;
388
- {items[index]?.dateCreated ? items[index]?.creatorFamilyName : ''}
389
- </StructuredListCell>
390
- <StructuredListCell>
391
- {items[index]?.completedDate ? formatDisplayDate(items[index]?.completedDate) : ''}
392
- &nbsp;
393
- {items[index]?.completedDate ? 'By' : ''}
394
- &nbsp;
395
- {items[index]?.completedDate ? items[index]?.creatorFamilyName : ''}
396
- </StructuredListCell>
397
- </StructuredListRow>
398
- <StructuredListRow>
399
- <StructuredListCell noWrap>
400
- {items[index]?.stockOperationItems.map((item) => item.quantity)[1]
401
- ? formatDisplayDate(items[index]?.dateCreated)
402
- : ''}
403
- &nbsp;
404
- {items[index]?.stockOperationItems.map((item) => item.quantity)[1] ? 'By' : ''}
405
- &nbsp;
406
- {items[index]?.stockOperationItems.map((item) => item.quantity)[1]
407
- ? items[index]?.creatorFamilyName
408
- : ''}
409
- </StructuredListCell>
410
- <StructuredListCell>
411
- {items[index]?.stockOperationItems.map((item) => item.quantity)[1]
412
- ? formatDisplayDate(items[index]?.completedDate)
413
- : ''}
414
- &nbsp;
415
- {items[index]?.stockOperationItems.map((item) => item.quantity)[1] &&
416
- items[index]?.completedDate
417
- ? 'By'
418
- : ''}
419
- &nbsp;
420
- {items[index]?.stockOperationItems.map((item) => item.quantity)[1] &&
421
- items[index]?.completedDate
422
- ? items[index]?.creatorFamilyName
423
- : ''}
424
- </StructuredListCell>
425
- </StructuredListRow>
426
- </StructuredListBody>
427
- </>
428
- </TableExpandedRow>
283
+ {row.isExpanded ? (
284
+ <TableExpandedRow colSpan={headers.length + 2}>
285
+ <StockOperationStatus model={items[index]} />
286
+ </TableExpandedRow>
287
+ ) : (
288
+ <TableExpandedRow className={styles.hiddenRow} colSpan={headers.length + 2} />
289
+ )}
429
290
  </React.Fragment>
430
291
  );
431
292
  })}
@@ -58,3 +58,21 @@
58
58
  margin: auto;
59
59
  width: 20%;
60
60
  }
61
+
62
+ .statusContainer {
63
+ display: flex;
64
+ margin: 10px;
65
+ gap: 1rem;
66
+ }
67
+
68
+ .textHeading {
69
+ font-weight: bold;
70
+ }
71
+
72
+ .statusDescriptions {
73
+ margin-top: 4px;
74
+
75
+ .text {
76
+ margin-right: 4px;
77
+ }
78
+ }
@@ -0,0 +1,248 @@
1
+ import React from 'react';
2
+ import { renderHook, act, render, screen, waitFor } from '@testing-library/react';
3
+ import userEvent from '@testing-library/user-event';
4
+ import '@testing-library/jest-dom/extend-expect';
5
+ import StockSourcesItems from './stock-sources-items-table.component';
6
+ import useStockSourcesPage from './stock-sources-items-table.resource';
7
+ import { useTranslation } from 'react-i18next';
8
+
9
+ jest.mock('react-i18next', () => ({
10
+ useTranslation: jest.fn(),
11
+ }));
12
+
13
+ const mockTranslation = {
14
+ t: (key) => key,
15
+ };
16
+
17
+ jest.mock('./stock-sources-items-table.resource', () => ({
18
+ __esModule: true,
19
+ default: jest.fn(),
20
+ useStockSourcesPage: jest.fn(),
21
+ }));
22
+
23
+ describe('StockSourcesItems', () => {
24
+ const mockFilter = {};
25
+ const mockItems = {
26
+ results: [{ name: 'Community', acronym: 'Community', sourceType: { display: 'Donation' } }],
27
+ totalCount: 1,
28
+ };
29
+
30
+ beforeEach(() => {
31
+ (useTranslation as jest.Mock).mockReturnValue(mockTranslation);
32
+ (useStockSourcesPage as jest.Mock).mockClear();
33
+ });
34
+
35
+ it('should return initial values', () => {
36
+ const mockFilter = {};
37
+ const mockItems = {
38
+ results: [{ name: 'Community', acronym: 'Community', sourceType: { display: 'Donation' } }],
39
+ totalCount: 1,
40
+ };
41
+
42
+ (useStockSourcesPage as jest.Mock).mockReturnValue({
43
+ items: mockItems.results,
44
+ isLoading: false,
45
+ totalItems: mockItems.totalCount,
46
+ currentPage: 1,
47
+ pageSizes: [10, 20, 50],
48
+ goTo: jest.fn(),
49
+ currentPageSize: 10,
50
+ setPageSize: jest.fn(),
51
+ error: null,
52
+ });
53
+
54
+ const { result } = renderHook(() => useStockSourcesPage(mockFilter));
55
+
56
+ expect(result.current.items).toEqual(mockItems.results);
57
+ expect(result.current.totalItems).toBe(mockItems.totalCount);
58
+ expect(result.current.currentPageSize).toBe(10);
59
+ expect(result.current.isLoading).toBe(false);
60
+ expect(result.current.error).toBe(null);
61
+ });
62
+
63
+ it('should update current page size', () => {
64
+ let currentPageSize = 10; // Track page size with a local variable
65
+
66
+ (useStockSourcesPage as jest.Mock).mockReturnValue({
67
+ items: mockItems.results,
68
+ isLoading: false,
69
+ totalItems: mockItems.totalCount,
70
+ currentPage: 1,
71
+ pageSizes: [10, 20, 50],
72
+ goTo: jest.fn(),
73
+ currentPageSize,
74
+ setPageSize: jest.fn((size) => {
75
+ currentPageSize = size; // Update local variable
76
+ }),
77
+ error: null,
78
+ });
79
+
80
+ const { result } = renderHook(() => useStockSourcesPage(mockFilter));
81
+
82
+ act(() => {
83
+ result.current.setPageSize(20);
84
+ });
85
+
86
+ expect(currentPageSize).toBe(20); // Check the local variable instead
87
+ });
88
+
89
+ it('should handle loading and error states', () => {
90
+ (useStockSourcesPage as jest.Mock).mockReturnValue({
91
+ items: { results: [], totalCount: 0 },
92
+ isLoading: true,
93
+ error: null,
94
+ totalItems: 0,
95
+ currentPage: 1,
96
+ currentPageSize: 10,
97
+ pageSizes: [10, 20, 50],
98
+ goTo: jest.fn(),
99
+ setPageSize: jest.fn(),
100
+ });
101
+
102
+ const { result } = renderHook(() => useStockSourcesPage(mockFilter));
103
+
104
+ expect(result.current.isLoading).toBe(true);
105
+ expect(result.current.error).toBe(null);
106
+ expect(result.current.totalItems).toBe(0);
107
+ });
108
+
109
+ test('renders loading state when data is being fetched', () => {
110
+ (useStockSourcesPage as jest.Mock).mockReturnValue({
111
+ items: [],
112
+ isLoading: true,
113
+ totalItems: 0,
114
+ tableHeaders: [],
115
+ currentPage: 1,
116
+ pageSizes: [10, 20, 50],
117
+ goTo: jest.fn(),
118
+ currentPageSize: 10,
119
+ setPageSize: jest.fn(),
120
+ });
121
+
122
+ render(<StockSourcesItems />);
123
+ expect(screen.getByRole('progressbar')).toBeInTheDocument();
124
+ });
125
+
126
+ test('renders data table when data is loaded', () => {
127
+ const mockItems = [
128
+ {
129
+ uuid: '1234',
130
+ name: 'Source A',
131
+ acronym: 'SA',
132
+ sourceType: { display: 'Internal' },
133
+ },
134
+ ];
135
+
136
+ (useStockSourcesPage as jest.Mock).mockReturnValue({
137
+ items: mockItems,
138
+ isLoading: false,
139
+ totalItems: 1,
140
+ tableHeaders: [
141
+ { key: 'name', header: 'Name' },
142
+ { key: 'sourceType', header: 'Source Type' },
143
+ ],
144
+ currentPage: 1,
145
+ pageSizes: [10, 20, 50],
146
+ goTo: jest.fn(),
147
+ currentPageSize: 10,
148
+ setPageSize: jest.fn(),
149
+ });
150
+
151
+ render(<StockSourcesItems />);
152
+
153
+ expect(screen.getByText('Source A')).toBeInTheDocument();
154
+ expect(screen.getByText('Internal')).toBeInTheDocument();
155
+ });
156
+
157
+ test('filters data based on source type', async () => {
158
+ const user = userEvent.setup();
159
+
160
+ const mockItems = [
161
+ {
162
+ uuid: '1234',
163
+ name: 'Source A',
164
+ acronym: 'SA',
165
+ sourceType: { display: 'Internal' },
166
+ },
167
+ {
168
+ uuid: '5678',
169
+ name: 'Source B',
170
+ acronym: 'SB',
171
+ sourceType: { display: 'External' },
172
+ },
173
+ ];
174
+
175
+ (useStockSourcesPage as jest.Mock).mockReturnValue({
176
+ items: mockItems,
177
+ isLoading: false,
178
+ totalItems: 2,
179
+ tableHeaders: [{ key: 'name', header: 'Name' }],
180
+ currentPage: 1,
181
+ pageSizes: [10, 20, 50],
182
+ goTo: jest.fn(),
183
+ currentPageSize: 10,
184
+ setPageSize: jest.fn(),
185
+ });
186
+
187
+ render(<StockSourcesItems />);
188
+
189
+ expect(screen.getByText('Source A')).toBeInTheDocument();
190
+ expect(screen.getByText('Source B')).toBeInTheDocument();
191
+
192
+ const filterInput = screen.getByLabelText('');
193
+ await user.type(filterInput, 'Internal');
194
+
195
+ await waitFor(() => {
196
+ expect(screen.getByText('Source A')).toBeInTheDocument();
197
+ });
198
+
199
+ await waitFor(() => {
200
+ expect(screen.getByText('Source B')).toBeInTheDocument();
201
+ });
202
+ });
203
+
204
+ test('renders a message when no stock sources are available', () => {
205
+ (useStockSourcesPage as jest.Mock).mockReturnValue({
206
+ items: [],
207
+ isLoading: false,
208
+ totalItems: 0,
209
+ tableHeaders: [],
210
+ currentPage: 1,
211
+ pageSizes: [10, 20, 50],
212
+ goTo: jest.fn(),
213
+ currentPageSize: 10,
214
+ setPageSize: jest.fn(),
215
+ });
216
+
217
+ render(<StockSourcesItems />);
218
+
219
+ expect(screen.getByText('noSourcesToDisplay')).toBeInTheDocument();
220
+ expect(screen.getByText('checkFilters')).toBeInTheDocument();
221
+ });
222
+
223
+ test('pagination works as expected', async () => {
224
+ const user = userEvent.setup();
225
+
226
+ const mockGoTo = jest.fn();
227
+ const mockSetPageSize = jest.fn();
228
+
229
+ (useStockSourcesPage as jest.Mock).mockReturnValue({
230
+ items: [],
231
+ isLoading: false,
232
+ totalItems: 20,
233
+ tableHeaders: [],
234
+ currentPage: 1,
235
+ pageSizes: [10, 20, 50],
236
+ goTo: mockGoTo,
237
+ currentPageSize: 10,
238
+ setPageSize: mockSetPageSize,
239
+ });
240
+
241
+ render(<StockSourcesItems />);
242
+
243
+ const nextPageButton = screen.getByLabelText('Next page');
244
+ await user.click(nextPageButton);
245
+
246
+ expect(mockGoTo).toHaveBeenCalledWith(2);
247
+ });
248
+ });