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.
- package/.claude/settings.local.json +7 -0
- 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/ProviderProductEdition/ProviderProductEdition.stories.js +2 -1
- package/dist/components/pages/ProviderProductEdition/context/provider-product-edition.context.js +325 -10
- package/dist/components/pages/ProviderProductEdition/context/provider-product-edition.reducer.js +12 -2
- package/dist/components/pages/ProviderProductEdition/index.js +486 -231
- 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/ProviderProductEdition/ProviderProductEdition.stories.js +1 -0
- package/src/components/pages/ProviderProductEdition/context/provider-product-edition.context.jsx +201 -0
- package/src/components/pages/ProviderProductEdition/context/provider-product-edition.reducer.js +12 -0
- package/src/components/pages/ProviderProductEdition/index.js +228 -7
- package/src/index.js +1 -0
|
@@ -0,0 +1,382 @@
|
|
|
1
|
+
import { useEffect, useState } from "react";
|
|
2
|
+
import {
|
|
3
|
+
Avatar,
|
|
4
|
+
AvatarGroup,
|
|
5
|
+
Box,
|
|
6
|
+
Button,
|
|
7
|
+
IconButton,
|
|
8
|
+
List,
|
|
9
|
+
ListItemAvatar,
|
|
10
|
+
ListItemButton,
|
|
11
|
+
ListItemText,
|
|
12
|
+
MobileStepper,
|
|
13
|
+
Popover,
|
|
14
|
+
Stack,
|
|
15
|
+
Tooltip,
|
|
16
|
+
Typography,
|
|
17
|
+
} from "@mui/material";
|
|
18
|
+
|
|
19
|
+
import { getProfilePicture } from "../../../../global-files/data";
|
|
20
|
+
import { useAiProductEdition } from "../../../../contexts/AiProductEdition";
|
|
21
|
+
|
|
22
|
+
import defaultProfile from "../../../../assets/images/defaultImages/defaultProfileImage.svg";
|
|
23
|
+
import InfoIcon from "../../../../assets/images/sliderToolTip/infoIcon.svg";
|
|
24
|
+
import Slide1 from "../../../../assets/images/sliderToolTip/slide21.svg";
|
|
25
|
+
import Slide2 from "../../../../assets/images/sliderToolTip/slide22.svg";
|
|
26
|
+
import Slide3 from "../../../../assets/images/sliderToolTip/slide23.svg";
|
|
27
|
+
import saveIcon from "../../../../assets/images/generalButton/saveIcon.svg";
|
|
28
|
+
import saveIconHover from "../../../../assets/images/generalButton/saveIconHover.svg";
|
|
29
|
+
import deleteIcon from "../../../../assets/images/generalButton/deleteIcon.svg";
|
|
30
|
+
import deleteIconHover from "../../../../assets/images/generalButton/deleteIconHover.svg";
|
|
31
|
+
import downloadIcon from "../../../../assets/images/generalButton/downloadIcon.svg";
|
|
32
|
+
import rowsLayoutIcon from "../../../../assets/images/generalButton/rowLayout.svg";
|
|
33
|
+
import gridLayoutIcon from "../../../../assets/images/generalButton/gridLayout.svg";
|
|
34
|
+
|
|
35
|
+
import { StatusChip } from "./StatusChip";
|
|
36
|
+
import { COLORS, FONTS } from "./tokens";
|
|
37
|
+
|
|
38
|
+
/*
|
|
39
|
+
Acciones de un servicio: ayuda, versión, estatus, guardar, (en imágenes)
|
|
40
|
+
borrar/descargar/cambiar vista, y asignaciones con su panel.
|
|
41
|
+
|
|
42
|
+
Es lo que hacía `molecules/StatusAsignationInfo`, que la vista original usa en
|
|
43
|
+
DOS lugares: junto a las pestañas y en la cabecera de los datos de la imagen.
|
|
44
|
+
Aquí también se comparte: TabsBar e ImagePanel usan este mismo componente.
|
|
45
|
+
*/
|
|
46
|
+
|
|
47
|
+
const SLIDES = [
|
|
48
|
+
{
|
|
49
|
+
slide: Slide1,
|
|
50
|
+
title: "Valida tu producto en cada activo digital",
|
|
51
|
+
description:
|
|
52
|
+
"Al solicitar a Content-oh! podrás validar la información, imágenes y documentación de tu producto, esto hará más sencillo que puedas aprobar o rechazar el contenido.",
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
slide: Slide2,
|
|
56
|
+
title: "Aprueba o rechaza con un clic",
|
|
57
|
+
description:
|
|
58
|
+
"Cuando termines de revisar tu información, elige si aproebas o rechazas, esto enviara una notificiación a nuestro equipo, para continuar o corregir el producto.",
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
slide: Slide3,
|
|
62
|
+
title: "Estatus de tu producto en el proceso",
|
|
63
|
+
description:
|
|
64
|
+
"La mayor parte del proceso podrás identificar que estatus tiene tu producto, esto dara visibilidad a todo tu equipo para saber la etapa se encuentra.",
|
|
65
|
+
},
|
|
66
|
+
];
|
|
67
|
+
|
|
68
|
+
const HelpSlides = () => {
|
|
69
|
+
const [anchor, setAnchor] = useState(null);
|
|
70
|
+
const [step, setStep] = useState(0);
|
|
71
|
+
const last = step === SLIDES.length - 1;
|
|
72
|
+
const close = () => {
|
|
73
|
+
setAnchor(null);
|
|
74
|
+
setStep(0);
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
return (
|
|
78
|
+
<>
|
|
79
|
+
<IconButton
|
|
80
|
+
aria-label="Ayuda"
|
|
81
|
+
size="small"
|
|
82
|
+
onClick={(e) => setAnchor(e.currentTarget)}
|
|
83
|
+
sx={{ p: 0 }}
|
|
84
|
+
>
|
|
85
|
+
<img src={InfoIcon} alt="" width={30} height={30} style={{ filter: "brightness(.35)" }} />
|
|
86
|
+
</IconButton>
|
|
87
|
+
<Popover
|
|
88
|
+
open={Boolean(anchor)}
|
|
89
|
+
anchorEl={anchor}
|
|
90
|
+
onClose={close}
|
|
91
|
+
anchorOrigin={{ vertical: "bottom", horizontal: "center" }}
|
|
92
|
+
transformOrigin={{ vertical: "top", horizontal: "center" }}
|
|
93
|
+
PaperProps={{ sx: { width: 300, maxWidth: "calc(100vw - 24px)", p: 2, mt: 1 } }}
|
|
94
|
+
>
|
|
95
|
+
<Box
|
|
96
|
+
component="img"
|
|
97
|
+
src={SLIDES[step].slide}
|
|
98
|
+
alt=""
|
|
99
|
+
sx={{ width: "100%", height: 150, objectFit: "contain" }}
|
|
100
|
+
/>
|
|
101
|
+
<Typography sx={{ fontFamily: FONTS.Raleway_700, fontSize: 14, color: COLORS.s5, mt: 1 }}>
|
|
102
|
+
{SLIDES[step].title}
|
|
103
|
+
</Typography>
|
|
104
|
+
<Typography sx={{ fontFamily: FONTS.AvenirNext, fontSize: 12, color: COLORS.s4, mt: 0.5 }}>
|
|
105
|
+
{SLIDES[step].description}
|
|
106
|
+
</Typography>
|
|
107
|
+
<MobileStepper
|
|
108
|
+
variant="dots"
|
|
109
|
+
steps={SLIDES.length}
|
|
110
|
+
position="static"
|
|
111
|
+
activeStep={step}
|
|
112
|
+
sx={{
|
|
113
|
+
bgcolor: "transparent",
|
|
114
|
+
px: 0,
|
|
115
|
+
mt: 1,
|
|
116
|
+
"& .MuiMobileStepper-dotActive": { bgcolor: COLORS.magenta_s2 },
|
|
117
|
+
}}
|
|
118
|
+
backButton={
|
|
119
|
+
<Button
|
|
120
|
+
size="small"
|
|
121
|
+
disabled={step === 0}
|
|
122
|
+
onClick={() => setStep((s) => s - 1)}
|
|
123
|
+
sx={{ color: COLORS.s5 }}
|
|
124
|
+
>
|
|
125
|
+
Anterior
|
|
126
|
+
</Button>
|
|
127
|
+
}
|
|
128
|
+
nextButton={
|
|
129
|
+
<Button
|
|
130
|
+
size="small"
|
|
131
|
+
onClick={() => (last ? close() : setStep((s) => s + 1))}
|
|
132
|
+
sx={{ color: COLORS.magenta_s2 }}
|
|
133
|
+
>
|
|
134
|
+
{last ? "Cerrar" : "Siguiente"}
|
|
135
|
+
</Button>
|
|
136
|
+
}
|
|
137
|
+
/>
|
|
138
|
+
</Popover>
|
|
139
|
+
</>
|
|
140
|
+
);
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
/** Botón redondo de 25px con el ícono de siempre y su versión "hover". */
|
|
144
|
+
const RoundIcon = ({
|
|
145
|
+
label,
|
|
146
|
+
icon,
|
|
147
|
+
hoverIcon,
|
|
148
|
+
onClick,
|
|
149
|
+
bordered = COLORS.s3,
|
|
150
|
+
hoverBg = COLORS.magenta_s2,
|
|
151
|
+
size = 15,
|
|
152
|
+
}) => (
|
|
153
|
+
<Tooltip title={label} arrow>
|
|
154
|
+
<IconButton
|
|
155
|
+
aria-label={label}
|
|
156
|
+
onClick={onClick}
|
|
157
|
+
sx={{
|
|
158
|
+
width: 25,
|
|
159
|
+
height: 25,
|
|
160
|
+
p: 0,
|
|
161
|
+
border: bordered ? `1px solid ${bordered}` : "none",
|
|
162
|
+
backgroundImage: `url(${icon})`,
|
|
163
|
+
backgroundRepeat: "no-repeat",
|
|
164
|
+
backgroundPosition: "center",
|
|
165
|
+
backgroundSize: size,
|
|
166
|
+
"&:hover": {
|
|
167
|
+
backgroundColor: hoverBg,
|
|
168
|
+
borderColor: hoverBg,
|
|
169
|
+
...(hoverIcon ? { backgroundImage: `url(${hoverIcon})` } : {}),
|
|
170
|
+
},
|
|
171
|
+
}}
|
|
172
|
+
/>
|
|
173
|
+
</Tooltip>
|
|
174
|
+
);
|
|
175
|
+
|
|
176
|
+
export const ServiceActions = ({
|
|
177
|
+
status = "",
|
|
178
|
+
imagesSection = false,
|
|
179
|
+
imageLayout,
|
|
180
|
+
setImageLayout,
|
|
181
|
+
assig,
|
|
182
|
+
setAssignation,
|
|
183
|
+
isRetailer,
|
|
184
|
+
downloadImages,
|
|
185
|
+
askToDeleteImages,
|
|
186
|
+
onClickSave,
|
|
187
|
+
showSaveButton,
|
|
188
|
+
canAssign,
|
|
189
|
+
version,
|
|
190
|
+
setShowVersionSelector,
|
|
191
|
+
// Cambia cuando el panel de asignación debe cerrarse (p. ej., otra pestaña).
|
|
192
|
+
resetKey,
|
|
193
|
+
}) => {
|
|
194
|
+
const isImagesTab = imagesSection;
|
|
195
|
+
const { inputsUsingAi } = useAiProductEdition();
|
|
196
|
+
|
|
197
|
+
const [assignAnchor, setAssignAnchor] = useState(null);
|
|
198
|
+
const [assignationType, setAssignationType] = useState("facilitator");
|
|
199
|
+
const collaborators = assig?.collaborators?.[assignationType];
|
|
200
|
+
|
|
201
|
+
useEffect(() => setAssignAnchor(null), [resetKey]);
|
|
202
|
+
|
|
203
|
+
const save = () => {
|
|
204
|
+
if (Object.values(inputsUsingAi ?? {}).some((input) => !!input?.using)) {
|
|
205
|
+
console.warn("Para guardar se debe de dejar de usar la generación con IA");
|
|
206
|
+
return;
|
|
207
|
+
}
|
|
208
|
+
onClickSave && onClickSave();
|
|
209
|
+
};
|
|
210
|
+
|
|
211
|
+
return (
|
|
212
|
+
<>
|
|
213
|
+
<Stack
|
|
214
|
+
direction="row"
|
|
215
|
+
alignItems="center"
|
|
216
|
+
flexWrap="wrap"
|
|
217
|
+
gap="10px"
|
|
218
|
+
justifyContent="flex-end"
|
|
219
|
+
sx={{ flex: "0 1 auto", ml: "auto" }}
|
|
220
|
+
>
|
|
221
|
+
<HelpSlides />
|
|
222
|
+
{version && (
|
|
223
|
+
<Tooltip title="Cambiar de versión" arrow>
|
|
224
|
+
<Button
|
|
225
|
+
onClick={(e) => {
|
|
226
|
+
e.stopPropagation();
|
|
227
|
+
setShowVersionSelector(true);
|
|
228
|
+
}}
|
|
229
|
+
sx={{
|
|
230
|
+
minWidth: 25,
|
|
231
|
+
width: 25,
|
|
232
|
+
height: 25,
|
|
233
|
+
p: 0,
|
|
234
|
+
borderRadius: "50%",
|
|
235
|
+
border: `1px solid ${COLORS.color_purple}`,
|
|
236
|
+
color: COLORS.color_purple,
|
|
237
|
+
fontFamily: FONTS.AvenirNext,
|
|
238
|
+
fontSize: 11,
|
|
239
|
+
lineHeight: "25px",
|
|
240
|
+
}}
|
|
241
|
+
>
|
|
242
|
+
V{version}
|
|
243
|
+
</Button>
|
|
244
|
+
</Tooltip>
|
|
245
|
+
)}
|
|
246
|
+
{status !== "-" && status !== "" && <StatusChip status={status} oval />}
|
|
247
|
+
{showSaveButton && (
|
|
248
|
+
<RoundIcon
|
|
249
|
+
label="Guardar"
|
|
250
|
+
icon={saveIcon}
|
|
251
|
+
hoverIcon={saveIconHover}
|
|
252
|
+
bordered={COLORS.color_gray}
|
|
253
|
+
hoverBg={COLORS.color_pink}
|
|
254
|
+
size={undefined}
|
|
255
|
+
onClick={save}
|
|
256
|
+
/>
|
|
257
|
+
)}
|
|
258
|
+
{isImagesTab && (
|
|
259
|
+
<>
|
|
260
|
+
{showSaveButton && (
|
|
261
|
+
<RoundIcon
|
|
262
|
+
label="Borrar seleccionadas"
|
|
263
|
+
icon={deleteIcon}
|
|
264
|
+
hoverIcon={deleteIconHover}
|
|
265
|
+
onClick={() => askToDeleteImages && askToDeleteImages()}
|
|
266
|
+
/>
|
|
267
|
+
)}
|
|
268
|
+
<RoundIcon
|
|
269
|
+
label="Descargar"
|
|
270
|
+
icon={downloadIcon}
|
|
271
|
+
onClick={() => downloadImages && downloadImages()}
|
|
272
|
+
/>
|
|
273
|
+
<Tooltip title={imageLayout ? "Ver como lista" : "Ver como cuadrícula"} arrow>
|
|
274
|
+
<IconButton
|
|
275
|
+
aria-label={imageLayout ? "Ver como lista" : "Ver como cuadrícula"}
|
|
276
|
+
onClick={() => setImageLayout && setImageLayout(!imageLayout)}
|
|
277
|
+
sx={{ width: 24, height: 24, p: 0 }}
|
|
278
|
+
>
|
|
279
|
+
<img
|
|
280
|
+
src={imageLayout ? rowsLayoutIcon : gridLayoutIcon}
|
|
281
|
+
alt=""
|
|
282
|
+
width={24}
|
|
283
|
+
height={24}
|
|
284
|
+
/>
|
|
285
|
+
</IconButton>
|
|
286
|
+
</Tooltip>
|
|
287
|
+
</>
|
|
288
|
+
)}
|
|
289
|
+
<Stack direction="row" alignItems="center" gap="6px">
|
|
290
|
+
<AvatarGroup
|
|
291
|
+
max={4}
|
|
292
|
+
sx={{ "& .MuiAvatar-root": { width: 20, height: 20, fontSize: 10, border: 0 } }}
|
|
293
|
+
>
|
|
294
|
+
{assig?.assignations?.map((assignation, index) => (
|
|
295
|
+
<Avatar
|
|
296
|
+
key={`${index}-${assignation.id}`}
|
|
297
|
+
src={getProfilePicture(assignation.id, 20, 20)}
|
|
298
|
+
alt="Persona asignada"
|
|
299
|
+
onClick={(e) => {
|
|
300
|
+
e.stopPropagation();
|
|
301
|
+
setAssignationType(assignation?.collaboratorType);
|
|
302
|
+
setAssignAnchor(e.currentTarget);
|
|
303
|
+
}}
|
|
304
|
+
sx={{ cursor: "pointer" }}
|
|
305
|
+
>
|
|
306
|
+
<img src={defaultProfile} alt="" width={20} height={20} />
|
|
307
|
+
</Avatar>
|
|
308
|
+
))}
|
|
309
|
+
</AvatarGroup>
|
|
310
|
+
<Typography sx={{ fontFamily: FONTS.AvenirNext, fontSize: 14, color: COLORS.color_gray }}>
|
|
311
|
+
Asig.
|
|
312
|
+
</Typography>
|
|
313
|
+
</Stack>
|
|
314
|
+
</Stack>
|
|
315
|
+
|
|
316
|
+
<Popover
|
|
317
|
+
open={Boolean(assignAnchor) && canAssign}
|
|
318
|
+
anchorEl={assignAnchor}
|
|
319
|
+
onClose={() => setAssignAnchor(null)}
|
|
320
|
+
anchorOrigin={{ vertical: "bottom", horizontal: "right" }}
|
|
321
|
+
transformOrigin={{ vertical: "top", horizontal: "right" }}
|
|
322
|
+
PaperProps={{ sx: { width: 260, maxWidth: "calc(100vw - 24px)", mt: 1, p: 1.5 } }}
|
|
323
|
+
>
|
|
324
|
+
<Stack direction="row" alignItems="center" gap={1} sx={{ mb: 1 }}>
|
|
325
|
+
<Typography sx={{ fontFamily: FONTS.AvenirNext, fontSize: 13, color: COLORS.s4 }}>
|
|
326
|
+
Estatus
|
|
327
|
+
</Typography>
|
|
328
|
+
<StatusChip status={status} oval />
|
|
329
|
+
</Stack>
|
|
330
|
+
{collaborators?.length ? (
|
|
331
|
+
<List dense disablePadding sx={{ maxHeight: 280, overflowY: "auto" }}>
|
|
332
|
+
{collaborators.map((person, index) => (
|
|
333
|
+
<ListItemButton
|
|
334
|
+
key={`${index}-${person.name}`}
|
|
335
|
+
onClick={() => {
|
|
336
|
+
setAssignation && setAssignation(assignationType, person?.id_user);
|
|
337
|
+
setAssignAnchor(null);
|
|
338
|
+
}}
|
|
339
|
+
sx={{ borderRadius: "3px" }}
|
|
340
|
+
>
|
|
341
|
+
<ListItemAvatar sx={{ minWidth: 40 }}>
|
|
342
|
+
<Avatar
|
|
343
|
+
src={getProfilePicture(person?.id_user, 40, 40)}
|
|
344
|
+
sx={{ width: 26, height: 26 }}
|
|
345
|
+
>
|
|
346
|
+
<img src={defaultProfile} alt="" width={26} height={26} />
|
|
347
|
+
</Avatar>
|
|
348
|
+
</ListItemAvatar>
|
|
349
|
+
<ListItemText
|
|
350
|
+
primary={person.name}
|
|
351
|
+
primaryTypographyProps={{
|
|
352
|
+
sx: { fontFamily: FONTS.AvenirNext, fontSize: 14, color: COLORS.s5 },
|
|
353
|
+
}}
|
|
354
|
+
/>
|
|
355
|
+
</ListItemButton>
|
|
356
|
+
))}
|
|
357
|
+
</List>
|
|
358
|
+
) : (
|
|
359
|
+
<Typography sx={{ fontFamily: FONTS.AvenirNext, fontSize: 13, color: COLORS.s4, py: 1 }}>
|
|
360
|
+
Sin personas para asignar
|
|
361
|
+
</Typography>
|
|
362
|
+
)}
|
|
363
|
+
{isRetailer === 1 && (
|
|
364
|
+
<List dense disablePadding sx={{ mt: 1, borderTop: `1px solid ${COLORS.s2}`, pt: 1 }}>
|
|
365
|
+
<ListItemText
|
|
366
|
+
primary="Solicitar a"
|
|
367
|
+
secondary="Proveedor"
|
|
368
|
+
sx={{ px: 2 }}
|
|
369
|
+
primaryTypographyProps={{ sx: { fontSize: 14, color: COLORS.s5 } }}
|
|
370
|
+
/>
|
|
371
|
+
<ListItemText
|
|
372
|
+
primary="Solicitar a"
|
|
373
|
+
secondary="Content-oh!"
|
|
374
|
+
sx={{ px: 2 }}
|
|
375
|
+
primaryTypographyProps={{ sx: { fontSize: 14, color: COLORS.s5 } }}
|
|
376
|
+
/>
|
|
377
|
+
</List>
|
|
378
|
+
)}
|
|
379
|
+
</Popover>
|
|
380
|
+
</>
|
|
381
|
+
);
|
|
382
|
+
};
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { Chip, Tooltip } from "@mui/material";
|
|
2
|
+
|
|
3
|
+
import { COLORS, FONTS, statusColor, statusName } from "./tokens";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Etiqueta de estatus ("RA/AS", "AA"...). Mismo color y tamaño que
|
|
7
|
+
* `atoms/Status`; el nombre completo va en el tooltip. `oval` es la variante
|
|
8
|
+
* redondeada de la barra de pestañas.
|
|
9
|
+
*/
|
|
10
|
+
export const StatusChip = ({ status, oval = false }) => {
|
|
11
|
+
const label = status ?? "NS";
|
|
12
|
+
const chip = (
|
|
13
|
+
<Chip
|
|
14
|
+
label={label}
|
|
15
|
+
size="small"
|
|
16
|
+
sx={{
|
|
17
|
+
height: 20,
|
|
18
|
+
borderRadius: oval ? "10px" : "3px",
|
|
19
|
+
backgroundColor: statusColor(status),
|
|
20
|
+
color: COLORS.white,
|
|
21
|
+
fontFamily: FONTS.Lato,
|
|
22
|
+
fontSize: 12,
|
|
23
|
+
cursor: "default",
|
|
24
|
+
"& .MuiChip-label": { px: "10px", lineHeight: "20px" },
|
|
25
|
+
}}
|
|
26
|
+
/>
|
|
27
|
+
);
|
|
28
|
+
|
|
29
|
+
const name = statusName(status);
|
|
30
|
+
return name ? (
|
|
31
|
+
<Tooltip title={name} arrow placement="top">
|
|
32
|
+
{chip}
|
|
33
|
+
</Tooltip>
|
|
34
|
+
) : (
|
|
35
|
+
chip
|
|
36
|
+
);
|
|
37
|
+
};
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
import { useState } from "react";
|
|
2
|
+
import {
|
|
3
|
+
Box,
|
|
4
|
+
Button,
|
|
5
|
+
Dialog,
|
|
6
|
+
DialogActions,
|
|
7
|
+
DialogContent,
|
|
8
|
+
DialogContentText,
|
|
9
|
+
DialogTitle,
|
|
10
|
+
Stack,
|
|
11
|
+
Tab,
|
|
12
|
+
Tabs,
|
|
13
|
+
useMediaQuery,
|
|
14
|
+
} from "@mui/material";
|
|
15
|
+
|
|
16
|
+
import { ServiceActions } from "./ServiceActions";
|
|
17
|
+
import { COLORS, FONTS } from "./tokens";
|
|
18
|
+
|
|
19
|
+
/*
|
|
20
|
+
Barra de pestañas (Descripción / Ficha técnica / Imágenes) y sus acciones:
|
|
21
|
+
ayuda, versión, estatus, guardar, borrar/descargar/cambiar vista de imágenes y
|
|
22
|
+
asignaciones.
|
|
23
|
+
|
|
24
|
+
Reemplaza a `organisms/FullTabsMenu` (TabsMenu + StatusAsignationInfo) con
|
|
25
|
+
los mismos props y el mismo comportamiento, hecho con MUI:
|
|
26
|
+
- las pestañas se desplazan de lado si no caben;
|
|
27
|
+
- si no caben junto a las pestañas, las acciones bajan a su propia fila
|
|
28
|
+
(las acciones son `ServiceActions`, compartido con el panel de imagen);
|
|
29
|
+
- cambiar de pestaña con cambios sin guardar pide confirmación.
|
|
30
|
+
*/
|
|
31
|
+
|
|
32
|
+
const tabSx = {
|
|
33
|
+
fontFamily: FONTS.Raleway_500,
|
|
34
|
+
textTransform: "none",
|
|
35
|
+
fontSize: { xs: 13, sm: 14 },
|
|
36
|
+
lineHeight: "21px",
|
|
37
|
+
whiteSpace: "nowrap",
|
|
38
|
+
color: COLORS.s5,
|
|
39
|
+
minWidth: { xs: 0, sm: 149 },
|
|
40
|
+
minHeight: 36,
|
|
41
|
+
px: { xs: 0.5, sm: 0 },
|
|
42
|
+
pb: "5px",
|
|
43
|
+
alignItems: "flex-start",
|
|
44
|
+
borderBottom: `2px solid ${COLORS.s3}`,
|
|
45
|
+
transition: "font-size 0.1s",
|
|
46
|
+
"&.Mui-selected": { color: COLORS.s5, fontSize: { xs: 14, sm: 18 } },
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
export const TabsBar = ({
|
|
50
|
+
tabsSections = {},
|
|
51
|
+
status = "",
|
|
52
|
+
activeTab,
|
|
53
|
+
setActiveTab,
|
|
54
|
+
imageLayout,
|
|
55
|
+
setImageLayout,
|
|
56
|
+
assig,
|
|
57
|
+
setAssignation,
|
|
58
|
+
isRetailer,
|
|
59
|
+
downloadImages,
|
|
60
|
+
askToDeleteImages,
|
|
61
|
+
onClickSave,
|
|
62
|
+
showSaveButton,
|
|
63
|
+
canAssign,
|
|
64
|
+
version,
|
|
65
|
+
setShowVersionSelector,
|
|
66
|
+
images,
|
|
67
|
+
updatedDescriptions = [],
|
|
68
|
+
updatedDatasheets = [],
|
|
69
|
+
updatedImages = [],
|
|
70
|
+
setUpdatedDescriptions,
|
|
71
|
+
setUpdatedDatasheets,
|
|
72
|
+
setImages,
|
|
73
|
+
}) => {
|
|
74
|
+
const tabs = Object.keys(tabsSections);
|
|
75
|
+
const initialTab = tabs.find((key) => tabsSections[key]) ?? tabs[0];
|
|
76
|
+
const currentTab = tabs.includes(activeTab) ? activeTab : initialTab;
|
|
77
|
+
const isImagesTab = currentTab === "Imágenes";
|
|
78
|
+
|
|
79
|
+
// En celular las pestañas se reparten a lo ancho: con tres caben todas y no
|
|
80
|
+
// queda ninguna escondida detrás de la flecha de desplazamiento.
|
|
81
|
+
const isPhone = useMediaQuery("(max-width:599.95px)");
|
|
82
|
+
|
|
83
|
+
// --- Cambio de pestaña con cambios sin guardar ---------------------------
|
|
84
|
+
const [pendingTab, setPendingTab] = useState(null);
|
|
85
|
+
|
|
86
|
+
const hasUnsavedChanges = () =>
|
|
87
|
+
updatedDescriptions.length > 0 ||
|
|
88
|
+
updatedDatasheets.length > 0 ||
|
|
89
|
+
updatedImages.length > 0 ||
|
|
90
|
+
images?.values?.filter((f) => !f.id)?.length > 0;
|
|
91
|
+
|
|
92
|
+
const requestTab = (tab) => {
|
|
93
|
+
if (tab === currentTab) return;
|
|
94
|
+
if (hasUnsavedChanges()) setPendingTab(tab);
|
|
95
|
+
else setActiveTab(tab);
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
const confirmTabChange = () => {
|
|
99
|
+
// Igual que TabsMenu: al aceptar se descartan los cambios pendientes.
|
|
100
|
+
setActiveTab(pendingTab);
|
|
101
|
+
setUpdatedDescriptions && setUpdatedDescriptions([]);
|
|
102
|
+
setUpdatedDatasheets && setUpdatedDatasheets([]);
|
|
103
|
+
setImages && setImages({});
|
|
104
|
+
setPendingTab(null);
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
return (
|
|
108
|
+
<Box sx={{ mb: "14px", minWidth: 0 }}>
|
|
109
|
+
{/* Pestañas y acciones comparten fila mientras quepan; si no, las acciones
|
|
110
|
+
bajan a la siguiente. Las pestañas solo se desplazan de lado cuando ni
|
|
111
|
+
solas caben (celular). */}
|
|
112
|
+
<Stack
|
|
113
|
+
direction="row"
|
|
114
|
+
flexWrap="wrap"
|
|
115
|
+
alignItems="flex-end"
|
|
116
|
+
justifyContent="space-between"
|
|
117
|
+
columnGap={2}
|
|
118
|
+
rowGap={1}
|
|
119
|
+
>
|
|
120
|
+
<Tabs
|
|
121
|
+
value={currentTab ?? false}
|
|
122
|
+
onChange={(_, tab) => requestTab(tab)}
|
|
123
|
+
variant={isPhone && tabs.length <= 3 ? "fullWidth" : "scrollable"}
|
|
124
|
+
scrollButtons="auto"
|
|
125
|
+
allowScrollButtonsMobile
|
|
126
|
+
TabIndicatorProps={{ sx: { bgcolor: COLORS.magenta_s2, height: 2 } }}
|
|
127
|
+
sx={{
|
|
128
|
+
flex: "1 1 auto",
|
|
129
|
+
maxWidth: "100%",
|
|
130
|
+
minHeight: 36,
|
|
131
|
+
minWidth: 0,
|
|
132
|
+
"& .MuiTabs-flexContainer": { gap: "10px" },
|
|
133
|
+
}}
|
|
134
|
+
>
|
|
135
|
+
{tabs.map((tab) => (
|
|
136
|
+
<Tab key={tab} value={tab} label={tab} disableRipple sx={tabSx} />
|
|
137
|
+
))}
|
|
138
|
+
</Tabs>
|
|
139
|
+
|
|
140
|
+
<ServiceActions
|
|
141
|
+
status={status}
|
|
142
|
+
imagesSection={isImagesTab}
|
|
143
|
+
imageLayout={imageLayout}
|
|
144
|
+
setImageLayout={setImageLayout}
|
|
145
|
+
assig={assig}
|
|
146
|
+
setAssignation={setAssignation}
|
|
147
|
+
isRetailer={isRetailer}
|
|
148
|
+
downloadImages={downloadImages}
|
|
149
|
+
askToDeleteImages={askToDeleteImages}
|
|
150
|
+
onClickSave={onClickSave}
|
|
151
|
+
showSaveButton={showSaveButton}
|
|
152
|
+
canAssign={canAssign}
|
|
153
|
+
version={version}
|
|
154
|
+
setShowVersionSelector={setShowVersionSelector}
|
|
155
|
+
resetKey={currentTab}
|
|
156
|
+
/>
|
|
157
|
+
</Stack>
|
|
158
|
+
|
|
159
|
+
<Dialog open={Boolean(pendingTab)} onClose={() => setPendingTab(null)} maxWidth="xs" fullWidth>
|
|
160
|
+
<DialogTitle sx={{ fontFamily: FONTS.Raleway_600, color: COLORS.s5 }}>Cambios sin Guardar</DialogTitle>
|
|
161
|
+
<DialogContent>
|
|
162
|
+
<DialogContentText sx={{ fontFamily: FONTS.AvenirNext }}>
|
|
163
|
+
Estás cambiando de vista y aun no guardas cambios ¿Estás seguro?
|
|
164
|
+
</DialogContentText>
|
|
165
|
+
</DialogContent>
|
|
166
|
+
<DialogActions sx={{ px: 3, pb: 2 }}>
|
|
167
|
+
<Button onClick={() => setPendingTab(null)} sx={{ color: COLORS.s5, textTransform: "none" }}>
|
|
168
|
+
Cancelar
|
|
169
|
+
</Button>
|
|
170
|
+
<Button
|
|
171
|
+
variant="contained"
|
|
172
|
+
disableElevation
|
|
173
|
+
onClick={confirmTabChange}
|
|
174
|
+
sx={{
|
|
175
|
+
textTransform: "none",
|
|
176
|
+
borderRadius: "20px",
|
|
177
|
+
bgcolor: COLORS.magenta_s2,
|
|
178
|
+
"&:hover": { bgcolor: COLORS.secondary_magenta },
|
|
179
|
+
}}
|
|
180
|
+
>
|
|
181
|
+
Aceptar
|
|
182
|
+
</Button>
|
|
183
|
+
</DialogActions>
|
|
184
|
+
</Dialog>
|
|
185
|
+
</Box>
|
|
186
|
+
);
|
|
187
|
+
};
|