contentoh-components-library 21.6.21 → 21.6.23

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.
@@ -262,3 +262,123 @@ export const Footer = styled.div`
262
262
  cursor: default;
263
263
  }
264
264
  `;
265
+
266
+ export const SectionHead = styled.div`
267
+ display: flex;
268
+ align-items: center;
269
+ gap: 8px;
270
+ margin-bottom: 6px;
271
+
272
+ .toggle {
273
+ flex: 1;
274
+ display: flex;
275
+ align-items: center;
276
+ gap: 8px;
277
+ border: none;
278
+ background: none;
279
+ padding: 0;
280
+ cursor: pointer;
281
+ font-family: ${FontFamily.Raleway_600};
282
+ font-size: 13px;
283
+ color: ${GlobalColors.deep_gray};
284
+ text-align: left;
285
+ }
286
+
287
+ .toggle .count {
288
+ font-family: ${FontFamily.Raleway_500};
289
+ font-size: 11px;
290
+ color: ${GlobalColors.gray};
291
+ }
292
+
293
+ .all {
294
+ border: none;
295
+ background: none;
296
+ padding: 0;
297
+ cursor: pointer;
298
+ font-family: ${FontFamily.Raleway_500};
299
+ font-size: 11px;
300
+ color: ${GlobalColors.original_magenta};
301
+ text-decoration: underline;
302
+ }
303
+ `;
304
+
305
+ export const ImageGrid = styled.div`
306
+ display: grid;
307
+ grid-template-columns: repeat(auto-fill, minmax(92px, 1fr));
308
+ gap: 8px;
309
+ margin-bottom: 16px;
310
+
311
+ label {
312
+ position: relative;
313
+ display: flex;
314
+ flex-direction: column;
315
+ gap: 4px;
316
+ padding: 6px;
317
+ border: 1px solid ${GlobalColors.s2};
318
+ border-radius: 6px;
319
+ cursor: pointer;
320
+ }
321
+
322
+ label.picked {
323
+ border-color: ${GlobalColors.original_magenta};
324
+ background-color: rgba(${GlobalColors.rgb_color_pink}, 0.06);
325
+ }
326
+
327
+ label input {
328
+ position: absolute;
329
+ top: 8px;
330
+ left: 8px;
331
+ }
332
+
333
+ label img {
334
+ width: 100%;
335
+ height: 72px;
336
+ object-fit: contain;
337
+ background-color: ${GlobalColors.s2};
338
+ border-radius: 4px;
339
+ }
340
+
341
+ label span {
342
+ font-size: 10px;
343
+ color: ${GlobalColors.gray};
344
+ text-align: center;
345
+ overflow: hidden;
346
+ text-overflow: ellipsis;
347
+ white-space: nowrap;
348
+ }
349
+ `;
350
+
351
+ export const AddRetailer = styled.div`
352
+ display: flex;
353
+ gap: 8px;
354
+ margin-bottom: 16px;
355
+
356
+ select {
357
+ flex: 1;
358
+ height: 32px;
359
+ padding: 0 8px;
360
+ border: 1px solid ${GlobalColors.s2};
361
+ border-radius: 6px;
362
+ font-family: ${FontFamily.Raleway};
363
+ font-size: 12px;
364
+ color: ${GlobalColors.deep_gray};
365
+ background-color: ${GlobalColors.white};
366
+ }
367
+
368
+ button {
369
+ height: 32px;
370
+ padding: 0 14px;
371
+ border: 1px solid ${GlobalColors.original_magenta};
372
+ border-radius: 6px;
373
+ background: none;
374
+ font-family: ${FontFamily.Raleway_600};
375
+ font-size: 12px;
376
+ color: ${GlobalColors.original_magenta};
377
+ cursor: pointer;
378
+ }
379
+
380
+ button:disabled {
381
+ opacity: 0.4;
382
+ cursor: default;
383
+ }
384
+ `;
@@ -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,8 @@ 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);
79
81
  const [showRejectModal, setShowRejectModal] = useState(false);
