@zenify-omni/chat-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.
- package/LICENSE +21 -0
- package/README.md +28 -0
- package/api/campaign.js +19 -0
- package/api/chat.js +518 -0
- package/api/chatroom.js +254 -0
- package/api/contact.js +45 -0
- package/api/crm-organization.js +101 -0
- package/api/customer.js +143 -0
- package/api/facebook-labels.js +11 -0
- package/api/field.js +11 -0
- package/api/file.js +17 -0
- package/api/filter.js +50 -0
- package/api/group.js +11 -0
- package/api/iam.js +23 -0
- package/api/index.js +54 -0
- package/api/orgTree.js +45 -0
- package/api/post.js +111 -0
- package/api/queue.js +15 -0
- package/api/report.js +422 -0
- package/api/send.js +21 -0
- package/api/session-event.js +30 -0
- package/api/session.js +249 -0
- package/api/settings/assign-task.js +10 -0
- package/api/settings/channel.js +136 -0
- package/api/settings/chat-layout.js +37 -0
- package/api/settings/chat-timeout.js +10 -0
- package/api/settings/common.js +32 -0
- package/api/settings/config.js +19 -0
- package/api/settings/index.js +24 -0
- package/api/settings/ivr.js +25 -0
- package/api/settings/queue-v3.js +56 -0
- package/api/settings/queue.js +48 -0
- package/api/settings/routing.js +32 -0
- package/api/settings/service.js +31 -0
- package/api/settings.js +567 -0
- package/api/shipping.js +19 -0
- package/api/shopee.js +21 -0
- package/api/smart-qc/index.js +4 -0
- package/api/smart-qc/keyword.js +10 -0
- package/api/tag.js +11 -0
- package/api/user.js +177 -0
- package/client.js +391 -0
- package/config/env.js +76 -0
- package/constants/advance-filter.js +358 -0
- package/constants/auto-assign-tag.js +13 -0
- package/constants/chat-message.js +423 -0
- package/constants/chat-room-tag.js +7 -0
- package/constants/chat-source.js +68 -0
- package/constants/chatbot.js +58 -0
- package/constants/common.js +4 -0
- package/constants/crm-organization.js +75 -0
- package/constants/distribute.js +8 -0
- package/constants/distribution-log.js +14 -0
- package/constants/events.js +27 -0
- package/constants/file-upload.js +86 -0
- package/constants/lead.js +111 -0
- package/constants/mask-data-config.js +16 -0
- package/constants/month.js +78 -0
- package/constants/quick-filter.js +49 -0
- package/constants/regex.js +34 -0
- package/constants/setting.js +62 -0
- package/constants/shipping.js +12 -0
- package/constants/social.js +21 -0
- package/constants/sortable-fields.js +146 -0
- package/constants/tenant_config.js +154 -0
- package/constants/use-query-key.js +470 -0
- package/constants/user.js +293 -0
- package/exceptions/axios-error.js +24 -0
- package/exceptions/https-error.js +28 -0
- package/exceptions/validator-error.js +20 -0
- package/lib/avatar-color.js +31 -0
- package/lib/i18n/index.js +85 -0
- package/lib/i18n/language-store.js +74 -0
- package/lib/i18n/locales/en/campaign.json +3 -0
- package/lib/i18n/locales/en/chat.json +656 -0
- package/lib/i18n/locales/en/common.json +1846 -0
- package/lib/i18n/locales/en/customer.json +20 -0
- package/lib/i18n/locales/en/lead.json +5 -0
- package/lib/i18n/locales/en/lumi.json +4 -0
- package/lib/i18n/locales/en/notification.json +107 -0
- package/lib/i18n/locales/en/org.json +4 -0
- package/lib/i18n/locales/en/posts.json +8 -0
- package/lib/i18n/locales/en/settings.json +39 -0
- package/lib/i18n/locales/vi/campaign.json +3 -0
- package/lib/i18n/locales/vi/chat.json +660 -0
- package/lib/i18n/locales/vi/common.json +1846 -0
- package/lib/i18n/locales/vi/customer.json +20 -0
- package/lib/i18n/locales/vi/lead.json +5 -0
- package/lib/i18n/locales/vi/lumi.json +4 -0
- package/lib/i18n/locales/vi/notification.json +107 -0
- package/lib/i18n/locales/vi/org.json +4 -0
- package/lib/i18n/locales/vi/posts.json +8 -0
- package/lib/i18n/locales/vi/settings.json +39 -0
- package/lib/i18n/server-label.js +33 -0
- package/lib/phone.js +70 -0
- package/lib/user-avatar.js +21 -0
- package/package.json +45 -0
- package/socket/index.js +30 -0
- package/transport-registry.js +77 -0
- package/utils/access-detail.js +119 -0
- package/utils/auth.js +205 -0
- package/utils/chunk-reload-guard.js +4 -0
- package/utils/concurrency-limiter.js +30 -0
- package/utils/crm-organization.js +0 -0
- package/utils/currency.js +14 -0
- package/utils/customers.js +42 -0
- package/utils/data.js +178 -0
- package/utils/date.js +144 -0
- package/utils/debounced-invalidate.js +15 -0
- package/utils/emoticon.js +134 -0
- package/utils/export.js +7 -0
- package/utils/field-layout.js +33 -0
- package/utils/filter-adapters.js +9 -0
- package/utils/filter.js +18 -0
- package/utils/formatters.js +6 -0
- package/utils/image-compression.js +169 -0
- package/utils/leads.js +13 -0
- package/utils/microlink.js +20 -0
- package/utils/org-tree.js +108 -0
- package/utils/request.js +461 -0
- package/utils/session-end.js +19 -0
- package/utils/string.js +154 -0
- package/utils/table-display-config.js +80 -0
- package/utils/validation.js +49 -0
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
{
|
|
2
|
+
"toast.transferOwnerSuccess": "Chuyển người phụ trách thành công",
|
|
3
|
+
"message.selectAgentToTransfer": "Vui lòng chọn chuyên viên để chuyển",
|
|
4
|
+
"toast.transferToAgentSuccess": "Chuyển sang chuyên viên {label} thành công",
|
|
5
|
+
"field.newOwner": "Người phụ trách mới",
|
|
6
|
+
"action.filterLeadFile": "Lọc tệp khách hàng tiềm năng",
|
|
7
|
+
"action.filterCustomerFile": "Lọc tệp khách hàng",
|
|
8
|
+
"placeholder.note": "Nhập ghi chú...",
|
|
9
|
+
"field.sourceChannel": "Kênh khởi tạo",
|
|
10
|
+
"placeholder.greetingMessage": "Nhập lời chào / tin nhắn đầu...",
|
|
11
|
+
"message.invalidZaloPhone": "Số điện thoại Zalo không hợp lệ",
|
|
12
|
+
"message.alreadyFriend": "User đã là bạn bè",
|
|
13
|
+
"message.friendRequestAlreadySent": "Lời mời kết bạn đã được gửi trước đó",
|
|
14
|
+
"message.friendRequestGreeting": "Xin chào, mình là {name}. Kết bạn với mình nhé",
|
|
15
|
+
"message.zaloUserNotFound": "Không tìm thấy người dùng Zalo với số điện thoại này",
|
|
16
|
+
"message.messageRequired": "Tin nhắn không được để trống",
|
|
17
|
+
"column.conversionMethod": "Phương thức chuyển đổi",
|
|
18
|
+
"label.customerIdCannotBeEmpty": "ID khách hàng không được để trống",
|
|
19
|
+
"label.allocationFailed": "Phân bổ thất bại"
|
|
20
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
{
|
|
2
|
+
"confirm.mergeIntoExisting": "Bạn có muốn hợp nhất thông tin vào tài khoản hiện có không?",
|
|
3
|
+
"action.mergeCustomer": "Hợp nhất khách hàng",
|
|
4
|
+
"message.phoneBelongsTo": "Số điện thoại <0>{{phone}}</0> đã thuộc về {{crmLabel}} <1>{{existingName}}</1> trong hệ thống."
|
|
5
|
+
}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
{
|
|
2
|
+
"_comment": "MIRROR of contact-center-be app/lib/i18n/locales/vi/notification.json — keep in sync. Rendered client-side for the real-time notification toast.",
|
|
3
|
+
"distribute.ticketTitle": "Phân bổ ticket",
|
|
4
|
+
"involvement.ccAdded": "Bạn đã được thêm vào CC cho {count} ticket",
|
|
5
|
+
"distribute.failureContent": "Đã xảy ra lỗi trong quá trình phân bổ {target} của bạn. Vui lòng thử lại sau. ",
|
|
6
|
+
"import.successContent": "File {tagName} import vào {beginDate} đã được xử lý xong. <br />\n - Tổng {tagName}: {total} <br />\n - Thành công: {success} <br />\n - Thất bại: {error} <br />",
|
|
7
|
+
"import.invalidFileContent": "File {tagName} của bạn không hợp lệ. Vui lòng kiểm tra lại file mẫu",
|
|
8
|
+
"import.processingErrorContent": "Đã xảy ra lỗi trong quá trình xử lý file {tagName} của bạn. Vui lòng thử lại sau",
|
|
9
|
+
"kpi.newTitle": "Chỉ tiêu mới",
|
|
10
|
+
"kpi.assignedContent": "Bạn vừa được giao chỉ tiêu \"{name}\". Xem ngay báo cáo chỉ tiêu của bạn.",
|
|
11
|
+
"distribute.title": "Phân bổ {target}",
|
|
12
|
+
"distribute.target.customer": "khách hàng",
|
|
13
|
+
"distribute.target.lead": "tiềm năng",
|
|
14
|
+
"distribute.target.organization": "tổ chức",
|
|
15
|
+
"distribute.completionContent": "Quá trình phân bổ {target} {formattedDate} đã được xử lý xong <br/>\n Tổng số: {total} <br/>\n Thành công: {updated}<br/>\n Thất bại: {failed} <br/>",
|
|
16
|
+
"distribute.failureContentAlt": "Đã xảy ra lỗi trong quá trình phân bổ {target} của bạn. Vui lòng thử lại sau.",
|
|
17
|
+
"distribute.assignedContent": "Bạn vừa được chuyển giao {count} {target} từ {name}<br/>Vui lòng kiểm tra thông tin chi tiết và tiếp nhận xử lý.<br/>",
|
|
18
|
+
"knowledge.crawlTitle": "Thu thập URL vào tri thức chatbot",
|
|
19
|
+
"knowledge.crawlContent": "\n Quá trình thêm URL {urlTitle} vào {formattedDate} đã được xử lý xong. <br />\n - Tổng số: {total} <br />\n - Thành công: {success} <br />\n - Thất bại: {failed} <br />",
|
|
20
|
+
"export.customerListTitle": "Danh sách khách hàng",
|
|
21
|
+
"export.leadListTitle": "Danh sách tiềm năng",
|
|
22
|
+
"export.customerListSuccessContent": "Đã xuất danh sách khách hàng thành công lúc {beginDate}",
|
|
23
|
+
"export.leadListSuccessContent": "Đã xuất danh sách tiềm năng thành công lúc {beginDate}",
|
|
24
|
+
"export.customerListErrorContent": "Quá trình xuất danh sách khách hàng của bạn đã xảy ra lỗi, vui lòng thử lại sau!",
|
|
25
|
+
"task.dueSoonTitle": "Công việc sắp tới hạn",
|
|
26
|
+
"task.overdueTitle": "Công việc quá hạn",
|
|
27
|
+
"task.assignedDueSoonContent": "Bạn có công việc {taskName} tới hạn lúc {deadline}. Vui lòng kiểm tra tiến độ xử lý.",
|
|
28
|
+
"task.sharedDueSoonContent": "Công việc {taskName} bạn theo dõi sắp hết hạn lúc {deadline}. Vui lòng kiểm tra tiến độ xử lý.",
|
|
29
|
+
"task.assignedOverdueContent": "Bạn có công việc {taskName} đã quá hạn lúc {deadline}.",
|
|
30
|
+
"task.sharedOverdueContent": "Công việc {taskName} bạn theo dõi đã quá hạn lúc {deadline}.",
|
|
31
|
+
"task.sharedTitle": "Công việc được chia sẻ",
|
|
32
|
+
"task.sharedNewContent": "Bạn đã được chia sẻ công việc {taskName}. Vui lòng kiểm tra tiến độ xử lý.",
|
|
33
|
+
"import.organizationSuccessTitle": "Import tổ chức",
|
|
34
|
+
"import.organizationSuccessContent": "File tổ chức import vào {beginDate} đã được xử lý xong. <br />\n - Tổng tổ chức: {total} <br />\n - Thành công: {success} <br />\n - Thất bại: {failure} <br />",
|
|
35
|
+
"involvement.ticketNotificationTitle": "Thông báo ticket",
|
|
36
|
+
"involvement.ccAddedSingle": "Bạn đã được thêm vào CC trong ticket #{code} {subject}",
|
|
37
|
+
"involvement.followerAdded": "Bạn đã được thêm làm người theo dõi cho {count} ticket",
|
|
38
|
+
"involvement.followerAddedSingle": "Bạn đã được thêm làm người theo dõi cho ticket #{code} {subject}",
|
|
39
|
+
"involvement.assigneeAdded": "Bạn đã được phân bổ xử lý {count} ticket",
|
|
40
|
+
"involvement.assigneeAddedSingle": "Bạn đã được phân bổ xử lý ticket {subject}",
|
|
41
|
+
"export.ticketListTitle": "Danh sách ticket",
|
|
42
|
+
"export.ticketListSuccessContent": "Đã xuất danh sách ticket thành công lúc {beginDate}",
|
|
43
|
+
"export.ticketListErrorContent": "Quá trình xuất danh sách ticket của bạn đã xảy ra lỗi, vui lòng thử lại sau!",
|
|
44
|
+
"distribute.dealCompletionContent": "Phân bổ {target} ngày {formattedDate} hoàn tất.<br/>Tổng: {total}<br/>Thành công: {updated}",
|
|
45
|
+
"distribute.dealFailureContent": "Lỗi phân bổ {target}. Vui lòng kiểm tra lại.",
|
|
46
|
+
"distribute.dealAssignedContent": "Bạn nhận được {count} {target} mới từ {name}",
|
|
47
|
+
"export.smsReportTitle": "Export báo cáo SMS",
|
|
48
|
+
"export.smsReportDoneContent": "Báo cáo SMS đã được xử lý xong",
|
|
49
|
+
"ticket.expiringTitle": "Thông báo ticket sắp hết hạn",
|
|
50
|
+
"ticket.expiringAdminMultiContent": "Có {count} ticket sắp đến hạn xử lý. Vui lòng kiểm tra danh sách chi tiết.",
|
|
51
|
+
"ticket.expiringAdminSingleContent": "Ticket #{code} {subject} sắp hết hạn. Vui lòng kiểm tra tiến độ xử lý.",
|
|
52
|
+
"ticket.expiringTeamMultiContent": "Có {count} ticket trong bộ phận của bạn sắp đến hạn xử lý. Vui lòng kiểm tra danh sách chi tiết.",
|
|
53
|
+
"ticket.expiringTeamSingleContent": "Ticket #{code} {subject} trong bộ phận của bạn sắp hết hạn. Vui lòng kiểm tra tiến độ xử lý.",
|
|
54
|
+
"ticket.expiringAgentMultiContent": "Bạn có {count} ticket sắp hết hạn xử lý.",
|
|
55
|
+
"ticket.expiringAgentSingleContent": "Bạn có ticket #{code} {subject} sắp hết hạn xử lý.",
|
|
56
|
+
"chatbot.agentRequestTitle": "Thông báo hội thoại",
|
|
57
|
+
"chatbot.agentRequestContent": "Cuộc hội thoại của ChatBot với {requesterName} đang cần hỗ trợ, vui lòng tiếp nhận và xử lý cuộc hội thoại ngay!",
|
|
58
|
+
"export.znsReportTitle": "Export báo cáo ZNS",
|
|
59
|
+
"export.znsReportDoneContent": "Báo cáo ZNS đã được xử lý xong",
|
|
60
|
+
"export.znsTemplatesReportTitle": "Export báo cáo ZNS templates",
|
|
61
|
+
"export.znsTemplatesReportDoneContent": "Báo cáo ZNS templates đã được xử lý xong",
|
|
62
|
+
"export.znsTemplatesReportErrorContent": "Đã xảy ra lỗi khi xuất báo cáo ZNS templates. Vui lòng thử lại",
|
|
63
|
+
"import.campaignTitle": "Import khách hàng trong chiến dịch",
|
|
64
|
+
"import.campaignErrorContent": "Import chiến dịch vào {beginDate} đã xong, nhưng file kết quả chưa khả dụng. Vui lòng thử tải lại sau ít phút.",
|
|
65
|
+
"import.campaignFileSuccessContent": "File {target} import vào {beginDate} đã được xử lý xong. <br />\n - Tổng {target}: {total} <br />\n - Thành công: {success} <br />\n - Thất bại: {error} <br />\n - Đã tồn tại: {existing}",
|
|
66
|
+
"import.campaignFilterSuccessContent": "Danh sách {target} từ bộ lọc lúc {beginDate} đã được xử lý xong. <br />\n - Tổng {target}: {total} <br />\n - Thành công: {success} <br />\n - Thất bại: {error} <br />\n - Đã tồn tại: {existing}",
|
|
67
|
+
"export.callHistoryReportTitle": "Báo cáo lịch sử cuộc gọi",
|
|
68
|
+
"export.callHistoryReportSuccessContent": "Báo cáo lịch sử cuộc gọi được xuất vào {beginDate} đã sẵn sàng để tải xuống",
|
|
69
|
+
"export.callHistoryReportSuccessLimitedContent": "Báo cáo lịch sử cuộc gọi được xuất vào {beginDate} đã sẵn sàng để tải xuống. <br /> Lưu ý: Chỉ hiển thị tối đa {maxRows} bản ghi gần nhất",
|
|
70
|
+
"export.callHistoryReportErrorContent": "Đã xảy ra lỗi khi xuất báo cáo lịch sử cuộc gọi vào {beginDate}. Vui lòng thử lại",
|
|
71
|
+
"export.znsCampaignDetailTitle": "Chi tiết khách hàng chiến dịch ZNS",
|
|
72
|
+
"export.znsCampaignDetailSuccessContent": "Đã xuất chi tiết khách hàng chiến dịch ZNS thành công lúc {beginDate}",
|
|
73
|
+
"export.campaignContactListTitle": "Danh sách liên hệ chiến dịch",
|
|
74
|
+
"export.campaignContactListSuccessContent": "Đã xuất danh sách liên hệ chiến dịch thành công lúc {beginDate}",
|
|
75
|
+
"export.campaignContactListErrorContent": "Quá trình xuất danh sách liên hệ chiến dịch của bạn đã xảy ra lỗi, vui lòng thử lại sau!",
|
|
76
|
+
"comment.selfMentionTitle": "Bạn tự nhắc đến mình trong một comment",
|
|
77
|
+
"comment.selfMentionContent": "Bạn đã tự nhắc đến mình trong comment trên ticket {code}",
|
|
78
|
+
"comment.mentionTitle": "Bạn được nhắc đến trong một comment",
|
|
79
|
+
"comment.mentionContent": "{name} đã nhắc đến bạn trong comment trên ticket {code}",
|
|
80
|
+
"import.contactSuccessContent": "File {target} import vào {beginDate} đã được xử lý xong. <br />\n - Tổng {target}: {total} <br />\n - Thành công: {success} <br />\n - Thất bại: {error} <br />",
|
|
81
|
+
"import.contactSuccessAssignedContent": "File {target} import vào {beginDate} đã được xử lý xong. <br />\n - Tổng {target}: {total} <br />\n - Thành công: {success} <br />\n - Thất bại: {error} <br />- Đã phân bổ: {assigned} <br />",
|
|
82
|
+
"export.sttLogSuccessContent": "Đã xuất {sheetName} thành công lúc {beginDate}",
|
|
83
|
+
"export.sttLogErrorContent": "Quá trình xuất {sheetName} của bạn đã xảy ra lỗi, vui lòng thử lại sau!",
|
|
84
|
+
"qc.mentionTitle": "Thông báo QA Comment",
|
|
85
|
+
"qc.mentionContent": "{name} đã nhắc đến bạn trong kết quả chiến dịch manual QA của ticket {code}",
|
|
86
|
+
"contact.newContactTitle": "Thông báo đã thêm {label} mới",
|
|
87
|
+
"contact.newContactContent": "Bạn là người phụ trách {label} {name}",
|
|
88
|
+
"deal.assignedTitle": "Thông báo Deal",
|
|
89
|
+
"deal.assignedContent": "Bạn đã được phân bổ phụ trách deal",
|
|
90
|
+
"import.campaignErrorTitle": "Import khách hàng trong chiến dịch thất bại",
|
|
91
|
+
"import.campaignErrorMessageContent": "Quá trình import vào chiến dịch gặp lỗi: {message}",
|
|
92
|
+
"survey.newTitle": "Khảo sát mới",
|
|
93
|
+
"survey.newContent": "Đã nhận được một khảo sát mới từ khách hàng {customerName}",
|
|
94
|
+
"account.disconnectedTitle": "Ngắt kết nối",
|
|
95
|
+
"account.disconnectedContentPersonal": "Tài khoản Zalo cá nhân riêng \"{name}\" đã ngắt kết nối.",
|
|
96
|
+
"account.disconnectedContentGeneral": "Tài khoản Zalo cá nhân chung \"{name}\" đã ngắt kết nối.",
|
|
97
|
+
"sla.breachTitle": "Vi phạm SLA: {metricLabel}",
|
|
98
|
+
"sla.breachContent": "{entityLabel} đã vi phạm SLA {metricLabel}. Vui lòng xử lý ngay.",
|
|
99
|
+
"sla.warningTitle": "Cảnh báo SLA: sắp đến hạn {metricLabel}",
|
|
100
|
+
"sla.warningContent": "{entityLabel} sẽ vi phạm SLA {metricLabel} trong {timeLeftMinutes} phút.",
|
|
101
|
+
"chat.timeoutReminderTitle": "Hội thoại sắp kết thúc",
|
|
102
|
+
"chat.timeoutReminderContent": "Hội thoại với khách hàng {name} sẽ kết thúc sau {minutes} phút",
|
|
103
|
+
"chat.timeoutEndedTitle": "Hội thoại đã kết thúc",
|
|
104
|
+
"chat.timeoutEndedContent": "Phiên hội thoại với {name} đã kết thúc. Xem lại phiên hội thoại ở tab \"Đã đóng\"",
|
|
105
|
+
"chat.unhandledTitle": "Hội thoại",
|
|
106
|
+
"chat.unhandledContent": "Hội thoại với {name} chưa có tương tác trong {duration}"
|
|
107
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
{
|
|
2
|
+
"dialog.selectPosts": "Chọn bài viết",
|
|
3
|
+
"column.postedAt": "Thời gian đăng",
|
|
4
|
+
"empty.noPosts": "Không có bài viết nào",
|
|
5
|
+
"dialog.totalPostsSuffix": "/ {totalCount} bài viết",
|
|
6
|
+
"message.selectedPostsCount": "Đã chọn {length} bài viết",
|
|
7
|
+
"label.noContent": "(Không có nội dung)"
|
|
8
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
{
|
|
2
|
+
"chatTimeout.allChannels": "Tất cả các kênh",
|
|
3
|
+
"chatTimeout.channelUnit": "kênh",
|
|
4
|
+
"channel.zaloPersonal": "Zalo cá nhân",
|
|
5
|
+
"user.agentList": "Danh sách chuyên viên",
|
|
6
|
+
"routing.generalSettings": "Thiết lập chung",
|
|
7
|
+
"common.saveSettingsSuccess": "Lưu thiết lập thành công",
|
|
8
|
+
"routing.settings.selectSeenRuleRequired": "Vui lòng chọn quy tắc đã xem cho hội thoại",
|
|
9
|
+
"routing.settings.conversationSetup": "Thiết lập hội thoại",
|
|
10
|
+
"routing.applyToOutbound": "Áp dụng Routing rule đối với hội thoại nhắn ra",
|
|
11
|
+
"routing.outboundAutoRoutingHint": "Khi thực hiện nhắn ra, hệ thống sẽ tự động gọi Routing Rule để\n thực hiện phân bổ theo câu hình.",
|
|
12
|
+
"routing.applyToManualAssign": "Áp dụng Routing rule đối với phân bổ thủ công",
|
|
13
|
+
"routing.manualAssignAutoRoutingHint": "Khi thực hiện gán chuyên viên thủ công, hệ thống tự động gọi\n Routing Rule để thực hiện phân bổ theo câu hình.",
|
|
14
|
+
"routing.assignWhenAgentInactive": "Phân bổ khi chuyên viên không hoạt động",
|
|
15
|
+
"routing.allowOfflineAssignHint": "Cho phép hệ thống tiếp tục phân bổ hội thoại cho chuyên viên\n ngay cả khi trạng thái hiện tại là Offline.",
|
|
16
|
+
"routing.settings.autoSavePhoneNumber": "Tự động lưu số điện thoại",
|
|
17
|
+
"routing.autoSavePhoneHint": "Khi phát hiện số điện thoại trong cuộc trò chuyện, hệ thống sẽ\n tự động lưu vào danh sách liên hệ.",
|
|
18
|
+
"routing.reassignConversationRule": "Quy tắc phân bổ lại hội thoại",
|
|
19
|
+
"routing.settings.redistributionRuleHint": "Mỗi nhóm áp dụng cho một trạng thái hội thoại và một tập kênh riêng, với mốc thời gian phân bổ riêng của nhóm.",
|
|
20
|
+
"routing.settings.redistributionRuleTitle": "Quy tắc phân bổ {index}",
|
|
21
|
+
"routing.settings.addRedistributionRule": "Thêm quy tắc phân bổ",
|
|
22
|
+
"routing.settings.conversationStatus": "Trạng thái hội thoại",
|
|
23
|
+
"routing.settings.openConversation": "Hội thoại đang mở",
|
|
24
|
+
"routing.settings.missedConversation": "Hội thoại nhỡ",
|
|
25
|
+
"routing.settings.applyToChannel": "Áp dụng cho kênh",
|
|
26
|
+
"routing.settings.dailyRedistributeTime": "Mốc thời gian phân bổ hàng ngày",
|
|
27
|
+
"routing.settings.addMilestone": "Thêm mốc",
|
|
28
|
+
"routing.viewedRuleForConversation": "Quy tắc đã xem cho hội thoại",
|
|
29
|
+
"routing.settings.seenByUserView": "Tính đã xem theo view người dùng",
|
|
30
|
+
"routing.settings.seenByAssignedAgent": "Tính đã xem đối với chuyên viên phụ trách",
|
|
31
|
+
"routing.settings.seenByAnyMember": "Tính đã xem khi bất kỳ thành viên nào trong hội thoại xem.",
|
|
32
|
+
"common.numberPlaceholder": "Nhập số",
|
|
33
|
+
"smartView.selectCycle": "Chọn chu kỳ",
|
|
34
|
+
"smartView.toleranceDays": "Sai số (ngày)",
|
|
35
|
+
"smartView.plusMinusDays": "± ngày",
|
|
36
|
+
"smartView.anchorDate": "Ngày mốc",
|
|
37
|
+
"smartView.emptyMeansTodayHint": "Để trống = tính theo ngày hiện tại, tự cập nhật mỗi ngày",
|
|
38
|
+
"user.unknownError": "Lỗi không xác định"
|
|
39
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import i18n, { loadNamespacesForLanguage } from "./index.js";
|
|
2
|
+
const SERVER_LABEL_NS = ["common", "chat"];
|
|
3
|
+
let index = null;
|
|
4
|
+
let viWarming = null;
|
|
5
|
+
function buildIndexFromStore() {
|
|
6
|
+
const map = /* @__PURE__ */ new Map();
|
|
7
|
+
for (const ns of SERVER_LABEL_NS) {
|
|
8
|
+
const bundle = i18n.getResourceBundle("vi", ns);
|
|
9
|
+
if (!bundle) continue;
|
|
10
|
+
for (const [key, value] of Object.entries(bundle)) {
|
|
11
|
+
if (typeof value !== "string") continue;
|
|
12
|
+
const text = value.trim();
|
|
13
|
+
if (text && !map.has(text)) map.set(text, `${ns}:${key}`);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
return map;
|
|
17
|
+
}
|
|
18
|
+
function translateServerLabel(text) {
|
|
19
|
+
if (typeof text !== "string" || !text) return text;
|
|
20
|
+
if ((i18n.resolvedLanguage || i18n.language) === "vi") return text;
|
|
21
|
+
if (!viWarming) {
|
|
22
|
+
viWarming = loadNamespacesForLanguage("vi", SERVER_LABEL_NS).then(() => {
|
|
23
|
+
index = buildIndexFromStore();
|
|
24
|
+
i18n.emit("languageChanged", i18n.resolvedLanguage || i18n.language);
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
if (!index) return text;
|
|
28
|
+
const key = index.get(text.trim());
|
|
29
|
+
return key ? i18n.t(key, { defaultValue: text }) : text;
|
|
30
|
+
}
|
|
31
|
+
export {
|
|
32
|
+
translateServerLabel
|
|
33
|
+
};
|
package/lib/phone.js
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import {
|
|
2
|
+
isValidPhoneNumber as libIsValidPhoneNumber,
|
|
3
|
+
findPhoneNumbersInText
|
|
4
|
+
} from "libphonenumber-js/max";
|
|
5
|
+
const DEFAULT_COUNTRY = "VN";
|
|
6
|
+
const VN_PHONE_SHAPE = String.raw`(?:\+84|0)(?:[\s.-]?\d){9}`;
|
|
7
|
+
const VN_PHONE_TEXT_REGEX = new RegExp(`(?<!\\d)${VN_PHONE_SHAPE}(?!\\d)`, "g");
|
|
8
|
+
const VN_PHONE_EXACT_REGEX = new RegExp(`^${VN_PHONE_SHAPE}$`);
|
|
9
|
+
const onlyDigits = (str) => str.replace(/\D/g, "");
|
|
10
|
+
const isValidPhone = (raw, defaultCountry = DEFAULT_COUNTRY) => {
|
|
11
|
+
if (!raw || typeof raw !== "string") return false;
|
|
12
|
+
try {
|
|
13
|
+
return libIsValidPhoneNumber(raw.trim(), defaultCountry);
|
|
14
|
+
} catch {
|
|
15
|
+
return false;
|
|
16
|
+
}
|
|
17
|
+
};
|
|
18
|
+
const findPhonesInText = (text, defaultCountry = DEFAULT_COUNTRY) => {
|
|
19
|
+
if (!text || typeof text !== "string") return [];
|
|
20
|
+
try {
|
|
21
|
+
return findPhoneNumbersInText(text, { defaultCountry }).map((match) => ({
|
|
22
|
+
raw: text.slice(match.startsAt, match.endsAt),
|
|
23
|
+
startsAt: match.startsAt,
|
|
24
|
+
endsAt: match.endsAt,
|
|
25
|
+
e164: match.number.number
|
|
26
|
+
}));
|
|
27
|
+
} catch {
|
|
28
|
+
return [];
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
const isVNPhoneLike = (raw, defaultCountry = DEFAULT_COUNTRY) => {
|
|
32
|
+
if (!raw || typeof raw !== "string") return false;
|
|
33
|
+
const trimmed = raw.trim();
|
|
34
|
+
return isValidPhone(trimmed, defaultCountry) || VN_PHONE_EXACT_REGEX.test(trimmed);
|
|
35
|
+
};
|
|
36
|
+
const findVNPhonesInText = (text, defaultCountry = DEFAULT_COUNTRY) => {
|
|
37
|
+
if (!text || typeof text !== "string") return [];
|
|
38
|
+
const seen = /* @__PURE__ */ new Set();
|
|
39
|
+
const result = [];
|
|
40
|
+
const push = (raw) => {
|
|
41
|
+
const key = onlyDigits(raw);
|
|
42
|
+
if (!key || seen.has(key)) return;
|
|
43
|
+
seen.add(key);
|
|
44
|
+
result.push(raw);
|
|
45
|
+
};
|
|
46
|
+
for (const match of findPhonesInText(text, defaultCountry)) push(match.raw);
|
|
47
|
+
const re = new RegExp(VN_PHONE_TEXT_REGEX.source, "g");
|
|
48
|
+
for (const match of text.matchAll(re)) push(match[0].trim());
|
|
49
|
+
return result;
|
|
50
|
+
};
|
|
51
|
+
const replacePhonesInText = (text, replacer, defaultCountry = DEFAULT_COUNTRY) => {
|
|
52
|
+
if (!text || typeof text !== "string") return text;
|
|
53
|
+
const matches = findPhonesInText(text, defaultCountry);
|
|
54
|
+
if (matches.length === 0) return text;
|
|
55
|
+
let result = text;
|
|
56
|
+
for (let i = matches.length - 1; i >= 0; i -= 1) {
|
|
57
|
+
const match = matches[i];
|
|
58
|
+
const replacement = replacer(match);
|
|
59
|
+
result = result.slice(0, match.startsAt) + replacement + result.slice(match.endsAt);
|
|
60
|
+
}
|
|
61
|
+
return result;
|
|
62
|
+
};
|
|
63
|
+
export {
|
|
64
|
+
DEFAULT_COUNTRY,
|
|
65
|
+
findPhonesInText,
|
|
66
|
+
findVNPhonesInText,
|
|
67
|
+
isVNPhoneLike,
|
|
68
|
+
isValidPhone,
|
|
69
|
+
replacePhonesInText
|
|
70
|
+
};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
const AVATAR_COLORS = [
|
|
2
|
+
{ bg: "bg-purple-100", text: "text-purple-500" },
|
|
3
|
+
{ bg: "bg-pink-100", text: "text-pink-500" },
|
|
4
|
+
{ bg: "bg-sky-100", text: "text-sky-500" },
|
|
5
|
+
{ bg: "bg-green-100", text: "text-green-500" },
|
|
6
|
+
{ bg: "bg-orange-100", text: "text-orange-500" },
|
|
7
|
+
{ bg: "bg-teal-100", text: "text-teal-500" },
|
|
8
|
+
{ bg: "bg-rose-100", text: "text-rose-500" }
|
|
9
|
+
];
|
|
10
|
+
function colorForName(name = "") {
|
|
11
|
+
const hash = name.split("").reduce((acc, c) => acc + c.charCodeAt(0), 0);
|
|
12
|
+
return AVATAR_COLORS[hash % AVATAR_COLORS.length];
|
|
13
|
+
}
|
|
14
|
+
function nameInitials(name = "") {
|
|
15
|
+
return name.split(" ").filter(Boolean).map((n) => n[0]).join("").slice(0, 2).toUpperCase() || "?";
|
|
16
|
+
}
|
|
17
|
+
export {
|
|
18
|
+
AVATAR_COLORS,
|
|
19
|
+
colorForName,
|
|
20
|
+
nameInitials
|
|
21
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@zenify-omni/chat-js",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Tầng logic của Zenify Chat: API, transport, socket instance. Không React.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "git+https://github.com/ZenifyAIContactCenter/sdk-omni-chat.git",
|
|
9
|
+
"directory": "chat-js"
|
|
10
|
+
},
|
|
11
|
+
"homepage": "https://github.com/ZenifyAIContactCenter/sdk-omni-chat#readme",
|
|
12
|
+
"bugs": {
|
|
13
|
+
"url": "https://github.com/ZenifyAIContactCenter/sdk-omni-chat/issues"
|
|
14
|
+
},
|
|
15
|
+
"type": "module",
|
|
16
|
+
"sideEffects": [
|
|
17
|
+
"./lib/i18n/index.js",
|
|
18
|
+
"./utils/request.js"
|
|
19
|
+
],
|
|
20
|
+
"exports": {
|
|
21
|
+
".": "./client.js",
|
|
22
|
+
"./*": "./*"
|
|
23
|
+
},
|
|
24
|
+
"publishConfig": {
|
|
25
|
+
"access": "public"
|
|
26
|
+
},
|
|
27
|
+
"dependencies": {
|
|
28
|
+
"browser-image-compression": "^2.0.2",
|
|
29
|
+
"i18next-browser-languagedetector": "^8.2.1",
|
|
30
|
+
"i18next-icu": "^2.4.4",
|
|
31
|
+
"i18next-resources-to-backend": "^1.2.3",
|
|
32
|
+
"libphonenumber-js": "^1.13.7",
|
|
33
|
+
"nanoid": "^5.1.16",
|
|
34
|
+
"socket.io-client": "^4.8.3"
|
|
35
|
+
},
|
|
36
|
+
"peerDependencies": {
|
|
37
|
+
"@date-fns/tz": "^1.1.2",
|
|
38
|
+
"axios": "^1.7.7",
|
|
39
|
+
"date-fns": "^4.4.0",
|
|
40
|
+
"file-saver": "^2.0.5",
|
|
41
|
+
"i18next": "^26.2.0",
|
|
42
|
+
"lodash-es": "^4.17.21",
|
|
43
|
+
"xlsx-js-style": "^1.2.0"
|
|
44
|
+
}
|
|
45
|
+
}
|
package/socket/index.js
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import io from "socket.io-client";
|
|
2
|
+
const identity = (user) => ({
|
|
3
|
+
user_id: user._id,
|
|
4
|
+
username: user.name,
|
|
5
|
+
tenant_id: user.tenant_id,
|
|
6
|
+
agent_id: user.agent_id,
|
|
7
|
+
is_agent: 1,
|
|
8
|
+
role: user?.role
|
|
9
|
+
});
|
|
10
|
+
const createChatSocket = (url, user) => io(url, {
|
|
11
|
+
transports: ["websocket"],
|
|
12
|
+
reconnection: true,
|
|
13
|
+
reconnectionDelay: 500,
|
|
14
|
+
reconnectionDelayMax: 3e4,
|
|
15
|
+
timeout: 5e3,
|
|
16
|
+
forceNew: true,
|
|
17
|
+
query: identity(user)
|
|
18
|
+
});
|
|
19
|
+
const createTypingSocket = (baseUrl, user) => io(`${baseUrl}/typing`, {
|
|
20
|
+
transports: ["websocket"],
|
|
21
|
+
reconnection: true,
|
|
22
|
+
reconnectionDelay: 1e3,
|
|
23
|
+
reconnectionDelayMax: 3e4,
|
|
24
|
+
timeout: 5e3,
|
|
25
|
+
query: identity(user)
|
|
26
|
+
});
|
|
27
|
+
export {
|
|
28
|
+
createChatSocket,
|
|
29
|
+
createTypingSocket
|
|
30
|
+
};
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
const transports = /* @__PURE__ */ new Map();
|
|
2
|
+
function registerChatTransport({ name, connect, disconnect, methods = {} }) {
|
|
3
|
+
if (!name) throw new Error("[chat-js] registerChatTransport: thi\u1EBFu `name`");
|
|
4
|
+
for (const [otherName, other] of transports) {
|
|
5
|
+
if (otherName === name) continue;
|
|
6
|
+
const clash = Object.keys(methods).filter((m) => m in other.methods);
|
|
7
|
+
if (clash.length) {
|
|
8
|
+
console.warn(
|
|
9
|
+
`[chat-js] transport "${name}" v\xE0 "${otherName}" c\xF9ng khai ${clash.join(", ")}. L\u1EDDi g\u1ECDi ph\u1EB3ng client.socket.${clash[0]}() s\u1EBD \u0111i v\xE0o transport \u0111\u0103ng k\xFD TR\u01AF\u1EDAC. D\xF9ng client.socket.${name}.${clash[0]}() \u0111\u1EC3 ch\u1EC9 \u0111\xEDch danh.`
|
|
10
|
+
);
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
transports.set(name, { name, connect, disconnect, methods });
|
|
14
|
+
return () => transports.delete(name);
|
|
15
|
+
}
|
|
16
|
+
const getChatTransport = (name) => transports.get(name);
|
|
17
|
+
const listChatTransports = () => [...transports.keys()];
|
|
18
|
+
const resetChatTransports = () => transports.clear();
|
|
19
|
+
const fanOut = (hook) => () => {
|
|
20
|
+
for (const t of transports.values()) t[hook]?.();
|
|
21
|
+
};
|
|
22
|
+
const connectChatTransports = fanOut("connect");
|
|
23
|
+
const disconnectChatTransports = fanOut("disconnect");
|
|
24
|
+
const findMethod = (prop) => {
|
|
25
|
+
for (const t of transports.values()) {
|
|
26
|
+
if (prop in t.methods) return t.methods[prop];
|
|
27
|
+
}
|
|
28
|
+
return void 0;
|
|
29
|
+
};
|
|
30
|
+
function createSocketFacade() {
|
|
31
|
+
return new Proxy(
|
|
32
|
+
{},
|
|
33
|
+
{
|
|
34
|
+
get(_target, prop) {
|
|
35
|
+
if (prop === "connect") return connectChatTransports;
|
|
36
|
+
if (prop === "disconnect") return disconnectChatTransports;
|
|
37
|
+
if (prop === "transports") return listChatTransports();
|
|
38
|
+
if (typeof prop !== "string") return void 0;
|
|
39
|
+
const transport = transports.get(prop);
|
|
40
|
+
if (transport) return transport.methods;
|
|
41
|
+
const method = findMethod(prop);
|
|
42
|
+
if (method) return method;
|
|
43
|
+
if (transports.size === 0) {
|
|
44
|
+
return () => {
|
|
45
|
+
throw new Error(
|
|
46
|
+
`[chat-js] client.socket.${prop}() ch\u01B0a s\u1EB5n s\xE0ng: ch\u01B0a transport n\xE0o \u0111\u0103ng k\xFD. D\xF9ng <ChatProvider>, ho\u1EB7c n\u1EA1p module @chat-ui/utils/chat_socket \u0111\u1EC3 b\u1EADt t\u1EA7ng realtime.`
|
|
47
|
+
);
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
return void 0;
|
|
51
|
+
},
|
|
52
|
+
has(_target, prop) {
|
|
53
|
+
return prop === "connect" || prop === "disconnect" || prop === "transports" || transports.has(prop) || findMethod(prop) !== void 0;
|
|
54
|
+
},
|
|
55
|
+
ownKeys() {
|
|
56
|
+
const keys = /* @__PURE__ */ new Set(["connect", "disconnect", "transports"]);
|
|
57
|
+
for (const t of transports.values()) {
|
|
58
|
+
keys.add(t.name);
|
|
59
|
+
for (const m of Object.keys(t.methods)) keys.add(m);
|
|
60
|
+
}
|
|
61
|
+
return [...keys];
|
|
62
|
+
},
|
|
63
|
+
getOwnPropertyDescriptor() {
|
|
64
|
+
return { enumerable: true, configurable: true };
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
export {
|
|
70
|
+
connectChatTransports,
|
|
71
|
+
createSocketFacade,
|
|
72
|
+
disconnectChatTransports,
|
|
73
|
+
getChatTransport,
|
|
74
|
+
listChatTransports,
|
|
75
|
+
registerChatTransport,
|
|
76
|
+
resetChatTransports
|
|
77
|
+
};
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import i18n from "../lib/i18n/index.js";
|
|
2
|
+
const ACCESS_DETAIL_KEY = {
|
|
3
|
+
ALL: "all",
|
|
4
|
+
GROUP: "group",
|
|
5
|
+
USER: "user"
|
|
6
|
+
};
|
|
7
|
+
const ACCESS_DETAIL_OPTIONS = [
|
|
8
|
+
{
|
|
9
|
+
value: ACCESS_DETAIL_KEY.ALL,
|
|
10
|
+
get label() {
|
|
11
|
+
return i18n.t("common:filter.all", { defaultValue: "T\u1EA5t c\u1EA3" });
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
value: ACCESS_DETAIL_KEY.GROUP,
|
|
16
|
+
get label() {
|
|
17
|
+
return i18n.t("common:label.department", { defaultValue: "B\u1ED9 ph\u1EADn" });
|
|
18
|
+
}
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
value: ACCESS_DETAIL_KEY.USER,
|
|
22
|
+
get label() {
|
|
23
|
+
return i18n.t("common:label.agent", { defaultValue: "Chuy\xEAn vi\xEAn" });
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
];
|
|
27
|
+
const ACCESS_DETAIL_ALL_LABEL = i18n.t("common:filter.everyone", {
|
|
28
|
+
defaultValue: "T\u1EA5t c\u1EA3 m\u1ECDi ng\u01B0\u1EDDi"
|
|
29
|
+
});
|
|
30
|
+
const labelOf = (item) => item?.label ?? item?.name ?? item?.value ?? item?._id ?? item?.id ?? null;
|
|
31
|
+
const buildAccessDetailPayload = ({
|
|
32
|
+
key = ACCESS_DETAIL_KEY.ALL,
|
|
33
|
+
groups = [],
|
|
34
|
+
users = []
|
|
35
|
+
} = {}) => ({
|
|
36
|
+
key,
|
|
37
|
+
group_ids: key === ACCESS_DETAIL_KEY.GROUP ? groups.map((g) => String(g.value)) : [],
|
|
38
|
+
user_ids: key === ACCESS_DETAIL_KEY.USER ? users.map((u) => String(u.value)) : []
|
|
39
|
+
});
|
|
40
|
+
const resolveGroupOptionList = (accessDetail, groupOptions) => {
|
|
41
|
+
if (Array.isArray(accessDetail?.groups) && accessDetail.groups.length) {
|
|
42
|
+
return accessDetail.groups.map((g) => ({
|
|
43
|
+
value: String(g.value ?? g.id ?? g._id),
|
|
44
|
+
label: labelOf(g) ?? String(g.value ?? g.id ?? g._id)
|
|
45
|
+
}));
|
|
46
|
+
}
|
|
47
|
+
const optionMap = new Map(
|
|
48
|
+
(groupOptions || []).map((o) => [String(o.value), o.label])
|
|
49
|
+
);
|
|
50
|
+
return (accessDetail?.group_ids || []).map((id) => ({
|
|
51
|
+
value: String(id),
|
|
52
|
+
label: optionMap.get(String(id)) ?? String(id)
|
|
53
|
+
}));
|
|
54
|
+
};
|
|
55
|
+
const resolveUserOptionList = (accessDetail) => {
|
|
56
|
+
if (Array.isArray(accessDetail?.users) && accessDetail.users.length) {
|
|
57
|
+
return accessDetail.users.map((u) => ({
|
|
58
|
+
type: "user",
|
|
59
|
+
groupKey: "user",
|
|
60
|
+
value: String(u.value ?? u._id ?? u.id),
|
|
61
|
+
label: labelOf(u) ?? String(u.value ?? u._id ?? u.id),
|
|
62
|
+
email: u.email
|
|
63
|
+
}));
|
|
64
|
+
}
|
|
65
|
+
return (accessDetail?.user_ids || []).map((id) => ({
|
|
66
|
+
type: "user",
|
|
67
|
+
groupKey: "user",
|
|
68
|
+
value: String(id),
|
|
69
|
+
label: String(id)
|
|
70
|
+
}));
|
|
71
|
+
};
|
|
72
|
+
const parseAccessDetailValue = (accessDetail, { groupOptions = [] } = {}) => {
|
|
73
|
+
const empty = { key: ACCESS_DETAIL_KEY.ALL, groups: [], users: [] };
|
|
74
|
+
if (!accessDetail?.key) return empty;
|
|
75
|
+
if (accessDetail.key === ACCESS_DETAIL_KEY.GROUP) {
|
|
76
|
+
return {
|
|
77
|
+
key: ACCESS_DETAIL_KEY.GROUP,
|
|
78
|
+
groups: resolveGroupOptionList(accessDetail, groupOptions),
|
|
79
|
+
users: []
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
if (accessDetail.key === ACCESS_DETAIL_KEY.USER) {
|
|
83
|
+
return {
|
|
84
|
+
key: ACCESS_DETAIL_KEY.USER,
|
|
85
|
+
groups: [],
|
|
86
|
+
users: resolveUserOptionList(accessDetail)
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
return empty;
|
|
90
|
+
};
|
|
91
|
+
const formatAccessDetailText = (accessDetail) => {
|
|
92
|
+
if (!accessDetail?.key) return "-";
|
|
93
|
+
if (accessDetail.key === ACCESS_DETAIL_KEY.ALL) return ACCESS_DETAIL_ALL_LABEL;
|
|
94
|
+
const items = accessDetail.key === ACCESS_DETAIL_KEY.GROUP ? accessDetail.groups || accessDetail.group_ids || [] : accessDetail.users || accessDetail.user_ids || [];
|
|
95
|
+
const labels = items.map(labelOf).filter(Boolean).map(String);
|
|
96
|
+
return labels.length ? labels.join(", ") : "-";
|
|
97
|
+
};
|
|
98
|
+
const isAccessDetailAllUsers = (accessDetail) => accessDetail?.key === ACCESS_DETAIL_KEY.ALL;
|
|
99
|
+
const accessDetailHasId = (ids, objects, target) => {
|
|
100
|
+
const wanted = String(target);
|
|
101
|
+
if ((ids || []).some((id) => String(id) === wanted)) return true;
|
|
102
|
+
return (objects || []).some(
|
|
103
|
+
(o) => String(o?.value ?? o?._id ?? o?.id) === wanted
|
|
104
|
+
);
|
|
105
|
+
};
|
|
106
|
+
const accessDetailMatchesUser = (accessDetail, userId) => !!userId && accessDetail?.key === ACCESS_DETAIL_KEY.USER && accessDetailHasId(accessDetail.user_ids, accessDetail.users, userId);
|
|
107
|
+
const accessDetailMatchesGroup = (accessDetail, groupId) => !!groupId && accessDetail?.key === ACCESS_DETAIL_KEY.GROUP && accessDetailHasId(accessDetail.group_ids, accessDetail.groups, groupId);
|
|
108
|
+
export {
|
|
109
|
+
ACCESS_DETAIL_ALL_LABEL,
|
|
110
|
+
ACCESS_DETAIL_KEY,
|
|
111
|
+
ACCESS_DETAIL_OPTIONS,
|
|
112
|
+
accessDetailHasId,
|
|
113
|
+
accessDetailMatchesGroup,
|
|
114
|
+
accessDetailMatchesUser,
|
|
115
|
+
buildAccessDetailPayload,
|
|
116
|
+
formatAccessDetailText,
|
|
117
|
+
isAccessDetailAllUsers,
|
|
118
|
+
parseAccessDetailValue
|
|
119
|
+
};
|