@faststore/core 4.4.0 → 4.5.0-dev.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. package/.turbo/turbo-generate.log +4 -4
  2. package/.turbo/turbo-test.log +63 -50
  3. package/@generated/cached-operations.json +2 -0
  4. package/@generated/gql.ts +12 -0
  5. package/@generated/graphql.ts +73 -0
  6. package/@generated/persisted-documents.json +2 -0
  7. package/@generated/schema.graphql +59 -0
  8. package/CHANGELOG.md +10 -0
  9. package/package.json +6 -6
  10. package/src/components/account/Drawer/OrganizationDrawer/OrganizationDrawer.tsx +5 -1
  11. package/src/components/account/Drawer/OrganizationDrawer/OrganizationDrawerBody.tsx +16 -6
  12. package/src/components/account/Layout/Layout.tsx +14 -5
  13. package/src/components/account/quotes/MyAccountListQuotes/MyAccountListQuotes.tsx +170 -0
  14. package/src/components/account/quotes/MyAccountListQuotes/MyAccountListQuotesTable/MyAccountListQuotesTable.tsx +194 -0
  15. package/src/components/account/quotes/MyAccountListQuotes/MyAccountListQuotesTable/MyAccountQuoteStatusBadge.tsx +15 -0
  16. package/src/components/account/quotes/MyAccountListQuotes/MyAccountQuotesFilterSlider/MyAccountQuotesFilterSlider.tsx +162 -0
  17. package/src/components/account/quotes/MyAccountListQuotes/MyAccountQuotesFilterSlider/section.module.scss +49 -0
  18. package/src/components/account/quotes/MyAccountListQuotes/MyAccountQuotesSelectedTags/MyAccountQuotesSelectedTags.tsx +125 -0
  19. package/src/components/account/quotes/MyAccountListQuotes/MyAccountQuotesStatusSelector/MyAccountQuotesStatusSelector.tsx +107 -0
  20. package/src/components/account/quotes/MyAccountListQuotes/MyAccountQuotesStatusSelector/styles.module.scss +144 -0
  21. package/src/components/account/quotes/MyAccountListQuotes/index.ts +2 -0
  22. package/src/components/account/quotes/MyAccountListQuotes/quoteFilters.ts +89 -0
  23. package/src/components/account/quotes/MyAccountListQuotes/styles.module.scss +174 -0
  24. package/src/customizations/src/myAccount/extensions/quotes/after.tsx +5 -0
  25. package/src/customizations/src/myAccount/extensions/quotes/before.tsx +5 -0
  26. package/src/pages/pvt/account/quotes/index.tsx +264 -0
  27. package/src/sdk/account/getMyAccountRoutes.ts +7 -0
  28. package/src/utils/quoteStatus.ts +27 -0
  29. package/test/components/account/Layout.test.tsx +62 -0
  30. package/test/components/account/OrganizationDrawerBody.test.tsx +52 -0
  31. package/test/components/quotes/MyAccountListQuotes.test.tsx +232 -0
  32. package/test/components/quotes/MyAccountListQuotesTable.test.tsx +240 -0
  33. package/test/components/quotes/MyAccountQuoteStatusBadge.test.tsx +28 -0
  34. package/test/components/quotes/MyAccountQuotesFilterSlider.test.tsx +152 -0
  35. package/test/components/quotes/MyAccountQuotesSelectedTags.test.tsx +122 -0
  36. package/test/components/quotes/MyAccountQuotesStatusSelector.test.tsx +85 -0
  37. package/test/components/quotes/quoteFilters.test.ts +185 -0
  38. package/test/sdk/account/getMyAccountRoutes.test.ts +90 -0
  39. package/test/server/index.test.ts +2 -0
