contentoh-components-library 21.6.55 → 21.6.57
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/launch.json +14 -0
- package/.env.test +1 -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/AsignationOption/index.js +1 -0
- package/dist/components/atoms/AsignationOption/styles.js +1 -1
- package/dist/components/atoms/AssetPreview/AssetPreview.stories.js +118 -0
- package/dist/components/atoms/AssetPreview/index.js +150 -0
- package/dist/components/atoms/AssetPreview/styles.js +28 -0
- package/dist/components/atoms/IconFile/index.js +85 -2
- package/dist/components/atoms/IconFile/styles.js +1 -1
- package/dist/components/atoms/ProductImage/ProductImage.stories.js +70 -2
- package/dist/components/atoms/ProductImage/index.js +100 -27
- package/dist/components/atoms/ProductImage/styles.js +3 -1
- package/dist/components/molecules/EditionActiveImage/index.js +4 -2
- package/dist/components/molecules/GalleryAssetElement/GalleryAssetElement.stories.js +147 -0
- package/dist/components/molecules/GalleryAssetElement/index.js +178 -0
- package/dist/components/molecules/GalleryAssetElement/styles.js +26 -0
- package/dist/components/molecules/GalleryElement/index.js +13 -4
- package/dist/components/molecules/GalleryHeader/index.js +96 -24
- package/dist/components/molecules/GalleryHeader/styles.js +4 -1
- package/dist/components/molecules/GalleryTrayTabs/GalleryTrayTabs.stories.js +56 -0
- package/dist/components/molecules/GalleryTrayTabs/index.js +60 -0
- package/dist/components/molecules/GalleryTrayTabs/styles.js +20 -0
- package/dist/components/molecules/ImageSelector/index.js +1 -0
- package/dist/components/molecules/RetailerSelector/RetailerSelector.stories.js +23 -2
- package/dist/components/molecules/RetailerSelector/index.js +7 -2
- package/dist/components/molecules/RetailerSelectorv2/index.js +6 -2
- package/dist/components/organisms/DigitalAssetsTray/DigitalAssetsTray.stories.js +235 -0
- package/dist/components/organisms/DigitalAssetsTray/index.js +219 -0
- package/dist/components/organisms/DigitalAssetsTray/styles.js +22 -0
- package/dist/components/organisms/ImagePreviewer/index.js +1 -0
- package/dist/components/organisms/ProductImageModal/index.js +3 -2
- package/dist/components/pages/CreatorsProductEdition/CreatorsProductEditionView.js +905 -0
- package/dist/components/pages/CreatorsProductEdition/constants.js +19 -0
- package/dist/components/pages/CreatorsProductEdition/hooks/useCreatorsProductEdition.js +2967 -0
- package/dist/components/pages/CreatorsProductEdition/index.js +33 -0
- package/dist/components/pages/CreatorsProductEdition/mocks/api.fixtures.js +537 -0
- package/dist/components/pages/CreatorsProductEdition/mocks/mockApi.js +251 -0
- package/dist/components/pages/CreatorsProductEdition/sections/CommentBox.js +311 -0
- package/dist/components/pages/CreatorsProductEdition/sections/ImagePanel.js +421 -0
- package/dist/components/pages/CreatorsProductEdition/sections/ImagesSection.js +473 -0
- package/dist/components/pages/CreatorsProductEdition/sections/ProductHeader.js +379 -0
- package/dist/components/pages/CreatorsProductEdition/sections/ProductMedia.js +138 -0
- package/dist/components/pages/CreatorsProductEdition/sections/RetailerMenu.js +162 -0
- package/dist/components/pages/CreatorsProductEdition/sections/ServiceActions.js +542 -0
- package/dist/components/pages/CreatorsProductEdition/sections/StatusChip.js +50 -0
- package/dist/components/pages/CreatorsProductEdition/sections/TabsBar.js +247 -0
- package/dist/components/pages/CreatorsProductEdition/sections/VariantsPanel.js +389 -0
- package/dist/components/pages/CreatorsProductEdition/sections/tokens.js +90 -0
- package/dist/components/pages/CreatorsProductEdition/stories/CreatorsProductEdition.stories.js +168 -0
- package/dist/components/pages/CreatorsProductEdition/stories/CreatorsProductEditionAPI.stories.js +74 -0
- package/dist/components/pages/CreatorsProductEdition/styles.js +46 -0
- package/dist/components/pages/RetailerProductEdition/context/provider-product-edition.context.js +330 -15
- package/dist/components/pages/RetailerProductEdition/context/provider-product-edition.reducer.js +12 -2
- package/dist/components/pages/RetailerProductEdition/index.js +606 -425
- package/dist/components/pages/RetailerProductEdition/stories/ImageEditor.stories.js +1 -0
- package/dist/global-files/data.js +16 -3
- package/dist/global-files/digital-assets/assetTypes.js +517 -0
- package/dist/global-files/digital-assets/assets.actions.js +64 -0
- package/dist/global-files/digital-assets/assets.adapter.js +304 -0
- package/dist/global-files/digital-assets/assets.mock.js +136 -0
- package/dist/global-files/digital-assets/assets.reducer.js +220 -0
- package/dist/global-files/digital-assets/uploadAssets.js +202 -0
- package/dist/global-files/digital-assets/useDigitalAssets.js +535 -0
- package/dist/index.js +142 -90
- package/docs/activos-digitales.md +273 -0
- package/package.json +1 -1
- package/src/components/atoms/AsignationOption/index.js +1 -1
- package/src/components/atoms/AsignationOption/styles.js +9 -0
- package/src/components/atoms/AssetPreview/AssetPreview.stories.js +78 -0
- package/src/components/atoms/AssetPreview/index.js +119 -0
- package/src/components/atoms/AssetPreview/styles.js +190 -0
- package/src/components/atoms/IconFile/index.js +28 -1
- package/src/components/atoms/IconFile/styles.js +8 -0
- package/src/components/atoms/ProductImage/ProductImage.stories.js +38 -0
- package/src/components/atoms/ProductImage/index.js +95 -20
- package/src/components/atoms/ProductImage/styles.js +92 -7
- package/src/components/molecules/EditionActiveImage/index.js +2 -2
- package/src/components/molecules/GalleryAssetElement/GalleryAssetElement.stories.js +102 -0
- package/src/components/molecules/GalleryAssetElement/index.js +162 -0
- package/src/components/molecules/GalleryAssetElement/styles.js +163 -0
- package/src/components/molecules/GalleryElement/index.js +9 -0
- package/src/components/molecules/GalleryHeader/index.js +65 -13
- package/src/components/molecules/GalleryHeader/styles.js +2 -1
- package/src/components/molecules/GalleryTrayTabs/GalleryTrayTabs.stories.js +32 -0
- package/src/components/molecules/GalleryTrayTabs/index.js +41 -0
- package/src/components/molecules/GalleryTrayTabs/styles.js +52 -0
- package/src/components/molecules/ImageSelector/index.js +1 -0
- package/src/components/molecules/RetailerSelector/RetailerSelector.stories.js +15 -1
- package/src/components/molecules/RetailerSelector/index.js +8 -1
- package/src/components/molecules/RetailerSelectorv2/index.js +7 -1
- package/src/components/organisms/DigitalAssetsTray/DigitalAssetsTray.stories.js +164 -0
- package/src/components/organisms/DigitalAssetsTray/index.js +193 -0
- package/src/components/organisms/DigitalAssetsTray/styles.js +183 -0
- package/src/components/organisms/ImagePreviewer/index.js +1 -0
- package/src/components/organisms/ProductImageModal/index.js +1 -0
- package/src/components/pages/RetailerProductEdition/context/provider-product-edition.context.jsx +201 -0
- package/src/components/pages/RetailerProductEdition/context/provider-product-edition.reducer.js +12 -0
- package/src/components/pages/RetailerProductEdition/index.js +356 -139
- package/src/components/pages/RetailerProductEdition/stories/ImageEditor.stories.js +1 -0
- package/src/global-files/data.js +17 -2
- package/src/global-files/digital-assets/assetTypes.js +451 -0
- package/src/global-files/digital-assets/assets.actions.js +50 -0
- package/src/global-files/digital-assets/assets.adapter.js +229 -0
- package/src/global-files/digital-assets/assets.mock.js +122 -0
- package/src/global-files/digital-assets/assets.reducer.js +197 -0
- package/src/global-files/digital-assets/uploadAssets.js +124 -0
- package/src/global-files/digital-assets/useDigitalAssets.js +340 -0
- package/src/index.js +4 -0
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
import styled from "styled-components";
|
|
2
|
+
import { FontFamily, GlobalColors } from "../../../global-files/variables";
|
|
3
|
+
|
|
4
|
+
/*
|
|
5
|
+
Calcado de `GalleryElement/styles.js` para que imágenes y activos se lean como
|
|
6
|
+
una sola tabla. Cambia solo la rejilla de la fila —cuatro columnas en vez de
|
|
7
|
+
cinco, porque un activo no tiene leyenda— y lo propio de un archivo que se
|
|
8
|
+
está subiendo: barra de progreso, mensaje de error y botón de quitar.
|
|
9
|
+
*/
|
|
10
|
+
export const Container = styled.div`
|
|
11
|
+
position: relative;
|
|
12
|
+
|
|
13
|
+
&.grid-gallery {
|
|
14
|
+
width: 179px;
|
|
15
|
+
border-radius: 3px;
|
|
16
|
+
overflow: hidden;
|
|
17
|
+
|
|
18
|
+
.buttons-container {
|
|
19
|
+
position: absolute;
|
|
20
|
+
top: 5px;
|
|
21
|
+
left: 5px;
|
|
22
|
+
z-index: 1;
|
|
23
|
+
display: flex;
|
|
24
|
+
align-items: center;
|
|
25
|
+
justify-content: space-between;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
.asset-container {
|
|
29
|
+
border: 1px solid #f0eef2;
|
|
30
|
+
|
|
31
|
+
.information-container {
|
|
32
|
+
background-color: ${GlobalColors.s2};
|
|
33
|
+
display: flex;
|
|
34
|
+
justify-content: space-between;
|
|
35
|
+
padding: 0 2px;
|
|
36
|
+
|
|
37
|
+
p {
|
|
38
|
+
font-family: ${FontFamily.Lato};
|
|
39
|
+
color: ${GlobalColors.s4};
|
|
40
|
+
font-size: 11px;
|
|
41
|
+
line-height: 17px;
|
|
42
|
+
overflow: hidden;
|
|
43
|
+
text-overflow: ellipsis;
|
|
44
|
+
white-space: nowrap;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
&.row-gallery {
|
|
51
|
+
width: 100%;
|
|
52
|
+
display: grid;
|
|
53
|
+
grid-template-columns: 0.05fr 1fr;
|
|
54
|
+
align-items: center;
|
|
55
|
+
padding-right: 10px;
|
|
56
|
+
|
|
57
|
+
.buttons-container {
|
|
58
|
+
position: relative;
|
|
59
|
+
display: flex;
|
|
60
|
+
align-items: center;
|
|
61
|
+
justify-content: space-between;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
.asset-container {
|
|
65
|
+
display: grid;
|
|
66
|
+
grid-template-columns: auto 1fr;
|
|
67
|
+
align-items: center;
|
|
68
|
+
flex: 1;
|
|
69
|
+
|
|
70
|
+
.information-container {
|
|
71
|
+
display: grid;
|
|
72
|
+
grid-template-columns: repeat(4, 1fr);
|
|
73
|
+
align-items: center;
|
|
74
|
+
|
|
75
|
+
p {
|
|
76
|
+
font-family: ${FontFamily.Lato};
|
|
77
|
+
color: ${GlobalColors.s4};
|
|
78
|
+
font-size: 14px;
|
|
79
|
+
line-height: 17px;
|
|
80
|
+
text-align: center;
|
|
81
|
+
overflow: hidden;
|
|
82
|
+
text-overflow: ellipsis;
|
|
83
|
+
white-space: nowrap;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
& > * {
|
|
87
|
+
flex: 1;
|
|
88
|
+
padding: 5px;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/* El nombre es el sitio donde uno intenta hacer clic para abrir un archivo. */
|
|
95
|
+
.asset-link {
|
|
96
|
+
cursor: pointer;
|
|
97
|
+
text-decoration: underline;
|
|
98
|
+
text-underline-offset: 2px;
|
|
99
|
+
|
|
100
|
+
&:hover {
|
|
101
|
+
color: ${GlobalColors.color_purple};
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
&.asset-error {
|
|
106
|
+
border: 1px solid ${GlobalColors.secondary_magenta};
|
|
107
|
+
border-radius: 3px;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/* Progreso de subida. Ocupa el borde inferior de la fila para no empujar el
|
|
111
|
+
resto del contenido mientras aparece y desaparece. */
|
|
112
|
+
.asset-progress {
|
|
113
|
+
position: absolute;
|
|
114
|
+
left: 0;
|
|
115
|
+
right: 0;
|
|
116
|
+
bottom: 0;
|
|
117
|
+
height: 3px;
|
|
118
|
+
background: ${GlobalColors.s3};
|
|
119
|
+
border-radius: 2px;
|
|
120
|
+
overflow: hidden;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
.asset-progress-fill {
|
|
124
|
+
height: 100%;
|
|
125
|
+
background: ${GlobalColors.color_purple};
|
|
126
|
+
transition: width 0.2s ease-out;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/* En la fila, que es una rejilla de dos columnas, el mensaje ocupa todo el
|
|
130
|
+
ancho. Si no, cae en la columna del checkbox, la ensancha hasta su palabra
|
|
131
|
+
más larga y descuadra la fila contra las demás. */
|
|
132
|
+
.asset-error-message {
|
|
133
|
+
grid-column: 1 / -1;
|
|
134
|
+
margin: 2px 0 0;
|
|
135
|
+
padding: 0 8px 6px;
|
|
136
|
+
font-family: ${FontFamily.Lato};
|
|
137
|
+
font-size: 11px;
|
|
138
|
+
line-height: 15px;
|
|
139
|
+
color: ${GlobalColors.secondary_magenta};
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
.asset-remove {
|
|
143
|
+
position: absolute;
|
|
144
|
+
top: 4px;
|
|
145
|
+
right: 6px;
|
|
146
|
+
width: 18px;
|
|
147
|
+
height: 18px;
|
|
148
|
+
display: grid;
|
|
149
|
+
place-items: center;
|
|
150
|
+
padding: 0;
|
|
151
|
+
border: none;
|
|
152
|
+
border-radius: 50%;
|
|
153
|
+
background: ${GlobalColors.s2};
|
|
154
|
+
color: ${GlobalColors.s5};
|
|
155
|
+
font-size: 14px;
|
|
156
|
+
line-height: 1;
|
|
157
|
+
cursor: pointer;
|
|
158
|
+
|
|
159
|
+
&:hover {
|
|
160
|
+
background: ${GlobalColors.s3};
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
`;
|
|
@@ -25,6 +25,7 @@ export const GalleryElement = ({
|
|
|
25
25
|
auditImages,
|
|
26
26
|
compare,
|
|
27
27
|
updateApprovedInputsImages,
|
|
28
|
+
onSelect,
|
|
28
29
|
}) => {
|
|
29
30
|
const [showValidationPanel, setShowValidationPanel] = useState(false);
|
|
30
31
|
const [showDivStates, setShowDivStates] = useState({});
|
|
@@ -93,7 +94,15 @@ export const GalleryElement = ({
|
|
|
93
94
|
<ProductImage
|
|
94
95
|
img={compare ? auditImages?.values[number]?.src : image?.src}
|
|
95
96
|
altText={image?.altText}
|
|
97
|
+
// Sin `ext`, un video se pinta como <img> rota: la URL guardada no
|
|
98
|
+
// conserva la extensión del archivo.
|
|
99
|
+
ext={compare ? auditImages?.values[number]?.ext : image?.ext}
|
|
96
100
|
imageType={`catalogue-image-${gridLayout ? "big" : "small"}-size`}
|
|
101
|
+
// La miniatura de un video ya no lleva controles, así que el clic es
|
|
102
|
+
// la única forma de llegar al visor grande. Se aplica también a las
|
|
103
|
+
// imágenes: dos recuadros idénticos donde solo uno responde al clic
|
|
104
|
+
// sería peor que no responder ninguno.
|
|
105
|
+
onClick={onSelect}
|
|
97
106
|
/>
|
|
98
107
|
<div className="information-container">
|
|
99
108
|
<p>{image?.name}</p>
|
|
@@ -1,28 +1,80 @@
|
|
|
1
1
|
import { Container } from "./styles";
|
|
2
2
|
import { CheckBox } from "../../atoms/CheckBox";
|
|
3
3
|
|
|
4
|
-
|
|
4
|
+
/*
|
|
5
|
+
Las columnas de la bandeja dependen de qué se está listando: un PDF no tiene
|
|
6
|
+
"tipo de toma" ni alto por ancho, y una foto no tiene "tipo de activo".
|
|
7
|
+
Estaban escritas a mano en el marcado, así que la única forma de cambiarlas
|
|
8
|
+
era duplicar el componente.
|
|
9
|
+
|
|
10
|
+
El preset `image` reproduce exactamente las columnas y clases de antes, para
|
|
11
|
+
que la bandeja de imágenes se vea idéntica sin que nadie tenga que pasar nada.
|
|
12
|
+
*/
|
|
13
|
+
export const GALLERY_COLUMNS = {
|
|
14
|
+
image: {
|
|
15
|
+
template: "0.1fr 1fr 1fr 1fr 1fr 1fr",
|
|
16
|
+
columns: [
|
|
17
|
+
{ key: "preview", label: "Imagen", className: "image-header" },
|
|
18
|
+
{ key: "name", label: "Nombre", className: "image-name" },
|
|
19
|
+
{ key: "size", label: "Tamaño", className: "image-size" },
|
|
20
|
+
{ key: "format", label: "Formato", className: "image-format" },
|
|
21
|
+
{
|
|
22
|
+
key: "legend",
|
|
23
|
+
label: "¿Tiene leyenda?",
|
|
24
|
+
className: "image-description",
|
|
25
|
+
},
|
|
26
|
+
{ key: "shotType", label: "Tipo de toma", className: "shoot-type" },
|
|
27
|
+
],
|
|
28
|
+
},
|
|
29
|
+
asset: {
|
|
30
|
+
template: "0.1fr 1fr 1fr 1fr 1fr",
|
|
31
|
+
columns: [
|
|
32
|
+
{ key: "preview", label: "Activo", className: "asset-header" },
|
|
33
|
+
{ key: "name", label: "Nombre", className: "asset-name" },
|
|
34
|
+
{ key: "weight", label: "Peso", className: "asset-weight" },
|
|
35
|
+
{ key: "format", label: "Formato", className: "asset-format" },
|
|
36
|
+
{ key: "assetType", label: "Tipo de activo", className: "asset-type" },
|
|
37
|
+
],
|
|
38
|
+
},
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Encabezado de la bandeja, con su casilla de "seleccionar todo".
|
|
43
|
+
*
|
|
44
|
+
* `checkAllId` e `itemSelector` se pueden cambiar porque imágenes y activos
|
|
45
|
+
* conviven en la misma pestaña: si las dos listas compartieran el id de la
|
|
46
|
+
* casilla global, marcar todas las imágenes marcaría también los activos.
|
|
47
|
+
*/
|
|
48
|
+
export const GalleryHeader = ({
|
|
49
|
+
setCheckAll,
|
|
50
|
+
setSelectedImages,
|
|
51
|
+
shotThd,
|
|
52
|
+
variant = "image",
|
|
53
|
+
columns,
|
|
54
|
+
checkAllId = "check-all-images",
|
|
55
|
+
itemSelector = "[id^='gallery-element-']",
|
|
56
|
+
}) => {
|
|
57
|
+
const preset = GALLERY_COLUMNS[variant] ?? GALLERY_COLUMNS.image;
|
|
58
|
+
const visibleColumns = columns ?? preset.columns;
|
|
59
|
+
|
|
5
60
|
const checkAll = (e) => {
|
|
6
|
-
const checkboxList = document.querySelectorAll(
|
|
61
|
+
const checkboxList = document.querySelectorAll(itemSelector);
|
|
7
62
|
checkboxList.forEach((chk) => (chk.checked = e.target.checked));
|
|
8
63
|
setCheckAll(e.target.checked);
|
|
9
64
|
if (!e.target.checked) setSelectedImages([]);
|
|
10
65
|
};
|
|
66
|
+
|
|
11
67
|
return (
|
|
12
|
-
<Container>
|
|
68
|
+
<Container template={preset.template}>
|
|
13
69
|
<div className="checkbox">
|
|
14
|
-
<CheckBox id=
|
|
70
|
+
<CheckBox id={checkAllId} onChange={(e) => checkAll(e)} />
|
|
15
71
|
</div>
|
|
16
72
|
<div className="information">
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
<p className="shoot-type">Tipo de toma</p>
|
|
23
|
-
{/* <p className="">Tipo de imagen</p> */}
|
|
24
|
-
{/* <p className="">Tipo de empaque</p> */}
|
|
25
|
-
{/* <p className="">Estatus</p> */}
|
|
73
|
+
{visibleColumns.map((column) => (
|
|
74
|
+
<p key={column.key} className={column.className}>
|
|
75
|
+
{column.label}
|
|
76
|
+
</p>
|
|
77
|
+
))}
|
|
26
78
|
</div>
|
|
27
79
|
</Container>
|
|
28
80
|
);
|
|
@@ -15,7 +15,8 @@ export const Container = styled.div`
|
|
|
15
15
|
.information{
|
|
16
16
|
width: 90%;
|
|
17
17
|
display: grid;
|
|
18
|
-
|
|
18
|
+
/* Lo define el preset de columnas; el valor por defecto es el de imágenes. */
|
|
19
|
+
grid-template-columns: ${({ template }) => template || "0.1fr 1fr 1fr 1fr 1fr 1fr"};
|
|
19
20
|
}
|
|
20
21
|
.image-header{
|
|
21
22
|
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { useState } from "react";
|
|
2
|
+
import { GalleryTrayTabs, TRAYS } from "./index";
|
|
3
|
+
|
|
4
|
+
export default {
|
|
5
|
+
title: "Components/molecules/GalleryTrayTabs",
|
|
6
|
+
component: GalleryTrayTabs,
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
export const Interactivo = () => {
|
|
10
|
+
const [tray, setTray] = useState(TRAYS.IMAGES);
|
|
11
|
+
return (
|
|
12
|
+
<GalleryTrayTabs
|
|
13
|
+
activeTray={tray}
|
|
14
|
+
setActiveTray={setTray}
|
|
15
|
+
imagesCount={8}
|
|
16
|
+
assetsCount={3}
|
|
17
|
+
/>
|
|
18
|
+
);
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
/** Estado real hasta que exista el backend: la bandeja de activos en cero. */
|
|
22
|
+
export const SinActivos = () => {
|
|
23
|
+
const [tray, setTray] = useState(TRAYS.IMAGES);
|
|
24
|
+
return (
|
|
25
|
+
<GalleryTrayTabs
|
|
26
|
+
activeTray={tray}
|
|
27
|
+
setActiveTray={setTray}
|
|
28
|
+
imagesCount={8}
|
|
29
|
+
assetsCount={0}
|
|
30
|
+
/>
|
|
31
|
+
);
|
|
32
|
+
};
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { Container } from "./styles";
|
|
2
|
+
|
|
3
|
+
export const TRAYS = {
|
|
4
|
+
IMAGES: "images",
|
|
5
|
+
ASSETS: "assets",
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Selector entre las dos bandejas de la pestaña Imágenes.
|
|
10
|
+
*
|
|
11
|
+
* Los activos viven aquí y no en una pestaña propia porque es lo que pide la
|
|
12
|
+
* tarea —"dentro de las bandejas de imágenes"— y porque así comparten
|
|
13
|
+
* selección, descarga y permisos con las fotos en vez de duplicarlos.
|
|
14
|
+
*/
|
|
15
|
+
export const GalleryTrayTabs = ({
|
|
16
|
+
activeTray = TRAYS.IMAGES,
|
|
17
|
+
setActiveTray,
|
|
18
|
+
imagesCount = 0,
|
|
19
|
+
assetsCount = 0,
|
|
20
|
+
}) => {
|
|
21
|
+
const trays = [
|
|
22
|
+
{ id: TRAYS.IMAGES, label: "Imágenes", count: imagesCount },
|
|
23
|
+
{ id: TRAYS.ASSETS, label: "Activos digitales", count: assetsCount },
|
|
24
|
+
];
|
|
25
|
+
|
|
26
|
+
return (
|
|
27
|
+
<Container>
|
|
28
|
+
{trays.map((tray) => (
|
|
29
|
+
<button
|
|
30
|
+
key={tray.id}
|
|
31
|
+
type="button"
|
|
32
|
+
className={tray.id === activeTray ? "tray-tab active" : "tray-tab"}
|
|
33
|
+
onClick={() => setActiveTray?.(tray.id)}
|
|
34
|
+
>
|
|
35
|
+
{tray.label}
|
|
36
|
+
<span className="tray-count">{tray.count}</span>
|
|
37
|
+
</button>
|
|
38
|
+
))}
|
|
39
|
+
</Container>
|
|
40
|
+
);
|
|
41
|
+
};
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import styled from "styled-components";
|
|
2
|
+
import { FontFamily, GlobalColors } from "../../../global-files/variables";
|
|
3
|
+
|
|
4
|
+
export const Container = styled.div`
|
|
5
|
+
display: flex;
|
|
6
|
+
align-items: center;
|
|
7
|
+
gap: 4px;
|
|
8
|
+
padding: 8px 0 0;
|
|
9
|
+
|
|
10
|
+
.tray-tab {
|
|
11
|
+
display: flex;
|
|
12
|
+
align-items: center;
|
|
13
|
+
gap: 6px;
|
|
14
|
+
padding: 6px 14px;
|
|
15
|
+
border: none;
|
|
16
|
+
border-bottom: 2px solid transparent;
|
|
17
|
+
background: none;
|
|
18
|
+
cursor: pointer;
|
|
19
|
+
font-family: ${FontFamily.AvenirNext};
|
|
20
|
+
font-size: 14px;
|
|
21
|
+
line-height: 21px;
|
|
22
|
+
letter-spacing: -0.015em;
|
|
23
|
+
color: ${GlobalColors.s4};
|
|
24
|
+
|
|
25
|
+
&:hover {
|
|
26
|
+
color: ${GlobalColors.s5};
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
&.active {
|
|
30
|
+
color: ${GlobalColors.s5};
|
|
31
|
+
font-weight: 600;
|
|
32
|
+
border-bottom-color: ${GlobalColors.color_purple};
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
.tray-count {
|
|
37
|
+
min-width: 20px;
|
|
38
|
+
padding: 0 6px;
|
|
39
|
+
border-radius: 10px;
|
|
40
|
+
background: ${GlobalColors.s2};
|
|
41
|
+
color: ${GlobalColors.s5};
|
|
42
|
+
font-family: ${FontFamily.Lato};
|
|
43
|
+
font-size: 11px;
|
|
44
|
+
line-height: 18px;
|
|
45
|
+
text-align: center;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
.active .tray-count {
|
|
49
|
+
background: ${GlobalColors.color_purple};
|
|
50
|
+
color: ${GlobalColors.white};
|
|
51
|
+
}
|
|
52
|
+
`;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { useState } from "react";
|
|
1
2
|
import { RetailerSelector } from "./index";
|
|
2
3
|
|
|
3
4
|
export default {
|
|
@@ -5,7 +6,20 @@ export default {
|
|
|
5
6
|
component: RetailerSelector,
|
|
6
7
|
};
|
|
7
8
|
|
|
8
|
-
|
|
9
|
+
// El panel se abre hacia la izquierda del avatar: sin margen quedaría fuera de
|
|
10
|
+
// la vista. La cadena activa vive en estado para que elegir otra se vea.
|
|
11
|
+
const Template = (args) => {
|
|
12
|
+
const [activeRetailer, setActiveRetailer] = useState(args.retailers[1]);
|
|
13
|
+
return (
|
|
14
|
+
<div style={{ display: "flex", justifyContent: "flex-end", width: 420 }}>
|
|
15
|
+
<RetailerSelector
|
|
16
|
+
{...args}
|
|
17
|
+
activeRetailer={activeRetailer}
|
|
18
|
+
setActiveRetailer={setActiveRetailer}
|
|
19
|
+
/>
|
|
20
|
+
</div>
|
|
21
|
+
);
|
|
22
|
+
};
|
|
9
23
|
|
|
10
24
|
export const RetailerSelectorDefault = Template.bind({});
|
|
11
25
|
RetailerSelectorDefault.args = {
|
|
@@ -89,7 +89,14 @@ export const RetailerSelector = ({
|
|
|
89
89
|
);
|
|
90
90
|
setRetailersPanel(false);
|
|
91
91
|
}}
|
|
92
|
-
|
|
92
|
+
// El id llega como `id` o como `id_retailer` según el origen
|
|
93
|
+
// (en RetailerProductEdition, `categoryRetailerInOrder` usa
|
|
94
|
+
// `id_retailer`); comparar solo `id` nunca marcaba la activa.
|
|
95
|
+
active={
|
|
96
|
+
(retailer?.id ?? retailer?.id_retailer) != null &&
|
|
97
|
+
(retailer?.id ?? retailer?.id_retailer) ===
|
|
98
|
+
(activeRetailer?.id ?? activeRetailer?.id_retailer)
|
|
99
|
+
}
|
|
93
100
|
showStatus={showStatus}
|
|
94
101
|
/>
|
|
95
102
|
))}
|
|
@@ -69,7 +69,13 @@ export const RetailerSelectorv2 = ({
|
|
|
69
69
|
);
|
|
70
70
|
setRetailersPanel(false);
|
|
71
71
|
}}
|
|
72
|
-
|
|
72
|
+
// El id llega como `id` o como `id_retailer` según el origen;
|
|
73
|
+
// comparar solo `id` nunca marcaba la cadena activa.
|
|
74
|
+
active={
|
|
75
|
+
(retailer?.id ?? retailer?.id_retailer) != null &&
|
|
76
|
+
(retailer?.id ?? retailer?.id_retailer) ===
|
|
77
|
+
(activeRetailer?.id ?? activeRetailer?.id_retailer)
|
|
78
|
+
}
|
|
73
79
|
showStatus={showStatus}
|
|
74
80
|
/>
|
|
75
81
|
))}
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
import { useReducer, useState } from "react";
|
|
2
|
+
|
|
3
|
+
import { DigitalAssetsTray } from "./index";
|
|
4
|
+
import { useDigitalAssets } from "../../../global-files/digital-assets/useDigitalAssets";
|
|
5
|
+
import {
|
|
6
|
+
assetsInitialState,
|
|
7
|
+
assetsReducer,
|
|
8
|
+
countUnsavedAssets,
|
|
9
|
+
hasUnsavedAssets,
|
|
10
|
+
selectAssetsToSave,
|
|
11
|
+
} from "../../../global-files/digital-assets/assets.reducer";
|
|
12
|
+
import {
|
|
13
|
+
ASSET_STATUS,
|
|
14
|
+
buildSavePayload,
|
|
15
|
+
fromFile,
|
|
16
|
+
} from "../../../global-files/digital-assets/assets.adapter";
|
|
17
|
+
import { ASSET_ACTIONS } from "../../../global-files/digital-assets/assets.actions";
|
|
18
|
+
import { backendAssetsMock } from "../../../global-files/digital-assets/assets.mock";
|
|
19
|
+
|
|
20
|
+
export default {
|
|
21
|
+
title: "Components/organisms/DigitalAssetsTray",
|
|
22
|
+
component: DigitalAssetsTray,
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
/*
|
|
26
|
+
Las historias usan EL MISMO reducer que la ficha, no un estado de juguete.
|
|
27
|
+
Así lo que se ve aquí es el comportamiento real: qué queda marcado para
|
|
28
|
+
guardar, qué se anota para borrar y qué payload saldría hacia el backend.
|
|
29
|
+
*/
|
|
30
|
+
const Demo = ({
|
|
31
|
+
initialAssets = [],
|
|
32
|
+
localAssets = [],
|
|
33
|
+
gridLayout = false,
|
|
34
|
+
readOnly = false,
|
|
35
|
+
showPayload = false,
|
|
36
|
+
}) => {
|
|
37
|
+
const [state, dispatch] = useReducer(assetsReducer, assetsInitialState);
|
|
38
|
+
const [notices, setNotices] = useState([]);
|
|
39
|
+
|
|
40
|
+
const tray = useDigitalAssets({
|
|
41
|
+
assets: state.assets_values,
|
|
42
|
+
dispatch,
|
|
43
|
+
notices,
|
|
44
|
+
setNotices,
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
// Carga inicial simulando la respuesta del backend, una sola vez.
|
|
48
|
+
const [loaded, setLoaded] = useState(false);
|
|
49
|
+
if (!loaded && (initialAssets.length || localAssets.length)) {
|
|
50
|
+
if (initialAssets.length) tray.replaceAll(initialAssets);
|
|
51
|
+
// Archivos locales ya en un estado de subida (subiendo, con error...), para
|
|
52
|
+
// ver cómo se pintan sin tener que provocar el fallo contra el backend.
|
|
53
|
+
if (localAssets.length) {
|
|
54
|
+
dispatch({
|
|
55
|
+
type: ASSET_ACTIONS.ADD_ASSETS,
|
|
56
|
+
payload: localAssets.map(({ file, ...estado }) => ({ ...fromFile(file), ...estado })),
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
setLoaded(true);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const payload = buildSavePayload(selectAssetsToSave(state));
|
|
63
|
+
|
|
64
|
+
return (
|
|
65
|
+
<div style={{ width: 960 }}>
|
|
66
|
+
<DigitalAssetsTray
|
|
67
|
+
{...tray}
|
|
68
|
+
unsaved={countUnsavedAssets(state)}
|
|
69
|
+
gridLayout={gridLayout}
|
|
70
|
+
readOnly={readOnly}
|
|
71
|
+
selectedAssets={state.selected_assets}
|
|
72
|
+
setSelectedAssets={(assets) => dispatch({ type: "SET_SELECTED_ASSETS", payload: assets })}
|
|
73
|
+
setCheckAll={(checked) => dispatch({ type: "TOGGLE_CHECK_ALL_ASSETS", payload: checked })}
|
|
74
|
+
/>
|
|
75
|
+
|
|
76
|
+
<p style={{ fontFamily: "Lato", fontSize: 12 }}>
|
|
77
|
+
Seleccionados: {state.selected_assets.length} · Sin subir: {tray.pending.length} ·
|
|
78
|
+
Marcados para actualizar: {state.updated_assets.length} · Para borrar:{" "}
|
|
79
|
+
{state.deleted_assets.length} · ¿Hay cambios sin guardar?{" "}
|
|
80
|
+
{hasUnsavedAssets(state) ? "sí" : "no"}
|
|
81
|
+
</p>
|
|
82
|
+
|
|
83
|
+
{showPayload && (
|
|
84
|
+
<pre
|
|
85
|
+
style={{
|
|
86
|
+
fontSize: 11,
|
|
87
|
+
background: "#f7f7fc",
|
|
88
|
+
padding: 12,
|
|
89
|
+
borderRadius: 6,
|
|
90
|
+
maxHeight: 280,
|
|
91
|
+
overflow: "auto",
|
|
92
|
+
}}
|
|
93
|
+
>
|
|
94
|
+
{JSON.stringify(payload, null, 2)}
|
|
95
|
+
</pre>
|
|
96
|
+
)}
|
|
97
|
+
</div>
|
|
98
|
+
);
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Estado real de hoy: sin backend, la bandeja arranca vacía. Arrastrar un mp4,
|
|
103
|
+
* un gif, un PDF, un xlsx o un psd lo valida y lo agrega; cualquier otra cosa se
|
|
104
|
+
* rechaza con su motivo. Nada se sube.
|
|
105
|
+
*/
|
|
106
|
+
export const Vacia = () => <Demo />;
|
|
107
|
+
|
|
108
|
+
export const ConActivos = () => <Demo initialAssets={backendAssetsMock} />;
|
|
109
|
+
|
|
110
|
+
export const Cuadricula = () => <Demo initialAssets={backendAssetsMock} gridLayout />;
|
|
111
|
+
|
|
112
|
+
export const SoloLectura = () => <Demo initialAssets={backendAssetsMock} readOnly />;
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Lo que se le enviaría al backend, actualizándose en vivo. Cambia el tipo de un
|
|
116
|
+
* activo y aparece en `updateAssets`; bórralo y su id pasa a `deleteAssets`;
|
|
117
|
+
* suelta un archivo nuevo y aparece en `newAssets` sin `key`, porque todavía no
|
|
118
|
+
* se ha subido a S3.
|
|
119
|
+
*/
|
|
120
|
+
export const PayloadDeGuardado = () => <Demo initialAssets={backendAssetsMock} showPayload />;
|
|
121
|
+
|
|
122
|
+
const archivo = (name, type) => new File(["x"], name, { type });
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Cómo se ve una subida que falla: el motivo aparece debajo del archivo y el
|
|
126
|
+
* resto sigue su curso. Los textos son los que producen de verdad
|
|
127
|
+
* `uploadAssets.js` (S3 y firma) y `presign-asset-upload` (rechazo del servidor).
|
|
128
|
+
*/
|
|
129
|
+
export const ErroresDeSubida = () => (
|
|
130
|
+
<Demo
|
|
131
|
+
localAssets={[
|
|
132
|
+
{
|
|
133
|
+
file: archivo("video-campana.mp4", "video/mp4"),
|
|
134
|
+
status: ASSET_STATUS.UPLOADING,
|
|
135
|
+
progress: 45,
|
|
136
|
+
},
|
|
137
|
+
{
|
|
138
|
+
file: archivo("manual-instalacion.pdf", "application/pdf"),
|
|
139
|
+
status: ASSET_STATUS.ERROR,
|
|
140
|
+
error: "No se pudo subir el archivo. Avisa a soporte (permiso de almacenamiento).",
|
|
141
|
+
},
|
|
142
|
+
{
|
|
143
|
+
file: archivo("render-final.psd", ""),
|
|
144
|
+
status: ASSET_STATUS.ERROR,
|
|
145
|
+
error: "El archivo supera el tamaño permitido.",
|
|
146
|
+
},
|
|
147
|
+
{
|
|
148
|
+
file: archivo("tabla-tallas.xlsx", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"),
|
|
149
|
+
status: ASSET_STATUS.ERROR,
|
|
150
|
+
error: "No se pudo conectar para subir el archivo. Revisa tu conexión e inténtalo de nuevo.",
|
|
151
|
+
},
|
|
152
|
+
{
|
|
153
|
+
file: archivo("foto-lifestyle.jpg", "image/jpeg"),
|
|
154
|
+
status: ASSET_STATUS.ERROR,
|
|
155
|
+
error: "El archivo pesa más del máximo permitido para imagen",
|
|
156
|
+
},
|
|
157
|
+
{
|
|
158
|
+
file: archivo("catalogo.zip", "application/zip"),
|
|
159
|
+
status: ASSET_STATUS.ERROR,
|
|
160
|
+
error: "No se pudo preparar la subida. Revisa tu conexión e inténtalo de nuevo.",
|
|
161
|
+
},
|
|
162
|
+
]}
|
|
163
|
+
/>
|
|
164
|
+
);
|