contentoh-components-library 21.6.23 → 21.6.24
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/.env.test +1 -0
- package/dist/_utils/helper.js +47 -2
- package/dist/components/organisms/LinkVariantsModal/index.js +800 -340
- package/dist/components/organisms/LinkVariantsModal/styles.js +22 -10
- package/dist/components/pages/ProviderProductEdition/index.js +193 -110
- package/package.json +1 -1
- package/src/_utils/helper.js +40 -0
- package/src/components/organisms/LinkVariantsModal/index.js +681 -279
- package/src/components/organisms/LinkVariantsModal/styles.js +176 -40
- package/src/components/pages/ProviderProductEdition/index.js +71 -9
|
@@ -2,6 +2,10 @@ import { useCallback, useEffect, useMemo, useState } from "react";
|
|
|
2
2
|
import axios from "axios";
|
|
3
3
|
|
|
4
4
|
import { readPayload, ApiError } from "./api";
|
|
5
|
+
import {
|
|
6
|
+
getProductImage,
|
|
7
|
+
getRetailerPicture,
|
|
8
|
+
} from "../../../_utils/helper";
|
|
5
9
|
|
|
6
10
|
import {
|
|
7
11
|
Overlay,
|
|
@@ -13,7 +17,10 @@ import {
|
|
|
13
17
|
List,
|
|
14
18
|
AttributeList,
|
|
15
19
|
Section,
|
|
16
|
-
|
|
20
|
+
Block,
|
|
21
|
+
Choices,
|
|
22
|
+
Tabs,
|
|
23
|
+
Paging,
|
|
17
24
|
ImageGrid,
|
|
18
25
|
AddRetailer,
|
|
19
26
|
Message,
|
|
@@ -69,10 +76,9 @@ export const LinkVariantsModal = ({
|
|
|
69
76
|
articleName,
|
|
70
77
|
articleUpc,
|
|
71
78
|
activeRetailerId,
|
|
72
|
-
retailersAvailable,
|
|
73
|
-
categoriesAvailable,
|
|
74
79
|
token,
|
|
75
80
|
endpoint = process.env.REACT_APP_VARIANTS_ENDPOINT,
|
|
81
|
+
filtersEndpoint = process.env.REACT_APP_CREATE_PRODUCTS_FILTERS,
|
|
76
82
|
onClose,
|
|
77
83
|
onLinked,
|
|
78
84
|
}) => {
|
|
@@ -84,6 +90,9 @@ export const LinkVariantsModal = ({
|
|
|
84
90
|
const [rejected, setRejected] = useState(null);
|
|
85
91
|
const [searched, setSearched] = useState(false);
|
|
86
92
|
const [selected, setSelected] = useState([]);
|
|
93
|
+
const [total, setTotal] = useState(0);
|
|
94
|
+
const [hasMore, setHasMore] = useState(false);
|
|
95
|
+
const [loadingMore, setLoadingMore] = useState(false);
|
|
87
96
|
|
|
88
97
|
// Lo que el padre ofrece en cada dimensión, y lo que el usuario eligió de
|
|
89
98
|
// cada una. Se guardan por separado porque se eligen por separado: heredar
|
|
@@ -101,14 +110,46 @@ export const LinkVariantsModal = ({
|
|
|
101
110
|
categoryRetailers: [],
|
|
102
111
|
});
|
|
103
112
|
const [nameScope, setNameScope] = useState(null);
|
|
104
|
-
|
|
105
|
-
|
|
113
|
+
|
|
114
|
+
// Un modo por dimensión, y ninguno decidido de entrada: el usuario tiene que
|
|
115
|
+
// pasar por las cuatro antes de poder ligar. Preseleccionar "todo" hacía que
|
|
116
|
+
// se pudiera ligar sin haber mirado nada, que es justo lo que hay que evitar
|
|
117
|
+
// en una operación que sobrescribe valores capturados.
|
|
118
|
+
const [modes, setModes] = useState({
|
|
119
|
+
categoryRetailers: null,
|
|
120
|
+
descriptions: null,
|
|
121
|
+
attributes: null,
|
|
122
|
+
images: null,
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
/*
|
|
126
|
+
Al ligar varios productos, la herencia puede ser la misma para todos o
|
|
127
|
+
distinta para cada uno.
|
|
128
|
+
|
|
129
|
+
Por defecto es la misma: es lo que se quiere casi siempre y evita repetir
|
|
130
|
+
cuatro decisiones por producto. Al desmarcarlo aparecen las pestañas y cada
|
|
131
|
+
producto guarda su propia selección en `perChild`.
|
|
132
|
+
*/
|
|
133
|
+
const [applyToAll, setApplyToAll] = useState(true);
|
|
134
|
+
const [activeChild, setActiveChild] = useState(null);
|
|
135
|
+
const [perChild, setPerChild] = useState({});
|
|
106
136
|
|
|
107
137
|
// Cadenas que el padre NO tiene y el usuario decidió agregar a la variante.
|
|
108
138
|
const [extraRetailers, setExtraRetailers] = useState([]);
|
|
109
139
|
const [newRetailer, setNewRetailer] = useState("");
|
|
140
|
+
const [newDepartment, setNewDepartment] = useState("");
|
|
110
141
|
const [newCategory, setNewCategory] = useState("");
|
|
111
142
|
|
|
143
|
+
// Las categorías no son una lista plana: dependen de la cadena y del
|
|
144
|
+
// departamento. Es la misma cascada que usa el alta de productos, y se sirve
|
|
145
|
+
// del mismo endpoint para no acabar con dos árboles de categorías distintos.
|
|
146
|
+
const [filters, setFilters] = useState({
|
|
147
|
+
retailers: [],
|
|
148
|
+
departments: [],
|
|
149
|
+
categories: [],
|
|
150
|
+
});
|
|
151
|
+
const [loadingFilters, setLoadingFilters] = useState(false);
|
|
152
|
+
|
|
112
153
|
const [loading, setLoading] = useState(false);
|
|
113
154
|
const [error, setError] = useState(null);
|
|
114
155
|
|
|
@@ -137,6 +178,8 @@ export const LinkVariantsModal = ({
|
|
|
137
178
|
setSelected([]);
|
|
138
179
|
setRejected(null);
|
|
139
180
|
setSearched(false);
|
|
181
|
+
setTotal(0);
|
|
182
|
+
setHasMore(false);
|
|
140
183
|
setError(null);
|
|
141
184
|
setStep("search");
|
|
142
185
|
}, [mode]);
|
|
@@ -153,6 +196,8 @@ export const LinkVariantsModal = ({
|
|
|
153
196
|
});
|
|
154
197
|
setCandidates(data.candidates || []);
|
|
155
198
|
setRejected(data.rejected || null);
|
|
199
|
+
setTotal(data.total ?? (data.candidates || []).length);
|
|
200
|
+
setHasMore(!!data.hasMore);
|
|
156
201
|
setSearched(true);
|
|
157
202
|
} catch (err) {
|
|
158
203
|
setError(readError(err));
|
|
@@ -161,6 +206,34 @@ export const LinkVariantsModal = ({
|
|
|
161
206
|
}
|
|
162
207
|
};
|
|
163
208
|
|
|
209
|
+
/*
|
|
210
|
+
Trae la siguiente página y la agrega a la lista.
|
|
211
|
+
|
|
212
|
+
Se acumula en vez de reemplazar porque la selección vive en esta pantalla: si
|
|
213
|
+
la página 2 borrara la 1, lo marcado antes desaparecería de la vista sin
|
|
214
|
+
dejar de estar seleccionado, y el usuario acabaría ligando productos que ya
|
|
215
|
+
no ve.
|
|
216
|
+
*/
|
|
217
|
+
const loadMore = async () => {
|
|
218
|
+
setLoadingMore(true);
|
|
219
|
+
setError(null);
|
|
220
|
+
try {
|
|
221
|
+
const data = await request("search", {
|
|
222
|
+
mode,
|
|
223
|
+
articleId,
|
|
224
|
+
query: query.trim(),
|
|
225
|
+
offset: candidates.length,
|
|
226
|
+
});
|
|
227
|
+
setCandidates((current) => [...current, ...(data.candidates || [])]);
|
|
228
|
+
setTotal(data.total ?? total);
|
|
229
|
+
setHasMore(!!data.hasMore);
|
|
230
|
+
} catch (err) {
|
|
231
|
+
setError(readError(err));
|
|
232
|
+
} finally {
|
|
233
|
+
setLoadingMore(false);
|
|
234
|
+
}
|
|
235
|
+
};
|
|
236
|
+
|
|
164
237
|
const toggle = (id) => {
|
|
165
238
|
setSelected((current) => {
|
|
166
239
|
if (mode === "parent") return current[0] === id ? [] : [id];
|
|
@@ -191,17 +264,22 @@ export const LinkVariantsModal = ({
|
|
|
191
264
|
|
|
192
265
|
setOffered(listas);
|
|
193
266
|
setNameScope(data.nameScope ?? null);
|
|
194
|
-
// Todo preseleccionado: heredar es el caso normal, y quitar lo que sobra
|
|
195
|
-
// es menos trabajo que marcar de cero lo que casi siempre se quiere.
|
|
196
267
|
setChosen({
|
|
197
|
-
attributes:
|
|
198
|
-
descriptions:
|
|
199
|
-
images:
|
|
200
|
-
categoryRetailers:
|
|
201
|
-
|
|
202
|
-
|
|
268
|
+
attributes: [],
|
|
269
|
+
descriptions: [],
|
|
270
|
+
images: [],
|
|
271
|
+
categoryRetailers: [],
|
|
272
|
+
});
|
|
273
|
+
setModes({
|
|
274
|
+
categoryRetailers: null,
|
|
275
|
+
descriptions: null,
|
|
276
|
+
attributes: null,
|
|
277
|
+
images: null,
|
|
203
278
|
});
|
|
204
279
|
setExtraRetailers([]);
|
|
280
|
+
setPerChild({});
|
|
281
|
+
setApplyToAll(true);
|
|
282
|
+
setActiveChild(mode === "children" ? selected[0] : null);
|
|
205
283
|
setStep("inherit");
|
|
206
284
|
} catch (err) {
|
|
207
285
|
setError(readError(err));
|
|
@@ -222,13 +300,69 @@ export const LinkVariantsModal = ({
|
|
|
222
300
|
});
|
|
223
301
|
};
|
|
224
302
|
|
|
225
|
-
const
|
|
303
|
+
const chooseMode = (dimension, modo, ids) => {
|
|
304
|
+
setModes((current) => ({ ...current, [dimension]: modo }));
|
|
226
305
|
setChosen((current) => ({
|
|
227
306
|
...current,
|
|
228
|
-
|
|
307
|
+
// "Todos" marca la lista completa para que se vea qué va a bajar; al pasar
|
|
308
|
+
// a "elegir" se parte de cero, que es lo que la palabra promete.
|
|
309
|
+
[dimension]: modo === "all" ? ids : [],
|
|
229
310
|
}));
|
|
230
311
|
};
|
|
231
312
|
|
|
313
|
+
const loadFilters = useCallback(
|
|
314
|
+
async (retailer, department) => {
|
|
315
|
+
setLoadingFilters(true);
|
|
316
|
+
try {
|
|
317
|
+
const res = await axios.get(
|
|
318
|
+
`${filtersEndpoint}?retailerSelected=${retailer ?? ""}&departmentSelected=${
|
|
319
|
+
department ?? ""
|
|
320
|
+
}`,
|
|
321
|
+
{ headers: { Authorization: token } }
|
|
322
|
+
);
|
|
323
|
+
|
|
324
|
+
const { body } = readPayload(res);
|
|
325
|
+
const lista = (body ? JSON.parse(body) : res.data)?.data ?? [];
|
|
326
|
+
|
|
327
|
+
setFilters((current) => {
|
|
328
|
+
if (!retailer) {
|
|
329
|
+
return {
|
|
330
|
+
...current,
|
|
331
|
+
retailers: lista.map((item) => ({ value: item.id, label: item.name })),
|
|
332
|
+
};
|
|
333
|
+
}
|
|
334
|
+
if (!department) {
|
|
335
|
+
return {
|
|
336
|
+
...current,
|
|
337
|
+
departments: lista.map((item) => ({
|
|
338
|
+
value: item.category,
|
|
339
|
+
label: item.category,
|
|
340
|
+
})),
|
|
341
|
+
categories: [],
|
|
342
|
+
};
|
|
343
|
+
}
|
|
344
|
+
return {
|
|
345
|
+
...current,
|
|
346
|
+
categories: lista.map((item) => ({ value: item.id, label: item.name })),
|
|
347
|
+
};
|
|
348
|
+
});
|
|
349
|
+
} catch (error) {
|
|
350
|
+
console.error("linkVariants/filters: ", error);
|
|
351
|
+
} finally {
|
|
352
|
+
setLoadingFilters(false);
|
|
353
|
+
}
|
|
354
|
+
},
|
|
355
|
+
[filtersEndpoint, token]
|
|
356
|
+
);
|
|
357
|
+
|
|
358
|
+
// Las cadenas del catálogo se piden al entrar al paso de herencia, no al abrir
|
|
359
|
+
// el modal: quien solo busca y cancela no llega a necesitarlas.
|
|
360
|
+
useEffect(() => {
|
|
361
|
+
if (step === "inherit" && !filters.retailers.length) {
|
|
362
|
+
loadFilters(null, null);
|
|
363
|
+
}
|
|
364
|
+
}, [step, filters.retailers.length, loadFilters]);
|
|
365
|
+
|
|
232
366
|
const addRetailer = () => {
|
|
233
367
|
const retailerId = Number(newRetailer);
|
|
234
368
|
const categoryId = Number(newCategory);
|
|
@@ -242,11 +376,11 @@ export const LinkVariantsModal = ({
|
|
|
242
376
|
|
|
243
377
|
if (yaEsta) return;
|
|
244
378
|
|
|
245
|
-
const retailer =
|
|
246
|
-
(item) => Number(item.
|
|
379
|
+
const retailer = filters.retailers.find(
|
|
380
|
+
(item) => Number(item.value) === retailerId
|
|
247
381
|
);
|
|
248
|
-
const category =
|
|
249
|
-
(item) => Number(item.
|
|
382
|
+
const category = filters.categories.find(
|
|
383
|
+
(item) => Number(item.value) === categoryId
|
|
250
384
|
);
|
|
251
385
|
|
|
252
386
|
setExtraRetailers((current) => [
|
|
@@ -255,8 +389,8 @@ export const LinkVariantsModal = ({
|
|
|
255
389
|
clave,
|
|
256
390
|
retailerId,
|
|
257
391
|
categoryId,
|
|
258
|
-
retailerName: retailer?.
|
|
259
|
-
categoryName: category?.
|
|
392
|
+
retailerName: retailer?.label ?? `Cadena ${retailerId}`,
|
|
393
|
+
categoryName: category?.label ?? `Categoría ${categoryId}`,
|
|
260
394
|
},
|
|
261
395
|
]);
|
|
262
396
|
setChosen((current) => ({
|
|
@@ -264,38 +398,108 @@ export const LinkVariantsModal = ({
|
|
|
264
398
|
categoryRetailers: [...current.categoryRetailers, clave],
|
|
265
399
|
}));
|
|
266
400
|
setNewRetailer("");
|
|
401
|
+
setNewDepartment("");
|
|
267
402
|
setNewCategory("");
|
|
268
403
|
};
|
|
269
404
|
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
setError(null);
|
|
405
|
+
/*
|
|
406
|
+
Convierte una selección de pantalla en el bloque `inherit` de la API.
|
|
273
407
|
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
408
|
+
Con el modo en "todos" se manda la palabra y no la lista que se pintó: si
|
|
409
|
+
entre la carga del modal y el guardado alguien agregó un valor al padre,
|
|
410
|
+
"todos" sigue significando todos. La lista sería una foto ya vieja.
|
|
411
|
+
|
|
412
|
+
Las parejas cadena+categoría viajan como objetos, no como ids, porque pueden
|
|
413
|
+
ser cadenas que el padre no tiene: ahí no hay nada que heredar, se asigna.
|
|
414
|
+
*/
|
|
415
|
+
const buildInherit = (seleccion) => {
|
|
416
|
+
const parejas = [
|
|
277
417
|
...offered.categoryRetailers.map((item) => ({
|
|
278
418
|
clave: `${item.retailerId}-${item.categoryId}`,
|
|
279
419
|
retailerId: item.retailerId,
|
|
280
420
|
categoryId: item.categoryId,
|
|
281
421
|
})),
|
|
282
|
-
...extraRetailers,
|
|
422
|
+
...(seleccion.extraRetailers ?? []),
|
|
283
423
|
];
|
|
284
424
|
|
|
285
|
-
|
|
425
|
+
return {
|
|
286
426
|
attributes:
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
.
|
|
427
|
+
seleccion.modes.attributes === "all"
|
|
428
|
+
? "all-filled"
|
|
429
|
+
: seleccion.chosen.attributes,
|
|
430
|
+
descriptions:
|
|
431
|
+
seleccion.modes.descriptions === "all"
|
|
432
|
+
? "all"
|
|
433
|
+
: seleccion.chosen.descriptions,
|
|
434
|
+
images: seleccion.modes.images === "all" ? "all" : seleccion.chosen.images,
|
|
435
|
+
categoryRetailers: parejas
|
|
436
|
+
.filter((par) => seleccion.chosen.categoryRetailers.includes(par.clave))
|
|
292
437
|
.map(({ retailerId, categoryId }) => ({ retailerId, categoryId })),
|
|
293
438
|
};
|
|
439
|
+
};
|
|
440
|
+
|
|
441
|
+
const seleccionVacia = () => ({
|
|
442
|
+
modes: {
|
|
443
|
+
categoryRetailers: null,
|
|
444
|
+
descriptions: null,
|
|
445
|
+
attributes: null,
|
|
446
|
+
images: null,
|
|
447
|
+
},
|
|
448
|
+
chosen: {
|
|
449
|
+
attributes: [],
|
|
450
|
+
descriptions: [],
|
|
451
|
+
images: [],
|
|
452
|
+
categoryRetailers: [],
|
|
453
|
+
},
|
|
454
|
+
extraRetailers: [],
|
|
455
|
+
});
|
|
456
|
+
|
|
457
|
+
/*
|
|
458
|
+
Cambia de pestaña conservando lo elegido en la anterior. Sin esto, moverse
|
|
459
|
+
entre productos borraría el trabajo hecho en cada uno.
|
|
460
|
+
*/
|
|
461
|
+
const switchChild = (id) => {
|
|
462
|
+
setPerChild((current) => ({
|
|
463
|
+
...current,
|
|
464
|
+
[activeChild]: { modes, chosen, extraRetailers },
|
|
465
|
+
}));
|
|
466
|
+
|
|
467
|
+
const guardado = perChild[id] ?? seleccionVacia();
|
|
468
|
+
setModes(guardado.modes);
|
|
469
|
+
setChosen(guardado.chosen);
|
|
470
|
+
setExtraRetailers(guardado.extraRetailers ?? []);
|
|
471
|
+
setActiveChild(id);
|
|
472
|
+
};
|
|
473
|
+
|
|
474
|
+
const submit = async () => {
|
|
475
|
+
setLoading(true);
|
|
476
|
+
setError(null);
|
|
477
|
+
|
|
478
|
+
const inherit = buildInherit({ modes, chosen, extraRetailers });
|
|
479
|
+
|
|
480
|
+
// Con las pestañas abiertas cada producto manda la suya. La selección de la
|
|
481
|
+
// pestaña a la vista todavía no está en perChild, así que se agrega aquí: si
|
|
482
|
+
// no, lo último que tocó el usuario se perdería al guardar.
|
|
483
|
+
const inheritByChild =
|
|
484
|
+
!applyToAll && mode === "children"
|
|
485
|
+
? selected.reduce((acc, id) => {
|
|
486
|
+
const guardado =
|
|
487
|
+
id === activeChild ? { modes, chosen, extraRetailers } : perChild[id];
|
|
488
|
+
if (guardado) acc[id] = buildInherit(guardado);
|
|
489
|
+
return acc;
|
|
490
|
+
}, {})
|
|
491
|
+
: undefined;
|
|
294
492
|
|
|
295
493
|
const body =
|
|
296
494
|
mode === "parent"
|
|
297
495
|
? { mode, articleId, parentArticleId: selected[0], inherit }
|
|
298
|
-
: {
|
|
496
|
+
: {
|
|
497
|
+
mode,
|
|
498
|
+
parentArticleId: articleId,
|
|
499
|
+
childrenArticleIds: selected,
|
|
500
|
+
inherit,
|
|
501
|
+
...(inheritByChild ? { inheritByChild } : {}),
|
|
502
|
+
};
|
|
299
503
|
|
|
300
504
|
try {
|
|
301
505
|
const data = await request("link", body);
|
|
@@ -315,6 +519,53 @@ export const LinkVariantsModal = ({
|
|
|
315
519
|
}
|
|
316
520
|
};
|
|
317
521
|
|
|
522
|
+
// Una dimensión está decidida si el usuario eligió un modo, o si el padre no
|
|
523
|
+
// ofrece nada en ella —no se puede pedir que decida sobre una lista vacía—.
|
|
524
|
+
const decidida = (dimension) =>
|
|
525
|
+
modes[dimension] !== null ||
|
|
526
|
+
(dimension === "categoryRetailers"
|
|
527
|
+
? offered.categoryRetailers.length === 0 && extraRetailers.length === 0
|
|
528
|
+
: offered[dimension].length === 0);
|
|
529
|
+
|
|
530
|
+
const DIMENSIONES = [
|
|
531
|
+
"categoryRetailers",
|
|
532
|
+
"descriptions",
|
|
533
|
+
"attributes",
|
|
534
|
+
"images",
|
|
535
|
+
];
|
|
536
|
+
|
|
537
|
+
// Con pestañas, que la que estoy viendo esté completa no basta: ligar escribe
|
|
538
|
+
// en todos, así que todos tienen que estar decididos. Si no, un producto se
|
|
539
|
+
// ligaría sin heredar nada solo porque nadie abrió su pestaña.
|
|
540
|
+
const decididoPara = (id) => {
|
|
541
|
+
const guardado =
|
|
542
|
+
id === activeChild ? { modes, chosen, extraRetailers } : perChild[id];
|
|
543
|
+
if (!guardado) return false;
|
|
544
|
+
|
|
545
|
+
return DIMENSIONES.every((dimension) => {
|
|
546
|
+
if (guardado.modes[dimension] !== null) return true;
|
|
547
|
+
return dimension === "categoryRetailers"
|
|
548
|
+
? offered.categoryRetailers.length === 0 &&
|
|
549
|
+
(guardado.extraRetailers ?? []).length === 0
|
|
550
|
+
: offered[dimension].length === 0;
|
|
551
|
+
});
|
|
552
|
+
};
|
|
553
|
+
|
|
554
|
+
const todoDecidido =
|
|
555
|
+
step === "inherit" &&
|
|
556
|
+
(applyToAll || mode !== "children"
|
|
557
|
+
? DIMENSIONES.every(decidida)
|
|
558
|
+
: selected.every(decididoPara));
|
|
559
|
+
|
|
560
|
+
const faltantes = [
|
|
561
|
+
["categoryRetailers", "cadenas"],
|
|
562
|
+
["descriptions", "descripciones"],
|
|
563
|
+
["attributes", "atributos"],
|
|
564
|
+
["images", "imágenes"],
|
|
565
|
+
]
|
|
566
|
+
.filter(([clave]) => !decidida(clave))
|
|
567
|
+
.map(([, nombre]) => nombre);
|
|
568
|
+
|
|
318
569
|
const selectionLabel =
|
|
319
570
|
mode === "parent"
|
|
320
571
|
? "Se elige un solo producto padre."
|
|
@@ -417,129 +668,274 @@ export const LinkVariantsModal = ({
|
|
|
417
668
|
))}
|
|
418
669
|
</List>
|
|
419
670
|
)}
|
|
671
|
+
|
|
672
|
+
{/* Cuántos hay de verdad. Sin esto la lista se corta en 20 y no hay
|
|
673
|
+
forma de saber si eso es todo o solo el principio. */}
|
|
674
|
+
{searched && candidates.length > 0 && (
|
|
675
|
+
<Paging>
|
|
676
|
+
<span>
|
|
677
|
+
Mostrando {candidates.length} de {total}
|
|
678
|
+
</span>
|
|
679
|
+
{hasMore && (
|
|
680
|
+
<button type="button" onClick={loadMore} disabled={loadingMore}>
|
|
681
|
+
{loadingMore ? "Cargando…" : "Cargar más"}
|
|
682
|
+
</button>
|
|
683
|
+
)}
|
|
684
|
+
</Paging>
|
|
685
|
+
)}
|
|
420
686
|
</>
|
|
421
687
|
)}
|
|
422
688
|
|
|
423
689
|
{step === "inherit" && (
|
|
424
690
|
<>
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
691
|
+
{mode === "children" && selected.length > 1 && (
|
|
692
|
+
<>
|
|
693
|
+
<label className="apply-all">
|
|
694
|
+
<input
|
|
695
|
+
type="checkbox"
|
|
696
|
+
checked={applyToAll}
|
|
697
|
+
onChange={(event) => {
|
|
698
|
+
setApplyToAll(event.target.checked);
|
|
699
|
+
if (event.target.checked) setPerChild({});
|
|
700
|
+
}}
|
|
701
|
+
/>
|
|
702
|
+
<span>
|
|
703
|
+
Aplicar lo mismo a los {selected.length} productos
|
|
704
|
+
<span className="hint">
|
|
705
|
+
Desmárcalo si alguno debe heredar algo distinto.
|
|
706
|
+
</span>
|
|
438
707
|
</span>
|
|
439
|
-
</
|
|
440
|
-
</label>
|
|
441
|
-
<label className="choice">
|
|
442
|
-
<input
|
|
443
|
-
type="radio"
|
|
444
|
-
checked={inheritMode === "selected"}
|
|
445
|
-
onChange={() => setInheritMode("selected")}
|
|
446
|
-
/>
|
|
447
|
-
<span>
|
|
448
|
-
Elegir cuáles
|
|
449
|
-
<span className="hint">
|
|
450
|
-
Lo que no elijas conserva el valor que ya tiene la variante.
|
|
451
|
-
</span>
|
|
452
|
-
</span>
|
|
453
|
-
</label>
|
|
454
|
-
</Section>
|
|
708
|
+
</label>
|
|
455
709
|
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
710
|
+
{!applyToAll && (
|
|
711
|
+
<Tabs>
|
|
712
|
+
{selected.map((id, indice) => {
|
|
713
|
+
const producto = candidates.find(
|
|
714
|
+
(item) => item.articleId === id
|
|
715
|
+
);
|
|
716
|
+
return (
|
|
717
|
+
<button
|
|
718
|
+
key={id}
|
|
719
|
+
type="button"
|
|
720
|
+
className={id === activeChild ? "active" : ""}
|
|
721
|
+
onClick={() => switchChild(id)}
|
|
722
|
+
>
|
|
723
|
+
<span className="orden">{indice + 1}</span>
|
|
724
|
+
{producto?.name || `Producto ${id}`}
|
|
725
|
+
{decididoPara(id) && (
|
|
726
|
+
<span className="listo" aria-label="listo">
|
|
727
|
+
✓
|
|
728
|
+
</span>
|
|
729
|
+
)}
|
|
730
|
+
</button>
|
|
731
|
+
);
|
|
732
|
+
})}
|
|
733
|
+
</Tabs>
|
|
734
|
+
)}
|
|
735
|
+
</>
|
|
461
736
|
)}
|
|
462
737
|
|
|
463
|
-
{
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
738
|
+
{/* El orden no es casual: primero se decide en qué cadenas vive la
|
|
739
|
+
variante, porque de eso depende qué atributos le aplican. Las
|
|
740
|
+
imágenes van al final: son la decisión más visual y la que menos
|
|
741
|
+
arrastra a las demás. */}
|
|
742
|
+
|
|
743
|
+
<Block>
|
|
744
|
+
<h3>
|
|
745
|
+
<span className="paso">1</span> Cadenas y categorías
|
|
746
|
+
</h3>
|
|
747
|
+
<Choices>
|
|
748
|
+
<label>
|
|
749
|
+
<input
|
|
750
|
+
type="radio"
|
|
751
|
+
checked={modes.categoryRetailers === "all"}
|
|
752
|
+
onChange={() =>
|
|
753
|
+
chooseMode(
|
|
754
|
+
"categoryRetailers",
|
|
755
|
+
"all",
|
|
756
|
+
offered.categoryRetailers.map(
|
|
757
|
+
(item) => `${item.retailerId}-${item.categoryId}`
|
|
758
|
+
)
|
|
759
|
+
)
|
|
760
|
+
}
|
|
761
|
+
/>
|
|
762
|
+
<span>
|
|
763
|
+
Todas las del producto padre
|
|
764
|
+
<span className="hint">
|
|
765
|
+
{offered.categoryRetailers.length} cadena(s).
|
|
766
|
+
</span>
|
|
767
|
+
</span>
|
|
768
|
+
</label>
|
|
769
|
+
<label>
|
|
770
|
+
<input
|
|
771
|
+
type="radio"
|
|
772
|
+
checked={modes.categoryRetailers === "some"}
|
|
773
|
+
onChange={() => chooseMode("categoryRetailers", "some", [])}
|
|
774
|
+
/>
|
|
775
|
+
<span>
|
|
776
|
+
Elegir cuáles
|
|
777
|
+
<span className="hint">
|
|
778
|
+
También puedes agregar una cadena que el padre no tenga.
|
|
779
|
+
</span>
|
|
780
|
+
</span>
|
|
781
|
+
</label>
|
|
782
|
+
</Choices>
|
|
783
|
+
|
|
784
|
+
<List>
|
|
785
|
+
{[
|
|
786
|
+
...offered.categoryRetailers.map((item) => ({
|
|
787
|
+
...item,
|
|
788
|
+
clave: `${item.retailerId}-${item.categoryId}`,
|
|
789
|
+
delPadre: true,
|
|
790
|
+
})),
|
|
791
|
+
...extraRetailers.map((item) => ({ ...item, delPadre: false })),
|
|
792
|
+
].map((item) => (
|
|
793
|
+
<li key={item.clave}>
|
|
467
794
|
<label>
|
|
468
795
|
<input
|
|
469
796
|
type="checkbox"
|
|
470
|
-
|
|
471
|
-
|
|
797
|
+
disabled={modes.categoryRetailers === "all"}
|
|
798
|
+
checked={chosen.categoryRetailers.includes(item.clave)}
|
|
799
|
+
onChange={() =>
|
|
800
|
+
toggleChoice("categoryRetailers", item.clave)
|
|
801
|
+
}
|
|
802
|
+
/>
|
|
803
|
+
<img
|
|
804
|
+
className="logo"
|
|
805
|
+
src={getRetailerPicture(item.retailerId, 24, 24)}
|
|
806
|
+
alt=""
|
|
472
807
|
/>
|
|
473
808
|
<span>
|
|
474
|
-
{
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
)}
|
|
480
|
-
{attribute.ambiguousName && (
|
|
481
|
-
<span className="note">
|
|
482
|
-
se llama distinto en otra cadena
|
|
483
|
-
</span>
|
|
484
|
-
)}
|
|
485
|
-
</span>
|
|
486
|
-
<span className="value" title={attribute.value}>
|
|
487
|
-
{attribute.value}
|
|
809
|
+
{item.retailerName}
|
|
810
|
+
<span className="meta">
|
|
811
|
+
{item.categoryName || "Sin categoría"}
|
|
812
|
+
{!item.delPadre && " · no la tiene el padre"}
|
|
813
|
+
</span>
|
|
488
814
|
</span>
|
|
489
815
|
</label>
|
|
490
816
|
</li>
|
|
491
817
|
))}
|
|
492
|
-
</
|
|
493
|
-
)}
|
|
818
|
+
</List>
|
|
494
819
|
|
|
495
|
-
|
|
496
|
-
|
|
820
|
+
<AddRetailer>
|
|
821
|
+
<select
|
|
822
|
+
value={newRetailer}
|
|
823
|
+
onChange={(event) => {
|
|
824
|
+
const valor = event.target.value;
|
|
825
|
+
setNewRetailer(valor);
|
|
826
|
+
setNewDepartment("");
|
|
827
|
+
setNewCategory("");
|
|
828
|
+
if (valor) loadFilters(valor, null);
|
|
829
|
+
}}
|
|
830
|
+
>
|
|
831
|
+
<option value="">Agregar otra cadena…</option>
|
|
832
|
+
{filters.retailers.map((item) => (
|
|
833
|
+
<option key={item.value} value={item.value}>
|
|
834
|
+
{item.label}
|
|
835
|
+
</option>
|
|
836
|
+
))}
|
|
837
|
+
</select>
|
|
838
|
+
<select
|
|
839
|
+
value={newDepartment}
|
|
840
|
+
disabled={!newRetailer || !filters.departments.length}
|
|
841
|
+
onChange={(event) => {
|
|
842
|
+
const valor = event.target.value;
|
|
843
|
+
setNewDepartment(valor);
|
|
844
|
+
setNewCategory("");
|
|
845
|
+
if (valor) loadFilters(newRetailer, valor);
|
|
846
|
+
}}
|
|
847
|
+
>
|
|
848
|
+
<option value="">Departamento…</option>
|
|
849
|
+
{filters.departments.map((item) => (
|
|
850
|
+
<option key={item.value} value={item.value}>
|
|
851
|
+
{item.label}
|
|
852
|
+
</option>
|
|
853
|
+
))}
|
|
854
|
+
</select>
|
|
855
|
+
<select
|
|
856
|
+
value={newCategory}
|
|
857
|
+
disabled={!newDepartment || !filters.categories.length}
|
|
858
|
+
onChange={(event) => setNewCategory(event.target.value)}
|
|
859
|
+
>
|
|
860
|
+
<option value="">Categoría…</option>
|
|
861
|
+
{filters.categories.map((item) => (
|
|
862
|
+
<option key={item.value} value={item.value}>
|
|
863
|
+
{item.label}
|
|
864
|
+
</option>
|
|
865
|
+
))}
|
|
866
|
+
</select>
|
|
497
867
|
<button
|
|
498
868
|
type="button"
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
onClick={() =>
|
|
502
|
-
setOpenSection(
|
|
503
|
-
openSection === "descriptions" ? null : "descriptions"
|
|
504
|
-
)
|
|
505
|
-
}
|
|
869
|
+
onClick={addRetailer}
|
|
870
|
+
disabled={!newRetailer || !newCategory || loadingFilters}
|
|
506
871
|
>
|
|
507
|
-
|
|
508
|
-
<span className="count">
|
|
509
|
-
{chosen.descriptions.length} de{" "}
|
|
510
|
-
{offered.descriptions.length}
|
|
511
|
-
</span>
|
|
872
|
+
{loadingFilters ? "…" : "Agregar"}
|
|
512
873
|
</button>
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
874
|
+
</AddRetailer>
|
|
875
|
+
</Block>
|
|
876
|
+
|
|
877
|
+
<Block>
|
|
878
|
+
<h3>
|
|
879
|
+
<span className="paso">2</span> Descripciones
|
|
880
|
+
</h3>
|
|
881
|
+
{offered.descriptions.length ? (
|
|
882
|
+
<>
|
|
883
|
+
<Choices>
|
|
884
|
+
<label>
|
|
885
|
+
<input
|
|
886
|
+
type="radio"
|
|
887
|
+
checked={modes.descriptions === "all"}
|
|
888
|
+
onChange={() =>
|
|
889
|
+
chooseMode(
|
|
890
|
+
"descriptions",
|
|
891
|
+
"all",
|
|
892
|
+
offered.descriptions.map((item) => item.id)
|
|
893
|
+
)
|
|
894
|
+
}
|
|
895
|
+
/>
|
|
896
|
+
<span>
|
|
897
|
+
Todas las que tienen valor
|
|
898
|
+
<span className="hint">
|
|
899
|
+
{offered.descriptions.length} descripción(es) con
|
|
900
|
+
información en el padre.
|
|
901
|
+
</span>
|
|
902
|
+
</span>
|
|
903
|
+
</label>
|
|
904
|
+
<label>
|
|
905
|
+
<input
|
|
906
|
+
type="radio"
|
|
907
|
+
checked={modes.descriptions === "some"}
|
|
908
|
+
onChange={() => chooseMode("descriptions", "some", [])}
|
|
909
|
+
/>
|
|
910
|
+
<span>
|
|
911
|
+
Elegir cuáles
|
|
912
|
+
<span className="hint">
|
|
913
|
+
Lo que no elijas conserva el valor que ya tiene la
|
|
914
|
+
variante.
|
|
915
|
+
</span>
|
|
916
|
+
</span>
|
|
917
|
+
</label>
|
|
918
|
+
</Choices>
|
|
530
919
|
|
|
531
|
-
{openSection === "descriptions" &&
|
|
532
|
-
(offered.descriptions.length ? (
|
|
533
920
|
<AttributeList>
|
|
534
921
|
{offered.descriptions.map((item) => (
|
|
535
922
|
<li key={item.id}>
|
|
536
923
|
<label>
|
|
537
924
|
<input
|
|
538
925
|
type="checkbox"
|
|
926
|
+
disabled={modes.descriptions === "all"}
|
|
539
927
|
checked={chosen.descriptions.includes(item.id)}
|
|
540
928
|
onChange={() => toggleChoice("descriptions", item.id)}
|
|
541
929
|
/>
|
|
542
|
-
<span>
|
|
930
|
+
<span>
|
|
931
|
+
{item.name}
|
|
932
|
+
{item.duplicated && (
|
|
933
|
+
<span className="note">
|
|
934
|
+
el padre tiene {item.rows} versiones de este
|
|
935
|
+
texto; se copian todas
|
|
936
|
+
</span>
|
|
937
|
+
)}
|
|
938
|
+
</span>
|
|
543
939
|
<span className="value" title={item.value}>
|
|
544
940
|
{item.value}
|
|
545
941
|
</span>
|
|
@@ -547,167 +943,175 @@ export const LinkVariantsModal = ({
|
|
|
547
943
|
</li>
|
|
548
944
|
))}
|
|
549
945
|
</AttributeList>
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
946
|
+
</>
|
|
947
|
+
) : (
|
|
948
|
+
<Message>
|
|
949
|
+
El producto padre no tiene descripciones que heredar.
|
|
950
|
+
</Message>
|
|
951
|
+
)}
|
|
952
|
+
</Block>
|
|
554
953
|
|
|
555
|
-
<
|
|
556
|
-
<
|
|
557
|
-
<
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
"
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
954
|
+
<Block>
|
|
955
|
+
<h3>
|
|
956
|
+
<span className="paso">3</span> Atributos de la ficha técnica
|
|
957
|
+
</h3>
|
|
958
|
+
{offered.attributes.length ? (
|
|
959
|
+
<>
|
|
960
|
+
<Choices>
|
|
961
|
+
<label>
|
|
962
|
+
<input
|
|
963
|
+
type="radio"
|
|
964
|
+
checked={modes.attributes === "all"}
|
|
965
|
+
onChange={() =>
|
|
966
|
+
chooseMode(
|
|
967
|
+
"attributes",
|
|
968
|
+
"all",
|
|
969
|
+
offered.attributes.map((item) => item.id)
|
|
970
|
+
)
|
|
971
|
+
}
|
|
972
|
+
/>
|
|
973
|
+
<span>
|
|
974
|
+
Todos los que tienen valor
|
|
975
|
+
<span className="hint">
|
|
976
|
+
{offered.attributes.length} atributo(s) con información
|
|
977
|
+
en el padre.
|
|
978
|
+
</span>
|
|
979
|
+
</span>
|
|
980
|
+
</label>
|
|
981
|
+
<label>
|
|
982
|
+
<input
|
|
983
|
+
type="radio"
|
|
984
|
+
checked={modes.attributes === "some"}
|
|
985
|
+
onChange={() => chooseMode("attributes", "some", [])}
|
|
986
|
+
/>
|
|
987
|
+
<span>
|
|
988
|
+
Elegir cuáles
|
|
989
|
+
<span className="hint">
|
|
990
|
+
Lo que no elijas conserva el valor que ya tiene la
|
|
991
|
+
variante.
|
|
992
|
+
</span>
|
|
993
|
+
</span>
|
|
994
|
+
</label>
|
|
995
|
+
</Choices>
|
|
996
|
+
|
|
997
|
+
{activeRetailerId && !nameScope && (
|
|
998
|
+
<Message>
|
|
999
|
+
El producto padre no comparte la cadena que tienes abierta,
|
|
1000
|
+
así que los nombres de abajo son los de otra de sus cadenas.
|
|
1001
|
+
</Message>
|
|
1002
|
+
)}
|
|
1003
|
+
|
|
1004
|
+
<AttributeList>
|
|
1005
|
+
{offered.attributes.map((attribute) => (
|
|
1006
|
+
<li key={attribute.id}>
|
|
1007
|
+
<label>
|
|
1008
|
+
<input
|
|
1009
|
+
type="checkbox"
|
|
1010
|
+
disabled={modes.attributes === "all"}
|
|
1011
|
+
checked={chosen.attributes.includes(attribute.id)}
|
|
1012
|
+
onChange={() => toggleChoice("attributes", attribute.id)}
|
|
1013
|
+
/>
|
|
1014
|
+
<span>
|
|
1015
|
+
{attribute.name}
|
|
1016
|
+
{attribute.unconfigured && (
|
|
1017
|
+
<span className="note">
|
|
1018
|
+
sin configuración en las cadenas del padre
|
|
1019
|
+
</span>
|
|
1020
|
+
)}
|
|
1021
|
+
{attribute.ambiguousName && (
|
|
1022
|
+
<span className="note">
|
|
1023
|
+
se llama distinto en otra cadena
|
|
1024
|
+
</span>
|
|
1025
|
+
)}
|
|
1026
|
+
</span>
|
|
1027
|
+
<span className="value" title={attribute.value}>
|
|
1028
|
+
{attribute.value}
|
|
1029
|
+
</span>
|
|
1030
|
+
</label>
|
|
1031
|
+
</li>
|
|
1032
|
+
))}
|
|
1033
|
+
</AttributeList>
|
|
1034
|
+
</>
|
|
1035
|
+
) : (
|
|
1036
|
+
<Message>
|
|
1037
|
+
El producto padre no tiene atributos con valor que heredar.
|
|
1038
|
+
</Message>
|
|
1039
|
+
)}
|
|
1040
|
+
</Block>
|
|
1041
|
+
|
|
1042
|
+
<Block>
|
|
1043
|
+
<h3>
|
|
1044
|
+
<span className="paso">4</span> Imágenes
|
|
1045
|
+
</h3>
|
|
1046
|
+
{offered.images.length ? (
|
|
1047
|
+
<>
|
|
1048
|
+
<Choices>
|
|
1049
|
+
<label>
|
|
1050
|
+
<input
|
|
1051
|
+
type="radio"
|
|
1052
|
+
checked={modes.images === "all"}
|
|
1053
|
+
onChange={() =>
|
|
1054
|
+
chooseMode(
|
|
1055
|
+
"images",
|
|
1056
|
+
"all",
|
|
1057
|
+
offered.images.map((item) => item.id)
|
|
1058
|
+
)
|
|
1059
|
+
}
|
|
1060
|
+
/>
|
|
1061
|
+
<span>
|
|
1062
|
+
Todas
|
|
1063
|
+
<span className="hint">
|
|
1064
|
+
{offered.images.length} imagen(es) en el padre.
|
|
1065
|
+
</span>
|
|
1066
|
+
</span>
|
|
1067
|
+
</label>
|
|
1068
|
+
<label>
|
|
1069
|
+
<input
|
|
1070
|
+
type="radio"
|
|
1071
|
+
checked={modes.images === "some"}
|
|
1072
|
+
onChange={() => chooseMode("images", "some", [])}
|
|
1073
|
+
/>
|
|
1074
|
+
<span>
|
|
1075
|
+
Elegir cuáles
|
|
1076
|
+
<span className="hint">
|
|
1077
|
+
Útil cuando la variante cambia de color o de empaque.
|
|
1078
|
+
</span>
|
|
1079
|
+
</span>
|
|
1080
|
+
</label>
|
|
1081
|
+
</Choices>
|
|
587
1082
|
|
|
588
|
-
{openSection === "images" &&
|
|
589
|
-
(offered.images.length ? (
|
|
590
1083
|
<ImageGrid>
|
|
591
1084
|
{offered.images.map((image) => (
|
|
592
1085
|
<label
|
|
593
1086
|
key={image.id}
|
|
594
|
-
className={
|
|
595
|
-
chosen.images.includes(image.id) ? "picked" : ""
|
|
596
|
-
|
|
1087
|
+
className={[
|
|
1088
|
+
chosen.images.includes(image.id) ? "picked" : "",
|
|
1089
|
+
modes.images === "all" ? "locked" : "",
|
|
1090
|
+
]
|
|
1091
|
+
.filter(Boolean)
|
|
1092
|
+
.join(" ")}
|
|
597
1093
|
>
|
|
598
1094
|
<input
|
|
599
1095
|
type="checkbox"
|
|
1096
|
+
disabled={modes.images === "all"}
|
|
600
1097
|
checked={chosen.images.includes(image.id)}
|
|
601
1098
|
onChange={() => toggleChoice("images", image.id)}
|
|
602
1099
|
/>
|
|
603
|
-
<img
|
|
604
|
-
|
|
1100
|
+
<img
|
|
1101
|
+
src={getProductImage(image.src)}
|
|
1102
|
+
alt={image.imageType || "Imagen del producto"}
|
|
1103
|
+
/>
|
|
1104
|
+
<span>{image.imageType || "Sin tipo de toma"}</span>
|
|
605
1105
|
</label>
|
|
606
1106
|
))}
|
|
607
1107
|
</ImageGrid>
|
|
608
|
-
) : (
|
|
609
|
-
<Message>El producto padre no tiene imágenes.</Message>
|
|
610
|
-
))}
|
|
611
|
-
</Section>
|
|
612
|
-
|
|
613
|
-
<Section>
|
|
614
|
-
<SectionHead>
|
|
615
|
-
<button
|
|
616
|
-
type="button"
|
|
617
|
-
className="toggle"
|
|
618
|
-
aria-expanded={openSection === "retailers"}
|
|
619
|
-
onClick={() =>
|
|
620
|
-
setOpenSection(
|
|
621
|
-
openSection === "retailers" ? null : "retailers"
|
|
622
|
-
)
|
|
623
|
-
}
|
|
624
|
-
>
|
|
625
|
-
Cadenas y categorías
|
|
626
|
-
<span className="count">
|
|
627
|
-
{chosen.categoryRetailers.length} seleccionada(s)
|
|
628
|
-
</span>
|
|
629
|
-
</button>
|
|
630
|
-
</SectionHead>
|
|
631
|
-
|
|
632
|
-
{openSection === "retailers" && (
|
|
633
|
-
<>
|
|
634
|
-
<List>
|
|
635
|
-
{[
|
|
636
|
-
...offered.categoryRetailers.map((item) => ({
|
|
637
|
-
...item,
|
|
638
|
-
clave: `${item.retailerId}-${item.categoryId}`,
|
|
639
|
-
delPadre: true,
|
|
640
|
-
})),
|
|
641
|
-
...extraRetailers.map((item) => ({
|
|
642
|
-
...item,
|
|
643
|
-
delPadre: false,
|
|
644
|
-
})),
|
|
645
|
-
].map((item) => (
|
|
646
|
-
<li key={item.clave}>
|
|
647
|
-
<label>
|
|
648
|
-
<input
|
|
649
|
-
type="checkbox"
|
|
650
|
-
checked={chosen.categoryRetailers.includes(
|
|
651
|
-
item.clave
|
|
652
|
-
)}
|
|
653
|
-
onChange={() =>
|
|
654
|
-
toggleChoice("categoryRetailers", item.clave)
|
|
655
|
-
}
|
|
656
|
-
/>
|
|
657
|
-
<span>
|
|
658
|
-
{item.retailerName}
|
|
659
|
-
<span className="meta">
|
|
660
|
-
{item.categoryName || "Sin categoría"}
|
|
661
|
-
{!item.delPadre && " · no la tiene el padre"}
|
|
662
|
-
</span>
|
|
663
|
-
</span>
|
|
664
|
-
</label>
|
|
665
|
-
</li>
|
|
666
|
-
))}
|
|
667
|
-
</List>
|
|
668
|
-
|
|
669
|
-
{/* Agregar una cadena que el padre no tiene. No es herencia:
|
|
670
|
-
la variante entra a esa cadena por su cuenta. */}
|
|
671
|
-
<AddRetailer>
|
|
672
|
-
<select
|
|
673
|
-
value={newRetailer}
|
|
674
|
-
onChange={(event) => setNewRetailer(event.target.value)}
|
|
675
|
-
>
|
|
676
|
-
<option value="">Agregar otra cadena…</option>
|
|
677
|
-
{(retailersAvailable || []).map((item) => (
|
|
678
|
-
<option
|
|
679
|
-
key={item.id ?? item.value}
|
|
680
|
-
value={item.id ?? item.value}
|
|
681
|
-
>
|
|
682
|
-
{item.name ?? item.label}
|
|
683
|
-
</option>
|
|
684
|
-
))}
|
|
685
|
-
</select>
|
|
686
|
-
<select
|
|
687
|
-
value={newCategory}
|
|
688
|
-
onChange={(event) => setNewCategory(event.target.value)}
|
|
689
|
-
>
|
|
690
|
-
<option value="">Categoría…</option>
|
|
691
|
-
{(categoriesAvailable || []).map((item) => (
|
|
692
|
-
<option
|
|
693
|
-
key={item.id ?? item.value}
|
|
694
|
-
value={item.id ?? item.value}
|
|
695
|
-
>
|
|
696
|
-
{item.name ?? item.label}
|
|
697
|
-
</option>
|
|
698
|
-
))}
|
|
699
|
-
</select>
|
|
700
|
-
<button
|
|
701
|
-
type="button"
|
|
702
|
-
onClick={addRetailer}
|
|
703
|
-
disabled={!newRetailer || !newCategory}
|
|
704
|
-
>
|
|
705
|
-
Agregar
|
|
706
|
-
</button>
|
|
707
|
-
</AddRetailer>
|
|
708
1108
|
</>
|
|
1109
|
+
) : (
|
|
1110
|
+
<Message>
|
|
1111
|
+
El producto padre no tiene imágenes que heredar.
|
|
1112
|
+
</Message>
|
|
709
1113
|
)}
|
|
710
|
-
</
|
|
1114
|
+
</Block>
|
|
711
1115
|
|
|
712
1116
|
{/* Es la diferencia entre link y propagate, y es la que sorprende:
|
|
713
1117
|
aquí lo elegido se escribe encima aunque la variante lo hubiera
|
|
@@ -722,7 +1126,13 @@ export const LinkVariantsModal = ({
|
|
|
722
1126
|
|
|
723
1127
|
<Footer>
|
|
724
1128
|
<span className="summary">
|
|
725
|
-
{step === "search"
|
|
1129
|
+
{step === "search"
|
|
1130
|
+
? selectionLabel
|
|
1131
|
+
: todoDecidido
|
|
1132
|
+
? "Todo revisado. Puedes ligar."
|
|
1133
|
+
: !applyToAll && mode === "children"
|
|
1134
|
+
? `Faltan ${selected.filter((id) => !decididoPara(id)).length} producto(s) por revisar.`
|
|
1135
|
+
: `Falta decidir: ${faltantes.join(", ")}.`}
|
|
726
1136
|
</span>
|
|
727
1137
|
<div className="actions">
|
|
728
1138
|
{step === "inherit" && (
|
|
@@ -752,15 +1162,7 @@ export const LinkVariantsModal = ({
|
|
|
752
1162
|
type="button"
|
|
753
1163
|
className="primary"
|
|
754
1164
|
onClick={submit}
|
|
755
|
-
disabled={
|
|
756
|
-
loading ||
|
|
757
|
-
// Solo estorba cuando hay algo que elegir y no se eligió nada.
|
|
758
|
-
// Con el padre sin atributos heredables, ligar sigue siendo
|
|
759
|
-
// válido: se heredan imágenes, descripciones y cadenas.
|
|
760
|
-
(inheritMode === "selected" &&
|
|
761
|
-
offered.attributes.length > 0 &&
|
|
762
|
-
!chosen.attributes.length)
|
|
763
|
-
}
|
|
1165
|
+
disabled={loading || !todoDecidido}
|
|
764
1166
|
>
|
|
765
1167
|
{loading ? "Ligando…" : "Ligar"}
|
|
766
1168
|
</button>
|