contentoh-components-library 21.6.58 → 21.6.60
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/dist/components/pages/CreatorsProductEdition/CreatorsProductEditionView.js +90 -135
- package/dist/components/pages/CreatorsProductEdition/hooks/useCreatorsProductEdition.js +148 -162
- package/dist/components/pages/CreatorsProductEdition/mocks/api.fixtures.js +16 -0
- package/dist/components/pages/CreatorsProductEdition/mocks/mockApi.js +1 -0
- package/dist/components/pages/CreatorsProductEdition/sections/ImageGallery.js +433 -0
- package/dist/components/pages/CreatorsProductEdition/sections/NoticeDialog.js +155 -0
- package/dist/components/pages/CreatorsProductEdition/sections/RejectDialog.js +201 -0
- package/dist/components/pages/CreatorsProductEdition/sections/VersionDrawer.js +615 -0
- package/dist/components/pages/CreatorsProductEdition/stories/CreatorsProductEdition.stories.js +3 -31
- package/dist/components/pages/CreatorsProductEdition/stories/CreatorsProductEditionAPI.stories.js +55 -30
- package/dist/components/pages/CreatorsProductEdition/stories/products.js +923 -0
- package/dist/components/pages/CreatorsProductEdition/styles.js +27 -7
- package/dist/components/pages/RetailerProductEdition/context/provider-product-edition.context.js +2 -2
- package/dist/index.js +13 -0
- package/package.json +1 -1
- package/src/components/pages/CreatorsProductEdition/CreatorsProductEditionView.jsx +777 -0
- package/src/components/pages/CreatorsProductEdition/constants.js +21 -0
- package/src/components/pages/CreatorsProductEdition/hooks/useCreatorsProductEdition.js +2238 -0
- package/src/components/pages/CreatorsProductEdition/index.js +19 -0
- package/src/components/pages/CreatorsProductEdition/mocks/api.fixtures.js +357 -0
- package/src/components/pages/CreatorsProductEdition/mocks/mockApi.js +144 -0
- package/src/components/pages/CreatorsProductEdition/sections/CommentBox.jsx +183 -0
- package/src/components/pages/CreatorsProductEdition/sections/ImageGallery.jsx +269 -0
- package/src/components/pages/CreatorsProductEdition/sections/ImagePanel.jsx +332 -0
- package/src/components/pages/CreatorsProductEdition/sections/ImagesSection.jsx +385 -0
- package/src/components/pages/CreatorsProductEdition/sections/NoticeDialog.jsx +111 -0
- package/src/components/pages/CreatorsProductEdition/sections/ProductHeader.jsx +305 -0
- package/src/components/pages/CreatorsProductEdition/sections/ProductMedia.jsx +101 -0
- package/src/components/pages/CreatorsProductEdition/sections/RejectDialog.jsx +125 -0
- package/src/components/pages/CreatorsProductEdition/sections/RetailerMenu.jsx +129 -0
- package/src/components/pages/CreatorsProductEdition/sections/ServiceActions.jsx +382 -0
- package/src/components/pages/CreatorsProductEdition/sections/StatusChip.jsx +37 -0
- package/src/components/pages/CreatorsProductEdition/sections/TabsBar.jsx +187 -0
- package/src/components/pages/CreatorsProductEdition/sections/VariantsPanel.jsx +311 -0
- package/src/components/pages/CreatorsProductEdition/sections/VersionDrawer.jsx +357 -0
- package/src/components/pages/CreatorsProductEdition/sections/tokens.js +63 -0
- package/src/components/pages/CreatorsProductEdition/stories/CreatorsProductEdition.stories.js +111 -0
- package/src/components/pages/CreatorsProductEdition/stories/CreatorsProductEditionAPI.stories.js +85 -0
- package/src/components/pages/CreatorsProductEdition/stories/products.js +966 -0
- package/src/components/pages/CreatorsProductEdition/styles.js +178 -0
- package/src/components/pages/RetailerProductEdition/context/provider-product-edition.context.jsx +6 -2
- package/src/index.js +1 -0
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
import { useEffect, useState } from "react";
|
|
2
|
+
import {
|
|
3
|
+
Box,
|
|
4
|
+
ButtonBase,
|
|
5
|
+
Dialog,
|
|
6
|
+
IconButton,
|
|
7
|
+
ListItemIcon,
|
|
8
|
+
ListItemText,
|
|
9
|
+
Menu,
|
|
10
|
+
MenuItem,
|
|
11
|
+
Stack,
|
|
12
|
+
Tooltip,
|
|
13
|
+
Typography,
|
|
14
|
+
useMediaQuery,
|
|
15
|
+
} from "@mui/material";
|
|
16
|
+
import CloseIcon from "@mui/icons-material/Close";
|
|
17
|
+
import ChevronLeftIcon from "@mui/icons-material/ChevronLeft";
|
|
18
|
+
import ChevronRightIcon from "@mui/icons-material/ChevronRight";
|
|
19
|
+
import { Swiper, SwiperSlide } from "swiper/react";
|
|
20
|
+
import { FreeMode, Mousewheel } from "swiper";
|
|
21
|
+
import "swiper/swiper.min.css";
|
|
22
|
+
|
|
23
|
+
import acceptIcon from "../../../../assets/images/generalButton/acceptIcon.svg";
|
|
24
|
+
import rejectIcon from "../../../../assets/images/generalButton/rejectIcon.svg";
|
|
25
|
+
import nullIcon from "../../../../assets/images/generalButton/nullIcon.svg";
|
|
26
|
+
|
|
27
|
+
import { ProductMedia } from "./ProductMedia";
|
|
28
|
+
import { COLORS, FONTS } from "./tokens";
|
|
29
|
+
|
|
30
|
+
/*
|
|
31
|
+
Galería de imágenes en grande.
|
|
32
|
+
|
|
33
|
+
Reemplaza a `organisms/ProductImageModal` con los mismos props y hecho con el
|
|
34
|
+
Dialog de MUI. El original ponía la imagen y un panel de 375px mínimo lado a
|
|
35
|
+
lado, así que en celular no cabía. Aquí:
|
|
36
|
+
- en desktop, imagen a la izquierda y datos + miniaturas a la derecha;
|
|
37
|
+
- en celular, pantalla completa: imagen arriba y datos debajo.
|
|
38
|
+
Se navega con las flechas del teclado o con los botones sobre la imagen.
|
|
39
|
+
|
|
40
|
+
La imagen grande es la original de S3 (`srcDB`), como en el original; las
|
|
41
|
+
miniaturas usan la versión reducida (`src`). Una imagen recién agregada aún
|
|
42
|
+
no tiene `srcDB`, así que se usa su `src` local.
|
|
43
|
+
*/
|
|
44
|
+
|
|
45
|
+
const bucketUrl = (image) =>
|
|
46
|
+
image?.srcDB
|
|
47
|
+
? `https://${process.env.REACT_APP_IMAGES_BUCKET}.s3.amazonaws.com/${image.srcDB}`
|
|
48
|
+
: image?.src;
|
|
49
|
+
|
|
50
|
+
const VALIDATION_ICONS = { approve: acceptIcon, reject: rejectIcon };
|
|
51
|
+
|
|
52
|
+
const DataRow = ({ label, value }) => (
|
|
53
|
+
<Stack direction="row" justifyContent="space-between" gap={2} sx={{ py: "3px" }}>
|
|
54
|
+
<Typography sx={{ fontFamily: FONTS.AvenirNext, fontSize: 12, color: COLORS.s3 }}>{label}</Typography>
|
|
55
|
+
<Typography
|
|
56
|
+
sx={{ fontFamily: FONTS.AvenirNext, fontSize: 12, color: COLORS.white, textAlign: "right", overflowWrap: "anywhere" }}
|
|
57
|
+
>
|
|
58
|
+
{value || "-"}
|
|
59
|
+
</Typography>
|
|
60
|
+
</Stack>
|
|
61
|
+
);
|
|
62
|
+
|
|
63
|
+
const arrowSx = {
|
|
64
|
+
position: "absolute",
|
|
65
|
+
top: "50%",
|
|
66
|
+
transform: "translateY(-50%)",
|
|
67
|
+
bgcolor: "rgba(40, 31, 51, 0.55)",
|
|
68
|
+
color: COLORS.white,
|
|
69
|
+
"&:hover": { bgcolor: "rgba(40, 31, 51, 0.8)" },
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
export const ImageGallery = ({
|
|
73
|
+
open = true,
|
|
74
|
+
images = {},
|
|
75
|
+
initialIndex = 0,
|
|
76
|
+
setShowModal,
|
|
77
|
+
sendToFacilitator,
|
|
78
|
+
approveRejectButtons = false,
|
|
79
|
+
}) => {
|
|
80
|
+
const values = images?.values ?? [];
|
|
81
|
+
const [index, setIndex] = useState(initialIndex);
|
|
82
|
+
const [validation, setValidation] = useState(null);
|
|
83
|
+
const [menuAnchor, setMenuAnchor] = useState(null);
|
|
84
|
+
const isPhone = useMediaQuery("(max-width:599.95px)");
|
|
85
|
+
|
|
86
|
+
useEffect(() => {
|
|
87
|
+
if (open) setIndex(Math.min(initialIndex, Math.max(values.length - 1, 0)));
|
|
88
|
+
}, [open, initialIndex]); // eslint-disable-line react-hooks/exhaustive-deps
|
|
89
|
+
|
|
90
|
+
const image = values[index];
|
|
91
|
+
const hasMany = values.length > 1;
|
|
92
|
+
const go = (step) => setIndex((i) => (i + step + values.length) % values.length);
|
|
93
|
+
const close = () => setShowModal(false);
|
|
94
|
+
|
|
95
|
+
const onKeyDown = (e) => {
|
|
96
|
+
if (!hasMany) return;
|
|
97
|
+
if (e.key === "ArrowRight") go(1);
|
|
98
|
+
if (e.key === "ArrowLeft") go(-1);
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
const validate = (kind) => {
|
|
102
|
+
setMenuAnchor(null);
|
|
103
|
+
setValidation(kind);
|
|
104
|
+
sendToFacilitator && sendToFacilitator(kind === "approve" ? "A" : "R");
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
const shotName = images?.inputs?.find((f) => f.id === +image?.image_id)?.name;
|
|
108
|
+
const size = image?.width && image?.height ? `${image.width}x${image.height}px` : null;
|
|
109
|
+
|
|
110
|
+
return (
|
|
111
|
+
<Dialog
|
|
112
|
+
open={open}
|
|
113
|
+
onClose={close}
|
|
114
|
+
onKeyDown={onKeyDown}
|
|
115
|
+
fullScreen={isPhone}
|
|
116
|
+
maxWidth={false}
|
|
117
|
+
PaperProps={{
|
|
118
|
+
sx: {
|
|
119
|
+
m: { sm: 2 },
|
|
120
|
+
width: { sm: "min(1200px, calc(100% - 32px))" },
|
|
121
|
+
height: { sm: "min(760px, calc(100% - 32px))" },
|
|
122
|
+
maxHeight: "none",
|
|
123
|
+
borderRadius: { xs: 0, sm: "4px" },
|
|
124
|
+
overflow: "hidden",
|
|
125
|
+
display: "flex",
|
|
126
|
+
flexDirection: { xs: "column", sm: "row" },
|
|
127
|
+
bgcolor: COLORS.deep_gray,
|
|
128
|
+
},
|
|
129
|
+
}}
|
|
130
|
+
>
|
|
131
|
+
{/* Imagen */}
|
|
132
|
+
<Box
|
|
133
|
+
sx={{
|
|
134
|
+
position: "relative",
|
|
135
|
+
flex: { xs: "0 0 auto", sm: "1 1 auto" },
|
|
136
|
+
height: { xs: "min(60vh, 100vw)", sm: "auto" },
|
|
137
|
+
minWidth: 0,
|
|
138
|
+
bgcolor: COLORS.white,
|
|
139
|
+
}}
|
|
140
|
+
>
|
|
141
|
+
{image ? (
|
|
142
|
+
<ProductMedia key={index} src={bucketUrl(image)} ext={image.ext} alt={image.name} />
|
|
143
|
+
) : (
|
|
144
|
+
<Stack alignItems="center" justifyContent="center" sx={{ height: "100%" }}>
|
|
145
|
+
<Typography sx={{ color: COLORS.s4 }}>Sin imágenes</Typography>
|
|
146
|
+
</Stack>
|
|
147
|
+
)}
|
|
148
|
+
{hasMany && (
|
|
149
|
+
<>
|
|
150
|
+
<IconButton aria-label="Imagen anterior" onClick={() => go(-1)} sx={{ ...arrowSx, left: 8 }}>
|
|
151
|
+
<ChevronLeftIcon />
|
|
152
|
+
</IconButton>
|
|
153
|
+
<IconButton aria-label="Imagen siguiente" onClick={() => go(1)} sx={{ ...arrowSx, right: 8 }}>
|
|
154
|
+
<ChevronRightIcon />
|
|
155
|
+
</IconButton>
|
|
156
|
+
</>
|
|
157
|
+
)}
|
|
158
|
+
</Box>
|
|
159
|
+
|
|
160
|
+
{/* Datos y miniaturas */}
|
|
161
|
+
<Stack
|
|
162
|
+
sx={{
|
|
163
|
+
flex: { xs: "1 1 auto", sm: "0 0 375px" },
|
|
164
|
+
minHeight: 0,
|
|
165
|
+
p: "15px",
|
|
166
|
+
gap: 2,
|
|
167
|
+
overflowY: "auto",
|
|
168
|
+
color: COLORS.white,
|
|
169
|
+
}}
|
|
170
|
+
>
|
|
171
|
+
<Stack direction="row" alignItems="center" gap={1}>
|
|
172
|
+
<Typography
|
|
173
|
+
title={image?.name}
|
|
174
|
+
sx={{
|
|
175
|
+
flex: 1,
|
|
176
|
+
minWidth: 0,
|
|
177
|
+
fontFamily: FONTS.Raleway,
|
|
178
|
+
fontSize: 14,
|
|
179
|
+
overflow: "hidden",
|
|
180
|
+
textOverflow: "ellipsis",
|
|
181
|
+
whiteSpace: "nowrap",
|
|
182
|
+
}}
|
|
183
|
+
>
|
|
184
|
+
{image?.name || "-"}
|
|
185
|
+
</Typography>
|
|
186
|
+
{hasMany && (
|
|
187
|
+
<Typography sx={{ fontFamily: FONTS.AvenirNext, fontSize: 12, color: COLORS.s3 }}>
|
|
188
|
+
{index + 1}/{values.length}
|
|
189
|
+
</Typography>
|
|
190
|
+
)}
|
|
191
|
+
{approveRejectButtons && (
|
|
192
|
+
<>
|
|
193
|
+
<Tooltip title="Validar" arrow>
|
|
194
|
+
<IconButton
|
|
195
|
+
aria-label="Validar"
|
|
196
|
+
onClick={(e) => setMenuAnchor(e.currentTarget)}
|
|
197
|
+
sx={{ width: 30, height: 30, p: 0, border: `1px solid ${COLORS.s2}`, borderRadius: "50%" }}
|
|
198
|
+
>
|
|
199
|
+
<Box component="img" src={VALIDATION_ICONS[validation] ?? nullIcon} alt="" sx={{ width: 16 }} />
|
|
200
|
+
</IconButton>
|
|
201
|
+
</Tooltip>
|
|
202
|
+
<Menu anchorEl={menuAnchor} open={Boolean(menuAnchor)} onClose={() => setMenuAnchor(null)}>
|
|
203
|
+
<MenuItem onClick={() => validate("approve")}>
|
|
204
|
+
<ListItemIcon>
|
|
205
|
+
<Box component="img" src={acceptIcon} alt="" sx={{ width: 16 }} />
|
|
206
|
+
</ListItemIcon>
|
|
207
|
+
<ListItemText>Validado</ListItemText>
|
|
208
|
+
</MenuItem>
|
|
209
|
+
<MenuItem onClick={() => validate("reject")}>
|
|
210
|
+
<ListItemIcon>
|
|
211
|
+
<Box component="img" src={rejectIcon} alt="" sx={{ width: 16 }} />
|
|
212
|
+
</ListItemIcon>
|
|
213
|
+
<ListItemText>Rechazado</ListItemText>
|
|
214
|
+
</MenuItem>
|
|
215
|
+
</Menu>
|
|
216
|
+
</>
|
|
217
|
+
)}
|
|
218
|
+
<IconButton
|
|
219
|
+
aria-label="Cerrar"
|
|
220
|
+
onClick={close}
|
|
221
|
+
sx={{ width: 30, height: 30, color: COLORS.white, border: `1px solid ${COLORS.s2}`, borderRadius: "3px" }}
|
|
222
|
+
>
|
|
223
|
+
<CloseIcon fontSize="small" />
|
|
224
|
+
</IconButton>
|
|
225
|
+
</Stack>
|
|
226
|
+
|
|
227
|
+
<Box sx={{ borderTop: `1px solid ${COLORS.s4}`, pt: 1 }}>
|
|
228
|
+
<DataRow label="Tipo de toma" value={shotName} />
|
|
229
|
+
<DataRow label="Formato" value={image?.ext} />
|
|
230
|
+
<DataRow label="Tamaño de imagen" value={size} />
|
|
231
|
+
</Box>
|
|
232
|
+
|
|
233
|
+
{hasMany && (
|
|
234
|
+
<Box sx={{ mt: { sm: "auto" } }}>
|
|
235
|
+
<Swiper
|
|
236
|
+
modules={[FreeMode, Mousewheel]}
|
|
237
|
+
slidesPerView="auto"
|
|
238
|
+
spaceBetween={6}
|
|
239
|
+
freeMode
|
|
240
|
+
mousewheel={{ forceToAxis: true }}
|
|
241
|
+
style={{ width: "100%" }}
|
|
242
|
+
>
|
|
243
|
+
{values.map((item, i) => (
|
|
244
|
+
<SwiperSlide key={`${i}-${item?.name}`} style={{ width: 85 }}>
|
|
245
|
+
<ButtonBase
|
|
246
|
+
aria-label={`Ver imagen ${i + 1}`}
|
|
247
|
+
aria-pressed={i === index}
|
|
248
|
+
onClick={() => setIndex(i)}
|
|
249
|
+
sx={{
|
|
250
|
+
width: 85,
|
|
251
|
+
height: 55,
|
|
252
|
+
borderRadius: "3px",
|
|
253
|
+
overflow: "hidden",
|
|
254
|
+
bgcolor: COLORS.white,
|
|
255
|
+
outline: i === index ? `2px solid ${COLORS.magenta_s2}` : "none",
|
|
256
|
+
outlineOffset: -2,
|
|
257
|
+
}}
|
|
258
|
+
>
|
|
259
|
+
<ProductMedia src={item.src} ext={item.ext} alt={item.altText} thumbnail />
|
|
260
|
+
</ButtonBase>
|
|
261
|
+
</SwiperSlide>
|
|
262
|
+
))}
|
|
263
|
+
</Swiper>
|
|
264
|
+
</Box>
|
|
265
|
+
)}
|
|
266
|
+
</Stack>
|
|
267
|
+
</Dialog>
|
|
268
|
+
);
|
|
269
|
+
};
|
|
@@ -0,0 +1,332 @@
|
|
|
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 "./ProductMedia";
|
|
23
|
+
import { ServiceActions } from "./ServiceActions";
|
|
24
|
+
import { COLORS, FONTS } from "./tokens";
|
|
25
|
+
|
|
26
|
+
/*
|
|
27
|
+
Panel de imagen: la imagen activa, las miniaturas y los datos de la imagen.
|
|
28
|
+
|
|
29
|
+
Reemplaza a `organisms/ImagePreviewer` (EditionActiveImage + ImageSelector) y a
|
|
30
|
+
`organisms/ImageDataTable` (TableHeader + filas), con los mismos props, hecho
|
|
31
|
+
con MUI y Swiper. Se estira al ancho de su columna en vez de medir 340px fijos:
|
|
32
|
+
en desktop ocupa el panel izquierdo y en tablet/celular va arriba del contenido.
|
|
33
|
+
*/
|
|
34
|
+
|
|
35
|
+
// --- Vista previa ----------------------------------------------------------
|
|
36
|
+
|
|
37
|
+
const Preview = ({ imagesArray, activeImage, setActiveImage, setShowModal, currentIndex }) => {
|
|
38
|
+
const images = imagesArray?.values ?? [];
|
|
39
|
+
|
|
40
|
+
return (
|
|
41
|
+
<Box>
|
|
42
|
+
<Box
|
|
43
|
+
sx={{
|
|
44
|
+
position: "relative",
|
|
45
|
+
width: "100%",
|
|
46
|
+
// Misma proporción que el visor original (340 x 295).
|
|
47
|
+
aspectRatio: "340 / 295",
|
|
48
|
+
bgcolor: COLORS.white,
|
|
49
|
+
}}
|
|
50
|
+
>
|
|
51
|
+
<ProductMedia src={activeImage?.src} ext={activeImage?.ext} alt={activeImage?.name} />
|
|
52
|
+
<Tooltip title="Ver en grande" arrow>
|
|
53
|
+
<IconButton
|
|
54
|
+
aria-label="Ver en grande"
|
|
55
|
+
onClick={() => setShowModal(true)}
|
|
56
|
+
sx={{
|
|
57
|
+
position: "absolute",
|
|
58
|
+
right: 4,
|
|
59
|
+
bottom: 4,
|
|
60
|
+
width: 30,
|
|
61
|
+
height: 30,
|
|
62
|
+
p: 0,
|
|
63
|
+
backgroundImage: `url(${openModalIcon})`,
|
|
64
|
+
backgroundRepeat: "no-repeat",
|
|
65
|
+
backgroundPosition: "center",
|
|
66
|
+
}}
|
|
67
|
+
/>
|
|
68
|
+
</Tooltip>
|
|
69
|
+
</Box>
|
|
70
|
+
|
|
71
|
+
{images.length > 0 && (
|
|
72
|
+
<Box sx={{ mt: 1 }}>
|
|
73
|
+
<Swiper
|
|
74
|
+
modules={[FreeMode, Mousewheel, Keyboard]}
|
|
75
|
+
slidesPerView="auto"
|
|
76
|
+
spaceBetween={4}
|
|
77
|
+
freeMode
|
|
78
|
+
mousewheel={{ forceToAxis: true }}
|
|
79
|
+
keyboard={{ enabled: true }}
|
|
80
|
+
style={{ width: "100%" }}
|
|
81
|
+
>
|
|
82
|
+
{images.map((image, index) => {
|
|
83
|
+
const selected = index === currentIndex;
|
|
84
|
+
return (
|
|
85
|
+
<SwiperSlide key={`${index}-${image?.name}`} style={{ width: 85 }}>
|
|
86
|
+
<ButtonBase
|
|
87
|
+
aria-label={`Ver imagen ${index + 1}`}
|
|
88
|
+
aria-pressed={selected}
|
|
89
|
+
onClick={() => setActiveImage(index)}
|
|
90
|
+
sx={{
|
|
91
|
+
width: 85,
|
|
92
|
+
height: 55,
|
|
93
|
+
borderRadius: "3px",
|
|
94
|
+
overflow: "hidden",
|
|
95
|
+
outline: selected ? `2px solid ${COLORS.magenta_s2}` : "none",
|
|
96
|
+
outlineOffset: -2,
|
|
97
|
+
bgcolor: COLORS.white,
|
|
98
|
+
}}
|
|
99
|
+
>
|
|
100
|
+
<ProductMedia src={image.src} ext={image.ext} alt={image.altText} thumbnail />
|
|
101
|
+
</ButtonBase>
|
|
102
|
+
</SwiperSlide>
|
|
103
|
+
);
|
|
104
|
+
})}
|
|
105
|
+
</Swiper>
|
|
106
|
+
</Box>
|
|
107
|
+
)}
|
|
108
|
+
</Box>
|
|
109
|
+
);
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
// --- Datos de la imagen ----------------------------------------------------
|
|
113
|
+
|
|
114
|
+
const labelSx = {
|
|
115
|
+
fontFamily: FONTS.AvenirNext,
|
|
116
|
+
fontSize: 11,
|
|
117
|
+
lineHeight: "18px",
|
|
118
|
+
color: COLORS.s4,
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
const DataRow = ({ label, children }) => (
|
|
122
|
+
<TableRow sx={{ "& td, & th": { border: 0, py: "2px", px: 0 } }}>
|
|
123
|
+
<TableCell component="th" scope="row" sx={labelSx}>
|
|
124
|
+
{label}
|
|
125
|
+
</TableCell>
|
|
126
|
+
<TableCell align="right" sx={{ ...labelSx, pl: 1 }}>
|
|
127
|
+
{children}
|
|
128
|
+
</TableCell>
|
|
129
|
+
</TableRow>
|
|
130
|
+
);
|
|
131
|
+
|
|
132
|
+
const GrayValue = ({ children }) => (
|
|
133
|
+
<Box
|
|
134
|
+
component="span"
|
|
135
|
+
sx={{
|
|
136
|
+
display: "inline-block",
|
|
137
|
+
maxWidth: "100%",
|
|
138
|
+
px: "3px",
|
|
139
|
+
py: "1px",
|
|
140
|
+
borderRadius: "3px",
|
|
141
|
+
bgcolor: COLORS.s2,
|
|
142
|
+
color: COLORS.s5,
|
|
143
|
+
fontFamily: FONTS.AvenirNext,
|
|
144
|
+
fontSize: 11,
|
|
145
|
+
lineHeight: "19px",
|
|
146
|
+
overflowWrap: "anywhere",
|
|
147
|
+
}}
|
|
148
|
+
>
|
|
149
|
+
{children}
|
|
150
|
+
</Box>
|
|
151
|
+
);
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Valor que se edita con un clic, como `atoms/LabelToInput`: se muestra como
|
|
155
|
+
* etiqueta y al hacer clic se vuelve campo de texto; al salir, vuelve a ser
|
|
156
|
+
* etiqueta. Cada cambio se avisa con el mismo payload que el original.
|
|
157
|
+
*/
|
|
158
|
+
const EditableValue = ({ defaultValue, onChange }) => {
|
|
159
|
+
const [editing, setEditing] = useState(false);
|
|
160
|
+
const [value, setValue] = useState(defaultValue === "" ? "-" : defaultValue);
|
|
161
|
+
const inputRef = useRef(null);
|
|
162
|
+
|
|
163
|
+
useEffect(() => setValue(defaultValue === "" ? "-" : defaultValue), [defaultValue]);
|
|
164
|
+
useEffect(() => {
|
|
165
|
+
if (editing) inputRef.current?.focus();
|
|
166
|
+
}, [editing]);
|
|
167
|
+
|
|
168
|
+
if (!editing) {
|
|
169
|
+
return (
|
|
170
|
+
<ButtonBase
|
|
171
|
+
onClick={() => setEditing(true)}
|
|
172
|
+
aria-label="Editar valor"
|
|
173
|
+
sx={{ borderRadius: "3px", justifyContent: "flex-end", maxWidth: "100%" }}
|
|
174
|
+
>
|
|
175
|
+
<GrayValue>{value ?? "-"}</GrayValue>
|
|
176
|
+
</ButtonBase>
|
|
177
|
+
);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
return (
|
|
181
|
+
<InputBase
|
|
182
|
+
inputRef={inputRef}
|
|
183
|
+
value={value ?? ""}
|
|
184
|
+
onChange={(e) => {
|
|
185
|
+
setValue(e.target.value);
|
|
186
|
+
onChange(e);
|
|
187
|
+
}}
|
|
188
|
+
onBlur={() => setEditing(false)}
|
|
189
|
+
onKeyDown={(e) => e.key === "Enter" && setEditing(false)}
|
|
190
|
+
sx={{
|
|
191
|
+
width: 120,
|
|
192
|
+
maxWidth: "100%",
|
|
193
|
+
fontFamily: FONTS.AvenirNext,
|
|
194
|
+
fontSize: 12,
|
|
195
|
+
border: `1px solid ${COLORS.s3}`,
|
|
196
|
+
borderRadius: "3px",
|
|
197
|
+
px: 0.5,
|
|
198
|
+
}}
|
|
199
|
+
/>
|
|
200
|
+
);
|
|
201
|
+
};
|
|
202
|
+
|
|
203
|
+
const ImageData = ({
|
|
204
|
+
activeImage,
|
|
205
|
+
lists,
|
|
206
|
+
retailerSelected,
|
|
207
|
+
setImages,
|
|
208
|
+
assignationsImages,
|
|
209
|
+
imagesStatus,
|
|
210
|
+
setAssignation,
|
|
211
|
+
isRetailer,
|
|
212
|
+
onClickSave,
|
|
213
|
+
showSaveButton,
|
|
214
|
+
version,
|
|
215
|
+
setShowVersionSelector,
|
|
216
|
+
shotThd,
|
|
217
|
+
}) => {
|
|
218
|
+
const shotName = lists?.inputs?.find((f) => f.id === +activeImage?.image_id)?.name || "-";
|
|
219
|
+
const thdShot =
|
|
220
|
+
lists?.imageShotType?.find((f) => f.id === +activeImage?.image_shot_type)?.type_shot || "-";
|
|
221
|
+
const size =
|
|
222
|
+
activeImage?.width && activeImage.height ? `${activeImage.width}x${activeImage.height}px` : "-";
|
|
223
|
+
|
|
224
|
+
return (
|
|
225
|
+
<Paper
|
|
226
|
+
variant="outlined"
|
|
227
|
+
sx={{ mt: { xs: 2, sm: 0, md: 2 }, p: "10px", borderColor: COLORS.s2, borderRadius: "3px", minWidth: 0 }}
|
|
228
|
+
>
|
|
229
|
+
<Stack
|
|
230
|
+
direction="row"
|
|
231
|
+
alignItems="center"
|
|
232
|
+
flexWrap="wrap"
|
|
233
|
+
gap={1}
|
|
234
|
+
sx={{ pb: "5px", borderBottom: `1px solid ${COLORS.s2}` }}
|
|
235
|
+
>
|
|
236
|
+
<Typography
|
|
237
|
+
title={activeImage?.name || "-"}
|
|
238
|
+
sx={{
|
|
239
|
+
fontFamily: FONTS.Raleway,
|
|
240
|
+
fontSize: 14,
|
|
241
|
+
lineHeight: "23px",
|
|
242
|
+
color: COLORS.s5,
|
|
243
|
+
flex: "1 1 90px",
|
|
244
|
+
minWidth: 0,
|
|
245
|
+
overflow: "hidden",
|
|
246
|
+
textOverflow: "ellipsis",
|
|
247
|
+
whiteSpace: "nowrap",
|
|
248
|
+
}}
|
|
249
|
+
>
|
|
250
|
+
{activeImage?.name || "-"}
|
|
251
|
+
</Typography>
|
|
252
|
+
{/* Igual que en la vista original: aquí no hay panel de asignación
|
|
253
|
+
(canAssign no se pasa) ni botones de imágenes. */}
|
|
254
|
+
<ServiceActions
|
|
255
|
+
status={imagesStatus}
|
|
256
|
+
assig={assignationsImages}
|
|
257
|
+
setAssignation={setAssignation}
|
|
258
|
+
isRetailer={isRetailer}
|
|
259
|
+
onClickSave={onClickSave}
|
|
260
|
+
showSaveButton={showSaveButton}
|
|
261
|
+
version={version}
|
|
262
|
+
setShowVersionSelector={setShowVersionSelector}
|
|
263
|
+
/>
|
|
264
|
+
</Stack>
|
|
265
|
+
|
|
266
|
+
<Table size="small" sx={{ mt: "4px", tableLayout: "fixed" }}>
|
|
267
|
+
<TableBody>
|
|
268
|
+
<DataRow label="Tipo de toma">
|
|
269
|
+
<GrayValue>{shotName}</GrayValue>
|
|
270
|
+
</DataRow>
|
|
271
|
+
{shotThd && (
|
|
272
|
+
<DataRow label="Tipo de imagen THD">
|
|
273
|
+
<GrayValue>{thdShot}</GrayValue>
|
|
274
|
+
</DataRow>
|
|
275
|
+
)}
|
|
276
|
+
<DataRow label="Formato">{activeImage?.ext || "-"}</DataRow>
|
|
277
|
+
<DataRow label="Tamaño de imagen">{size}</DataRow>
|
|
278
|
+
{retailerSelected &&
|
|
279
|
+
lists?.attrForImgs?.general?.map((attr, index) => (
|
|
280
|
+
<DataRow key={`row-${index}-${attr?.name}`} label={attr?.name || "-"}>
|
|
281
|
+
<EditableValue
|
|
282
|
+
defaultValue={attr?.value}
|
|
283
|
+
onChange={(e) =>
|
|
284
|
+
setImages({
|
|
285
|
+
action: "changeAttrValue",
|
|
286
|
+
value: e.target.value || "-",
|
|
287
|
+
id: attr.id,
|
|
288
|
+
})
|
|
289
|
+
}
|
|
290
|
+
/>
|
|
291
|
+
</DataRow>
|
|
292
|
+
))}
|
|
293
|
+
</TableBody>
|
|
294
|
+
</Table>
|
|
295
|
+
</Paper>
|
|
296
|
+
);
|
|
297
|
+
};
|
|
298
|
+
|
|
299
|
+
// --- Panel completo --------------------------------------------------------
|
|
300
|
+
|
|
301
|
+
export const ImagePanel = ({
|
|
302
|
+
// vista previa
|
|
303
|
+
imagesArray,
|
|
304
|
+
activeImage,
|
|
305
|
+
currentIndex,
|
|
306
|
+
setActiveImage,
|
|
307
|
+
setShowModal,
|
|
308
|
+
// datos (mismos props que ImageDataTable)
|
|
309
|
+
...dataProps
|
|
310
|
+
}) => (
|
|
311
|
+
// En tablet la imagen y sus datos van lado a lado: apilados, la imagen
|
|
312
|
+
// ocuparía todo el ancho y empujaría el contenido muy abajo.
|
|
313
|
+
<Box
|
|
314
|
+
sx={{
|
|
315
|
+
width: "100%",
|
|
316
|
+
minWidth: 0,
|
|
317
|
+
display: "grid",
|
|
318
|
+
gridTemplateColumns: { xs: "minmax(0, 1fr)", sm: "repeat(2, minmax(0, 1fr))", md: "minmax(0, 1fr)" },
|
|
319
|
+
columnGap: 2,
|
|
320
|
+
alignItems: "start",
|
|
321
|
+
}}
|
|
322
|
+
>
|
|
323
|
+
<Preview
|
|
324
|
+
imagesArray={imagesArray}
|
|
325
|
+
activeImage={activeImage}
|
|
326
|
+
currentIndex={currentIndex}
|
|
327
|
+
setActiveImage={setActiveImage}
|
|
328
|
+
setShowModal={setShowModal}
|
|
329
|
+
/>
|
|
330
|
+
<ImageData activeImage={activeImage} {...dataProps} />
|
|
331
|
+
</Box>
|
|
332
|
+
);
|