@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 ADDED
@@ -0,0 +1,7 @@
1
+ # @akinon/pz-b2b
2
+
3
+ ## 1.18.0
4
+
5
+ ### Minor Changes
6
+
7
+ - db017b2c: ZERO-2377: Add B2B plugin
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,6 @@
1
+ export * from './my-quotations';
2
+ export * from './basket-b2b';
3
+ export * from './views/my-quotations/tabs';
4
+ export * from './views/basket-b2b';
5
+ export * from './views/product/store-modal';
6
+ export * from './hook/use-b2b';
@@ -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
+ }