@openmrs/esm-stock-management-app 1.0.1-pre.785 → 1.0.1-pre.788

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 (27) hide show
  1. package/dist/155.js +1 -1
  2. package/dist/155.js.map +1 -1
  3. package/dist/922.js +1 -0
  4. package/dist/922.js.map +1 -0
  5. package/dist/main.js +1 -1
  6. package/dist/main.js.map +1 -1
  7. package/dist/openmrs-esm-stock-management-app.js +1 -1
  8. package/dist/openmrs-esm-stock-management-app.js.buildmanifest.json +12 -12
  9. package/dist/routes.json +1 -1
  10. package/package.json +1 -1
  11. package/src/config-schema.ts +6 -0
  12. package/src/stock-operations/add-stock-operation/stock-operations-expanded-row/stock-items-table.scss +34 -0
  13. package/src/stock-operations/add-stock-operation/stock-operations-expanded-row/stock-items-table.tsx +111 -0
  14. package/src/stock-operations/add-stock-operation/stock-operations-expanded-row/stock-operation-expanded-row.component.tsx +87 -0
  15. package/src/stock-operations/add-stock-operation/stock-operations-expanded-row/stock-operation-expanded-row.scss +31 -0
  16. package/src/stock-operations/add-stock-operation/stock-operations-expanded-row/stock-operations-status.tsx +45 -0
  17. package/src/stock-operations/stock-operations-forms/input-components/stock-item-search.component.tsx +30 -7
  18. package/src/stock-operations/stock-operations-forms/step2.test.tsx +6 -2
  19. package/src/stock-operations/stock-operations-forms/steps/received-items.component.tsx +110 -0
  20. package/src/stock-operations/stock-operations-forms/steps/stock-operation-item-cell.component.tsx +15 -3
  21. package/src/stock-operations/stock-operations-forms/steps/stock-operation-submission-form-step.component.tsx +7 -0
  22. package/src/stock-operations/stock-operations-forms/stock-item-form/stock-item-form.workspace.tsx +16 -2
  23. package/src/stock-operations/stock-operations-forms/stock-operation-form.component.tsx +18 -13
  24. package/src/stock-operations/stock-operations-table.component.tsx +39 -38
  25. package/dist/914.js +0 -1
  26. package/dist/914.js.map +0 -1
  27. package/src/stock-operations/received-items.component.tsx +0 -93
