contentoh-components-library 21.5.93 → 21.5.95

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 (70) hide show
  1. package/dist/ai/utils/compare-strings.js +45 -0
  2. package/dist/components/atoms/GeneralButton/styles.js +1 -1
  3. package/dist/components/atoms/GeneralInput/index.js +245 -54
  4. package/dist/components/atoms/GeneralInput/styles.js +7 -3
  5. package/dist/components/atoms/InputFormatter/index.js +223 -68
  6. package/dist/components/atoms/InputFormatter/styles.js +20 -4
  7. package/dist/components/molecules/StatusAsignationInfo/index.js +11 -1
  8. package/dist/components/molecules/TabsMenu/index.js +13 -12
  9. package/dist/components/molecules/TagAndInput/index.js +361 -24
  10. package/dist/components/molecules/TagAndInput/styles.js +2 -2
  11. package/dist/components/organisms/ChangeStatusModal/index.js +531 -0
  12. package/dist/components/organisms/ChangeStatusModal/styles.js +85 -0
  13. package/dist/components/organisms/FullProductNameHeader/index.js +6 -22
  14. package/dist/components/organisms/InputGroup/index.js +22 -18
  15. package/dist/components/pages/ProviderProductEdition/ProviderProductEdition.stories.js +150 -337
  16. package/dist/components/pages/ProviderProductEdition/context/provider-product-edition.context.js +15 -15
  17. package/dist/components/pages/ProviderProductEdition/index.js +408 -381
  18. package/dist/components/pages/ProviderProductEdition/utils.js +1 -0
  19. package/dist/components/pages/RetailerProductEdition/RetailerProductEdition.stories.js +179 -196
  20. package/dist/components/pages/RetailerProductEdition/context/provider-product-edition.context.js +59 -260
  21. package/dist/components/pages/RetailerProductEdition/context/reducers/product.js +50 -38
  22. package/dist/components/pages/RetailerProductEdition/index.js +1719 -2237
  23. package/dist/components/pages/RetailerProductEdition/styles.js +4 -2
  24. package/dist/components/pages/RetailerProductEdition/utils.js +251 -2
  25. package/dist/contexts/AiProductEdition.js +230 -158
  26. package/dist/global-files/statusDictionary.js +103 -0
  27. package/package.json +4 -2
  28. package/src/ai/utils/compare-strings.js +45 -0
  29. package/src/assets/images/Icons/arrow.png +0 -0
  30. package/src/assets/images/Icons/cancel.png +0 -0
  31. package/src/assets/images/Icons/ia-icon.png +0 -0
  32. package/src/assets/images/Icons/loading.svg +5 -0
  33. package/src/assets/images/Icons/reload.png +0 -0
  34. package/src/components/atoms/GeneralButton/styles.js +4 -0
  35. package/src/components/atoms/GeneralInput/index.js +237 -60
  36. package/src/components/atoms/GeneralInput/styles.js +81 -0
  37. package/src/components/atoms/InputFormatter/index.js +200 -51
  38. package/src/components/atoms/InputFormatter/styles.js +284 -0
  39. package/src/components/atoms/RetailerSelector/RetailerSelector.stories.js +10 -0
  40. package/src/components/atoms/RetailerSelector/index.js +3 -0
  41. package/src/components/atoms/RetailerSelector/styles.js +0 -0
  42. package/src/components/molecules/StatusAsignationInfo/index.js +9 -1
  43. package/src/components/molecules/TabsMenu/index.js +12 -11
  44. package/src/components/molecules/TagAndInput/index.js +286 -21
  45. package/src/components/molecules/TagAndInput/styles.js +59 -17
  46. package/src/components/organisms/ChangeStatusModal/index.jsx +488 -0
  47. package/src/components/organisms/ChangeStatusModal/styles.js +333 -0
  48. package/src/components/organisms/FullProductNameHeader/index.js +4 -28
  49. package/src/components/organisms/FullTabsMenu/index.js +1 -1
  50. package/src/components/organisms/InputGroup/index.js +12 -4
  51. package/src/components/pages/ProviderProductEdition/ProviderProductEdition.stories.js +174 -202
  52. package/src/components/pages/ProviderProductEdition/context/provider-product-edition.context.jsx +14 -14
  53. package/src/components/pages/ProviderProductEdition/index.js +489 -457
  54. package/src/components/pages/ProviderProductEdition/utils.js +2 -2
  55. package/src/components/pages/RetailerProductEdition/RetailerProductEdition.stories.js +201 -224
  56. package/src/components/pages/RetailerProductEdition/context/provider-product-edition.context.jsx +575 -0
  57. package/src/components/pages/RetailerProductEdition/context/provider-product-edition.reducer.js +62 -0
  58. package/src/components/pages/RetailerProductEdition/context/reducers/active-state.js +344 -0
  59. package/src/components/pages/RetailerProductEdition/context/reducers/inputs.js +155 -0
  60. package/src/components/pages/RetailerProductEdition/context/reducers/product.js +114 -0
  61. package/src/components/pages/RetailerProductEdition/context/reducers/system.js +60 -0
  62. package/src/components/pages/RetailerProductEdition/index.js +1545 -1718
  63. package/src/components/pages/RetailerProductEdition/index_old.js +1979 -0
  64. package/src/components/pages/RetailerProductEdition/stories/Auditor.stories.js +101 -0
  65. package/src/components/pages/RetailerProductEdition/stories/ImageEditor.stories.js +115 -0
  66. package/src/components/pages/RetailerProductEdition/stories/TextEditor.stories.js +174 -0
  67. package/src/components/pages/RetailerProductEdition/styles.js +67 -2
  68. package/src/components/pages/RetailerProductEdition/utils.js +240 -0
  69. package/src/contexts/AiProductEdition.jsx +339 -0
  70. package/src/global-files/statusDictionary.js +103 -0