80
82
  const [headerTop, setHeaderTop] = useState(0);
81
83
  const [assig, setAssig] = useState({});
@@ -697,6 +699,88 @@ const ProviderProductEditionView = ({
697
699
  });
698
700
  };
699
701
 
702
+ /*
703
+ Trae la familia del producto: sus variantes, o sus hermanas si el abierto es
704
+ una de ellas.
705
+
706
+ Se pide al abrir la sección y no al montar la pantalla: la mayoría de los
707
+ productos no son familia, y una llamada por ficha para no mostrar nada es
708
+ peso que paga todo el mundo por el caso de unos pocos.
709
+ */
710
+ const loadFamily = async () => {
711
+ if (family) return;
712
+ try {
713
+ const res = await axios.post(
714
+ `${process.env.REACT_APP_VARIANTS_ENDPOINT}/family`,
715
+ { articleId: state.product?.id_article },
716
+ { headers: { Authorization: token } }
717
+ );
718
+ setFamily(readPayload(res));
719
+ } catch (error) {
720
+ console.error("loadFamily: ", error);
721
+ setFamily({ variants: [], parent: null, error: true });
722
+ }
723
+ };
724
+
725
+ const openVariant = (articleId) => {
726
+ // Se navega igual que el resto de la app, con el id en la ruta. Inventar
727
+ // otra forma de abrir un producto es inventar otra forma de que se rompa.
728
+ window.location.assign(`/products/${articleId}/datasheet`);
729
+ };
730
+
731
+ /*
732
+ Refresca la ficha después de ligar, sin recargar la página.
733
+
734
+ Recargar no servía: el producto de esta pantalla viene del listado —por
735
+ sessionStorage—, así que tras el refresh volvía el mismo objeto de antes y el
736
+ contador seguía mostrando el número viejo. Además una recarga se lleva lo que
737
+ el usuario tenga sin guardar.
738
+
739
+ Los dos modos tienen efectos opuestos sobre este producto: ligando hijos GANA
740
+ variantes; ligándose a un padre pasa a SER una, y entonces el botón de ligar
741
+ desaparece porque la jerarquía es de un solo nivel.
742
+ */
743
+ const handleOnLinkedVariants = (result) => {
744
+ if (!result) return;
745
+
746
+ const updatedProduct =
747
+ result.mode === "children"
748
+ ? {
749
+ ...state.product,
750
+ variantsCount:
751
+ (state.product?.variantsCount ?? 0) +
752
+ (result.linked ?? []).length,
753
+ }
754
+ : {
755
+ ...state.product,
756
+ parentArticleId: result.parentArticleId ?? null,
757
+ variantsCount: 0,
758
+ };
759
+
760
+ dispatch({ type: "SET_PRODUCT", payload: updatedProduct });
761
+ // La lista cacheada quedó vieja: se descarta para que vuelva a pedirse.
762
+ setFamily(null);
763
+
764
+ // Y en sessionStorage, que es de donde sale el producto al recargar. Sin
765
+ // esto el arreglo dura hasta el siguiente F5 y vuelve el contador viejo,
766
+ // que es justo el síntoma que se reportó.
767
+ try {
768
+ sessionStorage.setItem("productSelected", JSON.stringify(updatedProduct));
769
+
770
+ const productEdit = JSON.parse(sessionStorage.getItem("productEdit"));
771
+ if (productEdit) {
772
+ sessionStorage.setItem(
773
+ "productEdit",
774
+ JSON.stringify({ ...productEdit, product: updatedProduct })
775
+ );
776
+ }
777
+ } catch (error) {
778
+ // Un sessionStorage inaccesible no debe tumbar la pantalla: el estado en
779
+ // memoria ya quedó bien y es lo que el usuario está viendo.
780
+ console.error("linkVariants/sessionStorage: ", error);
781
+ }
782
+ };
783
+
700
784
  /*
701
785
  Acción "Volver a heredar del producto" de los chips de herencia.
702
786
 
@@ -1190,9 +1274,11 @@ const ProviderProductEditionView = ({
1190
1274
  articleName={state.product?.name}
1191
1275
  articleUpc={state.product?.upc}
1192
1276
  activeRetailerId={state.active_retailer?.id_retailer}
1277
+ retailersAvailable={state.product?.retailersAvailable}
1278
+ categoriesAvailable={state.product?.categoriesAvailable}
1193
1279
  token={token}
1194
1280
  onClose={() => setShowLinkVariants(false)}
1195
- onLinked={() => window.location.reload()}
1281
+ onLinked={handleOnLinkedVariants}
1196
1282
  />
1197
1283
  )}
1198
1284
  {showModal && (
@@ -1395,10 +1481,82 @@ const ProviderProductEditionView = ({
1395
1481
  Ligar productos
1396
1482
  </button>
1397
1483
  {state.product?.variantsCount > 0 && (
1398
- <span className="variants-count">
1484
+ <button
1485
+ type="button"
1486
+ className="variants-count"
1487
+ aria-expanded={showFamily}
1488
+ onClick={() => {
1489
+ setShowFamily((abierto) => !abierto);
1490
+ loadFamily();
1491
+ }}
1492
+ >
1399
1493
  {state.product.variantsCount} variante
1400
1494
  {state.product.variantsCount === 1 ? "" : "s"}
1401
- </span>
1495
+ <span aria-hidden="true">{showFamily ? " ▴" : " ▾"}</span>
1496
+ </button>
1497
+ )}
1498
+ </div>
1499
+ )}
1500
+
1501
+ {/* La ficha de una variante también necesita ver la familia: desde
1502
+ aquí se salta al padre o a una hermana sin pasar por el listado. */}
1503
+ {state.product?.parentArticleId && (
1504
+ <div className="variants-actions">
1505
+ <button
1506
+ type="button"
1507
+ className="variants-count"
1508
+ aria-expanded={showFamily}
1509
+ onClick={() => {
1510
+ setShowFamily((abierto) => !abierto);
1511
+ loadFamily();
1512
+ }}
1513
+ >
1514
+ Variante de otro producto
1515
+ <span aria-hidden="true">{showFamily ? " ▴" : " ▾"}</span>
1516
+ </button>
1517
+ </div>
1518
+ )}
1519
+
1520
+ {showFamily && (
1521
+ <div className="variants-family">
1522
+ {!family && <p className="loading">Cargando familia…</p>}
1523
+
1524
+ {family?.error && (
1525
+ <p className="loading">
1526
+ No se pudo cargar la familia de este producto.
1527
+ </p>
1528
+ )}
1529
+
1530
+ {family?.parent && (
1531
+ <button
1532
+ type="button"
1533
+ className="family-row parent"
1534
+ onClick={() => openVariant(family.parent.articleId)}
1535
+ >
1536
+ <span className="role">Producto padre</span>
1537
+ <span className="name">{family.parent.name}</span>
1538
+ <span className="upc">{family.parent.upc}</span>
1539
+ </button>
1540
+ )}
1541
+
1542
+ {family?.variants?.map((variant) => (
1543
+ <button
1544
+ key={variant.articleId}
1545
+ type="button"
1546
+ className={`family-row${variant.isCurrent ? " current" : ""}`}
1547
+ disabled={variant.isCurrent}
1548
+ onClick={() => openVariant(variant.articleId)}
1549
+ >
1550
+ <span className="role">
1551
+ {variant.isCurrent ? "Estás aquí" : "Variante"}
1552
+ </span>
1553
+ <span className="name">{variant.name}</span>
1554
+ <span className="upc">{variant.upc}</span>
1555
+ </button>
1556
+ ))}
1557
+
1558
+ {family && !family.error && !family.variants?.length && (
1559
+ <p className="loading">Este producto no tiene variantes.</p>
1402
1560
  )}
1403
1561
  </div>
1404
1562
  )}
@@ -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
  `;