contentoh-components-library 21.6.61 → 21.6.63

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 (102) 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/atoms/MetricCard/index.js +3 -1
  7. package/dist/components/organisms/DashboardMetric/DashboardMetric.stories.js +12 -2
  8. package/dist/components/organisms/DashboardMetric/index.js +24 -1
  9. package/dist/components/organisms/DashboardMetric/styles.js +1 -1
  10. package/dist/components/pages/CreatorsProductEdition/CreatorsProductEditionView.js +11 -5
  11. package/dist/components/pages/CreatorsProductEdition/hooks/useCreatorsProductEdition.js +58 -31
  12. package/dist/components/pages/CreatorsProductEdition/mocks/api.fixtures.js +5 -1
  13. package/dist/components/pages/CreatorsProductEdition/mocks/mockApi.js +3 -40
  14. package/dist/components/pages/CreatorsProductEdition/sections/ImagesSection.js +18 -3
  15. package/dist/components/pages/CreatorsProductEdition/sections/StatusChip.js +4 -13
  16. package/dist/components/pages/CreatorsProductEdition/sections/TabsBar.js +10 -0
  17. package/dist/components/pages/CreatorsProductEdition/stories/CreatorsProductEdition.stories.js +10 -2
  18. package/dist/components/pages/Dashboard/dashboardUtils.js +3 -3
  19. package/dist/components/pages/Dashboard/index.js +102 -88
  20. package/dist/components/pages/Dashboard/styles.js +1 -1
  21. package/dist/components/pages/RetailerProductEdition/RetailerProductEdition.stories.js +2 -15
  22. package/dist/components/pages/RetailerProductEdition/index.js +40 -57
  23. package/dist/components/pages/RetailerProductEdition/utils.js +2 -6
  24. package/dist/index.js +0 -13
  25. package/package.json +1 -1
  26. package/src/components/atoms/MetricCard/index.js +2 -2
  27. package/src/components/organisms/DashboardMetric/DashboardMetric.stories.js +11 -0
  28. package/src/components/organisms/DashboardMetric/index.js +13 -1
  29. package/src/components/organisms/DashboardMetric/styles.js +13 -0
  30. package/src/components/pages/CreatorsProductEdition/CreatorsProductEditionView.jsx +22 -3
  31. package/src/components/pages/CreatorsProductEdition/hooks/useCreatorsProductEdition.js +36 -1
  32. package/src/components/pages/CreatorsProductEdition/mocks/api.fixtures.js +15 -3
  33. package/src/components/pages/CreatorsProductEdition/mocks/mockApi.js +8 -18
  34. package/src/components/pages/CreatorsProductEdition/sections/ImagesSection.jsx +24 -2
  35. package/src/components/pages/CreatorsProductEdition/sections/StatusChip.jsx +4 -8
  36. package/src/components/pages/CreatorsProductEdition/sections/TabsBar.jsx +9 -0
  37. package/src/components/pages/CreatorsProductEdition/stories/CreatorsProductEdition.stories.js +6 -0
  38. package/src/components/pages/Dashboard/dashboardUtils.js +3 -3
  39. package/src/components/pages/Dashboard/index.js +62 -106
  40. package/src/components/pages/Dashboard/styles.js +17 -12
  41. package/src/components/pages/RetailerProductEdition/RetailerProductEdition.stories.js +0 -12
  42. package/src/components/pages/RetailerProductEdition/index.js +3 -28
  43. package/src/components/pages/RetailerProductEdition/utils.js +0 -5
  44. package/src/index.js +0 -1
  45. package/dist/components/organisms/ServicesNavigation/ServiceToolbar.js +0 -210
  46. package/dist/components/organisms/ServicesNavigation/ServicesNavigation.stories.js +0 -138
  47. package/dist/components/organisms/ServicesNavigation/ServicesRail.js +0 -705
  48. package/dist/components/organisms/ServicesNavigation/catalog.js +0 -505
  49. package/dist/components/organisms/ServicesNavigation/index.js +0 -337
  50. package/dist/components/organisms/ServicesNavigation/model.js +0 -167
  51. package/dist/components/organisms/ServicesNavigation/ui.js +0 -371
  52. package/dist/components/organisms/ServicesNavigation/views.js +0 -1237
  53. package/dist/components/organisms/ServicesWorkspace/AssigneePicker.js +0 -459
  54. package/dist/components/organisms/ServicesWorkspace/ServicesWorkspace.stories.js +0 -119
  55. package/dist/components/organisms/ServicesWorkspace/demo.js +0 -137
  56. package/dist/components/organisms/ServicesWorkspace/index.js +0 -813
  57. package/dist/components/organisms/ServicesWorkspace/servicesModel.js +0 -351
  58. package/dist/components/organisms/ServicesWorkspace/tooltipStyles.js +0 -37
  59. package/dist/components/pages/CreatorsProductEdition/mocks/productServices.fixtures.js +0 -387
  60. package/dist/components/pages/ProductEdition/ProductEdition.stories.js +0 -148
  61. package/dist/components/pages/ProductEdition/ProductEditionView.js +0 -1355
  62. package/dist/components/pages/ProductEdition/ServiceHeader.js +0 -142
  63. package/dist/components/pages/ProductEdition/api.js +0 -256
  64. package/dist/components/pages/ProductEdition/index.js +0 -37
  65. package/dist/components/pages/ProductEdition/permissions.js +0 -200
  66. package/dist/components/pages/ProductEdition/permissions.test.js +0 -320
  67. package/dist/components/pages/ProductEdition/sections/CatalogServicePanel.js +0 -237
  68. package/dist/components/pages/ProductEdition/sections/EditionTopBar.js +0 -174
  69. package/dist/components/pages/ProductEdition/sections/ImagePanel.js +0 -440
  70. package/dist/components/pages/ProductEdition/sections/ServiceTeam.js +0 -275
  71. package/dist/components/pages/ProductEdition/sections/ServiceTools.js +0 -186
  72. package/dist/components/pages/ProductEdition/services.js +0 -233
  73. package/dist/components/pages/ProductEdition/services.test.js +0 -195
  74. package/src/components/organisms/ServicesNavigation/ServiceToolbar.jsx +0 -143
  75. package/src/components/organisms/ServicesNavigation/ServicesNavigation.stories.js +0 -101
  76. package/src/components/organisms/ServicesNavigation/ServicesRail.jsx +0 -591
  77. package/src/components/organisms/ServicesNavigation/catalog.js +0 -180
  78. package/src/components/organisms/ServicesNavigation/index.jsx +0 -232
  79. package/src/components/organisms/ServicesNavigation/model.js +0 -85
  80. package/src/components/organisms/ServicesNavigation/ui.jsx +0 -229
  81. package/src/components/organisms/ServicesNavigation/views.jsx +0 -705
  82. package/src/components/organisms/ServicesWorkspace/AssigneePicker.js +0 -348
  83. package/src/components/organisms/ServicesWorkspace/ServicesWorkspace.stories.js +0 -61
  84. package/src/components/organisms/ServicesWorkspace/demo.js +0 -58
  85. package/src/components/organisms/ServicesWorkspace/index.js +0 -556
  86. package/src/components/organisms/ServicesWorkspace/servicesModel.js +0 -203
  87. package/src/components/organisms/ServicesWorkspace/tooltipStyles.js +0 -27
  88. package/src/components/pages/CreatorsProductEdition/mocks/productServices.fixtures.js +0 -175
  89. package/src/components/pages/ProductEdition/ProductEdition.stories.js +0 -106
  90. package/src/components/pages/ProductEdition/ProductEditionView.jsx +0 -1142
  91. package/src/components/pages/ProductEdition/ServiceHeader.jsx +0 -104
  92. package/src/components/pages/ProductEdition/api.js +0 -84
  93. package/src/components/pages/ProductEdition/index.js +0 -21
  94. package/src/components/pages/ProductEdition/permissions.js +0 -128
  95. package/src/components/pages/ProductEdition/permissions.test.js +0 -249
  96. package/src/components/pages/ProductEdition/sections/CatalogServicePanel.jsx +0 -123
  97. package/src/components/pages/ProductEdition/sections/EditionTopBar.jsx +0 -134
  98. package/src/components/pages/ProductEdition/sections/ImagePanel.jsx +0 -342
  99. package/src/components/pages/ProductEdition/sections/ServiceTeam.jsx +0 -184
  100. package/src/components/pages/ProductEdition/sections/ServiceTools.jsx +0 -134
  101. package/src/components/pages/ProductEdition/services.js +0 -187
  102. 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
- };