contentoh-components-library 21.5.85 → 21.5.87

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (23) hide show
  1. package/dist/components/atoms/GeneralButton/styles.js +1 -1
  2. package/dist/components/molecules/TagAndInput/index.js +55 -14
  3. package/dist/components/molecules/TagAndInput/styles.js +1 -1
  4. package/dist/components/organisms/ChangeStatusModal/index.js +531 -0
  5. package/dist/components/organisms/ChangeStatusModal/styles.js +85 -0
  6. package/dist/components/pages/RetailerProductEdition/RetailerProductEdition.stories.js +108 -231
  7. package/dist/components/pages/RetailerProductEdition/context/reducers/product.js +48 -2
  8. package/dist/components/pages/RetailerProductEdition/index.js +329 -101
  9. package/dist/components/pages/RetailerProductEdition/styles.js +1 -1
  10. package/dist/contexts/AiProductEdition.js +75 -22
  11. package/dist/global-files/statusDictionary.js +103 -0
  12. package/package.json +1 -1
  13. package/src/components/atoms/GeneralButton/styles.js +4 -0
  14. package/src/components/molecules/TagAndInput/index.js +32 -2
  15. package/src/components/molecules/TagAndInput/styles.js +11 -0
  16. package/src/components/organisms/ChangeStatusModal/index.jsx +488 -0
  17. package/src/components/organisms/ChangeStatusModal/styles.js +333 -0
  18. package/src/components/pages/RetailerProductEdition/RetailerProductEdition.stories.js +107 -259
  19. package/src/components/pages/RetailerProductEdition/context/reducers/product.js +55 -0
  20. package/src/components/pages/RetailerProductEdition/index.js +236 -78
  21. package/src/components/pages/RetailerProductEdition/styles.js +6 -0
  22. package/src/contexts/AiProductEdition.jsx +46 -0
  23. package/src/global-files/statusDictionary.js +103 -0
