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