contentoh-components-library 21.6.43 → 21.6.45

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.
@@ -40,6 +40,7 @@ import { ImagePreviewer } from "../../organisms/ImagePreviewer";
40
40
  import { InputGroup } from "../../organisms/InputGroup";
41
41
  import { LinkVariantsModal } from "../../organisms/LinkVariantsModal";
42
42
  import { UnlinkVariantModal } from "../../organisms/UnlinkVariantModal";
43
+ import { VariantGroupModal } from "../../organisms/VariantGroupModal";
43
44
  import { PropagateChangesModal } from "../../organisms/PropagateChangesModal";
44
45
  import { readPayload } from "../../organisms/LinkVariantsModal/api";
45
46
  import { ProductImageModal } from "../../organisms/ProductImageModal";
@@ -85,6 +86,15 @@ const ProviderProductEditionView = ({
85
86
  const [family, setFamily] = useState(null);
86
87
  const [showFamily, setShowFamily] = useState(false);
87
88
  const [loadingVariant, setLoadingVariant] = useState(null);
89
+
90
+ /*
91
+ Agrupación de variantes. Vive aparte del padre/hijo de arriba a propósito:
92
+ son mecanismos independientes y un producto puede tener los dos. Padre/hijo
93
+ es herencia; esto es solo una agrupación por un código compartido.
94
+ */
95
+ const [variantGroup, setVariantGroup] = useState(null);
96
+ const [showGroup, setShowGroup] = useState(false);
97
+ const [showGroupModal, setShowGroupModal] = useState(false);
88
98
  const [pendingPropagation, setPendingPropagation] = useState(null);
89
99
  const [preparandoPropagacion, setPreparandoPropagacion] = useState(false);
90
100
  const [preparandoEdicionMultiple, setPreparandoEdicionMultiple] =
@@ -941,6 +951,72 @@ const ProviderProductEditionView = ({
941
951
  setLinkNonce((n) => n + 1);
942
952
  };
943
953
 
954
+ /*
955
+ Trae el grupo de variantes del producto y sus miembros.
956
+
957
+ Se pide al montar y no al abrir el desplegable —al contrario que la familia
958
+ padre/hijo— porque aquí el conteo no viene en el producto: `variant_group`
959
+ no lo devuelven los endpoints del listado, así que sin esta llamada la ficha
960
+ no sabría ni si el producto está agrupado.
961
+ */
962
+ const loadVariantGroup = async (forzar = false) => {
963
+ if (variantGroup && !forzar) return variantGroup;
964
+ try {
965
+ const res = await axios.post(
966
+ `${process.env.REACT_APP_VARIANTS_ENDPOINT}/group`,
967
+ { articleId: state.product?.id_article },
968
+ { headers: { Authorization: token } }
969
+ );
970
+ const data = readPayload(res);
971
+ setVariantGroup(data);
972
+ return data;
973
+ } catch (error) {
974
+ console.error("loadVariantGroup: ", error);
975
+ setVariantGroup({ group: null, members: [], error: true });
976
+ return null;
977
+ }
978
+ };
979
+
980
+ useEffect(() => {
981
+ if (!state.product?.id_article) return;
982
+ loadVariantGroup(true);
983
+ // eslint-disable-next-line react-hooks/exhaustive-deps
984
+ }, [state.product?.id_article]);
985
+
986
+ // Saca el producto del grupo. Los demás miembros siguen agrupados entre ellos;
987
+ // si era el último, el grupo deja de existir, porque no vive en ninguna tabla.
988
+ const handleOnLeaveGroup = async () => {
989
+ try {
990
+ const res = await axios.post(
991
+ `${process.env.REACT_APP_VARIANTS_ENDPOINT}/leave`,
992
+ { articleId: state.product?.id_article },
993
+ { headers: { Authorization: token } }
994
+ );
995
+ setVariantGroup(readPayload(res));
996
+ setShowGroup(false);
997
+ } catch (error) {
998
+ console.error("leaveVariantGroup: ", error);
999
+ dispatch({
1000
+ type: "SET_MODAL",
1001
+ payload: {
1002
+ show: true,
1003
+ title: "Variantes",
1004
+ message: "No se pudo quitar el producto del grupo. Intenta de nuevo.",
1005
+ image: warningIcon,
1006
+ // Con `buttons` vacío el modal se queda sin forma de cerrarse.
1007
+ buttons: [
1008
+ {
1009
+ text: "Entendido",
1010
+ buttonType: "general-white-button",
1011
+ action: () =>
1012
+ dispatch({ type: "SET_MODAL", payload: { show: false } }),
1013
+ },
1014
+ ],
1015
+ },
1016
+ });
1017
+ }
1018
+ };
1019
+
944
1020
  const openVariant = async (articleId) => {
945
1021
  setLoadingVariant(articleId);
946
1022
  try {
@@ -1585,6 +1661,20 @@ const ProviderProductEditionView = ({
1585
1661
  onLinked={handleOnLinkedVariants}
1586
1662
  />
1587
1663
  )}
1664
+ {showGroupModal && (
1665
+ <VariantGroupModal
1666
+ articleId={state.product?.id_article}
1667
+ articleName={state.product?.name}
1668
+ articleUpc={state.product?.upc}
1669
+ currentGroup={variantGroup?.group ?? null}
1670
+ token={token}
1671
+ onClose={() => setShowGroupModal(false)}
1672
+ onSaved={(result) => {
1673
+ setVariantGroup(result);
1674
+ setShowGroup(true);
1675
+ }}
1676
+ />
1677
+ )}
1588
1678
  {porDesvincular && (
1589
1679
  <UnlinkVariantModal
1590
1680
  {...porDesvincular}
@@ -1947,6 +2037,80 @@ const ProviderProductEditionView = ({
1947
2037
  )}
1948
2038
  </div>
1949
2039
  )}
2040
+ {/* AGRUPACIÓN DE VARIANTES.
2041
+
2042
+ Bloque aparte del de padre/hijo, y a propósito: son
2043
+ mecanismos independientes y un producto puede tener los dos.
2044
+ Padre/hijo es herencia y tiene jerarquía; esto es solo un
2045
+ código compartido que dice "estos son el mismo producto en
2046
+ otra presentación". Mezclarlos en una sola caja haría creer
2047
+ que agrupar hereda algo, que es justo lo que no pasa. */}
2048
+ <div className="variants-actions">
2049
+ <button
2050
+ type="button"
2051
+ className="link-variants"
2052
+ onClick={() => setShowGroupModal(true)}
2053
+ >
2054
+ {variantGroup?.group ? "Cambiar de grupo" : "Agrupar variantes"}
2055
+ </button>
2056
+
2057
+ {variantGroup?.group && (
2058
+ <button
2059
+ type="button"
2060
+ className="variants-count"
2061
+ aria-expanded={showGroup}
2062
+ onClick={() => setShowGroup((abierto) => !abierto)}
2063
+ >
2064
+ Variante {variantGroup.group}
2065
+ <span aria-hidden="true">{showGroup ? " ▴" : " ▾"}</span>
2066
+ </button>
2067
+ )}
2068
+ </div>
2069
+
2070
+ {showGroup && variantGroup?.group && (
2071
+ <div className="variants-family">
2072
+ {/* Los hermanos de grupo NO se listan como padre/hijo: aquí
2073
+ nadie hereda de nadie, así que no hay rol que mostrar,
2074
+ solo de qué productos se trata. */}
2075
+ {(variantGroup.members ?? [])
2076
+ .filter((miembro) => !miembro.isCurrent)
2077
+ .map((miembro) => (
2078
+ <div className="family-item" key={miembro.articleId}>
2079
+ <button
2080
+ type="button"
2081
+ className="family-row"
2082
+ disabled={loadingVariant !== null}
2083
+ onClick={() => openVariant(miembro.articleId)}
2084
+ >
2085
+ <span className="role">
2086
+ {loadingVariant === miembro.articleId
2087
+ ? "Abriendo…"
2088
+ : "Variante"}
2089
+ </span>
2090
+ <span className="name">{miembro.name}</span>
2091
+ <span className="upc">{miembro.upc}</span>
2092
+ </button>
2093
+ </div>
2094
+ ))}
2095
+
2096
+ {(variantGroup.members ?? []).filter((m) => !m.isCurrent)
2097
+ .length === 0 && (
2098
+ <p className="loading">
2099
+ Este producto es el único en el grupo {variantGroup.group}.
2100
+ </p>
2101
+ )}
2102
+
2103
+ <button
2104
+ type="button"
2105
+ className="desvincular"
2106
+ title="Quitar este producto del grupo"
2107
+ onClick={handleOnLeaveGroup}
2108
+ >
2109
+ Quitar del grupo
2110
+ </button>
2111
+ </div>
2112
+ )}
2113
+
1950
2114
  <FullProductNameHeader
1951
2115
  headerData={state.product}
1952
2116
  percent={state.active_percentage?.percentagesGeneral?.required}
@@ -52,6 +52,7 @@ import { VersionSelector } from "../../organisms/VersionSelector";
52
52
  import { Modal } from "../../organisms/Modal";
53
53
  import { LinkVariantsModal } from "../../organisms/LinkVariantsModal";
54
54
  import { UnlinkVariantModal } from "../../organisms/UnlinkVariantModal";
55
+ import { VariantGroupModal } from "../../organisms/VariantGroupModal";
55
56
  import { PropagateChangesModal } from "../../organisms/PropagateChangesModal";
56
57
  import { readPayload } from "../../organisms/LinkVariantsModal/api";
57
58
 
@@ -186,6 +187,15 @@ const RetailerProductEditionView = ({
186
187
  const [family, setFamily] = useState(null);
187
188
  const [showFamily, setShowFamily] = useState(false);
188
189
  const [loadingVariant, setLoadingVariant] = useState(null);
190
+
191
+ /*
192
+ Agrupación de variantes. Aparte del padre/hijo a propósito: son mecanismos
193
+ independientes y un producto puede tener los dos. Padre/hijo es herencia;
194
+ esto es solo un código compartido.
195
+ */
196
+ const [variantGroup, setVariantGroup] = useState(null);
197
+ const [showGroup, setShowGroup] = useState(false);
198
+ const [showGroupModal, setShowGroupModal] = useState(false);
189
199
  // Qué vínculo se está por romper, o null si no hay diálogo abierto. Guarda al
190
200
  // HIJO, que es quien lleva la relación y lo que pide la acción `unlink`.
191
201
  const [porDesvincular, setPorDesvincular] = useState(null);
@@ -975,6 +985,68 @@ const RetailerProductEditionView = ({
975
985
  loadData(false);
976
986
  };
977
987
 
988
+ /*
989
+ Trae el grupo de variantes y sus miembros.
990
+
991
+ Al montar, no al abrir el desplegable: `variant_group` no lo devuelve el
992
+ endpoint del listado, así que sin esta llamada la ficha no sabe si el
993
+ producto está agrupado.
994
+ */
995
+ const loadVariantGroup = async (forzar = false) => {
996
+ if (variantGroup && !forzar) return variantGroup;
997
+ try {
998
+ const res = await axios.post(
999
+ `${process.env.REACT_APP_VARIANTS_ENDPOINT}/group`,
1000
+ { articleId: state.product?.id_article },
1001
+ { headers: { Authorization: token } }
1002
+ );
1003
+ const data = readPayload(res);
1004
+ setVariantGroup(data);
1005
+ return data;
1006
+ } catch (error) {
1007
+ console.error("loadVariantGroup: ", error);
1008
+ setVariantGroup({ group: null, members: [], error: true });
1009
+ return null;
1010
+ }
1011
+ };
1012
+
1013
+ useEffect(() => {
1014
+ if (!state.product?.id_article) return;
1015
+ loadVariantGroup(true);
1016
+ // eslint-disable-next-line react-hooks/exhaustive-deps
1017
+ }, [state.product?.id_article]);
1018
+
1019
+ const handleOnLeaveGroup = async () => {
1020
+ try {
1021
+ const res = await axios.post(
1022
+ `${process.env.REACT_APP_VARIANTS_ENDPOINT}/leave`,
1023
+ { articleId: state.product?.id_article },
1024
+ { headers: { Authorization: token } }
1025
+ );
1026
+ setVariantGroup(readPayload(res));
1027
+ setShowGroup(false);
1028
+ } catch (error) {
1029
+ console.error("leaveVariantGroup: ", error);
1030
+ dispatch({
1031
+ type: "SET_MODAL",
1032
+ payload: {
1033
+ show: true,
1034
+ title: "Variantes",
1035
+ message: "No se pudo quitar el producto del grupo. Intenta de nuevo.",
1036
+ image: warningIcon,
1037
+ buttons: [
1038
+ {
1039
+ text: "Entendido",
1040
+ buttonType: "general-white-button",
1041
+ action: () =>
1042
+ dispatch({ type: "SET_MODAL", payload: { show: false } }),
1043
+ },
1044
+ ],
1045
+ },
1046
+ });
1047
+ }
1048
+ };
1049
+
978
1050
  const openFamilyMember = async (articleId) => {
979
1051
  setLoadingVariant(articleId);
980
1052
  setAvisoFamilia(null);
@@ -2030,6 +2102,20 @@ const RetailerProductEditionView = ({
2030
2102
  onLinked={handleOnLinkedVariants}
2031
2103
  />
2032
2104
  )}
2105
+ {showGroupModal && (
2106
+ <VariantGroupModal
2107
+ articleId={state.product?.id_article}
2108
+ articleName={state.product?.name}
2109
+ articleUpc={state.product?.upc}
2110
+ currentGroup={variantGroup?.group ?? null}
2111
+ token={token}
2112
+ onClose={() => setShowGroupModal(false)}
2113
+ onSaved={(result) => {
2114
+ setVariantGroup(result);
2115
+ setShowGroup(true);
2116
+ }}
2117
+ />
2118
+ )}
2033
2119
  {porDesvincular && (
2034
2120
  <UnlinkVariantModal
2035
2121
  {...porDesvincular}
@@ -2392,6 +2478,76 @@ const RetailerProductEditionView = ({
2392
2478
  </div>
2393
2479
  )}
2394
2480
 
2481
+ {/* AGRUPACIÓN DE VARIANTES.
2482
+
2483
+ Bloque aparte del de padre/hijo a propósito: son mecanismos
2484
+ independientes y un producto puede tener los dos. Padre/hijo es
2485
+ herencia y tiene jerarquía; esto es solo un código compartido
2486
+ que dice "estos son el mismo producto en otra presentación".
2487
+ Juntarlos haría creer que agrupar hereda algo, que no pasa. */}
2488
+ <div className="variants-actions">
2489
+ <button
2490
+ type="button"
2491
+ className="link-variants"
2492
+ onClick={() => setShowGroupModal(true)}
2493
+ >
2494
+ {variantGroup?.group ? "Cambiar de grupo" : "Agrupar variantes"}
2495
+ </button>
2496
+
2497
+ {variantGroup?.group && (
2498
+ <button
2499
+ type="button"
2500
+ className="variants-count"
2501
+ aria-expanded={showGroup}
2502
+ onClick={() => setShowGroup((abierto) => !abierto)}
2503
+ >
2504
+ Variante {variantGroup.group}
2505
+ <span aria-hidden="true">{showGroup ? " ▴" : " ▾"}</span>
2506
+ </button>
2507
+ )}
2508
+ </div>
2509
+
2510
+ {showGroup && variantGroup?.group && (
2511
+ <div className="variants-family">
2512
+ {(variantGroup.members ?? [])
2513
+ .filter((miembro) => !miembro.isCurrent)
2514
+ .map((miembro) => (
2515
+ <div className="family-item" key={miembro.articleId}>
2516
+ <button
2517
+ type="button"
2518
+ className="family-row"
2519
+ disabled={loadingVariant !== null}
2520
+ onClick={() => openFamilyMember(miembro.articleId)}
2521
+ >
2522
+ <span className="role">
2523
+ {loadingVariant === miembro.articleId
2524
+ ? "Abriendo…"
2525
+ : "Variante"}
2526
+ </span>
2527
+ <span className="name">{miembro.name}</span>
2528
+ <span className="upc">{miembro.upc}</span>
2529
+ </button>
2530
+ </div>
2531
+ ))}
2532
+
2533
+ {(variantGroup.members ?? []).filter((m) => !m.isCurrent)
2534
+ .length === 0 && (
2535
+ <p className="loading">
2536
+ Este producto es el único en el grupo {variantGroup.group}.
2537
+ </p>
2538
+ )}
2539
+
2540
+ <button
2541
+ type="button"
2542
+ className="desvincular"
2543
+ title="Quitar este producto del grupo"
2544
+ onClick={handleOnLeaveGroup}
2545
+ >
2546
+ Quitar del grupo
2547
+ </button>
2548
+ </div>
2549
+ )}
2550
+
2395
2551
  <FullProductNameHeader
2396
2552
  // states
2397
2553
  headerData={state.product}
@@ -35,8 +35,30 @@ export const getRetailerServices = async (
35
35
  const images = JSON.parse(responseArray[2].data.body).data;
36
36
 
37
37
  images.values = images?.values?.map((value) => {
38
- value.name = images.inputs.find((e) => e.id === value.image_id)?.name;
39
- return getImage(value);
38
+ /**
39
+ * La fila de `rel_article_image` trae la columna `id_image`; el resto del
40
+ * front lee `image_id`. Sin normalizarlo aquí queda `undefined`, y eso
41
+ * rompe DOS cosas a la vez:
42
+ *
43
+ * - `getImage` toma su rama `else` y deja `src` con la clave cruda de
44
+ * S3 en lugar de la URL de CloudFront, así que la etiqueta <img>
45
+ * apunta a una ruta relativa.
46
+ * - el filtro `filteredValues` de `RetailerProductEdition` compara
47
+ * `value.image_id` contra las reglas del retailer, no encuentra
48
+ * coincidencia y descarta la imagen.
49
+ *
50
+ * Resultado: la pantalla dice «Este producto no tiene imágenes» aunque la
51
+ * fila exista y el archivo esté en S3. Antes de guardar sí se veía, porque
52
+ * el objeto que arma el front al elegir el archivo sí trae `image_id`.
53
+ */
54
+ const normalized = {
55
+ ...value,
56
+ image_id: value.image_id ?? value.id_image,
57
+ };
58
+ normalized.name = images.inputs.find(
59
+ (e) => e.id === normalized.image_id
60
+ )?.name;
61
+ return getImage(normalized);
40
62
  });
41
63
 
42
64
  return [datasheets, descriptions, images];
@@ -47,7 +69,13 @@ export const getRetailerServices = async (
47
69
 
48
70
  export const getImage = (imageGeneral, width = 250, height = 250) => {
49
71
  try {
50
- const image = imageGeneral;
72
+ // `id_image` es el nombre de la columna en base de datos; `image_id` el que
73
+ // usa el front. Se acepta cualquiera de los dos para que la URL se arme
74
+ // igual, venga la imagen del backend o del estado local.
75
+ const image = {
76
+ ...imageGeneral,
77
+ image_id: imageGeneral.image_id ?? imageGeneral.id_image,
78
+ };
51
79
  const ext = image.src.split(".");
52
80
  const uuid = ext[0].split("-");
53
81
  return {
package/src/index.js CHANGED
@@ -90,6 +90,7 @@ export * from "./components/organisms/InputGroup/index";
90
90
  export * from "./components/organisms/LinkVariantsModal/index";
91
91
  export * from "./components/organisms/PropagateChangesModal/index";
92
92
  export * from "./components/organisms/UnlinkVariantModal/index";
93
+ export * from "./components/organisms/VariantGroupModal/index";
93
94
  export * from "./components/organisms/ProductImageModal/index";
94
95
  export * from "./components/organisms/Chat/index";
95
96
  export * from "./components/organisms/Modal/index";