@@ -0,0 +1,488 @@
1
+ import { startTransition, useEffect, useState } from "react";
2
+ import {
3
+ IconButton,
4
+ MenuItem,
5
+ Checkbox,
6
+ ListItemText,
7
+ InputLabel,
8
+ } from "@mui/material";
9
+
10
+ import CloseIcon from "@mui/icons-material/Close";
11
+ import LinkIcon from "@mui/icons-material/Link";
12
+ import BuildOutlinedIcon from "@mui/icons-material/BuildOutlined";
13
+ import ArrowForwardIcon from "@mui/icons-material/ArrowForward";
14
+ import LayersIcon from "@mui/icons-material/Layers";
15
+ import ViewInArOutlined from "@mui/icons-material/ViewInArOutlined";
16
+ import { CachedOutlined } from "@mui/icons-material";
17
+
18
+ import CheckCircleIcon from "@mui/icons-material/CheckCircle";
19
+ import RadioButtonUncheckedIcon from "@mui/icons-material/RadioButtonUnchecked";
20
+
21
+ import { STATUS_DICTIONARY } from "../../../global-files/statusDictionary";
22
+
23
+ import {
24
+ StyledDialog,
25
+ ModalHeader,
26
+ BadgeCircle,
27
+ MainSection,
28
+ Section,
29
+ SectionTitle,
30
+ ScopeButtonGroup,
31
+ ScopeButton,
32
+ HelperText,
33
+ StatusGrid,
34
+ StatusPill,
35
+ StyledTextArea,
36
+ StyledFormControl,
37
+ StyledSelect,
38
+ FooterActions,
39
+ ActionButton,
40
+ ImpactSummary,
41
+ colors,
42
+ } from "./styles";
43
+ import axios from "axios";
44
+
45
+ const statusOptions = [
46
+ { value: "PA", label: "Por Asignar" },
47
+ { value: "AS", label: "Asignado" },
48
+ { value: "AC", label: "Aprobado Coordinador" },
49
+ { value: "AP", label: "Aprobado Proveedor" },
50
+ { value: "R", label: "Rechazado" },
51
+ { value: "RP", label: "Rechazo Pendiente" },
52
+ ];
53
+
54
+ const availableServices = ["description", "datasheet", "images"];
55
+
56
+ const ChangeStatusModal = ({
57
+ state,
58
+ open,
59
+ onClose,
60
+ token = "",
61
+ reloadData = () => {},
62
+ }) => {
63
+ const [scope, setScope] = useState("product");
64
+ const [newStatus, setNewStatus] = useState("");
65
+ const [comment, setComment] = useState("");
66
+ const [availableRetailers, setAvailableRetailers] = useState([]);
67
+ const [selectedChains, setSelectedChains] = useState([]);
68
+ const [selectedServices, setSelectedServices] = useState([]);
69
+
70
+ const [isSubmitting, setIsSubmitting] = useState(false);
71
+
72
+ const [currentStatusObj, setCurrentStatusObj] = useState({});
73
+
74
+ const isStatusValid =
75
+ newStatus !== "" && newStatus !== currentStatusObj.label;
76
+ const isCommentValid = comment.trim().length > 0;
77
+
78
+ let isScopeValid = false;
79
+ if (scope === "product") isScopeValid = true;
80
+ if (scope === "retailer") isScopeValid = selectedChains.length > 0;
81
+ if (scope === "service")
82
+ isScopeValid = selectedChains.length > 0 && selectedServices.length > 0;
83
+
84
+ const canSubmit = isStatusValid && isCommentValid && isScopeValid;
85
+
86
+ const handleScopeChange = (newScope) => {
87
+ setScope(newScope);
88
+ setSelectedChains([]);
89
+ setSelectedServices([]);
90
+ };
91
+
92
+ const handleSubmit = async () => {
93
+ if (!canSubmit || isSubmitting) return;
94
+
95
+ setIsSubmitting(true);
96
+
97
+ const upc = state?.product?.upc;
98
+ const article_id = state?.product?.id_article;
99
+ const order_id = state?.product?.id_order;
100
+ const version = state?.product?.version;
101
+ const retailers_selected = availableRetailers
102
+ ?.filter((retailer) => selectedChains?.includes(retailer.id_retailer))
103
+ ?.map((retailer) => ({
104
+ id: retailer?.id_retailer,
105
+ name: retailer?.name,
106
+ }));
107
+ const services_selected = selectedServices;
108
+
109
+ const old_status = state?.product?.status;
110
+ const new_status = Object.values(STATUS_DICTIONARY).find(
111
+ (status) => status.name === newStatus,
112
+ ).status;
113
+
114
+ const commentary = comment;
115
+
116
+ const payload = {
117
+ upc,
118
+ articleId: article_id,
119
+ orderId: order_id,
120
+ version: version,
121
+ retailers:
122
+ scope === "product"
123
+ ? availableRetailers?.map((retailer) => ({
124
+ id: retailer?.id_retailer,
125
+ name: retailer?.name,
126
+ }))
127
+ : retailers_selected,
128
+ services:
129
+ scope !== "service"
130
+ ? Object.keys(state?.product.services)
131
+ .filter((service) => !!state?.product?.services[service])
132
+ .map((service) =>
133
+ service
134
+ .replace("datasheets", "datasheet")
135
+ .replace("descriptions", "description"),
136
+ )
137
+ : services_selected,
138
+ oldStatus: old_status,
139
+ newStatus: new_status,
140
+ commentary: commentary,
141
+ };
142
+
143
+ const response = await axios.post(
144
+ process.env.REACT_APP_REQUEST_CHANGE_STATUS,
145
+ payload,
146
+ {
147
+ headers: {
148
+ Authorization: token,
149
+ },
150
+ },
151
+ );
152
+
153
+ reloadData();
154
+ setIsSubmitting(false);
155
+ onClose();
156
+ };
157
+
158
+ useEffect(() => {
159
+ if (!state) return;
160
+ setCurrentStatusObj(STATUS_DICTIONARY[state?.product?.status]);
161
+
162
+ setAvailableRetailers(state?.product?.categoryRetailerInOrder || []);
163
+ }, [state]);
164
+
165
+ const selectMenuProps = {
166
+ PaperProps: {
167
+ style: {
168
+ borderRadius: "5px",
169
+ marginTop: "5px",
170
+ boxShadow: "0px 4px 20px rgba(0,0,0,0.1)",
171
+ },
172
+ },
173
+ };
174
+
175
+ return (
176
+ <StyledDialog open={open} onClose={onClose}>
177
+ <ModalHeader>
178
+ <IconButton className="close-icon" onClick={onClose} size="small">
179
+ <CloseIcon fontSize="small" />
180
+ </IconButton>
181
+ <BadgeCircle>
182
+ <CachedOutlined />
183
+ </BadgeCircle>
184
+ <div className="title-container">
185
+ <h2>Cambio de Estatus</h2>
186
+ <p>{state?.product?.name}</p>
187
+ </div>
188
+ </ModalHeader>
189
+
190
+ <MainSection>
191
+ <Section>
192
+ <SectionTitle>Alcance del cambio:</SectionTitle>
193
+ <ScopeButtonGroup>
194
+ <ScopeButton
195
+ active={scope === "product"}
196
+ onClick={() => handleScopeChange("product")}
197
+ >
198
+ <ViewInArOutlined /> Producto completo
199
+ </ScopeButton>
200
+ <ScopeButton
201
+ active={scope === "retailer"}
202
+ onClick={() => handleScopeChange("retailer")}
203
+ >
204
+ <LinkIcon /> Por Cadena
205
+ </ScopeButton>
206
+ <ScopeButton
207
+ active={scope === "service"}
208
+ onClick={() => handleScopeChange("service")}
209
+ >
210
+ <BuildOutlinedIcon /> Por Servicio
211
+ </ScopeButton>
212
+ </ScopeButtonGroup>
213
+ {scope === "product" && (
214
+ <HelperText>
215
+ Se cambiará el estatus de todos los servicios y cadenas
216
+ </HelperText>
217
+ )}
218
+ {scope === "retailer" && (
219
+ <HelperText>
220
+ Afectará todos los servicios de las cadenas seleccionadas
221
+ </HelperText>
222
+ )}
223
+ {scope === "service" && (
224
+ <HelperText>
225
+ Afectará únicamente los servicios de las cadenas seleccionadas
226
+ </HelperText>
227
+ )}
228
+ </Section>
229
+
230
+ {(scope === "retailer" || scope === "service") && (
231
+ <Section style={{ paddingTop: 10 }}>
232
+ <StyledFormControl fullWidth size="small">
233
+ <InputLabel id="chain-label">Selecciona Cadena(s)</InputLabel>
234
+ <StyledSelect
235
+ labelId="chain-label"
236
+ multiple
237
+ // 1. El value debe ser directamente tu estado (el arreglo de IDs)
238
+ value={selectedChains}
239
+ // 2. onChange seguro para MUI
240
+ onChange={(e) => {
241
+ const {
242
+ target: { value },
243
+ } = e;
244
+ setSelectedChains(
245
+ typeof value === "string" ? value.split(",") : value,
246
+ );
247
+ }}
248
+ // 3. renderValue traduce los IDs a los Nombres de las cadenas para mostrarlos separados por coma
249
+ renderValue={(selected) =>
250
+ availableRetailers
251
+ .filter((retailer) =>
252
+ selected.includes(retailer.id_retailer),
253
+ )
254
+ .map((retailer) => retailer.name)
255
+ .join(", ")
256
+ }
257
+ MenuProps={selectMenuProps}
258
+ >
259
+ {availableRetailers.map((retailer) => (
260
+ <MenuItem
261
+ key={retailer.name}
262
+ value={retailer.id_retailer}
263
+ sx={{
264
+ fontFamily: "Raleway",
265
+ fontSize: "0.85rem",
266
+ padding: "6px 12px",
267
+ "&.Mui-selected": {
268
+ backgroundColor: `${colors.primary}1A`,
269
+ },
270
+ "&.Mui-selected:hover": {
271
+ backgroundColor: `${colors.primary}33`,
272
+ },
273
+ }}
274
+ >
275
+ <Checkbox
276
+ icon={<RadioButtonUncheckedIcon sx={{ fontSize: 18 }} />}
277
+ checkedIcon={<CheckCircleIcon sx={{ fontSize: 18 }} />}
278
+ checked={
279
+ selectedChains.indexOf(retailer.id_retailer) > -1
280
+ }
281
+ style={{
282
+ color: colors.primary,
283
+ padding: "4px 8px 4px 4px",
284
+ }}
285
+ />
286
+ <img
287
+ src={`https://content-management-images.s3.amazonaws.com/retailers/${retailer?.id_retailer}.png`}
288
+ alt={retailer?.name}
289
+ style={{
290
+ width: "20px",
291
+ height: "20px",
292
+ objectFit: "contain",
293
+ marginRight: "12px",
294
+ borderRadius: "4px",
295
+ }}
296
+ />
297
+ <ListItemText
298
+ primary={retailer?.name}
299
+ primaryTypographyProps={{
300
+ style: { fontFamily: "Raleway", fontSize: "0.85rem" },
301
+ }}
302
+ />
303
+ </MenuItem>
304
+ ))}
305
+ </StyledSelect>
306
+ </StyledFormControl>
307
+ </Section>
308
+ )}
309
+
310
+ {scope === "service" && (
311
+ <Section style={{ paddingTop: 0 }}>
312
+ <StyledFormControl fullWidth size="small">
313
+ <InputLabel id="service-label">Selecciona Servicio(s)</InputLabel>
314
+ <StyledSelect
315
+ labelId="service-label"
316
+ multiple
317
+ value={selectedServices}
318
+ onChange={(e) => {
319
+ const {
320
+ target: { value },
321
+ } = e;
322
+ setSelectedServices(
323
+ typeof value === "string" ? value.split(",") : value,
324
+ );
325
+ }}
326
+ renderValue={(selected) =>
327
+ selected
328
+ .map((service) =>
329
+ service === "description"
330
+ ? "Descripciones"
331
+ : service === "datasheet"
332
+ ? "Ficha Técnica"
333
+ : "Imágenes",
334
+ )
335
+ .join(", ")
336
+ }
337
+ MenuProps={selectMenuProps}
338
+ >
339
+ {availableServices.map((service) => (
340
+ <MenuItem
341
+ key={service}
342
+ value={service}
343
+ sx={{
344
+ fontFamily: "Raleway",
345
+ fontSize: "0.85rem",
346
+ padding: "6px 12px",
347
+ "&.Mui-selected": {
348
+ backgroundColor: `${colors.primary}1A`,
349
+ },
350
+ "&.Mui-selected:hover": {
351
+ backgroundColor: `${colors.primary}33`,
352
+ },
353
+ }}
354
+ >
355
+ <Checkbox
356
+ icon={<RadioButtonUncheckedIcon sx={{ fontSize: 18 }} />}
357
+ checkedIcon={<CheckCircleIcon sx={{ fontSize: 18 }} />}
358
+ checked={selectedServices.indexOf(service) > -1}
359
+ style={{
360
+ color: colors.primary,
361
+ padding: "4px 8px 4px 4px",
362
+ }}
363
+ />
364
+ <ListItemText
365
+ primary={
366
+ service === "description"
367
+ ? "Descripciones"
368
+ : service === "datasheet"
369
+ ? "Ficha Técnica"
370
+ : "Imágenes"
371
+ }
372
+ primaryTypographyProps={{
373
+ style: { fontFamily: "Raleway", fontSize: "0.85rem" },
374
+ }}
375
+ />
376
+ </MenuItem>
377
+ ))}
378
+ </StyledSelect>
379
+ </StyledFormControl>
380
+ </Section>
381
+ )}
382
+
383
+ <Section>
384
+ <SectionTitle>Estatus actual:</SectionTitle>
385
+ <StatusPill
386
+ label={currentStatusObj.name}
387
+ selected={false}
388
+ disabled={true}
389
+ />
390
+ </Section>
391
+
392
+ <Section>
393
+ <SectionTitle>Nuevo estatus:</SectionTitle>
394
+ <StatusGrid>
395
+ {Object.values(STATUS_DICTIONARY).map((status) => (
396
+ <StatusPill
397
+ key={status.name}
398
+ label={status.name}
399
+ disabled={status.name === currentStatusObj.name}
400
+ selected={status.name === newStatus}
401
+ onClick={() =>
402
+ status.name !== currentStatusObj.name &&
403
+ setNewStatus(status.name)
404
+ }
405
+ />
406
+ ))}
407
+ </StatusGrid>
408
+ </Section>
409
+
410
+ <Section>
411
+ <SectionTitle>Comentario:</SectionTitle>
412
+ <StyledTextArea
413
+ multiline
414
+ rows={3}
415
+ placeholder="Explica el motivo del cambio de estatus..."
416
+ value={comment}
417
+ onChange={(e) => {
418
+ if (e.target.value.length <= 255) {
419
+ setComment(e.target.value);
420
+ }
421
+ }}
422
+ inputProps={{ maxLength: 255 }}
423
+ helperText={`${comment.length}/255 caracteres`}
424
+ FormHelperTextProps={{
425
+ style: { textAlign: "right" }
426
+ }}
427
+ />
428
+ </Section>
429
+
430
+ <ImpactSummary>
431
+ <span className="summary-title">Resumen de impacto:</span>
432
+ <div className="status-flow">
433
+ <StatusPill
434
+ label={currentStatusObj.name}
435
+ selected={false}
436
+ disabled={true}
437
+ size="small"
438
+ />
439
+ <ArrowForwardIcon
440
+ fontSize="small"
441
+ sx={{ color: colors.textMuted }}
442
+ />
443
+ <StatusPill
444
+ label={newStatus || "Selecciona un estatus"}
445
+ selected={!!newStatus}
446
+ size="small"
447
+ />
448
+ </div>
449
+ <div className="impact-counts">
450
+ <div>
451
+ <LinkIcon fontSize="small" />
452
+ {scope === "product"
453
+ ? state?.product?.categoryRetailerInOrder?.length
454
+ : selectedChains.length}{" "}
455
+ cadena(s)
456
+ </div>
457
+ <div>
458
+ <BuildOutlinedIcon fontSize="small" />
459
+ {scope === "service"
460
+ ? selectedServices.length
461
+ : Object.values(state?.product?.services ?? {})
462
+ ? Object.values(state?.product?.services ?? {}).filter(
463
+ (service) => !!service,
464
+ ).length
465
+ : "No es un array"}
466
+ &nbsp;servicios
467
+ </div>
468
+ </div>
469
+ </ImpactSummary>
470
+ </MainSection>
471
+
472
+ <FooterActions>
473
+ <ActionButton className="cancel" onClick={onClose}>
474
+ Cancelar
475
+ </ActionButton>
476
+ <ActionButton
477
+ className="submit"
478
+ disabled={!canSubmit || isSubmitting}
479
+ onClick={handleSubmit}
480
+ >
481
+ {isSubmitting ? "Guardando..." : "Realizar cambio"}
482
+ </ActionButton>
483
+ </FooterActions>
484
+ </StyledDialog>
485
+ );
486
+ };
487
+
488
+ export default ChangeStatusModal;