@droppii-org/chat-sdk 0.8.1 → 0.8.3

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 (191) hide show
  1. package/dist/components/canned-response/cannedTrigger.d.ts +2 -0
  2. package/dist/components/canned-response/cannedTrigger.d.ts.map +1 -0
  3. package/dist/components/canned-response/cannedTrigger.js +3 -0
  4. package/dist/components/canned-response/insertCannedContent.js +2 -2
  5. package/dist/components/conversation/ConversationByGroupItem.js +1 -1
  6. package/dist/components/conversation/ConversationBySessionItem.js +4 -4
  7. package/dist/components/conversation/DeskConversationList.d.ts.map +1 -1
  8. package/dist/components/conversation/DeskConversationList.js +6 -4
  9. package/dist/components/conversation/folderEmptyState.d.ts.map +1 -1
  10. package/dist/components/conversation/folderEmptyState.js +2 -0
  11. package/dist/components/conversation/internalNotePreview.d.ts +1 -1
  12. package/dist/components/conversation/internalNotePreview.d.ts.map +1 -1
  13. package/dist/components/conversation/internalNotePreview.js +22 -3
  14. package/dist/components/conversation/listHeading.d.ts +1 -1
  15. package/dist/components/conversation/listHeading.d.ts.map +1 -1
  16. package/dist/components/conversation/listHeading.js +7 -1
  17. package/dist/components/conversation/rowTime.d.ts +2 -2
  18. package/dist/components/conversation/rowTime.d.ts.map +1 -1
  19. package/dist/components/conversation/rowTime.js +2 -4
  20. package/dist/components/conversation-detail/ConversationDetailInput.d.ts.map +1 -1
  21. package/dist/components/conversation-detail/ConversationDetailInput.js +8 -3
  22. package/dist/components/conversation-detail/ConversationMessageList.d.ts.map +1 -1
  23. package/dist/components/conversation-detail/ConversationMessageList.js +6 -3
  24. package/dist/components/conversation-detail/bot/BotMenuSheet.d.ts.map +1 -1
  25. package/dist/components/conversation-detail/bot/BotMenuSheet.js +1 -1
  26. package/dist/components/conversation-inbox/ConversationInboxList.d.ts.map +1 -1
  27. package/dist/components/conversation-inbox/ConversationInboxList.js +4 -0
  28. package/dist/components/conversation-inbox/InboxBootStatus.d.ts.map +1 -1
  29. package/dist/components/conversation-inbox/InboxBootStatus.js +10 -2
  30. package/dist/components/group/CreateGroupForm.d.ts.map +1 -1
  31. package/dist/components/group/CreateGroupForm.js +2 -1
  32. package/dist/components/group/CreateGroupFormCrmView.d.ts.map +1 -1
  33. package/dist/components/group/CreateGroupFormCrmView.js +1 -1
  34. package/dist/components/group/GroupMemberPickerCrm.d.ts.map +1 -1
  35. package/dist/components/group/GroupMemberPickerCrm.js +1 -1
  36. package/dist/components/media-collection/FileCollection.d.ts.map +1 -1
  37. package/dist/components/media-collection/FileCollection.js +4 -3
  38. package/dist/components/media-collection/ImageCollection.d.ts.map +1 -1
  39. package/dist/components/media-collection/ImageCollection.js +2 -1
  40. package/dist/components/media-collection/LinkCollection.d.ts.map +1 -1
  41. package/dist/components/media-collection/LinkCollection.js +14 -26
  42. package/dist/components/media-collection/VideoCollection.d.ts.map +1 -1
  43. package/dist/components/media-collection/VideoCollection.js +2 -1
  44. package/dist/components/media-resources/FileResourceList.js +2 -2
  45. package/dist/components/media-resources/ResourceScrollContainer.d.ts.map +1 -1
  46. package/dist/components/media-resources/ResourceScrollContainer.js +34 -2
  47. package/dist/components/message/GroupMembersDrawer.d.ts.map +1 -1
  48. package/dist/components/message/GroupMembersDrawer.js +2 -3
  49. package/dist/components/message/MessageList.d.ts.map +1 -1
  50. package/dist/components/message/MessageList.js +15 -4
  51. package/dist/components/message/footer/ActionBar.d.ts.map +1 -1
  52. package/dist/components/message/footer/ActionBar.js +10 -44
  53. package/dist/components/message/footer/CannedResponsePlugin.d.ts.map +1 -1
  54. package/dist/components/message/footer/CannedResponsePlugin.js +3 -2
  55. package/dist/components/message/footer/EnterHandler.d.ts.map +1 -1
  56. package/dist/components/message/footer/EnterHandler.js +10 -47
  57. package/dist/components/message/footer/FilePreview.d.ts.map +1 -1
  58. package/dist/components/message/footer/FilePreview.js +2 -1
  59. package/dist/components/message/footer/LinkInputPreview.d.ts +1 -1
  60. package/dist/components/message/footer/LinkInputPreview.d.ts.map +1 -1
  61. package/dist/components/message/footer/LinkInputPreview.js +8 -1
  62. package/dist/components/message/footer/MediaUploadButtons.d.ts.map +1 -1
  63. package/dist/components/message/footer/MediaUploadButtons.js +4 -7
  64. package/dist/components/message/footer/index.d.ts.map +1 -1
  65. package/dist/components/message/footer/index.js +57 -16
  66. package/dist/components/message/footer/mention/useMentionOptions.d.ts.map +1 -1
  67. package/dist/components/message/footer/mention/useMentionOptions.js +2 -6
  68. package/dist/components/message/footer/resetEditorAfterSend.d.ts +7 -0
  69. package/dist/components/message/footer/resetEditorAfterSend.d.ts.map +1 -0
  70. package/dist/components/message/footer/resetEditorAfterSend.js +62 -0
  71. package/dist/components/message/groupMemberMenuCrm.d.ts +4 -4
  72. package/dist/components/message/groupMemberMenuCrm.d.ts.map +1 -1
  73. package/dist/components/message/groupMemberMenuCrm.js +73 -60
  74. package/dist/components/message/item/FileMessage.js +2 -2
  75. package/dist/components/message/item/SystemLogMessage.d.ts.map +1 -1
  76. package/dist/components/message/item/SystemLogMessage.js +5 -2
  77. package/dist/components/search-conversation/SearchConversationAsGroups.js +1 -1
  78. package/dist/components/search-conversation/SearchConversationAsMessages.js +1 -1
  79. package/dist/components/search-conversation/SearchConversationMyInbox.js +1 -1
  80. package/dist/components/session/SessionFolderList.d.ts +4 -3
  81. package/dist/components/session/SessionFolderList.d.ts.map +1 -1
  82. package/dist/components/session/SessionFolderList.js +14 -5
  83. package/dist/components/thread/GroupInfoBodyCrm.d.ts.map +1 -1
  84. package/dist/components/thread/GroupInfoBodyCrm.js +2 -2
  85. package/dist/components/thread/GroupThreadInfo.d.ts.map +1 -1
  86. package/dist/components/thread/GroupThreadInfo.js +2 -6
  87. package/dist/components/thread/ManualAssignPopover.d.ts.map +1 -1
  88. package/dist/components/thread/ManualAssignPopover.js +6 -8
  89. package/dist/components/thread/SessionDetailCard.d.ts.map +1 -1
  90. package/dist/components/thread/SessionDetailCard.js +3 -3
  91. package/dist/components/thread/SessionSection.d.ts.map +1 -1
  92. package/dist/components/thread/SessionSection.js +3 -9
  93. package/dist/components/thread/SessionTagPicker.d.ts +3 -1
  94. package/dist/components/thread/SessionTagPicker.d.ts.map +1 -1
  95. package/dist/components/thread/SessionTagPicker.js +2 -2
  96. package/dist/components/thread/sessionDateFormat.d.ts +2 -0
  97. package/dist/components/thread/sessionDateFormat.d.ts.map +1 -1
  98. package/dist/components/thread/sessionDateFormat.js +6 -0
  99. package/dist/constants/dateFormat.d.ts +2 -0
  100. package/dist/constants/dateFormat.d.ts.map +1 -0
  101. package/dist/constants/dateFormat.js +2 -0
  102. package/dist/hooks/common/useFetchUrlMetadata.d.ts +1 -0
  103. package/dist/hooks/common/useFetchUrlMetadata.d.ts.map +1 -1
  104. package/dist/hooks/conversation/useBotFlow.d.ts.map +1 -1
  105. package/dist/hooks/conversation/useBotFlow.js +64 -12
  106. package/dist/hooks/conversation/useCloseBotSession.d.ts +2 -0
  107. package/dist/hooks/conversation/useCloseBotSession.d.ts.map +1 -1
  108. package/dist/hooks/conversation/useCloseBotSession.js +17 -1
  109. package/dist/hooks/global/useGlobalEvent.d.ts.map +1 -1
  110. package/dist/hooks/global/useGlobalEvent.js +148 -16
  111. package/dist/hooks/global/useLoginHealth.d.ts +3 -0
  112. package/dist/hooks/global/useLoginHealth.d.ts.map +1 -0
  113. package/dist/hooks/global/useLoginHealth.js +55 -0
  114. package/dist/hooks/group/useCreateGroup.d.ts.map +1 -1
  115. package/dist/hooks/group/useCreateGroup.js +75 -6
  116. package/dist/hooks/init/useChatBootPhase.d.ts +2 -0
  117. package/dist/hooks/init/useChatBootPhase.d.ts.map +1 -1
  118. package/dist/hooks/init/useChatBootPhase.js +11 -6
  119. package/dist/hooks/message/useMessage.d.ts.map +1 -1
  120. package/dist/hooks/message/useMessage.js +97 -28
  121. package/dist/hooks/message/usePinnedMessages.d.ts.map +1 -1
  122. package/dist/hooks/message/usePinnedMessages.js +13 -2
  123. package/dist/hooks/message/useRevokeMessage.d.ts.map +1 -1
  124. package/dist/hooks/message/useRevokeMessage.js +25 -0
  125. package/dist/hooks/message/useSendMessage.d.ts +15 -12
  126. package/dist/hooks/message/useSendMessage.d.ts.map +1 -1
  127. package/dist/hooks/message/useSendMessage.js +199 -133
  128. package/dist/hooks/search/useSearchMessage.d.ts.map +1 -1
  129. package/dist/hooks/search/useSearchMessage.js +57 -3
  130. package/dist/hooks/session/useConversationSessionState.d.ts.map +1 -1
  131. package/dist/hooks/session/useConversationSessionState.js +4 -1
  132. package/dist/hooks/session/useGetSession.d.ts.map +1 -1
  133. package/dist/hooks/session/useGetSession.js +2 -0
  134. package/dist/hooks/session/useGetSessionList.d.ts +235 -0
  135. package/dist/hooks/session/useGetSessionList.d.ts.map +1 -0
  136. package/dist/hooks/session/useGetSessionList.js +23 -0
  137. package/dist/hooks/session/useSessionLatestPreview.d.ts.map +1 -1
  138. package/dist/hooks/session/useSessionLatestPreview.js +34 -4
  139. package/dist/hooks/user/useAuth.d.ts +1 -1
  140. package/dist/hooks/user/useAuth.d.ts.map +1 -1
  141. package/dist/hooks/user/useAuth.js +31 -15
  142. package/dist/index.d.ts +1 -0
  143. package/dist/index.d.ts.map +1 -1
  144. package/dist/index.js +1 -0
  145. package/dist/locales/vi/boot.json +5 -1
  146. package/dist/locales/vi/common.json +35 -15
  147. package/dist/locales/vi/conversation-inbox.json +2 -1
  148. package/dist/screens/conversation-detail/index.d.ts.map +1 -1
  149. package/dist/screens/conversation-detail/index.js +6 -0
  150. package/dist/screens/desk-message/DeskBootCard.d.ts.map +1 -1
  151. package/dist/screens/desk-message/DeskBootCard.js +6 -4
  152. package/dist/store/boot.d.ts +5 -0
  153. package/dist/store/boot.d.ts.map +1 -1
  154. package/dist/store/boot.js +11 -1
  155. package/dist/store/conversation.d.ts.map +1 -1
  156. package/dist/store/conversation.js +5 -1
  157. package/dist/store/session.d.ts +1 -13
  158. package/dist/store/session.d.ts.map +1 -1
  159. package/dist/store/session.js +10 -9
  160. package/dist/store/sessionFolder.d.ts +2 -0
  161. package/dist/store/sessionFolder.d.ts.map +1 -1
  162. package/dist/store/sessionFolder.js +3 -1
  163. package/dist/store/type.d.ts +7 -1
  164. package/dist/store/type.d.ts.map +1 -1
  165. package/dist/styles/global.css +1 -1
  166. package/dist/types/chat.d.ts +1 -1
  167. package/dist/types/chat.d.ts.map +1 -1
  168. package/dist/utils/common.d.ts.map +1 -1
  169. package/dist/utils/common.js +4 -1
  170. package/dist/utils/download.d.ts +10 -0
  171. package/dist/utils/download.d.ts.map +1 -1
  172. package/dist/utils/download.js +51 -0
  173. package/dist/utils/fileValidation.d.ts +7 -0
  174. package/dist/utils/fileValidation.d.ts.map +1 -1
  175. package/dist/utils/fileValidation.js +37 -13
  176. package/dist/utils/latestMessagePreviewable.d.ts +2 -0
  177. package/dist/utils/latestMessagePreviewable.d.ts.map +1 -0
  178. package/dist/utils/latestMessagePreviewable.js +18 -0
  179. package/dist/utils/mentionMembers.d.ts +3 -0
  180. package/dist/utils/mentionMembers.d.ts.map +1 -0
  181. package/dist/utils/mentionMembers.js +29 -0
  182. package/dist/utils/messageLog.d.ts +2 -2
  183. package/dist/utils/messageLog.d.ts.map +1 -1
  184. package/dist/utils/messageLog.js +129 -40
  185. package/dist/utils/notifiableMessage.d.ts +7 -0
  186. package/dist/utils/notifiableMessage.d.ts.map +1 -0
  187. package/dist/utils/notifiableMessage.js +26 -0
  188. package/dist/utils/withSdkTimeout.d.ts +11 -0
  189. package/dist/utils/withSdkTimeout.d.ts.map +1 -0
  190. package/dist/utils/withSdkTimeout.js +30 -0
  191. package/package.json +1 -1
