@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 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
+ }