@vtex/faststore-plugin-buyer-portal 1.3.47 → 1.3.49
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +14 -1
- package/package.json +1 -1
- package/src/features/buying-policies/components/BasicBuyingPolicyDrawer/BasicBuyingPolicyDrawer.tsx +43 -0
- package/src/features/buying-policies/components/BasicBuyingPolicyDrawer/basic-buying-policy-drawer.scss +1 -1
- package/src/features/buying-policies/components/CustomFieldCriteriaSelector/CustomFieldCriteriaSelector.tsx +154 -0
- package/src/features/buying-policies/components/CustomFieldCriteriaSelector/custom-field-criteria-selector.scss +139 -0
- package/src/features/buying-policies/utils/index.ts +2 -0
- package/src/features/buying-policies/utils/orderFieldsCriteriaOptions.ts +6 -4
- package/src/features/org-units/hooks/useOrgUnitByUser.ts +4 -1
- package/src/features/product-assortment/hooks/useGetProductAssortment.ts +52 -0
- package/src/features/product-assortment/hooks/useGetProductAssortmentFromContract.ts +35 -0
- package/src/features/product-assortment/layouts/ProductAssortmentLayout/ProductAssortmentLayout.tsx +55 -12
- package/src/features/product-assortment/layouts/ProductAssortmentLayout/product-assortment-layout.scss +1 -1
- package/src/features/product-assortment/types/index.ts +6 -14
- package/src/features/shared/hooks/usePageItems.ts +2 -2
- package/src/features/shared/layouts/ContractTabsLayout/ContractTabsLayout.tsx +1 -0
- package/src/features/shared/utils/constants.ts +1 -1
- package/src/pages/productAssortment.tsx +61 -65
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,17 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [1.3.49] - 2025-12-19
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- Add component of Criteria Selection of Custom Fields on Buying Policies
|
|
15
|
+
|
|
16
|
+
## [1.3.48] - 2025-12-19
|
|
17
|
+
|
|
18
|
+
- Adjustment from merge to Collections to Products Assortment
|
|
19
|
+
- Change Products Assortment to client side
|
|
20
|
+
|
|
10
21
|
## [1.3.47] - 2025-12-19
|
|
11
22
|
|
|
12
23
|
- Alternative Login Keys:
|
|
@@ -406,7 +417,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
406
417
|
- Add CHANGELOG file
|
|
407
418
|
- Add README file
|
|
408
419
|
|
|
409
|
-
[unreleased]: https://github.com/vtex/faststore-plugin-buyer-portal/compare/v1.3.
|
|
420
|
+
[unreleased]: https://github.com/vtex/faststore-plugin-buyer-portal/compare/v1.3.49...HEAD
|
|
410
421
|
[1.2.3]: https://github.com/vtex/faststore-plugin-buyer-portal/compare/v1.2.2...1.2.3
|
|
411
422
|
[1.2.3]: https://github.com/vtex/faststore-plugin-buyer-portal/releases/tag/1.2.3
|
|
412
423
|
[1.2.4]: https://github.com/vtex/faststore-plugin-buyer-portal/releases/tag/1.2.4
|
|
@@ -456,6 +467,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
456
467
|
[1.3.36]: https://github.com/vtex/faststore-plugin-buyer-portal/compare/v1.3.35...v1.3.36
|
|
457
468
|
[1.3.35]: https://github.com/vtex/faststore-plugin-buyer-portal/releases/tag/1.3.35
|
|
458
469
|
|
|
470
|
+
[1.3.49]: https://github.com/vtex/faststore-plugin-buyer-portal/compare/v1.3.48...v1.3.49
|
|
471
|
+
[1.3.48]: https://github.com/vtex/faststore-plugin-buyer-portal/compare/v1.3.47...v1.3.48
|
|
459
472
|
[1.3.47]: https://github.com/vtex/faststore-plugin-buyer-portal/compare/v1.3.46...v1.3.47
|
|
460
473
|
[1.3.46]: https://github.com/vtex/faststore-plugin-buyer-portal/compare/v1.3.45...v1.3.46
|
|
461
474
|
[1.3.45]: https://github.com/vtex/faststore-plugin-buyer-portal/compare/v1.3.44...v1.3.45
|
package/package.json
CHANGED
package/src/features/buying-policies/components/BasicBuyingPolicyDrawer/BasicBuyingPolicyDrawer.tsx
CHANGED
|
@@ -24,12 +24,15 @@ import {
|
|
|
24
24
|
BUYING_POLICIES_WORKFLOW_TYPES,
|
|
25
25
|
buyingPolicyDefault,
|
|
26
26
|
BUDGET_CRITERIA,
|
|
27
|
+
COST_CENTER_CRITERIA,
|
|
28
|
+
PO_NUMBER_CRITERIA,
|
|
27
29
|
orderFieldsCriteriaOptions,
|
|
28
30
|
spendingLimitsCriteriaOptions,
|
|
29
31
|
} from "../../utils";
|
|
30
32
|
import { BUYING_POLICIES_WORKFLOW_LABELS } from "../../utils/buyingPoliciesWorkflowTypes";
|
|
31
33
|
import { getHighlightedText } from "../../utils/criteriaHighlightSyntax";
|
|
32
34
|
import { BudgetCriteriaSelector } from "../BudgetCriteriaSelector";
|
|
35
|
+
import { CustomFieldCriteriaSelector } from "../CustomFieldCriteriaSelector/CustomFieldCriteriaSelector";
|
|
33
36
|
|
|
34
37
|
const LIMIT_OF_LEVELS = 5;
|
|
35
38
|
|
|
@@ -173,6 +176,18 @@ export const BasicBuyingPolicyDrawer = ({
|
|
|
173
176
|
criteria === BUDGET_CRITERIA ||
|
|
174
177
|
criteria.includes('$exists(budgetData.budgets[id="');
|
|
175
178
|
|
|
179
|
+
const isPONumberCriteria =
|
|
180
|
+
criteria === PO_NUMBER_CRITERIA ||
|
|
181
|
+
criteria.includes(
|
|
182
|
+
'customData.customFields.fields[name="PO Number"].value ='
|
|
183
|
+
);
|
|
184
|
+
|
|
185
|
+
const isCostCenterCriteria =
|
|
186
|
+
criteria === COST_CENTER_CRITERIA ||
|
|
187
|
+
criteria.includes(
|
|
188
|
+
'customData.customFields.fields[name="Cost Center"].value ='
|
|
189
|
+
);
|
|
190
|
+
|
|
176
191
|
const renderCriteria = (criteria: string) => {
|
|
177
192
|
if (isBudgetCriteria) {
|
|
178
193
|
return (
|
|
@@ -186,6 +201,34 @@ export const BasicBuyingPolicyDrawer = ({
|
|
|
186
201
|
);
|
|
187
202
|
}
|
|
188
203
|
|
|
204
|
+
if (isPONumberCriteria) {
|
|
205
|
+
return (
|
|
206
|
+
<CustomFieldCriteriaSelector
|
|
207
|
+
key={"PO Number"}
|
|
208
|
+
fieldName="PO Number"
|
|
209
|
+
currentCriteria={criteria}
|
|
210
|
+
onCriteriaChange={(newCriteria) =>
|
|
211
|
+
updateField("criteria", newCriteria)
|
|
212
|
+
}
|
|
213
|
+
onEditablePartClick={() => setCriteriaFocused(true)}
|
|
214
|
+
/>
|
|
215
|
+
);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
if (isCostCenterCriteria) {
|
|
219
|
+
return (
|
|
220
|
+
<CustomFieldCriteriaSelector
|
|
221
|
+
key={"Cost Center"}
|
|
222
|
+
fieldName="Cost Center"
|
|
223
|
+
currentCriteria={criteria}
|
|
224
|
+
onCriteriaChange={(newCriteria) =>
|
|
225
|
+
updateField("criteria", newCriteria)
|
|
226
|
+
}
|
|
227
|
+
onEditablePartClick={() => setCriteriaFocused(true)}
|
|
228
|
+
/>
|
|
229
|
+
);
|
|
230
|
+
}
|
|
231
|
+
|
|
189
232
|
return getHighlightedText(criteria);
|
|
190
233
|
};
|
|
191
234
|
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
@import '../../../shared/components/OrgUnitInputSearch/org-unit-input-search.scss';
|
|
3
3
|
@import '../../../shared/components/CustomDropdown/custom-dropdown.scss';
|
|
4
4
|
@import '../BudgetCriteriaSelector/budget-criteria-selector.scss';
|
|
5
|
+
@import '../CustomFieldCriteriaSelector/custom-field-criteria-selector.scss';
|
|
5
6
|
|
|
6
7
|
[data-fs-bp-basic-buying-policy-drawer] {
|
|
7
8
|
@import '../../../shared/components/InputText/input-text.scss';
|
|
@@ -108,7 +109,6 @@
|
|
|
108
109
|
text-wrap: nowrap;
|
|
109
110
|
text-overflow: ellipsis;
|
|
110
111
|
white-space: nowrap;
|
|
111
|
-
overflow: hidden;
|
|
112
112
|
max-width: 840px;
|
|
113
113
|
cursor: pointer;
|
|
114
114
|
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import { useEffect, useState } from "react";
|
|
2
|
+
|
|
3
|
+
import { CustomDropdown } from "../../../shared/components/CustomDropdown/CustomDropdown";
|
|
4
|
+
import { useBuyerPortal } from "../../../shared/hooks";
|
|
5
|
+
import { useCustomFieldValues } from "../../../shared/hooks/custom-field";
|
|
6
|
+
|
|
7
|
+
export type CustomFieldCriteriaSelectorProps = {
|
|
8
|
+
fieldName: "PO Number" | "Cost Center";
|
|
9
|
+
onCriteriaChange: (criteria: string) => void;
|
|
10
|
+
currentCriteria: string;
|
|
11
|
+
onEditablePartClick?: () => void;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export const CustomFieldCriteriaSelector = ({
|
|
15
|
+
fieldName,
|
|
16
|
+
onCriteriaChange,
|
|
17
|
+
currentCriteria,
|
|
18
|
+
onEditablePartClick,
|
|
19
|
+
}: CustomFieldCriteriaSelectorProps) => {
|
|
20
|
+
const {
|
|
21
|
+
currentOrgUnit: orgUnit,
|
|
22
|
+
currentContract: contract,
|
|
23
|
+
clientContext,
|
|
24
|
+
} = useBuyerPortal();
|
|
25
|
+
|
|
26
|
+
const cookie = clientContext?.cookie ?? "";
|
|
27
|
+
|
|
28
|
+
const [isDropdownOpen, setIsDropdownOpen] = useState(false);
|
|
29
|
+
|
|
30
|
+
const { data, isLoading, refetch } = useCustomFieldValues({
|
|
31
|
+
data: {
|
|
32
|
+
contractId: contract?.id || "",
|
|
33
|
+
customField: fieldName,
|
|
34
|
+
unitId: orgUnit?.id || "",
|
|
35
|
+
cookie,
|
|
36
|
+
params: {
|
|
37
|
+
value: "",
|
|
38
|
+
page: 1,
|
|
39
|
+
filterByUnit: true,
|
|
40
|
+
},
|
|
41
|
+
},
|
|
42
|
+
options: { lazy: true },
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
const extractValueFromCriteria = (criteria: string): string => {
|
|
46
|
+
const match = criteria.match(/value\s*=\s*"([^"]+)"/);
|
|
47
|
+
return match
|
|
48
|
+
? match[1]
|
|
49
|
+
: `${fieldName.toLowerCase().replace(/\s+/g, "-")}-001`;
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
const [currentFieldValue, setCurrentFieldValue] = useState<string>(
|
|
53
|
+
extractValueFromCriteria(currentCriteria)
|
|
54
|
+
);
|
|
55
|
+
|
|
56
|
+
useEffect(() => {
|
|
57
|
+
setIsDropdownOpen(false);
|
|
58
|
+
const extractedValue = extractValueFromCriteria(currentCriteria);
|
|
59
|
+
setCurrentFieldValue(extractedValue);
|
|
60
|
+
}, [currentCriteria, fieldName]);
|
|
61
|
+
|
|
62
|
+
const generateCriteriaString = (value: string) => {
|
|
63
|
+
return `customData.customFields.fields[name="${fieldName}"].value = "${value}"`;
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
const handleFieldSelect = (selectedIdOrValue: string) => {
|
|
67
|
+
if (selectedIdOrValue === "loading") return;
|
|
68
|
+
|
|
69
|
+
const items = data?.data || [];
|
|
70
|
+
|
|
71
|
+
const selectedItem = items.find(
|
|
72
|
+
(item) =>
|
|
73
|
+
item.value === selectedIdOrValue || item.id === selectedIdOrValue
|
|
74
|
+
);
|
|
75
|
+
|
|
76
|
+
if (!selectedItem) return;
|
|
77
|
+
|
|
78
|
+
const valueToUse = selectedItem.value || selectedIdOrValue;
|
|
79
|
+
setCurrentFieldValue(valueToUse);
|
|
80
|
+
onCriteriaChange(generateCriteriaString(valueToUse));
|
|
81
|
+
setIsDropdownOpen(false);
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
const handleFieldValueClick = (e: React.MouseEvent) => {
|
|
85
|
+
e.stopPropagation();
|
|
86
|
+
setIsDropdownOpen(true);
|
|
87
|
+
|
|
88
|
+
if ((!data?.data || data.data.length === 0) && !isLoading) {
|
|
89
|
+
refetch();
|
|
90
|
+
}
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
const handleEditablePartClick = (e: React.MouseEvent) => {
|
|
94
|
+
e.stopPropagation();
|
|
95
|
+
onEditablePartClick?.();
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
const loadingOptions = [
|
|
99
|
+
{
|
|
100
|
+
label: `Loading ${fieldName.toLowerCase()}...`,
|
|
101
|
+
value: "loading",
|
|
102
|
+
},
|
|
103
|
+
];
|
|
104
|
+
|
|
105
|
+
const fieldOptions = (data?.data || []).map((item) => ({
|
|
106
|
+
label: item.name || item.value || `${fieldName}-${item.id?.slice(0, 8)}`,
|
|
107
|
+
value: item.value || item.id,
|
|
108
|
+
}));
|
|
109
|
+
|
|
110
|
+
const options =
|
|
111
|
+
isLoading || !data?.data || data.data.length === 0
|
|
112
|
+
? loadingOptions
|
|
113
|
+
: fieldOptions;
|
|
114
|
+
|
|
115
|
+
const triggerLabel = isLoading ? "Loading..." : currentFieldValue;
|
|
116
|
+
|
|
117
|
+
return (
|
|
118
|
+
<div data-fs-bp-custom-field-criteria-selector data-field-name={fieldName}>
|
|
119
|
+
<div data-fs-bp-custom-field-criteria-string>
|
|
120
|
+
<span onClick={handleEditablePartClick} data-fs-bp-editable-part>
|
|
121
|
+
customData.customFields.fields[name="{fieldName}"].value = "
|
|
122
|
+
</span>
|
|
123
|
+
|
|
124
|
+
<div
|
|
125
|
+
onClick={handleFieldValueClick}
|
|
126
|
+
style={{ display: "inline-block" }}
|
|
127
|
+
>
|
|
128
|
+
<CustomDropdown
|
|
129
|
+
options={options}
|
|
130
|
+
onSelect={handleFieldSelect}
|
|
131
|
+
triggerLabel={triggerLabel}
|
|
132
|
+
isOpen={isDropdownOpen}
|
|
133
|
+
highlightText={(text) => (
|
|
134
|
+
<div data-fs-bp-dropdown-option>
|
|
135
|
+
<div data-fs-bp-dropdown-option-label>{text}</div>
|
|
136
|
+
{!isLoading && data?.data && (
|
|
137
|
+
<div data-fs-bp-dropdown-option-uuid>
|
|
138
|
+
{data.data.find(
|
|
139
|
+
(item) => (item.name || item.value) === text
|
|
140
|
+
)?.value || text}
|
|
141
|
+
</div>
|
|
142
|
+
)}
|
|
143
|
+
</div>
|
|
144
|
+
)}
|
|
145
|
+
/>
|
|
146
|
+
</div>
|
|
147
|
+
|
|
148
|
+
<span onClick={handleEditablePartClick} data-fs-bp-editable-part>
|
|
149
|
+
"
|
|
150
|
+
</span>
|
|
151
|
+
</div>
|
|
152
|
+
</div>
|
|
153
|
+
);
|
|
154
|
+
};
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
[data-fs-bp-custom-field-criteria-selector] {
|
|
2
|
+
position: absolute;
|
|
3
|
+
top: 0;
|
|
4
|
+
left: 0;
|
|
5
|
+
width: 100%;
|
|
6
|
+
height: 100%;
|
|
7
|
+
margin: 0;
|
|
8
|
+
background-color: var(--fs-bp-color-neutral-1);
|
|
9
|
+
border: var(--fs-border-width) solid var(--fs-bp-color-neutral-4);
|
|
10
|
+
border-radius: calc(var(--fs-border-radius) * 2);
|
|
11
|
+
display: flex;
|
|
12
|
+
flex-direction: column;
|
|
13
|
+
align-items: flex-start;
|
|
14
|
+
|
|
15
|
+
[data-fs-bp-custom-field-criteria-string] {
|
|
16
|
+
font-family: "Roboto", monospace;
|
|
17
|
+
font-size: var(--fs-text-size-1);
|
|
18
|
+
color: var(--fs-bp-color-neutral-7);
|
|
19
|
+
display: flex;
|
|
20
|
+
align-items: center;
|
|
21
|
+
padding: var(--fs-spacing-3) var(--fs-spacing-3);
|
|
22
|
+
gap: 0;
|
|
23
|
+
flex-wrap: wrap;
|
|
24
|
+
width: 100%;
|
|
25
|
+
|
|
26
|
+
> span {
|
|
27
|
+
white-space: nowrap;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
[data-fs-bp-editable-part] {
|
|
31
|
+
cursor: pointer;
|
|
32
|
+
transition: background-color 0.2s ease;
|
|
33
|
+
padding: 0.125rem 0 0.25rem;
|
|
34
|
+
border-radius: calc(var(--fs-border-radius) * 0.5);
|
|
35
|
+
|
|
36
|
+
&:hover {
|
|
37
|
+
background-color: var(--fs-bp-color-neutral-1);
|
|
38
|
+
color: var(--fs-bp-color-neutral-8);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
[data-fs-bp-custom-dropdown] {
|
|
43
|
+
display: inline-flex;
|
|
44
|
+
|
|
45
|
+
[data-fs-bp-custom-dropdown-trigger] {
|
|
46
|
+
color: var(--fs-color-highlight);
|
|
47
|
+
border: none;
|
|
48
|
+
min-width: 0;
|
|
49
|
+
border-radius: calc(var(--fs-border-radius) * 1.5);
|
|
50
|
+
font-family: "Roboto", monospace;
|
|
51
|
+
font-size: calc(var(--fs-text-size-1) * 0.9);
|
|
52
|
+
padding: 0;
|
|
53
|
+
cursor: pointer;
|
|
54
|
+
transition: background-color 0.2s ease;
|
|
55
|
+
background-color: transparent;
|
|
56
|
+
|
|
57
|
+
&:hover {
|
|
58
|
+
background-color: var(--fs-bp-color-neutral-1);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
[data-fs-bp-custom-dropdown-menu] {
|
|
63
|
+
position: absolute;
|
|
64
|
+
top: 100%;
|
|
65
|
+
left: 0;
|
|
66
|
+
margin-top: var(--fs-spacing-1);
|
|
67
|
+
padding: var(--fs-spacing-1) 0;
|
|
68
|
+
background-color: var(--fs-bp-color-neutral-0);
|
|
69
|
+
box-shadow: 0rem 0.5rem 0.625rem 0rem rgba(0, 0, 0, 0.0784313725);
|
|
70
|
+
max-height: 15rem;
|
|
71
|
+
min-width: 20rem;
|
|
72
|
+
overflow-y: auto;
|
|
73
|
+
z-index: 1;
|
|
74
|
+
border-radius: calc(var(--fs-border-radius) * 2);
|
|
75
|
+
|
|
76
|
+
&::-webkit-scrollbar {
|
|
77
|
+
width: 0.75rem;
|
|
78
|
+
height: 0.75rem;
|
|
79
|
+
background-color: var(--fs-bp-color-neutral-0);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
&::-webkit-scrollbar-thumb {
|
|
83
|
+
background-color: #adadad;
|
|
84
|
+
border: 0.25rem solid transparent;
|
|
85
|
+
border-radius: 0.375rem;
|
|
86
|
+
background-clip: content-box;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
[data-fs-bp-custom-dropdown-item] {
|
|
90
|
+
padding: var(--fs-spacing-2) var(--fs-spacing-3);
|
|
91
|
+
cursor: pointer;
|
|
92
|
+
border-bottom: var(--fs-border-width) solid #f0f0f0;
|
|
93
|
+
|
|
94
|
+
&:last-child {
|
|
95
|
+
border-bottom: none;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
&:hover,
|
|
99
|
+
&[data-focused="true"] {
|
|
100
|
+
background-color: var(--fs-bp-color-neutral-2);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
[data-fs-bp-dropdown-option] {
|
|
104
|
+
font-family: Inter;
|
|
105
|
+
|
|
106
|
+
[data-fs-bp-dropdown-option-label] {
|
|
107
|
+
font-weight: var(--fs-text-weight-semibold);
|
|
108
|
+
color: var(--fs-bp-color-neutral-8);
|
|
109
|
+
font-size: var(--fs-text-size-1);
|
|
110
|
+
margin-bottom: var(--fs-spacing-0);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
[data-fs-bp-dropdown-option-uuid] {
|
|
114
|
+
font-size: var(--fs-text-size-0);
|
|
115
|
+
color: var(--fs-bp-color-neutral-7);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
[data-fs-bp-custom-value-indicator] {
|
|
124
|
+
font-size: calc(var(--fs-text-size-0) * 0.85);
|
|
125
|
+
color: #666666;
|
|
126
|
+
font-style: italic;
|
|
127
|
+
padding: 0 var(--fs-spacing-3) calc(var(--fs-spacing-1) * 0.5);
|
|
128
|
+
border-top: 0.0625rem solid var(--fs-bp-color-neutral-3);
|
|
129
|
+
margin-top: auto;
|
|
130
|
+
width: 100%;
|
|
131
|
+
background-color: #fafafa;
|
|
132
|
+
border-radius: 0 0 calc(var(--fs-border-radius) * 2)
|
|
133
|
+
calc(var(--fs-border-radius) * 2);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
[data-fs-bp-custom-field-value] {
|
|
137
|
+
color: var(--fs-color-highlight);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
export { buyingPolicyDefault } from "./buyingPolicyDefault";
|
|
2
2
|
export {
|
|
3
3
|
BUDGET_CRITERIA,
|
|
4
|
+
PO_NUMBER_CRITERIA,
|
|
5
|
+
COST_CENTER_CRITERIA,
|
|
4
6
|
orderFieldsCriteriaOptions,
|
|
5
7
|
} from "./orderFieldsCriteriaOptions";
|
|
6
8
|
export { spendingLimitsCriteriaOptions } from "./spendingLimitsCriteriaOptions";
|
|
@@ -1,4 +1,8 @@
|
|
|
1
1
|
export const BUDGET_CRITERIA = '$exists(budgetData.budgets[id="${budget-id}"])';
|
|
2
|
+
export const PO_NUMBER_CRITERIA =
|
|
3
|
+
'customData.customFields.fields[name="PO Number"].value = "${po-number}"';
|
|
4
|
+
export const COST_CENTER_CRITERIA =
|
|
5
|
+
'customData.customFields.fields[name="Cost Center"].value = "${cost-center}"';
|
|
2
6
|
|
|
3
7
|
export const orderFieldsCriteriaOptions = [
|
|
4
8
|
{
|
|
@@ -25,8 +29,7 @@ export const orderFieldsCriteriaOptions = [
|
|
|
25
29
|
},
|
|
26
30
|
{
|
|
27
31
|
label: "If the order contains the PO numbers X, Y, Z",
|
|
28
|
-
criteria:
|
|
29
|
-
'customData.customFields.fields[name="PO Number"].value = "1112223334444" or customData.customFields.fields[name="PO Number"].value = "1234567890"',
|
|
32
|
+
criteria: PO_NUMBER_CRITERIA,
|
|
30
33
|
},
|
|
31
34
|
{
|
|
32
35
|
label: "If the order contains a cost center",
|
|
@@ -35,8 +38,7 @@ export const orderFieldsCriteriaOptions = [
|
|
|
35
38
|
},
|
|
36
39
|
{
|
|
37
40
|
label: "If the order contains the cost centers X, Y, Z",
|
|
38
|
-
criteria:
|
|
39
|
-
'customData.customFields.fields[name="Cost Center"].value = "CC1" or customData.customFields.fields[name="Cost Center"].value = "CC2"',
|
|
41
|
+
criteria: COST_CENTER_CRITERIA,
|
|
40
42
|
},
|
|
41
43
|
{
|
|
42
44
|
label: "If all order items are negotiated items",
|
|
@@ -8,7 +8,10 @@ export const useOrgUnitByUser = (
|
|
|
8
8
|
const { data, error, isLoading, refetch } = useQuery(
|
|
9
9
|
`api/org-unit-by-user-id/${userId}`,
|
|
10
10
|
({ cookie }) => getOrgUnitByUserIdService({ cookie, userId }),
|
|
11
|
-
|
|
11
|
+
{
|
|
12
|
+
...options,
|
|
13
|
+
lazy: !userId || options?.lazy,
|
|
14
|
+
}
|
|
12
15
|
);
|
|
13
16
|
return {
|
|
14
17
|
orgUnit: data,
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { type QueryOptions, useQuery } from "../../shared/hooks";
|
|
2
|
+
import { getProductAssortmentFromScopeService } from "../services/get-product-assortment-from-scope.service";
|
|
3
|
+
|
|
4
|
+
export const useGetProductAssortment = ({
|
|
5
|
+
contractId,
|
|
6
|
+
orgUnitId,
|
|
7
|
+
search = "",
|
|
8
|
+
filterByScope = false,
|
|
9
|
+
page = 1,
|
|
10
|
+
options,
|
|
11
|
+
}: {
|
|
12
|
+
contractId: string;
|
|
13
|
+
orgUnitId: string;
|
|
14
|
+
filterByScope?: boolean;
|
|
15
|
+
search?: string;
|
|
16
|
+
page?: number;
|
|
17
|
+
options?: QueryOptions<
|
|
18
|
+
AwaitedType<typeof getProductAssortmentFromScopeService>
|
|
19
|
+
>;
|
|
20
|
+
}) => {
|
|
21
|
+
const { data, error, isLoading, refetch } = useQuery(
|
|
22
|
+
`customers/${contractId}/units/${orgUnitId}/collections`,
|
|
23
|
+
({ cookie }) =>
|
|
24
|
+
getProductAssortmentFromScopeService({
|
|
25
|
+
args: {
|
|
26
|
+
cookie,
|
|
27
|
+
contractId,
|
|
28
|
+
unitId: orgUnitId,
|
|
29
|
+
filterByScope,
|
|
30
|
+
name: search,
|
|
31
|
+
page,
|
|
32
|
+
},
|
|
33
|
+
}),
|
|
34
|
+
{ ...options }
|
|
35
|
+
);
|
|
36
|
+
|
|
37
|
+
return {
|
|
38
|
+
data: {
|
|
39
|
+
items: data?.items ?? [],
|
|
40
|
+
paging: {
|
|
41
|
+
page: data?.paging?.page ?? 1,
|
|
42
|
+
total: data?.paging?.total ?? 0,
|
|
43
|
+
pages: data?.paging?.pages ?? 0,
|
|
44
|
+
limit: data?.paging?.limit ?? 10,
|
|
45
|
+
perPage: data?.paging?.perPage ?? 10,
|
|
46
|
+
},
|
|
47
|
+
},
|
|
48
|
+
hasError: !!error,
|
|
49
|
+
isProductAssortmentLoading: isLoading,
|
|
50
|
+
refetchProductAssortment: refetch,
|
|
51
|
+
};
|
|
52
|
+
};
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { type QueryOptions, useQuery } from "../../shared/hooks";
|
|
2
|
+
import { getProductAssortmentFromContractService } from "../services/get-product-assortment-from-contract.service";
|
|
3
|
+
|
|
4
|
+
export const useGetProductAssortmentFromContract = ({
|
|
5
|
+
contractId,
|
|
6
|
+
orgUnitId,
|
|
7
|
+
search = "",
|
|
8
|
+
options,
|
|
9
|
+
}: {
|
|
10
|
+
contractId: string;
|
|
11
|
+
orgUnitId: string;
|
|
12
|
+
search?: string;
|
|
13
|
+
options?: QueryOptions<
|
|
14
|
+
AwaitedType<typeof getProductAssortmentFromContractService>
|
|
15
|
+
>;
|
|
16
|
+
}) => {
|
|
17
|
+
const { data, error, isLoading, refetch } = useQuery(
|
|
18
|
+
`customers/${contractId}/units/${orgUnitId}/collections`,
|
|
19
|
+
({ cookie }) =>
|
|
20
|
+
getProductAssortmentFromContractService({
|
|
21
|
+
cookie,
|
|
22
|
+
contractId,
|
|
23
|
+
unitId: orgUnitId,
|
|
24
|
+
name: search,
|
|
25
|
+
}),
|
|
26
|
+
{ ...options }
|
|
27
|
+
);
|
|
28
|
+
|
|
29
|
+
return {
|
|
30
|
+
data,
|
|
31
|
+
hasError: !!error,
|
|
32
|
+
isProductAssortmentFromContractLoading: isLoading,
|
|
33
|
+
refetchProductAssortmentFromContract: refetch,
|
|
34
|
+
};
|
|
35
|
+
};
|
package/src/features/product-assortment/layouts/ProductAssortmentLayout/ProductAssortmentLayout.tsx
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { useMemo } from "react";
|
|
2
|
+
|
|
1
3
|
import { Tooltip } from "@faststore/ui";
|
|
2
4
|
|
|
3
5
|
import {
|
|
@@ -6,11 +8,16 @@ import {
|
|
|
6
8
|
Paginator,
|
|
7
9
|
} from "../../../shared/components";
|
|
8
10
|
import { EmptyState } from "../../../shared/components/EmptyState/EmptyState";
|
|
9
|
-
import {
|
|
10
|
-
|
|
11
|
+
import {
|
|
12
|
+
useBuyerPortal,
|
|
13
|
+
useDrawerProps,
|
|
14
|
+
usePageItems,
|
|
15
|
+
} from "../../../shared/hooks";
|
|
11
16
|
import { ContractTabsLayout, GlobalLayout } from "../../../shared/layouts";
|
|
12
17
|
import { AddProductAssortmentDrawer } from "../../components";
|
|
13
18
|
import { ProductAssortmentTable } from "../../components/ProductAssortmentTable/ProductAssortmentTable";
|
|
19
|
+
import { useGetProductAssortment } from "../../hooks/useGetProductAssortment";
|
|
20
|
+
import { useGetProductAssortmentFromContract } from "../../hooks/useGetProductAssortmentFromContract";
|
|
14
21
|
|
|
15
22
|
import type {
|
|
16
23
|
ProductAssortmentLayoutProps,
|
|
@@ -18,11 +25,10 @@ import type {
|
|
|
18
25
|
} from "../../types";
|
|
19
26
|
|
|
20
27
|
export const ProductAssortmentLayout = ({
|
|
21
|
-
initialProductAssortment,
|
|
22
|
-
drawerProductAssortment,
|
|
23
|
-
isContractEmpty,
|
|
24
28
|
page,
|
|
25
29
|
search,
|
|
30
|
+
orgUnitId,
|
|
31
|
+
contractId,
|
|
26
32
|
}: ProductAssortmentLayoutProps) => {
|
|
27
33
|
const {
|
|
28
34
|
currentOrgUnit: orgUnit,
|
|
@@ -30,6 +36,39 @@ export const ProductAssortmentLayout = ({
|
|
|
30
36
|
currentContract: contract,
|
|
31
37
|
} = useBuyerPortal();
|
|
32
38
|
|
|
39
|
+
const { data: drawerProductAssortment } = useGetProductAssortment({
|
|
40
|
+
contractId,
|
|
41
|
+
orgUnitId,
|
|
42
|
+
});
|
|
43
|
+
const { data: contractAssortment } = useGetProductAssortmentFromContract({
|
|
44
|
+
contractId,
|
|
45
|
+
orgUnitId,
|
|
46
|
+
});
|
|
47
|
+
const { data: initialProductAssortment, isProductAssortmentLoading } =
|
|
48
|
+
useGetProductAssortment({
|
|
49
|
+
filterByScope: true,
|
|
50
|
+
contractId,
|
|
51
|
+
orgUnitId,
|
|
52
|
+
search,
|
|
53
|
+
page,
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
const isContractEmpty = !contractAssortment || contractAssortment.total === 0;
|
|
57
|
+
|
|
58
|
+
const itemsKey = useMemo(
|
|
59
|
+
() =>
|
|
60
|
+
initialProductAssortment.items
|
|
61
|
+
.map((item) => item.id)
|
|
62
|
+
.sort()
|
|
63
|
+
.join(","),
|
|
64
|
+
[initialProductAssortment.items]
|
|
65
|
+
);
|
|
66
|
+
|
|
67
|
+
const memoizedInitialItems = useMemo(
|
|
68
|
+
() => initialProductAssortment.items,
|
|
69
|
+
[itemsKey]
|
|
70
|
+
);
|
|
71
|
+
|
|
33
72
|
const {
|
|
34
73
|
isLoading,
|
|
35
74
|
items: productAssortment,
|
|
@@ -38,7 +77,7 @@ export const ProductAssortmentLayout = ({
|
|
|
38
77
|
increasePage,
|
|
39
78
|
decreasePage,
|
|
40
79
|
} = usePageItems<ProductAssortmentWithAdditionalInformation>({
|
|
41
|
-
initialItems:
|
|
80
|
+
initialItems: memoizedInitialItems,
|
|
42
81
|
search,
|
|
43
82
|
page,
|
|
44
83
|
});
|
|
@@ -53,7 +92,6 @@ export const ProductAssortmentLayout = ({
|
|
|
53
92
|
...addProductAssortmentDrawerProps
|
|
54
93
|
} = useDrawerProps();
|
|
55
94
|
|
|
56
|
-
//const enabledAssortment = productAssortment.filter((p) => p.isEnabled);
|
|
57
95
|
const hasAllAssortment = drawerProductAssortment.items.length === 0;
|
|
58
96
|
const isEmpty = productAssortment.length === 0;
|
|
59
97
|
|
|
@@ -72,7 +110,7 @@ export const ProductAssortmentLayout = ({
|
|
|
72
110
|
<GlobalLayout>
|
|
73
111
|
<ContractTabsLayout
|
|
74
112
|
orgUnitName={orgUnit?.name ?? ""}
|
|
75
|
-
orgUnitId={orgUnit?.id ?? ""}
|
|
113
|
+
orgUnitId={orgUnitId ?? orgUnit?.id ?? ""}
|
|
76
114
|
contractName={contract?.name ?? ""}
|
|
77
115
|
contractId={contract?.id ?? ""}
|
|
78
116
|
pageName="Contract"
|
|
@@ -80,6 +118,7 @@ export const ProductAssortmentLayout = ({
|
|
|
80
118
|
image: undefined,
|
|
81
119
|
name: user?.name ?? "",
|
|
82
120
|
role: user?.role ?? "",
|
|
121
|
+
id: user?.id ?? "",
|
|
83
122
|
}}
|
|
84
123
|
>
|
|
85
124
|
<section data-fs-bp-product-assortment-container>
|
|
@@ -140,9 +179,11 @@ export const ProductAssortmentLayout = ({
|
|
|
140
179
|
{initialProductAssortment.paging.page > 1 ? (
|
|
141
180
|
<Paginator.NextPageButton
|
|
142
181
|
onClick={decreasePage}
|
|
143
|
-
disabled={isLoading}
|
|
182
|
+
disabled={isLoading || isProductAssortmentLoading}
|
|
144
183
|
>
|
|
145
|
-
{isLoading
|
|
184
|
+
{isLoading || isProductAssortmentLoading
|
|
185
|
+
? "Loading"
|
|
186
|
+
: "Previous Page"}
|
|
146
187
|
</Paginator.NextPageButton>
|
|
147
188
|
) : (
|
|
148
189
|
<></>
|
|
@@ -150,9 +191,11 @@ export const ProductAssortmentLayout = ({
|
|
|
150
191
|
{!isLastPage && !isEmpty ? (
|
|
151
192
|
<Paginator.NextPageButton
|
|
152
193
|
onClick={increasePage}
|
|
153
|
-
disabled={isLoading}
|
|
194
|
+
disabled={isLoading || isProductAssortmentLoading}
|
|
154
195
|
>
|
|
155
|
-
{isLoading
|
|
196
|
+
{isLoading || isProductAssortmentLoading
|
|
197
|
+
? "Loading"
|
|
198
|
+
: "Next Page"}
|
|
156
199
|
</Paginator.NextPageButton>
|
|
157
200
|
) : (
|
|
158
201
|
<></>
|
|
@@ -1,8 +1,5 @@
|
|
|
1
|
-
import type { ContractData } from "../../contracts/types";
|
|
2
|
-
import type { OrgUnitBasicData } from "../../org-units/types";
|
|
3
1
|
import type { BasicDrawerProps } from "../../shared/components";
|
|
4
2
|
import type { ErrorBoundaryProps } from "../../shared/components/ErrorBoundary/types";
|
|
5
|
-
import type { ClientContext } from "../../shared/utils";
|
|
6
3
|
|
|
7
4
|
export type ProductAssortmentSummary = {
|
|
8
5
|
id: string;
|
|
@@ -55,24 +52,19 @@ export interface ProductAssortmentSelectedProps {
|
|
|
55
52
|
}
|
|
56
53
|
|
|
57
54
|
export type ProductAssortmentLayoutProps = {
|
|
58
|
-
|
|
59
|
-
|
|
55
|
+
userId?: string;
|
|
56
|
+
orgUnitId: string;
|
|
57
|
+
contractId: string;
|
|
60
58
|
page: number;
|
|
61
59
|
search: string;
|
|
62
|
-
isContractEmpty: boolean;
|
|
63
60
|
};
|
|
64
61
|
|
|
65
62
|
export type ProductAssortmentData = {
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
63
|
+
userId: string;
|
|
64
|
+
orgUnitId: string;
|
|
65
|
+
contractId: string;
|
|
69
66
|
search: string;
|
|
70
67
|
page: number;
|
|
71
|
-
context: {
|
|
72
|
-
clientContext: ClientContext;
|
|
73
|
-
currentOrgUnit: OrgUnitBasicData;
|
|
74
|
-
currentContract: ContractData | null;
|
|
75
|
-
};
|
|
76
68
|
hasError?: boolean;
|
|
77
69
|
error?: ErrorBoundaryProps;
|
|
78
70
|
};
|
|
@@ -37,6 +37,8 @@ export const usePageItems = <T>({
|
|
|
37
37
|
const [searchTerm, setSearchTerm] = useState(search ?? "");
|
|
38
38
|
const [isLoading, setIsLoading] = useState(false);
|
|
39
39
|
|
|
40
|
+
const { setQueryStrings, setQueryString } = useQueryParams();
|
|
41
|
+
|
|
40
42
|
useDebounce(searchTerm, DEBOUNCE_TIMEOUT, {
|
|
41
43
|
onDebounce: (value) => {
|
|
42
44
|
setIsLoading(true);
|
|
@@ -54,8 +56,6 @@ export const usePageItems = <T>({
|
|
|
54
56
|
},
|
|
55
57
|
});
|
|
56
58
|
|
|
57
|
-
const { setQueryStrings, setQueryString } = useQueryParams();
|
|
58
|
-
|
|
59
59
|
useEffect(() => {
|
|
60
60
|
setItems([]);
|
|
61
61
|
}, [search]);
|
|
@@ -1,80 +1,80 @@
|
|
|
1
1
|
import { getContractDetailsService } from "../features/contracts/services";
|
|
2
2
|
import { getOrgUnitBasicDataService } from "../features/org-units/services";
|
|
3
3
|
import { ProductAssortmentLayout } from "../features/product-assortment/layouts";
|
|
4
|
-
import { getProductAssortmentFromContractService } from "../features/product-assortment/services/get-product-assortment-from-contract.service";
|
|
5
|
-
import { getProductAssortmentFromScopeService } from "../features/product-assortment/services/get-product-assortment-from-scope.service";
|
|
6
4
|
import { withErrorBoundary } from "../features/shared/components";
|
|
5
|
+
import { ErrorBoundaryProps } from "../features/shared/components/ErrorBoundary/types";
|
|
7
6
|
import { ErrorTabsLayout } from "../features/shared/layouts/ErrorTabsLayout/ErrorTabsLayout";
|
|
8
7
|
import {
|
|
8
|
+
type ClientContext,
|
|
9
9
|
getValidPage,
|
|
10
|
-
withLoaderErrorBoundary,
|
|
11
10
|
withAuthLoader,
|
|
11
|
+
withLoaderErrorBoundary,
|
|
12
12
|
withProviders,
|
|
13
13
|
} from "../features/shared/utils";
|
|
14
|
+
import { getUserByIdService } from "../features/users/services";
|
|
14
15
|
|
|
15
|
-
import type {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
} from "../features/product-assortment/types";
|
|
16
|
+
import type { ContractData } from "../features/contracts/types";
|
|
17
|
+
import type { OrgUnitBasicData } from "../features/org-units/types";
|
|
18
|
+
import type { ProductAssortmentQuery } from "../features/product-assortment/types";
|
|
19
19
|
import type { AuthRouteProps, LoaderData } from "../features/shared/types";
|
|
20
|
+
import type { UserData } from "../features/users/types";
|
|
21
|
+
|
|
22
|
+
export type ProductAssortmentPageData = {
|
|
23
|
+
data: {
|
|
24
|
+
orgUnitId: string;
|
|
25
|
+
contractId: string;
|
|
26
|
+
userId: string;
|
|
27
|
+
search: string;
|
|
28
|
+
page: number;
|
|
29
|
+
};
|
|
30
|
+
context: {
|
|
31
|
+
clientContext: ClientContext;
|
|
32
|
+
currentOrgUnit: OrgUnitBasicData;
|
|
33
|
+
currentContract: ContractData | null;
|
|
34
|
+
currentUser: UserData | null;
|
|
35
|
+
};
|
|
36
|
+
hasError?: boolean;
|
|
37
|
+
error?: ErrorBoundaryProps;
|
|
38
|
+
};
|
|
20
39
|
|
|
21
40
|
const loaderFunction = async (
|
|
22
41
|
data: LoaderData<ProductAssortmentQuery>
|
|
23
|
-
): Promise<AuthRouteProps<
|
|
42
|
+
): Promise<AuthRouteProps<ProductAssortmentPageData>> => {
|
|
24
43
|
const { contractId, orgUnitId, search = "", page: pageString } = data.query;
|
|
25
44
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
const [enabledAssortment, notAddedAssortments, contractAssortment] =
|
|
35
|
-
await Promise.all([
|
|
36
|
-
getProductAssortmentFromScopeService({
|
|
37
|
-
args: {
|
|
38
|
-
cookie,
|
|
39
|
-
contractId,
|
|
40
|
-
unitId: orgUnitId,
|
|
41
|
-
filterByScope: true,
|
|
42
|
-
name: search,
|
|
43
|
-
page,
|
|
44
|
-
},
|
|
45
|
-
}),
|
|
46
|
-
getProductAssortmentFromScopeService({
|
|
47
|
-
args: {
|
|
48
|
-
cookie,
|
|
49
|
-
contractId,
|
|
50
|
-
unitId: orgUnitId,
|
|
51
|
-
filterByScope: false,
|
|
52
|
-
page: 1,
|
|
53
|
-
},
|
|
45
|
+
return withAuthLoader(
|
|
46
|
+
data,
|
|
47
|
+
async ({ customerId, cookie, userId, ...clientContext }) => {
|
|
48
|
+
const [orgUnit, contract, user] = await Promise.all([
|
|
49
|
+
getOrgUnitBasicDataService({
|
|
50
|
+
id: orgUnitId,
|
|
51
|
+
cookie,
|
|
54
52
|
}),
|
|
55
|
-
|
|
53
|
+
getContractDetailsService({
|
|
56
54
|
contractId,
|
|
57
|
-
cookie,
|
|
58
55
|
unitId: orgUnitId,
|
|
56
|
+
cookie,
|
|
59
57
|
}),
|
|
58
|
+
getUserByIdService({ orgUnitId, userId, cookie }),
|
|
60
59
|
]);
|
|
61
60
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
61
|
+
return {
|
|
62
|
+
data: {
|
|
63
|
+
orgUnitId,
|
|
64
|
+
contractId,
|
|
65
|
+
userId,
|
|
66
|
+
search,
|
|
67
|
+
page: getValidPage(pageString),
|
|
68
|
+
},
|
|
69
|
+
context: {
|
|
70
|
+
clientContext: { customerId, cookie, userId, ...clientContext },
|
|
71
|
+
currentOrgUnit: orgUnit,
|
|
72
|
+
currentContract: contract,
|
|
73
|
+
currentUser: user,
|
|
74
|
+
},
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
);
|
|
78
78
|
};
|
|
79
79
|
|
|
80
80
|
export const loader = withLoaderErrorBoundary(loaderFunction, {
|
|
@@ -83,24 +83,20 @@ export const loader = withLoaderErrorBoundary(loaderFunction, {
|
|
|
83
83
|
});
|
|
84
84
|
|
|
85
85
|
const ProductAssortmentPage = ({
|
|
86
|
-
|
|
87
|
-
drawerProductAssortment,
|
|
88
|
-
isContractEmpty,
|
|
89
|
-
page,
|
|
90
|
-
search,
|
|
86
|
+
data,
|
|
91
87
|
hasError,
|
|
92
88
|
error,
|
|
93
|
-
}:
|
|
89
|
+
}: ProductAssortmentPageData) => (
|
|
94
90
|
<>
|
|
95
91
|
{hasError ? (
|
|
96
92
|
<ErrorTabsLayout error={error} />
|
|
97
93
|
) : (
|
|
98
94
|
<ProductAssortmentLayout
|
|
99
|
-
page={page}
|
|
100
|
-
search={search}
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
95
|
+
page={data.page}
|
|
96
|
+
search={data.search}
|
|
97
|
+
userId={data.userId}
|
|
98
|
+
orgUnitId={data.orgUnitId}
|
|
99
|
+
contractId={data.contractId}
|
|
104
100
|
/>
|
|
105
101
|
)}
|
|
106
102
|
</>
|