@vtex/faststore-plugin-buyer-portal 1.3.86 → 1.3.88

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 (130) hide show
  1. package/CHANGELOG.md +25 -1
  2. package/cypress/constants.ts +0 -8
  3. package/cypress/integration/product-assortment.test.ts +2 -2
  4. package/package.json +1 -1
  5. package/plugin.config.js +0 -12
  6. package/specs/assortments-page-fixes.md +222 -0
  7. package/specs/deprecate-address-locations.md +497 -0
  8. package/specs/deprecate-custom-fields.md +473 -0
  9. package/src/features/addresses/components/AddressDropdownMenu/AddressDropdownMenu.tsx +1 -15
  10. package/src/features/addresses/components/CreateAddressDrawer/CreateAddressDrawer.tsx +4 -13
  11. package/src/features/addresses/components/CreateAddressDrawer/create-address-drawer.scss +0 -1
  12. package/src/features/addresses/components/CreateAddressSettingsDrawer/create-address-settings-drawer.scss +0 -1
  13. package/src/features/addresses/components/RemoveAddressDrawer/remove-address-drawer.scss +0 -1
  14. package/src/features/addresses/components/index.ts +0 -3
  15. package/src/features/addresses/hooks/index.ts +0 -4
  16. package/src/features/addresses/layouts/AddressDetailsLayout/AddressDetailsLayout.tsx +14 -55
  17. package/src/features/addresses/layouts/AddressDetailsLayout/address-details-layout.scss +0 -49
  18. package/src/features/addresses/layouts/AddressesLayout/addresses-layout.scss +0 -2
  19. package/src/features/addresses/services/default-values/get-default-address.service.ts +0 -1
  20. package/src/features/addresses/types/AddressData.ts +0 -50
  21. package/src/features/budgets/hooks/useGetAllocations.ts +1 -34
  22. package/src/features/buying-policies/components/BasicBuyingPolicyDrawer/BasicBuyingPolicyDrawer.tsx +0 -43
  23. package/src/features/buying-policies/components/BasicBuyingPolicyDrawer/basic-buying-policy-drawer.scss +0 -1
  24. package/src/features/buying-policies/utils/__tests__/orderFieldsCriteriaOptions.test.ts +41 -0
  25. package/src/features/buying-policies/utils/index.ts +0 -2
  26. package/src/features/buying-policies/utils/orderFieldsCriteriaOptions.ts +0 -21
  27. package/src/features/org-units/components/AddAllToOrgUnitDropdown/AddAllToOrgUnitDropdown.tsx +3 -49
  28. package/src/features/org-units/layouts/OrgUnitDetailsLayout/OrgUnitDetailsLayout.tsx +14 -2
  29. package/src/features/org-units/layouts/OrgUnitDetailsLayout/org-units-details.scss +1 -0
  30. package/src/features/product-assortment/layouts/ProductAssortmentLayout/ProductAssortmentLayout.tsx +10 -10
  31. package/src/features/product-assortment/layouts/ProductAssortmentLayout/product-assortment-layout.scss +0 -46
  32. package/src/features/shared/components/BasicDropdownMenu/basic-dropdown-menu.scss +5 -0
  33. package/src/features/shared/components/CustomFieldsDeprecationAlert/CustomFieldsDeprecationAlert.tsx +27 -0
  34. package/src/features/shared/components/CustomFieldsDeprecationAlert/custom-fields-deprecation-alert.scss +33 -0
  35. package/src/features/shared/components/Tab/TabOption.tsx +8 -1
  36. package/src/features/shared/components/Tab/tab.scss +5 -0
  37. package/src/features/shared/components/VerticalNav/VerticalNavLink.tsx +30 -4
  38. package/src/features/shared/components/VerticalNav/vertical-nav.scss +9 -0
  39. package/src/features/shared/components/index.ts +4 -0
  40. package/src/features/shared/hooks/usePageItems.ts +0 -2
  41. package/src/features/shared/layouts/BaseTabsLayout/SidebarMenu.tsx +9 -2
  42. package/src/features/shared/layouts/index.ts +0 -4
  43. package/src/features/shared/services/logger/analytics/constants.ts +0 -16
  44. package/src/features/shared/utils/__tests__/getContractSettingsLinks.test.ts +47 -0
  45. package/src/features/shared/utils/__tests__/getKeyByValue.test.ts +28 -0
  46. package/src/features/shared/utils/buyerPortalRoutes.ts +0 -9
  47. package/src/features/shared/utils/constants.ts +4 -3
  48. package/src/features/shared/utils/getContractSettingsLinks.ts +15 -4
  49. package/src/features/shared/utils/getKeyByValue.ts +0 -3
  50. package/src/features/shared/utils/routeLayoutMapping.ts +1 -17
  51. package/src/pages/address-details.tsx +1 -29
  52. package/src/themes/layouts.scss +0 -3
  53. package/cypress/integration/locations.test.ts +0 -196
  54. package/src/features/addresses/clients/LocationsClient.ts +0 -98
  55. package/src/features/addresses/components/AddressLocationsList/AddressLocationsList.tsx +0 -219
  56. package/src/features/addresses/components/DeleteAddressLocationDrawer/DeleteAddressLocationDrawer.tsx +0 -126
  57. package/src/features/addresses/components/DeleteAddressLocationDrawer/delete-address-location-drawer.scss +0 -20
  58. package/src/features/addresses/components/EditAddressLocationDrawer/EditAddressLocationDrawer.tsx +0 -151
  59. package/src/features/addresses/components/EditAddressLocationDrawer/edit-address-location-drawer.scss +0 -14
  60. package/src/features/addresses/components/LocationForm/LocationField/LocationField.tsx +0 -43
  61. package/src/features/addresses/components/LocationForm/LocationField/location-field.scss +0 -34
  62. package/src/features/addresses/components/LocationForm/LocationForm.tsx +0 -54
  63. package/src/features/addresses/components/LocationForm/location-form.scss +0 -41
  64. package/src/features/addresses/components/LocationsDrawer/LocationsDrawer.tsx +0 -146
  65. package/src/features/addresses/hooks/useAddLocationsToAddress.ts +0 -25
  66. package/src/features/addresses/hooks/useAddressLocationDelete.ts +0 -26
  67. package/src/features/addresses/hooks/useAddressLocationEdit.ts +0 -27
  68. package/src/features/addresses/hooks/useAddressLocationList.ts +0 -35
  69. package/src/features/addresses/services/locations/add-address-locations.service.ts +0 -6
  70. package/src/features/addresses/services/locations/delete-address-location.service.ts +0 -7
  71. package/src/features/addresses/services/locations/edit-address-location.service.ts +0 -6
  72. package/src/features/addresses/services/locations/get-address-locations.service.ts +0 -28
  73. package/src/features/buying-policies/components/CustomFieldCriteriaSelector/CustomFieldCriteriaSelector.tsx +0 -154
  74. package/src/features/buying-policies/components/CustomFieldCriteriaSelector/custom-field-criteria-selector.scss +0 -139
  75. package/src/features/custom-fields/components/CreateCustomFieldValueDrawer/CreateCustomFieldValueDrawer.tsx +0 -208
  76. package/src/features/custom-fields/components/CustomFieldSettingsDrawer/CustomFieldSettingsDrawer.tsx +0 -379
  77. package/src/features/custom-fields/components/DeleteCustomFieldValueDrawer/DeleteCustomFieldValueDrawer.tsx +0 -183
  78. package/src/features/custom-fields/components/UpdateCustomFieldValueDrawer/UpdateCustomFieldValueDrawer.tsx +0 -108
  79. package/src/features/custom-fields/components/index.ts +0 -4
  80. package/src/features/custom-fields/layouts/CustomFieldsLayout/CustomFieldsLayout.tsx +0 -325
  81. package/src/features/custom-fields/layouts/CustomFieldsLayout/custom-fields-layout.scss +0 -37
  82. package/src/features/custom-fields/layouts/index.ts +0 -1
  83. package/src/features/custom-fields/types/index.ts +0 -5
  84. package/src/features/shared/clients/CustomField.ts +0 -204
  85. package/src/features/shared/clients/DefaultValueClient.ts +0 -62
  86. package/src/features/shared/components/CustomField/create-custom-field/CreateCustomFieldDrawer.tsx +0 -155
  87. package/src/features/shared/components/CustomField/create-custom-field/create-custom-field-drawer.scss +0 -30
  88. package/src/features/shared/components/CustomField/delete-custom-field/DeleteCustomFieldDrawer.tsx +0 -127
  89. package/src/features/shared/components/CustomField/delete-custom-field/delete-custom-field-drawer.scss +0 -34
  90. package/src/features/shared/components/CustomField/index.ts +0 -6
  91. package/src/features/shared/components/CustomField/selected-card-item/CustomFieldSelectedCardItem.tsx +0 -31
  92. package/src/features/shared/components/CustomField/selected-card-item/custom-field-selected-card-item.scss +0 -53
  93. package/src/features/shared/components/CustomField/settings-drawer/CustomFieldSettingsDrawer.tsx +0 -159
  94. package/src/features/shared/components/CustomField/settings-drawer/custom-field-settings-drawer.scss +0 -98
  95. package/src/features/shared/components/CustomField/table/CustomFieldTable.tsx +0 -151
  96. package/src/features/shared/components/CustomField/table/custom-field-table.scss +0 -41
  97. package/src/features/shared/components/CustomField/update-custom-field/UpdateCustomFieldDrawer.tsx +0 -184
  98. package/src/features/shared/components/CustomField/update-custom-field/update-custom-field-drawer.scss +0 -46
  99. package/src/features/shared/hooks/custom-field/index.ts +0 -11
  100. package/src/features/shared/hooks/custom-field/useAddCustomFieldValueToScope.ts +0 -25
  101. package/src/features/shared/hooks/custom-field/useAddDefaultValue.ts +0 -25
  102. package/src/features/shared/hooks/custom-field/useCreateCustomFieldValue.ts +0 -25
  103. package/src/features/shared/hooks/custom-field/useCustomFieldSettings.ts +0 -18
  104. package/src/features/shared/hooks/custom-field/useDeleteCustomFieldValue.ts +0 -25
  105. package/src/features/shared/hooks/custom-field/useDeleteCustomFieldValueAndUpdateUnitScope.ts +0 -27
  106. package/src/features/shared/hooks/custom-field/useDeleteCustomFieldValueToScope.ts +0 -27
  107. package/src/features/shared/hooks/custom-field/useGetCustomFieldValues.ts +0 -20
  108. package/src/features/shared/hooks/custom-field/useGetDefaultValue.ts +0 -18
  109. package/src/features/shared/hooks/custom-field/useRemoveDefaultValue.ts +0 -25
  110. package/src/features/shared/hooks/custom-field/useUpdateCustomFieldSettings.ts +0 -27
  111. package/src/features/shared/hooks/custom-field/useUpdateCustomFieldValue.ts +0 -25
  112. package/src/features/shared/layouts/CustomFieldLayout/CustomFieldLayout.tsx +0 -108
  113. package/src/features/shared/layouts/CustomFieldLayout/custom-field-layout.scss +0 -67
  114. package/src/features/shared/services/custom-field/add-custom-field-value-to-scope.service.ts +0 -26
  115. package/src/features/shared/services/custom-field/add-default-value.service.ts +0 -27
  116. package/src/features/shared/services/custom-field/create-custom-field-value.service.ts +0 -26
  117. package/src/features/shared/services/custom-field/delete-custom-field-value-and-update-unit-scope.service.ts +0 -26
  118. package/src/features/shared/services/custom-field/delete-custom-field-value-to-unit-scope.service.ts +0 -26
  119. package/src/features/shared/services/custom-field/delete-custom-field-value.service.ts +0 -26
  120. package/src/features/shared/services/custom-field/get-custom-field-settings.service.ts +0 -22
  121. package/src/features/shared/services/custom-field/get-custom-fields-values.service.ts +0 -30
  122. package/src/features/shared/services/custom-field/get-default-value.service.ts +0 -20
  123. package/src/features/shared/services/custom-field/index.ts +0 -59
  124. package/src/features/shared/services/custom-field/remove-default-value.service.ts +0 -25
  125. package/src/features/shared/services/custom-field/update-custom-field-settings.service.ts +0 -28
  126. package/src/features/shared/services/custom-field/update-custom-field-value.service.ts +0 -34
  127. package/src/features/shared/types/CustomFields.ts +0 -6
  128. package/src/pages/cost-centers.tsx +0 -104
  129. package/src/pages/po-numbers.tsx +0 -98
  130. package/src/pages/releases.tsx +0 -98
