@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.
- package/.turbo/turbo-generate.log +4 -4
- package/.turbo/turbo-test.log +63 -50
- package/@generated/cached-operations.json +2 -0
- package/@generated/gql.ts +12 -0
- package/@generated/graphql.ts +73 -0
- package/@generated/persisted-documents.json +2 -0
- package/@generated/schema.graphql +59 -0
- package/CHANGELOG.md +10 -0
- package/package.json +6 -6
- package/src/components/account/Drawer/OrganizationDrawer/OrganizationDrawer.tsx +5 -1
- package/src/components/account/Drawer/OrganizationDrawer/OrganizationDrawerBody.tsx +16 -6
- package/src/components/account/Layout/Layout.tsx +14 -5
- package/src/components/account/quotes/MyAccountListQuotes/MyAccountListQuotes.tsx +170 -0
- package/src/components/account/quotes/MyAccountListQuotes/MyAccountListQuotesTable/MyAccountListQuotesTable.tsx +194 -0
- package/src/components/account/quotes/MyAccountListQuotes/MyAccountListQuotesTable/MyAccountQuoteStatusBadge.tsx +15 -0
- package/src/components/account/quotes/MyAccountListQuotes/MyAccountQuotesFilterSlider/MyAccountQuotesFilterSlider.tsx +162 -0
- package/src/components/account/quotes/MyAccountListQuotes/MyAccountQuotesFilterSlider/section.module.scss +49 -0
- package/src/components/account/quotes/MyAccountListQuotes/MyAccountQuotesSelectedTags/MyAccountQuotesSelectedTags.tsx +125 -0
- package/src/components/account/quotes/MyAccountListQuotes/MyAccountQuotesStatusSelector/MyAccountQuotesStatusSelector.tsx +107 -0
- package/src/components/account/quotes/MyAccountListQuotes/MyAccountQuotesStatusSelector/styles.module.scss +144 -0
- package/src/components/account/quotes/MyAccountListQuotes/index.ts +2 -0
- package/src/components/account/quotes/MyAccountListQuotes/quoteFilters.ts +89 -0
- package/src/components/account/quotes/MyAccountListQuotes/styles.module.scss +174 -0
- package/src/customizations/src/myAccount/extensions/quotes/after.tsx +5 -0
- package/src/customizations/src/myAccount/extensions/quotes/before.tsx +5 -0
- package/src/pages/pvt/account/quotes/index.tsx +264 -0
- package/src/sdk/account/getMyAccountRoutes.ts +7 -0
- package/src/utils/quoteStatus.ts +27 -0
- package/test/components/account/Layout.test.tsx +62 -0
- package/test/components/account/OrganizationDrawerBody.test.tsx +52 -0
- package/test/components/quotes/MyAccountListQuotes.test.tsx +232 -0
- package/test/components/quotes/MyAccountListQuotesTable.test.tsx +240 -0
- package/test/components/quotes/MyAccountQuoteStatusBadge.test.tsx +28 -0
- package/test/components/quotes/MyAccountQuotesFilterSlider.test.tsx +152 -0
- package/test/components/quotes/MyAccountQuotesSelectedTags.test.tsx +122 -0
- package/test/components/quotes/MyAccountQuotesStatusSelector.test.tsx +85 -0
- package/test/components/quotes/quoteFilters.test.ts +185 -0
- package/test/sdk/account/getMyAccountRoutes.test.ts +90 -0
- package/test/server/index.test.ts +2 -0
|
@@ -2,10 +2,12 @@ import type { PropsWithChildren } from 'react'
|
|
|
2
2
|
import menuRoutes from 'src/customizations/src/myAccount/navigation'
|
|
3
3
|
import {
|
|
4
4
|
type AccountNavigationLabels,
|
|
5
|
+
ROUTES_ONLY_FOR_B2B_MEMBERS,
|
|
5
6
|
USER_DETAILS_ROUTE,
|
|
6
7
|
getExtraMyAccountRoutes,
|
|
7
8
|
getMyAccountRoutes,
|
|
8
9
|
} from 'src/sdk/account/getMyAccountRoutes'
|
|
10
|
+
import { useSession } from 'src/sdk/session'
|
|
9
11
|
import Menu from '../Menu'
|
|
10
12
|
import styles from '../section.module.scss'
|
|
11
13
|
|
|
@@ -23,6 +25,9 @@ const Layout = ({
|
|
|
23
25
|
isRepresentative = true,
|
|
24
26
|
navigationLabels,
|
|
25
27
|
}: PropsWithChildren<LayoutProps>) => {
|
|
28
|
+
const { b2b } = useSession()
|
|
29
|
+
const isOrgMember = Boolean(b2b?.unitId)
|
|
30
|
+
|
|
26
31
|
const menuItems = navigationLabels
|
|
27
32
|
? getMyAccountRoutes({
|
|
28
33
|
routes: getExtraMyAccountRoutes(menuRoutes),
|
|
@@ -30,11 +35,15 @@ const Layout = ({
|
|
|
30
35
|
})
|
|
31
36
|
: menuRoutes
|
|
32
37
|
|
|
33
|
-
const routes =
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
+
const routes = (
|
|
39
|
+
isRepresentative
|
|
40
|
+
? menuItems
|
|
41
|
+
: menuItems.filter(
|
|
42
|
+
({ route }) => !ROUTES_ONLY_FOR_REPRESENTATIVE.includes(route)
|
|
43
|
+
)
|
|
44
|
+
).filter(
|
|
45
|
+
({ route }) => isOrgMember || !ROUTES_ONLY_FOR_B2B_MEMBERS.includes(route)
|
|
46
|
+
)
|
|
38
47
|
|
|
39
48
|
return (
|
|
40
49
|
<section className={styles.layout}>
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Button,
|
|
3
|
+
EmptyState,
|
|
4
|
+
Icon as UIIcon,
|
|
5
|
+
SearchInputField,
|
|
6
|
+
useUI,
|
|
7
|
+
type SearchInputFieldRef,
|
|
8
|
+
} from '@faststore/ui'
|
|
9
|
+
import { useRef, useEffect, type MutableRefObject } from 'react'
|
|
10
|
+
|
|
11
|
+
import type { ServerListQuotesQueryQuery } from '@generated/graphql'
|
|
12
|
+
|
|
13
|
+
import AccountHeader from '../../components/Header'
|
|
14
|
+
import { useDebounce } from 'src/sdk/account/useDebounce'
|
|
15
|
+
import { useMyAccountFilter } from 'src/sdk/search/useMyAccountFilter'
|
|
16
|
+
import useScreenResize from 'src/sdk/ui/useScreenResize'
|
|
17
|
+
import MyAccountListQuotesTable, {
|
|
18
|
+
Pagination,
|
|
19
|
+
} from './MyAccountListQuotesTable/MyAccountListQuotesTable'
|
|
20
|
+
import MyAccountQuotesFilterSlider from './MyAccountQuotesFilterSlider/MyAccountQuotesFilterSlider'
|
|
21
|
+
import {
|
|
22
|
+
getSelectedFacets,
|
|
23
|
+
getAllFacets,
|
|
24
|
+
hasActiveFilters,
|
|
25
|
+
countActiveFilters,
|
|
26
|
+
} from './quoteFilters'
|
|
27
|
+
import styles from './styles.module.scss'
|
|
28
|
+
|
|
29
|
+
export type MyAccountListQuotesProps = Readonly<{
|
|
30
|
+
listQuotes: ServerListQuotesQueryQuery['listUserQuotes']
|
|
31
|
+
total: number
|
|
32
|
+
perPage: number
|
|
33
|
+
filters: {
|
|
34
|
+
page: number
|
|
35
|
+
status: string[]
|
|
36
|
+
createdAtFrom: string
|
|
37
|
+
createdAtTo: string
|
|
38
|
+
expiresAtFrom: string
|
|
39
|
+
expiresAtTo: string
|
|
40
|
+
label: string
|
|
41
|
+
}
|
|
42
|
+
}>
|
|
43
|
+
|
|
44
|
+
export default function MyAccountListQuotes({
|
|
45
|
+
listQuotes,
|
|
46
|
+
total,
|
|
47
|
+
perPage,
|
|
48
|
+
filters,
|
|
49
|
+
}: MyAccountListQuotesProps) {
|
|
50
|
+
const { isDesktop } = useScreenResize()
|
|
51
|
+
const searchInputRef = useRef(null) as MutableRefObject<SearchInputFieldRef>
|
|
52
|
+
|
|
53
|
+
useEffect(() => {
|
|
54
|
+
if (!searchInputRef.current?.inputRef) return
|
|
55
|
+
searchInputRef.current.inputRef.value = filters.label ?? ''
|
|
56
|
+
}, [filters.label])
|
|
57
|
+
|
|
58
|
+
const handleSearchChange = useDebounce(
|
|
59
|
+
(value: string) => {
|
|
60
|
+
const params = new URLSearchParams(globalThis.location.search)
|
|
61
|
+
params.delete('label')
|
|
62
|
+
params.delete('page')
|
|
63
|
+
if (value) params.set('label', value)
|
|
64
|
+
globalThis.location.href = `/pvt/account/quotes?${params.toString()}`
|
|
65
|
+
},
|
|
66
|
+
300,
|
|
67
|
+
filters.label
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
const selectedFacets = getSelectedFacets(filters)
|
|
71
|
+
const allFacets = getAllFacets(filters)
|
|
72
|
+
|
|
73
|
+
const filter = useMyAccountFilter({ allFacets, selectedFacets })
|
|
74
|
+
const { openFilter, filter: displayFilter } = useUI()
|
|
75
|
+
|
|
76
|
+
const hasFilters = hasActiveFilters(filters)
|
|
77
|
+
const activeFilterCount = countActiveFilters(filters)
|
|
78
|
+
const isEmpty = (listQuotes?.list.length ?? 0) === 0
|
|
79
|
+
|
|
80
|
+
return (
|
|
81
|
+
<div className={styles.page}>
|
|
82
|
+
<AccountHeader pageTitle="Quotes" />
|
|
83
|
+
<div data-fs-list-quotes-controls>
|
|
84
|
+
<div data-fs-list-quotes-search-filters>
|
|
85
|
+
<SearchInputField
|
|
86
|
+
ref={searchInputRef}
|
|
87
|
+
data-fs-search-input-field-list-quotes
|
|
88
|
+
placeholder="Search"
|
|
89
|
+
onBlur={() => {
|
|
90
|
+
handleSearchChange(searchInputRef.current.inputRef.value)
|
|
91
|
+
}}
|
|
92
|
+
onKeyDown={(e) => {
|
|
93
|
+
if (e.key === 'Enter') {
|
|
94
|
+
handleSearchChange(searchInputRef.current.inputRef.value)
|
|
95
|
+
}
|
|
96
|
+
}}
|
|
97
|
+
onSubmit={() => {
|
|
98
|
+
handleSearchChange(searchInputRef.current.inputRef.value)
|
|
99
|
+
}}
|
|
100
|
+
/>
|
|
101
|
+
<Button
|
|
102
|
+
data-fs-list-quotes-search-filters-button
|
|
103
|
+
size="small"
|
|
104
|
+
variant="tertiary"
|
|
105
|
+
icon={
|
|
106
|
+
<UIIcon
|
|
107
|
+
name="FadersHorizontal"
|
|
108
|
+
width={24}
|
|
109
|
+
height={24}
|
|
110
|
+
aria-label="Open Filters"
|
|
111
|
+
/>
|
|
112
|
+
}
|
|
113
|
+
iconPosition="left"
|
|
114
|
+
onClick={() => {
|
|
115
|
+
filter.dispatch({
|
|
116
|
+
type: 'selectFacets',
|
|
117
|
+
payload: getSelectedFacets(filters),
|
|
118
|
+
})
|
|
119
|
+
openFilter()
|
|
120
|
+
}}
|
|
121
|
+
>
|
|
122
|
+
Filters
|
|
123
|
+
{activeFilterCount > 0 && (
|
|
124
|
+
<span data-fs-filter-count>{activeFilterCount}</span>
|
|
125
|
+
)}
|
|
126
|
+
</Button>
|
|
127
|
+
</div>
|
|
128
|
+
{isDesktop && total > 0 && (
|
|
129
|
+
<Pagination page={filters.page} total={total} perPage={perPage} />
|
|
130
|
+
)}
|
|
131
|
+
</div>
|
|
132
|
+
|
|
133
|
+
{displayFilter && (
|
|
134
|
+
<MyAccountQuotesFilterSlider
|
|
135
|
+
{...filter}
|
|
136
|
+
title="Filters"
|
|
137
|
+
clearButtonLabel="Clear All"
|
|
138
|
+
applyButtonLabel="View Results"
|
|
139
|
+
testId="my-account-quotes-filter-slider"
|
|
140
|
+
/>
|
|
141
|
+
)}
|
|
142
|
+
|
|
143
|
+
{isEmpty ? (
|
|
144
|
+
<EmptyState
|
|
145
|
+
titleIcon={
|
|
146
|
+
<UIIcon
|
|
147
|
+
name={hasFilters ? 'MagnifyingGlass' : 'Bag2'}
|
|
148
|
+
width={56}
|
|
149
|
+
height={56}
|
|
150
|
+
weight="thin"
|
|
151
|
+
/>
|
|
152
|
+
}
|
|
153
|
+
title={hasFilters ? 'No results found' : "You don't have any quotes"}
|
|
154
|
+
bkgColor="light"
|
|
155
|
+
/>
|
|
156
|
+
) : (
|
|
157
|
+
<MyAccountListQuotesTable
|
|
158
|
+
listQuotes={listQuotes}
|
|
159
|
+
total={total}
|
|
160
|
+
perPage={perPage}
|
|
161
|
+
filters={filters}
|
|
162
|
+
/>
|
|
163
|
+
)}
|
|
164
|
+
|
|
165
|
+
{!isDesktop && total > 0 && (
|
|
166
|
+
<Pagination page={filters.page} total={total} perPage={perPage} />
|
|
167
|
+
)}
|
|
168
|
+
</div>
|
|
169
|
+
)
|
|
170
|
+
}
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
import { Icon, IconButton } from '@faststore/ui'
|
|
2
|
+
|
|
3
|
+
import MyAccountQuoteStatusBadge from './MyAccountQuoteStatusBadge'
|
|
4
|
+
import { useSession } from 'src/sdk/session'
|
|
5
|
+
import useScreenResize from 'src/sdk/ui/useScreenResize'
|
|
6
|
+
import type { ServerListQuotesQueryQuery } from '@generated/graphql'
|
|
7
|
+
|
|
8
|
+
function formatDateTime(isoString: string, locale: string) {
|
|
9
|
+
if (!isoString) return ''
|
|
10
|
+
const parsed = new Date(isoString)
|
|
11
|
+
if (Number.isNaN(parsed.getTime())) return isoString
|
|
12
|
+
return parsed.toLocaleString(locale, {
|
|
13
|
+
year: 'numeric',
|
|
14
|
+
month: 'short',
|
|
15
|
+
day: 'numeric',
|
|
16
|
+
hour: 'numeric',
|
|
17
|
+
minute: '2-digit',
|
|
18
|
+
})
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function formatDateShort(isoString: string, locale: string) {
|
|
22
|
+
if (!isoString) return ''
|
|
23
|
+
const parsed = new Date(isoString)
|
|
24
|
+
if (Number.isNaN(parsed.getTime())) return isoString
|
|
25
|
+
return parsed.toLocaleDateString(locale, {
|
|
26
|
+
year: 'numeric',
|
|
27
|
+
month: 'short',
|
|
28
|
+
day: 'numeric',
|
|
29
|
+
})
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function getRelativeExpiry(isoString: string): string | null {
|
|
33
|
+
const diffMs = new Date(isoString).getTime() - Date.now()
|
|
34
|
+
if (diffMs <= 0) return null
|
|
35
|
+
|
|
36
|
+
const hours = Math.floor(diffMs / 3_600_000)
|
|
37
|
+
const days = Math.floor(diffMs / 86_400_000)
|
|
38
|
+
const weeks = Math.floor(days / 7)
|
|
39
|
+
const months = Math.floor(days / 30)
|
|
40
|
+
|
|
41
|
+
if (hours < 24) return `${hours} ${hours === 1 ? 'hour' : 'hours'} left`
|
|
42
|
+
if (days < 14) return `${days} ${days === 1 ? 'day' : 'days'} left`
|
|
43
|
+
if (weeks < 8) return `${weeks} ${weeks === 1 ? 'week' : 'weeks'} left`
|
|
44
|
+
return `${months} ${months === 1 ? 'month' : 'months'} left`
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function formatAmount(amount: number, locale: string, currencyCode: string) {
|
|
48
|
+
return new Intl.NumberFormat(locale, {
|
|
49
|
+
style: 'currency',
|
|
50
|
+
currency: currencyCode,
|
|
51
|
+
minimumFractionDigits: 2,
|
|
52
|
+
}).format(amount)
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
type MyAccountListQuotesTableProps = Readonly<{
|
|
56
|
+
listQuotes: ServerListQuotesQueryQuery['listUserQuotes']
|
|
57
|
+
total: number
|
|
58
|
+
perPage: number
|
|
59
|
+
filters: {
|
|
60
|
+
page: number
|
|
61
|
+
status: string[]
|
|
62
|
+
createdAtFrom: string
|
|
63
|
+
createdAtTo: string
|
|
64
|
+
expiresAtFrom: string
|
|
65
|
+
expiresAtTo: string
|
|
66
|
+
label: string
|
|
67
|
+
}
|
|
68
|
+
}>
|
|
69
|
+
|
|
70
|
+
export function Pagination({
|
|
71
|
+
page,
|
|
72
|
+
total,
|
|
73
|
+
perPage,
|
|
74
|
+
}: Readonly<{
|
|
75
|
+
page: number
|
|
76
|
+
total: number
|
|
77
|
+
perPage: number
|
|
78
|
+
}>) {
|
|
79
|
+
const totalPages = Math.ceil(total / perPage)
|
|
80
|
+
const firstIndexLabel = page === 1 ? 1 : (page - 1) * perPage + 1
|
|
81
|
+
const lastIndexLabel = Math.min(firstIndexLabel + perPage - 1, total)
|
|
82
|
+
|
|
83
|
+
const handlePageChange = (newPage: number) => {
|
|
84
|
+
const params = new URLSearchParams(globalThis.location.search)
|
|
85
|
+
if (newPage === 1 || newPage === 0) {
|
|
86
|
+
params.delete('page')
|
|
87
|
+
} else {
|
|
88
|
+
params.set('page', String(newPage))
|
|
89
|
+
}
|
|
90
|
+
const search = params.toString()
|
|
91
|
+
globalThis.location.href = search
|
|
92
|
+
? `/pvt/account/quotes?${search}`
|
|
93
|
+
: '/pvt/account/quotes'
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
return (
|
|
97
|
+
<div data-fs-list-orders-table-pagination>
|
|
98
|
+
<p>{`${firstIndexLabel} — ${lastIndexLabel} of ${total}`}</p>
|
|
99
|
+
<IconButton
|
|
100
|
+
size="small"
|
|
101
|
+
variant="tertiary"
|
|
102
|
+
disabled={page === 1}
|
|
103
|
+
onClick={() => handlePageChange(page - 1)}
|
|
104
|
+
icon={<Icon name="CaretLeft" />}
|
|
105
|
+
aria-label="Previous Page"
|
|
106
|
+
/>
|
|
107
|
+
<IconButton
|
|
108
|
+
size="small"
|
|
109
|
+
variant="tertiary"
|
|
110
|
+
disabled={page === totalPages}
|
|
111
|
+
onClick={() => handlePageChange(page + 1)}
|
|
112
|
+
icon={<Icon name="CaretRight" />}
|
|
113
|
+
aria-label="Next Page"
|
|
114
|
+
/>
|
|
115
|
+
</div>
|
|
116
|
+
)
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export default function MyAccountListQuotesTable({
|
|
120
|
+
listQuotes,
|
|
121
|
+
total,
|
|
122
|
+
perPage,
|
|
123
|
+
filters,
|
|
124
|
+
}: MyAccountListQuotesTableProps) {
|
|
125
|
+
const { isDesktop } = useScreenResize()
|
|
126
|
+
const { locale, currency } = useSession()
|
|
127
|
+
const currencyCode = currency.code
|
|
128
|
+
|
|
129
|
+
return (
|
|
130
|
+
<>
|
|
131
|
+
<div data-fs-quotes-list>
|
|
132
|
+
{listQuotes.list.map((item) => {
|
|
133
|
+
const relativeExpiry = getRelativeExpiry(item.expiresAt)
|
|
134
|
+
const formattedAmount = formatAmount(
|
|
135
|
+
item.amount,
|
|
136
|
+
locale,
|
|
137
|
+
currencyCode
|
|
138
|
+
)
|
|
139
|
+
|
|
140
|
+
return (
|
|
141
|
+
<div data-fs-quotes-list-row key={item.id}>
|
|
142
|
+
{/* Left: ID + label */}
|
|
143
|
+
<div data-fs-quotes-list-col-main>
|
|
144
|
+
<p data-fs-quotes-list-id>{item.id}</p>
|
|
145
|
+
{item.label && <p data-fs-quotes-list-label>{item.label}</p>}
|
|
146
|
+
</div>
|
|
147
|
+
|
|
148
|
+
{/* Middle: created by + dates (desktop only) */}
|
|
149
|
+
{isDesktop && (
|
|
150
|
+
<div data-fs-quotes-list-col-dates>
|
|
151
|
+
{item.createdBy && (
|
|
152
|
+
<div data-fs-quotes-list-date-group>
|
|
153
|
+
<span data-fs-quotes-list-date-label>Created by</span>
|
|
154
|
+
<span data-fs-quotes-list-date-value>
|
|
155
|
+
{item.createdBy}
|
|
156
|
+
</span>
|
|
157
|
+
</div>
|
|
158
|
+
)}
|
|
159
|
+
<div data-fs-quotes-list-date-group>
|
|
160
|
+
<span data-fs-quotes-list-date-label>Creation date</span>
|
|
161
|
+
<span data-fs-quotes-list-date-value>
|
|
162
|
+
{formatDateTime(item.createdAt, locale)}
|
|
163
|
+
</span>
|
|
164
|
+
</div>
|
|
165
|
+
<div data-fs-quotes-list-date-group>
|
|
166
|
+
<span data-fs-quotes-list-date-label>Expires on</span>
|
|
167
|
+
<span data-fs-quotes-list-date-value>
|
|
168
|
+
{formatDateShort(item.expiresAt, locale)}
|
|
169
|
+
{relativeExpiry && (
|
|
170
|
+
<span data-fs-quotes-list-relative-expiry>
|
|
171
|
+
{' · '}
|
|
172
|
+
{relativeExpiry}
|
|
173
|
+
</span>
|
|
174
|
+
)}
|
|
175
|
+
</span>
|
|
176
|
+
</div>
|
|
177
|
+
</div>
|
|
178
|
+
)}
|
|
179
|
+
|
|
180
|
+
{/* Right: badge + total */}
|
|
181
|
+
<div data-fs-quotes-list-col-status>
|
|
182
|
+
<MyAccountQuoteStatusBadge status={item.status} />
|
|
183
|
+
<p data-fs-quotes-list-total>
|
|
184
|
+
Total <strong>{formattedAmount}</strong>
|
|
185
|
+
</p>
|
|
186
|
+
</div>
|
|
187
|
+
</div>
|
|
188
|
+
)
|
|
189
|
+
})}
|
|
190
|
+
</div>
|
|
191
|
+
<Pagination page={filters.page} total={total} perPage={perPage} />
|
|
192
|
+
</>
|
|
193
|
+
)
|
|
194
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { quoteStatusMap, type QuoteStatusKey } from 'src/utils/quoteStatus'
|
|
2
|
+
|
|
3
|
+
function MyAccountQuoteStatusBadge({ status }: Readonly<{ status: string }>) {
|
|
4
|
+
const entry = quoteStatusMap[status as QuoteStatusKey]
|
|
5
|
+
return (
|
|
6
|
+
<span
|
|
7
|
+
data-fs-my-account-badge
|
|
8
|
+
data-fs-my-account-badge-variant={entry?.variant ?? 'neutral'}
|
|
9
|
+
>
|
|
10
|
+
{entry?.label ?? status}
|
|
11
|
+
</span>
|
|
12
|
+
)
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export default MyAccountQuoteStatusBadge
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import { FilterSlider as UIFilterSlider } from '@faststore/ui'
|
|
2
|
+
|
|
3
|
+
import { useRef, useState } from 'react'
|
|
4
|
+
import MyAccountFilterFacetDateRange from 'src/components/account/orders/ListOrders/FilterSlider/FilterFacetDateRange/FilterFacetDateRange'
|
|
5
|
+
import type {
|
|
6
|
+
MyAccountFilter_FacetsFragment,
|
|
7
|
+
useMyAccountFilter,
|
|
8
|
+
} from 'src/sdk/search/useMyAccountFilter'
|
|
9
|
+
import MyAccountQuotesStatusSelector from '../MyAccountQuotesStatusSelector/MyAccountQuotesStatusSelector'
|
|
10
|
+
import styles from './section.module.scss'
|
|
11
|
+
|
|
12
|
+
export interface MyAccountQuotesFilterSliderProps {
|
|
13
|
+
testId?: string
|
|
14
|
+
facets: MyAccountFilter_FacetsFragment[]
|
|
15
|
+
title?: string
|
|
16
|
+
clearButtonLabel?: string
|
|
17
|
+
applyButtonLabel?: string
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function MyAccountQuotesFilterSlider({
|
|
21
|
+
facets,
|
|
22
|
+
testId,
|
|
23
|
+
dispatch,
|
|
24
|
+
expanded,
|
|
25
|
+
selected,
|
|
26
|
+
title,
|
|
27
|
+
clearButtonLabel,
|
|
28
|
+
applyButtonLabel,
|
|
29
|
+
}: MyAccountQuotesFilterSliderProps & ReturnType<typeof useMyAccountFilter>) {
|
|
30
|
+
const createdDateRangeRef = useRef<{
|
|
31
|
+
clear: () => void
|
|
32
|
+
getDataRangeFacet: () => {
|
|
33
|
+
key: string
|
|
34
|
+
value: { from: string; to: string }
|
|
35
|
+
}
|
|
36
|
+
}>(null)
|
|
37
|
+
|
|
38
|
+
const expiresDateRangeRef = useRef<{
|
|
39
|
+
clear: () => void
|
|
40
|
+
getDataRangeFacet: () => {
|
|
41
|
+
key: string
|
|
42
|
+
value: { from: string; to: string }
|
|
43
|
+
}
|
|
44
|
+
}>(null)
|
|
45
|
+
|
|
46
|
+
const [disabled, setDisabled] = useState(false)
|
|
47
|
+
|
|
48
|
+
const handleApply = () => {
|
|
49
|
+
const createdRange = createdDateRangeRef.current?.getDataRangeFacet()?.value
|
|
50
|
+
const expiresRange = expiresDateRangeRef.current?.getDataRangeFacet()?.value
|
|
51
|
+
|
|
52
|
+
const params = new URLSearchParams(globalThis.location.search)
|
|
53
|
+
params.delete('page')
|
|
54
|
+
params.delete('status')
|
|
55
|
+
params.delete('createdAtFrom')
|
|
56
|
+
params.delete('createdAtTo')
|
|
57
|
+
params.delete('expiresAtFrom')
|
|
58
|
+
params.delete('expiresAtTo')
|
|
59
|
+
|
|
60
|
+
const statusFacets = selected.filter((f) => f.key === 'status')
|
|
61
|
+
statusFacets.forEach((f) => params.append('status', f.value))
|
|
62
|
+
|
|
63
|
+
if (createdRange?.from?.trim()) {
|
|
64
|
+
params.set('createdAtFrom', createdRange.from.trim())
|
|
65
|
+
}
|
|
66
|
+
if (createdRange?.to?.trim()) {
|
|
67
|
+
params.set('createdAtTo', createdRange.to.trim())
|
|
68
|
+
}
|
|
69
|
+
if (expiresRange?.from?.trim()) {
|
|
70
|
+
params.set('expiresAtFrom', expiresRange.from.trim())
|
|
71
|
+
}
|
|
72
|
+
if (expiresRange?.to?.trim()) {
|
|
73
|
+
params.set('expiresAtTo', expiresRange.to.trim())
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const search = params.toString()
|
|
77
|
+
globalThis.location.href = search
|
|
78
|
+
? `/pvt/account/quotes?${search}`
|
|
79
|
+
: '/pvt/account/quotes'
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const statusFacet = facets.find(
|
|
83
|
+
(f) => f.__typename === 'StoreFacetBoolean' && f.key === 'status'
|
|
84
|
+
)
|
|
85
|
+
const rangeFacets = facets.filter((f) => f.__typename === 'StoreFacetRange')
|
|
86
|
+
const selectedStatusValues = selected
|
|
87
|
+
.filter((f) => f.key === 'status')
|
|
88
|
+
.map((f) => f.value)
|
|
89
|
+
|
|
90
|
+
return (
|
|
91
|
+
<UIFilterSlider
|
|
92
|
+
overlayProps={{
|
|
93
|
+
className: `section ${styles.section} section-filter-slider`,
|
|
94
|
+
}}
|
|
95
|
+
title={title}
|
|
96
|
+
size="partial"
|
|
97
|
+
direction="rightSide"
|
|
98
|
+
clearBtnProps={{
|
|
99
|
+
variant: 'secondary',
|
|
100
|
+
onClick: () => {
|
|
101
|
+
createdDateRangeRef.current?.clear()
|
|
102
|
+
expiresDateRangeRef.current?.clear()
|
|
103
|
+
dispatch({ type: 'selectFacets', payload: [] })
|
|
104
|
+
},
|
|
105
|
+
children: clearButtonLabel ?? 'Clear All',
|
|
106
|
+
}}
|
|
107
|
+
applyBtnProps={{
|
|
108
|
+
variant: 'primary',
|
|
109
|
+
onClick: handleApply,
|
|
110
|
+
disabled,
|
|
111
|
+
children: applyButtonLabel ?? 'Apply',
|
|
112
|
+
}}
|
|
113
|
+
onClose={() => {}}
|
|
114
|
+
>
|
|
115
|
+
{statusFacet && (
|
|
116
|
+
<MyAccountQuotesStatusSelector
|
|
117
|
+
value={selectedStatusValues}
|
|
118
|
+
onChange={(newSelected) => {
|
|
119
|
+
const added = newSelected.filter(
|
|
120
|
+
(v) => !selectedStatusValues.includes(v)
|
|
121
|
+
)
|
|
122
|
+
const removed = selectedStatusValues.filter(
|
|
123
|
+
(v) => !newSelected.includes(v)
|
|
124
|
+
)
|
|
125
|
+
;[...added, ...removed].forEach((v) =>
|
|
126
|
+
dispatch({
|
|
127
|
+
type: 'toggleFacet',
|
|
128
|
+
payload: { key: 'status', value: v },
|
|
129
|
+
})
|
|
130
|
+
)
|
|
131
|
+
}}
|
|
132
|
+
/>
|
|
133
|
+
)}
|
|
134
|
+
{rangeFacets.map((facet, index) => (
|
|
135
|
+
<div
|
|
136
|
+
key={`${testId}-${facet.label}-${index}`}
|
|
137
|
+
data-fs-quotes-filter-group
|
|
138
|
+
>
|
|
139
|
+
<p data-fs-quotes-filter-group-label>{facet.label}</p>
|
|
140
|
+
{facet.key === 'createdAt' && (
|
|
141
|
+
<MyAccountFilterFacetDateRange
|
|
142
|
+
ref={createdDateRangeRef}
|
|
143
|
+
from={facet.from}
|
|
144
|
+
to={facet.to}
|
|
145
|
+
setDisabled={setDisabled}
|
|
146
|
+
/>
|
|
147
|
+
)}
|
|
148
|
+
{facet.key === 'expiresAt' && (
|
|
149
|
+
<MyAccountFilterFacetDateRange
|
|
150
|
+
ref={expiresDateRangeRef}
|
|
151
|
+
from={facet.from}
|
|
152
|
+
to={facet.to}
|
|
153
|
+
setDisabled={setDisabled}
|
|
154
|
+
/>
|
|
155
|
+
)}
|
|
156
|
+
</div>
|
|
157
|
+
))}
|
|
158
|
+
</UIFilterSlider>
|
|
159
|
+
)
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
export default MyAccountQuotesFilterSlider
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
@use "sass:meta";
|
|
2
|
+
|
|
3
|
+
.section {
|
|
4
|
+
@include meta.load-css("~@faststore/ui/src/components/atoms/Button/styles.scss");
|
|
5
|
+
@include meta.load-css("~@faststore/ui/src/components/atoms/Badge/styles.scss");
|
|
6
|
+
@include meta.load-css("~@faststore/ui/src/components/atoms/Checkbox/styles.scss");
|
|
7
|
+
@include meta.load-css("~@faststore/ui/src/components/atoms/Icon/styles.scss");
|
|
8
|
+
@include meta.load-css("~@faststore/ui/src/components/atoms/Input/styles.scss");
|
|
9
|
+
@include meta.load-css("~@faststore/ui/src/components/atoms/Link/styles.scss");
|
|
10
|
+
@include meta.load-css("~@faststore/ui/src/components/atoms/List/styles.scss");
|
|
11
|
+
@include meta.load-css("~@faststore/ui/src/components/atoms/Slider/styles.scss");
|
|
12
|
+
@include meta.load-css("~@faststore/ui/src/components/molecules/Toggle/styles.scss");
|
|
13
|
+
@include meta.load-css("~@faststore/ui/src/components/molecules/DiscountBadge/styles.scss");
|
|
14
|
+
@include meta.load-css("~@faststore/ui/src/components/molecules/Accordion/styles.scss");
|
|
15
|
+
@include meta.load-css("~@faststore/ui/src/components/molecules/InputField/styles.scss");
|
|
16
|
+
@include meta.load-css("~@faststore/ui/src/components/molecules/Modal/styles.scss");
|
|
17
|
+
@include meta.load-css("~@faststore/ui/src/components/molecules/SelectField/styles.scss");
|
|
18
|
+
@include meta.load-css("~@faststore/ui/src/components/organisms/Filter/styles.scss");
|
|
19
|
+
@include meta.load-css("~@faststore/ui/src/components/organisms/FilterSlider/styles.scss");
|
|
20
|
+
@include meta.load-css("~@faststore/ui/src/components/organisms/SlideOver/styles.scss");
|
|
21
|
+
@include meta.load-css("../../../orders/ListOrders/FilterSlider/FilterFacetDateRange/styles.scss");
|
|
22
|
+
|
|
23
|
+
[data-fs-slide-over] {
|
|
24
|
+
--fs-slide-over-partial-width-notebook: calc(100% / 2.2);
|
|
25
|
+
--fs-slide-over-partial-max-width-notebook: calc(var(--fs-grid-breakpoint-notebook) / 2.2);
|
|
26
|
+
--fs-filter-slider-content-padding: 0 var(--fs-spacing-7);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
[data-fs-badge] {
|
|
30
|
+
display: none;
|
|
31
|
+
visibility: hidden;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Status selector inside the filter drawer
|
|
35
|
+
[data-fs-quotes-status-selector] {
|
|
36
|
+
padding: var(--fs-spacing-5) var(--fs-spacing-7) 0;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
[data-fs-quotes-filter-group] {
|
|
40
|
+
padding: var(--fs-spacing-5) var(--fs-spacing-7) 0;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
[data-fs-quotes-filter-group-label] {
|
|
44
|
+
margin: 0 0 var(--fs-spacing-3);
|
|
45
|
+
font-size: var(--fs-text-size-base);
|
|
46
|
+
font-weight: var(--fs-text-weight-regular);
|
|
47
|
+
color: var(--fs-color-text);
|
|
48
|
+
}
|
|
49
|
+
}
|