@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/src/utils/gtm.ts
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
'use client';
|
2
|
+
|
3
|
+
import { GTMEvent, Product } from '@akinon/next/types';
|
4
|
+
|
5
|
+
export const pushCartView = (
|
6
|
+
products: Pick<Product, 'name' | 'pk' | 'price'>[]
|
7
|
+
) => {
|
8
|
+
pushEvent({
|
9
|
+
Action: 'Checkout',
|
10
|
+
Label: 'Sepet',
|
11
|
+
ecommerce: {
|
12
|
+
products: products?.map((product) => ({
|
13
|
+
name: product.name,
|
14
|
+
id: product.pk,
|
15
|
+
price: product.price
|
16
|
+
}))
|
17
|
+
},
|
18
|
+
event: 'eeCheckout'
|
19
|
+
});
|
20
|
+
};
|
21
|
+
|
22
|
+
export const pushEvent = (event: GTMEvent) => {
|
23
|
+
const { Category = 'Enhanced Ecommerce', Value = 0, ...rest } = event;
|
24
|
+
|
25
|
+
window.dataLayer.push({ Category, Value, ...rest });
|
26
|
+
};
|
@@ -0,0 +1,144 @@
|
|
1
|
+
'use client';
|
2
|
+
|
3
|
+
import { useLocalization } from '@akinon/next/hooks';
|
4
|
+
import { Image, Price, Input, Icon } from '@akinon/next/components';
|
5
|
+
import { useUpdateProductMutation } from '@akinon/next/data/client/b2b';
|
6
|
+
import { BasketItemType } from '@akinon/next/types';
|
7
|
+
import React, { useState } from 'react';
|
8
|
+
|
9
|
+
interface Props {
|
10
|
+
basketItem: BasketItemType;
|
11
|
+
setProductToBeDeleted: (item: BasketItemType) => void;
|
12
|
+
setRemoveProduct: (open: boolean) => void;
|
13
|
+
}
|
14
|
+
|
15
|
+
export const BasketItem = (props: Props) => {
|
16
|
+
const { t } = useLocalization();
|
17
|
+
const { basketItem, setRemoveProduct, setProductToBeDeleted } = props;
|
18
|
+
const [updateProduct] = useUpdateProductMutation();
|
19
|
+
const [divisionErrors, setDivisionErrors] = useState([]);
|
20
|
+
|
21
|
+
const handleChange = (divisionID, e, index) => {
|
22
|
+
if (parseInt(e.target.value) < 1) {
|
23
|
+
setRemoveProduct(true);
|
24
|
+
setProductToBeDeleted(basketItem);
|
25
|
+
} else {
|
26
|
+
updateProduct({
|
27
|
+
product_remote_id: basketItem.product_remote_id,
|
28
|
+
division: divisionID,
|
29
|
+
quantity: parseInt(e.target.value)
|
30
|
+
}).then((res) => {
|
31
|
+
if ('error' in res && 'data' in res.error) {
|
32
|
+
setDivisionErrors((prevErrors) => {
|
33
|
+
const newErrors = [...prevErrors];
|
34
|
+
newErrors[index] = (res.error as any).data.quantity || [];
|
35
|
+
return newErrors;
|
36
|
+
});
|
37
|
+
} else {
|
38
|
+
setDivisionErrors((prevErrors) => {
|
39
|
+
const newErrors = [...prevErrors];
|
40
|
+
newErrors[index] = [];
|
41
|
+
return newErrors;
|
42
|
+
});
|
43
|
+
}
|
44
|
+
});
|
45
|
+
}
|
46
|
+
};
|
47
|
+
|
48
|
+
return (
|
49
|
+
<>
|
50
|
+
<tr>
|
51
|
+
<td>
|
52
|
+
<div className='h-[183px] w-full flex flex-col justify-between items-center text-xs'>
|
53
|
+
<div className='w-full flex items-center justify-center py-3 mb-3 border-b'>
|
54
|
+
<Icon name='chevron-down' size={10} />
|
55
|
+
</div>
|
56
|
+
|
57
|
+
<div className='h-full overflow-hidden'
|
58
|
+
style={{ writingMode: 'vertical-rl', textOrientation: 'mixed', display: '-webkit-box', lineClamp: 1, WebkitLineClamp: 1, WebkitBoxOrient: 'vertical' }}
|
59
|
+
>
|
60
|
+
{basketItem.product.name}{basketItem.product.name}{basketItem.product.name}
|
61
|
+
</div>
|
62
|
+
|
63
|
+
<button
|
64
|
+
onClick={() => { setProductToBeDeleted(basketItem), setRemoveProduct(true) }}
|
65
|
+
className='py-1.5 border-t w-full flex items-center justify-center'
|
66
|
+
style={{ writingMode: 'vertical-rl', textOrientation: 'mixed' }}
|
67
|
+
>
|
68
|
+
{t('basket.b2b.table.delete')}
|
69
|
+
</button>
|
70
|
+
</div>
|
71
|
+
</td>
|
72
|
+
|
73
|
+
<td className='border text-center'>
|
74
|
+
<div className='min-w-[80px] w-full h-full flex items-center justify-center'>
|
75
|
+
<Image
|
76
|
+
src={basketItem.product.product_image}
|
77
|
+
alt={basketItem.product.name}
|
78
|
+
width={50}
|
79
|
+
height={65}
|
80
|
+
/>
|
81
|
+
</div>
|
82
|
+
</td>
|
83
|
+
|
84
|
+
<td className='min-w-[181px] text-sm border text-[#3f4b5c] leading-normal'>
|
85
|
+
<div className='px-4'>
|
86
|
+
<div className='break-words'>{basketItem.product.name}</div>
|
87
|
+
<div className='break-words'>SKU: {basketItem.product.sku}</div>
|
88
|
+
</div>
|
89
|
+
</td>
|
90
|
+
|
91
|
+
<td className='text-sm border text-[#3f4b5c] leading-normal text-right px-3'>
|
92
|
+
<Price value={basketItem.price} className='w-max block' />
|
93
|
+
</td>
|
94
|
+
|
95
|
+
<td className='text-sm border text-[#3f4b5c] leading-normal text-center'>
|
96
|
+
<div className='min-w-[90px]'>
|
97
|
+
{basketItem.quantity}
|
98
|
+
</div>
|
99
|
+
</td>
|
100
|
+
|
101
|
+
<td className='border'>
|
102
|
+
<div className='min-w-[140px]'>
|
103
|
+
{basketItem.divisions.map((division) => (
|
104
|
+
<div className='py-5 px-1 border-b last:border-0 text-center flex items-center justify-center gap-x-1' key={division.name}>
|
105
|
+
<Icon name='b2b-info' size={18} className='text-[#293245]' />
|
106
|
+
|
107
|
+
<div className='text-sm underline text-[#3f4b5c] overflow-hidden' style={{ display: '-webkit-box', lineClamp: 1, WebkitLineClamp: 1, WebkitBoxOrient: 'vertical' }}>
|
108
|
+
{division.name}
|
109
|
+
</div>
|
110
|
+
</div>
|
111
|
+
))}
|
112
|
+
</div>
|
113
|
+
</td>
|
114
|
+
|
115
|
+
<td className='border'>
|
116
|
+
<div className='min-w-[120px]'>
|
117
|
+
{basketItem.divisions.map((division, index) => (
|
118
|
+
<div className='relative py-5 border-b last:border-0 text-center flex flex-col gap-y-1 items-center justify-center' key={`division-${division.id}`}>
|
119
|
+
<Input
|
120
|
+
className='appearance-none outline-none w-20 h-6 px-2 py-0 border border-[#c8daec] rounded text-right'
|
121
|
+
type="number"
|
122
|
+
value={division.quantity}
|
123
|
+
onChange={(e) => handleChange(division.id, e, index)}
|
124
|
+
/>
|
125
|
+
|
126
|
+
{divisionErrors[index] && (
|
127
|
+
<div className="text-xs text-error mb-2 absolute -bottom-1">
|
128
|
+
{divisionErrors[index].map((err) => (
|
129
|
+
<div key={err}>{err}</div>
|
130
|
+
))}
|
131
|
+
</div>
|
132
|
+
)}
|
133
|
+
</div>
|
134
|
+
))}
|
135
|
+
</div>
|
136
|
+
</td>
|
137
|
+
|
138
|
+
<td className='text-sm border text-[#3f4b5c] leading-normal text-right px-6'>
|
139
|
+
<Price value={basketItem.total_amount} className='w-max block' />
|
140
|
+
</td>
|
141
|
+
</tr>
|
142
|
+
</>
|
143
|
+
);
|
144
|
+
};
|
@@ -0,0 +1,109 @@
|
|
1
|
+
'use client';
|
2
|
+
|
3
|
+
import { useDeleteProductMutation } from '@akinon/next/data/client/b2b';
|
4
|
+
import { Image } from '@akinon/next/components';
|
5
|
+
import { useLocalization } from '@akinon/next/hooks';
|
6
|
+
import React, { useEffect } from 'react';
|
7
|
+
import { BasketItemType } from '@akinon/next/types';
|
8
|
+
import { useAddFavoriteMutation } from '@akinon/next/data/client/wishlist';
|
9
|
+
import clsx from 'clsx';
|
10
|
+
|
11
|
+
export const RemoveProductModal = ({
|
12
|
+
open,
|
13
|
+
setOpen,
|
14
|
+
basketItem
|
15
|
+
}: {
|
16
|
+
open: boolean;
|
17
|
+
setOpen: (open: boolean) => void;
|
18
|
+
basketItem: BasketItemType
|
19
|
+
}) => {
|
20
|
+
const { t } = useLocalization();
|
21
|
+
const [addFavorite] = useAddFavoriteMutation();
|
22
|
+
const [deleteProduct, { isLoading, isSuccess }] = useDeleteProductMutation();
|
23
|
+
|
24
|
+
const handleClick = (addToFavorite:boolean) => {
|
25
|
+
if(addToFavorite) {
|
26
|
+
addFavorite(basketItem.product_remote_id)
|
27
|
+
}
|
28
|
+
|
29
|
+
deleteProduct({ product_remote_id: basketItem.product_remote_id });
|
30
|
+
};
|
31
|
+
|
32
|
+
useEffect(() => {
|
33
|
+
if (isSuccess) {
|
34
|
+
setOpen(false);
|
35
|
+
}
|
36
|
+
}, [isSuccess]);
|
37
|
+
|
38
|
+
if (!open) return null;
|
39
|
+
|
40
|
+
return (
|
41
|
+
<div className="before:w-full before:h-screen before:content-[''] before:bg-black/60 before:z-40 before:fixed before:inset-0">
|
42
|
+
<div className="w-11/12 max-w-[344px] md:w-[370px] left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 z-50 fixed bg-white">
|
43
|
+
<header className="relative border-b border-[#d5dadb]">
|
44
|
+
<h3 className="text-left px-[30px] py-4 font-medium text-[17px]">
|
45
|
+
{t('basket.b2b.remove_product_modal.title')}
|
46
|
+
</h3>
|
47
|
+
|
48
|
+
<button
|
49
|
+
className="absolute right-[30px] top-5"
|
50
|
+
onClick={() => setOpen(false)}
|
51
|
+
>
|
52
|
+
<i className="pz-icon-close"></i>
|
53
|
+
</button>
|
54
|
+
</header>
|
55
|
+
|
56
|
+
<p className="text-left px-[30px] py-4 text-sm">
|
57
|
+
{t('basket.b2b.remove_product_modal.description')}
|
58
|
+
</p>
|
59
|
+
|
60
|
+
<div className="px-[30px] pb-5">
|
61
|
+
<div className='flex items-center gap-x-3'>
|
62
|
+
<div>
|
63
|
+
<Image src={basketItem.product.product_image} width={72} height={93} alt={basketItem.product.name} />
|
64
|
+
</div>
|
65
|
+
<div className='text-[#3f4b5c] text-sm'>
|
66
|
+
<p>{basketItem.product.name}</p>
|
67
|
+
<p>SKU: {basketItem.product.sku}</p>
|
68
|
+
</div>
|
69
|
+
</div>
|
70
|
+
|
71
|
+
<div className='flex gap-y-3 flex-col items-center'>
|
72
|
+
<button
|
73
|
+
type="submit"
|
74
|
+
className="h-8 bg-black text-white font-medium text-sm w-full"
|
75
|
+
disabled={isLoading}
|
76
|
+
onClick={() => handleClick(true)}
|
77
|
+
>
|
78
|
+
{isLoading ? (
|
79
|
+
<div className="w-full h-full flex justify-center items-center">
|
80
|
+
<div className="w-4 h-4 border-2 border-gray-600 border-t-transparent rounded-full animate-spin" />
|
81
|
+
</div>
|
82
|
+
) : (
|
83
|
+
t('basket.b2b.remove_product_modal.add_to_favorites_button')
|
84
|
+
)}
|
85
|
+
</button>
|
86
|
+
|
87
|
+
<button
|
88
|
+
type="submit"
|
89
|
+
className={clsx(
|
90
|
+
'h-auto bg-transparent text-[#181818] border-[#181818] text-xs w-auto',
|
91
|
+
!isLoading && 'border-b'
|
92
|
+
)}
|
93
|
+
disabled={isLoading}
|
94
|
+
onClick={() => handleClick(false)}
|
95
|
+
>
|
96
|
+
{isLoading ? (
|
97
|
+
<div className="w-full h-full flex justify-center items-center">
|
98
|
+
<div className="w-4 h-4 border-2 border-gray-600 border-t-transparent rounded-full animate-spin" />
|
99
|
+
</div>
|
100
|
+
) : (
|
101
|
+
t('basket.b2b.remove_product_modal.delete_product_button')
|
102
|
+
)}
|
103
|
+
</button>
|
104
|
+
</div>
|
105
|
+
</div>
|
106
|
+
</div>
|
107
|
+
</div>
|
108
|
+
);
|
109
|
+
}
|
@@ -0,0 +1,97 @@
|
|
1
|
+
'use client';
|
2
|
+
|
3
|
+
import { useCreateQuotationMutation } from '@akinon/next/data/client/b2b';
|
4
|
+
import { useLocalization } from '@akinon/next/hooks';
|
5
|
+
import React, { useEffect, useState } from 'react';
|
6
|
+
|
7
|
+
export const RequestQuoteModal = ({
|
8
|
+
open,
|
9
|
+
setOpen
|
10
|
+
}: {
|
11
|
+
open: boolean;
|
12
|
+
setOpen: (open: boolean) => void;
|
13
|
+
}) => {
|
14
|
+
const { t } = useLocalization();
|
15
|
+
const [createQuotation, { isLoading, isSuccess, error, isError }] = useCreateQuotationMutation();
|
16
|
+
const [errors, setErrors] = useState([]);
|
17
|
+
const [cartName, setCartName] = useState<string>('');
|
18
|
+
|
19
|
+
const handleSubmit = (e) => {
|
20
|
+
e.preventDefault();
|
21
|
+
|
22
|
+
if (!cartName) {
|
23
|
+
return;
|
24
|
+
}
|
25
|
+
|
26
|
+
createQuotation({ name: cartName }).then((res) => {
|
27
|
+
if ('error' in res && 'data' in res.error) {
|
28
|
+
setErrors((res.error.data as any).non_field_errors || []);
|
29
|
+
}
|
30
|
+
})
|
31
|
+
};
|
32
|
+
|
33
|
+
useEffect(() => {
|
34
|
+
if (isSuccess) {
|
35
|
+
setOpen(false);
|
36
|
+
}
|
37
|
+
}, [isSuccess]);
|
38
|
+
|
39
|
+
if (!open) return null;
|
40
|
+
|
41
|
+
return (
|
42
|
+
<div className="before:w-full before:h-screen before:content-[''] before:bg-black/60 before:z-40 before:fixed before:inset-0">
|
43
|
+
<div className="w-11/12 max-w-[344px] md:w-[370px] left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 z-50 fixed bg-white">
|
44
|
+
<header className="relative border-b border-[#d5dadb]">
|
45
|
+
<h3 className="text-left px-[30px] py-4 font-medium text-lg">
|
46
|
+
{t('basket.b2b.request_quote_modal.title')}
|
47
|
+
</h3>
|
48
|
+
|
49
|
+
<button
|
50
|
+
className="absolute right-[30px] top-5"
|
51
|
+
onClick={() => setOpen(false)}
|
52
|
+
>
|
53
|
+
<i className="pz-icon-close"></i>
|
54
|
+
</button>
|
55
|
+
</header>
|
56
|
+
|
57
|
+
<div className="p-[30px]">
|
58
|
+
<form onSubmit={(e) => handleSubmit(e)}>
|
59
|
+
<label htmlFor="cart-name-input" className="text-xs block mb-2">
|
60
|
+
{t('basket.b2b.request_quote_modal.quotation_name')}
|
61
|
+
</label>
|
62
|
+
|
63
|
+
<input
|
64
|
+
id="cart-name-input"
|
65
|
+
name="name"
|
66
|
+
type="text"
|
67
|
+
className="h-8 border border-[#eeeeee] text-sm mb-2.5 outline-none px-2 w-full"
|
68
|
+
onChange={(e) => setCartName(e.target.value)}
|
69
|
+
/>
|
70
|
+
|
71
|
+
{isError && (
|
72
|
+
<div className="text-xs text-error mb-2">
|
73
|
+
{errors.map((err) => (
|
74
|
+
<div key={err}>{err}</div>
|
75
|
+
))}
|
76
|
+
</div>
|
77
|
+
)}
|
78
|
+
|
79
|
+
<button
|
80
|
+
type="submit"
|
81
|
+
className="h-8 bg-black text-white font-medium text-sm w-full"
|
82
|
+
disabled={isLoading}
|
83
|
+
>
|
84
|
+
{isLoading ? (
|
85
|
+
<div className="w-full h-full flex justify-center items-center">
|
86
|
+
<div className="w-4 h-4 border-2 border-gray-600 border-t-transparent rounded-full animate-spin" />
|
87
|
+
</div>
|
88
|
+
) : (
|
89
|
+
t('basket.b2b.request_quote_modal.button')
|
90
|
+
)}
|
91
|
+
</button>
|
92
|
+
</form>
|
93
|
+
</div>
|
94
|
+
</div>
|
95
|
+
</div>
|
96
|
+
);
|
97
|
+
}
|
@@ -0,0 +1,80 @@
|
|
1
|
+
'use client';
|
2
|
+
|
3
|
+
import { useSaveBasketMutation } from '@akinon/next/data/client/b2b';
|
4
|
+
import { useLocalization } from '@akinon/next/hooks';
|
5
|
+
import React, { useEffect, useState } from 'react';
|
6
|
+
|
7
|
+
export const SaveCartModal = ({
|
8
|
+
open,
|
9
|
+
setOpen
|
10
|
+
}: {
|
11
|
+
open: boolean;
|
12
|
+
setOpen: (open: boolean) => void;
|
13
|
+
}) => {
|
14
|
+
const { t } = useLocalization();
|
15
|
+
const [cartName, setCartName] = useState<string>('');
|
16
|
+
const [saveBasket, { isLoading, isSuccess }] = useSaveBasketMutation();
|
17
|
+
|
18
|
+
const handleSubmit = (e) => {
|
19
|
+
e.preventDefault();
|
20
|
+
|
21
|
+
if (!cartName) {
|
22
|
+
return;
|
23
|
+
}
|
24
|
+
|
25
|
+
saveBasket({ name: cartName });
|
26
|
+
};
|
27
|
+
|
28
|
+
useEffect(() => {
|
29
|
+
if (isSuccess) {
|
30
|
+
setOpen(false);
|
31
|
+
}
|
32
|
+
}, [isSuccess]);
|
33
|
+
|
34
|
+
if (!open) return null;
|
35
|
+
|
36
|
+
return (
|
37
|
+
<div className="before:w-full before:h-screen before:content-[''] before:bg-black/60 before:z-40 before:fixed before:inset-0">
|
38
|
+
<div className="w-11/12 max-w-[344px] md:w-[370px] left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 z-50 fixed bg-white">
|
39
|
+
<header className="relative border-b border-[#d5dadb]">
|
40
|
+
<h3 className="text-left px-[30px] py-4 font-medium text-lg">
|
41
|
+
{t('basket.b2b.save_cart_modal.title')}
|
42
|
+
</h3>
|
43
|
+
<button
|
44
|
+
className="absolute right-[30px] top-5"
|
45
|
+
onClick={() => setOpen(false)}
|
46
|
+
>
|
47
|
+
<i className="pz-icon-close"></i>
|
48
|
+
</button>
|
49
|
+
</header>
|
50
|
+
<div className="p-[30px]">
|
51
|
+
<form onSubmit={(e) => handleSubmit(e)}>
|
52
|
+
<label htmlFor="cart-name-input" className="text-xs block mb-2">
|
53
|
+
{t('basket.b2b.save_cart_modal.cart_name')}
|
54
|
+
</label>
|
55
|
+
<input
|
56
|
+
id="cart-name-input"
|
57
|
+
name="name"
|
58
|
+
type="text"
|
59
|
+
className="h-8 border border-[#eeeeee] text-sm mb-2.5 outline-none px-2 w-full"
|
60
|
+
onChange={(e) => setCartName(e.target.value)}
|
61
|
+
/>
|
62
|
+
<button
|
63
|
+
type="submit"
|
64
|
+
className="h-8 bg-black text-white font-medium text-sm w-full"
|
65
|
+
disabled={isLoading}
|
66
|
+
>
|
67
|
+
{isLoading ? (
|
68
|
+
<div className="w-full h-full flex justify-center items-center">
|
69
|
+
<div className="w-4 h-4 border-2 border-gray-600 border-t-transparent rounded-full animate-spin" />
|
70
|
+
</div>
|
71
|
+
) : (
|
72
|
+
t('basket.b2b.save_cart_modal.save_cart_button')
|
73
|
+
)}
|
74
|
+
</button>
|
75
|
+
</form>
|
76
|
+
</div>
|
77
|
+
</div>
|
78
|
+
</div>
|
79
|
+
);
|
80
|
+
}
|
@@ -0,0 +1,184 @@
|
|
1
|
+
'use client';
|
2
|
+
|
3
|
+
import { GetQuotationsResponse } from '@akinon/next/data/client/account';
|
4
|
+
import clsx from 'clsx';
|
5
|
+
import React, { Dispatch, SetStateAction, useEffect, useState } from 'react';
|
6
|
+
import { twMerge } from 'tailwind-merge';
|
7
|
+
|
8
|
+
type TabsType = {
|
9
|
+
quotations: GetQuotationsResponse | undefined;
|
10
|
+
setActiveTab: Dispatch<SetStateAction<string>>;
|
11
|
+
activeTab: string;
|
12
|
+
};
|
13
|
+
|
14
|
+
const initialTabCountsState = {
|
15
|
+
pendingCount: 0,
|
16
|
+
rejectedCount: 0,
|
17
|
+
approvedCount: 0,
|
18
|
+
sendingApprovalRequestCount: 0
|
19
|
+
};
|
20
|
+
|
21
|
+
export function Tabs(props: TabsType) {
|
22
|
+
const quotations = props.quotations?.results;
|
23
|
+
const setActiveTab = props.setActiveTab;
|
24
|
+
const activeTab = props.activeTab;
|
25
|
+
const [tabCounts, setTabCounts] = useState(initialTabCountsState);
|
26
|
+
|
27
|
+
useEffect(() => {
|
28
|
+
setTabCounts(initialTabCountsState);
|
29
|
+
|
30
|
+
quotations?.forEach((quotation) => {
|
31
|
+
if (quotation.status === 'pending') {
|
32
|
+
setTabCounts((prevCount) => ({
|
33
|
+
...prevCount,
|
34
|
+
pendingCount: prevCount.pendingCount + 1
|
35
|
+
}));
|
36
|
+
} else if (quotation.status === 'rejected') {
|
37
|
+
setTabCounts((prevCount) => ({
|
38
|
+
...prevCount,
|
39
|
+
rejectedCount: prevCount.rejectedCount + 1
|
40
|
+
}));
|
41
|
+
} else if (quotation.status === 'approved') {
|
42
|
+
setTabCounts((prevCount) => ({
|
43
|
+
...prevCount,
|
44
|
+
approvedCount: prevCount.approvedCount + 1
|
45
|
+
}));
|
46
|
+
} else if (quotation.status === 'sending_approval_request') {
|
47
|
+
setTabCounts((prevCount) => ({
|
48
|
+
...prevCount,
|
49
|
+
sendingApprovalRequestCount: prevCount.sendingApprovalRequestCount + 1
|
50
|
+
}));
|
51
|
+
}
|
52
|
+
});
|
53
|
+
}, [quotations]);
|
54
|
+
|
55
|
+
return (
|
56
|
+
<div className="mb-9 mt-4 lg:mt-0">
|
57
|
+
<div className="relative flex justify-start lg:justify-between space-x-3 lg:space-x-4">
|
58
|
+
<div
|
59
|
+
className="flex flex-col items-center space-y-1.5 transition-all cursor-pointer"
|
60
|
+
onClick={() => setActiveTab('all')}
|
61
|
+
>
|
62
|
+
<span
|
63
|
+
className={twMerge(
|
64
|
+
clsx(
|
65
|
+
'flex items-center text-xl justify-center w-9 h-9 rounded-full p-1.5 border border-[#eeeded] text-black bg-white'
|
66
|
+
),
|
67
|
+
activeTab === 'all' && 'bg-[#71d200] text-white'
|
68
|
+
)}
|
69
|
+
>
|
70
|
+
☷
|
71
|
+
</span>
|
72
|
+
<span
|
73
|
+
className={clsx(
|
74
|
+
'text-xs text-center text-[#363636] w-[50px] lg:w-[110px]',
|
75
|
+
activeTab === 'all' && 'font-bold'
|
76
|
+
)}
|
77
|
+
>
|
78
|
+
All Quotes (<span>{quotations?.length}</span>)
|
79
|
+
</span>
|
80
|
+
</div>
|
81
|
+
|
82
|
+
<div
|
83
|
+
className="flex flex-col items-center space-y-1.5 transition-all cursor-pointer"
|
84
|
+
onClick={() => setActiveTab('pending')}
|
85
|
+
>
|
86
|
+
<span
|
87
|
+
className={twMerge(
|
88
|
+
clsx(
|
89
|
+
'flex items-center text-xl justify-center w-9 h-9 rounded-full p-1.5 border border-[#eeeded] text-black bg-white'
|
90
|
+
),
|
91
|
+
activeTab === 'pending' && 'bg-[#71d200] text-white'
|
92
|
+
)}
|
93
|
+
>
|
94
|
+
☷
|
95
|
+
</span>
|
96
|
+
<span
|
97
|
+
className={clsx(
|
98
|
+
'text-xs text-center text-[#363636] w-[50px] lg:w-[110px]',
|
99
|
+
activeTab === 'pending' && 'font-bold'
|
100
|
+
)}
|
101
|
+
>
|
102
|
+
Pending (<span>{tabCounts.pendingCount}</span>)
|
103
|
+
</span>
|
104
|
+
</div>
|
105
|
+
|
106
|
+
<div
|
107
|
+
className="flex flex-col items-center space-y-1.5 transition-all cursor-pointer"
|
108
|
+
onClick={() => setActiveTab('rejected')}
|
109
|
+
>
|
110
|
+
<span
|
111
|
+
className={twMerge(
|
112
|
+
clsx(
|
113
|
+
'flex items-center text-xl justify-center w-9 h-9 rounded-full p-1.5 border border-[#eeeded] text-black bg-white'
|
114
|
+
),
|
115
|
+
activeTab === 'rejected' && 'bg-[#71d200] text-white'
|
116
|
+
)}
|
117
|
+
>
|
118
|
+
☷
|
119
|
+
</span>
|
120
|
+
<span
|
121
|
+
className={clsx(
|
122
|
+
'text-xs text-center text-[#363636] w-[50px] lg:w-[110px]',
|
123
|
+
activeTab === 'rejected' && 'font-bold'
|
124
|
+
)}
|
125
|
+
>
|
126
|
+
Rejected (<span>{tabCounts.rejectedCount}</span>)
|
127
|
+
</span>
|
128
|
+
</div>
|
129
|
+
|
130
|
+
<div
|
131
|
+
className="flex flex-col items-center space-y-1.5 transition-all cursor-pointer"
|
132
|
+
onClick={() => setActiveTab('approved')}
|
133
|
+
>
|
134
|
+
<span
|
135
|
+
className={twMerge(
|
136
|
+
clsx(
|
137
|
+
'flex items-center text-xl justify-center w-9 h-9 rounded-full p-1.5 border border-[#eeeded] text-black bg-white'
|
138
|
+
),
|
139
|
+
activeTab === 'approved' && 'bg-[#71d200] text-white'
|
140
|
+
)}
|
141
|
+
>
|
142
|
+
☷
|
143
|
+
</span>
|
144
|
+
<span
|
145
|
+
className={clsx(
|
146
|
+
'text-xs text-center text-[#363636] w-[50px] lg:w-[110px]',
|
147
|
+
activeTab === 'approved' && 'font-bold'
|
148
|
+
)}
|
149
|
+
>
|
150
|
+
Approved (<span>{tabCounts.approvedCount}</span>)
|
151
|
+
</span>
|
152
|
+
</div>
|
153
|
+
|
154
|
+
<div
|
155
|
+
className="flex flex-col items-center space-y-1.5 transition-all cursor-pointer"
|
156
|
+
onClick={() => setActiveTab('sending_approval_request')}
|
157
|
+
>
|
158
|
+
<span
|
159
|
+
className={twMerge(
|
160
|
+
clsx(
|
161
|
+
'flex items-center text-xl justify-center w-9 h-9 rounded-full p-1.5 border border-[#eeeded] text-black bg-white'
|
162
|
+
),
|
163
|
+
activeTab === 'sending_approval_request' &&
|
164
|
+
'bg-[#71d200] text-white'
|
165
|
+
)}
|
166
|
+
>
|
167
|
+
☷
|
168
|
+
</span>
|
169
|
+
<span
|
170
|
+
className={clsx(
|
171
|
+
'text-xs text-center text-[#363636] w-[50px] lg:w-[110px]',
|
172
|
+
activeTab === 'sending_approval_request' && 'font-bold'
|
173
|
+
)}
|
174
|
+
>
|
175
|
+
Requires My Approval (
|
176
|
+
<span>{tabCounts.sendingApprovalRequestCount}</span>)
|
177
|
+
</span>
|
178
|
+
</div>
|
179
|
+
|
180
|
+
<span className="absolute h-px bg-[#eeeded] top-[18px] left-6 -translate-y-1/2 lg:left-1/2 lg:-translate-x-1/2 z-[-1] w-[250px] lg:w-[calc(100%-110px)]"></span>
|
181
|
+
</div>
|
182
|
+
</div>
|
183
|
+
);
|
184
|
+
}
|