package/CHANGELOG.md CHANGED
@@ -7,6 +7,28 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [1.3.88] - 2026-07-23
11
+
12
+ ### Removed
13
+
14
+ - PO Numbers, Cost Centers, and Releases pages, routes, and every dedicated client/hook/service/component (the `custom-fields` feature and the shared `custom-field` building blocks) — the underlying BFF service is discontinued on 2026-07-30
15
+ - Address Locations client, hooks, services, and dedicated components/drawers, and the "Locations" tab from the "Add address" drawer (same discontinued BFF service)
16
+ - PO Number/Cost Center/Release as selectable allocation types in "Add budget allocations" and as criteria presets in "Set order field criteria" (existing budget allocations and buying policy criteria created before this change keep displaying normally)
17
+ - "Add cost center"/"Add PO number"/"Add release" shortcuts in the Org Unit "Add" dropdown (now disabled, no action)
18
+
19
+ ### Added
20
+
21
+ - A persistent deprecation alert on the Org Unit Details page, explaining that PO Numbers, Cost Centers, and Releases were replaced by Accounting Fields and pointing to the upgrade path (buyer-portal plugin `v2.0.1+`, FastStore `v4`)
22
+ - A persistent deprecation alert on the Address Details page, explaining that Address Locations was replaced by Accounting Fields and pointing to the same upgrade path
23
+ - Support for a disabled, tooltip-carrying state on `VerticalNav.Link`, used to keep the "PO numbers"/"Cost centers"/"Releases" navigation entries visible (but disabled) in the Org Unit Details cards and the Contract-settings sidebar
24
+ - Support for a disabled state on `TabOption`, used to keep the "Locations" tab visible (but disabled) on the Address Details page; the "Add Locations" shortcut in the address actions dropdown is disabled the same way
25
+
26
+ ## [1.3.87] - 2026-05-21
27
+
28
+ ### Fixed
29
+
30
+ - Assortments page: use consistent "Assortments" title during loading and after page load by updating `routeLayoutMapping` and replacing the custom header with `HeaderInside`, aligning title position with other Contract settings pages
31
+
10
32
  ## [1.3.86] - 2026-05-19
