contentoh-components-library 21.6.22 → 21.6.24

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.
@@ -82,6 +82,25 @@ export const Body = styled.div`
82
82
  flex: 1;
83
83
  overflow-y: auto;
84
84
  padding: 0 24px;
85
+
86
+ .apply-all {
87
+ display: flex;
88
+ align-items: flex-start;
89
+ gap: 8px;
90
+ margin-bottom: 12px;
91
+ padding: 10px 12px;
92
+ border-radius: 6px;
93
+ background-color: ${GlobalColors.s2};
94
+ font-size: 12px;
95
+ color: ${GlobalColors.deep_gray};
96
+ cursor: pointer;
97
+ }
98
+
99
+ .apply-all .hint {
100
+ display: block;
101
+ font-size: 11px;
102
+ color: ${GlobalColors.gray};
103
+ }
85
104
  `;
86
105
 
87
106
  export const SearchRow = styled.form`
@@ -145,6 +164,14 @@ export const List = styled.ul`
145
164
  color: ${GlobalColors.gray};
146
165
  }
147
166
 
167
+ .logo {
168
+ width: 24px;
169
+ height: 24px;
170
+ object-fit: contain;
171
+ border-radius: 3px;
172
+ flex-shrink: 0;
173
+ }
174
+
148
175
  .count {
149
176
  margin-left: auto;
150
177
  font-size: 11px;
@@ -262,3 +289,232 @@ export const Footer = styled.div`
262
289
  cursor: default;
263
290
  }
264
291
  `;
292
+
293
+ export const ImageGrid = styled.div`
294
+ display: grid;
295
+ grid-template-columns: repeat(auto-fill, minmax(92px, 1fr));
296
+ gap: 8px;
297
+ margin-bottom: 16px;
298
+
299
+ label {
300
+ position: relative;
301
+ display: flex;
302
+ flex-direction: column;
303
+ gap: 4px;
304
+ padding: 6px;
305
+ border: 1px solid ${GlobalColors.s2};
306
+ border-radius: 6px;
307
+ cursor: pointer;
308
+ }
309
+
310
+ label.picked {
311
+ border-color: ${GlobalColors.original_magenta};
312
+ background-color: rgba(${GlobalColors.rgb_color_pink}, 0.06);
313
+ }
314
+
315
+ label.locked {
316
+ cursor: default;
317
+ }
318
+
319
+ label input {
320
+ position: absolute;
321
+ top: 8px;
322
+ left: 8px;
323
+ }
324
+
325
+ label img {
326
+ width: 100%;
327
+ height: 72px;
328
+ object-fit: contain;
329
+ background-color: ${GlobalColors.s2};
330
+ border-radius: 4px;
331
+ }
332
+
333
+ label span {
334
+ font-size: 10px;
335
+ color: ${GlobalColors.gray};
336
+ text-align: center;
337
+ overflow: hidden;
338
+ text-overflow: ellipsis;
339
+ white-space: nowrap;
340
+ }
341
+ `;
342
+
343
+ export const AddRetailer = styled.div`
344
+ display: flex;
345
+ flex-wrap: wrap;
346
+ gap: 8px;
347
+ margin-bottom: 16px;
348
+
349
+ select {
350
+ flex: 1 1 140px;
351
+ min-width: 0;
352
+ height: 32px;
353
+ padding: 0 8px;
354
+ border: 1px solid ${GlobalColors.s2};
355
+ border-radius: 6px;
356
+ font-family: ${FontFamily.Raleway};
357
+ font-size: 12px;
358
+ color: ${GlobalColors.deep_gray};
359
+ background-color: ${GlobalColors.white};
360
+ }
361
+
362
+ button {
363
+ height: 32px;
364
+ padding: 0 14px;
365
+ border: 1px solid ${GlobalColors.original_magenta};
366
+ border-radius: 6px;
367
+ background: none;
368
+ font-family: ${FontFamily.Raleway_600};
369
+ font-size: 12px;
370
+ color: ${GlobalColors.original_magenta};
371
+ cursor: pointer;
372
+ }
373
+
374
+ select:disabled {
375
+ opacity: 0.5;
376
+ cursor: default;
377
+ }
378
+
379
+ button:disabled {
380
+ opacity: 0.4;
381
+ cursor: default;
382
+ }
383
+ `;
384
+
385
+ export const Block = styled.section`
386
+ margin-bottom: 20px;
387
+ padding-bottom: 16px;
388
+ border-bottom: 1px solid ${GlobalColors.s2};
389
+
390
+ &:last-of-type {
391
+ border-bottom: none;
392
+ }
393
+
394
+ h3 {
395
+ display: flex;
396
+ align-items: center;
397
+ gap: 8px;
398
+ margin: 0 0 8px;
399
+ font-family: ${FontFamily.Raleway_600};
400
+ font-size: 13px;
401
+ color: ${GlobalColors.deep_gray};
402
+ }
403
+
404
+ h3 .paso {
405
+ display: inline-flex;
406
+ align-items: center;
407
+ justify-content: center;
408
+ width: 18px;
409
+ height: 18px;
410
+ border-radius: 50%;
411
+ background-color: ${GlobalColors.original_magenta};
412
+ color: ${GlobalColors.white};
413
+ font-size: 10px;
414
+ }
415
+ `;
416
+
417
+ export const Choices = styled.div`
418
+ display: flex;
419
+ flex-direction: column;
420
+ gap: 2px;
421
+ margin-bottom: 10px;
422
+
423
+ label {
424
+ display: flex;
425
+ align-items: flex-start;
426
+ gap: 8px;
427
+ padding: 4px 0;
428
+ font-size: 12px;
429
+ color: ${GlobalColors.deep_gray};
430
+ cursor: pointer;
431
+ }
432
+
433
+ label .hint {
434
+ display: block;
435
+ font-size: 11px;
436
+ color: ${GlobalColors.gray};
437
+ }
438
+ `;
439
+
440
+ export const Paging = styled.div`
441
+ display: flex;
442
+ align-items: center;
443
+ justify-content: space-between;
444
+ gap: 12px;
445
+ margin: -8px 0 16px;
446
+ font-family: ${FontFamily.Raleway_500};
447
+ font-size: 11px;
448
+ color: ${GlobalColors.gray};
449
+
450
+ button {
451
+ border: 1px solid ${GlobalColors.s2};
452
+ border-radius: 6px;
453
+ background: none;
454
+ padding: 5px 12px;
455
+ cursor: pointer;
456
+ font-family: ${FontFamily.Raleway_600};
457
+ font-size: 11px;
458
+ color: ${GlobalColors.original_magenta};
459
+ }
460
+
461
+ button:disabled {
462
+ opacity: 0.5;
463
+ cursor: default;
464
+ }
465
+ `;
466
+
467
+ export const Tabs = styled.div`
468
+ display: flex;
469
+ flex-wrap: wrap;
470
+ gap: 6px;
471
+ margin-bottom: 16px;
472
+ padding-bottom: 12px;
473
+ border-bottom: 1px solid ${GlobalColors.s2};
474
+
475
+ button {
476
+ display: inline-flex;
477
+ align-items: center;
478
+ gap: 6px;
479
+ max-width: 200px;
480
+ padding: 6px 10px;
481
+ border: 1px solid ${GlobalColors.s2};
482
+ border-radius: 6px;
483
+ background-color: ${GlobalColors.white};
484
+ cursor: pointer;
485
+ font-family: ${FontFamily.Raleway_500};
486
+ font-size: 11px;
487
+ color: ${GlobalColors.gray};
488
+ overflow: hidden;
489
+ text-overflow: ellipsis;
490
+ white-space: nowrap;
491
+ }
492
+
493
+ button.active {
494
+ border-color: ${GlobalColors.original_magenta};
495
+ color: ${GlobalColors.original_magenta};
496
+ background-color: rgba(${GlobalColors.rgb_color_pink}, 0.08);
497
+ }
498
+
499
+ button .orden {
500
+ display: inline-flex;
501
+ align-items: center;
502
+ justify-content: center;
503
+ width: 16px;
504
+ height: 16px;
505
+ border-radius: 50%;
506
+ background-color: ${GlobalColors.s2};
507
+ font-size: 9px;
508
+ flex-shrink: 0;
509
+ }
510
+
511
+ button.active .orden {
512
+ background-color: ${GlobalColors.original_magenta};
513
+ color: ${GlobalColors.white};
514
+ }
515
+
516
+ button .listo {
517
+ color: ${GlobalColors.original_magenta};
518
+ flex-shrink: 0;
519
+ }
520
+ `;
@@ -159,9 +159,22 @@ export const ProviderProductEditionProvider = ({ children }) => {
159
159
  type: "SET_UPDATED_DATASHEETS_INPUTS",
160
160
  payload: [],
161
161
  });
