@vtex/faststore-plugin-buyer-portal 1.3.48 → 1.3.50
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 +14 -1
- package/package.json +1 -1
- package/src/features/budgets/components/BudgetEditNotificationDrawer/BudgetEditNotificationDrawer.tsx +1 -0
- package/src/features/budgets/components/BudgetNotificationForm/BudgetNotificationForm.tsx +64 -26
- package/src/features/budgets/components/BudgetNotificationForm/budget-notification-form.scss +26 -15
- package/src/features/budgets/components/BudgetUsersTable/BudgetUsersTable.tsx +2 -13
- package/src/features/budgets/components/CreateBudgetDrawer/create-budget-drawer.scss +0 -9
- package/src/features/budgets/components/EditBudgetDrawer/edit-budget-drawer.scss +0 -6
- package/src/features/buying-policies/components/BasicBuyingPolicyDrawer/BasicBuyingPolicyDrawer.tsx +43 -0
- package/src/features/buying-policies/components/BasicBuyingPolicyDrawer/basic-buying-policy-drawer.scss +1 -1
- package/src/features/buying-policies/components/CustomFieldCriteriaSelector/CustomFieldCriteriaSelector.tsx +154 -0
- package/src/features/buying-policies/components/CustomFieldCriteriaSelector/custom-field-criteria-selector.scss +139 -0
- package/src/features/buying-policies/utils/index.ts +2 -0
- package/src/features/buying-policies/utils/orderFieldsCriteriaOptions.ts +6 -4
- package/src/features/shared/components/AutocompleteDropdown/AutocompleteDropdown.tsx +23 -6
- package/src/features/shared/components/AutocompleteDropdown/autocomplete-dropdown.scss +21 -7
- package/src/features/shared/components/BuyerPortalProvider/BuyerPortalProvider.tsx +1 -0
- package/src/features/shared/components/QuantitySelectorWithPercentage/QuantitySelectorWithPercentage.tsx +6 -3
- package/src/features/shared/components/Table/TableRow/table-row.scss +4 -0
- package/src/features/shared/utils/constants.ts +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,17 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [1.3.50] - 2025-12-19
|
|
11
|
+
|
|
12
|
+
### Changed
|
|
13
|
+
- Introduces several improvements and refactorings to the budget notification drawer, focusing on user experience.
|
|
14
|
+
|
|
15
|
+
## [1.3.49] - 2025-12-19
|
|
16
|
+
|
|
17
|
+
### Added
|
|
18
|
+
|
|
19
|
+
- Add component of Criteria Selection of Custom Fields on Buying Policies
|
|
20
|
+
|
|
10
21
|
## [1.3.48] - 2025-12-19
|
|
11
22
|
|
|
12
23
|
- Adjustment from merge to Collections to Products Assortment
|
|
@@ -411,7 +422,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
411
422
|
- Add CHANGELOG file
|
|
412
423
|
- Add README file
|
|
413
424
|
|
|
414
|
-
[unreleased]: https://github.com/vtex/faststore-plugin-buyer-portal/compare/v1.3.
|
|
425
|
+
[unreleased]: https://github.com/vtex/faststore-plugin-buyer-portal/compare/v1.3.50...HEAD
|
|
415
426
|
[1.2.3]: https://github.com/vtex/faststore-plugin-buyer-portal/compare/v1.2.2...1.2.3
|
|
416
427
|
[1.2.3]: https://github.com/vtex/faststore-plugin-buyer-portal/releases/tag/1.2.3
|
|
417
428
|
[1.2.4]: https://github.com/vtex/faststore-plugin-buyer-portal/releases/tag/1.2.4
|
|
@@ -461,6 +472,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
461
472
|
[1.3.36]: https://github.com/vtex/faststore-plugin-buyer-portal/compare/v1.3.35...v1.3.36
|
|
462
473
|
[1.3.35]: https://github.com/vtex/faststore-plugin-buyer-portal/releases/tag/1.3.35
|
|
463
474
|
|
|
475
|
+
[1.3.50]: https://github.com/vtex/faststore-plugin-buyer-portal/compare/v1.3.49...v1.3.50
|
|
476
|
+
[1.3.49]: https://github.com/vtex/faststore-plugin-buyer-portal/compare/v1.3.48...v1.3.49
|
|
464
477
|
[1.3.48]: https://github.com/vtex/faststore-plugin-buyer-portal/compare/v1.3.47...v1.3.48
|
|
465
478
|
[1.3.47]: https://github.com/vtex/faststore-plugin-buyer-portal/compare/v1.3.46...v1.3.47
|
|
466
479
|
[1.3.46]: https://github.com/vtex/faststore-plugin-buyer-portal/compare/v1.3.45...v1.3.46
|
package/package.json
CHANGED
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
QuantitySelectorWithPercentage,
|
|
8
8
|
Table,
|
|
9
9
|
} from "../../../shared/components";
|
|
10
|
+
import { useBuyerPortal } from "../../../shared/hooks";
|
|
10
11
|
import { CurrencyType, LocaleType } from "../../../shared/types";
|
|
11
12
|
import { useDebouncedSearchBudgetNotification } from "../../hooks/useDebouncedSearchBudgetNotification";
|
|
12
13
|
import BudgetUsersTable from "../BudgetUsersTable/BudgetUsersTable";
|
|
@@ -36,6 +37,8 @@ type BudgetNotificationFormProps = {
|
|
|
36
37
|
|
|
37
38
|
type UserOption = { id: string; name: string; email?: string };
|
|
38
39
|
|
|
40
|
+
const MAX_THRESHOLDS = 5;
|
|
41
|
+
|
|
39
42
|
export const BudgetNotificationForm = ({
|
|
40
43
|
budget,
|
|
41
44
|
contractId,
|
|
@@ -47,6 +50,8 @@ export const BudgetNotificationForm = ({
|
|
|
47
50
|
locale = "en-US",
|
|
48
51
|
showUsersError = false,
|
|
49
52
|
}: BudgetNotificationFormProps) => {
|
|
53
|
+
const { currentUser } = useBuyerPortal();
|
|
54
|
+
|
|
50
55
|
const [enabled, setEnabled] = useState<boolean>(
|
|
51
56
|
Boolean(budget?.notifications?.hasNotification)
|
|
52
57
|
);
|
|
@@ -124,7 +129,7 @@ export const BudgetNotificationForm = ({
|
|
|
124
129
|
const addThreshold = () => {
|
|
125
130
|
if (readonly || parsedTotal <= 0) return;
|
|
126
131
|
setThresholds((prev) =>
|
|
127
|
-
prev.length >=
|
|
132
|
+
prev.length >= MAX_THRESHOLDS ? prev : [...prev, { value: 50 }]
|
|
128
133
|
);
|
|
129
134
|
};
|
|
130
135
|
|
|
@@ -154,8 +159,20 @@ export const BudgetNotificationForm = ({
|
|
|
154
159
|
name: String(u.name ?? "Usuário"),
|
|
155
160
|
email: u.email,
|
|
156
161
|
}));
|
|
157
|
-
|
|
158
|
-
|
|
162
|
+
|
|
163
|
+
setOptions(
|
|
164
|
+
currentUser?.id
|
|
165
|
+
? [
|
|
166
|
+
{
|
|
167
|
+
id: String(currentUser.id),
|
|
168
|
+
email: currentUser?.email ?? "",
|
|
169
|
+
name: currentUser?.name ? `${currentUser.name} (You)` : "You",
|
|
170
|
+
},
|
|
171
|
+
...mapped,
|
|
172
|
+
]
|
|
173
|
+
: mapped
|
|
174
|
+
);
|
|
175
|
+
}, [usersResponse, currentUser]);
|
|
159
176
|
|
|
160
177
|
const addUserIfNotExists = (opt: UserOption) => {
|
|
161
178
|
const exists = selectedUsers.some((u) => u.userId === opt.id);
|
|
@@ -178,8 +195,21 @@ export const BudgetNotificationForm = ({
|
|
|
178
195
|
setSelectedUsers(next);
|
|
179
196
|
};
|
|
180
197
|
|
|
198
|
+
const handleThresholdsChange = (value: number, idx: number) => {
|
|
199
|
+
setThresholds((prev) =>
|
|
200
|
+
prev.map((th, i) =>
|
|
201
|
+
i === idx
|
|
202
|
+
? {
|
|
203
|
+
value: Math.min(100, Math.max(1, value)),
|
|
204
|
+
}
|
|
205
|
+
: th
|
|
206
|
+
)
|
|
207
|
+
);
|
|
208
|
+
};
|
|
209
|
+
|
|
181
210
|
const sizeProps = { width: 20, height: 20 };
|
|
182
211
|
const disableRows = readonly || !enabled;
|
|
212
|
+
const isLoading = isLoadingUsers || isDebouncing;
|
|
183
213
|
|
|
184
214
|
return (
|
|
185
215
|
<div data-fs-bp-budget-notification>
|
|
@@ -200,8 +230,8 @@ export const BudgetNotificationForm = ({
|
|
|
200
230
|
|
|
201
231
|
<div data-fs-bp-budget-notification-section-label>
|
|
202
232
|
<span>
|
|
203
|
-
Set up to
|
|
204
|
-
specific percentages
|
|
233
|
+
Set up to {MAX_THRESHOLDS} thresholds and notify users when the total
|
|
234
|
+
amount reaches specific percentages
|
|
205
235
|
</span>
|
|
206
236
|
</div>
|
|
207
237
|
|
|
@@ -240,37 +270,35 @@ export const BudgetNotificationForm = ({
|
|
|
240
270
|
title={
|
|
241
271
|
<div data-fs-bp-budget-notification-stepper>
|
|
242
272
|
<QuantitySelectorWithPercentage
|
|
243
|
-
min={
|
|
273
|
+
min={5}
|
|
244
274
|
max={100}
|
|
245
275
|
initial={t.value}
|
|
246
276
|
formatAsPercent
|
|
247
277
|
disabled={disableRows || parsedTotal <= 0}
|
|
248
|
-
onChange={(
|
|
249
|
-
|
|
250
|
-
prev.map((th, i) =>
|
|
251
|
-
i === idx
|
|
252
|
-
? {
|
|
253
|
-
value: Math.min(
|
|
254
|
-
100,
|
|
255
|
-
Math.max(1, val)
|
|
256
|
-
),
|
|
257
|
-
}
|
|
258
|
-
: th
|
|
259
|
-
)
|
|
260
|
-
)
|
|
278
|
+
onChange={(value) =>
|
|
279
|
+
handleThresholdsChange(value, idx)
|
|
261
280
|
}
|
|
281
|
+
increaseQuantityBy={5}
|
|
282
|
+
decreaseQuantityBy={5}
|
|
262
283
|
/>
|
|
263
284
|
</div>
|
|
264
285
|
}
|
|
265
286
|
enabled={!disableRows}
|
|
266
287
|
actionIcons={
|
|
267
|
-
<
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
data-fs-bp-budget-notification-remove
|
|
288
|
+
<button
|
|
289
|
+
data-fs-bp-budget-notification-remove-button
|
|
290
|
+
type="button"
|
|
271
291
|
onClick={() => removeThreshold(idx)}
|
|
272
292
|
aria-label="Remove threshold"
|
|
273
|
-
|
|
293
|
+
title="Remove threshold"
|
|
294
|
+
disabled={disableRows}
|
|
295
|
+
>
|
|
296
|
+
<Icon
|
|
297
|
+
name="MinusCircle"
|
|
298
|
+
{...sizeProps}
|
|
299
|
+
data-fs-bp-budget-notification-remove
|
|
300
|
+
/>
|
|
301
|
+
</button>
|
|
274
302
|
}
|
|
275
303
|
>
|
|
276
304
|
<Table.Cell>
|
|
@@ -294,7 +322,11 @@ export const BudgetNotificationForm = ({
|
|
|
294
322
|
<button
|
|
295
323
|
type="button"
|
|
296
324
|
onClick={addThreshold}
|
|
297
|
-
disabled={
|
|
325
|
+
disabled={
|
|
326
|
+
disableRows ||
|
|
327
|
+
parsedTotal <= 0 ||
|
|
328
|
+
thresholds.length >= MAX_THRESHOLDS
|
|
329
|
+
}
|
|
298
330
|
>
|
|
299
331
|
Add threshold
|
|
300
332
|
</button>
|
|
@@ -315,11 +347,17 @@ export const BudgetNotificationForm = ({
|
|
|
315
347
|
shouldOpenOnFocus={false}
|
|
316
348
|
autoComplete="off"
|
|
317
349
|
shouldShowArrowDown={false}
|
|
350
|
+
isLoading={isLoading}
|
|
351
|
+
disabled={isLoading}
|
|
318
352
|
onChange={(e: ChangeEvent<HTMLInputElement>) => {
|
|
319
353
|
setInputValue(e.target.value);
|
|
320
354
|
}}
|
|
321
355
|
options={
|
|
322
|
-
|
|
356
|
+
isLoading
|
|
357
|
+
? [loadingObject]
|
|
358
|
+
: options.filter(
|
|
359
|
+
(opt) => !selectedUsers.some((u) => u.userId === opt.id)
|
|
360
|
+
)
|
|
323
361
|
}
|
|
324
362
|
hasError={showUsersError && enabled && selectedUsers.length === 0}
|
|
325
363
|
helperLabel={
|
package/src/features/budgets/components/BudgetNotificationForm/budget-notification-form.scss
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
[data-fs-bp-budget-notification] {
|
|
2
|
-
@import
|
|
3
|
-
@import
|
|
4
|
-
@import
|
|
5
|
-
@import
|
|
6
|
-
@import
|
|
7
|
-
@import
|
|
8
|
-
@import
|
|
2
|
+
@import '@faststore/ui/src/components/molecules/Toggle/styles.scss';
|
|
3
|
+
@import '@faststore/ui/src/components/molecules/Tooltip/styles.scss';
|
|
4
|
+
@import '@faststore/ui/src/components/molecules/QuantitySelector/styles.scss';
|
|
5
|
+
@import '../../../shared/components/InputText/input-text.scss';
|
|
6
|
+
@import '../../../shared/components/Table/table.scss';
|
|
7
|
+
@import '../../../shared/components/ErrorMessage/error-message.scss';
|
|
8
|
+
@import '../BudgetUsersTable/budget-users-table.scss';
|
|
9
9
|
|
|
10
10
|
padding-bottom: var(--fs-spacing-7);
|
|
11
11
|
|
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
[data-fs-bp-budget-notification-section-label] {
|
|
30
30
|
font-weight: var(--fs-text-weight-regular);
|
|
31
31
|
font-size: var(--fs-text-size-1);
|
|
32
|
-
color:
|
|
32
|
+
color: var(--fs-bp-color-neutral-7);
|
|
33
33
|
line-height: calc(var(--fs-spacing-3) + var(--fs-spacing-0));
|
|
34
34
|
}
|
|
35
35
|
|
|
@@ -38,7 +38,7 @@
|
|
|
38
38
|
flex-direction: column;
|
|
39
39
|
width: 100%;
|
|
40
40
|
|
|
41
|
-
@include media(
|
|
41
|
+
@include media('>=notebook') {
|
|
42
42
|
align-items: center;
|
|
43
43
|
flex-direction: row;
|
|
44
44
|
gap: var(--fs-spacing-3);
|
|
@@ -62,9 +62,9 @@
|
|
|
62
62
|
}
|
|
63
63
|
}
|
|
64
64
|
|
|
65
|
-
[data-fs-bp-autocomplete-dropdown]{
|
|
66
|
-
[data-fs-bp-autocomplete-dropdown-menu]{
|
|
67
|
-
z-index:2;
|
|
65
|
+
[data-fs-bp-autocomplete-dropdown] {
|
|
66
|
+
[data-fs-bp-autocomplete-dropdown-menu] {
|
|
67
|
+
z-index: 2;
|
|
68
68
|
}
|
|
69
69
|
}
|
|
70
70
|
|
|
@@ -99,7 +99,7 @@
|
|
|
99
99
|
text-align: start;
|
|
100
100
|
font-weight: var(--fs-text-weight-medium);
|
|
101
101
|
font-size: var(--fs-text-size-1);
|
|
102
|
-
color:
|
|
102
|
+
color: var(--fs-bp-color-neutral-7);
|
|
103
103
|
padding: var(--fs-spacing-2) 0;
|
|
104
104
|
}
|
|
105
105
|
|
|
@@ -165,7 +165,7 @@
|
|
|
165
165
|
}
|
|
166
166
|
}
|
|
167
167
|
|
|
168
|
-
div[data-fs-quantity-selector=
|
|
168
|
+
div[data-fs-quantity-selector='disabled'] {
|
|
169
169
|
[data-fs-input] {
|
|
170
170
|
color: #c3c3c3;
|
|
171
171
|
}
|
|
@@ -185,18 +185,23 @@
|
|
|
185
185
|
[data-fs-bp-budget-notification-money] {
|
|
186
186
|
font-weight: var(--fs-text-weight-regular);
|
|
187
187
|
font-size: var(--fs-text-size-1);
|
|
188
|
-
color:
|
|
188
|
+
color: var(--fs-bp-color-neutral-7);
|
|
189
189
|
}
|
|
190
190
|
|
|
191
191
|
[data-fs-bp-budget-notification-remove] {
|
|
192
192
|
color: #000000;
|
|
193
193
|
stroke-width: 1.3rem;
|
|
194
|
+
cursor: pointer;
|
|
194
195
|
&[disabled] {
|
|
195
196
|
color: #9ca3af; /* gray when disabled */
|
|
196
197
|
cursor: not-allowed;
|
|
197
198
|
}
|
|
198
199
|
}
|
|
199
200
|
|
|
201
|
+
[data-fs-bp-budget-notification-remove-button] {
|
|
202
|
+
cursor: pointer;
|
|
203
|
+
}
|
|
204
|
+
|
|
200
205
|
[data-fs-bp-budget-notification-add] {
|
|
201
206
|
margin-top: var(--fs-spacing-3);
|
|
202
207
|
display: flex;
|
|
@@ -204,6 +209,7 @@
|
|
|
204
209
|
gap: var(--fs-spacing-2);
|
|
205
210
|
|
|
206
211
|
button {
|
|
212
|
+
cursor: pointer;
|
|
207
213
|
height: var(--fs-spacing-6);
|
|
208
214
|
width: 8.4375rem;
|
|
209
215
|
border-radius: var(--fs-border-radius-pill);
|
|
@@ -214,6 +220,11 @@
|
|
|
214
220
|
color: #0068d7;
|
|
215
221
|
font-size: var(--fs-text-size-1);
|
|
216
222
|
font-weight: var(--fs-text-weight-semibold);
|
|
223
|
+
|
|
224
|
+
&:disabled {
|
|
225
|
+
cursor: not-allowed;
|
|
226
|
+
color: #9ca3af; /* gray when disabled */
|
|
227
|
+
}
|
|
217
228
|
}
|
|
218
229
|
}
|
|
219
230
|
}
|
|
@@ -4,7 +4,6 @@ import { Icon, Tooltip } from "@faststore/ui";
|
|
|
4
4
|
|
|
5
5
|
import { Table } from "../../../shared/components";
|
|
6
6
|
import { getTableColumns } from "../../../shared/components/Table/utils/tableColumns";
|
|
7
|
-
import { useBuyerPortal } from "../../../shared/hooks";
|
|
8
7
|
|
|
9
8
|
type UserRow = {
|
|
10
9
|
userId: string;
|
|
@@ -28,8 +27,6 @@ function BudgetUsersTableBase({
|
|
|
28
27
|
className,
|
|
29
28
|
disabled = false,
|
|
30
29
|
}: BudgetUsersTableProps) {
|
|
31
|
-
const { clientContext } = useBuyerPortal();
|
|
32
|
-
const currClientId = clientContext.userId;
|
|
33
30
|
const columns = getTableColumns({
|
|
34
31
|
actionsLength: onRemove ? 1 : 0,
|
|
35
32
|
nameColumnLabel: "User name",
|
|
@@ -64,16 +61,7 @@ function BudgetUsersTableBase({
|
|
|
64
61
|
users.map((u) => (
|
|
65
62
|
<Table.Row
|
|
66
63
|
key={u.userId}
|
|
67
|
-
title={
|
|
68
|
-
currClientId === u.userId ? (
|
|
69
|
-
<span>
|
|
70
|
-
{u.userName}
|
|
71
|
-
<span data-fs-bp-users-you> (you)</span>
|
|
72
|
-
</span>
|
|
73
|
-
) : (
|
|
74
|
-
u.userName
|
|
75
|
-
)
|
|
76
|
-
}
|
|
64
|
+
title={u.userName}
|
|
77
65
|
enabled={!disabled}
|
|
78
66
|
actionIcons={(() => {
|
|
79
67
|
const rowDisabled =
|
|
@@ -85,6 +73,7 @@ function BudgetUsersTableBase({
|
|
|
85
73
|
aria-label={`Remove ${u.userName}`}
|
|
86
74
|
disabled={rowDisabled}
|
|
87
75
|
onClick={() => onRemove?.(u.userId)}
|
|
76
|
+
title={rowDisabled ? undefined : `Remove ${u.userName}`}
|
|
88
77
|
>
|
|
89
78
|
<Icon name="MinusCircle" width={20} height={20} />
|
|
90
79
|
</button>
|
|
@@ -21,15 +21,6 @@
|
|
|
21
21
|
width: 100%;
|
|
22
22
|
max-width: none;
|
|
23
23
|
|
|
24
|
-
@include media(">=tablet") {
|
|
25
|
-
max-width: 30rem;
|
|
26
|
-
min-width: 30rem;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
@include media(">=notebook") {
|
|
30
|
-
max-width: 40rem;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
24
|
[data-fs-divider] {
|
|
34
25
|
border: var(--fs-border-radius-small) solid #e0e0e0;
|
|
35
26
|
margin: var(--fs-spacing-4) 0rem;
|
package/src/features/buying-policies/components/BasicBuyingPolicyDrawer/BasicBuyingPolicyDrawer.tsx
CHANGED
|
@@ -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",
|
|
@@ -43,6 +43,7 @@ export type AutocompleteDropdownProps<T> = ComponentProps<typeof InputText> & {
|
|
|
43
43
|
renderOption?: (option: T, index: number) => ReactNode;
|
|
44
44
|
onConfirmKeyPress?: (option: T) => void;
|
|
45
45
|
hasError?: boolean;
|
|
46
|
+
isLoading?: boolean;
|
|
46
47
|
shouldOpenOnFocus?: boolean;
|
|
47
48
|
shouldShowArrowDown?: boolean;
|
|
48
49
|
shouldCloseOnSelect?: boolean;
|
|
@@ -57,6 +58,7 @@ export const AutocompleteDropdown = <T,>({
|
|
|
57
58
|
disabled,
|
|
58
59
|
onConfirmKeyPress,
|
|
59
60
|
hasError,
|
|
61
|
+
isLoading = false,
|
|
60
62
|
value,
|
|
61
63
|
helperLabel,
|
|
62
64
|
shouldOpenOnFocus = true,
|
|
@@ -138,6 +140,25 @@ export const AutocompleteDropdown = <T,>({
|
|
|
138
140
|
setIsOpened(false);
|
|
139
141
|
}, []);
|
|
140
142
|
|
|
143
|
+
const getIconElement = () => {
|
|
144
|
+
if (shouldShowArrowDown) {
|
|
145
|
+
return <Icon name="ArrowDropDown" width={20} height={20} />;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
if (isLoading) {
|
|
149
|
+
return (
|
|
150
|
+
<Icon
|
|
151
|
+
name="LoadingIndicator"
|
|
152
|
+
width={20}
|
|
153
|
+
height={20}
|
|
154
|
+
data-fs-icon-loading="true"
|
|
155
|
+
/>
|
|
156
|
+
);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
return undefined;
|
|
160
|
+
};
|
|
161
|
+
|
|
141
162
|
useEffect(() => {
|
|
142
163
|
const event = (e: MouseEvent) => {
|
|
143
164
|
const target = e.target as Node;
|
|
@@ -215,11 +236,7 @@ export const AutocompleteDropdown = <T,>({
|
|
|
215
236
|
<InputText
|
|
216
237
|
label={label}
|
|
217
238
|
onKeyDown={handleBackdropKeyDown}
|
|
218
|
-
icon={
|
|
219
|
-
shouldShowArrowDown ? (
|
|
220
|
-
<Icon name="ArrowDropDown" width={20} height={20} />
|
|
221
|
-
) : null
|
|
222
|
-
}
|
|
239
|
+
icon={getIconElement()}
|
|
223
240
|
onChange={onChange}
|
|
224
241
|
disabled={disabled}
|
|
225
242
|
hasError={hasError}
|
|
@@ -229,7 +246,7 @@ export const AutocompleteDropdown = <T,>({
|
|
|
229
246
|
{...props}
|
|
230
247
|
/>
|
|
231
248
|
|
|
232
|
-
{isOpened && options.length > 0 ? (
|
|
249
|
+
{!isLoading && isOpened && options.length > 0 ? (
|
|
233
250
|
<div
|
|
234
251
|
style={{ width: positionStyle.width }}
|
|
235
252
|
data-fs-bp-autocomplete-dropdown-menu={position}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
@import
|
|
1
|
+
@import '@faststore/ui/src/components/molecules/Dropdown/styles.scss';
|
|
2
2
|
|
|
3
3
|
[data-fs-bp-autocomplete-dropdown] {
|
|
4
4
|
position: relative;
|
|
5
|
-
|
|
6
|
-
&[data-fs-bp-autocomplete-dropdown-only-select=
|
|
5
|
+
|
|
6
|
+
&[data-fs-bp-autocomplete-dropdown-only-select='true'] {
|
|
7
7
|
[data-fs-bp-input-text-input] {
|
|
8
8
|
caret-color: transparent;
|
|
9
9
|
background-color: #ffffff;
|
|
@@ -11,6 +11,20 @@
|
|
|
11
11
|
}
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
+
[data-fs-icon-loading] {
|
|
15
|
+
&[data-fs-icon-loading='true'] {
|
|
16
|
+
@keyframes rotate {
|
|
17
|
+
from {
|
|
18
|
+
transform: rotate(0deg);
|
|
19
|
+
}
|
|
20
|
+
to {
|
|
21
|
+
transform: rotate(360deg);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
animation: rotate 2s linear infinite;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
14
28
|
[data-fs-bp-autocomplete-dropdown-menu] {
|
|
15
29
|
padding: var(--fs-spacing-1) 0;
|
|
16
30
|
background-color: #fff;
|
|
@@ -22,11 +36,11 @@
|
|
|
22
36
|
|
|
23
37
|
border-radius: calc(var(--fs-border-radius) * 2);
|
|
24
38
|
|
|
25
|
-
&[data-fs-bp-autocomplete-dropdown-menu=
|
|
39
|
+
&[data-fs-bp-autocomplete-dropdown-menu='bottom'] {
|
|
26
40
|
top: calc(100% + var(--fs-spacing-0));
|
|
27
41
|
}
|
|
28
42
|
|
|
29
|
-
&[data-fs-bp-autocomplete-dropdown-menu=
|
|
43
|
+
&[data-fs-bp-autocomplete-dropdown-menu='top'] {
|
|
30
44
|
bottom: calc(100% + var(--fs-spacing-0));
|
|
31
45
|
}
|
|
32
46
|
|
|
@@ -59,12 +73,12 @@
|
|
|
59
73
|
color: var(--fs-color-neutral-7);
|
|
60
74
|
}
|
|
61
75
|
|
|
62
|
-
&[data-fs-bp-autocomplete-dropdown-option-focused=
|
|
76
|
+
&[data-fs-bp-autocomplete-dropdown-option-focused='true'] {
|
|
63
77
|
background-color: #f5f5f5;
|
|
64
78
|
color: #3d3d3d;
|
|
65
79
|
}
|
|
66
80
|
|
|
67
|
-
&[data-fs-bp-autocomplete-dropdown-option-selected=
|
|
81
|
+
&[data-fs-bp-autocomplete-dropdown-option-selected='true'] {
|
|
68
82
|
color: #0366dd;
|
|
69
83
|
}
|
|
70
84
|
|
|
@@ -12,10 +12,11 @@ interface QuantitySelectorWithPercentageParams {
|
|
|
12
12
|
disabled?: boolean;
|
|
13
13
|
onChange?: (value: number) => void;
|
|
14
14
|
onValidateBlur?: (min: number, maxValue: number, quantity: number) => void;
|
|
15
|
-
|
|
16
15
|
formatAsPercent?: boolean;
|
|
17
16
|
allowPercentToggle?: boolean;
|
|
18
17
|
onFormatToggle?: (formatAsPercent: boolean) => void;
|
|
18
|
+
increaseQuantityBy?: number;
|
|
19
|
+
decreaseQuantityBy?: number;
|
|
19
20
|
}
|
|
20
21
|
|
|
21
22
|
const QuantitySelectorWithPercentage = ({
|
|
@@ -29,6 +30,8 @@ const QuantitySelectorWithPercentage = ({
|
|
|
29
30
|
onValidateBlur,
|
|
30
31
|
testId = "fs-quantity-selector",
|
|
31
32
|
formatAsPercent = true,
|
|
33
|
+
increaseQuantityBy = 1,
|
|
34
|
+
decreaseQuantityBy = 1,
|
|
32
35
|
...otherProps
|
|
33
36
|
}: QuantitySelectorWithPercentageParams) => {
|
|
34
37
|
const [quantity, setQuantity] = useState<number>(initial ?? min);
|
|
@@ -61,8 +64,8 @@ const QuantitySelectorWithPercentage = ({
|
|
|
61
64
|
: maxValue;
|
|
62
65
|
};
|
|
63
66
|
|
|
64
|
-
const increase = () => changeQuantity(
|
|
65
|
-
const decrease = () => changeQuantity(-
|
|
67
|
+
const increase = () => changeQuantity(increaseQuantityBy);
|
|
68
|
+
const decrease = () => changeQuantity(-decreaseQuantityBy);
|
|
66
69
|
|
|
67
70
|
const changeQuantity = (delta: number) => {
|
|
68
71
|
const next = validateQuantityBounds(quantity + delta);
|