contentoh-components-library 21.6.61 → 21.6.62
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/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/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/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/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,180 +0,0 @@
|
|
|
1
|
-
/*
|
|
2
|
-
Datos de ejemplo del prototipo de navegación por servicios.
|
|
3
|
-
|
|
4
|
-
Nada de esto sale de la base todavía. Es el catálogo de `cat_service`
|
|
5
|
-
(docs/sql/catalog/002) con dos datos que la edición necesita para pintar cada
|
|
6
|
-
servicio:
|
|
7
|
-
|
|
8
|
-
- group: en qué bloque de la lista lateral va. Los bloques son fijos y no
|
|
9
|
-
dependen del estatus, para que un servicio siempre esté en el mismo lugar
|
|
10
|
-
y el usuario lo encuentre de memoria.
|
|
11
|
-
- view: qué vista de trabajo abre. La vista depende del tipo de entregable,
|
|
12
|
-
no del servicio: Foto, Edición y Resize abren la misma vista de imágenes.
|
|
13
|
-
*/
|
|
14
|
-
|
|
15
|
-
export const SERVICE_GROUPS = [
|
|
16
|
-
{ key: "texts", label: "Textos" },
|
|
17
|
-
{ key: "images", label: "Imágenes" },
|
|
18
|
-
{ key: "media", label: "Multimedia y diseño" },
|
|
19
|
-
{ key: "review", label: "Revisión y entrega" },
|
|
20
|
-
{ key: "maintenance", label: "Mantenimiento" },
|
|
21
|
-
{ key: "logistics", label: "Logística" },
|
|
22
|
-
];
|
|
23
|
-
|
|
24
|
-
/*
|
|
25
|
-
Los servicios activos de `cat_service` que se trabajan en la edición
|
|
26
|
-
(docs/sql/catalog/002 y 006 del backend), con los mismos `service_name`,
|
|
27
|
-
`display_name` y bloque (`category_name`). Dentro de cada bloque van en el
|
|
28
|
-
orden del catálogo (`sort_order`). Los de logística (Levantamiento, Compra,
|
|
29
|
-
Armado) usan un flujo simple (casilla o avance) y abren la vista «simple»:
|
|
30
|
-
su estatus por cadena y quién lo tiene, sin editor.
|
|
31
|
-
|
|
32
|
-
`view` sale del tipo de entregable: attributes → ficha, text → textos
|
|
33
|
-
(Traducción tiene la suya), images → la vista de imágenes compartida,
|
|
34
|
-
video → videos, file → archivos. A+, Golden, Banner y Render entregan
|
|
35
|
-
imágenes, pero son diseños: van como archivos.
|
|
36
|
-
*/
|
|
37
|
-
export const SERVICE_CATALOG = [
|
|
38
|
-
// Textos
|
|
39
|
-
{ service_name: "datasheet", display_name: "Ficha técnica", group: "texts", view: "attributes", icon: "datasheet" },
|
|
40
|
-
{ service_name: "description", display_name: "Descripción", group: "texts", view: "text", icon: "description" },
|
|
41
|
-
{ service_name: "datasheetHeader", display_name: "Header Ficha", group: "texts", view: "text", icon: "header" },
|
|
42
|
-
{ service_name: "translation", display_name: "Traducción", group: "texts", view: "translation", icon: "translation" },
|
|
43
|
-
{ service_name: "seo", display_name: "SEO", group: "texts", view: "text", icon: "seo" },
|
|
44
|
-
{ service_name: "benefitBullets", display_name: "Legales + beneficios (bullets)", group: "texts", view: "text", icon: "bullets" },
|
|
45
|
-
// Imágenes (fotografía y edición sobre las imágenes del producto)
|
|
46
|
-
{ service_name: "images", display_name: "Imágenes", group: "images", view: "images", icon: "images" },
|
|
47
|
-
{ service_name: "photo", display_name: "Foto", group: "images", view: "images", icon: "photo" },
|
|
48
|
-
{ service_name: "edition", display_name: "Edición", group: "images", view: "images", icon: "edition" },
|
|
49
|
-
{ service_name: "premiumImage", display_name: "Premium", group: "images", view: "images", icon: "premium" },
|
|
50
|
-
{ service_name: "resize", display_name: "Resize", group: "images", view: "images", icon: "resize" },
|
|
51
|
-
{ service_name: "adaptations", display_name: "Adaptaciones", group: "images", view: "images", icon: "adaptations" },
|
|
52
|
-
{ service_name: "graphicTranslation", display_name: "Traducciones gráficas", group: "images", view: "images", icon: "graphicTranslation" },
|
|
53
|
-
{ service_name: "visualUpdate", display_name: "Actualización visual", group: "images", view: "images", icon: "visualUpdate" },
|
|
54
|
-
{ service_name: "ambientImage", display_name: "Ambientada", group: "images", view: "images", icon: "ambient" },
|
|
55
|
-
{ service_name: "specialPhoto", display_name: "Foto especial", group: "images", view: "images", icon: "specialPhoto" },
|
|
56
|
-
{ service_name: "modelPhoto", display_name: "Foto con modelo", group: "images", view: "images", icon: "modelPhoto" },
|
|
57
|
-
{ service_name: "premiumAmbientImage", display_name: "Foto ambientada premium", group: "images", view: "images", icon: "premiumAmbient" },
|
|
58
|
-
// Multimedia y diseño
|
|
59
|
-
{ service_name: "aplusPlatinum", display_name: "A+ / Platinum SKU", group: "media", view: "files", icon: "aplus", deliverable: "Diseño" },
|
|
60
|
-
{ service_name: "brandstore", display_name: "Brandstore", group: "media", view: "files", icon: "brandstore", deliverable: "Diseño" },
|
|
61
|
-
{ service_name: "image360", display_name: "360°", group: "media", view: "files", icon: "image360", deliverable: "Archivos 360" },
|
|
62
|
-
{ service_name: "videos", display_name: "Videos", group: "media", view: "video", icon: "video" },
|
|
63
|
-
{ service_name: "golden", display_name: "Golden", group: "media", view: "files", icon: "golden", deliverable: "Diseño" },
|
|
64
|
-
{ service_name: "banner", display_name: "Banner", group: "media", view: "files", icon: "banner", deliverable: "Diseño" },
|
|
65
|
-
{ service_name: "render", display_name: "Render", group: "media", view: "files", icon: "render", deliverable: "Render" },
|
|
66
|
-
{ service_name: "arModel", display_name: "Modelado 3D (realidad aumentada)", group: "media", view: "files", icon: "arModel", deliverable: "Modelo 3D" },
|
|
67
|
-
{ service_name: "manualDigitization", display_name: "Digitalización de manuales", group: "media", view: "files", icon: "manual", deliverable: "Manual digital" },
|
|
68
|
-
// Revisión y entrega
|
|
69
|
-
{ service_name: "quality", display_name: "Calidad de textos", group: "review", view: "files", icon: "quality", deliverable: "Reporte" },
|
|
70
|
-
{ service_name: "secodatReview", display_name: "Revisión secodat", group: "review", view: "files", icon: "secodat", deliverable: "Reporte" },
|
|
71
|
-
{ service_name: "followUp", display_name: "Seguimiento", group: "review", view: "files", icon: "followUp", deliverable: "Reporte" },
|
|
72
|
-
{ service_name: "uploadOnly", display_name: "Solo Carga", group: "review", view: "files", icon: "upload", deliverable: "Carga en cadena" },
|
|
73
|
-
{ service_name: "audit", display_name: "Auditoría de textos", group: "review", view: "files", icon: "audit", deliverable: "Reporte" },
|
|
74
|
-
{ service_name: "inputReview", display_name: "Revisión de Input", group: "review", view: "files", icon: "inputReview", deliverable: "Reporte" },
|
|
75
|
-
{ service_name: "clientReview", display_name: "Revisión Cliente", group: "review", view: "files", icon: "clientReview", deliverable: "Reporte" },
|
|
76
|
-
{ service_name: "visualQuality", display_name: "Calidad visual", group: "review", view: "files", icon: "visualQuality", deliverable: "Reporte" },
|
|
77
|
-
{ service_name: "visualAudit", display_name: "Auditoría visual", group: "review", view: "files", icon: "visualAudit", deliverable: "Reporte" },
|
|
78
|
-
{ service_name: "gdsnUpload", display_name: "Carga GDSN (Syncway)", group: "review", view: "files", icon: "gdsn", deliverable: "Carga en GDSN" },
|
|
79
|
-
// Mantenimiento
|
|
80
|
-
{ service_name: "maintenance", display_name: "Mantenimiento de textos", group: "maintenance", view: "files", icon: "maintenance", deliverable: "Archivo" },
|
|
81
|
-
{ service_name: "visualMaintenance", display_name: "Mantenimiento de imágenes", group: "maintenance", view: "images", icon: "visualMaintenance" },
|
|
82
|
-
// Logística: flujo simple (casilla o avance), sin editor. La edición muestra
|
|
83
|
-
// su estatus por cadena y quién lo tiene (ProductEdition/sections/CatalogServicePanel).
|
|
84
|
-
{ service_name: "productPickup", display_name: "Levantamiento y recolección", group: "logistics", view: "simple", icon: "pickup" },
|
|
85
|
-
{ service_name: "productPurchase", display_name: "Compra de producto en tienda", group: "logistics", view: "simple", icon: "purchase" },
|
|
86
|
-
{ service_name: "productAssembly", display_name: "Armado de producto", group: "logistics", view: "simple", icon: "assembly" },
|
|
87
|
-
];
|
|
88
|
-
|
|
89
|
-
export const DEMO_USERS = {
|
|
90
|
-
ana: { id_user: 9100, name: "Ana", last_name: "López" },
|
|
91
|
-
carlos: { id_user: 9101, name: "Carlos", last_name: "Méndez" },
|
|
92
|
-
diana: { id_user: 9102, name: "Diana", last_name: "Ruiz" },
|
|
93
|
-
eduardo: { id_user: 9103, name: "Eduardo", last_name: "Pérez" },
|
|
94
|
-
fernanda: { id_user: 9104, name: "Fernanda", last_name: "Soto" },
|
|
95
|
-
gabriel: { id_user: 9105, name: "Gabriel", last_name: "Torres" },
|
|
96
|
-
helena: { id_user: 9106, name: "Helena", last_name: "Vargas" },
|
|
97
|
-
};
|
|
98
|
-
|
|
99
|
-
/** Producto con la forma que espera la cabecera de la edición (ProductHeader). */
|
|
100
|
-
export const DEMO_PRODUCT = {
|
|
101
|
-
id_article: 11096001,
|
|
102
|
-
id_order: 11096,
|
|
103
|
-
name: "Taladro inalámbrico 20 V con 2 baterías",
|
|
104
|
-
upc: "7501234567890",
|
|
105
|
-
sku: "TAL-20V-2B",
|
|
106
|
-
categoryName: "Herramientas|Eléctricas|Taladros",
|
|
107
|
-
status: "CA",
|
|
108
|
-
timestamp: "2026-10-08T15:00:00Z",
|
|
109
|
-
article: { company_name: "Herramientas del Norte" },
|
|
110
|
-
categoryRetailer: [
|
|
111
|
-
{
|
|
112
|
-
id_retailer: 58,
|
|
113
|
-
name: "The Home Depot",
|
|
114
|
-
retailer: "The Home Depot",
|
|
115
|
-
category: "Herramientas|Eléctricas|Taladros",
|
|
116
|
-
},
|
|
117
|
-
],
|
|
118
|
-
};
|
|
119
|
-
|
|
120
|
-
const DATES = ["13/10/2026", "12/10/2026", "10/10/2026", "09/10/2026", "07/10/2026"];
|
|
121
|
-
|
|
122
|
-
/**
|
|
123
|
-
* Servicios del producto para las historias. Cada fila es
|
|
124
|
-
* [service_name, estatus, { especialist, facilitator, auditor }].
|
|
125
|
-
* El coordinador (facilitator) y el auditor tienen valores por omisión.
|
|
126
|
-
*/
|
|
127
|
-
export const buildServices = (rows) =>
|
|
128
|
-
rows.map(([serviceName, status, team = {}], index) => {
|
|
129
|
-
const definition = SERVICE_CATALOG.find((item) => item.service_name === serviceName);
|
|
130
|
-
return {
|
|
131
|
-
...definition,
|
|
132
|
-
status,
|
|
133
|
-
updated_at: DATES[index % DATES.length],
|
|
134
|
-
assignees: {
|
|
135
|
-
especialist: team.especialist ?? null,
|
|
136
|
-
facilitator: team.facilitator === undefined ? DEMO_USERS.helena : team.facilitator,
|
|
137
|
-
auditor: team.auditor === undefined ? DEMO_USERS.fernanda : team.auditor,
|
|
138
|
-
},
|
|
139
|
-
};
|
|
140
|
-
});
|
|
141
|
-
|
|
142
|
-
const { ana, carlos, diana, eduardo, fernanda, gabriel } = DEMO_USERS;
|
|
143
|
-
|
|
144
|
-
/** Veinte servicios del catálogo con estatus y equipos variados. */
|
|
145
|
-
export const TWENTY_SERVICES = buildServices([
|
|
146
|
-
["datasheet", "AA", { especialist: ana }],
|
|
147
|
-
["description", "RA/AS", { especialist: ana }],
|
|
148
|
-
["datasheetHeader", "AC", { especialist: ana }],
|
|
149
|
-
["seo", "CA", { especialist: eduardo }],
|
|
150
|
-
["translation", "CA", { especialist: diana }],
|
|
151
|
-
["images", "AS", { especialist: carlos }],
|
|
152
|
-
["photo", "Ex", { especialist: gabriel }],
|
|
153
|
-
["edition", "RA/AS", { especialist: carlos }],
|
|
154
|
-
["resize", "CA", { especialist: carlos }],
|
|
155
|
-
["adaptations", "PA"],
|
|
156
|
-
["premiumImage", "AP", { especialist: gabriel }],
|
|
157
|
-
["videos", "IE", { especialist: gabriel }],
|
|
158
|
-
["image360", "PA"],
|
|
159
|
-
["brandstore", "AC", { especialist: eduardo }],
|
|
160
|
-
["aplusPlatinum", "RC", { especialist: eduardo }],
|
|
161
|
-
["golden", "AS", { especialist: eduardo }],
|
|
162
|
-
["quality", "AA", { especialist: fernanda, auditor: diana }],
|
|
163
|
-
["audit", "R", { especialist: fernanda, auditor: diana }],
|
|
164
|
-
["secodatReview", "Ex", { especialist: fernanda, auditor: diana }],
|
|
165
|
-
["uploadOnly", "PA"],
|
|
166
|
-
]);
|
|
167
|
-
|
|
168
|
-
/** Estatus variados para mostrar todos los servicios del catálogo a la vez. */
|
|
169
|
-
const SAMPLE_STATUSES = ["PA", "AS", "CA", "IE", "AC", "AA", "AP", "Ex", "RA/AS", "RC"];
|
|
170
|
-
|
|
171
|
-
/**
|
|
172
|
-
* Todos los servicios activos del catálogo con un estatus de ejemplo cada uno
|
|
173
|
-
* (los tres de siempre se pueden excluir: en la edición real salen del
|
|
174
|
-
* producto).
|
|
175
|
-
*/
|
|
176
|
-
export const allCatalogServices = ({ exclude = [] } = {}) =>
|
|
177
|
-
SERVICE_CATALOG.filter((item) => !exclude.includes(item.service_name)).map((item, index) => ({
|
|
178
|
-
service_name: item.service_name,
|
|
179
|
-
status: SAMPLE_STATUSES[(index * 3) % SAMPLE_STATUSES.length],
|
|
180
|
-
}));
|
|
@@ -1,232 +0,0 @@
|
|
|
1
|
-
import { useState } from "react";
|
|
2
|
-
import { Box, Chip, Drawer, Stack, Typography, useMediaQuery } from "@mui/material";
|
|
3
|
-
import ImageOutlinedIcon from "@mui/icons-material/ImageOutlined";
|
|
4
|
-
|
|
5
|
-
import { ProductHeader } from "../../pages/CreatorsProductEdition/sections/ProductHeader";
|
|
6
|
-
import { COLORS, FONT_STACK, TITLE_STACK, captionSx } from "./ui";
|
|
7
|
-
import { ServicesRail } from "./ServicesRail";
|
|
8
|
-
import { ServiceToolbar } from "./ServiceToolbar";
|
|
9
|
-
import { ServiceView } from "./views";
|
|
10
|
-
import {
|
|
11
|
-
SUMMARY,
|
|
12
|
-
defaultFilter,
|
|
13
|
-
fullName,
|
|
14
|
-
hasUsefulFilter,
|
|
15
|
-
initialSelection,
|
|
16
|
-
isMine,
|
|
17
|
-
isPendingFor,
|
|
18
|
-
} from "./model";
|
|
19
|
-
|
|
20
|
-
/*
|
|
21
|
-
PROTOTIPO: navegación por servicios en la edición de producto.
|
|
22
|
-
|
|
23
|
-
Responde a dos preguntas:
|
|
24
|
-
1. Si el producto solo lleva Traducción, ¿por qué ver Ficha técnica,
|
|
25
|
-
Descripción e Imágenes? → Solo aparecen los servicios contratados. Con
|
|
26
|
-
uno solo no hay lista: la edición abre directo en él.
|
|
27
|
-
2. Con 20 servicios, ¿cómo se navega sin perderse? → Lista lateral en lugar
|
|
28
|
-
de pestañas, filtrada por omisión a lo que la persona trabaja («Míos»),
|
|
29
|
-
con un Resumen y un punto rosa en lo que le toca a cada quien.
|
|
30
|
-
|
|
31
|
-
Usa la cabecera real de la edición de Creators (ProductHeader); la columna de
|
|
32
|
-
imagen es un marcador de lugar y las vistas de trabajo son de ejemplo. No está
|
|
33
|
-
conectado a la base: los servicios y sus estatus llegan por props.
|
|
34
|
-
*/
|
|
35
|
-
|
|
36
|
-
const ImagePanelPlaceholder = () => (
|
|
37
|
-
<Box sx={{ display: { xs: "none", lg: "block" }, flex: "0 0 clamp(240px, 22%, 320px)", minWidth: 0 }}>
|
|
38
|
-
<Box
|
|
39
|
-
sx={{
|
|
40
|
-
aspectRatio: "1 / 1",
|
|
41
|
-
borderRadius: "8px",
|
|
42
|
-
border: `1px solid ${COLORS.s2}`,
|
|
43
|
-
background: `linear-gradient(135deg, ${COLORS.blue_light}, ${COLORS.s2})`,
|
|
44
|
-
display: "flex",
|
|
45
|
-
flexDirection: "column",
|
|
46
|
-
alignItems: "center",
|
|
47
|
-
justifyContent: "center",
|
|
48
|
-
gap: 1,
|
|
49
|
-
}}
|
|
50
|
-
>
|
|
51
|
-
<ImageOutlinedIcon sx={{ fontSize: 56, color: COLORS.s3 }} />
|
|
52
|
-
<Typography sx={captionSx}>Imagen principal</Typography>
|
|
53
|
-
</Box>
|
|
54
|
-
<Stack direction="row" spacing={1} sx={{ mt: 1 }}>
|
|
55
|
-
{[0, 1, 2, 3].map((index) => (
|
|
56
|
-
<Box
|
|
57
|
-
key={index}
|
|
58
|
-
sx={{
|
|
59
|
-
flex: 1,
|
|
60
|
-
aspectRatio: "1 / 1",
|
|
61
|
-
borderRadius: "6px",
|
|
62
|
-
border: `1px solid ${index === 0 ? COLORS.magenta_s2 : COLORS.s2}`,
|
|
63
|
-
bgcolor: COLORS.s1,
|
|
64
|
-
}}
|
|
65
|
-
/>
|
|
66
|
-
))}
|
|
67
|
-
</Stack>
|
|
68
|
-
<Typography sx={{ ...captionSx, mt: 1.5 }}>La columna de imagen de la edición se queda como está.</Typography>
|
|
69
|
-
</Box>
|
|
70
|
-
);
|
|
71
|
-
|
|
72
|
-
const TopBar = ({ currentUser, currentUserRole }) => (
|
|
73
|
-
<Stack
|
|
74
|
-
direction="row"
|
|
75
|
-
alignItems="center"
|
|
76
|
-
spacing={1.5}
|
|
77
|
-
sx={{ px: 2, py: 1.25, borderBottom: `1px solid ${COLORS.s2}`, flexWrap: "wrap", rowGap: 0.5 }}
|
|
78
|
-
>
|
|
79
|
-
<Typography sx={{ fontFamily: TITLE_STACK, fontWeight: 600, fontSize: 18, color: COLORS.s5 }}>
|
|
80
|
-
Edición de producto
|
|
81
|
-
</Typography>
|
|
82
|
-
<Chip
|
|
83
|
-
label="Prototipo · datos de ejemplo"
|
|
84
|
-
size="small"
|
|
85
|
-
sx={{ height: 22, bgcolor: COLORS.s2, color: COLORS.s4, fontFamily: FONT_STACK, fontWeight: 700, fontSize: 11 }}
|
|
86
|
-
/>
|
|
87
|
-
<Box sx={{ flex: 1 }} />
|
|
88
|
-
{currentUser && (
|
|
89
|
-
<Typography sx={captionSx}>
|
|
90
|
-
Viendo como <b>{fullName(currentUser)}</b>
|
|
91
|
-
{currentUserRole ? ` · ${currentUserRole}` : ""}
|
|
92
|
-
</Typography>
|
|
93
|
-
)}
|
|
94
|
-
</Stack>
|
|
95
|
-
);
|
|
96
|
-
|
|
97
|
-
export const ServicesNavigation = ({
|
|
98
|
-
product,
|
|
99
|
-
services = [],
|
|
100
|
-
currentUser,
|
|
101
|
-
currentUserRole,
|
|
102
|
-
initialService,
|
|
103
|
-
initialCollapsed = false,
|
|
104
|
-
}) => {
|
|
105
|
-
const userId = currentUser?.id_user;
|
|
106
|
-
const isDesktop = useMediaQuery("(min-width:900px)");
|
|
107
|
-
const single = services.length === 1;
|
|
108
|
-
const showFilter = hasUsefulFilter(services, userId);
|
|
109
|
-
|
|
110
|
-
const [selected, setSelected] = useState(() => initialService ?? initialSelection(services, userId));
|
|
111
|
-
const [filter, setFilter] = useState(() => defaultFilter(services, userId));
|
|
112
|
-
const [collapsed, setCollapsed] = useState(initialCollapsed);
|
|
113
|
-
const [drawerOpen, setDrawerOpen] = useState(false);
|
|
114
|
-
|
|
115
|
-
const retailers = product?.categoryRetailer ?? [];
|
|
116
|
-
const [activeRetailer, setActiveRetailer] = useState(() => ({
|
|
117
|
-
id: retailers[0]?.id_retailer,
|
|
118
|
-
name: retailers[0]?.name,
|
|
119
|
-
}));
|
|
120
|
-
|
|
121
|
-
const service = services.find((item) => item.service_name === selected) ?? null;
|
|
122
|
-
const isSummary = !single && (selected === SUMMARY || !service);
|
|
123
|
-
|
|
124
|
-
const select = (name) => {
|
|
125
|
-
const target = services.find((item) => item.service_name === name);
|
|
126
|
-
// Si se abre algo que no es suyo (desde el Resumen), se pasa a «Todos»
|
|
127
|
-
// para que el servicio abierto se vea marcado en la lista.
|
|
128
|
-
if (target && filter === "mine" && !isMine(target, userId)) setFilter("all");
|
|
129
|
-
setSelected(name);
|
|
130
|
-
setDrawerOpen(false);
|
|
131
|
-
};
|
|
132
|
-
|
|
133
|
-
const renderRail = (variant) => (
|
|
134
|
-
<ServicesRail
|
|
135
|
-
variant={variant}
|
|
136
|
-
services={services}
|
|
137
|
-
selected={isSummary ? SUMMARY : selected}
|
|
138
|
-
onSelect={select}
|
|
139
|
-
userId={userId}
|
|
140
|
-
filter={filter}
|
|
141
|
-
onFilterChange={setFilter}
|
|
142
|
-
showFilter={showFilter}
|
|
143
|
-
collapsed={collapsed}
|
|
144
|
-
onToggleCollapsed={() => setCollapsed((value) => !value)}
|
|
145
|
-
onClose={variant === "drawer" ? () => setDrawerOpen(false) : undefined}
|
|
146
|
-
/>
|
|
147
|
-
);
|
|
148
|
-
|
|
149
|
-
return (
|
|
150
|
-
<Box
|
|
151
|
-
sx={{
|
|
152
|
-
flex: 1,
|
|
153
|
-
minWidth: 0,
|
|
154
|
-
height: "100%",
|
|
155
|
-
display: "flex",
|
|
156
|
-
flexDirection: "column",
|
|
157
|
-
bgcolor: COLORS.white,
|
|
158
|
-
fontFamily: FONT_STACK,
|
|
159
|
-
textAlign: "left",
|
|
160
|
-
}}
|
|
161
|
-
>
|
|
162
|
-
<TopBar currentUser={currentUser} currentUserRole={currentUserRole} />
|
|
163
|
-
|
|
164
|
-
<Box sx={{ flex: 1, minHeight: 0, display: "flex" }}>
|
|
165
|
-
{!single && isDesktop && renderRail("rail")}
|
|
166
|
-
|
|
167
|
-
<Box
|
|
168
|
-
sx={{
|
|
169
|
-
flex: 1,
|
|
170
|
-
minWidth: 0,
|
|
171
|
-
overflowY: "auto",
|
|
172
|
-
p: { xs: 1.5, md: 2.5 },
|
|
173
|
-
display: "flex",
|
|
174
|
-
gap: 2.5,
|
|
175
|
-
alignItems: "flex-start",
|
|
176
|
-
}}
|
|
177
|
-
>
|
|
178
|
-
<ImagePanelPlaceholder />
|
|
179
|
-
|
|
180
|
-
<Box sx={{ flex: 1, minWidth: 0 }}>
|
|
181
|
-
<ProductHeader
|
|
182
|
-
headerData={product}
|
|
183
|
-
percent={72}
|
|
184
|
-
activeRetailer={activeRetailer}
|
|
185
|
-
setActiveRetailer={(retailer) =>
|
|
186
|
-
setActiveRetailer({
|
|
187
|
-
id: retailer?.id_retailer ?? retailer?.id,
|
|
188
|
-
name: retailer?.name ?? retailer?.retailer,
|
|
189
|
-
})
|
|
190
|
-
}
|
|
191
|
-
servicesData={null}
|
|
192
|
-
showValidationButtons={false}
|
|
193
|
-
/>
|
|
194
|
-
|
|
195
|
-
<ServiceToolbar
|
|
196
|
-
service={service}
|
|
197
|
-
isSummary={isSummary}
|
|
198
|
-
summaryCounts={{
|
|
199
|
-
total: services.length,
|
|
200
|
-
pending: services.filter((item) => isPendingFor(item, userId)).length,
|
|
201
|
-
}}
|
|
202
|
-
onOpenRail={!single && !isDesktop ? () => setDrawerOpen(true) : undefined}
|
|
203
|
-
userId={userId}
|
|
204
|
-
/>
|
|
205
|
-
|
|
206
|
-
<ServiceView
|
|
207
|
-
selected={isSummary ? SUMMARY : selected}
|
|
208
|
-
service={service}
|
|
209
|
-
services={services}
|
|
210
|
-
userId={userId}
|
|
211
|
-
onSelect={select}
|
|
212
|
-
retailerName={activeRetailer.name}
|
|
213
|
-
/>
|
|
214
|
-
</Box>
|
|
215
|
-
</Box>
|
|
216
|
-
</Box>
|
|
217
|
-
|
|
218
|
-
{!single && !isDesktop && (
|
|
219
|
-
<Drawer
|
|
220
|
-
anchor="left"
|
|
221
|
-
open={drawerOpen}
|
|
222
|
-
onClose={() => setDrawerOpen(false)}
|
|
223
|
-
PaperProps={{ sx: { width: "min(320px, 88vw)" } }}
|
|
224
|
-
>
|
|
225
|
-
{renderRail("drawer")}
|
|
226
|
-
</Drawer>
|
|
227
|
-
)}
|
|
228
|
-
</Box>
|
|
229
|
-
);
|
|
230
|
-
};
|
|
231
|
-
|
|
232
|
-
export default ServicesNavigation;
|
|
@@ -1,85 +0,0 @@
|
|
|
1
|
-
import { SERVICE_GROUPS } from "./catalog";
|
|
2
|
-
|
|
3
|
-
/** Identificador del elemento «Resumen» de la lista (no es un servicio). */
|
|
4
|
-
export const SUMMARY = "__summary";
|
|
5
|
-
|
|
6
|
-
export const ROLE_LABELS = {
|
|
7
|
-
especialist: "Especialista",
|
|
8
|
-
facilitator: "Coordinador",
|
|
9
|
-
auditor: "Auditor",
|
|
10
|
-
};
|
|
11
|
-
|
|
12
|
-
/*
|
|
13
|
-
Estatus en los que le toca actuar a cada rol. Un estatus compuesto como
|
|
14
|
-
«RA/AS» (rechazado por el auditor y de vuelta con el especialista) cuenta si
|
|
15
|
-
cualquiera de sus partes le toca al rol.
|
|
16
|
-
*/
|
|
17
|
-
const ACTIONABLE = {
|
|
18
|
-
especialist: ["AS", "CA", "RC", "RA"],
|
|
19
|
-
facilitator: ["PA", "IE"],
|
|
20
|
-
auditor: ["AC", "RP", "RCA", "SAC"],
|
|
21
|
-
};
|
|
22
|
-
|
|
23
|
-
const statusParts = (status) => String(status ?? "").split("/");
|
|
24
|
-
|
|
25
|
-
export const fullName = (user) =>
|
|
26
|
-
user ? [user.name, user.last_name].filter(Boolean).join(" ") : "";
|
|
27
|
-
|
|
28
|
-
export const initials = (user) =>
|
|
29
|
-
user ? `${user.name?.[0] ?? ""}${user.last_name?.[0] ?? ""}`.toUpperCase() : "";
|
|
30
|
-
|
|
31
|
-
export const rolesOf = (service, userId) =>
|
|
32
|
-
Object.entries(service.assignees ?? {})
|
|
33
|
-
.filter(([, user]) => user && user.id_user === userId)
|
|
34
|
-
.map(([role]) => role);
|
|
35
|
-
|
|
36
|
-
/*
|
|
37
|
-
`mine` y `pending` pueden venir ya calculados en el servicio (la edición real
|
|
38
|
-
los saca de ProductEdition/permissions.js con las asignaciones del
|
|
39
|
-
producto). Si no vienen, se calculan aquí con `assignees`, como en el
|
|
40
|
-
prototipo.
|
|
41
|
-
*/
|
|
42
|
-
export const isMine = (service, userId) =>
|
|
43
|
-
typeof service.mine === "boolean" ? service.mine : rolesOf(service, userId).length > 0;
|
|
44
|
-
|
|
45
|
-
export const isPendingFor = (service, userId) =>
|
|
46
|
-
typeof service.pending === "boolean"
|
|
47
|
-
? service.pending
|
|
48
|
-
: rolesOf(service, userId).some((role) =>
|
|
49
|
-
statusParts(service.status).some((part) => ACTIONABLE[role]?.includes(part))
|
|
50
|
-
);
|
|
51
|
-
|
|
52
|
-
export const groupLabel = (key) =>
|
|
53
|
-
SERVICE_GROUPS.find((group) => group.key === key)?.label ?? "";
|
|
54
|
-
|
|
55
|
-
/** Servicios en bloques fijos, en el orden del catálogo. */
|
|
56
|
-
export const groupServices = (services) =>
|
|
57
|
-
SERVICE_GROUPS.map((group) => ({
|
|
58
|
-
...group,
|
|
59
|
-
services: services.filter((service) => service.group === group.key),
|
|
60
|
-
})).filter((group) => group.services.length > 0);
|
|
61
|
-
|
|
62
|
-
/** El orden en que aparecen en la lista lateral. */
|
|
63
|
-
export const railOrder = (services) => groupServices(services).flatMap((group) => group.services);
|
|
64
|
-
|
|
65
|
-
/**
|
|
66
|
-
* Qué se abre al entrar: con un solo servicio, ese; si a la persona le toca
|
|
67
|
-
* algo, lo primero que le toca; si no, el Resumen.
|
|
68
|
-
*/
|
|
69
|
-
export const initialSelection = (services, userId) => {
|
|
70
|
-
if (services.length === 1) return services[0].service_name;
|
|
71
|
-
const pending = railOrder(services).find((service) => isPendingFor(service, userId));
|
|
72
|
-
return pending ? pending.service_name : SUMMARY;
|
|
73
|
-
};
|
|
74
|
-
|
|
75
|
-
/**
|
|
76
|
-
* «Míos» solo tiene sentido si la persona trabaja algunos servicios del
|
|
77
|
-
* producto y no todos: con todos o con ninguno, el filtro no cambia nada.
|
|
78
|
-
*/
|
|
79
|
-
export const hasUsefulFilter = (services, userId) => {
|
|
80
|
-
const mine = services.filter((service) => isMine(service, userId)).length;
|
|
81
|
-
return mine > 0 && mine < services.length;
|
|
82
|
-
};
|
|
83
|
-
|
|
84
|
-
export const defaultFilter = (services, userId) =>
|
|
85
|
-
hasUsefulFilter(services, userId) ? "mine" : "all";
|