162
+ // En una variante, todo atributo que el hijo toca deja de ser heredado:
163
+ // update-articles-data le pone is_overridden = 1 en la base. Si aquí solo
164
+ // se refresca el valor, el chip sigue diciendo "Heredado" hasta la
165
+ // siguiente carga de la ficha, y el usuario ve una marca que ya es falsa.
166
+ const esVariante =
167
+ state.services?.datasheets?.variant?.isVariant ??
168
+ !!state.product?.parentArticleId;
169
+
162
170
  dispatch({
163
171
  type: "UPDATE_DATASHEETS_INPUTS",
164
- payload: state.updated_datasheets_inputs,
172
+ payload: esVariante
173
+ ? state.updated_datasheets_inputs.map((item) => ({
174
+ ...item,
175
+ isOverridden: 1,
176
+ }))
177
+ : state.updated_datasheets_inputs,
165
178
  });
166
179
 
167
180
  // Mostrar modal de éxito
@@ -76,6 +76,9 @@ const ProviderProductEditionView = ({
76
76
  const [boxData, setBoxData] = useState(null);
77
77
  const [showModal, setShowModal] = useState(false);
78
78
  const [showLinkVariants, setShowLinkVariants] = useState(false);
79
+ const [family, setFamily] = useState(null);
80
+ const [showFamily, setShowFamily] = useState(false);
81
+ const [loadingVariant, setLoadingVariant] = useState(null);
79
82
  const [showRejectModal, setShowRejectModal] = useState(false);
80
83
  const [headerTop, setHeaderTop] = useState(0);
81
84
  const [assig, setAssig] = useState({});
@@ -697,6 +700,142 @@ const ProviderProductEditionView = ({
697
700
  });
698
701
  };
699
702
 
703
+ /*
704
+ Trae la familia del producto: sus variantes, o sus hermanas si el abierto es
705
+ una de ellas.
706
+
707
+ Se pide al abrir la sección y no al montar la pantalla: la mayoría de los
708
+ productos no son familia, y una llamada por ficha para no mostrar nada es
709
+ peso que paga todo el mundo por el caso de unos pocos.
710
+ */
711
+ const loadFamily = async () => {
712
+ if (family) return;
713
+ try {
714
+ const res = await axios.post(
715
+ `${process.env.REACT_APP_VARIANTS_ENDPOINT}/family`,
716
+ { articleId: state.product?.id_article },
717
+ { headers: { Authorization: token } }
718
+ );
719
+ setFamily(readPayload(res));
720
+ } catch (error) {
721
+ console.error("loadFamily: ", error);
722
+ setFamily({ variants: [], parent: null, error: true });
723
+ }
724
+ };
725
+
726
+ /*
727
+ Abre otra variante de la familia.
728
+
729
+ Esta pantalla no lee el producto de la URL: lo lee de sessionStorage, donde
730
+ lo dejó el listado al hacer clic. Así que navegar a /products/:id sin más
731
+ cambia la barra de direcciones y deja el producto anterior en pantalla.
732
+
733
+ Por eso primero se trae el producto por su ID —no por su UPC: hay 12,024 UPC
734
+ repetidos entre artículos activos y 5,579 de esos casos caen dentro de la
735
+ misma compañía, así que el UPC no identifica— y se deja en sessionStorage con
736
+ la misma forma que usa el listado. Después se navega.
737
+
738
+ Es un rodeo, y lo es porque el origen del dato está mal puesto. Arreglarlo de
739
+ raíz —que la ficha lea de la URL— toca todas las plataformas y no cabe aquí.
740
+ */
741
+ const openVariant = async (articleId) => {
742
+ setLoadingVariant(articleId);
743
+ try {
744
+ const res = await axios.get(
745
+ `${process.env.REACT_APP_NEW_ARTICLES_ENDPOINT}`,
746
+ {
747
+ params: { listType: "general", filterByArticleId: articleId },
748
+ headers: { Authorization: token },
749
+ }
750
+ );
751
+
752
+ // read-articles-v2 responde { count, data: [...] }.
753
+ const { data } = readPayload(res);
754
+ const encontrado = (data ?? []).find(
755
+ (item) => Number(item.id_article) === Number(articleId)
756
+ );
757
+
758
+ if (encontrado) {
759
+ sessionStorage.setItem(
760
+ "productSelected",
761
+ JSON.stringify({
762
+ ...encontrado,
763
+ article: { id_article: encontrado.id_article },
764
+ })
765
+ );
766
+ sessionStorage.setItem(
767
+ "productEdit",
768
+ JSON.stringify({
769
+ ArticleId: encontrado.id_article,
770
+ idCategory: encontrado.id_category,
771
+ product: encontrado,
772
+ })
773
+ );
774
+ }
775
+ } catch (error) {
776
+ // Si la consulta falla se navega igual: la pantalla mostrará el producto
777
+ // que hubiera en sessionStorage, que es el comportamiento de siempre.
778
+ // Peor sería dejar el clic sin efecto y que pareciera que no responde.
779
+ console.error("openVariant: ", error);
780
+ } finally {
781
+ setLoadingVariant(null);
782
+ window.location.assign(`/products/${articleId}/datasheet`);
783
+ }
784
+ };
785
+
786
+ /*
787
+ Refresca la ficha después de ligar, sin recargar la página.
788
+
789
+ Recargar no servía: el producto de esta pantalla viene del listado —por
790
+ sessionStorage—, así que tras el refresh volvía el mismo objeto de antes y el
791
+ contador seguía mostrando el número viejo. Además una recarga se lleva lo que
792
+ el usuario tenga sin guardar.
793
+
794
+ Los dos modos tienen efectos opuestos sobre este producto: ligando hijos GANA
795
+ variantes; ligándose a un padre pasa a SER una, y entonces el botón de ligar
796
+ desaparece porque la jerarquía es de un solo nivel.
797
+ */
798
+ const handleOnLinkedVariants = (result) => {
799
+ if (!result) return;
800
+
801
+ const updatedProduct =
802
+ result.mode === "children"
803
+ ? {
804
+ ...state.product,
805
+ variantsCount:
806
+ (state.product?.variantsCount ?? 0) +
807
+ (result.linked ?? []).length,
808
+ }
809
+ : {
810
+ ...state.product,
811
+ parentArticleId: result.parentArticleId ?? null,
812
+ variantsCount: 0,
813
+ };
814
+
815
+ dispatch({ type: "SET_PRODUCT", payload: updatedProduct });
816
+ // La lista cacheada quedó vieja: se descarta para que vuelva a pedirse.
817
+ setFamily(null);
818
+
819
+ // Y en sessionStorage, que es de donde sale el producto al recargar. Sin
820
+ // esto el arreglo dura hasta el siguiente F5 y vuelve el contador viejo,
821
+ // que es justo el síntoma que se reportó.
822
+ try {
823
+ sessionStorage.setItem("productSelected", JSON.stringify(updatedProduct));
824
+
825
+ const productEdit = JSON.parse(sessionStorage.getItem("productEdit"));
826
+ if (productEdit) {
827
+ sessionStorage.setItem(
828
+ "productEdit",
829
+ JSON.stringify({ ...productEdit, product: updatedProduct })
830
+ );
831
+ }
832
+ } catch (error) {
833
+ // Un sessionStorage inaccesible no debe tumbar la pantalla: el estado en
834
+ // memoria ya quedó bien y es lo que el usuario está viendo.
835
+ console.error("linkVariants/sessionStorage: ", error);
836
+ }
837
+ };
838
+
700
839
  /*
701
840
  Acción "Volver a heredar del producto" de los chips de herencia.
702
841
 
@@ -1192,7 +1331,7 @@ const ProviderProductEditionView = ({
1192
1331
  activeRetailerId={state.active_retailer?.id_retailer}
1193
1332
  token={token}
1194
1333
  onClose={() => setShowLinkVariants(false)}
1195
- onLinked={() => window.location.reload()}
1334
+ onLinked={handleOnLinkedVariants}
1196
1335
  />
1197
1336
  )}
1198
1337
  {showModal && (
@@ -1395,10 +1534,91 @@ const ProviderProductEditionView = ({
1395
1534
  Ligar productos
1396
1535
  </button>
1397
1536
  {state.product?.variantsCount > 0 && (
1398
- <span className="variants-count">
1537
+ <button
1538
+ type="button"
1539
+ className="variants-count"
1540
+ aria-expanded={showFamily}
1541
+ onClick={() => {
1542
+ setShowFamily((abierto) => !abierto);
1543
+ loadFamily();
1544
+ }}
1545
+ >
1399
1546
  {state.product.variantsCount} variante
1400
1547
  {state.product.variantsCount === 1 ? "" : "s"}
1401
- </span>
1548
+ <span aria-hidden="true">{showFamily ? " ▴" : " ▾"}</span>
1549
+ </button>
1550
+ )}
1551
+ </div>
1552
+ )}
1553
+
1554
+ {/* La ficha de una variante también necesita ver la familia: desde
1555
+ aquí se salta al padre o a una hermana sin pasar por el listado. */}
1556
+ {state.product?.parentArticleId && (
1557
+ <div className="variants-actions">
1558
+ <button
1559
+ type="button"
1560
+ className="variants-count"
1561
+ aria-expanded={showFamily}
1562
+ onClick={() => {
1563
+ setShowFamily((abierto) => !abierto);
1564
+ loadFamily();
1565
+ }}
1566
+ >
1567
+ Variante de otro producto
1568
+ <span aria-hidden="true">{showFamily ? " ▴" : " ▾"}</span>
1569
+ </button>
1570
+ </div>
1571
+ )}
1572
+
1573
+ {showFamily && (
1574
+ <div className="variants-family">
1575
+ {!family && <p className="loading">Cargando familia…</p>}
1576
+
1577
+ {family?.error && (
1578
+ <p className="loading">
1579
+ No se pudo cargar la familia de este producto.
1580
+ </p>
1581
+ )}
1582
+
1583
+ {family?.parent && (
1584
+ <button
1585
+ type="button"
1586
+ className="family-row parent"
1587
+ disabled={loadingVariant !== null}
1588
+ onClick={() => openVariant(family.parent.articleId)}
1589
+ >
1590
+ <span className="role">
1591
+ {loadingVariant === family.parent.articleId
1592
+ ? "Abriendo…"
1593
+ : "Producto padre"}
1594
+ </span>
1595
+ <span className="name">{family.parent.name}</span>
1596
+ <span className="upc">{family.parent.upc}</span>
1597
+ </button>
1598
+ )}
1599
+
1600
+ {family?.variants?.map((variant) => (
1601
+ <button
1602
+ key={variant.articleId}
1603
+ type="button"
1604
+ className={`family-row${variant.isCurrent ? " current" : ""}`}
1605
+ disabled={variant.isCurrent || loadingVariant !== null}
1606
+ onClick={() => openVariant(variant.articleId)}
1607
+ >
1608
+ <span className="role">
1609
+ {loadingVariant === variant.articleId
1610
+ ? "Abriendo…"
1611
+ : variant.isCurrent
1612
+ ? "Estás aquí"
1613
+ : "Variante"}
1614
+ </span>
1615
+ <span className="name">{variant.name}</span>
1616
+ <span className="upc">{variant.upc}</span>
1617
+ </button>
1618
+ ))}
1619
+
1620
+ {family && !family.error && !family.variants?.length && (
1621
+ <p className="loading">Este producto no tiene variantes.</p>
1402
1622
  )}
1403
1623
  </div>
1404
1624
  )}
