contentoh-components-library 21.6.61 → 21.6.62

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 (88) hide show
  1. package/.claude/settings.local.json +7 -0
  2. package/dist/ai/prompts/attribute.prompt.js +28 -0
  3. package/dist/ai/prompts/description.prompt.js +33 -0
  4. package/dist/ai/schemas/attribute.schema.js +35 -0
  5. package/dist/ai/schemas/description.schema.js +27 -0
  6. package/dist/components/pages/CreatorsProductEdition/CreatorsProductEditionView.js +11 -5
  7. package/dist/components/pages/CreatorsProductEdition/hooks/useCreatorsProductEdition.js +58 -31
  8. package/dist/components/pages/CreatorsProductEdition/mocks/api.fixtures.js +5 -1
  9. package/dist/components/pages/CreatorsProductEdition/mocks/mockApi.js +3 -40
  10. package/dist/components/pages/CreatorsProductEdition/sections/ImagesSection.js +18 -3
  11. package/dist/components/pages/CreatorsProductEdition/sections/StatusChip.js +4 -13
  12. package/dist/components/pages/CreatorsProductEdition/sections/TabsBar.js +10 -0
  13. package/dist/components/pages/CreatorsProductEdition/stories/CreatorsProductEdition.stories.js +10 -2
  14. package/dist/components/pages/RetailerProductEdition/RetailerProductEdition.stories.js +2 -15
  15. package/dist/components/pages/RetailerProductEdition/index.js +40 -57
  16. package/dist/components/pages/RetailerProductEdition/utils.js +2 -6
  17. package/dist/index.js +0 -13
  18. package/package.json +1 -1
  19. package/src/components/pages/CreatorsProductEdition/CreatorsProductEditionView.jsx +22 -3
  20. package/src/components/pages/CreatorsProductEdition/hooks/useCreatorsProductEdition.js +36 -1
  21. package/src/components/pages/CreatorsProductEdition/mocks/api.fixtures.js +15 -3
  22. package/src/components/pages/CreatorsProductEdition/mocks/mockApi.js +8 -18
  23. package/src/components/pages/CreatorsProductEdition/sections/ImagesSection.jsx +24 -2
  24. package/src/components/pages/CreatorsProductEdition/sections/StatusChip.jsx +4 -8
  25. package/src/components/pages/CreatorsProductEdition/sections/TabsBar.jsx +9 -0
  26. package/src/components/pages/CreatorsProductEdition/stories/CreatorsProductEdition.stories.js +6 -0
  27. package/src/components/pages/RetailerProductEdition/RetailerProductEdition.stories.js +0 -12
  28. package/src/components/pages/RetailerProductEdition/index.js +3 -28
  29. package/src/components/pages/RetailerProductEdition/utils.js +0 -5
  30. package/src/index.js +0 -1
  31. package/dist/components/organisms/ServicesNavigation/ServiceToolbar.js +0 -210
  32. package/dist/components/organisms/ServicesNavigation/ServicesNavigation.stories.js +0 -138
  33. package/dist/components/organisms/ServicesNavigation/ServicesRail.js +0 -705
  34. package/dist/components/organisms/ServicesNavigation/catalog.js +0 -505
  35. package/dist/components/organisms/ServicesNavigation/index.js +0 -337
  36. package/dist/components/organisms/ServicesNavigation/model.js +0 -167
  37. package/dist/components/organisms/ServicesNavigation/ui.js +0 -371
  38. package/dist/components/organisms/ServicesNavigation/views.js +0 -1237
  39. package/dist/components/organisms/ServicesWorkspace/AssigneePicker.js +0 -459
  40. package/dist/components/organisms/ServicesWorkspace/ServicesWorkspace.stories.js +0 -119
  41. package/dist/components/organisms/ServicesWorkspace/demo.js +0 -137
  42. package/dist/components/organisms/ServicesWorkspace/index.js +0 -813
  43. package/dist/components/organisms/ServicesWorkspace/servicesModel.js +0 -351
  44. package/dist/components/organisms/ServicesWorkspace/tooltipStyles.js +0 -37
  45. package/dist/components/pages/CreatorsProductEdition/mocks/productServices.fixtures.js +0 -387
  46. package/dist/components/pages/ProductEdition/ProductEdition.stories.js +0 -148
  47. package/dist/components/pages/ProductEdition/ProductEditionView.js +0 -1355
  48. package/dist/components/pages/ProductEdition/ServiceHeader.js +0 -142
  49. package/dist/components/pages/ProductEdition/api.js +0 -256
  50. package/dist/components/pages/ProductEdition/index.js +0 -37
  51. package/dist/components/pages/ProductEdition/permissions.js +0 -200
  52. package/dist/components/pages/ProductEdition/permissions.test.js +0 -320
  53. package/dist/components/pages/ProductEdition/sections/CatalogServicePanel.js +0 -237
  54. package/dist/components/pages/ProductEdition/sections/EditionTopBar.js +0 -174
  55. package/dist/components/pages/ProductEdition/sections/ImagePanel.js +0 -440
  56. package/dist/components/pages/ProductEdition/sections/ServiceTeam.js +0 -275
  57. package/dist/components/pages/ProductEdition/sections/ServiceTools.js +0 -186
  58. package/dist/components/pages/ProductEdition/services.js +0 -233
  59. package/dist/components/pages/ProductEdition/services.test.js +0 -195
  60. package/src/components/organisms/ServicesNavigation/ServiceToolbar.jsx +0 -143
  61. package/src/components/organisms/ServicesNavigation/ServicesNavigation.stories.js +0 -101
  62. package/src/components/organisms/ServicesNavigation/ServicesRail.jsx +0 -591
  63. package/src/components/organisms/ServicesNavigation/catalog.js +0 -180
  64. package/src/components/organisms/ServicesNavigation/index.jsx +0 -232
  65. package/src/components/organisms/ServicesNavigation/model.js +0 -85
  66. package/src/components/organisms/ServicesNavigation/ui.jsx +0 -229
  67. package/src/components/organisms/ServicesNavigation/views.jsx +0 -705
  68. package/src/components/organisms/ServicesWorkspace/AssigneePicker.js +0 -348
  69. package/src/components/organisms/ServicesWorkspace/ServicesWorkspace.stories.js +0 -61
  70. package/src/components/organisms/ServicesWorkspace/demo.js +0 -58
  71. package/src/components/organisms/ServicesWorkspace/index.js +0 -556
  72. package/src/components/organisms/ServicesWorkspace/servicesModel.js +0 -203
  73. package/src/components/organisms/ServicesWorkspace/tooltipStyles.js +0 -27
  74. package/src/components/pages/CreatorsProductEdition/mocks/productServices.fixtures.js +0 -175
  75. package/src/components/pages/ProductEdition/ProductEdition.stories.js +0 -106
  76. package/src/components/pages/ProductEdition/ProductEditionView.jsx +0 -1142
  77. package/src/components/pages/ProductEdition/ServiceHeader.jsx +0 -104
  78. package/src/components/pages/ProductEdition/api.js +0 -84
  79. package/src/components/pages/ProductEdition/index.js +0 -21
  80. package/src/components/pages/ProductEdition/permissions.js +0 -128
  81. package/src/components/pages/ProductEdition/permissions.test.js +0 -249
  82. package/src/components/pages/ProductEdition/sections/CatalogServicePanel.jsx +0 -123
  83. package/src/components/pages/ProductEdition/sections/EditionTopBar.jsx +0 -134
  84. package/src/components/pages/ProductEdition/sections/ImagePanel.jsx +0 -342
  85. package/src/components/pages/ProductEdition/sections/ServiceTeam.jsx +0 -184
  86. package/src/components/pages/ProductEdition/sections/ServiceTools.jsx +0 -134
  87. package/src/components/pages/ProductEdition/services.js +0 -187
  88. package/src/components/pages/ProductEdition/services.test.js +0 -88