@@ -0,0 +1,110 @@
1
+ import React from 'react';
2
+ import { useTranslation } from 'react-i18next';
3
+ import { StockOperationDTO } from '../../../core/api/types/stockOperation/StockOperationDTO';
4
+ import {
5
+ DataTable,
6
+ Table,
7
+ TableBody,
8
+ TableContainer,
9
+ TableHead,
10
+ TableHeader,
11
+ TableRow,
12
+ TableCell,
13
+ DataTableSkeleton,
14
+ Button,
15
+ } from '@carbon/react';
16
+ import styles from './stock-operation-items-form-step.scc.scss';
17
+
18
+ const formatDate = (date: Date | string | null) => {
19
+ if (!date) return ' ';
20
+ const d = new Date(date);
21
+ const day = String(d.getDate()).padStart(2, '0');
22
+ const month = String(d.getMonth() + 1).padStart(2, '0');
23
+ const year = d.getFullYear();
24
+ return `${month}/${day}/${year}`;
25
+ };
26
+
27
+ interface ReceivedItemsProps {
28
+ stockOperation?: StockOperationDTO;
29
+ onPrevious?: () => void;
30
+ }
31
+
32
+ const ReceivedItems: React.FC<ReceivedItemsProps> = ({ stockOperation, onPrevious }) => {
33
+ const { t } = useTranslation();
34
+
35
+ const headers = [
36
+ { key: 'item', header: t('item', 'Item') },
37
+ { key: 'requested', header: t('requested', 'Requested') },
38
+ { key: 'batch', header: t('batch', 'Batch No') },
39
+ { key: 'expiry', header: t('expiry', 'Expiry Date') },
40
+ { key: 'qtySent', header: t('quantitySent', 'Quantity Sent') },
41
+ { key: 'qtyReceived', header: t('quantityReceived', 'Quantity Received') },
42
+ {
43
+ key: 'qtyUoM',
44
+ header: t('quantityUoM', 'Quantity Unit of Measurement(UoM)'),
45
+ },
46
+ ];
47
+
48
+ const rows =
49
+ stockOperation?.stockOperationItems?.map((item) => ({
50
+ id: item.uuid,
51
+ item: item.stockItemName,
52
+ requested: item.quantityRequested || ' ',
53
+ batch: item.batchNo,
54
+ expiry: formatDate(item.expiration),
55
+ qtySent: item.quantity || ' ',
56
+ qtyReceived: item.quantityReceived || ' ',
57
+ qtyUoM: item.quantityReceivedPackagingUOMName,
58
+ })) || [];
59
+
60
+ if (!stockOperation) {
61
+ return <DataTableSkeleton role="progressbar" />;
62
+ }
63
+
64
+ const headerTitle = t('receivedItems', 'Received Items');
65
+
66
+ return (
67
+ <div style={{ margin: '10px' }}>
68
+ <div className={styles.tableContainer}>
69
+ <div className={styles.heading}>
70
+ <h4>{headerTitle}</h4>
71
+ <div className={styles.btnSet}>
72
+ {typeof onPrevious === 'function' && (
73
+ <Button kind="secondary" onClick={onPrevious}>
74
+ {t('previous', 'Previous')}
75
+ </Button>
76
+ )}
77
+ </div>
78
+ </div>
79
+ <DataTable rows={rows} headers={headers}>
80
+ {({ rows, headers, getHeaderProps, getTableProps, getRowProps }) => (
81
+ <TableContainer>
82
+ <Table {...getTableProps()}>
83
+ <TableHead>
84
+ <TableRow>
85
+ {headers.map((header) => (
86
+ <TableHeader {...getHeaderProps({ header })} key={header.key}>
87
+ {header.header}
88
+ </TableHeader>
89
+ ))}
90
+ </TableRow>
91
+ </TableHead>
92
+ <TableBody>
93
+ {rows.map((row) => (
94
+ <TableRow {...getRowProps({ row })} key={row.id}>
95
+ {row.cells.map((cell) => (
96
+ <TableCell key={cell.id}>{cell.value}</TableCell>
97
+ ))}
98
+ </TableRow>
99
+ ))}
100
+ </TableBody>
101
+ </Table>
102
+ </TableContainer>
103
+ )}
104
+ </DataTable>
105
+ </div>
106
+ </div>
107
+ );
108
+ };
109
+
110
+ export default ReceivedItems;
@@ -1,11 +1,12 @@
1
- import React, { useCallback, useEffect } from 'react';
1
+ import React, { useCallback, useEffect, useMemo } from 'react';
2
2
  import { useStockItem } from '../../../stock-items/stock-items.resource';
3
3
  import { useTranslation } from 'react-i18next';
4
- import { showSnackbar } from '@openmrs/esm-framework';
4
+ import { showSnackbar, useConfig } from '@openmrs/esm-framework';
5
5
  import { InlineLoading } from '@carbon/react';
6
6
  import { StockItemDTO } from '../../../core/api/types/stockItem/StockItem';
7
7
  import { URL_STOCK_ITEM } from '../../../constants';
8
8
  import { Link } from 'react-router-dom';
9
+ import { ConfigObject } from '../../../config-schema';
9
10
 
