@vtex/faststore-plugin-buyer-portal 1.3.17 → 1.3.18
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 +17 -6
- package/package.json +1 -1
- package/src/features/budgets/components/BudgetDeleteDrawer/BudgetDeleteDrawer.tsx +1 -1
- package/src/features/budgets/components/BudgetEditNotificationDrawer/BudgetEditNotificationDrawer.tsx +139 -0
- package/src/features/budgets/components/BudgetEditNotificationDrawer/budget-edit-notification-drawer.scss +34 -0
- package/src/features/budgets/components/BudgetNotificationForm/BudgetNotificationForm.tsx +361 -0
- package/src/features/budgets/components/BudgetNotificationForm/budget-notification-form.scss +219 -0
- package/src/features/budgets/components/BudgetNotificationsInfo/BudgetNotificationsInfo.tsx +116 -0
- package/src/features/budgets/components/BudgetNotificationsInfo/budget-notifications-info.scss +97 -0
- package/src/features/budgets/components/BudgetUsersTable/BudgetUsersTable.tsx +118 -0
- package/src/features/budgets/components/BudgetUsersTable/budget-users-table.scss +65 -0
- package/src/features/budgets/components/BudgetsTable/BudgetsTable.tsx +10 -0
- package/src/features/budgets/components/CreateBudgetAllocationDrawer/CreateBudgetAllocationDrawer.tsx +1 -1
- package/src/features/budgets/components/CreateBudgetDrawer/CreateBudgetDrawer.tsx +86 -25
- package/src/features/budgets/components/CreateBudgetDrawer/create-budget-drawer.scss +6 -0
- package/src/features/budgets/components/DeleteBudgetAllocationDrawer/DeleteBudgetAllocationDrawer.tsx +1 -1
- package/src/features/budgets/components/EditBudgetDrawer/EditBudgetDrawer.tsx +40 -1
- package/src/features/budgets/components/EditBudgetDrawer/edit-budget-drawer.scss +5 -0
- package/src/features/budgets/hooks/useDebouncedSearchBudgetNotification.ts +37 -0
- package/src/features/budgets/hooks/useListUsers.ts +1 -1
- package/src/features/budgets/layouts/BudgetsDetailsLayout/BudgetsDetailsLayout.tsx +9 -1
- package/src/features/budgets/layouts/BudgetsDetailsLayout/budget-details-layout.scss +14 -1
- package/src/features/budgets/layouts/BudgetsLayout/BudgetsLayout.tsx +39 -0
- package/src/features/budgets/layouts/BudgetsLayout/budgets-layout.scss +1 -1
- package/src/features/budgets/types/index.ts +17 -0
- package/src/features/shared/components/AutocompleteDropdown/AutocompleteDropdownItem.tsx +4 -0
- package/src/features/shared/components/QuantitySelectorWithPercentage/QuantitySelectorWithPercentage.tsx +150 -0
- package/src/features/shared/components/index.ts +24 -23
- package/src/features/shared/types/CurrencyType.d.ts +4 -0
- package/src/features/shared/types/index.ts +4 -3
- package/src/features/shared/utils/budgetAmountParse.ts +24 -0
- package/src/features/shared/utils/constants.ts +1 -1
|
@@ -11,14 +11,18 @@ import {
|
|
|
11
11
|
} from "../../../shared/components";
|
|
12
12
|
import { useBuyerPortal } from "../../../shared/hooks";
|
|
13
13
|
import { sortingOptionsAllocations } from "../../../shared/utils";
|
|
14
|
+
import { parseAmount } from "../../../shared/utils/budgetAmountParse";
|
|
14
15
|
import { getKeyByValue } from "../../../shared/utils/getKeyByValue";
|
|
15
|
-
import { useCreateBudget
|
|
16
|
+
import { useCreateBudget } from "../../hooks/useCreateBudget";
|
|
16
17
|
import { useGetAllocations } from "../../hooks/useGetAllocations";
|
|
18
|
+
import { useUpdateBudget } from "../../hooks/useUpdateBudget";
|
|
17
19
|
import { listBudgetsService } from "../../services";
|
|
18
|
-
import { BudgetInput, BudgetList } from "../../types";
|
|
19
20
|
import { BudgetAddForm } from "../BudgetAddForm/BudgetAddForm";
|
|
20
21
|
import { BudgetAddSuccess } from "../BudgetAddSuccess/BudgetAddSuccess";
|
|
21
22
|
import { BudgetAllocationsSelection } from "../BudgetAllocationsSelection/BudgetAllocationsSelection";
|
|
23
|
+
import { BudgetNotificationForm } from "../BudgetNotificationForm/BudgetNotificationForm";
|
|
24
|
+
|
|
25
|
+
import type { BudgetInput, BudgetList, BudgetListResponse } from "../../types";
|
|
22
26
|
|
|
23
27
|
const createTouchedState = (): Record<keyof BudgetInput, boolean> => ({
|
|
24
28
|
name: false,
|
|
@@ -31,6 +35,7 @@ const createTouchedState = (): Record<keyof BudgetInput, boolean> => ({
|
|
|
31
35
|
allocations: false,
|
|
32
36
|
preventCheckoutBudgetExceeded: false,
|
|
33
37
|
preventCheckoutBudgetExpired: false,
|
|
38
|
+
notifications: false,
|
|
34
39
|
});
|
|
35
40
|
|
|
36
41
|
const isBudgetValid = (budget: BudgetInput) => {
|
|
@@ -93,6 +98,7 @@ export const CreateBudgetDrawer = ({
|
|
|
93
98
|
preventCheckoutBudgetExpired: false,
|
|
94
99
|
description: "",
|
|
95
100
|
allocations: [],
|
|
101
|
+
notifications: undefined,
|
|
96
102
|
});
|
|
97
103
|
|
|
98
104
|
const [touched, setTouched] = useState(createTouchedState);
|
|
@@ -101,6 +107,8 @@ export const CreateBudgetDrawer = ({
|
|
|
101
107
|
const [blockOnExpiration, setBlockOnExpiration] = useState(true);
|
|
102
108
|
const [listBudgetsLoading, setListBudgetsLoading] = useState(false);
|
|
103
109
|
const [firstBudget, setFirstBudget] = useState<BudgetList | null>(null);
|
|
110
|
+
const [showNotificationsUsersError, setShowNotificationsUsersError] =
|
|
111
|
+
useState(false);
|
|
104
112
|
|
|
105
113
|
const { mutate: createBudget, isLoading: isCreatingBudget } = useCreateBudget(
|
|
106
114
|
{
|
|
@@ -108,13 +116,15 @@ export const CreateBudgetDrawer = ({
|
|
|
108
116
|
onSuccess: async () => {
|
|
109
117
|
setListBudgetsLoading(true);
|
|
110
118
|
|
|
111
|
-
const
|
|
119
|
+
const res = await listBudgetsService({
|
|
112
120
|
customerId: contractId,
|
|
113
121
|
unitId,
|
|
114
122
|
page: 1,
|
|
115
123
|
cookie,
|
|
116
124
|
}).finally(() => setListBudgetsLoading(false));
|
|
117
125
|
|
|
126
|
+
const { data, total } = res as BudgetListResponse;
|
|
127
|
+
|
|
118
128
|
if (total === 1) {
|
|
119
129
|
setStep("confirmation");
|
|
120
130
|
setFirstBudget(data[0]);
|
|
@@ -129,7 +139,7 @@ export const CreateBudgetDrawer = ({
|
|
|
129
139
|
close();
|
|
130
140
|
router.reload();
|
|
131
141
|
},
|
|
132
|
-
onError: (error) =>
|
|
142
|
+
onError: (error: Error) =>
|
|
133
143
|
pushToast({ message: error.message, status: "ERROR" }),
|
|
134
144
|
},
|
|
135
145
|
}
|
|
@@ -156,7 +166,7 @@ export const CreateBudgetDrawer = ({
|
|
|
156
166
|
close();
|
|
157
167
|
router.reload();
|
|
158
168
|
},
|
|
159
|
-
onError: (error) => {
|
|
169
|
+
onError: (error: Error) => {
|
|
160
170
|
pushToast({
|
|
161
171
|
message: error?.message || "An error occurred. Please try again.",
|
|
162
172
|
status: "ERROR",
|
|
@@ -166,15 +176,11 @@ export const CreateBudgetDrawer = ({
|
|
|
166
176
|
});
|
|
167
177
|
|
|
168
178
|
const handleChange = useCallback(
|
|
169
|
-
(field:
|
|
179
|
+
<K extends keyof BudgetInput>(field: K, value: BudgetInput[K]) => {
|
|
170
180
|
if (field === "amount" && typeof value === "string") {
|
|
171
|
-
setBudget((prev) => ({
|
|
172
|
-
...prev,
|
|
173
|
-
amount: value,
|
|
174
|
-
}));
|
|
181
|
+
setBudget((prev) => ({ ...prev, amount: value }));
|
|
175
182
|
return;
|
|
176
183
|
}
|
|
177
|
-
|
|
178
184
|
if (
|
|
179
185
|
(field === "startDate" || field === "expirationDate") &&
|
|
180
186
|
typeof value === "string" &&
|
|
@@ -208,18 +214,24 @@ export const CreateBudgetDrawer = ({
|
|
|
208
214
|
pushToast({ message: "Fill in all required fields.", status: "ERROR" });
|
|
209
215
|
return;
|
|
210
216
|
}
|
|
217
|
+
const hasUsers = (budget?.notifications?.users?.length ?? 0) > 0;
|
|
218
|
+
const hasThresholds = (budget?.notifications?.thresholds?.length ?? 0) > 0;
|
|
211
219
|
|
|
212
|
-
const payload = {
|
|
220
|
+
const payload: BudgetInput = {
|
|
213
221
|
...budget,
|
|
222
|
+
...(hasUsers &&
|
|
223
|
+
hasThresholds && {
|
|
224
|
+
notifications: budget.notifications,
|
|
225
|
+
}),
|
|
214
226
|
amount: budget.amount ? budget.amount.replace(",", "") : "0",
|
|
215
227
|
allocations: skipAllocations
|
|
216
228
|
? []
|
|
217
|
-
: selectedAllocations.map((allocation) => ({
|
|
229
|
+
: (selectedAllocations.map((allocation) => ({
|
|
218
230
|
id: ["USER", "ADDRESS"].includes(allocation.type)
|
|
219
231
|
? allocation.id
|
|
220
232
|
: allocation.name,
|
|
221
233
|
type: allocation.type,
|
|
222
|
-
})),
|
|
234
|
+
})) as NonNullable<BudgetInput["allocations"]>),
|
|
223
235
|
preventCheckoutBudgetExceeded: false,
|
|
224
236
|
preventCheckoutBudgetExpired: false,
|
|
225
237
|
};
|
|
@@ -241,9 +253,12 @@ export const CreateBudgetDrawer = ({
|
|
|
241
253
|
description: budget.description,
|
|
242
254
|
startDate: budget.startDate,
|
|
243
255
|
endDate: budget.endDate,
|
|
256
|
+
expirationDate: budget.expirationDate,
|
|
244
257
|
autoResetOnPeriodEnd: budget.autoResetOnPeriodEnd,
|
|
245
258
|
preventCheckoutBudgetExceeded: enforcePolicy ? blockOnExceed : false,
|
|
246
259
|
preventCheckoutBudgetExpired: enforcePolicy ? blockOnExpiration : false,
|
|
260
|
+
allocations: [],
|
|
261
|
+
notifications: budget.notifications,
|
|
247
262
|
};
|
|
248
263
|
|
|
249
264
|
updateBudget({
|
|
@@ -280,18 +295,31 @@ export const CreateBudgetDrawer = ({
|
|
|
280
295
|
}
|
|
281
296
|
onClose={close}
|
|
282
297
|
/>
|
|
283
|
-
|
|
284
298
|
<BasicDrawer.Body>
|
|
285
299
|
{step === "form" && (
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
300
|
+
<>
|
|
301
|
+
<BudgetAddForm
|
|
302
|
+
budget={budget}
|
|
303
|
+
setBudget={setBudget}
|
|
304
|
+
touched={touched}
|
|
305
|
+
setTouched={setTouched}
|
|
306
|
+
handleChange={handleChange}
|
|
307
|
+
handleBlur={handleBlur}
|
|
308
|
+
readonly={readonly}
|
|
309
|
+
/>
|
|
310
|
+
<div data-fs-divider />
|
|
311
|
+
<BudgetNotificationForm
|
|
312
|
+
budget={budget}
|
|
313
|
+
contractId={contractId}
|
|
314
|
+
unitId={unitId}
|
|
315
|
+
handleChange={handleChange}
|
|
316
|
+
totalAmount={parseAmount(budget.amount)}
|
|
317
|
+
currency="USD"
|
|
318
|
+
locale="en-US"
|
|
319
|
+
readonly={readonly}
|
|
320
|
+
showUsersError={showNotificationsUsersError}
|
|
321
|
+
/>
|
|
322
|
+
</>
|
|
295
323
|
)}
|
|
296
324
|
|
|
297
325
|
{step === "allocations" && (
|
|
@@ -337,7 +365,40 @@ export const CreateBudgetDrawer = ({
|
|
|
337
365
|
</BasicDrawer.Button>
|
|
338
366
|
<BasicDrawer.Button
|
|
339
367
|
variant="confirm"
|
|
340
|
-
onClick={() =>
|
|
368
|
+
onClick={() => {
|
|
369
|
+
const notificationsEnabled = Boolean(
|
|
370
|
+
budget?.notifications?.hasNotification
|
|
371
|
+
);
|
|
372
|
+
const hasUsers =
|
|
373
|
+
(budget?.notifications?.users?.length ?? 0) > 0;
|
|
374
|
+
if (!isBudgetValid(budget)) {
|
|
375
|
+
setTouched(
|
|
376
|
+
Object.keys(budget).reduce(
|
|
377
|
+
(acc, key) => ({ ...acc, [key]: true }),
|
|
378
|
+
{} as Record<keyof BudgetInput, boolean>
|
|
379
|
+
)
|
|
380
|
+
);
|
|
381
|
+
if (notificationsEnabled && !hasUsers)
|
|
382
|
+
setShowNotificationsUsersError(true);
|
|
383
|
+
pushToast({
|
|
384
|
+
message: "Fill in all required fields.",
|
|
385
|
+
status: "ERROR",
|
|
386
|
+
});
|
|
387
|
+
return;
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
if (notificationsEnabled && !hasUsers) {
|
|
391
|
+
setShowNotificationsUsersError(true);
|
|
392
|
+
pushToast({
|
|
393
|
+
message:
|
|
394
|
+
"Add at least one user to notifications or turn it off.",
|
|
395
|
+
status: "ERROR",
|
|
396
|
+
});
|
|
397
|
+
return;
|
|
398
|
+
}
|
|
399
|
+
setShowNotificationsUsersError(false);
|
|
400
|
+
setStep("allocations");
|
|
401
|
+
}}
|
|
341
402
|
>
|
|
342
403
|
Continue
|
|
343
404
|
</BasicDrawer.Button>
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
@import "@faststore/ui/src/components/molecules/Toggle/styles.scss";
|
|
2
2
|
@import "@faststore/ui/src/components/molecules/Tooltip/styles.scss";
|
|
3
3
|
@import "../BudgetAddForm/budget-add-form.scss";
|
|
4
|
+
@import "../BudgetNotificationForm/budget-notification-form.scss";
|
|
4
5
|
@import "../BudgetAllocationsSelection/budget-allocations-selection.scss";
|
|
5
6
|
@import "../BudgetAddSuccess/budget-add-success.scss";
|
|
6
7
|
|
|
@@ -29,6 +30,11 @@
|
|
|
29
30
|
max-width: 40rem;
|
|
30
31
|
}
|
|
31
32
|
|
|
33
|
+
[data-fs-divider] {
|
|
34
|
+
border: var(--fs-border-radius-small) solid #e0e0e0;
|
|
35
|
+
margin: var(--fs-spacing-4) 0rem;
|
|
36
|
+
}
|
|
37
|
+
|
|
32
38
|
& > [data-fs-bp-basic-drawer-footer] {
|
|
33
39
|
justify-content: flex-end;
|
|
34
40
|
gap: var(--fs-spacing-1);
|
|
@@ -10,7 +10,7 @@ import {
|
|
|
10
10
|
type BasicDrawerProps,
|
|
11
11
|
} from "../../../shared/components";
|
|
12
12
|
import { useBuyerPortal } from "../../../shared/hooks";
|
|
13
|
-
import { useDeleteAllocation } from "../../hooks";
|
|
13
|
+
import { useDeleteAllocation } from "../../hooks/useDeleteAllocations";
|
|
14
14
|
|
|
15
15
|
import type { BudgetAllocation } from "../../types";
|
|
16
16
|
|
|
@@ -6,8 +6,10 @@ import { useUI } from "@faststore/ui";
|
|
|
6
6
|
|
|
7
7
|
import { BasicDrawer, type BasicDrawerProps } from "../../../shared/components";
|
|
8
8
|
import { useBuyerPortal } from "../../../shared/hooks";
|
|
9
|
+
import { parseAmount } from "../../../shared/utils/budgetAmountParse";
|
|
9
10
|
import { useUpdateBudget } from "../../hooks";
|
|
10
11
|
import { BudgetAddForm } from "../BudgetAddForm/BudgetAddForm";
|
|
12
|
+
import { BudgetNotificationForm } from "../BudgetNotificationForm/BudgetNotificationForm";
|
|
11
13
|
|
|
12
14
|
import type { BudgetInput } from "../../types";
|
|
13
15
|
|
|
@@ -22,6 +24,7 @@ const getInitialBudget = (initial?: BudgetInput): BudgetInput => ({
|
|
|
22
24
|
preventCheckoutBudgetExceeded: false,
|
|
23
25
|
preventCheckoutBudgetExpired: false,
|
|
24
26
|
allocations: [],
|
|
27
|
+
notifications: initial?.notifications,
|
|
25
28
|
...initial,
|
|
26
29
|
});
|
|
27
30
|
|
|
@@ -36,6 +39,7 @@ const createTouchedObject = (): Record<keyof BudgetInput, boolean> => ({
|
|
|
36
39
|
preventCheckoutBudgetExceeded: false,
|
|
37
40
|
preventCheckoutBudgetExpired: false,
|
|
38
41
|
allocations: false,
|
|
42
|
+
notifications: false,
|
|
39
43
|
});
|
|
40
44
|
|
|
41
45
|
const isBudgetValid = (budget: BudgetInput) => {
|
|
@@ -76,6 +80,8 @@ export const EditBudgetDrawer = ({
|
|
|
76
80
|
getInitialBudget(initialBudget)
|
|
77
81
|
);
|
|
78
82
|
const [touched, setTouched] = useState(createTouchedObject);
|
|
83
|
+
const [showNotificationsUsersError, setShowNotificationsUsersError] =
|
|
84
|
+
useState(false);
|
|
79
85
|
|
|
80
86
|
const { mutate: updateBudget, isLoading } = useUpdateBudget({
|
|
81
87
|
options: {
|
|
@@ -85,7 +91,7 @@ export const EditBudgetDrawer = ({
|
|
|
85
91
|
close();
|
|
86
92
|
router.reload();
|
|
87
93
|
},
|
|
88
|
-
onError: (error) => {
|
|
94
|
+
onError: (error: Error) => {
|
|
89
95
|
pushToast({
|
|
90
96
|
message: error?.message || "An error occurred. Please try again.",
|
|
91
97
|
status: "ERROR",
|
|
@@ -117,6 +123,7 @@ export const EditBudgetDrawer = ({
|
|
|
117
123
|
preventCheckoutBudgetExceeded: true,
|
|
118
124
|
preventCheckoutBudgetExpired: true,
|
|
119
125
|
allocations: true,
|
|
126
|
+
notifications: true,
|
|
120
127
|
});
|
|
121
128
|
|
|
122
129
|
if (!isBudgetValid(budget)) {
|
|
@@ -127,8 +134,28 @@ export const EditBudgetDrawer = ({
|
|
|
127
134
|
return;
|
|
128
135
|
}
|
|
129
136
|
|
|
137
|
+
const notificationsEnabled = Boolean(
|
|
138
|
+
budget?.notifications?.hasNotification
|
|
139
|
+
);
|
|
140
|
+
const hasUsers = (budget?.notifications?.users?.length ?? 0) > 0;
|
|
141
|
+
if (notificationsEnabled && !hasUsers) {
|
|
142
|
+
setShowNotificationsUsersError(true);
|
|
143
|
+
pushToast({
|
|
144
|
+
message: "Add at least one user to notifications or turn it off.",
|
|
145
|
+
status: "ERROR",
|
|
146
|
+
});
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
setShowNotificationsUsersError(false);
|
|
150
|
+
|
|
151
|
+
const hasThresholds = (budget?.notifications?.thresholds?.length ?? 0) > 0;
|
|
152
|
+
|
|
130
153
|
const payload: BudgetInput = {
|
|
131
154
|
...budget,
|
|
155
|
+
...(hasUsers &&
|
|
156
|
+
hasThresholds && {
|
|
157
|
+
notifications: budget.notifications,
|
|
158
|
+
}),
|
|
132
159
|
endDate: budget.endDate || budget.expirationDate,
|
|
133
160
|
amount: budget.amount ? String(budget.amount).replace(",", "") : "0",
|
|
134
161
|
allocations: initialBudget?.allocations ?? [],
|
|
@@ -157,6 +184,18 @@ export const EditBudgetDrawer = ({
|
|
|
157
184
|
handleBlur={handleBlur}
|
|
158
185
|
readonly={readonly}
|
|
159
186
|
/>
|
|
187
|
+
<div data-fs-divider />
|
|
188
|
+
<BudgetNotificationForm
|
|
189
|
+
budget={budget}
|
|
190
|
+
contractId={contractId}
|
|
191
|
+
unitId={orgUnitId}
|
|
192
|
+
handleChange={handleChange}
|
|
193
|
+
totalAmount={parseAmount(budget.amount)}
|
|
194
|
+
currency="USD"
|
|
195
|
+
locale="en-US"
|
|
196
|
+
readonly={readonly}
|
|
197
|
+
showUsersError={showNotificationsUsersError}
|
|
198
|
+
/>
|
|
160
199
|
</BasicDrawer.Body>
|
|
161
200
|
|
|
162
201
|
<BasicDrawer.Footer>
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
@import "@faststore/ui/src/components/molecules/Tooltip/styles.scss";
|
|
4
4
|
@import "../../../shared/components/BasicDrawer/basic-drawer.scss";
|
|
5
5
|
@import "../BudgetAddForm/budget-add-form.scss";
|
|
6
|
+
@import "../BudgetNotificationForm/budget-notification-form.scss";
|
|
6
7
|
@import "../../../shared/components/InputText/input-text.scss";
|
|
7
8
|
@import "../../../shared/components/ErrorMessage/error-message.scss";
|
|
8
9
|
|
|
@@ -31,6 +32,10 @@
|
|
|
31
32
|
width: 100%;
|
|
32
33
|
}
|
|
33
34
|
}
|
|
35
|
+
[data-fs-divider] {
|
|
36
|
+
border: var(--fs-border-radius-small) solid #e0e0e0;
|
|
37
|
+
margin: var(--fs-spacing-4) 0rem;
|
|
38
|
+
}
|
|
34
39
|
|
|
35
40
|
footer[data-fs-bp-basic-drawer-footer] {
|
|
36
41
|
justify-content: flex-end;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { useDebounce } from "../../shared/hooks";
|
|
2
|
+
import { DEBOUNCE_TIMEOUT } from "../../shared/utils";
|
|
3
|
+
|
|
4
|
+
import { useListUsers } from "./useListUsers";
|
|
5
|
+
|
|
6
|
+
type Params = {
|
|
7
|
+
customerId: string;
|
|
8
|
+
unitId: string;
|
|
9
|
+
search?: string;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export const useDebouncedSearchBudgetNotification = ({
|
|
13
|
+
customerId,
|
|
14
|
+
unitId,
|
|
15
|
+
search = "",
|
|
16
|
+
}: Params) => {
|
|
17
|
+
const debouncedSearchTerm = useDebounce(search, DEBOUNCE_TIMEOUT);
|
|
18
|
+
const isDebouncing = search !== debouncedSearchTerm;
|
|
19
|
+
|
|
20
|
+
const { listUsers, isListUsersLoading } = useListUsers({
|
|
21
|
+
keys: `${debouncedSearchTerm}`,
|
|
22
|
+
data: {
|
|
23
|
+
customerId,
|
|
24
|
+
unitId,
|
|
25
|
+
params: {
|
|
26
|
+
search: debouncedSearchTerm,
|
|
27
|
+
page: 1,
|
|
28
|
+
},
|
|
29
|
+
},
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
return {
|
|
33
|
+
usersResponse: listUsers,
|
|
34
|
+
isLoadingUsers: isListUsersLoading,
|
|
35
|
+
isDebouncing,
|
|
36
|
+
};
|
|
37
|
+
};
|
|
@@ -16,11 +16,12 @@ import { ContractTabsLayout, GlobalLayout } from "../../../shared/layouts";
|
|
|
16
16
|
import { buyerPortalRoutes } from "../../../shared/utils/buyerPortalRoutes";
|
|
17
17
|
import { BudgetAllocationsTable } from "../../components/BudgetAllocationsTable/BudgetAllocationsTable";
|
|
18
18
|
import { BudgetDeleteDrawer } from "../../components/BudgetDeleteDrawer/BudgetDeleteDrawer";
|
|
19
|
+
import { BudgetNotificationsInfo } from "../../components/BudgetNotificationsInfo/BudgetNotificationsInfo";
|
|
19
20
|
import { BudgetRemainingBalance } from "../../components/BudgetRemainingBalance/BudgetRemainingBalance";
|
|
20
21
|
import { BudgetSettingsInfoBalance } from "../../components/BudgetSettingsInfo/BudgetSettingsInfo";
|
|
21
22
|
import { CreateBudgetAllocationDrawer } from "../../components/CreateBudgetAllocationDrawer/CreateBudgetAllocationDrawer";
|
|
22
23
|
import { EditBudgetDrawer } from "../../components/EditBudgetDrawer/EditBudgetDrawer";
|
|
23
|
-
import { useListAllocations } from "../../hooks";
|
|
24
|
+
import { useListAllocations } from "../../hooks/useListAllocations";
|
|
24
25
|
|
|
25
26
|
import type { Budget, BudgetAllocationListResponse } from "../../types";
|
|
26
27
|
|
|
@@ -203,6 +204,13 @@ export const BudgetsDetailsLayout = ({ budget }: BudgetsDetailsLayoutProps) => {
|
|
|
203
204
|
contractId={contract?.id ?? ""}
|
|
204
205
|
/>
|
|
205
206
|
|
|
207
|
+
<BudgetNotificationsInfo
|
|
208
|
+
initialBudget={budget}
|
|
209
|
+
budgetId={budget.id}
|
|
210
|
+
orgUnitId={orgUnit?.id ?? ""}
|
|
211
|
+
contractId={contract?.id ?? ""}
|
|
212
|
+
/>
|
|
213
|
+
|
|
206
214
|
{hasMounted && (
|
|
207
215
|
<BudgetAllocationsTable
|
|
208
216
|
loading={isAllocationsLoading && !loadingLoadMore}
|
|
@@ -4,12 +4,14 @@
|
|
|
4
4
|
@import "../../components/BudgetAllocationsSelection/budget-allocations-selection.scss";
|
|
5
5
|
@import "../../../budgets/components/DeleteBudgetAllocationDrawer/delete-budget-allocations.scss";
|
|
6
6
|
@import "../../components/CreateBudgetAllocationDrawer/create-budget-allocation-drawer.scss";
|
|
7
|
+
@import "../../components/BudgetEditNotificationDrawer/budget-edit-notification-drawer.scss";
|
|
7
8
|
|
|
8
9
|
[data-fs-bp-budgets-details-layout] {
|
|
9
10
|
@import "../../components/CreateBudgetDrawer/create-budget-drawer.scss";
|
|
10
11
|
@import "../../components/BudgetAddForm/budget-add-form.scss";
|
|
11
12
|
@import "../../components/BudgetRemainingBalance/budget-remaining-balance.scss";
|
|
12
|
-
|
|
13
|
+
@import "../../components/BudgetNotificationForm/budget-notification-form.scss";
|
|
14
|
+
@import "../../components/BudgetNotificationsInfo/budget-notifications-info.scss";
|
|
13
15
|
@import "../../components/BudgetSettingsInfo/budget-settings-info.scss";
|
|
14
16
|
@import "../../../shared/components/InternalSearch/internal-search.scss";
|
|
15
17
|
@import "../../components/BudgetsTable/budgets-table.scss";
|
|
@@ -37,6 +39,17 @@
|
|
|
37
39
|
cursor: pointer;
|
|
38
40
|
}
|
|
39
41
|
}
|
|
42
|
+
[data-fs-bp-budget-notifications-empty] {
|
|
43
|
+
[data-fs-empty-state-section] {
|
|
44
|
+
padding: 4rem 17rem;
|
|
45
|
+
text-align: center;
|
|
46
|
+
color: #858585;
|
|
47
|
+
|
|
48
|
+
@include media("<=tablet") {
|
|
49
|
+
padding: 4rem clamp(1rem, 5vw, 17rem);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
40
53
|
|
|
41
54
|
[data-fs-bp-budgets-details-section] {
|
|
42
55
|
min-height: calc(100vh - calc(var(--fs-spacing-9) + var(--fs-spacing-0)));
|
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
} from "../../../shared/hooks";
|
|
11
11
|
import { FinanceTabsLayout, GlobalLayout } from "../../../shared/layouts";
|
|
12
12
|
import { BudgetDeleteDrawer } from "../../components/BudgetDeleteDrawer/BudgetDeleteDrawer";
|
|
13
|
+
import { BudgetEditNotificationDrawer } from "../../components/BudgetEditNotificationDrawer/BudgetEditNotificationDrawer";
|
|
13
14
|
import { BudgetsTable } from "../../components/BudgetsTable/BudgetsTable";
|
|
14
15
|
import { CreateBudgetAllocationDrawer } from "../../components/CreateBudgetAllocationDrawer/CreateBudgetAllocationDrawer";
|
|
15
16
|
import { CreateBudgetDrawer } from "../../components/CreateBudgetDrawer/CreateBudgetDrawer";
|
|
@@ -73,6 +74,12 @@ export const BudgetsLayout = ({ data }: BudgetsLayoutProps) => {
|
|
|
73
74
|
...createBudgetDrawerProps
|
|
74
75
|
} = useDrawerProps();
|
|
75
76
|
|
|
77
|
+
const {
|
|
78
|
+
open: openNotificationDrawer,
|
|
79
|
+
isOpen: isNotificationDrawerOpen,
|
|
80
|
+
...notificationDrawerProps
|
|
81
|
+
} = useDrawerProps();
|
|
82
|
+
|
|
76
83
|
async function loadMore() {
|
|
77
84
|
setPaginationLoading(true);
|
|
78
85
|
setLoading(true);
|
|
@@ -134,6 +141,26 @@ export const BudgetsLayout = ({ data }: BudgetsLayoutProps) => {
|
|
|
134
141
|
[page, contract?.id, orgUnit?.id, cookie]
|
|
135
142
|
);
|
|
136
143
|
|
|
144
|
+
const handleCreateNotification = useCallback(
|
|
145
|
+
async (budgetId: string) => {
|
|
146
|
+
setSelectedBudgetId(budgetId);
|
|
147
|
+
try {
|
|
148
|
+
const budgetsData = await getBudgetByIdService({
|
|
149
|
+
budgetId,
|
|
150
|
+
customerId: contract?.id ?? "",
|
|
151
|
+
unitId: orgUnit?.id ?? "",
|
|
152
|
+
cookie,
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
setBudgetToEdit({ ...budgetsData, amount: String(budgetsData.amount) });
|
|
156
|
+
openNotificationDrawer();
|
|
157
|
+
} catch (error) {
|
|
158
|
+
console.error("Failed to load budget:", error);
|
|
159
|
+
}
|
|
160
|
+
},
|
|
161
|
+
[page, contract?.id, orgUnit?.id, cookie]
|
|
162
|
+
);
|
|
163
|
+
|
|
137
164
|
return (
|
|
138
165
|
<GlobalLayout>
|
|
139
166
|
<FinanceTabsLayout pageName="Finance and Compliance">
|
|
@@ -164,6 +191,7 @@ export const BudgetsLayout = ({ data }: BudgetsLayoutProps) => {
|
|
|
164
191
|
total={Number(total ?? 0)}
|
|
165
192
|
onClickAllocationPage={handleAddAllocation}
|
|
166
193
|
onClickEditBudget={handleBudgetEditPage}
|
|
194
|
+
onClickCreateNotification={handleCreateNotification}
|
|
167
195
|
openDeleteDrawer={(budgetId) => {
|
|
168
196
|
setSelectedBudgetId(budgetId);
|
|
169
197
|
openDeleteDrawer();
|
|
@@ -232,6 +260,17 @@ export const BudgetsLayout = ({ data }: BudgetsLayoutProps) => {
|
|
|
232
260
|
initialBudget={budgetsToEdit}
|
|
233
261
|
/>
|
|
234
262
|
)}
|
|
263
|
+
|
|
264
|
+
{isNotificationDrawerOpen && budgetsToEdit && (
|
|
265
|
+
<BudgetEditNotificationDrawer
|
|
266
|
+
{...notificationDrawerProps}
|
|
267
|
+
isOpen={isNotificationDrawerOpen}
|
|
268
|
+
budget={budgetsToEdit}
|
|
269
|
+
budgetId={selectedBudgetId}
|
|
270
|
+
orgUnitId={orgUnit?.id ?? ""}
|
|
271
|
+
contractId={contract?.id ?? ""}
|
|
272
|
+
/>
|
|
273
|
+
)}
|
|
235
274
|
</section>
|
|
236
275
|
</FinanceTabsLayout>
|
|
237
276
|
</GlobalLayout>
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
@import "@faststore/ui/src/components/molecules/Tooltip/styles.scss";
|
|
5
5
|
@import "@faststore/ui/src/components/molecules/Table/styles.scss";
|
|
6
6
|
@import "@faststore/ui/src/components/atoms/Button/styles.scss";
|
|
7
|
-
|
|
7
|
+
@import "../../components/BudgetNotificationForm/budget-notification-form.scss";
|
|
8
8
|
@import "../../../shared/components/InternalSearch/internal-search.scss";
|
|
9
9
|
@import "../../../shared/components/HeaderInside/header-inside.scss";
|
|
10
10
|
@import "../../components/BudgetsTable/budgets-table.scss";
|
|
@@ -25,6 +25,22 @@ export type BudgetAllocationListResponse = {
|
|
|
25
25
|
data: BudgetAllocation[];
|
|
26
26
|
total: number;
|
|
27
27
|
};
|
|
28
|
+
|
|
29
|
+
export type NotificationThresholds = {
|
|
30
|
+
value: number;
|
|
31
|
+
};
|
|
32
|
+
export type NotificationUsers = {
|
|
33
|
+
userId: string;
|
|
34
|
+
userName: string;
|
|
35
|
+
userEmail: string;
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
export type BudgetNotification = {
|
|
39
|
+
hasNotification: boolean;
|
|
40
|
+
thresholds: NotificationThresholds[];
|
|
41
|
+
users: NotificationUsers[];
|
|
42
|
+
};
|
|
43
|
+
|
|
28
44
|
export type Budget = {
|
|
29
45
|
id: string;
|
|
30
46
|
name: string;
|
|
@@ -39,6 +55,7 @@ export type Budget = {
|
|
|
39
55
|
preventCheckoutBudgetExceeded?: boolean;
|
|
40
56
|
preventCheckoutBudgetExpired?: boolean;
|
|
41
57
|
allocations?: BudgetAllocation[];
|
|
58
|
+
notifications?: BudgetNotification;
|
|
42
59
|
};
|
|
43
60
|
|
|
44
61
|
export type BudgetInputForm = Omit<BudgetInput, "amount"> & {
|
|
@@ -29,7 +29,11 @@ export const AutocompleteDropdownItem = ({
|
|
|
29
29
|
}
|
|
30
30
|
data-fs-bp-autocomplete-dropdown-option-selected={isSelected}
|
|
31
31
|
onMouseEnter={() => setFocusedItemIndex(index)}
|
|
32
|
+
onMouseDown={(e) => {
|
|
33
|
+
e.preventDefault();
|
|
34
|
+
}}
|
|
32
35
|
onClick={(e) => {
|
|
36
|
+
e.stopPropagation();
|
|
33
37
|
if (closeOnClick) {
|
|
34
38
|
close();
|
|
35
39
|
}
|