contentoh-components-library 21.6.57 → 21.6.59

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 (49) hide show
  1. package/.claude/settings.local.json +7 -0
  2. package/dist/components/pages/CreatorsProductEdition/CreatorsProductEditionView.js +90 -135
  3. package/dist/components/pages/CreatorsProductEdition/hooks/useCreatorsProductEdition.js +148 -162
  4. package/dist/components/pages/CreatorsProductEdition/mocks/api.fixtures.js +16 -0
  5. package/dist/components/pages/CreatorsProductEdition/mocks/mockApi.js +1 -0
  6. package/dist/components/pages/CreatorsProductEdition/sections/ImageGallery.js +433 -0
  7. package/dist/components/pages/CreatorsProductEdition/sections/NoticeDialog.js +155 -0
  8. package/dist/components/pages/CreatorsProductEdition/sections/RejectDialog.js +201 -0
  9. package/dist/components/pages/CreatorsProductEdition/sections/VersionDrawer.js +615 -0
  10. package/dist/components/pages/CreatorsProductEdition/stories/CreatorsProductEdition.stories.js +3 -31
  11. package/dist/components/pages/CreatorsProductEdition/stories/CreatorsProductEditionAPI.stories.js +55 -30
  12. package/dist/components/pages/CreatorsProductEdition/stories/products.js +923 -0
  13. package/dist/components/pages/CreatorsProductEdition/styles.js +27 -7
  14. package/dist/components/pages/ProviderProductEdition/ProviderProductEdition.stories.js +2 -1
  15. package/dist/components/pages/ProviderProductEdition/context/provider-product-edition.context.js +325 -10
  16. package/dist/components/pages/ProviderProductEdition/context/provider-product-edition.reducer.js +12 -2
  17. package/dist/components/pages/ProviderProductEdition/index.js +486 -231
  18. package/dist/index.js +13 -0
  19. package/package.json +1 -1
  20. package/src/components/pages/CreatorsProductEdition/CreatorsProductEditionView.jsx +777 -0
  21. package/src/components/pages/CreatorsProductEdition/constants.js +21 -0
  22. package/src/components/pages/CreatorsProductEdition/hooks/useCreatorsProductEdition.js +2238 -0
  23. package/src/components/pages/CreatorsProductEdition/index.js +19 -0
  24. package/src/components/pages/CreatorsProductEdition/mocks/api.fixtures.js +357 -0
  25. package/src/components/pages/CreatorsProductEdition/mocks/mockApi.js +144 -0
  26. package/src/components/pages/CreatorsProductEdition/sections/CommentBox.jsx +183 -0
  27. package/src/components/pages/CreatorsProductEdition/sections/ImageGallery.jsx +269 -0
  28. package/src/components/pages/CreatorsProductEdition/sections/ImagePanel.jsx +332 -0
  29. package/src/components/pages/CreatorsProductEdition/sections/ImagesSection.jsx +385 -0
  30. package/src/components/pages/CreatorsProductEdition/sections/NoticeDialog.jsx +111 -0
  31. package/src/components/pages/CreatorsProductEdition/sections/ProductHeader.jsx +305 -0
  32. package/src/components/pages/CreatorsProductEdition/sections/ProductMedia.jsx +101 -0
  33. package/src/components/pages/CreatorsProductEdition/sections/RejectDialog.jsx +125 -0
  34. package/src/components/pages/CreatorsProductEdition/sections/RetailerMenu.jsx +129 -0
  35. package/src/components/pages/CreatorsProductEdition/sections/ServiceActions.jsx +382 -0
  36. package/src/components/pages/CreatorsProductEdition/sections/StatusChip.jsx +37 -0
  37. package/src/components/pages/CreatorsProductEdition/sections/TabsBar.jsx +187 -0
  38. package/src/components/pages/CreatorsProductEdition/sections/VariantsPanel.jsx +311 -0
  39. package/src/components/pages/CreatorsProductEdition/sections/VersionDrawer.jsx +357 -0
  40. package/src/components/pages/CreatorsProductEdition/sections/tokens.js +63 -0
  41. package/src/components/pages/CreatorsProductEdition/stories/CreatorsProductEdition.stories.js +111 -0
  42. package/src/components/pages/CreatorsProductEdition/stories/CreatorsProductEditionAPI.stories.js +85 -0
  43. package/src/components/pages/CreatorsProductEdition/stories/products.js +966 -0
  44. package/src/components/pages/CreatorsProductEdition/styles.js +178 -0
  45. package/src/components/pages/ProviderProductEdition/ProviderProductEdition.stories.js +1 -0
  46. package/src/components/pages/ProviderProductEdition/context/provider-product-edition.context.jsx +201 -0
  47. package/src/components/pages/ProviderProductEdition/context/provider-product-edition.reducer.js +12 -0
  48. package/src/components/pages/ProviderProductEdition/index.js +228 -7
  49. package/src/index.js +1 -0
