@vtex/faststore-plugin-buyer-portal 1.0.45 → 1.0.47

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 (39) hide show
  1. package/CHANGELOG.md +2 -0
  2. package/package.json +1 -1
  3. package/src/features/buying-policies/clients/BuyingPoliciesClient.ts +123 -0
  4. package/src/features/buying-policies/components/BasicBuyingPolicyDrawer/BasicBuyingPolicyDrawer.tsx +5 -4
  5. package/src/features/buying-policies/components/BuyingPolicyDropdownMenu/BuyingPolicyDropdownMenu.tsx +4 -0
  6. package/src/features/buying-policies/components/DeleteBuyingPolicyDrawer/DeleteBuyingPolicyDrawer.tsx +2 -0
  7. package/src/features/buying-policies/components/UpdateBuyingPolicyDrawer/UpdateBuyingPolicyDrawer.tsx +8 -6
  8. package/src/features/buying-policies/components/UpdateBuyingPolicyDrawer/update-buying-policy-drawer.scss +1 -0
  9. package/src/features/buying-policies/hooks/useGetBuyingPolicies.ts +32 -0
  10. package/src/features/buying-policies/layouts/BuyingPoliciesLayout/BuyingPoliciesLayout.tsx +84 -52
  11. package/src/features/buying-policies/layouts/BuyingPoliciesLayout/buying-policies-layout.scss +7 -0
  12. package/src/features/buying-policies/layouts/BuyingPolicyDetailsLayout/BuyingPolicyDetailsLayout.tsx +29 -3
  13. package/src/features/buying-policies/layouts/BuyingPolicyDetailsLayout/buying-policy-details-layout.scss +1 -0
  14. package/src/features/buying-policies/mocks/buying-policy-data.ts +6 -6
  15. package/src/features/buying-policies/services/add-buying-policy.service.ts +11 -1
  16. package/src/features/buying-policies/services/get-buying-policies.service.ts +25 -0
  17. package/src/features/buying-policies/services/get-buying-policy.service.ts +8 -5
  18. package/src/features/buying-policies/services/index.ts +5 -0
  19. package/src/features/buying-policies/services/remove-buying-policy.service.ts +14 -1
  20. package/src/features/buying-policies/services/update-buying-policy.service.ts +11 -1
  21. package/src/features/buying-policies/types/BuyingPolicy.ts +9 -4
  22. package/src/features/buying-policies/utils/buyingPoliciesWorkflowTypes.ts +8 -0
  23. package/src/features/buying-policies/utils/buyingPolicyDefault.ts +3 -2
  24. package/src/features/buying-policies/utils/index.ts +4 -0
  25. package/src/features/shared/components/Paginator/Counter.tsx +10 -0
  26. package/src/features/shared/components/Paginator/NextPageButton.tsx +15 -0
  27. package/src/features/shared/components/Paginator/Paginator.tsx +4 -0
  28. package/src/features/shared/components/Paginator/paginator.scss +22 -0
  29. package/src/features/shared/components/index.ts +4 -0
  30. package/src/features/shared/hooks/index.ts +1 -0
  31. package/src/features/shared/hooks/useDebounce.ts +16 -2
  32. package/src/features/shared/hooks/usePageItems.ts +88 -0
  33. package/src/features/shared/hooks/useQueryParams.ts +20 -5
  34. package/src/features/shared/utils/constants.ts +2 -0
  35. package/src/features/shared/utils/getValidPage.ts +6 -0
  36. package/src/features/shared/utils/index.ts +2 -1
  37. package/src/features/users/hooks/useDebouncedSearchOrgUnit.ts +2 -1
  38. package/src/pages/buying-policies.tsx +35 -15
  39. package/src/pages/buying-policy-details.tsx +9 -3
package/CHANGELOG.md CHANGED
@@ -17,6 +17,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
17
17
  - Add Buying Policies Page
18
18
  - Add Buying Policy Details Page
19
19
  - Add Buying Policy Drawers
20
+ - Add Buying Policy Integrations
21
+ - Add Pagination Tools
20
22
 
21
23
  ### Added
22
24
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vtex/faststore-plugin-buyer-portal",
3
- "version": "1.0.45",
3
+ "version": "1.0.47",
4
4
  "description": "A plugin for faststore with buyer portal",
5
5
  "main": "index.js",
