@vtex/faststore-plugin-buyer-portal 1.3.17 → 1.3.19

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 (34) hide show
  1. package/CHANGELOG.md +24 -6
  2. package/package.json +1 -1
  3. package/src/features/budgets/components/BudgetDeleteDrawer/BudgetDeleteDrawer.tsx +1 -1
  4. package/src/features/budgets/components/BudgetEditNotificationDrawer/BudgetEditNotificationDrawer.tsx +139 -0
  5. package/src/features/budgets/components/BudgetEditNotificationDrawer/budget-edit-notification-drawer.scss +34 -0
  6. package/src/features/budgets/components/BudgetNotificationForm/BudgetNotificationForm.tsx +361 -0
  7. package/src/features/budgets/components/BudgetNotificationForm/budget-notification-form.scss +219 -0
  8. package/src/features/budgets/components/BudgetNotificationsInfo/BudgetNotificationsInfo.tsx +116 -0
  9. package/src/features/budgets/components/BudgetNotificationsInfo/budget-notifications-info.scss +97 -0
  10. package/src/features/budgets/components/BudgetUsersTable/BudgetUsersTable.tsx +118 -0
  11. package/src/features/budgets/components/BudgetUsersTable/budget-users-table.scss +65 -0
  12. package/src/features/budgets/components/BudgetsTable/BudgetsTable.tsx +10 -0
  13. package/src/features/budgets/components/CreateBudgetAllocationDrawer/CreateBudgetAllocationDrawer.tsx +1 -1
  14. package/src/features/budgets/components/CreateBudgetDrawer/CreateBudgetDrawer.tsx +86 -25
  15. package/src/features/budgets/components/CreateBudgetDrawer/create-budget-drawer.scss +6 -0
  16. package/src/features/budgets/components/DeleteBudgetAllocationDrawer/DeleteBudgetAllocationDrawer.tsx +1 -1
  17. package/src/features/budgets/components/EditBudgetDrawer/EditBudgetDrawer.tsx +40 -1
  18. package/src/features/budgets/components/EditBudgetDrawer/edit-budget-drawer.scss +5 -0
  19. package/src/features/budgets/hooks/useDebouncedSearchBudgetNotification.ts +37 -0
  20. package/src/features/budgets/hooks/useListUsers.ts +1 -1
  21. package/src/features/budgets/layouts/BudgetsDetailsLayout/BudgetsDetailsLayout.tsx +9 -1
  22. package/src/features/budgets/layouts/BudgetsDetailsLayout/budget-details-layout.scss +14 -1
  23. package/src/features/budgets/layouts/BudgetsLayout/BudgetsLayout.tsx +39 -0
  24. package/src/features/budgets/layouts/BudgetsLayout/budgets-layout.scss +1 -1
  25. package/src/features/budgets/types/index.ts +17 -0
  26. package/src/features/shared/components/AutocompleteDropdown/AutocompleteDropdownItem.tsx +4 -0
  27. package/src/features/shared/components/OrgUnitInputSearch/OrgUnitInputSearch.tsx +17 -13
  28. package/src/features/shared/components/QuantitySelectorWithPercentage/QuantitySelectorWithPercentage.tsx +150 -0
  29. package/src/features/shared/components/index.ts +24 -23
  30. package/src/features/shared/types/CurrencyType.d.ts +4 -0
  31. package/src/features/shared/types/index.ts +4 -3
  32. package/src/features/shared/utils/budgetAmountParse.ts +24 -0
  33. package/src/features/shared/utils/constants.ts +1 -1
  34. package/src/features/users/hooks/useDebouncedSearchOrgUnit.ts +7 -11
