contentoh-components-library 21.6.57 → 21.6.59

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 (49) hide show
  1. package/.claude/settings.local.json +7 -0
  2. package/dist/components/pages/CreatorsProductEdition/CreatorsProductEditionView.js +90 -135
  3. package/dist/components/pages/CreatorsProductEdition/hooks/useCreatorsProductEdition.js +148 -162
  4. package/dist/components/pages/CreatorsProductEdition/mocks/api.fixtures.js +16 -0
  5. package/dist/components/pages/CreatorsProductEdition/mocks/mockApi.js +1 -0
  6. package/dist/components/pages/CreatorsProductEdition/sections/ImageGallery.js +433 -0
  7. package/dist/components/pages/CreatorsProductEdition/sections/NoticeDialog.js +155 -0
  8. package/dist/components/pages/CreatorsProductEdition/sections/RejectDialog.js +201 -0
  9. package/dist/components/pages/CreatorsProductEdition/sections/VersionDrawer.js +615 -0
  10. package/dist/components/pages/CreatorsProductEdition/stories/CreatorsProductEdition.stories.js +3 -31
  11. package/dist/components/pages/CreatorsProductEdition/stories/CreatorsProductEditionAPI.stories.js +55 -30
  12. package/dist/components/pages/CreatorsProductEdition/stories/products.js +923 -0
  13. package/dist/components/pages/CreatorsProductEdition/styles.js +27 -7
  14. package/dist/components/pages/ProviderProductEdition/ProviderProductEdition.stories.js +2 -1
  15. package/dist/components/pages/ProviderProductEdition/context/provider-product-edition.context.js +325 -10
  16. package/dist/components/pages/ProviderProductEdition/context/provider-product-edition.reducer.js +12 -2
  17. package/dist/components/pages/ProviderProductEdition/index.js +486 -231
  18. package/dist/index.js +13 -0
  19. package/package.json +1 -1
  20. package/src/components/pages/CreatorsProductEdition/CreatorsProductEditionView.jsx +777 -0
  21. package/src/components/pages/CreatorsProductEdition/constants.js +21 -0
  22. package/src/components/pages/CreatorsProductEdition/hooks/useCreatorsProductEdition.js +2238 -0
  23. package/src/components/pages/CreatorsProductEdition/index.js +19 -0
  24. package/src/components/pages/CreatorsProductEdition/mocks/api.fixtures.js +357 -0
  25. package/src/components/pages/CreatorsProductEdition/mocks/mockApi.js +144 -0
  26. package/src/components/pages/CreatorsProductEdition/sections/CommentBox.jsx +183 -0
  27. package/src/components/pages/CreatorsProductEdition/sections/ImageGallery.jsx +269 -0
  28. package/src/components/pages/CreatorsProductEdition/sections/ImagePanel.jsx +332 -0
  29. package/src/components/pages/CreatorsProductEdition/sections/ImagesSection.jsx +385 -0
  30. package/src/components/pages/CreatorsProductEdition/sections/NoticeDialog.jsx +111 -0
  31. package/src/components/pages/CreatorsProductEdition/sections/ProductHeader.jsx +305 -0
  32. package/src/components/pages/CreatorsProductEdition/sections/ProductMedia.jsx +101 -0
  33. package/src/components/pages/CreatorsProductEdition/sections/RejectDialog.jsx +125 -0
  34. package/src/components/pages/CreatorsProductEdition/sections/RetailerMenu.jsx +129 -0
  35. package/src/components/pages/CreatorsProductEdition/sections/ServiceActions.jsx +382 -0
  36. package/src/components/pages/CreatorsProductEdition/sections/StatusChip.jsx +37 -0
  37. package/src/components/pages/CreatorsProductEdition/sections/TabsBar.jsx +187 -0
  38. package/src/components/pages/CreatorsProductEdition/sections/VariantsPanel.jsx +311 -0
  39. package/src/components/pages/CreatorsProductEdition/sections/VersionDrawer.jsx +357 -0
  40. package/src/components/pages/CreatorsProductEdition/sections/tokens.js +63 -0
  41. package/src/components/pages/CreatorsProductEdition/stories/CreatorsProductEdition.stories.js +111 -0
  42. package/src/components/pages/CreatorsProductEdition/stories/CreatorsProductEditionAPI.stories.js +85 -0
  43. package/src/components/pages/CreatorsProductEdition/stories/products.js +966 -0
  44. package/src/components/pages/CreatorsProductEdition/styles.js +178 -0
  45. package/src/components/pages/ProviderProductEdition/ProviderProductEdition.stories.js +1 -0
  46. package/src/components/pages/ProviderProductEdition/context/provider-product-edition.context.jsx +201 -0
  47. package/src/components/pages/ProviderProductEdition/context/provider-product-edition.reducer.js +12 -0
  48. package/src/components/pages/ProviderProductEdition/index.js +228 -7
  49. package/src/index.js +1 -0
