contentoh-components-library 21.6.44 → 21.6.46

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/dist/index.js CHANGED
@@ -1135,7 +1135,7 @@ Object.keys(_index86).forEach(function (key) {
1135
1135
  });
1136
1136
  });
1137
1137
 
1138
- var _index87 = require("./components/organisms/ProductImageModal/index");
1138
+ var _index87 = require("./components/organisms/VariantGroupModal/index");
1139
1139
 
1140
1140
  Object.keys(_index87).forEach(function (key) {
1141
1141
  if (key === "default" || key === "__esModule") return;
@@ -1148,7 +1148,7 @@ Object.keys(_index87).forEach(function (key) {
1148
1148
  });
1149
1149
  });
1150
1150
 
1151
- var _index88 = require("./components/organisms/Chat/index");
1151
+ var _index88 = require("./components/organisms/ProductImageModal/index");
1152
1152
 
1153
1153
  Object.keys(_index88).forEach(function (key) {
1154
1154
  if (key === "default" || key === "__esModule") return;
@@ -1161,7 +1161,7 @@ Object.keys(_index88).forEach(function (key) {
1161
1161
  });
1162
1162
  });
1163
1163
 
1164
- var _index89 = require("./components/organisms/Modal/index");
1164
+ var _index89 = require("./components/organisms/Chat/index");
1165
1165
 
1166
1166
  Object.keys(_index89).forEach(function (key) {
1167
1167
  if (key === "default" || key === "__esModule") return;
@@ -1174,7 +1174,7 @@ Object.keys(_index89).forEach(function (key) {
1174
1174
  });
1175
1175
  });
1176
1176
 
1177
- var _index90 = require("./components/organisms/OrderDetail/index");
1177
+ var _index90 = require("./components/organisms/Modal/index");
1178
1178
 
1179
1179
  Object.keys(_index90).forEach(function (key) {
1180
1180
  if (key === "default" || key === "__esModule") return;
@@ -1187,6 +1187,19 @@ Object.keys(_index90).forEach(function (key) {
1187
1187
  });
1188
1188
  });
1189
1189
 
1190
+ var _index91 = require("./components/organisms/OrderDetail/index");
1191
+
1192
+ Object.keys(_index91).forEach(function (key) {
1193
+ if (key === "default" || key === "__esModule") return;
1194
+ if (key in exports && exports[key] === _index91[key]) return;
1195
+ Object.defineProperty(exports, key, {
1196
+ enumerable: true,
1197
+ get: function get() {
1198
+ return _index91[key];
1199
+ }
1200
+ });
1201
+ });
1202
+
1190
1203
  var _RangeCalendar = require("./components/organisms/RangeCalendar");
1191
1204
 
1192
1205
  Object.keys(_RangeCalendar).forEach(function (key) {
@@ -1213,28 +1226,28 @@ Object.keys(_TableResizable).forEach(function (key) {
1213
1226
  });
1214
1227
  });
1215
1228
 
1216
- var _index91 = require("./components/organisms/SideModal/index");
1229
+ var _index92 = require("./components/organisms/SideModal/index");
1217
1230
 