@@ -0,0 +1,150 @@
1
+ import React, { useEffect, useMemo, useState } from "react";
2
+
3
+ import { Icon, IconButton, Input } from "@faststore/ui";
4
+
5
+ interface QuantitySelectorWithPercentageParams {
6
+ testId?: string;
7
+ max?: number;
8
+ min?: number;
9
+ initial?: number;
10
+ unitMultiplier?: number;
11
+ useUnitMultiplier?: boolean;
12
+ disabled?: boolean;
13
+ onChange?: (value: number) => void;
14
+ onValidateBlur?: (min: number, maxValue: number, quantity: number) => void;
15
+
16
+ formatAsPercent?: boolean;
17
+ allowPercentToggle?: boolean;
18
+ onFormatToggle?: (formatAsPercent: boolean) => void;
19
+ }
20
+
21
+ const QuantitySelectorWithPercentage = ({
22
+ max,
23
+ min = 1,
24
+ unitMultiplier = 1,
25
+ useUnitMultiplier,
26
+ initial,
27
+ disabled = false,
28
+ onChange,
29
+ onValidateBlur,
30
+ testId = "fs-quantity-selector",
31
+ formatAsPercent = true,
32
+ ...otherProps
33
+ }: QuantitySelectorWithPercentageParams) => {
34
+ const [quantity, setQuantity] = useState<number>(initial ?? min);
35
+ const [multipliedUnit, setMultipliedUnit] = useState<number>(
36
+ quantity * unitMultiplier
37
+ );
38
+
39
+ const [showPercent, setShowPercent] = useState<boolean>(formatAsPercent);
40
+
41
+ useEffect(() => {
42
+ if (initial) setQuantity(initial);
43
+ }, [initial]);
44
+
45
+ useEffect(() => {
46
+ setShowPercent(formatAsPercent);
47
+ }, [formatAsPercent]);
48
+
49
+ const roundUpQuantityIfNeeded = (q: number) => {
50
+ if (!useUnitMultiplier) return q;
51
+ return Math.ceil(q / unitMultiplier) * unitMultiplier;
52
+ };
53
+
54
+ const isLeftDisabled = quantity === min;
55
+ const isRightDisabled = quantity === max;
56
+
57
+ const validateQuantityBounds = (n: number): number => {
58
+ const maxValue = min ? Math.max(n, min) : n;
59
+ return max
60
+ ? Math.min(maxValue, useUnitMultiplier ? max * unitMultiplier : max)
61
+ : maxValue;
62
+ };
63
+
64
+ const increase = () => changeQuantity(1);
65
+ const decrease = () => changeQuantity(-1);
66
+
67
+ const changeQuantity = (delta: number) => {
68
+ const next = validateQuantityBounds(quantity + delta);
69
+ onChange?.(next);
70
+ setQuantity(next);
71
+ setMultipliedUnit(next * unitMultiplier);
72
+ };
73
+
74
+ const validateBlur = () => {
75
+ const q = validateQuantityBounds(quantity);
76
+ const rounded = roundUpQuantityIfNeeded(q);
77
+
78
+ const maxValue = max ?? (min ? Math.max(quantity, min) : quantity);
79
+ const isOut = quantity > maxValue || quantity < min;
80
+ if (isOut) {
81
+ onValidateBlur?.(min, maxValue, rounded);
82
+ }
83
+
84
+ setQuantity(() => {
85
+ setMultipliedUnit(rounded);
86
+ onChange?.(rounded / unitMultiplier);
87
+ return rounded / unitMultiplier;
88
+ });
89
+ };
90
+
91
+ const changeInputValue = (e: React.ChangeEvent<HTMLInputElement>) => {
92
+ const raw = e.target.value.replace(/\s+/g, "").replace(/[^\d]/g, "");
93
+
94
+ const numeric = Number(raw || "0");
95
+ setQuantity(numeric);
96
+ };
97
+
98
+ const displayValue = useMemo(() => {
99
+ const base = useUnitMultiplier ? multipliedUnit : quantity;
100
+ if (showPercent) return `${base}%`;
101
+ return String(base);
102
+ }, [showPercent, useUnitMultiplier, multipliedUnit, quantity]);
103
+
104
+ return (
105
+ <div
106
+ data-fs-quantity-selector={disabled ? "disabled" : "true"}
107
+ data-testid={testId}
108
+ {...otherProps}
109
+ style={{ display: "inline-flex", alignItems: "center", gap: 8 }}
110
+ >
111
+ <IconButton
112
+ data-quantity-selector-button="left"
113
+ icon={<Icon name="Minus" width={16} height={16} weight="bold" />}
114
+ aria-label="Decrement Quantity"
115
+ aria-controls="quantity-selector-input"
116
+ disabled={isLeftDisabled || disabled}
117
+ onClick={decrease}
118
+ testId={`${testId}-left-button`}
119
+ size="small"
120
+ />
121
+
122
+ <Input
123
+ data-quantity-selector-input
124
+ id="quantity-selector-input"
125
+ aria-label={showPercent ? "Quantity (percent)" : "Quantity"}
126
+ value={displayValue}
127
+ onChange={changeInputValue}
128
+ onBlur={validateBlur}
129
+ onInput={(event: React.FormEvent<HTMLInputElement>) => {
130
+ const input = event.currentTarget;
131
+ input.value = input.value.replace(/[^0-9%]/g, "");
132
+ }}
133
+ disabled={disabled}
134
+ />
135
+
136
+ <IconButton
137
+ data-quantity-selector-button="right"
138
+ aria-controls="quantity-selector-input"
139
+ aria-label="Increment Quantity"
140
+ disabled={isRightDisabled || disabled}
141
+ icon={<Icon name="Plus" width={16} height={16} weight="bold" />}
142
+ onClick={increase}
143
+ testId={`${testId}-right-button`}
144
+ size="small"
145
+ />
146
+ </div>
147
+ );
148
+ };
149
+
150
+ export default QuantitySelectorWithPercentage;
@@ -1,8 +1,8 @@
1
1
  export {
2
2
  AutocompleteDropdown,
3
+ useAutocompleteDropdownContext,
3
4
  type AutocompleteDropdownContextProps,
4
5
  type AutocompleteDropdownProps,
5
- useAutocompleteDropdownContext,
6
6
  } from "./AutocompleteDropdown/AutocompleteDropdown";