@@ -178,9 +178,77 @@ export const Container = styled.div`
178
178
  }
179
179
 
180
180
  .variants-count {
181
+ border: none;
182
+ background: none;
183
+ padding: 0;
184
+ cursor: pointer;
185
+ font-family: ${FontFamily.Raleway_500};
186
+ font-size: 11px;
187
+ color: ${GlobalColors.gray};
188
+ text-decoration: underline;
189
+ }
190
+ }
191
+
192
+ .variants-family {
193
+ display: flex;
194
+ flex-direction: column;
195
+ gap: 4px;
196
+ margin-bottom: 10px;
197
+ padding: 8px;
198
+ border: 1px solid ${GlobalColors.s2};
199
+ border-radius: 6px;
200
+
201
+ .loading {
202
+ margin: 0;
181
203
  font-family: ${FontFamily.Raleway_500};
182
204
  font-size: 11px;
183
205
  color: ${GlobalColors.gray};
184
206
  }
207
+
208
+ .family-row {
209
+ display: flex;
210
+ align-items: baseline;
211
+ gap: 8px;
212
+ padding: 6px 8px;
213
+ border: none;
214
+ border-radius: 4px;
215
+ background: none;
216
+ cursor: pointer;
217
+ text-align: left;
218
+ font-family: ${FontFamily.Raleway_500};
219
+ font-size: 12px;
220
+ color: ${GlobalColors.deep_gray};
221
+ }
222
+
223
+ .family-row:hover:not(:disabled) {
224
+ background-color: ${GlobalColors.s2};
225
+ }
226
+
227
+ .family-row:disabled {
228
+ cursor: default;
229
+ opacity: 0.7;
230
+ }
231
+
232
+ .family-row .role {
233
+ min-width: 96px;
234
+ font-size: 10px;
235
+ color: ${GlobalColors.original_magenta};
236
+ }
237
+
238
+ .family-row.current .role {
239
+ color: ${GlobalColors.gray};
240
+ }
241
+
242
+ .family-row .name {
243
+ flex: 1;
244
+ overflow: hidden;
245
+ text-overflow: ellipsis;
246
+ white-space: nowrap;
247
+ }
248
+
249
+ .family-row .upc {
250
+ font-size: 11px;
251
+ color: ${GlobalColors.gray};
252
+ }
185
253
  }
186
254
  `;