@@ -0,0 +1,185 @@
1
+ import { describe, expect, it } from 'vitest'
2
+
3
+ import {
4
+ getSelectedFacets,
5
+ getAllFacets,
6
+ hasActiveFilters,
7
+ countActiveFilters,
8
+ formatFilterDate,
9
+ } from '../../../src/components/account/quotes/MyAccountListQuotes/quoteFilters'
10
+ import {
11
+ quoteStatusMap,
12
+ type QuoteStatusKey,
13
+ } from '../../../src/utils/quoteStatus'
14
+
15
+ const emptyFilters = {
16
+ page: 1,
17
+ status: [],
18
+ createdAtFrom: '',
19
+ createdAtTo: '',
20
+ expiresAtFrom: '',
21
+ expiresAtTo: '',
22
+ label: '',
23
+ }
24
+
25
+ describe('quoteStatusMap', () => {
26
+ it('has entries for all 9 statuses', () => {
27
+ expect(Object.keys(quoteStatusMap)).toHaveLength(9)
28
+ })
29
+
30
+ it('all keys are PascalCase', () => {
31
+ Object.keys(quoteStatusMap).forEach((key) => {
32
+ expect(key[0]).toBe(key[0].toUpperCase())
33
+ expect(key).toBe(key[0].toUpperCase() + key.slice(1))
34
+ })
35
+ })
36
+
37
+ it('all entries have a non-empty label', () => {
38
+ Object.values(quoteStatusMap).forEach((entry) => {
39
+ expect(entry.label).toBeTruthy()
40
+ })
41
+ })
42
+
43
+ it('all entries have a valid variant', () => {
44
+ const validVariants = ['neutral', 'warning', 'info', 'success', 'danger']
45
+ Object.values(quoteStatusMap).forEach((entry) => {
46
+ expect(validVariants).toContain(entry.variant)
47
+ })
48
+ })
49
+ })
50
+
51
+ describe('getSelectedFacets', () => {
52
+ it('returns empty array when no status selected', () => {
53
+ expect(getSelectedFacets(emptyFilters)).toEqual([])
54
+ })
55
+
56
+ it('returns one facet per status, preserving PascalCase', () => {
57
+ const filters = { ...emptyFilters, status: ['InReview', 'Approved'] }
58
+ const facets = getSelectedFacets(filters)
59
+ expect(facets).toEqual([
60
+ { key: 'status', value: 'InReview' },
61
+ { key: 'status', value: 'Approved' },
62
+ ])
63
+ })
64
+
65
+ it('does NOT lowercase the status values', () => {
66
+ const filters = { ...emptyFilters, status: ['ConvertedToOrder'] }
67
+ const facets = getSelectedFacets(filters)
68
+ expect(facets[0].value).toBe('ConvertedToOrder')
69
+ expect(facets[0].value).not.toBe('convertedtoorder')
70
+ })
71
+ })
72
+
73
+ describe('getAllFacets', () => {
74
+ it('returns 3 facets: status, createdAt, expiresAt', () => {
75
+ const facets = getAllFacets(emptyFilters)
76
+ expect(facets).toHaveLength(3)
77
+ expect(facets[0].key).toBe('status')
78
+ expect(facets[1].key).toBe('createdAt')
79
+ expect(facets[2].key).toBe('expiresAt')
80
+ })
81
+
82
+ it('status facet values match quoteStatusMap keys (PascalCase)', () => {
83
+ const facets = getAllFacets(emptyFilters)
84
+ const statusFacet = facets[0]
85
+ if (statusFacet.__typename !== 'StoreFacetBoolean') throw new Error()
86
+
87
+ const expectedKeys = Object.keys(quoteStatusMap) as QuoteStatusKey[]
88
+ statusFacet.values.forEach((v, i) => {
89
+ expect(v.value).toBe(expectedKeys[i])
90
+ expect(v.label).toBe(quoteStatusMap[expectedKeys[i]].label)
91
+ })
92
+ })
93
+
94
+ it('range facets carry the date filters', () => {
95
+ const filters = {
96
+ ...emptyFilters,
97
+ createdAtFrom: '2024-01-01',
98
+ createdAtTo: '2024-12-31',
99
+ }
100
+ const facets = getAllFacets(filters)
101
+ const createdAt = facets[1]
102
+ if (createdAt.__typename !== 'StoreFacetRange') throw new Error()
103
+ expect(createdAt.from).toBe('2024-01-01')
104
+ expect(createdAt.to).toBe('2024-12-31')
105
+ })
106
+ })
107
+
108
+ describe('hasActiveFilters', () => {
109
+ it('returns false for empty filters', () => {
110
+ expect(hasActiveFilters(emptyFilters)).toBe(false)
111
+ })
112
+
113
+ it('returns true when status is selected', () => {
114
+ expect(hasActiveFilters({ ...emptyFilters, status: ['Draft'] })).toBe(true)
115
+ })
116
+
117
+ it('returns true when label is set', () => {
118
+ expect(hasActiveFilters({ ...emptyFilters, label: 'quote-123' })).toBe(true)
119
+ })
120
+
121
+ it('returns true when any date filter is set', () => {
122
+ expect(
123
+ hasActiveFilters({ ...emptyFilters, createdAtFrom: '2024-01-01' })
124
+ ).toBe(true)
125
+ })
126
+ })
127
+
128
+ describe('countActiveFilters', () => {
129
+ it('returns 0 for empty filters', () => {
130
+ expect(countActiveFilters(emptyFilters)).toBe(0)
131
+ })
132
+
133
+ it('counts status as one regardless of how many statuses', () => {
134
+ expect(
135
+ countActiveFilters({ ...emptyFilters, status: ['Draft', 'Approved'] })
136
+ ).toBe(1)
137
+ })
138
+
139
+ it('counts createdAt range as one', () => {
140
+ expect(
141
+ countActiveFilters({
142
+ ...emptyFilters,
143
+ createdAtFrom: '2024-01-01',
144
+ createdAtTo: '2024-12-31',
145
+ })
146
+ ).toBe(1)
147
+ })
148
+
149
+ it('counts all three groups independently', () => {
150
+ expect(
151
+ countActiveFilters({
152
+ ...emptyFilters,
153
+ status: ['Draft'],
154
+ createdAtFrom: '2024-01-01',
155
+ expiresAtTo: '2025-01-01',
156
+ })
157
+ ).toBe(3)
158
+ })
159
+ })
160
+
161
+ describe('formatFilterDate', () => {
162
+ it('formats a valid YYYY-MM-DD date', () => {
163
+ const result = formatFilterDate('2024-03-15', 'en-US')
164
+ expect(result).toMatch(/03.+15.+2024|2024.+03.+15/)
165
+ })
166
+
167
+ it('returns the original string for empty input', () => {
168
+ expect(formatFilterDate('', 'en-US')).toBe('')
169
+ })
170
+
171
+ it('returns the original string for invalid format', () => {
172
+ expect(formatFilterDate('not-a-date', 'en-US')).toBe('not-a-date')
173
+ expect(formatFilterDate('2024/03/15', 'en-US')).toBe('2024/03/15')
174
+ })
175
+
176
+ it('returns the original string for zero month/day', () => {
177
+ expect(formatFilterDate('2024-00-15', 'en-US')).toBe('2024-00-15')
178
+ expect(formatFilterDate('2024-03-00', 'en-US')).toBe('2024-03-00')
179
+ })
180
+
181
+ it('returns the original string for dates that roll over (e.g. Feb 31)', () => {
182
+ expect(formatFilterDate('2024-02-31', 'en-US')).toBe('2024-02-31')
183
+ expect(formatFilterDate('2024-04-31', 'en-US')).toBe('2024-04-31')
184
+ })
185
+ })
@@ -0,0 +1,90 @@
1
+ import { describe, expect, it } from 'vitest'
2
+
3
+ import {
4
+ ORDERS_ROUTE,
5
+ PROFILE_ROUTE,
6
+ QUOTES_ROUTE,
7
+ ROUTES_ONLY_FOR_B2B_MEMBERS,
8
+ SECURITY_ROUTE,
9
+ USER_DETAILS_ROUTE,
10
+ getExtraMyAccountRoutes,
11
+ getMyAccountRoutes,
12
+ } from '../../../src/sdk/account/getMyAccountRoutes'
13
+
14
+ describe('getMyAccountRoutes', () => {
15
+ it('includes the default routes plus any extra routes provided', () => {
16
+ const result = getMyAccountRoutes({
17
+ routes: [{ title: 'Custom', route: '/custom' }],
18
+ })
19
+
20
+ expect(result.map((r) => r.route)).toEqual([
21
+ PROFILE_ROUTE,
22
+ ORDERS_ROUTE,
23
+ QUOTES_ROUTE,
24
+ USER_DETAILS_ROUTE,
25
+ SECURITY_ROUTE,
26
+ '/pvt/custom',
27
+ ])
28
+ })
29
+
30
+ it('prefixes routes with /pvt when missing', () => {
31
+ const result = getMyAccountRoutes({
32
+ routes: [{ title: 'Custom', route: '/custom' }],
33
+ })
34
+
35
+ expect(result.find((r) => r.title === 'Custom')?.route).toBe('/pvt/custom')
36
+ })
37
+
38
+ it('leaves titles unchanged when no labels are provided', () => {
39
+ const result = getMyAccountRoutes({ routes: [] })
40
+
41
+ expect(result.find((r) => r.route === QUOTES_ROUTE)?.title).toBe('Quotes')
42
+ })
43
+
44
+ it('overrides default route titles with matching CMS labels', () => {
45
+ const result = getMyAccountRoutes({
46
+ routes: [],
47
+ labels: { profileLabel: 'Meu Perfil', ordersLabel: 'Meus Pedidos' },
48
+ })
49
+
50
+ expect(result.find((r) => r.route === PROFILE_ROUTE)?.title).toBe(
51
+ 'Meu Perfil'
52
+ )
53
+ expect(result.find((r) => r.route === ORDERS_ROUTE)?.title).toBe(
54
+ 'Meus Pedidos'
55
+ )
56
+ })
57
+
58
+ it('falls back to the default title when a label key has no CMS value', () => {
59
+ const result = getMyAccountRoutes({
60
+ routes: [],
61
+ labels: { profileLabel: undefined },
62
+ })
63
+
64
+ expect(result.find((r) => r.route === PROFILE_ROUTE)?.title).toBe('Profile')
65
+ })
66
+
67
+ it('does not relabel routes with no matching label key (e.g. Quotes)', () => {
68
+ const result = getMyAccountRoutes({
69
+ routes: [],
70
+ labels: { profileLabel: 'Meu Perfil' },
71
+ })
72
+
73
+ expect(result.find((r) => r.route === QUOTES_ROUTE)?.title).toBe('Quotes')
74
+ })
75
+
76
+ it('marks Quotes as a B2B-only route', () => {
77
+ expect(ROUTES_ONLY_FOR_B2B_MEMBERS).toContain(QUOTES_ROUTE)
78
+ })
79
+ })
80
+
81
+ describe('getExtraMyAccountRoutes', () => {
82
+ it('filters out routes that are already part of the default set', () => {
83
+ const result = getExtraMyAccountRoutes([
84
+ { title: 'Profile', route: PROFILE_ROUTE },
85
+ { title: 'Custom', route: '/pvt/custom' },
86
+ ])
87
+
88
+ expect(result).toEqual([{ title: 'Custom', route: '/pvt/custom' }])
89
+ })
90
+ })
@@ -74,9 +74,11 @@ const QUERIES = [
74
74
  'productCount',
75
75
  'userOrder',
76
76
  'listUserOrders',
77
+ 'listUserQuotes',
77
78
  'userDetails',
78
79
  'accountProfile',
79
80
  'validateUser',
81
+ 'isOrganizationMember',
80
82
  'pickupPoints',
81
83
  'orderEntryOperation',
82
84
  'orderFormItems',