1218
- Object.keys(_index91).forEach(function (key) {
1231
+ Object.keys(_index92).forEach(function (key) {
1219
1232
  if (key === "default" || key === "__esModule") return;
1220
- if (key in exports && exports[key] === _index91[key]) return;
1233
+ if (key in exports && exports[key] === _index92[key]) return;
1221
1234
  Object.defineProperty(exports, key, {
1222
1235
  enumerable: true,
1223
1236
  get: function get() {
1224
- return _index91[key];
1237
+ return _index92[key];
1225
1238
  }
1226
1239
  });
1227
1240
  });
1228
1241
 
1229
- var _index92 = require("./components/organisms/GridProducts/index");
1242
+ var _index93 = require("./components/organisms/GridProducts/index");
1230
1243
 
1231
- Object.keys(_index92).forEach(function (key) {
1244
+ Object.keys(_index93).forEach(function (key) {
1232
1245
  if (key === "default" || key === "__esModule") return;
1233
- if (key in exports && exports[key] === _index92[key]) return;
1246
+ if (key in exports && exports[key] === _index93[key]) return;
1234
1247
  Object.defineProperty(exports, key, {
1235
1248
  enumerable: true,
1236
1249
  get: function get() {
1237
- return _index92[key];
1250
+ return _index93[key];
1238
1251
  }
1239
1252
  });
1240
1253
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "contentoh-components-library",
3
- "version": "21.6.44",
3
+ "version": "21.6.46",
4
4
  "dependencies": {
5
5
  "@aws-amplify/auth": "^4.5.3",
6
6
  "@aws-amplify/datastore": "^3.11.0",
@@ -0,0 +1,252 @@
1
+ import { useCallback, useEffect, useState } from "react";
2
+ import axios from "axios";
3
+
4
+ import { readPayload, ApiError } from "../LinkVariantsModal/api";
5
+ import {
6
+ Overlay,
7
+ Panel,
8
+ Header,
9
+ Body,
10
+ SearchRow,
11
+ List,
12
+ NewGroup,
13
+ Message,
14
+ Footer,
15
+ } from "./styles";
16
+
17
+ /*
18
+ Agrupar el producto como variante de otros.
19
+
20
+ Ojo con la palabra: esto NO es padre/hijo. Ahí hay jerarquía y herencia —un
21
+ padre baja valores a sus hijos—. Aquí no hay ninguna de las dos: los productos
22
+ de un grupo son "el mismo producto en otra presentación" y nada más. Comparten
23
+ un código en `article.variant_group` y ya. Por eso el modal no pregunta qué
24
+ heredar: no hay nada que heredar.
25
+
26
+ Dos formas de agrupar, y son la misma operación por debajo:
27
+ - elegir un grupo que ya existe
28
+ - teclear un código nuevo, que crea el grupo con este producto dentro
29
+
30
+ No hay tabla de grupos: un grupo existe mientras algún producto use su código.
31
+ Eso hace que una errata no falle, solo cree un grupo de un miembro — de ahí que
32
+ la lista de los existentes vaya PRIMERO y el campo de código nuevo después.
33
+
34
+ props (* requerido):
35
+ - *articleId: el producto que se va a agrupar
36
+ - articleName / articleUpc: para nombrarlo
37
+ - currentGroup: el grupo en el que ya está, si lo hay
38
+ - *token, endpoint
39
+ - *onClose, onSaved
40
+ */
41
+ export const VariantGroupModal = ({
42
+ articleId,
43
+ articleName,
44
+ articleUpc,
45
+ currentGroup = null,
46
+ token,
47
+ endpoint = process.env.REACT_APP_VARIANTS_ENDPOINT,
48
+ onClose,
49
+ onSaved,
50
+ }) => {
51
+ const [query, setQuery] = useState("");
52
+ const [groups, setGroups] = useState([]);
53
+ const [loading, setLoading] = useState(false);
54
+ const [saving, setSaving] = useState(false);
55
+ const [error, setError] = useState(null);
56
+
57
+ // Qué eligió el usuario: un código de la lista, o el que está escribiendo.
58
+ const [selected, setSelected] = useState(null);
59
+ const [nuevo, setNuevo] = useState("");
60
+
61
+ const request = useCallback(
62
+ async (action, body) => {
63
+ const res = await axios.post(`${endpoint}/${action}`, body, {
64
+ headers: { Authorization: token },
65
+ });
66
+ // El transporte siempre responde 200; el resultado real va en el cuerpo.
67
+ return readPayload(res);
68
+ },
69
+ [endpoint, token]
70
+ );
71
+
72
+ const buscar = useCallback(
73
+ async (texto) => {
74
+ setLoading(true);
75
+ setError(null);
76
+ try {
77
+ const data = await request("groups", { articleId, query: texto ?? "" });
78
+ setGroups(data.groups ?? []);
79
+ } catch (err) {
80
+ setError(
81
+ err instanceof ApiError || err?.name === "ApiError"
82
+ ? err.message
83
+ : "No se pudieron cargar los grupos."
84
+ );
85
+ } finally {
86
+ setLoading(false);
87
+ }
88
+ },
89
+ [request, articleId]
90
+ );
91
+
92
+ // Al abrir se listan los grupos que ya existen. Es lo que evita que el usuario
93
+ // teclee un código nuevo cuando en realidad quería sumarse a uno que ya está.
94
+ useEffect(() => {
95
+ buscar("");
96
+ // eslint-disable-next-line react-hooks/exhaustive-deps
97
+ }, []);
98
+
99
+ /*
100
+ El código se normaliza igual que en el servidor —mayúsculas, sin espacios—
101
+ para que lo que se ve en el campo sea lo que se va a guardar. Si el front
102
+ mostrara "abc 1" y la base guardara "ABC-1", el usuario buscaría después su
103
+ grupo por el texto que creyó escribir y no lo encontraría.
104
+ */
105
+ const normalizar = (texto) =>
106
+ `${texto ?? ""}`.trim().replace(/\s+/g, "-").toUpperCase();
107
+
108
+ const codigoNuevo = normalizar(nuevo);
109
+ const codigoFinal = selected ?? codigoNuevo;
110
+ const esNuevo = !selected && !!codigoNuevo;
111
+ const yaEstaba = codigoFinal && codigoFinal === currentGroup;
112
+
113
+ const guardar = async () => {
114
+ if (!codigoFinal || yaEstaba) return;
115
+
116
+ setSaving(true);
117
+ setError(null);
118
+ try {
119
+ const data = await request("join", { articleId, code: codigoFinal });
120
+ onSaved?.({ ...data, group: data.group ?? codigoFinal });
121
+ onClose?.();
122
+ } catch (err) {
123
+ setError(
124
+ err instanceof ApiError || err?.name === "ApiError"
125
+ ? err.message
126
+ : "No se pudo agrupar el producto. Intenta de nuevo."
127
+ );
128
+ setSaving(false);
129
+ }
130
+ };
131
+
132
+ const resumen = () => {
133
+ if (yaEstaba) return `Este producto ya está en «${codigoFinal}».`;
134
+ if (esNuevo) return `Se creará el grupo «${codigoNuevo}» con este producto.`;
135
+ if (selected) return `Se agregará a «${selected}».`;
136
+ return "Elige un grupo o escribe un código nuevo.";
137
+ };
138
+
139
+ return (
140
+ <Overlay onClick={saving ? undefined : onClose}>
141
+ <Panel onClick={(event) => event.stopPropagation()}>
142
+ <Header>
143
+ <h2>Agrupar como variante</h2>
144
+ <p>
145
+ {articleName || `Producto ${articleId}`}
146
+ {articleUpc ? ` · UPC ${articleUpc}` : ""}
147
+ {currentGroup ? ` · hoy en «${currentGroup}»` : ""}
148
+ </p>
149
+ </Header>
150
+
151
+ <Body>
152
+ {error && <Message className="error">{error}</Message>}
153
+
154
+ <SearchRow
155
+ onSubmit={(event) => {
156
+ event.preventDefault();
157
+ buscar(query);
158
+ }}
159
+ >
160
+ <input
161
+ type="text"
162
+ value={query}
163
+ placeholder="Busca un grupo por su código"
164
+ onChange={(event) => setQuery(event.target.value)}
165
+ />
166
+ <button type="submit" disabled={loading}>
167
+ {loading ? "Buscando…" : "Buscar"}
168
+ </button>
169
+ </SearchRow>
170
+
171
+ {groups.length > 0 && (
172
+ <List>
173
+ {groups.map((grupo) => (
174
+ <li key={grupo.code}>
175
+ <label>
176
+ <input
177
+ type="radio"
178
+ name="variant-group"
179
+ checked={selected === grupo.code}
180
+ onChange={() => {
181
+ setSelected(grupo.code);
182
+ setNuevo("");
183
+ }}
184
+ />
185
+ <span className="codigo">{grupo.code}</span>
186
+ {grupo.code === currentGroup ? (
187
+ <span className="actual">grupo actual</span>
188
+ ) : (
189
+ <span className="cuenta">
190
+ {grupo.products} producto
191
+ {grupo.products === 1 ? "" : "s"}
192
+ </span>
193
+ )}
194
+ </label>
195
+ </li>
196
+ ))}
197
+ </List>
198
+ )}
199
+
200
+ {!loading && groups.length === 0 && (
201
+ <Message>
202
+ {query.trim()
203
+ ? "Ningún grupo coincide con esa búsqueda."
204
+ : "Todavía no hay grupos en esta compañía. Crea el primero abajo."}
205
+ </Message>
206
+ )}
207
+
208
+ <NewGroup>
209
+ <label htmlFor="nuevo-grupo">O crea un grupo nuevo</label>
210
+ <input
211
+ id="nuevo-grupo"
212
+ type="text"
213
+ value={nuevo}
214
+ placeholder="RELOJ-CASIO-A158"
215
+ maxLength={50}
216
+ onChange={(event) => {
217
+ setNuevo(event.target.value);
218
+ setSelected(null);
219
+ }}
220
+ />
221
+ <span className="ayuda">
222
+ Letras, números, guion y guion bajo. Se guarda en mayúsculas, así
223
+ que «abc-1» y «ABC-1» son el mismo grupo.
224
+ </span>
225
+ </NewGroup>
226
+ </Body>
227
+
228
+ <Footer>
229
+ <span className="resumen">{resumen()}</span>
230
+ <div className="acciones">
231
+ <button
232
+ type="button"
233
+ className="secondary"
234
+ onClick={onClose}
235
+ disabled={saving}
236
+ >
237
+ Cancelar
238
+ </button>
239
+ <button
240
+ type="button"
241
+ className="primary"
242
+ onClick={guardar}
243
+ disabled={saving || !codigoFinal || yaEstaba}
244
+ >
245
+ {saving ? "Guardando…" : "Aceptar"}
246
+ </button>
247
+ </div>
248
+ </Footer>
249
+ </Panel>
250
+ </Overlay>
251
+ );
252
+ };
@@ -0,0 +1,254 @@
1
+ import styled from "styled-components";
2
+
3
+ import { GlobalColors, FontFamily } from "../../../global-files/variables";
4
+
5
+ const PINK = "#E13AA8";
6
+ const PINK_RGB = "225, 58, 168";
7
+
8
+ export const Overlay = styled.div`
9
+ position: fixed;
10
+ inset: 0;
11
+ z-index: 1000;
12
+ display: flex;
13
+ align-items: center;
14
+ justify-content: center;
15
+ background-color: rgba(40, 31, 51, 0.4);
16
+ `;
17
+
18
+ export const Panel = styled.div`
19
+ display: flex;
20
+ flex-direction: column;
21
+ width: min(560px, 92vw);
22
+ max-height: 86vh;
23
+ border-radius: 10px;
24
+ background-color: ${GlobalColors.white};
25
+ font-family: ${FontFamily.Raleway};
26
+ overflow: hidden;
27
+ `;
28
+
29
+ export const Header = styled.div`
30
+ padding: 20px 24px 12px;
31
+
32
+ h2 {
33
+ margin: 0;
34
+ font-family: ${FontFamily.Raleway_700};
35
+ font-size: 17px;
36
+ color: ${GlobalColors.deep_gray};
37
+ }
38
+
39
+ p {
40
+ margin: 4px 0 0;
41
+ font-size: 13px;
42
+ line-height: 1.4;
43
+ color: ${GlobalColors.gray};
44
+ }
45
+ `;
46
+
47
+ export const Body = styled.div`
48
+ flex: 1;
49
+ min-height: 0;
50
+ overflow-y: auto;
51
+ padding: 4px 24px 12px;
52
+ `;
53
+
54
+ export const SearchRow = styled.form`
55
+ display: flex;
56
+ gap: 8px;
57
+ margin-bottom: 12px;
58
+
59
+ input {
60
+ flex: 1;
61
+ min-width: 0;
62
+ height: 36px;
63
+ padding: 0 12px;
64
+ border: 1px solid ${GlobalColors.s2};
65
+ border-radius: 6px;
66
+ font-family: ${FontFamily.Raleway};
67
+ font-size: 13px;
68
+ color: ${GlobalColors.deep_gray};
69
+ }
70
+
71
+ input:focus {
72
+ outline: none;
73
+ border-color: ${PINK};
74
+ }
75
+
76
+ /*
77
+ Estas reglas faltaban y el botón se salía del panel.
78
+
79
+ Llevaba className="secondary", pero esa clase solo está definida dentro de
80
+ Footer, así que aquí no aplicaba nada: salía como un botón crudo del
81
+ navegador, sin altura ni ancho acotados. El flex-shrink es para que el texto
82
+ no se parta cuando el campo de búsqueda se lleva el espacio.
83
+ */
84
+ button {
85
+ flex-shrink: 0;
86
+ height: 36px;
87
+ padding: 0 18px;
88
+ border: none;
89
+ border-radius: 6px;
90
+ background-color: ${PINK};
91
+ color: ${GlobalColors.white};
92
+ font-family: ${FontFamily.Raleway_600};
93
+ font-size: 13px;
94
+ cursor: pointer;
95
+ }
96
+
97
+ button:disabled {
98
+ opacity: 0.5;
99
+ cursor: default;
100
+ }
101
+ `;
102
+
103
+ export const List = styled.ul`
104
+ margin: 0 0 12px;
105
+ padding: 0;
106
+ list-style: none;
107
+ border: 1px solid ${GlobalColors.s2};
108
+ border-radius: 6px;
109
+ overflow: hidden;
110
+
111
+ li + li {
112
+ border-top: 1px solid ${GlobalColors.s2};
113
+ }
114
+
115
+ label {
116
+ display: flex;
117
+ align-items: center;
118
+ gap: 10px;
119
+ padding: 9px 12px;
120
+ cursor: pointer;
121
+ font-size: 13px;
122
+ color: ${GlobalColors.deep_gray};
123
+ }
124
+
125
+ label:hover {
126
+ background-color: ${GlobalColors.s1};
127
+ }
128
+
129
+ input[type="radio"] {
130
+ accent-color: ${PINK};
131
+ flex-shrink: 0;
132
+ }
133
+
134
+ .codigo {
135
+ font-family: ${FontFamily.Raleway_600};
136
+ }
137
+
138
+ .cuenta {
139
+ margin-left: auto;
140
+ font-size: 11px;
141
+ color: ${GlobalColors.gray};
142
+ white-space: nowrap;
143
+ }
144
+
145
+ /* El grupo en el que ya está el producto: se marca para que no lo elija otra
146
+ vez creyendo que hace algo. */
147
+ .actual {
148
+ margin-left: auto;
149
+ font-size: 11px;
150
+ font-style: italic;
151
+ color: ${PINK};
152
+ white-space: nowrap;
153
+ }
154
+ `;
155
+
156
+ export const NewGroup = styled.div`
157
+ padding: 12px;
158
+ border: 1px dashed rgba(${PINK_RGB}, 0.5);
159
+ border-radius: 6px;
160
+ background-color: rgba(${PINK_RGB}, 0.04);
161
+
162
+ label {
163
+ display: block;
164
+ margin-bottom: 6px;
165
+ font-family: ${FontFamily.Raleway_600};
166
+ font-size: 12px;
167
+ color: ${GlobalColors.deep_gray};
168
+ }
169
+
170
+ input {
171
+ width: 100%;
172
+ height: 36px;
173
+ padding: 0 12px;
174
+ border: 1px solid ${GlobalColors.s2};
175
+ border-radius: 6px;
176
+ font-family: inherit;
177
+ font-size: 13px;
178
+ text-transform: uppercase;
179
+ color: ${GlobalColors.deep_gray};
180
+ }
181
+
182
+ input:focus {
183
+ outline: none;
184
+ border-color: ${PINK};
185
+ }
186
+
187
+ .ayuda {
188
+ display: block;
189
+ margin-top: 6px;
190
+ font-size: 11px;
191
+ line-height: 1.4;
192
+ color: ${GlobalColors.gray};
193
+ }
194
+ `;
195
+
196
+ export const Message = styled.p`
197
+ margin: 0 0 12px;
198
+ padding: 10px 12px;
199
+ border-radius: 6px;
200
+ font-size: 13px;
201
+ line-height: 1.4;
202
+ background-color: ${GlobalColors.s2};
203
+ color: ${GlobalColors.deep_gray};
204
+
205
+ &.error {
206
+ background-color: rgba(${PINK_RGB}, 0.1);
207
+ color: ${PINK};
208
+ }
209
+ `;
210
+
211
+ export const Footer = styled.div`
212
+ display: flex;
213
+ align-items: center;
214
+ justify-content: space-between;
215
+ gap: 12px;
216
+ padding: 16px 24px;
217
+ border-top: 1px solid ${GlobalColors.s2};
218
+
219
+ .resumen {
220
+ font-size: 12px;
221
+ color: ${GlobalColors.gray};
222
+ }
223
+
224
+ .acciones {
225
+ display: flex;
226
+ gap: 10px;
227
+ }
228
+
229
+ button {
230
+ height: 38px;
231
+ padding: 0 16px;
232
+ border-radius: 6px;
233
+ cursor: pointer;
234
+ font-family: ${FontFamily.Raleway_600};
235
+ font-size: 13px;
236
+ }
237
+
238
+ button:disabled {
239
+ opacity: 0.5;
240
+ cursor: default;
241
+ }
242
+
243
+ .secondary {
244
+ border: 1px solid ${GlobalColors.s2};
245
+ background: none;
246
+ color: ${GlobalColors.deep_gray};
247
+ }
248
+
249
+ .primary {
250
+ border: 1px solid ${PINK};
251
+ background-color: ${PINK};
252
+ color: ${GlobalColors.white};
253
+ }
254
+ `;