7
7
  export { useAutocompletePosition } from "./AutocompleteDropdown/useAutocompletePosition";
8
8
  export { BasicCard, type BasicCardProps } from "./BasicCard/BasicCard";
@@ -12,19 +12,19 @@ export {
12
12
  type BasicDropdownMenuProps,
13
13
  } from "./BasicDropdownMenu/BasicDropdownMenu";
14
14
  export {
15
- BuyerPortalProvider,
16
- type BuyerPortalProviderProps,
17
15
  BuyerPortalContext,
16
+ BuyerPortalProvider,
18
17
  type BuyerPortalContextType,
18
+ type BuyerPortalProviderProps,
19
19
  } from "./BuyerPortalProvider/BuyerPortalProvider";
20
20
  export { Card, CardBody, CardFooter, CardHeader } from "./Card";
21
21
  export {
22
22
  CustomerSwitchDrawer,
23
23
  CustomerSwitchOption,
24
- type CustomerSwitchOptionProps,
25
24
  CustomerSwitchOptionsList,
26
- type CustomerSwitchOptionsListProps,
27
25
  CustomerSwitchSearch,
26
+ type CustomerSwitchOptionProps,
27
+ type CustomerSwitchOptionsListProps,
28
28
  type CustomerSwitchSearchProps,
29
29
  } from "./CustomerSwitch";
30
30
  export { default as DropdownFilter } from "./DropdownFilter/DropdownFilter";
@@ -32,6 +32,10 @@ export {
32
32
  ErrorMessage,
33
33
  type ErrorMessageProps,
34
34
  } from "./ErrorMessage/ErrorMessage";
