@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
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { useAddToBasketMutation } from '@akinon/next/data/client/b2b';
|
|
4
|
+
import { useLocalization } from '@akinon/next/hooks';
|
|
5
|
+
import { Division } from '@akinon/next/types';
|
|
6
|
+
import React, { useMemo, useState } from 'react';
|
|
7
|
+
import { twMerge } from 'tailwind-merge';
|
|
8
|
+
|
|
9
|
+
interface GetResponse<T> {
|
|
10
|
+
count: number;
|
|
11
|
+
next: null;
|
|
12
|
+
previous: null;
|
|
13
|
+
results: T[];
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function StoreModal({
|
|
17
|
+
storeData,
|
|
18
|
+
productPk,
|
|
19
|
+
open,
|
|
20
|
+
setOpen
|
|
21
|
+
}: {
|
|
22
|
+
storeData: GetResponse<Division>;
|
|
23
|
+
productPk: number;
|
|
24
|
+
open: boolean;
|
|
25
|
+
setOpen: (open: boolean) => void;
|
|
26
|
+
}) {
|
|
27
|
+
const initialStateSelectStore = {
|
|
28
|
+
id: null,
|
|
29
|
+
quantity: '0'
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
const { t } = useLocalization();
|
|
33
|
+
const [addToBasket] = useAddToBasketMutation();
|
|
34
|
+
const [selectedStore, setSelectedStore] = useState(initialStateSelectStore);
|
|
35
|
+
const inputRefs = {};
|
|
36
|
+
|
|
37
|
+
const handleRadioChange = (storeId) => {
|
|
38
|
+
setSelectedStore((prevSelectedStore) => ({
|
|
39
|
+
...prevSelectedStore,
|
|
40
|
+
id: storeId
|
|
41
|
+
}));
|
|
42
|
+
|
|
43
|
+
clearQuantityInput(storeId);
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
const handleQuantityChange = (storeId, e) => {
|
|
47
|
+
if (storeId !== selectedStore.id) {
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
setSelectedStore((prevSelectedStore) => ({
|
|
52
|
+
...prevSelectedStore,
|
|
53
|
+
quantity: e.target.value
|
|
54
|
+
}));
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
const handleAddToBasket = () => {
|
|
58
|
+
addToBasket({
|
|
59
|
+
division: selectedStore.id,
|
|
60
|
+
product_remote_id: productPk.toString(),
|
|
61
|
+
quantity: selectedStore.quantity
|
|
62
|
+
})
|
|
63
|
+
.unwrap()
|
|
64
|
+
.then(() => {
|
|
65
|
+
setOpen(false);
|
|
66
|
+
setSelectedStore(initialStateSelectStore);
|
|
67
|
+
});
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
const clearQuantityInput = (storeId) => {
|
|
71
|
+
inputRefs[storeId]?.focus();
|
|
72
|
+
inputRefs[storeId].value = '';
|
|
73
|
+
|
|
74
|
+
setSelectedStore((prevSelectedStore) => ({
|
|
75
|
+
...prevSelectedStore,
|
|
76
|
+
quantity: '0'
|
|
77
|
+
}));
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
const isButtonDisabled = useMemo(() => {
|
|
81
|
+
if (selectedStore.quantity === '') {
|
|
82
|
+
return true;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return !selectedStore.id || parseInt(selectedStore.quantity) < 1;
|
|
86
|
+
}, [selectedStore]);
|
|
87
|
+
|
|
88
|
+
if (!open) return null;
|
|
89
|
+
|
|
90
|
+
return (
|
|
91
|
+
<div className="before:w-full before:h-screen before:content-[''] before:bg-black/60 before:z-40 before:fixed before:inset-0">
|
|
92
|
+
<div className="w-full h-screen inset-0 md:inset-auto md:w-[600px] md:h-min md:left-1/2 md:top-1/2 md:-translate-x-1/2 md:-translate-y-1/2 z-50 fixed bg-white">
|
|
93
|
+
<header className="relative md:border-b border-[#d5dadb]">
|
|
94
|
+
<h3 className="text-left px-4 md:text-center py-4 font-bold text-lg">
|
|
95
|
+
{t('product.store_select_modal.title')}
|
|
96
|
+
</h3>
|
|
97
|
+
<button
|
|
98
|
+
className="absolute right-[15px] md:right-[30px] top-5"
|
|
99
|
+
onClick={() => {
|
|
100
|
+
setOpen(false);
|
|
101
|
+
setSelectedStore(initialStateSelectStore);
|
|
102
|
+
}}
|
|
103
|
+
>
|
|
104
|
+
<i className="pz-icon-close"></i>
|
|
105
|
+
</button>
|
|
106
|
+
</header>
|
|
107
|
+
<div className="p-[15px] md:p-[30px]">
|
|
108
|
+
<table className="w-full flex flex-col h-full md:h-auto md:table md:w-[540px]">
|
|
109
|
+
<thead className="w-full flex-grow-0 shrink-0 basis-auto table">
|
|
110
|
+
<tr className="border border-[#d7d7d7] bg-[#fafafa]">
|
|
111
|
+
<th className="w-[43px] border-r border-[#d7d7d7]"></th>
|
|
112
|
+
<th className="w-[190px] md:w-auto text-[11px] text-[#99999d] py-[18px] px-4 border-r border-[#d7d7d7] text-left font-normal">
|
|
113
|
+
{t('product.store_select_modal.store_name')}
|
|
114
|
+
</th>
|
|
115
|
+
<th className="max-w-[111px] w-[111px] md:w-[180px] md:max-w-[180px] text-[11px] text-[#99999d] py-[18px] text-right pr-6 md:pr-[37px] font-normal">
|
|
116
|
+
{t('product.store_select_modal.quantity')}
|
|
117
|
+
</th>
|
|
118
|
+
</tr>
|
|
119
|
+
</thead>
|
|
120
|
+
<tbody className="flex-1 basis-auto block overflow-y-auto h-[calc(100vh-206px)] md:h-auto md:max-h-[488px]">
|
|
121
|
+
{storeData?.results.map((store) => (
|
|
122
|
+
<tr
|
|
123
|
+
key={store.id}
|
|
124
|
+
className="w-full table table-fixed h-[61px] even:bg-[#fafafa] border border-t-0 border-[#d7d7d7]"
|
|
125
|
+
>
|
|
126
|
+
<td className="w-[43px] border-r border-[#d7d7d7] text-center">
|
|
127
|
+
<input
|
|
128
|
+
type="radio"
|
|
129
|
+
value={store.id}
|
|
130
|
+
name="division"
|
|
131
|
+
className="w-[18px] h-[18px] accent-black"
|
|
132
|
+
id={store.erp_code}
|
|
133
|
+
onChange={() => handleRadioChange(store.id)}
|
|
134
|
+
/>
|
|
135
|
+
</td>
|
|
136
|
+
<td className="border-r border-[#d7d7d7] text-xs px-4 py-6 leading-3 w-[190px] md:w-auto">
|
|
137
|
+
<label htmlFor={store.erp_code}>{store.name}</label>
|
|
138
|
+
</td>
|
|
139
|
+
<td className="text-right pr-6 md:pr-[37px] max-w-[111px] w-[111px] md:w-[180px] md:max-w-[180px]">
|
|
140
|
+
<input
|
|
141
|
+
type="number"
|
|
142
|
+
className="w-[64px] md:w-[84px] h-[31px] border border-[#d7d7d7] outline-none text-xs text-right pr-1 appearance-none"
|
|
143
|
+
onChange={(e) => handleQuantityChange(store.id, e)}
|
|
144
|
+
ref={(ref) => (inputRefs[store.id] = ref)}
|
|
145
|
+
/>
|
|
146
|
+
</td>
|
|
147
|
+
</tr>
|
|
148
|
+
))}
|
|
149
|
+
</tbody>
|
|
150
|
+
</table>
|
|
151
|
+
|
|
152
|
+
<div className="fixed bottom-0 left-0 w-full p-[15px] md:p-0 md:static mt-5 shadow-[0_0_4px_0_rgba(0,0,0,0.2)] md:shadow-none">
|
|
153
|
+
<p hidden>{t('product.store_select_modal.valid_quantity')}</p>
|
|
154
|
+
|
|
155
|
+
<button
|
|
156
|
+
className={twMerge(
|
|
157
|
+
'text-xs text-white bg-black py-3 px-14 w-full md:w-auto',
|
|
158
|
+
isButtonDisabled && 'bg-black/40'
|
|
159
|
+
)}
|
|
160
|
+
onClick={handleAddToBasket}
|
|
161
|
+
disabled={isButtonDisabled}
|
|
162
|
+
>
|
|
163
|
+
{t('product.store_select_modal.add_to_basket')}
|
|
164
|
+
</button>
|
|
165
|
+
</div>
|
|
166
|
+
</div>
|
|
167
|
+
</div>
|
|
168
|
+
</div>
|
|
169
|
+
);
|
|
170
|
+
}
|