11
33
 
12
34
  ### Added
@@ -640,7 +662,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
640
662
  - Add CHANGELOG file
641
663
  - Add README file
642
664
 
643
- [unreleased]: https://github.com/vtex/faststore-plugin-buyer-portal/compare/1.3.86...HEAD
665
+ [unreleased]: https://github.com/vtex/faststore-plugin-buyer-portal/compare/v1.3.87...HEAD
644
666
  [1.3.55]: https://github.com/vtex/faststore-plugin-buyer-portal/compare/v1.3.54...v1.3.55
645
667
  [1.3.54]: https://github.com/vtex/faststore-plugin-buyer-portal/compare/v1.3.53...v1.3.54
646
668
  [1.3.53]: https://github.com/vtex/faststore-plugin-buyer-portal/compare/v1.3.52...v1.3.53
@@ -725,4 +747,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
725
747
  [1.3.70]: https://github.com/vtex/faststore-plugin-buyer-portal/compare/v1.3.69...v1.3.70
726
748
  [1.3.85]: https://github.com/vtex/faststore-plugin-buyer-portal/releases/tag/1.3.85
727
749
 
750
+ [1.3.88]: https://github.com/vtex/faststore-plugin-buyer-portal/compare/v1.3.87...v1.3.88
751
+ [1.3.87]: https://github.com/vtex/faststore-plugin-buyer-portal/compare/v1.3.86...v1.3.87
728
752
  [1.3.86]: https://github.com/vtex/faststore-plugin-buyer-portal/releases/tag/1.3.86
