contentoh-components-library 21.6.27 → 21.6.29

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.
@@ -9,7 +9,11 @@ import {
9
9
  useProviderProductEdition,
10
10
  } from "./context/provider-product-edition.context";
11
11
  import { normalizeProduct, getConceptByTab } from "./utils";
12
- import { getMinorStatus, getMinorStatusFromRetailerMap, STATUS_DICTIONARY } from "../../../global-files/statusDictionary";
12
+ import {
13
+ getMinorStatus,
14
+ getMinorStatusFromRetailerMap,
15
+ STATUS_DICTIONARY,
16
+ } from "../../../global-files/statusDictionary";
13
17
 
14
18
  import {
15
19
  getPercentage,
@@ -35,6 +39,7 @@ import { ImageDataTable } from "../../organisms/ImageDataTable";
35
39
  import { ImagePreviewer } from "../../organisms/ImagePreviewer";
36
40
  import { InputGroup } from "../../organisms/InputGroup";
37
41
  import { LinkVariantsModal } from "../../organisms/LinkVariantsModal";
42
+ import { PropagateChangesModal } from "../../organisms/PropagateChangesModal";
38
43
  import { readPayload } from "../../organisms/LinkVariantsModal/api";
39
44
  import { ProductImageModal } from "../../organisms/ProductImageModal";
40
45
  import { VersionSelector } from "../../organisms/VersionSelector";
@@ -79,6 +84,8 @@ const ProviderProductEditionView = ({
79
84
  const [family, setFamily] = useState(null);
80
85
  const [showFamily, setShowFamily] = useState(false);
81
86
  const [loadingVariant, setLoadingVariant] = useState(null);
87
+ const [pendingPropagation, setPendingPropagation] = useState(null);
88
+ const [preparandoPropagacion, setPreparandoPropagacion] = useState(false);
82
89
  const [showRejectModal, setShowRejectModal] = useState(false);
83
90
  const [headerTop, setHeaderTop] = useState(0);
84
91
  const [assig, setAssig] = useState({});
@@ -233,7 +240,7 @@ const ProviderProductEditionView = ({
233
240
  id_category,
234
241
  version,
235
242
  token,
236
- id_retailer,
243
+ id_retailer
237
244
  ),
238
245
  getPercentage({ data, headers }),
239
246
  getServicesData({
@@ -245,11 +252,10 @@ const ProviderProductEditionView = ({
245
252
  ]);
246
253
 
247
254
  const percentageRaw = percentageRes?.[0];
248
- const percentages = (
249
- Array.isArray(percentageRaw)
255
+ const percentages =
256
+ (Array.isArray(percentageRaw)
250
257
  ? percentageRaw[0]?.relations
251
- : JSON.parse(percentageRaw?.body ?? "null")?.[0]?.relations
252
- ) ?? [];
258
+ : JSON.parse(percentageRaw?.body ?? "null")?.[0]?.relations) ?? [];
253
259
 
254
260
  const orderMap =
255
261
  services[2].inputsByRetailer ??
@@ -277,12 +283,16 @@ const ProviderProductEditionView = ({
277
283
 
278
284
  return acc;
279
285
  },
280
- {},
286
+ {}
281
287
  );
282
288
 
283
289
  const getServiceMinorStatus = (serviceName) => {
284
- const records = servicesDataRes.filter((s) => s.service === serviceName);
285
- return records.length > 0 ? getMinorStatus(records.map((s) => s.status)) : undefined;
290
+ const records = servicesDataRes.filter(
291
+ (s) => s.service === serviceName
292
+ );
293
+ return records.length > 0
294
+ ? getMinorStatus(records.map((s) => s.status))
295
+ : undefined;
286
296
  };
287
297
 
288
298
  const imagesMinorStatus = getServiceMinorStatus("images");
@@ -295,9 +305,15 @@ const ProviderProductEditionView = ({
295
305
  ...state.product,
296
306
  statusByRetailer: updatedStatusByRetailer,
297
307
  status: getMinorStatusFromRetailerMap(updatedStatusByRetailer),
298
- ...(imagesMinorStatus !== undefined && { images_status: imagesMinorStatus }),
299
- ...(descriptionMinorStatus !== undefined && { description_status: descriptionMinorStatus }),
300
- ...(datasheetMinorStatus !== undefined && { datasheet_status: datasheetMinorStatus }),
308
+ ...(imagesMinorStatus !== undefined && {
309
+ images_status: imagesMinorStatus,
310
+ }),
311
+ ...(descriptionMinorStatus !== undefined && {
312
+ description_status: descriptionMinorStatus,
313
+ }),
314
+ ...(datasheetMinorStatus !== undefined && {
315
+ datasheet_status: datasheetMinorStatus,
316
+ }),
301
317
  },
302
318
  });
303
319
 
@@ -320,7 +336,7 @@ const ProviderProductEditionView = ({
320
336
  const activePercentage =
321
337
  percentages.find(({ id_retailer: rId }) => rId === id_retailer) ||
322
338
  state.product.percentages?.find(
323
- ({ id_retailer: rId }) => rId === id_retailer,
339
+ ({ id_retailer: rId }) => rId === id_retailer
324
340
  );
325
341
 
326
342
  dispatch({
@@ -364,7 +380,10 @@ const ProviderProductEditionView = ({
364
380
  };
365
381
  dispatch({
366
382
  type: "SET_DATASHEETS_INPUTS",
367
- payload: [datasheetsActiveRetailer, state.services.datasheets.inputs],
383
+ payload: [
384
+ datasheetsActiveRetailer,
385
+ state.services.datasheets.inputs,
386
+ ],
368
387
  });
369
388
  } else {
370
389
  dispatch({ type: "SET_DATASHEETS_INPUTS", payload: [] });
@@ -373,7 +392,7 @@ const ProviderProductEditionView = ({
373
392
 
374
393
  if (state.services.descriptions) {
375
394
  const descriptionsActiveRetailer = state.services.descriptions.filter(
376
- (item) => item.id === state.active_retailer.id_retailer,
395
+ (item) => item.id === state.active_retailer.id_retailer
377
396
  );
378
397
 
379
398
  dispatch({
@@ -388,8 +407,8 @@ const ProviderProductEditionView = ({
388
407
  retailerInput.some(
389
408
  (input) =>
390
409
  input.id_retailer === state.active_retailer.id_retailer &&
391
- input.id_image === value.image_id,
392
- ),
410
+ input.id_image === value.image_id
411
+ )
393
412
  );
394
413
  });
395
414
 
@@ -419,7 +438,7 @@ const ProviderProductEditionView = ({
419
438
  const { status: currentStatus } = state.services_data.find(
420
439
  (service) =>
421
440
  service.service === currentService &&
422
- service.id_retailer === currentRetailer,
441
+ service.id_retailer === currentRetailer
423
442
  );
424
443
 
425
444
  const originTab = origin[state.active_tab];
@@ -488,7 +507,7 @@ const ProviderProductEditionView = ({
488
507
  name: e?.name,
489
508
  required: e?.required,
490
509
  active: state.images_values?.values?.some(
491
- (value) => value?.image_id === e?.id,
510
+ (value) => value?.image_id === e?.id
492
511
  ),
493
512
  }));
494
513
  const imageType = state.images_values?.imageType?.map((e) => ({
@@ -499,7 +518,7 @@ const ProviderProductEditionView = ({
499
518
  (e) => ({
500
519
  value: e?.id,
501
520
  name: e?.name,
502
- }),
521
+ })
503
522
  );
504
523
 
505
524
  return state.images_values?.values?.map((image, index) => (
@@ -658,15 +677,56 @@ const ProviderProductEditionView = ({
658
677
  });
659
678
  };
660
679
 
680
+ /*
681
+ Los atributos que se acaban de tocar, con el nombre que ve el usuario.
682
+
683
+ `updated_datasheets_inputs` trae { attributeId, value } y nada más, así que el
684
+ nombre se busca en los inputs de la cadena activa: un diálogo que preguntara
685
+ por "el atributo 1023" no serviría para decidir nada.
686
+ */
687
+ const cambiosDeFichaTecnica = () => {
688
+ const retailerId = state.active_retailer?.id_retailer;
689
+ const inputs = state.services?.datasheets?.inputs ?? {};
690
+
691
+ return (state.updated_datasheets_inputs ?? []).map((item) => {
692
+ const input = inputs[`${item.attributeId}_${retailerId}`];
693
+ return {
694
+ id: Number(item.attributeId),
695
+ name: input?.name ?? `Atributo ${item.attributeId}`,
696
+ value: item.value,
697
+ };
698
+ });
699
+ };
700
+
661
701
  const handleOnClickSave = () => {
662
- if (state.saving) return;
702
+ // `preparandoPropagacion` cubre la ventana entre el clic y la apertura del
703
+ // diálogo: sin esto, dos clics seguidos lanzan dos consultas de familia.
704
+ if (state.saving || preparandoPropagacion) return;
663
705
  switch (state.active_tab) {
664
706
  case "Descripción":
665
707
  saveDescriptions(token);
666
708
  break;
667
- case "Ficha técnica":
709
+ case "Ficha técnica": {
710
+ // Guardar un padre con variantes abre la decisión de propagar. Sin este
711
+ // paso el cambio se queda solo en el padre y las variantes se van
712
+ // separando de él sin que nadie lo note.
713
+ const esPadre = state.product?.variantsCount > 0;
714
+ const cambios = cambiosDeFichaTecnica();
715
+
716
+ if (esPadre && cambios.length > 0) {
717
+ // Se espera a tener la familia antes de abrir: si no, el diálogo
718
+ // aparece diciendo "0 variantes" hasta que llega la respuesta, que es
719
+ // justo lo contrario de lo que va a preguntar.
720
+ setPreparandoPropagacion(true);
721
+ loadFamily()
722
+ .then(() => setPendingPropagation({ cambios }))
723
+ .finally(() => setPreparandoPropagacion(false));
724
+ return;
725
+ }
726
+
668
727
  saveDatasheets(token);
669
728
  break;
729
+ }
670
730
  case "Imágenes":
671
731
  updateImages(token);
672
732
  break;
@@ -709,7 +769,7 @@ const ProviderProductEditionView = ({
709
769
  peso que paga todo el mundo por el caso de unos pocos.
710
770
  */
711
771
  const loadFamily = async () => {
712
- if (family) return;
772
+ if (family) return family;
713
773
  try {
714
774
  const res = await axios.post(
715
775
  `${process.env.REACT_APP_VARIANTS_ENDPOINT}/family`,
@@ -738,6 +798,40 @@ const ProviderProductEditionView = ({
738
798
  Es un rodeo, y lo es porque el origen del dato está mal puesto. Arreglarlo de
739
799
  raíz —que la ficha lea de la URL— toca todas las plataformas y no cabe aquí.
740
800
  */
801
+ /*
802
+ Abre la edición múltiple con las variantes de la familia ya cargadas.
803
+
804
+ Esa pantalla lee `multipleEditionList` de sessionStorage —así la deja el
805
+ listado al pulsar "editar varios"— y de cada producto solo usa id_article y
806
+ version. Se reproduce esa forma en vez de inventar otra vía de entrada.
807
+
808
+ Va SIN el producto padre a propósito, aunque estemos parados en su ficha: en
809
+ la edición múltiple no existe el diálogo de propagación, así que incluirlo
810
+ abriría un camino para cambiar el padre sin decidir si eso baja a los hijos
811
+ —justo la decisión que la ficha obliga a tomar—. Para editar el padre está su
812
+ propia pantalla.
813
+ */
814
+ const openMultipleEdition = () => {
815
+ const variantes = (family?.variants ?? []).map((variant) => ({
816
+ id_article: variant.articleId,
817
+ version: variant.version ?? 1,
818
+ }));
819
+
820
+ if (!variantes.length) return;
821
+
822
+ try {
823
+ sessionStorage.setItem(
824
+ "multipleEditionList",
825
+ JSON.stringify(
826
+ variantes.sort((a, b) => a.id_article - b.id_article)
827
+ )
828
+ );
829
+ window.location.assign("/products/multipleEdition");
830
+ } catch (error) {
831
+ console.error("openMultipleEdition: ", error);
832
+ }
833
+ };
834
+
741
835
  const openVariant = async (articleId) => {
742
836
  setLoadingVariant(articleId);
743
837
  try {
@@ -853,7 +947,10 @@ const ProviderProductEditionView = ({
853
947
  try {
854
948
  const res = await axios.post(
855
949
  `${process.env.REACT_APP_VARIANTS_ENDPOINT}/restore`,
856
- { articleId: state.product.id_article, attributes: [Number(attributeId)] },
950
+ {
951
+ articleId: state.product.id_article,
952
+ attributes: [Number(attributeId)],
953
+ },
857
954
  { headers: { Authorization: token } }
858
955
  );
859
956
 
@@ -929,7 +1026,7 @@ const ProviderProductEditionView = ({
929
1026
  headers: {
930
1027
  Authorization: token,
931
1028
  },
932
- },
1029
+ }
933
1030
  );
934
1031
 
935
1032
  const newStatuses = JSON.parse(res.data.body);
@@ -1180,10 +1277,13 @@ const ProviderProductEditionView = ({
1180
1277
  const isProvider = !user.is_retailer;
1181
1278
  state.services_data?.forEach((ret) => {
1182
1279
  const { service, id_retailer } = ret;
1183
- const currentEvalStatus = state.product.statusByRetailer[id_retailer]?.[service];
1280
+ const currentEvalStatus =
1281
+ state.product.statusByRetailer[id_retailer]?.[service];
1184
1282
 
1185
- if (isProvider && currentEvalStatus !== STATUS_DICTIONARY.AA.status) return;
1186
- if (!isProvider && currentEvalStatus !== STATUS_DICTIONARY.AP.status) return;
1283
+ if (isProvider && currentEvalStatus !== STATUS_DICTIONARY.AA.status)
1284
+ return;
1285
+ if (!isProvider && currentEvalStatus !== STATUS_DICTIONARY.AP.status)
1286
+ return;
1187
1287
 
1188
1288
  const data = {
1189
1289
  ...dataGeneral,
@@ -1196,7 +1296,7 @@ const ProviderProductEditionView = ({
1196
1296
  headers: {
1197
1297
  Authorization: token,
1198
1298
  },
1199
- }),
1299
+ })
1200
1300
  );
1201
1301
  });
1202
1302
 
@@ -1244,7 +1344,7 @@ const ProviderProductEditionView = ({
1244
1344
  ArticleId: updatedProduct.id_article,
1245
1345
  idCategory: updatedProduct.id_category,
1246
1346
  product: updatedProduct,
1247
- }),
1347
+ })
1248
1348
  );
1249
1349
 
1250
1350
  // Mostrar modal de éxito
@@ -1323,6 +1423,18 @@ const ProviderProductEditionView = ({
1323
1423
  jwt={token}
1324
1424
  />
1325
1425
  )}
1426
+ {pendingPropagation && (
1427
+ <PropagateChangesModal
1428
+ changedAttributes={pendingPropagation.cambios}
1429
+ variants={(family?.variants ?? []).filter((v) => !v.isCurrent)}
1430
+ saving={state.saving}
1431
+ onCancel={() => setPendingPropagation(null)}
1432
+ onConfirm={(propagation) => {
1433
+ setPendingPropagation(null);
1434
+ saveDatasheets(token, propagation ?? undefined);
1435
+ }}
1436
+ />
1437
+ )}
1326
1438
  {showLinkVariants && (
1327
1439
  <LinkVariantsModal
1328
1440
  articleId={state.product?.id_article}
@@ -1418,7 +1530,7 @@ const ProviderProductEditionView = ({
1418
1530
  size={12}
1419
1531
  onClick={async () => {
1420
1532
  const elements = document.querySelectorAll(
1421
- "#modal-message-box .ql-container .ql-editor > p",
1533
+ "#modal-message-box .ql-container .ql-editor > p"
1422
1534
  );
1423
1535
 
1424
1536
  if (!elements || elements.length === 0) {
@@ -1435,7 +1547,7 @@ const ProviderProductEditionView = ({
1435
1547
 
1436
1548
  if (isMessageEmpty) {
1437
1549
  const container = document.querySelector(
1438
- ".container-customComponent",
1550
+ ".container-customComponent"
1439
1551
  );
1440
1552
  const existingAlert =
1441
1553
  container.querySelector(".alert-error");
@@ -1601,7 +1713,9 @@ const ProviderProductEditionView = ({
1601
1713
  <button
1602
1714
  key={variant.articleId}
1603
1715
  type="button"
1604
- className={`family-row${variant.isCurrent ? " current" : ""}`}
1716
+ className={`family-row${
1717
+ variant.isCurrent ? " current" : ""
1718
+ }`}
1605
1719
  disabled={variant.isCurrent || loadingVariant !== null}
1606
1720
  onClick={() => openVariant(variant.articleId)}
1607
1721
  >
@@ -1620,6 +1734,18 @@ const ProviderProductEditionView = ({
1620
1734
  {family && !family.error && !family.variants?.length && (
1621
1735
  <p className="loading">Este producto no tiene variantes.</p>
1622
1736
  )}
1737
+
1738
+ {/* Editarlas juntas evita abrir seis fichas para llenar lo
1739
+ mismo seis veces, que es el caso real de una familia. */}
1740
+ {family?.variants?.length > 1 && (
1741
+ <button
1742
+ type="button"
1743
+ className="editar-juntas"
1744
+ onClick={openMultipleEdition}
1745
+ >
1746
+ Editar las {family.variants.length} variantes juntas
1747
+ </button>
1748
+ )}
1623
1749
  </div>
1624
1750
  )}
1625
1751
  <FullProductNameHeader
@@ -1754,7 +1880,7 @@ const ProviderProductEditionView = ({
1754
1880
  handleOnUpdateApprovedInputs
1755
1881
  }
1756
1882
  />
1757
- ),
1883
+ )
1758
1884
  )
1759
1885
  ) : (
1760
1886
  <ScreenHeader
@@ -253,5 +253,18 @@ export const Container = styled.div`
253
253
  font-size: 11px;
254
254
  color: ${GlobalColors.gray};
255
255
  }
256
+
257
+ .editar-juntas {
258
+ align-self: flex-start;
259
+ margin-top: 4px;
260
+ border: 1px solid ${PINK};
261
+ border-radius: 6px;
262
+ background: none;
263
+ padding: 6px 12px;
264
+ cursor: pointer;
265
+ font-family: ${FontFamily.Raleway_600};
266
+ font-size: 11px;
267
+ color: ${PINK};
268
+ }
256
269
  }
257
270
  `;
package/src/index.js CHANGED
@@ -88,6 +88,7 @@ export * from "./components/organisms/ImagePreviewer/index";
88
88
  export * from "./components/organisms/ImagesGroup/index";
89
89
  export * from "./components/organisms/InputGroup/index";
90
90
  export * from "./components/organisms/LinkVariantsModal/index";
91
+ export * from "./components/organisms/PropagateChangesModal/index";
91
92
  export * from "./components/organisms/ProductImageModal/index";
92
93
  export * from "./components/organisms/Chat/index";
93
94
  export * from "./components/organisms/Modal/index";