@akinon/pz-b2b 1.18.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/CHANGELOG.md +7 -0
- package/README.md +234 -0
- package/package.json +18 -0
- package/src/basket-b2b/index.tsx +220 -0
- package/src/hook/use-b2b.tsx +50 -0
- package/src/index.ts +6 -0
- package/src/my-quotations/index.tsx +136 -0
- package/src/utils/gtm.ts +26 -0
- package/src/views/basket-b2b/basket-item.tsx +144 -0
- package/src/views/basket-b2b/index.ts +4 -0
- package/src/views/basket-b2b/remove-product-modal.tsx +109 -0
- package/src/views/basket-b2b/request-quote-modal.tsx +97 -0
- package/src/views/basket-b2b/save-cart-modal.tsx +80 -0
- package/src/views/my-quotations/tabs.tsx +184 -0
- package/src/views/product/store-modal.tsx +170 -0
package/CHANGELOG.md
ADDED
package/README.md
ADDED
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
# pz-b2b
|
|
2
|
+
|
|
3
|
+
### Install the npm package
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
# For latest version
|
|
7
|
+
yarn add git+ssh://git@bitbucket.org:akinonteam/pz-b2b.git
|
|
8
|
+
|
|
9
|
+
# For specific version
|
|
10
|
+
yarn add git+ssh://git@bitbucket.org:akinonteam/pz-b2b.git#xxxxxxx
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
### Installation
|
|
14
|
+
|
|
15
|
+
##### File Path: src/routes/index.ts
|
|
16
|
+
|
|
17
|
+
Add ACCOUNT_MY_QUOTATIONS route into ACCOUNT_ROUTES
|
|
18
|
+
|
|
19
|
+
```javascript
|
|
20
|
+
enum ACCOUNT_ROUTES {
|
|
21
|
+
...,
|
|
22
|
+
ACCOUNT_MY_QUOTATIONS = '/users/my-quotations'
|
|
23
|
+
}
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
##### File Path: src/views/account/account-menu.tsx
|
|
27
|
+
|
|
28
|
+
Add my quotes link to account menu
|
|
29
|
+
|
|
30
|
+
```javascript
|
|
31
|
+
const ACCOUNT_MENU_ITEMS = [
|
|
32
|
+
...,
|
|
33
|
+
{
|
|
34
|
+
translationKey: 'account.base.menu.my_quotations',
|
|
35
|
+
href: ROUTES.ACCOUNT_MY_QUOTATIONS,
|
|
36
|
+
testId: 'account-my-quotations'
|
|
37
|
+
}
|
|
38
|
+
];
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
##### File Path: public/locales/en/account.json
|
|
42
|
+
|
|
43
|
+
##### File Path: public/locales/tr/account.json
|
|
44
|
+
|
|
45
|
+
Add translation to account.base.menu. Apply this for every language
|
|
46
|
+
|
|
47
|
+
```json
|
|
48
|
+
"my_quotations": "My Quotations"
|
|
49
|
+
"my_quotations": "Tüm Tekliflerim"
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
##### File Path: public/locales/en/product.json
|
|
53
|
+
|
|
54
|
+
##### File Path: public/locales/tr/product.json
|
|
55
|
+
|
|
56
|
+
Add the translation inside the product object. Apply this for every language
|
|
57
|
+
|
|
58
|
+
```json
|
|
59
|
+
"store_select_modal": {
|
|
60
|
+
"title": "CHOOSE STORE",
|
|
61
|
+
"store_name": "STORE NAME",
|
|
62
|
+
"quantity": "QUANTITY",
|
|
63
|
+
"add_to_basket": "Add to Basket",
|
|
64
|
+
"valid_quantity": "Please select a store and enter a valid quantity"
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
"store_select_modal": {
|
|
68
|
+
"title": "MAĞAZA SEÇ",
|
|
69
|
+
"store_name": "MAĞAZA ADI",
|
|
70
|
+
"quantity": "ADET",
|
|
71
|
+
"add_to_basket": "SEPETE EKLE",
|
|
72
|
+
"valid_quantity": "Lütfen bir mağaza seçin ve geçerli bir miktar girin"
|
|
73
|
+
}
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
##### File Path: public/locales/en/basket.json
|
|
77
|
+
|
|
78
|
+
##### File Path: public/locales/tr/basket.json
|
|
79
|
+
|
|
80
|
+
Add the translation inside the basket object. Apply this for every language
|
|
81
|
+
|
|
82
|
+
```json
|
|
83
|
+
"b2b": {
|
|
84
|
+
"my_cart": "My Cart",
|
|
85
|
+
"back_to_shopping": "BACK TO SHOPPING",
|
|
86
|
+
"save_cart": "SAVE CART",
|
|
87
|
+
"request_quote": "REQUEST A QUOTE",
|
|
88
|
+
"total_price": "Catalog Total Price (Excl. Tax)",
|
|
89
|
+
"save_cart_modal": {
|
|
90
|
+
"title": "SAVE CART",
|
|
91
|
+
"cart_name": "Cart Name",
|
|
92
|
+
"save_cart_button": "SAVE CART"
|
|
93
|
+
},
|
|
94
|
+
"request_quote_modal": {
|
|
95
|
+
"title": "REQUEST A QUOTE",
|
|
96
|
+
"quotation_name": "Quotation Name",
|
|
97
|
+
"button": "SAVE"
|
|
98
|
+
},
|
|
99
|
+
"remove_product_modal": {
|
|
100
|
+
"title": "DELETE AND ADD TO FAVORITES",
|
|
101
|
+
"description": "Would you like to add this product you want to delete to your favorites?",
|
|
102
|
+
"add_to_favorites_button": "ADD TO FAVORITES",
|
|
103
|
+
"delete_product_button": "delete product"
|
|
104
|
+
},
|
|
105
|
+
"empty": {
|
|
106
|
+
"title": "Your cart is empty, would you like to continue with your saved carts?",
|
|
107
|
+
"button": "SELECT CART",
|
|
108
|
+
"choose_cart_placeholder": "Chose a cart"
|
|
109
|
+
},
|
|
110
|
+
"table": {
|
|
111
|
+
"basket_qty": "<Qty /> Products",
|
|
112
|
+
"delete": "Delete",
|
|
113
|
+
"name_sku": "NAME & SKU",
|
|
114
|
+
"price": "PRICE",
|
|
115
|
+
"quantity": "QTY",
|
|
116
|
+
"store_name": "STORE NAME",
|
|
117
|
+
"store_qty": "STORE QTY",
|
|
118
|
+
"subtotal": "SUBTOTAL"
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
"b2b": {
|
|
123
|
+
"my_cart": "Sepetim",
|
|
124
|
+
"back_to_shopping": "ALIŞVERİŞE GERİ DÖN",
|
|
125
|
+
"save_cart": "SEPETİ KAYDET",
|
|
126
|
+
"request_quote": "TEKLİF İSTE",
|
|
127
|
+
"total_price": "Katalog Toplam Fiyatı (Vergi Hariç)",
|
|
128
|
+
"save_cart_modal": {
|
|
129
|
+
"title": "SEPETİ KAYDET",
|
|
130
|
+
"cart_name": "Sepet Adı",
|
|
131
|
+
"save_cart_button": "SEPETİ KAYDET"
|
|
132
|
+
},
|
|
133
|
+
"request_quote_modal": {
|
|
134
|
+
"title": "TEKLİF İSTE",
|
|
135
|
+
"quotation_name": "Teklif Adı",
|
|
136
|
+
"button": "KAYDET"
|
|
137
|
+
},
|
|
138
|
+
"remove_product_modal": {
|
|
139
|
+
"title": "SİL VE FAVORİLERE EKLE",
|
|
140
|
+
"description": "Silmek istediğiniz bu ürünü favorilerinize eklemek ister misiniz?",
|
|
141
|
+
"add_to_favorites_button": "FAVORİLERE EKLE",
|
|
142
|
+
"delete_product_button": "ürünü sil"
|
|
143
|
+
},
|
|
144
|
+
"empty": {
|
|
145
|
+
"title": "Sepetiniz boş, kaydettiğiniz sepetlerle devam etmek ister misiniz?",
|
|
146
|
+
"button": "SEPETİ SEÇ",
|
|
147
|
+
"choose_cart_placeholder": "Bir sepet seçin"
|
|
148
|
+
},
|
|
149
|
+
"table": {
|
|
150
|
+
"basket_qty": "<Qty /> Ürün",
|
|
151
|
+
"delete": "Sil",
|
|
152
|
+
"name_sku": "AD & SKU",
|
|
153
|
+
"price": "FİYAT",
|
|
154
|
+
"quantity": "ADET",
|
|
155
|
+
"store_name": "MAĞAZA ADI",
|
|
156
|
+
"store_qty": "MAĞAZA ADEDİ",
|
|
157
|
+
"subtotal": "ARA TOPLAM"
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
##### File Path: src/settings.js
|
|
163
|
+
|
|
164
|
+
Update destination path for basket route
|
|
165
|
+
|
|
166
|
+
```javascript
|
|
167
|
+
rewrites: [
|
|
168
|
+
{
|
|
169
|
+
source: ROUTES.BASKET,
|
|
170
|
+
destination: '/basket-b2b'
|
|
171
|
+
}
|
|
172
|
+
],
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
Add rewrite url
|
|
176
|
+
|
|
177
|
+
```javascript
|
|
178
|
+
rewrites: [
|
|
179
|
+
...,
|
|
180
|
+
{
|
|
181
|
+
source: ROUTES.ACCOUNT_MY_QUOTATIONS,
|
|
182
|
+
destination: '/account/my-quotations'
|
|
183
|
+
}
|
|
184
|
+
],
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
##### File Path: src/plugins.js
|
|
188
|
+
|
|
189
|
+
Add plugin name
|
|
190
|
+
|
|
191
|
+
```javascript
|
|
192
|
+
module.exports = ["...", "pz-b2b"];
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
##### File Path: src/views/product/product-info.tsx
|
|
196
|
+
|
|
197
|
+
Add imports
|
|
198
|
+
|
|
199
|
+
```javascript
|
|
200
|
+
import { useB2b, StoreModal } from "@akinon/pz-b2b";
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
Add useB2b hook
|
|
204
|
+
|
|
205
|
+
```javascript
|
|
206
|
+
const {
|
|
207
|
+
openSelectStoreModal,
|
|
208
|
+
setOpenSelectStoreModal,
|
|
209
|
+
divisions,
|
|
210
|
+
divisionLoading,
|
|
211
|
+
B2bButton,
|
|
212
|
+
} = useB2b();
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
Add open modal button
|
|
216
|
+
|
|
217
|
+
```javascript
|
|
218
|
+
<B2bButton buttonText={t("product.add_to_cart")} />
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
Add the end in the return
|
|
222
|
+
|
|
223
|
+
```javascript
|
|
224
|
+
{
|
|
225
|
+
!divisionLoading && (
|
|
226
|
+
<StoreModal
|
|
227
|
+
storeData={divisions}
|
|
228
|
+
productPk={data.product.pk}
|
|
229
|
+
open={openSelectStoreModal}
|
|
230
|
+
setOpen={setOpenSelectStoreModal}
|
|
231
|
+
/>
|
|
232
|
+
)
|
|
233
|
+
}
|
|
234
|
+
```
|
package/package.json
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@akinon/pz-b2b",
|
|
3
|
+
"version": "1.18.0",
|
|
4
|
+
"license": "MIT",
|
|
5
|
+
"main": "src/index.ts",
|
|
6
|
+
"peerDependencies": {
|
|
7
|
+
"react": "^18.0.0",
|
|
8
|
+
"react-dom": "^18.0.0"
|
|
9
|
+
},
|
|
10
|
+
"devDependencies": {
|
|
11
|
+
"@types/node": "^18.7.8",
|
|
12
|
+
"@types/react": "^18.0.17",
|
|
13
|
+
"@types/react-dom": "^18.0.6",
|
|
14
|
+
"react": "^18.2.0",
|
|
15
|
+
"react-dom": "^18.2.0",
|
|
16
|
+
"typescript": "^4.7.4"
|
|
17
|
+
}
|
|
18
|
+
}
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useEffect, useState } from "react";
|
|
4
|
+
import { pushCartView } from "../utils/gtm";
|
|
5
|
+
import {
|
|
6
|
+
LoaderSpinner,
|
|
7
|
+
Trans,
|
|
8
|
+
Price,
|
|
9
|
+
Icon,
|
|
10
|
+
Link,
|
|
11
|
+
} from "@akinon/next/components";
|
|
12
|
+
import { useLocalization } from "@akinon/next/hooks";
|
|
13
|
+
import {
|
|
14
|
+
useGetBasketB2bQuery,
|
|
15
|
+
useGetDraftsQuery,
|
|
16
|
+
useLoadBasketMutation,
|
|
17
|
+
} from "@akinon/next/data/client/b2b";
|
|
18
|
+
import {
|
|
19
|
+
BasketItem,
|
|
20
|
+
RequestQuoteModal,
|
|
21
|
+
RemoveProductModal,
|
|
22
|
+
SaveCartModal,
|
|
23
|
+
} from "../views/basket-b2b";
|
|
24
|
+
|
|
25
|
+
export function BasketB2b() {
|
|
26
|
+
const { data: basket, isLoading, isSuccess } = useGetBasketB2bQuery();
|
|
27
|
+
const [loadBasket] = useLoadBasketMutation();
|
|
28
|
+
const { t } = useLocalization();
|
|
29
|
+
const { data: dataDraft } = useGetDraftsQuery();
|
|
30
|
+
const [open, setOpen] = useState(false);
|
|
31
|
+
const [openRequestQuote, setRequestQuote] = useState(false);
|
|
32
|
+
const [openRemoveProduct, setRemoveProduct] = useState(false);
|
|
33
|
+
const [productToBeDeleted, setProductToBeDeleted] = useState(null);
|
|
34
|
+
const [selectedDraft, setSelectedDraft] = useState(null);
|
|
35
|
+
|
|
36
|
+
const handleSubmit = (e) => {
|
|
37
|
+
e.preventDefault();
|
|
38
|
+
|
|
39
|
+
if (!selectedDraft) {
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
loadBasket(selectedDraft);
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
useEffect(() => {
|
|
47
|
+
if (isSuccess) {
|
|
48
|
+
const products = basket?.basket_items.map((basketItem) => ({
|
|
49
|
+
...basketItem.product,
|
|
50
|
+
}));
|
|
51
|
+
pushCartView(products);
|
|
52
|
+
}
|
|
53
|
+
}, [basket, isSuccess]);
|
|
54
|
+
|
|
55
|
+
return (
|
|
56
|
+
<>
|
|
57
|
+
<div className="max-w-screen-xl p-4 flex flex-col text-primary-800 lg:p-8 xl:flex-row xl:mx-auto">
|
|
58
|
+
{isLoading && (
|
|
59
|
+
<div className="flex justify-center w-full">
|
|
60
|
+
<LoaderSpinner />
|
|
61
|
+
</div>
|
|
62
|
+
)}
|
|
63
|
+
|
|
64
|
+
{isSuccess &&
|
|
65
|
+
(basket && basket.basket_items && basket.basket_items.length > 0 ? (
|
|
66
|
+
<div className="w-full">
|
|
67
|
+
<div className="w-full">
|
|
68
|
+
<div className="flex items-center justify-between py-2 lg:py-3">
|
|
69
|
+
<h2 className="text-xl lg:text-2xl font-light">
|
|
70
|
+
{t("basket.b2b.my_cart")}
|
|
71
|
+
</h2>
|
|
72
|
+
|
|
73
|
+
<Link href="/" className="text-xs hover:text-secondary-500">
|
|
74
|
+
{t("basket.b2b.back_to_shopping")}
|
|
75
|
+
</Link>
|
|
76
|
+
</div>
|
|
77
|
+
|
|
78
|
+
<div className="px-5 py-4 border border-b-0 border-gray-200 text-xs text-[#363636]">
|
|
79
|
+
<Trans
|
|
80
|
+
i18nKey="basket.b2b.table.basket_qty"
|
|
81
|
+
components={{
|
|
82
|
+
Qty: <span>{basket.total_quantity}</span>,
|
|
83
|
+
}}
|
|
84
|
+
/>
|
|
85
|
+
</div>
|
|
86
|
+
|
|
87
|
+
<div className="w-full overflow-x-auto">
|
|
88
|
+
<table className="border table-fixed overflow-scroll align-middle lg:w-full">
|
|
89
|
+
<thead className="bg-[#eff1f8]">
|
|
90
|
+
<tr>
|
|
91
|
+
<th className="w-7 border border-[#d5dadb]"></th>
|
|
92
|
+
<th className="text-xs text-[#99999d] font-normal border border-[#d5dadb] py-3">
|
|
93
|
+
<div className="flex items-center justify-center">
|
|
94
|
+
<Icon
|
|
95
|
+
name="b2b-image"
|
|
96
|
+
size={18}
|
|
97
|
+
className="text-[#293245]"
|
|
98
|
+
/>
|
|
99
|
+
</div>
|
|
100
|
+
</th>
|
|
101
|
+
<th className="text-xs text-[#99999d] font-normal border border-[#d5dadb] py-3">
|
|
102
|
+
{t("basket.b2b.table.name_sku")}
|
|
103
|
+
</th>
|
|
104
|
+
<th className="text-xs text-[#99999d] font-normal border border-[#d5dadb] py-3">
|
|
105
|
+
{t("basket.b2b.table.price")}
|
|
106
|
+
</th>
|
|
107
|
+
<th className="text-xs text-[#99999d] font-normal border border-[#d5dadb] py-3">
|
|
108
|
+
{t("basket.b2b.table.quantity")}
|
|
109
|
+
</th>
|
|
110
|
+
<th className="text-xs text-[#99999d] font-normal border border-[#d5dadb] py-3">
|
|
111
|
+
{t("basket.b2b.table.store_name")}
|
|
112
|
+
</th>
|
|
113
|
+
<th className="text-xs text-[#99999d] font-normal border border-[#d5dadb] py-3">
|
|
114
|
+
<div className="flex items-center justify-between pl-5 pr-2.5">
|
|
115
|
+
<span>{t("basket.b2b.table.store_qty")}</span>
|
|
116
|
+
<Icon
|
|
117
|
+
name="b2b-distribute"
|
|
118
|
+
size={18}
|
|
119
|
+
className="text-[#293245]"
|
|
120
|
+
/>
|
|
121
|
+
</div>
|
|
122
|
+
</th>
|
|
123
|
+
<th className="text-xs text-[#99999d] font-normal border border-[#d5dadb] py-3">
|
|
124
|
+
{t("basket.b2b.table.subtotal")}
|
|
125
|
+
</th>
|
|
126
|
+
</tr>
|
|
127
|
+
</thead>
|
|
128
|
+
|
|
129
|
+
<tbody className="[&>*:nth-child(even)]:bg-[#f4f5fb] [&>*:nth-child(odd)>*:nth-child(1)]:bg-[#c7cbd2] [&>*:nth-child(odd)>*:nth-child(1)]>div>button:border-[#c7cbd2] [&>*:nth-child(odd)>*:nth-child(1)]:text-[#747d8f] [&>*:nth-child(even)>*:nth-child(1)]:bg-[#747d8f] [&>*:nth-child(even)>*:nth-child(1)]:text-white">
|
|
130
|
+
{basket.basket_items.map((basketItem, index) => (
|
|
131
|
+
<BasketItem
|
|
132
|
+
basketItem={basketItem}
|
|
133
|
+
key={index}
|
|
134
|
+
setRemoveProduct={setRemoveProduct}
|
|
135
|
+
setProductToBeDeleted={setProductToBeDeleted}
|
|
136
|
+
/>
|
|
137
|
+
))}
|
|
138
|
+
</tbody>
|
|
139
|
+
</table>
|
|
140
|
+
</div>
|
|
141
|
+
</div>
|
|
142
|
+
|
|
143
|
+
<div className="flex justify-end">
|
|
144
|
+
<div className="w-full md:w-[426px]">
|
|
145
|
+
<div className="w-full flex justify-between items-center text-sm text-[#3f4b5c] border border-[#d5dadb] py-6 px-4 mt-7 md:mt-0 md:px-5">
|
|
146
|
+
<span>{t("basket.b2b.total_price")}</span>
|
|
147
|
+
<Price value={basket.total_amount} />
|
|
148
|
+
</div>
|
|
149
|
+
|
|
150
|
+
<button
|
|
151
|
+
onClick={() => setRequestQuote(true)}
|
|
152
|
+
className="mt-4 lg:mt-5 w-full h-10 text-white bg-black border border-black text-sm hover:bg-white hover:text-black transition-colors"
|
|
153
|
+
>
|
|
154
|
+
{t("basket.b2b.request_quote")}
|
|
155
|
+
</button>
|
|
156
|
+
|
|
157
|
+
<button
|
|
158
|
+
onClick={() => setOpen(true)}
|
|
159
|
+
className="mt-4 lg:mt-2.5 w-full h-10 bg-white text-[#2189ff] border border-[#2189ff] text-sm hover:bg-[#2189ff] hover:text-white transition-colors"
|
|
160
|
+
>
|
|
161
|
+
{t("basket.b2b.save_cart")}
|
|
162
|
+
</button>
|
|
163
|
+
</div>
|
|
164
|
+
</div>
|
|
165
|
+
</div>
|
|
166
|
+
) : (
|
|
167
|
+
<div
|
|
168
|
+
className="flex flex-col items-center container max-w-screen-sm py-4 px-4 xs:py-6 xs:px-6 sm:py-8 sm:px-8 lg:max-w-screen-xl
|
|
169
|
+
shadow-[0_0_4px_0_rgba(194,194,194,0.5)]"
|
|
170
|
+
>
|
|
171
|
+
<div className="w-full text-sm text-black-800 text-center my-4 mb-2 sm:text-base">
|
|
172
|
+
<div className="relative mb-[60px]">
|
|
173
|
+
<i className="pz-icon-cart-b2b text-[102px]"></i>
|
|
174
|
+
<span className="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2">
|
|
175
|
+
0
|
|
176
|
+
</span>
|
|
177
|
+
</div>
|
|
178
|
+
<p className="text-xs">{t("basket.b2b.empty.title")}</p>
|
|
179
|
+
</div>
|
|
180
|
+
|
|
181
|
+
<form onSubmit={(e) => handleSubmit(e)}>
|
|
182
|
+
<div className="relative mt-5">
|
|
183
|
+
<i className="pz-icon-chevron-down absolute right-4 top-1/2 -translate-y-1/2 pointer-events-none"></i>
|
|
184
|
+
<select
|
|
185
|
+
onChange={(e) => setSelectedDraft(e.target.value)}
|
|
186
|
+
className="w-[230px] h-[40px] mb-1 border border-[#d4d4d4] px-4 appearance-none text-sm outline-none cursor-pointer"
|
|
187
|
+
>
|
|
188
|
+
<option value="">
|
|
189
|
+
{t("basket.b2b.empty.choose_cart_placeholder")}
|
|
190
|
+
</option>
|
|
191
|
+
{dataDraft &&
|
|
192
|
+
dataDraft.map((draft) => (
|
|
193
|
+
<option key={draft.id} value={draft.id}>
|
|
194
|
+
{draft.name}
|
|
195
|
+
</option>
|
|
196
|
+
))}
|
|
197
|
+
</select>
|
|
198
|
+
</div>
|
|
199
|
+
|
|
200
|
+
<button
|
|
201
|
+
type="submit"
|
|
202
|
+
className="px-10 mt-2 w-full md:w-[230px] h-[40px] text-sm font-medium bg-black text-white cursor-pointer"
|
|
203
|
+
>
|
|
204
|
+
{t("basket.empty.button")}
|
|
205
|
+
</button>
|
|
206
|
+
</form>
|
|
207
|
+
</div>
|
|
208
|
+
))}
|
|
209
|
+
|
|
210
|
+
<SaveCartModal setOpen={setOpen} open={open} />
|
|
211
|
+
<RequestQuoteModal setOpen={setRequestQuote} open={openRequestQuote} />
|
|
212
|
+
<RemoveProductModal
|
|
213
|
+
setOpen={setRemoveProduct}
|
|
214
|
+
open={openRemoveProduct}
|
|
215
|
+
basketItem={productToBeDeleted}
|
|
216
|
+
/>
|
|
217
|
+
</div>
|
|
218
|
+
</>
|
|
219
|
+
);
|
|
220
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import React, { useState, useEffect, useCallback } from 'react';
|
|
4
|
+
import { useLazyGetDivisionsQuery } from '@akinon/next/data/client/b2b';
|
|
5
|
+
import { Button } from '@akinon/next/components';
|
|
6
|
+
import { twMerge } from 'tailwind-merge';
|
|
7
|
+
|
|
8
|
+
export const useB2b = () => {
|
|
9
|
+
const [isVisible, setIsVisible] = useState<boolean>(false);
|
|
10
|
+
const [openSelectStoreModal, setOpenSelectStoreModal] = useState(false);
|
|
11
|
+
const [getDivisions, { data: divisions, isLoading: divisionLoading }] =
|
|
12
|
+
useLazyGetDivisionsQuery();
|
|
13
|
+
|
|
14
|
+
useEffect(() => {
|
|
15
|
+
if (!isVisible) {
|
|
16
|
+
setIsVisible(true);
|
|
17
|
+
}
|
|
18
|
+
}, []);
|
|
19
|
+
|
|
20
|
+
useEffect(() => {
|
|
21
|
+
if (!openSelectStoreModal) {
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
getDivisions();
|
|
26
|
+
}, [openSelectStoreModal, getDivisions]);
|
|
27
|
+
|
|
28
|
+
const B2bButton = useCallback(({buttonText}) => {
|
|
29
|
+
return isVisible ? (
|
|
30
|
+
<Button
|
|
31
|
+
className={twMerge(
|
|
32
|
+
'fixed bottom-0 right-0 w-1/2 h-14 z-[20] flex items-center justify-center fill-primary-foreground hover:fill-primary sm:relative sm:w-full sm:mt-3 sm:font-semibold',
|
|
33
|
+
divisionLoading && 'bg-black/50'
|
|
34
|
+
)}
|
|
35
|
+
onClick={() => setOpenSelectStoreModal(true)}
|
|
36
|
+
>
|
|
37
|
+
<span>{buttonText}</span>
|
|
38
|
+
</Button>
|
|
39
|
+
) : null;
|
|
40
|
+
}, [isVisible, divisionLoading]);
|
|
41
|
+
|
|
42
|
+
return {
|
|
43
|
+
openSelectStoreModal,
|
|
44
|
+
setOpenSelectStoreModal,
|
|
45
|
+
divisions,
|
|
46
|
+
divisionLoading,
|
|
47
|
+
B2bButton
|
|
48
|
+
};
|
|
49
|
+
};
|
|
50
|
+
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { useGetQuotationsQuery } from '@akinon/next/data/client/account';
|
|
4
|
+
import { Quotations } from '@akinon/next/types';
|
|
5
|
+
import { LoaderSpinner, Link, Pagination } from '@akinon/next/components';
|
|
6
|
+
import { Tabs } from '../views/my-quotations/tabs';
|
|
7
|
+
import clsx from 'clsx';
|
|
8
|
+
import React, { useEffect, useState } from 'react';
|
|
9
|
+
import { twMerge } from 'tailwind-merge';
|
|
10
|
+
import { useSearchParams } from 'next/navigation';
|
|
11
|
+
|
|
12
|
+
export function AccountMyQuotations() {
|
|
13
|
+
const searchParams = useSearchParams();
|
|
14
|
+
const { data: quotations, isLoading } = useGetQuotationsQuery();
|
|
15
|
+
const [activeTab, setActiveTab] = useState('all');
|
|
16
|
+
const [filteredQuotations, setFilteredQuotations] = useState<Quotations[]>(
|
|
17
|
+
[]
|
|
18
|
+
);
|
|
19
|
+
|
|
20
|
+
const thClasses =
|
|
21
|
+
'px-6 py-3 bg-[#f1f3f9] text-xs text-[#99999d] border-r border-[#c8daec] font-normal text-left';
|
|
22
|
+
const thTitles = ['NAME', 'DATE', 'STATUS', 'NUMBER', 'ACTIONS'];
|
|
23
|
+
|
|
24
|
+
const filterStatus = (quotation: Quotations, activeStatus: string) => {
|
|
25
|
+
if (activeStatus === 'all') {
|
|
26
|
+
return quotation;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
return quotation.status === activeStatus;
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
useEffect(() => {
|
|
33
|
+
if (!quotations) {
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const _filteredQuotations = quotations.results.filter((quotation) =>
|
|
38
|
+
filterStatus(quotation, activeTab)
|
|
39
|
+
);
|
|
40
|
+
|
|
41
|
+
setFilteredQuotations(_filteredQuotations);
|
|
42
|
+
}, [quotations, activeTab]);
|
|
43
|
+
|
|
44
|
+
if (isLoading) {
|
|
45
|
+
return <LoaderSpinner />;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return (
|
|
49
|
+
<div className="flex-1">
|
|
50
|
+
<Tabs
|
|
51
|
+
setActiveTab={setActiveTab}
|
|
52
|
+
activeTab={activeTab}
|
|
53
|
+
quotations={quotations}
|
|
54
|
+
/>
|
|
55
|
+
|
|
56
|
+
<div className="border border-[#d5dadb] mb-10 overflow-x-auto lg:overflow-hidden">
|
|
57
|
+
<p className="px-6 py-5 text-[#363636]">
|
|
58
|
+
<span hidden={activeTab !== 'all'}>All Quotas</span>
|
|
59
|
+
<span hidden={activeTab !== 'pending'}>Pending Quotas</span>
|
|
60
|
+
<span hidden={activeTab !== 'rejected'}>Rejected Quotas</span>
|
|
61
|
+
<span hidden={activeTab !== 'approved'}>Approved Quotas</span>
|
|
62
|
+
<span hidden={activeTab !== 'sending_approval_request'}>
|
|
63
|
+
Requires My Approval
|
|
64
|
+
</span>
|
|
65
|
+
(<span>{filteredQuotations?.length}</span>)
|
|
66
|
+
</p>
|
|
67
|
+
|
|
68
|
+
<table className="w-full">
|
|
69
|
+
<thead>
|
|
70
|
+
<tr>
|
|
71
|
+
{thTitles.map((title, i) => (
|
|
72
|
+
<th
|
|
73
|
+
key={i}
|
|
74
|
+
className={twMerge(
|
|
75
|
+
thClasses,
|
|
76
|
+
i + 1 === thTitles.length && 'border-r-0'
|
|
77
|
+
)}
|
|
78
|
+
>
|
|
79
|
+
{title}
|
|
80
|
+
</th>
|
|
81
|
+
))}
|
|
82
|
+
</tr>
|
|
83
|
+
</thead>
|
|
84
|
+
<tbody>
|
|
85
|
+
{filteredQuotations?.map((quotation) => (
|
|
86
|
+
<tr
|
|
87
|
+
key={quotation.id}
|
|
88
|
+
className="h-[64px] lg:h-[75px] even:bg-[#fbfcfd]"
|
|
89
|
+
>
|
|
90
|
+
<td className="text-sm text-[#3f4b5c] px-6 py-2 whitespace-nowrap border-r border-b border-[#eaeff0] h-[64px] lg:h-[75px]">
|
|
91
|
+
{quotation.name}
|
|
92
|
+
</td>
|
|
93
|
+
<td className="text-sm text-[#3f4b5c] px-6 py-2 whitespace-nowrap border-r border-b border-[#eaeff0] h-[64px] lg:h-[75px]">
|
|
94
|
+
{quotation.created_at}
|
|
95
|
+
</td>
|
|
96
|
+
<td className="px-6 py-5 flex whitespace-normal border-r border-b border-[#eaeff0] h-[64px] lg:h-[75px] text-sm text-[#3f4b5c]">
|
|
97
|
+
<button
|
|
98
|
+
className={clsx(
|
|
99
|
+
'text-xs font-medium min-w-[77px] w-[170px] text-center rounded-md m-auto p-1 leading-4 --w-auto',
|
|
100
|
+
quotation.status === 'approved' &&
|
|
101
|
+
'bg-[#e3f6cc] border border-[#71d200] text-[#67b50c]',
|
|
102
|
+
quotation.status === 'pending' &&
|
|
103
|
+
'bg-[#fce0ce] border border-[#ff7041] text-[#ff7041]',
|
|
104
|
+
quotation.status === 'rejected' &&
|
|
105
|
+
'bg-[#f9b9b9] border border-[#f05050] text-[#e33030]',
|
|
106
|
+
quotation.status === 'sending_approval_request' &&
|
|
107
|
+
'bg-[#eae7f4] border border-[#9387c7] text-[#7667b6]'
|
|
108
|
+
)}
|
|
109
|
+
>
|
|
110
|
+
{quotation.status}
|
|
111
|
+
</button>
|
|
112
|
+
</td>
|
|
113
|
+
<td className="text-center border-r border-b border-[#eaeff0] h-[64px] lg:h-[75px] text-sm text-[#3f4b5c]">
|
|
114
|
+
<Link href="#" className="text-sm text-[#1890ff]">
|
|
115
|
+
{quotation.number}
|
|
116
|
+
</Link>
|
|
117
|
+
</td>
|
|
118
|
+
<td className="px-5 py-3 border-b border-[#eaeff0] h-[64px] lg:h-[75px] text-sm text-[#3f4b5c]"></td>
|
|
119
|
+
</tr>
|
|
120
|
+
))}
|
|
121
|
+
</tbody>
|
|
122
|
+
</table>
|
|
123
|
+
</div>
|
|
124
|
+
|
|
125
|
+
{filteredQuotations?.length > 0 && (
|
|
126
|
+
<Pagination
|
|
127
|
+
total={filteredQuotations.length}
|
|
128
|
+
limit={20}
|
|
129
|
+
numberOfPages={Math.ceil(filteredQuotations.length / 20)}
|
|
130
|
+
containerClassName="lg:justify-end"
|
|
131
|
+
currentPage={Number(searchParams.get('page')) || 1}
|
|
132
|
+
/>
|
|
133
|
+
)}
|
|
134
|
+
</div>
|
|
135
|
+
);
|
|
136
|
+
}
|