@@ -1,1142 +0,0 @@
1
- /**
2
- * Edición de producto unificada (ProductEdition).
3
- *
4
- * Una sola pantalla para las dos aplicaciones; lo que cambia entre ellas son
5
- * los permisos (ver `permissions.js`). FASE 1: Creators (equipo Content-oh).
6
- *
7
- * Parte de la vista responsiva de CreatorsProductEdition (Jorge Alvarado) y
8
- * reutiliza su hook y sus secciones. Lo nuevo:
9
- * - Lista lateral de servicios, a la derecha, en lugar de la barra de
10
- * pestañas: solo los servicios contratados, «Míos / Todos», Resumen y
11
- * punto rosa en lo que le toca a cada quien (organisms/ServicesNavigation).
12
- * - Encabezado del servicio abierto (ServiceHeader): grupo, nombre y
13
- * estatus; equipo con nombre y rol (ServiceTeam) y herramientas sin el
14
- * botón de ayuda (ServiceTools).
15
- * - Barra superior delgada con el cierre siempre visible (EditionTopBar) y
16
- * panel de imagen en tarjeta (sections/ImagePanel).
17
- * - Las reglas de quién puede guardar, asignar, evaluar o comentar salen de
18
- * `permissions.js` en lugar de vivir dentro de la vista.
19
- */
20
- import { useCallback, useEffect, useMemo, useRef, useState } from "react";
21
- import axios from "axios";
22
- import {
23
- Skeleton,
24
- Box,
25
- Stack,
26
- Typography,
27
- CircularProgress,
28
- Button as MuiButton,
29
- Dialog,
30
- DialogActions,
31
- DialogContent,
32
- DialogContentText,
33
- DialogTitle,
34
- Drawer,
35
- useMediaQuery,
36
- } from "@mui/material";
37
-
38
- import { getConceptByTab } from "../RetailerProductEdition/utils";
39
- import { countUnsavedAssets } from "../../../global-files/digital-assets/assets.reducer";
40
-
41
-
42
- import { TRAYS } from "../../molecules/GalleryTrayTabs";
43
-
44
- import { InputGroup } from "../../organisms/InputGroup";
45
- import { LinkVariantsModal } from "../../organisms/LinkVariantsModal";
46
- import { UnlinkVariantModal } from "../../organisms/UnlinkVariantModal";
47
- import { VariantGroupModal } from "../../organisms/VariantGroupModal";
48
- import { PropagateChangesModal } from "../../organisms/PropagateChangesModal";
49
- import ChangeStatusModal from "../../organisms/ChangeStatusModal";
50
-
51
- import { AiProductEditionProvider } from "../../../contexts/AiProductEdition";
52
-
53
- import errorIcon from "../../../assets/images/genericModal/errorModal.svg";
54
-
55
- import { useCreatorsProductEdition } from "../CreatorsProductEdition/hooks/useCreatorsProductEdition";
56
- import { allowedAccountsToViewChangeStatus } from "../CreatorsProductEdition/constants";
57
- import { Container, ReusedModalsStyles } from "../CreatorsProductEdition/styles";
58
- import { ProductHeader } from "../CreatorsProductEdition/sections/ProductHeader";
59
- import { ImagePanel } from "./sections/ImagePanel";
60
- import { EditionTopBar } from "./sections/EditionTopBar";
61
- import { ServiceTeam } from "./sections/ServiceTeam";
62
- import { ServiceTools } from "./sections/ServiceTools";
63
- import { CatalogServicePanel } from "./sections/CatalogServicePanel";
64
- import { ImagesSection } from "../CreatorsProductEdition/sections/ImagesSection";
65
- import { VariantsPanel } from "../CreatorsProductEdition/sections/VariantsPanel";
66
- import { CommentBox } from "../CreatorsProductEdition/sections/CommentBox";
67
- import { NoticeDialog } from "../CreatorsProductEdition/sections/NoticeDialog";
68
- import { RejectDialog } from "../CreatorsProductEdition/sections/RejectDialog";
69
- import { ImageGallery } from "../CreatorsProductEdition/sections/ImageGallery";
70
- import { VersionDrawer } from "../CreatorsProductEdition/sections/VersionDrawer";
71
- import { COLORS, FONTS } from "../CreatorsProductEdition/sections/tokens";
72
-
73
- import { ServicesRail } from "../../organisms/ServicesNavigation/ServicesRail";
74
- import { ServiceView } from "../../organisms/ServicesNavigation/views";
75
- import {
76
- SUMMARY,
77
- defaultFilter,
78
- hasUsefulFilter,
79
- initialSelection,
80
- isPendingFor as isServicePending,
81
- } from "../../organisms/ServicesNavigation/model";
82
- import { ServiceHeader } from "./ServiceHeader";
83
- import { buildEditionServices, buildServicesFromApi } from "./services";
84
- import { assignService, fetchAssignables, fetchProductServices, setServiceStatus } from "./api";
85
- import {
86
- canAssign,
87
- canChangeStatus,
88
- canEvaluate,
89
- canSave,
90
- canSendToFacilitator,
91
- serviceOfTab,
92
- } from "./permissions";
93
-
94
- // Lado de la lista de servicios; en celular, el panel sale del mismo lado.
95
- const RAIL_SIDE = "right";
96
-
97
- // Aviso cuando el producto no tiene contratado el servicio de la pestaña.
98
- const NoService = () => (
99
- <Typography sx={{ fontFamily: FONTS.Raleway, fontSize: 14, color: COLORS.s5, py: 1 }}>
100
- No cuentas con este servicio
101
- </Typography>
102
- );
103
-
104
- export const ProductEditionView = ({
105
- // «creators» (equipo Content-oh). «management» (proveedor y cadena) llega
106
- // en la fase 2; mientras tanto se trata igual que «creators».
107
- app = "creators",
108
- // Solo para historias de Storybook: servicios de muestra (opcional).
109
- demoServices,
110
- tabsSections,
111
- productSelected = {},
112
- user = {},
113
- token,
114
- location,
115
- enableDigitalAssets = false,
116
- }) => {
117
- const {
118
- isRetailer,
119
- digitalAssets,
120
- currentTray,
121
- assetsLoadedFor,
122
- handleOnSetSelectedAssets,
123
- handleOnSetAllAssetsChecked,
124
- initializeProduct,
125
- loadAuditableData,
126
- loadData,
127
- handleOnChangeCurrentImage,
128
- handleOnShowModalGallery,
129
- handleOnChangeActiveRetailer,
130
- handleOnChangeActiveTab,
131
- handleOnToggleImageLayout,
132
- handleOnToggleObservation,
133
- handleOnHideObservation,
134
- isAuditorAssigned,
135
- isAssignedAs,
136
- isUserAssignedToService,
137
- canShowSingleEvaluationButtons,
138
- canShowBulkEvaluationButtons,
139
- getStatusByCurrentServiceAndRetailer,
140
- handleOnSaveAssets,
141
- handleOnClickSaveImages,
142
- loadFamily,
143
- hijosDelProducto,
144
- esHijoDeOtro,
145
- puedeAgruparse,
146
- handleOnUnlinked,
147
- loadVariantGroup,
148
- handleOnLeaveGroup,
149
- openFamilyMember,
150
- handleOnLinkedVariants,
151
- handleOnRestoreInheritance,
152
- cambiosDeFichaTecnica,
153
- cambiosDeDescripciones,
154
- handleOnClickSave,
155
- handleOnSetUpdatedDescriptions,
156
- handleOnSetUpdatedDatasheets,
157
- handleOnDownloadAssets,
158
- handleOnAskToDeleteAssets,
159
- handleOnAskToDeleteImages,
160
- handleOnApproveSingleService,
161
- handleOnRejectSingleService,
162
- sendSingleEvaluation,
163
- handleOnApproveAllServices,
164
- handleOnRejectAllServices,
165
- sendBulkEvaluation,
166
- handleOnChangeAssignations,
167
- handleOnSetAssignation,
168
- handleOnSetImages,
169
- handleOnSetCheckAll,
170
- handleOnSetSelectedImages,
171
- handleOnSubmitComment,
172
- handleOnSendEvaluationToFacilitator,
173
- isEvaluationFinished,
174
- handleOnChangeProductVersion,
175
- handleRetryLoad,
176
- showVersionSelector,
177
- setShowVersionSelector,
178
- auditableVersion,
179
- setAuditableVersion,
180
- headerTop,
181
- setHeaderTop,
182
- shotThd,
183
- setShotThd,
184
- observation,
185
- setObservation,
186
- showRejectModal,
187
- setShowRejectModal,
188
- isObservationVisible,
189
- setObservationVisible,
190
- imageLayout,
191
- setImageLayout,
192
- crossComment,
193
- setCrossComment,
194
- statusArray,
195
- setStatusArray,
196
- rejectAll,
197
- setRejectAll,
198
- showLinkVariants,
199
- setShowLinkVariants,
200
- family,
201
- setFamily,
202
- showFamily,
203
- setShowFamily,
204
- loadingVariant,
205
- setLoadingVariant,
206
- variantGroup,
207
- setVariantGroup,
208
- showGroup,
209
- setShowGroup,
210
- showGroupModal,
211
- setShowGroupModal,
212
- porDesvincular,
213
- setPorDesvincular,
214
- avisoFamilia,
215
- setAvisoFamilia,
216
- pendingPropagation,
217
- setPendingPropagation,
218
- preparandoPropagacion,
219
- setPreparandoPropagacion,
220
- auditServices,
221
- setAuditServices,
222
- auditDatasheets,
223
- setAuditDatasheets,
224
- auditDescriptions,
225
- setAuditDescriptions,
226
- showModal,
227
- setShowModal,
228
- compare,
229
- setCompare,
230
- openChangeStatusModal,
231
- setOpenChangeStatusModal,
232
- assetNotices,
233
- setAssetNotices,
234
- activeTray,
235
- setActiveTray,
236
- state,
237
- dispatch,
238
- saveDatasheets,
239
- saveDescriptions,
240
- updateImages,
241
- deleteImages,
242
- saveAssets,
243
- registerUploadedAssets,
244
- handleOnDownloadImages,
245
- createComment,
246
- getRootProps,
247
- getInputProps,
248
- isDragActive,
249
- open,
250
- } = useCreatorsProductEdition({
251
- tabsSections,
252
- productSelected,
253
- user,
254
- token,
255
- location,
256
- enableDigitalAssets,
257
- });
258
-
259
- /* ---------------------------------------------------------------------- */
260
- /* Navegación por servicios */
261
- /* ---------------------------------------------------------------------- */
262
-
263
- useEffect(() => {
264
- if (app !== "creators") {
265
- console.warn(
266
- `[ProductEdition] app «${app}»: los permisos de Content Management llegan en la fase 2; por ahora se usan los de Creators.`,
267
- );
268
- }
269
- }, [app]);
270
-
271
- const isDesktop = useMediaQuery("(min-width:900px)");
272
-
273
- // Auditores, para poner nombre al auditor del producto en el equipo del
274
- // servicio (`collaborator_assignations` solo trae especialistas y
275
- // coordinadores). Es la misma consulta que hace `fetchUsers` para el rol 6.
276
- const [auditors, setAuditors] = useState([]);
277
- useEffect(() => {
278
- if (!token) return undefined;
279
- let alive = true;
280
- axios
281
- .get(`${process.env.REACT_APP_USER_ENDPOINT}?fetchRole=6`, { headers: { Authorization: token } })
282
- .then((response) => alive && setAuditors(response.data?.data ?? []))
283
- .catch(() => {});
284
- return () => {
285
- alive = false;
286
- };
287
- }, [token]);
288
-
289
- /*
290
- Servicios del producto desde el backend (GET /product-services), leídos de
291
- las columnas del catálogo de rel_order_services: los de siempre y los
292
- nuevos, con su estatus, su equipo y lo que la persona puede hacer.
293
-
294
- Se vuelven a pedir cuando el flujo actual cambia el producto o sus estatus
295
- (evaluar, asignar, guardar), para que la lista no se quede atrás. Si el
296
- endpoint no responde, la lista se arma como antes, con los campos del
297
- producto (buildEditionServices). Las historias usan `demoServices`.
298
- */
299
- const [productServices, setProductServices] = useState(null);
300
- const productServicesRequest = useRef(0);
301
- const articleId = state.product?.id_article;
302
- const orderId = state.product?.id_order;
303
-
304
- const refreshProductServices = useCallback(async () => {
305
- if (!token || !articleId || !orderId || demoServices) return;
306
- productServicesRequest.current += 1;
307
- const request = productServicesRequest.current;
308
- try {
309
- const data = await fetchProductServices({ articleId, orderId, token });
310
- // Solo la respuesta más reciente (un producto que se abrió después gana),
311
- // y solo si trae la lista: si no, se queda la que arma el producto.
312
- if (request === productServicesRequest.current && Array.isArray(data?.services)) {
313
- setProductServices(data);
314
- }
315
- } catch (error) {
316
- console.warn("[ProductEdition] servicios del producto:", error?.message);
317
- }
318
- }, [token, articleId, orderId, demoServices]);
319
-
320
- useEffect(() => {
321
- setProductServices(null);
322
- }, [articleId, orderId]);
323
-
324
- useEffect(() => {
325
- refreshProductServices();
326
- }, [refreshProductServices, state.product, state.services_data]);
327
-
328
- const services = useMemo(
329
- () =>
330
- productServices
331
- ? buildServicesFromApi({
332
- productServices,
333
- user,
334
- activeRetailerId: state.active_retailer?.id_retailer,
335
- })
336
- : buildEditionServices({
337
- product: state.product,
338
- user,
339
- servicesData: state.services_data,
340
- activeRetailerId: state.active_retailer?.id_retailer,
341
- collaboratorAssignations: state.collaborator_assignations,
342
- auditors,
343
- demoServices,
344
- }),
345
- [
346
- productServices,
347
- state.product,
348
- user,
349
- state.services_data,
350
- state.active_retailer,
351
- state.collaborator_assignations,
352
- auditors,
353
- demoServices,
354
- ],
355
- );
356
- const single = services.length === 1;
357
- const showFilter = hasUsefulFilter(services, user?.id_user);
358
-
359
- // `null` hasta que cargan los servicios; después, el servicio abierto o SUMMARY.
360
- const [selected, setSelected] = useState(null);
361
- const [filter, setFilter] = useState("all");
362
- const [railCollapsed, setRailCollapsed] = useState(false);
363
- const [railOpen, setRailOpen] = useState(false);
364
- const [pendingSelection, setPendingSelection] = useState(null);
365
-
366
- /*
367
- Al cargar se abre, en este orden:
368
- 1. el único servicio, si solo hay uno;
369
- 2. lo primero que le toca a la persona;
370
- 3. su primer servicio, si trabaja solo algunos (un especialista);
371
- 4. el Resumen (quien ve todo: administración, coordinación, auditoría).
372
- */
373
- useEffect(() => {
374
- // Se espera a `services_data`: antes, los estatus son los generales del
375
- // producto y no los de la cadena activa, y se elegiría con datos viejos.
376
- if (selected !== null || services.length === 0 || !state.services_data) return;
377
- let first = initialSelection(services, user?.id_user);
378
- const mine = services.filter((item) => item.mine);
379
- if (first === SUMMARY && mine.length > 0 && mine.length < services.length) {
380
- first = mine[0].service_name;
381
- }
382
- setFilter(defaultFilter(services, user?.id_user));
383
- openService(first);
384
- // eslint-disable-next-line react-hooks/exhaustive-deps
385
- }, [services, state.services_data]);
386
-
387
- // Si el hook cambia de pestaña por su cuenta, la lista lo sigue.
388
- useEffect(() => {
389
- const current = services.find((item) => item.service_name === selected);
390
- const fromTab = serviceOfTab(state.active_tab);
391
- if (current?.tab && fromTab && fromTab !== selected) setSelected(fromTab);
392
- // eslint-disable-next-line react-hooks/exhaustive-deps
393
- }, [state.active_tab]);
394
-
395
- const selectedService = services.find((item) => item.service_name === selected) ?? null;
396
- const isSummary = selected === SUMMARY;
397
- const isClassic = Boolean(selectedService?.tab);
398
- // Servicio del catálogo (o de siempre sin editor) que llegó del backend.
399
- const catalogService = !isClassic && !isSummary && selectedService?.api ? selectedService : null;
400
-
401
- // A quién se puede asignar en el servicio del catálogo abierto (view=assignables).
402
- const [assignables, setAssignables] = useState({});
403
- const catalogServiceName = catalogService?.service_name;
404
- const catalogCanAssign = Boolean(catalogService?.api?.actions?.assign);
405
- useEffect(() => {
406
- setAssignables({});
407
- if (!catalogServiceName || !catalogCanAssign || !token) return undefined;
408
- let alive = true;
409
- const roles = (catalogService.api.roles ?? []).filter((role) => role === "especialist" || role === "facilitator");
410
- Promise.all(
411
- roles.map((role) =>
412
- fetchAssignables({ serviceName: catalogServiceName, role, token })
413
- .then((users) => [role, users])
414
- .catch(() => [role, []]),
415
- ),
416
- ).then((entries) => alive && setAssignables(Object.fromEntries(entries)));
417
- return () => {
418
- alive = false;
419
- };
420
- // eslint-disable-next-line react-hooks/exhaustive-deps
421
- }, [catalogServiceName, catalogCanAssign, token]);
422
-
423
- const handleOnSetCatalogStatus = async (statusName, retailerIds) => {
424
- await setServiceStatus({
425
- articleId,
426
- orderId,
427
- serviceName: catalogServiceName,
428
- statusName,
429
- retailerIds,
430
- token,
431
- });
432
- await refreshProductServices();
433
- };
434
-
435
- const handleOnAssignCatalogService = async (role, userId) => {
436
- try {
437
- await assignService({ articleId, orderId, serviceName: catalogServiceName, role, userId, token });
438
- } catch (error) {
439
- console.warn("[ProductEdition] asignar servicio:", error?.message);
440
- }
441
- await refreshProductServices();
442
- };
443
- const activeConcept = serviceOfTab(state.active_tab);
444
-
445
- // Permisos del servicio abierto (ver permissions.js).
446
- const userCanSave = canSave(state.product, user, activeConcept);
447
- const userCanEvaluate =
448
- Boolean(state.product && state.services_data) &&
449
- canEvaluate(state.product, user, activeConcept, getStatusByCurrentServiceAndRetailer());
450
-
451
- const hasUnsavedChanges = () =>
452
- state.updated_descriptions_inputs?.length > 0 ||
453
- state.updated_datasheets_inputs?.length > 0 ||
454
- state.updated_images_values?.length > 0 ||
455
- state.services?.images?.values?.filter((file) => !file.id)?.length > 0;
456
-
457
- function openService(name) {
458
- const target = services.find((item) => item.service_name === name);
459
- if (target?.tab && target.tab !== state.active_tab) handleOnChangeActiveTab(target.tab);
460
- setSelected(name);
461
- setRailOpen(false);
462
- }
463
-
464
- // Igual que la barra de pestañas: cambiar con cambios sin guardar pregunta.
465
- const requestService = (name) => {
466
- if (name === selected) return;
467
- if (hasUnsavedChanges()) setPendingSelection(name);
468
- else openService(name);
469
- };
470
-
471
- const confirmServiceChange = () => {
472
- handleOnSetUpdatedDescriptions && handleOnSetUpdatedDescriptions([]);
473
- handleOnSetUpdatedDatasheets && handleOnSetUpdatedDatasheets([]);
474
- handleOnSetImages && handleOnSetImages({});
475
- openService(pendingSelection);
476
- setPendingSelection(null);
477
- };
478
-
479
- const pendingCount = services.filter((item) => isServicePending(item, user?.id_user)).length;
480
-
481
- const renderRail = (variant) => (
482
- <ServicesRail
483
- variant={variant}
484
- services={services}
485
- selected={selected ?? SUMMARY}
486
- onSelect={requestService}
487
- userId={user?.id_user}
488
- filter={filter}
489
- onFilterChange={setFilter}
490
- showFilter={showFilter}
491
- collapsed={railCollapsed}
492
- onToggleCollapsed={() => setRailCollapsed((value) => !value)}
493
- onClose={variant === "drawer" ? () => setRailOpen(false) : undefined}
494
- side={RAIL_SIDE}
495
- />
496
- );
497
-
498
- const ProductEditionSkeleton = () => {
499
- return (
500
- <Container headerTop={0}>
501
- <Box
502
- px={1.5}
503
- py={1}
504
- pb={0}
505
- display="flex"
506
- alignItems="center"
507
- justifyContent="space-between"
508
- >
509
- <Skeleton variant="text" width="min(300px, 60%)" height={40} />
510
- <Stack direction="row" spacing={2}>
511
- <Skeleton variant="circular" width={32} height={32} />
512
- </Stack>
513
- </Box>
514
-
515
- <div className="data-container">
516
- <div
517
- className="image-data-panel"
518
- style={{ display: "flex", flexDirection: "column", gap: "20px" }}
519
- >
520
- <Skeleton variant="rounded" width="100%" height={320} />
521
-
522
- <Stack direction="row" spacing={2} justifyContent="center">
523
- {[1, 2, 3, 4].map((item) => (
524
- <Skeleton key={item} variant="rounded" width={60} height={80} />
525
- ))}
526
- </Stack>
527
-
528
- <Box mt={2}>
529
- {[1, 2, 3, 4].map((item) => (
530
- <Box
531
- key={item}
532
- display="flex"
533
- justifyContent="space-between"
534
- py={1}
535
- borderBottom="1px solid #f0f0f0"
536
- >
537
- <Skeleton variant="text" width="40%" />
538
- <Skeleton variant="text" width="20%" />
539
- </Box>
540
- ))}
541
- </Box>
542
- </div>
543
-
544
- <div
545
- className="product-information"
546
- style={{
547
- flex: 1,
548
- display: "flex",
549
- flexDirection: "column",
550
- gap: "14px",
551
- }}
552
- >
553
- <Box>
554
- <Skeleton variant="text" width="80%" height={50} />
555
- <Stack direction="row" spacing={2} alignItems="center">
556
- <Skeleton variant="rounded" width="30%" height={24} />
557
- <Skeleton variant="rounded" width="45%" height={24} />
558
- </Stack>
559
- </Box>
560
-
561
- <Box display="flex" gap={4} pb={1}>
562
- <Skeleton variant="text" width="28%" height={45} />
563
- <Skeleton variant="text" width="28%" height={45} />
564
- <Skeleton variant="text" width="28%" height={45} />
565
- </Box>
566
-
567
- <Box display="flex" flexDirection="column" gap={4}>
568
- {[1, 2, 3].map((item) => (
569
- <Box key={item}>
570
- <Box
571
- display="flex"
572
- justifyContent="space-between"
573
- alignItems="center"
574
- mb={1}
575
- >
576
- <Skeleton variant="text" width="30%" height={24} />
577
- <Skeleton
578
- variant="rounded"
579
- width="min(175px, 35%)"
580
- height={25}
581
- sx={{ borderRadius: "7.5px" }}
582
- />
583
- </Box>
584
- <Skeleton variant="rounded" width="100%" height={70} />
585
- <Box display="flex" justifyContent="flex-end" mt={0.5}>
586
- <Skeleton variant="text" width={60} />
587
- </Box>
588
- </Box>
589
- ))}
590
- </Box>
591
-
592
- <Box mt="auto" pt={4}>
593
- <Skeleton variant="rounded" width="100%" height={120} />
594
- <Box display="flex" justifyContent="flex-end" gap={2} mt={2}>
595
- <Skeleton
596
- variant="rounded"
597
- width="min(180px, 45%)"
598
- height={45}
599
- sx={{ borderRadius: "24px" }}
600
- />
601
- <Skeleton
602
- variant="rounded"
603
- width="min(180px, 45%)"
604
- height={45}
605
- sx={{ borderRadius: "24px" }}
606
- />
607
- </Box>
608
- </Box>
609
- </div>
610
- </div>
611
- </Container>
612
- );
613
- };
614
-
615
- // Si la carga falló y no tenemos servicios, mostramos un estado de error con
616
- // opción de reintentar en vez de quedarnos colgados en el skeleton.
617
- if (!state.loading && state.errors?.length > 0 && !state.services)
618
- return (
619
- <Container headerTop={0}>
620
- <Box
621
- display="flex"
622
- flexDirection="column"
623
- alignItems="center"
624
- justifyContent="center"
625
- gap={2}
626
- sx={{ minHeight: "60vh", textAlign: "center", padding: "40px" }}
627
- >
628
- <img src={errorIcon} alt="error" width={64} height={64} />
629
- <Typography sx={{ fontFamily: FONTS.Raleway, fontSize: 16, color: COLORS.s5 }}>
630
- No pudimos cargar el producto
631
- </Typography>
632
- <Typography sx={{ color: "#666", maxWidth: 420, fontSize: 14 }}>
633
- {state.errors[0] ||
634
- "Ocurrió un error al cargar la información del producto."}
635
- </Typography>
636
- <MuiButton
637
- variant="contained"
638
- disableElevation
639
- onClick={handleRetryLoad}
640
- sx={{
641
- textTransform: "none",
642
- borderRadius: "20px",
643
- px: 4,
644
- bgcolor: COLORS.magenta_s2,
645
- "&:hover": { bgcolor: COLORS.secondary_magenta },
646
- }}
647
- >
648
- Reintentar
649
- </MuiButton>
650
- </Box>
651
- </Container>
652
- );
653
-
654
- /*
655
- `images_values` entra al guard junto a `services` y `product`.
656
-
657
- React 17 no agrupa los dispatch que van después de un `await`: cada uno
658
- re-renderiza al instante. En `loadData` el orden es SET_SERVICES →
659
- …_INPUTS → SET_IMAGES_VALUES, así que había renders intermedios con
660
- `services` ya puesto —el guard los dejaba pasar— y el resto todavía en null.
661
- El cuerpo de abajo lee `datasheets_inputs[1]`, `descriptions_inputs[0]` y
662
- `images_values.values` sin protección, y tumbaba la pantalla entera.
663
-
664
- Se comprueba `images_values` y no cada uno: es el ÚLTIMO que `loadData`
665
- despacha, así que si ya está, los anteriores también.
666
- */
667
- if (
668
- state.loading ||
669
- !state.services ||
670
- !state.product ||
671
- !state.images_values
672
- )
673
- return (
674
- <>
675
- <ProductEditionSkeleton />
676
- </>
677
- );
678
-
679
- return (
680
- <AiProductEditionProvider
681
- isCreatorsEdition={true}
682
- user={user}
683
- token={token}
684
- state={state}
685
- >
686
- <Box sx={{ display: "flex", flex: 1, minWidth: 0, height: "100%", overflow: "hidden" }}>
687
- <Container headerTop={headerTop}>
688
- {pendingPropagation && (
689
- <PropagateChangesModal
690
- changedAttributes={pendingPropagation.cambios}
691
- kind={pendingPropagation.kind ?? "datasheet"}
692
- variants={
693
- esHijoDeOtro
694
- ? []
695
- : (family?.variants ?? []).filter((v) => !v.isCurrent)
696
- }
697
- groupMembers={(variantGroup?.members ?? []).filter(
698
- (m) => !m.isCurrent,
699
- )}
700
- groupCode={variantGroup?.group ?? null}
701
- saving={state.saving}
702
- onCancel={() => setPendingPropagation(null)}
703
- onConfirm={(propagation) => {
704
- const esDescripcion = pendingPropagation.kind === "description";
705
- setPendingPropagation(null);
706
-
707
- if (!propagation) {
708
- if (esDescripcion) saveDescriptions(token);
709
- else saveDatasheets(token);
710
- return;
711
- }
712
-
713
- /*
714
- El diálogo habla de «atributos» porque es el mismo componente
715
- para las dos pestañas. Al guardar hay que ponerlo en la lista
716
- que corresponde: el servidor recibe `attributes` o
717
- `descriptions`, nunca una por la otra.
718
- */
719
- const { attributes, ...destinos } = propagation;
720
-
721
- if (esDescripcion) {
722
- saveDescriptions(token, { ...destinos, descriptions: attributes });
723
- } else {
724
- saveDatasheets(token, propagation);
725
- }
726
- }}
727
- />
728
- )}
729
- {showLinkVariants && (
730
- <LinkVariantsModal
731
- articleId={state.product?.id_article}
732
- articleName={state.product?.name}
733
- articleUpc={state.product?.upc}
734
- activeRetailerId={state.active_retailer?.id_retailer}
735
- token={token}
736
- onClose={() => setShowLinkVariants(false)}
737
- onLinked={handleOnLinkedVariants}
738
- />
739
- )}
740
- {showGroupModal && (
741
- <VariantGroupModal
742
- articleId={state.product?.id_article}
743
- articleName={state.product?.name}
744
- articleUpc={state.product?.upc}
745
- currentGroup={variantGroup?.group ?? null}
746
- token={token}
747
- onClose={() => setShowGroupModal(false)}
748
- onSaved={(result) => {
749
- setVariantGroup(result);
750
- setShowGroup(true);
751
- }}
752
- />
753
- )}
754
- {porDesvincular && (
755
- <UnlinkVariantModal
756
- {...porDesvincular}
757
- token={token}
758
- onClose={() => setPorDesvincular(null)}
759
- onUnlinked={handleOnUnlinked}
760
- />
761
- )}
762
- <RejectDialog
763
- open={showRejectModal}
764
- rejectAll={rejectAll}
765
- onCancel={() => setShowRejectModal(false)}
766
- onConfirm={async (message) => {
767
- await createComment(message, token);
768
- rejectAll ? sendBulkEvaluation("R") : sendSingleEvaluation("R");
769
- setShowRejectModal(false);
770
- }}
771
- />
772
- <NoticeDialog
773
- modal={state.modal}
774
- onClose={() =>
775
- dispatch({
776
- type: "SET_MODAL",
777
- payload: { show: false, title: "", message: "", image: null },
778
- })
779
- }
780
- />
781
- {showModal && (
782
- <ImageGallery
783
- images={state.images_values}
784
- initialIndex={state.current_image}
785
- setShowModal={setShowModal}
786
- approveRejectButtons={canShowSingleEvaluationButtons()}
787
- sendToFacilitator={handleOnSendEvaluationToFacilitator}
788
- />
789
- )}
790
- <VersionDrawer
791
- open={showVersionSelector}
792
- modalId={"version-selector"}
793
- articleId={state.product.id_article}
794
- setVersion={handleOnChangeProductVersion}
795
- companyName={state.product.company}
796
- currentVersion={state.product.version}
797
- setShowVersionSelector={setShowVersionSelector}
798
- jwt={token}
799
- />
800
- <EditionTopBar
801
- setHeaderTop={setHeaderTop}
802
- auditableVersion={auditableVersion}
803
- setCompare={setCompare}
804
- isAuditor={[1, 6].includes(user.id_role)}
805
- withChat={location?.state?.withChat}
806
- chatType={location?.state?.chatType}
807
- productSelected={state.product}
808
- token={token}
809
- activeRetailer={{
810
- id: state.active_retailer?.id_retailer,
811
- name: state.active_retailer?.retailer,
812
- }}
813
- />
814
- <div className="data-container">
815
- <div className="image-data-panel">
816
- <ImagePanel
817
- /*
818
- `images_values` puede ser null en pleno render: React 17 no
819
- agrupa los dispatch que van después de un `await` y hay un render
820
- con `services` puesto y las imágenes todavía no (ver el guard de
821
- arriba). Por eso las interrogaciones.
822
- */
823
- imagesArray={state.images_values || []}
824
- activeImage={state.images_values?.values?.[state.current_image] ?? {}}
825
- currentIndex={state.current_image}
826
- setActiveImage={handleOnChangeCurrentImage}
827
- setShowModal={handleOnShowModalGallery}
828
- // datos de la imagen
829
- lists={state.images_values || []}
830
- assignationsImages={state.collaborator_assignations["Imágenes"]}
831
- imagesStatus={state.product?.images_status || "-"}
832
- retailerSelected={state.active_retailer?.id_retailer}
833
- isRetailer={isRetailer}
834
- version={state.product.version}
835
- shotThd={shotThd}
836
- setShowVersionSelector={setShowVersionSelector}
837
- setImages={handleOnChangeCurrentImage}
838
- setAssignation={handleOnChangeAssignations}
839
- onClickSave={handleOnClickSaveImages}
840
- showSaveButton={userCanSave}
841
- />
842
- </div>
843
-
844
- <div className="product-information">
845
- <ProductHeader
846
- // states
847
- headerData={state.product}
848
- productObservation={observation}
849
- percent={
850
- state.active_percentage?.percentagesGeneral?.required ?? 0
851
- }
852
- activeRetailer={{
853
- id: state.active_retailer?.id_retailer,
854
- name: state.active_retailer?.retailer,
855
- image: state.active_retailer?.image,
856
- }}
857
- servicesData={state.services_data ? state.services_data : null}
858
- isObservationVisible={isObservationVisible}
859
- // actions
860
- setActiveRetailer={handleOnChangeActiveRetailer}
861
- approve={handleOnApproveSingleService}
862
- approveAll={handleOnApproveAllServices}
863
- reject={handleOnRejectSingleService}
864
- rejectAll={handleOnRejectAllServices}
865
- toggleObservation={handleOnToggleObservation}
866
- hideObservation={handleOnHideObservation}
867
- // sendToFacilitator={handleOnSendEvaluationToFacilitator} // No se usa?
868
- // validations
869
- // La evaluación de todos los servicios a la vez sigue en el hook
870
- // (canShowBulkEvaluationButtons); pasa a permissions.js en la fase 2.
871
- showApproveRejectAll={canShowBulkEvaluationButtons() || userCanSave}
872
- // Validar o rechazar aplica al servicio abierto con editor: no en
873
- // el Resumen ni en los servicios sin editor.
874
- showValidationButtons={isClassic && userCanEvaluate && userCanSave}
875
- />
876
- <VariantsPanel
877
- product={state.product}
878
- esHijoDeOtro={esHijoDeOtro}
879
- hijosDelProducto={hijosDelProducto}
880
- family={family}
881
- showFamily={showFamily}
882
- setShowFamily={setShowFamily}
883
- loadFamily={loadFamily}
884
- loadingVariant={loadingVariant}
885
- avisoFamilia={avisoFamilia}
886
- openFamilyMember={openFamilyMember}
887
- setPorDesvincular={setPorDesvincular}
888
- setShowLinkVariants={setShowLinkVariants}
889
- puedeAgruparse={puedeAgruparse}
890
- variantGroup={variantGroup}
891
- showGroup={showGroup}
892
- setShowGroup={setShowGroup}
893
- setShowGroupModal={setShowGroupModal}
894
- handleOnLeaveGroup={handleOnLeaveGroup}
895
- />
896
- <ServiceHeader
897
- service={selectedService}
898
- isSummary={isSummary}
899
- subtitle={
900
- isSummary
901
- ? `${services.length} servicios · ${pendingCount} te ${pendingCount === 1 ? "toca" : "tocan"} a ti`
902
- : null
903
- }
904
- onOpenRail={!single && !isDesktop ? () => setRailOpen(true) : undefined}
905
- team={
906
- isClassic ? (
907
- <ServiceTeam
908
- assignees={selectedService.assignees}
909
- userId={user?.id_user}
910
- canAssign={canAssign(user)}
911
- assig={state.collaborator_assignations?.[state.active_tab]}
912
- // La que sí asigna (POST a asignaciones y actualiza el
913
- // producto). La barra de pestañas usaba `handleOnSetAssignation`,
914
- // que en el hook está vacía: asignar desde ahí no hacía nada.
915
- setAssignation={handleOnChangeAssignations}
916
- resetKey={state.active_tab}
917
- />
918
- ) : catalogService ? (
919
- <ServiceTeam
920
- assignees={catalogService.assignees}
921
- userId={user?.id_user}
922
- roles={catalogService.api.roles}
923
- canAssign={catalogCanAssign}
924
- assig={{ collaborators: assignables }}
925
- setAssignation={handleOnAssignCatalogService}
926
- resetKey={catalogService.service_name}
927
- />
928
- ) : null
929
- }
930
- >
931
- {isClassic && (
932
- <ServiceTools
933
- imagesSection={state.active_tab === "Imágenes"}
934
- imageLayout={imageLayout}
935
- setImageLayout={handleOnToggleImageLayout}
936
- // El botón actúa sobre la bandeja visible: en activos descargaba
937
- // las imágenes del producto, que no es lo que el usuario pidió.
938
- downloadImages={
939
- currentTray === TRAYS.ASSETS
940
- ? handleOnDownloadAssets
941
- : handleOnDownloadImages
942
- }
943
- askToDeleteImages={handleOnAskToDeleteImages}
944
- onClickSave={handleOnClickSave}
945
- showSaveButton={userCanSave}
946
- version={state.product.version}
947
- setShowVersionSelector={setShowVersionSelector}
948
- />
949
- )}
950
- </ServiceHeader>
951
- <div
952
- className="services-information-container"
953
- id="services-information-container"
954
- >
955
- {selected !== null && !isClassic ? (
956
- <Box sx={{ pb: 2 }}>
957
- {catalogService ? (
958
- <CatalogServicePanel service={catalogService} onSetStatus={handleOnSetCatalogStatus} />
959
- ) : (
960
- <ServiceView
961
- selected={isSummary ? SUMMARY : selected}
962
- service={selectedService}
963
- services={services}
964
- userId={user?.id_user}
965
- onSelect={requestService}
966
- retailerName={state.active_retailer?.retailer}
967
- />
968
- )}
969
- </Box>
970
- ) : state.saving ? (
971
- <Stack alignItems="center" justifyContent="center" sx={{ py: 6 }}>
972
- <CircularProgress size={32} sx={{ color: COLORS.magenta_s2 }} />
973
- </Stack>
974
- ) : (
975
- <>
976
- {state.active_tab === "Descripción" &&
977
- (state.product?.description_status !== "NS" ? (
978
- <InputGroup
979
- activeSection={state.active_tab}
980
- inputGroup={state.descriptions_inputs[0]}
981
- updatedDescriptions={state.updated_descriptions_inputs}
982
- /* El parentesco es del producto, no del servicio: los
983
- dos endpoints lo devuelven igual. */
984
- variant={state.services?.datasheets?.variant}
985
- onRestoreInheritance={handleOnRestoreInheritance}
986
- articleId={state.product?.id_article}
987
- version={state.product.version}
988
- auditInputGroup={auditDescriptions[0]}
989
- setUpdatedDescriptions={handleOnSetUpdatedDescriptions}
990
- dinamicHeight={true}
991
- compare={compare}
992
- />
993
- ) : (
994
- <NoService />
995
- ))}
996
- {state.active_tab === "Ficha técnica" &&
997
- (state.product?.datasheet_status !== "NS" ? (
998
- <>
999
- {state.datasheets_inputs[0]?.data?.map(
1000
- (dataGroup, index) => (
1001
- <InputGroup
1002
- key={index + "-" + state.active_retailer.retailer}
1003
- articleId={state.product.id_article}
1004
- version={state.version}
1005
- activeSection={state.active_tab}
1006
- inputGroup={dataGroup}
1007
- dataInputs={state.datasheets_inputs[1]}
1008
- variant={state.services?.datasheets?.variant}
1009
- onRestoreInheritance={handleOnRestoreInheritance}
1010
- auditInputs={auditDatasheets[1]}
1011
- updatedDatasheets={
1012
- state.updated_datasheets_inputs
1013
- }
1014
- setUpdatedDatasheets={
1015
- handleOnSetUpdatedDatasheets
1016
- }
1017
- compare={compare}
1018
- />
1019
- ),
1020
- )}
1021
- </>
1022
- ) : (
1023
- <NoService />
1024
- ))}
1025
- {state.active_tab === "Imágenes" &&
1026
- (state.product?.images_status !== "NS" ? (
1027
- <ImagesSection
1028
- imagesValues={state.images_values}
1029
- selectedImages={state.selected_images}
1030
- imageLayout={imageLayout}
1031
- enableDigitalAssets={enableDigitalAssets}
1032
- currentTray={currentTray}
1033
- setActiveTray={setActiveTray}
1034
- digitalAssets={digitalAssets}
1035
- unsavedAssets={countUnsavedAssets(state)}
1036
- selectedAssets={state.selected_assets}
1037
- setSelectedAssets={handleOnSetSelectedAssets}
1038
- setAllAssetsChecked={handleOnSetAllAssetsChecked}
1039
- canEdit={userCanSave}
1040
- setCheckAll={handleOnSetCheckAll}
1041
- setSelectedImages={handleOnSetSelectedImages}
1042
- onChangeCurrentImage={handleOnChangeCurrentImage}
1043
- onUpdateImage={(item) =>
1044
- dispatch({ type: "UPDATE_IMAGE_VALUE", payload: item })
1045
- }
1046
- onSelectImage={(item) =>
1047
- dispatch({ type: "SET_SELECTED_IMAGE", payload: item })
1048
- }
1049
- getRootProps={getRootProps}
1050
- getInputProps={getInputProps}
1051
- isDragActive={isDragActive}
1052
- openFilePicker={open}
1053
- />
1054
- ) : (
1055
- <NoService />
1056
- ))}
1057
- </>
1058
- )}
1059
- </div>
1060
- {isClassic &&
1061
- userCanSave &&
1062
- state.product[`${getConceptByTab(state.active_tab)}_status`] !==
1063
- "NS" && (
1064
- <CommentBox
1065
- comment={state.comment}
1066
- crossComment={crossComment}
1067
- createComment={createComment}
1068
- token={token}
1069
- dispatch={dispatch}
1070
- showChangeStatus={canChangeStatus(user, allowedAccountsToViewChangeStatus)}
1071
- onOpenChangeStatus={() => setOpenChangeStatusModal(true)}
1072
- showSendEvaluation={canSendToFacilitator(user)}
1073
- // Solo para los roles 7 y 8, como en la vista original: para
1074
- // el rol 6, `isEvaluationFinished` usa un `srv` que no existe
1075
- // (defecto heredado) y tumbaría la pantalla.
1076
- canSendEvaluation={
1077
- canSendToFacilitator(user) &&
1078
- isEvaluationFinished(user.id_role, state.active_tab, statusArray) &&
1079
- state.missing_required_fields[state.active_tab] === 0
1080
- }
1081
- onSendEvaluation={handleOnSendEvaluationToFacilitator}
1082
- />
1083
- )}
1084
- </div>
1085
- </div>
1086
- </Container>
1087
- {/* Después del editor: va a la derecha y, con el teclado, se llega a él
1088
- después del contenido. */}
1089
- {!single && isDesktop && renderRail("rail")}
1090
- </Box>
1091
-
1092
- {!single && !isDesktop && (
1093
- <Drawer
1094
- anchor={RAIL_SIDE}
1095
- open={railOpen}
1096
- onClose={() => setRailOpen(false)}
1097
- PaperProps={{ sx: { width: "min(320px, 88vw)" } }}
1098
- >
1099
- {renderRail("drawer")}
1100
- </Drawer>
1101
- )}
1102
-
1103
- <Dialog open={Boolean(pendingSelection)} onClose={() => setPendingSelection(null)} maxWidth="xs" fullWidth>
1104
- <DialogTitle sx={{ fontFamily: FONTS.Raleway_600, color: COLORS.s5 }}>Cambios sin Guardar</DialogTitle>
1105
- <DialogContent>
1106
- <DialogContentText sx={{ fontFamily: FONTS.AvenirNext }}>
1107
- Estás cambiando de servicio y aun no guardas cambios ¿Estás seguro?
1108
- </DialogContentText>
1109
- </DialogContent>
1110
- <DialogActions sx={{ px: 3, pb: 2 }}>
1111
- <MuiButton onClick={() => setPendingSelection(null)} sx={{ color: COLORS.s5, textTransform: "none" }}>
1112
- Cancelar
1113
- </MuiButton>
1114
- <MuiButton
1115
- variant="contained"
1116
- disableElevation
1117
- onClick={confirmServiceChange}
1118
- sx={{
1119
- textTransform: "none",
1120
- borderRadius: "20px",
1121
- bgcolor: COLORS.magenta_s2,
1122
- "&:hover": { bgcolor: COLORS.secondary_magenta },
1123
- }}
1124
- >
1125
- Aceptar
1126
- </MuiButton>
1127
- </DialogActions>
1128
- </Dialog>
1129
-
1130
- <ReusedModalsStyles />
1131
- <ChangeStatusModal
1132
- state={state}
1133
- open={openChangeStatusModal}
1134
- onClose={() => {
1135
- setOpenChangeStatusModal(false);
1136
- }}
1137
- reloadData={loadData}
1138
- token={token}
1139
- />
1140
- </AiProductEditionProvider>
1141
- );
1142
- };