@vtex/faststore-plugin-buyer-portal 1.0.40 → 1.0.41
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 +5 -0
- package/package.json +1 -1
- package/plugin.config.js +10 -0
- package/src/features/addresses/components/CreateAddressDrawer/create-address-drawer.scss +1 -1
- package/src/features/addresses/layouts/AddressDetailsLayout/address-details-layout.scss +2 -2
- package/src/features/buying-policies/components/BuyingPolicyDropdownMenu/BuyingPolicyDropdownMenu.tsx +23 -0
- package/src/features/buying-policies/components/BuyingPolicyDropdownMenu/buying-policy-dropdown-menu.scss +1 -0
- package/src/features/buying-policies/components/index.ts +1 -0
- package/src/features/buying-policies/layouts/BuyingPoliciesLayout/BuyingPoliciesLayout.tsx +62 -0
- package/src/features/buying-policies/layouts/BuyingPoliciesLayout/buying-policies-layout.scss +24 -0
- package/src/features/buying-policies/layouts/BuyingPolicyDetailsLayout/BuyingPolicyDetailsLayout.tsx +83 -0
- package/src/features/buying-policies/layouts/BuyingPolicyDetailsLayout/buying-policy-details-layout.scss +74 -0
- package/src/features/buying-policies/layouts/index.ts +1 -0
- package/src/features/buying-policies/mocks/buying-policy-data.ts +145 -0
- package/src/features/buying-policies/mocks/index.ts +1 -0
- package/src/features/buying-policies/types/BuyingPolicies.ts +10 -0
- package/src/features/buying-policies/types/index.ts +1 -0
- package/src/features/org-units/layouts/OrgUnitDetailsLayout/OrgUnitDetailsLayout.tsx +9 -7
- package/src/features/shared/components/ListLine/ListLine.tsx +52 -0
- package/src/features/shared/components/ListLine/list-line.scss +49 -0
- package/src/features/shared/components/index.ts +1 -0
- package/src/features/shared/layouts/BaseTabsLayout/base-tabs-layout.scss +1 -0
- package/src/features/shared/layouts/FinanceTabsLayout/FinanceTabsLayout.tsx +40 -0
- package/src/features/shared/layouts/FinanceTabsLayout/finance-tabs-layout.scss +4 -0
- package/src/features/shared/layouts/index.ts +4 -0
- package/src/features/shared/utils/buyerPortalRoutes.ts +12 -0
- package/src/features/shared/utils/getFinanceSettingsLinks.ts +15 -0
- package/src/features/shared/utils/index.ts +1 -0
- package/src/pages/buying-policies.tsx +81 -0
- package/src/pages/buying-policy-details.tsx +72 -0
- package/src/themes/layouts.scss +4 -0
package/CHANGELOG.md
CHANGED
package/package.json
CHANGED
package/plugin.config.js
CHANGED
|
@@ -49,6 +49,16 @@ module.exports = {
|
|
|
49
49
|
// appLayout: false,
|
|
50
50
|
// },
|
|
51
51
|
|
|
52
|
+
"buying-policies": {
|
|
53
|
+
path: "/buyer-portal/buying-policies/[orgUnitId]/[contractId]",
|
|
54
|
+
appLayout: false,
|
|
55
|
+
},
|
|
56
|
+
|
|
57
|
+
"buying-policy-details": {
|
|
58
|
+
path: "/buyer-portal/buying-policy/[orgUnitId]/[contractId]/[buyingPolicyId]",
|
|
59
|
+
appLayout: false,
|
|
60
|
+
},
|
|
61
|
+
|
|
52
62
|
users: {
|
|
53
63
|
path: "/buyer-portal/users/[orgUnitId]",
|
|
54
64
|
appLayout: false,
|
|
@@ -53,7 +53,7 @@
|
|
|
53
53
|
[data-fs-bp-tab-bar] {
|
|
54
54
|
width: fit-content;
|
|
55
55
|
border-radius: var(--fs-border-radius-pill);
|
|
56
|
-
border:
|
|
56
|
+
border: var(--fs-border-width);
|
|
57
57
|
padding: var(--fs-spacing-0);
|
|
58
58
|
gap: var(--fs-spacing-0);
|
|
59
59
|
border-style: solid;
|
|
@@ -82,7 +82,7 @@
|
|
|
82
82
|
line-height: var(--fs-spacing-4);
|
|
83
83
|
|
|
84
84
|
border-radius: var(--fs-border-radius-pill);
|
|
85
|
-
border:
|
|
85
|
+
border: var(--fs-border-width) solid #e0e0e0;
|
|
86
86
|
}
|
|
87
87
|
}
|
|
88
88
|
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { BasicDropdownMenu, Icon } from "../../../shared/components";
|
|
2
|
+
|
|
3
|
+
import { DropdownItem } from "@faststore/ui";
|
|
4
|
+
|
|
5
|
+
export const BuyingPolicyDropdownMenu = () => {
|
|
6
|
+
return (
|
|
7
|
+
<BasicDropdownMenu>
|
|
8
|
+
<DropdownItem>
|
|
9
|
+
<Icon name="OpenInNew" />
|
|
10
|
+
Open
|
|
11
|
+
</DropdownItem>
|
|
12
|
+
<DropdownItem>
|
|
13
|
+
<Icon name="Edit" />
|
|
14
|
+
Edit settings
|
|
15
|
+
</DropdownItem>
|
|
16
|
+
<BasicDropdownMenu.Separator />
|
|
17
|
+
<DropdownItem data-fs-bp-dropdown-menu-item-mode="danger">
|
|
18
|
+
<Icon name="Delete" />
|
|
19
|
+
<span>Delete</span>
|
|
20
|
+
</DropdownItem>
|
|
21
|
+
</BasicDropdownMenu>
|
|
22
|
+
);
|
|
23
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
@import "../../../shared/components/BasicDropdownMenu/basic-dropdown-menu.scss";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { BuyingPolicyDropdownMenu } from "./BuyingPolicyDropdownMenu/BuyingPolicyDropdownMenu";
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { FinanceTabsLayout, GlobalLayout } from "../../../shared/layouts";
|
|
2
|
+
import {
|
|
3
|
+
HeaderInside,
|
|
4
|
+
InternalSearch,
|
|
5
|
+
ListLine,
|
|
6
|
+
} from "../../../shared/components";
|
|
7
|
+
import { useBuyerPortal, useQueryParams } from "../../../shared/hooks";
|
|
8
|
+
import type { BuyingPolicy } from "../../types";
|
|
9
|
+
import { buyerPortalRoutes } from "../../../shared/utils/buyerPortalRoutes";
|
|
10
|
+
import { BuyingPolicyDropdownMenu } from "../../components";
|
|
11
|
+
|
|
12
|
+
export type BuyingPoliciesLayoutProps = {
|
|
13
|
+
data: { buyingPolicies: BuyingPolicy[] } | null;
|
|
14
|
+
search: string;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export const BuyingPoliciesLayout = ({
|
|
18
|
+
data,
|
|
19
|
+
search,
|
|
20
|
+
}: BuyingPoliciesLayoutProps) => {
|
|
21
|
+
const { setQueryString, removeQueryString } = useQueryParams();
|
|
22
|
+
|
|
23
|
+
const { currentContract, currentOrgUnit } = useBuyerPortal();
|
|
24
|
+
|
|
25
|
+
return (
|
|
26
|
+
<GlobalLayout>
|
|
27
|
+
<FinanceTabsLayout pageName="Finance and Compliance">
|
|
28
|
+
<section data-fs-buying-policies-section>
|
|
29
|
+
<HeaderInside title="Buying Policies">
|
|
30
|
+
<HeaderInside.Button />
|
|
31
|
+
</HeaderInside>
|
|
32
|
+
|
|
33
|
+
<div data-fs-buying-policies-filter>
|
|
34
|
+
<InternalSearch
|
|
35
|
+
defaultValue={search}
|
|
36
|
+
textSearch={(searchTerm) => {
|
|
37
|
+
searchTerm
|
|
38
|
+
? setQueryString("search", searchTerm)
|
|
39
|
+
: removeQueryString("search");
|
|
40
|
+
}}
|
|
41
|
+
/>
|
|
42
|
+
</div>
|
|
43
|
+
|
|
44
|
+
<span data-fs-buying-policies-heading>Name</span>
|
|
45
|
+
{data?.buyingPolicies.map((buyingPolicy) => (
|
|
46
|
+
<ListLine
|
|
47
|
+
key={buyingPolicy.id}
|
|
48
|
+
title={buyingPolicy.name}
|
|
49
|
+
iconName="Rebase"
|
|
50
|
+
href={buyerPortalRoutes.buyingPolicyDetails({
|
|
51
|
+
contractId: currentContract?.id ?? "",
|
|
52
|
+
orgUnitId: currentOrgUnit?.id ?? "",
|
|
53
|
+
buyingPolicyId: buyingPolicy.id,
|
|
54
|
+
})}
|
|
55
|
+
dropdownMenu={<BuyingPolicyDropdownMenu />}
|
|
56
|
+
/>
|
|
57
|
+
))}
|
|
58
|
+
</section>
|
|
59
|
+
</FinanceTabsLayout>
|
|
60
|
+
</GlobalLayout>
|
|
61
|
+
);
|
|
62
|
+
};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
@import "@faststore/ui/src/components/molecules/Dropdown/styles.scss";
|
|
2
|
+
@import "../../components/BuyingPolicyDropdownMenu/buying-policy-dropdown-menu.scss";
|
|
3
|
+
@import "../../../shared/layouts/FinanceTabsLayout/finance-tabs-layout.scss";
|
|
4
|
+
|
|
5
|
+
[data-fs-buying-policies-section] {
|
|
6
|
+
@import "../../../shared/components/HeaderInside/header-inside.scss";
|
|
7
|
+
@import "../../../shared/components/InternalSearch/internal-search.scss";
|
|
8
|
+
@import "../../../shared/components/ListLine/list-line.scss";
|
|
9
|
+
|
|
10
|
+
[data-fs-buying-policies-filter] {
|
|
11
|
+
display: flex;
|
|
12
|
+
justify-content: space-between;
|
|
13
|
+
padding: 0 0 var(--fs-spacing-4);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
[data-fs-buying-policies-heading] {
|
|
17
|
+
display: flex;
|
|
18
|
+
font-weight: 400;
|
|
19
|
+
font-size: var(--fs-text-size-1);
|
|
20
|
+
line-height: calc(var(--fs-spacing-4) - var(--fs-spacing-0));
|
|
21
|
+
color: #5c5c5c;
|
|
22
|
+
margin-bottom: var(--fs-spacing-1);
|
|
23
|
+
}
|
|
24
|
+
}
|
package/src/features/buying-policies/layouts/BuyingPolicyDetailsLayout/BuyingPolicyDetailsLayout.tsx
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { FinanceTabsLayout, GlobalLayout } from "../../../shared/layouts";
|
|
2
|
+
import {
|
|
3
|
+
BasicDropdownMenu,
|
|
4
|
+
HeaderInside,
|
|
5
|
+
Icon,
|
|
6
|
+
} from "../../../shared/components";
|
|
7
|
+
import { useBuyerPortal } from "../../../shared/hooks";
|
|
8
|
+
import type { BuyingPolicy } from "../../types";
|
|
9
|
+
import { buyerPortalRoutes } from "../../../shared/utils/buyerPortalRoutes";
|
|
10
|
+
import { Dropdown, DropdownItem } from "@faststore/ui";
|
|
11
|
+
import { BuyingPolicyDropdownMenu } from "../../components";
|
|
12
|
+
|
|
13
|
+
export type BuyingPolicyDetailsLayoutProps = {
|
|
14
|
+
data: { buyingPolicy: BuyingPolicy | null };
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export const BuyingPolicyDetailsLayout = ({
|
|
18
|
+
data: { buyingPolicy },
|
|
19
|
+
}: BuyingPolicyDetailsLayoutProps) => {
|
|
20
|
+
const { currentContract, currentOrgUnit } = useBuyerPortal();
|
|
21
|
+
|
|
22
|
+
return (
|
|
23
|
+
<GlobalLayout>
|
|
24
|
+
<FinanceTabsLayout pageName="Finance and Compliance">
|
|
25
|
+
<section data-fs-buying-policy-details-section>
|
|
26
|
+
<HeaderInside
|
|
27
|
+
title={buyingPolicy?.name ?? ""}
|
|
28
|
+
backLink={buyerPortalRoutes.buyingPolicies({
|
|
29
|
+
contractId: currentContract?.id || "",
|
|
30
|
+
orgUnitId: currentOrgUnit?.id || "",
|
|
31
|
+
})}
|
|
32
|
+
>
|
|
33
|
+
<Dropdown>
|
|
34
|
+
<BasicDropdownMenu.Trigger />
|
|
35
|
+
<BuyingPolicyDropdownMenu />
|
|
36
|
+
</Dropdown>
|
|
37
|
+
</HeaderInside>
|
|
38
|
+
|
|
39
|
+
<div data-fs-buying-policy-details-line>
|
|
40
|
+
<span data-fs-buying-policy-details-title>Settings</span>
|
|
41
|
+
<button type="button" data-fs-buying-policy-details-edit-button>
|
|
42
|
+
Edit
|
|
43
|
+
</button>
|
|
44
|
+
</div>
|
|
45
|
+
<div data-fs-buying-policy-details-line>
|
|
46
|
+
<span data-fs-buying-policy-details-field-label>Name</span>
|
|
47
|
+
<span data-fs-buying-policy-details-field-content>
|
|
48
|
+
{buyingPolicy?.name}
|
|
49
|
+
</span>
|
|
50
|
+
</div>
|
|
51
|
+
<div data-fs-buying-policy-details-line>
|
|
52
|
+
<span data-fs-buying-policy-details-field-label>Description</span>
|
|
53
|
+
<span data-fs-buying-policy-details-field-content>
|
|
54
|
+
{buyingPolicy?.description}
|
|
55
|
+
</span>
|
|
56
|
+
</div>
|
|
57
|
+
<div data-fs-buying-policy-details-line>
|
|
58
|
+
<span data-fs-buying-policy-details-field-label>Criteria</span>
|
|
59
|
+
<span data-fs-buying-policy-details-field-content>
|
|
60
|
+
{buyingPolicy?.criteria}
|
|
61
|
+
</span>
|
|
62
|
+
</div>
|
|
63
|
+
<div data-fs-buying-policy-details-line>
|
|
64
|
+
<span data-fs-buying-policy-details-field-label>Action</span>
|
|
65
|
+
<span data-fs-buying-policy-details-field-content>
|
|
66
|
+
{buyingPolicy?.action.type}
|
|
67
|
+
{buyingPolicy?.action.levels?.map((level, index) => (
|
|
68
|
+
<>
|
|
69
|
+
<span key={level} data-fs-buying-policy-details-level-title>
|
|
70
|
+
Level {index + 1}
|
|
71
|
+
</span>
|
|
72
|
+
<span key={level} data-fs-buying-policy-details-level-value>
|
|
73
|
+
{level}
|
|
74
|
+
</span>
|
|
75
|
+
</>
|
|
76
|
+
))}
|
|
77
|
+
</span>
|
|
78
|
+
</div>
|
|
79
|
+
</section>
|
|
80
|
+
</FinanceTabsLayout>
|
|
81
|
+
</GlobalLayout>
|
|
82
|
+
);
|
|
83
|
+
};
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
@import "@faststore/ui/src/components/molecules/Dropdown/styles.scss";
|
|
2
|
+
|
|
3
|
+
@import "../../../shared/layouts/FinanceTabsLayout/finance-tabs-layout.scss";
|
|
4
|
+
@import "../../components/BuyingPolicyDropdownMenu/buying-policy-dropdown-menu.scss";
|
|
5
|
+
|
|
6
|
+
[data-fs-buying-policy-details-section] {
|
|
7
|
+
@import "../../../shared/components/HeaderInside/header-inside.scss";
|
|
8
|
+
@import "../../../shared/components/InternalSearch/internal-search.scss";
|
|
9
|
+
|
|
10
|
+
[data-fs-buying-policy-details-line] {
|
|
11
|
+
padding: calc(var(--fs-spacing-4) - var(--fs-spacing-0)) 0;
|
|
12
|
+
border-top: var(--fs-border-width) solid #e0e0e0;
|
|
13
|
+
display: flex;
|
|
14
|
+
|
|
15
|
+
&:first-of-type {
|
|
16
|
+
border-top: none;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
[data-fs-buying-policy-details-title] {
|
|
20
|
+
font-weight: 600;
|
|
21
|
+
font-size: var(--fs-text-size-2);
|
|
22
|
+
line-height: var(--fs-spacing-4);
|
|
23
|
+
margin-right: auto;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
[data-fs-buying-policy-details-edit-button] {
|
|
27
|
+
border: var(--fs-border-width) solid #e0e0e0;
|
|
28
|
+
border-radius: var(--fs-border-radius-pill);
|
|
29
|
+
padding: var(--fs-spacing-1)
|
|
30
|
+
calc(var(--fs-spacing-4) - var(--fs-spacing-0));
|
|
31
|
+
font-weight: 600;
|
|
32
|
+
font-size: var(--fs-text-size-1);
|
|
33
|
+
line-height: calc(var(--fs-spacing-4) - var(--fs-spacing-0));
|
|
34
|
+
text-align: center;
|
|
35
|
+
color: #0366dd;
|
|
36
|
+
cursor: pointer;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
[data-fs-buying-policy-details-field-label] {
|
|
40
|
+
width: 14rem;
|
|
41
|
+
display: inline-block;
|
|
42
|
+
|
|
43
|
+
font-weight: 400;
|
|
44
|
+
font-size: var(--fs-text-size-1);
|
|
45
|
+
line-height: calc(var(--fs-spacing-4) - var(--fs-spacing-0));
|
|
46
|
+
|
|
47
|
+
color: #5c5c5c;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
[data-fs-buying-policy-details-field-content] {
|
|
51
|
+
font-weight: 400;
|
|
52
|
+
font-size: var(--fs-text-size-1);
|
|
53
|
+
line-height: calc(var(--fs-spacing-4) - var(--fs-spacing-0));
|
|
54
|
+
color: #000000;
|
|
55
|
+
|
|
56
|
+
[data-fs-buying-policy-details-level-title] {
|
|
57
|
+
display: block;
|
|
58
|
+
margin-top: var(--fs-spacing-3);
|
|
59
|
+
font-weight: 400;
|
|
60
|
+
font-size: var(--fs-text-size-1);
|
|
61
|
+
line-height: calc(var(--fs-spacing-4) - var(--fs-spacing-0));
|
|
62
|
+
color: #5c5c5c;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
[data-fs-buying-policy-details-level-value] {
|
|
66
|
+
display: block;
|
|
67
|
+
font-weight: 500;
|
|
68
|
+
font-size: var(--fs-text-size-1);
|
|
69
|
+
line-height: calc(var(--fs-spacing-4) - var(--fs-spacing-0));
|
|
70
|
+
color: #1f1f1f;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { BuyingPoliciesLayout } from "./BuyingPoliciesLayout/BuyingPoliciesLayout";
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import type { BuyingPolicy } from "../types";
|
|
2
|
+
|
|
3
|
+
export const buyingPoliciesData: BuyingPolicy[] = [
|
|
4
|
+
{
|
|
5
|
+
id: "1",
|
|
6
|
+
name: "Standard Purchase Policy",
|
|
7
|
+
description: "Defines the standard rules for purchasing items.",
|
|
8
|
+
criteria: "budget >= 100",
|
|
9
|
+
action: {
|
|
10
|
+
type: "Bypass all policies",
|
|
11
|
+
},
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
id: "2",
|
|
15
|
+
name: "Bulk Order Policy",
|
|
16
|
+
description: "Policy for handling bulk orders with discounts.",
|
|
17
|
+
criteria: "quantity > 100",
|
|
18
|
+
action: {
|
|
19
|
+
type: "Sequential approval workflow",
|
|
20
|
+
levels: ["Manager", "Director", "CEO"],
|
|
21
|
+
},
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
id: "3",
|
|
25
|
+
name: "Restricted Items Policy",
|
|
26
|
+
description: "Restricts the purchase of certain items.",
|
|
27
|
+
criteria: "category == 'restricted'",
|
|
28
|
+
action: {
|
|
29
|
+
type: "Deny all",
|
|
30
|
+
},
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
id: "4",
|
|
34
|
+
name: "Employee Discount Policy",
|
|
35
|
+
description: "Provides discounts for employees.",
|
|
36
|
+
criteria: "userRole == 'employee'",
|
|
37
|
+
action: {
|
|
38
|
+
type: "Bypass all policies",
|
|
39
|
+
},
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
id: "5",
|
|
43
|
+
name: "Seasonal Promotion Policy",
|
|
44
|
+
description: "Handles seasonal promotions and discounts.",
|
|
45
|
+
criteria: "season == 'holiday'",
|
|
46
|
+
action: {
|
|
47
|
+
type: "Sequential approval workflow",
|
|
48
|
+
levels: ["Marketing Manager", "Finance Director"],
|
|
49
|
+
},
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
id: "6",
|
|
53
|
+
name: "High-Value Purchase Policy",
|
|
54
|
+
description: "Requires approval for high-value purchases.",
|
|
55
|
+
criteria: "purchaseAmount > 10000",
|
|
56
|
+
action: {
|
|
57
|
+
type: "Sequential approval workflow",
|
|
58
|
+
levels: ["Manager", "Finance Director", "CEO"],
|
|
59
|
+
},
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
id: "7",
|
|
63
|
+
name: "Loyalty Program Policy",
|
|
64
|
+
description: "Rewards loyal customers with points.",
|
|
65
|
+
criteria: "loyaltyPoints >= 500",
|
|
66
|
+
action: {
|
|
67
|
+
type: "Bypass all policies",
|
|
68
|
+
},
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
id: "8",
|
|
72
|
+
name: "New Customer Policy",
|
|
73
|
+
description: "Special offers for new customers.",
|
|
74
|
+
criteria: "isFirstPurchase == true",
|
|
75
|
+
action: {
|
|
76
|
+
type: "Bypass all policies",
|
|
77
|
+
},
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
id: "9",
|
|
81
|
+
name: "Corporate Account Policy",
|
|
82
|
+
description: "Custom rules for corporate accounts.",
|
|
83
|
+
criteria: "accountType == 'corporate'",
|
|
84
|
+
action: {
|
|
85
|
+
type: "Sequential approval workflow",
|
|
86
|
+
levels: ["Account Manager", "Finance Director"],
|
|
87
|
+
},
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
id: "10",
|
|
91
|
+
name: "Subscription Renewal Policy",
|
|
92
|
+
description: "Handles subscription renewals.",
|
|
93
|
+
criteria: "subscriptionStatus == 'active'",
|
|
94
|
+
action: {
|
|
95
|
+
type: "Bypass all policies",
|
|
96
|
+
},
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
id: "11",
|
|
100
|
+
name: "International Shipping Policy",
|
|
101
|
+
description: "Rules for international orders.",
|
|
102
|
+
criteria: "shippingCountry != 'domestic'",
|
|
103
|
+
action: {
|
|
104
|
+
type: "Sequential approval workflow",
|
|
105
|
+
levels: ["Logistics Manager", "Finance Director"],
|
|
106
|
+
},
|
|
107
|
+
},
|
|
108
|
+
{
|
|
109
|
+
id: "12",
|
|
110
|
+
name: "Return and Refund Policy",
|
|
111
|
+
description: "Defines rules for returns and refunds.",
|
|
112
|
+
criteria: "daysSincePurchase <= 30",
|
|
113
|
+
action: {
|
|
114
|
+
type: "Deny all",
|
|
115
|
+
},
|
|
116
|
+
},
|
|
117
|
+
{
|
|
118
|
+
id: "13",
|
|
119
|
+
name: "Age-Restricted Items Policy",
|
|
120
|
+
description: "Restricts purchase of age-sensitive items.",
|
|
121
|
+
criteria: "userAge < 18",
|
|
122
|
+
action: {
|
|
123
|
+
type: "Deny all",
|
|
124
|
+
},
|
|
125
|
+
},
|
|
126
|
+
{
|
|
127
|
+
id: "14",
|
|
128
|
+
name: "VIP Customer Policy",
|
|
129
|
+
description: "Exclusive benefits for VIP customers.",
|
|
130
|
+
criteria: "userStatus == 'VIP'",
|
|
131
|
+
action: {
|
|
132
|
+
type: "Bypass all policies",
|
|
133
|
+
},
|
|
134
|
+
},
|
|
135
|
+
{
|
|
136
|
+
id: "15",
|
|
137
|
+
name: "Environmental Sustainability Policy",
|
|
138
|
+
description: "Encourages eco-friendly purchases.",
|
|
139
|
+
criteria: "productType == 'eco-friendly'",
|
|
140
|
+
action: {
|
|
141
|
+
type: "Sequential approval workflow",
|
|
142
|
+
levels: ["Sustainability Manager", "CEO"],
|
|
143
|
+
},
|
|
144
|
+
},
|
|
145
|
+
];
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { buyingPoliciesData } from "./buying-policy-data";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export type { BuyingPolicy } from "./BuyingPolicies";
|
|
@@ -16,6 +16,7 @@ import {
|
|
|
16
16
|
} from "../../../shared/components";
|
|
17
17
|
import {
|
|
18
18
|
getContractSettingsLinks,
|
|
19
|
+
getFinanceSettingsLinks,
|
|
19
20
|
getOrganizationSettingsLinks,
|
|
20
21
|
} from "../../../shared/utils";
|
|
21
22
|
import type { UserData } from "../../../users/types";
|
|
@@ -30,11 +31,6 @@ export type OrgUnitsDetailsLayoutProps = {
|
|
|
30
31
|
};
|
|
31
32
|
};
|
|
32
33
|
|
|
33
|
-
const financeSettings = [
|
|
34
|
-
{ name: "Budgets", link: "/budgets" },
|
|
35
|
-
{ name: "Buying Policies", link: "/buying-policies" },
|
|
36
|
-
];
|
|
37
|
-
|
|
38
34
|
export const OrgUnitsDetailsLayout = ({
|
|
39
35
|
data: { orgUnit, contracts, user },
|
|
40
36
|
}: OrgUnitsDetailsLayoutProps) => {
|
|
@@ -141,11 +137,17 @@ export const OrgUnitsDetailsLayout = ({
|
|
|
141
137
|
<BasicCard
|
|
142
138
|
data-fs-bp-compliance-settings-card
|
|
143
139
|
footerMessage="Manage finance and compliance settings"
|
|
144
|
-
footerLink=
|
|
140
|
+
footerLink={buyerPortalRoutes.buyingPolicies({
|
|
141
|
+
orgUnitId: orgUnit.id,
|
|
142
|
+
contractId: contracts[0].id,
|
|
143
|
+
})}
|
|
145
144
|
enableFooter
|
|
146
145
|
>
|
|
147
146
|
<VerticalNav.Menu title="Finance and Compliance">
|
|
148
|
-
{
|
|
147
|
+
{getFinanceSettingsLinks({
|
|
148
|
+
orgUnitId: orgUnit.id,
|
|
149
|
+
contractId: contracts[0].id,
|
|
150
|
+
}).map((option) => (
|
|
149
151
|
<VerticalNav.Link key={option.name} link={option.link}>
|
|
150
152
|
{option.name}
|
|
151
153
|
</VerticalNav.Link>
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import type { ReactNode } from "react";
|
|
2
|
+
import { Icon } from "../Icon";
|
|
3
|
+
import { BasicDropdownMenu } from "../BasicDropdownMenu/BasicDropdownMenu";
|
|
4
|
+
import { Dropdown } from "@faststore/components";
|
|
5
|
+
import Link from "next/link";
|
|
6
|
+
|
|
7
|
+
export type ListLineProps = {
|
|
8
|
+
children?: ReactNode;
|
|
9
|
+
title: string;
|
|
10
|
+
iconName?: string;
|
|
11
|
+
iconSize?: number;
|
|
12
|
+
dropdownMenu?: ReactNode;
|
|
13
|
+
href?: string;
|
|
14
|
+
onClick?: () => void;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export const ListLine = ({
|
|
18
|
+
iconName,
|
|
19
|
+
iconSize = 24,
|
|
20
|
+
title,
|
|
21
|
+
dropdownMenu,
|
|
22
|
+
children,
|
|
23
|
+
href,
|
|
24
|
+
onClick,
|
|
25
|
+
...otherProps
|
|
26
|
+
}: ListLineProps) => {
|
|
27
|
+
const ClickComponent = href ? Link : "button";
|
|
28
|
+
|
|
29
|
+
return (
|
|
30
|
+
<li data-fs-bp-line {...otherProps}>
|
|
31
|
+
{href && (
|
|
32
|
+
<ClickComponent data-fs-bp-line-link href={href} onClick={onClick} />
|
|
33
|
+
)}
|
|
34
|
+
{iconName && (
|
|
35
|
+
<Icon
|
|
36
|
+
data-fs-bp-line-icon
|
|
37
|
+
name={iconName}
|
|
38
|
+
width={iconSize}
|
|
39
|
+
height={iconSize}
|
|
40
|
+
/>
|
|
41
|
+
)}
|
|
42
|
+
<h3 data-fs-bp-line-title>{title}</h3>
|
|
43
|
+
{children}
|
|
44
|
+
{dropdownMenu && (
|
|
45
|
+
<Dropdown>
|
|
46
|
+
<BasicDropdownMenu.Trigger />
|
|
47
|
+
{dropdownMenu}
|
|
48
|
+
</Dropdown>
|
|
49
|
+
)}
|
|
50
|
+
</li>
|
|
51
|
+
);
|
|
52
|
+
};
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
@import "../BasicDropdownMenu/basic-dropdown-menu.scss";
|
|
2
|
+
|
|
3
|
+
[data-fs-bp-line] {
|
|
4
|
+
position: relative;
|
|
5
|
+
list-style: none;
|
|
6
|
+
border-top: var(--fs-border-width) solid #e0e0e0;
|
|
7
|
+
padding: var(--fs-spacing-1) var(--fs-spacing-1);
|
|
8
|
+
align-items: center;
|
|
9
|
+
|
|
10
|
+
display: flex;
|
|
11
|
+
width: 100%;
|
|
12
|
+
|
|
13
|
+
&:last-of-type {
|
|
14
|
+
border-bottom: var(--fs-border-width) solid #e0e0e0;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
[data-fs-bp-line-link] {
|
|
18
|
+
position: absolute;
|
|
19
|
+
left: 0;
|
|
20
|
+
right: 0;
|
|
21
|
+
top: 0;
|
|
22
|
+
bottom: 0;
|
|
23
|
+
|
|
24
|
+
&:hover {
|
|
25
|
+
background-color: #f5f5f5;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
[data-fs-bp-line-icon] {
|
|
30
|
+
color: #0366dd;
|
|
31
|
+
margin-right: var(--fs-spacing-2);
|
|
32
|
+
pointer-events: none;
|
|
33
|
+
z-index: 1;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
[data-fs-bp-line-title] {
|
|
37
|
+
font-weight: 500;
|
|
38
|
+
font-size: var(--fs-text-size-1);
|
|
39
|
+
line-height: calc(var(--fs-spacing-4) - var(--fs-spacing-0));
|
|
40
|
+
color: #1f1f1f;
|
|
41
|
+
z-index: 1;
|
|
42
|
+
pointer-events: none;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
[data-fs-bp-basic-dropdown-menu-trigger] {
|
|
46
|
+
z-index: 1;
|
|
47
|
+
margin-left: auto;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type { ReactNode } from "react";
|
|
2
|
+
import { BaseTabsLayout } from "../BaseTabsLayout/BaseTabsLayout";
|
|
3
|
+
import { getFinanceSettingsLinks } from "../../utils";
|
|
4
|
+
import { useBuyerPortal } from "../../hooks";
|
|
5
|
+
|
|
6
|
+
export type FinanceTabsLayoutProps = {
|
|
7
|
+
pageName: string;
|
|
8
|
+
children?: ReactNode;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
export const FinanceTabsLayout = ({
|
|
12
|
+
pageName,
|
|
13
|
+
children,
|
|
14
|
+
}: FinanceTabsLayoutProps) => {
|
|
15
|
+
const { currentOrgUnit, currentUser, currentContract } = useBuyerPortal();
|
|
16
|
+
|
|
17
|
+
const verticalLinks = getFinanceSettingsLinks({
|
|
18
|
+
contractId: currentContract?.id ?? "",
|
|
19
|
+
orgUnitId: currentOrgUnit?.id ?? "",
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
return (
|
|
23
|
+
<BaseTabsLayout data-fs-bp-finance-tabs-layout>
|
|
24
|
+
<BaseTabsLayout.Navbar
|
|
25
|
+
orgUnitName={currentOrgUnit?.name ?? ""}
|
|
26
|
+
orgUnitId={currentOrgUnit?.id ?? ""}
|
|
27
|
+
pageName={pageName}
|
|
28
|
+
person={{
|
|
29
|
+
name: currentUser?.name ?? "",
|
|
30
|
+
role: currentUser?.role ?? "",
|
|
31
|
+
}}
|
|
32
|
+
/>
|
|
33
|
+
<BaseTabsLayout.SidebarMenu
|
|
34
|
+
verticalLinks={verticalLinks}
|
|
35
|
+
showLetterHighlight={false}
|
|
36
|
+
/>
|
|
37
|
+
<BaseTabsLayout.Content>{children}</BaseTabsLayout.Content>
|
|
38
|
+
</BaseTabsLayout>
|
|
39
|
+
);
|
|
40
|
+
};
|
|
@@ -42,6 +42,18 @@ export const buyerPortalRoutes = {
|
|
|
42
42
|
releases: (params: { orgUnitId: string; contractId: string }) =>
|
|
43
43
|
replaceParams(`${base}/releases/[orgUnitId]/[contractId]`, params),
|
|
44
44
|
|
|
45
|
+
buyingPolicies: (params: { orgUnitId: string; contractId: string }) =>
|
|
46
|
+
replaceParams(`${base}/buying-policies/[orgUnitId]/[contractId]`, params),
|
|
47
|
+
|
|
48
|
+
buyingPolicyDetails: (params: {
|
|
49
|
+
orgUnitId: string;
|
|
50
|
+
contractId: string;
|
|
51
|
+
buyingPolicyId: string;
|
|
52
|
+
}) =>
|
|
53
|
+
replaceParams(
|
|
54
|
+
`${base}/buying-policy/[orgUnitId]/[contractId]/[buyingPolicyId]`,
|
|
55
|
+
params
|
|
56
|
+
),
|
|
45
57
|
// orgUnitId only
|
|
46
58
|
users: (params: { orgUnitId: string }) =>
|
|
47
59
|
replaceParams(`${base}/users/[orgUnitId]`, params),
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { buyerPortalRoutes } from "./buyerPortalRoutes";
|
|
2
|
+
|
|
3
|
+
export const getFinanceSettingsLinks = ({
|
|
4
|
+
orgUnitId,
|
|
5
|
+
contractId,
|
|
6
|
+
}: {
|
|
7
|
+
orgUnitId: string;
|
|
8
|
+
contractId: string;
|
|
9
|
+
}) => [
|
|
10
|
+
{ name: "Budgets", link: "/" },
|
|
11
|
+
{
|
|
12
|
+
name: "Buying Policies",
|
|
13
|
+
link: buyerPortalRoutes.buyingPolicies({ orgUnitId, contractId }),
|
|
14
|
+
},
|
|
15
|
+
];
|
|
@@ -25,3 +25,4 @@ export { roles, type Role } from "./roles";
|
|
|
25
25
|
export { getContractSettingsLinks } from "./getContractSettingsLinks";
|
|
26
26
|
export { getOrganizationSettingsLinks } from "./getOrganizationSettingsLinks";
|
|
27
27
|
export { maskCardNumber, maskCVV, maskExpirationDate } from "./creditCard";
|
|
28
|
+
export { getFinanceSettingsLinks } from "./getFinanceSettingsLinks";
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import {
|
|
2
|
+
getAddressesService,
|
|
3
|
+
type GetAddressesServiceProps,
|
|
4
|
+
} from "../features/addresses/services";
|
|
5
|
+
|
|
6
|
+
import {
|
|
7
|
+
type ClientContext,
|
|
8
|
+
getClientContext,
|
|
9
|
+
getCustomerIdFromCookieServerSide,
|
|
10
|
+
} from "../features/shared/utils";
|
|
11
|
+
import type { AddressData } from "../features/addresses/types";
|
|
12
|
+
import { AddressLayout } from "../features/addresses/layouts";
|
|
13
|
+
import { BuyerPortalProvider } from "../features/shared/components";
|
|
14
|
+
import type { LoaderData } from "../features/shared/types";
|
|
15
|
+
import type { OrgUnitBasicData } from "../features/org-units/types";
|
|
16
|
+
import { getOrgUnitBasicDataService } from "../features/org-units/services";
|
|
17
|
+
import { getUserByIdService } from "../features/users/services";
|
|
18
|
+
import type { UserData } from "../features/users/types";
|
|
19
|
+
import type { ContractData } from "../features/contracts/types";
|
|
20
|
+
import { getContractDetailsService } from "../features/contracts/services";
|
|
21
|
+
import type { BuyingPolicy } from "../features/buying-policies/types";
|
|
22
|
+
import { buyingPoliciesData } from "../features/buying-policies/mocks";
|
|
23
|
+
import { BuyingPoliciesLayout } from "../features/buying-policies/layouts";
|
|
24
|
+
|
|
25
|
+
export type BuyingPoliciesPageData = {
|
|
26
|
+
data: { buyingPolicies: BuyingPolicy[] };
|
|
27
|
+
search: string;
|
|
28
|
+
context: {
|
|
29
|
+
currentContract: ContractData | null;
|
|
30
|
+
clientContext: ClientContext;
|
|
31
|
+
currentOrgUnit: OrgUnitBasicData | null;
|
|
32
|
+
currentUser: UserData | null;
|
|
33
|
+
};
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
export type BuyingPoliciesPageQuery = GetAddressesServiceProps & {
|
|
37
|
+
orgUnitId: string;
|
|
38
|
+
contractId: string;
|
|
39
|
+
search?: string;
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
export async function loader(
|
|
43
|
+
data: LoaderData<BuyingPoliciesPageQuery>
|
|
44
|
+
): Promise<BuyingPoliciesPageData> {
|
|
45
|
+
const { contractId, orgUnitId, search = "" } = data.query;
|
|
46
|
+
|
|
47
|
+
const { cookie, userId, ...clientContext } = await getClientContext(data);
|
|
48
|
+
|
|
49
|
+
const currentOrgUnit = await getOrgUnitBasicDataService({
|
|
50
|
+
id: orgUnitId,
|
|
51
|
+
cookie,
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
const user = await getUserByIdService({ userId, cookie });
|
|
55
|
+
|
|
56
|
+
const contract = await getContractDetailsService({
|
|
57
|
+
contractId,
|
|
58
|
+
cookie,
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
return {
|
|
62
|
+
data: {
|
|
63
|
+
buyingPolicies: buyingPoliciesData,
|
|
64
|
+
},
|
|
65
|
+
search,
|
|
66
|
+
context: {
|
|
67
|
+
clientContext: { cookie, userId, ...clientContext },
|
|
68
|
+
currentOrgUnit,
|
|
69
|
+
currentUser: user,
|
|
70
|
+
currentContract: contract,
|
|
71
|
+
},
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const AddressPage = ({ data, search, context }: BuyingPoliciesPageData) => (
|
|
76
|
+
<BuyerPortalProvider {...context}>
|
|
77
|
+
<BuyingPoliciesLayout data={data} search={search} />
|
|
78
|
+
</BuyerPortalProvider>
|
|
79
|
+
);
|
|
80
|
+
|
|
81
|
+
export default AddressPage;
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import type { GetAddressesServiceProps } from "../features/addresses/services";
|
|
2
|
+
|
|
3
|
+
import { type ClientContext, getClientContext } from "../features/shared/utils";
|
|
4
|
+
import { BuyerPortalProvider } from "../features/shared/components";
|
|
5
|
+
import type { LoaderData } from "../features/shared/types";
|
|
6
|
+
import type { OrgUnitBasicData } from "../features/org-units/types";
|
|
7
|
+
import { getOrgUnitBasicDataService } from "../features/org-units/services";
|
|
8
|
+
import { getUserByIdService } from "../features/users/services";
|
|
9
|
+
import type { UserData } from "../features/users/types";
|
|
10
|
+
import type { ContractData } from "../features/contracts/types";
|
|
11
|
+
import { getContractDetailsService } from "../features/contracts/services";
|
|
12
|
+
import type { BuyingPolicy } from "../features/buying-policies/types";
|
|
13
|
+
import { buyingPoliciesData } from "../features/buying-policies/mocks";
|
|
14
|
+
import { BuyingPolicyDetailsLayout } from "../features/buying-policies/layouts/BuyingPolicyDetailsLayout/BuyingPolicyDetailsLayout";
|
|
15
|
+
|
|
16
|
+
export type BuyingPolicyDetailsPageData = {
|
|
17
|
+
data: { buyingPolicy: BuyingPolicy | null };
|
|
18
|
+
context: {
|
|
19
|
+
currentContract: ContractData | null;
|
|
20
|
+
clientContext: ClientContext;
|
|
21
|
+
currentOrgUnit: OrgUnitBasicData | null;
|
|
22
|
+
currentUser: UserData | null;
|
|
23
|
+
};
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export type BuyingPolicyDetailsPageQuery = GetAddressesServiceProps & {
|
|
27
|
+
orgUnitId: string;
|
|
28
|
+
contractId: string;
|
|
29
|
+
buyingPolicyId: string;
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
export async function loader(
|
|
33
|
+
data: LoaderData<BuyingPolicyDetailsPageQuery>
|
|
34
|
+
): Promise<BuyingPolicyDetailsPageData> {
|
|
35
|
+
const { contractId, orgUnitId, buyingPolicyId } = data.query;
|
|
36
|
+
|
|
37
|
+
const { cookie, userId, ...clientContext } = await getClientContext(data);
|
|
38
|
+
|
|
39
|
+
const currentOrgUnit = await getOrgUnitBasicDataService({
|
|
40
|
+
id: orgUnitId,
|
|
41
|
+
cookie,
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
const user = await getUserByIdService({ userId, cookie });
|
|
45
|
+
|
|
46
|
+
const contract = await getContractDetailsService({
|
|
47
|
+
contractId,
|
|
48
|
+
cookie,
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
return {
|
|
52
|
+
data: {
|
|
53
|
+
buyingPolicy:
|
|
54
|
+
buyingPoliciesData.find((policy) => policy.id === buyingPolicyId) ??
|
|
55
|
+
null,
|
|
56
|
+
},
|
|
57
|
+
context: {
|
|
58
|
+
clientContext: { cookie, userId, ...clientContext },
|
|
59
|
+
currentOrgUnit,
|
|
60
|
+
currentUser: user,
|
|
61
|
+
currentContract: contract,
|
|
62
|
+
},
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const AddressPage = ({ data, context }: BuyingPolicyDetailsPageData) => (
|
|
67
|
+
<BuyerPortalProvider {...context}>
|
|
68
|
+
<BuyingPolicyDetailsLayout data={data} />
|
|
69
|
+
</BuyerPortalProvider>
|
|
70
|
+
);
|
|
71
|
+
|
|
72
|
+
export default AddressPage;
|
package/src/themes/layouts.scss
CHANGED
|
@@ -21,3 +21,7 @@
|
|
|
21
21
|
|
|
22
22
|
// Credit Cards
|
|
23
23
|
@import "../features/credit-cards/layouts/CreditCardsLayout/credit-card-layout.scss";
|
|
24
|
+
|
|
25
|
+
// Buying Policies
|
|
26
|
+
@import "../features/buying-policies/layouts/BuyingPoliciesLayout/buying-policies-layout.scss";
|
|
27
|
+
@import "../features/buying-policies/layouts/BuyingPolicyDetailsLayout/buying-policy-details-layout.scss";
|