@@ -29,13 +29,13 @@ const GroupInfoBodyCrm = (props) => {
29
29
  const nameChanged = props.nameValue.trim() !== "" && props.nameValue.trim() !== ((_a = props.groupName) !== null && _a !== void 0 ? _a : "");
30
30
  const descChanged = props.descriptionValue.trim() !== ((_b = props.introduction) !== null && _b !== void 0 ? _b : "");
31
31
  const hasAdminRows = props.canConfigPermissions || props.showInviteLink;
32
- return (_jsxs("div", { className: "flex flex-1 flex-col gap-4 overflow-y-auto p-4", children: [_jsxs("div", { className: "flex items-center gap-3", children: [_jsxs("span", { className: "relative block h-[60px] w-[60px] shrink-0", children: [_jsx(GroupAvatar, { faceURL: props.faceURL, groupName: props.groupName, size: 56 }), props.isAvatarUploading && (_jsx("span", { className: "absolute left-0 top-0 flex h-14 w-14 items-center justify-center rounded-full bg-gray-900/30", children: _jsx("span", { className: "h-4 w-4 animate-spin rounded-full border-2 border-white border-t-transparent" }) })), props.canEditAvatar && (_jsxs(_Fragment, { children: [_jsx("input", { ref: props.avatarInputRef, type: "file", accept: ACCEPTED_IMAGE_TYPES.join(", "), className: "hidden", onChange: props.onAvatarSelect }), _jsx("button", { type: "button", disabled: props.isAvatarUploading, onClick: props.onAvatarClick, "aria-label": t("group_info_edit_avatar"), title: t("group_info_edit_avatar"), className: `absolute bottom-0 right-0 flex h-6 w-6 items-center justify-center rounded-full border-2 border-white bg-gray-900 text-white ${FOCUS_RING_CLASS}`, children: _jsx(Icon, { icon: "camera-o", size: 12 }) })] }))] }), _jsxs("div", { className: "flex min-w-0 flex-1 flex-col gap-1", children: [editingField === "name" ? (_jsx(Input, { autoFocus: true, value: props.nameValue, onChange: (event) => props.onNameChange(event.target.value), maxLength: 255, disabled: props.isSaving, "aria-label": t("group_info_edit_name"), onPressEnter: () => nameChanged && props.onSaveEdit("name") })) : (_jsxs("span", { className: "flex items-center gap-0.5", children: [_jsx("span", { className: "truncate text-base font-semibold text-gray-900", title: props.groupName, children: props.groupName }), props.canEditName && (_jsx("button", { type: "button", className: `h-7 w-7 ${ICON_BUTTON}`, onClick: () => props.onStartEdit("name"), "aria-label": t("group_info_edit_name"), title: t("group_info_edit_name"), children: _jsx(Icon, { icon: "edit-o", size: 16 }) }))] })), _jsxs("span", { className: "flex flex-wrap gap-1.5", children: [_jsx(Chip, { icon: props.isPublic ? "globe-o" : "lock-o", tone: "primary", children: t(props.isPublic ? "group_visibility_public" : "group_visibility_private") }), typeof props.memberCount === "number" && (_jsx(Chip, { icon: "user-two-o", tone: "neutral", children: t("member_count", { count: props.memberCount }) }))] })] })] }), editingField === "name" && (_jsx(EditActions, { disabled: props.isSaving, canSave: !props.isSaving && nameChanged, onCancel: () => props.onCancelEdit("name"), onSave: () => props.onSaveEdit("name") })), _jsxs("div", { className: "flex flex-col gap-1", children: [_jsx("span", { className: "text-xs font-medium text-gray-600", children: t("description") }), editingField === "description" ? (_jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Input.TextArea, { ref: descriptionRef, value: props.descriptionValue, onChange: (event) => props.onDescriptionChange(event.target.value), placeholder: t("enter_group_description"), "aria-label": t("description"), autoSize: { minRows: 2, maxRows: 5 }, maxLength: 500, showCount: true, disabled: props.isSaving }), _jsx(EditActions, { disabled: props.isSaving, canSave: !props.isSaving && descChanged, onCancel: () => props.onCancelEdit("description"), onSave: () => props.onSaveEdit("description") })] })) : (_jsxs(_Fragment, { children: [_jsx("p", { className: "m-0 whitespace-pre-wrap break-words text-sm text-gray-600", children: hasDescription ? props.introduction : t("group_info_no_description") }), props.canEditDescription && (_jsxs("button", { type: "button", onClick: () => props.onStartEdit("description"), className: `flex h-6 items-center gap-1.5 self-start rounded-lg text-[13px] font-medium text-primary-500 hover:text-primary-600 ${FOCUS_RING_CLASS}`, children: [_jsx(Icon, { icon: hasDescription ? "edit-o" : "plus-o", size: 14 }), t(hasDescription ? "group_info_edit_description" : "group_info_add_description")] }))] }))] }), _jsx(SectionLabel, { children: t("group_settings_section") }), _jsx(InfoCard, { children: [
32
+ return (_jsxs("div", { className: "flex flex-1 flex-col gap-4 overflow-y-auto p-4", children: [_jsxs("div", { className: "flex items-center gap-3", children: [_jsxs("span", { className: "relative block h-[60px] w-[60px] shrink-0", children: [_jsx(GroupAvatar, { faceURL: props.faceURL, groupName: props.groupName, size: 56 }), props.isAvatarUploading && (_jsx("span", { className: "absolute left-0 top-0 flex h-14 w-14 items-center justify-center rounded-full bg-gray-900/30", children: _jsx("span", { className: "h-4 w-4 animate-spin rounded-full border-2 border-white border-t-transparent" }) })), props.canEditAvatar && (_jsxs(_Fragment, { children: [_jsx("input", { ref: props.avatarInputRef, type: "file", accept: ACCEPTED_IMAGE_TYPES.join(", "), className: "hidden", onChange: props.onAvatarSelect }), _jsx("button", { type: "button", disabled: props.isAvatarUploading, onClick: props.onAvatarClick, "aria-label": t("group_info_edit_avatar"), title: t("group_info_edit_avatar"), className: `absolute bottom-0 right-0 flex h-6 w-6 items-center justify-center rounded-full border-2 border-white bg-gray-900 text-white ${FOCUS_RING_CLASS}`, children: _jsx(Icon, { icon: "camera-o", size: 12 }) })] }))] }), _jsxs("div", { className: "flex min-w-0 flex-1 flex-col gap-1", children: [editingField === "name" ? (_jsx(Input, { autoFocus: true, value: props.nameValue, onChange: (event) => props.onNameChange(event.target.value), maxLength: 255, showCount: true, disabled: props.isSaving, "aria-label": t("group_info_edit_name"), onPressEnter: () => nameChanged && props.onSaveEdit("name") })) : (_jsxs("span", { className: "flex items-center gap-0.5", children: [_jsx("span", { className: "truncate text-base font-semibold text-gray-900", title: props.groupName, children: props.groupName }), props.canEditName && (_jsx("button", { type: "button", className: `h-7 w-7 ${ICON_BUTTON}`, onClick: () => props.onStartEdit("name"), "aria-label": t("group_info_edit_name"), title: t("group_info_edit_name"), children: _jsx(Icon, { icon: "edit-o", size: 16 }) }))] })), _jsxs("span", { className: "flex flex-wrap gap-1.5", children: [_jsx(Chip, { icon: props.isPublic ? "globe-o" : "lock-o", tone: "primary", children: t(props.isPublic ? "group_visibility_public" : "group_visibility_private") }), typeof props.memberCount === "number" && (_jsx(Chip, { icon: "user-two-o", tone: "neutral", children: t("member_count", { count: props.memberCount }) }))] })] })] }), editingField === "name" && (_jsx(EditActions, { disabled: props.isSaving, canSave: !props.isSaving && nameChanged, onCancel: () => props.onCancelEdit("name"), onSave: () => props.onSaveEdit("name") })), _jsxs("div", { className: "flex flex-col gap-1", children: [_jsx("span", { className: "text-xs font-medium text-gray-600", children: t("description") }), editingField === "description" ? (_jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Input.TextArea, { ref: descriptionRef, value: props.descriptionValue, onChange: (event) => props.onDescriptionChange(event.target.value), placeholder: t("enter_group_description"), "aria-label": t("description"), autoSize: { minRows: 2, maxRows: 5 }, maxLength: 500, showCount: true, disabled: props.isSaving }), _jsx(EditActions, { disabled: props.isSaving, canSave: !props.isSaving && descChanged, onCancel: () => props.onCancelEdit("description"), onSave: () => props.onSaveEdit("description") })] })) : (_jsxs(_Fragment, { children: [_jsx("p", { className: "m-0 whitespace-pre-wrap break-words text-sm text-gray-600", children: hasDescription ? props.introduction : t("group_info_no_description") }), props.canEditDescription && (_jsxs("button", { type: "button", onClick: () => props.onStartEdit("description"), className: `flex h-6 items-center gap-1.5 self-start rounded-lg text-[13px] font-medium text-primary-500 hover:text-primary-600 ${FOCUS_RING_CLASS}`, children: [_jsx(Icon, { icon: hasDescription ? "edit-o" : "plus-o", size: 14 }), t(hasDescription ? "group_info_edit_description" : "group_info_add_description")] }))] }))] }), _jsx(SectionLabel, { children: t("group_settings_section") }), _jsx(InfoCard, { children: [
33
33
  _jsx(SwitchRow, { icon: "bell-o", label: t("notification"), checked: props.notifyEnabled, loading: props.isTogglingNotification, onChange: props.onToggleNotification }, "notify"),
34
34
  props.showAllowSend && (_jsx(SwitchRow, { icon: "chat-square-o", label: t("members_can_send_message"), hint: t("allow_send_message_hint"), checked: props.allowSendMessage, loading: props.isTogglingSendMessage, onChange: props.onToggleSendMessage }, "send")),
35
35
  ] }), hasAdminRows && (_jsxs(_Fragment, { children: [_jsx(SectionLabel, { children: t("group_admin_section") }), _jsx(InfoCard, { children: [
36
36
  props.canConfigPermissions && _jsx(NavRow, { icon: "shield-o", label: t("member_permission_row"), onClick: () => props.onOpenRow("member-permission") }, "member"),
37
37
  props.canConfigPermissions && _jsx(NavRow, { icon: "shield-o", label: t("admin_permission_row"), onClick: () => props.onOpenRow("admin-permission") }, "admin"),
38
38
  props.showInviteLink && _jsx(NavRow, { icon: "link-alt-o", label: t("invite_via_link_short"), onClick: () => props.onOpenRow("invite-link") }, "invite"),
39
- ] })] })), _jsxs("div", { className: "flex flex-col gap-0.5 border-t border-gray-25 pt-2", children: [props.isAdminOrOwner && _jsx(DangerRow, { icon: "trash-o", label: t("group_delete_conversation"), onClick: () => props.onConfirm("delete") }), !props.isViewOnlyMember && _jsx(DangerRow, { icon: "logout-o", label: t("leave_group"), onClick: () => props.onConfirm("leave") }), !props.isViewOnlyMember && (_jsx(DangerRow, { icon: "user-del-o", label: t("disband_group"), onClick: () => props.onConfirm("disband"), disabledNote: props.isOwner ? undefined : t("group_owner_only") }))] })] }));
39
+ ] })] })), _jsxs("div", { className: "flex flex-col gap-0.5 border-t border-gray-25 pt-2", children: [props.isAdminOrOwner && _jsx(DangerRow, { icon: "trash-o", label: t("group_delete_conversation"), onClick: () => props.onConfirm("delete") }), !props.isViewOnlyMember && _jsx(DangerRow, { icon: "logout-o", label: t("leave_group"), onClick: () => props.onConfirm("leave") }), props.isOwner && (_jsx(DangerRow, { icon: "user-del-o", label: t("disband_group"), onClick: () => props.onConfirm("disband") }))] })] }));
40
40
  };