@@ -0,0 +1,45 @@
1
+
2
+ //Calcula el porcentaje de similitud entre la descripción generada por IA y la versión editada por el usuario.
3
+
4
+ export function getTextSimilarityPercentage(originalText, candidateText) {
5
+
6
+ if(!originalText || !candidateText) return;
7
+
8
+ const normalize = (text) => {
9
+ return text
10
+ .trim()
11
+ .toLowerCase()
12
+ .replace(/\s+/g, ' ');
13
+ };
14
+
15
+ const source = normalize(originalText);
16
+ const target = normalize(candidateText);
17
+
18
+ if (source === target) return 100;
19
+ if (source.length === 0 || target.length === 0) return 0;
20
+
21
+ const sourceLength = source.length;
22
+ const targetLength = target.length;
23
+ const distanceMatrix = Array(targetLength + 1).fill(null).map(() => []);
24
+
25
+ for (let i = 0; i <= sourceLength; i++) distanceMatrix[0][i] = i;
26
+ for (let j = 0; j <= targetLength; j++) distanceMatrix[j][0] = j;
27
+
28
+ for (let j = 1; j <= targetLength; j++) {
29
+ for (let i = 1; i <= sourceLength; i++) {
30
+ const substitutionCost = (source[i - 1] === target[j - 1]) ? 0 : 1;
31
+
32
+ distanceMatrix[j][i] = Math.min(
33
+ distanceMatrix[j - 1][i] + 1,
34
+ distanceMatrix[j][i - 1] + 1,
35
+ distanceMatrix[j - 1][i - 1] + substitutionCost
36
+ );
37
+ }
38
+ }
39
+
40
+ const editDistance = distanceMatrix[targetLength][sourceLength];
41
+ const maxLength = Math.max(sourceLength, targetLength);
42
+ const similarityScore = 1 - (editDistance / maxLength);
43
+
44
+ return similarityScore * 100;
45
+ }
@@ -0,0 +1,5 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100" preserveAspectRatio="xMidYMid" width="283" height="283" style="shape-rendering: auto; display: block; background: transparent;" xmlns:xlink="http://www.w3.org/1999/xlink"><g><g>
2
+ <path stroke-width="10" stroke="#e13aa7" fill="none" d="M50 27A23 23 0 1 0 69.9785248320784 38.604450626054636"></path>
3
+ <path fill="#e13aa7" d="M49 15L49 39L61 27L49 15"></path>
4
+ <animateTransform keyTimes="0;1" values="0 50 50;360 50 50" dur="0.9803921568627451s" repeatCount="indefinite" type="rotate" attributeName="transform"></animateTransform>
5
+ </g><g></g></g><!-- [ldio] generated by https://loading.io --></svg>
@@ -38,6 +38,10 @@ export const Container = styled.button`
38
38
  background-color: #603888;
39
39
  }
40
40
 
41
+ &.general-pink-button {
42
+ background-color: #E33AA9;
43
+ }
44
+
41
45
  &.general-transparent-button {
42
46
  background-color: transparent;
43
47
  border: 1px solid #503d66;
@@ -3,6 +3,16 @@ import { Container } from "./styles";
3
3
  import { InputFormatter } from "../InputFormatter";
4
4
  import { CheckBox } from "../CheckBox";
5
5
 
6
+ import AiGenerationIcon from "../../../assets/images/Icons/ia-icon.png";
7
+ import LoadingIcon from "../../../assets/images/Icons/loading.svg";
8
+ import ArrowIcon from "../../../assets/images/Icons/arrow.png";
9
+ import CancelIcon from "../../../assets/images/Icons/cancel.png";
10
+ import ReloadIcon from "../../../assets/images/Icons/reload.png";
11
+
12
+ import { useAiProductEdition } from "../../../contexts/AiProductEdition";
13
+ import { InputContainer, BottomContainer, OptionsContainer, ButtonsContainer } from "../InputFormatter/styles";
14
+ import { getTextSimilarityPercentage } from "../../../ai/utils/compare-strings";
15
+
6
16
  export const GeneralInput = ({
7
17
  inputType,
8
18
  inputId,
@@ -27,45 +37,67 @@ export const GeneralInput = ({
27
37
  disabled,
28
38
  onKeyDown,
29
39
  auditClass,
40
+
41
+ //AI Generation
42
+ hasAiGeneration = false,
43
+ isBenefitInput = false,
44
+ isAiGenerationLoading = false,
45
+ isAiRegenerationLoading=false,
46
+ isAiActive = false,
47
+ isAiAvailable = false,
48
+ aiGenerated = false,
49
+
50
+ setIsAiActive = () => {},
51
+ handlerAiGeneration = () => {},
52
+ handlerRegenerateSuggestions = () => {},
53
+ handleChangeSuggestion = () => {}
54
+
30
55
  }) => {
56
+
57
+ const {
58
+ suggestions,
59
+ currentSuggestion,
60
+ setCurrentSuggestionValue
61
+ } = useAiProductEdition();
62
+
31
63
  const [textValue, setTextValue] = useState({
32
64
  value: inputValue,
33
65
  });
34
66
  const [requiredEmpty, setRequiredEmpty] = useState(false);
35
67
 
36
- const onHandleChange = (evt) => {
37
- if (validateInput) {
38
- setTextValue({ value: validateInput(evt, position, inputsArray) });
39
- } else if (
40
- updatedDatasheets ||
41
- updatedDescriptions ||
42
- inputType === "textarea"
43
- ) {
44
- let generalValue;
45
- if (optionList?.length > 0) {
46
- let valueSelected = evt.target.value;
47
- generalValue = valueSelected;
48
- setTextValue({ value: generalValue });
49
- } else {
50
- generalValue =
51
- inputType === "checkbox" ? evt.target.checked : evt.target.value;
52
- setTextValue({
53
- value: generalValue,
54
- });
55
- }
56
- let dataSave = updatedDatasheets?.slice();
68
+ const [aiSuggestionAccepted, setAiSuggestionAccepted] = useState(false);
69
+ const [valueAccepted, setValueAccepted] = useState(inputValue);
70
+
71
+ const updateParentData = (generalValue, isAiAccepted, newBaseValue = null) => {
72
+ setTextValue({ value: generalValue });
73
+
74
+ const baseToCompare = newBaseValue !== null ? newBaseValue : valueAccepted;
75
+
76
+ const similarity = getTextSimilarityPercentage(baseToCompare, generalValue);
77
+
78
+ const generatedWithAi = (isAiAccepted || aiGenerated) && similarity >= 50;
79
+
80
+ if (updatedDatasheets || updatedDescriptions || inputType === "textarea") {
81
+ let dataSave = updatedDatasheets?.slice() || [];
57
82
 
58
83
  if (dataSave?.length > 0) {
59
84
  const index = dataSave.findIndex((e) => e.attributeId === inputId);
60
85
  if (index !== -1) {
61
- if (generalValue !== inputValue) dataSave[index].value = generalValue;
62
- else dataSave.splice(index, 1);
86
+ if (generalValue !== inputValue) {
87
+ dataSave[index].value = generalValue;
88
+ dataSave[index].aiSuggestionAccepted = generatedWithAi;
89
+ } else {
90
+ dataSave.splice(index, 1);
91
+ }
63
92
  } else {
64
- dataSave.push({
65
- articleId: articleId,
66
- attributeId: inputId,
67
- value: generalValue,
68
- });
93
+ if (generalValue !== inputValue) {
94
+ dataSave.push({
95
+ articleId: articleId,
96
+ attributeId: inputId,
97
+ value: generalValue,
98
+ aiSuggestionAccepted: generatedWithAi,
99
+ });
100
+ }
69
101
  }
70
102
  } else {
71
103
  if (generalValue !== inputValue) {
@@ -73,16 +105,69 @@ export const GeneralInput = ({
73
105
  articleId: articleId,
74
106
  attributeId: inputId,
75
107
  value: generalValue,
108
+ aiSuggestionAccepted: generatedWithAi,
76
109
  });
77
110
  }
78
111
  }
112
+
79
113
  setUpdatedDatasheets(dataSave);
114
+ }
115
+ };
116
+
117
+ const onHandleChange = (evt) => {
118
+ if (validateInput) {
119
+ setTextValue({ value: validateInput(evt, position, inputsArray) });
120
+ } else if (
121
+ updatedDatasheets ||
122
+ updatedDescriptions ||
123
+ inputType === "textarea"
124
+ ) {
125
+ let generalValue;
126
+ if (optionList?.length > 0) {
127
+ generalValue = evt.target.value;
128
+ } else {
129
+ generalValue =
130
+ inputType === "checkbox" ? evt.target.checked : evt.target.value;
131
+ }
132
+
133
+ updateParentData(generalValue, aiSuggestionAccepted);
134
+
80
135
  } else {
81
136
  setTextValue({ value: evt.target.value });
82
137
  inputOnChange && inputOnChange(evt);
83
138
  }
84
139
  };
85
140
 
141
+ useEffect(() => {
142
+
143
+ if(Object.keys(suggestions).length === 0) return;
144
+
145
+ if(currentSuggestion?.[inputId]) return
146
+
147
+
148
+ const firstSuggestion = suggestions?.[inputId]?.[0];
149
+
150
+ if(!firstSuggestion) return;
151
+
152
+ setCurrentSuggestionValue({
153
+ inputId,
154
+ index: 0,
155
+ value: firstSuggestion?.value
156
+ });
157
+
158
+ }, [suggestions]);
159
+
160
+ useEffect(() => {
161
+ if(!isAiActive) return;
162
+ setAiSuggestionAccepted(false);
163
+ }, [isAiActive]);
164
+
165
+ useEffect(() => {
166
+ if(!isAiActive && !aiSuggestionAccepted) {
167
+ setTextValue({ value: valueAccepted });
168
+ }
169
+ }, [suggestions, isAiActive]);
170
+
86
171
  useEffect(() => {
87
172
  setRequiredEmpty(
88
173
  isRequired &&
@@ -90,14 +175,26 @@ export const GeneralInput = ({
90
175
  );
91
176
  }, [textValue]);
92
177
 
93
- const numberInputOnWheelPreventChange = (e) => {
94
- // Prevent the input value change
95
- e.target.blur();
96
- // Prevent the page/container scrolling
97
- e.stopPropagation();
98
- // Refocus immediately, on the next tick (after the current function is done)
99
- setTimeout(() => e.target.focus(), 0);
100
- };
178
+ // const numberInputOnWheelPreventChange = (e) => {
179
+ // // Prevent the input value change
180
+ // e.target.blur();
181
+ // // Prevent the page/container scrolling
182
+ // e.stopPropagation();
183
+ // // Refocus immediately, on the next tick (after the current function is done)
184
+ // setTimeout(() => e.target.focus(), 0);
185
+ // };
186
+
187
+ //AI Generation
188
+
189
+ const handleAcceptSuggestion = (suggestionValue) => {
190
+ if(!suggestionValue) return;
191
+
192
+ setValueAccepted(suggestionValue);
193
+ setAiSuggestionAccepted(true);
194
+ setIsAiActive(false);
195
+
196
+ updateParentData(suggestionValue, true, suggestionValue);
197
+ }
101
198
 
102
199
  return (
103
200
  <Container isRequired={requiredEmpty} className={auditClass}>
@@ -124,33 +221,113 @@ export const GeneralInput = ({
124
221
  disabled={disabled}
125
222
  />
126
223
  ) : inputType !== "textarea" ? (
127
- <input
128
- type={inputType}
129
- disabled={disabled}
130
- id={inputId}
131
- size={inputSize}
132
- className="general-input"
133
- placeholder={inputPlaceholder}
134
- value={textValue.value}
135
- onInput={(e) => onHandleChange(e)}
136
- maxLength={maxChar}
137
- required={isRequired}
138
- onKeyDown={onKeyDown}
139
- onWheel={numberInputOnWheelPreventChange}
140
- />
224
+ <div>
225
+ <InputContainer className={hasAiGeneration ? "ai-generation" : ""}>
226
+ <input
227
+ type={inputType}
228
+ disabled={disabled}
229
+ id={inputId}
230
+ size={inputSize}
231
+ className={`general-input ${isAiActive && isBenefitInput && "ia-input"}`}
232
+ placeholder={inputPlaceholder}
233
+ value={isAiActive ? (
234
+ currentSuggestion?.[inputId]?.value
235
+ ) : textValue.value}
236
+ onInput={(e) => onHandleChange(e)}
237
+ maxLength={maxChar}
238
+ required={isRequired}
239
+ onKeyDown={onKeyDown}
240
+ // onWheel={numberInputOnWheelPreventChange}
241
+ />
242
+ {
243
+
244
+ hasAiGeneration && isBenefitInput && (
245
+ <div className={`icon_container ${isAiAvailable ? "ai-available" : ''} ${isAiActive ? 'ai-active' : ''}`} title={!isAiAvailable ? 'Debes de completar ficha técnica e imágenes para desbloquear la generación con IA' : ''} onClick={() => {
246
+ handlerAiGeneration({ type: "attribute" })
247
+ }}>
248
+ <img className={`${isAiGenerationLoading ? 'loading' : ''}`} src={isAiGenerationLoading ? LoadingIcon : isAiActive ? CancelIcon : AiGenerationIcon} />
249
+ </div>
250
+ )
251
+
252
+ }
253
+ </InputContainer>
254
+ <BottomContainer className={isAiActive ? "with-ai" : ""}>
255
+ {
256
+ isAiActive && (
257
+ <div className="ai-options">
258
+
259
+ <OptionsContainer>
260
+ <div className={
261
+ `arrow ${currentSuggestion?.[inputId]?.index === 0 && "disabled"}`
262
+ } onClick={() => {
263
+ handleChangeSuggestion({ action: "prev" })
264
+ }}>
265
+ <img src={ArrowIcon} alt="" />
266
+ </div>
267
+ <p>
268
+ {(currentSuggestion?.[inputId]?.index + 1) || 1}
269
+ /
270
+ {suggestions?.[inputId]?.length}
271
+ </p>
272
+ <div className={`arrow right ${currentSuggestion?.[inputId]?.index === suggestions?.[inputId]?.length - 1 && "disabled"}`} onClick={() => {
273
+
274
+ }}>
275
+ <img onClick={() => {
276
+ handleChangeSuggestion({ action: "next" })
277
+ }} src={ArrowIcon} alt="ai icon" />
278
+ </div>
279
+ </OptionsContainer>
280
+
281
+ <ButtonsContainer>
282
+
283
+ <div className={`reload-suggestions ${isAiRegenerationLoading && "loading"}`} onClick={() => {
284
+ handlerRegenerateSuggestions({
285
+ type: "attribute"
286
+ });
287
+ }}>
288
+ <img className="" src={isAiRegenerationLoading ? LoadingIcon : ReloadIcon} />
289
+ </div>
290
+
291
+ <div className="accept-suggestion" onClick={() => {
292
+ handleAcceptSuggestion(currentSuggestion?.[inputId]?.value);
293
+ }}>
294
+ <p>Aceptar sugerencia</p>
295
+ </div>
296
+
297
+ </ButtonsContainer>
298
+
299
+ </div>
300
+ )
301
+ }
302
+ </BottomContainer>
303
+ </div>
141
304
  ) : (
142
305
  <InputFormatter
143
306
  name={inputName}
144
- inputId={inputId}
145
- placeholder={inputPlaceholder}
146
- mainValue={textValue.value}
147
- onChange={onHandleChange}
148
- articleId={articleId}
149
- updatedDescriptions={updatedDescriptions}
150
- setUpdatedDescriptions={setUpdatedDescriptions}
151
- maxChar={maxChar}
152
- isRequired={isRequired}
153
- disabled={disabled}
307
+ inputId={inputId}
308
+ placeholder={inputPlaceholder}
309
+ mainValue={textValue.value}
310
+ onChange={onHandleChange}
311
+ articleId={articleId}
312
+ updatedDescriptions={updatedDescriptions}
313
+ setUpdatedDescriptions={setUpdatedDescriptions}
314
+ maxChar={maxChar}
315
+ isRequired={isRequired}
316
+ disabled={disabled}
317
+ hasAiGeneration={hasAiGeneration && inputId != 'commentary-box'}
318
+ isAiAvailable={isAiAvailable}
319
+ aiGenerated={aiGenerated}
320
+ handlerAiGeneration={() => {
321
+ handlerAiGeneration({ type: "description" })
322
+ }}
323
+ handlerRegenerateSuggestions={() => {
324
+ handlerRegenerateSuggestions({ type: "description" })
325
+ }}
326
+ handleChangeSuggestion={handleChangeSuggestion}
327
+ isAiGenerationLoading={isAiGenerationLoading}
328
+ isAiRegenerationLoading={isAiRegenerationLoading}
329
+ isAiActive={isAiActive}
330
+ setIsAiActive={setIsAiActive}
154
331
  />
155
332
  )}
156
333
  {/* <p>{description}</p> */}
@@ -84,3 +84,84 @@ export const Container = styled.div`
84
84
  background: lightgray;
