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.
- package/.claude/settings.local.json +7 -0
- package/dist/ai/prompts/attribute.prompt.js +28 -0
- package/dist/ai/prompts/description.prompt.js +33 -0
- package/dist/ai/schemas/attribute.schema.js +35 -0
- package/dist/ai/schemas/description.schema.js +27 -0
- package/dist/components/atoms/MetricCard/index.js +3 -1
- package/dist/components/organisms/DashboardMetric/DashboardMetric.stories.js +12 -2
- package/dist/components/organisms/DashboardMetric/index.js +24 -1
- package/dist/components/organisms/DashboardMetric/styles.js +1 -1
- package/dist/components/pages/CreatorsProductEdition/CreatorsProductEditionView.js +11 -5
- package/dist/components/pages/CreatorsProductEdition/hooks/useCreatorsProductEdition.js +58 -31
- package/dist/components/pages/CreatorsProductEdition/mocks/api.fixtures.js +5 -1
- package/dist/components/pages/CreatorsProductEdition/mocks/mockApi.js +3 -40
- package/dist/components/pages/CreatorsProductEdition/sections/ImagesSection.js +18 -3
- package/dist/components/pages/CreatorsProductEdition/sections/StatusChip.js +4 -13
- package/dist/components/pages/CreatorsProductEdition/sections/TabsBar.js +10 -0
- package/dist/components/pages/CreatorsProductEdition/stories/CreatorsProductEdition.stories.js +10 -2
- package/dist/components/pages/Dashboard/dashboardUtils.js +3 -3
- package/dist/components/pages/Dashboard/index.js +102 -88
- package/dist/components/pages/Dashboard/styles.js +1 -1
- package/dist/components/pages/RetailerProductEdition/RetailerProductEdition.stories.js +2 -15
- package/dist/components/pages/RetailerProductEdition/index.js +40 -57
- package/dist/components/pages/RetailerProductEdition/utils.js +2 -6
- package/dist/index.js +0 -13
- package/package.json +1 -1
- package/src/components/atoms/MetricCard/index.js +2 -2
- package/src/components/organisms/DashboardMetric/DashboardMetric.stories.js +11 -0
- package/src/components/organisms/DashboardMetric/index.js +13 -1
- package/src/components/organisms/DashboardMetric/styles.js +13 -0
- package/src/components/pages/CreatorsProductEdition/CreatorsProductEditionView.jsx +22 -3
- package/src/components/pages/CreatorsProductEdition/hooks/useCreatorsProductEdition.js +36 -1
- package/src/components/pages/CreatorsProductEdition/mocks/api.fixtures.js +15 -3
- package/src/components/pages/CreatorsProductEdition/mocks/mockApi.js +8 -18
- package/src/components/pages/CreatorsProductEdition/sections/ImagesSection.jsx +24 -2
- package/src/components/pages/CreatorsProductEdition/sections/StatusChip.jsx +4 -8
- package/src/components/pages/CreatorsProductEdition/sections/TabsBar.jsx +9 -0
- package/src/components/pages/CreatorsProductEdition/stories/CreatorsProductEdition.stories.js +6 -0
- package/src/components/pages/Dashboard/dashboardUtils.js +3 -3
- package/src/components/pages/Dashboard/index.js +62 -106
- package/src/components/pages/Dashboard/styles.js +17 -12
- package/src/components/pages/RetailerProductEdition/RetailerProductEdition.stories.js +0 -12
- package/src/components/pages/RetailerProductEdition/index.js +3 -28
- package/src/components/pages/RetailerProductEdition/utils.js +0 -5
- package/src/index.js +0 -1
- package/dist/components/organisms/ServicesNavigation/ServiceToolbar.js +0 -210
- package/dist/components/organisms/ServicesNavigation/ServicesNavigation.stories.js +0 -138
- package/dist/components/organisms/ServicesNavigation/ServicesRail.js +0 -705
- package/dist/components/organisms/ServicesNavigation/catalog.js +0 -505
- package/dist/components/organisms/ServicesNavigation/index.js +0 -337
- package/dist/components/organisms/ServicesNavigation/model.js +0 -167
- package/dist/components/organisms/ServicesNavigation/ui.js +0 -371
- package/dist/components/organisms/ServicesNavigation/views.js +0 -1237
- package/dist/components/organisms/ServicesWorkspace/AssigneePicker.js +0 -459
- package/dist/components/organisms/ServicesWorkspace/ServicesWorkspace.stories.js +0 -119
- package/dist/components/organisms/ServicesWorkspace/demo.js +0 -137
- package/dist/components/organisms/ServicesWorkspace/index.js +0 -813
- package/dist/components/organisms/ServicesWorkspace/servicesModel.js +0 -351
- package/dist/components/organisms/ServicesWorkspace/tooltipStyles.js +0 -37
- package/dist/components/pages/CreatorsProductEdition/mocks/productServices.fixtures.js +0 -387
- package/dist/components/pages/ProductEdition/ProductEdition.stories.js +0 -148
- package/dist/components/pages/ProductEdition/ProductEditionView.js +0 -1355
- package/dist/components/pages/ProductEdition/ServiceHeader.js +0 -142
- package/dist/components/pages/ProductEdition/api.js +0 -256
- package/dist/components/pages/ProductEdition/index.js +0 -37
- package/dist/components/pages/ProductEdition/permissions.js +0 -200
- package/dist/components/pages/ProductEdition/permissions.test.js +0 -320
- package/dist/components/pages/ProductEdition/sections/CatalogServicePanel.js +0 -237
- package/dist/components/pages/ProductEdition/sections/EditionTopBar.js +0 -174
- package/dist/components/pages/ProductEdition/sections/ImagePanel.js +0 -440
- package/dist/components/pages/ProductEdition/sections/ServiceTeam.js +0 -275
- package/dist/components/pages/ProductEdition/sections/ServiceTools.js +0 -186
- package/dist/components/pages/ProductEdition/services.js +0 -233
- package/dist/components/pages/ProductEdition/services.test.js +0 -195
- package/src/components/organisms/ServicesNavigation/ServiceToolbar.jsx +0 -143
- package/src/components/organisms/ServicesNavigation/ServicesNavigation.stories.js +0 -101
- package/src/components/organisms/ServicesNavigation/ServicesRail.jsx +0 -591
- package/src/components/organisms/ServicesNavigation/catalog.js +0 -180
- package/src/components/organisms/ServicesNavigation/index.jsx +0 -232
- package/src/components/organisms/ServicesNavigation/model.js +0 -85
- package/src/components/organisms/ServicesNavigation/ui.jsx +0 -229
- package/src/components/organisms/ServicesNavigation/views.jsx +0 -705
- package/src/components/organisms/ServicesWorkspace/AssigneePicker.js +0 -348
- package/src/components/organisms/ServicesWorkspace/ServicesWorkspace.stories.js +0 -61
- package/src/components/organisms/ServicesWorkspace/demo.js +0 -58
- package/src/components/organisms/ServicesWorkspace/index.js +0 -556
- package/src/components/organisms/ServicesWorkspace/servicesModel.js +0 -203
- package/src/components/organisms/ServicesWorkspace/tooltipStyles.js +0 -27
- package/src/components/pages/CreatorsProductEdition/mocks/productServices.fixtures.js +0 -175
- package/src/components/pages/ProductEdition/ProductEdition.stories.js +0 -106
- package/src/components/pages/ProductEdition/ProductEditionView.jsx +0 -1142
- package/src/components/pages/ProductEdition/ServiceHeader.jsx +0 -104
- package/src/components/pages/ProductEdition/api.js +0 -84
- package/src/components/pages/ProductEdition/index.js +0 -21
- package/src/components/pages/ProductEdition/permissions.js +0 -128
- package/src/components/pages/ProductEdition/permissions.test.js +0 -249
- package/src/components/pages/ProductEdition/sections/CatalogServicePanel.jsx +0 -123
- package/src/components/pages/ProductEdition/sections/EditionTopBar.jsx +0 -134
- package/src/components/pages/ProductEdition/sections/ImagePanel.jsx +0 -342
- package/src/components/pages/ProductEdition/sections/ServiceTeam.jsx +0 -184
- package/src/components/pages/ProductEdition/sections/ServiceTools.jsx +0 -134
- package/src/components/pages/ProductEdition/services.js +0 -187
- package/src/components/pages/ProductEdition/services.test.js +0 -88
|
@@ -1,705 +0,0 @@
|
|
|
1
|
-
import { useState } from "react";
|
|
2
|
-
import { Box, Button, ButtonBase, LinearProgress, Stack, TextField, Typography } from "@mui/material";
|
|
3
|
-
import ImageOutlinedIcon from "@mui/icons-material/ImageOutlined";
|
|
4
|
-
import CloudUploadOutlinedIcon from "@mui/icons-material/CloudUploadOutlined";
|
|
5
|
-
import PlayCircleOutlineIcon from "@mui/icons-material/PlayCircleOutline";
|
|
6
|
-
import InsertDriveFileOutlinedIcon from "@mui/icons-material/InsertDriveFileOutlined";
|
|
7
|
-
import LockOutlinedIcon from "@mui/icons-material/LockOutlined";
|
|
8
|
-
import ErrorOutlineIcon from "@mui/icons-material/ErrorOutline";
|
|
9
|
-
import ArrowForwardIcon from "@mui/icons-material/ArrowForward";
|
|
10
|
-
|
|
11
|
-
import {
|
|
12
|
-
COLORS,
|
|
13
|
-
FONT_STACK,
|
|
14
|
-
TITLE_STACK,
|
|
15
|
-
PendingDot,
|
|
16
|
-
ServiceIcon,
|
|
17
|
-
StatusChip,
|
|
18
|
-
serviceStatusProps,
|
|
19
|
-
captionSx,
|
|
20
|
-
fieldSx,
|
|
21
|
-
labelSx,
|
|
22
|
-
outlineButtonSx,
|
|
23
|
-
statusColor,
|
|
24
|
-
serviceStatusColor,
|
|
25
|
-
statusName,
|
|
26
|
-
} from "./ui";
|
|
27
|
-
import { SUMMARY, fullName, groupServices, isPendingFor } from "./model";
|
|
28
|
-
|
|
29
|
-
/*
|
|
30
|
-
Vistas de trabajo del prototipo. Una por tipo de entregable, no una por
|
|
31
|
-
servicio: Descripción, SEO y Header de ficha usan la de textos; Foto, Edición
|
|
32
|
-
y Resize, la de imágenes. El contenido es de ejemplo: lo que se muestra es
|
|
33
|
-
cómo se navega y qué ve cada quien, no el editor final.
|
|
34
|
-
*/
|
|
35
|
-
|
|
36
|
-
const baseStatus = (status) => String(status ?? "").split("/")[0];
|
|
37
|
-
const isRejected = (status) =>
|
|
38
|
-
String(status ?? "")
|
|
39
|
-
.split("/")
|
|
40
|
-
.some((part) => ["RA", "RC", "RP", "RCA", "RAC"].includes(part));
|
|
41
|
-
const DONE = ["Ex", "FAP"];
|
|
42
|
-
|
|
43
|
-
/* ------------------------------------------------------------------------ */
|
|
44
|
-
/* Piezas comunes */
|
|
45
|
-
/* ------------------------------------------------------------------------ */
|
|
46
|
-
|
|
47
|
-
const Panel = ({ title, aside, children, sx }) => (
|
|
48
|
-
<Box
|
|
49
|
-
sx={{
|
|
50
|
-
border: `1px solid ${COLORS.s2}`,
|
|
51
|
-
borderRadius: "10px",
|
|
52
|
-
bgcolor: COLORS.white,
|
|
53
|
-
p: 2,
|
|
54
|
-
mb: 2,
|
|
55
|
-
...sx,
|
|
56
|
-
}}
|
|
57
|
-
>
|
|
58
|
-
{(title || aside) && (
|
|
59
|
-
<Stack direction="row" alignItems="baseline" spacing={1} sx={{ mb: 1.5, flexWrap: "wrap", rowGap: 0.5 }}>
|
|
60
|
-
{title && (
|
|
61
|
-
<Typography sx={{ fontFamily: TITLE_STACK, fontWeight: 600, fontSize: 15, color: COLORS.s5 }}>
|
|
62
|
-
{title}
|
|
63
|
-
</Typography>
|
|
64
|
-
)}
|
|
65
|
-
<Box sx={{ flex: 1 }} />
|
|
66
|
-
{aside}
|
|
67
|
-
</Stack>
|
|
68
|
-
)}
|
|
69
|
-
{children}
|
|
70
|
-
</Box>
|
|
71
|
-
);
|
|
72
|
-
|
|
73
|
-
const Note = ({ icon, tone = "info", children }) => (
|
|
74
|
-
<Box
|
|
75
|
-
sx={{
|
|
76
|
-
display: "flex",
|
|
77
|
-
alignItems: "flex-start",
|
|
78
|
-
gap: 1,
|
|
79
|
-
p: 1.5,
|
|
80
|
-
mb: 2,
|
|
81
|
-
borderRadius: "8px",
|
|
82
|
-
bgcolor: tone === "error" ? "rgba(215, 77, 237, 0.08)" : COLORS.blue_light,
|
|
83
|
-
border: `1px solid ${tone === "error" ? "rgba(215, 77, 237, 0.35)" : COLORS.s2}`,
|
|
84
|
-
}}
|
|
85
|
-
>
|
|
86
|
-
<Box sx={{ color: tone === "error" ? COLORS.rejected_status : COLORS.s4, display: "flex", mt: "1px" }}>{icon}</Box>
|
|
87
|
-
<Typography sx={{ fontFamily: FONT_STACK, fontSize: 13, lineHeight: "19px", color: COLORS.s5 }}>{children}</Typography>
|
|
88
|
-
</Box>
|
|
89
|
-
);
|
|
90
|
-
|
|
91
|
-
const REJECTION_COMMENTS = {
|
|
92
|
-
description: "Falta mencionar la garantía en la descripción larga.",
|
|
93
|
-
edition: "La toma de empaque tiene sombra: el fondo debe ser blanco puro.",
|
|
94
|
-
aplusPlatinum: "El banner principal no respeta la guía de marca de la cadena.",
|
|
95
|
-
};
|
|
96
|
-
|
|
97
|
-
const RejectionNote = ({ service }) =>
|
|
98
|
-
isRejected(service.status) ? (
|
|
99
|
-
<Note icon={<ErrorOutlineIcon sx={{ fontSize: 18 }} />} tone="error">
|
|
100
|
-
<b>{statusName(service.status) ?? "Rechazado"}.</b>{" "}
|
|
101
|
-
«{REJECTION_COMMENTS[service.service_name] ?? "Revisa los comentarios y vuelve a enviar."}»
|
|
102
|
-
</Note>
|
|
103
|
-
) : null;
|
|
104
|
-
|
|
105
|
-
const DropZone = ({ label, hint, sx }) => (
|
|
106
|
-
<ButtonBase
|
|
107
|
-
sx={{
|
|
108
|
-
width: "100%",
|
|
109
|
-
minHeight: 120,
|
|
110
|
-
display: "flex",
|
|
111
|
-
flexDirection: "column",
|
|
112
|
-
gap: 0.75,
|
|
113
|
-
border: `1.5px dashed ${COLORS.magenta_s2}`,
|
|
114
|
-
borderRadius: "10px",
|
|
115
|
-
bgcolor: "rgba(227, 58, 169, 0.04)",
|
|
116
|
-
color: COLORS.magenta_s2,
|
|
117
|
-
p: 2,
|
|
118
|
-
"&:hover": { bgcolor: "rgba(227, 58, 169, 0.08)" },
|
|
119
|
-
...sx,
|
|
120
|
-
}}
|
|
121
|
-
>
|
|
122
|
-
<CloudUploadOutlinedIcon sx={{ fontSize: 28 }} />
|
|
123
|
-
<Typography sx={{ fontFamily: FONT_STACK, fontSize: 13, fontWeight: 700 }}>{label}</Typography>
|
|
124
|
-
{hint && <Typography sx={{ ...captionSx, textAlign: "center" }}>{hint}</Typography>}
|
|
125
|
-
</ButtonBase>
|
|
126
|
-
);
|
|
127
|
-
|
|
128
|
-
const CountedField = ({ label, value: initial = "", max, rows = 1, required = false, placeholder }) => {
|
|
129
|
-
const [value, setValue] = useState(initial);
|
|
130
|
-
const over = max && value.length > max;
|
|
131
|
-
return (
|
|
132
|
-
<Box>
|
|
133
|
-
{label && (
|
|
134
|
-
<Typography sx={labelSx}>
|
|
135
|
-
{label}
|
|
136
|
-
{required && <Box component="span" sx={{ color: COLORS.magenta_s2 }}> *</Box>}
|
|
137
|
-
</Typography>
|
|
138
|
-
)}
|
|
139
|
-
<TextField
|
|
140
|
-
fullWidth
|
|
141
|
-
size="small"
|
|
142
|
-
multiline={rows > 1}
|
|
143
|
-
minRows={rows > 1 ? rows : undefined}
|
|
144
|
-
value={value}
|
|
145
|
-
placeholder={placeholder}
|
|
146
|
-
onChange={(event) => setValue(event.target.value)}
|
|
147
|
-
sx={fieldSx}
|
|
148
|
-
/>
|
|
149
|
-
{max && (
|
|
150
|
-
<Typography sx={{ ...captionSx, textAlign: "right", mt: 0.25, color: over ? COLORS.rejected_status : COLORS.s4 }}>
|
|
151
|
-
{value.length} / {max}
|
|
152
|
-
</Typography>
|
|
153
|
-
)}
|
|
154
|
-
</Box>
|
|
155
|
-
);
|
|
156
|
-
};
|
|
157
|
-
|
|
158
|
-
/* ------------------------------------------------------------------------ */
|
|
159
|
-
/* Resumen */
|
|
160
|
-
/* ------------------------------------------------------------------------ */
|
|
161
|
-
|
|
162
|
-
const StatTile = ({ value, label, color }) => (
|
|
163
|
-
<Box sx={{ flex: "1 1 120px", border: `1px solid ${COLORS.s2}`, borderRadius: "10px", p: 1.5, bgcolor: COLORS.white }}>
|
|
164
|
-
<Stack direction="row" alignItems="center" spacing={0.75}>
|
|
165
|
-
{color && <Box sx={{ width: 8, height: 8, borderRadius: "50%", bgcolor: color }} />}
|
|
166
|
-
<Typography sx={{ fontFamily: TITLE_STACK, fontWeight: 600, fontSize: 24, lineHeight: "30px", color: COLORS.s5 }}>
|
|
167
|
-
{value}
|
|
168
|
-
</Typography>
|
|
169
|
-
</Stack>
|
|
170
|
-
<Typography sx={captionSx}>{label}</Typography>
|
|
171
|
-
</Box>
|
|
172
|
-
);
|
|
173
|
-
|
|
174
|
-
export const SummaryView = ({ services, userId, onSelect }) => {
|
|
175
|
-
const pending = services.filter((service) => isPendingFor(service, userId)).length;
|
|
176
|
-
const rejected = services.filter((service) => isRejected(service.status)).length;
|
|
177
|
-
const done = services.filter((service) => DONE.includes(baseStatus(service.status))).length;
|
|
178
|
-
|
|
179
|
-
return (
|
|
180
|
-
<>
|
|
181
|
-
<Stack direction="row" spacing={1.5} sx={{ mb: 2, flexWrap: "wrap", rowGap: 1.5 }}>
|
|
182
|
-
<StatTile value={services.length} label="Servicios contratados" />
|
|
183
|
-
<StatTile value={pending} label="Te tocan a ti" color={COLORS.magenta_s2} />
|
|
184
|
-
<StatTile value={rejected} label="Rechazados" color={COLORS.rejected_status} />
|
|
185
|
-
<StatTile value={done} label="Terminados" color={COLORS.exported} />
|
|
186
|
-
</Stack>
|
|
187
|
-
|
|
188
|
-
<Panel title="Avance">
|
|
189
|
-
<Box aria-hidden="true" sx={{ display: "flex", gap: "3px", height: 10 }}>
|
|
190
|
-
{groupServices(services)
|
|
191
|
-
.flatMap((group) => group.services)
|
|
192
|
-
.map((service) => (
|
|
193
|
-
<Box
|
|
194
|
-
key={service.service_name}
|
|
195
|
-
title={`${service.display_name} · ${statusName(service.status) ?? service.status}`}
|
|
196
|
-
sx={{ flex: "1 1 0", minWidth: 4, borderRadius: "3px", bgcolor: serviceStatusColor(service) }}
|
|
197
|
-
/>
|
|
198
|
-
))}
|
|
199
|
-
</Box>
|
|
200
|
-
<Typography sx={{ ...captionSx, mt: 0.75 }}>
|
|
201
|
-
Cada segmento es un servicio, con el color de su estatus, en el orden de la lista.
|
|
202
|
-
</Typography>
|
|
203
|
-
</Panel>
|
|
204
|
-
|
|
205
|
-
{groupServices(services).map((group) => (
|
|
206
|
-
<Panel key={group.key} title={group.label} aside={<Typography sx={captionSx}>{group.services.length}</Typography>} sx={{ p: 1, pt: 1.5 }}>
|
|
207
|
-
{group.services.map((service) => {
|
|
208
|
-
const especialist = service.assignees?.especialist;
|
|
209
|
-
const mine = isPendingFor(service, userId);
|
|
210
|
-
return (
|
|
211
|
-
<ButtonBase
|
|
212
|
-
key={service.service_name}
|
|
213
|
-
onClick={() => onSelect(service.service_name)}
|
|
214
|
-
sx={{
|
|
215
|
-
width: "100%",
|
|
216
|
-
display: "grid",
|
|
217
|
-
gridTemplateColumns: {
|
|
218
|
-
xs: "minmax(0, 1fr) auto",
|
|
219
|
-
sm: "minmax(0, 1.6fr) 72px minmax(0, 1fr) 16px 84px 20px",
|
|
220
|
-
},
|
|
221
|
-
alignItems: "center",
|
|
222
|
-
columnGap: 1.5,
|
|
223
|
-
px: 1,
|
|
224
|
-
py: 1,
|
|
225
|
-
borderRadius: "8px",
|
|
226
|
-
textAlign: "left",
|
|
227
|
-
"&:hover": { bgcolor: COLORS.s1 },
|
|
228
|
-
"& + &": { borderTop: `1px solid ${COLORS.s2}` },
|
|
229
|
-
}}
|
|
230
|
-
>
|
|
231
|
-
<Stack direction="row" alignItems="center" spacing={1} sx={{ minWidth: 0 }}>
|
|
232
|
-
<ServiceIcon icon={service.icon} sx={{ color: COLORS.s4 }} />
|
|
233
|
-
<Typography noWrap sx={{ fontFamily: FONT_STACK, fontSize: 14, color: COLORS.color_gray }}>
|
|
234
|
-
{service.display_name}
|
|
235
|
-
</Typography>
|
|
236
|
-
</Stack>
|
|
237
|
-
<Box>
|
|
238
|
-
<StatusChip {...serviceStatusProps(service)} oval />
|
|
239
|
-
</Box>
|
|
240
|
-
<Typography noWrap sx={{ ...captionSx, display: { xs: "none", sm: "block" } }}>
|
|
241
|
-
{especialist ? fullName(especialist) : "Sin especialista"}
|
|
242
|
-
</Typography>
|
|
243
|
-
<Box sx={{ display: { xs: "none", sm: "flex" } }}>{mine && <PendingDot />}</Box>
|
|
244
|
-
<Typography sx={{ ...captionSx, display: { xs: "none", sm: "block" } }}>{service.updated_at}</Typography>
|
|
245
|
-
<ArrowForwardIcon sx={{ display: { xs: "none", sm: "block" }, fontSize: 16, color: COLORS.s3 }} />
|
|
246
|
-
</ButtonBase>
|
|
247
|
-
);
|
|
248
|
-
})}
|
|
249
|
-
</Panel>
|
|
250
|
-
))}
|
|
251
|
-
</>
|
|
252
|
-
);
|
|
253
|
-
};
|
|
254
|
-
|
|
255
|
-
/* ------------------------------------------------------------------------ */
|
|
256
|
-
/* Ficha técnica */
|
|
257
|
-
/* ------------------------------------------------------------------------ */
|
|
258
|
-
|
|
259
|
-
const ATTRIBUTES = [
|
|
260
|
-
{ label: "Marca", value: "Norteña Pro", required: true },
|
|
261
|
-
{ label: "Modelo", value: "NP-2040", required: true },
|
|
262
|
-
{ label: "Voltaje", value: "20 V", required: true },
|
|
263
|
-
{ label: "Tipo de batería", value: "Ion de litio", required: true },
|
|
264
|
-
{ label: "Velocidad máxima", value: "1 500 rpm" },
|
|
265
|
-
{ label: "Peso", value: "1.4 kg" },
|
|
266
|
-
{ label: "Garantía", value: "2 años", required: true },
|
|
267
|
-
{ label: "Contenido de la caja", value: "", required: true },
|
|
268
|
-
];
|
|
269
|
-
|
|
270
|
-
const AttributesView = ({ service, retailerName }) => {
|
|
271
|
-
const required = ATTRIBUTES.filter((item) => item.required);
|
|
272
|
-
const filled = required.filter((item) => item.value).length;
|
|
273
|
-
return (
|
|
274
|
-
<>
|
|
275
|
-
<RejectionNote service={service} />
|
|
276
|
-
<Panel
|
|
277
|
-
title={`Atributos de ${retailerName}`}
|
|
278
|
-
aside={
|
|
279
|
-
<Typography sx={captionSx}>
|
|
280
|
-
{filled} de {required.length} requeridos completos
|
|
281
|
-
</Typography>
|
|
282
|
-
}
|
|
283
|
-
>
|
|
284
|
-
<Box sx={{ display: "grid", gridTemplateColumns: { xs: "1fr", md: "1fr 1fr" }, gap: 2 }}>
|
|
285
|
-
{ATTRIBUTES.map((item) => (
|
|
286
|
-
<CountedField key={item.label} label={item.label} value={item.value} required={item.required} />
|
|
287
|
-
))}
|
|
288
|
-
</Box>
|
|
289
|
-
</Panel>
|
|
290
|
-
</>
|
|
291
|
-
);
|
|
292
|
-
};
|
|
293
|
-
|
|
294
|
-
/* ------------------------------------------------------------------------ */
|
|
295
|
-
/* Textos (Descripción, SEO, Header de ficha) */
|
|
296
|
-
/* ------------------------------------------------------------------------ */
|
|
297
|
-
|
|
298
|
-
const LONG_DESCRIPTION =
|
|
299
|
-
"Perfora madera, metal y plástico sin cables. El motor de 20 V llega a 1 500 rpm y el embrague de 21 posiciones evita barrir tornillos. Incluye dos baterías de ion de litio de 2 Ah, cargador rápido y estuche.";
|
|
300
|
-
|
|
301
|
-
const TEXT_FIELDS = {
|
|
302
|
-
description: [
|
|
303
|
-
{ label: "Descripción corta", max: 150, rows: 2, required: true, value: "Taladro inalámbrico de 20 V con dos baterías de ion de litio y cargador rápido." },
|
|
304
|
-
{ label: "Descripción larga", max: 1000, rows: 5, required: true, value: LONG_DESCRIPTION },
|
|
305
|
-
{ label: "Puntos clave", max: 400, rows: 3, value: "• Motor de 1 500 rpm\n• Dos baterías de 2 Ah\n• Luz LED de trabajo" },
|
|
306
|
-
],
|
|
307
|
-
datasheetHeader: [
|
|
308
|
-
{ label: "Encabezado de la ficha", max: 120, rows: 2, required: true, value: "Taladro inalámbrico 20 V | Norteña Pro NP-2040" },
|
|
309
|
-
],
|
|
310
|
-
seo: [
|
|
311
|
-
{ label: "Título SEO", max: 60, required: true, value: "Taladro inalámbrico 20 V con 2 baterías | Norteña Pro" },
|
|
312
|
-
{ label: "Meta descripción", max: 155, rows: 3, required: true, value: "", placeholder: "Resumen para buscadores" },
|
|
313
|
-
{ label: "Palabras clave", max: 200, rows: 2, value: "taladro inalámbrico, taladro 20v, herramienta eléctrica" },
|
|
314
|
-
],
|
|
315
|
-
};
|
|
316
|
-
|
|
317
|
-
const TextView = ({ service, retailerName }) => (
|
|
318
|
-
<>
|
|
319
|
-
<RejectionNote service={service} />
|
|
320
|
-
<Panel title={`${service.display_name} para ${retailerName}`}>
|
|
321
|
-
<Stack spacing={2}>
|
|
322
|
-
{(TEXT_FIELDS[service.service_name] ?? TEXT_FIELDS.description).map((field) => (
|
|
323
|
-
<CountedField key={field.label} {...field} />
|
|
324
|
-
))}
|
|
325
|
-
</Stack>
|
|
326
|
-
</Panel>
|
|
327
|
-
</>
|
|
328
|
-
);
|
|
329
|
-
|
|
330
|
-
/* ------------------------------------------------------------------------ */
|
|
331
|
-
/* Traducción */
|
|
332
|
-
/* ------------------------------------------------------------------------ */
|
|
333
|
-
|
|
334
|
-
const TRANSLATION_ROWS = [
|
|
335
|
-
{ label: "Nombre del producto", source: "Taladro inalámbrico 20 V con 2 baterías", target: "20V Cordless Drill with 2 Batteries" },
|
|
336
|
-
{ label: "Descripción corta", source: "Taladro inalámbrico de 20 V con dos baterías de ion de litio y cargador rápido.", target: "20V cordless drill with two lithium-ion batteries and a fast charger." },
|
|
337
|
-
{ label: "Descripción larga", source: LONG_DESCRIPTION, target: "" },
|
|
338
|
-
{ label: "Puntos clave", source: "• Motor de 1 500 rpm\n• Dos baterías de 2 Ah\n• Luz LED de trabajo", target: "" },
|
|
339
|
-
{ label: "Contenido de la caja", source: "Taladro, 2 baterías, cargador y estuche", target: "Drill, 2 batteries, charger and carrying case" },
|
|
340
|
-
];
|
|
341
|
-
|
|
342
|
-
const LanguagePill = ({ children, strong }) => (
|
|
343
|
-
<Box
|
|
344
|
-
component="span"
|
|
345
|
-
sx={{
|
|
346
|
-
px: 1.25,
|
|
347
|
-
height: 26,
|
|
348
|
-
display: "inline-flex",
|
|
349
|
-
alignItems: "center",
|
|
350
|
-
borderRadius: "13px",
|
|
351
|
-
fontFamily: FONT_STACK,
|
|
352
|
-
fontSize: 12,
|
|
353
|
-
fontWeight: 700,
|
|
354
|
-
bgcolor: strong ? COLORS.s5 : COLORS.s2,
|
|
355
|
-
color: strong ? COLORS.white : COLORS.s5,
|
|
356
|
-
}}
|
|
357
|
-
>
|
|
358
|
-
{children}
|
|
359
|
-
</Box>
|
|
360
|
-
);
|
|
361
|
-
|
|
362
|
-
const TranslationView = ({ service, hasSourceServices }) => {
|
|
363
|
-
const done = TRANSLATION_ROWS.filter((row) => row.target).length;
|
|
364
|
-
return (
|
|
365
|
-
<>
|
|
366
|
-
<RejectionNote service={service} />
|
|
367
|
-
<Note icon={<LockOutlinedIcon sx={{ fontSize: 18 }} />}>
|
|
368
|
-
El texto original viene de la Descripción y la Ficha técnica del producto y aquí solo se lee.
|
|
369
|
-
{hasSourceServices
|
|
370
|
-
? " Si hay que corregirlo, se hace en esos servicios."
|
|
371
|
-
: " Este producto no tiene esos servicios contratados: su contenido sirve de base, pero no aparecen en la lista."}
|
|
372
|
-
</Note>
|
|
373
|
-
<Panel
|
|
374
|
-
title={
|
|
375
|
-
<Stack direction="row" alignItems="center" spacing={1} component="span">
|
|
376
|
-
<LanguagePill>Español (México)</LanguagePill>
|
|
377
|
-
<ArrowForwardIcon sx={{ fontSize: 16, color: COLORS.s4 }} />
|
|
378
|
-
<LanguagePill strong>Inglés (EE. UU.)</LanguagePill>
|
|
379
|
-
</Stack>
|
|
380
|
-
}
|
|
381
|
-
aside={
|
|
382
|
-
<Box sx={{ minWidth: 150 }}>
|
|
383
|
-
<Typography sx={{ ...captionSx, textAlign: "right" }}>
|
|
384
|
-
{done} de {TRANSLATION_ROWS.length} campos traducidos
|
|
385
|
-
</Typography>
|
|
386
|
-
<LinearProgress
|
|
387
|
-
variant="determinate"
|
|
388
|
-
value={(done / TRANSLATION_ROWS.length) * 100}
|
|
389
|
-
sx={{
|
|
390
|
-
mt: 0.5,
|
|
391
|
-
height: 4,
|
|
392
|
-
borderRadius: 2,
|
|
393
|
-
bgcolor: COLORS.s2,
|
|
394
|
-
"& .MuiLinearProgress-bar": { bgcolor: COLORS.magenta_s2 },
|
|
395
|
-
}}
|
|
396
|
-
/>
|
|
397
|
-
</Box>
|
|
398
|
-
}
|
|
399
|
-
>
|
|
400
|
-
<Box
|
|
401
|
-
sx={{
|
|
402
|
-
display: { xs: "none", md: "grid" },
|
|
403
|
-
gridTemplateColumns: "1fr 1fr",
|
|
404
|
-
columnGap: 2,
|
|
405
|
-
mb: 1,
|
|
406
|
-
}}
|
|
407
|
-
>
|
|
408
|
-
<Typography sx={{ ...captionSx, fontWeight: 700, textTransform: "uppercase", letterSpacing: "0.05em" }}>
|
|
409
|
-
Original · solo lectura
|
|
410
|
-
</Typography>
|
|
411
|
-
<Typography sx={{ ...captionSx, fontWeight: 700, textTransform: "uppercase", letterSpacing: "0.05em" }}>
|
|
412
|
-
Traducción
|
|
413
|
-
</Typography>
|
|
414
|
-
</Box>
|
|
415
|
-
<Stack spacing={2}>
|
|
416
|
-
{TRANSLATION_ROWS.map((row) => (
|
|
417
|
-
<Box key={row.label}>
|
|
418
|
-
<Typography sx={labelSx}>{row.label}</Typography>
|
|
419
|
-
<Box sx={{ display: "grid", gridTemplateColumns: { xs: "1fr", md: "1fr 1fr" }, gap: 1.5 }}>
|
|
420
|
-
<Box
|
|
421
|
-
sx={{
|
|
422
|
-
p: "8.5px 14px",
|
|
423
|
-
borderRadius: "6px",
|
|
424
|
-
bgcolor: COLORS.s1,
|
|
425
|
-
border: `1px solid ${COLORS.s2}`,
|
|
426
|
-
fontFamily: FONT_STACK,
|
|
427
|
-
fontSize: 14,
|
|
428
|
-
lineHeight: "23px",
|
|
429
|
-
color: COLORS.gray,
|
|
430
|
-
whiteSpace: "pre-line",
|
|
431
|
-
}}
|
|
432
|
-
>
|
|
433
|
-
{row.source}
|
|
434
|
-
</Box>
|
|
435
|
-
<CountedField
|
|
436
|
-
label=""
|
|
437
|
-
value={row.target}
|
|
438
|
-
rows={row.source.length > 80 || row.source.includes("\n") ? 3 : 1}
|
|
439
|
-
placeholder="Pendiente de traducir"
|
|
440
|
-
/>
|
|
441
|
-
</Box>
|
|
442
|
-
</Box>
|
|
443
|
-
))}
|
|
444
|
-
</Stack>
|
|
445
|
-
</Panel>
|
|
446
|
-
</>
|
|
447
|
-
);
|
|
448
|
-
};
|
|
449
|
-
|
|
450
|
-
/* ------------------------------------------------------------------------ */
|
|
451
|
-
/* Imágenes (Imágenes, Foto, Edición, Resize, Adaptaciones, Imagen premium) */
|
|
452
|
-
/* ------------------------------------------------------------------------ */
|
|
453
|
-
|
|
454
|
-
const SHOTS = ["Frente", "Reverso", "Lateral", "Empaque", "En uso", "Detalle", "Ambiente", "Medidas"];
|
|
455
|
-
|
|
456
|
-
const TILE_STATES = {
|
|
457
|
-
ready: { label: "Lista", color: COLORS.finished },
|
|
458
|
-
pending: { label: "Pendiente", color: COLORS.s4 },
|
|
459
|
-
rejected: { label: "Rechazada", color: COLORS.rejected_status },
|
|
460
|
-
};
|
|
461
|
-
|
|
462
|
-
const tileState = (service, index) => {
|
|
463
|
-
const base = baseStatus(service.status);
|
|
464
|
-
if (["Ex", "FAP", "AA", "AP", "AC", "ACA", "AAC"].includes(base)) return "ready";
|
|
465
|
-
if (["PA", "R"].includes(base)) return "pending";
|
|
466
|
-
if (isRejected(service.status) && index === 3) return "rejected";
|
|
467
|
-
return index < 5 ? "ready" : "pending";
|
|
468
|
-
};
|
|
469
|
-
|
|
470
|
-
const ImagesView = ({ service, imageServices, onSelect, userId }) => {
|
|
471
|
-
const states = SHOTS.map((_, index) => tileState(service, index));
|
|
472
|
-
const ready = states.filter((state) => state === "ready").length;
|
|
473
|
-
return (
|
|
474
|
-
<>
|
|
475
|
-
{imageServices.length > 1 && (
|
|
476
|
-
<Box sx={{ mb: 2 }}>
|
|
477
|
-
<Typography sx={{ ...captionSx, mb: 1 }}>
|
|
478
|
-
Las imágenes del producto son las mismas para {imageServices.length} servicios. Elige en cuál trabajas:
|
|
479
|
-
</Typography>
|
|
480
|
-
<Stack direction="row" spacing={1} sx={{ flexWrap: "wrap", rowGap: 1 }}>
|
|
481
|
-
{imageServices.map((item) => {
|
|
482
|
-
const active = item.service_name === service.service_name;
|
|
483
|
-
return (
|
|
484
|
-
<ButtonBase
|
|
485
|
-
key={item.service_name}
|
|
486
|
-
aria-pressed={active}
|
|
487
|
-
onClick={() => onSelect(item.service_name)}
|
|
488
|
-
sx={{
|
|
489
|
-
gap: 0.75,
|
|
490
|
-
height: 32,
|
|
491
|
-
px: 1.5,
|
|
492
|
-
borderRadius: "16px",
|
|
493
|
-
fontFamily: FONT_STACK,
|
|
494
|
-
fontSize: 13,
|
|
495
|
-
fontWeight: active ? 700 : 400,
|
|
496
|
-
color: active ? COLORS.magenta_s2 : COLORS.s5,
|
|
497
|
-
bgcolor: active ? COLORS.white : COLORS.s2,
|
|
498
|
-
border: `1px solid ${active ? COLORS.magenta_s2 : COLORS.s2}`,
|
|
499
|
-
}}
|
|
500
|
-
>
|
|
501
|
-
<ServiceIcon icon={item.icon} sx={{ fontSize: 16, color: "inherit" }} />
|
|
502
|
-
{item.display_name}
|
|
503
|
-
{/* Mismo orden que la lista lateral: «te toca» y luego el estatus. */}
|
|
504
|
-
{isPendingFor(item, userId) && <PendingDot size={7} withTooltip={false} />}
|
|
505
|
-
<StatusChip {...serviceStatusProps(item)} oval />
|
|
506
|
-
</ButtonBase>
|
|
507
|
-
);
|
|
508
|
-
})}
|
|
509
|
-
</Stack>
|
|
510
|
-
</Box>
|
|
511
|
-
)}
|
|
512
|
-
|
|
513
|
-
<RejectionNote service={service} />
|
|
514
|
-
|
|
515
|
-
<Panel
|
|
516
|
-
title={`${service.display_name} · ${SHOTS.length} tomas`}
|
|
517
|
-
aside={<Typography sx={captionSx}>{ready} listas para este servicio</Typography>}
|
|
518
|
-
>
|
|
519
|
-
<Box sx={{ display: "grid", gridTemplateColumns: "repeat(auto-fill, minmax(132px, 1fr))", gap: 1.5 }}>
|
|
520
|
-
{SHOTS.map((shot, index) => {
|
|
521
|
-
const state = TILE_STATES[states[index]];
|
|
522
|
-
return (
|
|
523
|
-
<Box
|
|
524
|
-
key={shot}
|
|
525
|
-
sx={{
|
|
526
|
-
border: `1px solid ${states[index] === "rejected" ? COLORS.rejected_status : COLORS.s2}`,
|
|
527
|
-
borderRadius: "8px",
|
|
528
|
-
overflow: "hidden",
|
|
529
|
-
bgcolor: COLORS.white,
|
|
530
|
-
}}
|
|
531
|
-
>
|
|
532
|
-
<Box
|
|
533
|
-
sx={{
|
|
534
|
-
aspectRatio: "1 / 1",
|
|
535
|
-
display: "flex",
|
|
536
|
-
alignItems: "center",
|
|
537
|
-
justifyContent: "center",
|
|
538
|
-
background: `linear-gradient(135deg, ${COLORS.blue_light}, ${COLORS.s2})`,
|
|
539
|
-
}}
|
|
540
|
-
>
|
|
541
|
-
<ImageOutlinedIcon sx={{ fontSize: 34, color: COLORS.s3 }} />
|
|
542
|
-
</Box>
|
|
543
|
-
<Stack direction="row" alignItems="center" spacing={0.75} sx={{ px: 1, py: 0.75 }}>
|
|
544
|
-
<Typography noWrap sx={{ flex: 1, fontFamily: FONT_STACK, fontSize: 12, color: COLORS.color_gray }}>
|
|
545
|
-
{shot}
|
|
546
|
-
</Typography>
|
|
547
|
-
<Box sx={{ width: 7, height: 7, borderRadius: "50%", bgcolor: state.color }} />
|
|
548
|
-
<Typography sx={{ fontFamily: FONT_STACK, fontSize: 11, color: state.color }}>{state.label}</Typography>
|
|
549
|
-
</Stack>
|
|
550
|
-
</Box>
|
|
551
|
-
);
|
|
552
|
-
})}
|
|
553
|
-
<DropZone label="Subir imágenes" hint="JPG o PNG" sx={{ minHeight: 0, aspectRatio: "1 / 1.22" }} />
|
|
554
|
-
</Box>
|
|
555
|
-
</Panel>
|
|
556
|
-
</>
|
|
557
|
-
);
|
|
558
|
-
};
|
|
559
|
-
|
|
560
|
-
/* ------------------------------------------------------------------------ */
|
|
561
|
-
/* Video */
|
|
562
|
-
/* ------------------------------------------------------------------------ */
|
|
563
|
-
|
|
564
|
-
const VIDEOS = [
|
|
565
|
-
{ name: "Video demostrativo", length: "0:45", state: "ready" },
|
|
566
|
-
{ name: "Unboxing", length: "1:20", state: "pending" },
|
|
567
|
-
];
|
|
568
|
-
|
|
569
|
-
const VideoView = ({ service }) => (
|
|
570
|
-
<>
|
|
571
|
-
<RejectionNote service={service} />
|
|
572
|
-
<Panel title="Videos del producto" aside={<Typography sx={captionSx}>MP4, hasta 500 MB</Typography>}>
|
|
573
|
-
<Box sx={{ display: "grid", gridTemplateColumns: "repeat(auto-fill, minmax(220px, 1fr))", gap: 1.5 }}>
|
|
574
|
-
{VIDEOS.map((video) => {
|
|
575
|
-
const state = TILE_STATES[video.state];
|
|
576
|
-
return (
|
|
577
|
-
<Box key={video.name} sx={{ border: `1px solid ${COLORS.s2}`, borderRadius: "8px", overflow: "hidden" }}>
|
|
578
|
-
<Box
|
|
579
|
-
sx={{
|
|
580
|
-
aspectRatio: "16 / 9",
|
|
581
|
-
display: "flex",
|
|
582
|
-
alignItems: "center",
|
|
583
|
-
justifyContent: "center",
|
|
584
|
-
bgcolor: COLORS.deep_gray,
|
|
585
|
-
}}
|
|
586
|
-
>
|
|
587
|
-
<PlayCircleOutlineIcon sx={{ fontSize: 40, color: COLORS.white, opacity: 0.85 }} />
|
|
588
|
-
</Box>
|
|
589
|
-
<Stack direction="row" alignItems="center" spacing={0.75} sx={{ px: 1.25, py: 1 }}>
|
|
590
|
-
<Typography noWrap sx={{ flex: 1, fontFamily: FONT_STACK, fontSize: 13, color: COLORS.color_gray }}>
|
|
591
|
-
{video.name} · {video.length}
|
|
592
|
-
</Typography>
|
|
593
|
-
<Box sx={{ width: 7, height: 7, borderRadius: "50%", bgcolor: state.color }} />
|
|
594
|
-
<Typography sx={{ fontFamily: FONT_STACK, fontSize: 11, color: state.color }}>{state.label}</Typography>
|
|
595
|
-
</Stack>
|
|
596
|
-
</Box>
|
|
597
|
-
);
|
|
598
|
-
})}
|
|
599
|
-
<DropZone label="Subir video" hint="Arrastra el archivo o haz clic" sx={{ aspectRatio: "16 / 11.5" }} />
|
|
600
|
-
</Box>
|
|
601
|
-
</Panel>
|
|
602
|
-
</>
|
|
603
|
-
);
|
|
604
|
-
|
|
605
|
-
/* ------------------------------------------------------------------------ */
|
|
606
|
-
/* Archivos (diseño, 360°, reportes, carga en cadena) */
|
|
607
|
-
/* ------------------------------------------------------------------------ */
|
|
608
|
-
|
|
609
|
-
const FILES = {
|
|
610
|
-
brandstore: [
|
|
611
|
-
["brandstore_taladro_v2.zip", "Versión 2 · 10/10/2026 · 4.2 MB"],
|
|
612
|
-
["brandstore_taladro_v1.zip", "Versión 1 · 03/10/2026 · 4.0 MB"],
|
|
613
|
-
],
|
|
614
|
-
aplusPlatinum: [["aplus_taladro_v1.html", "Versión 1 · 09/10/2026 · 380 KB"]],
|
|
615
|
-
golden: [["golden_thd_taladro_borrador.zip", "Borrador · 12/10/2026 · 2.1 MB"]],
|
|
616
|
-
quality: [["reporte_calidad_11096.pdf", "Versión 1 · 11/10/2026 · 640 KB"]],
|
|
617
|
-
audit: [],
|
|
618
|
-
secodatReview: [["revision_secodat_11096.xlsx", "Versión final · 06/10/2026 · 85 KB"]],
|
|
619
|
-
};
|
|
620
|
-
|
|
621
|
-
const FilesView = ({ service }) => {
|
|
622
|
-
const files = FILES[service.service_name] ?? [];
|
|
623
|
-
return (
|
|
624
|
-
<>
|
|
625
|
-
<RejectionNote service={service} />
|
|
626
|
-
<Panel
|
|
627
|
-
title={`Entregable: ${service.deliverable ?? "Archivo"}`}
|
|
628
|
-
aside={<Typography sx={captionSx}>{files.length} {files.length === 1 ? "archivo" : "archivos"}</Typography>}
|
|
629
|
-
>
|
|
630
|
-
{files.length === 0 ? (
|
|
631
|
-
<Typography sx={{ ...captionSx, mb: 1.5 }}>Aún no hay entregables para este servicio.</Typography>
|
|
632
|
-
) : (
|
|
633
|
-
<Stack sx={{ mb: 1.5 }}>
|
|
634
|
-
{files.map(([name, meta]) => (
|
|
635
|
-
<Stack
|
|
636
|
-
key={name}
|
|
637
|
-
direction="row"
|
|
638
|
-
alignItems="center"
|
|
639
|
-
spacing={1.5}
|
|
640
|
-
sx={{ py: 1.25, "& + &": { borderTop: `1px solid ${COLORS.s2}` } }}
|
|
641
|
-
>
|
|
642
|
-
<Box
|
|
643
|
-
sx={{
|
|
644
|
-
width: 36,
|
|
645
|
-
height: 36,
|
|
646
|
-
borderRadius: "8px",
|
|
647
|
-
bgcolor: COLORS.s2,
|
|
648
|
-
display: "flex",
|
|
649
|
-
alignItems: "center",
|
|
650
|
-
justifyContent: "center",
|
|
651
|
-
flexShrink: 0,
|
|
652
|
-
}}
|
|
653
|
-
>
|
|
654
|
-
<InsertDriveFileOutlinedIcon sx={{ fontSize: 20, color: COLORS.s4 }} />
|
|
655
|
-
</Box>
|
|
656
|
-
<Box sx={{ flex: 1, minWidth: 0 }}>
|
|
657
|
-
<Typography noWrap sx={{ fontFamily: FONT_STACK, fontSize: 14, color: COLORS.color_gray }}>
|
|
658
|
-
{name}
|
|
659
|
-
</Typography>
|
|
660
|
-
<Typography sx={captionSx}>{meta}</Typography>
|
|
661
|
-
</Box>
|
|
662
|
-
<Button sx={{ ...outlineButtonSx, height: 28, px: 1.5, fontSize: 12 }}>Descargar</Button>
|
|
663
|
-
</Stack>
|
|
664
|
-
))}
|
|
665
|
-
</Stack>
|
|
666
|
-
)}
|
|
667
|
-
<DropZone label="Subir entregable" hint="PDF, ZIP, HTML o imágenes" />
|
|
668
|
-
</Panel>
|
|
669
|
-
</>
|
|
670
|
-
);
|
|
671
|
-
};
|
|
672
|
-
|
|
673
|
-
/* ------------------------------------------------------------------------ */
|
|
674
|
-
|
|
675
|
-
export const ServiceView = ({ selected, service, services, userId, onSelect, retailerName }) => {
|
|
676
|
-
if (selected === SUMMARY || !service) {
|
|
677
|
-
return <SummaryView services={services} userId={userId} onSelect={onSelect} />;
|
|
678
|
-
}
|
|
679
|
-
switch (service.view) {
|
|
680
|
-
case "attributes":
|
|
681
|
-
return <AttributesView key={service.service_name} service={service} retailerName={retailerName} />;
|
|
682
|
-
case "text":
|
|
683
|
-
return <TextView key={service.service_name} service={service} retailerName={retailerName} />;
|
|
684
|
-
case "translation":
|
|
685
|
-
return (
|
|
686
|
-
<TranslationView
|
|
687
|
-
service={service}
|
|
688
|
-
hasSourceServices={services.some((item) => ["description", "datasheet"].includes(item.service_name))}
|
|
689
|
-
/>
|
|
690
|
-
);
|
|
691
|
-
case "images":
|
|
692
|
-
return (
|
|
693
|
-
<ImagesView
|
|
694
|
-
service={service}
|
|
695
|
-
imageServices={services.filter((item) => item.view === "images")}
|
|
696
|
-
onSelect={onSelect}
|
|
697
|
-
userId={userId}
|
|
698
|
-
/>
|
|
699
|
-
);
|
|
700
|
-
case "video":
|
|
701
|
-
return <VideoView service={service} />;
|
|
702
|
-
default:
|
|
703
|
-
return <FilesView key={service.service_name} service={service} />;
|
|
704
|
-
}
|
|
705
|
-
};
|