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.
Files changed (102) hide show
  1. package/.claude/settings.local.json +7 -0
  2. package/dist/ai/prompts/attribute.prompt.js +28 -0
  3. package/dist/ai/prompts/description.prompt.js +33 -0
  4. package/dist/ai/schemas/attribute.schema.js +35 -0
  5. package/dist/ai/schemas/description.schema.js +27 -0
  6. package/dist/components/atoms/MetricCard/index.js +3 -1
  7. package/dist/components/organisms/DashboardMetric/DashboardMetric.stories.js +12 -2
  8. package/dist/components/organisms/DashboardMetric/index.js +24 -1
  9. package/dist/components/organisms/DashboardMetric/styles.js +1 -1
  10. package/dist/components/pages/CreatorsProductEdition/CreatorsProductEditionView.js +11 -5
  11. package/dist/components/pages/CreatorsProductEdition/hooks/useCreatorsProductEdition.js +58 -31
  12. package/dist/components/pages/CreatorsProductEdition/mocks/api.fixtures.js +5 -1
  13. package/dist/components/pages/CreatorsProductEdition/mocks/mockApi.js +3 -40
  14. package/dist/components/pages/CreatorsProductEdition/sections/ImagesSection.js +18 -3
  15. package/dist/components/pages/CreatorsProductEdition/sections/StatusChip.js +4 -13
  16. package/dist/components/pages/CreatorsProductEdition/sections/TabsBar.js +10 -0
  17. package/dist/components/pages/CreatorsProductEdition/stories/CreatorsProductEdition.stories.js +10 -2
  18. package/dist/components/pages/Dashboard/dashboardUtils.js +3 -3
  19. package/dist/components/pages/Dashboard/index.js +102 -88
  20. package/dist/components/pages/Dashboard/styles.js +1 -1
  21. package/dist/components/pages/RetailerProductEdition/RetailerProductEdition.stories.js +2 -15
  22. package/dist/components/pages/RetailerProductEdition/index.js +40 -57
  23. package/dist/components/pages/RetailerProductEdition/utils.js +2 -6
  24. package/dist/index.js +0 -13
  25. package/package.json +1 -1
  26. package/src/components/atoms/MetricCard/index.js +2 -2
  27. package/src/components/organisms/DashboardMetric/DashboardMetric.stories.js +11 -0
  28. package/src/components/organisms/DashboardMetric/index.js +13 -1
  29. package/src/components/organisms/DashboardMetric/styles.js +13 -0
  30. package/src/components/pages/CreatorsProductEdition/CreatorsProductEditionView.jsx +22 -3
  31. package/src/components/pages/CreatorsProductEdition/hooks/useCreatorsProductEdition.js +36 -1
  32. package/src/components/pages/CreatorsProductEdition/mocks/api.fixtures.js +15 -3
  33. package/src/components/pages/CreatorsProductEdition/mocks/mockApi.js +8 -18
  34. package/src/components/pages/CreatorsProductEdition/sections/ImagesSection.jsx +24 -2
  35. package/src/components/pages/CreatorsProductEdition/sections/StatusChip.jsx +4 -8
  36. package/src/components/pages/CreatorsProductEdition/sections/TabsBar.jsx +9 -0
  37. package/src/components/pages/CreatorsProductEdition/stories/CreatorsProductEdition.stories.js +6 -0
  38. package/src/components/pages/Dashboard/dashboardUtils.js +3 -3
  39. package/src/components/pages/Dashboard/index.js +62 -106
  40. package/src/components/pages/Dashboard/styles.js +17 -12
  41. package/src/components/pages/RetailerProductEdition/RetailerProductEdition.stories.js +0 -12
  42. package/src/components/pages/RetailerProductEdition/index.js +3 -28
  43. package/src/components/pages/RetailerProductEdition/utils.js +0 -5
  44. package/src/index.js +0 -1
  45. package/dist/components/organisms/ServicesNavigation/ServiceToolbar.js +0 -210
  46. package/dist/components/organisms/ServicesNavigation/ServicesNavigation.stories.js +0 -138
  47. package/dist/components/organisms/ServicesNavigation/ServicesRail.js +0 -705
  48. package/dist/components/organisms/ServicesNavigation/catalog.js +0 -505
  49. package/dist/components/organisms/ServicesNavigation/index.js +0 -337
  50. package/dist/components/organisms/ServicesNavigation/model.js +0 -167
  51. package/dist/components/organisms/ServicesNavigation/ui.js +0 -371
  52. package/dist/components/organisms/ServicesNavigation/views.js +0 -1237
  53. package/dist/components/organisms/ServicesWorkspace/AssigneePicker.js +0 -459
  54. package/dist/components/organisms/ServicesWorkspace/ServicesWorkspace.stories.js +0 -119
  55. package/dist/components/organisms/ServicesWorkspace/demo.js +0 -137
  56. package/dist/components/organisms/ServicesWorkspace/index.js +0 -813
  57. package/dist/components/organisms/ServicesWorkspace/servicesModel.js +0 -351
  58. package/dist/components/organisms/ServicesWorkspace/tooltipStyles.js +0 -37
  59. package/dist/components/pages/CreatorsProductEdition/mocks/productServices.fixtures.js +0 -387
  60. package/dist/components/pages/ProductEdition/ProductEdition.stories.js +0 -148
  61. package/dist/components/pages/ProductEdition/ProductEditionView.js +0 -1355
  62. package/dist/components/pages/ProductEdition/ServiceHeader.js +0 -142
  63. package/dist/components/pages/ProductEdition/api.js +0 -256
  64. package/dist/components/pages/ProductEdition/index.js +0 -37
  65. package/dist/components/pages/ProductEdition/permissions.js +0 -200
  66. package/dist/components/pages/ProductEdition/permissions.test.js +0 -320
  67. package/dist/components/pages/ProductEdition/sections/CatalogServicePanel.js +0 -237
  68. package/dist/components/pages/ProductEdition/sections/EditionTopBar.js +0 -174
  69. package/dist/components/pages/ProductEdition/sections/ImagePanel.js +0 -440
  70. package/dist/components/pages/ProductEdition/sections/ServiceTeam.js +0 -275
  71. package/dist/components/pages/ProductEdition/sections/ServiceTools.js +0 -186
  72. package/dist/components/pages/ProductEdition/services.js +0 -233
  73. package/dist/components/pages/ProductEdition/services.test.js +0 -195
  74. package/src/components/organisms/ServicesNavigation/ServiceToolbar.jsx +0 -143
  75. package/src/components/organisms/ServicesNavigation/ServicesNavigation.stories.js +0 -101
  76. package/src/components/organisms/ServicesNavigation/ServicesRail.jsx +0 -591
  77. package/src/components/organisms/ServicesNavigation/catalog.js +0 -180
  78. package/src/components/organisms/ServicesNavigation/index.jsx +0 -232
  79. package/src/components/organisms/ServicesNavigation/model.js +0 -85
  80. package/src/components/organisms/ServicesNavigation/ui.jsx +0 -229
  81. package/src/components/organisms/ServicesNavigation/views.jsx +0 -705
  82. package/src/components/organisms/ServicesWorkspace/AssigneePicker.js +0 -348
  83. package/src/components/organisms/ServicesWorkspace/ServicesWorkspace.stories.js +0 -61
  84. package/src/components/organisms/ServicesWorkspace/demo.js +0 -58
  85. package/src/components/organisms/ServicesWorkspace/index.js +0 -556
  86. package/src/components/organisms/ServicesWorkspace/servicesModel.js +0 -203
  87. package/src/components/organisms/ServicesWorkspace/tooltipStyles.js +0 -27
  88. package/src/components/pages/CreatorsProductEdition/mocks/productServices.fixtures.js +0 -175
  89. package/src/components/pages/ProductEdition/ProductEdition.stories.js +0 -106
  90. package/src/components/pages/ProductEdition/ProductEditionView.jsx +0 -1142
  91. package/src/components/pages/ProductEdition/ServiceHeader.jsx +0 -104
  92. package/src/components/pages/ProductEdition/api.js +0 -84
  93. package/src/components/pages/ProductEdition/index.js +0 -21
  94. package/src/components/pages/ProductEdition/permissions.js +0 -128
  95. package/src/components/pages/ProductEdition/permissions.test.js +0 -249
  96. package/src/components/pages/ProductEdition/sections/CatalogServicePanel.jsx +0 -123
  97. package/src/components/pages/ProductEdition/sections/EditionTopBar.jsx +0 -134
  98. package/src/components/pages/ProductEdition/sections/ImagePanel.jsx +0 -342
  99. package/src/components/pages/ProductEdition/sections/ServiceTeam.jsx +0 -184
  100. package/src/components/pages/ProductEdition/sections/ServiceTools.jsx +0 -134
  101. package/src/components/pages/ProductEdition/services.js +0 -187
  102. package/src/components/pages/ProductEdition/services.test.js +0 -88
