contentoh-components-library 21.5.99 → 21.6.1

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 (64) hide show
  1. package/dist/components/atoms/GeneralButton/styles.js +1 -1
  2. package/dist/components/atoms/GeneralInput/index.js +249 -54
  3. package/dist/components/atoms/GeneralInput/styles.js +7 -3
  4. package/dist/components/atoms/InputFormatter/index.js +223 -68
  5. package/dist/components/atoms/InputFormatter/styles.js +20 -4
  6. package/dist/components/molecules/StatusAsignationInfo/index.js +11 -1
  7. package/dist/components/molecules/TabsMenu/index.js +13 -1
  8. package/dist/components/molecules/TagAndInput/index.js +364 -24
  9. package/dist/components/molecules/TagAndInput/styles.js +2 -2
  10. package/dist/components/organisms/FullProductNameHeader/index.js +6 -22
  11. package/dist/components/organisms/InputGroup/index.js +22 -18
  12. package/dist/components/pages/ProviderProductEdition/ProviderProductEdition.stories.js +150 -337
  13. package/dist/components/pages/ProviderProductEdition/context/provider-product-edition.context.js +15 -15
  14. package/dist/components/pages/ProviderProductEdition/index.js +393 -352
  15. package/dist/components/pages/ProviderProductEdition/utils.js +1 -0
  16. package/dist/components/pages/RetailerProductEdition/RetailerProductEdition.stories.js +125 -211
  17. package/dist/components/pages/RetailerProductEdition/index.js +1743 -2239
  18. package/dist/components/pages/RetailerProductEdition/styles.js +4 -2
  19. package/dist/components/pages/RetailerProductEdition/utils.js +251 -2
  20. package/dist/contexts/AiProductEdition.js +34 -22
  21. package/package.json +4 -2
  22. package/src/ai/utils/compare-strings.js +45 -0
  23. package/src/assets/images/Icons/arrow.png +0 -0
  24. package/src/assets/images/Icons/cancel.png +0 -0
  25. package/src/assets/images/Icons/ia-icon.png +0 -0
  26. package/src/assets/images/Icons/loading.svg +5 -0
  27. package/src/assets/images/Icons/reload.png +0 -0
  28. package/src/components/atoms/GeneralButton/styles.js +4 -0
  29. package/src/components/atoms/GeneralInput/index.js +241 -60
  30. package/src/components/atoms/GeneralInput/styles.js +81 -0
  31. package/src/components/atoms/InputFormatter/index.js +200 -51
  32. package/src/components/atoms/InputFormatter/styles.js +284 -0
  33. package/src/components/atoms/RetailerSelector/RetailerSelector.stories.js +10 -0
  34. package/src/components/atoms/RetailerSelector/index.js +3 -0
  35. package/src/components/atoms/RetailerSelector/styles.js +0 -0
  36. package/src/components/molecules/StatusAsignationInfo/index.js +9 -1
  37. package/src/components/molecules/TabsMenu/index.js +12 -0
  38. package/src/components/molecules/TagAndInput/index.js +294 -21
  39. package/src/components/molecules/TagAndInput/styles.js +59 -17
  40. package/src/components/organisms/ChangeStatusModal/index.jsx +488 -0
  41. package/src/components/organisms/ChangeStatusModal/styles.js +333 -0
  42. package/src/components/organisms/FullProductNameHeader/index.js +4 -28
  43. package/src/components/organisms/FullTabsMenu/index.js +1 -1
  44. package/src/components/organisms/InputGroup/index.js +12 -4
  45. package/src/components/pages/ProviderProductEdition/ProviderProductEdition.stories.js +174 -202
  46. package/src/components/pages/ProviderProductEdition/context/provider-product-edition.context.jsx +14 -14
  47. package/src/components/pages/ProviderProductEdition/index.js +486 -417
  48. package/src/components/pages/ProviderProductEdition/utils.js +2 -2
  49. package/src/components/pages/RetailerProductEdition/RetailerProductEdition.stories.js +136 -243
  50. package/src/components/pages/RetailerProductEdition/context/provider-product-edition.context.jsx +575 -0
  51. package/src/components/pages/RetailerProductEdition/context/provider-product-edition.reducer.js +62 -0
  52. package/src/components/pages/RetailerProductEdition/context/reducers/active-state.js +344 -0
  53. package/src/components/pages/RetailerProductEdition/context/reducers/inputs.js +155 -0
  54. package/src/components/pages/RetailerProductEdition/context/reducers/product.js +114 -0
  55. package/src/components/pages/RetailerProductEdition/context/reducers/system.js +60 -0
  56. package/src/components/pages/RetailerProductEdition/index.js +1580 -1719
  57. package/src/components/pages/RetailerProductEdition/index_old.js +1979 -0
  58. package/src/components/pages/RetailerProductEdition/stories/Auditor.stories.js +101 -0
  59. package/src/components/pages/RetailerProductEdition/stories/ImageEditor.stories.js +115 -0
  60. package/src/components/pages/RetailerProductEdition/stories/TextEditor.stories.js +174 -0
  61. package/src/components/pages/RetailerProductEdition/styles.js +67 -2
  62. package/src/components/pages/RetailerProductEdition/utils.js +240 -0
  63. package/src/contexts/AiProductEdition.jsx +356 -0
  64. package/src/global-files/statusDictionary.js +103 -0
@@ -1,392 +1,297 @@
1
- import { Container } from "./styles";
2
- import { HeaderTop } from "../../molecules/HeaderTop";
3
- import { ImagePreviewer } from "../../organisms/ImagePreviewer";
4
- import { ImageDataTable } from "../../organisms/ImageDataTable";
5
- import { FullProductNameHeader } from "../../organisms/FullProductNameHeader";
6
- import { FullTabsMenu } from "../../organisms/FullTabsMenu";
7
- import { InputGroup } from "../../organisms/InputGroup";
8
- import { useEffect, useReducer, useState, useCallback } from "react";
9
- import { GalleryElement } from "../../molecules/GalleryElement";
1
+ import { useEffect, useState } from "react";
2
+
3
+ import axios from "axios";
4
+ import { useDropzone } from "react-dropzone";
10
5
  import { saveAs } from "file-saver";
11
- import { Commentary } from "../../atoms/Commentary";
6
+
12
7
  import {
13
- getRetailerServices,
14
- getPercentage,
15
- fetchUsers,
16
- } from "../../../global-files/data";
17
- import { GalleryHeader } from "../../molecules/GalleryHeader";
18
- import { ProductImageModal } from "../../organisms/ProductImageModal";
19
- import { useDropzone } from "react-dropzone";
20
- import axios from "axios";
21
- import { v4 as uuidv4 } from "uuid";
22
- import AWS from "aws-sdk";
23
- import attributesSent from "../../../assets/images/modalsSVGs/attributesSent.svg";
24
- import descriptionSent from "../../../assets/images/modalsSVGs/descriptionSent.svg";
25
- import imagesSent from "../../../assets/images/modalsSVGs/uploadingImages.svg";
26
- import { TagAndInput } from "../../molecules/TagAndInput/index";
27
- import { Button } from "../../atoms/GeneralButton";
28
- import { GenericModal } from "../../atoms/GenericModal";
29
- import { ScreenHeader } from "../../atoms/ScreenHeader";
30
- import { Loading } from "../../atoms/Loading";
31
- import succes from "../../../assets/images/genericModal/genericModalCheck.svg";
32
- import { VersionSelector } from "../../organisms/VersionSelector";
33
- import { useCloseModal } from "../../../global-files/customHooks";
8
+ ProviderProductEditionProvider,
9
+ useProviderProductEdition,
10
+ } from "./context/provider-product-edition.context";
34
11
  import {
12
+ normalizeProduct,
13
+ getConceptByTab,
35
14
  getAuditVersion,
36
15
  getInputsData,
37
16
  createMessage,
38
17
  sendMessage,
18
+ translateConcept,
19
+ getStatusArrayByRole,
20
+ getConceptsByRole,
21
+ buildCollaboratorAssignations,
22
+ calculateRequiredNull,
39
23
  } from "./utils";
40
- import { Modal } from "../../organisms/Modal";
24
+
25
+ import {
26
+ fetchUsers,
27
+ getPercentage,
28
+ getRetailerServices,
29
+ getServicesData,
30
+ } from "../../../global-files/data";
31
+ import { useCloseModal } from "../../../global-files/customHooks";
32
+
33
+ import { Button } from "../../atoms/GeneralButton";
34
+ import { GenericModal } from "../../atoms/GenericModal";
35
+ import { Loading } from "../../atoms/Loading";
36
+ import { ScreenHeader } from "../../atoms/ScreenHeader";
41
37
  import { ButtonV2 } from "../../atoms/ButtonV2";
42
- import generateThumbnail from "./generateThumnail";
43
-
44
- const reducerImages = (state, action) => {
45
- let { values, attrForImgs, inputsByRetailer } = state;
46
- switch (action.action) {
47
- case "init":
48
- const newInputsByRetailer = {};
49
- action?.init?.inputsByRetailer?.forEach((inputs) => {
50
- inputs?.forEach((input) => {
51
- if (!newInputsByRetailer[`${input.id_retailer}`])
52
- newInputsByRetailer[`${input.id_retailer}`] = [];
53
- newInputsByRetailer[`${input.id_retailer}`].push(input);
54
- });
55
- });
56
- inputsByRetailer = newInputsByRetailer;
57
- return {
58
- ...action.init,
59
- inputsByRetailer,
60
- valuesInitial: action.init.values,
61
- inputsInitial: action.init.inputs,
62
- };
63
- case "addImg":
64
- values = [...values, action.img];
65
- return { ...state, values };
66
- case "changeImageInfo":
67
- values[action.index][action.attribute] = action.value;
68
- return { ...state, values };
69
- case "changeShotType":
70
- values[action.index][action.attribute] = action.value;
71
- return { ...state, values };
72
-
73
- case "changeAttrValue":
74
- const index = attrForImgs.general.findIndex((f) => f.id === action.id);
75
- if (index !== -1) {
76
- attrForImgs.general[index].value = action.value;
77
- }
78
38
 
79
- return { ...state, attrForImgs, values };
80
- case "deleteImage":
81
- values = values.filter(
82
- (value) => action.selectedImages.indexOf(value) === -1
83
- );
84
- return { ...state, values };
85
- case "orderImages": {
86
- let { inputsByRetailer, valuesInitial, inputsInitial, inputs } = state;
87
- try {
88
- const orderedImages = [];
89
- const imageIdArray = [];
90
- if (action?.retailerId && !inputsByRetailer[action.retailerId]) {
91
- inputsByRetailer[action.retailerId] = [];
92
- }
93
- action.retailerId &&
94
- inputsByRetailer[action.retailerId]?.filter((input) => {
95
- imageIdArray.push(input.id_image);
96
- valuesInitial.forEach(async (value) => {
97
- if (value.ext == "mp4") {
98
- convertirVideoABase64(
99
- "https://" +
100
- process.env.REACT_APP_IMAGES_BUCKET +
101
- ".s3.amazonaws.com/" +
102
- value.srcDB
103
- )
104
- .then(async (base64) => {
105
- const videoSrc =
106
- "data:video/mp4;base64," + base64.split(",")[1]; // Añade el tipo MIME adecuado
107
- value.src = await generateThumbnail(videoSrc, 2);
108
- })
109
- .catch((error) => {
110
- console.error("Error:", error);
111
- });
112
- }
113
- if (value.image_id === input.id_image) orderedImages.push(value);
114
- });
115
- });
116
- inputs = inputsInitial?.filter((input) =>
117
- imageIdArray.includes(input.id)
118
- );
39
+ import { GalleryElement } from "../../molecules/GalleryElement";
40
+ import { GalleryHeader } from "../../molecules/GalleryHeader";
41
+ import { HeaderTop } from "../../molecules/HeaderTop";
42
+ import { TagAndInput } from "../../molecules/TagAndInput/index";
119
43
 
120
- values = orderedImages.length > 0 ? orderedImages : [];
121
- } catch (error) {
122
- console.log(error);
123
- }
124
- return { ...state, values, inputs };
125
- }
126
- default:
127
- return state;
128
- }
129
- };
44
+ import { FullProductNameHeader } from "../../organisms/FullProductNameHeader";
45
+ import { FullTabsMenu } from "../../organisms/FullTabsMenu";
46
+ import { ImageDataTable } from "../../organisms/ImageDataTable";
47
+ import { ImagePreviewer } from "../../organisms/ImagePreviewer";
48
+ import { InputGroup } from "../../organisms/InputGroup";
49
+ import { ProductImageModal } from "../../organisms/ProductImageModal";
50
+ import { VersionSelector } from "../../organisms/VersionSelector";
51
+ import { Modal } from "../../organisms/Modal";
130
52
 
131
- const S3_BUCKET = process.env.REACT_APP_IMAGES_BUCKET;
132
- const REGION = "us-east-1";
133
-
134
- function convertirVideoABase64(url) {
135
- return new Promise((resolve, reject) => {
136
- const xhr = new XMLHttpRequest();
137
- xhr.open("GET", url, true);
138
- xhr.responseType = "blob";
139
- xhr.onload = function () {
140
- if (xhr.status === 200) {
141
- const blob = xhr.response;
142
- const reader = new FileReader();
143
- reader.onloadend = function () {
144
- resolve(reader.result);
145
- };
146
- reader.readAsDataURL(blob);
147
- } else {
148
- reject(new Error("Error al cargar el video"));
149
- }
150
- };
151
- xhr.onerror = function () {
152
- reject(new Error("Error de red al cargar el video"));
153
- };
154
- xhr.send();
155
- });
156
- }
157
-
158
- function obtenerDuracionVideoBase64(base64Data) {
159
- return new Promise((resolve, reject) => {
160
- const video = document.createElement("video");
161
- video.src = base64Data;
162
- video.addEventListener("loadedmetadata", () => {
163
- resolve(video.duration);
164
- });
165
- video.addEventListener("error", reject);
166
- });
167
- }
53
+ import successIcon from "../../../assets/images/genericModal/genericModalCheck.svg";
54
+ import errorIcon from "../../../assets/images/genericModal/errorModal.svg";
55
+ import warningIcon from "../../../assets/images/genericModal/genericModalWarning.svg";
56
+ import attributesSent from "../../../assets/images/modalsSVGs/attributesSent.svg";
57
+ import descriptionSent from "../../../assets/images/modalsSVGs/descriptionSent.svg";
58
+ import imagesSent from "../../../assets/images/modalsSVGs/uploadingImages.svg";
59
+
60
+ import { Container } from "./styles";
61
+ import { Commentary } from "../../atoms/Commentary";
168
62
 
169
- AWS.config.update({
170
- accessKeyId: process.env.REACT_APP_KUTS3,
171
- secretAccessKey: process.env.REACT_APP_AKUTS3,
172
- });
63
+ import { AiProductEditionProvider } from "../../../contexts/AiProductEdition";
64
+ import ChangeStatusModal from "../../organisms/ChangeStatusModal";
173
65
 
174
- const myBucket = new AWS.S3({
175
- params: { Bucket: S3_BUCKET },
176
- region: REGION,
177
- });
66
+ import { Skeleton, Box, Stack } from "@mui/material";
178
67
 
