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.
- package/.claude/settings.local.json +7 -0
- package/dist/ai/prompts/attribute.prompt.js +28 -0
- package/dist/ai/prompts/description.prompt.js +33 -0
- package/dist/ai/schemas/attribute.schema.js +35 -0
- package/dist/ai/schemas/description.schema.js +27 -0
- package/dist/components/pages/CreatorsProductEdition/CreatorsProductEditionView.js +11 -5
- package/dist/components/pages/CreatorsProductEdition/hooks/useCreatorsProductEdition.js +58 -31
- package/dist/components/pages/CreatorsProductEdition/mocks/api.fixtures.js +5 -1
- package/dist/components/pages/CreatorsProductEdition/mocks/mockApi.js +3 -40
- package/dist/components/pages/CreatorsProductEdition/sections/ImagesSection.js +18 -3
- package/dist/components/pages/CreatorsProductEdition/sections/StatusChip.js +4 -13
- package/dist/components/pages/CreatorsProductEdition/sections/TabsBar.js +10 -0
- package/dist/components/pages/CreatorsProductEdition/stories/CreatorsProductEdition.stories.js +10 -2
- package/dist/components/pages/RetailerProductEdition/RetailerProductEdition.stories.js +2 -15
- package/dist/components/pages/RetailerProductEdition/index.js +40 -57
- package/dist/components/pages/RetailerProductEdition/utils.js +2 -6
- package/dist/index.js +0 -13
- package/package.json +1 -1
- package/src/components/pages/CreatorsProductEdition/CreatorsProductEditionView.jsx +22 -3
- package/src/components/pages/CreatorsProductEdition/hooks/useCreatorsProductEdition.js +36 -1
- package/src/components/pages/CreatorsProductEdition/mocks/api.fixtures.js +15 -3
- package/src/components/pages/CreatorsProductEdition/mocks/mockApi.js +8 -18
- package/src/components/pages/CreatorsProductEdition/sections/ImagesSection.jsx +24 -2
- package/src/components/pages/CreatorsProductEdition/sections/StatusChip.jsx +4 -8
- package/src/components/pages/CreatorsProductEdition/sections/TabsBar.jsx +9 -0
- package/src/components/pages/CreatorsProductEdition/stories/CreatorsProductEdition.stories.js +6 -0
- package/src/components/pages/RetailerProductEdition/RetailerProductEdition.stories.js +0 -12
- package/src/components/pages/RetailerProductEdition/index.js +3 -28
- package/src/components/pages/RetailerProductEdition/utils.js +0 -5
- package/src/index.js +0 -1
- package/dist/components/organisms/ServicesNavigation/ServiceToolbar.js +0 -210
- package/dist/components/organisms/ServicesNavigation/ServicesNavigation.stories.js +0 -138
- package/dist/components/organisms/ServicesNavigation/ServicesRail.js +0 -705
- package/dist/components/organisms/ServicesNavigation/catalog.js +0 -505
- package/dist/components/organisms/ServicesNavigation/index.js +0 -337
- package/dist/components/organisms/ServicesNavigation/model.js +0 -167
- package/dist/components/organisms/ServicesNavigation/ui.js +0 -371
- package/dist/components/organisms/ServicesNavigation/views.js +0 -1237
- package/dist/components/organisms/ServicesWorkspace/AssigneePicker.js +0 -459
- package/dist/components/organisms/ServicesWorkspace/ServicesWorkspace.stories.js +0 -119
- package/dist/components/organisms/ServicesWorkspace/demo.js +0 -137
- package/dist/components/organisms/ServicesWorkspace/index.js +0 -813
- package/dist/components/organisms/ServicesWorkspace/servicesModel.js +0 -351
- package/dist/components/organisms/ServicesWorkspace/tooltipStyles.js +0 -37
- package/dist/components/pages/CreatorsProductEdition/mocks/productServices.fixtures.js +0 -387
- package/dist/components/pages/ProductEdition/ProductEdition.stories.js +0 -148
- package/dist/components/pages/ProductEdition/ProductEditionView.js +0 -1355
- package/dist/components/pages/ProductEdition/ServiceHeader.js +0 -142
- package/dist/components/pages/ProductEdition/api.js +0 -256
- package/dist/components/pages/ProductEdition/index.js +0 -37
- package/dist/components/pages/ProductEdition/permissions.js +0 -200
- package/dist/components/pages/ProductEdition/permissions.test.js +0 -320
- package/dist/components/pages/ProductEdition/sections/CatalogServicePanel.js +0 -237
- package/dist/components/pages/ProductEdition/sections/EditionTopBar.js +0 -174
- package/dist/components/pages/ProductEdition/sections/ImagePanel.js +0 -440
- package/dist/components/pages/ProductEdition/sections/ServiceTeam.js +0 -275
- package/dist/components/pages/ProductEdition/sections/ServiceTools.js +0 -186
- package/dist/components/pages/ProductEdition/services.js +0 -233
- package/dist/components/pages/ProductEdition/services.test.js +0 -195
- package/src/components/organisms/ServicesNavigation/ServiceToolbar.jsx +0 -143
- package/src/components/organisms/ServicesNavigation/ServicesNavigation.stories.js +0 -101
- package/src/components/organisms/ServicesNavigation/ServicesRail.jsx +0 -591
- package/src/components/organisms/ServicesNavigation/catalog.js +0 -180
- package/src/components/organisms/ServicesNavigation/index.jsx +0 -232
- package/src/components/organisms/ServicesNavigation/model.js +0 -85
- package/src/components/organisms/ServicesNavigation/ui.jsx +0 -229
- package/src/components/organisms/ServicesNavigation/views.jsx +0 -705
- package/src/components/organisms/ServicesWorkspace/AssigneePicker.js +0 -348
- package/src/components/organisms/ServicesWorkspace/ServicesWorkspace.stories.js +0 -61
- package/src/components/organisms/ServicesWorkspace/demo.js +0 -58
- package/src/components/organisms/ServicesWorkspace/index.js +0 -556
- package/src/components/organisms/ServicesWorkspace/servicesModel.js +0 -203
- package/src/components/organisms/ServicesWorkspace/tooltipStyles.js +0 -27
- package/src/components/pages/CreatorsProductEdition/mocks/productServices.fixtures.js +0 -175
- package/src/components/pages/ProductEdition/ProductEdition.stories.js +0 -106
- package/src/components/pages/ProductEdition/ProductEditionView.jsx +0 -1142
- package/src/components/pages/ProductEdition/ServiceHeader.jsx +0 -104
- package/src/components/pages/ProductEdition/api.js +0 -84
- package/src/components/pages/ProductEdition/index.js +0 -21
- package/src/components/pages/ProductEdition/permissions.js +0 -128
- package/src/components/pages/ProductEdition/permissions.test.js +0 -249
- package/src/components/pages/ProductEdition/sections/CatalogServicePanel.jsx +0 -123
- package/src/components/pages/ProductEdition/sections/EditionTopBar.jsx +0 -134
- package/src/components/pages/ProductEdition/sections/ImagePanel.jsx +0 -342
- package/src/components/pages/ProductEdition/sections/ServiceTeam.jsx +0 -184
- package/src/components/pages/ProductEdition/sections/ServiceTools.jsx +0 -134
- package/src/components/pages/ProductEdition/services.js +0 -187
- 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
|
-
};
|