@vtex/faststore-plugin-buyer-portal 1.3.48 → 1.3.49

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/CHANGELOG.md CHANGED
@@ -7,6 +7,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [1.3.49] - 2025-12-19
11
+
12
+ ### Added
13
+
14
+ - Add component of Criteria Selection of Custom Fields on Buying Policies
15
+
10
16
  ## [1.3.48] - 2025-12-19
11
17
 
12
18
  - Adjustment from merge to Collections to Products Assortment
@@ -411,7 +417,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
411
417
  - Add CHANGELOG file
412
418
  - Add README file
413
419
 
414
- [unreleased]: https://github.com/vtex/faststore-plugin-buyer-portal/compare/v1.3.48...HEAD
420
+ [unreleased]: https://github.com/vtex/faststore-plugin-buyer-portal/compare/v1.3.49...HEAD
415
421
  [1.2.3]: https://github.com/vtex/faststore-plugin-buyer-portal/compare/v1.2.2...1.2.3
416
422
  [1.2.3]: https://github.com/vtex/faststore-plugin-buyer-portal/releases/tag/1.2.3
417
423
  [1.2.4]: https://github.com/vtex/faststore-plugin-buyer-portal/releases/tag/1.2.4
@@ -461,6 +467,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
461
467
  [1.3.36]: https://github.com/vtex/faststore-plugin-buyer-portal/compare/v1.3.35...v1.3.36
462
468
  [1.3.35]: https://github.com/vtex/faststore-plugin-buyer-portal/releases/tag/1.3.35
463
469
 
470
+ [1.3.49]: https://github.com/vtex/faststore-plugin-buyer-portal/compare/v1.3.48...v1.3.49
464
471
  [1.3.48]: https://github.com/vtex/faststore-plugin-buyer-portal/compare/v1.3.47...v1.3.48
465
472
  [1.3.47]: https://github.com/vtex/faststore-plugin-buyer-portal/compare/v1.3.46...v1.3.47
466
473
  [1.3.46]: https://github.com/vtex/faststore-plugin-buyer-portal/compare/v1.3.45...v1.3.46
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vtex/faststore-plugin-buyer-portal",
3
- "version": "1.3.48",
3
+ "version": "1.3.49",
4
4
  "description": "A plugin for faststore with buyer portal",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -24,12 +24,15 @@ import {
24
24
  BUYING_POLICIES_WORKFLOW_TYPES,
25
25
  buyingPolicyDefault,
26
26
  BUDGET_CRITERIA,
27
+ COST_CENTER_CRITERIA,
28
+ PO_NUMBER_CRITERIA,
27
29
  orderFieldsCriteriaOptions,
28
30
  spendingLimitsCriteriaOptions,
29
31
  } from "../../utils";
30
32
  import { BUYING_POLICIES_WORKFLOW_LABELS } from "../../utils/buyingPoliciesWorkflowTypes";
31
33
  import { getHighlightedText } from "../../utils/criteriaHighlightSyntax";
32
34
  import { BudgetCriteriaSelector } from "../BudgetCriteriaSelector";
35
+ import { CustomFieldCriteriaSelector } from "../CustomFieldCriteriaSelector/CustomFieldCriteriaSelector";
33
36
 
34
37
  const LIMIT_OF_LEVELS = 5;
35
38
 
