widget-chatbot 1.0.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/.prettierrc +7 -0
- package/CHANGELOG.md +614 -0
- package/CONTRIBUTING.md +44 -0
- package/LICENSE +177 -0
- package/README.md +77 -0
- package/cloudbuild.yaml +29 -0
- package/commitlint.config.js +1 -0
- package/dist/index.js +1 -0
- package/index.js +228 -0
- package/live_chat_helpdesk/HelpdeskChatContextProvider.js +323 -0
- package/live_chat_helpdesk/HelpdeskSender.js +287 -0
- package/live_chat_helpdesk/PayloadContextProvider.js +131 -0
- package/live_chat_helpdesk/constant.js +28 -0
- package/live_chat_helpdesk/useWSChatbot.js +178 -0
- package/npmrc.enc +0 -0
- package/package.json +203 -0
- package/publish_process/README.md +64 -0
- package/test-setup.js +22 -0
- package/umd.js +3 -0
- package/webpack.dev.js +81 -0
- package/webpack.prod.js +100 -0
|
@@ -0,0 +1,287 @@
|
|
|
1
|
+
import React, { memo, useState, useMemo, useEffect } from 'react';
|
|
2
|
+
import { Button, Tooltip, Upload } from 'antd';
|
|
3
|
+
import TextareaAutosize from 'react-textarea-autosize';
|
|
4
|
+
import AttachmentIcon from 'assets/icons/AttachmentIcon';
|
|
5
|
+
import DeleteUploadIcon from 'assets/icons/DeleteUploadIcon';
|
|
6
|
+
import LoadingLiveChatIcon from 'assets/LoadingLiveChatIcon';
|
|
7
|
+
import Send from 'assets/send_button';
|
|
8
|
+
import isStringNull from './helpers/isStringNull';
|
|
9
|
+
import { useHelpdeskChatContext } from '../../HelpdeskChatContextProvider';
|
|
10
|
+
import useFetch from '../../../../../../hooks/useFetch';
|
|
11
|
+
import { useConversationContext } from '../../../../../../context/ConversationContextProvider';
|
|
12
|
+
import ModalResult from '../../../../../../helpers/ModalResult';
|
|
13
|
+
import getBase64 from '../../../../../../helpers/getBase64';
|
|
14
|
+
import getTextWidth from '../../../../../../helpers/getTextWidth';
|
|
15
|
+
import { useErrorBoundaryContext } from '../../../../../../context/ErrorBoundaryContextProvider';
|
|
16
|
+
import useDictionary from '../../../../../../hooks/useDictionary';
|
|
17
|
+
import { usePayloadContext } from '../../../../../../PayloadContextProvider';
|
|
18
|
+
|
|
19
|
+
export const WrapperFileName = ({ fileName, children }) => {
|
|
20
|
+
const widthFileName = getTextWidth(fileName);
|
|
21
|
+
|
|
22
|
+
if (widthFileName > 250) {
|
|
23
|
+
return <Tooltip title={fileName}>{children}</Tooltip>;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
return children;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
const AttachmentField = () => {
|
|
30
|
+
const { chatState, setChatState } = useHelpdeskChatContext();
|
|
31
|
+
const [urlImage, setUrlImg] = useState('');
|
|
32
|
+
|
|
33
|
+
const isImage = useMemo(() => {
|
|
34
|
+
return chatState?.fileInput?.type?.includes('image');
|
|
35
|
+
}, [chatState?.fileInput]);
|
|
36
|
+
|
|
37
|
+
const onConvertImgToBase64 = async () => {
|
|
38
|
+
const base64Img = await getBase64(chatState?.fileInput);
|
|
39
|
+
|
|
40
|
+
setUrlImg(base64Img);
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
useEffect(() => {
|
|
44
|
+
if (chatState?.fileInput?.uid) {
|
|
45
|
+
onConvertImgToBase64();
|
|
46
|
+
}
|
|
47
|
+
}, [chatState?.fileInput?.uid]);
|
|
48
|
+
|
|
49
|
+
useEffect(() => {
|
|
50
|
+
// Monitor socket connection status
|
|
51
|
+
if (socket) {
|
|
52
|
+
console.log("Socket connection status:", socket.connected);
|
|
53
|
+
}
|
|
54
|
+
}, [socket]);
|
|
55
|
+
|
|
56
|
+
return (
|
|
57
|
+
<div className="rw-file-name">
|
|
58
|
+
<div style={{ display: 'flex', gap: 8, alignItems: 'flex-start' }}>
|
|
59
|
+
{isImage ? (
|
|
60
|
+
<img
|
|
61
|
+
src={urlImage}
|
|
62
|
+
style={{ width: 52, cursor: 'pointer' }}
|
|
63
|
+
onClick={(e) => {
|
|
64
|
+
e?.stopPropagation();
|
|
65
|
+
|
|
66
|
+
setChatState((prev) => ({
|
|
67
|
+
...prev,
|
|
68
|
+
isShowModalPreviewFile: true,
|
|
69
|
+
urlImagePreview: urlImage,
|
|
70
|
+
}));
|
|
71
|
+
}}
|
|
72
|
+
/>
|
|
73
|
+
) : (
|
|
74
|
+
<AttachmentIcon className="rw-attachment-icon-list" />
|
|
75
|
+
)}
|
|
76
|
+
<WrapperFileName fileName={chatState?.fileInput?.name}>
|
|
77
|
+
<p className="rw-file-name-text">{chatState?.fileInput?.name}</p>
|
|
78
|
+
</WrapperFileName>
|
|
79
|
+
</div>
|
|
80
|
+
<Button
|
|
81
|
+
style={{ color: 'rgba(0, 0, 0, 0.45)' }}
|
|
82
|
+
icon={<DeleteUploadIcon />}
|
|
83
|
+
onClick={() => {
|
|
84
|
+
setChatState((prev) => ({
|
|
85
|
+
...prev,
|
|
86
|
+
fileInput: null,
|
|
87
|
+
}));
|
|
88
|
+
}}
|
|
89
|
+
/>
|
|
90
|
+
</div>
|
|
91
|
+
);
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
const HelpdeskSender = () => {
|
|
95
|
+
const [inputVal, setInputVal] = useState('');
|
|
96
|
+
const { socket } = usePayloadContext(); // Tambahkan ini jika belum ada
|
|
97
|
+
const { taskIDSelect, connected, isBackClick } = useConversationContext();
|
|
98
|
+
const { setChatState, chatState } = useHelpdeskChatContext();
|
|
99
|
+
const { isErrorBoundary } = useErrorBoundaryContext();
|
|
100
|
+
|
|
101
|
+
const LabelTextFieldHint = useDictionary('TypeMessage', 'Type a message');
|
|
102
|
+
|
|
103
|
+
const inputValDOM = document.querySelector('.rw-new-message')?.value;
|
|
104
|
+
|
|
105
|
+
const fetch = useFetch();
|
|
106
|
+
|
|
107
|
+
const isDisabledInput = useMemo(() => {
|
|
108
|
+
return (
|
|
109
|
+
isErrorBoundary ||
|
|
110
|
+
!connected ||
|
|
111
|
+
((isStringNull(inputValDOM) || !chatState?.fileInput) && chatState?.loadingSend)
|
|
112
|
+
);
|
|
113
|
+
}, [inputValDOM, chatState?.fileInput, chatState?.loadingSend, connected]);
|
|
114
|
+
|
|
115
|
+
const classNameHelpdesk = useMemo(() => {
|
|
116
|
+
if (isBackClick) {
|
|
117
|
+
return 'rw-container-helpdesk-sender-from-detail';
|
|
118
|
+
}
|
|
119
|
+
return 'rw-container-helpdesk-sender';
|
|
120
|
+
}, [isBackClick]);
|
|
121
|
+
|
|
122
|
+
// HelpdeskSender.js - onSendMessage yang direvisi
|
|
123
|
+
const onSendMessage = () => {
|
|
124
|
+
if (!socket?.connected) {
|
|
125
|
+
console.warn("Socket not connected!");
|
|
126
|
+
ModalResult({
|
|
127
|
+
type: 'error',
|
|
128
|
+
error: new Error("Connection lost. Please refresh the page."),
|
|
129
|
+
});
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
setChatState((prev) => ({
|
|
134
|
+
...prev,
|
|
135
|
+
loadingSend: true,
|
|
136
|
+
}));
|
|
137
|
+
|
|
138
|
+
fetch({
|
|
139
|
+
endpoint: 'v1/sf7/general/helpdesk/chat/submit',
|
|
140
|
+
data: {
|
|
141
|
+
TASK_ID: taskIDSelect,
|
|
142
|
+
ATTACHMENT: '',
|
|
143
|
+
FAQ_ID: null,
|
|
144
|
+
MESSAGE: inputVal,
|
|
145
|
+
},
|
|
146
|
+
})
|
|
147
|
+
?.then(() => {
|
|
148
|
+
console.log("Message sent successfully");
|
|
149
|
+
setInputVal('');
|
|
150
|
+
// Clear input field
|
|
151
|
+
const messageInput = document.querySelector('.rw-new-message');
|
|
152
|
+
if (messageInput) {
|
|
153
|
+
messageInput.value = '';
|
|
154
|
+
}
|
|
155
|
+
// Reset loading state after successful send
|
|
156
|
+
setChatState((prev) => ({
|
|
157
|
+
...prev,
|
|
158
|
+
loadingSend: false
|
|
159
|
+
}));
|
|
160
|
+
})
|
|
161
|
+
?.catch((error) => {
|
|
162
|
+
console.error("Error sending message:", error);
|
|
163
|
+
ModalResult({
|
|
164
|
+
type: 'error',
|
|
165
|
+
error,
|
|
166
|
+
});
|
|
167
|
+
setChatState((prev) => ({
|
|
168
|
+
...prev,
|
|
169
|
+
loadingSend: false,
|
|
170
|
+
}));
|
|
171
|
+
});
|
|
172
|
+
};
|
|
173
|
+
|
|
174
|
+
const onSendFile = (url) => {
|
|
175
|
+
fetch({
|
|
176
|
+
endpoint: 'v1/sf7/general/helpdesk/chat/submit',
|
|
177
|
+
data: {
|
|
178
|
+
ATTACHMENT: url,
|
|
179
|
+
FAQ_ID: null,
|
|
180
|
+
TASK_ID: taskIDSelect,
|
|
181
|
+
},
|
|
182
|
+
})?.catch((error) => {
|
|
183
|
+
ModalResult({
|
|
184
|
+
type: 'error',
|
|
185
|
+
error,
|
|
186
|
+
});
|
|
187
|
+
setChatState((prev) => ({
|
|
188
|
+
...prev,
|
|
189
|
+
loadingSend: false,
|
|
190
|
+
}));
|
|
191
|
+
});
|
|
192
|
+
};
|
|
193
|
+
|
|
194
|
+
const onGetURL = () => {
|
|
195
|
+
setChatState((prev) => ({
|
|
196
|
+
...prev,
|
|
197
|
+
loadingSend: true,
|
|
198
|
+
}));
|
|
199
|
+
|
|
200
|
+
const formData = new FormData();
|
|
201
|
+
|
|
202
|
+
formData?.append('FILE', chatState?.fileInput);
|
|
203
|
+
formData?.append('UPLOAD_CODE', 'helpdesk');
|
|
204
|
+
formData?.append('UPLOAD_TYPE', 3);
|
|
205
|
+
|
|
206
|
+
fetch({
|
|
207
|
+
endpoint: 'v1/sf7/hrm/upload/temp',
|
|
208
|
+
data: formData,
|
|
209
|
+
isFormData: true,
|
|
210
|
+
})?.then(({ data }) => {
|
|
211
|
+
const urlAttachment = data?.DATA?.LIST?.TEMP_PATH;
|
|
212
|
+
onSendFile(urlAttachment);
|
|
213
|
+
});
|
|
214
|
+
};
|
|
215
|
+
|
|
216
|
+
const onEnterSendMsg = (e) => {
|
|
217
|
+
if (!isErrorBoundary && e?.keyCode === 13 && !isStringNull(inputVal) && !e.shiftKey) {
|
|
218
|
+
e.preventDefault();
|
|
219
|
+
onSendMessage();
|
|
220
|
+
}
|
|
221
|
+
};
|
|
222
|
+
|
|
223
|
+
return (
|
|
224
|
+
<div className={`${classNameHelpdesk} rw-container-sender`}>
|
|
225
|
+
{chatState?.fileInput ? (
|
|
226
|
+
<div className="rw-filelist-container">
|
|
227
|
+
<AttachmentField />
|
|
228
|
+
</div>
|
|
229
|
+
) : (
|
|
230
|
+
<TextareaAutosize
|
|
231
|
+
type="text"
|
|
232
|
+
minRows={2}
|
|
233
|
+
onKeyDown={onEnterSendMsg}
|
|
234
|
+
maxRows={5}
|
|
235
|
+
onChange={({ target: { value } }) => {
|
|
236
|
+
setInputVal(value);
|
|
237
|
+
}}
|
|
238
|
+
className="rw-new-message"
|
|
239
|
+
name="message"
|
|
240
|
+
autoFocus
|
|
241
|
+
autoComplete="off"
|
|
242
|
+
placeholder={`${LabelTextFieldHint}...`}
|
|
243
|
+
/>
|
|
244
|
+
)}
|
|
245
|
+
|
|
246
|
+
<div className="rw-container-btn-send">
|
|
247
|
+
<Upload
|
|
248
|
+
className="btn_upload"
|
|
249
|
+
beforeUpload={() => false}
|
|
250
|
+
fileList={[]}
|
|
251
|
+
accept=".doc, .jpg, .ods, .png, .txt, .docx, .pdf"
|
|
252
|
+
onChange={(e) => {
|
|
253
|
+
setChatState((prev) => ({
|
|
254
|
+
...prev,
|
|
255
|
+
fileInput: e?.file,
|
|
256
|
+
}));
|
|
257
|
+
}}
|
|
258
|
+
>
|
|
259
|
+
<div className="rw-new-topic">
|
|
260
|
+
<AttachmentIcon />
|
|
261
|
+
Attachment
|
|
262
|
+
</div>
|
|
263
|
+
</Upload>
|
|
264
|
+
|
|
265
|
+
<button
|
|
266
|
+
onClick={() => {
|
|
267
|
+
if (chatState?.fileInput) {
|
|
268
|
+
onGetURL();
|
|
269
|
+
} else if (!isStringNull(inputVal)) {
|
|
270
|
+
onSendMessage();
|
|
271
|
+
}
|
|
272
|
+
}}
|
|
273
|
+
type="submit"
|
|
274
|
+
className="rw-send"
|
|
275
|
+
disabled={isDisabledInput}
|
|
276
|
+
>
|
|
277
|
+
{chatState?.loadingSend ? (
|
|
278
|
+
<LoadingLiveChatIcon />
|
|
279
|
+
) : (
|
|
280
|
+
<Send ready={!isDisabledInput} className="rw-send-icon" alt="send" />
|
|
281
|
+
)}
|
|
282
|
+
</button>
|
|
283
|
+
</div>
|
|
284
|
+
</div>
|
|
285
|
+
);
|
|
286
|
+
};
|
|
287
|
+
export default memo(HelpdeskSender);
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import axios from 'axios';
|
|
2
|
+
import React, { useState, useMemo, useContext, createContext, useEffect } from 'react';
|
|
3
|
+
import { BASE_WEBSOCKET_URL } from './helpers/constant';
|
|
4
|
+
import { io } from 'socket.io-client';
|
|
5
|
+
|
|
6
|
+
/** @type {import('react').Context<PayloadContextData>} */
|
|
7
|
+
const PayloadContext = createContext({});
|
|
8
|
+
|
|
9
|
+
export const usePayloadContext = () => useContext(PayloadContext);
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
*
|
|
13
|
+
* @param {object} props
|
|
14
|
+
* @param {string} props.initPayload
|
|
15
|
+
* @returns
|
|
16
|
+
*/
|
|
17
|
+
const PayloadContextProvider = ({ children, initPayload, setInitPayload }) => {
|
|
18
|
+
/** @type {PayloadContextData} */
|
|
19
|
+
const objInitPayload = useMemo(() => {
|
|
20
|
+
return JSON.parse(initPayload?.replace('/get_started', '') || '{}');
|
|
21
|
+
}, [initPayload]);
|
|
22
|
+
|
|
23
|
+
const [isRefetch, setRefetch] = useState(false);
|
|
24
|
+
const [isVerifyHelpdesk, setVerifyHelpdesk] = useState(false);
|
|
25
|
+
const [isLoadedAccessWS, setLoadedAccessWS] = useState(false);
|
|
26
|
+
const [socket, setSocket] = useState(null);
|
|
27
|
+
|
|
28
|
+
// Initialize the Socket.io client
|
|
29
|
+
useEffect(() => {
|
|
30
|
+
const newSocket = io(BASE_WEBSOCKET_URL);
|
|
31
|
+
setSocket(newSocket);
|
|
32
|
+
|
|
33
|
+
return () => {
|
|
34
|
+
newSocket.disconnect();
|
|
35
|
+
};
|
|
36
|
+
}, []);
|
|
37
|
+
|
|
38
|
+
const onRefreshToken = async () => {
|
|
39
|
+
try {
|
|
40
|
+
const response = await axios({
|
|
41
|
+
url: objInitPayload?.uriBackend,
|
|
42
|
+
params: {
|
|
43
|
+
ofid: 'sfsystem.RefreshToken',
|
|
44
|
+
accname: objInitPayload?.companycode,
|
|
45
|
+
},
|
|
46
|
+
data: {
|
|
47
|
+
refreshtoken:
|
|
48
|
+
objInitPayload?.refresh_token ||
|
|
49
|
+
'4D8C30EE29749D0F06B4F6E2527516B397C09D4DB148D42214E2941C7A86139D3CB512D925E1B098C6090A45E6BCC545723222FA3BABDC2A',
|
|
50
|
+
},
|
|
51
|
+
method: 'POST',
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
if (response?.data?.DATA) {
|
|
55
|
+
const data = response?.data?.DATA;
|
|
56
|
+
|
|
57
|
+
let payload = '/get_started';
|
|
58
|
+
|
|
59
|
+
payload += JSON.stringify({
|
|
60
|
+
access_token: data?.JWT_TOKEN,
|
|
61
|
+
refresh_token: objInitPayload?.refresh_token,
|
|
62
|
+
uagent: objInitPayload?.uagent,
|
|
63
|
+
uriBackend: objInitPayload?.uriBackend,
|
|
64
|
+
uriService: objInitPayload?.uriService,
|
|
65
|
+
companyid: objInitPayload?.companyid,
|
|
66
|
+
companycode: objInitPayload?.companycode,
|
|
67
|
+
lang: objInitPayload?.lang,
|
|
68
|
+
uriBackendEnt: objInitPayload?.uriBackendEnt,
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
setInitPayload(payload);
|
|
72
|
+
|
|
73
|
+
return data?.JWT_TOKEN;
|
|
74
|
+
} else {
|
|
75
|
+
throw new Error('Failed to refresh token');
|
|
76
|
+
}
|
|
77
|
+
} catch (err) {
|
|
78
|
+
throw err;
|
|
79
|
+
}
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
// Handle Socket.io events
|
|
83
|
+
useEffect(() => {
|
|
84
|
+
if (!socket) return;
|
|
85
|
+
|
|
86
|
+
const { companyid, companycode, lang, uagent, access_token, uriBackendEnt, uriBackend } =
|
|
87
|
+
objInitPayload;
|
|
88
|
+
|
|
89
|
+
const messageHandshake = {
|
|
90
|
+
coid: companyid,
|
|
91
|
+
cocode: companycode,
|
|
92
|
+
lang,
|
|
93
|
+
uagent,
|
|
94
|
+
jwt: access_token,
|
|
95
|
+
uribackendent: uriBackendEnt,
|
|
96
|
+
accname: companycode?.toUpperCase(),
|
|
97
|
+
uribackendlucee: uriBackend,
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
socket.emit('access_ws', messageHandshake);
|
|
101
|
+
|
|
102
|
+
socket.on('access_ws_response', (data) => {
|
|
103
|
+
if (!isLoadedAccessWS) {
|
|
104
|
+
setLoadedAccessWS(true);
|
|
105
|
+
setVerifyHelpdesk(data?.result);
|
|
106
|
+
}
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
// Cleanup on component unmount
|
|
110
|
+
return () => {
|
|
111
|
+
socket.off('access_ws_response');
|
|
112
|
+
};
|
|
113
|
+
}, [socket, initPayload]);
|
|
114
|
+
|
|
115
|
+
return (
|
|
116
|
+
<PayloadContext.Provider
|
|
117
|
+
value={{
|
|
118
|
+
...objInitPayload,
|
|
119
|
+
onRefreshToken,
|
|
120
|
+
isRefetch,
|
|
121
|
+
setRefetch,
|
|
122
|
+
socket,
|
|
123
|
+
isVerifyHelpdesk,
|
|
124
|
+
}}
|
|
125
|
+
>
|
|
126
|
+
{children}
|
|
127
|
+
</PayloadContext.Provider>
|
|
128
|
+
);
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
export default PayloadContextProvider;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import CryptoJS from 'crypto-js';
|
|
2
|
+
import isMobileUserAgent from './isMobileUserAgent';
|
|
3
|
+
|
|
4
|
+
export const IS_MOBILE = isMobileUserAgent();
|
|
5
|
+
// && isWorkplazeMobile();
|
|
6
|
+
|
|
7
|
+
// export const KEY = process?.env?.REACT_APP_KEY || '6Le0DgMTAAAANokdEEial';
|
|
8
|
+
export const KEY = '6Le0DgMTAAAANokdEEial';
|
|
9
|
+
|
|
10
|
+
export const APP_KEY_HEX = (keyEncrypt) =>
|
|
11
|
+
CryptoJS.enc.Hex.parse(CryptoJS.enc.Utf8.parse(keyEncrypt || KEY).toString());
|
|
12
|
+
|
|
13
|
+
export const CIPHER_OPTIONS = { mode: CryptoJS.mode.ECB, padding: CryptoJS.pad.Pkcs7 };
|
|
14
|
+
|
|
15
|
+
export const ENCRYPTED_ROUTE_PREFIX = '|';
|
|
16
|
+
export const UNENCRYPTED_ROUTE_PREFIX = '#';
|
|
17
|
+
|
|
18
|
+
// export const = 'wss://sfchatbot.dataon.com:5005/livechat-he lpdesk/ws';
|
|
19
|
+
|
|
20
|
+
// export const BASE_WEBSOCKET_URL = 'ws://localhost:8002/livechat-helpdesk/ws';
|
|
21
|
+
export const BASE_WEBSOCKET_URL = "http://localhost:5100/livechat-helpdesk/ws";
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
import { useEffect, useRef } from 'react';
|
|
2
|
+
import { usePayloadContext } from '../PayloadContextProvider';
|
|
3
|
+
import sortArr from '../helpers/sortArr';
|
|
4
|
+
import { scrollToBottom } from '../components/Widget/components/Conversation/components/Messages';
|
|
5
|
+
import uniqueArrObj from '../helpers/uniqueArrObj';
|
|
6
|
+
import useDecodeJWT from './useDecodeJWT';
|
|
7
|
+
import formatChat, { deFormatChat } from '../helpers/formatChat';
|
|
8
|
+
import { useConversationContext } from '../context/ConversationContextProvider';
|
|
9
|
+
import { useHelpdeskChatContext } from '../components/Widget/components/Conversation/HelpdeskChatContextProvider';
|
|
10
|
+
import { useListTicketContext } from '../context/ListTicketContextProvider';
|
|
11
|
+
import ModalResult from '../helpers/ModalResult';
|
|
12
|
+
|
|
13
|
+
const useWSChatbot = () => {
|
|
14
|
+
const { socket } = usePayloadContext();
|
|
15
|
+
const { taskIDSelect } = useConversationContext();
|
|
16
|
+
const { ticketState, taskIDs, setTicketState } = useListTicketContext();
|
|
17
|
+
const { chatState, setChatState, isFetchPrevChat } = useHelpdeskChatContext();
|
|
18
|
+
const { EMP_ID } = useDecodeJWT();
|
|
19
|
+
|
|
20
|
+
// Refs untuk mengakses state terbaru
|
|
21
|
+
const chatStateRef = useRef(chatState);
|
|
22
|
+
const ticketStateRef = useRef(ticketState);
|
|
23
|
+
const isFetchPrevChatRef = useRef(isFetchPrevChat);
|
|
24
|
+
|
|
25
|
+
useEffect(() => {
|
|
26
|
+
chatStateRef.current = chatState;
|
|
27
|
+
}, [chatState]);
|
|
28
|
+
|
|
29
|
+
useEffect(() => {
|
|
30
|
+
ticketStateRef.current = ticketState;
|
|
31
|
+
}, [ticketState]);
|
|
32
|
+
|
|
33
|
+
useEffect(() => {
|
|
34
|
+
isFetchPrevChatRef.current = isFetchPrevChat;
|
|
35
|
+
}, [isFetchPrevChat]);
|
|
36
|
+
|
|
37
|
+
const handleNewNotif = (data) => {
|
|
38
|
+
console.log("New Notification Data:", data);
|
|
39
|
+
const newTicket = data?.filter((val) => val?.newChat);
|
|
40
|
+
|
|
41
|
+
setTimeout(() => {
|
|
42
|
+
setTicketState((prev) => ({
|
|
43
|
+
...prev,
|
|
44
|
+
count: newTicket?.length,
|
|
45
|
+
}));
|
|
46
|
+
}, 250);
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
const handleNewChat = (data) => {
|
|
50
|
+
try {
|
|
51
|
+
console.log("Processing new chat data:", data);
|
|
52
|
+
if (!Array.isArray(data)) {
|
|
53
|
+
console.warn("Received non-array data in handleNewChat:", data);
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const newChat = data;
|
|
58
|
+
const deFormatOldChat = deFormatChat(chatStateRef.current?.list || []);
|
|
59
|
+
console.log("Current chat list:", deFormatOldChat);
|
|
60
|
+
|
|
61
|
+
const arrNewChat = [...deFormatOldChat, ...newChat];
|
|
62
|
+
console.log("Combined chat list:", arrNewChat);
|
|
63
|
+
|
|
64
|
+
const sortNewChat = uniqueArrObj(sortArr(arrNewChat, 'ID'), 'ID');
|
|
65
|
+
console.log("Sorted and deduplicated chat list:", sortNewChat);
|
|
66
|
+
|
|
67
|
+
const unreadChat = newChat?.filter(
|
|
68
|
+
(item) => item?.READ_BY === '' && item?.ACTOR?.EMP_ID !== EMP_ID
|
|
69
|
+
);
|
|
70
|
+
|
|
71
|
+
setTicketState((prev) => ({
|
|
72
|
+
...prev,
|
|
73
|
+
count: unreadChat?.length,
|
|
74
|
+
}));
|
|
75
|
+
|
|
76
|
+
setChatState((prev) => {
|
|
77
|
+
console.log("Updating chat state with new list");
|
|
78
|
+
return {
|
|
79
|
+
...prev,
|
|
80
|
+
loadingSend: false,
|
|
81
|
+
...(prev.loadingInit && {
|
|
82
|
+
loadingInit: false,
|
|
83
|
+
}),
|
|
84
|
+
list: formatChat(sortNewChat),
|
|
85
|
+
fileInput: null,
|
|
86
|
+
};
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
if (!isFetchPrevChatRef.current) {
|
|
90
|
+
console.log("Scrolling to bottom");
|
|
91
|
+
scrollToBottom();
|
|
92
|
+
}
|
|
93
|
+
} catch (error) {
|
|
94
|
+
console.error("Error in handleNewChat:", error);
|
|
95
|
+
}
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
useEffect(() => {
|
|
99
|
+
if (!socket) return;
|
|
100
|
+
|
|
101
|
+
console.log("Setting up WebSocket listeners");
|
|
102
|
+
|
|
103
|
+
// Debug socket connection
|
|
104
|
+
socket.on('connect', () => {
|
|
105
|
+
console.log('WebSocket connected');
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
socket.on('disconnect', () => {
|
|
109
|
+
console.log('WebSocket disconnected');
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
// Listener untuk new_notif
|
|
113
|
+
socket.on('new_notif', handleNewNotif);
|
|
114
|
+
|
|
115
|
+
// Listener untuk new_chat
|
|
116
|
+
socket.on('new_chat', (data) => {
|
|
117
|
+
console.log("Received new_chat event:", data);
|
|
118
|
+
handleNewChat(data);
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
// Clean up listeners saat komponen unmount
|
|
122
|
+
return () => {
|
|
123
|
+
console.log("Cleaning up WebSocket listeners");
|
|
124
|
+
socket.off('new_notif', handleNewNotif);
|
|
125
|
+
socket.off('new_chat', handleNewChat);
|
|
126
|
+
};
|
|
127
|
+
}, [socket, EMP_ID]); // Hapus chatState, ticketState, dan isFetchPrevChat
|
|
128
|
+
|
|
129
|
+
const onWatchNotif = () => {
|
|
130
|
+
const params = { task_id_list: taskIDs };
|
|
131
|
+
console.log("Watching Notifications:", params);
|
|
132
|
+
|
|
133
|
+
if (!ticketState?.isInitNotif) {
|
|
134
|
+
socket?.emit('load_notif', params);
|
|
135
|
+
console.log("Notification Event Sent to Server:", params);
|
|
136
|
+
|
|
137
|
+
setTimeout(() => {
|
|
138
|
+
setTicketState((prev) => ({
|
|
139
|
+
...prev,
|
|
140
|
+
isInitNotif: true,
|
|
141
|
+
}));
|
|
142
|
+
}, 350);
|
|
143
|
+
}
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
const onWatchLiveChat = () => {
|
|
147
|
+
const params = {
|
|
148
|
+
taskid: taskIDSelect,
|
|
149
|
+
list_chat_id: deFormatChat(chatStateRef.current?.list || [])?.map((item) => item?.ID),
|
|
150
|
+
};
|
|
151
|
+
console.log("Watching Live Chat:", params);
|
|
152
|
+
|
|
153
|
+
if (deFormatChat(chatStateRef.current?.list || [])?.length > 0) {
|
|
154
|
+
if (!chatStateRef.current?.isInitChat) {
|
|
155
|
+
setChatState((prev) => ({
|
|
156
|
+
...prev,
|
|
157
|
+
isInitChat: true,
|
|
158
|
+
}));
|
|
159
|
+
}
|
|
160
|
+
socket?.emit('live_chat', params);
|
|
161
|
+
console.log("Live Chat Event Sent to Server:", params);
|
|
162
|
+
}
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
const onStopNotif = () => {
|
|
166
|
+
socket?.emit('stop_notif');
|
|
167
|
+
console.log("Stopping Notifications");
|
|
168
|
+
};
|
|
169
|
+
|
|
170
|
+
const onStopLiveChat = () => {
|
|
171
|
+
socket?.emit('stop_live_chat');
|
|
172
|
+
console.log("Stopping Live Chat");
|
|
173
|
+
};
|
|
174
|
+
|
|
175
|
+
return { onStopNotif, onStopLiveChat, onWatchNotif, onWatchLiveChat };
|
|
176
|
+
};
|
|
177
|
+
|
|
178
|
+
export default useWSChatbot;
|
package/npmrc.enc
ADDED
|
Binary file
|