@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,931 @@
1
+ import { create } from "./index.js";
2
+ import { devtools } from "./devtools.js";
3
+ import { formatDay } from "@zenify-omni/chat-js/utils/date.js";
4
+ import { differenceInMinutes } from "date-fns";
5
+ import {
6
+ ActionMessageTypes,
7
+ ChatFilterAssignStatus,
8
+ ChatFilterSource,
9
+ DEFAULT_CHAT_SORT
10
+ } from "@zenify-omni/chat-js/constants/chat-message.js";
11
+ import { ChatRoomTagBatchAction } from "@zenify-omni/chat-js/constants/chat-room-tag.js";
12
+ import { DEFAULT_FILTER_STATE } from "../components/chat-route/chat-list/util.js";
13
+ import { CHATTER_TYPES } from "../components/chat-route/chat-box/constants.js";
14
+ import { isNil, orderBy, uniqBy } from "lodash-es";
15
+ import {
16
+ getTabKeyFromStatus,
17
+ getTabStatusFromChat,
18
+ shouldCountForTab
19
+ } from "./chat-store-helpers.js";
20
+ import { getUser } from "@zenify-omni/chat-js/utils/auth.js";
21
+ const removePinFromTab = (pinChatIds, tab, id) => {
22
+ const ids = pinChatIds[tab] ?? [];
23
+ return ids.includes(id) ? { ...pinChatIds, [tab]: ids.filter((x) => x !== id) } : pinChatIds;
24
+ };
25
+ const normalizeOneAppended = (prevMessage, newMessage) => {
26
+ const msgDate = newMessage.created_at;
27
+ const prevMsgDate = prevMessage?.created_at ?? null;
28
+ const formattedSelfDate = formatDay(msgDate);
29
+ const formattedPrevDate = prevMsgDate ? formatDay(prevMsgDate) : null;
30
+ const dateLabel = formattedPrevDate && formattedSelfDate !== formattedPrevDate ? formattedSelfDate : "";
31
+ const prevChatterId = prevMessage?.chatter_id ?? null;
32
+ const prevChatterName = prevMessage?.chatter_name ?? null;
33
+ const prevMsgType = prevMessage?.content?.type ?? null;
34
+ let showUserName = !prevMessage || newMessage.chatter_id !== prevChatterId || newMessage.chatter_name !== prevChatterName || ActionMessageTypes.includes(prevMsgType);
35
+ if (!showUserName) {
36
+ showUserName = differenceInMinutes(new Date(msgDate), new Date(prevMsgDate)) > 60;
37
+ }
38
+ return { ...newMessage, dateLabel, showUserName, normalized: true };
39
+ };
40
+ const normalizeChatMessagesBulk = (chatMessages) => {
41
+ const mapIdToIndex = {};
42
+ const orderedMessages = orderBy(
43
+ chatMessages,
44
+ (message) => new Date(message.created_at),
45
+ "asc"
46
+ );
47
+ let uniqueChatMessages = {};
48
+ orderedMessages.forEach((message) => {
49
+ uniqueChatMessages[message._id] = message;
50
+ });
51
+ uniqueChatMessages = Object.values(uniqueChatMessages);
52
+ const clientMsgIdsWithRealMessage = /* @__PURE__ */ new Set();
53
+ uniqueChatMessages.forEach((message) => {
54
+ if (message.client_msg_id && message._id !== message.client_msg_id) {
55
+ clientMsgIdsWithRealMessage.add(message.client_msg_id);
56
+ }
57
+ });
58
+ if (clientMsgIdsWithRealMessage.size > 0) {
59
+ uniqueChatMessages = uniqueChatMessages.filter(
60
+ (message) => !(message.client_msg_id && message._id === message.client_msg_id && clientMsgIdsWithRealMessage.has(message.client_msg_id))
61
+ );
62
+ }
63
+ const data = uniqueChatMessages.reduce((prev, message, index) => {
64
+ if (message.normalized) {
65
+ prev.push(message);
66
+ return prev;
67
+ }
68
+ const msgDate = message.created_at;
69
+ const prevMsgDate = index === 0 ? null : uniqueChatMessages[index - 1].created_at;
70
+ let dateLabel = "";
71
+ const formattedSelfDate = formatDay(msgDate);
72
+ const formattedPrevMsgDate = formatDay(prevMsgDate);
73
+ if (formattedPrevMsgDate && formattedSelfDate !== formattedPrevMsgDate) {
74
+ dateLabel = formattedSelfDate;
75
+ }
76
+ const chatterId = message.chatter_id;
77
+ const chatterName = message.chatter_name;
78
+ const prevChatterId = index === 0 ? null : uniqueChatMessages[index - 1].chatter_id;
79
+ const prevChatterName = index === 0 ? null : uniqueChatMessages[index - 1].chatter_name;
80
+ const prevMsgType = index === 0 ? null : uniqueChatMessages[index - 1].content?.type;
81
+ let showUserName = false;
82
+ if (index === 0 || chatterId !== prevChatterId || ActionMessageTypes.includes(prevMsgType) || prevMsgType === "ads_content" || chatterName !== prevChatterName) {
83
+ showUserName = true;
84
+ } else {
85
+ const diffInMinute = differenceInMinutes(
86
+ new Date(msgDate),
87
+ new Date(prevMsgDate)
88
+ );
89
+ if (diffInMinute > 60) showUserName = true;
90
+ }
91
+ const dataToReturn = {
92
+ ...message,
93
+ dateLabel,
94
+ showUserName,
95
+ normalized: true
96
+ };
97
+ if (!mapIdToIndex[message._id]) {
98
+ mapIdToIndex[message._id] = index;
99
+ prev.push(dataToReturn);
100
+ } else {
101
+ const prevIndex = mapIdToIndex[message._id];
102
+ prev[prevIndex] = {
103
+ ...prev[prevIndex],
104
+ ...dataToReturn,
105
+ showUserName: prev[prevIndex].showUserName,
106
+ dateLabel: prev[prevIndex].dateLabel
107
+ };
108
+ }
109
+ return prev;
110
+ }, []);
111
+ return data;
112
+ };
113
+ const requiresChronologicalSort = (source) => source === ChatFilterSource.Shopee;
114
+ const MAX_CACHED_MESSAGE_ROOMS = 20;
115
+ const evictOldestMessageRooms = (state, ticketChatMessages) => {
116
+ const roomIds = Object.keys(ticketChatMessages);
117
+ const overflow = roomIds.length - MAX_CACHED_MESSAGE_ROOMS;
118
+ if (overflow <= 0) {
119
+ return { ticketChatMessages };
120
+ }
121
+ const newTicketChatMessages = { ...ticketChatMessages };
122
+ const newLoadedRooms = { ...state.loadedRooms };
123
+ const newTokenForGetMessageApi = { ...state.tokenForGetMessageApi };
124
+ const removable = roomIds.filter((id) => id !== state.currentChatId);
125
+ for (let i = 0; i < overflow && i < removable.length; i++) {
126
+ const id = removable[i];
127
+ delete newTicketChatMessages[id];
128
+ delete newLoadedRooms[id];
129
+ delete newTokenForGetMessageApi[id];
130
+ }
131
+ return {
132
+ ticketChatMessages: newTicketChatMessages,
133
+ loadedRooms: newLoadedRooms,
134
+ tokenForGetMessageApi: newTokenForGetMessageApi
135
+ };
136
+ };
137
+ const storedShowChatDetail = localStorage.getItem("show-chat-detail");
138
+ const getDraftStorageKey = () => {
139
+ const user = getUser();
140
+ return user?.agent_id ? `chat_drafts_${user.agent_id}` : "chat_drafts";
141
+ };
142
+ const getStoredDrafts = () => {
143
+ try {
144
+ return JSON.parse(localStorage.getItem(getDraftStorageKey()) || "{}");
145
+ } catch {
146
+ return {};
147
+ }
148
+ };
149
+ const useChatStore = create(
150
+ devtools(
151
+ (set, get) => ({
152
+ isSocketConnected: false,
153
+ isSilentRefreshChatList: false,
154
+ isChatListLoading: false,
155
+ isReceivedTabLoading: false,
156
+ isNotReceivedTabLoading: false,
157
+ isClosedTabLoading: false,
158
+ isChatAllowed: true,
159
+ isZaloConnected: false,
160
+ draftMessages: getStoredDrafts(),
161
+ chatList: [],
162
+ currentChat: [],
163
+ pinChatIds: {
164
+ [ChatFilterAssignStatus.Received]: [],
165
+ [ChatFilterAssignStatus.NotReceived]: [],
166
+ [ChatFilterAssignStatus.Closed]: []
167
+ },
168
+ currentChatId: "",
169
+ currentChatTab: ChatFilterAssignStatus.Received,
170
+ currentChatSource: "all",
171
+ currentChatSearch: "",
172
+ currentChatFilter: "",
173
+ chatFilterFlags: [],
174
+ unread: 0,
175
+ subInfoExpanded: false,
176
+ lastRefreshTimeTicketPage: Date.now(),
177
+ refreshFriendRequestListAt: Date.now(),
178
+ chatContentRefreshAt: Date.now(),
179
+ toggleRightSide: isNil(storedShowChatDetail) ? true : storedShowChatDetail === "true",
180
+ ticketChatMessages: {},
181
+ roomReadUpdates: {},
182
+ loadedRooms: {},
183
+ typingUsers: {},
184
+ viewModeRightSide: null,
185
+ viewModeRightSideEditer: null,
186
+ dataDealEditer: null,
187
+ replySelected: null,
188
+ tokenForGetMessageApi: {},
189
+ findReplySelected: null,
190
+ findPinSelected: null,
191
+ highlightMessageId: null,
192
+ feedbackMessage: null,
193
+ insightMessage: null,
194
+ forwardMessage: null,
195
+ isSelectMode: false,
196
+ selectedMessages: [],
197
+ // isSelectMode ở trên là chọn nhiều tin nhắn trong khung chat; cờ dưới đây
198
+ // là chọn nhiều hội thoại ở danh sách bên trái, hai chế độ độc lập nhau.
199
+ isChatListSelectMode: false,
200
+ selectedChatIds: [],
201
+ // "Chọn tất cả" là phạm vi theo tab + bộ lọc hiện tại chứ không phải theo
202
+ // id, nên để riêng một cờ; chat-item đọc cờ này để tick sẵn mọi dòng.
203
+ // Bỏ tick lúc đó chỉ ghi id vào excludeChatIds (thường rất ít) thay vì
204
+ // trải toàn bộ id của tab ra selectedChatIds.
205
+ isChatListSelectAll: false,
206
+ excludeChatIds: [],
207
+ aliasMap: {},
208
+ setAliasMap: (aliases) => set({ aliasMap: aliases }),
209
+ setIsChatAllowed: (isAllowed) => set({ isChatAllowed: isAllowed }),
210
+ setIsZaloConnected: (v) => set({ isZaloConnected: v }),
211
+ setFeedbackMessage: (data) => set({ feedbackMessage: data }),
212
+ setInsightMessage: (data) => set({ insightMessage: data }),
213
+ setForwardMessage: (data) => set({ forwardMessage: data }),
214
+ setSelectMode: (bool) => set({ isSelectMode: bool, selectedMessages: [] }),
215
+ // Thoát/vào chế độ chọn đều dọn danh sách đã chọn để lần mở sau bắt đầu lại
216
+ // từ đầu.
217
+ setChatListSelectMode: (bool) => set({
218
+ isChatListSelectMode: bool,
219
+ selectedChatIds: [],
220
+ isChatListSelectAll: false,
221
+ excludeChatIds: []
222
+ }),
223
+ // Đang chọn tất cả thì tick/bỏ tick chỉ thêm/bớt id trong danh sách loại
224
+ // trừ, phạm vi vẫn là "tất cả trừ mấy dòng này"; ngoài chế độ đó mới ghi
225
+ // vào danh sách id đã chọn.
226
+ toggleSelectedChatId: (id) => set((state) => {
227
+ if (state.isChatListSelectAll) {
228
+ return {
229
+ excludeChatIds: state.excludeChatIds.includes(id) ? state.excludeChatIds.filter((x) => x !== id) : [...state.excludeChatIds, id]
230
+ };
231
+ }
232
+ return {
233
+ selectedChatIds: state.selectedChatIds.includes(id) ? state.selectedChatIds.filter((x) => x !== id) : [...state.selectedChatIds, id]
234
+ };
235
+ }),
236
+ setChatListSelectAll: (bool) => set({
237
+ isChatListSelectAll: bool,
238
+ selectedChatIds: [],
239
+ excludeChatIds: []
240
+ }),
241
+ toggleSelectedMessage: (msg) => set((state) => ({
242
+ selectedMessages: state.selectedMessages.some(
243
+ (m) => m._id === msg._id
244
+ ) ? state.selectedMessages.filter((m) => m._id !== msg._id) : [...state.selectedMessages, msg]
245
+ })),
246
+ setHighlightMessageId: (data) => set({ highlightMessageId: data }),
247
+ setFindReplySelected: (data) => set({ findReplySelected: data }),
248
+ setFindPinSelected: (data) => set({ findPinSelected: data }),
249
+ setReplySelected: (data) => set({ replySelected: data }),
250
+ setViewModeRightSideEditer: (data) => set({ viewModeRightSideEditer: data }),
251
+ setViewModeRightSide: (viewModeRightSide) => set({ viewModeRightSide }),
252
+ resetLastRefreshTimeTicketPage: () => set({ lastRefreshTimeTicketPage: Date.now() }),
253
+ setRefreshFriendRequestListAt: () => set({ refreshFriendRequestListAt: Date.now() }),
254
+ setIsSocketConnected: (isConnected) => set({ isSocketConnected: isConnected }),
255
+ setIsSilentRefreshChatList: (v) => set({ isSilentRefreshChatList: v }),
256
+ markRoomLoaded: (roomId) => set((state) => ({
257
+ loadedRooms: { ...state.loadedRooms, [roomId]: true }
258
+ })),
259
+ // Đánh dấu room chưa load (initial_load:false) -> lần vào lại sẽ fetch tin
260
+ // mới nhất. Dùng khi room không được làm mới (vd reconnect nhưng user đã
261
+ // chuyển sang room khác trong lúc fetch).
262
+ markRoomUnloaded: (roomId) => set((state) => {
263
+ if (!state.loadedRooms[roomId]) return state;
264
+ const newLoadedRooms = { ...state.loadedRooms };
265
+ delete newLoadedRooms[roomId];
266
+ return { loadedRooms: newLoadedRooms };
267
+ }),
268
+ // Reset cờ load tất cả room trừ room đang mở (dùng khi reconnect): lần vào
269
+ // lại các room khác sẽ fetch message mới nhất.
270
+ resetLoadedRooms: () => set((state) => ({
271
+ loadedRooms: state.currentChatId ? { [state.currentChatId]: true } : {}
272
+ })),
273
+ setIsChatListLoading: (isLoading) => set({ isChatListLoading: isLoading }),
274
+ setIsReceivedTabLoading: (v) => set({ isReceivedTabLoading: v }),
275
+ setIsNotReceivedTabLoading: (v) => set({ isNotReceivedTabLoading: v }),
276
+ setIsClosedTabLoading: (v) => set({ isClosedTabLoading: v }),
277
+ setChatList: (data) => set({ chatList: data }),
278
+ setPinChatIds: (data) => set({ pinChatIds: data }),
279
+ setUnread: (data) => set({ unread: data }),
280
+ setCurrentChat: (data) => set({ currentChat: data }),
281
+ setCurrentChatId: (id) => set({ currentChatId: id }),
282
+ setTokenForGetMessageApi: (room_id, token) => set((state) => ({
283
+ tokenForGetMessageApi: {
284
+ ...state.tokenForGetMessageApi,
285
+ [room_id]: token
286
+ }
287
+ })),
288
+ // Chọn nhiều chỉ dùng ở tab "Đang xử lý" và lựa chọn gắn theo tab, nên đổi
289
+ // tab là bỏ hết id đã tick, rời khỏi tab đó thì tắt luôn chế độ chọn.
290
+ setCurrentChatTab: (tab) => set({
291
+ currentChatTab: tab,
292
+ selectedChatIds: [],
293
+ isChatListSelectAll: false,
294
+ excludeChatIds: [],
295
+ ...tab === ChatFilterAssignStatus.Received ? {} : { isChatListSelectMode: false }
296
+ }),
297
+ setCurrentChatSource: (source) => set({ currentChatSource: source }),
298
+ setCurrentChatSearch: (search) => set({ currentChatSearch: search }),
299
+ setCurrentChatFilter: (filter) => set({ currentChatFilter: filter }),
300
+ setChatFilterFlags: (filter) => set({ chatFilterFlags: filter }),
301
+ setToggleRightSide: (status) => set({ toggleRightSide: status }),
302
+ setDataDealEditer: (dataDealEditer) => set({ dataDealEditer }),
303
+ setSubInfoExpanded: (status) => set({ subInfoExpanded: status }),
304
+ setDraftMessages: (key, message) => {
305
+ const drafts = getStoredDrafts();
306
+ drafts[key] = message;
307
+ const keys = Object.keys(drafts);
308
+ if (keys.length > 50) {
309
+ keys.slice(0, keys.length - 50).forEach((k) => delete drafts[k]);
310
+ }
311
+ localStorage.setItem(getDraftStorageKey(), JSON.stringify(drafts));
312
+ set((state) => ({
313
+ draftMessages: { ...state.draftMessages, [key]: message }
314
+ }));
315
+ },
316
+ clearDraftMessage: (key) => {
317
+ const drafts = getStoredDrafts();
318
+ delete drafts[key];
319
+ localStorage.setItem(getDraftStorageKey(), JSON.stringify(drafts));
320
+ set((state) => {
321
+ const newDraftMessages = { ...state.draftMessages };
322
+ delete newDraftMessages[key];
323
+ return { draftMessages: newDraftMessages };
324
+ });
325
+ },
326
+ setTypingUser: (roomId, agentId, agentName, agentAvatar) => set((state) => ({
327
+ typingUsers: {
328
+ ...state.typingUsers,
329
+ [roomId]: {
330
+ ...state.typingUsers[roomId],
331
+ [agentId]: { name: agentName, avatar: agentAvatar }
332
+ }
333
+ }
334
+ })),
335
+ clearTypingUser: (roomId, agentId) => set((state) => {
336
+ const room = { ...state.typingUsers[roomId] };
337
+ delete room[agentId];
338
+ return { typingUsers: { ...state.typingUsers, [roomId]: room } };
339
+ }),
340
+ setTicketChatMessages: (ticketId, chatMessages) => set((state) => {
341
+ const normalizedChatMessages = normalizeChatMessagesBulk(chatMessages);
342
+ const rest = { ...state.ticketChatMessages };
343
+ delete rest[ticketId];
344
+ const nextTicketChatMessages = {
345
+ ...rest,
346
+ [ticketId]: normalizedChatMessages
347
+ };
348
+ return evictOldestMessageRooms(state, nextTicketChatMessages);
349
+ }),
350
+ removeTicketChatMessage: (ticketId) => set((state) => {
351
+ const newTicketChatMessages = { ...state.ticketChatMessages };
352
+ delete newTicketChatMessages[ticketId];
353
+ const newTokenForGetMessageApi = { ...state.tokenForGetMessageApi };
354
+ delete newTokenForGetMessageApi[ticketId];
355
+ const newLoadedRooms = { ...state.loadedRooms };
356
+ delete newLoadedRooms[ticketId];
357
+ return {
358
+ ticketChatMessages: newTicketChatMessages,
359
+ tokenForGetMessageApi: newTokenForGetMessageApi,
360
+ loadedRooms: newLoadedRooms
361
+ };
362
+ }),
363
+ addNewMessage: (ticketId, message) => set((state) => {
364
+ const currentMessages = state.ticketChatMessages[ticketId] || [];
365
+ const existingIdx = currentMessages.findIndex(
366
+ (m) => m._id === message._id
367
+ );
368
+ if (existingIdx !== -1) {
369
+ const updated = [...currentMessages];
370
+ updated[existingIdx] = { ...updated[existingIdx], ...message };
371
+ return {
372
+ ticketChatMessages: {
373
+ ...state.ticketChatMessages,
374
+ [ticketId]: updated
375
+ }
376
+ };
377
+ }
378
+ const lastMessage = currentMessages[currentMessages.length - 1];
379
+ if (message.client_msg_id && lastMessage?.client_msg_id === message.client_msg_id)
380
+ return state;
381
+ const normalized = normalizeOneAppended(lastMessage, message);
382
+ const rest = { ...state.ticketChatMessages };
383
+ delete rest[ticketId];
384
+ const source = message?.source;
385
+ const appended = [...currentMessages, normalized];
386
+ const nextTicketChatMessages = {
387
+ ...rest,
388
+ [ticketId]: requiresChronologicalSort(source) ? orderBy(appended, (m) => m?.created_at ?? "", "asc") : appended
389
+ };
390
+ return evictOldestMessageRooms(state, nextTicketChatMessages);
391
+ }),
392
+ updateMessageByClientMsgId: (ticketId, clientMsgId, updates) => set((state) => {
393
+ const messages = state.ticketChatMessages[ticketId] || [];
394
+ const mapped = messages.map(
395
+ (msg) => msg.client_msg_id === clientMsgId ? { ...msg, ...updates } : msg
396
+ );
397
+ const seen = /* @__PURE__ */ new Set();
398
+ const deduped = mapped.filter((msg) => {
399
+ if (seen.has(msg._id)) return false;
400
+ seen.add(msg._id);
401
+ return true;
402
+ });
403
+ return {
404
+ ticketChatMessages: {
405
+ ...state.ticketChatMessages,
406
+ [ticketId]: deduped
407
+ }
408
+ };
409
+ }),
410
+ updateMessageViewers: (roomId, messageId, viewers) => set((state) => {
411
+ const messages = state.ticketChatMessages[roomId] || [];
412
+ return {
413
+ ticketChatMessages: {
414
+ ...state.ticketChatMessages,
415
+ [roomId]: messages.map(
416
+ (msg) => msg._id === messageId ? { ...msg, viewed_by: viewers } : msg
417
+ )
418
+ }
419
+ };
420
+ }),
421
+ applyOptimisticReaction: (roomId, messageId, reactions, opTs) => set((state) => {
422
+ const messages = state.ticketChatMessages[roomId] || [];
423
+ return {
424
+ ticketChatMessages: {
425
+ ...state.ticketChatMessages,
426
+ [roomId]: messages.map(
427
+ (msg) => msg._id === messageId ? {
428
+ ...msg,
429
+ extra_data: { ...msg.extra_data, reactions },
430
+ _pendingReactionTs: opTs
431
+ } : msg
432
+ )
433
+ }
434
+ };
435
+ }),
436
+ confirmPendingReaction: (roomId, messageId) => set((state) => {
437
+ const messages = state.ticketChatMessages[roomId] || [];
438
+ if (!messages.some(
439
+ (msg) => msg._id === messageId && "_pendingReactionTs" in msg
440
+ ))
441
+ return state;
442
+ return {
443
+ ticketChatMessages: {
444
+ ...state.ticketChatMessages,
445
+ [roomId]: messages.map((msg) => {
446
+ if (msg._id !== messageId) return msg;
447
+ const rest = { ...msg };
448
+ delete rest._pendingReactionTs;
449
+ return rest;
450
+ })
451
+ }
452
+ };
453
+ }),
454
+ rollbackPendingReaction: (roomId, messageId, opTs, previousReactions) => set((state) => {
455
+ const messages = state.ticketChatMessages[roomId] || [];
456
+ const target = messages.find((msg) => msg._id === messageId);
457
+ if (!target || target._pendingReactionTs !== opTs) return state;
458
+ return {
459
+ ticketChatMessages: {
460
+ ...state.ticketChatMessages,
461
+ [roomId]: messages.map((msg) => {
462
+ if (msg._id !== messageId) return msg;
463
+ const rest = { ...msg };
464
+ delete rest._pendingReactionTs;
465
+ return {
466
+ ...rest,
467
+ extra_data: {
468
+ ...msg.extra_data,
469
+ reactions: previousReactions
470
+ }
471
+ };
472
+ })
473
+ }
474
+ };
475
+ }),
476
+ updateRoomReadUpdate: (roomId, userId, data) => set((state) => {
477
+ const prev = state.roomReadUpdates[roomId] ?? {};
478
+ return {
479
+ roomReadUpdates: {
480
+ ...state.roomReadUpdates,
481
+ [roomId]: { ...prev, [userId]: data }
482
+ }
483
+ };
484
+ }),
485
+ // Khách đọc tin -> backfill read_receipt cho các tin agent trước đó chưa có,
486
+ // để UI show "đã xem" cho cả các tin gửi liên tiếp trước tin đang chốt mốc đọc.
487
+ applyReadReceipt: (roomId, readReceipt) => set((state) => {
488
+ const messages = state.ticketChatMessages[roomId];
489
+ if (!messages?.length) return state;
490
+ const needsUpdate = messages.some(
491
+ (msg) => msg.chatter_type === CHATTER_TYPES.AGENT && !msg.read_receipt
492
+ );
493
+ if (!needsUpdate) return state;
494
+ return {
495
+ ticketChatMessages: {
496
+ ...state.ticketChatMessages,
497
+ [roomId]: messages.map(
498
+ (msg) => msg.chatter_type === CHATTER_TYPES.AGENT && !msg.read_receipt ? { ...msg, read_receipt: readReceipt } : msg
499
+ )
500
+ }
501
+ };
502
+ }),
503
+ filterState: DEFAULT_FILTER_STATE,
504
+ setFilterState: (filterState) => set((state) => {
505
+ return {
506
+ ...state,
507
+ filterState: {
508
+ ...state.filterState,
509
+ ...filterState
510
+ },
511
+ lastRefreshTime: Date.now()
512
+ };
513
+ }),
514
+ // Kiểu sắp xếp để riêng, không gộp vào filterState: nó không thuộc form
515
+ // bộ lọc, mà mọi đường reset đều đi qua DEFAULT_FILTER_STATE nên nằm
516
+ // chung là sớm muộn cũng bị xoá theo.
517
+ chatSort: DEFAULT_CHAT_SORT,
518
+ setChatSort: (chatSort) => set({ chatSort, lastRefreshTime: Date.now() }),
519
+ selectedFilterId: null,
520
+ setSelectedFilterId: (id) => set({ selectedFilterId: id }),
521
+ lastRefreshTime: Date.now(),
522
+ setLastRefreshTime: () => set({ lastRefreshTime: Date.now() }),
523
+ setChatContentRefreshAt: () => set({ chatContentRefreshAt: Date.now() }),
524
+ unreadReceivedTab: {
525
+ total: 0,
526
+ lastMsgId: null,
527
+ isLoadMore: false,
528
+ cursor: null
529
+ },
530
+ notReceivedTab: {
531
+ total: 0,
532
+ lastMsgId: null,
533
+ isLoadMore: false,
534
+ cursor: null
535
+ },
536
+ closedTab: {
537
+ total: 0,
538
+ lastMsgId: null,
539
+ isLoadMore: false,
540
+ cursor: null
541
+ },
542
+ setLoadMore: (tab, isLoadMore) => {
543
+ set((state) => {
544
+ const key = getTabKeyFromStatus(tab);
545
+ return {
546
+ ...state,
547
+ [key]: {
548
+ ...state[key],
549
+ isLoadMore
550
+ }
551
+ };
552
+ });
553
+ },
554
+ setLastChatId: (tab, chatId) => {
555
+ set((state) => {
556
+ const key = getTabKeyFromStatus(tab);
557
+ return {
558
+ ...state,
559
+ [key]: {
560
+ ...state[key],
561
+ lastMsgId: chatId
562
+ }
563
+ };
564
+ });
565
+ },
566
+ getFullFilterState: ({ tab, options }) => {
567
+ const store = get();
568
+ const key = getTabKeyFromStatus(tab);
569
+ const tabState = store[key];
570
+ let cursor;
571
+ if (options?.is_new) {
572
+ cursor = null;
573
+ } else {
574
+ cursor = tabState.cursor;
575
+ }
576
+ const filterState = {
577
+ ...store.filterState,
578
+ assign_status: tab,
579
+ cursor,
580
+ sort: store.chatSort
581
+ };
582
+ return filterState;
583
+ },
584
+ setTabInitialChats: ({ tab, chats, total, cursor }) => {
585
+ const key = getTabKeyFromStatus(tab);
586
+ set((state) => ({
587
+ ...state,
588
+ chatList: uniqBy([...state.chatList, ...chats], "_id"),
589
+ [key]: {
590
+ total,
591
+ lastChatId: null,
592
+ isLoadMore: false,
593
+ cursor
594
+ }
595
+ }));
596
+ },
597
+ appendChats: ({ tab, chats, total, next_cursor: nextCursor }) => {
598
+ const key = getTabKeyFromStatus(tab);
599
+ set((state) => {
600
+ const chatTabState = state[key];
601
+ const newChatList = uniqBy([...chats, ...state.chatList], "_id");
602
+ let newTotal = chatTabState.total;
603
+ if (total !== void 0) {
604
+ newTotal = total;
605
+ } else {
606
+ const newChats = chats.filter(
607
+ (chat) => !state.chatList.some((x) => x._id === chat._id)
608
+ );
609
+ let autoIncrement = 0;
610
+ for (const chat of newChats) {
611
+ if (shouldCountForTab(tab, chat)) {
612
+ autoIncrement += 1;
613
+ }
614
+ }
615
+ newTotal = chatTabState.total + autoIncrement;
616
+ }
617
+ return {
618
+ ...state,
619
+ chatList: newChatList,
620
+ [key]: {
621
+ ...chatTabState,
622
+ total: newTotal,
623
+ ...nextCursor !== void 0 ? { cursor: nextCursor } : {}
624
+ }
625
+ };
626
+ });
627
+ },
628
+ moveChat: ({ roomId, payload }) => {
629
+ set((state) => {
630
+ const chatToMove = state.chatList.find((x) => x._id === roomId);
631
+ if (!chatToMove) return state;
632
+ const newChatList = state.chatList.map((chat) => {
633
+ if (chat._id !== roomId) return chat;
634
+ return {
635
+ ...chat,
636
+ ...payload
637
+ };
638
+ });
639
+ return {
640
+ ...state,
641
+ chatList: newChatList
642
+ };
643
+ });
644
+ },
645
+ transitionChatTab: (oldTab, newTab, chat) => {
646
+ if (oldTab === newTab) return;
647
+ set((state) => {
648
+ const oldKey = getTabKeyFromStatus(oldTab);
649
+ const newKey = getTabKeyFromStatus(newTab);
650
+ return {
651
+ ...state,
652
+ pinChatIds: removePinFromTab(state.pinChatIds, oldTab, chat._id),
653
+ [oldKey]: {
654
+ ...state[oldKey],
655
+ total: shouldCountForTab(oldTab, chat) ? Math.max(state[oldKey].total - 1, 0) : state[oldKey].total
656
+ },
657
+ [newKey]: {
658
+ ...state[newKey],
659
+ total: shouldCountForTab(newTab, chat) ? state[newKey].total + 1 : state[newKey].total
660
+ }
661
+ };
662
+ });
663
+ },
664
+ removeChat: (chatId) => {
665
+ set((state) => {
666
+ const chatToRemove = state.chatList.find((x) => x._id === chatId);
667
+ if (!chatToRemove) return state;
668
+ let newChatList = state.chatList.filter((chat) => chat._id !== chatId);
669
+ const currentTabStatus = getTabStatusFromChat(chatToRemove);
670
+ const currentTabKey = getTabKeyFromStatus(currentTabStatus);
671
+ const currentTabState = state[currentTabKey];
672
+ currentTabState.total = Math.max(currentTabState.total - 1, 0);
673
+ const newTicketChatMessages = { ...state.ticketChatMessages };
674
+ delete newTicketChatMessages[chatId];
675
+ const newTokenForGetMessageApi = { ...state.tokenForGetMessageApi };
676
+ delete newTokenForGetMessageApi[chatId];
677
+ const newLoadedRooms = { ...state.loadedRooms };
678
+ delete newLoadedRooms[chatId];
679
+ const updatedState = {
680
+ ...state,
681
+ chatList: newChatList,
682
+ pinChatIds: removePinFromTab(
683
+ state.pinChatIds,
684
+ currentTabStatus,
685
+ chatId
686
+ ),
687
+ [currentTabKey]: {
688
+ ...currentTabState
689
+ },
690
+ ticketChatMessages: newTicketChatMessages,
691
+ tokenForGetMessageApi: newTokenForGetMessageApi,
692
+ loadedRooms: newLoadedRooms
693
+ };
694
+ return updatedState;
695
+ });
696
+ },
697
+ // Chỉ gỡ room khỏi LIST (và trừ total của tab), GIỮ NGUYÊN cache message,
698
+ // token phân trang & cờ loadedRooms. Dùng khi room không còn khớp filter
699
+ // đang áp (vd filter "chưa trả lời" mà agent vừa reply): room biến mất
700
+ // khỏi list nhưng nếu đang mở theo link thì agent vẫn xem & nhắn tiếp
701
+ // bình thường, và các tin tiếp theo vẫn được đẩy thẳng vào thread nhờ
702
+ // loadedRooms còn giữ (xem addContentfulMessage).
703
+ removeChatFromList: (chatId) => {
704
+ set((state) => {
705
+ const chatToRemove = state.chatList.find((x) => x._id === chatId);
706
+ if (!chatToRemove) return state;
707
+ const currentTabStatus = getTabStatusFromChat(chatToRemove);
708
+ const currentTabKey = getTabKeyFromStatus(currentTabStatus);
709
+ const currentTabState = state[currentTabKey];
710
+ return {
711
+ ...state,
712
+ chatList: state.chatList.filter((chat) => chat._id !== chatId),
713
+ [currentTabKey]: {
714
+ ...currentTabState,
715
+ total: Math.max(currentTabState.total - 1, 0)
716
+ }
717
+ };
718
+ });
719
+ },
720
+ updateChatUnread: (chatId, payload) => {
721
+ set((state) => {
722
+ const currentChat = state.chatList.find((x) => x._id === chatId);
723
+ if (!currentChat) return state;
724
+ const newChatList = state.chatList.map((chat) => {
725
+ if (chat._id !== chatId) return chat;
726
+ return {
727
+ ...chat,
728
+ ...payload
729
+ };
730
+ });
731
+ return {
732
+ ...state,
733
+ chatList: newChatList
734
+ };
735
+ });
736
+ },
737
+ reopenChat: (chatId, { is_received: isReceived, last_msg: lastMsg }) => {
738
+ set((state) => {
739
+ const chat = state.chatList.find((x) => x._id === chatId);
740
+ if (!chat) return state;
741
+ const newChatList = state.chatList.map((chat2) => {
742
+ if (chat2._id !== chatId) return chat2;
743
+ return {
744
+ ...chat2,
745
+ session: {
746
+ ...chat2.session,
747
+ status: void 0,
748
+ is_received: isReceived
749
+ },
750
+ last_msg: lastMsg || chat2.last_msg
751
+ };
752
+ });
753
+ const closedTab = { ...state.closedTab };
754
+ closedTab.total = Math.max(closedTab.total - 1, 0);
755
+ const unreadReceivedTab = { ...state.unreadReceivedTab };
756
+ const notReceivedTab = { ...state.notReceivedTab };
757
+ if (isReceived) {
758
+ unreadReceivedTab.total += 1;
759
+ } else {
760
+ notReceivedTab.total += 1;
761
+ }
762
+ return {
763
+ ...state,
764
+ chatList: newChatList,
765
+ pinChatIds: removePinFromTab(
766
+ state.pinChatIds,
767
+ ChatFilterAssignStatus.Closed,
768
+ chatId
769
+ ),
770
+ closedTab,
771
+ unreadReceivedTab,
772
+ notReceivedTab
773
+ };
774
+ });
775
+ },
776
+ // updateChatJoin: (chatId, payload) => {
777
+ // set((state) => {
778
+ // const newChatList = state.chatList.map((chat) => {
779
+ // if (chat._id !== chatId) return chat
780
+ // return { ...chat, ...payload }
781
+ // })
782
+ // return {
783
+ // ...state,
784
+ // chatList: newChatList,
785
+ // }
786
+ // })
787
+ // },
788
+ // acceptInvite: (chatId, payload) => {
789
+ // set((state) => {
790
+ // const chat = state.chatList.find((chat) => chat._id === chatId)
791
+ // if (!chat) return state
792
+ // const newChatList = state.chatList.map((chat) => {
793
+ // if (chat._id !== chatId) return chat
794
+ // return { ...chat, ...payload }
795
+ // })
796
+ // return {
797
+ // ...state,
798
+ // chatList: newChatList,
799
+ // }
800
+ // })
801
+ // },
802
+ updateRequester: (chatId, payload) => {
803
+ set((state) => {
804
+ const newChatList = state.chatList.map((chat) => {
805
+ if (chat._id !== chatId) return chat;
806
+ return {
807
+ ...chat,
808
+ requester: {
809
+ ...chat.requester,
810
+ ...payload
811
+ }
812
+ };
813
+ });
814
+ return {
815
+ ...state,
816
+ chatList: newChatList
817
+ };
818
+ });
819
+ },
820
+ updatePersonalTagInAllChats: (updatedTag) => {
821
+ set((state) => ({
822
+ ...state,
823
+ chatList: state.chatList.map((chat) => ({
824
+ ...chat,
825
+ personal_tags: (chat.personal_tags ?? []).map(
826
+ (t) => t._id === updatedTag._id ? { ...t, ...updatedTag } : t
827
+ )
828
+ }))
829
+ }));
830
+ },
831
+ // Gán/bỏ tag hàng loạt: mỗi hội thoại có sẵn bộ tag riêng nên gộp thêm (bỏ
832
+ // trùng theo _id) hoặc lọc bớt thay vì ghi đè như updateChatDetail ở màn
833
+ // chi tiết một phòng. Đi theo id vì API batch trả kết quả từng phòng —
834
+ // phòng lỗi phải giữ nguyên tag cũ.
835
+ updateTagsForChats: ({
836
+ chatIds,
837
+ tags = [],
838
+ personalTags = [],
839
+ action = ChatRoomTagBatchAction.Add
840
+ }) => {
841
+ if (!chatIds?.length) return;
842
+ if (!tags.length && !personalTags.length) return;
843
+ const mergeById = (current = [], incoming = []) => {
844
+ if (!incoming.length) return current;
845
+ const existed = new Set(current.map((tag) => String(tag._id)));
846
+ return [
847
+ ...current,
848
+ ...incoming.filter((tag) => !existed.has(String(tag._id)))
849
+ ];
850
+ };
851
+ const excludeById = (current = [], incoming = []) => {
852
+ if (!incoming.length) return current;
853
+ const removed = new Set(incoming.map((tag) => String(tag._id)));
854
+ return current.filter((tag) => !removed.has(String(tag._id)));
855
+ };
856
+ const applyTags = action === ChatRoomTagBatchAction.Remove ? excludeById : mergeById;
857
+ const targetIds = new Set(chatIds);
858
+ set((state) => ({
859
+ ...state,
860
+ chatList: state.chatList.map(
861
+ (chat) => targetIds.has(chat._id) ? {
862
+ ...chat,
863
+ tags: applyTags(chat.tags ?? [], tags),
864
+ personal_tags: applyTags(
865
+ chat.personal_tags ?? [],
866
+ personalTags
867
+ )
868
+ } : chat
869
+ )
870
+ }));
871
+ },
872
+ // Chuyển người phụ trách hàng loạt: vẽ lại ngay tên chuyên viên trong chat
873
+ // list. Không có tên (BE chỉ trả id) thì để trống, chat-item-tags sẽ tự
874
+ // gọi API lấy tên theo assigned._id. Đi theo id vì API batch trả kết quả
875
+ // từng phòng — phòng lỗi phải giữ nguyên người phụ trách cũ.
876
+ updateAssigneeForChats: ({ chatIds, assignedId, assignedName }) => {
877
+ if (!assignedId || !chatIds?.length) return;
878
+ const targetIds = new Set(chatIds);
879
+ set((state) => ({
880
+ ...state,
881
+ chatList: state.chatList.map(
882
+ (chat) => targetIds.has(chat._id) ? {
883
+ ...chat,
884
+ assigned: { _id: assignedId, name: assignedName },
885
+ ...chat.session ? {
886
+ session: { ...chat.session, assigned_id: assignedId }
887
+ } : {}
888
+ } : chat
889
+ )
890
+ }));
891
+ },
892
+ removePersonalTagFromAllChats: (tagId) => {
893
+ set((state) => ({
894
+ ...state,
895
+ chatList: state.chatList.map((chat) => ({
896
+ ...chat,
897
+ personal_tags: (chat.personal_tags ?? []).filter(
898
+ (t) => t._id !== tagId
899
+ )
900
+ }))
901
+ }));
902
+ },
903
+ updateChatDetail: (chatId, payload) => {
904
+ set((state) => {
905
+ const chat = state.chatList.find((x) => x._id === chatId);
906
+ if (!chat) return state;
907
+ const newChatList = state.chatList.map((chat2) => {
908
+ if (chat2._id !== chatId) return chat2;
909
+ return {
910
+ ...chat2,
911
+ ...payload
912
+ };
913
+ });
914
+ return {
915
+ ...state,
916
+ chatList: newChatList
917
+ };
918
+ });
919
+ }
920
+ }),
921
+ {
922
+ name: "chat-store"
923
+ }
924
+ )
925
+ );
926
+ var chat_store_default = useChatStore;
927
+ export {
928
+ chat_store_default as default,
929
+ normalizeChatMessagesBulk,
930
+ normalizeOneAppended
931
+ };