contentoh-components-library 21.6.27 → 21.6.28
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/organisms/PropagateChangesModal/index.js +213 -0
- package/dist/components/organisms/PropagateChangesModal/styles.js +53 -0
- package/dist/components/pages/ProviderProductEdition/context/provider-product-edition.context.js +16 -7
- package/dist/components/pages/ProviderProductEdition/index.js +132 -56
- package/dist/index.js +25 -12
- package/package.json +1 -1
- package/src/components/organisms/PropagateChangesModal/index.js +209 -0
- package/src/components/organisms/PropagateChangesModal/styles.js +184 -0
- package/src/components/pages/ProviderProductEdition/context/provider-product-edition.context.jsx +9 -1
- package/src/components/pages/ProviderProductEdition/index.js +114 -34
- package/src/index.js +1 -0
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
import { useState } from "react";
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
Overlay,
|
|
5
|
+
Panel,
|
|
6
|
+
Header,
|
|
7
|
+
Body,
|
|
8
|
+
List,
|
|
9
|
+
Choices,
|
|
10
|
+
Note,
|
|
11
|
+
Footer,
|
|
12
|
+
} from "./styles";
|
|
13
|
+
|
|
14
|
+
/*
|
|
15
|
+
Diálogo de propagación al guardar un producto padre.
|
|
16
|
+
|
|
17
|
+
Aparece cuando se guardan cambios de ficha técnica en un producto que tiene
|
|
18
|
+
variantes. El backend NO propaga nada por su cuenta: solo baja a los hijos lo
|
|
19
|
+
que venga en `propagation`, y esa lista la arma esta pantalla. Guardar sin
|
|
20
|
+
abrirla, o cerrarla sin propagar, deja al padre modificado y a las variantes
|
|
21
|
+
como estaban.
|
|
22
|
+
|
|
23
|
+
props (* requerido):
|
|
24
|
+
- *changedAttributes: [{ id, name, value }] lo que se acaba de editar
|
|
25
|
+
- *variants: [{ articleId, name, upc }] las variantes activas del padre
|
|
26
|
+
- *onCancel: cerrar sin guardar nada
|
|
27
|
+
- *onConfirm: (propagation | null) => void
|
|
28
|
+
null → guardar solo el padre
|
|
29
|
+
{ attributes, targetChildren } → guardar y propagar
|
|
30
|
+
- saving: deshabilita los botones mientras el guardado corre
|
|
31
|
+
*/
|
|
32
|
+
|
|
33
|
+
// Por encima de este número el backend delega la propagación a una invocación
|
|
34
|
+
// en segundo plano. Se avisa aquí porque cambia lo que el usuario debe esperar:
|
|
35
|
+
// el guardado responde antes de que los hijos estén actualizados.
|
|
36
|
+
const UMBRAL_SEGUNDO_PLANO = 20;
|
|
37
|
+
|
|
38
|
+
export const PropagateChangesModal = ({
|
|
39
|
+
changedAttributes = [],
|
|
40
|
+
variants = [],
|
|
41
|
+
onCancel,
|
|
42
|
+
onConfirm,
|
|
43
|
+
saving = false,
|
|
44
|
+
}) => {
|
|
45
|
+
const [chosenAttributes, setChosenAttributes] = useState([]);
|
|
46
|
+
const [scope, setScope] = useState("all");
|
|
47
|
+
const [chosenChildren, setChosenChildren] = useState([]);
|
|
48
|
+
|
|
49
|
+
const destinatarios = scope === "all" ? variants.length : chosenChildren.length;
|
|
50
|
+
const puedePropagar = chosenAttributes.length > 0 && destinatarios > 0;
|
|
51
|
+
const enSegundoPlano = destinatarios > UMBRAL_SEGUNDO_PLANO;
|
|
52
|
+
|
|
53
|
+
const toggleAttribute = (id) => {
|
|
54
|
+
setChosenAttributes((current) =>
|
|
55
|
+
current.includes(id)
|
|
56
|
+
? current.filter((item) => item !== id)
|
|
57
|
+
: [...current, id]
|
|
58
|
+
);
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
const toggleChild = (id) => {
|
|
62
|
+
setChosenChildren((current) =>
|
|
63
|
+
current.includes(id)
|
|
64
|
+
? current.filter((item) => item !== id)
|
|
65
|
+
: [...current, id]
|
|
66
|
+
);
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
const confirmar = () => {
|
|
70
|
+
onConfirm?.({
|
|
71
|
+
attributes: chosenAttributes,
|
|
72
|
+
targetChildren: scope === "all" ? "all" : chosenChildren,
|
|
73
|
+
});
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
return (
|
|
77
|
+
<Overlay>
|
|
78
|
+
<Panel onClick={(event) => event.stopPropagation()}>
|
|
79
|
+
<Header>
|
|
80
|
+
<h2>Este producto tiene {variants.length} variante{variants.length === 1 ? "" : "s"}</h2>
|
|
81
|
+
<p>
|
|
82
|
+
Cambiaste {changedAttributes.length} atributo
|
|
83
|
+
{changedAttributes.length === 1 ? "" : "s"}. Elige cuáles bajan a las
|
|
84
|
+
variantes; el resto se guarda solo en este producto.
|
|
85
|
+
</p>
|
|
86
|
+
</Header>
|
|
87
|
+
|
|
88
|
+
<Body>
|
|
89
|
+
<h3>¿Qué se baja a las variantes?</h3>
|
|
90
|
+
<List>
|
|
91
|
+
{changedAttributes.map((attribute) => (
|
|
92
|
+
<li key={attribute.id}>
|
|
93
|
+
<label>
|
|
94
|
+
<input
|
|
95
|
+
type="checkbox"
|
|
96
|
+
checked={chosenAttributes.includes(attribute.id)}
|
|
97
|
+
onChange={() => toggleAttribute(attribute.id)}
|
|
98
|
+
/>
|
|
99
|
+
<span>{attribute.name}</span>
|
|
100
|
+
<span className="valor" title={attribute.value}>
|
|
101
|
+
{attribute.value}
|
|
102
|
+
</span>
|
|
103
|
+
</label>
|
|
104
|
+
</li>
|
|
105
|
+
))}
|
|
106
|
+
</List>
|
|
107
|
+
|
|
108
|
+
<h3>¿A cuáles?</h3>
|
|
109
|
+
<Choices>
|
|
110
|
+
<label>
|
|
111
|
+
<input
|
|
112
|
+
type="radio"
|
|
113
|
+
name="propagation-scope"
|
|
114
|
+
checked={scope === "all"}
|
|
115
|
+
onChange={() => setScope("all")}
|
|
116
|
+
/>
|
|
117
|
+
<span>
|
|
118
|
+
A todas
|
|
119
|
+
<span className="hint">
|
|
120
|
+
{variants.length} variante{variants.length === 1 ? "" : "s"} activa
|
|
121
|
+
{variants.length === 1 ? "" : "s"}.
|
|
122
|
+
</span>
|
|
123
|
+
</span>
|
|
124
|
+
</label>
|
|
125
|
+
<label>
|
|
126
|
+
<input
|
|
127
|
+
type="radio"
|
|
128
|
+
name="propagation-scope"
|
|
129
|
+
checked={scope === "some"}
|
|
130
|
+
onChange={() => setScope("some")}
|
|
131
|
+
/>
|
|
132
|
+
<span>Elegir cuáles</span>
|
|
133
|
+
</label>
|
|
134
|
+
</Choices>
|
|
135
|
+
|
|
136
|
+
{scope === "some" && (
|
|
137
|
+
<List>
|
|
138
|
+
{variants.map((variant) => (
|
|
139
|
+
<li key={variant.articleId}>
|
|
140
|
+
<label>
|
|
141
|
+
<input
|
|
142
|
+
type="checkbox"
|
|
143
|
+
checked={chosenChildren.includes(variant.articleId)}
|
|
144
|
+
onChange={() => toggleChild(variant.articleId)}
|
|
145
|
+
/>
|
|
146
|
+
<span>
|
|
147
|
+
{variant.name}
|
|
148
|
+
<span className="meta">UPC {variant.upc || "—"}</span>
|
|
149
|
+
</span>
|
|
150
|
+
</label>
|
|
151
|
+
</li>
|
|
152
|
+
))}
|
|
153
|
+
</List>
|
|
154
|
+
)}
|
|
155
|
+
|
|
156
|
+
{/* La diferencia con "ligar", y la que sorprende si no se dice: aquí
|
|
157
|
+
lo que la variante editó a mano NO se pisa. */}
|
|
158
|
+
<Note>
|
|
159
|
+
Una variante que haya editado alguno de estos atributos por su cuenta
|
|
160
|
+
conserva su valor. Para devolverlo al del padre está la acción
|
|
161
|
+
«Volver a heredar» en la ficha de esa variante.
|
|
162
|
+
</Note>
|
|
163
|
+
|
|
164
|
+
{enSegundoPlano && (
|
|
165
|
+
<Note className="accent">
|
|
166
|
+
Son más de {UMBRAL_SEGUNDO_PLANO} variantes, así que la propagación
|
|
167
|
+
corre en segundo plano: el guardado termina antes de que todas estén
|
|
168
|
+
actualizadas.
|
|
169
|
+
</Note>
|
|
170
|
+
)}
|
|
171
|
+
</Body>
|
|
172
|
+
|
|
173
|
+
<Footer>
|
|
174
|
+
<span className="resumen">
|
|
175
|
+
{puedePropagar
|
|
176
|
+
? `${chosenAttributes.length} atributo(s) a ${destinatarios} variante(s).`
|
|
177
|
+
: "No se bajará nada a las variantes."}
|
|
178
|
+
</span>
|
|
179
|
+
<button
|
|
180
|
+
type="button"
|
|
181
|
+
className="secondary"
|
|
182
|
+
onClick={onCancel}
|
|
183
|
+
disabled={saving}
|
|
184
|
+
>
|
|
185
|
+
Cancelar
|
|
186
|
+
</button>
|
|
187
|
+
{/* Guardar sin propagar es una salida legítima y visible: el usuario
|
|
188
|
+
puede querer cambiar solo el padre. */}
|
|
189
|
+
<button
|
|
190
|
+
type="button"
|
|
191
|
+
className="secondary"
|
|
192
|
+
onClick={() => onConfirm?.(null)}
|
|
193
|
+
disabled={saving}
|
|
194
|
+
>
|
|
195
|
+
Solo este producto
|
|
196
|
+
</button>
|
|
197
|
+
<button
|
|
198
|
+
type="button"
|
|
199
|
+
className="primary"
|
|
200
|
+
onClick={confirmar}
|
|
201
|
+
disabled={saving || !puedePropagar}
|
|
202
|
+
>
|
|
203
|
+
{saving ? "Guardando…" : "Guardar y propagar"}
|
|
204
|
+
</button>
|
|
205
|
+
</Footer>
|
|
206
|
+
</Panel>
|
|
207
|
+
</Overlay>
|
|
208
|
+
);
|
|
209
|
+
};
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
import styled from "styled-components";
|
|
2
|
+
import "../../../global-files/global-styles.css";
|
|
3
|
+
import { GlobalColors, FontFamily } from "../../../global-files/variables";
|
|
4
|
+
|
|
5
|
+
const PINK = "#E13AA8";
|
|
6
|
+
const PINK_RGB = "225, 58, 168";
|
|
7
|
+
|
|
8
|
+
export const Overlay = styled.div`
|
|
9
|
+
position: fixed;
|
|
10
|
+
inset: 0;
|
|
11
|
+
z-index: 110;
|
|
12
|
+
display: flex;
|
|
13
|
+
align-items: center;
|
|
14
|
+
justify-content: center;
|
|
15
|
+
background-color: rgba(40, 31, 51, 0.45);
|
|
16
|
+
`;
|
|
17
|
+
|
|
18
|
+
export const Panel = styled.div`
|
|
19
|
+
display: flex;
|
|
20
|
+
flex-direction: column;
|
|
21
|
+
width: min(560px, 92vw);
|
|
22
|
+
max-height: 86vh;
|
|
23
|
+
border-radius: 10px;
|
|
24
|
+
background-color: ${GlobalColors.white};
|
|
25
|
+
font-family: ${FontFamily.Raleway};
|
|
26
|
+
overflow: hidden;
|
|
27
|
+
|
|
28
|
+
input[type="checkbox"],
|
|
29
|
+
input[type="radio"] {
|
|
30
|
+
accent-color: ${PINK};
|
|
31
|
+
width: 15px;
|
|
32
|
+
height: 15px;
|
|
33
|
+
cursor: pointer;
|
|
34
|
+
}
|
|
35
|
+
`;
|
|
36
|
+
|
|
37
|
+
export const Header = styled.div`
|
|
38
|
+
padding: 20px 24px 8px;
|
|
39
|
+
|
|
40
|
+
h2 {
|
|
41
|
+
margin: 0;
|
|
42
|
+
font-family: ${FontFamily.Raleway_700};
|
|
43
|
+
font-size: 17px;
|
|
44
|
+
color: ${GlobalColors.deep_gray};
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
p {
|
|
48
|
+
margin: 6px 0 0;
|
|
49
|
+
font-size: 13px;
|
|
50
|
+
line-height: 1.4;
|
|
51
|
+
color: ${GlobalColors.gray};
|
|
52
|
+
}
|
|
53
|
+
`;
|
|
54
|
+
|
|
55
|
+
export const Body = styled.div`
|
|
56
|
+
flex: 1;
|
|
57
|
+
overflow-y: auto;
|
|
58
|
+
padding: 12px 24px 0;
|
|
59
|
+
|
|
60
|
+
h3 {
|
|
61
|
+
margin: 0 0 8px;
|
|
62
|
+
font-family: ${FontFamily.Raleway_600};
|
|
63
|
+
font-size: 13px;
|
|
64
|
+
color: ${GlobalColors.deep_gray};
|
|
65
|
+
}
|
|
66
|
+
`;
|
|
67
|
+
|
|
68
|
+
export const List = styled.ul`
|
|
69
|
+
list-style: none;
|
|
70
|
+
margin: 0 0 18px;
|
|
71
|
+
padding: 0;
|
|
72
|
+
border: 1px solid ${GlobalColors.s2};
|
|
73
|
+
border-radius: 6px;
|
|
74
|
+
max-height: 190px;
|
|
75
|
+
overflow-y: auto;
|
|
76
|
+
|
|
77
|
+
li + li {
|
|
78
|
+
border-top: 1px solid ${GlobalColors.s2};
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
label {
|
|
82
|
+
display: flex;
|
|
83
|
+
align-items: center;
|
|
84
|
+
gap: 10px;
|
|
85
|
+
padding: 9px 12px;
|
|
86
|
+
cursor: pointer;
|
|
87
|
+
font-size: 13px;
|
|
88
|
+
color: ${GlobalColors.deep_gray};
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
.valor {
|
|
92
|
+
margin-left: auto;
|
|
93
|
+
max-width: 45%;
|
|
94
|
+
font-size: 12px;
|
|
95
|
+
color: ${GlobalColors.gray};
|
|
96
|
+
overflow: hidden;
|
|
97
|
+
text-overflow: ellipsis;
|
|
98
|
+
white-space: nowrap;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
.meta {
|
|
102
|
+
display: block;
|
|
103
|
+
font-size: 12px;
|
|
104
|
+
color: ${GlobalColors.gray};
|
|
105
|
+
}
|
|
106
|
+
`;
|
|
107
|
+
|
|
108
|
+
export const Choices = styled.div`
|
|
109
|
+
display: flex;
|
|
110
|
+
flex-direction: column;
|
|
111
|
+
gap: 2px;
|
|
112
|
+
margin-bottom: 10px;
|
|
113
|
+
|
|
114
|
+
label {
|
|
115
|
+
display: flex;
|
|
116
|
+
align-items: flex-start;
|
|
117
|
+
gap: 8px;
|
|
118
|
+
padding: 4px 0;
|
|
119
|
+
font-size: 13px;
|
|
120
|
+
color: ${GlobalColors.deep_gray};
|
|
121
|
+
cursor: pointer;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
.hint {
|
|
125
|
+
display: block;
|
|
126
|
+
font-size: 12px;
|
|
127
|
+
color: ${GlobalColors.gray};
|
|
128
|
+
}
|
|
129
|
+
`;
|
|
130
|
+
|
|
131
|
+
export const Note = styled.p`
|
|
132
|
+
margin: 0 0 16px;
|
|
133
|
+
padding: 10px 12px;
|
|
134
|
+
border-radius: 6px;
|
|
135
|
+
font-size: 12px;
|
|
136
|
+
line-height: 1.45;
|
|
137
|
+
background-color: ${GlobalColors.s2};
|
|
138
|
+
color: ${GlobalColors.deep_gray};
|
|
139
|
+
|
|
140
|
+
&.accent {
|
|
141
|
+
background-color: rgba(${PINK_RGB}, 0.1);
|
|
142
|
+
color: ${PINK};
|
|
143
|
+
}
|
|
144
|
+
`;
|
|
145
|
+
|
|
146
|
+
export const Footer = styled.div`
|
|
147
|
+
display: flex;
|
|
148
|
+
align-items: center;
|
|
149
|
+
gap: 10px;
|
|
150
|
+
padding: 16px 24px;
|
|
151
|
+
border-top: 1px solid ${GlobalColors.s2};
|
|
152
|
+
|
|
153
|
+
.resumen {
|
|
154
|
+
flex: 1;
|
|
155
|
+
font-size: 12px;
|
|
156
|
+
color: ${GlobalColors.gray};
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
button {
|
|
160
|
+
height: 38px;
|
|
161
|
+
padding: 0 16px;
|
|
162
|
+
border-radius: 6px;
|
|
163
|
+
font-family: ${FontFamily.Raleway_600};
|
|
164
|
+
font-size: 13px;
|
|
165
|
+
cursor: pointer;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
button.secondary {
|
|
169
|
+
border: 1px solid ${GlobalColors.s2};
|
|
170
|
+
background-color: ${GlobalColors.white};
|
|
171
|
+
color: ${GlobalColors.gray};
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
button.primary {
|
|
175
|
+
border: none;
|
|
176
|
+
background-color: ${PINK};
|
|
177
|
+
color: ${GlobalColors.white};
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
button:disabled {
|
|
181
|
+
opacity: 0.5;
|
|
182
|
+
cursor: default;
|
|
183
|
+
}
|
|
184
|
+
`;
|
package/src/components/pages/ProviderProductEdition/context/provider-product-edition.context.jsx
CHANGED
|
@@ -118,13 +118,21 @@ export const ProviderProductEditionProvider = ({ children }) => {
|
|
|
118
118
|
}
|
|
119
119
|
};
|
|
120
120
|
|
|
121
|
-
|
|
121
|
+
/*
|
|
122
|
+
`propagation` es opcional y solo lo manda la ficha de un producto padre, con
|
|
123
|
+
lo que el usuario eligió en el diálogo: { attributes: [...], targetChildren }.
|
|
124
|
+
|
|
125
|
+
Sin ese bloque el backend no propaga nada. Es deliberado: bajar un cambio a
|
|
126
|
+
las variantes tiene que ser una decisión, no un efecto secundario de guardar.
|
|
127
|
+
*/
|
|
128
|
+
const saveDatasheets = async (token, propagation) => {
|
|
122
129
|
if (state.updated_datasheets_inputs.length === 0) return;
|
|
123
130
|
dispatch({ type: "SET_SAVING", payload: true });
|
|
124
131
|
|
|
125
132
|
const dataObject = {
|
|
126
133
|
articleId: state.product?.id_article,
|
|
127
134
|
articleData: state.updated_datasheets_inputs,
|
|
135
|
+
...(propagation ? { propagation } : {}),
|
|
128
136
|
};
|
|
129
137
|
|
|
130
138
|
// Marcar todos los items como aprobados
|