contentoh-components-library 21.6.48 → 21.6.50
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/.claude/launch.json +14 -0
- package/.env.test +1 -0
- package/dist/ai/prompts/attribute.prompt.js +28 -0
- package/dist/ai/prompts/description.prompt.js +33 -0
- package/dist/ai/schemas/attribute.schema.js +35 -0
- package/dist/ai/schemas/description.schema.js +27 -0
- package/dist/components/atoms/AssetPreview/AssetPreview.stories.js +118 -0
- package/dist/components/atoms/AssetPreview/index.js +150 -0
- package/dist/components/atoms/AssetPreview/styles.js +28 -0
- package/dist/components/atoms/IconFile/index.js +85 -2
- package/dist/components/atoms/IconFile/styles.js +1 -1
- package/dist/components/atoms/ProductImage/ProductImage.stories.js +70 -2
- package/dist/components/atoms/ProductImage/index.js +100 -27
- package/dist/components/atoms/ProductImage/styles.js +3 -1
- package/dist/components/molecules/EditionActiveImage/index.js +4 -2
- package/dist/components/molecules/GalleryAssetElement/GalleryAssetElement.stories.js +147 -0
- package/dist/components/molecules/GalleryAssetElement/index.js +178 -0
- package/dist/components/molecules/GalleryAssetElement/styles.js +26 -0
- package/dist/components/molecules/GalleryElement/index.js +13 -4
- package/dist/components/molecules/GalleryHeader/index.js +96 -24
- package/dist/components/molecules/GalleryHeader/styles.js +4 -1
- package/dist/components/molecules/GalleryTrayTabs/GalleryTrayTabs.stories.js +56 -0
- package/dist/components/molecules/GalleryTrayTabs/index.js +60 -0
- package/dist/components/molecules/GalleryTrayTabs/styles.js +20 -0
- package/dist/components/molecules/ImageSelector/index.js +1 -0
- package/dist/components/organisms/DigitalAssetsTray/DigitalAssetsTray.stories.js +171 -0
- package/dist/components/organisms/DigitalAssetsTray/index.js +211 -0
- package/dist/components/organisms/DigitalAssetsTray/styles.js +22 -0
- package/dist/components/organisms/ImagePreviewer/index.js +1 -0
- package/dist/components/organisms/ProductImageModal/index.js +3 -2
- package/dist/components/organisms/PropagateChangesModal/index.js +12 -1
- package/dist/components/organisms/PropagateChangesModal/non-propagable.js +45 -0
- package/dist/components/organisms/VariantGroupModal/index.js +261 -37
- package/dist/components/organisms/VariantGroupModal/styles.js +25 -5
- package/dist/components/pages/ProviderProductEdition/index.js +11 -1
- package/dist/components/pages/RetailerProductEdition/context/provider-product-edition.context.js +330 -15
- package/dist/components/pages/RetailerProductEdition/context/provider-product-edition.reducer.js +12 -2
- package/dist/components/pages/RetailerProductEdition/index.js +605 -414
- package/dist/components/pages/RetailerProductEdition/stories/ImageEditor.stories.js +1 -0
- package/dist/components/pages/RetailerProductEdition/stories/TextEditor.stories.js +45 -145
- package/dist/global-files/data.js +16 -3
- package/dist/global-files/digital-assets/assetTypes.js +480 -0
- package/dist/global-files/digital-assets/assets.actions.js +64 -0
- package/dist/global-files/digital-assets/assets.adapter.js +304 -0
- package/dist/global-files/digital-assets/assets.mock.js +136 -0
- package/dist/global-files/digital-assets/assets.reducer.js +220 -0
- package/dist/global-files/digital-assets/uploadAssets.js +179 -0
- package/dist/global-files/digital-assets/useDigitalAssets.js +522 -0
- package/dist/index.js +142 -90
- package/docs/activos-digitales.md +273 -0
- package/package.json +1 -1
- package/src/components/atoms/AssetPreview/AssetPreview.stories.js +78 -0
- package/src/components/atoms/AssetPreview/index.js +119 -0
- package/src/components/atoms/AssetPreview/styles.js +190 -0
- package/src/components/atoms/IconFile/index.js +28 -1
- package/src/components/atoms/IconFile/styles.js +8 -0
- package/src/components/atoms/ProductImage/ProductImage.stories.js +38 -0
- package/src/components/atoms/ProductImage/index.js +95 -20
- package/src/components/atoms/ProductImage/styles.js +92 -7
- package/src/components/molecules/EditionActiveImage/index.js +2 -2
- package/src/components/molecules/GalleryAssetElement/GalleryAssetElement.stories.js +102 -0
- package/src/components/molecules/GalleryAssetElement/index.js +162 -0
- package/src/components/molecules/GalleryAssetElement/styles.js +159 -0
- package/src/components/molecules/GalleryElement/index.js +9 -0
- package/src/components/molecules/GalleryHeader/index.js +65 -13
- package/src/components/molecules/GalleryHeader/styles.js +2 -1
- package/src/components/molecules/GalleryTrayTabs/GalleryTrayTabs.stories.js +32 -0
- package/src/components/molecules/GalleryTrayTabs/index.js +41 -0
- package/src/components/molecules/GalleryTrayTabs/styles.js +52 -0
- package/src/components/molecules/ImageSelector/index.js +1 -0
- package/src/components/organisms/DigitalAssetsTray/DigitalAssetsTray.stories.js +101 -0
- package/src/components/organisms/DigitalAssetsTray/index.js +189 -0
- package/src/components/organisms/DigitalAssetsTray/styles.js +183 -0
- package/src/components/organisms/ImagePreviewer/index.js +1 -0
- package/src/components/organisms/ProductImageModal/index.js +1 -0
- package/src/components/organisms/PropagateChangesModal/index.js +11 -1
- package/src/components/organisms/PropagateChangesModal/non-propagable.js +29 -0
- package/src/components/organisms/VariantGroupModal/index.js +235 -38
- package/src/components/organisms/VariantGroupModal/styles.js +148 -1
- package/src/components/pages/ProviderProductEdition/index.js +10 -1
- package/src/components/pages/RetailerProductEdition/context/provider-product-edition.context.jsx +201 -0
- package/src/components/pages/RetailerProductEdition/context/provider-product-edition.reducer.js +12 -0
- package/src/components/pages/RetailerProductEdition/index.js +366 -140
- package/src/components/pages/RetailerProductEdition/stories/ImageEditor.stories.js +1 -0
- package/src/components/pages/RetailerProductEdition/stories/TextEditor.stories.js +51 -104
- package/src/global-files/data.js +17 -2
- package/src/global-files/digital-assets/assetTypes.js +419 -0
- package/src/global-files/digital-assets/assets.actions.js +50 -0
- package/src/global-files/digital-assets/assets.adapter.js +229 -0
- package/src/global-files/digital-assets/assets.mock.js +122 -0
- package/src/global-files/digital-assets/assets.reducer.js +197 -0
- package/src/global-files/digital-assets/uploadAssets.js +109 -0
- package/src/global-files/digital-assets/useDigitalAssets.js +329 -0
- package/src/index.js +4 -0
|
@@ -7,9 +7,12 @@ import {
|
|
|
7
7
|
Panel,
|
|
8
8
|
Header,
|
|
9
9
|
Body,
|
|
10
|
+
Step,
|
|
10
11
|
SearchRow,
|
|
11
12
|
List,
|
|
13
|
+
GhostButton,
|
|
12
14
|
NewGroup,
|
|
15
|
+
Candidates,
|
|
13
16
|
Message,
|
|
14
17
|
Footer,
|
|
15
18
|
} from "./styles";
|
|
@@ -23,9 +26,18 @@ import {
|
|
|
23
26
|
un código en `article.variant_group` y ya. Por eso el modal no pregunta qué
|
|
24
27
|
heredar: no hay nada que heredar.
|
|
25
28
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
+
El modal tiene dos pasos y el segundo depende del primero:
|
|
30
|
+
|
|
31
|
+
1. QUÉ GRUPO. Se elige uno de los que ya existen, o se crea uno nuevo. El
|
|
32
|
+
campo para crearlo está escondido detrás de un botón a propósito: abrirlo
|
|
33
|
+
de entrada le contesta al usuario una pregunta que todavía no se ha
|
|
34
|
+
hecho —si quiere buscar o crear— y empuja a teclear un código que
|
|
35
|
+
probablemente ya existe.
|
|
36
|
+
|
|
37
|
+
2. QUÉ PRODUCTOS. Aparece solo cuando ya hay un grupo. Permite sumar varios
|
|
38
|
+
de una vez sin salir de aquí; antes había que volver a la tabla, entrar a
|
|
39
|
+
cada producto y repetir el trámite, que para un grupo de cinco eran cinco
|
|
40
|
+
vueltas completas.
|
|
29
41
|
|
|
30
42
|
No hay tabla de grupos: un grupo existe mientras algún producto use su código.
|
|
31
43
|
Eso hace que una errata no falle, solo cree un grupo de un miembro — de ahí que
|
|
@@ -57,6 +69,15 @@ export const VariantGroupModal = ({
|
|
|
57
69
|
// Qué eligió el usuario: un código de la lista, o el que está escribiendo.
|
|
58
70
|
const [selected, setSelected] = useState(null);
|
|
59
71
|
const [nuevo, setNuevo] = useState("");
|
|
72
|
+
// El creador arranca cerrado: que esté abierto ya es una sugerencia de crear.
|
|
73
|
+
const [creando, setCreando] = useState(false);
|
|
74
|
+
|
|
75
|
+
// Paso 2: productos que se van a sumar junto con el de origen.
|
|
76
|
+
const [busquedaProd, setBusquedaProd] = useState("");
|
|
77
|
+
const [candidatos, setCandidatos] = useState([]);
|
|
78
|
+
const [totalCandidatos, setTotalCandidatos] = useState(0);
|
|
79
|
+
const [cargandoProd, setCargandoProd] = useState(false);
|
|
80
|
+
const [elegidos, setElegidos] = useState([]);
|
|
60
81
|
|
|
61
82
|
const request = useCallback(
|
|
62
83
|
async (action, body) => {
|
|
@@ -69,6 +90,11 @@ export const VariantGroupModal = ({
|
|
|
69
90
|
[endpoint, token]
|
|
70
91
|
);
|
|
71
92
|
|
|
93
|
+
const mensajeDe = (err, porDefecto) =>
|
|
94
|
+
err instanceof ApiError || err?.name === "ApiError"
|
|
95
|
+
? err.message
|
|
96
|
+
: porDefecto;
|
|
97
|
+
|
|
72
98
|
const buscar = useCallback(
|
|
73
99
|
async (texto) => {
|
|
74
100
|
setLoading(true);
|
|
@@ -77,11 +103,7 @@ export const VariantGroupModal = ({
|
|
|
77
103
|
const data = await request("groups", { articleId, query: texto ?? "" });
|
|
78
104
|
setGroups(data.groups ?? []);
|
|
79
105
|
} catch (err) {
|
|
80
|
-
setError(
|
|
81
|
-
err instanceof ApiError || err?.name === "ApiError"
|
|
82
|
-
? err.message
|
|
83
|
-
: "No se pudieron cargar los grupos."
|
|
84
|
-
);
|
|
106
|
+
setError(mensajeDe(err, "No se pudieron cargar los grupos."));
|
|
85
107
|
} finally {
|
|
86
108
|
setLoading(false);
|
|
87
109
|
}
|
|
@@ -108,34 +130,95 @@ export const VariantGroupModal = ({
|
|
|
108
130
|
const codigoNuevo = normalizar(nuevo);
|
|
109
131
|
const codigoFinal = selected ?? codigoNuevo;
|
|
110
132
|
const esNuevo = !selected && !!codigoNuevo;
|
|
111
|
-
const yaEstaba = codigoFinal && codigoFinal === currentGroup;
|
|
133
|
+
const yaEstaba = !!codigoFinal && codigoFinal === currentGroup;
|
|
134
|
+
|
|
135
|
+
const buscarProductos = useCallback(
|
|
136
|
+
async (texto) => {
|
|
137
|
+
setCargandoProd(true);
|
|
138
|
+
setError(null);
|
|
139
|
+
try {
|
|
140
|
+
const data = await request("group-candidates", {
|
|
141
|
+
articleId,
|
|
142
|
+
code: codigoFinal || "",
|
|
143
|
+
query: texto ?? "",
|
|
144
|
+
});
|
|
145
|
+
setCandidatos(data.candidates ?? []);
|
|
146
|
+
setTotalCandidatos(Number(data.total ?? 0));
|
|
147
|
+
} catch (err) {
|
|
148
|
+
setError(mensajeDe(err, "No se pudieron cargar los productos."));
|
|
149
|
+
} finally {
|
|
150
|
+
setCargandoProd(false);
|
|
151
|
+
}
|
|
152
|
+
},
|
|
153
|
+
[request, articleId, codigoFinal]
|
|
154
|
+
);
|
|
155
|
+
|
|
156
|
+
/*
|
|
157
|
+
La lista se recarga cuando cambia el código porque el servidor excluye a los
|
|
158
|
+
que ya están dentro: con otro grupo, el conjunto de candidatos es otro. Se
|
|
159
|
+
espera un momento porque el código nuevo se teclea letra por letra y sin la
|
|
160
|
+
pausa saldría una consulta por pulsación.
|
|
161
|
+
*/
|
|
162
|
+
useEffect(() => {
|
|
163
|
+
if (!codigoFinal) {
|
|
164
|
+
setCandidatos([]);
|
|
165
|
+
setTotalCandidatos(0);
|
|
166
|
+
return undefined;
|
|
167
|
+
}
|
|
168
|
+
const t = setTimeout(() => buscarProductos(busquedaProd), 300);
|
|
169
|
+
return () => clearTimeout(t);
|
|
170
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
171
|
+
}, [codigoFinal]);
|
|
172
|
+
|
|
173
|
+
const alternar = (id) =>
|
|
174
|
+
setElegidos((previos) =>
|
|
175
|
+
previos.includes(id)
|
|
176
|
+
? previos.filter((x) => x !== id)
|
|
177
|
+
: [...previos, id]
|
|
178
|
+
);
|
|
112
179
|
|
|
113
180
|
const guardar = async () => {
|
|
114
|
-
if (!codigoFinal
|
|
181
|
+
if (!codigoFinal) return;
|
|
182
|
+
// Sin código nuevo y sin productos que sumar no hay nada que hacer.
|
|
183
|
+
if (yaEstaba && elegidos.length === 0) return;
|
|
115
184
|
|
|
116
185
|
setSaving(true);
|
|
117
186
|
setError(null);
|
|
118
187
|
try {
|
|
119
|
-
const data = await request("join", {
|
|
188
|
+
const data = await request("join", {
|
|
189
|
+
articleId,
|
|
190
|
+
code: codigoFinal,
|
|
191
|
+
alsoArticleIds: elegidos,
|
|
192
|
+
});
|
|
120
193
|
onSaved?.({ ...data, group: data.group ?? codigoFinal });
|
|
121
194
|
onClose?.();
|
|
122
195
|
} catch (err) {
|
|
123
|
-
setError(
|
|
124
|
-
err instanceof ApiError || err?.name === "ApiError"
|
|
125
|
-
? err.message
|
|
126
|
-
: "No se pudo agrupar el producto. Intenta de nuevo."
|
|
127
|
-
);
|
|
196
|
+
setError(mensajeDe(err, "No se pudo agrupar el producto. Intenta de nuevo."));
|
|
128
197
|
setSaving(false);
|
|
129
198
|
}
|
|
130
199
|
};
|
|
131
200
|
|
|
201
|
+
// Cuántos de los elegidos cambian de grupo, para avisarlo antes y no después.
|
|
202
|
+
const seMueven = candidatos.filter(
|
|
203
|
+
(c) => elegidos.includes(c.articleId) && c.currentGroup && c.currentGroup !== codigoFinal
|
|
204
|
+
).length;
|
|
205
|
+
|
|
132
206
|
const resumen = () => {
|
|
133
|
-
if (
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
207
|
+
if (!codigoFinal) return "Elige un grupo o crea uno nuevo.";
|
|
208
|
+
|
|
209
|
+
const sufijo = elegidos.length
|
|
210
|
+
? ` y ${elegidos.length} producto${elegidos.length === 1 ? "" : "s"} más`
|
|
211
|
+
: "";
|
|
212
|
+
|
|
213
|
+
if (yaEstaba && !elegidos.length) return `Este producto ya está en «${codigoFinal}».`;
|
|
214
|
+
if (yaEstaba) return `Se agregarán ${elegidos.length} producto${elegidos.length === 1 ? "" : "s"} a «${codigoFinal}».`;
|
|
215
|
+
if (esNuevo) return `Se creará «${codigoNuevo}» con este producto${sufijo}.`;
|
|
216
|
+
return `Se agregará a «${selected}»${sufijo}.`;
|
|
137
217
|
};
|
|
138
218
|
|
|
219
|
+
const puedeGuardar =
|
|
220
|
+
!saving && !!codigoFinal && (!yaEstaba || elegidos.length > 0);
|
|
221
|
+
|
|
139
222
|
return (
|
|
140
223
|
<Overlay onClick={saving ? undefined : onClose}>
|
|
141
224
|
<Panel onClick={(event) => event.stopPropagation()}>
|
|
@@ -151,6 +234,10 @@ export const VariantGroupModal = ({
|
|
|
151
234
|
<Body>
|
|
152
235
|
{error && <Message className="error">{error}</Message>}
|
|
153
236
|
|
|
237
|
+
<Step>
|
|
238
|
+
<h3>1 · Elige el grupo</h3>
|
|
239
|
+
</Step>
|
|
240
|
+
|
|
154
241
|
<SearchRow
|
|
155
242
|
onSubmit={(event) => {
|
|
156
243
|
event.preventDefault();
|
|
@@ -180,6 +267,7 @@ export const VariantGroupModal = ({
|
|
|
180
267
|
onChange={() => {
|
|
181
268
|
setSelected(grupo.code);
|
|
182
269
|
setNuevo("");
|
|
270
|
+
setCreando(false);
|
|
183
271
|
}}
|
|
184
272
|
/>
|
|
185
273
|
<span className="codigo">{grupo.code}</span>
|
|
@@ -201,28 +289,137 @@ export const VariantGroupModal = ({
|
|
|
201
289
|
<Message>
|
|
202
290
|
{query.trim()
|
|
203
291
|
? "Ningún grupo coincide con esa búsqueda."
|
|
204
|
-
: "Todavía no hay grupos en esta compañía.
|
|
292
|
+
: "Todavía no hay grupos en esta compañía."}
|
|
205
293
|
</Message>
|
|
206
294
|
)}
|
|
207
295
|
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
296
|
+
{/*
|
|
297
|
+
El campo para crear vive detrás de este botón. Mostrarlo de entrada
|
|
298
|
+
daba por hecho que el usuario venía a crear, cuando la mayoría de las
|
|
299
|
+
veces lo que quiere es sumarse a un grupo que ya existe.
|
|
300
|
+
*/}
|
|
301
|
+
{!creando ? (
|
|
302
|
+
<GhostButton
|
|
303
|
+
type="button"
|
|
304
|
+
onClick={() => {
|
|
305
|
+
setCreando(true);
|
|
218
306
|
setSelected(null);
|
|
219
307
|
}}
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
308
|
+
>
|
|
309
|
+
+ Crear un grupo nuevo
|
|
310
|
+
</GhostButton>
|
|
311
|
+
) : (
|
|
312
|
+
<NewGroup>
|
|
313
|
+
<div className="encabezado">
|
|
314
|
+
<label htmlFor="nuevo-grupo">Código del grupo nuevo</label>
|
|
315
|
+
<button
|
|
316
|
+
type="button"
|
|
317
|
+
onClick={() => {
|
|
318
|
+
setCreando(false);
|
|
319
|
+
setNuevo("");
|
|
320
|
+
}}
|
|
321
|
+
>
|
|
322
|
+
Cancelar
|
|
323
|
+
</button>
|
|
324
|
+
</div>
|
|
325
|
+
<input
|
|
326
|
+
id="nuevo-grupo"
|
|
327
|
+
type="text"
|
|
328
|
+
value={nuevo}
|
|
329
|
+
placeholder="RELOJ-CASIO-A158"
|
|
330
|
+
maxLength={50}
|
|
331
|
+
autoFocus
|
|
332
|
+
onChange={(event) => {
|
|
333
|
+
setNuevo(event.target.value);
|
|
334
|
+
setSelected(null);
|
|
335
|
+
}}
|
|
336
|
+
/>
|
|
337
|
+
<span className="ayuda">
|
|
338
|
+
Letras, números, guion y guion bajo. Se guarda en mayúsculas, así
|
|
339
|
+
que «abc-1» y «ABC-1» son el mismo grupo.
|
|
340
|
+
</span>
|
|
341
|
+
</NewGroup>
|
|
342
|
+
)}
|
|
343
|
+
|
|
344
|
+
{/*
|
|
345
|
+
Paso 2. Solo tiene sentido con un grupo ya elegido: sin código no hay
|
|
346
|
+
a dónde sumar, y el servidor necesita el código para excluir a los
|
|
347
|
+
que ya están dentro.
|
|
348
|
+
*/}
|
|
349
|
+
{!!codigoFinal && (
|
|
350
|
+
<>
|
|
351
|
+
<Step className="segundo">
|
|
352
|
+
<h3>2 · Agrega más productos a «{codigoFinal}»</h3>
|
|
353
|
+
<p>Opcional. Evita entrar a cada producto por separado.</p>
|
|
354
|
+
</Step>
|
|
355
|
+
|
|
356
|
+
<SearchRow
|
|
357
|
+
onSubmit={(event) => {
|
|
358
|
+
event.preventDefault();
|
|
359
|
+
buscarProductos(busquedaProd);
|
|
360
|
+
}}
|
|
361
|
+
>
|
|
362
|
+
<input
|
|
363
|
+
type="text"
|
|
364
|
+
value={busquedaProd}
|
|
365
|
+
placeholder="Busca por UPC, SKU o nombre"
|
|
366
|
+
onChange={(event) => setBusquedaProd(event.target.value)}
|
|
367
|
+
/>
|
|
368
|
+
<button type="submit" disabled={cargandoProd}>
|
|
369
|
+
{cargandoProd ? "Buscando…" : "Buscar"}
|
|
370
|
+
</button>
|
|
371
|
+
</SearchRow>
|
|
372
|
+
|
|
373
|
+
{candidatos.length > 0 && (
|
|
374
|
+
<Candidates>
|
|
375
|
+
{candidatos.map((c) => (
|
|
376
|
+
<li key={c.articleId}>
|
|
377
|
+
<label>
|
|
378
|
+
<input
|
|
379
|
+
type="checkbox"
|
|
380
|
+
checked={elegidos.includes(c.articleId)}
|
|
381
|
+
onChange={() => alternar(c.articleId)}
|
|
382
|
+
/>
|
|
383
|
+
<span className="datos">
|
|
384
|
+
<span className="nombre">{c.name}</span>
|
|
385
|
+
<span className="ids">
|
|
386
|
+
{c.upc ? `UPC ${c.upc}` : ""}
|
|
387
|
+
{c.upc && c.sku ? " · " : ""}
|
|
388
|
+
{c.sku ? `SKU ${c.sku}` : ""}
|
|
389
|
+
</span>
|
|
390
|
+
</span>
|
|
391
|
+
{c.currentGroup && c.currentGroup !== codigoFinal && (
|
|
392
|
+
<span className="mueve">hoy en «{c.currentGroup}»</span>
|
|
393
|
+
)}
|
|
394
|
+
</label>
|
|
395
|
+
</li>
|
|
396
|
+
))}
|
|
397
|
+
</Candidates>
|
|
398
|
+
)}
|
|
399
|
+
|
|
400
|
+
{!cargandoProd && candidatos.length === 0 && (
|
|
401
|
+
<Message>
|
|
402
|
+
{busquedaProd.trim()
|
|
403
|
+
? "Ningún producto coincide con esa búsqueda."
|
|
404
|
+
: "No hay otros productos que puedas agrupar aquí."}
|
|
405
|
+
</Message>
|
|
406
|
+
)}
|
|
407
|
+
|
|
408
|
+
{totalCandidatos > candidatos.length && (
|
|
409
|
+
<Message className="sutil">
|
|
410
|
+
Se muestran {candidatos.length} de {totalCandidatos}. Afina la
|
|
411
|
+
búsqueda para ver el resto.
|
|
412
|
+
</Message>
|
|
413
|
+
)}
|
|
414
|
+
|
|
415
|
+
{seMueven > 0 && (
|
|
416
|
+
<Message className="aviso">
|
|
417
|
+
{seMueven} de los productos elegidos ya está en otro grupo y se
|
|
418
|
+
moverá a «{codigoFinal}». Un producto solo puede estar en uno.
|
|
419
|
+
</Message>
|
|
420
|
+
)}
|
|
421
|
+
</>
|
|
422
|
+
)}
|
|
226
423
|
</Body>
|
|
227
424
|
|
|
228
425
|
<Footer>
|
|
@@ -240,7 +437,7 @@ export const VariantGroupModal = ({
|
|
|
240
437
|
type="button"
|
|
241
438
|
className="primary"
|
|
242
439
|
onClick={guardar}
|
|
243
|
-
disabled={
|
|
440
|
+
disabled={!puedeGuardar}
|
|
244
441
|
>
|
|
245
442
|
{saving ? "Guardando…" : "Aceptar"}
|
|
246
443
|
</button>
|
|
@@ -159,9 +159,30 @@ export const NewGroup = styled.div`
|
|
|
159
159
|
border-radius: 6px;
|
|
160
160
|
background-color: rgba(${PINK_RGB}, 0.04);
|
|
161
161
|
|
|
162
|
+
.encabezado {
|
|
163
|
+
display: flex;
|
|
164
|
+
align-items: baseline;
|
|
165
|
+
justify-content: space-between;
|
|
166
|
+
gap: 12px;
|
|
167
|
+
margin-bottom: 6px;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/* Salir de aqui tiene que costar lo mismo que entrar: si abrir el creador es
|
|
171
|
+
un clic, cerrarlo tambien. Sin esto el usuario queda atrapado en el paso de
|
|
172
|
+
crear y la unica salida visible es cerrar el modal entero. */
|
|
173
|
+
.encabezado button {
|
|
174
|
+
border: none;
|
|
175
|
+
background: none;
|
|
176
|
+
padding: 0;
|
|
177
|
+
font-family: ${FontFamily.Raleway_600};
|
|
178
|
+
font-size: 11px;
|
|
179
|
+
color: ${GlobalColors.gray};
|
|
180
|
+
cursor: pointer;
|
|
181
|
+
text-decoration: underline;
|
|
182
|
+
}
|
|
183
|
+
|
|
162
184
|
label {
|
|
163
185
|
display: block;
|
|
164
|
-
margin-bottom: 6px;
|
|
165
186
|
font-family: ${FontFamily.Raleway_600};
|
|
166
187
|
font-size: 12px;
|
|
167
188
|
color: ${GlobalColors.deep_gray};
|
|
@@ -193,6 +214,117 @@ export const NewGroup = styled.div`
|
|
|
193
214
|
}
|
|
194
215
|
`;
|
|
195
216
|
|
|
217
|
+
export const Step = styled.div`
|
|
218
|
+
margin: 4px 0 10px;
|
|
219
|
+
|
|
220
|
+
h3 {
|
|
221
|
+
margin: 0;
|
|
222
|
+
font-family: ${FontFamily.Raleway_700};
|
|
223
|
+
font-size: 13px;
|
|
224
|
+
color: ${GlobalColors.deep_gray};
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
p {
|
|
228
|
+
margin: 2px 0 0;
|
|
229
|
+
font-size: 11px;
|
|
230
|
+
color: ${GlobalColors.gray};
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/* El segundo paso aparece y desaparece segun haya grupo elegido; la linea de
|
|
234
|
+
arriba es lo que evita que se lea como continuacion del bloque anterior. */
|
|
235
|
+
&.segundo {
|
|
236
|
+
margin-top: 18px;
|
|
237
|
+
padding-top: 14px;
|
|
238
|
+
border-top: 1px solid ${GlobalColors.s2};
|
|
239
|
+
}
|
|
240
|
+
`;
|
|
241
|
+
|
|
242
|
+
/*
|
|
243
|
+
El boton que revela el campo de codigo nuevo.
|
|
244
|
+
|
|
245
|
+
Va deliberadamente discreto: es la salida menos frecuente —lo normal es
|
|
246
|
+
sumarse a un grupo que ya existe— y competir en peso con "Buscar" volveria a
|
|
247
|
+
empujar hacia crear, que es justo lo que se queria dejar de hacer.
|
|
248
|
+
*/
|
|
249
|
+
export const GhostButton = styled.button`
|
|
250
|
+
display: block;
|
|
251
|
+
width: 100%;
|
|
252
|
+
height: 36px;
|
|
253
|
+
border: 1px dashed rgba(${PINK_RGB}, 0.5);
|
|
254
|
+
border-radius: 6px;
|
|
255
|
+
background-color: rgba(${PINK_RGB}, 0.04);
|
|
256
|
+
font-family: ${FontFamily.Raleway_600};
|
|
257
|
+
font-size: 12px;
|
|
258
|
+
color: ${PINK};
|
|
259
|
+
cursor: pointer;
|
|
260
|
+
|
|
261
|
+
&:hover {
|
|
262
|
+
background-color: rgba(${PINK_RGB}, 0.09);
|
|
263
|
+
}
|
|
264
|
+
`;
|
|
265
|
+
|
|
266
|
+
export const Candidates = styled.ul`
|
|
267
|
+
margin: 0 0 10px;
|
|
268
|
+
padding: 0;
|
|
269
|
+
list-style: none;
|
|
270
|
+
border: 1px solid ${GlobalColors.s2};
|
|
271
|
+
border-radius: 6px;
|
|
272
|
+
/* Acotado a proposito: la lista vive dentro de un modal que ya scrollea, y sin
|
|
273
|
+
tope empuja el pie fuera de la vista. */
|
|
274
|
+
max-height: 210px;
|
|
275
|
+
overflow-y: auto;
|
|
276
|
+
|
|
277
|
+
li + li {
|
|
278
|
+
border-top: 1px solid ${GlobalColors.s2};
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
label {
|
|
282
|
+
display: flex;
|
|
283
|
+
align-items: center;
|
|
284
|
+
gap: 10px;
|
|
285
|
+
padding: 8px 12px;
|
|
286
|
+
cursor: pointer;
|
|
287
|
+
font-size: 13px;
|
|
288
|
+
color: ${GlobalColors.deep_gray};
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
label:hover {
|
|
292
|
+
background-color: ${GlobalColors.s1};
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
input[type="checkbox"] {
|
|
296
|
+
accent-color: ${PINK};
|
|
297
|
+
flex-shrink: 0;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
.datos {
|
|
301
|
+
display: flex;
|
|
302
|
+
flex-direction: column;
|
|
303
|
+
min-width: 0;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
.nombre {
|
|
307
|
+
font-family: ${FontFamily.Raleway_600};
|
|
308
|
+
overflow: hidden;
|
|
309
|
+
text-overflow: ellipsis;
|
|
310
|
+
white-space: nowrap;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
.ids {
|
|
314
|
+
font-size: 11px;
|
|
315
|
+
color: ${GlobalColors.gray};
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
.mueve {
|
|
319
|
+
margin-left: auto;
|
|
320
|
+
flex-shrink: 0;
|
|
321
|
+
font-size: 11px;
|
|
322
|
+
font-style: italic;
|
|
323
|
+
color: ${PINK};
|
|
324
|
+
white-space: nowrap;
|
|
325
|
+
}
|
|
326
|
+
`;
|
|
327
|
+
|
|
196
328
|
export const Message = styled.p`
|
|
197
329
|
margin: 0 0 12px;
|
|
198
330
|
padding: 10px 12px;
|
|
@@ -206,6 +338,21 @@ export const Message = styled.p`
|
|
|
206
338
|
background-color: rgba(${PINK_RGB}, 0.1);
|
|
207
339
|
color: ${PINK};
|
|
208
340
|
}
|
|
341
|
+
|
|
342
|
+
/* Informativo, no accionable: no debe competir con el contenido. */
|
|
343
|
+
&.sutil {
|
|
344
|
+
background: none;
|
|
345
|
+
padding: 2px 0 10px;
|
|
346
|
+
font-size: 11px;
|
|
347
|
+
color: ${GlobalColors.gray};
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
/* Agrupar es un UPDATE, asi que sumar un producto que ya tenia grupo lo MUEVE.
|
|
351
|
+
Se avisa antes de guardar, no despues. */
|
|
352
|
+
&.aviso {
|
|
353
|
+
background-color: rgba(${PINK_RGB}, 0.08);
|
|
354
|
+
color: ${GlobalColors.deep_gray};
|
|
355
|
+
}
|
|
209
356
|
`;
|
|
210
357
|
|
|
211
358
|
export const Footer = styled.div`
|
|
@@ -42,6 +42,7 @@ import { LinkVariantsModal } from "../../organisms/LinkVariantsModal";
|
|
|
42
42
|
import { UnlinkVariantModal } from "../../organisms/UnlinkVariantModal";
|
|
43
43
|
import { VariantGroupModal } from "../../organisms/VariantGroupModal";
|
|
44
44
|
import { PropagateChangesModal } from "../../organisms/PropagateChangesModal";
|
|
45
|
+
import { soloPropagables } from "../../organisms/PropagateChangesModal/non-propagable";
|
|
45
46
|
import { readPayload } from "../../organisms/LinkVariantsModal/api";
|
|
46
47
|
import { ProductImageModal } from "../../organisms/ProductImageModal";
|
|
47
48
|
import { VersionSelector } from "../../organisms/VersionSelector";
|
|
@@ -712,7 +713,7 @@ const ProviderProductEditionView = ({
|
|
|
712
713
|
const retailerId = state.active_retailer?.id_retailer;
|
|
713
714
|
const inputs = state.services?.datasheets?.inputs ?? {};
|
|
714
715
|
|
|
715
|
-
|
|
716
|
+
const cambios = (state.updated_datasheets_inputs ?? []).map((item) => {
|
|
716
717
|
const input = inputs[`${item.attributeId}_${retailerId}`];
|
|
717
718
|
return {
|
|
718
719
|
id: Number(item.attributeId),
|
|
@@ -720,6 +721,14 @@ const ProviderProductEditionView = ({
|
|
|
720
721
|
value: item.value,
|
|
721
722
|
};
|
|
722
723
|
});
|
|
724
|
+
|
|
725
|
+
/*
|
|
726
|
+
UPC y SKU se quitan aquí y no dentro del diálogo porque esta lista también
|
|
727
|
+
decide SI el diálogo se abre. Editar solo el UPC no tiene nada que
|
|
728
|
+
propagar, así que preguntarlo sería pedirle al usuario una decisión sobre
|
|
729
|
+
un conjunto vacío.
|
|
730
|
+
*/
|
|
731
|
+
return soloPropagables(cambios);
|
|
723
732
|
};
|
|
724
733
|
|
|
725
734
|
const handleOnClickSave = () => {
|