contentoh-components-library 21.6.61 → 21.6.63

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.
Files changed (102) hide show
  1. package/.claude/settings.local.json +7 -0
  2. package/dist/ai/prompts/attribute.prompt.js +28 -0
  3. package/dist/ai/prompts/description.prompt.js +33 -0
  4. package/dist/ai/schemas/attribute.schema.js +35 -0
  5. package/dist/ai/schemas/description.schema.js +27 -0
  6. package/dist/components/atoms/MetricCard/index.js +3 -1
  7. package/dist/components/organisms/DashboardMetric/DashboardMetric.stories.js +12 -2
  8. package/dist/components/organisms/DashboardMetric/index.js +24 -1
  9. package/dist/components/organisms/DashboardMetric/styles.js +1 -1
  10. package/dist/components/pages/CreatorsProductEdition/CreatorsProductEditionView.js +11 -5
  11. package/dist/components/pages/CreatorsProductEdition/hooks/useCreatorsProductEdition.js +58 -31
  12. package/dist/components/pages/CreatorsProductEdition/mocks/api.fixtures.js +5 -1
  13. package/dist/components/pages/CreatorsProductEdition/mocks/mockApi.js +3 -40
  14. package/dist/components/pages/CreatorsProductEdition/sections/ImagesSection.js +18 -3
  15. package/dist/components/pages/CreatorsProductEdition/sections/StatusChip.js +4 -13
  16. package/dist/components/pages/CreatorsProductEdition/sections/TabsBar.js +10 -0
  17. package/dist/components/pages/CreatorsProductEdition/stories/CreatorsProductEdition.stories.js +10 -2
  18. package/dist/components/pages/Dashboard/dashboardUtils.js +3 -3
  19. package/dist/components/pages/Dashboard/index.js +102 -88
  20. package/dist/components/pages/Dashboard/styles.js +1 -1
  21. package/dist/components/pages/RetailerProductEdition/RetailerProductEdition.stories.js +2 -15
  22. package/dist/components/pages/RetailerProductEdition/index.js +40 -57
  23. package/dist/components/pages/RetailerProductEdition/utils.js +2 -6
  24. package/dist/index.js +0 -13
  25. package/package.json +1 -1
  26. package/src/components/atoms/MetricCard/index.js +2 -2
  27. package/src/components/organisms/DashboardMetric/DashboardMetric.stories.js +11 -0
  28. package/src/components/organisms/DashboardMetric/index.js +13 -1
  29. package/src/components/organisms/DashboardMetric/styles.js +13 -0
  30. package/src/components/pages/CreatorsProductEdition/CreatorsProductEditionView.jsx +22 -3
  31. package/src/components/pages/CreatorsProductEdition/hooks/useCreatorsProductEdition.js +36 -1
  32. package/src/components/pages/CreatorsProductEdition/mocks/api.fixtures.js +15 -3
  33. package/src/components/pages/CreatorsProductEdition/mocks/mockApi.js +8 -18
  34. package/src/components/pages/CreatorsProductEdition/sections/ImagesSection.jsx +24 -2
  35. package/src/components/pages/CreatorsProductEdition/sections/StatusChip.jsx +4 -8
  36. package/src/components/pages/CreatorsProductEdition/sections/TabsBar.jsx +9 -0
  37. package/src/components/pages/CreatorsProductEdition/stories/CreatorsProductEdition.stories.js +6 -0
  38. package/src/components/pages/Dashboard/dashboardUtils.js +3 -3
  39. package/src/components/pages/Dashboard/index.js +62 -106
  40. package/src/components/pages/Dashboard/styles.js +17 -12
  41. package/src/components/pages/RetailerProductEdition/RetailerProductEdition.stories.js +0 -12
  42. package/src/components/pages/RetailerProductEdition/index.js +3 -28
  43. package/src/components/pages/RetailerProductEdition/utils.js +0 -5
  44. package/src/index.js +0 -1
  45. package/dist/components/organisms/ServicesNavigation/ServiceToolbar.js +0 -210
  46. package/dist/components/organisms/ServicesNavigation/ServicesNavigation.stories.js +0 -138
  47. package/dist/components/organisms/ServicesNavigation/ServicesRail.js +0 -705
  48. package/dist/components/organisms/ServicesNavigation/catalog.js +0 -505
  49. package/dist/components/organisms/ServicesNavigation/index.js +0 -337
  50. package/dist/components/organisms/ServicesNavigation/model.js +0 -167
  51. package/dist/components/organisms/ServicesNavigation/ui.js +0 -371
  52. package/dist/components/organisms/ServicesNavigation/views.js +0 -1237
  53. package/dist/components/organisms/ServicesWorkspace/AssigneePicker.js +0 -459
  54. package/dist/components/organisms/ServicesWorkspace/ServicesWorkspace.stories.js +0 -119
  55. package/dist/components/organisms/ServicesWorkspace/demo.js +0 -137
  56. package/dist/components/organisms/ServicesWorkspace/index.js +0 -813
  57. package/dist/components/organisms/ServicesWorkspace/servicesModel.js +0 -351
  58. package/dist/components/organisms/ServicesWorkspace/tooltipStyles.js +0 -37
  59. package/dist/components/pages/CreatorsProductEdition/mocks/productServices.fixtures.js +0 -387
  60. package/dist/components/pages/ProductEdition/ProductEdition.stories.js +0 -148
  61. package/dist/components/pages/ProductEdition/ProductEditionView.js +0 -1355
  62. package/dist/components/pages/ProductEdition/ServiceHeader.js +0 -142
  63. package/dist/components/pages/ProductEdition/api.js +0 -256
  64. package/dist/components/pages/ProductEdition/index.js +0 -37
  65. package/dist/components/pages/ProductEdition/permissions.js +0 -200
  66. package/dist/components/pages/ProductEdition/permissions.test.js +0 -320
  67. package/dist/components/pages/ProductEdition/sections/CatalogServicePanel.js +0 -237
  68. package/dist/components/pages/ProductEdition/sections/EditionTopBar.js +0 -174
  69. package/dist/components/pages/ProductEdition/sections/ImagePanel.js +0 -440
  70. package/dist/components/pages/ProductEdition/sections/ServiceTeam.js +0 -275
  71. package/dist/components/pages/ProductEdition/sections/ServiceTools.js +0 -186
  72. package/dist/components/pages/ProductEdition/services.js +0 -233
  73. package/dist/components/pages/ProductEdition/services.test.js +0 -195
  74. package/src/components/organisms/ServicesNavigation/ServiceToolbar.jsx +0 -143
  75. package/src/components/organisms/ServicesNavigation/ServicesNavigation.stories.js +0 -101
  76. package/src/components/organisms/ServicesNavigation/ServicesRail.jsx +0 -591
  77. package/src/components/organisms/ServicesNavigation/catalog.js +0 -180
  78. package/src/components/organisms/ServicesNavigation/index.jsx +0 -232
  79. package/src/components/organisms/ServicesNavigation/model.js +0 -85
  80. package/src/components/organisms/ServicesNavigation/ui.jsx +0 -229
  81. package/src/components/organisms/ServicesNavigation/views.jsx +0 -705
  82. package/src/components/organisms/ServicesWorkspace/AssigneePicker.js +0 -348
  83. package/src/components/organisms/ServicesWorkspace/ServicesWorkspace.stories.js +0 -61
  84. package/src/components/organisms/ServicesWorkspace/demo.js +0 -58
  85. package/src/components/organisms/ServicesWorkspace/index.js +0 -556
  86. package/src/components/organisms/ServicesWorkspace/servicesModel.js +0 -203
  87. package/src/components/organisms/ServicesWorkspace/tooltipStyles.js +0 -27
  88. package/src/components/pages/CreatorsProductEdition/mocks/productServices.fixtures.js +0 -175
  89. package/src/components/pages/ProductEdition/ProductEdition.stories.js +0 -106
  90. package/src/components/pages/ProductEdition/ProductEditionView.jsx +0 -1142
  91. package/src/components/pages/ProductEdition/ServiceHeader.jsx +0 -104
  92. package/src/components/pages/ProductEdition/api.js +0 -84
  93. package/src/components/pages/ProductEdition/index.js +0 -21
  94. package/src/components/pages/ProductEdition/permissions.js +0 -128
  95. package/src/components/pages/ProductEdition/permissions.test.js +0 -249
  96. package/src/components/pages/ProductEdition/sections/CatalogServicePanel.jsx +0 -123
  97. package/src/components/pages/ProductEdition/sections/EditionTopBar.jsx +0 -134
  98. package/src/components/pages/ProductEdition/sections/ImagePanel.jsx +0 -342
  99. package/src/components/pages/ProductEdition/sections/ServiceTeam.jsx +0 -184
  100. package/src/components/pages/ProductEdition/sections/ServiceTools.jsx +0 -134
  101. package/src/components/pages/ProductEdition/services.js +0 -187
  102. package/src/components/pages/ProductEdition/services.test.js +0 -88