85
85
  }
86
86
  `;
87
+
88
+ export const InputContainer = styled.div`
89
+
90
+ &.ai-generation {
91
+ position: relative;
92
+
93
+ cursor: pointer;
94
+
95
+ .ia-input {
96
+ padding-right: 2.5rem;
97
+ }
98
+
99
+ .icon_container {
100
+ position: absolute;
101
+ right: 10px;
102
+ top: 50%;
103
+ transform: translateY(-50%);
104
+ width: 20px;
105
+ height: 20px;
106
+ padding: 4px;
107
+
108
+ border-radius: 50%;
109
+ overflow: hidden;
110
+ display: flex;
111
+ align-items: center;
112
+ justify-content: center;
113
+ z-index: 1;
114
+
115
+ &::before {
116
+ content: '';
117
+ position: absolute;
118
+ top: 0;
119
+ left: 0;
120
+ width: 100%;
121
+ height: 100%;
122
+
123
+ background: linear-gradient(
124
+ 120deg,
125
+ #ffffff 10%,
126
+ #ffe0f4 50%,
127
+ #ffffff 90%
128
+ );
129
+ background-size: 200% 200%;
130
+
131
+ animation: ai-shimmer 3s ease-in-out infinite alternate;
132
+ z-index: -1;
133
+ }
134
+
135
+ &.ai-available::before{
136
+
137
+ background: gray;
138
+ background-size: 200% 200%;
139
+
140
+ }
141
+
142
+ img {
143
+ width: 100%;
144
+ height: 100%;
145
+ object-fit: contain;
146
+ position: relative;
147
+ z-index: 2;
148
+ }
149
+
150
+ img.loading {
151
+ width: 20px;
152
+ height: 20px;
153
+ }
154
+
155
+ }
156
+ }
157
+
158
+ @keyframes ai-shimmer {
159
+ 0% {
160
+ background-position: 0% 50%;
161
+ }
162
+ 100% {
163
+ background-position: 100% 50%;
164
+ }
165
+ }
166
+
167
+ `;