@@ -0,0 +1,385 @@
1
+ import { Box, Checkbox, Chip, Link, Stack, Tab, Tabs, Typography } from "@mui/material";
2
+
3
+ import { GalleryElement } from "../../../molecules/GalleryElement";
4
+ import { TRAYS } from "../../../molecules/GalleryTrayTabs";
5
+ import { DigitalAssetsTray } from "../../../organisms/DigitalAssetsTray";
6
+
7
+ import { COLORS, FONTS } from "./tokens";
8
+
9
+ /*
10
+ Pestaña Imágenes: bandejas (imágenes / activos digitales), cabecera de la
11
+ lista con "seleccionar todo", la galería con su zona para soltar archivos, y
12
+ el estado vacío.
13
+
14
+ Reemplaza a GalleryTrayTabs, GalleryHeader y el marcado de la galería de la
15
+ vista original. Se reutilizan tal cual las tarjetas (`GalleryElement`, con sus
16
+ selects de tipo y empaque) y la bandeja de activos (`DigitalAssetsTray`): son
17
+ componentes grandes con lógica propia y rehacerlos sería reinventarlos.
18
+
19
+ Dos cosas que las tarjetas exigen y que por eso se conservan:
20
+ - Una casilla global con id `check-all-images`. Cada tarjeta la busca por
21
+ id al marcarse; si no existe, marcar una imagen lanza un error.
22
+ - "Seleccionar todo" marca las casillas de las tarjetas directamente en el
23
+ DOM (`[id^='gallery-element-']`), igual que GalleryHeader.
24
+ */
25
+
26
+ // Columnas de la lista, calcadas de GalleryHeader para que queden alineadas con
27
+ // las filas de GalleryElement.
28
+ const LIST_COLUMNS = [
29
+ { label: "Imagen", pl: 0 },
30
+ { label: "Nombre", pl: "1.75rem" },
31
+ { label: "Tamaño", pl: "2.75rem" },
32
+ { label: "Formato", pl: "4.5rem" },
33
+ { label: "¿Tiene leyenda?", ml: "-1rem" },
34
+ { label: "Tipo de toma" },
35
+ ];
36
+
37
+ // La lista es una tabla de columnas fijas: por debajo de este ancho se desplaza
38
+ // de lado dentro de su caja en vez de aplastar las columnas.
39
+ const LIST_MIN_WIDTH = 760;
40
+
41
+ const TrayTabs = ({ activeTray, setActiveTray, imagesCount, assetsCount }) => {
42
+ const trays = [
43
+ { id: TRAYS.IMAGES, label: "Imágenes", count: imagesCount },
44
+ { id: TRAYS.ASSETS, label: "Activos digitales", count: assetsCount },
45
+ ];
46
+ return (
47
+ <Tabs
48
+ value={activeTray}
49
+ onChange={(_, tray) => setActiveTray?.(tray)}
50
+ variant="scrollable"
51
+ scrollButtons={false}
52
+ TabIndicatorProps={{ sx: { bgcolor: COLORS.color_purple, height: 2 } }}
53
+ sx={{ minHeight: 36, pt: 1 }}
54
+ >
55
+ {trays.map((tray) => {
56
+ const active = tray.id === activeTray;
57
+ return (
58
+ <Tab
59
+ key={tray.id}
60
+ value={tray.id}
61
+ disableRipple
62
+ label={
63
+ <Stack direction="row" alignItems="center" gap="6px">
64
+ {tray.label}
65
+ <Chip
66
+ label={tray.count}
67
+ size="small"
68
+ sx={{
69
+ height: 18,
70
+ minWidth: 20,
71
+ fontFamily: FONTS.Lato,
72
+ fontSize: 11,
73
+ bgcolor: active ? COLORS.color_purple : COLORS.s2,
74
+ color: active ? COLORS.white : COLORS.s5,
75
+ "& .MuiChip-label": { px: "6px" },
76
+ }}
77
+ />
78
+ </Stack>
79
+ }
80
+ sx={{
81
+ minHeight: 36,
82
+ px: "14px",
83
+ py: "6px",
84
+ textTransform: "none",
85
+ fontFamily: FONTS.AvenirNext,
86
+ fontSize: 14,
87
+ letterSpacing: "-0.015em",
88
+ color: COLORS.s4,
89
+ "&.Mui-selected": { color: COLORS.s5, fontWeight: 600 },
90
+ }}
91
+ />
92
+ );
93
+ })}
94
+ </Tabs>
95
+ );
96
+ };
97
+
98
+ /*
99
+ La casilla global se deriva de la selección real y no de
100
+ `state.all_image_checked`: el reducer nunca pone ese valor en true
101
+ (TOGGLE_CHECK_ALL_IMAGES solo llena `selected_images`). En la vista original no
102
+ se nota porque su casilla no está controlada por React y guarda su estado en
103
+ el DOM; controlada con ese valor, se quedaría siempre desmarcada.
104
+ */
105
+ const ListHeader = ({ selectedCount, totalCount, setCheckAll, setSelectedImages }) => {
106
+ const allSelected = totalCount > 0 && selectedCount >= totalCount;
107
+ const someSelected = selectedCount > 0 && !allSelected;
108
+
109
+ const toggleAll = (e) => {
110
+ const checked = e.target.checked;
111
+ document
112
+ .querySelectorAll("[id^='gallery-element-']")
113
+ .forEach((chk) => (chk.checked = checked));
114
+ setCheckAll(checked);
115
+ if (!checked) setSelectedImages([]);
116
+ };
117
+
118
+ return (
119
+ <Box sx={{ display: "flex", alignItems: "center", width: "100%", py: "10px" }}>
120
+ <Box sx={{ width: "5.25%", minWidth: 36 }}>
121
+ <Checkbox
122
+ size="small"
123
+ checked={allSelected}
124
+ indeterminate={someSelected}
125
+ onChange={toggleAll}
126
+ inputProps={{ id: "check-all-images", "aria-label": "Seleccionar todas las imágenes" }}
127
+ sx={{
128
+ p: "4px",
129
+ color: COLORS.s3,
130
+ "&.Mui-checked, &.MuiCheckbox-indeterminate": { color: COLORS.magenta_s2 },
131
+ }}
132
+ />
133
+ </Box>
134
+ <Box
135
+ sx={{
136
+ width: "90%",
137
+ display: "grid",
138
+ gridTemplateColumns: "0.1fr 1fr 1fr 1fr 1fr 1fr",
139
+ }}
140
+ >
141
+ {LIST_COLUMNS.map((column) => (
142
+ <Typography
143
+ key={column.label}
144
+ sx={{
145
+ pl: column.pl,
146
+ ml: column.ml,
147
+ textAlign: "center",
148
+ fontFamily: FONTS.AvenirNext,
149
+ fontWeight: 600,
150
+ fontSize: 14,
151
+ lineHeight: "21px",
152
+ letterSpacing: "-0.015em",
153
+ color: COLORS.s5,
154
+ }}
155
+ >
156
+ {column.label}
157
+ </Typography>
158
+ ))}
159
+ </Box>
160
+ </Box>
161
+ );
162
+ };
163
+
164
+ export const ImagesSection = ({
165
+ imagesValues,
166
+ selectedImages,
167
+ imageLayout,
168
+ // bandejas
169
+ enableDigitalAssets,
170
+ currentTray,
171
+ setActiveTray,
172
+ digitalAssets,
173
+ unsavedAssets,
174
+ selectedAssets,
175
+ setSelectedAssets,
176
+ setAllAssetsChecked,
177
+ // permisos
178
+ canEdit,
179
+ // imágenes
180
+ setCheckAll,
181
+ setSelectedImages,
182
+ onChangeCurrentImage,
183
+ onUpdateImage,
184
+ onSelectImage,
185
+ // zona para soltar archivos (react-dropzone, desde el hook)
186
+ getRootProps,
187
+ getInputProps,
188
+ isDragActive,
189
+ openFilePicker,
190
+ }) => {
191
+ const images = imagesValues?.values ?? [];
192
+
193
+ const imageInputs = imagesValues?.inputs?.map((e) => ({
194
+ value: e?.id,
195
+ name: e?.name,
196
+ required: e?.required,
197
+ active: images.some((value) => value?.image_id === e?.id),
198
+ }));
199
+ const imageType = imagesValues?.imageType?.map((e) => ({ value: e?.id, name: e?.name }));
200
+ const imagePackagingType = imagesValues?.imagePackagingType?.map((e) => ({
201
+ value: e?.id,
202
+ name: e?.name,
203
+ }));
204
+
205
+ const cards = images.map((image, index) => (
206
+ <GalleryElement
207
+ id={"gallery-element-" + index}
208
+ index={index + "-" + image.name}
209
+ key={index}
210
+ image={image}
211
+ number={index}
212
+ gridLayout={imageLayout}
213
+ imageType={imageType}
214
+ imagePackagingType={imagePackagingType}
215
+ imageInputs={imageInputs}
216
+ selectedImages={selectedImages}
217
+ setCheckAll={setCheckAll}
218
+ changeImage={onUpdateImage}
219
+ setSelectedImages={onSelectImage}
220
+ updateApprovedInputsImages={(item) => console.log("updateApprovedInputsImages", item)}
221
+ onSelect={() => onChangeCurrentImage(index)}
222
+ />
223
+ ));
224
+
225
+ return (
226
+ <Box sx={{ minWidth: 0 }}>
227
+ {enableDigitalAssets && (
228
+ <TrayTabs
229
+ activeTray={currentTray}
230
+ setActiveTray={setActiveTray}
231
+ imagesCount={images.length}
232
+ assetsCount={digitalAssets?.assets?.length ?? 0}
233
+ />
234
+ )}
235
+
236
+ {currentTray === TRAYS.ASSETS && (
237
+ <Box sx={{ position: "relative", minWidth: 0 }}>
238
+ <DigitalAssetsTray
239
+ {...digitalAssets}
240
+ unsaved={unsavedAssets}
241
+ gridLayout={imageLayout}
242
+ readOnly={!canEdit}
243
+ selectedAssets={selectedAssets}
244
+ setSelectedAssets={setSelectedAssets}
245
+ setCheckAll={setAllAssetsChecked}
246
+ />
247
+ </Box>
248
+ )}
249
+
250
+ {currentTray === TRAYS.IMAGES && (
251
+ <Box sx={{ overflowX: imageLayout ? "visible" : "auto", WebkitOverflowScrolling: "touch" }}>
252
+ <Box sx={{ minWidth: imageLayout || images.length === 0 ? 0 : LIST_MIN_WIDTH }}>
253
+ {!imageLayout && images.length > 0 ? (
254
+ <ListHeader
255
+ selectedCount={selectedImages?.length ?? 0}
256
+ totalCount={images.length}
257
+ setCheckAll={setCheckAll}
258
+ setSelectedImages={setSelectedImages}
259
+ />
260
+ ) : (
261
+ /* Sin cabecera (cuadrícula o sin imágenes) las tarjetas siguen
262
+ buscando `#check-all-images` al marcarse. En la vista original,
263
+ en cuadrícula no existe: marcar una imagen lanza un TypeError y
264
+ la selección no se guarda. Esta casilla oculta lo evita. */
265
+ <input type="checkbox" id="check-all-images" hidden readOnly aria-hidden="true" />
266
+ )}
267
+
268
+ <Box sx={{ position: "relative" }}>
269
+ <Box
270
+ {...getRootProps()}
271
+ sx={{
272
+ position: "relative",
273
+ minHeight: { xs: 160, md: 240 },
274
+ borderRadius: "8px",
275
+ border: "2px dashed transparent",
276
+ transition: "all 0.3s ease",
277
+ ...(isDragActive && {
278
+ bgcolor: `rgba(${COLORS.rgb_color_pink}, 0.05)`,
279
+ borderColor: COLORS.color_pink,
280
+ }),
281
+ }}
282
+ >
283
+ <input {...getInputProps()} />
284
+ {isDragActive && (
285
+ <Box
286
+ sx={{
287
+ position: "absolute",
288
+ inset: 0,
289
+ zIndex: 10,
290
+ display: "flex",
291
+ alignItems: "center",
292
+ justifyContent: "center",
293
+ bgcolor: `rgba(${COLORS.rgb_color_pink}, 0.1)`,
294
+ borderRadius: "8px",
295
+ pointerEvents: "none",
296
+ }}
297
+ >
298
+ <Typography
299
+ sx={{
300
+ fontFamily: FONTS.AvenirNext,
301
+ fontSize: { xs: 18, sm: 24 },
302
+ fontWeight: 600,
303
+ color: COLORS.color_pink,
304
+ textAlign: "center",
305
+ p: 2.5,
306
+ bgcolor: COLORS.white,
307
+ borderRadius: "8px",
308
+ boxShadow: "0 4px 12px rgba(0, 0, 0, 0.15)",
309
+ }}
310
+ >
311
+ Suelta las imágenes aquí
312
+ </Typography>
313
+ </Box>
314
+ )}
315
+
316
+ <Box
317
+ component="aside"
318
+ sx={
319
+ imageLayout
320
+ ? {
321
+ display: "grid",
322
+ gridTemplateColumns: {
323
+ xs: "repeat(2, minmax(0, 1fr))",
324
+ sm: "repeat(auto-fill, 179px)",
325
+ },
326
+ justifyContent: "center",
327
+ gap: { xs: 1, sm: "15px" },
328
+ p: { xs: "12px 0", sm: "20px" },
329
+ opacity: isDragActive ? 0.3 : 1,
330
+ pointerEvents: isDragActive ? "none" : "auto",
331
+ // En celular dos tarjetas por fila: las de 179px fijos
332
+ // se estiran a su columna.
333
+ "@media (max-width: 599.95px)": {
334
+ // !important: el estilo propio de la tarjeta (dos
335
+ // clases) le gana a este selector.
336
+ "& > *": { width: "100% !important" },
337
+ "& img, & video": {
338
+ width: "100% !important",
339
+ height: "auto !important",
340
+ aspectRatio: "1 / 1",
341
+ },
342
+ },
343
+ }
344
+ : { opacity: isDragActive ? 0.3 : 1 }
345
+ }
346
+ >
347
+ {cards}
348
+ </Box>
349
+
350
+ {images.length === 0 && (
351
+ <Box
352
+ sx={{
353
+ position: { xs: "static", md: "absolute" },
354
+ top: "50%",
355
+ left: "50%",
356
+ transform: { md: "translate(-50%, -50%)" },
357
+ width: { xs: "100%", md: "80%" },
358
+ maxWidth: 500,
359
+ mx: "auto",
360
+ p: { xs: "24px 8px", md: "40px" },
361
+ textAlign: "center",
362
+ boxSizing: "border-box",
363
+ }}
364
+ >
365
+ <Typography sx={{ fontSize: 18, color: "#666", mb: 2 }}>
366
+ Este producto no tiene imágenes
367
+ </Typography>
368
+ {canEdit && (
369
+ <Typography sx={{ fontSize: 14, color: "#999", mb: 2.5 }}>
370
+ Arrastra las imágenes aquí o{" "}
371
+ <Link component="button" type="button" onClick={openFilePicker}>
372
+ haz clic para abrir el explorador de archivos
373
+ </Link>
374
+ </Typography>
375
+ )}
376
+ </Box>
377
+ )}
378
+ </Box>
379
+ </Box>
380
+ </Box>
381
+ </Box>
382
+ )}
383
+ </Box>
384
+ );
385
+ };
@@ -0,0 +1,111 @@
1
+ import { Box, Button, Dialog, IconButton, Stack, Typography } from "@mui/material";
2
+ import CloseIcon from "@mui/icons-material/Close";
3
+
4
+ import { COLORS, FONTS } from "./tokens";
5
+
6
+ /*
7
+ Aviso de la edición de producto (guardado, error, confirmaciones).
8
+
9
+ Reemplaza a `atoms/GenericModal` + ScreenHeader con el mismo `state.modal`
10
+ (`image`, `title`, `message`, `buttons`) y el mismo diseño oscuro, hecho con
11
+ el Dialog de MUI: en celular no se sale de la pantalla (el original medía
12
+ 500px fijos) y se cierra con Escape o tocando fuera.
13
+
14
+ Los botones llegan con los `buttonType` de GeneralButton; se traducen a los
15
+ dos estilos que usa la pantalla: contorno blanco o relleno magenta.
16
+ */
17
+
18
+ const pill = {
19
+ minWidth: 140,
20
+ height: 40,
21
+ borderRadius: "20px",
22
+ textTransform: "none",
23
+ fontFamily: FONTS.Raleway_500,
24
+ fontSize: 14,
25
+ boxShadow: "none",
26
+ "&:hover": { boxShadow: "none" },
27
+ };
28
+
29
+ const buttonSx = (buttonType = "") =>
30
+ buttonType.includes("white")
31
+ ? {
32
+ ...pill,
33
+ color: COLORS.s2,
34
+ border: `1px solid ${COLORS.white}`,
35
+ "&:hover": { ...pill["&:hover"], bgcolor: "rgba(255,255,255,0.08)" },
36
+ }
37
+ : {
38
+ ...pill,
39
+ color: COLORS.white,
40
+ bgcolor: COLORS.magenta_s2,
41
+ "&:hover": { ...pill["&:hover"], bgcolor: COLORS.secondary_magenta },
42
+ };
43
+
44
+ export const NoticeDialog = ({ modal = {}, onClose }) => {
45
+ const { show, image, title, message, buttons } = modal;
46
+
47
+ return (
48
+ <Dialog
49
+ open={Boolean(show)}
50
+ onClose={onClose}
51
+ fullWidth
52
+ maxWidth={false}
53
+ PaperProps={{
54
+ sx: {
55
+ width: "min(500px, calc(100% - 32px))",
56
+ m: 2,
57
+ bgcolor: COLORS.deep_gray,
58
+ borderRadius: "15px",
59
+ p: { xs: "40px 20px 30px", sm: "40px" },
60
+ textAlign: "center",
61
+ color: COLORS.white,
62
+ },
63
+ }}
64
+ >
65
+ <IconButton
66
+ aria-label="Cerrar"
67
+ onClick={onClose}
68
+ size="small"
69
+ sx={{ position: "absolute", top: 12, right: 12, color: COLORS.white }}
70
+ >
71
+ <CloseIcon fontSize="small" />
72
+ </IconButton>
73
+
74
+ <Stack alignItems="center" gap={2.5}>
75
+ {image && <Box component="img" src={image} alt="" sx={{ maxWidth: 64, maxHeight: 64 }} />}
76
+ {title && (
77
+ <Typography sx={{ fontFamily: FONTS.Raleway_600, fontSize: 18, lineHeight: 1.3 }}>
78
+ {title}
79
+ </Typography>
80
+ )}
81
+ {message && (
82
+ <Typography
83
+ sx={{
84
+ fontFamily: FONTS.Raleway,
85
+ fontSize: 16,
86
+ lineHeight: 1.4,
87
+ whiteSpace: "pre-wrap",
88
+ overflowWrap: "anywhere",
89
+ }}
90
+ >
91
+ {message}
92
+ </Typography>
93
+ )}
94
+ {buttons?.length > 0 && (
95
+ <Stack
96
+ direction={{ xs: "column", sm: "row" }}
97
+ justifyContent="center"
98
+ gap={2}
99
+ sx={{ mt: 1, width: { xs: "100%", sm: "auto" } }}
100
+ >
101
+ {buttons.map((button, index) => (
102
+ <Button key={index} onClick={button.action} sx={buttonSx(button.buttonType)}>
103
+ {button.text}
104
+ </Button>
105
+ ))}
106
+ </Stack>
107
+ )}
108
+ </Stack>
109
+ </Dialog>
110
+ );
111
+ };