contentoh-components-library 21.6.29 → 21.6.31
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/dist/components/pages/ProviderProductEdition/index.js +209 -130
- package/dist/components/pages/RetailerProductEdition/context/provider-product-edition.context.js +44 -26
- package/dist/components/pages/RetailerProductEdition/index.js +600 -172
- package/dist/components/pages/RetailerProductEdition/styles.js +5 -2
- package/dist/components/pages/RetailerProductEdition/utils.js +8 -0
- package/package.json +1 -1
- package/src/components/pages/ProviderProductEdition/index.js +50 -15
- package/src/components/pages/RetailerProductEdition/context/provider-product-edition.context.jsx +22 -2
- package/src/components/pages/RetailerProductEdition/index.js +291 -2
- package/src/components/pages/RetailerProductEdition/styles.js +96 -0
- package/src/components/pages/RetailerProductEdition/utils.js +8 -0
|
@@ -15,9 +15,12 @@ var _variables = require("../../../global-files/variables");
|
|
|
15
15
|
|
|
16
16
|
var _templateObject;
|
|
17
17
|
|
|
18
|
-
|
|
18
|
+
// Rosa ContentOh, el mismo del modal de ligar productos.
|
|
19
|
+
var PINK = "#E13AA8";
|
|
20
|
+
|
|
21
|
+
var Container = _styledComponents.default.div(_templateObject || (_templateObject = (0, _taggedTemplateLiteral2.default)(["\n display: flex;\n flex-direction: column;\n height: 100%;\n flex: 1;\n overflow: auto;\n\n .data-container {\n display: flex;\n flex: 0%;\n height: calc(100% - ", "px);\n .image-data-panel {\n width: 340px;\n\n & + * {\n margin-left: 10px;\n }\n }\n\n .product-information {\n width: 100%;\n height: 100%;\n display: flex;\n flex-direction: column;\n\n .services-information-container {\n height: 100%;\n overflow: auto;\n }\n\n .image-services {\n aside {\n display: grid;\n grid-template-columns: repeat(auto-fill, 179px);\n column-gap: 15px;\n row-gap: 15px;\n padding: 20px;\n }\n }\n\n .commentary-box {\n display: flex;\n justify-content: space-between;\n align-items: flex-end;\n padding: 10px;\n padding-left: 0;\n\n .action-buttons{\n display: flex;\n flex-direction: column;\n gap: 1rem;\n }\n\n .commentary {\n display: flex;\n align-items: flex-end;\n\n .input-container {\n width: 500px;\n\n .quill {\n height: 100px;\n }\n\n & + * {\n margin-left: 5px;\n }\n }\n\n .buttons-box {\n display: flex;\n width: 210px;\n flex-wrap: wrap;\n\n .general-transparent-button {\n & + * {\n margin-top: 5px;\n }\n }\n\n .general-transparent-button,\n .general-green-button,\n .general-button-disabled {\n width: fit-content;\n min-width: 201px;\n height: 40px;\n }\n }\n }\n }\n\n .feedback-box {\n display: flex;\n }\n\n .required-inputs-message {\n font-family: ", ";\n font-size: 13px;\n color: ", ";\n padding: 10px;\n display: flex;\n align-items: center;\n\n p + * {\n margin-top: 10px;\n }\n\n button {\n min-width: fit-content;\n }\n }\n }\n }\n #modal-message-box {\n width: 400px;\n height: 100px;\n }\n .container {\n width: 100%;\n height: 90%;\n .dropzone {\n height: 100%;\n width: 100%;\n position: relative;\n transition: all 0.5s ease;\n\n &.drag-active {\n background-color: rgba(", ", 0.05);\n border: 2px dashed ", ";\n border-radius: 8px;\n\n aside {\n opacity: 0.3;\n pointer-events: none;\n }\n }\n\n .drag-overlay {\n position: absolute;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n display: flex;\n align-items: center;\n justify-content: center;\n background-color: rgba(", ", 0.1);\n border-radius: 8px;\n pointer-events: none;\n z-index: 10;\n\n p {\n font-family: ", ";\n font-size: 24px;\n font-weight: 600;\n color: ", ";\n text-align: center;\n padding: 20px;\n background-color: ", ";\n border-radius: 8px;\n box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);\n }\n }\n }\n }\n\n .variants-actions {\n display: flex;\n align-items: center;\n gap: 10px;\n margin-bottom: 8px;\n\n .link-variants {\n border: 1px solid ", ";\n border-radius: 6px;\n background: none;\n padding: 6px 12px;\n font-family: ", ";\n font-size: 11px;\n color: ", ";\n cursor: pointer;\n }\n\n .variants-count {\n border: none;\n background: none;\n padding: 0;\n cursor: pointer;\n font-family: ", ";\n font-size: 11px;\n color: ", ";\n text-decoration: underline;\n }\n }\n\n .variants-family {\n display: flex;\n flex-direction: column;\n gap: 4px;\n margin-bottom: 10px;\n padding: 8px;\n border: 1px solid ", ";\n border-radius: 6px;\n\n .loading {\n margin: 0;\n font-family: ", ";\n font-size: 11px;\n color: ", ";\n }\n\n .family-row {\n display: flex;\n align-items: baseline;\n gap: 8px;\n padding: 6px 8px;\n border: none;\n border-radius: 4px;\n background: none;\n cursor: pointer;\n text-align: left;\n font-family: ", ";\n font-size: 12px;\n color: ", ";\n }\n\n .family-row:hover:not(:disabled) {\n background-color: ", ";\n }\n\n .family-row:disabled {\n cursor: default;\n opacity: 0.7;\n }\n\n .family-row .role {\n min-width: 96px;\n font-size: 10px;\n color: ", ";\n }\n\n .family-row.current .role {\n color: ", ";\n }\n\n .family-row .name {\n flex: 1;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n }\n\n .family-row .upc {\n font-size: 11px;\n color: ", ";\n }\n }\n"])), function (_ref) {
|
|
19
22
|
var headerTop = _ref.headerTop;
|
|
20
23
|
return headerTop;
|
|
21
|
-
}, _variables.FontFamily.AvenirNext, _variables.GlobalColors.color_gray, _variables.GlobalColors.rgb_color_pink, _variables.GlobalColors.color_pink, _variables.GlobalColors.rgb_color_pink, _variables.FontFamily.AvenirNext, _variables.GlobalColors.color_pink, _variables.GlobalColors.white);
|
|
24
|
+
}, _variables.FontFamily.AvenirNext, _variables.GlobalColors.color_gray, _variables.GlobalColors.rgb_color_pink, _variables.GlobalColors.color_pink, _variables.GlobalColors.rgb_color_pink, _variables.FontFamily.AvenirNext, _variables.GlobalColors.color_pink, _variables.GlobalColors.white, PINK, _variables.FontFamily.Raleway_600, PINK, _variables.FontFamily.Raleway_500, _variables.GlobalColors.gray, _variables.GlobalColors.s2, _variables.FontFamily.Raleway_500, _variables.GlobalColors.gray, _variables.FontFamily.Raleway_500, _variables.GlobalColors.deep_gray, _variables.GlobalColors.s2, PINK, _variables.GlobalColors.gray, _variables.GlobalColors.gray);
|
|
22
25
|
|
|
23
26
|
exports.Container = Container;
|
|
@@ -126,6 +126,8 @@ var sendMessage = /*#__PURE__*/function () {
|
|
|
126
126
|
exports.sendMessage = sendMessage;
|
|
127
127
|
|
|
128
128
|
var normalizeProduct = function normalizeProduct(product) {
|
|
129
|
+
var _ref5, _productData$parentAr, _ref6, _productData$parent, _ref7, _productData$variants;
|
|
130
|
+
|
|
129
131
|
// Handle case where product might be wrapped in a product property
|
|
130
132
|
var productData = product.product || product;
|
|
131
133
|
var article = productData.article || {};
|
|
@@ -161,6 +163,12 @@ var normalizeProduct = function normalizeProduct(product) {
|
|
|
161
163
|
percentages: productData.percentages || null,
|
|
162
164
|
summary: productData.summary || null,
|
|
163
165
|
version: productData.version || 0,
|
|
166
|
+
// Parentesco. read-articles-v2 ya lo devuelve; sin arrastrarlo hasta aquí la
|
|
167
|
+
// pantalla no puede saber si el producto pertenece a una familia.
|
|
168
|
+
// `?? null` en vez de `|| null`: variantsCount 0 es un dato válido.
|
|
169
|
+
parentArticleId: (_ref5 = (_productData$parentAr = productData.parentArticleId) !== null && _productData$parentAr !== void 0 ? _productData$parentAr : article.parentArticleId) !== null && _ref5 !== void 0 ? _ref5 : null,
|
|
170
|
+
parent: (_ref6 = (_productData$parent = productData.parent) !== null && _productData$parent !== void 0 ? _productData$parent : article.parent) !== null && _ref6 !== void 0 ? _ref6 : null,
|
|
171
|
+
variantsCount: (_ref7 = (_productData$variants = productData.variantsCount) !== null && _productData$variants !== void 0 ? _productData$variants : article.variantsCount) !== null && _ref7 !== void 0 ? _ref7 : 0,
|
|
164
172
|
id_order: productData.orderId || productData.id_order || 0,
|
|
165
173
|
status: productData.status || null,
|
|
166
174
|
datasheet_status: productData.datasheet_status || null,
|
package/package.json
CHANGED
|
@@ -86,6 +86,8 @@ const ProviderProductEditionView = ({
|
|
|
86
86
|
const [loadingVariant, setLoadingVariant] = useState(null);
|
|
87
87
|
const [pendingPropagation, setPendingPropagation] = useState(null);
|
|
88
88
|
const [preparandoPropagacion, setPreparandoPropagacion] = useState(false);
|
|
89
|
+
const [preparandoEdicionMultiple, setPreparandoEdicionMultiple] =
|
|
90
|
+
useState(false);
|
|
89
91
|
const [showRejectModal, setShowRejectModal] = useState(false);
|
|
90
92
|
const [headerTop, setHeaderTop] = useState(0);
|
|
91
93
|
const [assig, setAssig] = useState({});
|
|
@@ -802,33 +804,63 @@ const ProviderProductEditionView = ({
|
|
|
802
804
|
Abre la edición múltiple con las variantes de la familia ya cargadas.
|
|
803
805
|
|
|
804
806
|
Esa pantalla lee `multipleEditionList` de sessionStorage —así la deja el
|
|
805
|
-
listado al pulsar "editar varios"— y
|
|
806
|
-
|
|
807
|
+
listado al pulsar "editar varios"— y necesita el PRODUCTO COMPLETO de cada
|
|
808
|
+
uno, no solo su id: pinta el UPC, el nombre, parte `categoryName` por "|",
|
|
809
|
+
cuenta `retailersAvailable` y decide columnas según `id_order`. Con un objeto
|
|
810
|
+
reducido revienta al entrar.
|
|
807
811
|
|
|
808
|
-
|
|
809
|
-
|
|
812
|
+
Por eso se piden a /new-articles, que devuelve justo la forma del listado, y
|
|
813
|
+
se filtran por id y no por UPC: hay 12,024 UPC repetidos entre artículos
|
|
814
|
+
activos y el UPC no identifica un producto.
|
|
815
|
+
|
|
816
|
+
Va SIN el producto padre a propósito, aunque el botón viva en su ficha. En la
|
|
817
|
+
edición múltiple no existe el diálogo de propagación, así que incluirlo
|
|
810
818
|
abriría un camino para cambiar el padre sin decidir si eso baja a los hijos
|
|
811
|
-
—justo la decisión que
|
|
812
|
-
propia pantalla.
|
|
819
|
+
—justo la decisión que su propia ficha obliga a tomar—.
|
|
813
820
|
*/
|
|
814
|
-
const openMultipleEdition = () => {
|
|
815
|
-
const
|
|
816
|
-
|
|
817
|
-
version: variant.version ?? 1,
|
|
818
|
-
}));
|
|
819
|
-
|
|
820
|
-
if (!variantes.length) return;
|
|
821
|
+
const openMultipleEdition = async () => {
|
|
822
|
+
const ids = (family?.variants ?? []).map((variant) => variant.articleId);
|
|
823
|
+
if (!ids.length) return;
|
|
821
824
|
|
|
825
|
+
setPreparandoEdicionMultiple(true);
|
|
822
826
|
try {
|
|
827
|
+
const res = await axios.get(`${process.env.REACT_APP_NEW_ARTICLES_ENDPOINT}`, {
|
|
828
|
+
params: { listType: "general", filterByArticleId: ids.join(",") },
|
|
829
|
+
headers: { Authorization: token },
|
|
830
|
+
});
|
|
831
|
+
|
|
832
|
+
const { data } = readPayload(res);
|
|
833
|
+
const productos = (data ?? []).filter((item) =>
|
|
834
|
+
ids.includes(Number(item.id_article))
|
|
835
|
+
);
|
|
836
|
+
|
|
837
|
+
if (!productos.length) {
|
|
838
|
+
console.error("openMultipleEdition: sin productos que abrir");
|
|
839
|
+
return;
|
|
840
|
+
}
|
|
841
|
+
|
|
823
842
|
sessionStorage.setItem(
|
|
824
843
|
"multipleEditionList",
|
|
825
844
|
JSON.stringify(
|
|
826
|
-
|
|
845
|
+
productos.sort((a, b) => a.id_article - b.id_article)
|
|
827
846
|
)
|
|
828
847
|
);
|
|
829
848
|
window.location.assign("/products/multipleEdition");
|
|
830
849
|
} catch (error) {
|
|
850
|
+
// Se avisa y se queda donde está: navegar con la lista vieja abriría otros
|
|
851
|
+
// productos, y navegar sin lista deja la pantalla rota.
|
|
831
852
|
console.error("openMultipleEdition: ", error);
|
|
853
|
+
dispatch({
|
|
854
|
+
type: "SET_MODAL",
|
|
855
|
+
payload: {
|
|
856
|
+
show: true,
|
|
857
|
+
title: "",
|
|
858
|
+
message: "No se pudieron cargar las variantes para editarlas juntas.",
|
|
859
|
+
image: errorIcon,
|
|
860
|
+
},
|
|
861
|
+
});
|
|
862
|
+
} finally {
|
|
863
|
+
setPreparandoEdicionMultiple(false);
|
|
832
864
|
}
|
|
833
865
|
};
|
|
834
866
|
|
|
@@ -1741,9 +1773,12 @@ const ProviderProductEditionView = ({
|
|
|
1741
1773
|
<button
|
|
1742
1774
|
type="button"
|
|
1743
1775
|
className="editar-juntas"
|
|
1776
|
+
disabled={preparandoEdicionMultiple}
|
|
1744
1777
|
onClick={openMultipleEdition}
|
|
1745
1778
|
>
|
|
1746
|
-
|
|
1779
|
+
{preparandoEdicionMultiple
|
|
1780
|
+
? "Abriendo…"
|
|
1781
|
+
: `Editar las ${family.variants.length} variantes juntas`}
|
|
1747
1782
|
</button>
|
|
1748
1783
|
)}
|
|
1749
1784
|
</div>
|
package/src/components/pages/RetailerProductEdition/context/provider-product-edition.context.jsx
CHANGED
|
@@ -137,13 +137,20 @@ export const ProviderProductEditionProvider = ({ children }) => {
|
|
|
137
137
|
}
|
|
138
138
|
};
|
|
139
139
|
|
|
140
|
-
|
|
140
|
+
/*
|
|
141
|
+
`propagation` es opcional y solo lo manda la ficha de un producto padre, con
|
|
142
|
+
lo que el usuario eligió en el diálogo. Sin ese bloque el backend no propaga
|
|
143
|
+
nada: bajar un cambio a las variantes es una decisión, no un efecto
|
|
144
|
+
secundario de guardar.
|
|
145
|
+
*/
|
|
146
|
+
const saveDatasheets = async (token, propagation) => {
|
|
141
147
|
if (state.updated_datasheets_inputs.length === 0) return;
|
|
142
148
|
dispatch({ type: "SET_SAVING", payload: true });
|
|
143
149
|
|
|
144
150
|
const dataObject = {
|
|
145
151
|
articleId: state.product?.id_article,
|
|
146
152
|
articleData: state.updated_datasheets_inputs,
|
|
153
|
+
...(propagation ? { propagation } : {}),
|
|
147
154
|
};
|
|
148
155
|
|
|
149
156
|
if (state.product?.id_order) {
|
|
@@ -174,9 +181,22 @@ export const ProviderProductEditionProvider = ({ children }) => {
|
|
|
174
181
|
type: "SET_UPDATED_DATASHEETS_INPUTS",
|
|
175
182
|
payload: [],
|
|
176
183
|
});
|
|
184
|
+
// En una variante, todo atributo que el hijo toca deja de ser heredado:
|
|
185
|
+
// update-articles-data le pone is_overridden = 1 en la base. Sin
|
|
186
|
+
// reflejarlo aquí, el chip sigue diciendo "Heredado" hasta la siguiente
|
|
187
|
+
// carga de la ficha, y el usuario ve una marca que ya es falsa.
|
|
188
|
+
const esVariante =
|
|
189
|
+
state.services?.datasheets?.variant?.isVariant ??
|
|
190
|
+
!!state.product?.parentArticleId;
|
|
191
|
+
|
|
177
192
|
dispatch({
|
|
178
193
|
type: "UPDATE_DATASHEETS_INPUTS",
|
|
179
|
-
payload:
|
|
194
|
+
payload: esVariante
|
|
195
|
+
? state.updated_datasheets_inputs.map((item) => ({
|
|
196
|
+
...item,
|
|
197
|
+
isOverridden: 1,
|
|
198
|
+
}))
|
|
199
|
+
: state.updated_datasheets_inputs,
|
|
180
200
|
});
|
|
181
201
|
|
|
182
202
|
// Contar cuántos campos requeridos que antes estaban vacíos ahora tienen valor
|
|
@@ -50,6 +50,9 @@ import { InputGroup } from "../../organisms/InputGroup";
|
|
|
50
50
|
import { ProductImageModal } from "../../organisms/ProductImageModal";
|
|
51
51
|
import { VersionSelector } from "../../organisms/VersionSelector";
|
|
52
52
|
import { Modal } from "../../organisms/Modal";
|
|
53
|
+
import { LinkVariantsModal } from "../../organisms/LinkVariantsModal";
|
|
54
|
+
import { PropagateChangesModal } from "../../organisms/PropagateChangesModal";
|
|
55
|
+
import { readPayload } from "../../organisms/LinkVariantsModal/api";
|
|
53
56
|
|
|
54
57
|
import successIcon from "../../../assets/images/genericModal/genericModalCheck.svg";
|
|
55
58
|
import errorIcon from "../../../assets/images/genericModal/errorModal.svg";
|
|
@@ -178,6 +181,14 @@ const RetailerProductEditionView = ({
|
|
|
178
181
|
const [statusArray, setStatusArray] = useState([]);
|
|
179
182
|
const [rejectAll, setRejectAll] = useState(false);
|
|
180
183
|
|
|
184
|
+
// Variantes: mismo comportamiento que la ficha de la aplicación de clientes.
|
|
185
|
+
const [showLinkVariants, setShowLinkVariants] = useState(false);
|
|
186
|
+
const [family, setFamily] = useState(null);
|
|
187
|
+
const [showFamily, setShowFamily] = useState(false);
|
|
188
|
+
const [loadingVariant, setLoadingVariant] = useState(null);
|
|
189
|
+
const [pendingPropagation, setPendingPropagation] = useState(null);
|
|
190
|
+
const [preparandoPropagacion, setPreparandoPropagacion] = useState(false);
|
|
191
|
+
|
|
181
192
|
const [auditServices, setAuditServices] = useState([]);
|
|
182
193
|
const [auditDatasheets, setAuditDatasheets] = useState([]);
|
|
183
194
|
const [auditDescriptions, setAuditDescriptions] = useState([]);
|
|
@@ -771,16 +782,170 @@ const RetailerProductEditionView = ({
|
|
|
771
782
|
}
|
|
772
783
|
};
|
|
773
784
|
|
|
785
|
+
/*
|
|
786
|
+
Trae la familia: las variantes del producto, o sus hermanas si el abierto es
|
|
787
|
+
una de ellas. Se pide al abrir la sección y no al montar la pantalla: la
|
|
788
|
+
mayoría de los productos no son familia.
|
|
789
|
+
*/
|
|
790
|
+
const loadFamily = async () => {
|
|
791
|
+
if (family) return family;
|
|
792
|
+
try {
|
|
793
|
+
const res = await axios.post(
|
|
794
|
+
`${process.env.REACT_APP_VARIANTS_ENDPOINT}/family`,
|
|
795
|
+
{ articleId: state.product?.id_article },
|
|
796
|
+
{ headers: { Authorization: token } }
|
|
797
|
+
);
|
|
798
|
+
const data = readPayload(res);
|
|
799
|
+
setFamily(data);
|
|
800
|
+
return data;
|
|
801
|
+
} catch (error) {
|
|
802
|
+
console.error("loadFamily: ", error);
|
|
803
|
+
setFamily({ variants: [], parent: null, error: true });
|
|
804
|
+
return null;
|
|
805
|
+
}
|
|
806
|
+
};
|
|
807
|
+
|
|
808
|
+
/*
|
|
809
|
+
Abre otra variante. Esta pantalla lee el producto de sessionStorage, no de la
|
|
810
|
+
URL, así que hay que dejarlo ahí antes de navegar; si no, cambia la dirección
|
|
811
|
+
y sigue mostrando el producto anterior.
|
|
812
|
+
|
|
813
|
+
Se pide por ID y no por UPC: hay 12,024 UPC repetidos entre artículos activos
|
|
814
|
+
y 5,579 de esos casos caen dentro de la misma compañía.
|
|
815
|
+
*/
|
|
816
|
+
const openVariant = async (articleId) => {
|
|
817
|
+
setLoadingVariant(articleId);
|
|
818
|
+
try {
|
|
819
|
+
const res = await axios.get(`${process.env.REACT_APP_NEW_ARTICLES_ENDPOINT}`, {
|
|
820
|
+
params: { listType: "general", filterByArticleId: articleId },
|
|
821
|
+
headers: { Authorization: token },
|
|
822
|
+
});
|
|
823
|
+
|
|
824
|
+
const { data } = readPayload(res);
|
|
825
|
+
const encontrado = (data ?? []).find(
|
|
826
|
+
(item) => Number(item.id_article) === Number(articleId)
|
|
827
|
+
);
|
|
828
|
+
|
|
829
|
+
if (encontrado) {
|
|
830
|
+
sessionStorage.setItem(
|
|
831
|
+
"productSelected",
|
|
832
|
+
JSON.stringify({
|
|
833
|
+
...encontrado,
|
|
834
|
+
article: { id_article: encontrado.id_article },
|
|
835
|
+
})
|
|
836
|
+
);
|
|
837
|
+
}
|
|
838
|
+
} catch (error) {
|
|
839
|
+
// Se navega igual: dejar el clic sin efecto parecería que no responde.
|
|
840
|
+
console.error("openVariant: ", error);
|
|
841
|
+
} finally {
|
|
842
|
+
setLoadingVariant(null);
|
|
843
|
+
window.location.assign("/EditProducts");
|
|
844
|
+
}
|
|
845
|
+
};
|
|
846
|
+
|
|
847
|
+
/*
|
|
848
|
+
Refresca tras ligar, sin recargar: el producto sale de sessionStorage, así que
|
|
849
|
+
un refresh devolvería el mismo objeto y el contador seguiría igual.
|
|
850
|
+
*/
|
|
851
|
+
const handleOnLinkedVariants = (result) => {
|
|
852
|
+
if (!result) return;
|
|
853
|
+
|
|
854
|
+
const updatedProduct =
|
|
855
|
+
result.mode === "children"
|
|
856
|
+
? {
|
|
857
|
+
...state.product,
|
|
858
|
+
variantsCount:
|
|
859
|
+
(state.product?.variantsCount ?? 0) + (result.linked ?? []).length,
|
|
860
|
+
}
|
|
861
|
+
: {
|
|
862
|
+
...state.product,
|
|
863
|
+
parentArticleId: result.parentArticleId ?? null,
|
|
864
|
+
variantsCount: 0,
|
|
865
|
+
};
|
|
866
|
+
|
|
867
|
+
dispatch({ type: "SET_PRODUCT", payload: updatedProduct });
|
|
868
|
+
setFamily(null);
|
|
869
|
+
|
|
870
|
+
try {
|
|
871
|
+
sessionStorage.setItem("productSelected", JSON.stringify(updatedProduct));
|
|
872
|
+
} catch (error) {
|
|
873
|
+
console.error("linkVariants/sessionStorage: ", error);
|
|
874
|
+
}
|
|
875
|
+
};
|
|
876
|
+
|
|
877
|
+
/*
|
|
878
|
+
Acción "Volver a heredar" de los chips. Repinta en sitio con lo que devuelve
|
|
879
|
+
el endpoint: recargar la ficha se llevaría lo que haya sin guardar.
|
|
880
|
+
*/
|
|
881
|
+
const handleOnRestoreInheritance = async (attributeId) => {
|
|
882
|
+
if (!attributeId || !state.product?.id_article) return;
|
|
883
|
+
|
|
884
|
+
try {
|
|
885
|
+
const res = await axios.post(
|
|
886
|
+
`${process.env.REACT_APP_VARIANTS_ENDPOINT}/restore`,
|
|
887
|
+
{ articleId: state.product.id_article, attributes: [Number(attributeId)] },
|
|
888
|
+
{ headers: { Authorization: token } }
|
|
889
|
+
);
|
|
890
|
+
|
|
891
|
+
const data = readPayload(res);
|
|
892
|
+
|
|
893
|
+
dispatch({
|
|
894
|
+
type: "UPDATE_DATASHEETS_INPUTS",
|
|
895
|
+
payload: (data.values || []).map((item) => ({
|
|
896
|
+
attributeId: item.id,
|
|
897
|
+
value: item.value,
|
|
898
|
+
isOverridden: 0,
|
|
899
|
+
})),
|
|
900
|
+
});
|
|
901
|
+
} catch (error) {
|
|
902
|
+
console.error("restoreInheritance: ", error);
|
|
903
|
+
}
|
|
904
|
+
};
|
|
905
|
+
|
|
906
|
+
/*
|
|
907
|
+
Los atributos recién tocados, con el nombre que ve el usuario:
|
|
908
|
+
`updated_datasheets_inputs` solo trae { attributeId, value }.
|
|
909
|
+
*/
|
|
910
|
+
const cambiosDeFichaTecnica = () => {
|
|
911
|
+
const retailerId = state.active_retailer?.id_retailer;
|
|
912
|
+
const inputs = state.services?.datasheets?.inputs ?? {};
|
|
913
|
+
|
|
914
|
+
return (state.updated_datasheets_inputs ?? []).map((item) => {
|
|
915
|
+
const input = inputs[`${item.attributeId}_${retailerId}`];
|
|
916
|
+
return {
|
|
917
|
+
id: Number(item.attributeId),
|
|
918
|
+
name: input?.name ?? `Atributo ${item.attributeId}`,
|
|
919
|
+
value: item.value,
|
|
920
|
+
};
|
|
921
|
+
});
|
|
922
|
+
};
|
|
923
|
+
|
|
774
924
|
const handleOnClickSave = async () => {
|
|
775
|
-
if (state.saving) return;
|
|
925
|
+
if (state.saving || preparandoPropagacion) return;
|
|
776
926
|
|
|
777
927
|
switch (state.active_tab) {
|
|
778
928
|
case "Descripción":
|
|
779
929
|
await saveDescriptions(token);
|
|
780
930
|
break;
|
|
781
|
-
case "Ficha técnica":
|
|
931
|
+
case "Ficha técnica": {
|
|
932
|
+
// Guardar un padre con variantes abre la decisión de propagar. Sin este
|
|
933
|
+
// paso el cambio se queda solo en el padre y las variantes se van
|
|
934
|
+
// separando de él sin que nadie lo note.
|
|
935
|
+
const esPadre = state.product?.variantsCount > 0;
|
|
936
|
+
const cambios = cambiosDeFichaTecnica();
|
|
937
|
+
|
|
938
|
+
if (esPadre && cambios.length > 0) {
|
|
939
|
+
setPreparandoPropagacion(true);
|
|
940
|
+
loadFamily()
|
|
941
|
+
.then(() => setPendingPropagation({ cambios }))
|
|
942
|
+
.finally(() => setPreparandoPropagacion(false));
|
|
943
|
+
return;
|
|
944
|
+
}
|
|
945
|
+
|
|
782
946
|
await saveDatasheets(token);
|
|
783
947
|
break;
|
|
948
|
+
}
|
|
784
949
|
case "Imágenes":
|
|
785
950
|
await updateImages(token);
|
|
786
951
|
break;
|
|
@@ -1534,6 +1699,29 @@ const RetailerProductEditionView = ({
|
|
|
1534
1699
|
state={state}
|
|
1535
1700
|
>
|
|
1536
1701
|
<Container headerTop={headerTop}>
|
|
1702
|
+
{pendingPropagation && (
|
|
1703
|
+
<PropagateChangesModal
|
|
1704
|
+
changedAttributes={pendingPropagation.cambios}
|
|
1705
|
+
variants={(family?.variants ?? []).filter((v) => !v.isCurrent)}
|
|
1706
|
+
saving={state.saving}
|
|
1707
|
+
onCancel={() => setPendingPropagation(null)}
|
|
1708
|
+
onConfirm={(propagation) => {
|
|
1709
|
+
setPendingPropagation(null);
|
|
1710
|
+
saveDatasheets(token, propagation ?? undefined);
|
|
1711
|
+
}}
|
|
1712
|
+
/>
|
|
1713
|
+
)}
|
|
1714
|
+
{showLinkVariants && (
|
|
1715
|
+
<LinkVariantsModal
|
|
1716
|
+
articleId={state.product?.id_article}
|
|
1717
|
+
articleName={state.product?.name}
|
|
1718
|
+
articleUpc={state.product?.upc}
|
|
1719
|
+
activeRetailerId={state.active_retailer?.id_retailer}
|
|
1720
|
+
token={token}
|
|
1721
|
+
onClose={() => setShowLinkVariants(false)}
|
|
1722
|
+
onLinked={handleOnLinkedVariants}
|
|
1723
|
+
/>
|
|
1724
|
+
)}
|
|
1537
1725
|
{showRejectModal && (
|
|
1538
1726
|
<Modal
|
|
1539
1727
|
title={
|
|
@@ -1738,6 +1926,105 @@ const RetailerProductEditionView = ({
|
|
|
1738
1926
|
</div>
|
|
1739
1927
|
|
|
1740
1928
|
<div className="product-information">
|
|
1929
|
+
{/* Solo si el producto NO es ya variante de alguien: ligarlo otra vez
|
|
1930
|
+
devolvería VARIANT_ALREADY_LINKED sin excepción. */}
|
|
1931
|
+
{!state.product?.parentArticleId && (
|
|
1932
|
+
<div className="variants-actions">
|
|
1933
|
+
<button
|
|
1934
|
+
type="button"
|
|
1935
|
+
className="link-variants"
|
|
1936
|
+
onClick={() => setShowLinkVariants(true)}
|
|
1937
|
+
>
|
|
1938
|
+
Ligar productos
|
|
1939
|
+
</button>
|
|
1940
|
+
{state.product?.variantsCount > 0 && (
|
|
1941
|
+
<button
|
|
1942
|
+
type="button"
|
|
1943
|
+
className="variants-count"
|
|
1944
|
+
aria-expanded={showFamily}
|
|
1945
|
+
onClick={() => {
|
|
1946
|
+
setShowFamily((abierto) => !abierto);
|
|
1947
|
+
loadFamily();
|
|
1948
|
+
}}
|
|
1949
|
+
>
|
|
1950
|
+
{state.product.variantsCount} variante
|
|
1951
|
+
{state.product.variantsCount === 1 ? "" : "s"}
|
|
1952
|
+
<span aria-hidden="true">{showFamily ? " ▴" : " ▾"}</span>
|
|
1953
|
+
</button>
|
|
1954
|
+
)}
|
|
1955
|
+
</div>
|
|
1956
|
+
)}
|
|
1957
|
+
|
|
1958
|
+
{state.product?.parentArticleId && (
|
|
1959
|
+
<div className="variants-actions">
|
|
1960
|
+
<button
|
|
1961
|
+
type="button"
|
|
1962
|
+
className="variants-count"
|
|
1963
|
+
aria-expanded={showFamily}
|
|
1964
|
+
onClick={() => {
|
|
1965
|
+
setShowFamily((abierto) => !abierto);
|
|
1966
|
+
loadFamily();
|
|
1967
|
+
}}
|
|
1968
|
+
>
|
|
1969
|
+
Variante de otro producto
|
|
1970
|
+
<span aria-hidden="true">{showFamily ? " ▴" : " ▾"}</span>
|
|
1971
|
+
</button>
|
|
1972
|
+
</div>
|
|
1973
|
+
)}
|
|
1974
|
+
|
|
1975
|
+
{showFamily && (
|
|
1976
|
+
<div className="variants-family">
|
|
1977
|
+
{!family && <p className="loading">Cargando familia…</p>}
|
|
1978
|
+
|
|
1979
|
+
{family?.error && (
|
|
1980
|
+
<p className="loading">
|
|
1981
|
+
No se pudo cargar la familia de este producto.
|
|
1982
|
+
</p>
|
|
1983
|
+
)}
|
|
1984
|
+
|
|
1985
|
+
{family?.parent && (
|
|
1986
|
+
<button
|
|
1987
|
+
type="button"
|
|
1988
|
+
className="family-row parent"
|
|
1989
|
+
disabled={loadingVariant !== null}
|
|
1990
|
+
onClick={() => openVariant(family.parent.articleId)}
|
|
1991
|
+
>
|
|
1992
|
+
<span className="role">
|
|
1993
|
+
{loadingVariant === family.parent.articleId
|
|
1994
|
+
? "Abriendo…"
|
|
1995
|
+
: "Producto padre"}
|
|
1996
|
+
</span>
|
|
1997
|
+
<span className="name">{family.parent.name}</span>
|
|
1998
|
+
<span className="upc">{family.parent.upc}</span>
|
|
1999
|
+
</button>
|
|
2000
|
+
)}
|
|
2001
|
+
|
|
2002
|
+
{family?.variants?.map((variant) => (
|
|
2003
|
+
<button
|
|
2004
|
+
key={variant.articleId}
|
|
2005
|
+
type="button"
|
|
2006
|
+
className={`family-row${variant.isCurrent ? " current" : ""}`}
|
|
2007
|
+
disabled={variant.isCurrent || loadingVariant !== null}
|
|
2008
|
+
onClick={() => openVariant(variant.articleId)}
|
|
2009
|
+
>
|
|
2010
|
+
<span className="role">
|
|
2011
|
+
{loadingVariant === variant.articleId
|
|
2012
|
+
? "Abriendo…"
|
|
2013
|
+
: variant.isCurrent
|
|
2014
|
+
? "Estás aquí"
|
|
2015
|
+
: "Variante"}
|
|
2016
|
+
</span>
|
|
2017
|
+
<span className="name">{variant.name}</span>
|
|
2018
|
+
<span className="upc">{variant.upc}</span>
|
|
2019
|
+
</button>
|
|
2020
|
+
))}
|
|
2021
|
+
|
|
2022
|
+
{family && !family.error && !family.variants?.length && (
|
|
2023
|
+
<p className="loading">Este producto no tiene variantes.</p>
|
|
2024
|
+
)}
|
|
2025
|
+
</div>
|
|
2026
|
+
)}
|
|
2027
|
+
|
|
1741
2028
|
<FullProductNameHeader
|
|
1742
2029
|
// states
|
|
1743
2030
|
headerData={state.product}
|
|
@@ -1845,6 +2132,8 @@ const RetailerProductEditionView = ({
|
|
|
1845
2132
|
activeSection={state.active_tab}
|
|
1846
2133
|
inputGroup={dataGroup}
|
|
1847
2134
|
dataInputs={state.datasheets_inputs[1]}
|
|
2135
|
+
variant={state.services?.datasheets?.variant}
|
|
2136
|
+
onRestoreInheritance={handleOnRestoreInheritance}
|
|
1848
2137
|
auditInputs={auditDatasheets[1]}
|
|
1849
2138
|
updatedDatasheets={
|
|
1850
2139
|
state.updated_datasheets_inputs
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import styled from "styled-components";
|
|
2
2
|
import { FontFamily, GlobalColors } from "../../../global-files/variables";
|
|
3
3
|
|
|
4
|
+
// Rosa ContentOh, el mismo del modal de ligar productos.
|
|
5
|
+
const PINK = "#E13AA8";
|
|
6
|
+
|
|
4
7
|
export const Container = styled.div`
|
|
5
8
|
display: flex;
|
|
6
9
|
flex-direction: column;
|
|
@@ -166,4 +169,97 @@ export const Container = styled.div`
|
|
|
166
169
|
}
|
|
167
170
|
}
|
|
168
171
|
}
|
|
172
|
+
|
|
173
|
+
.variants-actions {
|
|
174
|
+
display: flex;
|
|
175
|
+
align-items: center;
|
|
176
|
+
gap: 10px;
|
|
177
|
+
margin-bottom: 8px;
|
|
178
|
+
|
|
179
|
+
.link-variants {
|
|
180
|
+
border: 1px solid ${PINK};
|
|
181
|
+
border-radius: 6px;
|
|
182
|
+
background: none;
|
|
183
|
+
padding: 6px 12px;
|
|
184
|
+
font-family: ${FontFamily.Raleway_600};
|
|
185
|
+
font-size: 11px;
|
|
186
|
+
color: ${PINK};
|
|
187
|
+
cursor: pointer;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
.variants-count {
|
|
191
|
+
border: none;
|
|
192
|
+
background: none;
|
|
193
|
+
padding: 0;
|
|
194
|
+
cursor: pointer;
|
|
195
|
+
font-family: ${FontFamily.Raleway_500};
|
|
196
|
+
font-size: 11px;
|
|
197
|
+
color: ${GlobalColors.gray};
|
|
198
|
+
text-decoration: underline;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
.variants-family {
|
|
203
|
+
display: flex;
|
|
204
|
+
flex-direction: column;
|
|
205
|
+
gap: 4px;
|
|
206
|
+
margin-bottom: 10px;
|
|
207
|
+
padding: 8px;
|
|
208
|
+
border: 1px solid ${GlobalColors.s2};
|
|
209
|
+
border-radius: 6px;
|
|
210
|
+
|
|
211
|
+
.loading {
|
|
212
|
+
margin: 0;
|
|
213
|
+
font-family: ${FontFamily.Raleway_500};
|
|
214
|
+
font-size: 11px;
|
|
215
|
+
color: ${GlobalColors.gray};
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
.family-row {
|
|
219
|
+
display: flex;
|
|
220
|
+
align-items: baseline;
|
|
221
|
+
gap: 8px;
|
|
222
|
+
padding: 6px 8px;
|
|
223
|
+
border: none;
|
|
224
|
+
border-radius: 4px;
|
|
225
|
+
background: none;
|
|
226
|
+
cursor: pointer;
|
|
227
|
+
text-align: left;
|
|
228
|
+
font-family: ${FontFamily.Raleway_500};
|
|
229
|
+
font-size: 12px;
|
|
230
|
+
color: ${GlobalColors.deep_gray};
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
.family-row:hover:not(:disabled) {
|
|
234
|
+
background-color: ${GlobalColors.s2};
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
.family-row:disabled {
|
|
238
|
+
cursor: default;
|
|
239
|
+
opacity: 0.7;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
.family-row .role {
|
|
243
|
+
min-width: 96px;
|
|
244
|
+
font-size: 10px;
|
|
245
|
+
color: ${PINK};
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
.family-row.current .role {
|
|
249
|
+
color: ${GlobalColors.gray};
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
.family-row .name {
|
|
253
|
+
flex: 1;
|
|
254
|
+
overflow: hidden;
|
|
255
|
+
text-overflow: ellipsis;
|
|
256
|
+
white-space: nowrap;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
.family-row .upc {
|
|
260
|
+
font-size: 11px;
|
|
261
|
+
color: ${GlobalColors.gray};
|
|
262
|
+
}
|
|
263
|
+
}
|
|
169
264
|
`;
|
|
265
|
+
|
|
@@ -114,6 +114,14 @@ export const normalizeProduct = (product) => {
|
|
|
114
114
|
percentages: productData.percentages || null,
|
|
115
115
|
summary: productData.summary || null,
|
|
116
116
|
version: productData.version || 0,
|
|
117
|
+
|
|
118
|
+
// Parentesco. read-articles-v2 ya lo devuelve; sin arrastrarlo hasta aquí la
|
|
119
|
+
// pantalla no puede saber si el producto pertenece a una familia.
|
|
120
|
+
// `?? null` en vez de `|| null`: variantsCount 0 es un dato válido.
|
|
121
|
+
parentArticleId:
|
|
122
|
+
productData.parentArticleId ?? article.parentArticleId ?? null,
|
|
123
|
+
parent: productData.parent ?? article.parent ?? null,
|
|
124
|
+
variantsCount: productData.variantsCount ?? article.variantsCount ?? 0,
|
|
117
125
|
id_order: productData.orderId || productData.id_order || 0,
|
|
118
126
|
status: productData.status || null,
|
|
119
127
|
datasheet_status: productData.datasheet_status || null,
|