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,184 +0,0 @@
|
|
|
1
|
-
import { useEffect, useState } from "react";
|
|
2
|
-
import {
|
|
3
|
-
Avatar,
|
|
4
|
-
Box,
|
|
5
|
-
ButtonBase,
|
|
6
|
-
List,
|
|
7
|
-
ListItemAvatar,
|
|
8
|
-
ListItemButton,
|
|
9
|
-
ListItemText,
|
|
10
|
-
Popover,
|
|
11
|
-
Stack,
|
|
12
|
-
Tooltip,
|
|
13
|
-
Typography,
|
|
14
|
-
} from "@mui/material";
|
|
15
|
-
|
|
16
|
-
import { getProfilePicture } from "../../../../global-files/data";
|
|
17
|
-
import { COLORS, FONTS } from "../../CreatorsProductEdition/sections/tokens";
|
|
18
|
-
|
|
19
|
-
/*
|
|
20
|
-
Equipo del servicio abierto: especialista, coordinador y auditor, con foto,
|
|
21
|
-
rol y nombre («Tú» para quien está viendo).
|
|
22
|
-
|
|
23
|
-
Reemplaza al grupo de avatares «Asig.» de la barra de pestañas, que no decía
|
|
24
|
-
quién era quién. Quien puede asignar (permissions.canAssign) hace clic en el
|
|
25
|
-
especialista o en el coordinador y elige de la lista, igual que antes.
|
|
26
|
-
|
|
27
|
-
`roles` (opcional) limita los roles que se muestran: un servicio del catálogo
|
|
28
|
-
de flujo simple solo tiene especialista. Por omisión, los tres.
|
|
29
|
-
*/
|
|
30
|
-
|
|
31
|
-
const ROLES = [
|
|
32
|
-
{ role: "especialist", label: "Especialista", assignable: true },
|
|
33
|
-
{ role: "facilitator", label: "Coordinador", assignable: true },
|
|
34
|
-
{ role: "auditor", label: "Auditor", assignable: false },
|
|
35
|
-
];
|
|
36
|
-
|
|
37
|
-
const nameOf = (person) =>
|
|
38
|
-
person ? [person.name, person.last_name].filter(Boolean).join(" ").trim() : "";
|
|
39
|
-
|
|
40
|
-
const initialsOf = (person) =>
|
|
41
|
-
nameOf(person)
|
|
42
|
-
.split(/\s+/)
|
|
43
|
-
.filter(Boolean)
|
|
44
|
-
.slice(0, 2)
|
|
45
|
-
.map((word) => word[0])
|
|
46
|
-
.join("")
|
|
47
|
-
.toUpperCase();
|
|
48
|
-
|
|
49
|
-
const PersonAvatar = ({ person, isMe, size = 26 }) => (
|
|
50
|
-
<Avatar
|
|
51
|
-
src={person?.id_user ? getProfilePicture(person.id_user, 48, 48) : undefined}
|
|
52
|
-
alt={nameOf(person)}
|
|
53
|
-
sx={{
|
|
54
|
-
width: size,
|
|
55
|
-
height: size,
|
|
56
|
-
fontSize: size * 0.4,
|
|
57
|
-
fontFamily: FONTS.Lato,
|
|
58
|
-
fontWeight: 700,
|
|
59
|
-
bgcolor: person ? COLORS.s2 : COLORS.white,
|
|
60
|
-
color: person ? COLORS.s5 : COLORS.s3,
|
|
61
|
-
border: `2px solid ${isMe ? COLORS.magenta_s2 : COLORS.white}`,
|
|
62
|
-
outline: person ? "none" : `1px dashed ${COLORS.s3}`,
|
|
63
|
-
outlineOffset: -3,
|
|
64
|
-
}}
|
|
65
|
-
>
|
|
66
|
-
{person ? initialsOf(person) : "?"}
|
|
67
|
-
</Avatar>
|
|
68
|
-
);
|
|
69
|
-
|
|
70
|
-
export const ServiceTeam = ({
|
|
71
|
-
assignees = {},
|
|
72
|
-
userId,
|
|
73
|
-
canAssign = false,
|
|
74
|
-
assig,
|
|
75
|
-
setAssignation,
|
|
76
|
-
resetKey,
|
|
77
|
-
roles,
|
|
78
|
-
}) => {
|
|
79
|
-
const [anchor, setAnchor] = useState(null);
|
|
80
|
-
const [role, setRole] = useState(null);
|
|
81
|
-
useEffect(() => setAnchor(null), [resetKey]);
|
|
82
|
-
|
|
83
|
-
const options = assig?.collaborators?.[role] ?? [];
|
|
84
|
-
|
|
85
|
-
return (
|
|
86
|
-
<>
|
|
87
|
-
<Stack direction="row" alignItems="center" spacing={1.5} sx={{ flexWrap: "wrap", rowGap: 0.75, minWidth: 0 }}>
|
|
88
|
-
{ROLES.filter((item) => !roles || roles.includes(item.role)).map((item) => {
|
|
89
|
-
const person = assignees[item.role];
|
|
90
|
-
const isMe = Boolean(person) && person.id_user === userId;
|
|
91
|
-
const clickable = canAssign && item.assignable;
|
|
92
|
-
const content = (
|
|
93
|
-
<Stack direction="row" alignItems="center" spacing={0.75}>
|
|
94
|
-
<PersonAvatar person={person} isMe={isMe} />
|
|
95
|
-
<Box sx={{ lineHeight: 1.1, textAlign: "left", minWidth: 0 }}>
|
|
96
|
-
<Typography
|
|
97
|
-
sx={{
|
|
98
|
-
fontFamily: FONTS.Lato,
|
|
99
|
-
fontSize: 10,
|
|
100
|
-
color: COLORS.s4,
|
|
101
|
-
textTransform: "uppercase",
|
|
102
|
-
letterSpacing: "0.05em",
|
|
103
|
-
}}
|
|
104
|
-
>
|
|
105
|
-
{item.label}
|
|
106
|
-
</Typography>
|
|
107
|
-
<Typography
|
|
108
|
-
noWrap
|
|
109
|
-
sx={{
|
|
110
|
-
maxWidth: 120,
|
|
111
|
-
fontFamily: FONTS.Lato,
|
|
112
|
-
fontSize: 12,
|
|
113
|
-
fontWeight: isMe ? 700 : 400,
|
|
114
|
-
color: person ? COLORS.s5 : COLORS.s3,
|
|
115
|
-
}}
|
|
116
|
-
>
|
|
117
|
-
{person ? (isMe ? "Tú" : nameOf(person)) : "Sin asignar"}
|
|
118
|
-
</Typography>
|
|
119
|
-
</Box>
|
|
120
|
-
</Stack>
|
|
121
|
-
);
|
|
122
|
-
const title = person ? `${item.label}: ${nameOf(person)}` : `${item.label}: sin asignar`;
|
|
123
|
-
return clickable ? (
|
|
124
|
-
<Tooltip key={item.role} title={`${title} · Clic para asignar`} arrow>
|
|
125
|
-
<ButtonBase
|
|
126
|
-
onClick={(event) => {
|
|
127
|
-
setRole(item.role);
|
|
128
|
-
setAnchor(event.currentTarget);
|
|
129
|
-
}}
|
|
130
|
-
sx={{ borderRadius: "8px", px: 0.5, py: 0.25, "&:hover": { bgcolor: COLORS.s2 } }}
|
|
131
|
-
>
|
|
132
|
-
{content}
|
|
133
|
-
</ButtonBase>
|
|
134
|
-
</Tooltip>
|
|
135
|
-
) : (
|
|
136
|
-
<Tooltip key={item.role} title={title} arrow>
|
|
137
|
-
<Box sx={{ px: 0.5, py: 0.25 }}>{content}</Box>
|
|
138
|
-
</Tooltip>
|
|
139
|
-
);
|
|
140
|
-
})}
|
|
141
|
-
</Stack>
|
|
142
|
-
|
|
143
|
-
<Popover
|
|
144
|
-
open={Boolean(anchor)}
|
|
145
|
-
anchorEl={anchor}
|
|
146
|
-
onClose={() => setAnchor(null)}
|
|
147
|
-
anchorOrigin={{ vertical: "bottom", horizontal: "left" }}
|
|
148
|
-
transformOrigin={{ vertical: "top", horizontal: "left" }}
|
|
149
|
-
PaperProps={{ sx: { width: 260, maxWidth: "calc(100vw - 24px)", mt: 1, p: 1.5, borderRadius: "10px" } }}
|
|
150
|
-
>
|
|
151
|
-
<Typography sx={{ fontFamily: FONTS.Lato, fontSize: 12, fontWeight: 700, color: COLORS.s4, mb: 1 }}>
|
|
152
|
-
Asignar {ROLES.find((item) => item.role === role)?.label.toLowerCase()}
|
|
153
|
-
</Typography>
|
|
154
|
-
{options.length ? (
|
|
155
|
-
<List dense disablePadding sx={{ maxHeight: 280, overflowY: "auto" }}>
|
|
156
|
-
{options.map((person, index) => (
|
|
157
|
-
<ListItemButton
|
|
158
|
-
key={`${index}-${person?.id_user}`}
|
|
159
|
-
selected={person?.id_user === assignees[role]?.id_user}
|
|
160
|
-
onClick={() => {
|
|
161
|
-
setAssignation && setAssignation(role, person?.id_user);
|
|
162
|
-
setAnchor(null);
|
|
163
|
-
}}
|
|
164
|
-
sx={{ borderRadius: "6px" }}
|
|
165
|
-
>
|
|
166
|
-
<ListItemAvatar sx={{ minWidth: 38 }}>
|
|
167
|
-
<PersonAvatar person={person} />
|
|
168
|
-
</ListItemAvatar>
|
|
169
|
-
<ListItemText
|
|
170
|
-
primary={nameOf(person)}
|
|
171
|
-
primaryTypographyProps={{ sx: { fontFamily: FONTS.Lato, fontSize: 14, color: COLORS.s5 } }}
|
|
172
|
-
/>
|
|
173
|
-
</ListItemButton>
|
|
174
|
-
))}
|
|
175
|
-
</List>
|
|
176
|
-
) : (
|
|
177
|
-
<Typography sx={{ fontFamily: FONTS.Lato, fontSize: 13, color: COLORS.s4, py: 1 }}>
|
|
178
|
-
Sin personas para asignar
|
|
179
|
-
</Typography>
|
|
180
|
-
)}
|
|
181
|
-
</Popover>
|
|
182
|
-
</>
|
|
183
|
-
);
|
|
184
|
-
};
|
|
@@ -1,134 +0,0 @@
|
|
|
1
|
-
import { Button, IconButton, Stack, Tooltip } from "@mui/material";
|
|
2
|
-
|
|
3
|
-
import { useAiProductEdition } from "../../../../contexts/AiProductEdition";
|
|
4
|
-
|
|
5
|
-
import saveIcon from "../../../../assets/images/generalButton/saveIcon.svg";
|
|
6
|
-
import saveIconHover from "../../../../assets/images/generalButton/saveIconHover.svg";
|
|
7
|
-
import deleteIcon from "../../../../assets/images/generalButton/deleteIcon.svg";
|
|
8
|
-
import deleteIconHover from "../../../../assets/images/generalButton/deleteIconHover.svg";
|
|
9
|
-
import downloadIcon from "../../../../assets/images/generalButton/downloadIcon.svg";
|
|
10
|
-
import rowsLayoutIcon from "../../../../assets/images/generalButton/rowLayout.svg";
|
|
11
|
-
import gridLayoutIcon from "../../../../assets/images/generalButton/gridLayout.svg";
|
|
12
|
-
|
|
13
|
-
import { COLORS, FONTS } from "../../CreatorsProductEdition/sections/tokens";
|
|
14
|
-
|
|
15
|
-
/*
|
|
16
|
-
Herramientas del servicio abierto: versión, guardar y, en imágenes, borrar,
|
|
17
|
-
descargar y cambiar de vista.
|
|
18
|
-
|
|
19
|
-
Sale de `ServiceActions` de CreatorsProductEdition sin tres cosas que ahora
|
|
20
|
-
viven en otro lado o ya no se usan:
|
|
21
|
-
- el botón de ayuda (las diapositivas de «Valida tu producto…»): el equipo
|
|
22
|
-
ya no lo usa;
|
|
23
|
-
- el estatus: lo muestra el encabezado del servicio junto a su nombre;
|
|
24
|
-
- las asignaciones: las muestra ServiceTeam con nombre y rol.
|
|
25
|
-
*/
|
|
26
|
-
|
|
27
|
-
/** Botón redondo con el ícono de siempre y su versión «hover». */
|
|
28
|
-
const RoundIcon = ({ label, icon, hoverIcon, onClick, bordered = COLORS.s3, hoverBg = COLORS.magenta_s2, size = 15 }) => (
|
|
29
|
-
<Tooltip title={label} arrow>
|
|
30
|
-
<IconButton
|
|
31
|
-
aria-label={label}
|
|
32
|
-
onClick={onClick}
|
|
33
|
-
sx={{
|
|
34
|
-
width: 28,
|
|
35
|
-
height: 28,
|
|
36
|
-
p: 0,
|
|
37
|
-
border: bordered ? `1px solid ${bordered}` : "none",
|
|
38
|
-
backgroundImage: `url(${icon})`,
|
|
39
|
-
backgroundRepeat: "no-repeat",
|
|
40
|
-
backgroundPosition: "center",
|
|
41
|
-
backgroundSize: size,
|
|
42
|
-
"&:hover": {
|
|
43
|
-
backgroundColor: hoverBg,
|
|
44
|
-
borderColor: hoverBg,
|
|
45
|
-
...(hoverIcon ? { backgroundImage: `url(${hoverIcon})` } : {}),
|
|
46
|
-
},
|
|
47
|
-
}}
|
|
48
|
-
/>
|
|
49
|
-
</Tooltip>
|
|
50
|
-
);
|
|
51
|
-
|
|
52
|
-
export const ServiceTools = ({
|
|
53
|
-
imagesSection = false,
|
|
54
|
-
imageLayout,
|
|
55
|
-
setImageLayout,
|
|
56
|
-
downloadImages,
|
|
57
|
-
askToDeleteImages,
|
|
58
|
-
onClickSave,
|
|
59
|
-
showSaveButton,
|
|
60
|
-
version,
|
|
61
|
-
setShowVersionSelector,
|
|
62
|
-
}) => {
|
|
63
|
-
const { inputsUsingAi } = useAiProductEdition();
|
|
64
|
-
|
|
65
|
-
const save = () => {
|
|
66
|
-
if (Object.values(inputsUsingAi ?? {}).some((input) => !!input?.using)) {
|
|
67
|
-
console.warn("Para guardar se debe de dejar de usar la generación con IA");
|
|
68
|
-
return;
|
|
69
|
-
}
|
|
70
|
-
onClickSave && onClickSave();
|
|
71
|
-
};
|
|
72
|
-
|
|
73
|
-
return (
|
|
74
|
-
<Stack direction="row" alignItems="center" gap="8px" sx={{ flexShrink: 0 }}>
|
|
75
|
-
{version && (
|
|
76
|
-
<Tooltip title="Cambiar de versión" arrow>
|
|
77
|
-
<Button
|
|
78
|
-
onClick={(event) => {
|
|
79
|
-
event.stopPropagation();
|
|
80
|
-
setShowVersionSelector(true);
|
|
81
|
-
}}
|
|
82
|
-
sx={{
|
|
83
|
-
minWidth: 28,
|
|
84
|
-
width: 28,
|
|
85
|
-
height: 28,
|
|
86
|
-
p: 0,
|
|
87
|
-
borderRadius: "50%",
|
|
88
|
-
border: `1px solid ${COLORS.color_purple}`,
|
|
89
|
-
color: COLORS.color_purple,
|
|
90
|
-
fontFamily: FONTS.AvenirNext,
|
|
91
|
-
fontSize: 11,
|
|
92
|
-
lineHeight: "28px",
|
|
93
|
-
}}
|
|
94
|
-
>
|
|
95
|
-
V{version}
|
|
96
|
-
</Button>
|
|
97
|
-
</Tooltip>
|
|
98
|
-
)}
|
|
99
|
-
{showSaveButton && (
|
|
100
|
-
<RoundIcon
|
|
101
|
-
label="Guardar"
|
|
102
|
-
icon={saveIcon}
|
|
103
|
-
hoverIcon={saveIconHover}
|
|
104
|
-
bordered={COLORS.color_gray}
|
|
105
|
-
hoverBg={COLORS.color_pink}
|
|
106
|
-
size={undefined}
|
|
107
|
-
onClick={save}
|
|
108
|
-
/>
|
|
109
|
-
)}
|
|
110
|
-
{imagesSection && (
|
|
111
|
-
<>
|
|
112
|
-
{showSaveButton && (
|
|
113
|
-
<RoundIcon
|
|
114
|
-
label="Borrar seleccionadas"
|
|
115
|
-
icon={deleteIcon}
|
|
116
|
-
hoverIcon={deleteIconHover}
|
|
117
|
-
onClick={() => askToDeleteImages && askToDeleteImages()}
|
|
118
|
-
/>
|
|
119
|
-
)}
|
|
120
|
-
<RoundIcon label="Descargar" icon={downloadIcon} onClick={() => downloadImages && downloadImages()} />
|
|
121
|
-
<Tooltip title={imageLayout ? "Ver como lista" : "Ver como cuadrícula"} arrow>
|
|
122
|
-
<IconButton
|
|
123
|
-
aria-label={imageLayout ? "Ver como lista" : "Ver como cuadrícula"}
|
|
124
|
-
onClick={() => setImageLayout && setImageLayout(!imageLayout)}
|
|
125
|
-
sx={{ width: 28, height: 28, p: 0 }}
|
|
126
|
-
>
|
|
127
|
-
<img src={imageLayout ? rowsLayoutIcon : gridLayoutIcon} alt="" width={24} height={24} />
|
|
128
|
-
</IconButton>
|
|
129
|
-
</Tooltip>
|
|
130
|
-
</>
|
|
131
|
-
)}
|
|
132
|
-
</Stack>
|
|
133
|
-
);
|
|
134
|
-
};
|
|
@@ -1,187 +0,0 @@
|
|
|
1
|
-
import { SERVICE_CATALOG } from "../../organisms/ServicesNavigation/catalog";
|
|
2
|
-
import {
|
|
3
|
-
CLASSIC_SERVICES,
|
|
4
|
-
isAssignedToService,
|
|
5
|
-
isAuditorAssigned,
|
|
6
|
-
isPendingFor,
|
|
7
|
-
rolesInService,
|
|
8
|
-
tabOf,
|
|
9
|
-
} from "./permissions";
|
|
10
|
-
|
|
11
|
-
/*
|
|
12
|
-
Servicios del producto para la lista lateral de la edición.
|
|
13
|
-
|
|
14
|
-
Hoy los servicios con editor son los tres de siempre: salen de los estatus
|
|
15
|
-
del producto (`<servicio>_status`) y el de la cadena activa sale de
|
|
16
|
-
`services_data`. Un servicio con estatus vacío o «NS» no está contratado y
|
|
17
|
-
no aparece.
|
|
18
|
-
|
|
19
|
-
Esta lista es el respaldo de cuando GET /product-services no responde
|
|
20
|
-
(buildServicesFromApi, abajo): solo los servicios de siempre. Los servicios
|
|
21
|
-
del producto nunca salen de datos de muestra; la prop `demoServices` es solo
|
|
22
|
-
para las historias de Storybook.
|
|
23
|
-
*/
|
|
24
|
-
|
|
25
|
-
const NOT_REQUESTED = [undefined, null, "", "NS", "NULL", "-"];
|
|
26
|
-
|
|
27
|
-
const catalogEntry = (serviceName) =>
|
|
28
|
-
SERVICE_CATALOG.find((item) => item.service_name === serviceName) ?? {
|
|
29
|
-
service_name: serviceName,
|
|
30
|
-
group: "review",
|
|
31
|
-
view: "files",
|
|
32
|
-
icon: null,
|
|
33
|
-
};
|
|
34
|
-
|
|
35
|
-
/** Persona de las listas de asignables (`collaborator_assignations`). */
|
|
36
|
-
const findUser = (collaborators, id) => {
|
|
37
|
-
if (!id) return null;
|
|
38
|
-
const all = Object.values(collaborators ?? {}).flat();
|
|
39
|
-
const user = all.find((item) => item?.id_user === id);
|
|
40
|
-
return user ?? { id_user: id, name: "Asignado", last_name: "" };
|
|
41
|
-
};
|
|
42
|
-
|
|
43
|
-
// `services_data` y `collaborator_assignations` llegan en null mientras carga.
|
|
44
|
-
const retailerStatus = (servicesData, serviceName, retailerId, fallback) =>
|
|
45
|
-
(servicesData ?? []).find((item) => item.service === serviceName && item.id_retailer === retailerId)?.status ??
|
|
46
|
-
fallback;
|
|
47
|
-
|
|
48
|
-
/**
|
|
49
|
-
* @returns {Array<{service_name, display_name, group, view, icon, status,
|
|
50
|
-
* tab, assignees, mine, pending, is_demo}>}
|
|
51
|
-
*/
|
|
52
|
-
export const buildEditionServices = ({
|
|
53
|
-
product,
|
|
54
|
-
user,
|
|
55
|
-
servicesData,
|
|
56
|
-
activeRetailerId,
|
|
57
|
-
collaboratorAssignations,
|
|
58
|
-
// Auditores (rol 6): `collaborator_assignations` solo trae especialistas y
|
|
59
|
-
// coordinadores, y sin esta lista el auditor no tendría nombre.
|
|
60
|
-
auditors,
|
|
61
|
-
demoServices,
|
|
62
|
-
}) => {
|
|
63
|
-
if (!product) return [];
|
|
64
|
-
|
|
65
|
-
const classic = CLASSIC_SERVICES.filter(
|
|
66
|
-
(item) => !NOT_REQUESTED.includes(product[`${item.service_name}_status`])
|
|
67
|
-
).map((item) => {
|
|
68
|
-
const definition = catalogEntry(item.service_name);
|
|
69
|
-
const collaborators = collaboratorAssignations?.[item.tab]?.collaborators ?? {};
|
|
70
|
-
const status = retailerStatus(
|
|
71
|
-
servicesData,
|
|
72
|
-
item.service_name,
|
|
73
|
-
activeRetailerId,
|
|
74
|
-
product[`${item.service_name}_status`]
|
|
75
|
-
);
|
|
76
|
-
return {
|
|
77
|
-
...definition,
|
|
78
|
-
status,
|
|
79
|
-
tab: item.tab,
|
|
80
|
-
assignees: {
|
|
81
|
-
especialist: findUser(collaborators, product[`id_${item.service_name}_especialist`]),
|
|
82
|
-
facilitator: findUser(collaborators, product[`id_${item.service_name}_facilitator`]),
|
|
83
|
-
auditor: product.id_auditor
|
|
84
|
-
? findUser({ ...collaborators, auditors: auditors ?? [] }, product.id_auditor)
|
|
85
|
-
: null,
|
|
86
|
-
},
|
|
87
|
-
mine:
|
|
88
|
-
rolesInService(product, user, item.service_name).length > 0 ||
|
|
89
|
-
isAssignedToService(product, user, item.service_name) ||
|
|
90
|
-
isAuditorAssigned(product, user),
|
|
91
|
-
pending: isPendingFor(product, user, item.service_name, status),
|
|
92
|
-
is_demo: false,
|
|
93
|
-
};
|
|
94
|
-
});
|
|
95
|
-
|
|
96
|
-
// Solo historias (`demoServices`): el auditor del producto lo es de todos sus
|
|
97
|
-
// servicios, así «Míos» de un auditor no esconde los que no tienen editor.
|
|
98
|
-
const productAuditor = product.id_auditor ? findUser({ auditors: auditors ?? [] }, product.id_auditor) : null;
|
|
99
|
-
const auditorIsMe = isAuditorAssigned(product, user);
|
|
100
|
-
|
|
101
|
-
const demo = (demoServices ?? []).map((service) => ({
|
|
102
|
-
...catalogEntry(service.service_name),
|
|
103
|
-
display_name: service.display_name ?? catalogEntry(service.service_name).display_name ?? service.service_name,
|
|
104
|
-
status: service.status,
|
|
105
|
-
tab: null,
|
|
106
|
-
assignees: { especialist: null, facilitator: null, auditor: productAuditor },
|
|
107
|
-
mine: auditorIsMe,
|
|
108
|
-
pending: false,
|
|
109
|
-
is_demo: true,
|
|
110
|
-
}));
|
|
111
|
-
|
|
112
|
-
return [...classic, ...demo];
|
|
113
|
-
};
|
|
114
|
-
|
|
115
|
-
/* ------------------------------------------------------------------------ */
|
|
116
|
-
/* Desde el backend (GET /product-services) */
|
|
117
|
-
/* ------------------------------------------------------------------------ */
|
|
118
|
-
|
|
119
|
-
/*
|
|
120
|
-
Los servicios del producto como los devuelve GET /product-services: leídos
|
|
121
|
-
de las columnas del catálogo de rel_order_services (id_service, id_status,
|
|
122
|
-
id_especialist, id_facilitator). Trae todos los servicios del producto, los
|
|
123
|
-
de siempre y los del catálogo, con su estatus por cadena, su equipo y lo que
|
|
124
|
-
la persona puede hacer con cada uno (`actions`).
|
|
125
|
-
|
|
126
|
-
Cuando esta lista está, la edición la usa en lugar de armar los servicios con
|
|
127
|
-
los campos del producto (buildEditionServices).
|
|
128
|
-
*/
|
|
129
|
-
|
|
130
|
-
/** En qué estatus le toca actuar a cada rol (flujo completo, el del motor). */
|
|
131
|
-
const WORKFLOW_ACTIONABLE = {
|
|
132
|
-
especialist: ["AS", "CA", "RC", "RA"],
|
|
133
|
-
facilitator: ["PA", "IE"],
|
|
134
|
-
auditor: ["AC", "RP", "RCA", "SAC"],
|
|
135
|
-
};
|
|
136
|
-
|
|
137
|
-
/** Roles de la persona en el servicio, según su equipo. */
|
|
138
|
-
const rolesOfUser = (assignees, userId) =>
|
|
139
|
-
Object.entries(assignees ?? {})
|
|
140
|
-
.filter(([, person]) => person && userId && person.id_user === userId)
|
|
141
|
-
.map(([role]) => role);
|
|
142
|
-
|
|
143
|
-
/**
|
|
144
|
-
* @param {{ productServices: { services: Array }, user: Object, activeRetailerId?: number }} params
|
|
145
|
-
* @returns {Array} servicios con la forma de la lista lateral, más `api` (el
|
|
146
|
-
* servicio tal como llegó) para los paneles que lo necesitan.
|
|
147
|
-
*/
|
|
148
|
-
export const buildServicesFromApi = ({ productServices, user, activeRetailerId }) => {
|
|
149
|
-
const userId = user?.id_user;
|
|
150
|
-
return (productServices?.services ?? []).map((service) => {
|
|
151
|
-
const entry = catalogEntry(service.service_name);
|
|
152
|
-
const flowType = service.status_flow?.flow_type ?? "workflow";
|
|
153
|
-
const simple = flowType === "simple";
|
|
154
|
-
// Estatus de la cadena activa; si el servicio no es por cadena (o no está
|
|
155
|
-
// en ella), el general.
|
|
156
|
-
const status =
|
|
157
|
-
service.retailers?.find((retailer) => retailer.id_retailer === activeRetailerId)?.status ??
|
|
158
|
-
service.status ??
|
|
159
|
-
null;
|
|
160
|
-
const assignees = service.assignees ?? {};
|
|
161
|
-
const roles = rolesOfUser(assignees, userId);
|
|
162
|
-
const parts = String(status?.status_name ?? "").split("/");
|
|
163
|
-
const pending = simple
|
|
164
|
-
? roles.includes("especialist") && Boolean(status) && !status.is_final
|
|
165
|
-
: roles.some((role) => parts.some((part) => WORKFLOW_ACTIONABLE[role]?.includes(part)));
|
|
166
|
-
|
|
167
|
-
return {
|
|
168
|
-
...entry,
|
|
169
|
-
service_name: service.service_name,
|
|
170
|
-
display_name: service.display_name ?? entry.display_name ?? service.service_name,
|
|
171
|
-
group: service.category?.category_name ?? entry.group,
|
|
172
|
-
view: simple ? "simple" : entry.view,
|
|
173
|
-
icon: service.icon_name ?? entry.icon,
|
|
174
|
-
status: status?.status_name ?? null,
|
|
175
|
-
status_label: status?.display_name ?? null,
|
|
176
|
-
status_color: status?.color ?? null,
|
|
177
|
-
status_flow_type: flowType,
|
|
178
|
-
tab: tabOf(service.service_name),
|
|
179
|
-
assignees,
|
|
180
|
-
mine: roles.length > 0,
|
|
181
|
-
pending,
|
|
182
|
-
is_demo: false,
|
|
183
|
-
is_catalog: !service.is_legacy,
|
|
184
|
-
api: service,
|
|
185
|
-
};
|
|
186
|
-
});
|
|
187
|
-
};
|
|
@@ -1,88 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Lista lateral de la edición a partir de GET /product-services.
|
|
3
|
-
*
|
|
4
|
-
* npx vitest run src/components/pages/ProductEdition
|
|
5
|
-
*/
|
|
6
|
-
import { describe, expect, it } from "vitest";
|
|
7
|
-
import { buildServicesFromApi } from "./services";
|
|
8
|
-
|
|
9
|
-
const person = (id_user, name) => ({ id_user, name, last_name: "Prueba" });
|
|
10
|
-
const status = (status_name, extra = {}) => ({ status_name, display_name: status_name, color: null, ...extra });
|
|
11
|
-
|
|
12
|
-
const productServices = {
|
|
13
|
-
article_id: 10,
|
|
14
|
-
order_id: 500,
|
|
15
|
-
services: [
|
|
16
|
-
{
|
|
17
|
-
service_name: "datasheet",
|
|
18
|
-
display_name: "Ficha técnica",
|
|
19
|
-
category: { category_name: "texts" },
|
|
20
|
-
status_flow: { flow_name: "workflow", flow_type: "workflow" },
|
|
21
|
-
is_legacy: true,
|
|
22
|
-
status: status("PA"),
|
|
23
|
-
retailers: [
|
|
24
|
-
{ id_retailer: 58, status: status("PA") },
|
|
25
|
-
{ id_retailer: 59, status: status("RA/AS") },
|
|
26
|
-
],
|
|
27
|
-
roles: ["especialist", "facilitator", "auditor"],
|
|
28
|
-
assignees: { especialist: person(426, "Textos"), facilitator: person(901, "Coord"), auditor: person(903, "QA") },
|
|
29
|
-
actions: { set_status: false, assign: false },
|
|
30
|
-
},
|
|
31
|
-
{
|
|
32
|
-
service_name: "productPurchase",
|
|
33
|
-
display_name: "Compra de producto",
|
|
34
|
-
icon_name: null,
|
|
35
|
-
category: { category_name: "logistics" },
|
|
36
|
-
status_flow: { flow_name: "checkbox", flow_type: "simple" },
|
|
37
|
-
is_legacy: false,
|
|
38
|
-
per_retailer: false,
|
|
39
|
-
status: status("PE", { display_name: "Pendiente", color: "#FFAA00", is_final: false }),
|
|
40
|
-
retailers: [{ id_retailer: 0, status: status("PE", { display_name: "Pendiente", color: "#FFAA00", is_final: false }) }],
|
|
41
|
-
status_options: [status("PE"), status("HE", { is_final: true })],
|
|
42
|
-
roles: ["especialist"],
|
|
43
|
-
assignees: { especialist: person(426, "Textos"), facilitator: null, auditor: null },
|
|
44
|
-
actions: { set_status: true, assign: true },
|
|
45
|
-
},
|
|
46
|
-
],
|
|
47
|
-
};
|
|
48
|
-
|
|
49
|
-
describe("buildServicesFromApi", () => {
|
|
50
|
-
const specialist = { id_user: 426, id_role: 7 };
|
|
51
|
-
|
|
52
|
-
it("toma el estatus de la cadena activa; si no está en ella, el general", () => {
|
|
53
|
-
const [datasheet, purchase] = buildServicesFromApi({ productServices, user: specialist, activeRetailerId: 59 });
|
|
54
|
-
expect(datasheet.status).toBe("RA/AS");
|
|
55
|
-
expect(purchase.status).toBe("PE");
|
|
56
|
-
});
|
|
57
|
-
|
|
58
|
-
it("los de siempre conservan su pestaña; los del catálogo usan su bloque y la vista simple", () => {
|
|
59
|
-
const [datasheet, purchase] = buildServicesFromApi({ productServices, user: specialist, activeRetailerId: 58 });
|
|
60
|
-
expect(datasheet).toMatchObject({ tab: "Ficha técnica", group: "texts", is_catalog: false });
|
|
61
|
-
expect(purchase).toMatchObject({
|
|
62
|
-
tab: null,
|
|
63
|
-
group: "logistics",
|
|
64
|
-
view: "simple",
|
|
65
|
-
icon: "purchase",
|
|
66
|
-
status_label: "Pendiente",
|
|
67
|
-
status_color: "#FFAA00",
|
|
68
|
-
status_flow_type: "simple",
|
|
69
|
-
is_catalog: true,
|
|
70
|
-
});
|
|
71
|
-
expect(purchase.api).toBe(productServices.services[1]);
|
|
72
|
-
});
|
|
73
|
-
|
|
74
|
-
it("«Míos» y «te toca» salen del equipo del servicio", () => {
|
|
75
|
-
const [datasheet, purchase] = buildServicesFromApi({ productServices, user: specialist, activeRetailerId: 59 });
|
|
76
|
-
// RA/AS: le toca a la especialista asignada.
|
|
77
|
-
expect(datasheet).toMatchObject({ mine: true, pending: true });
|
|
78
|
-
// Flujo simple sin terminar: le toca a su especialista.
|
|
79
|
-
expect(purchase).toMatchObject({ mine: true, pending: true });
|
|
80
|
-
|
|
81
|
-
const [, other] = buildServicesFromApi({ productServices, user: { id_user: 1 }, activeRetailerId: 59 });
|
|
82
|
-
expect(other).toMatchObject({ mine: false, pending: false });
|
|
83
|
-
});
|
|
84
|
-
|
|
85
|
-
it("sin respuesta del backend, lista vacía", () => {
|
|
86
|
-
expect(buildServicesFromApi({ productServices: null, user: specialist })).toEqual([]);
|
|
87
|
-
});
|
|
88
|
-
});
|