@zenify-omni/chat-ui-react-js 0.1.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.
Files changed (403) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +92 -0
  3. package/assets/asset-url.js +6 -0
  4. package/assets/icons/chatter_agent.js +27 -0
  5. package/assets/icons/chatter_chatbot.js +43 -0
  6. package/assets/icons/chatter_contact.js +36 -0
  7. package/assets/icons/email_icon.png +0 -0
  8. package/assets/icons/emoji/angry.svg +56 -0
  9. package/assets/icons/emoji/haha.svg +20 -0
  10. package/assets/icons/emoji/like.svg +10 -0
  11. package/assets/icons/emoji/like_out_line.png +0 -0
  12. package/assets/icons/emoji/love.svg +10 -0
  13. package/assets/icons/emoji/sad.svg +46 -0
  14. package/assets/icons/emoji/wow.svg +51 -0
  15. package/assets/icons/instagram.js +29 -0
  16. package/assets/icons/not_found.png +0 -0
  17. package/assets/icons/not_found_template.svg +9 -0
  18. package/assets/icons/phone_icon.png +0 -0
  19. package/assets/icons/shopee.js +17 -0
  20. package/assets/icons/sms_icon.png +0 -0
  21. package/assets/icons/tiktok.js +17 -0
  22. package/assets/icons/whatsapp.js +28 -0
  23. package/assets/icons/zalo.js +17 -0
  24. package/assets/icons/zalo_icon.png +0 -0
  25. package/assets/icons/zns_icon.png +0 -0
  26. package/assets/images/emoji.webp +0 -0
  27. package/assets/images/error.png +0 -0
  28. package/assets/images/menu.png +0 -0
  29. package/assets/images/money.png +0 -0
  30. package/assets/images/success.png +0 -0
  31. package/assets/public-urls.js +78 -0
  32. package/components/campaign-detail-route/select/group.js +38 -0
  33. package/components/campaign-detail-route/select/multi-groups.js +43 -0
  34. package/components/chat-route/chat-box/ads-context-card.js +143 -0
  35. package/components/chat-route/chat-box/chat-drop-file.js +18 -0
  36. package/components/chat-route/chat-box/chat-feedback/chat-insight.js +214 -0
  37. package/components/chat-route/chat-box/chat-feedback/index.js +127 -0
  38. package/components/chat-route/chat-box/chat-header-actions-inline.js +1055 -0
  39. package/components/chat-route/chat-box/chat-header.js +465 -0
  40. package/components/chat-route/chat-box/chat-input.js +1164 -0
  41. package/components/chat-route/chat-box/chat-message-action.js +399 -0
  42. package/components/chat-route/chat-box/chat-message-list.js +995 -0
  43. package/components/chat-route/chat-box/chat-message.js +2099 -0
  44. package/components/chat-route/chat-box/chat-reply-preview.js +317 -0
  45. package/components/chat-route/chat-box/common-groups-modal.js +430 -0
  46. package/components/chat-route/chat-box/constants.js +29 -0
  47. package/components/chat-route/chat-box/contact-card/index.js +87 -0
  48. package/components/chat-route/chat-box/edit-customer-alias-dialog.js +197 -0
  49. package/components/chat-route/chat-box/emoji-picker-combined.js +91 -0
  50. package/components/chat-route/chat-box/emoji-sprite.js +45 -0
  51. package/components/chat-route/chat-box/emojiMap.js +92 -0
  52. package/components/chat-route/chat-box/facebook-reconnect-notice.js +49 -0
  53. package/components/chat-route/chat-box/forward-message-modal.js +659 -0
  54. package/components/chat-route/chat-box/index.js +165 -0
  55. package/components/chat-route/chat-box/message-time.js +44 -0
  56. package/components/chat-route/chat-box/message-viewers.js +114 -0
  57. package/components/chat-route/chat-box/pasted-file-preview.js +138 -0
  58. package/components/chat-route/chat-box/pinned-messages.js +255 -0
  59. package/components/chat-route/chat-box/react-message/react-preview.js +83 -0
  60. package/components/chat-route/chat-box/react-message/reaction-button.js +200 -0
  61. package/components/chat-route/chat-box/right-side/chat-history.js +142 -0
  62. package/components/chat-route/chat-box/right-side/chat-tag-selector.js +232 -0
  63. package/components/chat-route/chat-box/right-side/create-personal-tag-modal.js +458 -0
  64. package/components/chat-route/chat-box/right-side/customer/customer-info-item.js +60 -0
  65. package/components/chat-route/chat-box/right-side/in-room-dialog.js +225 -0
  66. package/components/chat-route/chat-box/right-side/zalo-group/add-friend-dialog.js +154 -0
  67. package/components/chat-route/chat-box/right-side/zalo-group/send-message-dialog.js +165 -0
  68. package/components/chat-route/chat-box/send-contact-card-modal.js +102 -0
  69. package/components/chat-route/chat-box/send-contact-card.js +80 -0
  70. package/components/chat-route/chat-box/text-content.js +400 -0
  71. package/components/chat-route/chat-box/use-facebook-page-invalid.js +22 -0
  72. package/components/chat-route/chat-box/zalo-emoji-decoration.js +98 -0
  73. package/components/chat-route/chat-box/zalo-emoji-picker.js +74 -0
  74. package/components/chat-route/chat-box/zalo-personal-editor.js +590 -0
  75. package/components/chat-route/chat-box/zalo-reminder/create-zalo-reminder.js +868 -0
  76. package/components/chat-route/chat-box/zalo-reminder/use-send-zalo-reminder.js +98 -0
  77. package/components/chat-route/chat-box/zalo-reminder/zalo-reminder-content.js +294 -0
  78. package/components/chat-route/chat-box/zalo-rtf-utils.js +422 -0
  79. package/components/chat-route/chat-list/action-chat-item.js +116 -0
  80. package/components/chat-route/chat-list/assign-agent-dialog.js +170 -0
  81. package/components/chat-route/chat-list/assign-tag-dialog.js +242 -0
  82. package/components/chat-route/chat-list/chat-item-tags.js +105 -0
  83. package/components/chat-route/chat-list/chat-item.js +749 -0
  84. package/components/chat-route/chat-list/chat-list-item.js +370 -0
  85. package/components/chat-route/chat-list/chat-list-selection-bar.js +426 -0
  86. package/components/chat-route/chat-list/chat-search-preview.js +48 -0
  87. package/components/chat-route/chat-list/chat-sort-menu.js +89 -0
  88. package/components/chat-route/chat-list/edit-filter-modal.js +468 -0
  89. package/components/chat-route/chat-list/filter-chat-sources.js +222 -0
  90. package/components/chat-route/chat-list/filter-chat.js +501 -0
  91. package/components/chat-route/chat-list/filter-criteria-rows.js +845 -0
  92. package/components/chat-route/chat-list/filter-group-select.js +66 -0
  93. package/components/chat-route/chat-list/filter-pin-menu.js +165 -0
  94. package/components/chat-route/chat-list/friend-request/list-friend-request.js +287 -0
  95. package/components/chat-route/chat-list/index.js +243 -0
  96. package/components/chat-route/chat-list/inline-search-results.js +178 -0
  97. package/components/chat-route/chat-list/post-ids-field.js +104 -0
  98. package/components/chat-route/chat-list/quick-filter-bar.js +391 -0
  99. package/components/chat-route/chat-list/save-filter-popover.js +258 -0
  100. package/components/chat-route/chat-list/search-messages/index.js +225 -0
  101. package/components/chat-route/chat-list/search-result-header.js +51 -0
  102. package/components/chat-route/chat-list/util.js +604 -0
  103. package/components/chat-route/chat-toolbar.js +108 -0
  104. package/components/chat-route/hooks/use-reassign.js +111 -0
  105. package/components/chat-route/shared/close-conversation-dialog.js +111 -0
  106. package/components/chat-route/shared/force-transfer-dialog.js +109 -0
  107. package/components/chat-route/shared/in-queue-confirm-dialog.js +40 -0
  108. package/components/chat-route/util.js +146 -0
  109. package/components/chat-route/zalo/modal-action-zalo.js +76 -0
  110. package/components/chat-route/zalo/modal-add-friend-zalo.js +277 -0
  111. package/components/custom/SafeHTML.js +45 -0
  112. package/components/custom/audio-player.js +81 -0
  113. package/components/custom/avatar.js +95 -0
  114. package/components/custom/between-date-picker/index.js +86 -0
  115. package/components/custom/button-close.js +28 -0
  116. package/components/custom/button.js +80 -0
  117. package/components/custom/color-picker.js +81 -0
  118. package/components/custom/combobox.js +168 -0
  119. package/components/custom/custom-dialog.js +44 -0
  120. package/components/custom/custom-select.js +256 -0
  121. package/components/custom/cycle-value-helpers.js +11 -0
  122. package/components/custom/cycle-value.js +93 -0
  123. package/components/custom/date-time-picker/components/date-time-group.js +44 -0
  124. package/components/custom/date-time-picker/components/date-time-input.js +383 -0
  125. package/components/custom/date-time-picker/components/date-time-unit-input.js +138 -0
  126. package/components/custom/date-time-picker/components/month-year-picker.js +232 -0
  127. package/components/custom/date-time-picker/hooks/use-merge-refs.js +18 -0
  128. package/components/custom/date-time-picker/hooks/use-scroll-to-center.js +18 -0
  129. package/components/custom/date-time-picker/index.js +348 -0
  130. package/components/custom/field-file-upload/files-upload.js +87 -0
  131. package/components/custom/field-file-upload/image-upload.js +115 -0
  132. package/components/custom/field-file-upload/preview-images-modal.js +284 -0
  133. package/components/custom/field-file-upload/preview-list-file.js +134 -0
  134. package/components/custom/field-file-upload/preview-list-image.js +108 -0
  135. package/components/custom/field-file-upload/upload-custom.js +222 -0
  136. package/components/custom/field-generator.js +639 -0
  137. package/components/custom/field-layout-popover.js +304 -0
  138. package/components/custom/file-preview-modal.js +351 -0
  139. package/components/custom/html-edtior.js +100 -0
  140. package/components/custom/icon.js +49 -0
  141. package/components/custom/infinite-scroll-combobox.js +253 -0
  142. package/components/custom/inline-edit-popover.js +90 -0
  143. package/components/custom/inputs/calendar-input.js +0 -0
  144. package/components/custom/inputs/chip-input.js +136 -0
  145. package/components/custom/inputs/file-input.js +71 -0
  146. package/components/custom/inputs/mask-data/mask-data-input.js +80 -0
  147. package/components/custom/inputs/mask-data/mask-data-select.js +78 -0
  148. package/components/custom/inputs/mask-data/mask-data-textarea.js +75 -0
  149. package/components/custom/inputs/mask-data/reconcile-masked-edit.js +20 -0
  150. package/components/custom/jxl-decode.js +34 -0
  151. package/components/custom/jxl-image.js +68 -0
  152. package/components/custom/jxl-worker.js +12 -0
  153. package/components/custom/label.js +55 -0
  154. package/components/custom/layout.js +30 -0
  155. package/components/custom/multi-select-search-cc.js +179 -0
  156. package/components/custom/multiple-select-setting-tags.js +404 -0
  157. package/components/custom/multiple-selector.js +821 -0
  158. package/components/custom/pagination.js +95 -0
  159. package/components/custom/popup-confirm.js +120 -0
  160. package/components/custom/previews/facebook/facebook-appointment-booking-preview.js +83 -0
  161. package/components/custom/previews/facebook/facebook-audio-preview.js +130 -0
  162. package/components/custom/previews/facebook/facebook-event-preview.js +72 -0
  163. package/components/custom/previews/facebook/facebook-file-preview.js +95 -0
  164. package/components/custom/previews/facebook/facebook-group-preview.js +103 -0
  165. package/components/custom/previews/facebook/facebook-image-preview.js +81 -0
  166. package/components/custom/previews/facebook/facebook-page-preview.js +85 -0
  167. package/components/custom/previews/facebook/facebook-post-preview.js +77 -0
  168. package/components/custom/previews/facebook/facebook-reel-preview.js +83 -0
  169. package/components/custom/previews/facebook/facebook-sticker-preview.js +23 -0
  170. package/components/custom/previews/facebook/facebook-video-preview.js +114 -0
  171. package/components/custom/previews/facebook/index.js +24 -0
  172. package/components/custom/previews/file-preview.js +747 -0
  173. package/components/custom/previews/image-preview.js +288 -0
  174. package/components/custom/previews/instagram/index.js +4 -0
  175. package/components/custom/previews/instagram/instagram-share-post-preview.js +77 -0
  176. package/components/custom/previews/link-preview.js +166 -0
  177. package/components/custom/previews/product-preview.js +334 -0
  178. package/components/custom/previews/tiktok-order-preview.js +278 -0
  179. package/components/custom/previews/tiktok-product-preview.js +321 -0
  180. package/components/custom/previews/tiktok-voucher-preview.js +151 -0
  181. package/components/custom/previews/whatsapp/index.js +10 -0
  182. package/components/custom/previews/whatsapp/whatsapp-contact-card-preview.js +150 -0
  183. package/components/custom/previews/whatsapp/whatsapp-order-preview.js +38 -0
  184. package/components/custom/previews/whatsapp/whatsapp-poll-preview.js +29 -0
  185. package/components/custom/previews/whatsapp/whatsapp-product-preview.js +29 -0
  186. package/components/custom/retry-image.js +44 -0
  187. package/components/custom/search-input.js +26 -0
  188. package/components/custom/separator-with-text.js +14 -0
  189. package/components/custom/showToast.js +21 -0
  190. package/components/custom/sortable-header.js +62 -0
  191. package/components/custom/table-resize-line.js +92 -0
  192. package/components/custom/time-picker/index.js +73 -0
  193. package/components/custom/time-picker/time-picker-input.js +159 -0
  194. package/components/custom/time-picker/time-picker-utils.js +152 -0
  195. package/components/custom/viewers/audio-player.js +192 -0
  196. package/components/custom/viewers/code-viewer.js +140 -0
  197. package/components/custom/viewers/csv-viewer.js +174 -0
  198. package/components/custom/viewers/docx-viewer.js +145 -0
  199. package/components/custom/viewers/image-viewer.js +104 -0
  200. package/components/custom/viewers/markdown-viewer.js +124 -0
  201. package/components/custom/viewers/pdf-viewer.js +333 -0
  202. package/components/custom/viewers/spreadsheet-viewer.js +232 -0
  203. package/components/custom/viewers/unsupported-viewer.js +85 -0
  204. package/components/custom/viewers/video-player.js +241 -0
  205. package/components/customers-route/change-owner-modal.js +430 -0
  206. package/components/customers-route/contact-note-button.js +212 -0
  207. package/components/customers-route/customer-source.js +37 -0
  208. package/components/customers-route/table/columns.js +675 -0
  209. package/components/customers-route/table/edit-fields.js +125 -0
  210. package/components/customers-route/table/editable-cell.js +218 -0
  211. package/components/customers-route/table/note-cell.js +77 -0
  212. package/components/dialogs/invite-agent.js +677 -0
  213. package/components/error-boundary.js +53 -0
  214. package/components/leads-route/duplicate-phone-warning-toast.js +96 -0
  215. package/components/leads-route/table/columns.js +523 -0
  216. package/components/leads-route/table/contact-cell-name.js +82 -0
  217. package/components/leads-route/table/edit-fields.js +142 -0
  218. package/components/leads-route/table/editable-cell.js +228 -0
  219. package/components/org-tree/group-name-display.js +36 -0
  220. package/components/org-tree/group-tree-multi-select.js +208 -0
  221. package/components/org-tree/group-tree-select.js +220 -0
  222. package/components/posts-route/dialog-select-posts.js +284 -0
  223. package/components/reports-route/agents/multi-select-agents.js +55 -0
  224. package/components/reports-route/agents/multi-select-search-agents.js +43 -0
  225. package/components/settings-route/chat-layout/detail/constants.js +353 -0
  226. package/components/settings-route/chat-layout/detail/form.js +43 -0
  227. package/components/settings-route/chat-timeout/channel-multi-select.js +119 -0
  228. package/components/settings-route/multi-channel/facebook/hooks/use-facebook-sdk.js +3 -0
  229. package/components/settings-route/object-rule/util.js +79 -0
  230. package/components/settings-route/routing/settings-dialog.js +561 -0
  231. package/components/settings-route/switchboard/queue-new/constant.js +9 -0
  232. package/components/shared/linked-customer-fields/LinkedCustomerFields.js +91 -0
  233. package/components/shared/linked-customer-fields/use-linked-customer-fields.js +123 -0
  234. package/components/smartqc-route/qc-form/controls/multi-select-search-agents.js +50 -0
  235. package/components/ui/alert-dialog.js +112 -0
  236. package/components/ui/avatar.js +44 -0
  237. package/components/ui/badge.js +54 -0
  238. package/components/ui/button.js +48 -0
  239. package/components/ui/calendar.js +70 -0
  240. package/components/ui/card.js +58 -0
  241. package/components/ui/carousel.js +238 -0
  242. package/components/ui/checkbox.js +27 -0
  243. package/components/ui/command.js +111 -0
  244. package/components/ui/context-menu.js +154 -0
  245. package/components/ui/dialog.js +104 -0
  246. package/components/ui/dropdown-menu.js +169 -0
  247. package/components/ui/form.js +104 -0
  248. package/components/ui/input.js +115 -0
  249. package/components/ui/label.js +20 -0
  250. package/components/ui/popover.js +29 -0
  251. package/components/ui/radio-group.js +53 -0
  252. package/components/ui/scroll-area.js +38 -0
  253. package/components/ui/select.js +147 -0
  254. package/components/ui/separator.js +24 -0
  255. package/components/ui/sheet.js +113 -0
  256. package/components/ui/skeleton.js +17 -0
  257. package/components/ui/slider.js +26 -0
  258. package/components/ui/sonner.js +76 -0
  259. package/components/ui/switch.js +54 -0
  260. package/components/ui/table-v2.js +269 -0
  261. package/components/ui/table.js +245 -0
  262. package/components/ui/tabs.js +89 -0
  263. package/components/ui/textarea.js +33 -0
  264. package/components/ui/tooltip.js +40 -0
  265. package/components/workflow-editor/nodes/ticket/picker-field.js +233 -0
  266. package/components/zalo-login-qr.js +237 -0
  267. package/components/zalo-login-status-modal.js +45 -0
  268. package/config/env.js +27 -0
  269. package/config/hmr.js +8 -0
  270. package/config/navigation.js +54 -0
  271. package/config/runtime.js +46 -0
  272. package/config/theme.js +274 -0
  273. package/constants/brand-colors.js +32 -0
  274. package/constants/chat-icon.js +34 -0
  275. package/dist/chat.css +1 -0
  276. package/exceptions/throw-exception.js +15 -0
  277. package/hooks/use-chat-attachments.js +106 -0
  278. package/hooks/use-chat-configs.js +62 -0
  279. package/hooks/use-chat-customer.js +33 -0
  280. package/hooks/use-chat-display-config-sync.js +72 -0
  281. package/hooks/use-chat-permission.js +68 -0
  282. package/hooks/use-chat-room-detail.js +44 -0
  283. package/hooks/use-chat-selection.js +26 -0
  284. package/hooks/use-chat-sort.js +69 -0
  285. package/hooks/use-chat-surface.js +8 -0
  286. package/hooks/use-common-filter-options.js +70 -0
  287. package/hooks/use-crm-organization-filter-options.js +0 -0
  288. package/hooks/use-crm-organizations-data.js +23 -0
  289. package/hooks/use-debounce.js +14 -0
  290. package/hooks/use-dynamic-fields.js +7 -0
  291. package/hooks/use-facebook-notice.js +24 -0
  292. package/hooks/use-feature-flags.js +6 -0
  293. package/hooks/use-field-layout-override.js +46 -0
  294. package/hooks/use-file-upload.js +119 -0
  295. package/hooks/use-file-viewer.js +93 -0
  296. package/hooks/use-frozen-columns.js +29 -0
  297. package/hooks/use-horizontal-scroll.js +41 -0
  298. package/hooks/use-image-preview-queue.js +101 -0
  299. package/hooks/use-inline-editable-cell.js +97 -0
  300. package/hooks/use-intersection-observer.js +24 -0
  301. package/hooks/use-interval.js +20 -0
  302. package/hooks/use-latest-message-id.js +13 -0
  303. package/hooks/use-lead-filter-options.js +0 -0
  304. package/hooks/use-logout-listener.js +43 -0
  305. package/hooks/use-org-tree.js +43 -0
  306. package/hooks/use-permission.js +33 -0
  307. package/hooks/use-quick-filters.js +175 -0
  308. package/hooks/use-resizable-table.js +0 -0
  309. package/hooks/use-setting-common.js +389 -0
  310. package/hooks/use-settings.js +41 -0
  311. package/hooks/use-sort-bindings.js +24 -0
  312. package/hooks/use-table-display-config.js +0 -0
  313. package/hooks/use-tenant-config.js +99 -0
  314. package/hooks/use-update-user-config.js +150 -0
  315. package/hooks/use-zalo-connection.js +57 -0
  316. package/hooks/use-zalo-mention-list.js +31 -0
  317. package/index.css +624 -0
  318. package/index.js +100 -0
  319. package/lib/deal-name.js +27 -0
  320. package/lib/i18n/react.js +7 -0
  321. package/lib/i18n/use-language.js +0 -0
  322. package/lib/query-client.js +34 -0
  323. package/lib/utils.js +25 -0
  324. package/package.json +108 -0
  325. package/provider/chat-context.js +9 -0
  326. package/provider/chat-provider.js +177 -0
  327. package/provider/chat-room-context.js +9 -0
  328. package/provider/chat-room-hooks.js +8 -0
  329. package/provider/chat-room-provider.js +84 -0
  330. package/provider/hooks.js +22 -0
  331. package/provider/index.js +42 -0
  332. package/public/contact-card-user.svg +25 -0
  333. package/public/images/icons/facebook.svg +1 -0
  334. package/public/images/icons/instagram.svg +1 -0
  335. package/public/images/icons/shopee.svg +1 -0
  336. package/public/images/icons/whatsapp.svg +1 -0
  337. package/public/images/icons/zalo.png +0 -0
  338. package/public/images/images.jpg +0 -0
  339. package/public/images/ticket_sources/api.png +0 -0
  340. package/public/images/ticket_sources/email.png +0 -0
  341. package/public/images/ticket_sources/facebook.png +0 -0
  342. package/public/images/ticket_sources/hand.png +0 -0
  343. package/public/images/ticket_sources/instagram.png +0 -0
  344. package/public/images/ticket_sources/livechat.png +0 -0
  345. package/public/images/ticket_sources/shopee.svg +1 -0
  346. package/public/images/ticket_sources/sms.png +0 -0
  347. package/public/images/ticket_sources/telephone-call-in.png +0 -0
  348. package/public/images/ticket_sources/telephone-call-out.png +0 -0
  349. package/public/images/ticket_sources/tiktok.png +0 -0
  350. package/public/images/ticket_sources/tiktok_shop.png +0 -0
  351. package/public/images/ticket_sources/whatsapp.png +0 -0
  352. package/public/images/ticket_sources/whatsapp.svg +1 -0
  353. package/public/images/ticket_sources/zalo.png +0 -0
  354. package/public/placeholder.svg +13 -0
  355. package/store/app-status-store.js +17 -0
  356. package/store/chat-display-config-store.js +182 -0
  357. package/store/chat-display-config.resolvers.js +88 -0
  358. package/store/chat-store-helpers.js +62 -0
  359. package/store/chat-store.js +931 -0
  360. package/store/crm-organization-list-filter-store.js +126 -0
  361. package/store/customer-list-filter-store.js +139 -0
  362. package/store/devtools.js +9 -0
  363. package/store/duplicate-phone-warning-store.js +18 -0
  364. package/store/entity-extract-store.js +72 -0
  365. package/store/file-preview-store.js +27 -0
  366. package/store/filter-store.js +289 -0
  367. package/store/index.js +22 -0
  368. package/store/knowledge-crawl-store.js +53 -0
  369. package/store/lead-list-filter-store.js +142 -0
  370. package/store/list-pagination-state.js +44 -0
  371. package/store/lumi-learn-from-chat-store.js +69 -0
  372. package/store/merge-suggestion-store.js +27 -0
  373. package/store/notification-store.js +63 -0
  374. package/store/preview-image-store.js +22 -0
  375. package/store/settings-store/multi-channel/email-list-store.js +20 -0
  376. package/store/settings-store/multi-channel/facebook-list-store.js +22 -0
  377. package/store/settings-store/multi-channel/google-list-store.js +20 -0
  378. package/store/settings-store/multi-channel/instagram-list-store.js +20 -0
  379. package/store/settings-store/multi-channel/misa-list-store.js +20 -0
  380. package/store/settings-store/multi-channel/nhanhvn-list-store.js +20 -0
  381. package/store/settings-store/multi-channel/shopee-list-store.js +20 -0
  382. package/store/settings-store/multi-channel/shopee-order-list-store.js +20 -0
  383. package/store/settings-store/multi-channel/sms-list-store.js +20 -0
  384. package/store/settings-store/multi-channel/tiktok-business-list-store.js +20 -0
  385. package/store/settings-store/multi-channel/tiktok-list-store.js +20 -0
  386. package/store/settings-store/multi-channel/zalo-list-store.js +22 -0
  387. package/store/settings-store/multi-channel/zalo-user-list-store.js +25 -0
  388. package/store/smart-qc-store/smart-qc-list-filter-store.js +70 -0
  389. package/store/tenant-config-store.js +21 -0
  390. package/store/user-detail-store.js +17 -0
  391. package/store/user-status-store.js +33 -0
  392. package/store/workflow-store.js +24 -0
  393. package/store/workspace-store.js +32 -0
  394. package/tailwind-preset.js +129 -0
  395. package/utils/chat-actions.js +414 -0
  396. package/utils/chat_socket.js +1225 -0
  397. package/utils/extra-columns.js +108 -0
  398. package/utils/file.js +316 -0
  399. package/utils/html-to-plain-text.js +12 -0
  400. package/utils/notification.js +71 -0
  401. package/utils/replaceZaloSprite.js +64 -0
  402. package/utils/show-be-error.js +17 -0
  403. package/utils/typing_socket.js +101 -0
