contentoh-components-library 21.6.18 → 21.6.20
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/.env.test +2 -1
- package/dist/components/molecules/TagAndInput/index.js +14 -1
- package/dist/components/organisms/InputGroup/index.js +23 -17
- package/dist/components/organisms/LinkVariantsModal/api.js +102 -0
- package/dist/components/organisms/LinkVariantsModal/index.js +617 -0
- package/dist/components/organisms/LinkVariantsModal/styles.js +60 -0
- package/dist/components/pages/ProductEditionv2/index.js +3 -0
- package/dist/components/pages/ProviderProductEdition/context/reducers/inputs.js +8 -2
- package/dist/components/pages/ProviderProductEdition/index.js +201 -91
- package/dist/components/pages/ProviderProductEdition/styles.js +2 -2
- package/dist/components/pages/ProviderProductEdition/utils.js +8 -0
- package/dist/index.js +25 -12
- package/package.json +1 -1
- package/src/components/molecules/TagAndInput/index.js +15 -0
- package/src/components/organisms/InputGroup/index.js +6 -0
- package/src/components/organisms/LinkVariantsModal/api.js +61 -0
- package/src/components/organisms/LinkVariantsModal/index.js +474 -0
- package/src/components/organisms/LinkVariantsModal/styles.js +264 -0
- package/src/components/pages/ProductEditionv2/index.js +1 -0
- package/src/components/pages/ProviderProductEdition/context/reducers/inputs.js +21 -13
- package/src/components/pages/ProviderProductEdition/index.js +77 -0
- package/src/components/pages/ProviderProductEdition/styles.js +23 -0
- package/src/components/pages/ProviderProductEdition/utils.js +8 -0
- package/src/index.js +1 -0
- package/dist/ai/prompts/attribute.prompt.js +0 -28
- package/dist/ai/prompts/description.prompt.js +0 -33
- package/dist/ai/schemas/attribute.schema.js +0 -35
- package/dist/ai/schemas/description.schema.js +0 -27
|
@@ -0,0 +1,474 @@
|
|
|
1
|
+
import { useCallback, useEffect, useMemo, useState } from "react";
|
|
2
|
+
import axios from "axios";
|
|
3
|
+
|
|
4
|
+
import { readPayload, ApiError } from "./api";
|
|
5
|
+
|
|
6
|
+
import {
|
|
7
|
+
Overlay,
|
|
8
|
+
Panel,
|
|
9
|
+
Header,
|
|
10
|
+
ModeSwitch,
|
|
11
|
+
Body,
|
|
12
|
+
SearchRow,
|
|
13
|
+
List,
|
|
14
|
+
AttributeList,
|
|
15
|
+
Section,
|
|
16
|
+
Message,
|
|
17
|
+
Footer,
|
|
18
|
+
} from "./styles";
|
|
19
|
+
|
|
20
|
+
/*
|
|
21
|
+
Modal para ligar productos como padre/variante.
|
|
22
|
+
|
|
23
|
+
props (* requerido):
|
|
24
|
+
- *articleId: producto desde el que se liga (el que está en edición)
|
|
25
|
+
- articleName / articleUpc: para que el usuario sepa sobre qué está trabajando
|
|
26
|
+
- *token: JWT que va en el header Authorization
|
|
27
|
+
- endpoint: base del API de variantes. Por defecto REACT_APP_VARIANTS_ENDPOINT
|
|
28
|
+
- *onClose: cerrar sin cambios
|
|
29
|
+
- onLinked: se llama con la respuesta del backend cuando la liga tuvo éxito
|
|
30
|
+
|
|
31
|
+
Los dos modos son los de POST /variants/link:
|
|
32
|
+
parent → este producto pasa a ser variante del que se elija
|
|
33
|
+
children → los elegidos pasan a ser variantes de este producto
|
|
34
|
+
|
|
35
|
+
En ambos el padre baja al hijo, nunca al revés. Lo que cambia es quién es quién.
|
|
36
|
+
*/
|
|
37
|
+
|
|
38
|
+
// El backend devuelve códigos estables justamente para no mandar textos de UI
|
|
39
|
+
// desde el servidor. La traducción a lenguaje humano vive aquí.
|
|
40
|
+
const ERROR_TEXT = {
|
|
41
|
+
VARIANT_SELF_LINK: "Un producto no puede ser variante de sí mismo.",
|
|
42
|
+
VARIANT_PARENT_IS_CHILD:
|
|
43
|
+
"Ese producto ya es variante de otro. Las familias son de un solo nivel.",
|
|
44
|
+
VARIANT_CHILD_IS_PARENT:
|
|
45
|
+
"Ese producto ya tiene variantes propias, así que no puede colgar de otro.",
|
|
46
|
+
VARIANT_ALREADY_LINKED:
|
|
47
|
+
"Ese producto ya pertenece a otra familia. Desvincúlalo primero.",
|
|
48
|
+
VARIANT_NOT_OWNED_BY_USER: "Ese producto está fuera de tu alcance.",
|
|
49
|
+
VARIANT_CROSS_COMPANY: "Ese producto pertenece a otra compañía.",
|
|
50
|
+
VARIANT_INACTIVE_ARTICLE: "Ese producto está desactivado.",
|
|
51
|
+
VARIANT_NOT_FOUND: "No encontramos ese producto.",
|
|
52
|
+
VARIANT_PLAN_LIMIT_REACHED:
|
|
53
|
+
"Tu plan no tiene cupo disponible para más productos.",
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
const readError = (error) => {
|
|
57
|
+
if (error instanceof ApiError || error?.name === "ApiError") {
|
|
58
|
+
return ERROR_TEXT[error.code] || error.message;
|
|
59
|
+
}
|
|
60
|
+
// Falla de transporte de verdad: red caída, DNS, CORS.
|
|
61
|
+
return "No se pudo conectar con el servidor.";
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
export const LinkVariantsModal = ({
|
|
65
|
+
articleId,
|
|
66
|
+
articleName,
|
|
67
|
+
articleUpc,
|
|
68
|
+
activeRetailerId,
|
|
69
|
+
token,
|
|
70
|
+
endpoint = process.env.REACT_APP_VARIANTS_ENDPOINT,
|
|
71
|
+
onClose,
|
|
72
|
+
onLinked,
|
|
73
|
+
}) => {
|
|
74
|
+
const [mode, setMode] = useState("parent");
|
|
75
|
+
const [step, setStep] = useState("search");
|
|
76
|
+
|
|
77
|
+
const [query, setQuery] = useState("");
|
|
78
|
+
const [candidates, setCandidates] = useState([]);
|
|
79
|
+
const [rejected, setRejected] = useState(null);
|
|
80
|
+
const [searched, setSearched] = useState(false);
|
|
81
|
+
const [selected, setSelected] = useState([]);
|
|
82
|
+
|
|
83
|
+
const [attributes, setAttributes] = useState([]);
|
|
84
|
+
const [nameScope, setNameScope] = useState(null);
|
|
85
|
+
const [inheritMode, setInheritMode] = useState("all-filled");
|
|
86
|
+
const [chosenAttributes, setChosenAttributes] = useState([]);
|
|
87
|
+
const [inheritImages, setInheritImages] = useState(true);
|
|
88
|
+
const [inheritDescriptions, setInheritDescriptions] = useState(true);
|
|
89
|
+
const [inheritCategoryRetailers, setInheritCategoryRetailers] = useState(true);
|
|
90
|
+
|
|
91
|
+
const [loading, setLoading] = useState(false);
|
|
92
|
+
const [error, setError] = useState(null);
|
|
93
|
+
|
|
94
|
+
const request = useCallback(
|
|
95
|
+
async (action, body) => {
|
|
96
|
+
const res = await axios.post(`${endpoint}/${action}`, body, {
|
|
97
|
+
headers: { Authorization: token },
|
|
98
|
+
});
|
|
99
|
+
// El transporte siempre responde 200; el resultado real va en el cuerpo.
|
|
100
|
+
return readPayload(res);
|
|
101
|
+
},
|
|
102
|
+
[endpoint, token]
|
|
103
|
+
);
|
|
104
|
+
|
|
105
|
+
// En modo children el padre es el producto en edición; en modo parent es el
|
|
106
|
+
// candidato que el usuario acaba de elegir. De ahí sale qué atributos ofrecer.
|
|
107
|
+
const parentArticleId = useMemo(
|
|
108
|
+
() => (mode === "children" ? articleId : selected[0]),
|
|
109
|
+
[mode, articleId, selected]
|
|
110
|
+
);
|
|
111
|
+
|
|
112
|
+
// Cambiar de dirección invalida la selección: los candidatos elegibles no son
|
|
113
|
+
// los mismos, y arrastrar la anterior llevaría a ligar algo que no se buscó.
|
|
114
|
+
useEffect(() => {
|
|
115
|
+
setCandidates([]);
|
|
116
|
+
setSelected([]);
|
|
117
|
+
setRejected(null);
|
|
118
|
+
setSearched(false);
|
|
119
|
+
setError(null);
|
|
120
|
+
setStep("search");
|
|
121
|
+
}, [mode]);
|
|
122
|
+
|
|
123
|
+
const search = async (event) => {
|
|
124
|
+
event?.preventDefault();
|
|
125
|
+
setLoading(true);
|
|
126
|
+
setError(null);
|
|
127
|
+
try {
|
|
128
|
+
const data = await request("search", {
|
|
129
|
+
mode,
|
|
130
|
+
articleId,
|
|
131
|
+
query: query.trim(),
|
|
132
|
+
});
|
|
133
|
+
setCandidates(data.candidates || []);
|
|
134
|
+
setRejected(data.rejected || null);
|
|
135
|
+
setSearched(true);
|
|
136
|
+
} catch (err) {
|
|
137
|
+
setError(readError(err));
|
|
138
|
+
} finally {
|
|
139
|
+
setLoading(false);
|
|
140
|
+
}
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
const toggle = (id) => {
|
|
144
|
+
setSelected((current) => {
|
|
145
|
+
if (mode === "parent") return current[0] === id ? [] : [id];
|
|
146
|
+
return current.includes(id)
|
|
147
|
+
? current.filter((item) => item !== id)
|
|
148
|
+
: [...current, id];
|
|
149
|
+
});
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
const goToInheritance = async () => {
|
|
153
|
+
setLoading(true);
|
|
154
|
+
setError(null);
|
|
155
|
+
try {
|
|
156
|
+
// La cadena activa decide con qué nombre se ve cada atributo: el equipo
|
|
157
|
+
// interno y los proveedores los renombran por cadena en attribute_manager,
|
|
158
|
+
// y mostrar el nombre de otra cadena confundiría al elegir.
|
|
159
|
+
const data = await request("attributes", {
|
|
160
|
+
parentArticleId,
|
|
161
|
+
retailerId: activeRetailerId,
|
|
162
|
+
});
|
|
163
|
+
setAttributes(data.attributes || []);
|
|
164
|
+
setNameScope(data.nameScope ?? null);
|
|
165
|
+
setChosenAttributes((data.attributes || []).map((item) => item.id));
|
|
166
|
+
setStep("inherit");
|
|
167
|
+
} catch (err) {
|
|
168
|
+
setError(readError(err));
|
|
169
|
+
} finally {
|
|
170
|
+
setLoading(false);
|
|
171
|
+
}
|
|
172
|
+
};
|
|
173
|
+
|
|
174
|
+
const toggleAttribute = (id) => {
|
|
175
|
+
setChosenAttributes((current) =>
|
|
176
|
+
current.includes(id)
|
|
177
|
+
? current.filter((item) => item !== id)
|
|
178
|
+
: [...current, id]
|
|
179
|
+
);
|
|
180
|
+
};
|
|
181
|
+
|
|
182
|
+
const submit = async () => {
|
|
183
|
+
setLoading(true);
|
|
184
|
+
setError(null);
|
|
185
|
+
|
|
186
|
+
const inherit = {
|
|
187
|
+
attributes: inheritMode === "all-filled" ? "all-filled" : chosenAttributes,
|
|
188
|
+
images: inheritImages,
|
|
189
|
+
descriptions: inheritDescriptions,
|
|
190
|
+
categoryRetailers: inheritCategoryRetailers,
|
|
191
|
+
};
|
|
192
|
+
|
|
193
|
+
const body =
|
|
194
|
+
mode === "parent"
|
|
195
|
+
? { mode, articleId, parentArticleId: selected[0], inherit }
|
|
196
|
+
: { mode, parentArticleId: articleId, childrenArticleIds: selected, inherit };
|
|
197
|
+
|
|
198
|
+
try {
|
|
199
|
+
const data = await request("link", body);
|
|
200
|
+
onLinked?.(data);
|
|
201
|
+
onClose?.();
|
|
202
|
+
} catch (err) {
|
|
203
|
+
setError(readError(err));
|
|
204
|
+
} finally {
|
|
205
|
+
setLoading(false);
|
|
206
|
+
}
|
|
207
|
+
};
|
|
208
|
+
|
|
209
|
+
const selectionLabel =
|
|
210
|
+
mode === "parent"
|
|
211
|
+
? "Se elige un solo producto padre."
|
|
212
|
+
: `${selected.length} producto(s) pasarán a ser variantes.`;
|
|
213
|
+
|
|
214
|
+
return (
|
|
215
|
+
<Overlay onClick={onClose}>
|
|
216
|
+
<Panel onClick={(event) => event.stopPropagation()}>
|
|
217
|
+
<Header>
|
|
218
|
+
<div>
|
|
219
|
+
<h2>Ligar productos</h2>
|
|
220
|
+
<p>
|
|
221
|
+
{articleName || `Producto ${articleId}`}
|
|
222
|
+
{articleUpc ? ` · UPC ${articleUpc}` : ""}
|
|
223
|
+
</p>
|
|
224
|
+
</div>
|
|
225
|
+
<button type="button" className="close" onClick={onClose} aria-label="Cerrar">
|
|
226
|
+
×
|
|
227
|
+
</button>
|
|
228
|
+
</Header>
|
|
229
|
+
|
|
230
|
+
{step === "search" && (
|
|
231
|
+
<ModeSwitch>
|
|
232
|
+
<button
|
|
233
|
+
type="button"
|
|
234
|
+
className={mode === "parent" ? "active" : ""}
|
|
235
|
+
onClick={() => setMode("parent")}
|
|
236
|
+
>
|
|
237
|
+
Este producto es variante de otro
|
|
238
|
+
</button>
|
|
239
|
+
<button
|
|
240
|
+
type="button"
|
|
241
|
+
className={mode === "children" ? "active" : ""}
|
|
242
|
+
onClick={() => setMode("children")}
|
|
243
|
+
>
|
|
244
|
+
Agregar variantes a este producto
|
|
245
|
+
</button>
|
|
246
|
+
</ModeSwitch>
|
|
247
|
+
)}
|
|
248
|
+
|
|
249
|
+
<Body>
|
|
250
|
+
{error && <Message className="error">{error}</Message>}
|
|
251
|
+
|
|
252
|
+
{step === "search" && (
|
|
253
|
+
<>
|
|
254
|
+
<SearchRow onSubmit={search}>
|
|
255
|
+
<input
|
|
256
|
+
type="text"
|
|
257
|
+
value={query}
|
|
258
|
+
placeholder="Busca por UPC, SKU o nombre"
|
|
259
|
+
onChange={(event) => setQuery(event.target.value)}
|
|
260
|
+
/>
|
|
261
|
+
<button type="submit" disabled={loading}>
|
|
262
|
+
{loading ? "Buscando…" : "Buscar"}
|
|
263
|
+
</button>
|
|
264
|
+
</SearchRow>
|
|
265
|
+
|
|
266
|
+
{/* El backend explica por qué un UPC existente no es ligable. Sin
|
|
267
|
+
esto el usuario ve "sin resultados" y no sabe qué hacer. */}
|
|
268
|
+
{rejected && (
|
|
269
|
+
<Message className="error">
|
|
270
|
+
{rejected.name || rejected.upc}: {ERROR_TEXT[rejected.code]}
|
|
271
|
+
</Message>
|
|
272
|
+
)}
|
|
273
|
+
|
|
274
|
+
{searched && !candidates.length && !rejected && (
|
|
275
|
+
<Message>
|
|
276
|
+
No hay productos que puedas ligar con esa búsqueda. Solo
|
|
277
|
+
aparecen productos activos de la misma compañía que todavía no
|
|
278
|
+
pertenecen a otra familia.
|
|
279
|
+
</Message>
|
|
280
|
+
)}
|
|
281
|
+
|
|
282
|
+
{candidates.length > 0 && (
|
|
283
|
+
<List>
|
|
284
|
+
{candidates.map((candidate) => (
|
|
285
|
+
<li key={candidate.articleId}>
|
|
286
|
+
<label>
|
|
287
|
+
<input
|
|
288
|
+
type={mode === "parent" ? "radio" : "checkbox"}
|
|
289
|
+
name="candidate"
|
|
290
|
+
checked={selected.includes(candidate.articleId)}
|
|
291
|
+
onChange={() => toggle(candidate.articleId)}
|
|
292
|
+
/>
|
|
293
|
+
<span>
|
|
294
|
+
{candidate.name || `Producto ${candidate.articleId}`}
|
|
295
|
+
<span className="meta">
|
|
296
|
+
UPC {candidate.upc || "—"}
|
|
297
|
+
{candidate.categoryName ? ` · ${candidate.categoryName}` : ""}
|
|
298
|
+
</span>
|
|
299
|
+
</span>
|
|
300
|
+
{candidate.variantsCount > 0 && (
|
|
301
|
+
<span className="count">
|
|
302
|
+
{candidate.variantsCount} variante
|
|
303
|
+
{candidate.variantsCount === 1 ? "" : "s"}
|
|
304
|
+
</span>
|
|
305
|
+
)}
|
|
306
|
+
</label>
|
|
307
|
+
</li>
|
|
308
|
+
))}
|
|
309
|
+
</List>
|
|
310
|
+
)}
|
|
311
|
+
</>
|
|
312
|
+
)}
|
|
313
|
+
|
|
314
|
+
{step === "inherit" && (
|
|
315
|
+
<>
|
|
316
|
+
<Section>
|
|
317
|
+
<h3>¿Qué se hereda del producto padre?</h3>
|
|
318
|
+
<label className="choice">
|
|
319
|
+
<input
|
|
320
|
+
type="radio"
|
|
321
|
+
checked={inheritMode === "all-filled"}
|
|
322
|
+
onChange={() => setInheritMode("all-filled")}
|
|
323
|
+
/>
|
|
324
|
+
<span>
|
|
325
|
+
Todos los atributos con valor
|
|
326
|
+
<span className="hint">
|
|
327
|
+
{attributes.length} atributo(s) con información en el padre.
|
|
328
|
+
</span>
|
|
329
|
+
</span>
|
|
330
|
+
</label>
|
|
331
|
+
<label className="choice">
|
|
332
|
+
<input
|
|
333
|
+
type="radio"
|
|
334
|
+
checked={inheritMode === "selected"}
|
|
335
|
+
onChange={() => setInheritMode("selected")}
|
|
336
|
+
/>
|
|
337
|
+
<span>
|
|
338
|
+
Elegir cuáles
|
|
339
|
+
<span className="hint">
|
|
340
|
+
Lo que no elijas conserva el valor que ya tiene la variante.
|
|
341
|
+
</span>
|
|
342
|
+
</span>
|
|
343
|
+
</label>
|
|
344
|
+
</Section>
|
|
345
|
+
|
|
346
|
+
{inheritMode === "selected" && activeRetailerId && !nameScope && (
|
|
347
|
+
<Message>
|
|
348
|
+
El producto padre no comparte la cadena que tienes abierta, así
|
|
349
|
+
que los nombres de abajo son los de otra de sus cadenas.
|
|
350
|
+
</Message>
|
|
351
|
+
)}
|
|
352
|
+
|
|
353
|
+
{inheritMode === "selected" && (
|
|
354
|
+
<AttributeList>
|
|
355
|
+
{attributes.map((attribute) => (
|
|
356
|
+
<li key={attribute.id}>
|
|
357
|
+
<label>
|
|
358
|
+
<input
|
|
359
|
+
type="checkbox"
|
|
360
|
+
checked={chosenAttributes.includes(attribute.id)}
|
|
361
|
+
onChange={() => toggleAttribute(attribute.id)}
|
|
362
|
+
/>
|
|
363
|
+
<span>
|
|
364
|
+
{attribute.name}
|
|
365
|
+
{attribute.unconfigured && (
|
|
366
|
+
<span className="note">
|
|
367
|
+
sin configuración en las cadenas del padre
|
|
368
|
+
</span>
|
|
369
|
+
)}
|
|
370
|
+
{attribute.ambiguousName && (
|
|
371
|
+
<span className="note">
|
|
372
|
+
se llama distinto en otra cadena
|
|
373
|
+
</span>
|
|
374
|
+
)}
|
|
375
|
+
</span>
|
|
376
|
+
<span className="value" title={attribute.value}>
|
|
377
|
+
{attribute.value}
|
|
378
|
+
</span>
|
|
379
|
+
</label>
|
|
380
|
+
</li>
|
|
381
|
+
))}
|
|
382
|
+
</AttributeList>
|
|
383
|
+
)}
|
|
384
|
+
|
|
385
|
+
<Section>
|
|
386
|
+
<h3>También se hereda</h3>
|
|
387
|
+
<label className="choice">
|
|
388
|
+
<input
|
|
389
|
+
type="checkbox"
|
|
390
|
+
checked={inheritImages}
|
|
391
|
+
onChange={() => setInheritImages((value) => !value)}
|
|
392
|
+
/>
|
|
393
|
+
<span>Imágenes</span>
|
|
394
|
+
</label>
|
|
395
|
+
<label className="choice">
|
|
396
|
+
<input
|
|
397
|
+
type="checkbox"
|
|
398
|
+
checked={inheritDescriptions}
|
|
399
|
+
onChange={() => setInheritDescriptions((value) => !value)}
|
|
400
|
+
/>
|
|
401
|
+
<span>Descripciones</span>
|
|
402
|
+
</label>
|
|
403
|
+
<label className="choice">
|
|
404
|
+
<input
|
|
405
|
+
type="checkbox"
|
|
406
|
+
checked={inheritCategoryRetailers}
|
|
407
|
+
onChange={() => setInheritCategoryRetailers((value) => !value)}
|
|
408
|
+
/>
|
|
409
|
+
<span>
|
|
410
|
+
Cadenas y categorías
|
|
411
|
+
<span className="hint">
|
|
412
|
+
Las parejas cadena-categoría del padre se copian a la variante.
|
|
413
|
+
</span>
|
|
414
|
+
</span>
|
|
415
|
+
</label>
|
|
416
|
+
</Section>
|
|
417
|
+
|
|
418
|
+
{/* Es la diferencia entre link y propagate, y es la que sorprende:
|
|
419
|
+
aquí lo elegido se escribe encima aunque la variante lo hubiera
|
|
420
|
+
editado a mano. Decirlo antes evita el reclamo después. */}
|
|
421
|
+
<Message>
|
|
422
|
+
Lo que elijas heredar reemplaza el valor actual de la variante,
|
|
423
|
+
incluso si alguien lo había capturado a mano.
|
|
424
|
+
</Message>
|
|
425
|
+
</>
|
|
426
|
+
)}
|
|
427
|
+
</Body>
|
|
428
|
+
|
|
429
|
+
<Footer>
|
|
430
|
+
<span className="summary">
|
|
431
|
+
{step === "search" ? selectionLabel : "Revisa la herencia antes de ligar."}
|
|
432
|
+
</span>
|
|
433
|
+
<div className="actions">
|
|
434
|
+
{step === "inherit" && (
|
|
435
|
+
<button
|
|
436
|
+
type="button"
|
|
437
|
+
className="secondary"
|
|
438
|
+
onClick={() => setStep("search")}
|
|
439
|
+
disabled={loading}
|
|
440
|
+
>
|
|
441
|
+
Atrás
|
|
442
|
+
</button>
|
|
443
|
+
)}
|
|
444
|
+
<button type="button" className="secondary" onClick={onClose} disabled={loading}>
|
|
445
|
+
Cancelar
|
|
446
|
+
</button>
|
|
447
|
+
{step === "search" ? (
|
|
448
|
+
<button
|
|
449
|
+
type="button"
|
|
450
|
+
className="primary"
|
|
451
|
+
onClick={goToInheritance}
|
|
452
|
+
disabled={loading || !selected.length}
|
|
453
|
+
>
|
|
454
|
+
Continuar
|
|
455
|
+
</button>
|
|
456
|
+
) : (
|
|
457
|
+
<button
|
|
458
|
+
type="button"
|
|
459
|
+
className="primary"
|
|
460
|
+
onClick={submit}
|
|
461
|
+
disabled={
|
|
462
|
+
loading ||
|
|
463
|
+
(inheritMode === "selected" && !chosenAttributes.length)
|
|
464
|
+
}
|
|
465
|
+
>
|
|
466
|
+
{loading ? "Ligando…" : "Ligar"}
|
|
467
|
+
</button>
|
|
468
|
+
)}
|
|
469
|
+
</div>
|
|
470
|
+
</Footer>
|
|
471
|
+
</Panel>
|
|
472
|
+
</Overlay>
|
|
473
|
+
);
|
|
474
|
+
};
|