@openmrs/esm-stock-management-app 3.0.1-pre.818 → 3.0.1-pre.826

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 (44) hide show
  1. package/dist/10.js +1 -0
  2. package/dist/10.js.map +1 -0
  3. package/dist/165.js +1 -1
  4. package/dist/165.js.map +1 -1
  5. package/dist/20.js +1 -1
  6. package/dist/20.js.map +1 -1
  7. package/dist/642.js +1 -0
  8. package/dist/642.js.map +1 -0
  9. package/dist/675.js +1 -0
  10. package/dist/675.js.map +1 -0
  11. package/dist/{880.js → 727.js} +1 -1
  12. package/dist/727.js.map +1 -0
  13. package/dist/780.js +1 -0
  14. package/dist/780.js.map +1 -0
  15. package/dist/main.js +1 -1
  16. package/dist/main.js.map +1 -1
  17. package/dist/openmrs-esm-stock-management-app.js +1 -1
  18. package/dist/openmrs-esm-stock-management-app.js.buildmanifest.json +129 -56
  19. package/dist/openmrs-esm-stock-management-app.js.map +1 -1
  20. package/dist/routes.json +1 -1
  21. package/package.json +1 -1
  22. package/src/index.ts +8 -0
  23. package/src/routes.json +14 -0
  24. package/src/stock-items/add-stock-item/add-stock-action-button.component.tsx +2 -3
  25. package/src/stock-items/add-stock-item/add-stock-item.component.tsx +29 -20
  26. package/src/stock-items/add-stock-item/add-stock-item.scss +22 -3
  27. package/src/stock-items/add-stock-item/add-stock-item.test.tsx +11 -11
  28. package/src/stock-items/add-stock-item/packaging-units/packaging-units.component.tsx +9 -10
  29. package/src/stock-items/add-stock-item/packaging-units/packaging-units.resource.tsx +1 -1
  30. package/src/stock-items/add-stock-item/stock-item-details/stock-item-details.component.tsx +226 -214
  31. package/src/stock-items/add-stock-item/stock-item-details/stock-item-details.resource.tsx +7 -2
  32. package/src/stock-items/add-stock-item/stock-item-rules/add-stock-rule-button.component.tsx +6 -4
  33. package/src/stock-items/add-stock-item/stock-item-rules/add-stock-rules.component.tsx +162 -166
  34. package/src/stock-items/add-stock-item/stock-item-rules/add-stock-rules.scss +19 -11
  35. package/src/stock-items/add-stock-item/stock-item-rules/edit-stock-rule.component.tsx +8 -5
  36. package/src/stock-items/edit-stock-item/edit-stock-item-action-menu.component.tsx +2 -2
  37. package/src/stock-items/stock-item.utils.tsx +7 -50
  38. package/src/stock-items/stock-items-table.component.tsx +2 -2
  39. package/src/stock-items/stock-items-table.test.tsx +28 -23
  40. package/src/stock-items/stock-items.resource.ts +7 -5
  41. package/src/stock-operations/stock-operations-forms/stock-operation-stepper/stock-operation-stepper.component.tsx +1 -0
  42. package/dist/600.js +0 -1
  43. package/dist/600.js.map +0 -1
  44. package/dist/880.js.map +0 -1
@@ -1,5 +1,24 @@
1
+ @use '@carbon/type';
2
+ @use '@carbon/layout';
3
+ @use '@carbon/colors';
4
+
1
5
  .formContainer {
2
- max-height: calc(100vh - 100px);
3
- overflow: scroll;
4
- padding: 0 1rem;
6
+ display: flex;
7
+ flex-direction: column;
8
+ justify-content: space-between;
9
+ width: 100%;
10
+ height: 100%;
11
+ }
12
+
13
+ .button {
14
+ display: flex;
15
+ align-content: flex-start;
16
+ align-items: baseline;
17
+ min-width: 50%;
18
+ }
19
+
20
+ .buttonSet {
21
+ display: flex;
22
+ justify-content: space-between;
23
+ width: 100%;
5
24
  }
@@ -22,8 +22,8 @@ jest.mock('@carbon/react/icons', () => ({
22
22
  Save: () => <div>Save Icon</div>,
23
23
  }));