6
6
  "dependencies": {
@@ -0,0 +1,123 @@
1
+ import { Client } from "../../shared/clients/Client";
2
+ import { getApiUrl } from "../../shared/utils";
3
+ import type { BuyingPolicy } from "../types"; // ajuste conforme necessário
4
+
5
+ class BuyingPoliciesClient extends Client {
6
+ constructor() {
7
+ super(getApiUrl());
8
+ }
9
+
10
+ getBuyingPoliciesByOrgUnitId(
11
+ customerId: string,
12
+ orgUnitId: string,
13
+ cookie: string,
14
+ search?: string,
15
+ page = 1
16
+ ) {
17
+ const params = new URLSearchParams();
18
+ if (search) params.append("search", search);
19
+ if (page && page > 1) params.append("page", String(page));
20
+ const queryString = params.toString();
21
+ const query = queryString ? `?${queryString}` : "";
22
+
23
+ return this.get<{
24
+ data: BuyingPolicy[];
25
+ total: number;
26
+ }>(`customers/${customerId}/units/${orgUnitId}/buying-policies${query}`, {
27
+ headers: {
28
+ Cookie: cookie,
29
+ },
30
+ });
31
+ }
32
+
33
+ getBuyingPolicyById(
34
+ customerId: string,
35
+ orgUnitId: string,
36
+ policyId: string,
37
+ cookie: string
38
+ ) {
39
+ return this.get<BuyingPolicy>(
40
+ `customers/${customerId}/units/${orgUnitId}/buying-policies/${policyId}`,
41
+ {
42
+ headers: {
43
+ Cookie: cookie,
44
+ },
45
+ }
46
+ );
47
+ }
48
+
49
+ addBuyingPolicyToOrgUnit(
50
+ props: {
51
+ customerId: string;
52
+ orgUnitId: string;
53
+ name: string;
54
+ description: string;
55
+ criteria: string;
56
+ action: BuyingPolicy["action"];
57
+ },
58
+ cookie: string
59
+ ) {
60
+ const { customerId, orgUnitId, ...data } = props;
61
+
62
+ return this.post<BuyingPolicy, Omit<BuyingPolicy, "id">>(
63
+ `customers/${customerId}/units/${orgUnitId}/buying-policies`,
64
+ data,
65
+ {
66
+ headers: {
67
+ Cookie: cookie,
68
+ "Content-Type": "application/json",
69
+ },
70
+ }
71
+ );
72
+ }
73
+
74
+ updateBuyingPolicy(
75
+ props: {
76
+ customerId: string;
77
+ orgUnitId: string;
78
+ policyId: string;
79
+ name: string;
80
+ description: string;
81
+ criteria: string;
82
+ action: BuyingPolicy["action"];
83
+ },
84
+ cookie: string
85
+ ) {
86
+ const { customerId, orgUnitId, policyId, ...data } = props;
87
+
88
+ return this.patch<BuyingPolicy, Omit<BuyingPolicy, "id">>(
89
+ `customers/${customerId}/units/${orgUnitId}/buying-policies/${policyId}`,
90
+ data,
91
+ {
92
+ headers: {
93
+ Cookie: cookie,
94
+ "Content-Type": "application/json",
95
+ },
96
+ }
97
+ );
98
+ }
99
+
100
+ removeBuyingPolicyFromOrgUnit(
101
+ props: {
102
+ customerId: string;
103
+ orgUnitId: string;
104
+ policyId: string;
105
+ },
106
+ cookie: string
107
+ ) {
108
+ const { customerId, orgUnitId, policyId } = props;
109
+
110
+ return this.delete<unknown, unknown>(
111
+ `customers/${customerId}/units/${orgUnitId}/buying-policies/${policyId}`,
112
+ undefined,
113
+ {
114
+ headers: {
115
+ Cookie: cookie,
116
+ },
117
+ }
118
+ );
119
+ }
120
+ }
121
+
122
+ const buyingPoliciesClient = new BuyingPoliciesClient();
123
+ export { buyingPoliciesClient };
@@ -19,6 +19,7 @@ import {
19
19
  import { buyingPolicyOptions, type BuyingPolicy } from "../../types";
20
20
  import { OrgUnitInputSearch } from "../../../shared/components/OrgUnitInputSearch/OrgUnitInputSearch";
21
21
  import {
22
+ BUYING_POLICIES_WORKFLOW_TYPES,
22
23
  buyingPolicyDefault,
23
24
  orderFieldsCriteriaOptions,
24
25
  spendingLimitsCriteriaOptions,
@@ -31,7 +32,7 @@ export type BuyingPolicyForm = Omit<BuyingPolicy, "id"> & {
31
32
  id?: string;
32
33
  };
33
34
 
34
- const defaultBuyingPolicyValues: BuyingPolicyForm = buyingPolicyDefault;
35
+ const { id, ...defaultBuyingPolicyValues } = buyingPolicyDefault;
35
36
 
36
37
  export type BasicBuyingPolicyDrawerProps = Omit<
37
38
  BasicDrawerProps,
@@ -91,7 +92,7 @@ export const BasicBuyingPolicyDrawer = ({
91
92
  return false;
92
93
  }
93
94
 
94
- if (valueAction.type === "Sequential approval workflow") {
95
+ if (valueAction.type === BUYING_POLICIES_WORKFLOW_TYPES.SEQUENTIAL) {
95
96
  return (
96
97
  valueAction.levels?.length &&
97
98
  valueAction.levels.every((level) => {
@@ -312,7 +313,7 @@ export const BasicBuyingPolicyDrawer = ({
312
313
  )}
313
314
  />
314
315
 
315
- {action.type === "Bypass all buying policies" && (
316
+ {action.type === BUYING_POLICIES_WORKFLOW_TYPES.BYPASS && (
316
317
  <InputText.Legend>
317
318
  When this policy is applied, all opther buying policy will be
318
319
  ignored
@@ -324,7 +325,7 @@ export const BasicBuyingPolicyDrawer = ({
324
325
  message="Action is required"
325
326
  />
326
327
 
327
- {form.action.type === "Sequential approval workflow" && (
328
+ {form.action.type === BUYING_POLICIES_WORKFLOW_TYPES.SEQUENTIAL && (
328
329
  <>
329
330
  {form.action.levels?.map((level, index) => (
330
331
  <Fragment key={`${level.id}-${index}`}>
@@ -5,17 +5,20 @@ import { useBuyerPortal, useDrawerProps } from "../../../shared/hooks";
5
5
  import { DeleteBuyingPolicyDrawer, UpdateBuyingPolicyDrawer } from "..";
6
6
  import { useRouter } from "next/router";
7
7
  import { buyerPortalRoutes } from "../../../shared/utils/buyerPortalRoutes";
8
+ import type { BuyingPolicy } from "../../types";
8
9
 
9
10
  export type BuyingPolicyDropdownMenuProps = {
10
11
  id: string;
11
12
  name: string;
12
13
  onDelete?: () => void;
14
+ onUpdate?: (buyingPolicy: BuyingPolicy) => void;
13
15
  };
14
16
 
15
17
  export const BuyingPolicyDropdownMenu = ({
16
18
  id,
17
19
  name,
18
20
  onDelete,
21
+ onUpdate,
19
22
  }: BuyingPolicyDropdownMenuProps) => {
20
23
  const { currentContract, currentOrgUnit } = useBuyerPortal();
21
24
 
@@ -76,6 +79,7 @@ export const BuyingPolicyDropdownMenu = ({
76
79
  orgUnitId={currentOrgUnit?.id ?? ""}
77
80
  contractId={currentContract?.id ?? ""}
78
81
  buyingPolicyId={id}
82
+ onUpdate={onUpdate}
79
83
  {...updateBuyingPolicyDrawerProps}
80
84
  isOpen={isUpdateBuyingPolicyDrawerOpen}
81
85
  />
@@ -47,6 +47,8 @@ export const DeleteBuyingPolicyDrawer = ({
47
47
 
48
48
  removeBuyingPolicy({
49
49
  buyingPolicyId: buyingPolicy.id,
50
+ contractId: currentContract?.id ?? "",
51
+ orgUnitId: currentOrgUnit?.id ?? "",
50
52
  });
51
53
  };
52
54
 
@@ -4,12 +4,13 @@ import { useGetBuyingPolicy, useUpdateBuyingPolicy } from "../../hooks";
4
4
  import { buyerPortalRoutes } from "../../../shared/utils/buyerPortalRoutes";
5
5
  import { BasicBuyingPolicyDrawer, type BasicBuyingPolicyDrawerProps } from "..";
6
6
  import { buyingPolicyDefault } from "../../utils";
7
+ import type { BuyingPolicy } from "../../types";
7
8
 
8
9
  export type UpdateBuyingPolicyDrawerProps = Omit<
9
10
  BasicBuyingPolicyDrawerProps,
10
11
  "children" | "initialValues"
11
12
  > & {
12
- onCreate?: () => void;
13
+ onUpdate?: (buyingPolicy: BuyingPolicy) => void;
13
14
  orgUnitId: string;
14
15
  contractId: string;
15
16
  buyingPolicyId: string;
@@ -17,7 +18,7 @@ export type UpdateBuyingPolicyDrawerProps = Omit<
17
18
 
18
19
  export const UpdateBuyingPolicyDrawer = ({
19
20
  close,
20
- onCreate,
21
+ onUpdate,
21
22
  orgUnitId,
22
23
  contractId,
23
24
  buyingPolicyId,
@@ -32,7 +33,7 @@ export const UpdateBuyingPolicyDrawer = ({
32
33
  const { pushToast } = useUI();
33
34
  const router = useRouter();
34
35
 
35
- const handleUpdateBuyingPolicySuccess = () => {
36
+ const handleUpdateBuyingPolicySuccess = (buyingPolicy: BuyingPolicy) => {
36
37
  pushToast({
37
38
  message: "Buying policy updated successfully",
38
39
  status: "INFO",
@@ -55,16 +56,17 @@ export const UpdateBuyingPolicyDrawer = ({
55
56
  </button>
56
57
  ),
57
58
  });
58
- onCreate?.();
59
+ onUpdate?.(buyingPolicy);
59
60
  close();
60
61
  };
61
62
 
62
63
  const { updateBuyingPolicy, isUpdateBuyingPolicyLoading } =
63
64
  useUpdateBuyingPolicy({
64
65
  onSuccess: handleUpdateBuyingPolicySuccess,
65
- onError: () => {
66
+ onError: (err) => {
67
+ // TODO: Handle error
66
68
  pushToast({
67
- message: "Error updating buying policy",
69
+ message: "Error adding buying policy",
68
70
  status: "ERROR",
69
71
  });
70
72
  },
@@ -0,0 +1 @@
1
+ @import "../BasicBuyingPolicyDrawer/basic-buying-policy-drawer.scss";
@@ -0,0 +1,32 @@
1
+ import { type QueryOptions, useQuery } from "../../shared/hooks";
2
+ import { getBuyingPoliciesService } from "../services";
3
+
4
+ export const useGetBuyingPolicies = (
5
+ {
6
+ contractId,
7
+ orgUnitId,
8
+ search,
9
+ page,
10
+ }: { orgUnitId: string; contractId: string; search?: string; page: number },
11
+ options?: QueryOptions<AwaitedType<typeof getBuyingPoliciesService>>
12
+ ) => {
13
+ const { data, error, isLoading, refetch } = useQuery(
14
+ ["api/buying-policies", orgUnitId, contractId, page].join("/"),
15
+ ({ cookie }) =>
16
+ getBuyingPoliciesService({
17
+ orgUnitId,
18
+ contractId,
19
+ cookie,
20
+ page,
21
+ search,
22
+ }),
23
+ options
24
+ );
25
+
26
+ return {
27
+ buyingPolicies: data,
28
+ hasBuyingPoliciesError: error,
29
+ isBuyingPoliciesLoading: isLoading,
30
+ refetchBuyingPolicies: refetch,
31
+ };
32
+ };
@@ -2,7 +2,9 @@ import { FinanceTabsLayout, GlobalLayout } from "../../../shared/layouts";
2
2
  import { Table } from "../../../shared/components/Table/Table";
3
3
  import { getTableColumns } from "../../../shared/components/Table/utils/tableColumns";
4
4
  import { HeaderInside, InternalSearch } from "../../../shared/components";
5
+ import { Paginator } from "../../../shared/components";
5
6
  import {
7
+ usePageItems,
6
8
  useBuyerPortal,
7
9
  useDrawerProps,
8
10
  useQueryParams,
@@ -13,18 +15,33 @@ import { BuyingPolicyDropdownMenu } from "../../components";
13
15
  import { AddBuyingPolicyDrawer } from "../../components/AddBuyingPolicyDrawer/AddBuyingPolicyDrawer";
14
16
 
15
17
  export type BuyingPoliciesLayoutProps = {
16
- data: { buyingPolicies: BuyingPolicy[] } | null;
18
+ buyingPolicies: BuyingPolicy[];
19
+ total: number;
17
20
  search: string;
21
+ page: number;
18
22
  };
19
23
 
20
24
  export const BuyingPoliciesLayout = ({
21
- data,
25
+ buyingPolicies: initialBuyingPolicies,
22
26
  search,
27
+ total,
28
+ page,
23
29
  }: BuyingPoliciesLayoutProps) => {
24
- const { setQueryString, removeQueryString } = useQueryParams();
25
-
26
30
  const { currentContract, currentOrgUnit } = useBuyerPortal();
27
31
 
32
+ const {
33
+ isLoading,
34
+ items: buyingPolicies,
35
+ searchTerm,
36
+ setSearchTerm,
37
+ increasePage,
38
+ setItems: setBuyingPolicies,
39
+ } = usePageItems<BuyingPolicy>({
40
+ initialItems: initialBuyingPolicies,
41
+ search,
42
+ page,
43
+ });
44
+
28
45
  const {
29
46
  isOpen: isAddBuyingPolicyDrawerOpen,
30
47
  open: openAddBuyingPolicyDrawer,
@@ -34,58 +51,73 @@ export const BuyingPoliciesLayout = ({
34
51
  return (
35
52
  <GlobalLayout>
36
53
  <FinanceTabsLayout pageName="Finance and Compliance">
37
- <>
38
- <section data-fs-buying-policies-section>
39
- <HeaderInside title="Buying Policies">
40
- <HeaderInside.Button onClick={openAddBuyingPolicyDrawer} />
41
- </HeaderInside>
54
+ <section data-fs-buying-policies-section>
55
+ <HeaderInside title="Buying Policies">
56
+ <HeaderInside.Button onClick={openAddBuyingPolicyDrawer} />
57
+ </HeaderInside>
58
+
59
+ <div data-fs-buying-policies-filter>
60
+ <InternalSearch
61
+ defaultValue={searchTerm}
62
+ textSearch={setSearchTerm}
63
+ />
64
+ <Paginator.Counter
65
+ total={total}
66
+ itemsLength={buyingPolicies.length}
67
+ />
68
+ </div>
42
69
 
43
- <div data-fs-buying-policies-filter>
44
- <InternalSearch
45
- defaultValue={search}
46
- textSearch={(searchTerm) => {
47
- searchTerm
48
- ? setQueryString("search", searchTerm)
49
- : removeQueryString("search");
50
- }}
51
- />
52
- </div>
70
+ <Table>
71
+ <Table.Head columns={getTableColumns({ withType: false })} />
72
+ <Table.Body>
73
+ {buyingPolicies.map((buyingPolicy) => (
74
+ <Table.Row
75
+ key={buyingPolicy.id}
76
+ title={buyingPolicy.name}
77
+ iconName="Rebase"
78
+ iconSize={20}
79
+ href={buyerPortalRoutes.buyingPolicyDetails({
80
+ contractId: currentContract?.id ?? "",
81
+ orgUnitId: currentOrgUnit?.id ?? "",
82
+ buyingPolicyId: buyingPolicy.id,
83
+ })}
84
+ dropdownMenu={
85
+ <BuyingPolicyDropdownMenu
86
+ id={buyingPolicy.id}
87
+ name={buyingPolicy.name}
88
+ />
89
+ }
90
+ />
91
+ ))}
92
+ </Table.Body>
93
+ </Table>
53
94
 
54
- <Table>
55
- <Table.Head columns={getTableColumns({ withType: false })} />
56
- <Table.Body>
57
- {data?.buyingPolicies.map((buyingPolicy) => (
58
- <Table.Row
59
- key={buyingPolicy.id}
60
- title={buyingPolicy.name}
61
- iconName="Rebase"
62
- iconSize={20}
63
- href={buyerPortalRoutes.buyingPolicyDetails({
64
- contractId: currentContract?.id ?? "",
65
- orgUnitId: currentOrgUnit?.id ?? "",
66
- buyingPolicyId: buyingPolicy.id,
67
- })}
68
- dropdownMenu={
69
- <BuyingPolicyDropdownMenu
70
- id={buyingPolicy.id}
71
- name={buyingPolicy.name}
72
- />
73
- }
74
- />
75
- ))}
76
- </Table.Body>
77
- </Table>
78
- </section>
95
+ <div data-fs-bp-buying-policies-paginator>
96
+ {total > buyingPolicies.length ? (
97
+ <Paginator.NextPageButton
98
+ onClick={increasePage}
99
+ disabled={isLoading}
100
+ >
101
+ {isLoading ? "Loading" : "Load More"}
102
+ </Paginator.NextPageButton>
103
+ ) : (
104
+ <span />
105
+ )}
79
106
 
80
- {isAddBuyingPolicyDrawerOpen && (
81
- <AddBuyingPolicyDrawer
82
- orgUnitId={currentOrgUnit?.id ?? ""}
83
- contractId={currentContract?.id ?? ""}
84
- isOpen={isAddBuyingPolicyDrawerOpen}
85
- {...addBuyingPolicyDrawerProps}
107
+ <Paginator.Counter
108
+ total={total}
109
+ itemsLength={buyingPolicies.length}
86
110
  />
87
- )}
88
- </>
111
+ </div>
112
+ </section>
113
+ {isAddBuyingPolicyDrawerOpen && (
114
+ <AddBuyingPolicyDrawer
115
+ orgUnitId={currentOrgUnit?.id ?? ""}
116
+ contractId={currentContract?.id ?? ""}
117
+ isOpen={isAddBuyingPolicyDrawerOpen}
118
+ {...addBuyingPolicyDrawerProps}
119
+ />
120
+ )}
89
121
  </FinanceTabsLayout>
90
122
  </GlobalLayout>
91
123
  );
@@ -23,4 +23,11 @@
23
23
  color: #5c5c5c;
24
24
  margin-bottom: var(--fs-spacing-1);
25
25
  }
26
+
27
+ [data-fs-bp-buying-policies-paginator] {
28
+ display: flex;
29
+ align-items: center;
30
+ justify-content: space-between;
31
+ margin-top: var(--fs-spacing-0);
32
+ }
26
33
  }
@@ -1,10 +1,14 @@
1
1
  import { FinanceTabsLayout, GlobalLayout } from "../../../shared/layouts";
2
2
  import { BasicDropdownMenu, HeaderInside } from "../../../shared/components";
3
- import { useBuyerPortal } from "../../../shared/hooks";
3
+ import { useBuyerPortal, useDrawerProps } from "../../../shared/hooks";
4
4
  import type { BuyingPolicy } from "../../types";
5
5
  import { buyerPortalRoutes } from "../../../shared/utils/buyerPortalRoutes";
6
6
  import { Dropdown } from "@faststore/ui";
7
- import { BuyingPolicyDropdownMenu } from "../../components";
7
+ import {
8
+ BuyingPolicyDropdownMenu,
9
+ UpdateBuyingPolicyDrawer,
10
+ } from "../../components";
11
+ import { useRouter } from "next/router";
8
12
 
9
13
  export type BuyingPolicyDetailsLayoutProps = {
10
14
  data: { buyingPolicy: BuyingPolicy | null };
@@ -15,6 +19,14 @@ export const BuyingPolicyDetailsLayout = ({
15
19
  }: BuyingPolicyDetailsLayoutProps) => {
16
20
  const { currentContract, currentOrgUnit } = useBuyerPortal();
17
21
 
22
+ const { reload } = useRouter();
23
+
24
+ const {
25
+ open: openUpdateBuyingPolicyDrawerProps,
26
+ isOpen: isUpdateBuyingPolicyDrawerOpen,
27
+ ...updateBuyingPolicyDrawerProps
28
+ } = useDrawerProps();
29
+
18
30
  return (
19
31
  <GlobalLayout>
20
32
  <FinanceTabsLayout pageName="Finance and Compliance">
@@ -37,7 +49,11 @@ export const BuyingPolicyDetailsLayout = ({
37
49
 
38
50
  <div data-fs-buying-policy-details-line>
39
51
  <span data-fs-buying-policy-details-title>Settings</span>
40
- <button type="button" data-fs-buying-policy-details-edit-button>
52
+ <button
53
+ type="button"
54
+ data-fs-buying-policy-details-edit-button
55
+ onClick={openUpdateBuyingPolicyDrawerProps}
56
+ >
41
57
  Edit
42
58
  </button>
43
59
  </div>
@@ -82,6 +98,16 @@ export const BuyingPolicyDetailsLayout = ({
82
98
  </span>
83
99
  </div>
84
100
  </section>
101
+ {isUpdateBuyingPolicyDrawerOpen && (
102
+ <UpdateBuyingPolicyDrawer
103
+ orgUnitId={currentOrgUnit?.id ?? ""}
104
+ contractId={currentContract?.id ?? ""}
105
+ buyingPolicyId={buyingPolicy?.id ?? ""}
106
+ onUpdate={reload}
107
+ {...updateBuyingPolicyDrawerProps}
108
+ isOpen={isUpdateBuyingPolicyDrawerOpen}
109
+ />
110
+ )}
85
111
  </FinanceTabsLayout>
86
112
  </GlobalLayout>
87
113
  );
@@ -2,6 +2,7 @@
2
2
 
3
3
  @import "../../../shared/layouts/FinanceTabsLayout/finance-tabs-layout.scss";
4
4
  @import "../../components/BuyingPolicyDropdownMenu/buying-policy-dropdown-menu.scss";
5
+ @import "../../components/UpdateBuyingPolicyDrawer/update-buying-policy-drawer.scss";
5
6
 
6
7
  [data-fs-buying-policy-details-section] {
7
8
  @import "../../../shared/components/HeaderInside/header-inside.scss";
@@ -16,7 +16,7 @@ export const buyingPoliciesData: BuyingPolicy[] = [
16
16
  description: "Policy for handling bulk orders with discounts.",
17
17
  criteria: "quantity > 100",
18
18
  action: {
19
- type: "Sequential approval workflow",
19
+ type: "Sequential workflow",
20
20
  levels: [
21
21
  { id: "1", name: "Manager" },
22
22
  { id: "2", name: "Director" },
@@ -48,7 +48,7 @@ export const buyingPoliciesData: BuyingPolicy[] = [
48
48
  description: "Handles seasonal promotions and discounts.",
49
49
  criteria: "season == 'holiday'",
50
50
  action: {
51
- type: "Sequential approval workflow",
51
+ type: "Sequential workflow",
52
52
  levels: [
53
53
  { id: "1", name: "Marketing Manager" },
54
54
  { id: "2", name: "Finance Director" },
@@ -61,7 +61,7 @@ export const buyingPoliciesData: BuyingPolicy[] = [
61
61
  description: "Requires approval for high-value purchases.",
62
62
  criteria: "purchaseAmount > 10000",
63
63
  action: {
64
- type: "Sequential approval workflow",
64
+ type: "Sequential workflow",
65
65
  levels: [
66
66
  { id: "1", name: "Manager" },
67
67
  { id: "2", name: "Finance Director" },
@@ -93,7 +93,7 @@ export const buyingPoliciesData: BuyingPolicy[] = [
93
93
  description: "Custom rules for corporate accounts.",
94
94
  criteria: "accountType == 'corporate'",
95
95
  action: {
96
- type: "Sequential approval workflow",
96
+ type: "Sequential workflow",
97
97
  levels: [
98
98
  { id: "1", name: "Account Manager" },
99
99
  { id: "2", name: "Finance Director" },
@@ -115,7 +115,7 @@ export const buyingPoliciesData: BuyingPolicy[] = [
115
115
  description: "Rules for international orders.",
116
116
  criteria: "shippingCountry != 'domestic'",
117
117
  action: {
118
- type: "Sequential approval workflow",
118
+ type: "Sequential workflow",
119
119
  levels: [
120
120
  { id: "1", name: "Logistics Manager" },
121
121
  { id: "2", name: "Finance Director" },
@@ -155,7 +155,7 @@ export const buyingPoliciesData: BuyingPolicy[] = [
155
155
  description: "Encourages eco-friendly purchases.",
156
156
  criteria: "productType == 'eco-friendly'",
157
157
  action: {
158
- type: "Sequential approval workflow",
158
+ type: "Sequential workflow",
159
159
  levels: [
160
160
  { id: "1", name: "Sustainability Manager" },
161
161
  { id: "2", name: "CEO" },
@@ -1,3 +1,4 @@
1
+ import { buyingPoliciesClient } from "../clients/BuyingPoliciesClient";
1
2
  import type { BuyingPolicy } from "../types";
2
3
 
3
4
  export type AddBuyingPolicyServiceProps = {
@@ -9,7 +10,16 @@ export type AddBuyingPolicyServiceProps = {
9
10
 
10
11
  export const addBuyingPolicyService = async ({
11
12
  buyingPolicy,
13
+ contractId,
14
+ orgUnitId,
12
15
  cookie,
13
16
  }: AddBuyingPolicyServiceProps) => {
14
- return null as unknown as BuyingPolicy; //TODO: Placeholder for the actual implementation
17
+ return buyingPoliciesClient.addBuyingPolicyToOrgUnit(
18
+ {
19
+ ...buyingPolicy,
20
+ customerId: contractId,
21
+ orgUnitId,
22
+ },
23
+ cookie
24
+ );
15
25
  };
@@ -0,0 +1,25 @@
1
+ import { buyingPoliciesClient } from "../clients/BuyingPoliciesClient";
2
+
3
+ export type GetBuyingPoliciesServiceProps = {
4
+ orgUnitId: string;
5
+ contractId: string;
6
+ cookie: string;
7
+ search?: string;
8
+ page?: number;
9
+ };
10
+
11
+ export const getBuyingPoliciesService = async ({
12
+ search,
13
+ page = 1,
14
+ contractId,
15
+ cookie,
16
+ orgUnitId,
17
+ }: GetBuyingPoliciesServiceProps) => {
18
+ return buyingPoliciesClient.getBuyingPoliciesByOrgUnitId(
19
+ contractId,
20
+ orgUnitId,
21
+ cookie,
22
+ search,
23
+ page
24
+ );
25
+ };
@@ -1,4 +1,4 @@
1
- import { buyingPoliciesData } from "../mocks";
1
+ import { buyingPoliciesClient } from "../clients/BuyingPoliciesClient";
2
2
 
3
3
  export type GetBuyingPolicyServiceProps = {
4
4
  orgUnitId: string;
@@ -11,9 +11,12 @@ export const getBuyingPolicyService = async ({
11
11
  orgUnitId,
12
12
  contractId,
13
13
  buyingPolicyId,
14
+ cookie,
14
15
  }: GetBuyingPolicyServiceProps) => {
15
- // biome-ignore lint/style/noNonNullAssertion: <explanation>
16
- return buyingPoliciesData.find((buyingPolicy) => {
17
- return buyingPolicy.id === buyingPolicyId;
18
- })!;
16
+ return buyingPoliciesClient.getBuyingPolicyById(
17
+ contractId,
18
+ orgUnitId,
19
+ buyingPolicyId,
20
+ cookie
21
+ );
19
22
  };
@@ -17,3 +17,8 @@ export {
17
17
  getBuyingPolicyService,
18
18
  type GetBuyingPolicyServiceProps,
19
19
  } from "./get-buying-policy.service";
20
+
21
+ export {
22
+ getBuyingPoliciesService,
23
+ type GetBuyingPoliciesServiceProps,
24
+ } from "./get-buying-policies.service";
@@ -1,11 +1,24 @@
1
+ import { buyingPoliciesClient } from "../clients/BuyingPoliciesClient";
2
+
1
3
  export type RemoveBuyingPolicyServiceProps = {
4
+ contractId: string;
5
+ orgUnitId: string;
2
6
  buyingPolicyId: string;
3
7
  cookie: string;
4
8
  };
5
9
 
6
10
  export const removeBuyingPolicyService = async ({
11
+ contractId,
12
+ orgUnitId,
7
13
  buyingPolicyId,
8
14
  cookie,
9
15
  }: RemoveBuyingPolicyServiceProps) => {
10
- return null; //TODO: Placeholder for the actual implementation
16
+ return buyingPoliciesClient.removeBuyingPolicyFromOrgUnit(
17
+ {
18
+ customerId: contractId,
19
+ orgUnitId: orgUnitId,
20
+ policyId: buyingPolicyId,
21
+ },
22
+ cookie
23
+ );
11
24
  };
@@ -1,3 +1,4 @@
1
+ import { buyingPoliciesClient } from "../clients/BuyingPoliciesClient";
1
2
  import type { BuyingPolicy } from "../types";
2
3
 
3
4
  export type UpdateBuyingPolicyServiceProps = {
@@ -13,6 +14,15 @@ export const updateBuyingPolicyService = async ({
13
14
  buyingPolicy,
14
15
  contractId,
15
16
  orgUnitId,
17
+ cookie,
16
18
  }: UpdateBuyingPolicyServiceProps) => {
17
- return;
19
+ return buyingPoliciesClient.updateBuyingPolicy(
20
+ {
21
+ ...buyingPolicy,
22
+ customerId: contractId,
23
+ orgUnitId,
24
+ policyId: buyingPolicyId,
25
+ },
26
+ cookie
27
+ );
18
28
  };
@@ -1,7 +1,12 @@
1
+ import {
2
+ BUYING_POLICIES_WORKFLOW_TYPES,
3
+ type BuyingPoliciesWorkflowType,
4
+ } from "../utils";
5
+
1
6
  export const buyingPolicyOptions = [
2
- "Deny order",
3
- "Sequential approval workflow",
4
- "Bypass all buying policies",
7
+ BUYING_POLICIES_WORKFLOW_TYPES.DENY,
8
+ BUYING_POLICIES_WORKFLOW_TYPES.SEQUENTIAL,
9
+ BUYING_POLICIES_WORKFLOW_TYPES.BYPASS,
5
10
  ] as const;
6
11
 
7
12
  export type BuyingPolicy = {
@@ -10,7 +15,7 @@ export type BuyingPolicy = {
10
15
  description: string;
11
16
  criteria: string;
12
17
  action: {
13
- type: (typeof buyingPolicyOptions)[number];
18
+ type: BuyingPoliciesWorkflowType;
14
19
  levels?: {
15
20
  id: string;
16
21
  name?: string;
@@ -0,0 +1,8 @@
1
+ export const BUYING_POLICIES_WORKFLOW_TYPES = {
2
+ SEQUENTIAL: "Sequential workflow",
3
+ DENY: "Deny order",
4
+ BYPASS: "Bypass all buying policies",
5
+ } as const;
6
+
7
+ export type BuyingPoliciesWorkflowType =
8
+ (typeof BUYING_POLICIES_WORKFLOW_TYPES)[keyof typeof BUYING_POLICIES_WORKFLOW_TYPES];
@@ -1,4 +1,5 @@
1
1
  import type { BuyingPolicy } from "../types";
2
+ import { BUYING_POLICIES_WORKFLOW_TYPES } from "./buyingPoliciesWorkflowTypes";
2
3
 
3
4
  export const buyingPolicyDefault: BuyingPolicy = {
4
5
  id: "",
@@ -6,7 +7,7 @@ export const buyingPolicyDefault: BuyingPolicy = {
6
7
  description: "",
7
8
  criteria: "",
8
9
  action: {
9
- type: "Deny order",
10
+ type: BUYING_POLICIES_WORKFLOW_TYPES.DENY,
10
11
  levels: [
11
12
  {
12
13
  id: "",
@@ -14,4 +15,4 @@ export const buyingPolicyDefault: BuyingPolicy = {
14
15
  },
15
16
  ],
16
17
  },
17
- };
18
+ } as const;
@@ -1,3 +1,7 @@
1
1
  export { buyingPolicyDefault } from "./buyingPolicyDefault";
2
2
  export { orderFieldsCriteriaOptions } from "./orderFieldsCriteriaOptions";
3
3
  export { spendingLimitsCriteriaOptions } from "./spendingLimitsCriteriaOptions";
4
+ export {
5
+ BUYING_POLICIES_WORKFLOW_TYPES,
6
+ BuyingPoliciesWorkflowType,
7
+ } from "./buyingPoliciesWorkflowTypes";
@@ -0,0 +1,10 @@
1
+ export type CounterProps = {
2
+ total: number;
3
+ itemsLength: number;
4
+ };
5
+
6
+ export const Counter = ({ total, itemsLength }: CounterProps) => (
7
+ <span data-fs-bp-paginator-counter>
8
+ {itemsLength} of {total}
9
+ </span>
10
+ );
@@ -0,0 +1,15 @@
1
+ import type { ComponentProps } from "react";
2
+
3
+ export type NextPageButtonProps = ComponentProps<"button">;
4
+
5
+ export const NextPageButton = ({
6
+ type = "button",
7
+ children = "Next Page",
8
+ ...otherProps
9
+ }: NextPageButtonProps) => {
10
+ return (
11
+ <button data-fs-paginator-next-page-button type="button" {...otherProps}>
12
+ {children}
13
+ </button>
14
+ );
15
+ };
@@ -0,0 +1,4 @@
1
+ import { Counter } from "./Counter";
2
+ import { NextPageButton } from "./NextPageButton";
3
+
4
+ export const Paginator = { Counter, NextPageButton };
@@ -0,0 +1,22 @@
1
+ [data-fs-bp-paginator-counter] {
2
+ color: #5c5c5c;
3
+ font-weight: var(--fs-text-weight-medium);
4
+ font-size: var(--fs-text-size-1);
5
+ line-height: calc(var(--fs-spacing-3) - var(--fs-spacing-0));
6
+ text-align: right;
7
+ }
8
+
9
+ [data-fs-paginator-next-page-button] {
10
+ color: #0366dd;
11
+ font-weight: var(--fs-text-weight-semibold);
12
+ font-size: var(--fs-text-size-1);
13
+ line-height: var(--fs-spacing-3);
14
+ text-align: center;
15
+ cursor: pointer;
16
+ padding: var(--fs-spacing-0);
17
+ border-radius: var(--fs-spacing-0);
18
+
19
+ &:hover {
20
+ background-color: #f5f5f5;
21
+ }
22
+ }
@@ -71,3 +71,7 @@ export {
71
71
  LevelDivider,
72
72
  type LevelDividerProps,
73
73
  } from "./LevelDivider/LevelDivider";
74
+
75
+ export { Paginator } from "./Paginator/Paginator";
76
+ export { CounterProps as PaginatorCounterProps } from "./Paginator/Counter";
77
+ export { NextPageButtonProps as PaginatorNextPageButtonProps } from "./Paginator/NextPageButton";
@@ -10,3 +10,4 @@ export { useQueryParams } from "./useQueryParams";
10
10
  export { useDebounce } from "./useDebounce";
11
11
  export { useAddToScope } from "./useAddToScope";
12
12
  export { useRemoveFromScope } from "./useRemoveFromScope";
13
+ export { usePageItems, type UsePageItemsProps } from "./usePageItems";
@@ -1,11 +1,25 @@
1
- import { useEffect, useState } from "react";
1
+ import { useEffect, useRef, useState } from "react";
2
2
 
3
- export function useDebounce<T>(value: T, delay: number): T {
3
+ export function useDebounce<T>(
4
+ value: T,
5
+ delay: number,
6
+ options?: { onDebounce?: (value: T) => void }
7
+ ): T {
8
+ const { onDebounce } = options || {};
4
9
  const [debouncedValue, setDebouncedValue] = useState(value);
5
10
 
11
+ const firstChangeRef = useRef(true);
12
+
6
13
  useEffect(() => {
7
14
  const handler = setTimeout(() => {
15
+ if (firstChangeRef.current) {
16
+ firstChangeRef.current = false;
17
+
18
+ return;
19
+ }
8
20
  setDebouncedValue(value);
21
+
22
+ onDebounce?.(value);
9
23
  }, delay);
10
24
 
11
25
  return () => {
@@ -0,0 +1,88 @@
1
+ import { useState, useRef, useEffect, useCallback } from "react";
2
+ import { useDebounce } from "./useDebounce";
3
+ import { useQueryParams } from "./useQueryParams";
4
+ import { DEBOUNCE_TIMEOUT } from "../utils";
5
+
6
+ export type UsePageItemsProps<T> = {
7
+ initialItems: T[];
8
+ search?: string;
9
+ page?: number;
10
+ };
11
+
12
+ export const usePageItems = <T>({
13
+ initialItems,
14
+ search,
15
+ page = 1,
16
+ }: UsePageItemsProps<T>) => {
17
+ const [items, setItems] = useState<T[]>(initialItems ?? []);
18
+
19
+ const [searchTerm, setSearchTerm] = useState(search ?? "");
20
+
21
+ const [isLoading, setIsLoading] = useState(false);
22
+
23
+ useDebounce(searchTerm, DEBOUNCE_TIMEOUT, {
24
+ onDebounce: (value) => {
25
+ setIsLoading(true);
26
+ if (value) {
27
+ setQueryStrings({
28
+ search: value,
29
+ page: "1",
30
+ });
31
+ } else {
32
+ setQueryStrings({
33
+ search: null,
34
+ page: "1",
35
+ });
36
+ }
37
+ },
38
+ });
39
+
40
+ const { setQueryStrings, setQueryString } = useQueryParams();
41
+
42
+ const firstSearchRef = useRef(true);
43
+
44
+ useEffect(() => {
45
+ setItems([]);
46
+ }, [search]);
47
+
48
+ useEffect(() => {
49
+ if (firstSearchRef.current) {
50
+ setItems(initialItems);
51
+ firstSearchRef.current = false;
52
+ return;
53
+ }
54
+
55
+ setItems((prev) => [...prev, ...initialItems]);
56
+ setIsLoading(false);
57
+ }, [initialItems]);
58
+
59
+ const increasePage = useCallback(() => {
60
+ setQueryString("page", (page + 1).toString());
61
+ setIsLoading(true);
62
+ }, [page, setQueryString]);
63
+
64
+ const decreasePage = useCallback(() => {
65
+ setQueryString("page", (page - 1).toString());
66
+ setIsLoading(true);
67
+ }, [setQueryString, page]);
68
+
69
+ const setPage = useCallback(
70
+ (newPage: number) => {
71
+ setQueryString("page", newPage.toString());
72
+ setIsLoading(true);
73
+ },
74
+ [setQueryString]
75
+ );
76
+
77
+ return {
78
+ items,
79
+ setItems,
80
+ searchTerm,
81
+ setSearchTerm,
82
+ isLoading,
83
+ setIsLoading,
84
+ increasePage,
85
+ decreasePage,
86
+ setPage,
87
+ };
88
+ };
@@ -19,9 +19,24 @@ export const useQueryParams = () => {
19
19
 
20
20
  const setQueryString = useCallback(
21
21
  (prop: string, value: string) => {
22
- router.push(pathname + "?" + createQueryString(prop, value));
22
+ router.push(`${pathname}?${createQueryString(prop, value)}`);
23
23
  },
24
- [router]
24
+ [router, pathname, createQueryString]
25
+ );
26
+
27
+ const setQueryStrings = useCallback(
28
+ (props: Record<string, string | null>) => {
29
+ const params = new URLSearchParams(searchParams.toString());
30
+ for (const [key, value] of Object.entries(props)) {
31
+ if (value === null) {
32
+ params.delete(key);
33
+ } else {
34
+ params.set(key, value);
35
+ }
36
+ }
37
+ router.push(`${pathname}?${params.toString()}`);
38
+ },
39
+ [router, pathname, searchParams]
25
40
  );
26
41
 
27
42
  const removeQueryString = useCallback(
@@ -29,10 +44,10 @@ export const useQueryParams = () => {
29
44
  const params = new URLSearchParams(searchParams.toString());
30
45
  params.delete(prop);
31
46
 
32
- router.push(pathname + "?" + params.toString());
47
+ router.push(`${pathname}?${params.toString()}`);
33
48
  },
34
- [router]
49
+ [router, pathname, searchParams]
35
50
  );
36
51
 
37
- return { setQueryString, removeQueryString };
52
+ return { setQueryString, removeQueryString, setQueryStrings };
38
53
  };
@@ -5,3 +5,5 @@ export const API_URL = (checkoutUrl: string, operation?: string) =>
5
5
  // DEV URL - CHANGE BEFORE MERGE
6
6
  // export const API_URL = (checkoutUrl?: string, operation?: string) =>
7
7
  // `https://everton--b2bfaststoredev.myvtex.com/_v/buyer-portal/${operation}`;
8
+
9
+ export const DEBOUNCE_TIMEOUT = 500;
@@ -0,0 +1,6 @@
1
+ export function getValidPage(pageValue: unknown): number {
2
+ const parsed = Number(pageValue);
3
+ return Number.isNaN(parsed) || !Number.isFinite(parsed) || parsed < 1
4
+ ? 1
5
+ : Math.floor(parsed);
6
+ }
@@ -1,7 +1,7 @@
1
1
  export { addressLabelToPropMapping } from "./addresLabelToPropMapping";
2
2
  export { getApiUrl, getPostalCodeApiUrl } from "./api";
3
3
  export { compareItems } from "./compareItems";
4
- export { API_URL, AUT_COOKIE_KEY } from "./constants";
4
+ export { API_URL, AUT_COOKIE_KEY, DEBOUNCE_TIMEOUT } from "./constants";
5
5
  export {
6
6
  getAuthCookie,
7
7
  getCookieAsString,
@@ -26,3 +26,4 @@ export { getContractSettingsLinks } from "./getContractSettingsLinks";
26
26
  export { getOrganizationSettingsLinks } from "./getOrganizationSettingsLinks";
27
27
  export { maskCardNumber, maskCVV, maskExpirationDate } from "./creditCard";
28
28
  export { getFinanceSettingsLinks } from "./getFinanceSettingsLinks";
29
+ export { getValidPage } from "./getValidPage";
@@ -1,8 +1,9 @@
1
1
  import { useSearchOrgUnits } from "../../org-units/hooks";
2
2
  import { useDebounce } from "../../shared/hooks";
3
+ import { DEBOUNCE_TIMEOUT } from "../../shared/utils";
3
4
 
4
5
  export const useDebouncedSearchOrgUnit = (searchTerm = "") => {
5
- const debouncedSearchTerm = useDebounce(searchTerm, 500);
6
+ const debouncedSearchTerm = useDebounce(searchTerm, DEBOUNCE_TIMEOUT);
6
7
  const { searchedOrgUnits } = useSearchOrgUnits(debouncedSearchTerm);
7
8
 
8
9
  if (searchTerm === "") {
@@ -1,15 +1,10 @@
1
- import {
2
- getAddressesService,
3
- type GetAddressesServiceProps,
4
- } from "../features/addresses/services";
1
+ import type { GetAddressesServiceProps } from "../features/addresses/services";
5
2
 
6
3
  import {
7
4
  type ClientContext,
8
5
  getClientContext,
9
- getCustomerIdFromCookieServerSide,
6
+ getValidPage,
10
7
  } from "../features/shared/utils";
11
- import type { AddressData } from "../features/addresses/types";
12
- import { AddressLayout } from "../features/addresses/layouts";
13
8
  import { BuyerPortalProvider } from "../features/shared/components";
14
9
  import type { LoaderData } from "../features/shared/types";
15
10
  import type { OrgUnitBasicData } from "../features/org-units/types";
@@ -19,12 +14,14 @@ import type { UserData } from "../features/users/types";
19
14
  import type { ContractData } from "../features/contracts/types";
20
15
  import { getContractDetailsService } from "../features/contracts/services";
21
16
  import type { BuyingPolicy } from "../features/buying-policies/types";
22
- import { buyingPoliciesData } from "../features/buying-policies/mocks";
23
17
  import { BuyingPoliciesLayout } from "../features/buying-policies/layouts";
18
+ import { getBuyingPoliciesService } from "../features/buying-policies/services";
24
19
 
25
20
  export type BuyingPoliciesPageData = {
26
- data: { buyingPolicies: BuyingPolicy[] };
21
+ buyingPolicies: BuyingPolicy[];
22
+ total: number;
27
23
  search: string;
24
+ page: number;
28
25
  context: {
29
26
  currentContract: ContractData | null;
30
27
  clientContext: ClientContext;
@@ -37,12 +34,15 @@ export type BuyingPoliciesPageQuery = GetAddressesServiceProps & {
37
34
  orgUnitId: string;
38
35
  contractId: string;
39
36
  search?: string;
37
+ page?: string;
40
38
  };
41
39
 
42
40
  export async function loader(
43
41
  data: LoaderData<BuyingPoliciesPageQuery>
44
42
  ): Promise<BuyingPoliciesPageData> {
45
- const { contractId, orgUnitId, search = "" } = data.query;
43
+ const { contractId, orgUnitId, search = "", page: pageString } = data.query;
44
+
45
+ const page = getValidPage(pageString);
46
46
 
47
47
  const { cookie, userId, ...clientContext } = await getClientContext(data);
48
48
 
@@ -58,11 +58,20 @@ export async function loader(
58
58
  cookie,
59
59
  });
60
60
 
61
+ const { data: buyingPolicies = [], total = 0 } =
62
+ await getBuyingPoliciesService({
63
+ orgUnitId,
64
+ contractId,
65
+ cookie,
66
+ page,
67
+ search,
68
+ });
69
+
61
70
  return {
62
- data: {
63
- buyingPolicies: buyingPoliciesData,
64
- },
71
+ buyingPolicies,
72
+ total,
65
73
  search,
74
+ page,
66
75
  context: {
67
76
  clientContext: { cookie, userId, ...clientContext },
68
77
  currentOrgUnit,
@@ -72,9 +81,20 @@ export async function loader(
72
81
  };
73
82
  }
74
83
 
75
- const AddressPage = ({ data, search, context }: BuyingPoliciesPageData) => (
84
+ const AddressPage = ({
85
+ buyingPolicies,
86
+ search,
87
+ page,
88
+ context,
89
+ total,
90
+ }: BuyingPoliciesPageData) => (
76
91
  <BuyerPortalProvider {...context}>
77
- <BuyingPoliciesLayout data={data} search={search} />
92
+ <BuyingPoliciesLayout
93
+ buyingPolicies={buyingPolicies}
94
+ search={search}
95
+ page={page}
96
+ total={total}
97
+ />
78
98
  </BuyerPortalProvider>
79
99
  );
80
100
 
@@ -12,6 +12,7 @@ import { getContractDetailsService } from "../features/contracts/services";
12
12
  import type { BuyingPolicy } from "../features/buying-policies/types";
13
13
  import { buyingPoliciesData } from "../features/buying-policies/mocks";
14
14
  import { BuyingPolicyDetailsLayout } from "../features/buying-policies/layouts/BuyingPolicyDetailsLayout/BuyingPolicyDetailsLayout";
15
+ import { getBuyingPolicyService } from "../features/buying-policies/services";
15
16
 
16
17
  export type BuyingPolicyDetailsPageData = {
17
18
  data: { buyingPolicy: BuyingPolicy | null };
@@ -48,11 +49,16 @@ export async function loader(
48
49
  cookie,
49
50
  });
50
51
 
52
+ const buyingPolicy = await getBuyingPolicyService({
53
+ orgUnitId,
54
+ contractId,
55
+ buyingPolicyId,
56
+ cookie,
57
+ });
58
+
51
59
  return {
52
60
  data: {
53
- buyingPolicy:
54
- buyingPoliciesData.find((policy) => policy.id === buyingPolicyId) ??
55
- null,
61
+ buyingPolicy,
56
62
  },
57
63
  context: {
58
64
  clientContext: { cookie, userId, ...clientContext },