@akinon/pz-b2b 1.18.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
}
|