@@ -89,14 +89,6 @@ export const TEST_DATA = {
89
89
  NEW_ADDRESS_STATE: "RJ",
90
90
  NEW_ADDRESS_POSTAL_CODE: "22250-905",
91
91
  },
92
- LOCATIONS: {
93
- LOCATION_ADDRESS_NAME: "LOCATIONS",
94
- FIRST_LOCATION: "Location 1",
95
- SECOND_LOCATION: "Location 2",
96
- THIRD_LOCATION: "Location 3",
97
- FOURTH_LOCATION: "Location 4",
98
- EDITED_LOCATION: "Edited Location",
99
- },
100
92
  RECIPIENTS: {
101
93
  RECIPIENTS_ADDRESS_NAME: "RECIPIENTS",
102
94
  FIRST_RECIPIENT_NAME: "First Recipient",
@@ -6,7 +6,7 @@ function visitProductAssortmentPage() {
6
6
 
7
7
  // Navigate to product assortment page
8
8
  cy.get("[data-fs-vertical-nav-menu-item]")
9
- .contains("Product assortment")
9
+ .contains("Assortments")
10
10
  .click({ timeout: TEST_CONFIG.TIMEOUTS.PAGE_LOAD });
11
11
 
12
12
  cy.wait(1000);
@@ -58,7 +58,7 @@ describe(
58
58
 
59
59
  // Wait for product assortment section to be visible
60
60
  cy.get("[data-fs-bp-header-inside-title]")
61
- .contains("Product assortment")
61
+ .contains("Assortments")
62
62
  .should("be.visible");
63
63
 
64
64
  // Wait for product assortment table to be loaded
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vtex/faststore-plugin-buyer-portal",
3
- "version": "1.3.86",
3
+ "version": "1.3.88",
4
4
  "description": "A plugin for faststore with buyer portal",
5
5
  "main": "index.js",
6
6
  "scripts": {
package/plugin.config.js CHANGED
@@ -40,18 +40,6 @@ module.exports = {
40
40
  path: "/pvt/organization-account/product-assortment/[orgUnitId]/[contractId]",
41
41
  appLayout: false,
42
42
  },
43
- "po-numbers": {
44
- path: "/pvt/organization-account/po-numbers/[orgUnitId]/[contractId]",
45
- appLayout: false,
46
- },
47
- "cost-centers": {
48
- path: "/pvt/organization-account/cost-centers/[orgUnitId]/[contractId]",
49
- appLayout: false,
50
- },
51
- releases: {
52
- path: "/pvt/organization-account/releases/[orgUnitId]/[contractId]",
53
- appLayout: false,
54
- },
55
43
  roles: {
56
44
  path: "/pvt/organization-account/roles/[orgUnitId]",
57
45
  appLayout: false,
@@ -0,0 +1,222 @@
1
+ # Assortments Page Fixes
2
+
3
+ > **Status**: Approved
4
+ > **Created**: 2026-05-19
5
+
6
+ ## 1. Business Context
7
+
8
+ ### Problem Statement
9
+
10
+ The **Assortments** page was recently released in the Buyer Portal (Contract settings), but manual testing revealed two UX inconsistencies compared to sibling pages in the same group (Profile, Addresses, Payment methods, Credit cards):
11
+
12
+ 1. **Unstable title during loading** — when navigating via the sidebar, users temporarily see "Product Assortment", and after the page finishes loading the title changes to "Assortments".
13
+ 2. **Incorrect title vertical position** — the "Assortments" title appears higher on the page than on sibling pages.
14
+
15
+ These issues affect B2B administrators who manage organizational units and need to select the correct product assortment for each unit.
16
+
17
+ ### Goals
18
+
19
+ - Ensure the title "Assortments" is displayed consistently during and after page load.
20
+ - Align the page header positioning and typography with other Contract settings pages.
21
+
22
+ ### User Stories
23
+
24
+ #### US-1: Consistent title during navigation
25
+
26
+ - **Story**: As a contract administrator, I want the page title to remain "Assortments" while the page loads, so that I am not confused by a temporary, incorrect label.
27
+ - **Acceptance Criteria**:
28
+ - **Given** I am on any Contract settings page, **when** I click "Assortments" in the sidebar, **then** the loading state shows the title "Assortments" (not "Product Assortment" or "Product assortment").
29
+ - **Given** the Assortments page finishes loading, **when** the content is rendered, **then** the title remains "Assortments" with no visible title swap.
30
+
31
+ #### US-2: Header aligned with sibling pages
32
+
33
+ - **Story**: As a contract administrator, I want the Assortments page header to match Profile, Addresses, and Payment methods, so that the portal feels cohesive.
34
+ - **Acceptance Criteria**:
35
+ - **Given** I open Assortments and any other Contract settings page (e.g. Addresses), **when** I compare the page title position, **then** both titles share the same vertical offset from the top of the content area.
36
+ - **Given** the Assortments page is loaded, **when** I view the header, **then** it uses the shared `HeaderInside` component and styling used by sibling pages.
37
+ - **Given** the Assortments page is loaded, **when** I read the descriptive text below the title, **then** the subtitle "Select the product assortment that users in this organizational unit should access" is still visible.
38
+
39
+ ### Key Scenarios
40
+
41
+ | Scenario | Pre-conditions | Steps | Expected Result |
42
+ |---|---|---|---|
43
+ | Happy path — list and selection | Contract with assortments; one assortment enabled | Open Assortments → select a different assortment | Title stays "Assortments"; all assortments listed; selection saves with success toast |
44
+ | Error case — failed selection save | User on Assortments page; API PUT fails | Select a different assortment | Radio reverts to previous selection; error toast "Failed to save assortment" |
45
+ | Edge case — loading transition | User on Profile page | Click "Assortments" in sidebar | Loading skeleton shows `HeaderInside` with title "Assortments"; no flicker to a different label after load |
46
+
47
+ ### Functional Requirements
48
+
49
+ - FR-1: The assortments route `pageTitle` in `routeLayoutMapping.ts` must be `"Assortments"`.
50
+ - FR-2: `ProductAssortmentLayout` must use `HeaderInside title="Assortments"` instead of the custom header (`h1` + custom styles).
51
+ - FR-3: The descriptive subtitle must remain below `HeaderInside`, with scoped styles in the page section.
52
+ - FR-4: Radio selection behavior and persistence via `useSetAssortment` remain unchanged.
53
+ - FR-5: The listing must display all assortments returned by the API in a single request (maximum of 20 per contract — pagination is not required).
54
+
55
+ ### Non-Functional Requirements
56
+
57
+ - Maintain plugin boundary compatibility — changes limited to `src/features/product-assortment/` and existing shared utilities.
58
+ - Pass `make lint`, `make typecheck`, and `make test`.
59
+ - Update Cypress tests only where the title or navigation label changed to "Assortments".
60
+
61
+ ### Out of Scope
62
+
63
+ - **Pagination** — a contract supports at most 20 assortments; the API returns all of them in a single response, making pagination controls unnecessary.
64
+ - Add/remove assortment drawer functionality (present in legacy Cypress tests, absent from the current implementation).
65
+ - Name search/filter (the hook already accepts `search`, but it is not part of this fix).
66
+ - Changes to the API logic that places the enabled assortment at the top.
67
+ - Renaming routes or paths (`/product-assortment/...` remains unchanged).
68
+
69
+ ---
70
+
71
+ ## 2. Arch Decisions
72
+
73
+ ### Proposed Solution
74
+
75
+ Fix the two UX bugs by aligning the Assortments page header with other Contract settings pages, reusing `HeaderInside` and unifying the title via `routeLayoutMapping`.
76
+
77
+ ### Architecture Overview
78
+
79
+ ```mermaid
80
+ sequenceDiagram
81
+ participant User
82
+ participant LoadingTabsLayout
83
+ participant ProductAssortmentLayout
84
+ participant useGetProductAssortment
85
+ participant API
86
+
87
+ User->>LoadingTabsLayout: Click "Assortments"
88
+ LoadingTabsLayout->>User: HeaderInside title="Assortments" (from routeLayoutMapping)
89
+ LoadingTabsLayout->>ProductAssortmentLayout: Route loaded
90
+ ProductAssortmentLayout->>useGetProductAssortment: orgUnitId, contractId
91
+ useGetProductAssortment->>API: GET .../assortments/available
92
+ API-->>ProductAssortmentLayout: items (up to 20)
93
+ ProductAssortmentLayout->>User: HeaderInside + table
94
+ ```
95
+
96
+ ### Root Cause Analysis
97
+
98
+ | Bug | Root cause (original code) |
99
+ |---|---|
100
+ | Title flicker | `LoadingTabsLayout` reads `pageTitle: "Product assortment"` from `routeLayoutMapping.ts` and renders it via `HeaderInside` (with `text-transform: capitalize` → "Product Assortment"). After load, `ProductAssortmentLayout` renders a custom `h1` with text "Assortments". |
101
+ | Title position | `ProductAssortmentLayout` does not use `HeaderInside`; it uses `[data-fs-bp-assortments-header]` with `padding: var(--fs-spacing-3) 0` and custom typography, unlike `[data-fs-bp-header-inside]` (`padding: var(--fs-bp-padding-4/8/9) 0`). |
102
+
103
+ ### Alternatives Considered
104
+
105
+ | Alternative | Pros | Cons | Verdict |
106
+ |---|---|---|---|
107
+ | A. Unified `HeaderInside` + `routeLayoutMapping` | Consistent with sibling pages; fixes flicker and position | Requires subtitle adjustment below the header | **Accepted** |
108
+ | B. Keep custom header and only adjust padding | Minimal diff | Still diverges from `HeaderInside` typography/responsiveness; does not fix flicker alone | Rejected |
109
+ | C. Server-side pagination | Supports large lists | Unnecessary — maximum of 20 assortments per contract | Rejected |
110
+ | D. Remove subtitle to match Profile | Less markup | Loses useful context for the user; subtitle is a product requirement | Rejected |
111
+
112
+ ### Risks & Mitigations
113
+
114
+ | Risk | Impact | Likelihood | Mitigation |
115
+ |---|---|---|---|
116
+ | Legacy Cypress tests fail | Med | High | Update only title and navigation label expectations; leave legacy drawer/filter scenarios unchanged |
117
+ | Contract assortment limit increases in the future | Low | Low | Re-evaluate pagination if the business limit changes |
118
+
119
+ ### Key Decisions
120
+
121
+ #### Decision 1: Unify title via `routeLayoutMapping` + `HeaderInside`
122
+
123
+ - **Status**: Accepted
124
+ - **Context**: Loading and loaded states use different title mechanisms.
125
+ - **Decision**: Change `pageTitle` to `"Assortments"` and migrate the loaded layout to `HeaderInside title="Assortments"`.
126
+ - **Consequences**: `LoadingTabsLayout`, `ErrorTabsLayout`, and the loaded page all display the same title. Removes the custom `[data-fs-bp-assortments-title]`.
127
+
128
+ #### Decision 2: No pagination
129
+
130
+ - **Status**: Accepted
131
+ - **Context**: Contracts support at most 20 assortments; the API returns all of them in a single response.
132
+ - **Decision**: Do not implement pagination, `useUrlPaginatedSearch`, or a `page` query param.
133
+ - **Consequences**: Simpler layout; single fetch via `useGetProductAssortment` without lazy/router sync.
134
+
135
+ #### Decision 3: Keep subtitle as a separate element
136
+
137
+ - **Status**: Accepted
138
+ - **Context**: No sibling page has a subtitle, but Assortments needs explanatory text.
139
+ - **Decision**: Render `<p data-fs-bp-assortments-subtitle>` immediately below `HeaderInside`, keeping existing scoped styles.
140
+ - **Consequences**: Title position aligns with sibling pages; subtitle sits below without affecting the `h1` offset.
141
+
142
+ ### Implementation Plan
143
+
144
+ 1. **Consistent title**
145
+ - Edit `src/features/shared/utils/routeLayoutMapping.ts`: `pageTitle: "Assortments"`.
146
+
147
+ 2. **Aligned header**
148
+ - Refactor `ProductAssortmentLayout.tsx`:
149
+ - Import and use `HeaderInside title="Assortments"`.
150
+ - Remove `div[data-fs-bp-assortments-header]` and the custom `h1`.
151
+ - Keep the subtitle below the header.
152
+ - Remove obsolete `[data-fs-bp-assortments-title]` and header styles from `product-assortment-layout.scss`; preserve subtitle styles.
153
+
154
+ 3. **Tests**
155
+ - Update `cypress/integration/product-assortment.test.ts` only where the title or navigation label changed to "Assortments".
156
+
157
+ 4. **Verification**
158
+ - Run `make check`.
159
+ - Manual test against a linked host store: navigate to Assortments, confirm stable title, visual alignment vs Addresses, and full assortment listing.
160
+
161
+ ---
162
+
163
+ ## 3. Technical Contract
164
+
165
+ ### Data Models
166
+
167
+ No changes to existing types in `src/features/product-assortment/types/index.ts`. The API response may include `paging`, but the frontend does not consume pagination in this scope.
168
+
169
+ ### Interfaces
170
+
171
+ #### API — GET assortments (consumed by the plugin)
172
+
173
+ | Field | Type | Notes |
174
+ |---|---|---|
175
+ | Path | `GET /customers/{contractId}/units/{unitId}/assortments/available` | Existing |
176
+ | Query `name` | `string` (optional) | Existing, out of scope |
177
+ | Response `items[]` | `{ id, name, enabled }` | Rendered in full (up to 20) |
178
+
179
+ #### `useGetProductAssortment`
180
+
181
+ ```typescript
182
+ useGetProductAssortment({
183
+ contractId: string;
184
+ orgUnitId: string;
185
+ search?: string;
186
+ options?: QueryOptions<...>;
187
+ })
188
+ // Returns: { data: { items, paging }, isProductAssortmentLoading, ... }
189
+ ```
190
+
191
+ Single fetch on mount — no `page`, no `lazy`, no URL sync.
192
+
193
+ #### Component boundaries
194
+
195
+ | Module | Responsibility |
196
+ |---|---|
197
+ | `routeLayoutMapping.ts` | Loading/error title: `"Assortments"` |
198
+ | `ProductAssortmentLayout` | Header, data fetch, empty state |
199
+ | `ProductAssortmentTable` | Table rendering + radio selection |
200
+ | `useSetAssortment` | PUT selection (unchanged) |
201
+
202
+ ### Integration Points
203
+
204
+ - **LoadingTabsLayout / ErrorTabsLayout** — read `pageTitle` via `getTabsLayoutConfigFromRoute`; fixed indirectly by the `routeLayoutMapping` change.
205
+ - **BFF/API** — `assortments/available` endpoint returns all contract assortments (max. 20).
206
+ - **Host store** — no changes to `plugin.config.js` (route already registered).
207
+
208
+ ### Invariants & Constraints
209
+
210
+ - The sidebar menu continues to display **"Assortments"** (`getContractSettingsLinks.ts` — already correct).
211
+ - The loaded, loading, and error page title must always be **"Assortments"**.
212
+ - The assortment with `enabled: true` remains selected via radio; the API is responsible for placing it at the top when applicable.
213
+ - No new cross-domain dependencies — only `shared` (`HeaderInside`).
214
+
215
+ ### Files to Change
216
+
217
+ | File | Change |
218
+ |---|---|
219
+ | `src/features/shared/utils/routeLayoutMapping.ts` | `pageTitle` → `"Assortments"` |
220
+ | `src/features/product-assortment/layouts/ProductAssortmentLayout/ProductAssortmentLayout.tsx` | `HeaderInside`, simple fetch |
221
+ | `src/features/product-assortment/layouts/ProductAssortmentLayout/product-assortment-layout.scss` | Remove custom title styles; keep subtitle |
222
+ | `cypress/integration/product-assortment.test.ts` | Update title and navigation label to "Assortments" |