@openmrs/esm-stock-management-app 1.0.1-pre.438 → 1.0.1-pre.444

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,4 +1,4 @@
1
- import React, { useEffect, useMemo } from "react";
1
+ import React, { useEffect, useMemo, useState } from "react";
2
2
  import { showSnackbar } from "@openmrs/esm-framework";
3
3
  import { useTranslation } from "react-i18next";
4
4
  import { useStockItemPackageUnitsHook } from "./packaging-units.resource";
@@ -21,7 +21,10 @@ import { FormProvider, useForm, useFormContext } from "react-hook-form";
21
21
  import { zodResolver } from "@hookform/resolvers/zod";
22
22
  import { PackageUnitFormData, packageUnitSchema } from "./validationSchema";
23
23
  import { StockItemPackagingUOMDTO } from "../../../core/api/types/stockItem/StockItemPackagingUOM";
24
- import { createStockItemPackagingUnit } from "../../stock-items.resource";
24
+ import {
25
+ createStockItemPackagingUnit,
26
+ updateStockItemPackagingUnit,
27
+ } from "../../stock-items.resource";
25
28
  import DeleteModalButton from "./packaging-units-delete-modal-button.component";
26
29
 
27
30
  import styles from "./packaging-units.scss";
@@ -39,10 +42,28 @@ const PackagingUnits: React.FC<PackagingUnitsProps> = ({
39
42
  }) => {
40
43
  const { items, isLoading, setStockItemUuid, mutate } =
41
44
  useStockItemPackageUnitsHook();
45
+
46
+ const [packagingUnits, setPackagingUnits] =
47
+ useState<StockItemPackagingUOMDTO[]>(items);
48
+
49
+ const [newUnit, setNewUnit] = useState<{
50
+ factor: number;
51
+ packagingUomUuid: string;
52
+ packagingUomName: string;
53
+ }>({
54
+ packagingUomUuid: undefined,
55
+ factor: 0,
56
+ packagingUomName: "",
57
+ });
58
+
42
59
  useEffect(() => {
43
60
  setStockItemUuid(stockItemUuid);
44
61
  }, [stockItemUuid, setStockItemUuid]);
45
62
 
63
+ useEffect(() => {
64
+ setPackagingUnits(items);
65
+ }, [items]);
66
+
46
67
  const { t } = useTranslation();
47
68
  const tableHeaders = useMemo(
48
69
  () => [
@@ -72,40 +93,118 @@ const PackagingUnits: React.FC<PackagingUnitsProps> = ({
72
93
  });
73
94
 
74
95
  const handleSavePackageUnits = () => {
75
- const { getValues, setValue } = packageUnitForm;
76
- const { factor, packagingUomUuid } = getValues();
77
- const payload: StockItemPackagingUOMDTO = {
78
- factor: factor,
79
- packagingUomUuid,
80
- stockItemUuid,
81
- };
82
-
83
- createStockItemPackagingUnit(payload).then(
84
- () => {
85
- mutate();
86
-
87
- showSnackbar({
88
- title: t("savePackingUnitTitle", "Package Unit"),
89
- subtitle: t(
90
- "savePackingUnitMessage",
91
- "Package Unit saved successfully"
92
- ),
93
- kind: "success",
94
- });
95
- setValue("factor", 0);
96
- },
97
- () => {
98
- showSnackbar({
99
- title: t("savePackagingUnitErrorTitle", "Package Unit"),
100
- subtitle: t(
101
- "savePackagingUnitErrorMessage",
102
- "Error saving package unit"
103
- ),
104
- kind: "error",
105
- });
106
- }
96
+ const { reset } = packageUnitForm;
97
+
98
+ const newPayload = newUnit
99
+ ? {
100
+ factor: newUnit.factor,
101
+ packagingUomUuid: newUnit.packagingUomUuid,
102
+ stockItemUuid,
103
+ }
104
+ : null;
105
+
106
+ // Filter changed units
107
+ const updatedUnits = packagingUnits.filter((unit) => {
108
+ const originalUnit = items.find((item) => item.uuid === unit.uuid);
109
+ return originalUnit && originalUnit.factor !== unit.factor;
110
+ });
111
+
112
+ // Create new unit
113
+ const createPromises = newPayload.packagingUomUuid
114
+ ? createStockItemPackagingUnit(newPayload).then(
115
+ () => {
116
+ showSnackbar({
117
+ title: t("savePackingUnitTitle", "Package Unit"),
118
+ subtitle: t(
119
+ "savePackingUnitMessage",
120
+ "Package Unit saved successfully"
121
+ ),
122
+ kind: "success",
123
+ });
124
+ setNewUnit({
125
+ factor: 0,
126
+ packagingUomUuid: undefined,
127
+ packagingUomName: "",
128
+ }); // Reset new unit
129
+ },
130
+ () => {
131
+ showSnackbar({
132
+ title: t("savePackagingUnitErrorTitle", "Package Unit"),
133
+ subtitle: t(
134
+ "savePackagingUnitErrorMessage",
135
+ "Error saving package unit"
136
+ ),
137
+ kind: "error",
138
+ });
139
+ }
140
+ )
141
+ : Promise.resolve({ status: "no-create" });
142
+
143
+ // Update existing units
144
+ const updatePromises = updatedUnits.map((unit) =>
145
+ updateStockItemPackagingUnit(unit, unit.uuid).then(
146
+ () => {
147
+ showSnackbar({
148
+ title: t("updatePackingUnitTitle", "Package Unit"),
149
+ subtitle: t(
150
+ "updatePackingUnitMessage",
151
+ "Package Unit {{ name }} updated successfully",
152
+ { name: unit.packagingUomName }
153
+ ),
154
+ kind: "success",
155
+ });
156
+ },
157
+ () => {
158
+ showSnackbar({
159
+ title: t("updatePackagingUnitErrorTitle", "Package Unit"),
160
+ subtitle: t(
161
+ "updatePackagingUnitErrorMessage",
162
+ "Error updating package unit {{name}}",
163
+ { name: unit.packagingUomName }
164
+ ),
165
+ kind: "error",
166
+ });
167
+ }
168
+ )
169
+ );
170
+
171
+ // Wait for all requests to complete
172
+ Promise.all([createPromises, ...updatePromises]).then(() => {
173
+ mutate();
174
+ reset();
175
+ handleTabChange(0);
176
+ });
177
+ };
178
+
179
+ const handleNewUnitFactorChange = (value: string | number) => {
180
+ setNewUnit({
181
+ ...newUnit,
182
+ factor: Number(value),
183
+ });
184
+ };
185
+
186
+ const handleNewUnitPackageUnitChange = (unit: {
187
+ uuid: string;
188
+ display: string;
189
+ }) => {
190
+ setNewUnit({
191
+ ...newUnit,
192
+ packagingUomUuid: unit.uuid,
193
+ packagingUomName: unit.display,
194
+ });
195
+ };
196
+
197
+ const onFactorFieldUpdate = (
198
+ row: StockItemPackagingUOMDTO,
199
+ value: string | number
200
+ ) => {
201
+ const qtyValue = typeof value === "number" ? value : parseFloat(value);
202
+
203
+ setPackagingUnits((prevState) =>
204
+ prevState.map((item) =>
205
+ item.uuid === row.uuid ? { ...item, factor: qtyValue } : item
206
+ )
107
207
  );
108
- handleTabChange(0);
109
208
  };
110
209
 
111
210
  if (isLoading)
@@ -147,25 +246,38 @@ const PackagingUnits: React.FC<PackagingUnitsProps> = ({
147
246
  </TableHead>
148
247
  <TableBody className={styles.packingTableBody}>
149
248
  {items?.map((row: StockItemPackagingUOMDTO, index) => (
150
- <PackagingUnitRow row={row} key={`${index}-${row?.uuid}`} />
249
+ <PackagingUnitRow
250
+ row={row}
251
+ id={`${index}-${row?.uuid}`}
252
+ onChange={(value) => onFactorFieldUpdate(row, value)}
253
+ />
151
254
  ))}
152
- <PackagingUnitRow row={{}} key="bottom-row" isEditing />
255
+ <PackagingUnitRow
256
+ row={newUnit || {}}
257
+ id="new-package-unit"
258
+ isEditing
259
+ onChangePackageUnit={(value) =>
260
+ handleNewUnitPackageUnitChange(value)
261
+ }
262
+ onChange={(value) => handleNewUnitFactorChange(value)}
263
+ />
153
264
  </TableBody>
154
265
  </Table>
155
266
  </TableContainer>
156
267
  )}
157
268
  />
158
-
159
- <Button
160
- name="save"
161
- type="submit"
162
- className="submitButton"
163
- onClick={handleSavePackageUnits}
164
- kind="primary"
165
- renderIcon={Save}
166
- >
167
- {t("save", "Save")}
168
- </Button>
269
+ <div className={styles.packageUnitsBtn}>
270
+ <Button
271
+ name="save"
272
+ type="submit"
273
+ className="submitButton"
274
+ onClick={handleSavePackageUnits}
275
+ kind="primary"
276
+ renderIcon={Save}
277
+ >
278
+ {t("save", "Save")}
279
+ </Button>
280
+ </div>
169
281
  </FormProvider>
170
282
  );
171
283
  };
@@ -175,8 +287,10 @@ export default PackagingUnits;
175
287
  const PackagingUnitRow: React.FC<{
176
288
  isEditing?: boolean;
177
289
  row: StockItemPackagingUOMDTO;
178
- key?: string;
179
- }> = ({ isEditing, row, key }) => {
290
+ id?: string;
291
+ onChange?: (value: string | number) => void;
292
+ onChangePackageUnit?: (value: { uuid: string; display: string }) => void;
293
+ }> = ({ isEditing, row, id, onChange, onChangePackageUnit }) => {
180
294
  const {
181
295
  control,
182
296
  formState: { errors },
@@ -194,6 +308,7 @@ const PackagingUnitRow: React.FC<{
194
308
  name="packagingUomUuid"
195
309
  placeholder="Filter"
196
310
  control={control}
311
+ onPackageUnitChange={(concept) => onChangePackageUnit(concept)}
197
312
  invalid={!!errors.packagingUomUuid}
198
313
  />
199
314
  ) : (
@@ -202,20 +317,20 @@ const PackagingUnitRow: React.FC<{
202
317
  )}
203
318
  </TableCell>
204
319
  <TableCell>
205
- <div className={styles.packingTableCell}>
206
- <ControlledNumberInput
207
- row={row}
208
- controllerName="factor"
209
- name="factor"
210
- min={minPackagingQuantity}
211
- control={control}
212
- id={`${row.uuid}-${key}`}
213
- invalid={!!errors.factor}
214
- hideSteppers={true}
215
- />
216
-
217
- <DeleteModalButton closeModal={() => true} row={row} />
218
- </div>
320
+ <ControlledNumberInput
321
+ row={row}
322
+ controllerName="factor"
323
+ name="factor"
324
+ min={minPackagingQuantity}
325
+ control={control}
326
+ id={id}
327
+ invalid={!!errors.factor}
328
+ hideSteppers={true}
329
+ onChange={(e, state) => onChange(state.value)}
330
+ />
331
+ </TableCell>
332
+ <TableCell>
333
+ <DeleteModalButton closeModal={() => true} row={row} />
219
334
  </TableCell>
220
335
  </TableRow>
221
336
  </>
@@ -1,44 +1,56 @@
1
1
  @use '@carbon/styles/scss/colors';
2
2
  @use "@carbon/styles/scss/spacing";
3
3
  @use "@carbon/styles/scss/type";
4
+
4
5
  .packingTable {
5
- min-height: 15rem;
6
- display: block;
6
+ min-height: 15rem;
7
+ display: block;
7
8
  }
8
9
 
9
10
  .packingTableCell {
10
- display: flex;
11
- align-items: center;
11
+ display: flex;
12
+ align-items: center;
12
13
  }
13
14
 
14
15
  .packagingTableBody {
15
- min-height: spacing.$spacing-13;
16
+ min-height: spacing.$spacing-13;
16
17
  }
17
18
 
18
19
  .packagingTableContainer {
19
- min-height: 15rem;
20
- display: block;
20
+ padding-bottom: 1rem;
21
+ min-height: 15rem;
22
+ display: block;
23
+
24
+ :global(.cds--data-table-content) {
25
+ overflow-x: visible;
26
+ }
21
27
  }
22
28
 
23
29
  .searchAndSave {
24
- display: flex;
25
- flex-direction: column;
26
- align-items: flex-start;
27
- .submitButton {
28
- margin-top: 1rem;
29
- }
30
+ display: flex;
31
+ flex-direction: column;
32
+ align-items: flex-start;
33
+ .submitButton {
34
+ margin-top: 1rem;
35
+ }
30
36
  }
31
37
 
32
38
  .deleteModal {
33
- :global(.cds--modal-container.cds--modal-container--sm) {
34
- :global(.cds--modal-content) {
35
- :global(.cds--form-item) {
36
- margin-top: 1rem;
37
- }
38
- }
39
+ :global(.cds--modal-container.cds--modal-container--sm) {
40
+ :global(.cds--modal-content) {
41
+ :global(.cds--form-item) {
42
+ margin-top: 1rem;
43
+ }
39
44
  }
45
+ }
46
+ }
47
+
48
+ .packageUnitsBtn {
49
+ display: flex;
50
+ justify-content: flex-end;
51
+ margin-right: 2rem;
40
52
  }
41
53
 
42
54
  :global(cds--form-item) {
43
- margin-top: 1rem;
44
- }
55
+ margin-top: 1rem;
56
+ }
@@ -9,7 +9,7 @@ import { type ConfigObject } from "../../../config-schema";
9
9
 
10
10
  interface PackagingUnitsConceptSelectorProps<T> {
11
11
  row?: StockItemPackagingUOMDTO;
12
- onPackageUnitChange?: (unit: Concept) => void;
12
+ onPackageUnitChange?: (unit: { uuid: string; display: string }) => void;
13
13
  title?: string;
14
14
  placeholder?: string;
15
15
  invalid?: boolean;
@@ -62,7 +62,9 @@ const PackagingUnitsConceptSelector = <T,>(
62
62
  ]
63
63
  : dispensingUnits || []
64
64
  }
65
- onChange={(data: { selectedItem: Concept }) => {
65
+ onChange={(data: {
66
+ selectedItem: { uuid: string; display: string };
67
+ }) => {
66
68
  props.onPackageUnitChange?.(data?.selectedItem);
67
69
  onChange(data?.selectedItem?.uuid || ""); // Provide a default value if needed
68
70
  }}
@@ -96,6 +96,7 @@ const StockItemDetails = forwardRef<never, StockItemDetailsProps>(
96
96
  controllerName="drugUuid"
97
97
  control={control}
98
98
  title={t("pleaseSpecify", "Please specify:")}
99
+ placeholder="Choose a drug"
99
100
  invalid={!!errors.drugUuid}
100
101
  invalidText={errors.drugUuid && errors?.drugUuid?.message}
101
102
  />
@@ -105,7 +106,7 @@ const StockItemDetails = forwardRef<never, StockItemDetailsProps>(
105
106
  name="conceptUuid"
106
107
  controllerName="conceptUuid"
107
108
  control={control}
108
- title={t("pleaseSpecify", "Please specify:")}
109
+ title={t("pleaseSpecify", "Please specify") + ":"}
109
110
  placeholder={t("chooseAnItem", "Choose an item")}
110
111
  invalid={!!errors.drugUuid}
111
112
  invalidText={errors.drugUuid && errors?.drugUuid?.message}
@@ -119,7 +120,7 @@ const StockItemDetails = forwardRef<never, StockItemDetailsProps>(
119
120
  maxLength={255}
120
121
  size={"md"}
121
122
  value={`${model?.commonName ?? ""}`}
122
- labelText={t("commonName", "Common name:")}
123
+ labelText={t("commonName", "Common name") + ":"}
123
124
  invalid={!!errors.commonName}
124
125
  invalidText={errors.commonName && errors?.commonName?.message}
125
126
  />
@@ -130,7 +131,7 @@ const StockItemDetails = forwardRef<never, StockItemDetailsProps>(
130
131
  control={control}
131
132
  controllerName="acronym"
132
133
  size={"md"}
133
- labelText={t("abbreviation", "Abbreviation:")}
134
+ labelText={t("abbreviation", "Abbreviation") + ":"}
134
135
  invalid={!!errors.acronym}
135
136
  invalidText={errors.acronym && errors?.acronym?.message}
136
137
  />
@@ -200,7 +201,7 @@ const StockItemDetails = forwardRef<never, StockItemDetailsProps>(
200
201
  name="categoryUuid"
201
202
  controllerName="categoryUuid"
202
203
  control={control}
203
- title={t("category:", "Category:")}
204
+ title={t("category:", "Category") + ":"}
204
205
  placeholder={t("chooseACategory", "Choose a category")}
205
206
  invalid={!!errors.categoryUuid}
206
207
  invalidText={errors.categoryUuid && errors?.categoryUuid?.message}
@@ -210,7 +211,7 @@ const StockItemDetails = forwardRef<never, StockItemDetailsProps>(
210
211
  name="dispensingUnitUuid"
211
212
  controllerName="dispensingUnitUuid"
212
213
  control={control}
213
- title={t("dispensingUnit", "Dispensing Unit:")}
214
+ title={t("dispensingUnit", "Dispensing Unit") + ":"}
214
215
  placeholder={t("dispensingUnitHolder", "Choose a dispensing unit")}
215
216
  invalid={!!errors.dispensingUnitUuid}
216
217
  invalidText={
@@ -90,6 +90,12 @@ export function useStockItems(filter: StockItemFilter) {
90
90
  };
91
91
  }
92
92
 
93
+ // fetch filtered stock item
94
+ export function fetchStockItem(drugUuid: string) {
95
+ const apiUrl = `${restBaseUrl}/stockmanagement/stockitem?drugUuid=${drugUuid}&limit=1`;
96
+ return openmrsFetch(apiUrl).then(({ data }) => data);
97
+ }
98
+
93
99
  // getStockItemTransactions
94
100
  export function useStockItemTransactions(filter: StockItemTransactionFilter) {
95
101
  const apiUrl = `${restBaseUrl}/stockmanagement/stockitemtransaction${toQueryParams(
@@ -282,7 +288,10 @@ export function createStockItemPackagingUnit(item: StockItemPackagingUOMDTO) {
282
288
  }
283
289
 
284
290
  // updateStockItemPackagingUnit
285
- export function updateStockItemPackagingUnit(item: StockItemDTO, uuid: string) {
291
+ export function updateStockItemPackagingUnit(
292
+ item: StockItemPackagingUOMDTO,
293
+ uuid: string
294
+ ) {
286
295
  const apiUrl = `${restBaseUrl}/stockmanagement/stockitempackaginguom/${uuid}`;
287
296
  const abortController = new AbortController();
288
297
  return openmrsFetch(apiUrl, {