@@ -1,591 +0,0 @@
1
- import { useEffect, useMemo, useState } from "react";
2
- import {
3
- Box,
4
- ButtonBase,
5
- Collapse,
6
- IconButton,
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 MenuOpenIcon from "@mui/icons-material/MenuOpen";
15
- import CloseIcon from "@mui/icons-material/Close";
16
-
17
- import {
18
- COLORS,
19
- FONT_STACK,
20
- TITLE_STACK,
21
- PendingDot,
22
- ServiceIcon,
23
- StatusChip,
24
- serviceStatusProps,
25
- fieldSx,
26
- statusColor,
27
- serviceStatusColor,
28
- statusName,
29
- } from "./ui";
30
- import { SUMMARY, groupServices, isMine, isPendingFor } from "./model";
31
-
32
- /*
33
- Lista lateral de servicios: reemplaza a la barra de pestañas.
34
-
35
- - Solo muestra los servicios que el producto tiene contratados.
36
- - «Resumen» va primero: el panorama de todos los servicios.
37
- - Los servicios van en bloques fijos (Textos, Imágenes...). No se reordenan
38
- por estatus: cada uno queda siempre en el mismo lugar.
39
- - «Míos / Todos»: por omisión, solo lo que la persona trabaja. Aparece solo
40
- cuando sirve (trabaja algunos servicios del producto, no todos).
41
- - El buscador aparece cuando hay más de 8 servicios a la vista.
42
- - Se contrae a una columna de íconos para dejarle el ancho al editor.
43
- - `side`: de qué lado de la pantalla va («left» o «right»). Del lado derecho
44
- es el espejo del izquierdo: borde, marca del servicio abierto, flechas de
45
- contraer y globos de ayuda cambian de lado.
46
- */
47
-
48
- export const RAIL_WIDTH = 256;
49
- export const RAIL_COLLAPSED_WIDTH = 64;
50
- const SEARCH_THRESHOLD = 8;
51
- const EXPAND_ALL_THRESHOLD = 12;
52
-
53
- const CountBadge = ({ children, active = false }) => (
54
- <Box
55
- component="span"
56
- sx={{
57
- minWidth: 20,
58
- height: 18,
59
- px: 0.75,
60
- borderRadius: "9px",
61
- display: "inline-flex",
62
- alignItems: "center",
63
- justifyContent: "center",
64
- fontFamily: FONT_STACK,
65
- fontSize: 11,
66
- fontWeight: 700,
67
- bgcolor: active ? COLORS.s5 : COLORS.s2,
68
- color: active ? COLORS.white : COLORS.s4,
69
- }}
70
- >
71
- {children}
72
- </Box>
73
- );
74
-
75
- const FilterToggle = ({ value, onChange, mineCount, allCount }) => {
76
- const options = [
77
- { key: "mine", label: "Míos", count: mineCount },
78
- { key: "all", label: "Todos", count: allCount },
79
- ];
80
- return (
81
- <Box
82
- role="group"
83
- aria-label="Qué servicios ver"
84
- sx={{ display: "flex", p: "3px", mx: 2, mb: 1.5, bgcolor: COLORS.s2, borderRadius: "18px" }}
85
- >
86
- {options.map((option) => {
87
- const active = option.key === value;
88
- return (
89
- <ButtonBase
90
- key={option.key}
91
- aria-pressed={active}
92
- onClick={() => onChange(option.key)}
93
- sx={{
94
- flex: 1,
95
- height: 28,
96
- gap: 0.75,
97
- borderRadius: "15px",
98
- fontFamily: FONT_STACK,
99
- fontSize: 13,
100
- fontWeight: active ? 700 : 400,
101
- color: active ? COLORS.s5 : COLORS.s4,
102
- bgcolor: active ? COLORS.white : "transparent",
103
- boxShadow: active ? "0 1px 3px rgba(80, 61, 102, 0.18)" : "none",
104
- transition: "background-color 120ms, box-shadow 120ms",
105
- }}
106
- >
107
- {option.label}
108
- <CountBadge active={active}>{option.count}</CountBadge>
109
- </ButtonBase>
110
- );
111
- })}
112
- </Box>
113
- );
114
- };
115
-
116
- const itemSx = (active, side = "left") => ({
117
- width: "100%",
118
- display: "flex",
119
- alignItems: "center",
120
- gap: 1.25,
121
- height: 36,
122
- pl: 1.5,
123
- pr: 1,
124
- borderRadius: "8px",
125
- position: "relative",
126
- textAlign: "left",
127
- bgcolor: active ? COLORS.s2 : "transparent",
128
- "&:hover": { bgcolor: COLORS.s2 },
129
- "&::before": {
130
- content: '""',
131
- position: "absolute",
132
- [side]: 0,
133
- top: 8,
134
- bottom: 8,
135
- width: 3,
136
- borderRadius: "2px",
137
- bgcolor: active ? COLORS.magenta_s2 : "transparent",
138
- },
139
- });
140
-
141
- const ItemLabel = ({ active, children }) => (
142
- <Typography
143
- sx={{
144
- flex: 1,
145
- minWidth: 0,
146
- fontFamily: FONT_STACK,
147
- fontSize: 14,
148
- fontWeight: active ? 700 : 400,
149
- color: active ? COLORS.s5 : COLORS.color_gray,
150
- overflow: "hidden",
151
- textOverflow: "ellipsis",
152
- whiteSpace: "nowrap",
153
- }}
154
- >
155
- {children}
156
- </Typography>
157
- );
158
-
159
- /* ------------------------------------------------------------------------ */
160
- /* Lista completa */
161
- /* ------------------------------------------------------------------------ */
162
-
163
- const ExpandedRail = ({
164
- services,
165
- visible,
166
- selected,
167
- onSelect,
168
- userId,
169
- filter,
170
- onFilterChange,
171
- showFilter,
172
- onCollapse,
173
- onClose,
174
- side,
175
- }) => {
176
- const [query, setQuery] = useState("");
177
- const text = query.trim().toLowerCase();
178
- const matches = text
179
- ? visible.filter((service) => service.display_name.toLowerCase().includes(text))
180
- : visible;
181
- const groups = groupServices(matches);
182
- const pendingCount = services.filter((service) => isPendingFor(service, userId)).length;
183
-
184
- /*
185
- Bloques abiertos: todos si caben; si no, el del servicio abierto y los que
186
- tienen algo que le toca a la persona. Al buscar se abren todos.
187
- */
188
- // `visible` es un arreglo nuevo en cada render: se compara por sus nombres.
189
- const visibleKey = visible.map((service) => service.service_name).join(",");
190
- const defaultOpen = useMemo(() => {
191
- const all = groupServices(visible);
192
- if (visible.length <= EXPAND_ALL_THRESHOLD) return all.map((group) => group.key);
193
- return all
194
- .filter((group) =>
195
- group.services.some(
196
- (service) => service.service_name === selected || isPendingFor(service, userId)
197
- )
198
- )
199
- .map((group) => group.key);
200
- // Solo al cambiar lo que se ve: abrir un servicio no vuelve a cerrar bloques.
201
- // eslint-disable-next-line react-hooks/exhaustive-deps
202
- }, [visibleKey, userId]);
203
- const [open, setOpen] = useState(defaultOpen);
204
- useEffect(() => setOpen(defaultOpen), [defaultOpen]);
205
-
206
- // Si se abre un servicio de un bloque cerrado (desde el Resumen), se abre.
207
- const selectedGroup = services.find((service) => service.service_name === selected)?.group;
208
- useEffect(() => {
209
- if (selectedGroup) setOpen((prev) => (prev.includes(selectedGroup) ? prev : [...prev, selectedGroup]));
210
- }, [selectedGroup]);
211
-
212
- const toggle = (key) =>
213
- setOpen((prev) => (prev.includes(key) ? prev.filter((item) => item !== key) : [...prev, key]));
214
-
215
- return (
216
- <>
217
- <Box sx={{ display: "flex", alignItems: "center", gap: 1, px: 2, pt: 2, pb: 1.5 }}>
218
- <Typography sx={{ flex: 1, fontFamily: TITLE_STACK, fontWeight: 600, fontSize: 16, color: COLORS.s5 }}>
219
- Servicios
220
- </Typography>
221
- <CountBadge>{services.length}</CountBadge>
222
- {onCollapse && (
223
- <Tooltip title="Contraer la lista" arrow>
224
- <IconButton aria-label="Contraer la lista de servicios" size="small" onClick={onCollapse}>
225
- {/* La flecha apunta hacia el borde al que se contrae. */}
226
- <MenuOpenIcon
227
- sx={{ fontSize: 20, color: COLORS.s4, transform: side === "right" ? "scaleX(-1)" : "none" }}
228
- />
229
- </IconButton>
230
- </Tooltip>
231
- )}
232
- {onClose && (
233
- <IconButton aria-label="Cerrar la lista de servicios" size="small" onClick={onClose}>
234
- <CloseIcon sx={{ fontSize: 20, color: COLORS.s4 }} />
235
- </IconButton>
236
- )}
237
- </Box>
238
-
239
- {showFilter && (
240
- <FilterToggle
241
- value={filter}
242
- onChange={onFilterChange}
243
- mineCount={services.filter((service) => isMine(service, userId)).length}
244
- allCount={services.length}
245
- />
246
- )}
247
-
248
- {visible.length > SEARCH_THRESHOLD && (
249
- <Box sx={{ px: 2, mb: 1 }}>
250
- <TextField
251
- size="small"
252
- fullWidth
253
- placeholder="Buscar servicio…"
254
- value={query}
255
- onChange={(event) => setQuery(event.target.value)}
256
- sx={fieldSx}
257
- InputProps={{
258
- startAdornment: (
259
- <InputAdornment position="start">
260
- <SearchIcon sx={{ fontSize: 18, color: COLORS.s4 }} />
261
- </InputAdornment>
262
- ),
263
- }}
264
- />
265
- </Box>
266
- )}
267
-
268
- <Box sx={{ flex: 1, minHeight: 0, overflowY: "auto", px: 1, pb: 1 }}>
269
- <ButtonBase
270
- onClick={() => onSelect(SUMMARY)}
271
- aria-current={selected === SUMMARY ? "true" : undefined}
272
- sx={{ ...itemSx(selected === SUMMARY, side), mb: 0.5 }}
273
- >
274
- <ServiceIcon
275
- icon="summary"
276
- sx={{ color: selected === SUMMARY ? COLORS.magenta_s2 : COLORS.s4 }}
277
- />
278
- <ItemLabel active={selected === SUMMARY}>Resumen</ItemLabel>
279
- {pendingCount > 0 && <CountBadge>{pendingCount}</CountBadge>}
280
- </ButtonBase>
281
-
282
- {groups.length === 0 && (
283
- <Typography sx={{ py: 3, textAlign: "center", fontFamily: FONT_STACK, fontSize: 13, color: COLORS.s4 }}>
284
- Sin coincidencias
285
- </Typography>
286
- )}
287
-
288
- {groups.map((group) => {
289
- const isOpen = Boolean(text) || open.includes(group.key);
290
- return (
291
- <Box key={group.key} sx={{ mt: 0.5 }}>
292
- <ButtonBase
293
- onClick={() => toggle(group.key)}
294
- aria-expanded={isOpen}
295
- sx={{
296
- width: "100%",
297
- display: "flex",
298
- alignItems: "center",
299
- gap: 1,
300
- px: 1.5,
301
- py: 0.75,
302
- borderRadius: "6px",
303
- "&:hover": { bgcolor: COLORS.s1 },
304
- }}
305
- >
306
- <Typography
307
- sx={{
308
- flex: 1,
309
- textAlign: "left",
310
- fontFamily: FONT_STACK,
311
- fontSize: 11,
312
- fontWeight: 700,
313
- letterSpacing: "0.06em",
314
- textTransform: "uppercase",
315
- color: COLORS.s4,
316
- }}
317
- >
318
- {group.label}
319
- </Typography>
320
- {group.services.some((service) => isPendingFor(service, userId)) && !isOpen && (
321
- <PendingDot size={6} withTooltip={false} />
322
- )}
323
- <Typography sx={{ fontFamily: FONT_STACK, fontSize: 11, fontWeight: 700, color: COLORS.s4 }}>
324
- {group.services.length}
325
- </Typography>
326
- <ExpandMoreIcon
327
- sx={{
328
- fontSize: 18,
329
- color: COLORS.s4,
330
- transition: "transform 150ms",
331
- transform: isOpen ? "rotate(180deg)" : "none",
332
- }}
333
- />
334
- </ButtonBase>
335
- <Collapse in={isOpen}>
336
- {group.services.map((service) => {
337
- const active = service.service_name === selected;
338
- return (
339
- <ButtonBase
340
- key={service.service_name}
341
- onClick={() => onSelect(service.service_name)}
342
- aria-current={active ? "true" : undefined}
343
- sx={itemSx(active, side)}
344
- >
345
- <ServiceIcon
346
- icon={service.icon}
347
- sx={{ color: active ? COLORS.magenta_s2 : COLORS.s4 }}
348
- />
349
- <ItemLabel active={active}>{service.display_name}</ItemLabel>
350
- {isPendingFor(service, userId) && <PendingDot />}
351
- <StatusChip {...serviceStatusProps(service)} oval />
352
- </ButtonBase>
353
- );
354
- })}
355
- </Collapse>
356
- </Box>
357
- );
358
- })}
359
- </Box>
360
-
361
- {pendingCount > 0 && (
362
- <Box
363
- sx={{
364
- display: "flex",
365
- alignItems: "center",
366
- gap: 1,
367
- px: 2,
368
- py: 1.25,
369
- borderTop: `1px solid ${COLORS.s2}`,
370
- }}
371
- >
372
- <PendingDot withTooltip={false} />
373
- <Typography sx={{ fontFamily: FONT_STACK, fontSize: 12, color: COLORS.s4 }}>
374
- Te toca a ti · {pendingCount} {pendingCount === 1 ? "servicio" : "servicios"}
375
- </Typography>
376
- </Box>
377
- )}
378
- </>
379
- );
380
- };
381
-
382
- /* ------------------------------------------------------------------------ */
383
- /* Lista contraída: solo íconos */
384
- /* ------------------------------------------------------------------------ */
385
-
386
- const CollapsedRail = ({ visible, selected, onSelect, userId, onExpand, side }) => {
387
- const groups = groupServices(visible);
388
- // Los globos de ayuda salen hacia el editor, no hacia el borde de la pantalla.
389
- const tooltipPlacement = side === "right" ? "left" : "right";
390
- const iconButtonSx = (active) => ({
391
- width: 44,
392
- height: 40,
393
- borderRadius: "8px",
394
- position: "relative",
395
- bgcolor: active ? COLORS.s2 : "transparent",
396
- "&:hover": { bgcolor: COLORS.s2 },
397
- "&::before": {
398
- content: '""',
399
- position: "absolute",
400
- [side]: -10,
401
- top: 8,
402
- bottom: 8,
403
- width: 3,
404
- borderRadius: "2px",
405
- bgcolor: active ? COLORS.magenta_s2 : "transparent",
406
- },
407
- });
408
-
409
- return (
410
- <>
411
- <Box sx={{ display: "flex", justifyContent: "center", pt: 1.5, pb: 1 }}>
412
- <Tooltip title="Mostrar la lista de servicios" arrow placement={tooltipPlacement}>
413
- <IconButton aria-label="Mostrar la lista de servicios" onClick={onExpand}>
414
- <MenuOpenIcon
415
- sx={{ fontSize: 22, color: COLORS.s4, transform: side === "right" ? "none" : "scaleX(-1)" }}
416
- />
417
- </IconButton>
418
- </Tooltip>
419
- </Box>
420
- <Box
421
- sx={{
422
- flex: 1,
423
- minHeight: 0,
424
- overflowY: "auto",
425
- display: "flex",
426
- flexDirection: "column",
427
- alignItems: "center",
428
- gap: 0.5,
429
- pb: 1,
430
- }}
431
- >
432
- <Tooltip title="Resumen" arrow placement={tooltipPlacement}>
433
- <ButtonBase
434
- aria-label="Resumen"
435
- aria-current={selected === SUMMARY ? "true" : undefined}
436
- onClick={() => onSelect(SUMMARY)}
437
- sx={iconButtonSx(selected === SUMMARY)}
438
- >
439
- <ServiceIcon
440
- icon="summary"
441
- sx={{ fontSize: 22, color: selected === SUMMARY ? COLORS.magenta_s2 : COLORS.s4 }}
442
- />
443
- </ButtonBase>
444
- </Tooltip>
445
- {groups.map((group) => (
446
- <Box
447
- key={group.key}
448
- sx={{
449
- display: "flex",
450
- flexDirection: "column",
451
- alignItems: "center",
452
- gap: 0.5,
453
- pt: 0.75,
454
- mt: 0.25,
455
- borderTop: `1px solid ${COLORS.s2}`,
456
- width: 40,
457
- }}
458
- >
459
- {group.services.map((service) => {
460
- const active = service.service_name === selected;
461
- const pending = isPendingFor(service, userId);
462
- const label = [
463
- service.display_name,
464
- statusName(service.status) ?? service.status,
465
- pending ? "Te toca a ti" : null,
466
- ]
467
- .filter(Boolean)
468
- .join(" · ");
469
- return (
470
- <Tooltip key={service.service_name} title={label} arrow placement={tooltipPlacement}>
471
- <ButtonBase
472
- aria-label={label}
473
- aria-current={active ? "true" : undefined}
474
- onClick={() => onSelect(service.service_name)}
475
- sx={iconButtonSx(active)}
476
- >
477
- <ServiceIcon
478
- icon={service.icon}
479
- sx={{ fontSize: 22, color: active ? COLORS.magenta_s2 : COLORS.s4 }}
480
- />
481
- {/* Estatus: punto con el color de la etiqueta. */}
482
- <Box
483
- component="span"
484
- sx={{
485
- position: "absolute",
486
- top: 5,
487
- right: 6,
488
- width: 9,
489
- height: 9,
490
- borderRadius: "50%",
491
- bgcolor: serviceStatusColor(service),
492
- border: `2px solid ${active ? COLORS.s2 : COLORS.white}`,
493
- }}
494
- />
495
- {pending && (
496
- <Box
497
- component="span"
498
- sx={{
499
- position: "absolute",
500
- bottom: 4,
501
- left: "50%",
502
- transform: "translateX(-50%)",
503
- width: 14,
504
- height: 3,
505
- borderRadius: "2px",
506
- bgcolor: COLORS.magenta_s2,
507
- }}
508
- />
509
- )}
510
- </ButtonBase>
511
- </Tooltip>
512
- );
513
- })}
514
- </Box>
515
- ))}
516
- </Box>
517
- </>
518
- );
519
- };
520
-
521
- /* ------------------------------------------------------------------------ */
522
-
523
- export const ServicesRail = ({
524
- services,
525
- selected,
526
- onSelect,
527
- userId,
528
- filter,
529
- onFilterChange,
530
- showFilter,
531
- collapsed = false,
532
- onToggleCollapsed,
533
- onClose,
534
- variant = "rail",
535
- side = "left",
536
- }) => {
537
- const visible =
538
- showFilter && filter === "mine"
539
- ? services.filter(
540
- (service) => isMine(service, userId) || service.service_name === selected
541
- )
542
- : services;
543
-
544
- const isRail = variant === "rail";
545
- const width = isRail ? (collapsed ? RAIL_COLLAPSED_WIDTH : RAIL_WIDTH) : "100%";
546
-
547
- return (
548
- <Box
549
- component="nav"
550
- aria-label="Servicios del producto"
551
- sx={{
552
- width,
553
- flexShrink: 0,
554
- height: "100%",
555
- minHeight: 0,
556
- display: "flex",
557
- flexDirection: "column",
558
- bgcolor: COLORS.white,
559
- [side === "right" ? "borderLeft" : "borderRight"]: isRail ? `1px solid ${COLORS.s2}` : "none",
560
- transition: "width 160ms ease",
561
- overflow: "hidden",
562
- fontFamily: FONT_STACK,
563
- }}
564
- >
565
- {isRail && collapsed ? (
566
- <CollapsedRail
567
- visible={visible}
568
- selected={selected}
569
- onSelect={onSelect}
570
- userId={userId}
571
- onExpand={onToggleCollapsed}
572
- side={side}
573
- />
574
- ) : (
575
- <ExpandedRail
576
- services={services}
577
- visible={visible}
578
- selected={selected}
579
- onSelect={onSelect}
580
- userId={userId}
581
- filter={filter}
582
- onFilterChange={onFilterChange}
583
- showFilter={showFilter}
584
- onCollapse={isRail ? onToggleCollapsed : undefined}
585
- onClose={onClose}
586
- side={side}
587
- />
588
- )}
589
- </Box>
590
- );
591
- };