35
+ export {
36
+ HeaderInside,
37
+ type HeaderInsideProps,
38
+ } from "./HeaderInside/HeaderInside";
35
39
  export {
36
40
  HierarchyTree,
37
41
  type HierarchyTreeLevelProps,
@@ -42,37 +46,34 @@ export { Icon } from "./Icon";
42
46
  export { InputText, type InputTextProps } from "./InputText/InputText";
43
47
  export { default as InternalSearch } from "./InternalSearch/InternalSearch";
44
48
  export { InternalTopBar } from "./InternalTopbar/InternalTopbar";
49
+ export {
50
+ LetterHighlight,
51
+ type LetterHighlightProps,
52
+ } from "./LetterHighlight/LetterHighlight";
53
+ export {
54
+ LevelDivider,
55
+ type LevelDividerProps,
56
+ } from "./LevelDivider/LevelDivider";
45
57
  export { Logo } from "./Logo/Logo";
46
58
  export { MainLinksDropdownMenu } from "./MainLinksDropdownMenu/MainLinksDropdownMenu";
59
+ export { Pagination } from "./Pagination/Pagination";
47
60
  export { default as SortFilter } from "./SortFilter/SortFilter";
48
- export { Tab, type TabProps, useTab } from "./Tab/Tab";
61
+ export { Tab, useTab, type TabProps } from "./Tab/Tab";
62
+ export { Table, type TableProps } from "./Table/Table";
49
63
  export { Tag, type TagProps } from "./Tag/Tag";
50
64
  export { default as Toast } from "./Toast/Toast";
51
65
  export {
52
66
  VerticalNav,
53
67
  type VerticalNavMenuProps,
54
68
  } from "./VerticalNav/VerticalNavMenu";
55
- export {
56
- LetterHighlight,
57
- type LetterHighlightProps,
58
- } from "./LetterHighlight/LetterHighlight";
59
- export {
60
- HeaderInside,
61
- type HeaderInsideProps,
62
- } from "./HeaderInside/HeaderInside";
63
- export { Pagination } from "./Pagination/Pagination";
64
- export { Table, type TableProps } from "./Table/Table";
65
- export {
66
- LevelDivider,
67
- type LevelDividerProps,
68
- } from "./LevelDivider/LevelDivider";
69
69
 
70
- export { Paginator } from "./Paginator/Paginator";
71
70
  export type { CounterProps as PaginatorCounterProps } from "./Paginator/Counter";
72
71
  export type { NextPageButtonProps as PaginatorNextPageButtonProps } from "./Paginator/NextPageButton";
72
+ export { Paginator } from "./Paginator/Paginator";
73
73
 
74
74
  export { EmptyState, type EmptyStateProps } from "./EmptyState/EmptyState";
75
- export { ErrorBoundary } from "./ErrorBoundary/ErrorBoundary";
76
- export { withErrorBoundary } from "./withErrorBoundary/withErrorBoundary";
77
75
  export { default as Error } from "./Error/Error";
76
+ export { ErrorBoundary } from "./ErrorBoundary/ErrorBoundary";
78
77
  export { IconBookmarked } from "./IconBookmarked/IconBookmarked";
78
+ export { default as QuantitySelectorWithPercentage } from "./QuantitySelectorWithPercentage/QuantitySelectorWithPercentage";
79
+ export { withErrorBoundary } from "./withErrorBoundary/withErrorBoundary";
@@ -0,0 +1,4 @@
1
+ type CurrencyType = "USD" | "BRL";
2
+ type LocaleType = "en-US" | "pt-BR";
3
+
4
+ export type { CurrencyType, LocaleType };
@@ -1,10 +1,11 @@
1
+ export { type AuthRouteProps } from "./AuthRouteProps";
1
2
  export type { BreadcrumbData } from "./BreadcrumbData";
3
+ export type { CurrencyType, LocaleType } from "./CurrencyType";
2
4
  export type { LoaderData } from "./LoaderData";
3
5
  export type { IPagination } from "./Pagination";
4
- export type { ScopeInput } from "./ScopeInput";
5
6
  export type {
6
- PaymentMethodsIdArray,
7
7
  PaymentMethodResponse,
8
+ PaymentMethodsIdArray,
8
9
  PaymentMethodsReqCommonParams,
9
10
  } from "./PaymentMethodsClientTypes";
10
- export { type AuthRouteProps } from "./AuthRouteProps";
11
+ export type { ScopeInput } from "./ScopeInput";
@@ -0,0 +1,24 @@
1
+ export const parseAmount = (raw?: string) => {
2
+ if (!raw) return 0;
3
+ const cleaned = raw
4
+ .toString()
5
+ .replace(/[^\d.,-]/g, "")
6
+ .trim();
7
+
8
+ const lastComma = cleaned.lastIndexOf(",");
9
+ const lastDot = cleaned.lastIndexOf(".");
10
+ let normalized = cleaned;
11
+
12
+ if (lastComma > -1 && lastDot > -1) {
13
+ if (lastComma > lastDot) {
14
+ normalized = cleaned.replace(/\./g, "").replace(",", ".");
15
+ } else {
16
+ normalized = cleaned.replace(/,/g, "");
17
+ }
18
+ } else if (lastComma > -1) {
19
+ normalized = cleaned.replace(",", ".");
20
+ }
21
+
22
+ const n = Number(normalized);
23
+ return Number.isFinite(n) ? n : 0;
24
+ };
@@ -13,4 +13,4 @@ export const LOCAL_STORAGE_LOCATION_EDIT_KEY = "bp_hide_edit_location_confirm";
13
13
  export const LOCAL_STORAGE_RECIPIENT_EDIT_KEY =
14
14
  "bp_hide_edit_recipient_confirm";
15
15
 
16
- export const CURRENT_VERSION = "1.3.17";
16
+ export const CURRENT_VERSION = "1.3.19";
@@ -7,18 +7,14 @@ export const useDebouncedSearchOrgUnit = (searchTerm = "") => {
7
7
  const { searchedOrgUnits, isSearchedOrgUnitsLoading } =
8
8
  useSearchOrgUnits(debouncedSearchTerm);
9
9
 
10
- if (searchTerm === "") {
11
- return {
12
- searchedOrgUnits: [] as {
13
- name: string;
14
- id: string;
15
- }[],
16
- isLoading: false,
17
- };
18
- }
10
+ const isDebouncing = searchTerm !== debouncedSearchTerm;
11
+ const isLoading =
12
+ searchTerm !== "" && (isDebouncing || isSearchedOrgUnitsLoading);
19
13
 
20
14
  return {
21
- searchedOrgUnits: searchedOrgUnits?.organizationalUnits ?? [],
22
- isDebouncedSearchOrgUnitLoading: isSearchedOrgUnitsLoading,
15
+ searchedOrgUnits: searchTerm
16
+ ? searchedOrgUnits?.organizationalUnits ?? []
17
+ : [],
18
+ isSearchedOrgUnitsLoading: isLoading,
23
19
  };
24
20
  };