@@ -1,123 +0,0 @@
1
- import { useState } from "react";
2
- import { Alert, Box, CircularProgress, MenuItem, Select, Stack, Typography } from "@mui/material";
3
-
4
- import { StatusChip } from "../../CreatorsProductEdition/sections/StatusChip";
5
- import { COLORS, FONTS } from "../../CreatorsProductEdition/sections/tokens";
6
-
7
- /*
8
- Panel de un servicio del catálogo en la edición (los que no tienen editor):
9
- su estatus en cada cadena y, si la persona puede, el selector para cambiarlo.
10
-
11
- Sale de GET /product-services (`service.api`):
12
- - Flujo simple (casilla, avance): se elige el estatus de `status_options`
13
- con PUT /product-services/set-status, por cadena.
14
- - Flujo completo: solo se ve; lo moverá el motor de estatus.
15
- - De registro: solo se ve.
16
- Los servicios de los uno por producto aparecen como «Producto» (cadena 0).
17
- */
18
-
19
- const FLOW_NOTE = {
20
- workflow:
21
- "Este servicio sigue el flujo de aprobación (especialista, coordinador y auditor). " +
22
- "Su estatus todavía no se cambia desde aquí: lo moverá el motor de estatus.",
23
- flag: "Este servicio solo se registra en el producto: no cambia de estatus.",
24
- };
25
-
26
- const chipProps = (status) =>
27
- status
28
- ? { status: status.status_name, label: status.display_name ?? status.status_name, color: status.color ?? undefined }
29
- : { status: null, label: "Sin estatus" };
30
-
31
- export const CatalogServicePanel = ({ service, onSetStatus }) => {
32
- const api = service?.api;
33
- const [savingRetailer, setSavingRetailer] = useState(null);
34
- const [error, setError] = useState(null);
35
- if (!api) return null;
36
-
37
- const flowType = api.status_flow?.flow_type ?? "workflow";
38
- const canSet = Boolean(api.actions?.set_status) && flowType === "simple";
39
- const options = api.status_options ?? [];
40
-
41
- const change = async (retailerId, statusName) => {
42
- setError(null);
43
- setSavingRetailer(retailerId);
44
- try {
45
- await onSetStatus(statusName, [retailerId]);
46
- } catch (err) {
47
- setError(err?.message ?? "No se pudo cambiar el estatus.");
48
- } finally {
49
- setSavingRetailer(null);
50
- }
51
- };
52
-
53
- return (
54
- <Box
55
- sx={{
56
- p: 2,
57
- borderRadius: "10px",
58
- border: `1px solid ${COLORS.s2}`,
59
- bgcolor: COLORS.white,
60
- maxWidth: 720,
61
- }}
62
- >
63
- <Typography sx={{ fontFamily: FONTS.Lato, fontSize: 14, fontWeight: 700, color: COLORS.s5, mb: 0.5 }}>
64
- Estatus {api.per_retailer ? "por cadena" : "del producto"}
65
- </Typography>
66
- {FLOW_NOTE[flowType] && (
67
- <Typography sx={{ fontFamily: FONTS.Lato, fontSize: 13, color: COLORS.s4, mb: 1.5 }}>
68
- {FLOW_NOTE[flowType]}
69
- </Typography>
70
- )}
71
-
72
- <Stack spacing={1}>
73
- {(api.retailers ?? []).map((retailer) => (
74
- <Stack
75
- key={retailer.id_retailer}
76
- direction="row"
77
- alignItems="center"
78
- spacing={1.5}
79
- sx={{ py: 0.75, borderTop: `1px solid ${COLORS.s2}`, "&:first-of-type": { borderTop: "none" } }}
80
- >
81
- <Typography sx={{ fontFamily: FONTS.Lato, fontSize: 13, color: COLORS.s5, flex: "1 1 auto", minWidth: 0 }} noWrap>
82
- {api.per_retailer ? retailer.retailer_name ?? `Cadena ${retailer.id_retailer}` : "Producto"}
83
- </Typography>
84
- {canSet ? (
85
- <Select
86
- size="small"
87
- value={retailer.status?.status_name ?? ""}
88
- disabled={savingRetailer !== null}
89
- onChange={(event) => change(retailer.id_retailer, event.target.value)}
90
- renderValue={(value) => {
91
- const option = options.find((item) => item.status_name === value) ?? retailer.status;
92
- return <StatusChip {...chipProps(option)} oval />;
93
- }}
94
- sx={{ minWidth: 170, fontFamily: FONTS.Lato, fontSize: 13 }}
95
- inputProps={{ "aria-label": `Estatus de ${service.display_name}` }}
96
- >
97
- {options.map((option) => (
98
- <MenuItem key={option.status_name} value={option.status_name}>
99
- <StatusChip {...chipProps(option)} oval />
100
- </MenuItem>
101
- ))}
102
- </Select>
103
- ) : (
104
- <StatusChip {...chipProps(retailer.status)} oval />
105
- )}
106
- {savingRetailer === retailer.id_retailer && <CircularProgress size={16} sx={{ color: COLORS.magenta_s2 }} />}
107
- </Stack>
108
- ))}
109
- </Stack>
110
-
111
- {flowType === "simple" && !canSet && (
112
- <Typography sx={{ fontFamily: FONTS.Lato, fontSize: 12, color: COLORS.s4, mt: 1.5 }}>
113
- Lo cambia quien está asignado al servicio, un administrador o un coordinador.
114
- </Typography>
115
- )}
116
- {error && (
117
- <Alert severity="error" sx={{ mt: 1.5, fontFamily: FONTS.Lato, fontSize: 13 }} onClose={() => setError(null)}>
118
- {error}
119
- </Alert>
120
- )}
121
- </Box>
122
- );
123
- };
@@ -1,134 +0,0 @@
1
- import { useEffect, useMemo, useRef } from "react";
2
- import { Box, IconButton, Stack, Tooltip, Typography } from "@mui/material";
3
- import CloseIcon from "@mui/icons-material/Close";
4
-
5
- import { Chat } from "../../../organisms/Chat";
6
- import auditIcon from "../../../../assets/images/generalButton/Icono AB.svg";
7
- import { COLORS, FONTS } from "../../CreatorsProductEdition/sections/tokens";
8
-
9
- /*
10
- Barra superior de la edición. Reemplaza a `molecules/HeaderTop` con lo mismo
11
- (título, chat, comparar versión auditable y cerrar), pero:
12
- - el título es más chico y la barra tiene aire y una línea abajo, para que
13
- no quede encimado sobre la imagen y el nombre del producto;
14
- - el botón de cerrar queda dentro del margen y con borde, para que siempre
15
- se vea (antes iba pegado al borde derecho).
16
-
17
- Reporta su alto con `setHeaderTop`, como HeaderTop: la pantalla lo resta para
18
- calcular el alto de las columnas. Incluye el margen de abajo.
19
- */
20
-
21
- const GAP_BELOW = 12;
22
-
23
- const buttonSx = {
24
- width: 32,
25
- height: 32,
26
- border: `1px solid ${COLORS.s3}`,
27
- bgcolor: COLORS.white,
28
- "&:hover": { bgcolor: COLORS.s2, borderColor: COLORS.s4 },
29
- };
30
-
31
- export const EditionTopBar = ({
32
- setHeaderTop,
33
- withChat,
34
- chatType,
35
- productSelected,
36
- token,
37
- auditableVersion,
38
- setCompare,
39
- isAuditor,
40
- activeRetailer,
41
- onClose = () => {
42
- window.location.href = "/products";
43
- },
44
- }) => {
45
- const ref = useRef(null);
46
-
47
- useEffect(() => {
48
- if (ref.current && setHeaderTop) setHeaderTop(ref.current.offsetHeight + GAP_BELOW);
49
- // eslint-disable-next-line react-hooks/exhaustive-deps
50
- }, []);
51
-
52
- // Mismo chat que HeaderTop, según `chatType`.
53
- const chat = useMemo(() => {
54
- if (!withChat || !chatType || !productSelected || !token) return null;
55
- if (chatType === "product_status") {
56
- if (!activeRetailer?.id) return null;
57
- return (
58
- <Chat
59
- chatType="product_status"
60
- chatContainerType="popUp"
61
- chatData={{
62
- id: productSelected?.article?.id_article || productSelected?.id_article,
63
- version: productSelected?.version,
64
- retailerId: activeRetailer.id,
65
- status: productSelected?.status,
66
- userToken: token,
67
- }}
68
- size={18}
69
- activeRetailer={activeRetailer}
70
- />
71
- );
72
- }
73
- return (
74
- <Chat
75
- chatType="merchant_product"
76
- chatContainerType="popUp"
77
- chatData={{ userToken: token, id: productSelected.id_article, version: productSelected.version }}
78
- size={18}
79
- activeRetailer={activeRetailer}
80
- />
81
- );
82
- // eslint-disable-next-line react-hooks/exhaustive-deps
83
- }, [withChat, chatType, productSelected?.id_article, productSelected?.version, token, activeRetailer?.id]);
84
-
85
- return (
86
- <Box
87
- ref={ref}
88
- component="header"
89
- sx={{
90
- display: "flex",
91
- alignItems: "center",
92
- gap: 1.5,
93
- px: { xs: 1.5, sm: 2 },
94
- py: 1,
95
- mb: `${GAP_BELOW}px`,
96
- borderBottom: `1px solid ${COLORS.s2}`,
97
- minWidth: 0,
98
- }}
99
- >
100
- <Typography
101
- component="h1"
102
- sx={{
103
- flex: 1,
104
- minWidth: 0,
105
- fontFamily: FONTS.Raleway_600,
106
- fontSize: 16,
107
- lineHeight: "24px",
108
- color: COLORS.s5,
109
- overflow: "hidden",
110
- textOverflow: "ellipsis",
111
- whiteSpace: "nowrap",
112
- }}
113
- >
114
- Edición de producto
115
- </Typography>
116
-
117
- <Stack direction="row" alignItems="center" spacing={1} sx={{ flexShrink: 0 }}>
118
- {chat}
119
- {isAuditor && auditableVersion && (
120
- <Tooltip title="Comparar con la versión auditable" arrow>
121
- <IconButton aria-label="Comparar con la versión auditable" onClick={() => setCompare((value) => !value)} sx={buttonSx}>
122
- <img src={auditIcon} alt="" width={16} height={16} />
123
- </IconButton>
124
- </Tooltip>
125
- )}
126
- <Tooltip title="Cerrar" arrow>
127
- <IconButton aria-label="Cerrar la edición" onClick={onClose} sx={buttonSx}>
128
- <CloseIcon sx={{ fontSize: 18, color: COLORS.s5 }} />
129
- </IconButton>
130
- </Tooltip>
131
- </Stack>
132
- </Box>
133
- );
134
- };
@@ -1,342 +0,0 @@
1
- import { useEffect, useRef, useState } from "react";
2
- import {
3
- Box,
4
- ButtonBase,
5
- IconButton,
6
- InputBase,
7
- Paper,
8
- Stack,
9
- Table,
10
- TableBody,
11
- TableCell,
12
- TableRow,
13
- Tooltip,
14
- Typography,
15
- } from "@mui/material";
16
- import { Swiper, SwiperSlide } from "swiper/react";
17
- import { FreeMode, Keyboard, Mousewheel } from "swiper";
18
- import "swiper/swiper.min.css";
19
-
20
- import openModalIcon from "../../../../assets/images/generalButton/openModal.svg";
21
-
22
- import { ProductMedia } from "../../CreatorsProductEdition/sections/ProductMedia";
23
- import { StatusChip } from "../../CreatorsProductEdition/sections/StatusChip";
24
- import { COLORS, FONTS } from "../../CreatorsProductEdition/sections/tokens";
25
- import { ServiceTools } from "./ServiceTools";
26
-
27
- /*
28
- Panel de imagen de ProductEdition: la imagen activa, las miniaturas y los
29
- datos de la imagen.
30
-
31
- Copia del de CreatorsProductEdition con el diseño de la propuesta de
32
- navegación por servicios: la imagen en una tarjeta con fondo claro y bordes
33
- redondeados, miniaturas cuadradas con borde rosa en la activa, y la cabecera
34
- de los datos sin el botón de ayuda (estatus + versión + guardar).
35
-
36
- Reemplaza a `organisms/ImagePreviewer` (EditionActiveImage + ImageSelector) y a
37
- `organisms/ImageDataTable` (TableHeader + filas), con los mismos props, hecho
38
- con MUI y Swiper. Se estira al ancho de su columna en vez de medir 340px fijos:
39
- en desktop ocupa el panel izquierdo y en tablet/celular va arriba del contenido.
40
- */
41
-
42
- // --- Vista previa ----------------------------------------------------------
43
-
44
- const Preview = ({ imagesArray, activeImage, setActiveImage, setShowModal, currentIndex }) => {
45
- const images = imagesArray?.values ?? [];
46
-
47
- return (
48
- <Box>
49
- <Box
50
- sx={{
51
- position: "relative",
52
- width: "100%",
53
- // Misma proporción que el visor original (340 x 295).
54
- aspectRatio: "340 / 295",
55
- p: 1.5,
56
- boxSizing: "border-box",
57
- borderRadius: "10px",
58
- border: `1px solid ${COLORS.s2}`,
59
- background: `linear-gradient(135deg, ${COLORS.white}, ${COLORS.s1})`,
60
- overflow: "hidden",
61
- }}
62
- >
63
- <ProductMedia src={activeImage?.src} ext={activeImage?.ext} alt={activeImage?.name} />
64
- <Tooltip title="Ver en grande" arrow>
65
- <IconButton
66
- aria-label="Ver en grande"
67
- onClick={() => setShowModal(true)}
68
- sx={{
69
- position: "absolute",
70
- right: 8,
71
- bottom: 8,
72
- width: 32,
73
- height: 32,
74
- p: 0,
75
- bgcolor: COLORS.white,
76
- border: `1px solid ${COLORS.s2}`,
77
- backgroundImage: `url(${openModalIcon})`,
78
- backgroundRepeat: "no-repeat",
79
- backgroundPosition: "center",
80
- backgroundSize: 16,
81
- "&:hover": { bgcolor: COLORS.s2 },
82
- }}
83
- />
84
- </Tooltip>
85
- </Box>
86
-
87
- {images.length > 0 && (
88
- <Box sx={{ mt: 1.25 }}>
89
- <Swiper
90
- modules={[FreeMode, Mousewheel, Keyboard]}
91
- slidesPerView="auto"
92
- spaceBetween={8}
93
- freeMode
94
- mousewheel={{ forceToAxis: true }}
95
- keyboard={{ enabled: true }}
96
- style={{ width: "100%" }}
97
- >
98
- {images.map((image, index) => {
99
- const selected = index === currentIndex;
100
- return (
101
- <SwiperSlide key={`${index}-${image?.name}`} style={{ width: 64 }}>
102
- <ButtonBase
103
- aria-label={`Ver imagen ${index + 1}`}
104
- aria-pressed={selected}
105
- onClick={() => setActiveImage(index)}
106
- sx={{
107
- width: 64,
108
- height: 64,
109
- p: "4px",
110
- boxSizing: "border-box",
111
- borderRadius: "8px",
112
- overflow: "hidden",
113
- border: `${selected ? 2 : 1}px solid ${selected ? COLORS.magenta_s2 : COLORS.s2}`,
114
- bgcolor: COLORS.white,
115
- "&:hover": { borderColor: selected ? COLORS.magenta_s2 : COLORS.s3 },
116
- }}
117
- >
118
- <ProductMedia src={image.src} ext={image.ext} alt={image.altText} thumbnail />
119
- </ButtonBase>
120
- </SwiperSlide>
121
- );
122
- })}
123
- </Swiper>
124
- </Box>
125
- )}
126
- </Box>
127
- );
128
- };
129
-
130
- // --- Datos de la imagen ----------------------------------------------------
131
-
132
- const labelSx = {
133
- fontFamily: FONTS.AvenirNext,
134
- fontSize: 11,
135
- lineHeight: "18px",
136
- color: COLORS.s4,
137
- };
138
-
139
- const DataRow = ({ label, children }) => (
140
- <TableRow sx={{ "& td, & th": { border: 0, py: "2px", px: 0 } }}>
141
- <TableCell component="th" scope="row" sx={labelSx}>
142
- {label}
143
- </TableCell>
144
- <TableCell align="right" sx={{ ...labelSx, pl: 1 }}>
145
- {children}
146
- </TableCell>
147
- </TableRow>
148
- );
149
-
150
- const GrayValue = ({ children }) => (
151
- <Box
152
- component="span"
153
- sx={{
154
- display: "inline-block",
155
- maxWidth: "100%",
156
- px: "3px",
157
- py: "1px",
158
- borderRadius: "3px",
159
- bgcolor: COLORS.s2,
160
- color: COLORS.s5,
161
- fontFamily: FONTS.AvenirNext,
162
- fontSize: 11,
163
- lineHeight: "19px",
164
- overflowWrap: "anywhere",
165
- }}
166
- >
167
- {children}
168
- </Box>
169
- );
170
-
171
- /**
172
- * Valor que se edita con un clic, como `atoms/LabelToInput`: se muestra como
173
- * etiqueta y al hacer clic se vuelve campo de texto; al salir, vuelve a ser
174
- * etiqueta. Cada cambio se avisa con el mismo payload que el original.
175
- */
176
- const EditableValue = ({ defaultValue, onChange }) => {
177
- const [editing, setEditing] = useState(false);
178
- const [value, setValue] = useState(defaultValue === "" ? "-" : defaultValue);
179
- const inputRef = useRef(null);
180
-
181
- useEffect(() => setValue(defaultValue === "" ? "-" : defaultValue), [defaultValue]);
182
- useEffect(() => {
183
- if (editing) inputRef.current?.focus();
184
- }, [editing]);
185
-
186
- if (!editing) {
187
- return (
188
- <ButtonBase
189
- onClick={() => setEditing(true)}
190
- aria-label="Editar valor"
191
- sx={{ borderRadius: "3px", justifyContent: "flex-end", maxWidth: "100%" }}
192
- >
193
- <GrayValue>{value ?? "-"}</GrayValue>
194
- </ButtonBase>
195
- );
196
- }
197
-
198
- return (
199
- <InputBase
200
- inputRef={inputRef}
201
- value={value ?? ""}
202
- onChange={(e) => {
203
- setValue(e.target.value);
204
- onChange(e);
205
- }}
206
- onBlur={() => setEditing(false)}
207
- onKeyDown={(e) => e.key === "Enter" && setEditing(false)}
208
- sx={{
209
- width: 120,
210
- maxWidth: "100%",
211
- fontFamily: FONTS.AvenirNext,
212
- fontSize: 12,
213
- border: `1px solid ${COLORS.s3}`,
214
- borderRadius: "3px",
215
- px: 0.5,
216
- }}
217
- />
218
- );
219
- };
220
-
221
- const ImageData = ({
222
- activeImage,
223
- lists,
224
- retailerSelected,
225
- setImages,
226
- imagesStatus,
227
- onClickSave,
228
- showSaveButton,
229
- version,
230
- setShowVersionSelector,
231
- shotThd,
232
- }) => {
233
- const shotName = lists?.inputs?.find((f) => f.id === +activeImage?.image_id)?.name || "-";
234
- const thdShot =
235
- lists?.imageShotType?.find((f) => f.id === +activeImage?.image_shot_type)?.type_shot || "-";
236
- const size =
237
- activeImage?.width && activeImage.height ? `${activeImage.width}x${activeImage.height}px` : "-";
238
-
239
- return (
240
- <Paper
241
- variant="outlined"
242
- sx={{ mt: { xs: 2, sm: 0, md: 2 }, p: 1.5, borderColor: COLORS.s2, borderRadius: "10px", minWidth: 0 }}
243
- >
244
- <Stack
245
- direction="row"
246
- alignItems="center"
247
- flexWrap="wrap"
248
- gap={1}
249
- sx={{ pb: "5px", borderBottom: `1px solid ${COLORS.s2}` }}
250
- >
251
- <Typography
252
- title={activeImage?.name || "-"}
253
- sx={{
254
- fontFamily: FONTS.Raleway,
255
- fontSize: 14,
256
- lineHeight: "23px",
257
- color: COLORS.s5,
258
- flex: "1 1 90px",
259
- minWidth: 0,
260
- overflow: "hidden",
261
- textOverflow: "ellipsis",
262
- whiteSpace: "nowrap",
263
- }}
264
- >
265
- {activeImage?.name || "-"}
266
- </Typography>
267
- {imagesStatus && imagesStatus !== "-" && <StatusChip status={imagesStatus} oval />}
268
- <ServiceTools
269
- onClickSave={onClickSave}
270
- showSaveButton={showSaveButton}
271
- version={version}
272
- setShowVersionSelector={setShowVersionSelector}
273
- />
274
- </Stack>
275
-
276
- <Table size="small" sx={{ mt: "4px", tableLayout: "fixed" }}>
277
- <TableBody>
278
- <DataRow label="Tipo de toma">
279
- <GrayValue>{shotName}</GrayValue>
280
- </DataRow>
281
- {shotThd && (
282
- <DataRow label="Tipo de imagen THD">
283
- <GrayValue>{thdShot}</GrayValue>
284
- </DataRow>
285
- )}
286
- <DataRow label="Formato">{activeImage?.ext || "-"}</DataRow>
287
- <DataRow label="Tamaño de imagen">{size}</DataRow>
288
- {retailerSelected &&
289
- lists?.attrForImgs?.general?.map((attr, index) => (
290
- <DataRow key={`row-${index}-${attr?.name}`} label={attr?.name || "-"}>
291
- <EditableValue
292
- defaultValue={attr?.value}
293
- onChange={(e) =>
294
- setImages({
295
- action: "changeAttrValue",
296
- value: e.target.value || "-",
297
- id: attr.id,
298
- })
299
- }
300
- />
301
- </DataRow>
302
- ))}
303
- </TableBody>
304
- </Table>
305
- </Paper>
306
- );
307
- };
308
-
309
- // --- Panel completo --------------------------------------------------------
310
-
311
- export const ImagePanel = ({
312
- // vista previa
313
- imagesArray,
314
- activeImage,
315
- currentIndex,
316
- setActiveImage,
317
- setShowModal,
318
- // datos (mismos props que ImageDataTable)
319
- ...dataProps
320
- }) => (
321
- // En tablet la imagen y sus datos van lado a lado: apilados, la imagen
322
- // ocuparía todo el ancho y empujaría el contenido muy abajo.
323
- <Box
324
- sx={{
325
- width: "100%",
326
- minWidth: 0,
327
- display: "grid",
328
- gridTemplateColumns: { xs: "minmax(0, 1fr)", sm: "repeat(2, minmax(0, 1fr))", md: "minmax(0, 1fr)" },
329
- columnGap: 2,
330
- alignItems: "start",
331
- }}
332
- >
333
- <Preview
334
- imagesArray={imagesArray}
335
- activeImage={activeImage}
336
- currentIndex={currentIndex}
337
- setActiveImage={setActiveImage}
338
- setShowModal={setShowModal}
339
- />
340
- <ImageData activeImage={activeImage} {...dataProps} />
341
- </Box>
342
- );