@@ -173,6 +176,18 @@ export const BasicBuyingPolicyDrawer = ({
173
176
  criteria === BUDGET_CRITERIA ||
174
177
  criteria.includes('$exists(budgetData.budgets[id="');
175
178
 
179
+ const isPONumberCriteria =
180
+ criteria === PO_NUMBER_CRITERIA ||
181
+ criteria.includes(
182
+ 'customData.customFields.fields[name="PO Number"].value ='
183
+ );
184
+
185
+ const isCostCenterCriteria =
186
+ criteria === COST_CENTER_CRITERIA ||
187
+ criteria.includes(
188
+ 'customData.customFields.fields[name="Cost Center"].value ='
189
+ );
190
+
176
191
  const renderCriteria = (criteria: string) => {
177
192
  if (isBudgetCriteria) {
178
193
  return (
@@ -186,6 +201,34 @@ export const BasicBuyingPolicyDrawer = ({
186
201
  );
187
202
  }
188
203
 
204
+ if (isPONumberCriteria) {
205
+ return (
206
+ <CustomFieldCriteriaSelector
207
+ key={"PO Number"}
208
+ fieldName="PO Number"
209
+ currentCriteria={criteria}
210
+ onCriteriaChange={(newCriteria) =>
211
+ updateField("criteria", newCriteria)
212
+ }
213
+ onEditablePartClick={() => setCriteriaFocused(true)}
214
+ />
215
+ );
216
+ }
217
+
218
+ if (isCostCenterCriteria) {
219
+ return (
220
+ <CustomFieldCriteriaSelector
221
+ key={"Cost Center"}
222
+ fieldName="Cost Center"
223
+ currentCriteria={criteria}
224
+ onCriteriaChange={(newCriteria) =>
225
+ updateField("criteria", newCriteria)
226
+ }
227
+ onEditablePartClick={() => setCriteriaFocused(true)}
228
+ />
229
+ );
230
+ }
231
+
189
232
  return getHighlightedText(criteria);
190
233
  };
191
234
 
@@ -2,6 +2,7 @@
2
2
  @import '../../../shared/components/OrgUnitInputSearch/org-unit-input-search.scss';
3
3
  @import '../../../shared/components/CustomDropdown/custom-dropdown.scss';
4
4
  @import '../BudgetCriteriaSelector/budget-criteria-selector.scss';
5
+ @import '../CustomFieldCriteriaSelector/custom-field-criteria-selector.scss';
5
6
 
6
7
  [data-fs-bp-basic-buying-policy-drawer] {
7
8
  @import '../../../shared/components/InputText/input-text.scss';
@@ -108,7 +109,6 @@
108
109
  text-wrap: nowrap;
109
110
  text-overflow: ellipsis;
110
111
  white-space: nowrap;
111
- overflow: hidden;
112
112
  max-width: 840px;
113
113
  cursor: pointer;
114
114
 
@@ -0,0 +1,154 @@
1
+ import { useEffect, useState } from "react";
2
+
3
+ import { CustomDropdown } from "../../../shared/components/CustomDropdown/CustomDropdown";
4
+ import { useBuyerPortal } from "../../../shared/hooks";
5
+ import { useCustomFieldValues } from "../../../shared/hooks/custom-field";
6
+
7
+ export type CustomFieldCriteriaSelectorProps = {
8
+ fieldName: "PO Number" | "Cost Center";
9
+ onCriteriaChange: (criteria: string) => void;
10
+ currentCriteria: string;
11
+ onEditablePartClick?: () => void;
12
+ };
13
+
14
+ export const CustomFieldCriteriaSelector = ({
15
+ fieldName,
16
+ onCriteriaChange,
17
+ currentCriteria,
18
+ onEditablePartClick,
19
+ }: CustomFieldCriteriaSelectorProps) => {
20
+ const {
21
+ currentOrgUnit: orgUnit,
22
+ currentContract: contract,
23
+ clientContext,
24
+ } = useBuyerPortal();
25
+
26
+ const cookie = clientContext?.cookie ?? "";
27
+
28
+ const [isDropdownOpen, setIsDropdownOpen] = useState(false);
29
+
30
+ const { data, isLoading, refetch } = useCustomFieldValues({
31
+ data: {
32
+ contractId: contract?.id || "",
33
+ customField: fieldName,
34
+ unitId: orgUnit?.id || "",
35
+ cookie,
36
+ params: {
37
+ value: "",
38
+ page: 1,
39
+ filterByUnit: true,
40
+ },
41
+ },
42
+ options: { lazy: true },
43
+ });
44
+
45
+ const extractValueFromCriteria = (criteria: string): string => {
46
+ const match = criteria.match(/value\s*=\s*"([^"]+)"/);
47
+ return match
48
+ ? match[1]
49
+ : `${fieldName.toLowerCase().replace(/\s+/g, "-")}-001`;
50
+ };
51
+
52
+ const [currentFieldValue, setCurrentFieldValue] = useState<string>(
53
+ extractValueFromCriteria(currentCriteria)
54
+ );
55
+
56
+ useEffect(() => {
57
+ setIsDropdownOpen(false);
58
+ const extractedValue = extractValueFromCriteria(currentCriteria);
59
+ setCurrentFieldValue(extractedValue);
60
+ }, [currentCriteria, fieldName]);
61
+
62
+ const generateCriteriaString = (value: string) => {
63
+ return `customData.customFields.fields[name="${fieldName}"].value = "${value}"`;
64
+ };
65
+
66
+ const handleFieldSelect = (selectedIdOrValue: string) => {
67
+ if (selectedIdOrValue === "loading") return;
68
+
69
+ const items = data?.data || [];
70
+
71
+ const selectedItem = items.find(
72
+ (item) =>
73
+ item.value === selectedIdOrValue || item.id === selectedIdOrValue
74
+ );
75
+
76
+ if (!selectedItem) return;
77
+
78
+ const valueToUse = selectedItem.value || selectedIdOrValue;
79
+ setCurrentFieldValue(valueToUse);
80
+ onCriteriaChange(generateCriteriaString(valueToUse));
81
+ setIsDropdownOpen(false);
82
+ };
83
+
84
+ const handleFieldValueClick = (e: React.MouseEvent) => {
85
+ e.stopPropagation();
86
+ setIsDropdownOpen(true);
87
+
88
+ if ((!data?.data || data.data.length === 0) && !isLoading) {
89
+ refetch();
90
+ }
91
+ };
92
+
93
+ const handleEditablePartClick = (e: React.MouseEvent) => {
94
+ e.stopPropagation();
95
+ onEditablePartClick?.();
96
+ };
97
+
98
+ const loadingOptions = [
99
+ {
100
+ label: `Loading ${fieldName.toLowerCase()}...`,
101
+ value: "loading",
102
+ },
103
+ ];
104
+
105
+ const fieldOptions = (data?.data || []).map((item) => ({
106
+ label: item.name || item.value || `${fieldName}-${item.id?.slice(0, 8)}`,
107
+ value: item.value || item.id,
108
+ }));
109
+
110
+ const options =
111
+ isLoading || !data?.data || data.data.length === 0
112
+ ? loadingOptions
113
+ : fieldOptions;
114
+
115
+ const triggerLabel = isLoading ? "Loading..." : currentFieldValue;
116
+
117
+ return (
118
+ <div data-fs-bp-custom-field-criteria-selector data-field-name={fieldName}>
119
+ <div data-fs-bp-custom-field-criteria-string>
120
+ <span onClick={handleEditablePartClick} data-fs-bp-editable-part>
121
+ customData.customFields.fields[name="{fieldName}"].value = "
122
+ </span>
123
+
124
+ <div
125
+ onClick={handleFieldValueClick}
126
+ style={{ display: "inline-block" }}
127
+ >
128
+ <CustomDropdown
129
+ options={options}
130
+ onSelect={handleFieldSelect}
131
+ triggerLabel={triggerLabel}
132
+ isOpen={isDropdownOpen}
133
+ highlightText={(text) => (
134
+ <div data-fs-bp-dropdown-option>
135
+ <div data-fs-bp-dropdown-option-label>{text}</div>
136
+ {!isLoading && data?.data && (
137
+ <div data-fs-bp-dropdown-option-uuid>
138
+ {data.data.find(
139
+ (item) => (item.name || item.value) === text
140
+ )?.value || text}
141
+ </div>
142
+ )}
143
+ </div>
144
+ )}
145
+ />
146
+ </div>
147
+
148
+ <span onClick={handleEditablePartClick} data-fs-bp-editable-part>
149
+ "
150
+ </span>
151
+ </div>
152
+ </div>
153
+ );
154
+ };
@@ -0,0 +1,139 @@
1
+ [data-fs-bp-custom-field-criteria-selector] {
2
+ position: absolute;
3
+ top: 0;
4
+ left: 0;
5
+ width: 100%;
6
+ height: 100%;
7
+ margin: 0;
8
+ background-color: var(--fs-bp-color-neutral-1);
9
+ border: var(--fs-border-width) solid var(--fs-bp-color-neutral-4);
10
+ border-radius: calc(var(--fs-border-radius) * 2);
11
+ display: flex;
12
+ flex-direction: column;
13
+ align-items: flex-start;
14
+
15
+ [data-fs-bp-custom-field-criteria-string] {
16
+ font-family: "Roboto", monospace;
17
+ font-size: var(--fs-text-size-1);
18
+ color: var(--fs-bp-color-neutral-7);
19
+ display: flex;
20
+ align-items: center;
21
+ padding: var(--fs-spacing-3) var(--fs-spacing-3);
22
+ gap: 0;
23
+ flex-wrap: wrap;
24
+ width: 100%;
25
+
26
+ > span {
27
+ white-space: nowrap;
28
+ }
29
+
30
+ [data-fs-bp-editable-part] {
31
+ cursor: pointer;
32
+ transition: background-color 0.2s ease;
33
+ padding: 0.125rem 0 0.25rem;
34
+ border-radius: calc(var(--fs-border-radius) * 0.5);
35
+
36
+ &:hover {
37
+ background-color: var(--fs-bp-color-neutral-1);
38
+ color: var(--fs-bp-color-neutral-8);
39
+ }
40
+ }
41
+
42
+ [data-fs-bp-custom-dropdown] {
43
+ display: inline-flex;
44
+
45
+ [data-fs-bp-custom-dropdown-trigger] {
46
+ color: var(--fs-color-highlight);
47
+ border: none;
48
+ min-width: 0;
49
+ border-radius: calc(var(--fs-border-radius) * 1.5);
50
+ font-family: "Roboto", monospace;
51
+ font-size: calc(var(--fs-text-size-1) * 0.9);
52
+ padding: 0;
53
+ cursor: pointer;
54
+ transition: background-color 0.2s ease;
55
+ background-color: transparent;
56
+
57
+ &:hover {
58
+ background-color: var(--fs-bp-color-neutral-1);
59
+ }
60
+ }
61
+
62
+ [data-fs-bp-custom-dropdown-menu] {
63
+ position: absolute;
64
+ top: 100%;
65
+ left: 0;
66
+ margin-top: var(--fs-spacing-1);
67
+ padding: var(--fs-spacing-1) 0;
68
+ background-color: var(--fs-bp-color-neutral-0);
69
+ box-shadow: 0rem 0.5rem 0.625rem 0rem rgba(0, 0, 0, 0.0784313725);
70
+ max-height: 15rem;
71
+ min-width: 20rem;
72
+ overflow-y: auto;
73
+ z-index: 1;
74
+ border-radius: calc(var(--fs-border-radius) * 2);
75
+
76
+ &::-webkit-scrollbar {
77
+ width: 0.75rem;
78
+ height: 0.75rem;
79
+ background-color: var(--fs-bp-color-neutral-0);
80
+ }
81
+
82
+ &::-webkit-scrollbar-thumb {
83
+ background-color: #adadad;
84
+ border: 0.25rem solid transparent;
85
+ border-radius: 0.375rem;
86
+ background-clip: content-box;
87
+ }
88
+
89
+ [data-fs-bp-custom-dropdown-item] {
90
+ padding: var(--fs-spacing-2) var(--fs-spacing-3);
91
+ cursor: pointer;
92
+ border-bottom: var(--fs-border-width) solid #f0f0f0;
93
+
94
+ &:last-child {
95
+ border-bottom: none;
96
+ }
97
+
98
+ &:hover,
99
+ &[data-focused="true"] {
100
+ background-color: var(--fs-bp-color-neutral-2);
101
+ }
102
+
103
+ [data-fs-bp-dropdown-option] {
104
+ font-family: Inter;
105
+
106
+ [data-fs-bp-dropdown-option-label] {
107
+ font-weight: var(--fs-text-weight-semibold);
108
+ color: var(--fs-bp-color-neutral-8);
109
+ font-size: var(--fs-text-size-1);
110
+ margin-bottom: var(--fs-spacing-0);
111
+ }
112
+
113
+ [data-fs-bp-dropdown-option-uuid] {
114
+ font-size: var(--fs-text-size-0);
115
+ color: var(--fs-bp-color-neutral-7);
116
+ }
117
+ }
118
+ }
119
+ }
120
+ }
121
+ }
122
+
123
+ [data-fs-bp-custom-value-indicator] {
124
+ font-size: calc(var(--fs-text-size-0) * 0.85);
125
+ color: #666666;
126
+ font-style: italic;
127
+ padding: 0 var(--fs-spacing-3) calc(var(--fs-spacing-1) * 0.5);
128
+ border-top: 0.0625rem solid var(--fs-bp-color-neutral-3);
129
+ margin-top: auto;
130
+ width: 100%;
131
+ background-color: #fafafa;
132
+ border-radius: 0 0 calc(var(--fs-border-radius) * 2)
133
+ calc(var(--fs-border-radius) * 2);
134
+ }
135
+
136
+ [data-fs-bp-custom-field-value] {
137
+ color: var(--fs-color-highlight);
138
+ }
139
+ }
@@ -1,6 +1,8 @@
1
1
  export { buyingPolicyDefault } from "./buyingPolicyDefault";
2
2
  export {
3
3
  BUDGET_CRITERIA,
4
+ PO_NUMBER_CRITERIA,
5
+ COST_CENTER_CRITERIA,
4
6
  orderFieldsCriteriaOptions,
5
7
  } from "./orderFieldsCriteriaOptions";
6
8
  export { spendingLimitsCriteriaOptions } from "./spendingLimitsCriteriaOptions";
@@ -1,4 +1,8 @@
1
1
  export const BUDGET_CRITERIA = '$exists(budgetData.budgets[id="${budget-id}"])';
2
+ export const PO_NUMBER_CRITERIA =
3
+ 'customData.customFields.fields[name="PO Number"].value = "${po-number}"';
4
+ export const COST_CENTER_CRITERIA =
5
+ 'customData.customFields.fields[name="Cost Center"].value = "${cost-center}"';
2
6
 
3
7
  export const orderFieldsCriteriaOptions = [
4
8
  {
@@ -25,8 +29,7 @@ export const orderFieldsCriteriaOptions = [
25
29
  },
26
30
  {
27
31
  label: "If the order contains the PO numbers X, Y, Z",
28
- criteria:
29
- 'customData.customFields.fields[name="PO Number"].value = "1112223334444" or customData.customFields.fields[name="PO Number"].value = "1234567890"',
32
+ criteria: PO_NUMBER_CRITERIA,
30
33
  },
31
34
  {
32
35
  label: "If the order contains a cost center",
@@ -35,8 +38,7 @@ export const orderFieldsCriteriaOptions = [
35
38
  },
36
39
  {
37
40
  label: "If the order contains the cost centers X, Y, Z",
38
- criteria:
39
- 'customData.customFields.fields[name="Cost Center"].value = "CC1" or customData.customFields.fields[name="Cost Center"].value = "CC2"',
41
+ criteria: COST_CENTER_CRITERIA,
40
42
  },
41
43
  {
42
44
  label: "If all order items are negotiated items",
@@ -22,4 +22,4 @@ export const SCOPE_KEYS = {
22
22
  CREDIT_CARDS: "creditCards",
23
23
  } as const;
24
24
 
25
- export const CURRENT_VERSION = "1.3.48";
25
+ export const CURRENT_VERSION = "1.3.49";