@@ -0,0 +1,1225 @@
1
+ import i18n from "@zenify-omni/chat-js/lib/i18n/index.js";
2
+ import { createChatSocket } from "@zenify-omni/chat-js/socket/index.js";
3
+ import { registerChatTransport } from "@zenify-omni/chat-js/transport-registry.js";
4
+ import { PRE_LOGOUT_EVENT } from "@zenify-omni/chat-js/constants/events.js";
5
+ import { v4 as uuidv4 } from "uuid";
6
+ import { getUser, isAdmin, isRoleTeamLeader, isSupervisor } from "@zenify-omni/chat-js/utils/auth.js";
7
+ import {
8
+ buildChatMessage,
9
+ ChatFilterAssignStatus,
10
+ ChatMessageKey,
11
+ ChatSource,
12
+ ContentfulMessageTypes,
13
+ InviteStatus,
14
+ MessageType,
15
+ PinMessageTypes,
16
+ ReminderMessageTypes,
17
+ AgentReplyMessageTypes
18
+ } from "@zenify-omni/chat-js/constants/chat-message.js";
19
+ import useChatStore from "../store/chat-store.js";
20
+ import useLumiLearnFromChatStore from "../store/lumi-learn-from-chat-store.js";
21
+ import useEntityExtractStore from "../store/entity-extract-store.js";
22
+ import useKnowledgeCrawlStore from "../store/knowledge-crawl-store.js";
23
+ import {
24
+ LUMI_LEARN_FROM_CHAT_SOCKET_EVENT,
25
+ ENTITY_EXTRACT_SOCKET_EVENT,
26
+ KNOWLEDGE_CRAWL_SOCKET_EVENT
27
+ } from "@zenify-omni/chat-js/constants/chatbot.js";
28
+ import { queryClient } from "../config/runtime.js";
29
+ import { isChatVisible, navigate } from "../config/navigation.js";
30
+ import { pushNotification } from "./notification.js";
31
+ import QUERY_KEY from "@zenify-omni/chat-js/constants/use-query-key.js";
32
+ import {
33
+ getChatListV2,
34
+ leaveChat as leaveChatApi
35
+ } from "@zenify-omni/chat-js/api/chat.js";
36
+ import { SeniorStaffRoles, UserRole } from "@zenify-omni/chat-js/constants/user.js";
37
+ import { isInArray } from "@zenify-omni/chat-js/utils/data.js";
38
+ import { throwException } from "../exceptions/throw-exception.js";
39
+ import { buildQueryParams } from "../components/chat-route/chat-list/util.js";
40
+ import { getAllAgent } from "@zenify-omni/chat-js/api/report.js";
41
+ import {
42
+ isFacebook,
43
+ isZaloGroup
44
+ } from "../components/chat-route/util.js";
45
+ import { CHATTER_TYPES } from "../components/chat-route/chat-box/constants.js";
46
+ import { receiveInvite, getChatMessageList } from "@zenify-omni/chat-js/api/chatroom.js";
47
+ import { debouncedInvalidate } from "@zenify-omni/chat-js/utils/debounced-invalidate.js";
48
+ import { env } from "../config/env.js";
49
+ import { onModuleDispose } from "../config/hmr.js";
50
+ import {
51
+ getTabStatusFromChat,
52
+ getClosedSessionPayload,
53
+ getReceivedSessionPayload,
54
+ isClosedChatBySession
55
+ } from "../store/chat-store-helpers.js";
56
+ import {
57
+ applyEndedChat,
58
+ applyJoinedChat,
59
+ applyReceivedChat,
60
+ applyReopenedChat,
61
+ isJoinHandled,
62
+ isReceiveHandled,
63
+ isChatListSearching,
64
+ queueRoomForChatList,
65
+ limitedGetChatRoomDetail,
66
+ addUnhiddenChats
67
+ } from "./chat-actions.js";
68
+ let socket = null;
69
+ let clientId = null;
70
+ let reconnectDebounceTimer = null;
71
+ let reconnectResyncCursor = null;
72
+ const onSocketConnect = () => {
73
+ console.log("Connected to chat server");
74
+ useChatStore.getState().setIsSocketConnected(true);
75
+ };
76
+ const onSocketReconnect = (attempt) => {
77
+ useChatStore.getState().setIsSocketConnected(true);
78
+ if (reconnectDebounceTimer) clearTimeout(reconnectDebounceTimer);
79
+ reconnectDebounceTimer = setTimeout(() => {
80
+ reconnectDebounceTimer = null;
81
+ handleReconnect(attempt);
82
+ }, 500);
83
+ };
84
+ const handleReconnect = async (attempt) => {
85
+ console.log(`Reconnected to chat server (attempt ${attempt})`);
86
+ const onChatScreen = isChatVisible();
87
+ const store = useChatStore.getState();
88
+ if (onChatScreen) {
89
+ store.setIsSilentRefreshChatList(true);
90
+ store.resetLastRefreshTimeTicketPage();
91
+ }
92
+ const { currentChatId, setTicketChatMessages } = useChatStore.getState();
93
+ if (onChatScreen && currentChatId) {
94
+ }
95
+ if (!currentChatId) {
96
+ reconnectResyncCursor = null;
97
+ return;
98
+ }
99
+ const cursor = reconnectResyncCursor;
100
+ reconnectResyncCursor = null;
101
+ if (!cursor || cursor.roomId !== currentChatId || !cursor.lastMessageId)
102
+ return;
103
+ const roomIdAtStart = currentChatId;
104
+ const params = {
105
+ limit: 1e3,
106
+ sort: "-created_at -_id",
107
+ type: "cursor",
108
+ ignore_state: true,
109
+ until_id: cursor.lastMessageId
110
+ };
111
+ try {
112
+ const { data } = await getChatMessageList(currentChatId, { params });
113
+ if (useChatStore.getState().currentChatId !== roomIdAtStart) {
114
+ useChatStore.getState().markRoomUnloaded(roomIdAtStart);
115
+ return;
116
+ }
117
+ const items = data?.items || [];
118
+ if (!items.length) return;
119
+ const current = useChatStore.getState().ticketChatMessages[currentChatId] || [];
120
+ setTicketChatMessages(currentChatId, [...current, ...items]);
121
+ } catch (error) {
122
+ console.error("Error fetching messages after reconnecting:", error);
123
+ }
124
+ };
125
+ const onLumiLearnFromChat = (payload) => {
126
+ if (!payload?.type || !payload?.bot_id) return;
127
+ if (String(payload.user_id) !== String(getUser()?._id)) return;
128
+ useLumiLearnFromChatStore.getState().ingestEvent(payload);
129
+ };
130
+ const onEntityExtract = (payload) => {
131
+ if (!payload?.type || !payload?.bot_id) return;
132
+ if (!payload?.data?.source_link) return;
133
+ if (String(payload.user_id) !== String(getUser()?._id)) return;
134
+ useEntityExtractStore.getState().ingestEvent(payload);
135
+ };
136
+ const onKnowledgeCrawl = (payload) => {
137
+ if (!payload?.type || !payload?.data?.knowledge_id) return;
138
+ if (payload.user_id && String(payload.user_id) !== String(getUser()?._id)) {
139
+ return;
140
+ }
141
+ useKnowledgeCrawlStore.getState().ingestEvent(payload);
142
+ };
143
+ const onConnectError = (error) => {
144
+ useChatStore.getState().setIsSocketConnected(false);
145
+ console.error("Chat server connection error", error);
146
+ };
147
+ const onDisconnectError = (reason) => {
148
+ useChatStore.getState().setIsSocketConnected(false);
149
+ const onChatScreen = isChatVisible();
150
+ const { currentChatId, ticketChatMessages } = useChatStore.getState();
151
+ if (onChatScreen && currentChatId && !reconnectResyncCursor) {
152
+ const lastMessageId = (ticketChatMessages[currentChatId] || []).findLast(
153
+ (message) => message?._id && message.status === "success"
154
+ )?._id;
155
+ if (lastMessageId) {
156
+ reconnectResyncCursor = {
157
+ roomId: currentChatId,
158
+ lastMessageId
159
+ };
160
+ }
161
+ }
162
+ useChatStore.getState().resetLoadedRooms();
163
+ console.error("Chat server disconnect error", reason);
164
+ if (onChatScreen && currentChatId) {
165
+ }
166
+ };
167
+ const addContentfulMessage = async (chat) => {
168
+ const room_id = chat.room_id;
169
+ if (!room_id) return;
170
+ if (!chat._id) return;
171
+ const chatStore = useChatStore.getState();
172
+ let oldTab = null;
173
+ let chatItem = chatStore.chatList.find((x) => x._id === room_id);
174
+ const existedInList = !!chatItem;
175
+ if (chatItem) {
176
+ oldTab = getTabStatusFromChat(chatItem);
177
+ }
178
+ if (!chatItem) {
179
+ const isLoaded = chatStore.currentChatId === room_id || !!chatStore.loadedRooms[room_id];
180
+ if (isLoaded) {
181
+ chatStore.addNewMessage(room_id, {
182
+ ...chat,
183
+ _id: chat._id,
184
+ chatter_id: chat.chatter_id,
185
+ chatter_name: chat?.chatter_name,
186
+ chatter_type: chat.chatter_type,
187
+ content: chat.content,
188
+ status: chat.status,
189
+ type: chat.type,
190
+ created_at: chat.created_at,
191
+ updated_at: chat.updated_at,
192
+ source: chat.source
193
+ });
194
+ return;
195
+ }
196
+ if (!isChatVisible()) return;
197
+ if (isChatListSearching()) return;
198
+ queueRoomForChatList(room_id);
199
+ return;
200
+ }
201
+ if (chatItem) {
202
+ const liveStore = useChatStore.getState();
203
+ const currentChatId = liveStore.currentChatId;
204
+ const isCommentFb = chat?.content?.type === MessageType.ACTION_COMMENT_FB;
205
+ const msgContentType = chat?.content?.type;
206
+ const isAgentOrBot = [CHATTER_TYPES.AGENT, CHATTER_TYPES.BOT].includes(
207
+ chat?.chatter_type
208
+ );
209
+ const isCustomerChatter = [
210
+ CHATTER_TYPES.CUSTOMER,
211
+ CHATTER_TYPES.CONTACT
212
+ ].includes(chat?.chatter_type);
213
+ const isUndoMessage = chat.extra_data && chat.extra_data.is_undo === true && !!chat.extra_data.undo_original;
214
+ const isDeleteMessage = chat.extra_data && chat.extra_data.is_deleted === true && !!chat.extra_data.deleted_original;
215
+ const isMessageReadByContact = !!chat.read_receipt;
216
+ const isReaction = (
217
+ // Tin nhắn có extra_data chứa reactions array nhưng content type không phải reaction (chatting fallback khi nhận về trước khi pzalo trẳ về)
218
+ chat.content && chat.content.type !== MessageType.REACTION && chat.extra_data && Array.isArray(chat.extra_data.reactions)
219
+ );
220
+ const roomMessages = liveStore.ticketChatMessages[room_id] || [];
221
+ const lastRoomMessage = roomMessages[roomMessages.length - 1];
222
+ const isReactionUndoOrDelete = isReaction || isUndoMessage || isDeleteMessage;
223
+ const isLastRoomMessage = !!lastRoomMessage && lastRoomMessage._id === chat._id;
224
+ const shouldKeepLastMsg = isReactionUndoOrDelete && !isLastRoomMessage;
225
+ const shouldSkipUpdateTime = isReactionUndoOrDelete && isLastRoomMessage;
226
+ const isAgentReplySuccess = isAgentOrBot && AgentReplyMessageTypes.includes(msgContentType) && !isUndoMessage && !isDeleteMessage && !isReaction && chat.status === "success";
227
+ const shouldStopSla = isAgentReplySuccess && chat?.chatter_type === CHATTER_TYPES.AGENT;
228
+ const isUnanswered = isAgentReplySuccess ? false : isCustomerChatter ? true : chatItem.is_unanswered;
229
+ const isReopeningFromClosed = isClosedChatBySession(chatItem?.session) && !isClosedChatBySession(chat?.session);
230
+ const newChatItem = {
231
+ ...chatItem,
232
+ last_msg: isCommentFb || shouldKeepLastMsg || isMessageReadByContact ? chatItem.last_msg : {
233
+ ...chatItem.last_msg || {},
234
+ ...chat?.content || {},
235
+ ...shouldSkipUpdateTime ? {} : { time: chat.updated_at },
236
+ chatter_name: chat?.chatter_name,
237
+ chatter_type: chat?.chatter_type
238
+ },
239
+ // Tin chưa trả lời (tin khách) -> tăng unread; ngược lại (agent/bot đã
240
+ // reply) -> reset về 0. Nếu agent đang mở room này thì không tăng unread.
241
+ // isUnanswered=true + agent/bot (vd: status chưa 'success' do server không
242
+ // gửi field này): vẫn reset về 0 vì agent đang reply, badge phải biến mất.
243
+ unread: isUnanswered && room_id !== currentChatId ? isCustomerChatter ? isReopeningFromClosed ? 1 : (chatItem.unread || 0) + 1 : isAgentOrBot ? 0 : chatItem.unread : 0,
244
+ is_unanswered: isUnanswered,
245
+ // Agent đã trả lời -> gỡ badge sla_status (WARNING/BREACHED) trên chat
246
+ // item. Clear ngay tại đây vì newChatItem là bản append cuối cùng vào
247
+ // list, patch chỗ khác sẽ bị nó đè lại.
248
+ ...shouldStopSla ? { sla_status: "" } : {},
249
+ ...isClosedChatBySession(chat?.session) ? getClosedSessionPayload() : getReceivedSessionPayload(chat?.session?.is_received || false),
250
+ ...oldTab === ChatFilterAssignStatus.Closed ? { assigned: null } : {}
251
+ };
252
+ if (shouldStopSla) {
253
+ }
254
+ const isGroupZalo = isZaloGroup(chatItem);
255
+ const chatter_name = isGroupZalo && chat.status === "pending" ? chatItem.source_name + ` (${chat?.chatter_name})` : chat?.chatter_name;
256
+ const message = {
257
+ ...chat,
258
+ _id: chat._id,
259
+ chatter_id: chat.chatter_id,
260
+ chatter_name,
261
+ chatter_type: chat.chatter_type,
262
+ content: chat.content,
263
+ status: chat.status,
264
+ type: chat.type,
265
+ created_at: chat.created_at,
266
+ updated_at: chat.updated_at,
267
+ source: chat.source
268
+ };
269
+ const isLoaded = currentChatId === room_id || !!liveStore.loadedRooms[room_id];
270
+ if (isLoaded) {
271
+ chatStore.addNewMessage(room_id, message);
272
+ if (isReaction) {
273
+ chatStore.confirmPendingReaction(room_id, chat._id);
274
+ }
275
+ }
276
+ if (isLoaded && chat.read_receipt) {
277
+ useChatStore.getState().applyReadReceipt(room_id, chat.read_receipt);
278
+ }
279
+ if (isLoaded && message.seq != null && message.status === "success") {
280
+ const roomReads = useChatStore.getState().roomReadUpdates[room_id] ?? {};
281
+ const toApply = Object.entries(roomReads).filter(
282
+ ([, data]) => data.lastReadSeq != null && data.lastReadSeq >= message.seq
283
+ );
284
+ if (toApply.length > 0) {
285
+ useChatStore.setState((state) => ({
286
+ ticketChatMessages: {
287
+ ...state.ticketChatMessages,
288
+ [room_id]: (state.ticketChatMessages[room_id] ?? []).map((m) => {
289
+ if (m._id !== message._id) return m;
290
+ const existing = new Set((m.agent_reads ?? []).map((r) => r._id));
291
+ const newReads = toApply.filter(([uid]) => !existing.has(uid)).map(([uid, data]) => ({
292
+ _id: uid,
293
+ name: data.name,
294
+ avatar: data.avatar,
295
+ readed_at: data.readAt
296
+ }));
297
+ if (newReads.length === 0) return m;
298
+ return {
299
+ ...m,
300
+ agent_reads: [...m.agent_reads ?? [], ...newReads]
301
+ };
302
+ })
303
+ }
304
+ }));
305
+ }
306
+ }
307
+ const unansweredFilter = liveStore.filterState?.is_unanswered;
308
+ const noLongerMatchesUnansweredFilter = unansweredFilter === "true" && !isUnanswered || unansweredFilter === "false" && isUnanswered;
309
+ if (noLongerMatchesUnansweredFilter && existedInList) {
310
+ chatStore.removeChatFromList(room_id);
311
+ return;
312
+ }
313
+ const newTab = getTabStatusFromChat(newChatItem);
314
+ if (existedInList && oldTab !== newTab) {
315
+ chatStore.transitionChatTab(oldTab, newTab, newChatItem);
316
+ chatStore.resetLastRefreshTimeTicketPage();
317
+ }
318
+ chatStore.appendChats({
319
+ tab: newTab,
320
+ chats: [newChatItem]
321
+ });
322
+ }
323
+ };
324
+ const onAddMessage = async (data) => {
325
+ const resetLastRefreshTimeTicketPage = useChatStore.getState().resetLastRefreshTimeTicketPage;
326
+ const msgType = data.content.type;
327
+ if (!msgType) return;
328
+ if (ContentfulMessageTypes.includes(msgType) || ReminderMessageTypes.includes(msgType) || PinMessageTypes.includes(msgType)) {
329
+ if (PinMessageTypes.includes(msgType)) {
330
+ debouncedInvalidate(
331
+ queryClient,
332
+ [QUERY_KEY.GET_PINNED_MESSAGES, data.room_id],
333
+ { delay: 500 }
334
+ );
335
+ }
336
+ if (data.client_id === clientId) {
337
+ return;
338
+ }
339
+ await addContentfulMessage(data);
340
+ return;
341
+ }
342
+ const roomId = data.room_id;
343
+ if (!roomId) return;
344
+ switch (msgType) {
345
+ case MessageType.ACTION_END: {
346
+ applyEndedChat(data);
347
+ debouncedInvalidate(queryClient, [
348
+ QUERY_KEY.GET_DISTRIBUTION_LOGS,
349
+ roomId
350
+ ]);
351
+ return;
352
+ }
353
+ case MessageType.ACTION_RECEIVE: {
354
+ if (isReceiveHandled(roomId, data.chatter_id)) return;
355
+ await applyReceivedChat(data);
356
+ debouncedInvalidate(queryClient, [
357
+ QUERY_KEY.GET_DISTRIBUTION_LOGS,
358
+ roomId
359
+ ]);
360
+ return;
361
+ }
362
+ case MessageType.ACTION_REOPEN: {
363
+ await applyReopenedChat(data);
364
+ debouncedInvalidate(queryClient, [
365
+ QUERY_KEY.GET_DISTRIBUTION_LOGS,
366
+ roomId
367
+ ]);
368
+ return;
369
+ }
370
+ case MessageType.ACTION_LEAVE: {
371
+ const store = useChatStore.getState();
372
+ const isLoaded = roomId === store.currentChatId || !!store.loadedRooms[roomId];
373
+ if (!isLoaded) return;
374
+ store.addNewMessage(
375
+ roomId,
376
+ buildChatMessage({
377
+ id: data._id,
378
+ roomId,
379
+ tenantId: data.tenant_id,
380
+ source: data.source,
381
+ chatterId: data.chatter_id,
382
+ chatterName: data.chatter_name,
383
+ chatterType: data.chatter_type,
384
+ type: MessageType.ACTION_LEAVE,
385
+ status: data.status,
386
+ createdAt: data.created_at,
387
+ updatedAt: data.updated_at,
388
+ content: data.content
389
+ })
390
+ );
391
+ store.updateChatDetail(roomId, {
392
+ last_msg: {
393
+ _id: data._id,
394
+ msg_id: data._id,
395
+ text: data.content?.text,
396
+ type: data.content?.type,
397
+ time: data.created_at,
398
+ chatter_type: data.chatter_type,
399
+ chatter_name: data.chatter_name,
400
+ chatter_id: data.chatter_id,
401
+ // Socket gửi removed_by_* trong content, còn preview (chat-item.jsx)
402
+ // đọc chúng ở cấp cao nhất của last_msg vì API gắn sẵn ở đó. Không
403
+ // nâng lên thì last_msg mới (updateChatDetail ghi đè cả object) mất
404
+ // hai trường này ⇒ danh sách chat báo "tự rời" thay vì "bị X xóa",
405
+ // chỉ đúng lại sau khi reload.
406
+ removed_by_id: data.content?.removed_by_id,
407
+ removed_by_name: data.content?.removed_by_name,
408
+ ...data.session_id ? { session_id: data.session_id } : {}
409
+ }
410
+ });
411
+ return;
412
+ }
413
+ case MessageType.ACTION_TIKTOK_END_SESSION: {
414
+ const store = useChatStore.getState();
415
+ const isLoaded = roomId === store.currentChatId || !!store.loadedRooms[roomId];
416
+ if (!isLoaded) return;
417
+ const message = buildChatMessage({
418
+ id: data._id,
419
+ roomId,
420
+ tenantId: data.tenant_id,
421
+ source: data.source,
422
+ chatterId: data.chatter_id,
423
+ chatterName: data.chatter_name,
424
+ chatterType: data.chatter_type,
425
+ type: MessageType.ACTION_TIKTOK_END_SESSION,
426
+ status: data.status,
427
+ createdAt: data.created_at,
428
+ updatedAt: data.updated_at,
429
+ content: data.content
430
+ });
431
+ store.addNewMessage(roomId, message);
432
+ return;
433
+ }
434
+ case MessageType.ZALO_ACCEPT_FRIEND: {
435
+ await addContentfulMessage(data);
436
+ const timeoutId = setTimeout(() => {
437
+ resetLastRefreshTimeTicketPage();
438
+ clearTimeout(timeoutId);
439
+ }, 3e3);
440
+ return;
441
+ }
442
+ case MessageType.ZALO_CREATE_GROUP:
443
+ case MessageType.ZALO_CALL: {
444
+ await addContentfulMessage(data);
445
+ return;
446
+ }
447
+ default:
448
+ return;
449
+ }
450
+ };
451
+ const getTicketInfoWithFilter = async (ticketId) => {
452
+ try {
453
+ const filterState = {
454
+ ...useChatStore.getState().filterState,
455
+ ticket_ids: [ticketId],
456
+ assign_status: null
457
+ };
458
+ const params = buildQueryParams(filterState);
459
+ const { data } = await getChatListV2({ params });
460
+ if (data && data.list && data.list.length > 0) {
461
+ return data.list[0];
462
+ } else {
463
+ return null;
464
+ }
465
+ } catch (error) {
466
+ console.error("Error fetching ticket detail:", error);
467
+ return null;
468
+ }
469
+ };
470
+ const getTicketChatDetail = async (ticketId) => getTicketInfoWithFilter(ticketId);
471
+ const onRequestContactChat = async (data) => {
472
+ const ticketId = data._id;
473
+ if (!ticketId) return;
474
+ const chatStore = useChatStore.getState();
475
+ const chatList = chatStore.chatList || [];
476
+ if (chatList.some((item) => item._id === ticketId)) return;
477
+ const currentUser = getUser();
478
+ if (!currentUser) return;
479
+ try {
480
+ let updatedTicket = await getTicketChatDetail(ticketId);
481
+ if (!updatedTicket) return;
482
+ let chatItem = null;
483
+ let agentInGroups = [];
484
+ if (isRoleTeamLeader()) {
485
+ try {
486
+ const { data: data2, error } = await getAllAgent(
487
+ JSON.stringify([currentUser.group_id])
488
+ );
489
+ if (!error && data2 && data2?.list?.length) {
490
+ agentInGroups = data2?.list?.map((agent) => agent._id);
491
+ }
492
+ } catch (e) {
493
+ console.error("Error fetching agents in groups:", e);
494
+ }
495
+ }
496
+ const isTeamLeaderOfGroup = isRoleTeamLeader() && (data?.group_id && data.group_ids?.includes(currentUser.group_id) || Number(currentUser.group_id) === Number(updatedTicket?.group_id) || agentInGroups?.includes(updatedTicket?.assign_agent_id) || agentInGroups?.some((id) => !!isInArray(id, updatedTicket?.in_room)));
497
+ const isAssignedAgent = updatedTicket.assign_agent_id === currentUser._id || updatedTicket.agent_uid === currentUser._id;
498
+ const isInRoom = isInArray(currentUser._id, updatedTicket?.in_room);
499
+ if (isAdmin() || isSupervisor() || isTeamLeaderOfGroup || isAssignedAgent || isInRoom) {
500
+ chatItem = {
501
+ ...updatedTicket,
502
+ receiver_id: updatedTicket?.receiver_id || null
503
+ // Ensure receiver_id is set
504
+ };
505
+ }
506
+ if (!chatItem) {
507
+ console.log("No permission to access this chat");
508
+ return;
509
+ }
510
+ joinChat(chatItem._id);
511
+ const tabChat = chatItem?.receiver_id ? ChatFilterAssignStatus.Received : ChatFilterAssignStatus.NotReceived;
512
+ useChatStore.getState().appendChats({
513
+ source: ChatSource.Socket,
514
+ tab: tabChat,
515
+ chats: [chatItem]
516
+ });
517
+ if (useChatStore.getState().currentChatId === chatItem._id) {
518
+ useChatStore.getState().resetLastRefreshTimeTicketPage();
519
+ }
520
+ } catch (error) {
521
+ console.error("Error in onRequestContactChat:", error);
522
+ }
523
+ };
524
+ const onInviteChat = async (data) => {
525
+ const user = getUser();
526
+ if (!user) return;
527
+ switch (data.status) {
528
+ case InviteStatus.INVITE: {
529
+ if (!Array.isArray(data.userIds) || !data.userIds.includes(user._id))
530
+ return;
531
+ const payload = {
532
+ chat_room_id: data.chat_room_id,
533
+ chat_session_id: data.chat_session_id
534
+ };
535
+ pushNotification(
536
+ i18n.t("common:nav.notification", { defaultValue: "Th\xF4ng b\xE1o" }),
537
+ {
538
+ // description: `${fromUserName} vừa mời bạn vào cuộc hội thoại!`,
539
+ description: data.message,
540
+ duration: Infinity,
541
+ notificationKey: `invite-${data.chat_room_id}`,
542
+ action: {
543
+ label: i18n.t("common:action.agree", { defaultValue: "\u0110\u1ED3ng \xFD" }),
544
+ onClick: () => acceptInviteChat(payload)
545
+ },
546
+ cancel: {
547
+ label: i18n.t("common:action.reject", { defaultValue: "T\u1EEB ch\u1ED1i" }),
548
+ onClick: () => rejectInviteChat(payload)
549
+ }
550
+ }
551
+ );
552
+ return;
553
+ }
554
+ case InviteStatus.ACCEPT: {
555
+ const lastMessage = data?.data || null;
556
+ if (!lastMessage) return;
557
+ const roomId = lastMessage.room_id;
558
+ if (!isJoinHandled(roomId, lastMessage.chatter_id)) {
559
+ await applyJoinedChat(lastMessage);
560
+ }
561
+ if (data.message && Array.isArray(data.notifyUserIds) && data.notifyUserIds.length > 0 && data.notifyUserIds.includes(user?._id)) {
562
+ pushNotification(
563
+ i18n.t("common:nav.notification", { defaultValue: "Th\xF4ng b\xE1o" }),
564
+ {
565
+ description: data.message
566
+ }
567
+ );
568
+ }
569
+ return;
570
+ }
571
+ case InviteStatus.REJECT:
572
+ return;
573
+ default:
574
+ return;
575
+ }
576
+ };
577
+ async function onDistributeChat(data) {
578
+ const chatStore = useChatStore.getState();
579
+ const {
580
+ chatRoomId,
581
+ userIds,
582
+ notify_user_ids: notifyUserIds,
583
+ redistribution_force: redistributionForce,
584
+ redistribution_message: redistributionMessage
585
+ } = data;
586
+ const user = getUser();
587
+ if (!user) return;
588
+ const params = buildQueryParams(chatStore.filterState || {});
589
+ const chat = chatStore.chatList.find((x) => x._id === chatRoomId);
590
+ if (Array.isArray(userIds) && userIds.includes(user._id) || isAdmin() || isSupervisor()) {
591
+ const isAssigned = Array.isArray(userIds) && userIds.includes(user._id);
592
+ if (isChatVisible() && (chat || isAssigned)) {
593
+ const { data: newChat } = await limitedGetChatRoomDetail(
594
+ chatRoomId,
595
+ params
596
+ );
597
+ if (newChat) {
598
+ if (!chat) {
599
+ const tab = getTabStatusFromChat(newChat);
600
+ chatStore.appendChats({
601
+ tab,
602
+ chats: [newChat]
603
+ });
604
+ } else {
605
+ const oldTab = getTabStatusFromChat(chat);
606
+ const newTab = getTabStatusFromChat(newChat);
607
+ chatStore.transitionChatTab(oldTab, newTab, newChat);
608
+ chatStore.moveChat({
609
+ roomId: chatRoomId,
610
+ payload: {
611
+ ...getReceivedSessionPayload(
612
+ newChat?.session?.is_received || false
613
+ )
614
+ }
615
+ });
616
+ const nextAssignedId = newChat?.session?.assigned_id;
617
+ if (nextAssignedId) {
618
+ const inRoom = newChat?.users_in_room?.find(
619
+ (x) => x._id === nextAssignedId
620
+ );
621
+ chatStore.updateChatDetail(chatRoomId, {
622
+ assigned: {
623
+ _id: nextAssignedId,
624
+ name: inRoom?.name
625
+ }
626
+ });
627
+ } else {
628
+ chatStore.updateChatDetail(chatRoomId, { assigned: null });
629
+ }
630
+ }
631
+ }
632
+ }
633
+ if (Array.isArray(notifyUserIds) && notifyUserIds.includes(user._id)) {
634
+ pushNotification(
635
+ i18n.t("common:nav.notification", { defaultValue: "Th\xF4ng b\xE1o" }),
636
+ {
637
+ // Phân bổ lại (redistribution_force): dùng text do BE build sẵn
638
+ // (vd "Hệ thống đã phân bổ lại hội thoại cho ...") thay cho text mặc định.
639
+ description: redistributionForce && redistributionMessage ? redistributionMessage : i18n.t("common:toast.assignedNewConversation", {
640
+ defaultValue: "B\u1EA1n \u0111\xE3 \u0111\u01B0\u1EE3c g\xE1n v\xE0o cu\u1ED9c h\u1ED9i tho\u1EA1i m\u1EDBi"
641
+ }),
642
+ duration: 5e3
643
+ }
644
+ );
645
+ }
646
+ }
647
+ if (chatStore.currentChatId === chatRoomId) {
648
+ chatStore.resetLastRefreshTimeTicketPage();
649
+ }
650
+ debouncedInvalidate(queryClient, [
651
+ QUERY_KEY.GET_DISTRIBUTION_LOGS,
652
+ chatRoomId
653
+ ]);
654
+ }
655
+ function keepsRoomVisibilityByRole(user) {
656
+ const { roles = [], role } = user || {};
657
+ return [...roles, role].filter(Boolean).some((r) => SeniorStaffRoles.includes(r));
658
+ }
659
+ function onRemoveUserFromChat(data) {
660
+ const chatStore = useChatStore.getState();
661
+ const { chatRoomId, userIds, actionBy, source } = data || {};
662
+ const user = getUser();
663
+ if (!user || !chatRoomId || !Array.isArray(userIds)) return;
664
+ const refreshRoomDetail = () => {
665
+ if (chatStore.currentChatId === chatRoomId) {
666
+ chatStore.resetLastRefreshTimeTicketPage();
667
+ return;
668
+ }
669
+ queryClient.removeQueries({
670
+ predicate: (query) => query.queryKey?.[0] === QUERY_KEY.GET_CHAT_ROOM_DETAIL && query.queryKey?.[1]?.chatRoomId === chatRoomId
671
+ });
672
+ };
673
+ if (!userIds.includes(user._id)) {
674
+ refreshRoomDetail();
675
+ return;
676
+ }
677
+ const keepsVisibility = source === "remove_members" && keepsRoomVisibilityByRole(user);
678
+ const isSelfLeave = String(actionBy?._id ?? "") === String(user._id);
679
+ if (actionBy?.name && !isSelfLeave) {
680
+ pushNotification(i18n.t("common:nav.notification"), {
681
+ description: keepsVisibility ? i18n.t("chat:message.youWereRemovedButCanStillView", {
682
+ actor: actionBy.name,
683
+ defaultValue: "B\u1EA1n \u0111\xE3 b\u1ECB {actor} x\xF3a kh\u1ECFi \u0111o\u1EA1n chat, kh\xF4ng g\u1EEDi \u0111\u01B0\u1EE3c tin nh\u1EAFn n\u1EEFa"
684
+ }) : i18n.t("chat:message.youWereRemovedFromChat", {
685
+ actor: actionBy.name,
686
+ defaultValue: "B\u1EA1n \u0111\xE3 b\u1ECB {actor} x\xF3a kh\u1ECFi \u0111o\u1EA1n chat"
687
+ })
688
+ });
689
+ }
690
+ if (keepsVisibility) {
691
+ refreshRoomDetail();
692
+ return;
693
+ }
694
+ chatStore.removeChat(chatRoomId);
695
+ if (chatStore.currentChatId === chatRoomId) {
696
+ navigate({ to: "/chat" });
697
+ }
698
+ }
699
+ function onHideChatRoom(data) {
700
+ const { room_ids: roomIds } = data;
701
+ if (!roomIds?.length) return;
702
+ const chatStore = useChatStore.getState();
703
+ const currentChatId = chatStore.currentChatId;
704
+ const shouldNavigateAway = roomIds.includes(currentChatId) && isChatVisible();
705
+ roomIds.forEach((roomId) => chatStore.removeChat(roomId));
706
+ if (shouldNavigateAway) {
707
+ navigate({ to: "/chat" });
708
+ }
709
+ }
710
+ function onUnhideChatRoom(data) {
711
+ const { room_ids: roomIds } = data;
712
+ if (!roomIds?.length) return;
713
+ addUnhiddenChats(roomIds);
714
+ }
715
+ async function onUnhandledChanged(data) {
716
+ const { chatRoomId, is_unhandled: isUnhandled } = data || {};
717
+ if (!chatRoomId) return;
718
+ const chatStore = useChatStore.getState();
719
+ const chat = chatStore.chatList.find((c) => c._id === chatRoomId);
720
+ if (chat) {
721
+ const filterValue = chatStore.filterState?.is_unhandled;
722
+ const noLongerMatchesFilter = filterValue === "true" && !isUnhandled || filterValue === "false" && !!isUnhandled;
723
+ if (noLongerMatchesFilter) {
724
+ chatStore.removeChat(chatRoomId);
725
+ } else {
726
+ chatStore.updateChatDetail(chatRoomId, { is_unhandled: !!isUnhandled });
727
+ }
728
+ return;
729
+ }
730
+ if (isChatListSearching()) return;
731
+ queueRoomForChatList(chatRoomId, { is_unhandled: !!isUnhandled });
732
+ }
733
+ const onChangePersonalZaloType = (data) => {
734
+ const { pzalo_type, page_id, page_name, name, email, user_ids } = data;
735
+ const user = getUser();
736
+ if (!user) return;
737
+ useChatStore.getState().setFilterState({});
738
+ const currentChatId = useChatStore.getState().currentChatId;
739
+ const msg = pzalo_type === "personal" ? i18n.t("common:toast.zaloTransferredToUser", {
740
+ page_name,
741
+ name,
742
+ email,
743
+ defaultValue: "Zalo c\xE1 nh\xE2n {page_name} \u0111\xE3 \u0111\u01B0\u1EE3c chuy\u1EC3n sang cho: {name}, email: {email}!"
744
+ }) : i18n.t("common:toast.zaloTransferredToSharedPersonal", {
745
+ page_name,
746
+ defaultValue: "Zalo {page_name} \u0111\xE3 \u0111\u01B0\u1EE3c chuy\u1EC3n sang Zalo c\xE1 nh\xE2n d\xF9ng chung!"
747
+ });
748
+ let notifiedUserId = "";
749
+ if (isChatVisible()) {
750
+ if (currentChatId) {
751
+ const chatList = useChatStore.getState().chatList;
752
+ const zaloChatting = chatList.find(
753
+ (chat) => chat._id === currentChatId && chat.is_personal_zalo === true && chat.page_id === page_id
754
+ );
755
+ if (zaloChatting) {
756
+ useChatStore.getState().resetLastRefreshTimeTicketPage();
757
+ pushNotification(
758
+ i18n.t("common:nav.notification", { defaultValue: "Th\xF4ng b\xE1o" }),
759
+ {
760
+ description: msg,
761
+ duration: 1e4
762
+ }
763
+ );
764
+ notifiedUserId = user._id;
765
+ }
766
+ }
767
+ }
768
+ if (user_ids.includes(user._id) && notifiedUserId !== user._id) {
769
+ pushNotification(
770
+ i18n.t("common:nav.notification", { defaultValue: "Th\xF4ng b\xE1o" }),
771
+ {
772
+ description: msg,
773
+ duration: 1e4
774
+ }
775
+ );
776
+ }
777
+ };
778
+ const onZaloFriendEvent = (data) => {
779
+ const { message, room_id, tenant_id } = data;
780
+ const currentUser = getUser();
781
+ if (!currentUser || tenant_id !== currentUser.tenant_id) return;
782
+ const currentChatId = useChatStore.getState().currentChatId;
783
+ if (currentChatId !== room_id) return;
784
+ const chatStore = useChatStore.getState();
785
+ const currentMessages = chatStore.ticketChatMessages[room_id] || [];
786
+ const messageIndex = currentMessages.findIndex(
787
+ (msg) => msg._id === message._id
788
+ );
789
+ if (messageIndex !== -1) {
790
+ const updatedMessages = [...currentMessages];
791
+ const existingMessage = updatedMessages[messageIndex];
792
+ updatedMessages[messageIndex] = {
793
+ ...existingMessage,
794
+ ...message,
795
+ // content_obj: newContentObj,
796
+ // extra_data: newExtraData,
797
+ content: message.content !== void 0 ? message.content : existingMessage.content,
798
+ // Giữ nguyên các field đã normalize
799
+ dateLabel: existingMessage.dateLabel,
800
+ showUserName: existingMessage.showUserName,
801
+ normalized: existingMessage.normalized
802
+ };
803
+ useChatStore.setState((state) => ({
804
+ ticketChatMessages: {
805
+ ...state.ticketChatMessages,
806
+ [room_id]: updatedMessages
807
+ }
808
+ }));
809
+ }
810
+ };
811
+ const onMessageRead = ({ roomId, userId, messageIds, readAt }) => {
812
+ const messageIdSet = new Set(messageIds);
813
+ const messages = useChatStore.getState().ticketChatMessages[roomId] ?? [];
814
+ const needsUpdate = messages.some(
815
+ (msg) => messageIdSet.has(msg._id) && !(msg.agent_reads ?? []).some((r) => r._id === userId)
816
+ );
817
+ if (!needsUpdate) return;
818
+ useChatStore.setState((state) => ({
819
+ ticketChatMessages: {
820
+ ...state.ticketChatMessages,
821
+ [roomId]: (state.ticketChatMessages[roomId] ?? []).map((msg) => {
822
+ if (!messageIdSet.has(msg._id)) return msg;
823
+ if ((msg.agent_reads ?? []).some((r) => r._id === userId)) return msg;
824
+ return {
825
+ ...msg,
826
+ agent_reads: [
827
+ ...msg.agent_reads ?? [],
828
+ { _id: userId, readed_at: readAt }
829
+ ]
830
+ };
831
+ })
832
+ }
833
+ }));
834
+ };
835
+ const onRoomReadUpdated = ({
836
+ roomId,
837
+ userId,
838
+ lastReadSeq,
839
+ readAt,
840
+ name,
841
+ avatar
842
+ }) => {
843
+ useChatStore.getState().updateRoomReadUpdate(roomId, userId, { name, avatar, lastReadSeq, readAt });
844
+ useChatStore.getState().updateChatUnread(roomId, { unread: 0 });
845
+ const messages = useChatStore.getState().ticketChatMessages[roomId] ?? [];
846
+ const needsUpdate = messages.some(
847
+ (msg) => msg.seq != null && msg.seq <= lastReadSeq && !(msg.agent_reads ?? []).some((r) => r._id === userId)
848
+ );
849
+ if (needsUpdate) {
850
+ useChatStore.setState((state) => ({
851
+ ticketChatMessages: {
852
+ ...state.ticketChatMessages,
853
+ [roomId]: (state.ticketChatMessages[roomId] ?? []).map((msg) => {
854
+ if (msg.seq == null || msg.seq > lastReadSeq) return msg;
855
+ if ((msg.agent_reads ?? []).some((r) => r._id === userId)) return msg;
856
+ return {
857
+ ...msg,
858
+ agent_reads: [
859
+ ...msg.agent_reads ?? [],
860
+ { _id: userId, readed_at: readAt }
861
+ ]
862
+ };
863
+ })
864
+ }
865
+ }));
866
+ }
867
+ };
868
+ const connectSocket = () => {
869
+ const user = getUser();
870
+ if (!user) return;
871
+ if (socket) return;
872
+ socket = createChatSocket(env.CHAT_URL, user);
873
+ socket.on("connect", onSocketConnect);
874
+ socket.on("connect_error", onConnectError);
875
+ socket.on("reconnect", onSocketReconnect);
876
+ socket.on("disconnect", onDisconnectError);
877
+ socket.on(ChatMessageKey.ADD_MESSAGE, onAddMessage);
878
+ socket.on(ChatMessageKey.REQUEST_CONTACT_CHAT, onRequestContactChat);
879
+ socket.on(ChatMessageKey.INVITE, onInviteChat);
880
+ socket.on(ChatMessageKey.CHANGE_PERSONAL_ZALO_TYPE, onChangePersonalZaloType);
881
+ socket.on(ChatMessageKey.ZALO_FRIEND_EVENT, onZaloFriendEvent);
882
+ socket.on(ChatMessageKey.DISTRIBUTE_CHAT, onDistributeChat);
883
+ socket.on(ChatMessageKey.REMOVE_USER_FROM_CHAT, onRemoveUserFromChat);
884
+ socket.on(ChatMessageKey.HIDE_CHAT_ROOM, onHideChatRoom);
885
+ socket.on(ChatMessageKey.UNHIDE_CHAT_ROOM, onUnhideChatRoom);
886
+ socket.on(ChatMessageKey.UNHANDLED_CHANGED, onUnhandledChanged);
887
+ socket.on(ChatMessageKey.ROOM_READ_UPDATED, onRoomReadUpdated);
888
+ socket.on(ChatMessageKey.MESSAGE_READ, onMessageRead);
889
+ socket.on(LUMI_LEARN_FROM_CHAT_SOCKET_EVENT, onLumiLearnFromChat);
890
+ socket.on(ENTITY_EXTRACT_SOCKET_EVENT, onEntityExtract);
891
+ socket.on(KNOWLEDGE_CRAWL_SOCKET_EVENT, onKnowledgeCrawl);
892
+ clientId = uuidv4();
893
+ };
894
+ const disconnectSocket = () => {
895
+ if (!socket) return;
896
+ if (reconnectDebounceTimer) {
897
+ clearTimeout(reconnectDebounceTimer);
898
+ reconnectDebounceTimer = null;
899
+ }
900
+ reconnectResyncCursor = null;
901
+ socket.off("connect", onSocketConnect);
902
+ socket.off("connect_error", onConnectError);
903
+ socket.off("reconnect", onSocketReconnect);
904
+ socket.off("disconnect", onDisconnectError);
905
+ socket.off(ChatMessageKey.ADD_MESSAGE, onAddMessage);
906
+ socket.off(ChatMessageKey.REQUEST_CONTACT_CHAT, onRequestContactChat);
907
+ socket.off(ChatMessageKey.INVITE, onInviteChat);
908
+ socket.off(ChatMessageKey.CHANGE_PERSONAL_ZALO_TYPE, onChangePersonalZaloType);
909
+ socket.off(ChatMessageKey.ZALO_FRIEND_EVENT, onZaloFriendEvent);
910
+ socket.off(ChatMessageKey.DISTRIBUTE_CHAT, onDistributeChat);
911
+ socket.off(ChatMessageKey.REMOVE_USER_FROM_CHAT, onRemoveUserFromChat);
912
+ socket.off(ChatMessageKey.HIDE_CHAT_ROOM, onHideChatRoom);
913
+ socket.off(ChatMessageKey.UNHIDE_CHAT_ROOM, onUnhideChatRoom);
914
+ socket.off(ChatMessageKey.UNHANDLED_CHANGED, onUnhandledChanged);
915
+ socket.off(ChatMessageKey.ROOM_READ_UPDATED, onRoomReadUpdated);
916
+ socket.off(ChatMessageKey.MESSAGE_READ, onMessageRead);
917
+ socket.off(LUMI_LEARN_FROM_CHAT_SOCKET_EVENT, onLumiLearnFromChat);
918
+ socket.off(ENTITY_EXTRACT_SOCKET_EVENT, onEntityExtract);
919
+ socket.off(KNOWLEDGE_CRAWL_SOCKET_EVENT, onKnowledgeCrawl);
920
+ useChatStore.getState().setIsSocketConnected(false);
921
+ socket.disconnect();
922
+ socket = null;
923
+ };
924
+ let __preLogoutBound = false;
925
+ if (!__preLogoutBound) {
926
+ document.addEventListener(PRE_LOGOUT_EVENT, disconnectSocket);
927
+ __preLogoutBound = true;
928
+ }
929
+ onModuleDispose(
930
+ () => document.removeEventListener(PRE_LOGOUT_EVENT, disconnectSocket)
931
+ );
932
+ const sendMessage = (chatItem, contentTxt, type = MessageType.TEXT, options = {}, fileName, contentOption) => {
933
+ const { replySelected } = useChatStore.getState();
934
+ let contentSocket = contentTxt;
935
+ let mention_infos = {};
936
+ let extra_data = {};
937
+ let styles;
938
+ const user = getUser();
939
+ if (!user) return;
940
+ if (replySelected) {
941
+ options.reply_msg_id = isFacebook(chatItem) ? replySelected.extra_data?.msg_id : replySelected._id;
942
+ }
943
+ if (options._mentionData) {
944
+ const { textOriginal, mentions, mentionsDisplay } = options._mentionData;
945
+ if (mentions?.length > 0) {
946
+ options.mentions = mentions;
947
+ mention_infos = {
948
+ mention_to: mentionsDisplay,
949
+ txt: textOriginal
950
+ };
951
+ }
952
+ delete options._mentionData;
953
+ }
954
+ if (options.styles) {
955
+ styles = options.styles;
956
+ }
957
+ const {
958
+ _id: room_id,
959
+ source,
960
+ source_id,
961
+ source_room_id
962
+ // source_name,
963
+ } = chatItem;
964
+ if (!socket || !room_id) return;
965
+ if ((ContentfulMessageTypes.includes(type) || type === MessageType.ACTION_JOIN) && !ReminderMessageTypes.includes(type)) {
966
+ if (replySelected) {
967
+ extra_data.reply_msg_info = replySelected;
968
+ }
969
+ if (mention_infos) {
970
+ extra_data.mention_infos = mention_infos;
971
+ }
972
+ if (styles) {
973
+ extra_data.styles = styles;
974
+ }
975
+ }
976
+ const content = {
977
+ type,
978
+ text: contentSocket,
979
+ file_name: fileName,
980
+ ...type === "image" || type === "file" || type === "video" ? { src: contentSocket } : {},
981
+ ...type == MessageType.REACTION ? contentOption.reaction : {},
982
+ ...type == "pin_message" ? contentOption.pin : {},
983
+ ...type == "unpin_message" ? contentOption.unpin : {},
984
+ ...options.tag ? { tag: options.tag } : {}
985
+ };
986
+ delete options.tag;
987
+ socket.emit(ChatMessageKey.NEW_MESSAGE, room_id, {
988
+ room_id,
989
+ chatter_id: user._id,
990
+ chatter_name: user.name,
991
+ chatter_avatar: user.avatar,
992
+ chatter_type: "agent",
993
+ date: Date.now(),
994
+ content,
995
+ source,
996
+ source_id,
997
+ source_room_id,
998
+ client_id: clientId,
999
+ extra_data,
1000
+ ...options
1001
+ });
1002
+ };
1003
+ const acceptInviteChat = async (data) => {
1004
+ if (!socket) return;
1005
+ const sessionId = data.chat_session_id;
1006
+ if (!sessionId) return;
1007
+ try {
1008
+ await receiveInvite({ sessionId });
1009
+ } catch (e) {
1010
+ throwException(e);
1011
+ }
1012
+ };
1013
+ const rejectInviteChat = () => {
1014
+ if (!socket) return;
1015
+ };
1016
+ const assignChat = (data) => {
1017
+ if (!socket) return;
1018
+ socket.emit(
1019
+ ChatMessageKey.ASSIGN,
1020
+ data.agentId,
1021
+ data.fromUserId,
1022
+ data.fromUser,
1023
+ data.tenantId,
1024
+ data.ticketId
1025
+ );
1026
+ };
1027
+ const leaveChat = async (roomId) => {
1028
+ if (!socket) return;
1029
+ const { error } = await leaveChatApi(roomId);
1030
+ if (error) return;
1031
+ socket.emit(ChatMessageKey.LEAVE, {
1032
+ room_id: roomId
1033
+ });
1034
+ sendMessage({ _id: roomId }, "", MessageType.ACTION_LEAVE);
1035
+ const chatList = useChatStore.getState().chatList || [];
1036
+ const newChatList = chatList.filter((chat) => chat?._id !== roomId);
1037
+ useChatStore.getState().setChatList(newChatList);
1038
+ useChatStore.getState().removeTicketChatMessage(roomId);
1039
+ navigate({ to: "/chat" });
1040
+ };
1041
+ const onForwardChat = (data) => {
1042
+ const user = getUser();
1043
+ if (!user) return;
1044
+ if (data?.agent_id === user._id) {
1045
+ const description = data?.from_user;
1046
+ if (data?.status === "reject") {
1047
+ pushNotification(
1048
+ i18n.t("common:nav.notification", { defaultValue: "Th\xF4ng b\xE1o" }),
1049
+ {
1050
+ description: i18n.t("common:toast.agentRejectedConversation", {
1051
+ description,
1052
+ defaultValue: "{description} \u0111\xE3 t\u1EEB ch\u1ED1i ti\u1EBFp nh\u1EADn cu\u1ED9c h\u1ED9i tho\u1EA1i!"
1053
+ })
1054
+ }
1055
+ );
1056
+ } else if (data?.status === "accept") {
1057
+ pushNotification(
1058
+ i18n.t("common:nav.notification", { defaultValue: "Th\xF4ng b\xE1o" }),
1059
+ {
1060
+ description: i18n.t("common:toast.agentAcceptedConversation", {
1061
+ description,
1062
+ defaultValue: "{description} \u0111\xE3 \u0111\u1ED3ng \xFD ti\u1EBFp nh\u1EADn cu\u1ED9c h\u1ED9i tho\u1EA1i!"
1063
+ })
1064
+ }
1065
+ );
1066
+ const chatList = useChatStore.getState().chatList;
1067
+ const newChatList = chatList.map((chat) => {
1068
+ if (chat?._id === data?.ticket_id) {
1069
+ const currentReceiverId = chat?.receiver_id;
1070
+ chat.receiver_id = data?.from_user_id;
1071
+ chat.in_room = (chat?.in_room || [])?.filter((ir) => ir !== currentReceiverId).push(data?.from_user_id);
1072
+ if (user?.role === UserRole.AGENT && currentReceiverId === user._id) {
1073
+ return null;
1074
+ }
1075
+ }
1076
+ return chat;
1077
+ });
1078
+ useChatStore.getState().setChatList(newChatList.filter((r) => r));
1079
+ } else if (data?.status === "invite") {
1080
+ const payload = {
1081
+ agentId: data?.from_user_id,
1082
+ fromUserId: user._id,
1083
+ fromUser: user.name,
1084
+ tenantId: user.tenant_id,
1085
+ ticketId: data?.ticket_id
1086
+ };
1087
+ pushNotification(
1088
+ i18n.t("common:nav.notification", { defaultValue: "Th\xF4ng b\xE1o" }),
1089
+ {
1090
+ description: i18n.t("common:toast.conversationForwardRequest", {
1091
+ description,
1092
+ defaultValue: "{description} v\u1EEBa y\xEAu c\u1EA7u chuy\u1EC3n ti\u1EBFp cu\u1ED9c h\u1ED9i tho\u1EA1i cho b\u1EA1n!"
1093
+ }),
1094
+ duration: Infinity,
1095
+ action: {
1096
+ label: i18n.t("common:action.agree", { defaultValue: "\u0110\u1ED3ng \xFD" }),
1097
+ onClick: () => forwardChat({ ...payload, status: "accept" })
1098
+ },
1099
+ cancel: {
1100
+ label: i18n.t("common:action.reject", { defaultValue: "T\u1EEB ch\u1ED1i" }),
1101
+ onClick: () => forwardChat({ ...payload, status: "reject" })
1102
+ }
1103
+ }
1104
+ );
1105
+ }
1106
+ }
1107
+ };
1108
+ const onRefreshStatus = (data) => {
1109
+ const chatList = useChatStore.getState().chatList || [];
1110
+ const currentChat = useChatStore.getState().currentChat || [];
1111
+ const currentChatId = useChatStore.getState().currentChatId || null;
1112
+ if (data?.type === "contact") {
1113
+ const updatedChatList = chatList.map((d) => {
1114
+ if (d?._id === data?.ticket_id && d.requester) {
1115
+ d.requester.is_online = data?.status || 0;
1116
+ }
1117
+ return d;
1118
+ });
1119
+ useChatStore.getState().setChatList(updatedChatList);
1120
+ }
1121
+ if (currentChatId && currentChat?.length) {
1122
+ const updateCurrentChat = currentChat.map((d) => {
1123
+ if (d?.chatter_id === data?.user_id) {
1124
+ d.is_online = data?.status || 0;
1125
+ }
1126
+ return d;
1127
+ });
1128
+ useChatStore.setState({ currentChat: updateCurrentChat });
1129
+ }
1130
+ };
1131
+ const monitor = (data) => {
1132
+ if (!socket) return;
1133
+ socket.emit(ChatMessageKey.MONITOR, data);
1134
+ };
1135
+ const joinFbWebhook = (data) => {
1136
+ if (!socket) return;
1137
+ socket.emit(ChatMessageKey.JOIN_FB_WEBHOOK, data);
1138
+ };
1139
+ const sendUtilityMessage = (chatItem, content, extraData = {}) => {
1140
+ const user = getUser();
1141
+ if (!user) return;
1142
+ const { _id: room_id, source, source_id, source_room_id } = chatItem;
1143
+ if (!socket || !room_id) return;
1144
+ socket.emit(ChatMessageKey.NEW_MESSAGE, room_id, {
1145
+ room_id,
1146
+ chatter_id: user._id,
1147
+ chatter_name: user.name,
1148
+ chatter_avatar: user.avatar,
1149
+ chatter_type: "agent",
1150
+ date: Date.now(),
1151
+ content,
1152
+ source,
1153
+ source_id,
1154
+ source_room_id,
1155
+ client_id: clientId,
1156
+ extra_data: extraData
1157
+ });
1158
+ };
1159
+ const joinChat = (ticketId) => {
1160
+ socket?.emit(ChatMessageKey.JOIN, ticketId);
1161
+ };
1162
+ const forwardChat = (data) => {
1163
+ if (!socket) return;
1164
+ socket.emit(ChatMessageKey.FORWARD, {
1165
+ agent_id: data.agentId,
1166
+ from_user_id: data.fromUserId,
1167
+ from_user: data.fromUser,
1168
+ tenant_id: data.tenantId,
1169
+ ticket_id: data.ticketId,
1170
+ status: data.status
1171
+ });
1172
+ };
1173
+ const sendInternalMessage = (roomId, message) => {
1174
+ if (!socket || !roomId || !message) return;
1175
+ socket.emit(ChatMessageKey.NEW_INTERNAL_MESSAGE, {
1176
+ room_id: roomId,
1177
+ message
1178
+ });
1179
+ };
1180
+ const joinInternalChat = (data) => {
1181
+ if (!socket) return;
1182
+ socket.emit(ChatMessageKey.JOIN_INTERNAL, {
1183
+ room_id: data?.roomId,
1184
+ agent_id: data?.agentId
1185
+ });
1186
+ };
1187
+ registerChatTransport({
1188
+ name: "chat",
1189
+ connect: connectSocket,
1190
+ disconnect: disconnectSocket,
1191
+ methods: {
1192
+ connectSocket,
1193
+ disconnectSocket,
1194
+ sendMessage,
1195
+ sendUtilityMessage,
1196
+ assignChat,
1197
+ joinChat,
1198
+ leaveChat,
1199
+ forwardChat,
1200
+ monitor,
1201
+ sendInternalMessage,
1202
+ joinInternalChat,
1203
+ joinFbWebhook,
1204
+ onMessageRead,
1205
+ onRoomReadUpdated,
1206
+ addContentfulMessage
1207
+ }
1208
+ });
1209
+ export {
1210
+ addContentfulMessage,
1211
+ assignChat,
1212
+ connectSocket,
1213
+ disconnectSocket,
1214
+ forwardChat,
1215
+ joinChat,
1216
+ joinFbWebhook,
1217
+ joinInternalChat,
1218
+ leaveChat,
1219
+ monitor,
1220
+ onMessageRead,
1221
+ onRoomReadUpdated,
1222
+ sendInternalMessage,
1223
+ sendMessage,
1224
+ sendUtilityMessage
1225
+ };