10
11
  type StockOperationItemCellProps = {
11
12
  stockItemUuid: string;
@@ -14,7 +15,18 @@ type StockOperationItemCellProps = {
14
15
  const StockOperationItemCell: React.FC<StockOperationItemCellProps> = ({ stockItemUuid }) => {
15
16
  const { isLoading, error, item } = useStockItem(stockItemUuid);
16
17
  const { t } = useTranslation();
18
+ const { useItemCommonNameAsDisplay } = useConfig<ConfigObject>();
19
+ const commonName = useMemo(() => {
20
+ if (!useItemCommonNameAsDisplay) return;
21
+ const drugName = item?.drugName ? `(Drug name: ${item.drugName})` : undefined;
22
+ return `${item?.commonName || t('noCommonNameAvailable', 'No common name available') + (drugName ?? '')}`;
23
+ }, [item, useItemCommonNameAsDisplay, t]);
17
24
 
25
+ const drugName = useMemo(() => {
26
+ if (useItemCommonNameAsDisplay) return;
27
+ const commonName = item?.commonName ? `(Common name: ${item.commonName})` : undefined;
28
+ return `${item?.drugName || t('noDrugNameAvailable', 'No drug name available') + (commonName ?? '')}`;
29
+ }, [item, useItemCommonNameAsDisplay, t]);
18
30
  useEffect(() => {
19
31
  if (error) {
20
32
  showSnackbar({
@@ -30,7 +42,7 @@ const StockOperationItemCell: React.FC<StockOperationItemCellProps> = ({ stockIt
30
42
 
31
43
  return (
32
44
  <Link target={'_blank'} to={URL_STOCK_ITEM(stockItemUuid)}>
33
- {(item as StockItemDTO)?.commonName || 'No name available'}
45
+ {useItemCommonNameAsDisplay ? commonName : drugName}
34
46
  </Link>
35
47
  );
36
48
  };
@@ -20,11 +20,13 @@ type StockOperationSubmissionFormStepProps = {
20
20
  onPrevious?: () => void;
21
21
  stockOperation?: StockOperationDTO;
22
22
  stockOperationType: StockOperationType;
23
+ onNext?: () => void;
23
24
  };
24
25
  const StockOperationSubmissionFormStep: React.FC<StockOperationSubmissionFormStepProps> = ({
25
26
  onPrevious,
26
27
  stockOperationType,
27
28
  stockOperation,
29
+ onNext,
28
30
  }) => {
29
31
  const { t } = useTranslation();
30
32
  const operationTypePermision = useOperationTypePermisions(stockOperationType);
@@ -147,6 +149,11 @@ const StockOperationSubmissionFormStep: React.FC<StockOperationSubmissionFormSte
147
149
  Previous
148
150
  </Button>
149
151
  )}
152
+ {typeof onNext === 'function' && (
153
+ <Button kind="primary" onClick={onNext}>
154
+ Next
155
+ </Button>
156
+ )}
150
157
  </div>
151
158
  </div>
152
159
 
@@ -10,7 +10,7 @@ import {
10
10
  TextInput,
11
11
  } from '@carbon/react';
12
12
  import { zodResolver } from '@hookform/resolvers/zod';
13
- import { DefaultWorkspaceProps } from '@openmrs/esm-framework';
13
+ import { DefaultWorkspaceProps, useConfig } from '@openmrs/esm-framework';
14
14
  import React, { useMemo } from 'react';
15
15
  import { Controller, useForm } from 'react-hook-form';
16
16
  import { useTranslation } from 'react-i18next';
@@ -24,6 +24,7 @@ import BatchNoSelector from '../input-components/batch-no-selector.component';
24
24
  import QtyUomSelector from '../input-components/quantity-uom-selector.component';
25
25
  import styles from './stock-item-form.scss';
26
26
  import UniqueBatchNoEntryInput from '../input-components/unique-batch-no-entry-input.component';
27
+ import { ConfigObject } from '../../../config-schema';
27
28
 
28
29
  export interface StockItemFormProps {
29
30
  stockOperationType: StockOperationType;
@@ -40,6 +41,7 @@ const StockItemForm: React.FC<Props> = ({ closeWorkspace, stockOperationType, st
40
41
  return getStockOperationItemFormSchema(operationType);
41
42
  }, [operationType]);
42
43
  const operationTypePermision = useOperationTypePermisions(stockOperationType);
44
+ const { useItemCommonNameAsDisplay } = useConfig<ConfigObject>();
43
45
 
44
46
  const fields = formschema.keyof().options;
45
47
  const form = useForm<z.infer<typeof formschema>>({
@@ -49,6 +51,18 @@ const StockItemForm: React.FC<Props> = ({ closeWorkspace, stockOperationType, st
49
51
  });
50
52
  const { t } = useTranslation();
51
53
  const { item } = useStockItem(form.getValues('stockItemUuid'));
54
+ const commonName = useMemo(() => {
55
+ if (!useItemCommonNameAsDisplay) return;
56
+ const drugName = item?.drugName ? `(Drug name: ${item.drugName})` : undefined;
57
+ return `${item?.commonName || t('noCommonNameAvailable', 'No common name available') + (drugName ?? '')}`;
58
+ }, [item, useItemCommonNameAsDisplay, t]);
59
+
60
+ const drugName = useMemo(() => {
61
+ if (useItemCommonNameAsDisplay) return;
62
+ const commonName = item?.commonName ? `(Common name: ${item.commonName})` : undefined;
63
+ return `${item?.drugName || t('noDrugNameAvailable', 'No drug name available') + (commonName ?? '')}`;
64
+ }, [item, useItemCommonNameAsDisplay, t]);
65
+
52
66
  const onSubmit = (data: z.infer<typeof formschema>) => {
53
67
  onSave?.(data);
54
68
  closeWorkspace();
@@ -57,7 +71,7 @@ const StockItemForm: React.FC<Props> = ({ closeWorkspace, stockOperationType, st
57
71
  return (
58
72
  <Form onSubmit={form.handleSubmit(onSubmit)} className={styles.form}>
59
73
  <Stack gap={4} className={styles.grid}>
60
- {item?.commonName && <p className={styles.title}>{item?.commonName}</p>}
74
+ <p className={styles.title}>{useItemCommonNameAsDisplay ? commonName : drugName}</p>
61
75
 
62
76
  {(operationTypePermision.requiresActualBatchInfo || operationTypePermision.requiresBatchUuid) &&
63
77
  fields.includes('batchNo' as any) && (
@@ -15,7 +15,7 @@ import {
15
15
  } from '../../core/api/types/stockOperation/StockOperationType';
16
16
  import { TabItem } from '../../core/components/tabs/types';
17
17
  import { otherUser, pick } from '../../core/utils/utils';
18
- import ReceivedItems from '../received-items.component';
18
+ import ReceivedItems from './steps/received-items.component';
19
19
  import {
20
20
  getStockOperationFormSchema,
21
21
  getStockOperationItemFormSchema,
@@ -59,6 +59,13 @@ const StockOperationForm: React.FC<StockOperationFormProps> = ({
59
59
  const formschema = useMemo(() => {
60
60
  return getStockOperationFormSchema(operationType);
61
61
  }, [operationType]);
62
+ const showReceivedItems = useMemo(() => {
63
+ return (
64
+ (StockOperationTypeIsStockIssue(stockOperation?.operationType as OperationType) ||
65
+ stockOperation?.permission?.canDisplayReceivedItems) &&
66
+ (stockOperation.status === 'DISPATCHED' || stockOperation.status === 'COMPLETED')
67
+ );
68
+ }, [stockOperation]);
62
69
  const steps: TabItem[] = useMemo(() => {
63
70
  return [
64
71
  {
@@ -91,25 +98,23 @@ const StockOperationForm: React.FC<StockOperationFormProps> = ({
91
98
  stockOperation={stockOperation}
92
99
  stockOperationType={stockOperationType}
93
100
  onPrevious={() => setSelectedIndex(1)}
101
+ onNext={showReceivedItems ? () => setSelectedIndex(3) : undefined}
94
102
  />
95
103
  ),
96
104
  disabled: true,
97
105
  },
98
106
  ].concat(
99
- StockOperationTypeIsStockIssue(stockOperation?.operationType as OperationType) ||
100
- stockOperation?.permission?.canDisplayReceivedItems
101
- ? stockOperation.status === 'DISPATCHED' || stockOperation.status === 'COMPLETED'
102
- ? [
103
- {
104
- name: t('receivedItems', 'Received Items'),
105
- component: <ReceivedItems model={stockOperation} />,
106
- disabled: true,
107
- },
108
- ]
109
- : []
107
+ showReceivedItems
108
+ ? [
109
+ {
110
+ name: t('receivedItems', 'Received Items'),
111
+ component: <ReceivedItems stockOperation={stockOperation} onPrevious={() => setSelectedIndex(2)} />,
112
+ disabled: true,
113
+ },
114
+ ]
110
115
  : [],
111
116
  ) as TabItem[];
112
- }, [stockOperation, stockOperationType, t, operationTypePermision]);
117
+ }, [stockOperation, stockOperationType, t, operationTypePermision, showReceivedItems]);
113
118
  const {
114
119
  user: { uuid: defaultLoggedUserUuid },
115
120
  } = useSession();
@@ -37,8 +37,9 @@ import StockOperationTypesSelector from './stock-operation-types-selector/stock-
37
37
  import StockOperationsFilters from './stock-operations-filters.component';
38
38
  import { useStockOperationPages } from './stock-operations-table.resource';
39
39
 
40
+ import { Link } from '@carbon/react';
41
+ import StockOperationExpandedRow from './add-stock-operation/stock-operations-expanded-row/stock-operation-expanded-row.component';
40
42
  import styles from './stock-operations-table.scss';
41
- import StockOperationStatusRow from './stock-operation-status/stock-operation-status-row';
42
43
 
43
44
  interface StockOperationsTableProps {
44
45
  status?: string;
@@ -49,32 +50,6 @@ const StockOperations: React.FC<StockOperationsTableProps> = () => {
49
50
  const handleRefresh = () => {
50
51
  handleMutate(`${restBaseUrl}/stockmanagement/stockoperation`);
51
52
  };
52
- const operation: StockOperationType = useMemo(
53
- () => ({
54
- uuid: '',
55
- name: '',
56
- description: '',
57
- operationType: '',
58
- hasSource: false,
59
- sourceType: 'Location',
60
- hasDestination: false,
61
- destinationType: 'Location',
62
- hasRecipient: false,
63
- recipientRequired: false,
64
- availableWhenReserved: false,
65
- allowExpiredBatchNumbers: false,
66
- stockOperationTypeLocationScopes: [],
67
- creator: undefined,
68
- dateCreated: undefined,
69
- changedBy: undefined,
70
- dateChanged: undefined,
71
- dateVoided: undefined,
72
- voidedBy: undefined,
73
- voidReason: '',
74
- voided: false,
75
- }),
76
- [],
77
- );
78
53
 
79
54
  const [selectedFromDate, setSelectedFromDate] = useState(null);
80
55
  const [selectedToDate, setSelectedToDate] = useState(null);
@@ -123,9 +98,13 @@ const StockOperations: React.FC<StockOperationsTableProps> = () => {
123
98
 
124
99
  const tableRows = useMemo(() => {
125
100
  return items?.map((stockOperation, index) => {
126
- const commonNames = stockOperation?.stockOperationItems
127
- ? stockOperation?.stockOperationItems.map((item) => item.commonName).join(', ')
128
- : '';
101
+ const threshHold = 1;
102
+ const itemCountGreaterThanThreshhold = (stockOperation?.stockOperationItems?.length ?? 0) > threshHold;
103
+ const commonNames =
104
+ stockOperation?.stockOperationItems
105
+ ?.slice(0, itemCountGreaterThanThreshhold ? threshHold : undefined)
106
+ .map((item) => item.commonName)
107
+ .join(', ') ?? '';
129
108
 
130
109
  return {
131
110
  ...stockOperation,
@@ -135,7 +114,10 @@ const StockOperations: React.FC<StockOperationsTableProps> = () => {
135
114
  operationNumber: (
136
115
  <EditStockOperationActionMenu stockOperation={stockOperation} showIcon={false} showprops={true} />
137
116
  ),
138
- stockOperationItems: commonNames,
117
+ stockOperationItems: {
118
+ commonNames,
119
+ more: itemCountGreaterThanThreshhold ? stockOperation?.stockOperationItems?.length - threshHold : 0,
120
+ },
139
121
  status: `${stockOperation?.status}`,
140
122
  source: `${stockOperation?.sourceName ?? ''}`,
141
123
  destination: `${stockOperation?.destinationName ?? ''}`,
@@ -173,7 +155,16 @@ const StockOperations: React.FC<StockOperationsTableProps> = () => {
173
155
  headers={tableHeaders}
174
156
  isSortable={true}
175
157
  useZebraStyles={true}
176
- render={({ rows, headers, getHeaderProps, getTableProps, getRowProps, onInputChange }) => (
158
+ render={({
159
+ rows,
160
+ headers,
161
+ getHeaderProps,
162
+ getTableProps,
163
+ getRowProps,
164
+ onInputChange,
165
+ getExpandedRowProps,
166
+ expandRow,
167
+ }) => (
177
168
  <TableContainer>
178
169
  <TableToolbar
179
170
  style={{
@@ -241,19 +232,29 @@ const StockOperations: React.FC<StockOperationsTableProps> = () => {
241
232
  </TableHead>
242
233
  <TableBody>
243
234
  {rows?.map((row: any, index) => {
235
+ const props = getRowProps({ row });
236
+ const expandedRowProps = getExpandedRowProps({ row });
244
237
  return (
245
238
  <React.Fragment key={row.id}>
246
- <TableExpandRow
247
- className={isDesktop ? styles.desktopRow : styles.tabletRow}
248
- {...getRowProps({ row })}
249
- >
239
+ <TableExpandRow className={isDesktop ? styles.desktopRow : styles.tabletRow} {...props}>
250
240
  {row.cells.map((cell) => (
251
- <TableCell key={cell.id}>{cell.value}</TableCell>
241
+ <TableCell key={cell.id}>
242
+ {cell?.info?.header === 'stockOperationItems' ? (
243
+ <span>
244
+ <span>{cell.value.commonNames}</span>
245
+ {cell.value.more > 0 && (
246
+ <Link onClick={() => expandRow(row.id)}>{`...(${cell.value.more} more)`}</Link>
247
+ )}
248
+ </span>
249
+ ) : (
250
+ cell.value
251
+ )}
252
+ </TableCell>
252
253
  ))}
253
254
  </TableExpandRow>
254
255
  {row.isExpanded ? (
255
256
  <TableExpandedRow colSpan={headers.length + 2}>
256
- <StockOperationStatusRow stockOperation={items[index]} />
257
+ <StockOperationExpandedRow model={items[index]} />
257
258
  </TableExpandedRow>
258
259
  ) : (
259
260
  <TableExpandedRow className={styles.hiddenRow} colSpan={headers.length + 2} />