24
24
 
25
- jest.mock('./stock-item-details/stock-item-details.component', () => ({ model }) => (
26
- <div data-testid="stock-item-details">Stock Item Details: {model.uuid}</div>
25
+ jest.mock('./stock-item-details/stock-item-details.component', () => ({ stockItem }) => (
26
+ <div data-testid="stock-item-details">Stock Item Details: {stockItem?.uuid}</div>
27
27
  ));
28
28
 
29
29
  jest.mock('./packaging-units/packaging-units.component', () => ({ stockItemUuid }) => (
@@ -120,14 +120,14 @@ describe('AddEditStockItem', () => {
120
120
  };
121
121
 
122
122
  it('renders correctly with initial state and default selected tab', () => {
123
- render(<AddEditStockItem model={mockModel} />);
123
+ render(<AddEditStockItem stockItem={mockModel} />);
124
124
  expect(screen.getByTestId('stock-item-details')).toBeInTheDocument();
125
125
  expect(screen.getByText('Stock Item Details: test-uuid-123')).toBeInTheDocument();
126
126
  });
127
127
 
128
128
  it('changes selected tab when clicking on different tabs', async () => {
129
129
  const user = userEvent.setup();
130
- render(<AddEditStockItem model={mockModel} isEditing={true} />);
130
+ render(<AddEditStockItem stockItem={mockModel} />);
131
131
 
132
132
  await user.click(screen.getByText('packagingUnits'));
133
133
  expect(screen.getByTestId('packaging-units')).toBeInTheDocument();
@@ -139,7 +139,7 @@ describe('AddEditStockItem', () => {
139
139
  });
140
140
 
141
141
  it('disables tabs when isEditing is false', () => {
142
- render(<AddEditStockItem model={mockModel} isEditing={false} />);
142
+ render(<AddEditStockItem />);
143
143
 
144
144
  const disabledTabs = [
145
145
  'packagingUnits',
@@ -150,13 +150,13 @@ describe('AddEditStockItem', () => {
150
150
  'references',
151
151
  ];
152
152
  disabledTabs.forEach((tabName) => {
153
- const tab = screen.getByRole('tab', { name: tabName });
153
+ const tab = screen.getByRole('button', { name: tabName });
154
154
  expect(tab).toHaveAttribute('aria-disabled', 'true');
155
155
  });
156
156
  });
157
157
 
158
158
  it('enables tabs when isEditing is true', () => {
159
- render(<AddEditStockItem model={mockModel} isEditing={true} />);
159
+ render(<AddEditStockItem stockItem={mockModel} />);
160
160
 
161
161
  const enabledTabs = [
162
162
  'packagingUnits',
@@ -167,14 +167,14 @@ describe('AddEditStockItem', () => {
167
167
  'references',
168
168
  ];
169
169
  enabledTabs.forEach((tabName) => {
170
- const tab = screen.getByRole('tab', { name: tabName });
171
- expect(tab).not.toHaveAttribute('aria-disabled', 'true');
170
+ const tab = screen.getByRole('button', { name: tabName });
171
+ expect(tab).toHaveAttribute('aria-disabled', 'false');
172
172
  });
173
173
  });
174
174
 
175
175
  it('renders correct components based on model prop', async () => {
176
176
  const user = userEvent.setup();
177
- render(<AddEditStockItem model={mockModel} isEditing={true} />);
177
+ render(<AddEditStockItem stockItem={mockModel} />);
178
178
 
179
179
  expect(screen.getByText('Stock Item Details: test-uuid-123')).toBeInTheDocument();
180
180
 
@@ -198,7 +198,7 @@ describe('AddEditStockItem', () => {
198
198
  });
199
199
 
200
200
  it('translates tab names correctly', () => {
201
- render(<AddEditStockItem model={mockModel} />);
201
+ render(<AddEditStockItem stockItem={mockModel} />);
202
202
 
203
203
  const tabNames = [
204
204
  'stockItemDetails',
@@ -1,7 +1,3 @@
1
- import React, { useEffect, useMemo, useState } from 'react';
2
- import { showSnackbar, restBaseUrl } from '@openmrs/esm-framework';
3
- import { useTranslation } from 'react-i18next';
4
- import { useStockItemPackageUnitsHook } from './packaging-units.resource';
5
1
  import {
6
2
  Button,
7
3
  DataTable,
@@ -14,19 +10,22 @@ import {
14
10
  TableHeader,
15
11
  TableRow,
16
12
  } from '@carbon/react';
17
- import PackagingUnitsConceptSelector from '../packaging-units-concept-selector/packaging-units-concept-selector.component';
18
- import ControlledNumberInput from '../../../core/components/carbon/controlled-number-input/controlled-number-input.component';
19
13
  import { Save } from '@carbon/react/icons';
20
- import { FormProvider, useForm, useFormContext } from 'react-hook-form';
21
14
  import { zodResolver } from '@hookform/resolvers/zod';
22
- import { type PackageUnitFormData, packageUnitSchema } from './validationSchema';
15
+ import { restBaseUrl, showSnackbar } from '@openmrs/esm-framework';
16
+ import React, { useEffect, useMemo, useState } from 'react';
17
+ import { FormProvider, useForm, useFormContext } from 'react-hook-form';
18
+ import { useTranslation } from 'react-i18next';
23
19
  import { type StockItemPackagingUOMDTO } from '../../../core/api/types/stockItem/StockItemPackagingUOM';
20
+ import ControlledNumberInput from '../../../core/components/carbon/controlled-number-input/controlled-number-input.component';
21
+ import { handleMutate } from '../../../utils';
24
22
  import { createStockItemPackagingUnit, updateStockItemPackagingUnit } from '../../stock-items.resource';
23
+ import PackagingUnitsConceptSelector from '../packaging-units-concept-selector/packaging-units-concept-selector.component';
25
24
  import DeleteModalButton from './packaging-units-delete-modal-button.component';
26
- import { handleMutate } from '../../../utils';
25
+ import { useStockItemPackageUnitsHook } from './packaging-units.resource';
26
+ import { type PackageUnitFormData, packageUnitSchema } from './validationSchema';
27
27
 
28
28
  import styles from './packaging-units.scss';
29
- import { closeOverlay } from '../../../core/components/overlay/hook';
30
29
 
31
30
  interface PackagingUnitsProps {
32
31
  isEditing?: boolean;
@@ -24,7 +24,7 @@ export function useStockItemPackageUnitsHook(v?: ResourceRepresentation) {
24
24
  const { items, isLoading, error, mutate } = useStockItemPackagingUOMs(stockItemFilter);
25
25
 
26
26
  return {
27
- items: items.results,
27
+ items: items.results ?? [],
28
28
  totalCount: items.totalCount,
29
29
  isLoading,
30
30
  error,
@@ -1,239 +1,251 @@
1
- import React, { forwardRef, useEffect, useState } from 'react';
2
- import { useTranslation } from 'react-i18next';
1
+ import { Button, ButtonSet, FormGroup, InlineLoading } from '@carbon/react';
3
2
  import { Save } from '@carbon/react/icons';
4
-
5
- import { Button, FormGroup, InlineLoading } from '@carbon/react';
6
- import { type StockItemDTO } from '../../../core/api/types/stockItem/StockItem';
7
- import DrugSelector from '../drug-selector/drug-selector.component';
8
- import { useForm } from 'react-hook-form';
9
3
  import { zodResolver } from '@hookform/resolvers/zod';
10
- import { stockItemDetailsSchema, type StockItemFormData } from '../../validationSchema';
11
- import ControlledRadioButtonGroup from '../../../core/components/carbon/controlled-radio-button-group/controlled-radio-button-group.component';
4
+ import { restBaseUrl, showSnackbar } from '@openmrs/esm-framework';
5
+ import React, { forwardRef, useMemo } from 'react';
6
+ import { type SubmitHandler, useForm } from 'react-hook-form';
7
+ import { useTranslation } from 'react-i18next';
8
+ import { type StockItemDTO } from '../../../core/api/types/stockItem/StockItem';
12
9
  import ControlledNumberInput from '../../../core/components/carbon/controlled-number-input/controlled-number-input.component';
10
+ import ControlledRadioButtonGroup from '../../../core/components/carbon/controlled-radio-button-group/controlled-radio-button-group.component';
13
11
  import ControlledTextInput from '../../../core/components/carbon/controlled-text-input/controlled-text-input.component';
12
+ import { handleMutate } from '../../../utils';
13
+ import styles from '../../add-stock-item/add-stock-item.scss';
14
+ import { launchAddOrStockItemWorkspace } from '../../stock-item.utils';
15
+ import { createStockItem, updateStockItem } from '../../stock-items.resource';
16
+ import { stockItemDetailsSchema, type StockItemFormData } from '../../validationSchema';
17
+ import ConceptsSelector from '../concepts-selector/concepts-selector.component';
14
18
  import DispensingUnitSelector from '../dispensing-unit-selector/dispensing-unit-selector.component';
19
+ import DrugSelector from '../drug-selector/drug-selector.component';
15
20
  import PreferredVendorSelector from '../preferred-vendor-selector/preferred-vendor-selector.component';
16
21
  import StockItemCategorySelector from '../stock-item-category-selector/stock-item-category-selector.component';
17
22
  import StockItemUnitsEdit from '../stock-item-units-edit/stock-item-units-edit.component';
18
- import { type SaveStockItem } from '../../types';
19
- import ConceptsSelector from '../concepts-selector/concepts-selector.component';
20
- import styles from '../../add-stock-item/add-stock-item.scss';
21
- import { closeOverlay } from '../../../core/components/overlay/hook';
22
- import { expirationOptions, radioOptions } from './stock-item-details.resource';
23
- import { restBaseUrl } from '@openmrs/esm-framework';
24
- import { handleMutate } from '../../../utils';
23
+ import { expirationOptions, radioOptions, StockItemType } from './stock-item-details.resource';
25
24
 
26
25
  interface StockItemDetailsProps {
27
- model: StockItemDTO;
28
- onSave: SaveStockItem;
29
- isEditing?: boolean;
26
+ stockItem?: StockItemDTO;
30
27
  handleTabChange: (index) => void;
28
+ onCloseWorkspace?: () => void;
31
29
  }
32
30
 
33
- const StockItemDetails = forwardRef<never, StockItemDetailsProps>(({ model, onSave, isEditing, handleTabChange }) => {
34
- const { t } = useTranslation();
35
- const { handleSubmit, control, formState } = useForm<StockItemFormData>({
36
- defaultValues: model,
37
- mode: 'all',
38
- resolver: zodResolver(stockItemDetailsSchema),
39
- });
31
+ const StockItemDetails = forwardRef<never, StockItemDetailsProps>(
32
+ ({ stockItem, handleTabChange, onCloseWorkspace }) => {
33
+ const { t } = useTranslation();
34
+ const { handleSubmit, control, formState, watch } = useForm<StockItemFormData>({
35
+ defaultValues: stockItem ?? {},
36
+ mode: 'all',
37
+ resolver: zodResolver(stockItemDetailsSchema),
38
+ });
40
39
 
41
- const { errors } = formState;
42
- const handleSave = async (item: StockItemDTO) => {
43
- try {
44
- setIsSaving(true);
45
-
46
- // Restore uuid
47
- item.uuid = model.uuid;
48
- await onSave(item);
49
- handleTabChange(1);
50
- handleMutate(`${restBaseUrl}/stockmanagement/stockitem`);
51
- } catch (e) {
52
- // Show notification
53
- } finally {
54
- setIsSaving(false);
55
- }
56
- };
57
-
58
- const [isSaving, setIsSaving] = useState(false);
59
- const [isDrug, setIsDrug] = useState(false);
60
- const [selectedItemType, setSelectedItemType] = useState('');
61
- const [hasExpiration, setHasExpiration] = useState(false);
40
+ const { errors } = formState;
41
+ const handleSave: SubmitHandler<StockItemFormData> = async (formValues) => {
42
+ try {
43
+ const response = stockItem
44
+ ? await updateStockItem(stockItem?.uuid, formValues)
45
+ : await createStockItem(formValues);
46
+ if (response?.data) {
47
+ showSnackbar({
48
+ isLowContrast: true,
49
+ title: stockItem ? `${t('editStockItem', 'Edit Stock Item')}` : `${t('addStockItem', 'Add Stock Item')}`,
50
+ kind: 'success',
51
+ subtitle: stockItem
52
+ ? `${t('stockItemEdited', 'Stock Item Edited Successfully')}`
53
+ : `${t('stockItemAdded', 'Stock Item Added Successfully')}`,
54
+ });
55
+ if (!stockItem) {
56
+ onCloseWorkspace?.();
57
+ // launch edit dialog
58
+ const item = response.data;
59
+ item.isDrug = !!item.drugUuid;
60
+ launchAddOrStockItemWorkspace(t, item);
61
+ }
62
+ }
62
63
 
63
- useEffect(() => {
64
- setIsDrug(model.isDrug ?? false);
65
- setHasExpiration(model.hasExpiration ?? false);
66
- }, [model.hasExpiration, model.isDrug]);
64
+ handleTabChange(1);
65
+ handleMutate(`${restBaseUrl}/stockmanagement/stockitem`);
66
+ } catch (e) {
67
+ // Show notification
68
+ showSnackbar({
69
+ title: stockItem
70
+ ? t('errorEditingStockItem', 'Error editing a stock Item')
71
+ : t('errorAddingStockItem', 'Error adding a stock Item'),
72
+ kind: 'error',
73
+ isLowContrast: true,
74
+ subtitle: e?.responseBody?.error?.message,
75
+ });
76
+ }
77
+ };
78
+ const [observableIsDrug, observableHasExpiration] = watch(['isDrug', 'hasExpiration']);
79
+ const selectedItemType = useMemo<StockItemType | null>(() => {
80
+ if (observableIsDrug === true) return StockItemType.PHARMACEUTICALS;
81
+ else if (observableIsDrug === false) return StockItemType.NONE_PHARMACEUTICALS;
82
+ return null;
83
+ }, [observableIsDrug]);
67
84
 
68
- return (
69
- <form className={`${styles.formContainer} ${styles.verticalForm}`}>
70
- {!isEditing && (
71
- <FormGroup
72
- className="clear-margin-bottom"
73
- legendText={t('itemType', 'Item Type')}
74
- title={t('itemType', 'Item Type')}
75
- >
76
- <ControlledRadioButtonGroup
85
+ return (
86
+ <form className={styles.formContainer}>
87
+ <div>
88
+ {!stockItem && (
89
+ <FormGroup
90
+ className="clear-margin-bottom"
91
+ legendText={t('itemType', 'Item Type')}
92
+ title={t('itemType', 'Item Type')}
93
+ >
94
+ <ControlledRadioButtonGroup
95
+ control={control}
96
+ name="isDrug"
97
+ controllerName="isDrug"
98
+ legendText=""
99
+ invalid={!!errors.isDrug}
100
+ invalidText={errors.isDrug && errors?.isDrug?.message}
101
+ options={radioOptions} // Pass radioOptions directly
102
+ />
103
+ </FormGroup>
104
+ )}
105
+ {selectedItemType === StockItemType.PHARMACEUTICALS ? (
106
+ <DrugSelector
107
+ name="drugUuid"
108
+ controllerName="drugUuid"
109
+ control={control}
110
+ title={t('pleaseSpecify', 'Please specify:')}
111
+ placeholder="Choose a drug"
112
+ drugUuid={stockItem?.drugUuid}
113
+ invalid={!!errors.drugUuid}
114
+ invalidText={errors.drugUuid && errors?.drugUuid?.message}
115
+ />
116
+ ) : selectedItemType === StockItemType.NONE_PHARMACEUTICALS ? (
117
+ <ConceptsSelector
118
+ name="conceptUuid"
119
+ controllerName="conceptUuid"
120
+ control={control}
121
+ title={t('pleaseSpecify', 'Please specify') + ':'}
122
+ placeholder={t('chooseAnItem', 'Choose an item')}
123
+ invalid={!!errors.drugUuid}
124
+ invalidText={errors.drugUuid && errors?.drugUuid?.message}
125
+ />
126
+ ) : null}
127
+ <ControlledTextInput
128
+ id="commonName"
129
+ name="commonName"
77
130
  control={control}
78
- name="isDrug"
79
- controllerName="isDrug"
80
- legendText=""
81
- invalid={!!errors.isDrug}
82
- invalidText={errors.isDrug && errors?.isDrug?.message}
83
- onChange={(selectedValue: string) => {
84
- const selectedOption = radioOptions.find((option) => String(option.value) === selectedValue);
85
- const selectedLabel = selectedOption ? selectedOption.label : '';
86
- setIsDrug(selectedValue === 'true');
87
- setSelectedItemType(selectedLabel);
88
- }}
89
- options={radioOptions} // Pass radioOptions directly
131
+ controllerName="commonName"
132
+ maxLength={255}
133
+ size={'md'}
134
+ value={`${stockItem?.commonName ?? ''}`}
135
+ labelText={t('commonName', 'Common name') + ':'}
136
+ invalid={!!errors.commonName}
137
+ invalidText={errors.commonName && errors?.commonName?.message}
90
138
  />
91
- </FormGroup>
92
- )}
93
- {selectedItemType === 'Pharmaceuticals' ? (
94
- <DrugSelector
95
- name="drugUuid"
96
- controllerName="drugUuid"
97
- control={control}
98
- title={t('pleaseSpecify', 'Please specify:')}
99
- placeholder="Choose a drug"
100
- drugUuid={model.drugUuid}
101
- invalid={!!errors.drugUuid}
102
- invalidText={errors.drugUuid && errors?.drugUuid?.message}
103
- />
104
- ) : selectedItemType === 'Non Pharmaceuticals' ? (
105
- <ConceptsSelector
106
- name="conceptUuid"
107
- controllerName="conceptUuid"
108
- control={control}
109
- title={t('pleaseSpecify', 'Please specify') + ':'}
110
- placeholder={t('chooseAnItem', 'Choose an item')}
111
- invalid={!!errors.drugUuid}
112
- invalidText={errors.drugUuid && errors?.drugUuid?.message}
113
- />
114
- ) : null}
115
- <ControlledTextInput
116
- id="commonName"
117
- name="commonName"
118
- control={control}
119
- controllerName="commonName"
120
- maxLength={255}
121
- size={'md'}
122
- value={`${model?.commonName ?? ''}`}
123
- labelText={t('commonName', 'Common name') + ':'}
124
- invalid={!!errors.commonName}
125
- invalidText={errors.commonName && errors?.commonName?.message}
126
- />
127
- <ControlledTextInput
128
- id="acronym"
129
- maxLength={255}
130
- name="acronym"
131
- control={control}
132
- controllerName="acronym"
133
- size={'md'}
134
- labelText={t('abbreviation', 'Abbreviation') + ':'}
135
- invalid={!!errors.acronym}
136
- invalidText={errors.acronym && errors?.acronym?.message}
137
- />
138
- <div
139
- style={{
140
- display: 'grid',
141
- gridTemplateColumns: '1fr 1fr',
142
- justifyContent: 'center',
143
- }}
144
- >
145
- <FormGroup
146
- className="clear-margin-bottom"
147
- legendText={t('hasExpiration', 'Does the item expire?')}
148
- title={t('hasExpiration', 'Does the item expire?')}
149
- >
150
- <ControlledRadioButtonGroup
151
- name="hasExpiration"
152
- controllerName="hasExpiration"
139
+ <ControlledTextInput
140
+ id="acronym"
141
+ maxLength={255}
142
+ name="acronym"
153
143
  control={control}
154
- legendText=""
155
- invalid={!!errors.hasExpiration}
156
- invalidText={errors.hasExpiration && errors?.hasExpiration?.message}
157
- onChange={(selectedValue: string) => {
158
- setHasExpiration(selectedValue === 'true');
159
- }}
160
- options={expirationOptions} // Pass expirationOptions directly
144
+ controllerName="acronym"
145
+ size={'md'}
146
+ labelText={t('abbreviation', 'Abbreviation') + ':'}
147
+ invalid={!!errors.acronym}
148
+ invalidText={errors.acronym && errors?.acronym?.message}
161
149
  />
162
- </FormGroup>
150
+ <div
151
+ style={{
152
+ display: 'grid',
153
+ gridTemplateColumns: '1fr 1fr',
154
+ justifyContent: 'center',
155
+ }}
156
+ >
157
+ <FormGroup
158
+ className="clear-margin-bottom"
159
+ legendText={t('hasExpiration', 'Does the item expire?')}
160
+ title={t('hasExpiration', 'Does the item expire?')}
161
+ >
162
+ <ControlledRadioButtonGroup
163
+ name="hasExpiration"
164
+ controllerName="hasExpiration"
165
+ control={control}
166
+ legendText=""
167
+ invalid={!!errors.hasExpiration}
168
+ invalidText={errors.hasExpiration && errors?.hasExpiration?.message}
169
+ options={expirationOptions} // Pass expirationOptions directly
170
+ />
171
+ </FormGroup>
163
172
 
164
- {hasExpiration && (
165
- <FormGroup className="clear-margin-bottom" title={t('expirationNotice', 'Expiration Notice (days)')}>
166
- <ControlledNumberInput
167
- id="expiryNotice"
168
- name="expiryNotice"
173
+ {observableHasExpiration && (
174
+ <FormGroup className="clear-margin-bottom" title={t('expirationNotice', 'Expiration Notice (days)')}>
175
+ <ControlledNumberInput
176
+ id="expiryNotice"
177
+ name="expiryNotice"
178
+ control={control}
179
+ controllerName="expiryNotice"
180
+ min={0}
181
+ hideSteppers={true}
182
+ size={'md'}
183
+ allowEmpty={true}
184
+ label={t('expiryNoticeDays', 'Expiration Notice (days)')}
185
+ invalid={!!errors.expiryNotice}
186
+ invalidText={errors.expiryNotice && errors?.expiryNotice?.message}
187
+ />
188
+ </FormGroup>
189
+ )}
190
+ </div>
191
+ <PreferredVendorSelector
192
+ name="preferredVendorUuid"
193
+ controllerName="preferredVendorUuid"
194
+ control={control}
195
+ title={t('whoIsThePreferredVendor', 'Who is the preferred vendor?')}
196
+ placeholder={t('chooseVendor', 'Choose vendor')}
197
+ invalid={!!errors.preferredVendorUuid}
198
+ invalidText={errors.preferredVendorUuid && errors?.preferredVendorUuid?.message}
199
+ />
200
+ <StockItemCategorySelector
201
+ name="categoryUuid"
202
+ controllerName="categoryUuid"
203
+ control={control}
204
+ itemType={
205
+ selectedItemType === StockItemType.PHARMACEUTICALS
206
+ ? 'Drugs'
207
+ : selectedItemType === StockItemType.NONE_PHARMACEUTICALS
208
+ ? 'Non Drugs'
209
+ : undefined
210
+ }
211
+ title={t('category', 'Category') + ':'}
212
+ placeholder={t('chooseACategory', 'Choose a category')}
213
+ invalid={!!errors.categoryUuid}
214
+ invalidText={errors.categoryUuid && errors?.categoryUuid?.message}
215
+ />
216
+ {observableIsDrug && (
217
+ <DispensingUnitSelector
218
+ name="dispensingUnitUuid"
219
+ controllerName="dispensingUnitUuid"
169
220
  control={control}
170
- controllerName="expiryNotice"
171
- min={0}
172
- hideSteppers={true}
173
- size={'md'}
174
- allowEmpty={true}
175
- label={t('expiryNoticeDays', 'Expiration Notice (days)')}
176
- invalid={!!errors.expiryNotice}
177
- invalidText={errors.expiryNotice && errors?.expiryNotice?.message}
221
+ title={t('dispensingUnit', 'Dispensing Unit') + ':'}
222
+ placeholder={t('dispensingUnitHolder', 'Choose a dispensing unit')}
223
+ invalid={!!errors.dispensingUnitUuid}
224
+ invalidText={errors.dispensingUnitUuid && errors?.dispensingUnitUuid?.message}
178
225
  />
179
- </FormGroup>
180
- )}
181
- </div>
182
- <PreferredVendorSelector
183
- name="preferredVendorUuid"
184
- controllerName="preferredVendorUuid"
185
- control={control}
186
- title={t('whoIsThePreferredVendor', 'Who is the preferred vendor?')}
187
- placeholder={t('chooseVendor', 'Choose vendor')}
188
- invalid={!!errors.preferredVendorUuid}
189
- invalidText={errors.preferredVendorUuid && errors?.preferredVendorUuid?.message}
190
- />
191
- <StockItemCategorySelector
192
- name="categoryUuid"
193
- controllerName="categoryUuid"
194
- control={control}
195
- itemType={
196
- selectedItemType === 'Pharmaceuticals'
197
- ? 'Drugs'
198
- : selectedItemType === 'Non Pharmaceuticals'
199
- ? 'Non Drugs'
200
- : undefined
201
- }
202
- title={t('category', 'Category') + ':'}
203
- placeholder={t('chooseACategory', 'Choose a category')}
204
- invalid={!!errors.categoryUuid}
205
- invalidText={errors.categoryUuid && errors?.categoryUuid?.message}
206
- />
207
- {isDrug && (
208
- <DispensingUnitSelector
209
- name="dispensingUnitUuid"
210
- controllerName="dispensingUnitUuid"
211
- control={control}
212
- title={t('dispensingUnit', 'Dispensing Unit') + ':'}
213
- placeholder={t('dispensingUnitHolder', 'Choose a dispensing unit')}
214
- invalid={!!errors.dispensingUnitUuid}
215
- invalidText={errors.dispensingUnitUuid && errors?.dispensingUnitUuid?.message}
216
- />
217
- )}
218
- {isDrug && isEditing && <StockItemUnitsEdit control={control} formState={formState} stockItemUuid={model.uuid} />}
219
-
220
- <div style={{ display: 'flex', flexDirection: 'row-reverse' }}>
221
- <Button
222
- name="save"
223
- type="button"
224
- className="submitButton"
225
- onClick={handleSubmit(handleSave)}
226
- kind="primary"
227
- renderIcon={Save}
228
- >
229
- {isSaving ? <InlineLoading /> : t('save', 'Save')}
230
- </Button>
231
- <Button kind="secondary" onClick={closeOverlay}>
232
- {t('cancel', 'Cancel')}
233
- </Button>
234
- </div>
235
- </form>
236
- );
237
- });
226
+ )}
227
+ {observableIsDrug && stockItem && (
228
+ <StockItemUnitsEdit control={control} formState={formState} stockItemUuid={stockItem?.uuid} />
229
+ )}
230
+ </div>
231
+ <ButtonSet className={styles.buttonSet}>
232
+ <Button kind="secondary" onClick={onCloseWorkspace} className={styles.button}>
233
+ {t('cancel', 'Cancel')}
234
+ </Button>
235
+ <Button
236
+ name="save"
237
+ type="button"
238
+ className={styles.button}
239
+ onClick={handleSubmit(handleSave)}
240
+ kind="primary"
241
+ renderIcon={Save}
242
+ >
243
+ {formState.isSubmitting ? <InlineLoading /> : t('save', 'Save')}
244
+ </Button>
245
+ </ButtonSet>
246
+ </form>
247
+ );
248
+ },
249
+ );
238
250
 
239
251
  export default StockItemDetails;
@@ -3,9 +3,14 @@ export interface RadioOption {
3
3
  value: boolean;
4
4
  }
5
5
 
6
+ export enum StockItemType {
7
+ PHARMACEUTICALS = 'Pharmaceuticals',
8
+ NONE_PHARMACEUTICALS = 'Non Pharmaceuticals',
9
+ }
10
+
6
11
  export const radioOptions: RadioOption[] = [
7
- { label: 'Pharmaceuticals', value: true },
8
- { label: 'Non Pharmaceuticals', value: false },
12
+ { label: StockItemType.PHARMACEUTICALS, value: true },
13
+ { label: StockItemType.NONE_PHARMACEUTICALS, value: false },
9
14
  ];
10
15
 
11
16
  export const expirationOptions: RadioOption[] = [