@@ -0,0 +1,777 @@
1
+ /**
2
+ * Vista de la edición de producto de Creators.
3
+ *
4
+ * El JSX parte de `RetailerProductEdition/index.js` (commit ff06fba) y la
5
+ * lógica vive en `hooks/useCreatorsProductEdition.js`. Mismos props que
6
+ * RetailerProductEdition. Lo que cambia es el layout: responsivo en celular,
7
+ * tablet y desktop (ver `styles.js`).
8
+ */
9
+ import {
10
+ Skeleton,
11
+ Box,
12
+ Stack,
13
+ Typography,
14
+ CircularProgress,
15
+ Button as MuiButton,
16
+ } from "@mui/material";
17
+
18
+ import { getConceptByTab } from "../RetailerProductEdition/utils";
19
+ import { countUnsavedAssets } from "../../../global-files/digital-assets/assets.reducer";
20
+
21
+
22
+ import { TRAYS } from "../../molecules/GalleryTrayTabs";
23
+ import { HeaderTop } from "../../molecules/HeaderTop";
24
+
25
+ import { InputGroup } from "../../organisms/InputGroup";
26
+ import { LinkVariantsModal } from "../../organisms/LinkVariantsModal";
27
+ import { UnlinkVariantModal } from "../../organisms/UnlinkVariantModal";
28
+ import { VariantGroupModal } from "../../organisms/VariantGroupModal";
29
+ import { PropagateChangesModal } from "../../organisms/PropagateChangesModal";
30
+ import ChangeStatusModal from "../../organisms/ChangeStatusModal";
31
+
32
+ import { AiProductEditionProvider } from "../../../contexts/AiProductEdition";
33
+
34
+ import errorIcon from "../../../assets/images/genericModal/errorModal.svg";
35
+
36
+ import { useCreatorsProductEdition } from "./hooks/useCreatorsProductEdition";
37
+ import { allowedAccountsToViewChangeStatus } from "./constants";
38
+ import { Container, ReusedModalsStyles } from "./styles";
39
+ import { ProductHeader } from "./sections/ProductHeader";
40
+ import { TabsBar } from "./sections/TabsBar";
41
+ import { ImagePanel } from "./sections/ImagePanel";
42
+ import { ImagesSection } from "./sections/ImagesSection";
43
+ import { VariantsPanel } from "./sections/VariantsPanel";
44
+ import { CommentBox } from "./sections/CommentBox";
45
+ import { NoticeDialog } from "./sections/NoticeDialog";
46
+ import { RejectDialog } from "./sections/RejectDialog";
47
+ import { ImageGallery } from "./sections/ImageGallery";
48
+ import { VersionDrawer } from "./sections/VersionDrawer";
49
+ import { COLORS, FONTS } from "./sections/tokens";
50
+
51
+ // Aviso cuando el producto no tiene contratado el servicio de la pestaña.
52
+ const NoService = () => (
53
+ <Typography sx={{ fontFamily: FONTS.Raleway, fontSize: 14, color: COLORS.s5, py: 1 }}>
54
+ No cuentas con este servicio
55
+ </Typography>
56
+ );
57
+
58
+ export const CreatorsProductEditionView = ({
59
+ tabsSections,
60
+ productSelected = {},
61
+ user = {},
62
+ token,
63
+ location,
64
+ enableDigitalAssets = false,
65
+ }) => {
66
+ const {
67
+ isRetailer,
68
+ digitalAssets,
69
+ currentTray,
70
+ assetsLoadedFor,
71
+ handleOnSetSelectedAssets,
72
+ handleOnSetAllAssetsChecked,
73
+ initializeProduct,
74
+ loadAuditableData,
75
+ loadData,
76
+ handleOnChangeCurrentImage,
77
+ handleOnShowModalGallery,
78
+ handleOnChangeActiveRetailer,
79
+ handleOnChangeActiveTab,
80
+ handleOnToggleImageLayout,
81
+ handleOnToggleObservation,
82
+ handleOnHideObservation,
83
+ isAuditorAssigned,
84
+ isAssignedAs,
85
+ isUserAssignedToService,
86
+ canShowSingleEvaluationButtons,
87
+ canShowBulkEvaluationButtons,
88
+ getStatusByCurrentServiceAndRetailer,
89
+ handleOnSaveAssets,
90
+ handleOnClickSaveImages,
91
+ loadFamily,
92
+ hijosDelProducto,
93
+ esHijoDeOtro,
94
+ puedeAgruparse,
95
+ handleOnUnlinked,
96
+ loadVariantGroup,
97
+ handleOnLeaveGroup,
98
+ openFamilyMember,
99
+ handleOnLinkedVariants,
100
+ handleOnRestoreInheritance,
101
+ cambiosDeFichaTecnica,
102
+ cambiosDeDescripciones,
103
+ handleOnClickSave,
104
+ handleOnSetUpdatedDescriptions,
105
+ handleOnSetUpdatedDatasheets,
106
+ handleOnDownloadAssets,
107
+ handleOnAskToDeleteAssets,
108
+ handleOnAskToDeleteImages,
109
+ handleOnApproveSingleService,
110
+ handleOnRejectSingleService,
111
+ sendSingleEvaluation,
112
+ handleOnApproveAllServices,
113
+ handleOnRejectAllServices,
114
+ sendBulkEvaluation,
115
+ handleOnChangeAssignations,
116
+ handleOnSetAssignation,
117
+ handleOnSetImages,
118
+ handleOnSetCheckAll,
119
+ handleOnSetSelectedImages,
120
+ handleOnSubmitComment,
121
+ handleOnSendEvaluationToFacilitator,
122
+ isEvaluationFinished,
123
+ handleOnChangeProductVersion,
124
+ handleRetryLoad,
125
+ showVersionSelector,
126
+ setShowVersionSelector,
127
+ auditableVersion,
128
+ setAuditableVersion,
129
+ headerTop,
130
+ setHeaderTop,
131
+ shotThd,
132
+ setShotThd,
133
+ observation,
134
+ setObservation,
135
+ showRejectModal,
136
+ setShowRejectModal,
137
+ isObservationVisible,
138
+ setObservationVisible,
139
+ imageLayout,
140
+ setImageLayout,
141
+ crossComment,
142
+ setCrossComment,
143
+ statusArray,
144
+ setStatusArray,
145
+ rejectAll,
146
+ setRejectAll,
147
+ showLinkVariants,
148
+ setShowLinkVariants,
149
+ family,
150
+ setFamily,
151
+ showFamily,
152
+ setShowFamily,
153
+ loadingVariant,
154
+ setLoadingVariant,
155
+ variantGroup,
156
+ setVariantGroup,
157
+ showGroup,
158
+ setShowGroup,
159
+ showGroupModal,
160
+ setShowGroupModal,
161
+ porDesvincular,
162
+ setPorDesvincular,
163
+ avisoFamilia,
164
+ setAvisoFamilia,
165
+ pendingPropagation,
166
+ setPendingPropagation,
167
+ preparandoPropagacion,
168
+ setPreparandoPropagacion,
169
+ auditServices,
170
+ setAuditServices,
171
+ auditDatasheets,
172
+ setAuditDatasheets,
173
+ auditDescriptions,
174
+ setAuditDescriptions,
175
+ showModal,
176
+ setShowModal,
177
+ compare,
178
+ setCompare,
179
+ openChangeStatusModal,
180
+ setOpenChangeStatusModal,
181
+ assetNotices,
182
+ setAssetNotices,
183
+ activeTray,
184
+ setActiveTray,
185
+ state,
186
+ dispatch,
187
+ saveDatasheets,
188
+ saveDescriptions,
189
+ updateImages,
190
+ deleteImages,
191
+ saveAssets,
192
+ registerUploadedAssets,
193
+ handleOnDownloadImages,
194
+ createComment,
195
+ getRootProps,
196
+ getInputProps,
197
+ isDragActive,
198
+ open,
199
+ } = useCreatorsProductEdition({
200
+ tabsSections,
201
+ productSelected,
202
+ user,
203
+ token,
204
+ location,
205
+ enableDigitalAssets,
206
+ });
207
+
208
+ const ProductEditionSkeleton = () => {
209
+ return (
210
+ <Container headerTop={0}>
211
+ <Box
212
+ px={1.5}
213
+ py={1}
214
+ pb={0}
215
+ display="flex"
216
+ alignItems="center"
217
+ justifyContent="space-between"
218
+ >
219
+ <Skeleton variant="text" width="min(300px, 60%)" height={40} />
220
+ <Stack direction="row" spacing={2}>
221
+ <Skeleton variant="circular" width={32} height={32} />
222
+ </Stack>
223
+ </Box>
224
+
225
+ <div className="data-container">
226
+ <div
227
+ className="image-data-panel"
228
+ style={{ display: "flex", flexDirection: "column", gap: "20px" }}
229
+ >
230
+ <Skeleton variant="rounded" width="100%" height={320} />
231
+
232
+ <Stack direction="row" spacing={2} justifyContent="center">
233
+ {[1, 2, 3, 4].map((item) => (
234
+ <Skeleton key={item} variant="rounded" width={60} height={80} />
235
+ ))}
236
+ </Stack>
237
+
238
+ <Box mt={2}>
239
+ {[1, 2, 3, 4].map((item) => (
240
+ <Box
241
+ key={item}
242
+ display="flex"
243
+ justifyContent="space-between"
244
+ py={1}
245
+ borderBottom="1px solid #f0f0f0"
246
+ >
247
+ <Skeleton variant="text" width="40%" />
248
+ <Skeleton variant="text" width="20%" />
249
+ </Box>
250
+ ))}
251
+ </Box>
252
+ </div>
253
+
254
+ <div
255
+ className="product-information"
256
+ style={{
257
+ flex: 1,
258
+ display: "flex",
259
+ flexDirection: "column",
260
+ gap: "14px",
261
+ }}
262
+ >
263
+ <Box>
264
+ <Skeleton variant="text" width="80%" height={50} />
265
+ <Stack direction="row" spacing={2} alignItems="center">
266
+ <Skeleton variant="rounded" width="30%" height={24} />
267
+ <Skeleton variant="rounded" width="45%" height={24} />
268
+ </Stack>
269
+ </Box>
270
+
271
+ <Box display="flex" gap={4} pb={1}>
272
+ <Skeleton variant="text" width="28%" height={45} />
273
+ <Skeleton variant="text" width="28%" height={45} />
274
+ <Skeleton variant="text" width="28%" height={45} />
275
+ </Box>
276
+
277
+ <Box display="flex" flexDirection="column" gap={4}>
278
+ {[1, 2, 3].map((item) => (
279
+ <Box key={item}>
280
+ <Box
281
+ display="flex"
282
+ justifyContent="space-between"
283
+ alignItems="center"
284
+ mb={1}
285
+ >
286
+ <Skeleton variant="text" width="30%" height={24} />
287
+ <Skeleton
288
+ variant="rounded"
289
+ width="min(175px, 35%)"
290
+ height={25}
291
+ sx={{ borderRadius: "7.5px" }}
292
+ />
293
+ </Box>
294
+ <Skeleton variant="rounded" width="100%" height={70} />
295
+ <Box display="flex" justifyContent="flex-end" mt={0.5}>
296
+ <Skeleton variant="text" width={60} />
297
+ </Box>
298
+ </Box>
299
+ ))}
300
+ </Box>
301
+
302
+ <Box mt="auto" pt={4}>
303
+ <Skeleton variant="rounded" width="100%" height={120} />
304
+ <Box display="flex" justifyContent="flex-end" gap={2} mt={2}>
305
+ <Skeleton
306
+ variant="rounded"
307
+ width="min(180px, 45%)"
308
+ height={45}
309
+ sx={{ borderRadius: "24px" }}
310
+ />
311
+ <Skeleton
312
+ variant="rounded"
313
+ width="min(180px, 45%)"
314
+ height={45}
315
+ sx={{ borderRadius: "24px" }}
316
+ />
317
+ </Box>
318
+ </Box>
319
+ </div>
320
+ </div>
321
+ </Container>
322
+ );
323
+ };
324
+
325
+ // Si la carga falló y no tenemos servicios, mostramos un estado de error con
326
+ // opción de reintentar en vez de quedarnos colgados en el skeleton.
327
+ if (!state.loading && state.errors?.length > 0 && !state.services)
328
+ return (
329
+ <Container headerTop={0}>
330
+ <Box
331
+ display="flex"
332
+ flexDirection="column"
333
+ alignItems="center"
334
+ justifyContent="center"
335
+ gap={2}
336
+ sx={{ minHeight: "60vh", textAlign: "center", padding: "40px" }}
337
+ >
338
+ <img src={errorIcon} alt="error" width={64} height={64} />
339
+ <Typography sx={{ fontFamily: FONTS.Raleway, fontSize: 16, color: COLORS.s5 }}>
340
+ No pudimos cargar el producto
341
+ </Typography>
342
+ <Typography sx={{ color: "#666", maxWidth: 420, fontSize: 14 }}>
343
+ {state.errors[0] ||
344
+ "Ocurrió un error al cargar la información del producto."}
345
+ </Typography>
346
+ <MuiButton
347
+ variant="contained"
348
+ disableElevation
349
+ onClick={handleRetryLoad}
350
+ sx={{
351
+ textTransform: "none",
352
+ borderRadius: "20px",
353
+ px: 4,
354
+ bgcolor: COLORS.magenta_s2,
355
+ "&:hover": { bgcolor: COLORS.secondary_magenta },
356
+ }}
357
+ >
358
+ Reintentar
359
+ </MuiButton>
360
+ </Box>
361
+ </Container>
362
+ );
363
+
364
+ /*
365
+ `images_values` entra al guard junto a `services` y `product`.
366
+
367
+ React 17 no agrupa los dispatch que van después de un `await`: cada uno
368
+ re-renderiza al instante. En `loadData` el orden es SET_SERVICES →
369
+ …_INPUTS → SET_IMAGES_VALUES, así que había renders intermedios con
370
+ `services` ya puesto —el guard los dejaba pasar— y el resto todavía en null.
371
+ El cuerpo de abajo lee `datasheets_inputs[1]`, `descriptions_inputs[0]` y
372
+ `images_values.values` sin protección, y tumbaba la pantalla entera.
373
+
374
+ Se comprueba `images_values` y no cada uno: es el ÚLTIMO que `loadData`
375
+ despacha, así que si ya está, los anteriores también.
376
+ */
377
+ if (
378
+ state.loading ||
379
+ !state.services ||
380
+ !state.product ||
381
+ !state.images_values
382
+ )
383
+ return (
384
+ <>
385
+ <ProductEditionSkeleton />
386
+ </>
387
+ );
388
+
389
+ return (
390
+ <AiProductEditionProvider
391
+ isCreatorsEdition={true}
392
+ user={user}
393
+ token={token}
394
+ state={state}
395
+ >
396
+ <Container headerTop={headerTop}>
397
+ {pendingPropagation && (
398
+ <PropagateChangesModal
399
+ changedAttributes={pendingPropagation.cambios}
400
+ kind={pendingPropagation.kind ?? "datasheet"}
401
+ variants={
402
+ esHijoDeOtro
403
+ ? []
404
+ : (family?.variants ?? []).filter((v) => !v.isCurrent)
405
+ }
406
+ groupMembers={(variantGroup?.members ?? []).filter(
407
+ (m) => !m.isCurrent,
408
+ )}
409
+ groupCode={variantGroup?.group ?? null}
410
+ saving={state.saving}
411
+ onCancel={() => setPendingPropagation(null)}
412
+ onConfirm={(propagation) => {
413
+ const esDescripcion = pendingPropagation.kind === "description";
414
+ setPendingPropagation(null);
415
+
416
+ if (!propagation) {
417
+ if (esDescripcion) saveDescriptions(token);
418
+ else saveDatasheets(token);
419
+ return;
420
+ }
421
+
422
+ /*
423
+ El diálogo habla de «atributos» porque es el mismo componente
424
+ para las dos pestañas. Al guardar hay que ponerlo en la lista
425
+ que corresponde: el servidor recibe `attributes` o
426
+ `descriptions`, nunca una por la otra.
427
+ */
428
+ const { attributes, ...destinos } = propagation;
429
+
430
+ if (esDescripcion) {
431
+ saveDescriptions(token, { ...destinos, descriptions: attributes });
432
+ } else {
433
+ saveDatasheets(token, propagation);
434
+ }
435
+ }}
436
+ />
437
+ )}
438
+ {showLinkVariants && (
439
+ <LinkVariantsModal
440
+ articleId={state.product?.id_article}
441
+ articleName={state.product?.name}
442
+ articleUpc={state.product?.upc}
443
+ activeRetailerId={state.active_retailer?.id_retailer}
444
+ token={token}
445
+ onClose={() => setShowLinkVariants(false)}
446
+ onLinked={handleOnLinkedVariants}
447
+ />
448
+ )}
449
+ {showGroupModal && (
450
+ <VariantGroupModal
451
+ articleId={state.product?.id_article}
452
+ articleName={state.product?.name}
453
+ articleUpc={state.product?.upc}
454
+ currentGroup={variantGroup?.group ?? null}
455
+ token={token}
456
+ onClose={() => setShowGroupModal(false)}
457
+ onSaved={(result) => {
458
+ setVariantGroup(result);
459
+ setShowGroup(true);
460
+ }}
461
+ />
462
+ )}
463
+ {porDesvincular && (
464
+ <UnlinkVariantModal
465
+ {...porDesvincular}
466
+ token={token}
467
+ onClose={() => setPorDesvincular(null)}
468
+ onUnlinked={handleOnUnlinked}
469
+ />
470
+ )}
471
+ <RejectDialog
472
+ open={showRejectModal}
473
+ rejectAll={rejectAll}
474
+ onCancel={() => setShowRejectModal(false)}
475
+ onConfirm={async (message) => {
476
+ await createComment(message, token);
477
+ rejectAll ? sendBulkEvaluation("R") : sendSingleEvaluation("R");
478
+ setShowRejectModal(false);
479
+ }}
480
+ />
481
+ <NoticeDialog
482
+ modal={state.modal}
483
+ onClose={() =>
484
+ dispatch({
485
+ type: "SET_MODAL",
486
+ payload: { show: false, title: "", message: "", image: null },
487
+ })
488
+ }
489
+ />
490
+ {showModal && (
491
+ <ImageGallery
492
+ images={state.images_values}
493
+ initialIndex={state.current_image}
494
+ setShowModal={setShowModal}
495
+ approveRejectButtons={canShowSingleEvaluationButtons()}
496
+ sendToFacilitator={handleOnSendEvaluationToFacilitator}
497
+ />
498
+ )}
499
+ <VersionDrawer
500
+ open={showVersionSelector}
501
+ modalId={"version-selector"}
502
+ articleId={state.product.id_article}
503
+ setVersion={handleOnChangeProductVersion}
504
+ companyName={state.product.company}
505
+ currentVersion={state.product.version}
506
+ setShowVersionSelector={setShowVersionSelector}
507
+ jwt={token}
508
+ />
509
+ <HeaderTop
510
+ setHeaderTop={setHeaderTop}
511
+ auditableVersion={auditableVersion}
512
+ setCompare={setCompare}
513
+ isAuditor={[1, 6].includes(user.id_role)}
514
+ withChat={location?.state?.withChat}
515
+ chatType={location?.state?.chatType}
516
+ productSelected={state.product}
517
+ token={token}
518
+ activeRetailer={{
519
+ id: state.active_retailer?.id_retailer,
520
+ name: state.active_retailer?.retailer,
521
+ }}
522
+ />
523
+ <div className="data-container">
524
+ <div className="image-data-panel">
525
+ <ImagePanel
526
+ /*
527
+ `images_values` puede ser null en pleno render: React 17 no
528
+ agrupa los dispatch que van después de un `await` y hay un render
529
+ con `services` puesto y las imágenes todavía no (ver el guard de
530
+ arriba). Por eso las interrogaciones.
531
+ */
532
+ imagesArray={state.images_values || []}
533
+ activeImage={state.images_values?.values?.[state.current_image] ?? {}}
534
+ currentIndex={state.current_image}
535
+ setActiveImage={handleOnChangeCurrentImage}
536
+ setShowModal={handleOnShowModalGallery}
537
+ // datos de la imagen
538
+ lists={state.images_values || []}
539
+ assignationsImages={state.collaborator_assignations["Imágenes"]}
540
+ imagesStatus={state.product?.images_status || "-"}
541
+ retailerSelected={state.active_retailer?.id_retailer}
542
+ isRetailer={isRetailer}
543
+ version={state.product.version}
544
+ shotThd={shotThd}
545
+ setShowVersionSelector={setShowVersionSelector}
546
+ setImages={handleOnChangeCurrentImage}
547
+ setAssignation={handleOnChangeAssignations}
548
+ onClickSave={handleOnClickSaveImages}
549
+ showSaveButton={isAuditorAssigned() || isUserAssignedToService()}
550
+ />
551
+ </div>
552
+
553
+ <div className="product-information">
554
+ <VariantsPanel
555
+ product={state.product}
556
+ esHijoDeOtro={esHijoDeOtro}
557
+ hijosDelProducto={hijosDelProducto}
558
+ family={family}
559
+ showFamily={showFamily}
560
+ setShowFamily={setShowFamily}
561
+ loadFamily={loadFamily}
562
+ loadingVariant={loadingVariant}
563
+ avisoFamilia={avisoFamilia}
564
+ openFamilyMember={openFamilyMember}
565
+ setPorDesvincular={setPorDesvincular}
566
+ setShowLinkVariants={setShowLinkVariants}
567
+ puedeAgruparse={puedeAgruparse}
568
+ variantGroup={variantGroup}
569
+ showGroup={showGroup}
570
+ setShowGroup={setShowGroup}
571
+ setShowGroupModal={setShowGroupModal}
572
+ handleOnLeaveGroup={handleOnLeaveGroup}
573
+ />
574
+
575
+ <ProductHeader
576
+ // states
577
+ headerData={state.product}
578
+ productObservation={observation}
579
+ percent={
580
+ state.active_percentage?.percentagesGeneral?.required ?? 0
581
+ }
582
+ activeRetailer={{
583
+ id: state.active_retailer?.id_retailer,
584
+ name: state.active_retailer?.retailer,
585
+ image: state.active_retailer?.image,
586
+ }}
587
+ servicesData={state.services_data ? state.services_data : null}
588
+ isObservationVisible={isObservationVisible}
589
+ // actions
590
+ setActiveRetailer={handleOnChangeActiveRetailer}
591
+ approve={handleOnApproveSingleService}
592
+ approveAll={handleOnApproveAllServices}
593
+ reject={handleOnRejectSingleService}
594
+ rejectAll={handleOnRejectAllServices}
595
+ toggleObservation={handleOnToggleObservation}
596
+ hideObservation={handleOnHideObservation}
597
+ // sendToFacilitator={handleOnSendEvaluationToFacilitator} // No se usa?
598
+ // validations
599
+ showApproveRejectAll={
600
+ canShowBulkEvaluationButtons() ||
601
+ isAuditorAssigned() ||
602
+ isUserAssignedToService()
603
+ }
604
+ showValidationButtons={
605
+ canShowSingleEvaluationButtons() &&
606
+ (isAuditorAssigned() || isUserAssignedToService())
607
+ }
608
+ />
609
+ <TabsBar
610
+ tabsSections={tabsSections}
611
+ imageLayout={imageLayout}
612
+ status={getStatusByCurrentServiceAndRetailer()}
613
+ activeTab={state.active_tab}
614
+ isRetailer={isRetailer}
615
+ assig={state.collaborator_assignations[state.active_tab]}
616
+ version={state.product.version}
617
+ updatedDescriptions={state.updated_descriptions_inputs}
618
+ updatedDatasheets={state.updated_datasheets_inputs}
619
+ updatedImages={state.updated_images_values}
620
+ images={state.services.images}
621
+ // setters
622
+ setActiveTab={handleOnChangeActiveTab}
623
+ setImageLayout={handleOnToggleImageLayout}
624
+ setUpdatedDescriptions={handleOnSetUpdatedDescriptions}
625
+ setUpdatedDatasheets={handleOnSetUpdatedDatasheets}
626
+ setAssignation={handleOnSetAssignation} // No se usa?
627
+ setImages={handleOnSetImages} // No se usa?
628
+ setShowVersionSelector={setShowVersionSelector}
629
+ // actions
630
+ // El botón actúa sobre la bandeja visible: en activos descargaba
631
+ // las imágenes del producto, que no es lo que el usuario pidió.
632
+ downloadImages={
633
+ currentTray === TRAYS.ASSETS
634
+ ? handleOnDownloadAssets
635
+ : handleOnDownloadImages
636
+ }
637
+ askToDeleteImages={handleOnAskToDeleteImages}
638
+ showSaveButton={isAuditorAssigned() || isUserAssignedToService()}
639
+ onClickSave={handleOnClickSave}
640
+ // validations
641
+ canAssign={![7, 8].includes(user.id_role)}
642
+ />
643
+ <div
644
+ className="services-information-container"
645
+ id="services-information-container"
646
+ >
647
+ {state.saving ? (
648
+ <Stack alignItems="center" justifyContent="center" sx={{ py: 6 }}>
649
+ <CircularProgress size={32} sx={{ color: COLORS.magenta_s2 }} />
650
+ </Stack>
651
+ ) : (
652
+ <>
653
+ {state.active_tab === "Descripción" &&
654
+ (state.product?.description_status !== "NS" ? (
655
+ <InputGroup
656
+ activeSection={state.active_tab}
657
+ inputGroup={state.descriptions_inputs[0]}
658
+ updatedDescriptions={state.updated_descriptions_inputs}
659
+ /* El parentesco es del producto, no del servicio: los
660
+ dos endpoints lo devuelven igual. */
661
+ variant={state.services?.datasheets?.variant}
662
+ onRestoreInheritance={handleOnRestoreInheritance}
663
+ articleId={state.product?.id_article}
664
+ version={state.product.version}
665
+ auditInputGroup={auditDescriptions[0]}
666
+ setUpdatedDescriptions={handleOnSetUpdatedDescriptions}
667
+ dinamicHeight={true}
668
+ compare={compare}
669
+ />
670
+ ) : (
671
+ <NoService />
672
+ ))}
673
+ {state.active_tab === "Ficha técnica" &&
674
+ (state.product?.datasheet_status !== "NS" ? (
675
+ <>
676
+ {state.datasheets_inputs[0]?.data?.map(
677
+ (dataGroup, index) => (
678
+ <InputGroup
679
+ key={index + "-" + state.active_retailer.retailer}
680
+ articleId={state.product.id_article}
681
+ version={state.version}
682
+ activeSection={state.active_tab}
683
+ inputGroup={dataGroup}
684
+ dataInputs={state.datasheets_inputs[1]}
685
+ variant={state.services?.datasheets?.variant}
686
+ onRestoreInheritance={handleOnRestoreInheritance}
687
+ auditInputs={auditDatasheets[1]}
688
+ updatedDatasheets={
689
+ state.updated_datasheets_inputs
690
+ }
691
+ setUpdatedDatasheets={
692
+ handleOnSetUpdatedDatasheets
693
+ }
694
+ compare={compare}
695
+ />
696
+ ),
697
+ )}
698
+ </>
699
+ ) : (
700
+ <NoService />
701
+ ))}
702
+ {state.active_tab === "Imágenes" &&
703
+ (state.product?.images_status !== "NS" ? (
704
+ <ImagesSection
705
+ imagesValues={state.images_values}
706
+ selectedImages={state.selected_images}
707
+ imageLayout={imageLayout}
708
+ enableDigitalAssets={enableDigitalAssets}
709
+ currentTray={currentTray}
710
+ setActiveTray={setActiveTray}
711
+ digitalAssets={digitalAssets}
712
+ unsavedAssets={countUnsavedAssets(state)}
713
+ selectedAssets={state.selected_assets}
714
+ setSelectedAssets={handleOnSetSelectedAssets}
715
+ setAllAssetsChecked={handleOnSetAllAssetsChecked}
716
+ canEdit={isAuditorAssigned() || isUserAssignedToService()}
717
+ setCheckAll={handleOnSetCheckAll}
718
+ setSelectedImages={handleOnSetSelectedImages}
719
+ onChangeCurrentImage={handleOnChangeCurrentImage}
720
+ onUpdateImage={(item) =>
721
+ dispatch({ type: "UPDATE_IMAGE_VALUE", payload: item })
722
+ }
723
+ onSelectImage={(item) =>
724
+ dispatch({ type: "SET_SELECTED_IMAGE", payload: item })
725
+ }
726
+ getRootProps={getRootProps}
727
+ getInputProps={getInputProps}
728
+ isDragActive={isDragActive}
729
+ openFilePicker={open}
730
+ />
731
+ ) : (
732
+ <NoService />
733
+ ))}
734
+ </>
735
+ )}
736
+ </div>
737
+ {(isUserAssignedToService(state.active_tab) ||
738
+ isAuditorAssigned()) &&
739
+ state.product[`${getConceptByTab(state.active_tab)}_status`] !==
740
+ "NS" && (
741
+ <CommentBox
742
+ comment={state.comment}
743
+ crossComment={crossComment}
744
+ createComment={createComment}
745
+ token={token}
746
+ dispatch={dispatch}
747
+ showChangeStatus={allowedAccountsToViewChangeStatus.includes(user?.email)}
748
+ onOpenChangeStatus={() => setOpenChangeStatusModal(true)}
749
+ showSendEvaluation={[7, 8].includes(user.id_role)}
750
+ // Solo para los roles 7 y 8, como en la vista original: para
751
+ // el rol 6, `isEvaluationFinished` usa un `srv` que no existe
752
+ // (defecto heredado) y tumbaría la pantalla.
753
+ canSendEvaluation={
754
+ [7, 8].includes(user.id_role) &&
755
+ isEvaluationFinished(user.id_role, state.active_tab, statusArray) &&
756
+ state.missing_required_fields[state.active_tab] === 0
757
+ }
758
+ onSendEvaluation={handleOnSendEvaluationToFacilitator}
759
+ />
760
+ )}
761
+ </div>
762
+ </div>
763
+ </Container>
764
+
765
+ <ReusedModalsStyles />
766
+ <ChangeStatusModal
767
+ state={state}
768
+ open={openChangeStatusModal}
769
+ onClose={() => {
770
+ setOpenChangeStatusModal(false);
771
+ }}
772
+ reloadData={loadData}
773
+ token={token}
774
+ />
775
+ </AiProductEditionProvider>
776
+ );
777
+ };