179
- export const RetailerProductEdition = ({
68
+ const allowedAccountsToViewChangeStatus = [
69
+ "mperez@contentoh.com",
70
+ "gmorales@contentoh.com",
71
+ "rvega@contentoh.com",
72
+ "amora@contentoh.com",
73
+ "oparra@contentoh.com",
74
+ "zsegura@contentoh.com",
75
+ "oserna@contentoh.com",
76
+ "jalvarado@contentoh.com"
77
+ ];
78
+
79
+ const RetailerProductEditionView = ({
180
80
  tabsSections,
181
81
  productSelected = {},
182
82
  user = {},
183
83
  token,
184
84
  location,
185
85
  }) => {
186
- const [activeTab, setActiveTab] = useState("Descripción");
187
- const [activeImage, setActiveImage] = useState();
188
- const [imageLayout, setImageLayout] = useState(false);
86
+ const {
87
+ state,
88
+ dispatch,
89
+ saveDatasheets,
90
+ saveDescriptions,
91
+ updateImages,
92
+ saveImageAttrs,
93
+ deleteImages,
94
+ handleOnDownloadImages,
95
+ createComment,
96
+ } = useProviderProductEdition();
97
+
98
+ const isRetailer = user?.is_retailer;
99
+
100
+ const [showVersionSelector, setShowVersionSelector] =
101
+ useCloseModal("version-selector");
102
+ const [auditableVersion, setAuditableVersion] = useState(null);
189
103
  const [headerTop, setHeaderTop] = useState(0);
190
- const [descriptions, setDescriptions] = useState([]);
191
- const [datasheets, setDatasheets] = useState([]);
192
- const [images, setImages] = useReducer(reducerImages, {});
193
- const [showModal, setShowModal] = useState(false);
104
+ const [shotThd, setShotThd] = useState(false);
105
+ const [observation, setObservation] = useState();
194
106
  const [showRejectModal, setShowRejectModal] = useState(false);
195
- const { getRootProps, getInputProps } = useDropzone({
196
- accept: "image/*, video/mp4",
107
+ const [isObservationVisible, setObservationVisible] = useState(false);
108
+ const [imageLayout, setImageLayout] = useState(false);
109
+ const [crossComment, setCrossComment] = useState(false);
110
+ const [statusArray, setStatusArray] = useState([]);
111
+ const [rejectAll, setRejectAll] = useState(false);
112
+
113
+ const [auditServices, setAuditServices] = useState([]);
114
+ const [auditDatasheets, setAuditDatasheets] = useState([]);
115
+ const [auditDescriptions, setAuditDescriptions] = useState([]);
116
+ const [showModal, setShowModal] = useState(false);
117
+
118
+ const [compare, setCompare] = useState(false);
119
+ const { getRootProps, getInputProps, isDragActive, open } = useDropzone({
120
+ accept: "image/*",
197
121
  noKeyboard: true,
198
122
  multiple: true,
199
123
  noClick: true,
200
124
  onDrop: (acceptedFiles) => {
201
- const newImages = [];
202
125
  acceptedFiles.map((file) => {
203
126
  const reader = new FileReader();
204
127
  reader.fileName = file.name;
128
+
205
129
  reader.onload = async function (e) {
206
- const ext = e.srcElement.fileName.split(".");
130
+ const fileName = e.target.fileName;
131
+ const fileExtension = fileName.split(".").pop();
132
+ const fileDataURL = e.target.result;
133
+
207
134
  const img = new Image();
208
- if (ext[ext.length - 1] == "mp4") {
209
- obtenerDuracionVideoBase64(e.target.result)
210
- .then(async (duracion) => {
211
- if (duracion > 15 && duracion < 600) {
212
- img.src = await generateThumbnail(e.target.result, 2);
213
- setTimeout(() => {
214
- const width = img.width;
215
- const height = img.height;
216
- const newImg = {
217
- action: "addImg",
218
- img: {
219
- src: img.src, //e.target.result,
220
- name: e.target.fileName,
221
- ext: ext[ext.length - 1],
222
- width: width,
223
- height: height,
224
- video_src:
225
- ext[ext.length - 1] == "mp4" ? e.target.result : "",
226
- },
227
- };
228
- setImages(newImg);
229
- }, 500);
230
- } else {
231
- setModalAlert({
232
- show: true,
233
- title: "Hubo un error al subir el video",
234
- message:
235
- "Los videos deben durar entre 15 segundos y 10 minutos",
236
- });
237
- }
238
- })
239
- .catch((error) => {
240
- console.error("Error al obtener la duración del video:", error);
241
- });
242
- } else {
243
- img.src = e.target.result;
244
- setTimeout(() => {
245
- const width = img.width;
246
- const height = img.height;
247
- const newImg = {
248
- action: "addImg",
249
- img: {
250
- src: img.src, //e.target.result,
251
- name: e.target.fileName,
252
- ext: ext[ext.length - 1],
253
- width: width,
254
- height: height,
255
- video_src:
256
- ext[ext.length - 1] == "mp4" ? e.target.result : "",
257
- },
258
- };
259
- setImages(newImg);
260
- }, 500);
261
- }
135
+ img.src = fileDataURL;
136
+
137
+ img.onload = function () {
138
+ const width = img.width;
139
+ const height = img.height;
140
+ const newImg = {
141
+ src: fileDataURL,
142
+ name: fileName,
143
+ ext: fileExtension,
144
+ width: width,
145
+ height: height,
146
+ isApproved: true, // VALIDAR QUE SOLO SEA PARA RADIOSHACK
147
+ };
148
+
149
+ dispatch({
150
+ type: "ADD_IMAGE_VALUE",
151
+ payload: newImg,
152
+ });
153
+ };
262
154
  };
155
+
263
156
  reader.onerror = function (error) {
264
157
  console.log("dropzoneError: ", error);
265
158
  };
159
+
266
160
  reader.readAsDataURL(file);
267
161
  return file;
268
162
  });
269
163
  },
270
164
  });
271
- const [updatedDatasheets, setUpdatedDatasheets] = useState([]);
272
- const [updatedDescriptions, setUpdatedDescriptions] = useState([]);
273
- const [imagesUploaded, setImagesUploaded] = useState(false);
274
- const [dataImages, setDataImages] = useState();
275
- const [percentages, setPercentages] = useState(
276
- new Array(product?.retailers?.length).fill({ percentage: 0 })
277
- );
278
- // const [percentages, setPercentages] = useState([{ retailers: {} }]);
279
- const [activePercentage, setActivePercentage] = useState(0);
280
- const [activeRetailer, setActiveRetailer] = useState({});
281
- const [services, setServices] = useState([]);
282
- const [servicesData, setServicesData] = useState([]);
283
- const [message, setMessage] = useState("");
284
- const [product, setProduct] = useState(
285
- JSON.parse(sessionStorage.getItem("productSelected"))
286
- ? JSON.parse(sessionStorage.getItem("productSelected"))
287
- : productSelected
288
- );
289
- const [icon, setIcon] = useState(null);
290
- const [version, setVersion] = useState(product?.version);
291
- const [comments, setComments] = useState({});
292
- const [comment, setComment] = useState("");
293
- const [requiredNull, setRequiredNull] = useState({
294
- "Ficha técnica": 0,
295
- Descripción: 0,
296
- Imágenes: 0,
297
- });
298
- const [crossComment, setCrossComment] = useState(false);
299
- const [userGroups, setUserGroups] = useState([]);
300
- const [assig, setAssig] = useState({});
301
- const [selectedImages, setSelectedImages] = useState([]);
302
- const [componentsArray, setComponentsArray] = useState([]);
303
- const [checkAll, setCheckAll] = useState(false);
304
- const isRetailer = user?.is_retailer;
305
- const [loading, setLoading] = useState(true);
306
- const [retailerStatus, setRetailerStatus] = useState("-");
307
- const [statusArray, setStatusArray] = useState([]);
308
- const [socketType, setSocketType] = useState(null);
309
- const [saving, setSaving] = useState(loading);
310
- const [showVersionSelector, setShowVersionSelector] =
311
- useCloseModal("version-selector");
312
- const shotThd = [58, 59, 60, 61].includes(activeRetailer.id);
313
- const [auditableVersion, setAuditableVersion] = useState(null);
314
- const [auditServices, setAuditServices] = useState([]);
315
- const [auditDatasheets, setAuditDatasheets] = useState([]);
316
- const [auditDescriptions, setAuditDescriptions] = useState([]);
317
- const [auditImages, setAuditImages] = useState([]);
318
- const [compare, setCompare] = useState(false);
319
- const [observation, setObservation] = useState();
320
- const [valRejAll, setValRejAll] = useState(false);
321
- const [modalAlert, setModalAlert] = useState({
322
- show: false,
323
- title: "",
324
- message: "",
325
- errorInputMessage: false,
326
- });
327
165
 
328
- const [desc, setDesc] = useState([]);
329
- const [fich, setFich] = useState([]);
330
- const [imag, setImag] = useState([]);
166
+ const [openChangeStatusModal, setOpenChangeStatusModal] = useState(false);
331
167
 
332
- const [isObservationVisible, setObservationVisible] = useState(false);
168
+ const thumbs = () => {
169
+ const imageInputs = state.images_values?.inputs?.map((e) => ({
170
+ value: e?.id,
171
+ name: e?.name,
172
+ required: e?.required,
173
+ active: state.images_values?.values?.some(
174
+ (value) => value?.image_id === e?.id,
175
+ ),
176
+ }));
177
+ const imageType = state.images_values?.imageType?.map((e) => ({
178
+ value: e?.id,
179
+ name: e?.name,
180
+ }));
181
+ const imagePackagingType = state.images_values?.imagePackagingType?.map(
182
+ (e) => ({
183
+ value: e?.id,
184
+ name: e?.name,
185
+ }),
186
+ );
333
187
 
334
- const toggleObservation = () => {
335
- setObservationVisible(!isObservationVisible);
336
- };
337
- // const handleClickOutside = (event) => {
338
- // if (
339
- // isObservationVisible &&
340
- // !event.target.closest(".Observation") &&
341
- // !event.target.closest(".Container")
342
- // ) {
343
- // hideObservation();
344
- // }
345
- // };
346
-
347
- // useEffect(() => {
348
- // document.addEventListener("click", handleClickOutside);
349
- // return () => {
350
- // document.removeEventListener("click", handleClickOutside);
351
- // };
352
- // }, [isObservationVisible]);
353
-
354
- const hideObservation = () => {
355
- setObservationVisible(false);
188
+ return state.images_values?.values?.map((image, index) => (
189
+ <GalleryElement
190
+ id={"gallery-element-" + index}
191
+ index={index + "-" + image.name}
192
+ key={index}
193
+ image={image}
194
+ number={index}
195
+ gridLayout={imageLayout}
196
+ imageType={imageType}
197
+ imagePackagingType={imagePackagingType}
198
+ imageInputs={imageInputs}
199
+ selectedImages={state.selected_images}
200
+ setCheckAll={handleOnSetCheckAll}
201
+ changeImage={(item) => {
202
+ dispatch({ type: "UPDATE_IMAGE_VALUE", payload: item });
203
+ }}
204
+ setSelectedImages={(item) =>
205
+ dispatch({ type: "SET_SELECTED_IMAGE", payload: item })
206
+ }
207
+ updateApprovedInputsImages={(item) =>
208
+ console.log("updateApprovedInputsImages", item)
209
+ }
210
+ />
211
+ ));
356
212
  };
357
213
 
358
- const handleCommentSubmit = (e) => {
359
- e.preventDefault();
360
- const commentText = document.querySelector(
361
- "#commentary-box .ql-container .ql-editor > p"
362
- ).innerHTML;
363
- createComment(e, commentText, activeTab);
214
+ const initializeProduct = async () => {
215
+ try {
216
+ const product = productSelected ? productSelected : JSON.parse(sessionStorage.getItem("productSelected"));
217
+
218
+ const productNormalized = normalizeProduct(product);
219
+
220
+ console.log({productNormalized});
221
+
222
+ dispatch({
223
+ type: "SET_PRODUCT",
224
+ payload: productNormalized,
225
+ });
226
+ dispatch({
227
+ type: "SET_ACTIVE_RETAILER",
228
+ payload: productNormalized.categoryRetailerInOrder[0],
229
+ });
230
+ setShotThd(
231
+ [58, 59, 60, 61].includes(
232
+ productNormalized.categoryRetailerInOrder[0].id_retailer,
233
+ ),
234
+ );
235
+
236
+ // Configurar statusArray según el rol del usuario
237
+ setStatusArray(getStatusArrayByRole(user.id_role));
238
+
239
+ // Cargar usuarios y asignaciones
240
+ const users = await fetchUsers(token);
241
+ if (users.length > 0) {
242
+ dispatch({
243
+ type: "SET_COLLABORATOR_ASSIGNATIONS",
244
+ payload: buildCollaboratorAssignations(productNormalized, users),
245
+ });
246
+ }
247
+
248
+ const { id_article } = productNormalized;
249
+ if (id_article) {
250
+ await getAuditVersion(id_article, setAuditableVersion, token);
251
+ }
252
+
253
+ if (id_article && productNormalized.id_order) {
254
+ const response = await axios.get(
255
+ process.env.REACT_APP_READ_OBSERVATION,
256
+ {
257
+ params: {
258
+ articleId: id_article,
259
+ orderId: productNormalized.id_order,
260
+ },
261
+ headers: {
262
+ Authorization: sessionStorage.getItem("jwt"),
263
+ },
264
+ },
265
+ );
266
+ const parseData = JSON.parse(response.data.body).data[0];
267
+ setObservation(parseData.observations);
268
+ }
269
+ } catch (error) {
270
+ console.log("Error setting product data: ", error);
271
+ }
364
272
  };
365
273
 
366
- useEffect(async () => {
367
- const { id_article } = product?.article || {};
368
- if (id_article)
369
- await getAuditVersion(id_article, setAuditableVersion, token);
274
+ // Setter del producto y carga inicial (optimizado para evitar cascada)
275
+ useEffect(() => {
276
+ initializeProduct();
370
277
  }, []);
371
278
 
372
279
  const loadAuditableData = async () => {
373
- if (auditableVersion) {
374
- const auditServices = await getRetailerServices(
375
- product?.article?.id_article,
376
- product?.article?.category,
377
- parseInt(product?.article?.id_category),
378
- auditableVersion.version,
379
- token
380
- );
381
- getInputsData(
382
- auditServices,
383
- activeRetailer,
384
- setAuditDatasheets,
385
- setAuditDescriptions
386
- );
387
- setAuditServices(auditServices);
388
- setAuditImages(auditServices[2]);
389
- }
280
+ const auditServices = await getRetailerServices(
281
+ state.product?.id_article,
282
+ state.product?.categoryName,
283
+ parseInt(state.product?.id_category),
284
+ auditableVersion.version,
285
+ token,
286
+ );
287
+ getInputsData(
288
+ auditServices,
289
+ { id: state.active_retailer.id_retailer },
290
+ setAuditDatasheets,
291
+ setAuditDescriptions,
292
+ );
293
+ setAuditServices(auditServices);
294
+ setAuditImages(auditServices[2]);
390
295
  };
391
296
 
392
297
  useEffect(() => {
@@ -395,682 +300,438 @@ export const RetailerProductEdition = ({
395
300
  }
396
301
  }, [auditableVersion]);
397
302
 
398
- useEffect(() => {
399
- checkAll && setSelectedImages(images.values);
400
- }, [checkAll]);
401
-
402
- const loadData = async () => {
303
+ const loadData = async (enableLoading = true, versionSelected = null) => {
403
304
  try {
404
- setLoading(true);
405
-
406
- const ids =
407
- product?.statusByRetailer?.map((item) => item.retailer_id) ??
408
- product?.statusByRetailer?.map((item) => item.id_retailer) ??
409
- [];
410
- const uniqueIds = [...new Set(ids)];
411
-
412
- let activeRetailerId = activeRetailer?.id;
413
- if (!activeRetailerId) {
414
- const firstValid = product?.article?.categoryRetailer?.find((item) =>
415
- uniqueIds.includes(item.id_retailer || item.retailer_id)
416
- );
417
- if (firstValid) {
418
- activeRetailerId = firstValid.id_retailer || firstValid.retailer_id;
419
- }
420
- }
421
-
422
- const categoryRetailer =
423
- product?.article?.categoryRetailer?.filter(
424
- (item) =>
425
- uniqueIds.includes(item.id_retailer) &&
426
- item.id_retailer === activeRetailerId
427
- ) ??
428
- product?.article?.categoryRetailer?.filter(
429
- (item) =>
430
- uniqueIds.includes(item.retailer_id) &&
431
- item.retailer_id === activeRetailerId
432
- ) ??
433
- [];
434
-
435
- const categoryName = categoryRetailer[0]?.category_name;
436
- const categoryId = categoryRetailer[0]?.id_category;
437
-
438
- const category_name =
439
- categoryName ??
440
- (product?.article?.company_name || product?.categoryName);
441
- const category_id =
442
- categoryId ??
443
- parseInt(product?.article?.id_category || product?.id_category);
444
-
445
- const retailer_id = activeRetailerId ?? categoryRetailer?.id_retailer;
446
-
447
- const services = await getRetailerServices(
448
- product?.id_article || product?.article?.id_article,
449
- category_name,
450
- category_id,
451
- version,
452
- token,
453
- retailer_id
454
- );
455
-
456
- if (auditableVersion && retailer_id) {
457
- loadAuditableData();
458
- }
459
-
460
- setServices(services);
461
- const generalServices = await getServices();
462
-
463
- setImages({ action: "init", init: services[2] });
305
+ if (enableLoading) dispatch({ type: "SET_LOADING", payload: true });
464
306
 
465
- if (services[2]?.values?.length > 0) setActiveImage(0);
307
+ const { id_article, version, id_order } = state.product;
308
+ const { category, id_category, id_retailer } = state.active_retailer;
466
309
 
310
+ // Payloads y headers
467
311
  const data = [
468
312
  {
469
- id_article: product?.article?.id_article,
313
+ id_article,
314
+ version: versionSelected ? versionSelected : version,
470
315
  relations: [
471
316
  {
472
- id_retailer: retailer_id,
473
- id_category: category_id,
317
+ id_retailer,
318
+ id_category,
474
319
  },
475
320
  ],
476
- version: product.version,
477
321
  },
478
322
  ];
479
323
 
480
324
  const headers = { Authorization: token };
481
- getPercentage({ data, headers }).then((res) => {
482
- setPercentages(JSON.parse(res?.[0]?.body));
325
+
326
+ const [services, percentagesRes, servicesDataRes, commentsResponse] =
327
+ await Promise.all([
328
+ getRetailerServices(
329
+ id_article,
330
+ category,
331
+ id_category,
332
+ versionSelected ?? version,
333
+ token,
334
+ id_retailer,
335
+ ),
336
+ getPercentage({ data, headers }),
337
+ getServicesData({
338
+ articleId: id_article,
339
+ orderId: id_order,
340
+ end: true,
341
+ token,
342
+ }),
343
+ // Obtener comentarios en paralelo
344
+ Promise.all([
345
+ axios.get(process.env.REACT_APP_COMMENTS_ENDPOINT, {
346
+ params: {
347
+ articleId: id_article,
348
+ concept: "description",
349
+ orderIdColab: id_order,
350
+ version: versionSelected ?? version,
351
+ },
352
+ }),
353
+ axios.get(process.env.REACT_APP_COMMENTS_ENDPOINT, {
354
+ params: {
355
+ articleId: id_article,
356
+ concept: "datasheet",
357
+ orderIdColab: id_order,
358
+ version: versionSelected ?? version,
359
+ },
360
+ }),
361
+ axios.get(process.env.REACT_APP_COMMENTS_ENDPOINT, {
362
+ params: {
363
+ articleId: id_article,
364
+ concept: "images",
365
+ orderIdColab: id_order,
366
+ version: versionSelected ?? version,
367
+ },
368
+ }),
369
+ ]),
370
+ ]);
371
+
372
+ dispatch({
373
+ type: "SET_RETAILER_STATUS",
374
+ payload: servicesDataRes.map((service) => ({
375
+ retailer_id: service?.id_retailer,
376
+ service: service?.service,
377
+ status: service?.status,
378
+ task_user_group_id: service?.task_user_group_id,
379
+ })),
380
+ });
381
+
382
+ // Extraer relaciones del response
383
+ const percentages =
384
+ JSON.parse(percentagesRes?.[0]?.body)?.[0]?.relations ?? [];
385
+
386
+ // Procesar comentarios
387
+ let commentsMap = {};
388
+ commentsResponse.forEach((commentRes) => {
389
+ const commentData = JSON.parse(commentRes?.data?.body).data[0];
390
+ if (commentData) {
391
+ const tab = translateConcept(commentData.concept);
392
+ commentsMap[tab] = commentData;
393
+ }
394
+ });
395
+ dispatch({ type: "SET_COMMENTS", payload: commentsMap });
396
+
397
+ // Ordenamiento de imágenes
398
+ const orderMap = services[2].inputsByRetailer
399
+ .flat()
400
+ .reduce((acc, item) => {
401
+ acc[item.id_image] = item.order;
402
+ return acc;
403
+ }, {});
404
+
405
+ const orderedValues = [...services[2].values].sort((a, b) => {
406
+ const orderA = orderMap[a.image_id] ?? Number.MAX_SAFE_INTEGER;
407
+ const orderB = orderMap[b.image_id] ?? Number.MAX_SAFE_INTEGER;
408
+ return orderA - orderB;
409
+ });
410
+
411
+ // Procesamiento de inputs del retailer activo (evita cascada con state.services)
412
+ const retailerDatasheets = services[0][id_retailer];
413
+ const datasheetsActiveRetailer = {
414
+ ...retailerDatasheets,
415
+ data: Object.values(retailerDatasheets.data),
416
+ };
417
+
418
+ const descriptionsActiveRetailer = services[1].filter(
419
+ (item) => item.id === id_retailer,
420
+ );
421
+
422
+ const filteredValues = services[2].values.filter((value) => {
423
+ return services[2].inputsByRetailer.some((retailerInput) =>
424
+ retailerInput.some(
425
+ (input) =>
426
+ input.id_retailer === id_retailer &&
427
+ input.id_image === value.image_id,
428
+ ),
429
+ );
430
+ });
431
+
432
+ // Dispatch consolidado
433
+ dispatch({
434
+ type: "SET_SERVICES",
435
+ payload: {
436
+ datasheets: services[0],
437
+ descriptions: services[1],
438
+ images: {
439
+ ...services[2],
440
+ values: orderedValues,
441
+ },
442
+ },
443
+ });
444
+
445
+ dispatch({ type: "SET_PERCENTAGES", payload: percentages });
446
+ dispatch({
447
+ type: "SET_ACTIVE_PERCENTAGES",
448
+ payload: percentages.find(
449
+ ({ id_retailer: rId }) => rId === id_retailer,
450
+ ),
451
+ });
452
+ dispatch({ type: "SET_SERVICES_DATA", payload: servicesDataRes });
453
+
454
+ // Setear inputs del retailer activo directamente (evita cascada)
455
+ dispatch({
456
+ type: "SET_DATASHEETS_INPUTS",
457
+ payload: [datasheetsActiveRetailer, services[0].inputs],
458
+ });
459
+
460
+ dispatch({
461
+ type: "SET_DESCRIPTIONS_INPUTS",
462
+ payload: descriptionsActiveRetailer,
463
+ });
464
+
465
+ dispatch({
466
+ type: "SET_IMAGES_VALUES",
467
+ payload: {
468
+ ...services[2],
469
+ values: filteredValues,
470
+ },
483
471
  });
484
472
 
485
- setImages({ action: "orderImages", retailerId: activeRetailer.id });
473
+ // Calcular campos requeridos sin valor
474
+ dispatch({
475
+ type: "SET_MISSING_REQUIRED_FIELDS",
476
+ payload: calculateRequiredNull(services, servicesDataRes, id_retailer),
477
+ });
486
478
 
487
- setLoading(false);
479
+ // Auditable
480
+ if (auditableVersion && id_retailer) {
481
+ loadAuditableData();
482
+ }
488
483
  } catch (error) {
489
- setLoading(false);
490
- console.log(error);
484
+ console.log("Error loading data: ", error);
485
+ dispatch({ type: "SET_ERRORS", payload: [error.message] });
486
+ } finally {
487
+ dispatch({ type: "SET_LOADING", payload: false });
491
488
  }
492
489
  };
493
490
 
494
491
  useEffect(() => {
495
- setLoading(true);
496
- loadData();
497
- }, [activeRetailer]);
498
-
499
- const getServices = async () => {
500
- const servicesResponse = await axios.get(
501
- `${process.env.REACT_APP_SERVICES_ENDPOINT}?articleId=${
502
- product?.article?.id_article
503
- }&orderId=${product?.article?.id_order || product.orderId}&end=true`
504
- );
505
- const parsedResponse = JSON.parse(servicesResponse?.data?.body).data;
492
+ if (state.active_retailer) {
493
+ loadData();
494
+ }
495
+ }, [state.active_retailer]);
506
496
 
507
- const retailers = product.retailers || product.retailersAvailable;
508
- const retailerResponse = parsedResponse?.map((srv) => srv.id_retailer);
509
- const active = retailers?.find((retailer) =>
510
- retailerResponse.includes(retailer.id)
511
- );
512
- !activeRetailer.id && setActiveRetailer(active ? active : retailers[0]);
513
- setServicesData(parsedResponse);
497
+ const handleOnChangeCurrentImage = (index) => {
498
+ if (typeof index === "number") {
499
+ dispatch({ type: "SET_CURRENT_IMAGE", payload: index });
500
+ } else if (typeof index === "object") {
501
+ dispatch({ type: "CHANGE_ATTR_VALUE", payload: index });
502
+ }
514
503
  };
515
504
 
516
- const translateConcept = (concept) => {
517
- const translation = {
518
- datasheet: "Ficha técnica",
519
- description: "Descripción",
520
- images: "Imágenes",
521
- };
522
- return translation[concept];
505
+ const handleOnShowModalGallery = () => {
506
+ dispatch({
507
+ type: "SET_ACTIVE_TAB",
508
+ payload: "Imágenes",
509
+ });
510
+ setShowModal(true);
523
511
  };
524
512
 
525
- const getComments = async (tab = "Descripción") => {
526
- const commentsResponse = await Promise.all([
527
- axios.get(
528
- `${process.env.REACT_APP_COMMENTS_ENDPOINT}?articleId=${product?.article?.id_article}&concept=description&orderIdColab=${product?.orderId}&version=${version}`
529
- ),
530
- axios.get(
531
- `${process.env.REACT_APP_COMMENTS_ENDPOINT}?articleId=${product?.article?.id_article}&concept=datasheet&orderIdColab=${product?.orderId}&version=${version}`
532
- ),
533
- axios.get(
534
- `${process.env.REACT_APP_COMMENTS_ENDPOINT}?articleId=${product?.article?.id_article}&concept=images&orderIdColab=${product?.orderId}&version=${version}`
535
- ),
536
- ]);
537
-
538
- let comments = {};
539
- commentsResponse.forEach(
540
- (comment) =>
541
- JSON.parse(comment?.data?.body).data[0] &&
542
- (comments[
543
- translateConcept(JSON.parse(comment?.data?.body)?.data[0]?.concept)
544
- ] = JSON.parse(comment?.data?.body).data[0])
545
- );
546
- setComment(comments[tab]);
547
- setComments(comments);
513
+ const handleOnChangeActiveRetailer = (retailer) => {
514
+ dispatch({
515
+ type: "SET_ACTIVE_RETAILER",
516
+ payload: retailer,
517
+ });
548
518
  };
549
519
 
550
- useEffect(async () => {
551
- setLoading(true);
552
- // loadData();
553
- getComments();
554
- setUserGroups(await fetchUsers(token));
555
- let arr = [];
556
- switch (user.id_role) {
557
- case 7:
558
- case 8:
559
- arr = ["PA", "AS", "CA", "RC", "RA", "RP", "RCA"];
560
- break;
561
- case 4:
562
- case 5:
563
- arr = ["RC", "AC", "AA", "AP", "ACA"];
564
- break;
565
- case 6:
566
- arr = ["RP", "RCA", "AC", "RA"];
567
- break;
568
- default:
569
- arr = [];
570
- break;
571
- }
572
- setStatusArray(arr);
573
- setLoading(false);
574
- }, [product, version]);
575
-
576
- const loadAssignations = (currentProduct) => {
577
- setAssig({
578
- Descripción: {
579
- assignations: [
580
- {
581
- collaboratorType: "especialist",
582
- id: currentProduct?.article?.id_description_especialist,
583
- },
584
- {
585
- collaboratorType: "facilitator",
586
- id: currentProduct?.article?.id_description_facilitator,
587
- },
588
- ],
589
- collaborators: {
590
- especialist: userGroups[0] || [],
591
- facilitator: userGroups[2] || [],
592
- },
593
- },
594
- "Ficha técnica": {
595
- assignations: [
596
- {
597
- collaboratorType: "especialist",
598
- id: currentProduct?.article?.id_datasheet_especialist,
599
- },
600
- {
601
- collaboratorType: "facilitator",
602
- id: currentProduct?.article?.id_datasheet_facilitator,
603
- },
604
- ],
605
- collaborators: {
606
- especialist: userGroups[0] || [],
607
- facilitator: userGroups[2] || [],
608
- },
609
- },
610
- Imágenes: {
611
- assignations: [
612
- {
613
- collaboratorType: "especialist",
614
- id: currentProduct?.article?.id_images_especialist,
615
- },
616
- {
617
- collaboratorType: "facilitator",
618
- id: currentProduct?.article?.id_images_facilitator,
619
- },
620
- ],
621
- collaborators: {
622
- especialist: userGroups[1] || [],
623
- facilitator: userGroups[3] || [],
624
- },
625
- },
520
+ const handleOnChangeActiveTab = (tab) => {
521
+ dispatch({
522
+ type: "SET_ACTIVE_TAB",
523
+ payload: tab,
626
524
  });
627
525
  };
628
526
 
629
- useEffect(() => {
630
- loadAssignations(product);
631
- }, [userGroups]);
632
-
633
- useEffect(() => {
634
- const retailerPercentages =
635
- percentages?.[0]?.relations?.[0]?.percentagesGeneral;
636
-
637
- const requiredPercentage = retailerPercentages?.required;
638
-
639
- setActivePercentage(requiredPercentage);
640
-
641
- // const retailers = product?.retailersAvailable || product?.retailers;
642
- // if (
643
- // Object.keys(percentages[product.article.id_article] ?? {})?.length > 0
644
- // ) {
645
- // retailers?.forEach((retailer, index) => {
646
- // retailer["percentage"] = Number(
647
- // percentages[product.article.id_article][retailer.id]
648
- // ?.percentageRequired
649
- // );
650
- // });
651
- // }
652
- // setActivePercentage(retailers[0]?.percentage);
653
- }, [percentages]);
654
-
655
- useEffect(() => {
656
- getInputsData(services, activeRetailer, setDatasheets, setDescriptions);
657
- auditableVersion && loadAuditableData();
658
- setActivePercentage(Math.round(activeRetailer?.percentage, 0));
659
- activeRetailer?.id &&
660
- setImages({ action: "orderImages", retailerId: activeRetailer.id });
661
- }, [activeRetailer, services]);
662
-
663
- const thumbs = () => {
664
- const imageInputs = socketType?.slice();
665
- const imageType = images?.imageType?.map((e) => ({
666
- value: e?.id,
667
- name: e?.name,
668
- }));
669
- const imagePackagingType = images?.imagePackagingType?.map((e) => ({
670
- value: e?.id,
671
- name: e?.name,
672
- }));
673
- const imageShotType = images?.imageShotType?.map((e) => ({
674
- value: e?.id,
675
- name: e?.type_shot,
676
- }));
677
- return images?.values?.map((image, index) => (
678
- <GalleryElement
679
- setCheckAll={setCheckAll}
680
- key={index + "-" + image.name}
681
- image={image}
682
- gridLayout={imageLayout}
683
- id={"gallery-element-" + index}
684
- index={index + "-" + image.name + "-" + compare}
685
- number={index}
686
- imageType={imageType}
687
- imagePackagingType={imagePackagingType}
688
- imageInputs={imageInputs}
689
- imageShotType={imageShotType}
690
- setSocketType={setSocketType}
691
- changeImage={setImages}
692
- selectedImages={selectedImages}
693
- setSelectedImages={setSelectedImages}
694
- auditImages={auditImages}
695
- compare={compare}
696
- />
697
- ));
527
+ const handleOnToggleImageLayout = () => {
528
+ setImageLayout(!imageLayout);
698
529
  };
699
530
 
700
- const saveDescriptions = async () => {
701
- const dataClean = updatedDescriptions.filter((f) => f.value !== "");
702
- if (dataClean.length > 0) {
703
- setLoading(true);
704
- const productTemp = product;
705
- const articleId = product?.article?.id_article;
706
- const dataObject = {
707
- articleId,
708
- articleData: dataClean,
709
- };
710
- if (product?.orderId) dataObject["orderId"] = product?.orderId;
711
- try {
712
- const res = await axios.put(
713
- `${process.env.REACT_APP_ARTICLE_DATA_ENDPOINT}?description=true&version=${version}`,
714
- dataObject,
715
- {
716
- headers: {
717
- Authorization: token,
718
- },
719
- }
720
- );
721
- if (res.data.statusCode === 200) {
722
- const { newStatus, newArticleStatus } = JSON.parse(res.data.body);
723
- if (newArticleStatus)
724
- productTemp.status = newArticleStatus[articleId];
725
- if (newStatus) productTemp.description_status = newStatus;
726
- setProduct(productTemp);
727
- sessionStorage.setItem(
728
- "productSelected",
729
- JSON.stringify(productTemp)
730
- );
531
+ const handleOnToggleObservation = () => {
532
+ setObservationVisible(!isObservationVisible);
533
+ };
731
534
 
732
- setUpdatedDescriptions([]);
733
- setMessage("Descripciones guardadas con éxito");
734
- await loadData();
735
- }
736
- } catch (error) {
737
- console.log(error);
738
- } finally {
739
- setLoading(false);
740
- }
741
- }
535
+ const handleOnHideObservation = () => {
536
+ setObservationVisible(false);
742
537
  };
743
538
 
744
- const saveDatasheets = async () => {
745
- const dataClean = updatedDatasheets.filter((f) => f.value !== "");
746
- if (dataClean.length > 0) {
747
- setLoading(true);
748
- const productTemp = product;
749
- const articleId = product?.article?.id_article;
750
- const dataObject = {
751
- articleId,
752
- articleData: dataClean,
753
- };
754
- if (product?.orderId) dataObject["orderId"] = product?.orderId;
755
- try {
756
- const res = await axios.put(
757
- `${process.env.REACT_APP_ARTICLE_DATA_ENDPOINT}?datasheet=true&version=${version}`,
758
- dataObject,
759
- {
760
- headers: {
761
- Authorization: token,
762
- },
763
- }
764
- );
765
- if (res.data.statusCode === 200) {
766
- const { newStatus, newArticleStatus } = JSON.parse(res.data.body);
767
- if (newArticleStatus)
768
- productTemp.status = newArticleStatus[articleId];
769
- if (newStatus) productTemp.datasheet_status = newStatus;
770
- setProduct(productTemp);
771
- sessionStorage.setItem(
772
- "productSelected",
773
- JSON.stringify(productTemp)
774
- );
775
- setUpdatedDatasheets([]);
776
- setMessage("Fichas técnicas guardadas");
777
- await loadData();
778
- }
779
- } catch (error) {
780
- console.log(error);
781
- } finally {
782
- setLoading(false);
783
- }
784
- }
539
+ const isAuditorAssigned = () => {
540
+ return state.product?.id_auditor === user?.id_user;
785
541
  };
786
542
 
787
- useEffect(() => {
788
- const imageInputs = images?.inputs?.map((e) => ({
789
- value: e?.id,
790
- name: e?.name,
791
- required: e?.required,
792
- active: images?.values?.some((value) => value?.image_id === e?.id),
793
- }));
794
- setSocketType(imageInputs);
795
- }, [images]);
796
-
797
- const updateImages = useCallback(async () => {
798
- const imagesList = images?.values?.slice();
799
- const imagesListTemp = imagesList?.reduce((acc, image) => {
800
- acc[image?.image_id] = ++acc[image?.image_id] || 0;
801
- return acc;
802
- }, {});
803
-
804
- const duplicated = imagesList?.filter((image) => {
805
- return imagesListTemp[image?.image_id];
806
- });
543
+ const isUserAssignedToService = (tab, rol) => {
544
+ const concept = getConceptByTab(state.active_tab);
807
545
 
808
- const attrForImgs = Object.values(images?.attrForImgs ?? {}).pop();
809
- const data = {
810
- articleId: product?.article?.id_article,
811
- attrReqImgs: attrForImgs?.map((e) => ({
812
- attrId: e.id,
813
- value: e.value ?? "-",
814
- })),
815
- articleData: imagesList?.filter((e) => !e.id),
816
- updateImages: imagesList?.filter((e) => e.id),
817
- };
818
- if (product?.orderId) data["orderId"] = product?.orderId;
819
- let valid =
820
- data?.articleData?.length === 0
821
- ? true
822
- : data?.articleData?.every((e, i) => {
823
- // if (e?.image_id && e?.packing_type && e?.image_type) {
824
- if (e?.image_id) {
825
- return true;
826
- }
827
- return false;
828
- });
829
- if (valid && data?.updateImages?.length > 0 && duplicated?.length === 0) {
830
- valid = data?.updateImages?.every((e, i) => {
831
- // if (e?.image_id && e?.packing_type && e?.image_type) {
832
- if (e?.image_id) {
833
- return true;
834
- }
835
- return false;
836
- });
837
- }
838
- if (valid && duplicated?.length === 0) {
839
- setLoading(true);
840
- try {
841
- data?.articleData?.forEach((e) => {
842
- e.uuid = uuidv4();
843
- });
844
- setDataImages(data);
845
- if (data?.articleData?.length > 0) {
846
- setImagesUploaded(false);
847
- const promiseArray = [];
848
- data?.articleData?.forEach((e) => {
849
- let file;
850
- if (e.ext == "mp4") {
851
- file = Buffer.from(
852
- e.video_src.replace(/^data:video\/\w+;base64,/, ""),
853
- "base64"
854
- );
855
- e.video_src = "";
856
- } else {
857
- file = Buffer.from(
858
- e.src.replace(/^data:image\/\w+;base64,/, ""),
859
- "base64"
860
- );
861
- }
862
- const params = {
863
- ACL: "public-read",
864
- Body: file,
865
- Bucket: S3_BUCKET,
866
- Key: `id-${data.articleId}/${version}/${e?.image_id}-${e?.uuid}.${e?.ext}`,
867
- };
868
- promiseArray.push(myBucket.putObject(params).promise());
869
- });
870
- await Promise.all(promiseArray);
871
- setImagesUploaded(true);
872
- } else {
873
- setImagesUploaded(true);
874
- }
875
- } catch (err) {
876
- console.log(err);
877
- // setMainLoading(false);
878
- } finally {
879
- setLoading(false);
880
- }
881
- } else {
882
- // setMainLoading(false);
883
- setMessage(
884
- "Completa los campos que solicita cada una de la imágenes o hay imágenes con el mismo tipo de toma.\nRecuerda hay campos obligatorios y no podras avanzar si no estan completos."
885
- );
886
- }
887
- // eslint-disable-next-line react-hooks/exhaustive-deps
888
- }, [images, imagesUploaded]);
889
-
890
- useEffect(async () => {
891
- if (imagesUploaded) {
892
- dataImages.articleData = dataImages?.articleData.map((e) => {
893
- delete e.src;
894
- e.imageID = e.image_id;
895
- // e.packingType = e.packing_type;
896
- // e.imageType = e.image_type;
897
- if (product?.orderId) e["orderId"] = product?.orderId;
898
- return e;
899
- });
900
- try {
901
- const res = await axios.put(
902
- `${process.env.REACT_APP_ARTICLE_DATA_ENDPOINT}?image=true&version=${version}`,
903
- dataImages,
904
- {
905
- headers: {
906
- Authorization: token,
907
- },
908
- }
909
- );
910
- if (res.data.statusCode === 200) {
911
- let productTemp = product;
912
- const { newStatus, newArticleStatus } = JSON.parse(res.data.body);
913
- if (newArticleStatus)
914
- productTemp.status = newArticleStatus[product?.article?.id_article];
915
- if (newStatus) productTemp.images_status = newStatus;
916
- setProduct(productTemp);
917
- sessionStorage.setItem(
918
- "productSelected",
919
- JSON.stringify(productTemp)
920
- );
921
- setImages({});
922
- setMessage("Imágenes guardadas con éxito");
923
- sessionStorage.removeItem("imagesList");
924
- }
925
- await loadData();
926
- } catch (error) {
927
- console.log(error);
546
+ const allowedRoles = [1, 4, 5, 6, 7, 8];
547
+ const validUser = allowedRoles.includes(user?.id_role);
548
+
549
+ // Determinar rol por defecto si no se pasa
550
+ if (!rol) {
551
+ if (user.id_role === 4 || user.id_role === 5) {
552
+ rol = "facilitator";
553
+ } else if (user.id_role === 7 || user.id_role === 8) {
554
+ rol = "especialist";
928
555
  }
929
556
  }
930
- }, [dataImages, imagesUploaded]);
931
557
 
932
- const evaluationFinished = (userId, tab, statusArray) => {
933
- const srv = servicesData.filter((serv) => serv.service === getConcept(tab));
934
- const srvActive = srv
935
- .find((serv) => serv.id_retailer === activeRetailer?.id)
936
- ?.status?.replace(/.*\//, "");
937
- const currStatus = product[`${getConcept(tab)}_status`]?.replace(
938
- /.*\//,
939
- ""
940
- );
941
- const unvalidated = ["IE", "CA"].includes(currStatus);
942
-
943
- const auditorUnvalidated = !["RA", "AA", "ACA", "AP"].includes(currStatus);
944
-
945
- switch (userId) {
946
- case 7:
947
- case 8:
948
- return (
949
- (statusArray.includes(srvActive) &&
950
- statusArray.includes(product?.status)) ||
951
- srv.filter((serv) =>
952
- statusArray.includes(serv.status?.replace(/.*\//, ""))
953
- ).length === srv.length
954
- );
955
- case 4:
956
- case 5:
957
- return (
958
- unvalidated &&
959
- ["CA", "IE"].includes(product.status) && // "RC", "AC", "AA", "AP", "ACA"
960
- srv.filter((serv) => statusArray.includes(serv.status)).length ===
961
- srv.length
962
- );
963
- case 6:
964
- return (
965
- statusArray.includes(product.status) && // RP, RCA, AC, RA true
966
- srv.every((serv) =>
967
- ["RA", "AA", "AP", "ACA"].includes(serv.status)
968
- ) &&
969
- auditorUnvalidated
970
- );
971
- default:
972
- break;
973
- }
974
- };
558
+ const isAdmin = user.id_role === 1;
559
+ const idUserAssigned = state.product?.[`id_${concept}_${rol}`];
560
+ const isAssigned = idUserAssigned === user.id_user;
975
561
 
976
- const getConcept = (tab) => {
977
- switch (tab) {
978
- case "Descripción":
979
- return "description";
980
- case "Ficha técnica":
981
- return "datasheet";
982
- case "Imágenes":
983
- return "images";
984
- }
562
+ return isAdmin || (isAssigned && validUser);
985
563
  };
986
564
 
987
- const approveRejectButtons = (action) => {
988
- let concept = getConcept(action || activeTab);
565
+ const canShowSingleEvaluationButtons = (action) => {
566
+ let concept = getConceptByTab(action || state.active_tab);
989
567
 
990
- const retailerStatus = servicesData
991
- .find(
568
+ const retailerStatus = state.services_data
569
+ ?.find(
992
570
  (srv) =>
993
- srv.id_retailer === activeRetailer?.id && srv.service === concept
571
+ srv.id_retailer === state.active_retailer?.id_retailer &&
572
+ srv.service === concept,
994
573
  )
995
574
  ?.status?.replace(/.*\//, "");
996
575
 
997
- //sessionStorage product
998
576
  const adminFacilitatorCanEvaluate =
999
577
  retailerStatus === "IE" && [1, 4, 5].includes(user.id_role);
1000
578
  const adminAuditorCanEvaluate =
1001
579
  ["AC", "RP", "RCA", "SAC"].includes(retailerStatus) &&
1002
580
  [1, 6].includes(user.id_role);
581
+
1003
582
  return adminFacilitatorCanEvaluate || adminAuditorCanEvaluate;
1004
583
  };
1005
584
 
1006
- const approveRejectAllButtons = () => {
1007
- let concepts = [];
1008
- switch (user.id_role) {
1009
- case 4:
1010
- concepts = ["description", "datasheet"];
1011
- break;
1012
- case 5:
1013
- concepts = ["images"];
1014
- break;
1015
-
1016
- default:
1017
- concepts = ["description", "datasheet", "images"];
1018
- break;
1019
- }
585
+ const canShowBulkEvaluationButtons = () => {
586
+ const concepts = getConceptsByRole(user.id_role);
1020
587
 
1021
- const services = servicesData.filter(({ service }) =>
1022
- concepts.includes(service)
588
+ const services = state.services_data.filter(({ service }) =>
589
+ concepts.includes(service),
1023
590
  );
1024
591
 
1025
592
  const adminFacilitatorCanEvaluate =
1026
593
  services.every((srv) => srv?.status?.replace(/.*\//, "") === "IE") &&
1027
594
  [1, 4, 5].includes(user.id_role);
1028
595
 
1029
- //sessionStorage product
1030
596
  const adminAuditorCanEvaluate =
1031
- servicesData.every((srv) =>
1032
- ["AC", "RP", "RCA"].includes(srv?.status?.replace(/.*\//, ""))
597
+ state.services_data.every((srv) =>
598
+ ["AC", "RP", "RCA"].includes(srv?.status?.replace(/.*\//, "")),
1033
599
  ) && [1, 6].includes(user.id_role);
1034
600
  return adminFacilitatorCanEvaluate || adminAuditorCanEvaluate;
1035
601
  };
1036
602
 
1037
- const getSectionIcon = () => {
1038
- switch (activeTab) {
1039
- case "Ficha técnica":
1040
- setIcon(attributesSent);
1041
- break;
603
+ const getStatusByCurrentServiceAndRetailer = () => {
604
+ const { statusByRetailer } = state.product;
605
+ if (!statusByRetailer) return "-";
606
+
607
+ const currentService = getConceptByTab(state.active_tab);
608
+ const currentRetailer = state.active_retailer.id_retailer;
609
+ const currentStatus =
610
+ statusByRetailer.filter(
611
+ (item) =>
612
+ item.retailer_id === currentRetailer &&
613
+ item.service === currentService,
614
+ )[0]?.status || "-";
615
+
616
+ return currentStatus;
617
+ };
618
+
619
+ const handleOnClickSaveImages = () => {
620
+ if (state.product?.services?.images === 1) {
621
+ saveImageAttrs(token);
622
+ }
623
+ };
624
+
625
+ const handleOnClickSave = async () => {
626
+ if (state.saving) return;
627
+
628
+ switch (state.active_tab) {
1042
629
  case "Descripción":
1043
- setIcon(descriptionSent);
630
+ await saveDescriptions(token);
631
+ break;
632
+ case "Ficha técnica":
633
+ await saveDatasheets(token);
1044
634
  break;
1045
635
  case "Imágenes":
1046
- setIcon(imagesSent);
636
+ await updateImages(token);
1047
637
  break;
1048
638
  default:
1049
639
  break;
1050
640
  }
641
+
642
+ await loadData(false);
643
+ };
644
+
645
+ const handleOnSetUpdatedDescriptions = (items) => {
646
+ dispatch({
647
+ type: "SET_UPDATED_DESCRIPTIONS_INPUTS",
648
+ payload: items,
649
+ });
650
+ };
651
+
652
+ const handleOnSetUpdatedDatasheets = (items) => {
653
+ dispatch({
654
+ type: "SET_UPDATED_DATASHEETS_INPUTS",
655
+ payload: items,
656
+ });
657
+ };
658
+
659
+ const handleOnAskToDeleteImages = () => {
660
+ if (state.selected_images.length === 0) {
661
+ dispatch({
662
+ type: "SET_MODAL",
663
+ payload: {
664
+ show: true,
665
+ title: "Eliminar imágenes",
666
+ message: "No has seleccionado ninguna imagen para eliminar.",
667
+ image: warningIcon,
668
+ },
669
+ });
670
+ return;
671
+ }
672
+
673
+ dispatch({
674
+ type: "SET_MODAL",
675
+ payload: {
676
+ show: true,
677
+ title: "Eliminar imágenes",
678
+ message:
679
+ "¿Estás seguro de que deseas eliminar las imágenes seleccionadas?",
680
+ image: warningIcon,
681
+ buttons: [
682
+ {
683
+ text: "Cancelar",
684
+ buttonType: "general-white-button",
685
+ action: () =>
686
+ dispatch({ type: "SET_MODAL", payload: { show: false } }),
687
+ },
688
+ {
689
+ text: "Eliminar",
690
+ buttonType: "general-button-default",
691
+ action: () => {
692
+ dispatch({ type: "SET_MODAL", payload: { show: false } });
693
+ deleteImages(token);
694
+ },
695
+ },
696
+ ],
697
+ },
698
+ });
699
+ };
700
+
701
+ const handleOnApproveSingleService = async () => {
702
+ await sendSingleEvaluation("A");
1051
703
  };
1052
704
 
1053
- const sendToFacilitator = async (result) => {
1054
- if (!loading) setLoading(true);
705
+ const handleOnRejectSingleService = () => {
706
+ setShowRejectModal(true);
707
+ };
708
+
709
+ const sendSingleEvaluation = async (result) => {
710
+ if (state.saving) return;
711
+
712
+ dispatch({ type: "SET_SAVING", payload: true });
713
+
714
+ const concept = getConceptByTab(state.active_tab);
715
+
716
+ const articleId = state.product.id_article;
717
+ const orderId = state.product.id_order ?? state.product.orderId;
718
+ const sectionStatusKey = `${concept}_status`;
719
+ const evalStatus = state.product[sectionStatusKey];
720
+ const retailerId = state.active_retailer?.id_retailer;
721
+
722
+ let data = { articleId, orderId, concept, evalStatus, retailerId };
723
+ let res;
724
+ let message;
725
+ let icon;
726
+
727
+ const activeTab = state.active_tab;
728
+
1055
729
  try {
1056
- let concept = getConcept(activeTab);
1057
-
1058
- const productTemp = { ...product };
1059
- const evalStatus = retailerStatus;
1060
- const articleId = product.article.id_article;
1061
- const orderId = product.orderId;
1062
-
1063
- let data = {
1064
- articleId,
1065
- orderId,
1066
- concept,
1067
- evalStatus,
1068
- retailerId: activeRetailer.id,
1069
- };
1070
- let res;
1071
- let message;
730
+ //Esto solo se ejecutará cuando se aprueba o rechaza desde el modal superior derecho
1072
731
  if (result) {
1073
732
  data.result = result;
733
+ data.retailerId = retailerId;
734
+
1074
735
  res = await axios.put(
1075
736
  `${process.env.REACT_APP_EVALUATION_ENDPOINT}`,
1076
737
  data,
@@ -1078,254 +739,297 @@ export const RetailerProductEdition = ({
1078
739
  headers: {
1079
740
  Authorization: token,
1080
741
  },
1081
- }
742
+ },
1082
743
  );
1083
- getServices();
1084
- } else {
744
+
745
+ const newStatuses = JSON.parse(res.data.body);
746
+ const serviceStatus = newStatuses.newServiceStatus[articleId][`${concept}Status`];
747
+ const articleStatus = newStatuses.newArticleStatus[articleId];
748
+
749
+ console.log({newStatuses});
750
+
751
+ // Actualizar el producto con los nuevos estados
752
+ const updatedStatusByRetailer = state.product.statusByRetailer.map(
753
+ (item) =>
754
+ item.retailer_id === retailerId && item.service === concept
755
+ ? { ...item, status: serviceStatus }
756
+ : item,
757
+ );
758
+
759
+ const updatedProduct = {
760
+ ...state.product,
761
+ statusByRetailer: updatedStatusByRetailer,
762
+ // version_status: articleStatus,
763
+ // status: articleStatus,
764
+ // article_status: articleStatus
765
+ };
766
+
767
+ dispatch({ type: "SET_PRODUCT", payload: updatedProduct });
768
+
769
+ // Mostrar modal de éxito
770
+ dispatch({
771
+ type: "SET_MODAL",
772
+ payload: {
773
+ show: true,
774
+ title: "",
775
+ message:
776
+ result === "A" ? "Aprobado con éxito" : "Rechazado con éxito",
777
+ image: successIcon,
778
+ },
779
+ });
780
+ } else { //Caso del botón "Enviar evaluación"
781
+
782
+ //Se construye el mensaje y se actualiza el estatus
783
+
1085
784
  const specialistDone = ["RC", "RA", "CA"].includes(evalStatus);
1086
785
 
1087
786
  if (specialistDone) {
1088
787
  message = `${activeTab} enviada a facilitador`;
1089
- getSectionIcon();
788
+ icon =
789
+ activeTab === "Descripción"
790
+ ? descriptionSent
791
+ : activeTab === "Ficha técnica"
792
+ ? attributesSent
793
+ : imagesSent;
1090
794
  } else if (["IE", "AC", "RP", "RCA"].includes(evalStatus)) {
1091
795
  message = "Evaluación enviada";
1092
- getSectionIcon();
796
+ icon = successIcon;
1093
797
  }
798
+
1094
799
  res = await axios.put(`${process.env.REACT_APP_SEND_EVAL}`, data, {
1095
800
  headers: {
1096
801
  Authorization: token,
1097
802
  },
1098
803
  });
1099
804
  }
805
+
1100
806
  if (res.data.statusCode === 200) {
1101
- const { newStatus, newOrderStatus, newArticleStatus } = JSON.parse(
1102
- res.data.body
1103
- );
1104
- const messageToChat = createMessage(
1105
- product.retailers,
1106
- activeRetailer.id,
1107
- evalStatus,
1108
- newStatus,
1109
- activeTab
1110
- );
1111
807
 
1112
- const data = {
1113
- paramsBody: {
1114
- id: product.article.id_article,
1115
- version: version,
1116
- items: [{ type: "status", value: messageToChat }],
1117
- retailerId: activeRetailer.id,
1118
- status: product.status,
1119
- },
1120
- paramsHeader: { Authorization: token },
808
+ const { newStatus, newOrderStatus, newArticleStatus } = JSON.parse( res.data.body );
809
+
810
+ const articleStatus = newArticleStatus[articleId];
811
+
812
+ console.log({newArticleStatus});
813
+
814
+ // const retailers = state.product.categoryRetailer.map((r) => ({
815
+ // id: r.id_retailer,
816
+ // name: r.retailerName,
817
+ // }));
818
+ // const messageToChat = createMessage(
819
+ // retailers,
820
+ // retailerId,
821
+ // evalStatus,
822
+ // newStatus,
823
+ // activeTab,
824
+ // );
825
+
826
+ // const messageData = {
827
+ // paramsBody: {
828
+ // id: articleId,
829
+ // version: state.product.version,
830
+ // items: [{ type: "status", value: messageToChat }],
831
+ // retailerId: retailerId,
832
+ // status: state.product.status,
833
+ // },
834
+ // paramsHeader: { Authorization: token },
835
+ // };
836
+ // await sendMessage(messageData);
837
+
838
+ const updatedProduct = {
839
+ ...state.product,
840
+ [`${concept}_status`]: newStatus,
841
+ // actualizar el status es statusByRetailer
842
+ statusByRetailer: state.product.statusByRetailer.map((item) => {
843
+ if (item.retailer_id === retailerId && item.service === concept) {
844
+ return { ...item, status: newStatus };
845
+ }
846
+ return item;
847
+ }),
848
+ // version_status: articleStatus,
849
+ // status: articleStatus,
850
+ // article_status: articleStatus
1121
851
  };
1122
- await sendMessage(data);
1123
- if (newOrderStatus) productTemp.status = newArticleStatus[articleId];
1124
- productTemp[`${concept}_status`] = newStatus;
1125
- await loadData();
1126
- if (message) setMessage(message);
1127
- sessionStorage.setItem("productSelected", JSON.stringify(productTemp));
1128
- setProduct(productTemp);
1129
- }
1130
- } catch (error) {
1131
- setLoading(false);
1132
- console.log(error);
1133
- }
1134
- };
1135
852
 
1136
- const userAssigned = (tab, rol) => {
1137
- let concept = getConcept(activeTab);
853
+ //Actualizamos el services_data en el servicio específico del retailer actual
1138
854
 
1139
- const allowedRoles = [1, 4, 5, 6, 7, 8];
1140
- const validUser = allowedRoles.includes(user?.id_role);
855
+ const updatedServicesData = state.services_data?.map(service => ({
856
+ ...service,
857
+ status: service?.id_retailer === retailerId && service?.service === concept ? newStatus : service?.status
858
+ }));
1141
859
 
1142
- if (!rol) {
1143
- switch (user.id_role) {
1144
- case 4:
1145
- case 5:
1146
- rol = "facilitator";
1147
- break;
1148
- case 7:
1149
- case 8:
1150
- rol = "especialist";
1151
- break;
860
+ const sessionProduct = sessionStorage.getItem("productSelected");
861
+
862
+ if (sessionProduct) {
863
+ const productInSession = JSON.parse(sessionProduct);
864
+ const updatedProductInSession = {
865
+ ...productInSession,
866
+ [`${concept}_status`]: newStatus,
867
+ statusByRetailer: state.product.statusByRetailer.map((item) => {
868
+ if (item.retailer_id === retailerId && item.service === concept) {
869
+ return { ...item, status: newStatus };
870
+ }
871
+ return item;
872
+ }),
873
+ };
874
+ sessionStorage.setItem(
875
+ "productSelected",
876
+ JSON.stringify(updatedProductInSession),
877
+ );
878
+ }
879
+
880
+ if (message) {
881
+ dispatch({
882
+ type: "SET_MODAL",
883
+ payload: {
884
+ show: true,
885
+ title: "",
886
+ message: message,
887
+ image: successIcon,
888
+ },
889
+ });
890
+ }
891
+
892
+ dispatch({ type: "SET_PRODUCT", payload: updatedProduct });
893
+ dispatch({ type: "SET_SERVICES_DATA", payload: updatedServicesData });
1152
894
  }
895
+ } catch (error) {
896
+ console.error("Error sending evaluation:", error);
897
+ dispatch({
898
+ type: "SET_MODAL",
899
+ payload: {
900
+ show: true,
901
+ title: "Error",
902
+ message: "Hubo un error al procesar la evaluación",
903
+ image: errorIcon,
904
+ },
905
+ });
906
+ } finally {
907
+ dispatch({ type: "SET_SAVING", payload: false });
1153
908
  }
1154
-
1155
- return (
1156
- user.id_role === 1 ||
1157
- (product.article[`id_${concept}_${rol}`] === user.id_user && validUser)
1158
- );
1159
909
  };
1160
910
 
1161
- const auditorAssigned = () => {
1162
- return product?.article[`id_auditor`] === user.id_user;
911
+ const handleOnApproveAllServices = async () => {
912
+ await sendBulkEvaluation("A");
1163
913
  };
1164
914
 
1165
- const createComment = async (e, body, tab) => {
1166
- let concept = "";
1167
- switch (activeTab) {
1168
- case "Ficha técnica":
1169
- concept = "datasheet";
1170
- break;
1171
- case "Imágenes":
1172
- concept = "images";
1173
- break;
1174
-
1175
- default:
1176
- concept = "description";
1177
- break;
1178
- }
1179
- const data = {
1180
- articleId: product?.article?.id_article,
1181
- orderId: product?.orderId,
1182
- message: body?.replace(/<.*?\/?>/gm, ""),
1183
- concept: concept,
1184
- version: version,
1185
- };
1186
- await axios.post(`${process.env.REACT_APP_COMMENTS_ENDPOINT}`, data, {
1187
- headers: {
1188
- Authorization: token,
1189
- },
1190
- });
1191
- await getComments(tab);
1192
- setMessage("");
1193
- setComponentsArray([]);
915
+ const handleOnRejectAllServices = () => {
916
+ setRejectAll(true);
917
+ setShowRejectModal(true);
1194
918
  };
1195
919
 
1196
- const getRequired = (services) => {
920
+ const sendBulkEvaluation = async (result) => {
921
+ dispatch({ type: "SET_SAVING", payload: true });
1197
922
  try {
1198
- const objetcTemp = {};
1199
- const datasheetServicesArray = Object.values(services[0]);
1200
- const allDatasheetInputs = datasheetServicesArray.pop();
1201
- const descriptionsServicesArray = services[1];
1202
-
1203
- let dsInputsRequired = [];
1204
-
1205
- let desInputsRequired = 0;
1206
- datasheetServicesArray?.forEach((datasheet) => {
1207
- const [requested] = servicesData?.filter(
1208
- (srv) =>
1209
- srv.id_retailer === datasheet.retailer.id &&
1210
- srv.service === getConcept(activeTab)
1211
- );
923
+ const evaluationArray = [];
1212
924
 
1213
- requested &&
1214
- datasheet?.data &&
1215
- Object.values(datasheet?.data).forEach((dataGroup) => {
1216
- dsInputsRequired.push(
1217
- ...dataGroup.inputs.filter((input) => {
1218
- return (
1219
- allDatasheetInputs[input].required &&
1220
- allDatasheetInputs[input].id_retailer === activeRetailer.id &&
1221
- (allDatasheetInputs[input].value === undefined ||
1222
- !allDatasheetInputs[input].value)
1223
- );
1224
- })
1225
- );
1226
- });
1227
- });
925
+ const conceptArray = getConceptsByRole(user.id_role);
1228
926
 
1229
- objetcTemp["Ficha técnica"] = dsInputsRequired.length;
927
+ state.services_data?.forEach((ret, idx) => {
928
+ const { service: retailer_service, id_retailer, status: status_retailer_service } = ret;
1230
929
 
1231
- const regex = /(<\/?p>)|(<\/?strong>)|(<br>)/gm;
1232
- descriptionsServicesArray.forEach((description) => {
1233
- if (description.id != activeRetailer.id) return;
930
+ const lastStatusLevel = status_retailer_service.replace(/.*\//, "");
1234
931
 
1235
- const [requested] = servicesData.filter(
1236
- (srv) =>
1237
- srv.id_retailer === description.id &&
1238
- srv.service === getConcept(activeTab)
1239
- );
932
+ if (conceptArray.includes(retailer_service) && statusArray?.includes(lastStatusLevel)) {
933
+ const data = {
934
+ articleId: state.product.id_article,
935
+ orderId: state.product.id_order,
936
+ concept: retailer_service,
937
+ result,
938
+ evalStatus: status_retailer_service,
939
+ retailerId: id_retailer,
940
+ };
1240
941
 
1241
- if (requested) {
1242
- description.inputs.forEach((input) => {
1243
- if (
1244
- input.required &&
1245
- (!input.value || input.value?.replace(regex, "") === "")
1246
- ) {
1247
- desInputsRequired++;
1248
- }
1249
- });
942
+ evaluationArray.push(
943
+ axios.put(`${process.env.REACT_APP_EVALUATION_ENDPOINT}`, data, {
944
+ headers: {
945
+ Authorization: token,
946
+ },
947
+ }),
948
+ );
1250
949
  }
1251
950
  });
1252
951
 
1253
- objetcTemp["Descripción"] = desInputsRequired;
952
+ await Promise.all(evaluationArray);
1254
953
 
1255
- const retailersRequested = [];
954
+ const status = `${result}A`;
1256
955
 
1257
- services[2]?.retailerMandatories?.forEach((retMan) =>
1258
- retMan.forEach((rm) => retailersRequested.push(rm))
956
+ // Actualizar el statusByRetailer para todos los retailers y conceptos
957
+ const updatedStatusByRetailer = state.product.statusByRetailer.map(
958
+ (item) =>
959
+ conceptArray.includes(item.service) ? { ...item, status } : item,
1259
960
  );
1260
961
 
1261
- const requiredImages = services[2]?.inputs?.filter(
1262
- (e) =>
1263
- e.required === 1 &&
1264
- retailersRequested.filter(
1265
- (ret) =>
1266
- ret.id_image === e.id &&
1267
- servicesData.filter((srv) => srv.id_retailer === ret.id_retailer)
1268
- .length > 0
1269
- ).length > 0
1270
- );
962
+ // Actualizar el producto con los nuevos estados
963
+ const updatedProduct = {
964
+ ...state.product,
965
+ article_status: status,
966
+ status: status,
967
+ datasheet_status:
968
+ state.product.datasheet_status === "NA" ? "NA" : status,
969
+ description_status:
970
+ state.product.description_status === "NA" ? "NA" : status,
971
+ images_status: state.product.images_status === "NA" ? "NA" : status,
972
+ statusByRetailer: updatedStatusByRetailer,
973
+ };
1271
974
 
1272
- let requiredCounter = 0;
1273
- requiredImages?.forEach((req) => {
1274
- return (
1275
- services[2].values.filter((img) => img.image_id === req.id).length ===
1276
- 0 && requiredCounter++
1277
- );
975
+ dispatch({
976
+ type: "SET_PRODUCT",
977
+ payload: updatedProduct,
1278
978
  });
1279
- objetcTemp["Imágenes"] = requiredCounter;
1280
- setRequiredNull(objetcTemp);
1281
- } catch (error) {
1282
- console.log(error);
1283
- }
1284
- };
1285
979
 
1286
- useEffect(() => {
1287
- setComment(comments[activeTab]);
1288
- }, [activeTab]);
1289
-
1290
- const commentRevised = async () => {
1291
- const data = {
1292
- commentId: comment.id,
1293
- };
1294
- await axios.put(`${process.env.REACT_APP_COMMENTS_ENDPOINT}`, data, {
1295
- headers: {
1296
- Authorization: sessionStorage.getItem("jwt"),
1297
- },
1298
- });
1299
- setCrossComment(false);
1300
- await getComments();
1301
- };
980
+ const updatedServicesData = state.services_data.map(service => conceptArray.includes(service.service) ? { ...service, status: status } : service);
1302
981
 
1303
- const setAssignation = async (assignationType, assignationId) => {
1304
- let concept = "";
1305
- switch (activeTab) {
1306
- case "Ficha técnica":
1307
- concept = "datasheet";
1308
- break;
1309
- case "Imágenes":
1310
- concept = "images";
1311
- break;
982
+ dispatch({
983
+ type: "SET_SERVICES_DATA",
984
+ payload: updatedServicesData,
985
+ });
1312
986
 
1313
- default:
1314
- concept = "description";
1315
- break;
987
+ // sessionStorage.setItem(
988
+ // "productSelected",
989
+ // JSON.stringify(updatedProduct),
990
+ // );
991
+ // sessionStorage.setItem(
992
+ // "productEdit",
993
+ // JSON.stringify({
994
+ // ArticleId: updatedProduct.id_article,
995
+ // idCategory: updatedProduct.id_category,
996
+ // product: updatedProduct,
997
+ // }),
998
+ // );
999
+
1000
+ // Mostrar modal de éxito
1001
+ dispatch({
1002
+ type: "SET_MODAL",
1003
+ payload: {
1004
+ show: true,
1005
+ title: "",
1006
+ message:
1007
+ result === "A"
1008
+ ? "Todos los servicios aprobados con éxito"
1009
+ : "Todos los servicios rechazados con éxito",
1010
+ image: successIcon,
1011
+ },
1012
+ });
1013
+ } catch (error) {
1014
+ console.error("Error in bulk evaluation:", error);
1015
+ } finally {
1016
+ dispatch({ type: "SET_SAVING", payload: false });
1316
1017
  }
1317
- const productTemp = product;
1318
- productTemp.article[`id_${concept}_${assignationType}`] = assignationId;
1018
+ };
1019
+
1020
+ const handleOnChangeAssignations = async (assignationType, assignationId) => {
1021
+ const concept = getConceptByTab(state.active_tab);
1319
1022
  const data = {
1320
1023
  articleList: [
1321
1024
  {
1322
- orderId: product.orderId,
1323
- articleId: product?.article?.id_article,
1025
+ orderId: state.product.id_order,
1026
+ articleId: state.product.id_article,
1324
1027
  },
1325
1028
  ],
1326
1029
  concept: concept,
1327
1030
  userId: assignationId,
1328
1031
  };
1032
+
1329
1033
  await axios({
1330
1034
  method: "post",
1331
1035
  url: process.env.REACT_APP_ASSIGNATIONS_ENDPOINT,
@@ -1334,646 +1038,803 @@ export const RetailerProductEdition = ({
1334
1038
  Authorization: token,
1335
1039
  },
1336
1040
  });
1337
- loadAssignations(productTemp);
1338
- sessionStorage.setItem("productSelected", JSON.stringify(productTemp));
1339
- };
1340
1041
 
1341
- const downloadImages = () => {
1342
- selectedImages.length > 0
1343
- ? selectedImages.forEach((e) => {
1344
- if (e.id) {
1345
- saveAs(
1346
- `https://${process.env.REACT_APP_IMAGES_BUCKET}.s3.amazonaws.com/${e.srcDB}`,
1347
- `${product.article.upc}_${e.name}.${e.ext}`
1348
- );
1349
- }
1350
- })
1351
- : images?.values?.forEach((e) => {
1352
- if (e.id) {
1353
- saveAs(
1354
- `https://${process.env.REACT_APP_IMAGES_BUCKET}.s3.amazonaws.com/${e.srcDB}`,
1355
- `${product.article.upc}_${e.name}.${e.ext}`
1356
- );
1357
- }
1358
- });
1042
+ const updatedProduct = {
1043
+ ...state.product,
1044
+ [`id_${concept}_${assignationType}`]: assignationId,
1045
+ };
1046
+
1047
+ dispatch({ type: "SET_PRODUCT", payload: updatedProduct });
1359
1048
  };
1360
1049
 
1361
- const deleteImages = async () => {
1362
- setLoading(true);
1363
- const { values } = images;
1364
- const imgsInBack = [];
1050
+ const handleOnSetAssignation = () => {
1051
+ console.log("handleOnSetAssignation - pendiente de implementar");
1052
+ };
1365
1053
 
1366
- selectedImages.forEach((selectedImg) => {
1367
- if (selectedImg.id) imgsInBack.push(selectedImg);
1368
- });
1054
+ const handleOnSetImages = () => {
1055
+ console.log("handleOnSetImages - pendiente de implementar");
1056
+ };
1369
1057
 
1370
- const imgsLeft = values.filter(
1371
- (value) => selectedImages.indexOf(value) === -1
1372
- );
1058
+ const handleOnSetCheckAll = (isChecked) => {
1059
+ dispatch({ type: "TOGGLE_CHECK_ALL_IMAGES", payload: isChecked });
1060
+ };
1373
1061
 
1374
- if (imgsInBack.length > 0) {
1375
- const data = {
1376
- articleId: product.article.id_article,
1377
- deleteImages: imgsInBack,
1378
- orderId: product.orderId,
1379
- };
1380
- try {
1381
- await axios.put(
1382
- `${process.env.REACT_APP_ARTICLE_DATA_ENDPOINT}?image=true&version=${version}`,
1383
- data,
1384
- {
1385
- headers: { Authorization: token },
1386
- }
1387
- );
1388
- } catch (err) {
1389
- console.log(err);
1390
- }
1062
+ const handleOnSetSelectedImages = () => {
1063
+ /*
1064
+ Esta función no se utiliza actualmente.
1065
+
1066
+ En la versión anterior se usaban dos funciones distintas para el checkbox
1067
+ "Seleccionar todo":
1068
+ - Una para cambiar el valor booleano del checkbox.
1069
+ - Otra para actualizar el array de imágenes seleccionadas.
1070
+
1071
+ En la versión actual se unificó la lógica en una sola función,
1072
+ `handleOnSetCheckAll`, que recibe el valor `true` o `false` del checkbox
1073
+ "Seleccionar todo" y, a partir de este, agrega o elimina todas las imágenes
1074
+ del array de imágenes seleccionadas.
1075
+ */
1076
+ console.log("setSelectedImages");
1077
+ };
1078
+ const handleOnSubmitComment = async (e) => {
1079
+ e.preventDefault();
1080
+ const commentText = document.querySelector(
1081
+ "#commentary-box .ql-container .ql-editor > p",
1082
+ ).innerHTML;
1083
+ try {
1084
+ await createComment(commentText, token);
1085
+
1086
+ dispatch({
1087
+ type: "SET_MODAL",
1088
+ payload: {
1089
+ show: true,
1090
+ title: "",
1091
+ message: "Comentario guardado con éxito",
1092
+ image: successIcon,
1093
+ },
1094
+ });
1095
+ } catch (error) {
1096
+ console.error("Error saving comment:", error);
1097
+ dispatch({
1098
+ type: "SET_MODAL",
1099
+ payload: {
1100
+ show: true,
1101
+ title: "Error",
1102
+ message: "Hubo un error al guardar el comentario",
1103
+ image: errorIcon,
1104
+ },
1105
+ });
1391
1106
  }
1107
+ };
1392
1108
 
1393
- setImages({
1394
- action: "deleteImage",
1395
- selectedImages,
1396
- });
1397
-
1398
- getRequired([
1399
- services[0],
1400
- services[1],
1401
- { ...services[2], values: imgsLeft },
1402
- ]);
1109
+ const handleOnSendEvaluationToFacilitator = () => {
1110
+ sendSingleEvaluation();
1111
+ };
1403
1112
 
1404
- setLoading(false);
1113
+ const isEvaluationFinished = (id_rol, tab, statusArray) => {
1114
+ const servicesData = state.services_data;
1115
+ const activeRetailer = state.active_retailer;
1116
+ const product = state.product;
1117
+ const concept = getConceptByTab(tab);
1405
1118
 
1406
- setMessage("");
1407
- setComponentsArray([]);
1408
- };
1119
+ const servicesByTab = servicesData?.filter(
1120
+ (serv) => serv.service === concept,
1121
+ );
1409
1122
 
1410
- const askToDeleteImages = () => {
1411
- if (selectedImages.length > 0) {
1412
- setMessage("¿Está seguro de eliminar las imágenes seleccionadas?");
1413
- setComponentsArray([
1414
- <ScreenHeader
1415
- key={"1"}
1416
- text={"¿Está seguro de eliminar las imágenes seleccionadas?"}
1417
- headerType="retailer-name-header"
1418
- color={"white"}
1419
- />,
1420
- <Button
1421
- key={"2"}
1422
- buttonType="general-white-button"
1423
- label={"Cancelar"}
1424
- onClick={() => setMessage("")}
1425
- />,
1426
- <Button
1427
- key={"3"}
1428
- buttonType="general-button-default"
1429
- label={"Aceptar"}
1430
- onClick={() => {
1431
- setMessage("");
1432
- deleteImages();
1433
- }}
1434
- />,
1435
- ]);
1436
- }
1437
- };
1123
+ const statusBySelectedRetailerService = servicesByTab
1124
+ ?.find((serv) => serv.id_retailer === activeRetailer?.id_retailer)
1125
+ ?.status?.replace(/.*\//, "");
1438
1126
 
1439
- const getRetailerStatus = (servicesData, tab) => {
1440
- const arr = servicesData?.slice();
1441
- let concept = getConcept(tab);
1127
+ const currentProducStatus = product?.[`${concept}_status`]?.replace(
1128
+ /.*\//,
1129
+ "",
1130
+ );
1442
1131
 
1443
- let retailerService = {};
1444
- [retailerService] = arr?.filter(
1445
- (service) =>
1446
- service.id_retailer === activeRetailer?.id &&
1447
- service.service === concept
1132
+ const unvalidated = ["IE", "CA"].includes(currentProducStatus);
1133
+ const auditorUnvalidated = !["RA", "AA", "ACA", "AP"].includes(
1134
+ currentProducStatus,
1448
1135
  );
1449
- return retailerService ? retailerService.status : "NS";
1450
- };
1451
1136
 
1452
- useEffect(() => {
1453
- let status = getRetailerStatus(servicesData, activeTab);
1454
- setRetailerStatus(status);
1455
- }, [activeTab, servicesData, activeRetailer]);
1137
+ let result;
1138
+ switch (id_rol) {
1139
+ case 7:
1140
+ case 8:
1456
1141
 
1457
- useEffect(() => {
1458
- services.length > 0 && getRequired(services);
1459
- }, [services, servicesData, activeTab]);
1142
+ const canSendEvaluation = statusArray.includes(statusBySelectedRetailerService);
1460
1143
 
1461
- useEffect(() => {
1462
- setSaving(loading);
1463
- }, [loading]);
1144
+ // const conditionTwo = statusArray.includes(product?.status);
1464
1145
 
1465
- const validateAll = async (result) => {
1466
- try {
1467
- setLoading(true);
1468
- const evaluationArray = [];
1469
- let conceptArray = [];
1470
- switch (user.id_role) {
1471
- case 4:
1472
- conceptArray = ["description", "datasheet"];
1473
- break;
1474
- case 5:
1475
- conceptArray = ["images"];
1476
- break;
1477
-
1478
- default:
1479
- conceptArray = ["description", "datasheet", "images"];
1480
- break;
1481
- }
1146
+ // const conditionThree =
1147
+ // servicesByTab.filter((service) =>
1148
+ // statusArray.includes(service.status?.replace(/.*\//, "")),
1149
+ // ).length === servicesByTab.length;
1482
1150
 
1483
- const messages = [];
1484
-
1485
- servicesData?.forEach((ret) => {
1486
- if (conceptArray.includes(ret.service)) {
1487
- let data = {
1488
- articleId: product.article.id_article,
1489
- orderId: product.id_order ?? product.orderId,
1490
- concept: ret.service,
1491
- result: result,
1492
- evalStatus: ret.status,
1493
- retailerId: ret.id_retailer,
1494
- };
1495
- evaluationArray.push(
1496
- axios.put(`${process.env.REACT_APP_EVALUATION_ENDPOINT}`, data, {
1497
- headers: {
1498
- Authorization: token,
1499
- },
1500
- })
1501
- );
1502
- }
1503
- });
1504
- await Promise.all(evaluationArray);
1151
+ // return (canSendEvaluation && conditionTwo) || conditionThree;
1505
1152
 
1506
- const productTemp = product;
1507
- productTemp.status = `${result}A`;
1508
- productTemp.datasheet_status =
1509
- productTemp.datasheet_status === "NS" ? "NS" : `${result}A`;
1510
- productTemp.description_status =
1511
- productTemp.description_status === "NS" ? "NS" : `${result}A`;
1512
- productTemp.images_status =
1513
- productTemp.images_status === "NS" ? "NS" : `${result}A`;
1514
- sessionStorage.setItem("productSelected", JSON.stringify(productTemp));
1515
- setProduct(productTemp);
1516
- const messagesResponse = await Promise.all(messages);
1517
-
1518
- await loadData();
1519
- } catch (error) {
1520
- console.log(error);
1521
- } finally {
1522
- setLoading(false);
1153
+ return canSendEvaluation;
1154
+
1155
+ case 4:
1156
+ case 5:
1157
+ const filteredSrv45 = servicesByTab?.filter((serv) =>
1158
+ statusArray.includes(serv?.status),
1159
+ );
1160
+ const condition45 =
1161
+ unvalidated &&
1162
+ ["CA", "IE"].includes(product?.status) &&
1163
+ filteredSrv45?.length === servicesByTab?.length;
1164
+ return condition45;
1165
+ case 6:
1166
+ const statusInArray = statusArray.includes(product?.status);
1167
+ const allSrvValid = srv?.every((serv) =>
1168
+ ["RA", "AA", "AP", "ACA"].includes(serv?.status),
1169
+ );
1170
+ const condition6 = statusInArray && allSrvValid && auditorUnvalidated;
1171
+ return condition6;
1172
+ default:
1173
+ console.log("Default case - returning false");
1174
+ return false;
1523
1175
  }
1524
1176
  };
1525
1177
 
1526
- const getObservation = async () => {
1527
- const response = await axios.get(
1528
- `${process.env.REACT_APP_READ_OBSERVATION}?articleId=${product.article.id_article}&orderId=${product?.orderId}`,
1529
- {
1530
- headers: {
1531
- Authorization: sessionStorage.getItem("jwt"),
1532
- },
1533
- }
1178
+ const handleOnChangeProductVersion = (version) => {
1179
+ console.log('se cambia la version')
1180
+ dispatch({
1181
+ type: "SET_PRODUCT_VERSION",
1182
+ payload: version,
1183
+ });
1184
+
1185
+ loadData(true, version);
1186
+ };
1187
+
1188
+ const ProductEditionSkeleton = () => {
1189
+ return (
1190
+ <Container headerTop={0}>
1191
+ <Box
1192
+ px={1.5}
1193
+ py={1}
1194
+ pb={0}
1195
+ display="flex"
1196
+ alignItems="center"
1197
+ justifyContent="space-between"
1198
+ >
1199
+ <Skeleton variant="text" width={300} height={40} />
1200
+ <Stack direction="row" spacing={2}>
1201
+ <Skeleton variant="circular" width={32} height={32} />
1202
+ </Stack>
1203
+ </Box>
1204
+
1205
+ <div
1206
+ className="data-container"
1207
+ style={{ display: "flex", gap: "8px", padding: "0 12px 12px" }}
1208
+ >
1209
+ <div
1210
+ className="image-data-panel"
1211
+ style={{
1212
+ flex: "0 0 320px",
1213
+ display: "flex",
1214
+ flexDirection: "column",
1215
+ gap: "20px",
1216
+ }}
1217
+ >
1218
+ <Skeleton variant="rounded" width="100%" height={400} />
1219
+
1220
+ <Stack direction="row" spacing={2} justifyContent="center">
1221
+ {[1, 2, 3, 4].map((item) => (
1222
+ <Skeleton key={item} variant="rounded" width={60} height={80} />
1223
+ ))}
1224
+ </Stack>
1225
+
1226
+ <Box mt={2}>
1227
+ {[1, 2, 3, 4].map((item) => (
1228
+ <Box
1229
+ key={item}
1230
+ display="flex"
1231
+ justifyContent="space-between"
1232
+ py={1}
1233
+ borderBottom="1px solid #f0f0f0"
1234
+ >
1235
+ <Skeleton variant="text" width="40%" />
1236
+ <Skeleton variant="text" width="20%" />
1237
+ </Box>
1238
+ ))}
1239
+ </Box>
1240
+ </div>
1241
+
1242
+ <div
1243
+ className="product-information"
1244
+ style={{
1245
+ flex: 1,
1246
+ display: "flex",
1247
+ flexDirection: "column",
1248
+ gap: "14px",
1249
+ }}
1250
+ >
1251
+ <Box>
1252
+ <Skeleton variant="text" width="80%" height={50} />
1253
+ <Stack direction="row" spacing={2} alignItems="center">
1254
+ <Skeleton variant="rounded" width={180} height={24} />
1255
+ <Skeleton variant="rounded" width={300} height={24} />
1256
+ </Stack>
1257
+ </Box>
1258
+
1259
+ <Box display="flex" gap={4} pb={1}>
1260
+ <Skeleton variant="text" width={140} height={45} />
1261
+ <Skeleton variant="text" width={140} height={45} />
1262
+ <Skeleton variant="text" width={140} height={45} />
1263
+ </Box>
1264
+
1265
+ <Box display="flex" flexDirection="column" gap={4}>
1266
+ {[1, 2, 3].map((item) => (
1267
+ <Box key={item}>
1268
+ <Box
1269
+ display="flex"
1270
+ justifyContent="space-between"
1271
+ alignItems="center"
1272
+ mb={1}
1273
+ >
1274
+ <Skeleton variant="text" width="30%" height={24} />
1275
+ <Skeleton
1276
+ variant="rounded"
1277
+ width={175}
1278
+ height={25}
1279
+ sx={{ borderRadius: "7.5px" }}
1280
+ />
1281
+ </Box>
1282
+ <Skeleton variant="rounded" width="100%" height={70} />
1283
+ <Box display="flex" justifyContent="flex-end" mt={0.5}>
1284
+ <Skeleton variant="text" width={60} />
1285
+ </Box>
1286
+ </Box>
1287
+ ))}
1288
+ </Box>
1289
+
1290
+ <Box mt="auto" pt={4}>
1291
+ <Skeleton variant="rounded" width="100%" height={120} />
1292
+ <Box display="flex" justifyContent="flex-end" gap={2} mt={2}>
1293
+ <Skeleton
1294
+ variant="rounded"
1295
+ width={180}
1296
+ height={45}
1297
+ sx={{ borderRadius: "24px" }}
1298
+ />
1299
+ <Skeleton
1300
+ variant="rounded"
1301
+ width={180}
1302
+ height={45}
1303
+ sx={{ borderRadius: "24px" }}
1304
+ />
1305
+ </Box>
1306
+ </Box>
1307
+ </div>
1308
+ </div>
1309
+ </Container>
1534
1310
  );
1535
- const parseData = JSON.parse(response.data.body).data[0];
1536
- setObservation(parseData.observations);
1537
1311
  };
1312
+
1538
1313
  useEffect(() => {
1539
- getObservation();
1540
- }, []);
1314
+ console.log({stateProduct: state.product})
1315
+ }, [state.product]);
1316
+
1317
+ if (state.loading || !state.services || !state.product)
1318
+ return (
1319
+ <>
1320
+ <ProductEditionSkeleton />
1321
+ </>
1322
+ );
1323
+
1541
1324
  return (
1542
- <Container headerTop={headerTop}>
1543
- <HeaderTop
1544
- setHeaderTop={setHeaderTop}
1545
- auditableVersion={auditableVersion}
1546
- setCompare={setCompare}
1547
- isAuditor={[1, 6].includes(user.id_role)}
1548
- withChat={location?.state?.withChat}
1549
- chatType={location?.state?.chatType}
1550
- productSelected={product}
1551
- token={token}
1552
- activeRetailer={activeRetailer}
1553
- />
1554
- <div className="data-container">
1555
- <div className="image-data-panel">
1556
- <ImagePreviewer
1557
- activeImage={images?.values ? images?.values[activeImage] : {}}
1558
- imagesArray={images}
1559
- setActiveImage={setActiveImage}
1560
- setShowModal={setShowModal}
1561
- />
1562
- <ImageDataTable
1563
- lists={images}
1564
- activeImage={images?.values ? images?.values[activeImage] : {}}
1565
- retailerSelected={activeRetailer?.id}
1566
- setImages={setImages}
1567
- assignationsImages={assig["Imágenes"]}
1568
- imagesStatus={product?.images_status}
1569
- setAssignation={setAssignation}
1570
- isRetailer={isRetailer}
1571
- onClickSave={() =>
1572
- product?.services?.images === 1 && updateImages()
1325
+ <AiProductEditionProvider
1326
+ isCreatorsEdition={true}
1327
+ user={user}
1328
+ token={token}
1329
+ state={state}
1330
+ >
1331
+ <Container headerTop={headerTop}>
1332
+ {showRejectModal && (
1333
+ <Modal
1334
+ title={
1335
+ rejectAll
1336
+ ? "Agregar mensaje para rechazar todo"
1337
+ : "Agregar mensaje de rechazo"
1573
1338
  }
1574
- showSaveButton={auditorAssigned() || userAssigned()}
1575
- setShowVersionSelector={setShowVersionSelector}
1576
- version={version}
1577
- shotThd={shotThd}
1578
- />
1579
- </div>
1580
- <div className="product-information">
1581
- <FullProductNameHeader
1582
- headerData={product}
1583
- productObservation={observation}
1584
- percent={activePercentage}
1585
- activeRetailer={activeRetailer}
1586
- servicesData={servicesData}
1587
- setActiveRetailer={setActiveRetailer}
1588
- sendToFacilitator={sendToFacilitator}
1589
- approve={() => {
1590
- sendToFacilitator("A");
1591
- }}
1592
- reject={() => {
1593
- setShowRejectModal(true);
1594
- }}
1595
- showApproveRejectAll={
1596
- approveRejectAllButtons() && (auditorAssigned() || userAssigned())
1339
+ show={showRejectModal}
1340
+ customComponent={
1341
+ <TagAndInput
1342
+ inputType={"textarea"}
1343
+ inputId={"modal-message-box"}
1344
+ index={0}
1345
+ color={"white"}
1346
+ />
1597
1347
  }
1598
- showValidationButtons={
1599
- approveRejectButtons() && (auditorAssigned() || userAssigned())
1348
+ buttons={[
1349
+ <ButtonV2
1350
+ key={"btn-Cancelar"}
1351
+ type={"white"}
1352
+ label={"Cancelar"}
1353
+ size={12}
1354
+ onClick={() => {
1355
+ setShowRejectModal(false);
1356
+ }}
1357
+ />,
1358
+ <ButtonV2
1359
+ key={"btn-Aceptar"}
1360
+ type={"pink"}
1361
+ label={"Aceptar"}
1362
+ size={12}
1363
+ onClick={async () => {
1364
+ const elements = document.querySelectorAll(
1365
+ "#modal-message-box .ql-container .ql-editor > p",
1366
+ );
1367
+
1368
+ if (!elements || elements.length === 0) {
1369
+ console.error("Elemento no encontrado");
1370
+ return;
1371
+ }
1372
+
1373
+ const isMessageEmpty = Array.from(elements).every((el) => {
1374
+ const body = el.innerHTML;
1375
+ return (
1376
+ !body || body.replace(/<.*?\/?>/gm, "").trim() === ""
1377
+ );
1378
+ });
1379
+
1380
+ if (isMessageEmpty) {
1381
+ const container = document.querySelector(
1382
+ ".container-customComponent",
1383
+ );
1384
+ const existingAlert =
1385
+ container.querySelector(".alert-error");
1386
+
1387
+ if (!existingAlert) {
1388
+ const alert = document.createElement("div");
1389
+ alert.className = "alert-error";
1390
+ alert.style.cssText = `
1391
+ color: #d32f2f;
1392
+ background-color: #ffebee;
1393
+ border: 1px solid #ef5350;
1394
+ border-radius: 4px;
1395
+ padding: 12px 16px;
1396
+ margin-top: 10px;
1397
+ font-size: 14px;
1398
+ display: flex;
1399
+ align-items: center;
1400
+ gap: 8px;
1401
+ font-family: 'Roboto', sans-serif;
1402
+ text-align: center;
1403
+ `;
1404
+ alert.innerHTML = `<span>El mensaje no puede estar vacío.</span>`;
1405
+ container.appendChild(alert);
1406
+ }
1407
+ return;
1408
+ }
1409
+
1410
+ const fullMessage = Array.from(elements)
1411
+ .map((element) => element.innerHTML)
1412
+ .join("")
1413
+ .replace(/<br\s*\/?>/gi, "\n");
1414
+
1415
+ await createComment(fullMessage, token);
1416
+
1417
+ rejectAll
1418
+ ? sendBulkEvaluation("R")
1419
+ : sendSingleEvaluation("R");
1420
+ setShowRejectModal(false);
1421
+ }}
1422
+ />,
1423
+ ]}
1424
+ />
1425
+ )}
1426
+ {state.modal.show && (
1427
+ <GenericModal
1428
+ componentsArray={[
1429
+ state.modal.image && (
1430
+ <img key="1" src={state.modal.image} alt="modal icon" />
1431
+ ),
1432
+ state.modal.title && (
1433
+ <ScreenHeader
1434
+ key="2"
1435
+ headerType={"retailer-name-header"}
1436
+ text={state.modal.title}
1437
+ color={"white"}
1438
+ />
1439
+ ),
1440
+ state.modal.message && (
1441
+ <ScreenHeader
1442
+ key="3"
1443
+ headerType={"retailer-name-header"}
1444
+ text={state.modal.message}
1445
+ color={"white"}
1446
+ />
1447
+ ),
1448
+ state.modal.buttons && (
1449
+ <div key="4" style={{ marginTop: "16px" }}>
1450
+ {state.modal.buttons.map((button, index) => (
1451
+ <Button
1452
+ key={index}
1453
+ buttonType={button.buttonType}
1454
+ label={button.text}
1455
+ onClick={button.action}
1456
+ />
1457
+ ))}
1458
+ </div>
1459
+ ),
1460
+ ].filter(Boolean)}
1461
+ onClick={() =>
1462
+ dispatch({
1463
+ type: "SET_MODAL",
1464
+ payload: { show: false, title: "", message: "", image: null },
1465
+ })
1600
1466
  }
1601
- approveAll={() => validateAll("A")}
1602
- rejectAll={() => {
1603
- setShowRejectModal(true);
1604
- setValRejAll(true);
1605
- }}
1606
- isObservationVisible={isObservationVisible}
1607
- toggleObservation={toggleObservation}
1608
- // handleClickOutside={handleClickOutside}
1609
- hideObservation={hideObservation}
1610
1467
  />
1611
- <FullTabsMenu
1612
- tabsSections={tabsSections}
1613
- status={retailerStatus}
1614
- activeTab={activeTab}
1615
- setActiveTab={setActiveTab}
1616
- setImageLayout={setImageLayout}
1617
- downloadImages={downloadImages}
1618
- askToDeleteImages={askToDeleteImages}
1619
- assig={assig[activeTab]}
1620
- setAssignation={setAssignation}
1621
- isRetailer={isRetailer}
1622
- showSaveButton={auditorAssigned() || userAssigned()}
1623
- version={version}
1624
- desc={desc}
1625
- setDesc={setDesc}
1626
- fich={fich}
1627
- setFich={setFich}
1628
- imag={imag}
1629
- setImag={setImag}
1630
- updatedDescriptions={updatedDescriptions}
1631
- setUpdatedDescriptions={setUpdatedDescriptions}
1632
- updatedDatasheets={updatedDatasheets}
1633
- setUpdatedDatasheets={setUpdatedDatasheets}
1634
- images={images}
1635
- setImages={setImages}
1636
- selectedImages={selectedImages}
1637
- setSelectedImages={setSelectedImages}
1468
+ )}
1469
+ {showModal && (
1470
+ <ProductImageModal
1471
+ images={state.images_values}
1472
+ setShowModal={setShowModal}
1473
+ approveRejectButtons={canShowSingleEvaluationButtons()}
1474
+ sendToFacilitator={handleOnSendEvaluationToFacilitator}
1475
+ />
1476
+ )}
1477
+ {showVersionSelector && (
1478
+ <VersionSelector
1479
+ modalId={"version-selector"}
1480
+ articleId={state.product.id_article}
1481
+ setVersion={handleOnChangeProductVersion}
1482
+ companyName={state.product.company}
1483
+ currentVersion={state.product.version}
1638
1484
  setShowVersionSelector={setShowVersionSelector}
1639
- onClickSave={() => {
1640
- switch (activeTab) {
1641
- case "Descripción":
1642
- !saving &&
1643
- product?.description_status !== "NS" &&
1644
- saveDescriptions();
1645
- break;
1646
- case "Ficha técnica":
1647
- !saving &&
1648
- product?.datasheet_status !== "NS" &&
1649
- saveDatasheets();
1650
- break;
1651
- case "Imágenes":
1652
- !saving && product?.images_status !== "NS" && updateImages();
1653
- break;
1654
-
1655
- default:
1656
- break;
1657
- }
1658
- }}
1659
- canAssign={![7, 8].includes(user.id_role)}
1485
+ jwt={token}
1660
1486
  />
1661
- <div
1662
- className={
1663
- "services-information-container " +
1664
- (imageLayout && activeTab === "Imágenes" ? "image-services" : "")
1665
- }
1666
- >
1667
- {loading ? (
1668
- <Loading />
1669
- ) : (
1670
- <>
1671
- {!imageLayout &&
1672
- activeTab === "Imágenes" &&
1673
- product?.services?.images === 1 && (
1674
- <GalleryHeader
1675
- setSelectedImages={setSelectedImages}
1676
- checkAll={checkAll}
1677
- setCheckAll={setCheckAll}
1678
- shotThd={shotThd}
1679
- />
1680
- )}
1487
+ )}
1488
+ <HeaderTop
1489
+ setHeaderTop={setHeaderTop}
1490
+ auditableVersion={auditableVersion}
1491
+ setCompare={setCompare}
1492
+ isAuditor={[1, 6].includes(user.id_role)}
1493
+ withChat={location?.state?.withChat}
1494
+ chatType={location?.state?.chatType}
1495
+ productSelected={state.product}
1496
+ token={token}
1497
+ activeRetailer={{
1498
+ id: state.active_retailer?.id_retailer,
1499
+ name: state.active_retailer?.retailer,
1500
+ }}
1501
+ />
1502
+ <div className="data-container">
1503
+ <div className="image-data-panel">
1504
+ <ImagePreviewer
1505
+ activeImage={
1506
+ state.images_values.values[state.current_image] ?? {}
1507
+ }
1508
+ imagesArray={state.images_values || []}
1509
+ setActiveImage={handleOnChangeCurrentImage}
1510
+ setShowModal={handleOnShowModalGallery}
1511
+ />
1512
+ <ImageDataTable
1513
+ // states
1514
+ lists={state.images_values || []}
1515
+ activeImage={
1516
+ state.images_values?.values
1517
+ ? state.images_values?.values[state.current_image]
1518
+ : {}
1519
+ }
1520
+ assignationsImages={state.collaborator_assignations["Imágenes"]}
1521
+ imagesStatus={state.product?.images_status || "-"}
1522
+ retailerSelected={state.active_retailer?.id_retailer}
1523
+ isRetailer={isRetailer}
1524
+ version={state.product.version}
1525
+ shotThd={shotThd}
1526
+ setShowVersionSelector={setShowVersionSelector}
1527
+ // actions
1528
+ setImages={handleOnChangeCurrentImage}
1529
+ setAssignation={handleOnChangeAssignations} // No se usa?
1530
+ onClickSave={handleOnClickSaveImages}
1531
+ showSaveButton={isAuditorAssigned() || isUserAssignedToService()}
1532
+ />
1533
+ </div>
1681
1534
 
1682
- {activeTab === "Ficha técnica" &&
1683
- (product?.datasheet_status !== "NS" ? (
1684
- datasheets[0]?.data?.map((dataGroup, index) => (
1535
+ <div className="product-information">
1536
+ <FullProductNameHeader
1537
+ // states
1538
+ headerData={state.product}
1539
+ productObservation={observation}
1540
+ percent={state.active_percentage.percentagesGeneral.required}
1541
+ activeRetailer={{
1542
+ id: state.active_retailer?.id_retailer,
1543
+ name: state.active_retailer?.retailer,
1544
+ image: state.active_retailer?.image,
1545
+ }}
1546
+ servicesData={state.services_data ? state.services_data : null}
1547
+ isObservationVisible={isObservationVisible}
1548
+ // actions
1549
+ setActiveRetailer={handleOnChangeActiveRetailer}
1550
+ approve={handleOnApproveSingleService}
1551
+ approveAll={handleOnApproveAllServices}
1552
+ reject={handleOnRejectSingleService}
1553
+ rejectAll={handleOnRejectAllServices}
1554
+ toggleObservation={handleOnToggleObservation}
1555
+ hideObservation={handleOnHideObservation}
1556
+ // sendToFacilitator={handleOnSendEvaluationToFacilitator} // No se usa?
1557
+ // validations
1558
+ showApproveRejectAll={
1559
+ canShowBulkEvaluationButtons() ||
1560
+ isAuditorAssigned() ||
1561
+ isUserAssignedToService()
1562
+ }
1563
+ showValidationButtons={
1564
+ canShowSingleEvaluationButtons() &&
1565
+ (isAuditorAssigned() || isUserAssignedToService())
1566
+ }
1567
+ />
1568
+ <FullTabsMenu
1569
+ tabsSections={tabsSections}
1570
+ status={getStatusByCurrentServiceAndRetailer()}
1571
+ activeTab={state.active_tab}
1572
+ isRetailer={isRetailer}
1573
+ assig={state.collaborator_assignations[state.active_tab]}
1574
+ version={state.product.version}
1575
+ updatedDescriptions={state.updated_descriptions_inputs}
1576
+ updatedDatasheets={state.updated_datasheets_inputs}
1577
+ updatedImages={state.updated_images_values}
1578
+ images={state.services.images}
1579
+ // selectedImages={selectedImages} prop pasada asi: FullTabsMenu -> TabsMenu Pero en TabsMenu no se espera ninguna prop setSelectedImages
1580
+ // setters
1581
+ setActiveTab={handleOnChangeActiveTab}
1582
+ setImageLayout={handleOnToggleImageLayout}
1583
+ setUpdatedDescriptions={handleOnSetUpdatedDescriptions}
1584
+ setUpdatedDatasheets={handleOnSetUpdatedDatasheets}
1585
+ setAssignation={handleOnSetAssignation} // No se usa?
1586
+ setImages={handleOnSetImages} // No se usa?
1587
+ setShowVersionSelector={setShowVersionSelector}
1588
+ // setSelectedImages={setSelectedImages} prop pasada asi: FullTabsMenu -> TabsMenu Pero en TabsMenu no se espera ninguna prop setSelectedImages
1589
+ // actions
1590
+ downloadImages={handleOnDownloadImages}
1591
+ askToDeleteImages={handleOnAskToDeleteImages}
1592
+ showSaveButton={isAuditorAssigned() || isUserAssignedToService()}
1593
+ onClickSave={handleOnClickSave}
1594
+ // validations
1595
+ canAssign={![7, 8].includes(user.id_role)}
1596
+ />
1597
+ <div
1598
+ className={
1599
+ "services-information-container " +
1600
+ (imageLayout && state.active_tab === "Imágenes"
1601
+ ? "image-services"
1602
+ : "")
1603
+ }
1604
+ id="services-information-container"
1605
+ >
1606
+ {state.saving ? (
1607
+ <Loading />
1608
+ ) : (
1609
+ <>
1610
+ {state.active_tab === "Descripción" &&
1611
+ (state.product?.description_status !== "NS" ? (
1685
1612
  <InputGroup
1686
- key={index + "-" + activeRetailer.name}
1687
- articleId={product.article.id_article}
1688
- version={version}
1689
- activeSection={activeTab}
1690
- inputGroup={dataGroup}
1691
- dataInputs={datasheets[1]}
1692
- auditInputs={auditDatasheets[1]}
1693
- updatedDatasheets={updatedDatasheets}
1694
- setUpdatedDatasheets={setUpdatedDatasheets}
1613
+ activeSection={state.active_tab}
1614
+ inputGroup={state.descriptions_inputs[0]}
1615
+ updatedDescriptions={state.updated_descriptions_inputs}
1616
+ articleId={state.product?.id_article}
1617
+ version={state.product.version}
1618
+ auditInputGroup={auditDescriptions[0]}
1619
+ setUpdatedDescriptions={handleOnSetUpdatedDescriptions}
1620
+ dinamicHeight={true}
1695
1621
  compare={compare}
1696
1622
  />
1697
- ))
1698
- ) : (
1699
- <ScreenHeader
1700
- text={"No cuentas con este servicio"}
1701
- headerType={"input-name-header"}
1702
- />
1703
- ))}
1704
- {activeTab === "Descripción" &&
1705
- (product?.description_status !== "NS" ? (
1706
- <InputGroup
1707
- activeSection={activeTab}
1708
- inputGroup={descriptions[0]}
1709
- auditInputGroup={auditDescriptions[0]}
1710
- updatedDescriptions={updatedDescriptions}
1711
- setUpdatedDescriptions={setUpdatedDescriptions}
1712
- articleId={product?.article?.id_article}
1713
- version={version}
1714
- dinamicHeight={true}
1715
- compare={compare}
1716
- />
1717
- ) : (
1718
- <ScreenHeader
1719
- text={"No cuentas con este servicio"}
1720
- headerType={"input-name-header"}
1721
- />
1722
- ))}
1723
-
1724
- {activeTab === "Imágenes" &&
1725
- (product?.images_status !== "NS" ? (
1726
- <section className="container">
1727
- <div {...getRootProps({ className: "dropzone" })}>
1728
- <input {...getInputProps()} />
1729
- <aside>{thumbs()}</aside>
1623
+ ) : (
1624
+ <ScreenHeader
1625
+ text={"No cuentas con este servicio"}
1626
+ headerType={"input-name-header"}
1627
+ />
1628
+ ))}
1629
+ {state.active_tab === "Ficha técnica" &&
1630
+ (state.product?.datasheet_status !== "NS" ? (
1631
+ <>
1632
+ {state.datasheets_inputs[0]?.data?.map(
1633
+ (dataGroup, index) => (
1634
+ <InputGroup
1635
+ key={index + "-" + state.active_retailer.retailer}
1636
+ articleId={state.product.id_article}
1637
+ version={state.version}
1638
+ activeSection={state.active_tab}
1639
+ inputGroup={dataGroup}
1640
+ dataInputs={state.datasheets_inputs[1]}
1641
+ auditInputs={auditDatasheets[1]}
1642
+ updatedDatasheets={
1643
+ state.updated_datasheets_inputs
1644
+ }
1645
+ setUpdatedDatasheets={
1646
+ handleOnSetUpdatedDatasheets
1647
+ }
1648
+ compare={compare}
1649
+ />
1650
+ ),
1651
+ )}
1652
+ </>
1653
+ ) : (
1654
+ <ScreenHeader
1655
+ text={"No cuentas con este servicio"}
1656
+ headerType={"input-name-header"}
1657
+ />
1658
+ ))}
1659
+ {state.active_tab === "Imágenes" &&
1660
+ (state.product?.images_status !== "NS" ? (
1661
+ <>
1662
+ {!imageLayout && (
1663
+ <GalleryHeader
1664
+ checkAll={state.all_image_checked} // Determina si el checkbox "Seleccionar todo" está marcado
1665
+ setCheckAll={handleOnSetCheckAll} // Toggler de true o false para el checkbox "Seleccionar todo"
1666
+ setSelectedImages={handleOnSetSelectedImages}
1667
+ // shotThd={shotThd} // No se usa?
1668
+ />
1669
+ )}
1670
+ <section
1671
+ className="container"
1672
+ style={{ position: "relative" }}
1673
+ >
1674
+ <div
1675
+ {...getRootProps({
1676
+ className: `dropzone ${
1677
+ isDragActive ? "drag-active" : ""
1678
+ }`,
1679
+ })}
1680
+ >
1681
+ <input {...getInputProps()} />
1682
+ {isDragActive && (
1683
+ <div className="drag-overlay">
1684
+ <p>Suelta las imágenes aquí</p>
1685
+ </div>
1686
+ )}
1687
+ <aside>{thumbs()}</aside>
1688
+ </div>
1689
+ {state.images_values?.values.length === 0 && (
1690
+ <div
1691
+ style={{
1692
+ position: "absolute",
1693
+ top: "50%",
1694
+ left: "50%",
1695
+ transform: "translate(-50%, -50%)",
1696
+ textAlign: "center",
1697
+ padding: "40px",
1698
+ width: "80%",
1699
+ maxWidth: "500px",
1700
+ fontFamily: "Arial, sans-serif",
1701
+ }}
1702
+ >
1703
+ <p
1704
+ style={{
1705
+ fontSize: "18px",
1706
+ color: "#666",
1707
+ marginBottom: "16px",
1708
+ }}
1709
+ >
1710
+ Este producto no tiene imágenes
1711
+ </p>
1712
+ {isAuditorAssigned() ||
1713
+ isUserAssignedToService() ? (
1714
+ <p
1715
+ style={{
1716
+ fontSize: "14px",
1717
+ color: "#999",
1718
+ marginBottom: "20px",
1719
+ }}
1720
+ >
1721
+ Arrastra las imágenes aquí o{" "}
1722
+ <span
1723
+ onClick={open}
1724
+ style={{
1725
+ color: "#007bff",
1726
+ cursor: "pointer",
1727
+ textDecoration: "underline",
1728
+ }}
1729
+ >
1730
+ haz clic para abrir el explorador de
1731
+ archivos
1732
+ </span>
1733
+ </p>
1734
+ ) : null}
1735
+ </div>
1736
+ )}
1737
+ </section>
1738
+ </>
1739
+ ) : (
1740
+ <ScreenHeader
1741
+ text={"No cuentas con este servicio"}
1742
+ headerType={"input-name-header"}
1743
+ />
1744
+ ))}
1745
+ </>
1746
+ )}
1747
+ </div>
1748
+ {(isUserAssignedToService(state.active_tab) ||
1749
+ isAuditorAssigned()) &&
1750
+ state.product[`${getConceptByTab(state.active_tab)}_status`] !==
1751
+ "NS" && (
1752
+ <div className="commentary-box">
1753
+ {!state.comment ? (
1754
+ <div className="commentary">
1755
+ <TagAndInput
1756
+ label={"Caja de Comentario"}
1757
+ inputType={"textarea"}
1758
+ inputCols={80}
1759
+ inputRows={4}
1760
+ inputId={"commentary-box"}
1761
+ index={0}
1762
+ />
1763
+ <div className="buttons-box">
1764
+ <Button
1765
+ buttonType={"general-transparent-button"}
1766
+ label={"Enviar comentario"}
1767
+ onClick={handleOnSubmitComment}
1768
+ />
1730
1769
  </div>
1731
- </section>
1770
+ </div>
1732
1771
  ) : (
1733
- <ScreenHeader
1734
- text={"No cuentas con este servicio"}
1735
- headerType={"input-name-header"}
1736
- />
1737
- ))}
1738
- </>
1739
- )}
1740
- </div>
1741
- {/* {(userAssigned(activeTab) || auditorAssigned()) &&
1742
- product[`${getConcept(activeTab)}_status`] !== "NS" && (
1743
- <div className="commentary-box">
1744
- {[7, 8].includes(user.id_role) && (
1745
- <Button
1746
- buttonType={
1747
- evaluationFinished(
1748
- user.id_role,
1749
- activeTab,
1750
- statusArray
1751
- ) && requiredNull[activeTab] === 0
1752
- ? "general-green-button"
1753
- : "general-button-disabled"
1754
- }
1755
- label={"Enviar evaluación"}
1756
- onClick={() => sendToFacilitator()}
1757
- />
1758
- )}
1759
- </div>
1760
- )} */}
1761
-
1762
- {(userAssigned(activeTab) || auditorAssigned()) &&
1763
- product[`${getConcept(activeTab)}_status`] !== "NS" && (
1764
- <div className="commentary-box">
1765
- {!comment ? (
1766
- <div className="commentary">
1767
- <TagAndInput
1768
- label={"Caja de Comentario"}
1769
- inputType={"textarea"}
1770
- inputCols={80}
1771
- inputRows={4}
1772
- inputId={"commentary-box"}
1773
- index={0}
1774
- />
1775
- <div className="buttons-box">
1772
+ <div className="feedback-box">
1773
+ <Commentary
1774
+ comment={state.comment?.message}
1775
+ reviewed={crossComment}
1776
+ />
1776
1777
  <Button
1777
- buttonType={"general-transparent-button"}
1778
- label={"Enviar comentario"}
1779
- onClick={handleCommentSubmit}
1778
+ buttonType={"circular-button accept-button"}
1779
+ onClick={async () => {
1780
+ setCrossComment(true);
1781
+ commentRevised();
1782
+ }}
1780
1783
  />
1781
1784
  </div>
1782
- </div>
1783
- ) : (
1784
- <div className="feedback-box">
1785
- <Commentary
1786
- comment={comment?.message}
1787
- reviewed={crossComment}
1788
- />
1789
- <Button
1790
- buttonType={"circular-button accept-button"}
1791
- onClick={async () => {
1792
- setCrossComment(true);
1793
- commentRevised();
1794
- }}
1795
- />
1796
- </div>
1797
- )}
1798
- {[7, 8].includes(user.id_role) && (
1799
- <Button
1800
- buttonType={
1801
- evaluationFinished(
1802
- user.id_role,
1803
- activeTab,
1804
- statusArray
1805
- ) && requiredNull[activeTab] === 0
1806
- ? "general-green-button"
1807
- : "general-button-disabled"
1785
+ )}
1786
+
1787
+ <div className="action-buttons">
1788
+ {
1789
+ allowedAccountsToViewChangeStatus.includes(user?.email) && (
1790
+ <Button
1791
+ buttonType={"general-pink-button"}
1792
+ label={"Cambio de Estatus"}
1793
+ onClick={() => setOpenChangeStatusModal(true)}
1794
+ id="button-change-status"
1795
+ />
1796
+ )
1808
1797
  }
1809
- label={"Enviar evaluación"}
1810
- onClick={() => sendToFacilitator()}
1811
- />
1812
- )}
1813
- </div>
1814
- )}
1798
+ {[7, 8].includes(user.id_role) && (
1799
+ <Button
1800
+ buttonType={
1801
+ isEvaluationFinished(
1802
+ user.id_role,
1803
+ state.active_tab,
1804
+ statusArray,
1805
+ ) &&
1806
+ state.missing_required_fields[state.active_tab] === 0
1807
+ ? "general-green-button"
1808
+ : "general-button-disabled"
1809
+ }
1810
+ label={"Enviar evaluación"}
1811
+ onClick={handleOnSendEvaluationToFacilitator}
1812
+ />
1813
+ )}
1814
+ </div>
1815
+ </div>
1816
+ )}
1817
+ </div>
1815
1818
  </div>
1816
- </div>
1817
- {showModal && (
1818
- <ProductImageModal
1819
- images={images}
1820
- setShowModal={setShowModal}
1821
- sendToFacilitator={sendToFacilitator}
1822
- approveRejectButtons={approveRejectButtons()}
1823
- />
1824
- )}
1825
- {message.length > 0 && (
1826
- <GenericModal
1827
- buttonType={componentsArray.length > 0 && "delete-product"}
1828
- componentsArray={
1829
- componentsArray.length > 0
1830
- ? componentsArray
1831
- : [
1832
- <img key="1" src={succes} alt="success icon" />,
1833
- <ScreenHeader
1834
- key="2"
1835
- headerType={"retailer-name-header"}
1836
- text={message}
1837
- color={"white"}
1838
- />,
1839
- ]
1840
- }
1841
- onClick={() => setMessage("")}
1842
- />
1843
- )}
1844
- {showVersionSelector && (
1845
- <VersionSelector
1846
- modalId={"version-selector"}
1847
- articleId={product.article.id_article}
1848
- setVersion={setVersion}
1849
- companyName={product.article.company_name}
1850
- currentVersion={version}
1851
- setShowVersionSelector={setShowVersionSelector}
1852
- jwt={token}
1853
- />
1854
- )}
1855
- {showRejectModal && (
1856
- <Modal
1857
- title={`Agregar mensaje de rechazo para ${activeTab?.toLowerCase()}`}
1858
- show={showRejectModal}
1859
- customComponent={
1860
- <TagAndInput
1861
- inputType={"textarea"}
1862
- inputId={"modal-message-box"}
1863
- index={0}
1864
- color={"white"}
1865
- />
1866
- }
1867
- buttons={[
1868
- <ButtonV2
1869
- key={"btn-Cancelar"}
1870
- type={"white"}
1871
- label={"Cancelar"}
1872
- size={12}
1873
- onClick={() => {
1874
- setShowRejectModal(false);
1875
- }}
1876
- />,
1877
- <ButtonV2
1878
- key={"btn-Aceptar"}
1879
- type={"pink"}
1880
- label={"Aceptar"}
1881
- size={12}
1882
- onClick={async (e) => {
1883
- const elements = document.querySelectorAll(
1884
- "#modal-message-box .ql-container .ql-editor > p"
1885
- );
1886
-
1887
- if (!elements || elements.length === 0) {
1888
- console.error("Elemento no encontrado");
1889
- return;
1890
- }
1891
-
1892
- let brCounter = 0;
1893
-
1894
- elements.forEach((el) => {
1895
- const body = el.innerHTML;
1896
-
1897
- if (typeof body !== "string") {
1898
- console.log("El contenido de body no es una cadena", body);
1899
- isMessageEmpty = true;
1900
- return;
1901
- }
1902
-
1903
- if (!body || body.replace(/<.*?\/?>/gm, "").trim() === "") {
1904
- brCounter++;
1905
- }
1906
- });
1907
-
1908
- if (brCounter === elements.length) {
1909
- const container = document.querySelector(
1910
- ".container-customComponent"
1911
- );
1912
-
1913
- const existingAlert = container.querySelector(".alert-error");
1914
- if (existingAlert) {
1915
- return; // Si ya existe, no crear otra
1916
- }
1819
+ </Container>
1917
1820
 
1918
- // Crear alerta con estilos
1919
- const alert = document.createElement("div");
1920
- alert.className = "alert-error";
1921
- alert.style.cssText = `
1922
- color: #d32f2f;
1923
- background-color: #ffebee;
1924
- border: 1px solid #ef5350;
1925
- border-radius: 4px;
1926
- padding: 12px 16px;
1927
- margin-top: 10px;
1928
- font-size: 14px;
1929
- display: flex;
1930
- align-items: center;
1931
- gap: 8px;
1932
- font-family: 'Roboto', sans-serif;
1933
- text-align: center;
1934
- `;
1935
-
1936
- alert.innerHTML = `
1937
- <span>El mensaje no puede estar vacío.</span>
1938
- `;
1939
-
1940
- container.appendChild(alert);
1941
- return;
1942
- }
1943
-
1944
- let fullMessage = "";
1945
-
1946
- elements.forEach((element) => {
1947
- const body = element.innerHTML;
1948
-
1949
- fullMessage += body;
1950
- });
1951
-
1952
- fullMessage = fullMessage.replace(/<br\s*\/?>/gi, "\n");
1953
-
1954
- await createComment(e, fullMessage, activeTab);
1955
- valRejAll ? validateAll("R") : sendToFacilitator("R");
1956
- setMessage("Rechazado");
1957
- setShowRejectModal(false);
1958
- }}
1959
- />,
1960
- ]}
1961
- />
1962
- )}
1963
- <Modal
1964
- className="container-modalAlert"
1965
- show={modalAlert.show}
1966
- title={modalAlert.title}
1967
- message={modalAlert.message}
1968
- icon={"info"}
1969
- onClickBtnDefault={(event) => {
1970
- setModalAlert((prev) => ({
1971
- ...prev,
1972
- show: false,
1973
- errorInputMessage: false,
1974
- }));
1821
+ <ChangeStatusModal
1822
+ state={state}
1823
+ open={openChangeStatusModal}
1824
+ onClose={() => {
1825
+ setOpenChangeStatusModal(false);
1975
1826
  }}
1827
+ reloadData={loadData}
1828
+ token={token}
1976
1829
  />
1977
- </Container>
1830
+ </AiProductEditionProvider>
1831
+ );
1832
+ };
1833
+
1834
+ export const RetailerProductEdition = (props) => {
1835
+ return (
1836
+ <ProviderProductEditionProvider>
1837
+ <RetailerProductEditionView {...props} />
1838
+ </ProviderProductEditionProvider>
1978
1839
  );
1979
1840
  };