41
41
  export default GroupInfoBodyCrm;
@@ -1 +1 @@
1
- {"version":3,"file":"GroupThreadInfo.d.ts","sourceRoot":"","sources":["../../../src/components/thread/GroupThreadInfo.tsx"],"names":[],"mappings":"AA0BA,MAAM,WAAW,qBAAqB;IACpC,oBAAoB,CAAC,EAAE,OAAO,CAAC;IAC/B,iBAAiB,CAAC,EAAE,OAAO,CAAC;CAC7B;AAED,UAAU,oBAAoB;IAC5B,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB,MAAM,CAAC,EAAE,qBAAqB,CAAC;CAChC;AASD,QAAA,MAAM,eAAe,GAAI,qBAAqB,oBAAoB,mDAirBjE,CAAC;AAEF,eAAe,eAAe,CAAC"}
1
+ {"version":3,"file":"GroupThreadInfo.d.ts","sourceRoot":"","sources":["../../../src/components/thread/GroupThreadInfo.tsx"],"names":[],"mappings":"AA0BA,MAAM,WAAW,qBAAqB;IACpC,oBAAoB,CAAC,EAAE,OAAO,CAAC;IAC/B,iBAAiB,CAAC,EAAE,OAAO,CAAC;CAC7B;AAED,UAAU,oBAAoB;IAC5B,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB,MAAM,CAAC,EAAE,qBAAqB,CAAC;CAChC;AASD,QAAA,MAAM,eAAe,GAAI,qBAAqB,oBAAoB,mDA6qBjE,CAAC;AAEF,eAAe,eAAe,CAAC"}
@@ -47,10 +47,6 @@ const GroupThreadInfo = ({ onClose, config }) => {
47
47
  // Non-member viewing an existing public group in view-only mode — no
48
48
  // longer a member, so "Rời khỏi nhóm" (leave) doesn't apply.
49
49
  const isViewOnlyMember = useConversationStore((state) => state.isViewOnlyMember);
50
- // Feature ships to staging first (DROPPII-30551). Same env var apps/web already
51
- // reads in src/app/layout.tsx:51 — this repo has no feature-flag service and
52
- // this ticket is not the place to introduce one.
53
- const isInviteLinkFeatureEnabled = process.env.NEXT_PUBLIC_ENV === "stg";
54
50
  const [editingField, setEditingField] = useState(null);
55
51
  const [nameValue, setNameValue] = useState((_a = currentGroupInfo === null || currentGroupInfo === void 0 ? void 0 : currentGroupInfo.groupName) !== null && _a !== void 0 ? _a : "");
56
52
  const [descriptionValue, setDescriptionValue] = useState((_b = currentGroupInfo === null || currentGroupInfo === void 0 ? void 0 : currentGroupInfo.introduction) !== null && _b !== void 0 ? _b : "");
@@ -256,10 +252,10 @@ const GroupThreadInfo = ({ onClose, config }) => {
256
252
  if (!groupID) {
257
253
  return null;
258
254
  }
259
- return (_jsxs("div", { className: "h-full w-full bg-white flex flex-col relative overflow-hidden", "data-cp-name": "GroupThreadInfo", children: [isCrm ? (_jsxs(_Fragment, { children: [_jsx(GroupPanelHeader, { title: t("group_info_title"), onClose: onClose }), _jsx(GroupInfoBodyCrm, { groupName: groupDisplayName, faceURL: currentGroupInfo === null || currentGroupInfo === void 0 ? void 0 : currentGroupInfo.faceURL, introduction: currentGroupInfo === null || currentGroupInfo === void 0 ? void 0 : currentGroupInfo.introduction, memberCount: currentGroupInfo === null || currentGroupInfo === void 0 ? void 0 : currentGroupInfo.memberCount, isPublic: (currentGroupInfo === null || currentGroupInfo === void 0 ? void 0 : currentGroupInfo.visibility) === GroupVisibility.Public, canEditAvatar: permissions.updateGroupAvatar, canEditName: permissions.updateGroupName, canEditDescription: permissions.updateGroupDescription, canConfigPermissions: permissions.configGroupPermission, showInviteLink: isInviteLinkFeatureEnabled && !isViewOnlyMember, showAllowSend: Boolean(config === null || config === void 0 ? void 0 : config.showAllowSendMessage) && isOwner, isOwner: isOwner, isAdminOrOwner: isAdminOrOwner, isViewOnlyMember: isViewOnlyMember, notifyEnabled: notifyEnabled, isTogglingNotification: isTogglingNotification, allowSendMessage: allowSendMessage, isTogglingSendMessage: isTogglingSendMessage, isAvatarUploading: isAvatarUploading, isSaving: isSavingGroupInfo, editingField: editingField, nameValue: nameValue, descriptionValue: descriptionValue, avatarInputRef: avatarInputRef, onNameChange: setNameValue, onDescriptionChange: setDescriptionValue, onStartEdit: (field) => (field === "name" ? startEditName() : startEditDescription()), onCancelEdit: (field) => (field === "name" ? cancelEditName() : cancelEditDescription()), onSaveEdit: (field) => (field === "name" ? saveName() : saveDescription()), onAvatarClick: handleAvatarClick, onAvatarSelect: handleAvatarSelect, onToggleNotification: handleToggleNotification, onToggleSendMessage: handleToggleSendMessage, onOpenRow: setActiveRow, onConfirm: setConfirmAction })] })) : (_jsxs(_Fragment, { children: [_jsxs("div", { className: "flex items-center justify-between px-4 py-4 border-b border-gray-100", children: [_jsx("span", { className: "text-lg font-medium", children: t("thread_info_title") }), _jsx("button", { type: "button", className: "flex items-center justify-center w-8 h-8 rounded-full text-gray-500 hover:bg-gray-100", onClick: onClose, children: _jsx(Icon, { icon: "close-o", size: 20 }) })] }), _jsxs("div", { className: "flex-1 overflow-y-auto", children: [_jsxs("div", { className: "flex items-center gap-3 px-4 py-4", children: [_jsxs("div", { className: "relative shrink-0", children: [_jsx(GroupAvatar, { faceURL: currentGroupInfo === null || currentGroupInfo === void 0 ? void 0 : currentGroupInfo.faceURL, groupName: groupDisplayName, size: 56 }), isAvatarUploading && (_jsx("span", { className: "absolute inset-0 flex items-center justify-center rounded-full bg-black/30", children: _jsx("span", { className: "h-4 w-4 animate-spin rounded-full border-2 border-white border-t-transparent" }) })), permissions.updateGroupAvatar && (_jsxs(_Fragment, { children: [_jsx("input", { ref: avatarInputRef, type: "file", accept: ACCEPTED_IMAGE_TYPES.join(", "), className: "hidden", onChange: handleAvatarSelect }), _jsx("button", { type: "button", disabled: isAvatarUploading, onClick: handleAvatarClick, className: "absolute -bottom-1 -right-1 flex items-center justify-center w-5 h-5 rounded-full bg-black text-white", children: _jsx(Icon, { icon: "camera-o", size: 12 }) })] }))] }), _jsx("div", { className: "flex-1 min-w-0", children: editingField !== "name" && (_jsxs("div", { className: "flex items-center gap-2 min-w-0", children: [_jsx("p", { className: "text-base font-medium truncate", children: groupDisplayName }), permissions.updateGroupName && (_jsx(Icon, { icon: "edit-o", size: 16, className: "shrink-0 cursor-pointer text-gray-500", onClick: startEditName }))] })) })] }), editingField === "name" && (_jsx("div", { className: "px-4 pb-4", children: _jsxs("div", { className: "flex flex-col gap-3 bg-[#f5f5f5] rounded-lg p-3", children: [_jsx(Input, { autoFocus: true, value: nameValue, onChange: (event) => setNameValue(event.target.value), maxLength: 255, showCount: true, disabled: isSavingGroupInfo, className: "bg-white" }), _jsxs("div", { className: "flex items-center justify-end gap-2", children: [_jsx("button", { type: "button", className: "px-4 py-1.5 rounded-lg border border-[var(--dc-color-primary-hover)] text-[var(--dc-color-primary-hover)] text-sm font-medium disabled:border-gray-200 disabled:text-gray-300", disabled: isSavingGroupInfo, onClick: cancelEditName, children: t("cancel") }), _jsx("button", { type: "button", className: "px-4 py-1.5 rounded-lg bg-[var(--dc-color-primary-hover)] text-white text-sm font-medium disabled:bg-gray-100 disabled:text-gray-300", disabled: isSavingGroupInfo ||
255
+ return (_jsxs("div", { className: "h-full w-full bg-white flex flex-col relative overflow-hidden", "data-cp-name": "GroupThreadInfo", children: [isCrm ? (_jsxs(_Fragment, { children: [_jsx(GroupPanelHeader, { title: t("group_info_title"), onClose: onClose }), _jsx(GroupInfoBodyCrm, { groupName: groupDisplayName, faceURL: currentGroupInfo === null || currentGroupInfo === void 0 ? void 0 : currentGroupInfo.faceURL, introduction: currentGroupInfo === null || currentGroupInfo === void 0 ? void 0 : currentGroupInfo.introduction, memberCount: currentGroupInfo === null || currentGroupInfo === void 0 ? void 0 : currentGroupInfo.memberCount, isPublic: (currentGroupInfo === null || currentGroupInfo === void 0 ? void 0 : currentGroupInfo.visibility) === GroupVisibility.Public, canEditAvatar: permissions.updateGroupAvatar, canEditName: permissions.updateGroupName, canEditDescription: permissions.updateGroupDescription, canConfigPermissions: permissions.configGroupPermission, showInviteLink: !isViewOnlyMember, showAllowSend: Boolean(config === null || config === void 0 ? void 0 : config.showAllowSendMessage) && isOwner, isOwner: isOwner, isAdminOrOwner: isAdminOrOwner, isViewOnlyMember: isViewOnlyMember, notifyEnabled: notifyEnabled, isTogglingNotification: isTogglingNotification, allowSendMessage: allowSendMessage, isTogglingSendMessage: isTogglingSendMessage, isAvatarUploading: isAvatarUploading, isSaving: isSavingGroupInfo, editingField: editingField, nameValue: nameValue, descriptionValue: descriptionValue, avatarInputRef: avatarInputRef, onNameChange: setNameValue, onDescriptionChange: setDescriptionValue, onStartEdit: (field) => (field === "name" ? startEditName() : startEditDescription()), onCancelEdit: (field) => (field === "name" ? cancelEditName() : cancelEditDescription()), onSaveEdit: (field) => (field === "name" ? saveName() : saveDescription()), onAvatarClick: handleAvatarClick, onAvatarSelect: handleAvatarSelect, onToggleNotification: handleToggleNotification, onToggleSendMessage: handleToggleSendMessage, onOpenRow: setActiveRow, onConfirm: setConfirmAction })] })) : (_jsxs(_Fragment, { children: [_jsxs("div", { className: "flex items-center justify-between px-4 py-4 border-b border-gray-100", children: [_jsx("span", { className: "text-lg font-medium", children: t("thread_info_title") }), _jsx("button", { type: "button", className: "flex items-center justify-center w-8 h-8 rounded-full text-gray-500 hover:bg-gray-100", onClick: onClose, children: _jsx(Icon, { icon: "close-o", size: 20 }) })] }), _jsxs("div", { className: "flex-1 overflow-y-auto", children: [_jsxs("div", { className: "flex items-center gap-3 px-4 py-4", children: [_jsxs("div", { className: "relative shrink-0", children: [_jsx(GroupAvatar, { faceURL: currentGroupInfo === null || currentGroupInfo === void 0 ? void 0 : currentGroupInfo.faceURL, groupName: groupDisplayName, size: 56 }), isAvatarUploading && (_jsx("span", { className: "absolute inset-0 flex items-center justify-center rounded-full bg-black/30", children: _jsx("span", { className: "h-4 w-4 animate-spin rounded-full border-2 border-white border-t-transparent" }) })), permissions.updateGroupAvatar && (_jsxs(_Fragment, { children: [_jsx("input", { ref: avatarInputRef, type: "file", accept: ACCEPTED_IMAGE_TYPES.join(", "), className: "hidden", onChange: handleAvatarSelect }), _jsx("button", { type: "button", disabled: isAvatarUploading, onClick: handleAvatarClick, className: "absolute -bottom-1 -right-1 flex items-center justify-center w-5 h-5 rounded-full bg-black text-white", children: _jsx(Icon, { icon: "camera-o", size: 12 }) })] }))] }), _jsx("div", { className: "flex-1 min-w-0", children: editingField !== "name" && (_jsxs("div", { className: "flex items-center gap-2 min-w-0", children: [_jsx("p", { className: "text-base font-medium truncate", children: groupDisplayName }), permissions.updateGroupName && (_jsx(Icon, { icon: "edit-o", size: 16, className: "shrink-0 cursor-pointer text-gray-500", onClick: startEditName }))] })) })] }), editingField === "name" && (_jsx("div", { className: "px-4 pb-4", children: _jsxs("div", { className: "flex flex-col gap-3 bg-[#f5f5f5] rounded-lg p-3", children: [_jsx(Input, { autoFocus: true, value: nameValue, onChange: (event) => setNameValue(event.target.value), maxLength: 255, showCount: true, disabled: isSavingGroupInfo, className: "bg-white" }), _jsxs("div", { className: "flex items-center justify-end gap-2", children: [_jsx("button", { type: "button", className: "px-4 py-1.5 rounded-lg border border-[var(--dc-color-primary-hover)] text-[var(--dc-color-primary-hover)] text-sm font-medium disabled:border-gray-200 disabled:text-gray-300", disabled: isSavingGroupInfo, onClick: cancelEditName, children: t("cancel") }), _jsx("button", { type: "button", className: "px-4 py-1.5 rounded-lg bg-[var(--dc-color-primary-hover)] text-white text-sm font-medium disabled:bg-gray-100 disabled:text-gray-300", disabled: isSavingGroupInfo ||
260
256
  !nameValue.trim() ||
261
257
  nameValue.trim() === ((_c = currentGroupInfo === null || currentGroupInfo === void 0 ? void 0 : currentGroupInfo.groupName) !== null && _c !== void 0 ? _c : ""), onClick: saveName, children: t("save") })] })] }) })), _jsx("div", { className: "px-4 pb-4", children: editingField === "description" ? (_jsxs("div", { className: "flex flex-col gap-3 bg-[#f5f5f5] rounded-lg p-3", children: [_jsxs("span", { className: "flex items-center gap-2 text-sm font-medium text-gray-900", children: [_jsx(Icon, { icon: "info-circle-o", size: 18, className: "text-gray-500" }), t("description")] }), _jsx(Input.TextArea, { ref: descriptionInputRef, value: descriptionValue, onChange: (event) => setDescriptionValue(event.target.value), placeholder: t("enter_group_description"), autoSize: { minRows: 1, maxRows: 4 }, maxLength: 500, disabled: isSavingGroupInfo, className: "bg-white" }), _jsxs("span", { className: "self-end text-xs text-gray-400", children: [descriptionValue.length, " / 500"] }), _jsxs("div", { className: "flex items-center justify-end gap-2", children: [_jsx("button", { type: "button", className: "px-4 py-1.5 rounded-lg border border-[var(--dc-color-primary-hover)] text-[var(--dc-color-primary-hover)] text-sm font-medium disabled:border-gray-200 disabled:text-gray-300", disabled: isSavingGroupInfo, onClick: cancelEditDescription, children: t("cancel") }), _jsx("button", { type: "button", className: "px-4 py-1.5 rounded-lg bg-[var(--dc-color-primary-hover)] text-white text-sm font-medium disabled:bg-gray-100 disabled:text-gray-300", disabled: isSavingGroupInfo ||
262
- descriptionValue.trim() === ((_d = currentGroupInfo === null || currentGroupInfo === void 0 ? void 0 : currentGroupInfo.introduction) !== null && _d !== void 0 ? _d : ""), onClick: saveDescription, children: t("save") })] })] })) : (_jsxs("div", { className: "flex items-start justify-between gap-2 bg-[#f5f5f5] rounded-lg px-3 py-2.5", children: [_jsxs("div", { className: "flex items-start gap-2 flex-1 min-w-0", children: [_jsx(Icon, { icon: "info-circle-o", size: 18, className: "shrink-0 mt-0.5 text-gray-900" }), _jsxs("div", { className: "flex flex-col gap-0.5 min-w-0", children: [_jsx("span", { className: "text-sm font-semibold text-gray-900", children: t("description") }), _jsx("p", { className: "text-sm text-gray-400 truncate", children: (currentGroupInfo === null || currentGroupInfo === void 0 ? void 0 : currentGroupInfo.introduction) || t("no_description") })] })] }), permissions.updateGroupDescription && (_jsx(Icon, { icon: "edit-o", size: 16, className: "shrink-0 cursor-pointer text-gray-500", onClick: startEditDescription }))] })) }), _jsxs("div", { className: "flex flex-col gap-1 bg-[#f5f5f5] mx-4 rounded-lg overflow-hidden", children: [_jsxs("div", { className: "flex items-center justify-between px-3 py-3", children: [_jsxs("span", { className: "flex items-center gap-3 text-sm", children: [_jsx(Icon, { icon: "bell-o", size: 18 }), t("notification")] }), _jsx(Switch, { checked: notifyEnabled, onChange: handleToggleNotification, loading: isTogglingNotification })] }), (config === null || config === void 0 ? void 0 : config.showAllowSendMessage) && isOwner && (_jsxs("div", { className: "flex items-center justify-between px-3 py-3", children: [_jsxs("span", { className: "flex items-center gap-3 text-sm", children: [_jsx(Icon, { icon: "user-check-o", size: 18 }), t("allow_send_message")] }), _jsx(Switch, { checked: allowSendMessage, onChange: handleToggleSendMessage, loading: isTogglingSendMessage })] })), isAdminOrOwner && (config === null || config === void 0 ? void 0 : config.showMutedChatList) && (_jsxs("button", { type: "button", className: "flex items-center justify-between px-3 py-3 text-left", onClick: () => setActiveRow("muted-chat-list"), children: [_jsxs("span", { className: "flex items-center gap-3 text-sm", children: [_jsx(Icon, { icon: "bell-off-o", size: 18 }), t("muted_chat_list")] }), _jsx(Icon, { icon: "angle-right-o", size: 16, className: "text-gray-400" })] })), isAdminOrOwner && (_jsxs("button", { type: "button", className: "flex items-center justify-between px-3 py-3 text-left", onClick: () => setActiveRow("block-list"), children: [_jsxs("span", { className: "flex items-center gap-3 text-sm", children: [_jsx(Icon, { icon: "user-del-o", size: 18 }), t("block_list")] }), _jsx(Icon, { icon: "angle-right-o", size: 16, className: "text-gray-400" })] }))] }), permissions.configGroupPermission && (_jsxs("div", { className: "flex flex-col gap-1 bg-[#f5f5f5] mx-4 mt-3 rounded-lg overflow-hidden", children: [_jsxs("button", { type: "button", className: "flex items-center justify-between px-3 py-3 text-left", onClick: () => setActiveRow("member-permission"), children: [_jsxs("span", { className: "flex items-center gap-3 text-sm", children: [_jsx(Icon, { icon: "shield-o", size: 18 }), t("member_permission_settings")] }), _jsx(Icon, { icon: "angle-right-o", size: 16, className: "text-gray-400" })] }), _jsxs("button", { type: "button", className: "flex items-center justify-between px-3 py-3 text-left", onClick: () => setActiveRow("admin-permission"), children: [_jsxs("span", { className: "flex items-center gap-3 text-sm", children: [_jsx(Icon, { icon: "shield-done-o", size: 18 }), t("admin_permission_settings")] }), _jsx(Icon, { icon: "angle-right-o", size: 16, className: "text-gray-400" })] })] })), isInviteLinkFeatureEnabled && !isViewOnlyMember && (_jsx("div", { className: "mx-4 mt-3 rounded-lg overflow-hidden bg-[#f5f5f5]", children: _jsxs("button", { type: "button", className: "w-full flex items-center justify-between px-3 py-3 text-left text-sm", onClick: () => setActiveRow("invite-link"), children: [_jsxs("span", { className: "flex items-center gap-3", children: [_jsx(Icon, { icon: "link-alt-o", size: 18 }), t("invite_via_link")] }), _jsx(Icon, { icon: "angle-right-o", size: 16, className: "text-gray-400" })] }) })), _jsxs("div", { className: "flex flex-col gap-1 bg-[#f5f5f5] mx-4 mt-3 mb-4 rounded-lg overflow-hidden", children: [isAdminOrOwner && (_jsxs("button", { type: "button", className: "flex items-center gap-3 px-3 py-3 text-left text-sm text-red-500", onClick: () => setConfirmAction("delete"), children: [_jsx(Icon, { icon: "trash-o", size: 18 }), t("delete_conversation")] })), !isViewOnlyMember && (_jsxs("button", { type: "button", className: "flex items-center gap-3 px-3 py-3 text-left text-sm text-red-500", onClick: () => setConfirmAction("leave"), children: [_jsx(Icon, { icon: "logout-o", size: 18 }), t("leave_group")] })), isOwner && (_jsxs("button", { type: "button", className: "flex items-center gap-3 px-3 py-3 text-left text-sm text-red-500", onClick: () => setConfirmAction("disband"), children: [_jsx(Icon, { icon: "user-del-o", size: 18 }), t("disband_group")] }))] })] })] })), _jsx(ConfirmModal, { open: confirmAction === "leave", onOk: handleConfirmLeave, onCancel: () => setConfirmAction(null), title: t("leave_group_confirm_title"), okText: t("leave_group"), cancelText: t(isCrm ? "action_cancel" : "cancel"), okType: "danger", confirmLoading: isLeaving, children: _jsx("p", { children: t("leave_group_confirm_message") }) }), _jsx(ConfirmModal, { open: confirmAction === "disband", onOk: handleConfirmDisband, onCancel: () => setConfirmAction(null), title: t("disband_group_confirm_title"), okText: t("disband_group"), cancelText: t(isCrm ? "action_cancel" : "cancel"), okType: "danger", confirmLoading: isDismissing, children: _jsx("p", { children: t("disband_group_confirm_message") }) }), _jsx(ConfirmModal, { open: confirmAction === "delete", onOk: handleConfirmDelete, onCancel: () => setConfirmAction(null), title: t("delete_conversation_confirm_title"), okText: t("delete_conversation"), cancelText: t(isCrm ? "action_cancel" : "cancel"), okType: "danger", confirmLoading: isDeleting, children: _jsx("p", { children: t("delete_conversation_confirm_message") }) }), _jsx(DisableSendPermissionConfirm, { open: confirmAction === "mute-all", title: t("disable_send_permission_title_all_members"), onConfirm: () => {
258
+ descriptionValue.trim() === ((_d = currentGroupInfo === null || currentGroupInfo === void 0 ? void 0 : currentGroupInfo.introduction) !== null && _d !== void 0 ? _d : ""), onClick: saveDescription, children: t("save") })] })] })) : (_jsxs("div", { className: "flex items-start justify-between gap-2 bg-[#f5f5f5] rounded-lg px-3 py-2.5", children: [_jsxs("div", { className: "flex items-start gap-2 flex-1 min-w-0", children: [_jsx(Icon, { icon: "info-circle-o", size: 18, className: "shrink-0 mt-0.5 text-gray-900" }), _jsxs("div", { className: "flex flex-col gap-0.5 min-w-0", children: [_jsx("span", { className: "text-sm font-semibold text-gray-900", children: t("description") }), _jsx("p", { className: "text-sm text-gray-400 truncate", children: (currentGroupInfo === null || currentGroupInfo === void 0 ? void 0 : currentGroupInfo.introduction) || t("no_description") })] })] }), permissions.updateGroupDescription && (_jsx(Icon, { icon: "edit-o", size: 16, className: "shrink-0 cursor-pointer text-gray-500", onClick: startEditDescription }))] })) }), _jsxs("div", { className: "flex flex-col gap-1 bg-[#f5f5f5] mx-4 rounded-lg overflow-hidden", children: [_jsxs("div", { className: "flex items-center justify-between px-3 py-3", children: [_jsxs("span", { className: "flex items-center gap-3 text-sm", children: [_jsx(Icon, { icon: "bell-o", size: 18 }), t("notification")] }), _jsx(Switch, { checked: notifyEnabled, onChange: handleToggleNotification, loading: isTogglingNotification })] }), (config === null || config === void 0 ? void 0 : config.showAllowSendMessage) && isOwner && (_jsxs("div", { className: "flex items-center justify-between px-3 py-3", children: [_jsxs("span", { className: "flex items-center gap-3 text-sm", children: [_jsx(Icon, { icon: "user-check-o", size: 18 }), t("allow_send_message")] }), _jsx(Switch, { checked: allowSendMessage, onChange: handleToggleSendMessage, loading: isTogglingSendMessage })] })), isAdminOrOwner && (config === null || config === void 0 ? void 0 : config.showMutedChatList) && (_jsxs("button", { type: "button", className: "flex items-center justify-between px-3 py-3 text-left", onClick: () => setActiveRow("muted-chat-list"), children: [_jsxs("span", { className: "flex items-center gap-3 text-sm", children: [_jsx(Icon, { icon: "bell-off-o", size: 18 }), t("muted_chat_list")] }), _jsx(Icon, { icon: "angle-right-o", size: 16, className: "text-gray-400" })] })), isAdminOrOwner && (_jsxs("button", { type: "button", className: "flex items-center justify-between px-3 py-3 text-left", onClick: () => setActiveRow("block-list"), children: [_jsxs("span", { className: "flex items-center gap-3 text-sm", children: [_jsx(Icon, { icon: "user-del-o", size: 18 }), t("block_list")] }), _jsx(Icon, { icon: "angle-right-o", size: 16, className: "text-gray-400" })] }))] }), permissions.configGroupPermission && (_jsxs("div", { className: "flex flex-col gap-1 bg-[#f5f5f5] mx-4 mt-3 rounded-lg overflow-hidden", children: [_jsxs("button", { type: "button", className: "flex items-center justify-between px-3 py-3 text-left", onClick: () => setActiveRow("member-permission"), children: [_jsxs("span", { className: "flex items-center gap-3 text-sm", children: [_jsx(Icon, { icon: "shield-o", size: 18 }), t("member_permission_settings")] }), _jsx(Icon, { icon: "angle-right-o", size: 16, className: "text-gray-400" })] }), _jsxs("button", { type: "button", className: "flex items-center justify-between px-3 py-3 text-left", onClick: () => setActiveRow("admin-permission"), children: [_jsxs("span", { className: "flex items-center gap-3 text-sm", children: [_jsx(Icon, { icon: "shield-done-o", size: 18 }), t("admin_permission_settings")] }), _jsx(Icon, { icon: "angle-right-o", size: 16, className: "text-gray-400" })] })] })), !isViewOnlyMember && (_jsx("div", { className: "mx-4 mt-3 rounded-lg overflow-hidden bg-[#f5f5f5]", children: _jsxs("button", { type: "button", className: "w-full flex items-center justify-between px-3 py-3 text-left text-sm", onClick: () => setActiveRow("invite-link"), children: [_jsxs("span", { className: "flex items-center gap-3", children: [_jsx(Icon, { icon: "link-alt-o", size: 18 }), t("invite_via_link")] }), _jsx(Icon, { icon: "angle-right-o", size: 16, className: "text-gray-400" })] }) })), _jsxs("div", { className: "flex flex-col gap-1 bg-[#f5f5f5] mx-4 mt-3 mb-4 rounded-lg overflow-hidden", children: [isAdminOrOwner && (_jsxs("button", { type: "button", className: "flex items-center gap-3 px-3 py-3 text-left text-sm text-red-500", onClick: () => setConfirmAction("delete"), children: [_jsx(Icon, { icon: "trash-o", size: 18 }), t("delete_conversation")] })), !isViewOnlyMember && (_jsxs("button", { type: "button", className: "flex items-center gap-3 px-3 py-3 text-left text-sm text-red-500", onClick: () => setConfirmAction("leave"), children: [_jsx(Icon, { icon: "logout-o", size: 18 }), t("leave_group")] })), isOwner && (_jsxs("button", { type: "button", className: "flex items-center gap-3 px-3 py-3 text-left text-sm text-red-500", onClick: () => setConfirmAction("disband"), children: [_jsx(Icon, { icon: "user-del-o", size: 18 }), t("disband_group")] }))] })] })] })), _jsx(ConfirmModal, { open: confirmAction === "leave", onOk: handleConfirmLeave, onCancel: () => setConfirmAction(null), title: t("leave_group_confirm_title"), okText: t("leave_group"), cancelText: t(isCrm ? "action_cancel" : "cancel"), okType: "danger", confirmLoading: isLeaving, children: _jsx("p", { children: t("leave_group_confirm_message") }) }), _jsx(ConfirmModal, { open: confirmAction === "disband", onOk: handleConfirmDisband, onCancel: () => setConfirmAction(null), title: t("disband_group_confirm_title"), okText: t("disband_group"), cancelText: t(isCrm ? "action_cancel" : "cancel"), okType: "danger", confirmLoading: isDismissing, children: _jsx("p", { children: t("disband_group_confirm_message") }) }), _jsx(ConfirmModal, { open: confirmAction === "delete", onOk: handleConfirmDelete, onCancel: () => setConfirmAction(null), title: t("delete_conversation_confirm_title"), okText: t("delete_conversation"), cancelText: t(isCrm ? "action_cancel" : "cancel"), okType: "danger", confirmLoading: isDeleting, children: _jsx("p", { children: t("delete_conversation_confirm_message") }) }), _jsx(DisableSendPermissionConfirm, { open: confirmAction === "mute-all", title: t("disable_send_permission_title_all_members"), onConfirm: () => {
263
259
  setConfirmAction(null);
264
260
  applySendMessage(false);
265
261
  }, onCancel: () => setConfirmAction(null) }), _jsx(MemberPermissionPanel, { open: activeRow === "member-permission", groupID: groupID, onClose: () => setActiveRow(null) }), _jsx(AdminPermissionModal, { open: activeRow === "admin-permission", groupID: groupID, onClose: () => setActiveRow(null), inline: true }), _jsx(InviteLinkPanel, { open: activeRow === "invite-link", groupID: groupID, conversationID: conversationID, onClose: () => setActiveRow(null) }), (activeRow === "muted-chat-list" || activeRow === "block-list") && (_jsx(Modal, { centered: true, open: true, footer: null, onCancel: () => setActiveRow(null), title: t(activeRow === "muted-chat-list" ? "muted_chat_list" : "block_list"), getContainer: false, forceRender: true, children: _jsx("p", { className: "text-sm text-gray-500", children: t("feature_coming_soon") }) }))] }));
@@ -1 +1 @@
1
- {"version":3,"file":"ManualAssignPopover.d.ts","sourceRoot":"","sources":["../../../src/components/thread/ManualAssignPopover.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAY,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAIjD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,MAAM,CAAC;AAsBzC,UAAU,aAAa;IACrB,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,EAAE,EAAE,MAAM,CAAC;CACZ;AAED,UAAU,wBAAwB;IAChC,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,aAAa,GAAG,IAAI,CAAC;IACjC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,SAAS,CAAC;IACrB,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAC7B,SAAS,CAAC,EAAE,YAAY,CAAC,WAAW,CAAC,CAAC;CACvC;AA0ED,QAAA,MAAM,mBAAmB,GAAI,wFAO1B,wBAAwB,4CA+P1B,CAAC;AAEF,eAAe,mBAAmB,CAAC"}
1
+ {"version":3,"file":"ManualAssignPopover.d.ts","sourceRoot":"","sources":["../../../src/components/thread/ManualAssignPopover.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAY,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAIjD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,MAAM,CAAC;AAsBzC,UAAU,aAAa;IACrB,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,EAAE,EAAE,MAAM,CAAC;CACZ;AAED,UAAU,wBAAwB;IAChC,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,aAAa,GAAG,IAAI,CAAC;IACjC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,SAAS,CAAC;IACrB,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAC7B,SAAS,CAAC,EAAE,YAAY,CAAC,WAAW,CAAC,CAAC;CACvC;AA0ED,QAAA,MAAM,mBAAmB,GAAI,wFAO1B,wBAAwB,4CAyP1B,CAAC;AAEF,eAAe,mBAAmB,CAAC"}
@@ -116,14 +116,12 @@ const ManualAssignPopover = ({ sessionId, supporter, teamId: propTeamId, childre
116
116
  .filter((item) => item.memberId !== (supporter === null || supporter === void 0 ? void 0 : supporter.id))
117
117
  .filter((item) => !excludeCurrentUser || item.memberId !== authUserID)
118
118
  .sort((supporterA, supporterB) => {
119
- const isSupporterACurrentUser = supporterA.memberId === authUserID;
120
- const isSupporterBCurrentUser = supporterB.memberId === authUserID;
121
- if (isSupporterACurrentUser !== isSupporterBCurrentUser) {
122
- return isSupporterACurrentUser ? -1 : 1;
123
- }
124
- return supporterA.displayName.localeCompare(supporterB.displayName, "vi", {
125
- sensitivity: "base",
126
- });
119
+ // Online first, then A-Z by display name; the viewer is not pinned.
120
+ const onlineA = supporterA.status === "ONLINE";
121
+ const onlineB = supporterB.status === "ONLINE";
122
+ if (onlineA !== onlineB)
123
+ return onlineA ? -1 : 1;
124
+ return supporterA.displayName.localeCompare(supporterB.displayName, "vi", { sensitivity: "base" });
127
125
  })
128
126
  .map((item) => (_jsx(SupporterItem, { supporter: item, onClick: handleSelectAgent, isCurrentUser: item.memberId === authUserID }, item.memberId))) })) : (_jsx("div", { className: "flex items-center justify-center py-8", children: _jsx("span", { className: "text-sm text-gray-400", children: t("manual_assign_no_results") }) })) })] }));
129
127
  const triggerNode = children !== null && children !== void 0 ? children : (_jsxs("button", { type: "button", "aria-label": t("manual_assign_button_reassign"), className: clsx("flex items-center border border-gray-200 rounded-lg p-1 gap-1 bg-transparent transition-opacity focus-visible:outline focus-visible:outline-2 focus-visible:outline-primary-200", isSubmitting
@@ -1 +1 @@
1
- {"version":3,"file":"SessionDetailCard.d.ts","sourceRoot":"","sources":["../../../src/components/thread/SessionDetailCard.tsx"],"names":[],"mappings":"AAyBA,OAAO,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAsD/C,eAAO,MAAM,iBAAiB,GAAI,wBAG/B;IACD,OAAO,EAAE,gBAAgB,CAAC;IAC1B,QAAQ,EAAE,OAAO,CAAC;CACnB,4CAoNA,CAAC"}
1
+ {"version":3,"file":"SessionDetailCard.d.ts","sourceRoot":"","sources":["../../../src/components/thread/SessionDetailCard.tsx"],"names":[],"mappings":"AAyBA,OAAO,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAsD/C,eAAO,MAAM,iBAAiB,GAAI,wBAG/B;IACD,OAAO,EAAE,gBAAgB,CAAC;IAC1B,QAAQ,EAAE,OAAO,CAAC;CACnB,4CAqNA,CAAC"}
@@ -9,7 +9,7 @@ import ManualAssignPopover from "./ManualAssignPopover";
9
9
  import SessionTagPicker from "./SessionTagPicker";
10
10
  import SessionLabelRow, { readOnlyValueClass, sessionFieldClass } from "./SessionLabelRow";
11
11
  import EndSessionConfirm from "./EndSessionConfirm";
12
- import { formatSessionDate, formatSessionTime } from "./sessionDateFormat";
12
+ import { formatSessionDate, formatSessionRange } from "./sessionDateFormat";
13
13
  import { useGetLabelSession } from "../../hooks/session/useGetLabelSession";
14
14
  import { useUpdateSessionInfo } from "../../hooks/session/useUpdateSessionInfo";
15
15
  import { useSessionFieldAutosave, } from "../../hooks/session/useSessionFieldAutosave";
@@ -92,9 +92,9 @@ export const SessionDetailCard = ({ session, isActive, }) => {
92
92
  };
93
93
  return (_jsxs("div", { className: `flex flex-col gap-3 ${isActive ? "" : "rounded-lg border border-gray-50 p-3"}`, "data-cp-name": "SessionDetailCard", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx("span", { className: "text-sm font-bold text-gray-900", children: t("session_label", { order: session.order }) }), _jsx("span", { className: "text-xs text-gray-600", children: isActive
94
94
  ? t("session_started_at", {
95
- time: formatSessionTime(session.createdDate),
95
+ time: formatSessionDate(session.createdDate),
96
96
  })
97
- : formatSessionDate(session.closedDate || session.createdDate) }), _jsx("span", { className: "flex-1" }), isActive ? (_jsx("span", { className: "inline-flex h-[22px] items-center rounded-full bg-success-25 px-2 text-xs font-medium text-success-900", children: t("session_active") })) : (_jsx(TintedAvatar, { name: ((_b = session.supporter) === null || _b === void 0 ? void 0 : _b.fullName) || "A", src: (_c = session.supporter) === null || _c === void 0 ? void 0 : _c.avatar, size: 24 }))] }), _jsxs("div", { className: "flex flex-col gap-1", children: [fieldLabel(`${ids}-tags`, t("session_tags_label")), readOnly && labels.labels.length === 0 ? (_jsx("p", { className: emptyValueClass, children: "\u2014" })) : (_jsxs("div", { className: "divide-y divide-gray-50 rounded-lg border border-gray-50", children: [!readOnly && (_jsx(SessionTagPicker, { labelId: `${ids}-tags`, available: availableLabels !== null && availableLabels !== void 0 ? availableLabels : [], selectedIds: selectedLabelIds, onChange: labels.setLabelIds })), labels.labels.map((label) => {
97
+ : formatSessionRange(session.createdDate, session.closedDate) }), _jsx("span", { className: "flex-1" }), isActive ? (_jsx("span", { className: "inline-flex h-[22px] items-center rounded-full bg-success-25 px-2 text-xs font-medium text-success-900", children: t("session_active") })) : (_jsx(TintedAvatar, { name: ((_b = session.supporter) === null || _b === void 0 ? void 0 : _b.fullName) || "A", src: (_c = session.supporter) === null || _c === void 0 ? void 0 : _c.avatar, size: 24 }))] }), _jsxs("div", { className: "flex flex-col gap-1", children: [fieldLabel(`${ids}-tags`, t("session_tags_label")), readOnly && labels.labels.length === 0 ? (_jsx("p", { className: emptyValueClass, children: "\u2014" })) : (_jsxs("div", { className: "divide-y divide-gray-50 rounded-lg border border-gray-50", children: [!readOnly && (_jsx(SessionTagPicker, { labelId: `${ids}-tags`, available: availableLabels !== null && availableLabels !== void 0 ? availableLabels : [], selectedIds: selectedLabelIds, selectedNames: selectedLabelIds.map((id) => { var _a; return (_a = labelNames.get(id)) !== null && _a !== void 0 ? _a : ""; }), onChange: labels.setLabelIds })), labels.labels.map((label) => {
98
98
  var _a;
99
99
  return (_jsx(SessionLabelRow, { name: (_a = labelNames.get(label.id)) !== null && _a !== void 0 ? _a : "", note: label.note, readOnly: readOnly, onNoteChange: (value) => labels.setNote(label.id, value), onRemove: () => labels.setLabelIds(selectedLabelIds.filter((id) => id !== label.id)) }, label.id));
100
100
  })] }))] }), session.issueDetail && (_jsxs("div", { className: "flex flex-col gap-1", children: [_jsx("span", { className: fieldLabelClass, children: t("session_issue_detail_label") }), _jsx("p", { className: readOnlyValueClass, "data-testid": "session-legacy-issue", children: session.issueDetail })] })), _jsxs("div", { className: "flex flex-col gap-1", children: [_jsxs("div", { className: "flex items-center justify-between", children: [fieldLabel(`${ids}-note`, t("session_note_label")), !readOnly && _jsx(SaveStatus, { status: saveStatus, onRetry: retrySave })] }), readOnly ? (_jsx("p", { className: ((_d = session.note) === null || _d === void 0 ? void 0 : _d.trim()) ? readOnlyValueClass : emptyValueClass, "data-testid": "session-note-text", children: ((_e = session.note) === null || _e === void 0 ? void 0 : _e.trim()) ? session.note : "—" })) : (_jsx(TextArea, { id: `${ids}-note`, value: note.value, onChange: (e) => note.setValue(e.target.value), placeholder: t("session_note_placeholder"), className: sessionFieldClass, autoSize: { minRows: 2, maxRows: 4 } }))] }), isActive && (_jsxs(_Fragment, { children: [session.status !== SessionStatus.UNASSIGNED &&
@@ -1 +1 @@
1
- {"version":3,"file":"SessionSection.d.ts","sourceRoot":"","sources":["../../../src/components/thread/SessionSection.tsx"],"names":[],"mappings":"AAGA,OAAO,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AAExD,OAAO,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAE/C,OAAO,EAAE,iBAAiB,EAAE,CAAC;AAoF7B,UAAU,mBAAmB;IAC3B,QAAQ,EAAE,gBAAgB,EAAE,CAAC;IAC7B,SAAS,EAAE,OAAO,CAAC;IACnB,cAAc,CAAC,EAAE,OAAO,CAAC;CAC1B;AAED,QAAA,MAAM,cAAc,GAAI,0CAIrB,mBAAmB,4CAwCrB,CAAC;AAEF,eAAe,cAAc,CAAC"}
1
+ {"version":3,"file":"SessionSection.d.ts","sourceRoot":"","sources":["../../../src/components/thread/SessionSection.tsx"],"names":[],"mappings":"AAGA,OAAO,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AAGxD,OAAO,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAE/C,OAAO,EAAE,iBAAiB,EAAE,CAAC;AA4E7B,UAAU,mBAAmB;IAC3B,QAAQ,EAAE,gBAAgB,EAAE,CAAC;IAC7B,SAAS,EAAE,OAAO,CAAC;IACnB,cAAc,CAAC,EAAE,OAAO,CAAC;CAC1B;AAED,QAAA,MAAM,cAAc,GAAI,0CAIrB,mBAAmB,4CAwCrB,CAAC;AAEF,eAAe,cAAc,CAAC"}
@@ -4,15 +4,9 @@ import { Spin } from "antd";
4
4
  import { useTranslation } from "react-i18next";
5
5
  import { SessionDetailCard } from "./SessionDetailCard";
6
6
  import { formatSessionDuration } from "./sessionDuration";
7
+ import { formatSessionRange } from "./sessionDateFormat";
7
8
  export { SessionDetailCard };
8
9
  const MAX_VISIBLE_TAGS = 2;
9
- const pad = (value) => String(value).padStart(2, "0");
10
- const formatDayMonth = (dateString) => {
11
- if (!dateString)
12
- return "";
13
- const date = new Date(dateString);
14
- return `${pad(date.getDate())}/${pad(date.getMonth() + 1)}`;
15
- };
16
10
  // One row per past session: order, date and duration, then tags and handler.
17
11
  const ClosedSessionItem = ({ session, expandedSessionId, onToggleExpand, }) => {
18
12
  var _a, _b;
@@ -23,10 +17,10 @@ const ClosedSessionItem = ({ session, expandedSessionId, onToggleExpand, }) => {
23
17
  }
24
18
  const tags = (_a = session.labels) !== null && _a !== void 0 ? _a : [];
25
19
  const duration = formatSessionDuration(session.createdDate, session.closedDate, t);
26
- const dateText = [formatDayMonth(session.closedDate || session.createdDate), duration]
20
+ const dateText = [formatSessionRange(session.createdDate, session.closedDate), duration]
27
21
  .filter(Boolean)
28
22
  .join(" · ");
29
- return (_jsxs("button", { type: "button", "aria-expanded": false, className: "flex w-full flex-col gap-1 border-t border-gray-25 py-2.5 text-left hover:bg-gray-5", onClick: () => onToggleExpand(session.id), "data-testid": "past-session-row", children: [_jsxs("span", { className: "flex items-center justify-between gap-2 text-[13px]", children: [_jsx("span", { className: "font-semibold text-gray-900", children: t("session_label", { order: session.order }) }), _jsx("span", { className: "text-gray-600", children: dateText })] }), _jsxs("span", { className: "flex min-w-0 items-center gap-1.5 text-xs text-gray-600", children: [tags.slice(0, MAX_VISIBLE_TAGS).map((label) => (_jsx("span", { className: "inline-flex h-5 max-w-[120px] shrink-0 items-center truncate rounded-full bg-[var(--dc-color-neutral-surface)] px-2 font-medium text-gray-900", children: label.name }, label.id))), tags.length > MAX_VISIBLE_TAGS && (_jsxs("span", { className: "shrink-0", children: ["+", tags.length - MAX_VISIBLE_TAGS] })), _jsx("span", { className: "truncate", children: (_b = session.supporter) === null || _b === void 0 ? void 0 : _b.fullName })] })] }));
23
+ return (_jsxs("button", { type: "button", "aria-expanded": false, className: "flex w-full flex-col gap-1 border-t border-gray-25 py-2.5 text-left hover:bg-gray-5", onClick: () => onToggleExpand(session.id), "data-testid": "past-session-row", children: [_jsxs("span", { className: "flex items-center justify-between gap-2 text-[13px]", children: [_jsx("span", { className: "font-semibold text-gray-900", children: t("session_label", { order: session.order }) }), _jsx("span", { className: "min-w-0 truncate text-gray-600", children: dateText })] }), _jsxs("span", { className: "flex min-w-0 items-center gap-1.5 text-xs text-gray-600", children: [tags.slice(0, MAX_VISIBLE_TAGS).map((label) => (_jsx("span", { className: "inline-flex h-5 max-w-[120px] shrink-0 items-center truncate rounded-full bg-[var(--dc-color-neutral-surface)] px-2 font-medium text-gray-900", children: label.name }, label.id))), tags.length > MAX_VISIBLE_TAGS && (_jsxs("span", { className: "shrink-0", children: ["+", tags.length - MAX_VISIBLE_TAGS] })), _jsx("span", { className: "truncate", children: (_b = session.supporter) === null || _b === void 0 ? void 0 : _b.fullName })] })] }));
30
24
  };
31
25
  const SessionSection = ({ sessions, isLoading, isFetchingMore = false, }) => {
32
26
  const { t } = useTranslation("common");
@@ -3,8 +3,10 @@ interface SessionTagPickerProps {
3
3
  labelId: string;
4
4
  available: ILabelResponse[];
5
5
  selectedIds: string[];
6
+ /** Names of the picked tags, shown inside the field. */
7
+ selectedNames?: string[];
6
8
  onChange: (ids: string[]) => void;
7
9
  }
8
- declare const SessionTagPicker: ({ labelId, available, selectedIds, onChange, }: SessionTagPickerProps) => import("react/jsx-runtime").JSX.Element;
10
+ declare const SessionTagPicker: ({ labelId, available, selectedIds, selectedNames, onChange, }: SessionTagPickerProps) => import("react/jsx-runtime").JSX.Element;
9
11
  export default SessionTagPicker;
10
12
  //# sourceMappingURL=SessionTagPicker.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"SessionTagPicker.d.ts","sourceRoot":"","sources":["../../../src/components/thread/SessionTagPicker.tsx"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAElD,UAAU,qBAAqB;IAC7B,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,cAAc,EAAE,CAAC;IAC5B,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,QAAQ,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,IAAI,CAAC;CACnC;AAMD,QAAA,MAAM,gBAAgB,GAAI,gDAKvB,qBAAqB,4CAkHvB,CAAC;AAEF,eAAe,gBAAgB,CAAC"}
1
+ {"version":3,"file":"SessionTagPicker.d.ts","sourceRoot":"","sources":["../../../src/components/thread/SessionTagPicker.tsx"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAElD,UAAU,qBAAqB;IAC7B,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,cAAc,EAAE,CAAC;IAC5B,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,wDAAwD;IACxD,aAAa,CAAC,EAAE,MAAM,EAAE,CAAC;IACzB,QAAQ,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,IAAI,CAAC;CACnC;AAMD,QAAA,MAAM,gBAAgB,GAAI,+DAMvB,qBAAqB,4CAoIvB,CAAC;AAEF,eAAe,gBAAgB,CAAC"}
@@ -12,7 +12,7 @@ import { LAYOUT_MEDIA } from "../../constants/layout";
12
12
  const OPTION_HEIGHT = 36;
13
13
  const MAX_VISIBLE_OPTIONS = 8;
14
14
  // The "Thêm thẻ" row of the tag box. Its list opens inline, so it pushes the card instead of floating over it.
15
- const SessionTagPicker = ({ labelId, available, selectedIds, onChange, }) => {
15
+ const SessionTagPicker = ({ labelId, available, selectedIds, selectedNames = [], onChange, }) => {
16
16
  const { t } = useTranslation("common");
17
17
  const isTouch = useMediaQuery(LAYOUT_MEDIA.touch);
18
18
  const [open, setOpen] = useState(false);
@@ -50,7 +50,7 @@ const SessionTagPicker = ({ labelId, available, selectedIds, onChange, }) => {
50
50
  event.stopPropagation();
51
51
  close();
52
52
  }
53
- }, children: [_jsxs("button", { ref: toggleRef, id: labelId, type: "button", "aria-label": t("session_add_tag"), "aria-expanded": open, "aria-controls": listId, className: "flex h-9 touch:min-h-10 w-full items-center gap-1 px-3 text-[13px] text-gray-600 hover:text-gray-900", onClick: () => (open ? close() : setOpen(true)), children: [_jsx(StrokeIcon, { name: "plus", size: 14 }), t("session_add_tag")] }), open && (_jsxs("div", { ref: panelRef, className: "mx-2 mb-2 flex flex-col gap-1 rounded-lg border border-gray-50 p-1", children: [_jsx(Input, { autoFocus: !isTouch, "aria-label": t("session_search_tag_placeholder"), "aria-controls": listId, placeholder: t("session_search_tag_placeholder"), value: keyword, onChange: (event) => setKeyword(event.target.value), prefix: _jsx(StrokeIcon, { name: "search", size: 16, className: "text-gray-400" }), className: "rounded-lg border-gray-300 text-[13px] focus-within:border-chat-focus focus-within:shadow-chat-focus-ring", allowClear: true }), _jsxs("ul", { id: listId, role: "listbox", "aria-multiselectable": true, "aria-labelledby": labelId, className: "overflow-y-auto", style: { height: listHeight }, children: [isLoading && (_jsx("li", { className: "flex justify-center py-2", children: _jsx(Spin, { size: "small" }) })), !isLoading && options.length === 0 && (_jsx("li", { className: "px-2 py-1.5 text-[13px] text-gray-600", children: t("session_no_tags") })), options.map((label) => {
53
+ }, children: [_jsxs("button", { ref: toggleRef, id: labelId, type: "button", "aria-label": t("session_add_tag"), "aria-expanded": open, "aria-controls": listId, className: "group flex min-h-9 touch:min-h-10 w-full items-center gap-1 px-3 py-1 text-left text-[13px] text-gray-600 hover:text-gray-900", onClick: () => (open ? close() : setOpen(true)), children: [selectedNames.length === 0 ? (_jsx("span", { className: "min-w-0 flex-1 truncate", children: t("session_add_tag_placeholder") })) : (_jsx("span", { className: "flex min-w-0 flex-1 flex-wrap gap-1", children: selectedNames.map((name, index) => (_jsx("span", { className: "inline-flex h-5 max-w-full items-center truncate rounded-md bg-[var(--dc-color-neutral-surface)] px-2 text-xs font-medium text-gray-900", children: name }, `${name}-${index}`))) })), _jsx(StrokeIcon, { name: "plus", size: 14, className: "shrink-0 opacity-0 group-hover:opacity-100 group-focus-visible:opacity-100 touch:opacity-100" })] }), open && (_jsxs("div", { ref: panelRef, className: "mx-2 mb-2 flex flex-col gap-1 rounded-lg border border-gray-50 p-1", children: [_jsx(Input, { autoFocus: !isTouch, "aria-label": t("session_search_tag_placeholder"), "aria-controls": listId, placeholder: t("session_search_tag_placeholder"), value: keyword, onChange: (event) => setKeyword(event.target.value), prefix: _jsx(StrokeIcon, { name: "search", size: 16, className: "text-gray-400" }), className: "rounded-lg border-gray-300 text-[13px] focus-within:border-chat-focus focus-within:shadow-chat-focus-ring", allowClear: true }), _jsxs("ul", { id: listId, role: "listbox", "aria-multiselectable": true, "aria-labelledby": labelId, className: "overflow-y-auto", style: { height: listHeight }, children: [isLoading && (_jsx("li", { className: "flex justify-center py-2", children: _jsx(Spin, { size: "small" }) })), !isLoading && options.length === 0 && (_jsx("li", { className: "px-2 py-1.5 text-[13px] text-gray-600", children: t("session_no_tags") })), options.map((label) => {
54
54
  const isSelected = selectedIds.includes(label.id);
55
55
  return (_jsx("li", { role: "option", "aria-selected": isSelected, children: _jsxs("button", { type: "button", className: `flex h-9 w-full items-center justify-between rounded px-2 text-left text-[13px] text-gray-900 hover:bg-gray-5 ${isSelected ? "bg-primary-25" : ""}`, onClick: () => toggle(label.id), children: [label.name, isSelected && _jsx(StrokeIcon, { name: "check", size: 14 })] }) }, label.id));
56
56
  })] })] }))] }));
@@ -1,3 +1,5 @@
1
1
  export declare const formatSessionTime: (dateString?: string | null) => string;
2
2
  export declare const formatSessionDate: (dateString?: string | null) => string;
3
+ /** "HH:mm dd/MM - HH:mm dd/MM" (start - end); an open session has no end, so only the start. */
4
+ export declare const formatSessionRange: (startDate?: string | null, endDate?: string | null) => string;
3
5
  //# sourceMappingURL=sessionDateFormat.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"sessionDateFormat.d.ts","sourceRoot":"","sources":["../../../src/components/thread/sessionDateFormat.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,iBAAiB,GAAI,aAAa,MAAM,GAAG,IAAI,WAI3D,CAAC;AAEF,eAAO,MAAM,iBAAiB,GAAI,aAAa,MAAM,GAAG,IAAI,WAI3D,CAAC"}
1
+ {"version":3,"file":"sessionDateFormat.d.ts","sourceRoot":"","sources":["../../../src/components/thread/sessionDateFormat.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,iBAAiB,GAAI,aAAa,MAAM,GAAG,IAAI,WAI3D,CAAC;AAEF,eAAO,MAAM,iBAAiB,GAAI,aAAa,MAAM,GAAG,IAAI,WAI3D,CAAC;AAEF,gGAAgG;AAChG,eAAO,MAAM,kBAAkB,GAC7B,YAAY,MAAM,GAAG,IAAI,EACzB,UAAU,MAAM,GAAG,IAAI,WAKxB,CAAC"}
@@ -11,3 +11,9 @@ export const formatSessionDate = (dateString) => {
11
11
  const date = new Date(dateString);
12
12
  return `${formatSessionTime(dateString)} ${pad(date.getDate())}/${pad(date.getMonth() + 1)}`;
13
13
  };
14
+ /** "HH:mm dd/MM - HH:mm dd/MM" (start - end); an open session has no end, so only the start. */
15
+ export const formatSessionRange = (startDate, endDate) => {
16
+ const start = formatSessionDate(startDate);
17
+ const end = formatSessionDate(endDate);
18
+ return end ? `${start} - ${end}` : start;
19
+ };
@@ -0,0 +1,2 @@
1
+ export declare const LIBRARY_DATE_HEADER_FORMAT = "DD/MM/YYYY";
2
+ //# sourceMappingURL=dateFormat.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dateFormat.d.ts","sourceRoot":"","sources":["../../src/constants/dateFormat.ts"],"names":[],"mappings":"AACA,eAAO,MAAM,0BAA0B,eAAe,CAAC"}
@@ -0,0 +1,2 @@
1
+ // Day group headers in the media library follow the product spec (DD/MM/YYYY).
2
+ export const LIBRARY_DATE_HEADER_FORMAT = "DD/MM/YYYY";
@@ -2,6 +2,7 @@ export interface UrlMetadata {
2
2
  title?: string;
3
3
  description?: string;
4
4
  image?: string;
5
+ isEmpty?: boolean;
5
6
  url: string;
6
7
  }
7
8
  export declare const fetchUrlMetadata: (url: string) => Promise<UrlMetadata | null>;
@@ -1 +1 @@
1
- {"version":3,"file":"useFetchUrlMetadata.d.ts","sourceRoot":"","sources":["../../../src/hooks/common/useFetchUrlMetadata.ts"],"names":[],"mappings":"AAMA,MAAM,WAAW,WAAW;IAC1B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,GAAG,EAAE,MAAM,CAAC;CACb;AAED,eAAO,MAAM,gBAAgB,GAAU,KAAK,MAAM,KAAG,OAAO,CAAC,WAAW,GAAG,IAAI,CAY9E,CAAC;AAEF,eAAO,MAAM,mBAAmB,GAAI,KAAK,MAAM,EAAE,iBAAc,8EAO3D,CAAC"}
1
+ {"version":3,"file":"useFetchUrlMetadata.d.ts","sourceRoot":"","sources":["../../../src/hooks/common/useFetchUrlMetadata.ts"],"names":[],"mappings":"AAMA,MAAM,WAAW,WAAW;IAC1B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,KAAK,CAAC,EAAE,MAAM,CAAC;IAEf,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,GAAG,EAAE,MAAM,CAAC;CACb;AAED,eAAO,MAAM,gBAAgB,GAAU,KAAK,MAAM,KAAG,OAAO,CAAC,WAAW,GAAG,IAAI,CAY9E,CAAC;AAEF,eAAO,MAAM,mBAAmB,GAAI,KAAK,MAAM,EAAE,iBAAc,8EAO3D,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"useBotFlow.d.ts","sourceRoot":"","sources":["../../../src/hooks/conversation/useBotFlow.ts"],"names":[],"mappings":"AAMA,OAAO,EAKL,KAAK,oBAAoB,EACzB,KAAK,eAAe,EACrB,MAAM,sBAAsB,CAAC;AAgB9B,MAAM,MAAM,aAAa,GAAG,SAAS,GAAG,aAAa,CAAC;AAMtD,UAAU,gBAAgB;IACxB,cAAc,EAAE,MAAM,CAAC;IACvB,WAAW,EAAE,OAAO,CAAC;CACtB;AAsBD,eAAO,MAAM,UAAU,GAAI,kCAGxB,gBAAgB;;;;;;;;;;;;6BAoKqB,MAAM;;6BA+DN,eAAe;;;CAmDtD,CAAC"}
1
+ {"version":3,"file":"useBotFlow.d.ts","sourceRoot":"","sources":["../../../src/hooks/conversation/useBotFlow.ts"],"names":[],"mappings":"AAMA,OAAO,EAKL,KAAK,oBAAoB,EACzB,KAAK,eAAe,EACrB,MAAM,sBAAsB,CAAC;AAgB9B,MAAM,MAAM,aAAa,GAAG,SAAS,GAAG,aAAa,CAAC;AAMtD,UAAU,gBAAgB;IACxB,cAAc,EAAE,MAAM,CAAC;IACvB,WAAW,EAAE,OAAO,CAAC;CACtB;AAoCD,eAAO,MAAM,UAAU,GAAI,kCAGxB,gBAAgB;;;;;;;;;;;;6BA8KqB,MAAM;;6BAwFN,eAAe;;;CA+DtD,CAAC"}
@@ -21,6 +21,17 @@ const INITIAL_BOT_FLOW_STATE = {
21
21
  flowCurrentStep: null,
22
22
  flowIsEnd: false,
23
23
  };
24
+ // Safety net: a group whose message never arrives must not hide buttons forever.
25
+ const PENDING_GROUP_TIMEOUT_MS = 8000;
26
+ // Only these blocks make the server push a chat message carrying the group id.
27
+ const MESSAGE_BLOCK_TYPES = [
28
+ BotBlockType.BubbleText,
29
+ BotBlockType.BubbleImage,
30
+ ];
31
+ const hasMessageBlock = (step) => {
32
+ var _a, _b;
33
+ return !!((_b = (_a = step === null || step === void 0 ? void 0 : step.data) === null || _a === void 0 ? void 0 : _a.children) === null || _b === void 0 ? void 0 : _b.some((child) => MESSAGE_BLOCK_TYPES.includes(child.type)));
34
+ };
24
35
  const parseMessageFlowGroupId = (ex) => {
25
36
  var _a;
26
37
  try {
@@ -97,6 +108,12 @@ export const useBotFlow = ({ conversationId, isBotThread, }) => {
97
108
  emitter.off("PUSH_NEW_MSG", handleNewMessage);
98
109
  };
99
110
  }, [authUserID]);
111
+ useEffect(() => {
112
+ if (!pendingGroupId)
113
+ return;
114
+ const timer = setTimeout(() => setPendingGroupId(null), PENDING_GROUP_TIMEOUT_MS);
115
+ return () => clearTimeout(timer);
116
+ }, [pendingGroupId]);
100
117
  const activeStep = isActiveConversation ? flowCurrentStep : null;
101
118
  const inputButtonsBlocks = (_d = activeStep === null || activeStep === void 0 ? void 0 : activeStep.data.children.filter((child) => child.type === BotBlockType.InputButtons)) !== null && _d !== void 0 ? _d : [];
102
119
  const currentButtonsBlock = inputButtonsBlocks.length > 0
@@ -138,6 +155,7 @@ export const useBotFlow = ({ conversationId, isBotThread, }) => {
138
155
  });
139
156
  const nextGroupId = (_a = result.flowNextStep) === null || _a === void 0 ? void 0 : _a.id;
140
157
  setPendingGroupId(result.havingBotMessage &&
158
+ hasMessageBlock(result.flowNextStep) &&
141
159
  nextGroupId &&
142
160
  latestBotFlowGroupIdRef.current !== nextGroupId
143
161
  ? nextGroupId
@@ -187,24 +205,46 @@ export const useBotFlow = ({ conversationId, isBotThread, }) => {
187
205
  await runStep(resetDetail.flowCurrentStep);
188
206
  });
189
207
  };
208
+ // The CS may have closed the session on the desk while this thread stayed
209
+ // open, so confirm against the server before showing the "end session" modal.
210
+ const checkCsSession = async () => {
211
+ var _a, _b;
212
+ if (!isInActiveCsSession)
213
+ return { active: false, step: activeStep };
214
+ const fresh = await detailQuery.refetch();
215
+ // A missing step means the session is gone; the stale CS step must not win.
216
+ const step = (_b = (_a = fresh.data) === null || _a === void 0 ? void 0 : _a.flowCurrentStep) !== null && _b !== void 0 ? _b : null;
217
+ return { active: isInCsSession({ flowCurrentStep: step }), step };
218
+ };
219
+ // The session refetch is async; a second tap meanwhile would run the same
220
+ // check-then-act again, so claim the slot before awaiting anything.
221
+ const checkingRef = useRef(false);
190
222
  const restart = async () => {
191
- if (isInActiveCsSession) {
192
- setPendingCsAction({ type: "restart" });
223
+ if (checkingRef.current)
193
224
  return;
225
+ checkingRef.current = true;
226
+ try {
227
+ if ((await checkCsSession()).active) {
228
+ setPendingCsAction({ type: "restart" });
229
+ return;
230
+ }
231
+ await performRestart();
232
+ }
233
+ finally {
234
+ checkingRef.current = false;
194
235
  }
195
- await performRestart();
196
236
  };
197
- const runMenuOption = async (item) => {
198
- if (!activeStep)
237
+ const runMenuOption = async (item, step = activeStep) => {
238
+ if (!step)
199
239
  return;
200
240
  await whileChoosing(async () => {
201
241
  await sendMergeMessage({
202
242
  plainText: item.label,
203
243
  richText: "",
204
244
  isInternal: false,
205
- flow: { groupId: activeStep.id, childId: null, actionId: item.id },
245
+ flow: { groupId: step.id, childId: null, actionId: item.id },
206
246
  });
207
- await runStep(activeStep, item.id);
247
+ await runStep(step, item.id);
208
248
  });
209
249
  };
210
250
  const selectMenuOption = async (item) => {
@@ -212,13 +252,23 @@ export const useBotFlow = ({ conversationId, isBotThread, }) => {
212
252
  await restart();
213
253
  return;
214
254
  }
215
- if (isInActiveCsSession) {
216
- setPendingCsAction({ type: "menu", item });
255
+ if (checkingRef.current)
217
256
  return;
257
+ checkingRef.current = true;
258
+ try {
259
+ const { active, step } = await checkCsSession();
260
+ if (active) {
261
+ setPendingCsAction({ type: "menu", item });
262
+ return;
263
+ }
264
+ await runMenuOption(item, step);
265
+ }
266
+ finally {
267
+ checkingRef.current = false;
218
268
  }
219
- await runMenuOption(item);
220
269
  };
221
270
  const confirmEndCsSession = async () => {
271
+ var _a, _b;
222
272
  if (!pendingCsAction)
223
273
  return;
224
274
  const action = pendingCsAction;
@@ -229,10 +279,12 @@ export const useBotFlow = ({ conversationId, isBotThread, }) => {
229
279
  await performRestart();
230
280
  }
231
281
  else {
232
- await runMenuOption(action.item);
282
+ // The server resets the flow step on close; run the menu item on it.
283
+ const fresh = await detailQuery.refetch();
284
+ await runMenuOption(action.item, (_b = (_a = fresh.data) === null || _a === void 0 ? void 0 : _a.flowCurrentStep) !== null && _b !== void 0 ? _b : activeStep);
233
285
  }
234
286
  }
235
- catch (_a) {
287
+ catch (_c) {
236
288
  toastMessage.error(t("thread_detail.bot.cs_confirm.close_failed"));
237
289
  setPendingCsAction(null);
238
290
  }
@@ -1,2 +1,4 @@
1
+ export declare const SESSION_ALREADY_CLOSED_CODE = 2005;
2
+ export declare const isSessionAlreadyClosedError: (error: unknown) => boolean;
1
3
  export declare const useCloseBotSession: () => import("@tanstack/react-query").UseMutationResult<void, Error, string, unknown>;
2
4
  //# sourceMappingURL=useCloseBotSession.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"useCloseBotSession.d.ts","sourceRoot":"","sources":["../../../src/hooks/conversation/useCloseBotSession.ts"],"names":[],"mappings":"AAQA,eAAO,MAAM,kBAAkB,uFAS9B,CAAC"}
1
+ {"version":3,"file":"useCloseBotSession.d.ts","sourceRoot":"","sources":["../../../src/hooks/conversation/useCloseBotSession.ts"],"names":[],"mappings":"AASA,eAAO,MAAM,2BAA2B,OAAO,CAAC;AAEhD,eAAO,MAAM,2BAA2B,GAAI,OAAO,OAAO,KAAG,OAQ5D,CAAC;AAEF,eAAO,MAAM,kBAAkB,uFAc9B,CAAC"}
@@ -3,11 +3,27 @@ import { useMutation } from "@tanstack/react-query";
3
3
  import { apiInstance } from "../../services/api";
4
4
  import { ENDPOINTS } from "../../services/routes";
5
5
  import { QUERY_KEYS } from "../../services/query";
6
+ // Backend code for "session already closed" (e.g. CS ended it on the desk).
7
+ export const SESSION_ALREADY_CLOSED_CODE = 2005;
8
+ export const isSessionAlreadyClosedError = (error) => {
9
+ var _a;
10
+ const data = (_a = error === null || error === void 0 ? void 0 : error.response) === null || _a === void 0 ? void 0 : _a.data;
11
+ if (!data)
12
+ return false;
13
+ return [data.code, data.errorCode, data.errCode, data.statusCode].some((value) => Number(value) === SESSION_ALREADY_CLOSED_CODE);
14
+ };
6
15
  export const useCloseBotSession = () => {
7
16
  return useMutation({
8
17
  mutationKey: [QUERY_KEYS.CLOSE_BOT_SESSION],
9
18
  mutationFn: async (conversationId) => {
10
- await apiInstance.post(ENDPOINTS.chatService.closeBotSession(conversationId));
19
+ try {
20
+ await apiInstance.post(ENDPOINTS.chatService.closeBotSession(conversationId));
21
+ }
22
+ catch (error) {
23
+ // Already closed elsewhere is the outcome we wanted; let the caller go on.
24
+ if (!isSessionAlreadyClosedError(error))
25
+ throw error;
26
+ }
11
27
  },
12
28
  });
13
29
  };
@@ -1 +1 @@
1
- {"version":3,"file":"useGlobalEvent.d.ts","sourceRoot":"","sources":["../../../src/hooks/global/useGlobalEvent.ts"],"names":[],"mappings":"AAkDA,eAAO,MAAM,cAAc,YA8nB1B,CAAC"}
1
+ {"version":3,"file":"useGlobalEvent.d.ts","sourceRoot":"","sources":["../../../src/hooks/global/useGlobalEvent.ts"],"names":[],"mappings":"AA6DA,eAAO,MAAM,cAAc,YA0wB1B,CAAC"}