contentoh-components-library 21.6.30 → 21.6.32
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/RetailerProductEdition/context/provider-product-edition.context.js +44 -26
- package/dist/components/pages/RetailerProductEdition/index.js +554 -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/RetailerProductEdition/context/provider-product-edition.context.jsx +22 -2
- package/src/components/pages/RetailerProductEdition/index.js +279 -2
- package/src/components/pages/RetailerProductEdition/styles.js +85 -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-radius: 4px;\n text-align: left;\n font-family: ", ";\n font-size: 12px;\n color: ", ";\n }\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, 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
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,13 @@ 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 [pendingPropagation, setPendingPropagation] = useState(null);
|
|
189
|
+
const [preparandoPropagacion, setPreparandoPropagacion] = useState(false);
|
|
190
|
+
|
|
181
191
|
const [auditServices, setAuditServices] = useState([]);
|
|
182
192
|
const [auditDatasheets, setAuditDatasheets] = useState([]);
|
|
183
193
|
const [auditDescriptions, setAuditDescriptions] = useState([]);
|
|
@@ -771,16 +781,175 @@ const RetailerProductEditionView = ({
|
|
|
771
781
|
}
|
|
772
782
|
};
|
|
773
783
|
|
|
784
|
+
/*
|
|
785
|
+
Trae la familia: las variantes del producto, o sus hermanas si el abierto es
|
|
786
|
+
una de ellas. Se pide al abrir la sección y no al montar la pantalla: la
|
|
787
|
+
mayoría de los productos no son familia.
|
|
788
|
+
*/
|
|
789
|
+
const loadFamily = async (forzar = false) => {
|
|
790
|
+
if (family && !forzar) return family;
|
|
791
|
+
try {
|
|
792
|
+
const res = await axios.post(
|
|
793
|
+
`${process.env.REACT_APP_VARIANTS_ENDPOINT}/family`,
|
|
794
|
+
{ articleId: state.product?.id_article },
|
|
795
|
+
{ headers: { Authorization: token } }
|
|
796
|
+
);
|
|
797
|
+
const data = readPayload(res);
|
|
798
|
+
setFamily(data);
|
|
799
|
+
return data;
|
|
800
|
+
} catch (error) {
|
|
801
|
+
console.error("loadFamily: ", error);
|
|
802
|
+
setFamily({ variants: [], parent: null, error: true });
|
|
803
|
+
return null;
|
|
804
|
+
}
|
|
805
|
+
};
|
|
806
|
+
|
|
807
|
+
/*
|
|
808
|
+
En Creators el parentesco se pregunta, no viene con el producto.
|
|
809
|
+
|
|
810
|
+
Su listado sale del endpoint de reportes, que no trae `variantsCount` ni
|
|
811
|
+
`parentArticleId`: esas columnas se agregaron a las vistas pero DynamoDB no
|
|
812
|
+
las tiene hasta que se corra el recorrido de datos. Y aunque las tuviera,
|
|
813
|
+
dependería de lo actualizado que esté ese recorrido.
|
|
814
|
+
|
|
815
|
+
Preguntar a `family` cuesta una llamada por ficha y da el dato de la base, no
|
|
816
|
+
de una copia. Además aplica el alcance del perfil (§5.1): al equipo interno le
|
|
817
|
+
devuelve los productos solicitados a ContentOh, que es justo lo que puede
|
|
818
|
+
tocar.
|
|
819
|
+
*/
|
|
820
|
+
useEffect(() => {
|
|
821
|
+
if (!state.product?.id_article) return;
|
|
822
|
+
loadFamily(true);
|
|
823
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
824
|
+
}, [state.product?.id_article]);
|
|
825
|
+
|
|
826
|
+
// El conteo sale de la familia recién consultada, no del producto.
|
|
827
|
+
const variantesDelProducto = family?.error
|
|
828
|
+
? 0
|
|
829
|
+
: (family?.variants ?? []).filter((v) => !v.isCurrent).length;
|
|
830
|
+
|
|
831
|
+
const esVarianteDeOtro = family?.error
|
|
832
|
+
? !!state.product?.parentArticleId
|
|
833
|
+
: !!family?.parent;
|
|
834
|
+
|
|
835
|
+
/*
|
|
836
|
+
En Creators la lista de familia es INFORMATIVA: no se salta a otra variante.
|
|
837
|
+
|
|
838
|
+
Saltar exige precargar el producto destino, y el endpoint que hace eso en la
|
|
839
|
+
aplicación de clientes —read-articles-v2— filtra por la compañía del usuario:
|
|
840
|
+
`AND a.company_id = <la del usuario>`. El equipo interno pertenece a ContentOh
|
|
841
|
+
y trabaja productos de sus clientes, así que ahí no devuelve nada.
|
|
842
|
+
|
|
843
|
+
Navegar sin precargar sería peor que no ofrecerlo: esta pantalla lee el
|
|
844
|
+
producto de sessionStorage, así que cambiaría la URL y seguiría mostrando el
|
|
845
|
+
producto anterior, dando a entender que se cambió de variante cuando no.
|
|
846
|
+
|
|
847
|
+
Para habilitarlo hace falta una forma de traer un producto por id sin filtro
|
|
848
|
+
de compañía, respetando el alcance del perfil interno (§5.1 del contrato).
|
|
849
|
+
*/
|
|
850
|
+
|
|
851
|
+
/*
|
|
852
|
+
Refresca tras ligar, sin recargar: el producto sale de sessionStorage, así que
|
|
853
|
+
un refresh devolvería el mismo objeto y el contador seguiría igual.
|
|
854
|
+
*/
|
|
855
|
+
const handleOnLinkedVariants = (result) => {
|
|
856
|
+
if (!result) return;
|
|
857
|
+
|
|
858
|
+
const updatedProduct =
|
|
859
|
+
result.mode === "children"
|
|
860
|
+
? {
|
|
861
|
+
...state.product,
|
|
862
|
+
variantsCount:
|
|
863
|
+
(state.product?.variantsCount ?? 0) + (result.linked ?? []).length,
|
|
864
|
+
}
|
|
865
|
+
: {
|
|
866
|
+
...state.product,
|
|
867
|
+
parentArticleId: result.parentArticleId ?? null,
|
|
868
|
+
variantsCount: 0,
|
|
869
|
+
};
|
|
870
|
+
|
|
871
|
+
dispatch({ type: "SET_PRODUCT", payload: updatedProduct });
|
|
872
|
+
setFamily(null);
|
|
873
|
+
|
|
874
|
+
try {
|
|
875
|
+
sessionStorage.setItem("productSelected", JSON.stringify(updatedProduct));
|
|
876
|
+
} catch (error) {
|
|
877
|
+
console.error("linkVariants/sessionStorage: ", error);
|
|
878
|
+
}
|
|
879
|
+
};
|
|
880
|
+
|
|
881
|
+
/*
|
|
882
|
+
Acción "Volver a heredar" de los chips. Repinta en sitio con lo que devuelve
|
|
883
|
+
el endpoint: recargar la ficha se llevaría lo que haya sin guardar.
|
|
884
|
+
*/
|
|
885
|
+
const handleOnRestoreInheritance = async (attributeId) => {
|
|
886
|
+
if (!attributeId || !state.product?.id_article) return;
|
|
887
|
+
|
|
888
|
+
try {
|
|
889
|
+
const res = await axios.post(
|
|
890
|
+
`${process.env.REACT_APP_VARIANTS_ENDPOINT}/restore`,
|
|
891
|
+
{ articleId: state.product.id_article, attributes: [Number(attributeId)] },
|
|
892
|
+
{ headers: { Authorization: token } }
|
|
893
|
+
);
|
|
894
|
+
|
|
895
|
+
const data = readPayload(res);
|
|
896
|
+
|
|
897
|
+
dispatch({
|
|
898
|
+
type: "UPDATE_DATASHEETS_INPUTS",
|
|
899
|
+
payload: (data.values || []).map((item) => ({
|
|
900
|
+
attributeId: item.id,
|
|
901
|
+
value: item.value,
|
|
902
|
+
isOverridden: 0,
|
|
903
|
+
})),
|
|
904
|
+
});
|
|
905
|
+
} catch (error) {
|
|
906
|
+
console.error("restoreInheritance: ", error);
|
|
907
|
+
}
|
|
908
|
+
};
|
|
909
|
+
|
|
910
|
+
/*
|
|
911
|
+
Los atributos recién tocados, con el nombre que ve el usuario:
|
|
912
|
+
`updated_datasheets_inputs` solo trae { attributeId, value }.
|
|
913
|
+
*/
|
|
914
|
+
const cambiosDeFichaTecnica = () => {
|
|
915
|
+
const retailerId = state.active_retailer?.id_retailer;
|
|
916
|
+
const inputs = state.services?.datasheets?.inputs ?? {};
|
|
917
|
+
|
|
918
|
+
return (state.updated_datasheets_inputs ?? []).map((item) => {
|
|
919
|
+
const input = inputs[`${item.attributeId}_${retailerId}`];
|
|
920
|
+
return {
|
|
921
|
+
id: Number(item.attributeId),
|
|
922
|
+
name: input?.name ?? `Atributo ${item.attributeId}`,
|
|
923
|
+
value: item.value,
|
|
924
|
+
};
|
|
925
|
+
});
|
|
926
|
+
};
|
|
927
|
+
|
|
774
928
|
const handleOnClickSave = async () => {
|
|
775
|
-
if (state.saving) return;
|
|
929
|
+
if (state.saving || preparandoPropagacion) return;
|
|
776
930
|
|
|
777
931
|
switch (state.active_tab) {
|
|
778
932
|
case "Descripción":
|
|
779
933
|
await saveDescriptions(token);
|
|
780
934
|
break;
|
|
781
|
-
case "Ficha técnica":
|
|
935
|
+
case "Ficha técnica": {
|
|
936
|
+
// Guardar un padre con variantes abre la decisión de propagar. Sin este
|
|
937
|
+
// paso el cambio se queda solo en el padre y las variantes se van
|
|
938
|
+
// separando de él sin que nadie lo note.
|
|
939
|
+
const esPadre = variantesDelProducto > 0;
|
|
940
|
+
const cambios = cambiosDeFichaTecnica();
|
|
941
|
+
|
|
942
|
+
if (esPadre && cambios.length > 0) {
|
|
943
|
+
setPreparandoPropagacion(true);
|
|
944
|
+
loadFamily()
|
|
945
|
+
.then(() => setPendingPropagation({ cambios }))
|
|
946
|
+
.finally(() => setPreparandoPropagacion(false));
|
|
947
|
+
return;
|
|
948
|
+
}
|
|
949
|
+
|
|
782
950
|
await saveDatasheets(token);
|
|
783
951
|
break;
|
|
952
|
+
}
|
|
784
953
|
case "Imágenes":
|
|
785
954
|
await updateImages(token);
|
|
786
955
|
break;
|
|
@@ -1534,6 +1703,29 @@ const RetailerProductEditionView = ({
|
|
|
1534
1703
|
state={state}
|
|
1535
1704
|
>
|
|
1536
1705
|
<Container headerTop={headerTop}>
|
|
1706
|
+
{pendingPropagation && (
|
|
1707
|
+
<PropagateChangesModal
|
|
1708
|
+
changedAttributes={pendingPropagation.cambios}
|
|
1709
|
+
variants={(family?.variants ?? []).filter((v) => !v.isCurrent)}
|
|
1710
|
+
saving={state.saving}
|
|
1711
|
+
onCancel={() => setPendingPropagation(null)}
|
|
1712
|
+
onConfirm={(propagation) => {
|
|
1713
|
+
setPendingPropagation(null);
|
|
1714
|
+
saveDatasheets(token, propagation ?? undefined);
|
|
1715
|
+
}}
|
|
1716
|
+
/>
|
|
1717
|
+
)}
|
|
1718
|
+
{showLinkVariants && (
|
|
1719
|
+
<LinkVariantsModal
|
|
1720
|
+
articleId={state.product?.id_article}
|
|
1721
|
+
articleName={state.product?.name}
|
|
1722
|
+
articleUpc={state.product?.upc}
|
|
1723
|
+
activeRetailerId={state.active_retailer?.id_retailer}
|
|
1724
|
+
token={token}
|
|
1725
|
+
onClose={() => setShowLinkVariants(false)}
|
|
1726
|
+
onLinked={handleOnLinkedVariants}
|
|
1727
|
+
/>
|
|
1728
|
+
)}
|
|
1537
1729
|
{showRejectModal && (
|
|
1538
1730
|
<Modal
|
|
1539
1731
|
title={
|
|
@@ -1738,6 +1930,89 @@ const RetailerProductEditionView = ({
|
|
|
1738
1930
|
</div>
|
|
1739
1931
|
|
|
1740
1932
|
<div className="product-information">
|
|
1933
|
+
{/* Solo si el producto NO es ya variante de alguien: ligarlo otra vez
|
|
1934
|
+
devolvería VARIANT_ALREADY_LINKED sin excepción. */}
|
|
1935
|
+
{!esVarianteDeOtro && (
|
|
1936
|
+
<div className="variants-actions">
|
|
1937
|
+
<button
|
|
1938
|
+
type="button"
|
|
1939
|
+
className="link-variants"
|
|
1940
|
+
onClick={() => setShowLinkVariants(true)}
|
|
1941
|
+
>
|
|
1942
|
+
Ligar productos
|
|
1943
|
+
</button>
|
|
1944
|
+
{variantesDelProducto > 0 && (
|
|
1945
|
+
<button
|
|
1946
|
+
type="button"
|
|
1947
|
+
className="variants-count"
|
|
1948
|
+
aria-expanded={showFamily}
|
|
1949
|
+
onClick={() => {
|
|
1950
|
+
setShowFamily((abierto) => !abierto);
|
|
1951
|
+
loadFamily();
|
|
1952
|
+
}}
|
|
1953
|
+
>
|
|
1954
|
+
{variantesDelProducto} variante
|
|
1955
|
+
{variantesDelProducto === 1 ? "" : "s"}
|
|
1956
|
+
<span aria-hidden="true">{showFamily ? " ▴" : " ▾"}</span>
|
|
1957
|
+
</button>
|
|
1958
|
+
)}
|
|
1959
|
+
</div>
|
|
1960
|
+
)}
|
|
1961
|
+
|
|
1962
|
+
{esVarianteDeOtro && (
|
|
1963
|
+
<div className="variants-actions">
|
|
1964
|
+
<button
|
|
1965
|
+
type="button"
|
|
1966
|
+
className="variants-count"
|
|
1967
|
+
aria-expanded={showFamily}
|
|
1968
|
+
onClick={() => {
|
|
1969
|
+
setShowFamily((abierto) => !abierto);
|
|
1970
|
+
loadFamily();
|
|
1971
|
+
}}
|
|
1972
|
+
>
|
|
1973
|
+
Variante de otro producto
|
|
1974
|
+
<span aria-hidden="true">{showFamily ? " ▴" : " ▾"}</span>
|
|
1975
|
+
</button>
|
|
1976
|
+
</div>
|
|
1977
|
+
)}
|
|
1978
|
+
|
|
1979
|
+
{showFamily && (
|
|
1980
|
+
<div className="variants-family">
|
|
1981
|
+
{!family && <p className="loading">Cargando familia…</p>}
|
|
1982
|
+
|
|
1983
|
+
{family?.error && (
|
|
1984
|
+
<p className="loading">
|
|
1985
|
+
No se pudo cargar la familia de este producto.
|
|
1986
|
+
</p>
|
|
1987
|
+
)}
|
|
1988
|
+
|
|
1989
|
+
{family?.parent && (
|
|
1990
|
+
<div className="family-row parent">
|
|
1991
|
+
<span className="role">Producto padre</span>
|
|
1992
|
+
<span className="name">{family.parent.name}</span>
|
|
1993
|
+
<span className="upc">{family.parent.upc}</span>
|
|
1994
|
+
</div>
|
|
1995
|
+
)}
|
|
1996
|
+
|
|
1997
|
+
{family?.variants?.map((variant) => (
|
|
1998
|
+
<div
|
|
1999
|
+
key={variant.articleId}
|
|
2000
|
+
className={`family-row${variant.isCurrent ? " current" : ""}`}
|
|
2001
|
+
>
|
|
2002
|
+
<span className="role">
|
|
2003
|
+
{variant.isCurrent ? "Estás aquí" : "Variante"}
|
|
2004
|
+
</span>
|
|
2005
|
+
<span className="name">{variant.name}</span>
|
|
2006
|
+
<span className="upc">{variant.upc}</span>
|
|
2007
|
+
</div>
|
|
2008
|
+
))}
|
|
2009
|
+
|
|
2010
|
+
{family && !family.error && !family.variants?.length && (
|
|
2011
|
+
<p className="loading">Este producto no tiene variantes.</p>
|
|
2012
|
+
)}
|
|
2013
|
+
</div>
|
|
2014
|
+
)}
|
|
2015
|
+
|
|
1741
2016
|
<FullProductNameHeader
|
|
1742
2017
|
// states
|
|
1743
2018
|
headerData={state.product}
|
|
@@ -1845,6 +2120,8 @@ const RetailerProductEditionView = ({
|
|
|
1845
2120
|
activeSection={state.active_tab}
|
|
1846
2121
|
inputGroup={dataGroup}
|
|
1847
2122
|
dataInputs={state.datasheets_inputs[1]}
|
|
2123
|
+
variant={state.services?.datasheets?.variant}
|
|
2124
|
+
onRestoreInheritance={handleOnRestoreInheritance}
|
|
1848
2125
|
auditInputs={auditDatasheets[1]}
|
|
1849
2126
|
updatedDatasheets={
|
|
1850
2127
|
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,86 @@ 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-radius: 4px;
|
|
224
|
+
text-align: left;
|
|
225
|
+
font-family: ${FontFamily.Raleway_500};
|
|
226
|
+
font-size: 12px;
|
|
227
|
+
color: ${GlobalColors.deep_gray};
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
.family-row .role {
|
|
232
|
+
min-width: 96px;
|
|
233
|
+
font-size: 10px;
|
|
234
|
+
color: ${PINK};
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
.family-row.current .role {
|
|
238
|
+
color: ${GlobalColors.gray};
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
.family-row .name {
|
|
242
|
+
flex: 1;
|
|
243
|
+
overflow: hidden;
|
|
244
|
+
text-overflow: ellipsis;
|
|
245
|
+
white-space: nowrap;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
.family-row .upc {
|
|
249
|
+
font-size: 11px;
|
|
250
|
+
color: ${GlobalColors.gray};
|
|
251
|
+
}
|
|
252
|
+
}
|
|
169
253
|
`;
|
|
254
|
+
|
|
@@ -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,
|