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,556 +0,0 @@
|
|
|
1
|
-
import { useEffect, useMemo, useState } from "react";
|
|
2
|
-
import {
|
|
3
|
-
Box,
|
|
4
|
-
Button,
|
|
5
|
-
ButtonBase,
|
|
6
|
-
Collapse,
|
|
7
|
-
InputAdornment,
|
|
8
|
-
TextField,
|
|
9
|
-
Tooltip,
|
|
10
|
-
Typography,
|
|
11
|
-
} from "@mui/material";
|
|
12
|
-
import ExpandMoreIcon from "@mui/icons-material/ExpandMore";
|
|
13
|
-
import SearchIcon from "@mui/icons-material/Search";
|
|
14
|
-
import EditOutlinedIcon from "@mui/icons-material/EditOutlined";
|
|
15
|
-
import CloudUploadOutlinedIcon from "@mui/icons-material/CloudUploadOutlined";
|
|
16
|
-
import ArrowForwardIcon from "@mui/icons-material/ArrowForward";
|
|
17
|
-
import AssigneePicker from "./AssigneePicker";
|
|
18
|
-
import { whiteTooltipProps } from "./tooltipStyles";
|
|
19
|
-
import { DEMO_CATALOG } from "./demo";
|
|
20
|
-
import {
|
|
21
|
-
getAttention,
|
|
22
|
-
getProductServices,
|
|
23
|
-
getRetailerStatuses,
|
|
24
|
-
getServiceStage,
|
|
25
|
-
getStatusColor,
|
|
26
|
-
getStatusName,
|
|
27
|
-
groupServicesByStage,
|
|
28
|
-
pickInitialService,
|
|
29
|
-
} from "./servicesModel";
|
|
30
|
-
|
|
31
|
-
/*
|
|
32
|
-
Pestaña "Servicios" de la edición de producto.
|
|
33
|
-
|
|
34
|
-
Con 3 servicios bastaban 3 pestañas; con 20 la barra deja de servir. Aquí
|
|
35
|
-
todos los servicios del producto viven en un mismo lugar, con el lenguaje que
|
|
36
|
-
ya usa la tabla de Creators:
|
|
37
|
-
- izquierda: el resumen (barra por etapas y lo que pide atención), un
|
|
38
|
-
buscador y la lista agrupada por etapa, desplegable.
|
|
39
|
-
- derecha: el servicio elegido: estatus, equipo (responsable, facilitador,
|
|
40
|
-
auditor), su avance por cadena y su entregable.
|
|
41
|
-
Ficha técnica, Descripción e Imágenes también aparecen en la lista para ver
|
|
42
|
-
el panorama completo, pero se siguen editando en su pestaña (botón "Ir a").
|
|
43
|
-
*/
|
|
44
|
-
|
|
45
|
-
const C = {
|
|
46
|
-
pink: "#E33AA9",
|
|
47
|
-
pinkHard: "#B12D84",
|
|
48
|
-
purple: "#8A6CAA",
|
|
49
|
-
textPurple: "#503D66",
|
|
50
|
-
purpleLight: "#F0EEF2",
|
|
51
|
-
white: "#FFFFFF",
|
|
52
|
-
gray: "#707070",
|
|
53
|
-
grayLight: "#F0F0F0",
|
|
54
|
-
black: "#262626",
|
|
55
|
-
};
|
|
56
|
-
|
|
57
|
-
const sectionTitleSx = {
|
|
58
|
-
fontFamily: "Lato, sans-serif",
|
|
59
|
-
fontSize: 12,
|
|
60
|
-
fontWeight: 700,
|
|
61
|
-
letterSpacing: 0.4,
|
|
62
|
-
textTransform: "uppercase",
|
|
63
|
-
color: C.gray,
|
|
64
|
-
mb: 1,
|
|
65
|
-
};
|
|
66
|
-
|
|
67
|
-
function StatusChip({ status, long = false }) {
|
|
68
|
-
const color = getStatusColor(status);
|
|
69
|
-
return (
|
|
70
|
-
<Box
|
|
71
|
-
component="span"
|
|
72
|
-
title={getStatusName(status)}
|
|
73
|
-
sx={{
|
|
74
|
-
display: "inline-flex",
|
|
75
|
-
alignItems: "center",
|
|
76
|
-
height: 22,
|
|
77
|
-
px: 1,
|
|
78
|
-
borderRadius: "6px",
|
|
79
|
-
background: color,
|
|
80
|
-
color: C.white,
|
|
81
|
-
fontFamily: "Lato, sans-serif",
|
|
82
|
-
fontSize: 12,
|
|
83
|
-
fontWeight: 700,
|
|
84
|
-
whiteSpace: "nowrap",
|
|
85
|
-
}}
|
|
86
|
-
>
|
|
87
|
-
{long ? `${status} · ${getStatusName(status)}` : status}
|
|
88
|
-
</Box>
|
|
89
|
-
);
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
function StageDot({ color, size = 8 }) {
|
|
93
|
-
return (
|
|
94
|
-
<Box component="span" sx={{ width: size, height: size, borderRadius: "50%", background: color, flexShrink: 0 }} />
|
|
95
|
-
);
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
function ServicesBar({ groups }) {
|
|
99
|
-
return (
|
|
100
|
-
<Box aria-hidden="true" sx={{ display: "flex", gap: "2px", height: 8 }}>
|
|
101
|
-
{groups.flatMap((group) =>
|
|
102
|
-
group.services.map((service) => (
|
|
103
|
-
<Box
|
|
104
|
-
key={service.service_name}
|
|
105
|
-
component="span"
|
|
106
|
-
sx={{ flex: "1 1 0", minWidth: 3, borderRadius: "2px", background: group.stage.color }}
|
|
107
|
-
/>
|
|
108
|
-
))
|
|
109
|
-
)}
|
|
110
|
-
</Box>
|
|
111
|
-
);
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
export default function ServicesWorkspace({
|
|
115
|
-
product,
|
|
116
|
-
retailers = [],
|
|
117
|
-
users = {},
|
|
118
|
-
canAssign = true,
|
|
119
|
-
onAssign,
|
|
120
|
-
onOpenEditor,
|
|
121
|
-
}) {
|
|
122
|
-
const services = useMemo(() => getProductServices(product), [product]);
|
|
123
|
-
const groups = useMemo(() => groupServicesByStage(services), [services]);
|
|
124
|
-
const attention = getAttention(groups);
|
|
125
|
-
|
|
126
|
-
const [query, setQuery] = useState("");
|
|
127
|
-
const [selectedName, setSelectedName] = useState(() => pickInitialService(services));
|
|
128
|
-
const [expanded, setExpanded] = useState([]);
|
|
129
|
-
|
|
130
|
-
// Al cambiar de producto: se elige lo que pide atención y se abre su etapa.
|
|
131
|
-
const productKey = `${product?.id_article}-${product?.id_order}`;
|
|
132
|
-
useEffect(() => {
|
|
133
|
-
const first = pickInitialService(services);
|
|
134
|
-
setSelectedName(first);
|
|
135
|
-
const firstService = services.find((service) => service.service_name === first);
|
|
136
|
-
setExpanded(firstService ? [getServiceStage(firstService).stage_name] : []);
|
|
137
|
-
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
138
|
-
}, [productKey]);
|
|
139
|
-
|
|
140
|
-
const text = query.trim().toLowerCase();
|
|
141
|
-
const visibleGroups = text
|
|
142
|
-
? groupServicesByStage(services.filter((service) => service.display_name.toLowerCase().includes(text)))
|
|
143
|
-
: groups;
|
|
144
|
-
|
|
145
|
-
const selected = services.find((service) => service.service_name === selectedName) ?? null;
|
|
146
|
-
|
|
147
|
-
const toggle = (stageName) =>
|
|
148
|
-
setExpanded((prev) => (prev.includes(stageName) ? prev.filter((name) => name !== stageName) : [...prev, stageName]));
|
|
149
|
-
|
|
150
|
-
if (services.length === 0) {
|
|
151
|
-
return (
|
|
152
|
-
<Box sx={{ p: 4, textAlign: "center", color: C.gray, fontFamily: "Lato, sans-serif" }}>
|
|
153
|
-
Este producto no tiene servicios solicitados.
|
|
154
|
-
</Box>
|
|
155
|
-
);
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
return (
|
|
159
|
-
<Box
|
|
160
|
-
sx={{
|
|
161
|
-
display: "grid",
|
|
162
|
-
gridTemplateColumns: { xs: "1fr", md: "300px minmax(0, 1fr)" },
|
|
163
|
-
minHeight: 480,
|
|
164
|
-
border: `1px solid ${C.purpleLight}`,
|
|
165
|
-
borderRadius: "12px",
|
|
166
|
-
background: C.white,
|
|
167
|
-
overflow: "hidden",
|
|
168
|
-
textAlign: "left",
|
|
169
|
-
// La librería no fija la tipografía del cuerpo: sin esto, los textos
|
|
170
|
-
// sueltos (Box) heredan la del navegador fuera de las aplicaciones.
|
|
171
|
-
fontFamily: "Lato, sans-serif",
|
|
172
|
-
}}
|
|
173
|
-
>
|
|
174
|
-
{/* ---- Lista ---- */}
|
|
175
|
-
<Box
|
|
176
|
-
component="nav"
|
|
177
|
-
aria-label="Servicios del producto"
|
|
178
|
-
sx={{
|
|
179
|
-
borderRight: { md: `1px solid ${C.purpleLight}` },
|
|
180
|
-
p: 2,
|
|
181
|
-
display: "flex",
|
|
182
|
-
flexDirection: "column",
|
|
183
|
-
gap: 1.5,
|
|
184
|
-
minWidth: 0,
|
|
185
|
-
}}
|
|
186
|
-
>
|
|
187
|
-
<Box sx={{ display: "flex", alignItems: "baseline", gap: 1 }}>
|
|
188
|
-
<Typography sx={{ fontFamily: "Raleway, sans-serif", fontSize: 16, fontWeight: 600, color: C.textPurple }}>
|
|
189
|
-
Servicios
|
|
190
|
-
</Typography>
|
|
191
|
-
<Typography sx={{ flex: 1, fontFamily: "Lato, sans-serif", fontSize: 12, color: C.gray }}>
|
|
192
|
-
{services.length}
|
|
193
|
-
</Typography>
|
|
194
|
-
{attention && (
|
|
195
|
-
<Box
|
|
196
|
-
component="span"
|
|
197
|
-
sx={{
|
|
198
|
-
display: "inline-flex",
|
|
199
|
-
alignItems: "center",
|
|
200
|
-
gap: 0.75,
|
|
201
|
-
px: 1,
|
|
202
|
-
height: 22,
|
|
203
|
-
alignSelf: "center",
|
|
204
|
-
borderRadius: "11px",
|
|
205
|
-
background: C.purpleLight,
|
|
206
|
-
fontFamily: "Lato, sans-serif",
|
|
207
|
-
fontSize: 12,
|
|
208
|
-
fontWeight: 700,
|
|
209
|
-
color: C.textPurple,
|
|
210
|
-
whiteSpace: "nowrap",
|
|
211
|
-
}}
|
|
212
|
-
>
|
|
213
|
-
<StageDot color={attention.stage.color} />
|
|
214
|
-
{attention.label}
|
|
215
|
-
</Box>
|
|
216
|
-
)}
|
|
217
|
-
</Box>
|
|
218
|
-
|
|
219
|
-
<ServicesBar groups={groups} />
|
|
220
|
-
|
|
221
|
-
<TextField
|
|
222
|
-
size="small"
|
|
223
|
-
placeholder="Buscar servicio…"
|
|
224
|
-
value={query}
|
|
225
|
-
onChange={(event) => setQuery(event.target.value)}
|
|
226
|
-
InputProps={{
|
|
227
|
-
startAdornment: (
|
|
228
|
-
<InputAdornment position="start">
|
|
229
|
-
<SearchIcon sx={{ fontSize: 18, color: C.gray }} />
|
|
230
|
-
</InputAdornment>
|
|
231
|
-
),
|
|
232
|
-
sx: {
|
|
233
|
-
borderRadius: "8px",
|
|
234
|
-
fontFamily: "Lato, sans-serif",
|
|
235
|
-
fontSize: 13,
|
|
236
|
-
"&.Mui-focused .MuiOutlinedInput-notchedOutline": { borderColor: C.pink },
|
|
237
|
-
},
|
|
238
|
-
}}
|
|
239
|
-
/>
|
|
240
|
-
|
|
241
|
-
<Box sx={{ display: "flex", flexDirection: "column", gap: 0.5, overflowY: "auto", maxHeight: 520 }}>
|
|
242
|
-
{visibleGroups.length === 0 && (
|
|
243
|
-
<Typography sx={{ py: 2, textAlign: "center", fontSize: 13, color: C.gray }}>Sin coincidencias</Typography>
|
|
244
|
-
)}
|
|
245
|
-
{visibleGroups.map(({ stage, services: inStage }) => {
|
|
246
|
-
const isOpen = Boolean(text) || expanded.includes(stage.stage_name);
|
|
247
|
-
return (
|
|
248
|
-
<Box key={stage.stage_name}>
|
|
249
|
-
<ButtonBase
|
|
250
|
-
onClick={() => toggle(stage.stage_name)}
|
|
251
|
-
aria-expanded={isOpen}
|
|
252
|
-
sx={{
|
|
253
|
-
width: "100%",
|
|
254
|
-
display: "flex",
|
|
255
|
-
alignItems: "center",
|
|
256
|
-
gap: 1,
|
|
257
|
-
px: 1,
|
|
258
|
-
py: 0.75,
|
|
259
|
-
borderRadius: "8px",
|
|
260
|
-
textAlign: "left",
|
|
261
|
-
"&:hover": { backgroundColor: C.purpleLight },
|
|
262
|
-
}}
|
|
263
|
-
>
|
|
264
|
-
<StageDot color={stage.color} size={10} />
|
|
265
|
-
<Typography sx={{ flex: 1, fontSize: 13, fontWeight: 700, color: C.textPurple }}>
|
|
266
|
-
{stage.display_name}
|
|
267
|
-
</Typography>
|
|
268
|
-
<Typography sx={{ fontFamily: "Lato, sans-serif", fontSize: 12, fontWeight: 700, color: C.gray }}>
|
|
269
|
-
{inStage.length}
|
|
270
|
-
</Typography>
|
|
271
|
-
<ExpandMoreIcon
|
|
272
|
-
fontSize="small"
|
|
273
|
-
sx={{ color: C.purple, transition: "transform 150ms", transform: isOpen ? "rotate(180deg)" : "none" }}
|
|
274
|
-
/>
|
|
275
|
-
</ButtonBase>
|
|
276
|
-
<Collapse in={isOpen} unmountOnExit>
|
|
277
|
-
<Box sx={{ display: "flex", flexDirection: "column", py: 0.25 }}>
|
|
278
|
-
{inStage.map((service) => {
|
|
279
|
-
const active = service.service_name === selectedName;
|
|
280
|
-
return (
|
|
281
|
-
<ButtonBase
|
|
282
|
-
key={service.service_name}
|
|
283
|
-
onClick={() => setSelectedName(service.service_name)}
|
|
284
|
-
aria-current={active ? "true" : undefined}
|
|
285
|
-
sx={{
|
|
286
|
-
display: "flex",
|
|
287
|
-
alignItems: "center",
|
|
288
|
-
gap: 1,
|
|
289
|
-
pl: 3,
|
|
290
|
-
pr: 1,
|
|
291
|
-
py: 0.75,
|
|
292
|
-
borderRadius: "8px",
|
|
293
|
-
textAlign: "left",
|
|
294
|
-
borderLeft: `3px solid ${active ? C.pink : "transparent"}`,
|
|
295
|
-
backgroundColor: active ? C.purpleLight : "transparent",
|
|
296
|
-
"&:hover": { backgroundColor: C.purpleLight },
|
|
297
|
-
}}
|
|
298
|
-
>
|
|
299
|
-
<Typography
|
|
300
|
-
sx={{
|
|
301
|
-
flex: 1,
|
|
302
|
-
minWidth: 0,
|
|
303
|
-
fontSize: 14,
|
|
304
|
-
fontWeight: active ? 700 : 400,
|
|
305
|
-
color: active ? C.textPurple : C.black,
|
|
306
|
-
overflow: "hidden",
|
|
307
|
-
textOverflow: "ellipsis",
|
|
308
|
-
whiteSpace: "nowrap",
|
|
309
|
-
}}
|
|
310
|
-
>
|
|
311
|
-
{service.display_name}
|
|
312
|
-
</Typography>
|
|
313
|
-
{service.editor_tab && (
|
|
314
|
-
<Tooltip {...whiteTooltipProps} title={`Se edita en la pestaña ${service.editor_tab}`} arrow>
|
|
315
|
-
<EditOutlinedIcon sx={{ fontSize: 18, color: C.purple }} />
|
|
316
|
-
</Tooltip>
|
|
317
|
-
)}
|
|
318
|
-
<StatusChip status={service.has_workflow ? service.status : "REG"} />
|
|
319
|
-
</ButtonBase>
|
|
320
|
-
);
|
|
321
|
-
})}
|
|
322
|
-
</Box>
|
|
323
|
-
</Collapse>
|
|
324
|
-
</Box>
|
|
325
|
-
);
|
|
326
|
-
})}
|
|
327
|
-
</Box>
|
|
328
|
-
</Box>
|
|
329
|
-
|
|
330
|
-
{/* ---- Detalle ---- */}
|
|
331
|
-
<Box component="section" aria-live="polite" sx={{ p: 3, minWidth: 0 }}>
|
|
332
|
-
{selected ? (
|
|
333
|
-
<ServiceDetail
|
|
334
|
-
product={product}
|
|
335
|
-
service={selected}
|
|
336
|
-
retailers={retailers}
|
|
337
|
-
users={users}
|
|
338
|
-
canAssign={canAssign}
|
|
339
|
-
onAssign={onAssign}
|
|
340
|
-
onOpenEditor={onOpenEditor}
|
|
341
|
-
/>
|
|
342
|
-
) : (
|
|
343
|
-
<Typography sx={{ color: C.gray }}>Elige un servicio de la lista.</Typography>
|
|
344
|
-
)}
|
|
345
|
-
</Box>
|
|
346
|
-
</Box>
|
|
347
|
-
);
|
|
348
|
-
}
|
|
349
|
-
|
|
350
|
-
const personName = (list = [], id) => {
|
|
351
|
-
if (!id) return "Por asignar";
|
|
352
|
-
const person = list.find((user) => user.id_user === id);
|
|
353
|
-
return person ? [person.name, person.last_name].filter(Boolean).join(" ") : "Asignado";
|
|
354
|
-
};
|
|
355
|
-
|
|
356
|
-
function ServiceDetail({ product, service, retailers, users, canAssign, onAssign, onOpenEditor }) {
|
|
357
|
-
const stage = getServiceStage(service);
|
|
358
|
-
const retailerStatuses = getRetailerStatuses(product, service, retailers);
|
|
359
|
-
const deliverableType =
|
|
360
|
-
service.deliverable_type ??
|
|
361
|
-
DEMO_CATALOG.find((item) => item.service_name === service.service_name)?.deliverable_type ??
|
|
362
|
-
"Entregable";
|
|
363
|
-
|
|
364
|
-
const lockReason = (role) => {
|
|
365
|
-
// El auditor es del producto, no del servicio: no depende de este.
|
|
366
|
-
const perService = role !== "auditor";
|
|
367
|
-
if (perService && !service.has_workflow) return "Servicio de registro: no lleva equipo";
|
|
368
|
-
if (perService && service.is_demo) return "Se podrá asignar cuando el servicio exista en el catálogo";
|
|
369
|
-
if (!canAssign) return "Tu rol puede ver la asignación, pero no cambiarla";
|
|
370
|
-
if (!onAssign) return "Asigna desde la tabla de productos";
|
|
371
|
-
if (!(users[role] ?? []).length) return "No hay personas asignables para este rol";
|
|
372
|
-
return null;
|
|
373
|
-
};
|
|
374
|
-
|
|
375
|
-
const team = [
|
|
376
|
-
{ role: "especialist", label: "Responsable", currentId: service.assignees?.especialist },
|
|
377
|
-
{ role: "facilitator", label: "Facilitador", currentId: service.assignees?.facilitator },
|
|
378
|
-
{ role: "auditor", label: "Auditor", currentId: product?.id_auditor || null, note: "del producto" },
|
|
379
|
-
];
|
|
380
|
-
|
|
381
|
-
return (
|
|
382
|
-
<Box sx={{ display: "flex", flexDirection: "column", gap: 3 }}>
|
|
383
|
-
<Box>
|
|
384
|
-
<Box sx={{ display: "flex", alignItems: "center", gap: 1, flexWrap: "wrap" }}>
|
|
385
|
-
<Typography
|
|
386
|
-
component="h3"
|
|
387
|
-
sx={{ m: 0, fontFamily: "Raleway, sans-serif", fontSize: 22, fontWeight: 600, color: C.textPurple }}
|
|
388
|
-
>
|
|
389
|
-
{service.display_name}
|
|
390
|
-
</Typography>
|
|
391
|
-
{service.is_demo && (
|
|
392
|
-
<Typography component="span" sx={{ fontSize: 12, fontStyle: "italic", color: C.gray }}>
|
|
393
|
-
ejemplo
|
|
394
|
-
</Typography>
|
|
395
|
-
)}
|
|
396
|
-
</Box>
|
|
397
|
-
<Box sx={{ mt: 1, display: "flex", alignItems: "center", gap: 1, flexWrap: "wrap" }}>
|
|
398
|
-
<StatusChip status={service.has_workflow ? service.status : "REG"} long />
|
|
399
|
-
<Box
|
|
400
|
-
component="span"
|
|
401
|
-
sx={{ display: "inline-flex", alignItems: "center", gap: 0.75, fontSize: 13, color: C.gray }}
|
|
402
|
-
>
|
|
403
|
-
<StageDot color={stage.color} />
|
|
404
|
-
{stage.display_name}
|
|
405
|
-
</Box>
|
|
406
|
-
</Box>
|
|
407
|
-
</Box>
|
|
408
|
-
|
|
409
|
-
{service.editor_tab && (
|
|
410
|
-
<Box
|
|
411
|
-
sx={{
|
|
412
|
-
display: "flex",
|
|
413
|
-
alignItems: "center",
|
|
414
|
-
gap: 2,
|
|
415
|
-
p: 1.5,
|
|
416
|
-
borderRadius: "10px",
|
|
417
|
-
background: C.purpleLight,
|
|
418
|
-
}}
|
|
419
|
-
>
|
|
420
|
-
<EditOutlinedIcon sx={{ color: C.purple }} />
|
|
421
|
-
<Typography sx={{ flex: 1, fontSize: 14, color: C.textPurple }}>
|
|
422
|
-
Este servicio se trabaja en la pestaña <b>{service.editor_tab}</b>.
|
|
423
|
-
</Typography>
|
|
424
|
-
<Button
|
|
425
|
-
onClick={() => onOpenEditor?.(service.editor_tab)}
|
|
426
|
-
endIcon={<ArrowForwardIcon fontSize="small" />}
|
|
427
|
-
sx={{ textTransform: "none", fontWeight: 700, color: C.pinkHard, borderRadius: "8px" }}
|
|
428
|
-
>
|
|
429
|
-
Ir a {service.editor_tab}
|
|
430
|
-
</Button>
|
|
431
|
-
</Box>
|
|
432
|
-
)}
|
|
433
|
-
|
|
434
|
-
{service.has_workflow && (
|
|
435
|
-
<Box>
|
|
436
|
-
<Typography sx={sectionTitleSx}>Acciones</Typography>
|
|
437
|
-
{/* Propuesta: los botones salen de cat_status para el estatus actual
|
|
438
|
-
(aprobar -> approved_status_id, rechazar -> rejected_status_id) y
|
|
439
|
-
"Cambiar estatus" es la corrección manual con motivo. Deshabilitados
|
|
440
|
-
hasta que exista status-engine (ver plan técnico, §8.1). */}
|
|
441
|
-
<Tooltip {...whiteTooltipProps} title="Se habilita con el motor de estatus (status-engine)" arrow placement="top-start">
|
|
442
|
-
<Box sx={{ display: "inline-flex", gap: 1, flexWrap: "wrap" }}>
|
|
443
|
-
<Button disabled variant="contained" sx={{ textTransform: "none", borderRadius: "8px", boxShadow: "none" }}>
|
|
444
|
-
Aprobar
|
|
445
|
-
</Button>
|
|
446
|
-
<Button disabled variant="outlined" sx={{ textTransform: "none", borderRadius: "8px" }}>
|
|
447
|
-
Rechazar
|
|
448
|
-
</Button>
|
|
449
|
-
<Button disabled sx={{ textTransform: "none", borderRadius: "8px" }}>
|
|
450
|
-
Cambiar estatus…
|
|
451
|
-
</Button>
|
|
452
|
-
</Box>
|
|
453
|
-
</Tooltip>
|
|
454
|
-
</Box>
|
|
455
|
-
)}
|
|
456
|
-
|
|
457
|
-
<Box>
|
|
458
|
-
<Typography sx={sectionTitleSx}>Equipo</Typography>
|
|
459
|
-
<Box sx={{ display: "flex", flexDirection: "column", gap: 1 }}>
|
|
460
|
-
{team.map(({ role, label, currentId, note }) => (
|
|
461
|
-
<Box
|
|
462
|
-
key={role}
|
|
463
|
-
sx={{ display: "grid", gridTemplateColumns: "minmax(0, 1fr) auto", alignItems: "center", gap: 1 }}
|
|
464
|
-
>
|
|
465
|
-
<Typography sx={{ fontSize: 14, color: C.black }}>
|
|
466
|
-
{label}
|
|
467
|
-
{note && (
|
|
468
|
-
<Typography component="span" sx={{ ml: 0.75, fontSize: 12, color: C.gray }}>
|
|
469
|
-
{note}
|
|
470
|
-
</Typography>
|
|
471
|
-
)}
|
|
472
|
-
</Typography>
|
|
473
|
-
<AssigneePicker
|
|
474
|
-
key={`${service.service_name}-${role}-${currentId}`}
|
|
475
|
-
// Aquí el estatus ya está arriba: la pastilla dice quién es.
|
|
476
|
-
statusLabel={(current) =>
|
|
477
|
-
current ? personName([current], current.id_user) : personName(users[role], currentId)
|
|
478
|
-
}
|
|
479
|
-
statusTitle={getStatusName(service.status)}
|
|
480
|
-
users={users[role] ?? []}
|
|
481
|
-
currentId={currentId || null}
|
|
482
|
-
roleLabel={label}
|
|
483
|
-
lockedReason={lockReason(role)}
|
|
484
|
-
onAssign={(user) => onAssign?.(service.service_name, role, user)}
|
|
485
|
-
/>
|
|
486
|
-
</Box>
|
|
487
|
-
))}
|
|
488
|
-
</Box>
|
|
489
|
-
</Box>
|
|
490
|
-
|
|
491
|
-
<Box>
|
|
492
|
-
<Typography sx={sectionTitleSx}>Por cadena</Typography>
|
|
493
|
-
{retailerStatuses.length === 0 ? (
|
|
494
|
-
<Typography sx={{ fontSize: 13, color: C.gray }}>Sin cadenas para este servicio.</Typography>
|
|
495
|
-
) : (
|
|
496
|
-
<Box sx={{ display: "flex", flexDirection: "column", gap: 0.5 }}>
|
|
497
|
-
{retailerStatuses.map((item) => (
|
|
498
|
-
<Box
|
|
499
|
-
key={item.id_retailer}
|
|
500
|
-
sx={{
|
|
501
|
-
display: "flex",
|
|
502
|
-
alignItems: "center",
|
|
503
|
-
gap: 1,
|
|
504
|
-
py: 0.75,
|
|
505
|
-
px: 1.25,
|
|
506
|
-
borderRadius: "8px",
|
|
507
|
-
background: C.grayLight,
|
|
508
|
-
}}
|
|
509
|
-
>
|
|
510
|
-
<Typography sx={{ flex: 1, fontSize: 14, color: C.black }}>{item.retailer}</Typography>
|
|
511
|
-
<StatusChip status={item.status} />
|
|
512
|
-
</Box>
|
|
513
|
-
))}
|
|
514
|
-
</Box>
|
|
515
|
-
)}
|
|
516
|
-
</Box>
|
|
517
|
-
|
|
518
|
-
<Box>
|
|
519
|
-
<Typography sx={sectionTitleSx}>Entregable</Typography>
|
|
520
|
-
{service.editor_tab ? (
|
|
521
|
-
<Typography sx={{ fontSize: 13, color: C.gray }}>
|
|
522
|
-
El entregable es el contenido de la pestaña {service.editor_tab}.
|
|
523
|
-
</Typography>
|
|
524
|
-
) : (
|
|
525
|
-
<Box
|
|
526
|
-
sx={{
|
|
527
|
-
display: "flex",
|
|
528
|
-
alignItems: "center",
|
|
529
|
-
gap: 2,
|
|
530
|
-
p: 2,
|
|
531
|
-
borderRadius: "10px",
|
|
532
|
-
border: `1px dashed ${C.purple}`,
|
|
533
|
-
}}
|
|
534
|
-
>
|
|
535
|
-
<CloudUploadOutlinedIcon sx={{ color: C.purple, fontSize: 32 }} />
|
|
536
|
-
<Box sx={{ flex: 1 }}>
|
|
537
|
-
<Typography sx={{ fontSize: 14, fontWeight: 700, color: C.textPurple }}>
|
|
538
|
-
Aún no hay entregables
|
|
539
|
-
</Typography>
|
|
540
|
-
<Typography sx={{ fontSize: 13, color: C.gray }}>Tipo: {deliverableType}</Typography>
|
|
541
|
-
</Box>
|
|
542
|
-
<Tooltip {...whiteTooltipProps} title="Se habilita con el gestor de servicios" arrow>
|
|
543
|
-
<span>
|
|
544
|
-
<Button disabled variant="outlined" sx={{ textTransform: "none", borderRadius: "8px" }}>
|
|
545
|
-
Subir entregable
|
|
546
|
-
</Button>
|
|
547
|
-
</span>
|
|
548
|
-
</Tooltip>
|
|
549
|
-
</Box>
|
|
550
|
-
)}
|
|
551
|
-
</Box>
|
|
552
|
-
</Box>
|
|
553
|
-
);
|
|
554
|
-
}
|
|
555
|
-
|
|
556
|
-
export { ServicesWorkspace };
|