update web to dashboard

This commit is contained in:
mlogclub
2026-04-15 17:20:57 +08:00
parent 3d98cf1e27
commit c07e588030
206 changed files with 0 additions and 0 deletions
-1693
View File
File diff suppressed because it is too large Load Diff
-268
View File
@@ -1,268 +0,0 @@
import { request } from "@/lib/api/client"
import { readSession } from "@/lib/auth"
import { createWebSocketBaseUrl } from "@/lib/api/websocket"
export type Paging = {
page: number
limit: number
total: number
}
export type PageResult<T> = {
results: T[]
page: Paging
}
export type CursorResult<T> = {
results: T[]
cursor: string
hasMore: boolean
}
export type AgentConversationTag = {
id: number
name: string
}
export type AgentConversationParticipant = {
id: number
participantType: string
participantId: number
externalParticipantId?: string
joinedAt?: string
leftAt?: string
status: number
}
export type AgentConversation = {
id: number
aiAgentId?: number
customerId?: number
externalSource: string
externalId: string
subject: string
status: number
serviceMode: number
priority: number
currentAssigneeId: number
currentAssigneeName?: string
lastMessageId: number
lastMessageAt?: string
lastActiveAt?: string
lastMessageSummary?: string
customerUnreadCount: number
agentUnreadCount: number
customerLastReadMessageId: number
customerLastReadSeqNo: number
customerLastReadAt?: string
agentLastReadMessageId: number
agentLastReadSeqNo: number
agentLastReadAt?: string
customerOnline: boolean
closedAt?: string
tags?: AgentConversationTag[]
participants?: AgentConversationParticipant[]
}
export type AgentConversationDetail = AgentConversation & {
participants?: AgentConversationParticipant[]
}
export type AgentMessage = {
id: number
conversationId: number
clientMsgId?: string
senderType: string
senderId: number
senderName?: string
senderAvatar?: string
messageType: string
content: string
payload?: string
seqNo: number
sendStatus: number
sentAt?: string
deliveredAt?: string
readAt?: string
customerRead: boolean
customerReadAt?: string
agentRead: boolean
agentReadAt?: string
recalledAt?: string
quotedMessageId?: number
}
export type AgentAsset = {
id: number
assetId: string
provider: string
filename: string
fileSize: number
mimeType: string
status: number
url: string
createdAt: string
updatedAt: string
createUserId: number
createUserName: string
updateUserId: number
updateUserName: string
}
function toQueryString(query?: Record<string, string | number | undefined>) {
if (!query) {
return ""
}
const params = new URLSearchParams()
Object.entries(query).forEach(([key, value]) => {
if (value === undefined || value === "") {
return
}
params.set(key, String(value))
})
const output = params.toString()
return output ? `?${output}` : ""
}
export function fetchAgentConversations(
query?: Record<string, string | number | undefined>
) {
return request<PageResult<AgentConversation>>(
`/api/dashboard/conversation/conversations${toQueryString(query)}`
)
}
export function fetchAgentConversationDetail(id: number) {
return request<AgentConversationDetail>(`/api/dashboard/conversation/${id}`)
}
export function fetchAgentMessages(
query?: Record<string, string | number | undefined>
) {
return request<CursorResult<AgentMessage>>(
`/api/dashboard/conversation/message_list${toQueryString(query)}`
)
}
export function sendAgentMessage(payload: {
conversationId: number
messageType: string
content: string
payload?: string
clientMsgId?: string
}) {
return request<AgentMessage>("/api/dashboard/conversation/send_message", {
method: "POST",
body: JSON.stringify(payload),
})
}
export function recallAgentMessage(messageId: number) {
return request<AgentMessage>("/api/dashboard/conversation/recall_message", {
method: "POST",
body: JSON.stringify({ messageId }),
})
}
export function markAgentMessageRead(conversationId: number, messageId = 0) {
return request<void>("/api/dashboard/conversation/read", {
method: "POST",
body: JSON.stringify({ conversationId, messageId }),
})
}
export function uploadAgentConversationImage(conversationId: number, file: File) {
const formData = new FormData()
formData.set("conversationId", String(conversationId))
formData.set("file", file)
return request<AgentAsset>("/api/dashboard/conversation/upload_image", {
method: "POST",
body: formData,
})
}
export function uploadAgentConversationAttachment(conversationId: number, file: File) {
const formData = new FormData()
formData.set("conversationId", String(conversationId))
formData.set("file", file)
return request<AgentAsset>("/api/dashboard/conversation/upload_attachment", {
method: "POST",
body: formData,
})
}
export function closeAgentConversation(
conversationId: number,
closeReason: string
) {
return request<void>("/api/dashboard/conversation/close", {
method: "POST",
body: JSON.stringify({ conversationId, closeReason }),
})
}
export function assignAgentConversation(
conversationId: number,
assigneeId: number,
reason: string
) {
return request<void>("/api/dashboard/conversation/assign", {
method: "POST",
body: JSON.stringify({ conversationId, assigneeId, reason }),
})
}
export function transferAgentConversation(
conversationId: number,
toUserId: number,
reason: string
) {
return request<void>("/api/dashboard/conversation/transfer", {
method: "POST",
body: JSON.stringify({ conversationId, toUserId, reason }),
})
}
export function linkConversationToCustomer(payload: {
conversationId: number
customerId: number
}) {
return request<void>("/api/dashboard/conversation/link_customer", {
method: "POST",
body: JSON.stringify(payload),
})
}
export function addConversationTag(payload: {
conversationId: number
tagId: number
}) {
return request<void>("/api/dashboard/conversation/add_tag", {
method: "POST",
body: JSON.stringify(payload),
})
}
export function removeConversationTag(payload: {
conversationId: number
tagId: number
}) {
return request<void>("/api/dashboard/conversation/remove_tag", {
method: "POST",
body: JSON.stringify(payload),
})
}
export function createAgentWebSocketUrl() {
const session = readSession()
if (!session?.accessToken) {
throw new Error("未登录或登录已过期")
}
const baseUrl = createWebSocketBaseUrl()
const params = new URLSearchParams({
accessToken: session.accessToken,
})
return `${baseUrl}/api/dashboard/ws?${params.toString()}`
}
-44
View File
@@ -1,44 +0,0 @@
import { clearSession, writeSession, type AuthSession } from "@/lib/auth"
import { request } from "@/lib/api/client"
export type LoginRequest = {
username: string
password: string
}
export async function loginWithPassword(payload: LoginRequest) {
const data = await request<AuthSession>("/api/auth/login", {
method: "POST",
body: JSON.stringify(payload),
skipAuth: true,
})
writeSession(data)
return data
}
export async function exchangeWxWorkTicket(ticket: string) {
const data = await request<AuthSession>("/api/auth/wxwork_exchange", {
method: "POST",
body: JSON.stringify({ ticket }),
skipAuth: true,
})
writeSession(data)
return data
}
export async function fetchProfile() {
return request<AuthSession>("/api/auth/profile")
}
export async function logout(refreshToken?: string) {
try {
await request("/api/auth/logout", {
method: "POST",
body: JSON.stringify({
refreshToken,
}),
})
} finally {
clearSession()
}
}
-104
View File
@@ -1,104 +0,0 @@
import { clearSession, readSession, writeSession, type AuthSession } from "@/lib/auth"
const API_BASE_URL =
process.env.NEXT_PUBLIC_API_BASE_URL?.trim() || ""
type JsonResult<T> = {
errorCode: number
message: string
data: T
success: boolean
}
type RequestOptions = RequestInit & {
skipAuth?: boolean
retryOnAuthError?: boolean
}
async function parseResult<T>(response: Response) {
const payload = (await response.json()) as JsonResult<T>
if (!response.ok || !payload.success) {
const error = new Error(payload.message || "请求失败")
;(error as Error & { errorCode?: number }).errorCode = payload.errorCode
throw error
}
return payload.data
}
async function refreshAccessToken() {
const session = readSession()
if (!session?.refreshToken) {
clearSession()
return null
}
const data = await request<AuthSession>(
"/api/auth/refresh_token",
{
method: "POST",
body: JSON.stringify({ refreshToken: session.refreshToken }),
skipAuth: true,
headers: {
"Content-Type": "application/json",
},
},
false
)
const merged = {
...data,
refreshToken: data.refreshToken || session.refreshToken,
}
writeSession(merged)
return merged
}
export async function request<T>(
path: string,
options: RequestOptions = {},
retryOnAuthError = true
): Promise<T> {
const { headers, skipAuth, ...rest } = options
delete (rest as RequestOptions).retryOnAuthError
const session = readSession()
const authHeaders = new Headers(headers)
if (!skipAuth && session?.accessToken) {
authHeaders.set("Authorization", `Bearer ${session.accessToken}`)
}
if (
!authHeaders.has("Content-Type") &&
rest.body &&
!(typeof FormData !== "undefined" && rest.body instanceof FormData)
) {
authHeaders.set("Content-Type", "application/json")
}
const response = await fetch(`${API_BASE_URL}${path}`, {
...rest,
headers: authHeaders,
cache: "no-store",
})
try {
return await parseResult<T>(response)
} catch (error) {
const errorCode = (error as Error & { errorCode?: number }).errorCode
if (
!skipAuth &&
retryOnAuthError &&
(errorCode === 3000 || errorCode === 3002) &&
session?.refreshToken
) {
const refreshed = await refreshAccessToken()
if (!refreshed) {
throw error
}
return request<T>(path, options, false)
}
if (errorCode === 3000 || errorCode === 3002) {
clearSession()
}
throw error
}
}
-77
View File
@@ -1,77 +0,0 @@
import { request } from "@/lib/api/client"
import type { PageResult } from "@/lib/api/admin"
export type AdminCompany = {
id: number
name: string
code: string
customerCount: number
status: number
remark: string
createdAt: string
updatedAt: string
}
export type CreateAdminCompanyPayload = {
name: string
code: string
remark: string
}
export type UpdateAdminCompanyPayload = CreateAdminCompanyPayload & {
id: number
}
function toQueryString(query?: Record<string, string | number | undefined>) {
if (!query) {
return ""
}
const params = new URLSearchParams()
Object.entries(query).forEach(([key, value]) => {
if (value === undefined || value === "") {
return
}
params.set(key, String(value))
})
const output = params.toString()
return output ? `?${output}` : ""
}
export function fetchCompanies(query?: Record<string, string | number | undefined>) {
return request<PageResult<AdminCompany>>(
`/api/dashboard/company/list${toQueryString(query)}`
)
}
export function fetchCompany(id: number) {
return request<AdminCompany>(`/api/dashboard/company/${id}`)
}
export function createCompany(payload: CreateAdminCompanyPayload) {
return request<AdminCompany>("/api/dashboard/company/create", {
method: "POST",
body: JSON.stringify(payload),
})
}
export function updateCompany(payload: UpdateAdminCompanyPayload) {
return request<void>("/api/dashboard/company/update", {
method: "POST",
body: JSON.stringify(payload),
})
}
export function updateCompanyStatus(id: number, status: number) {
return request<void>("/api/dashboard/company/update_status", {
method: "POST",
body: JSON.stringify({ id, status }),
})
}
export function deleteCompany(id: number) {
return request<void>("/api/dashboard/company/delete", {
method: "POST",
body: JSON.stringify({ id }),
})
}
-77
View File
@@ -1,77 +0,0 @@
import { request } from "@/lib/api/client"
import type { ContactType } from "@/lib/generated/enums"
export type AdminCustomerContact = {
id: number
customerId: number
contactType: ContactType | string
contactValue: string
isPrimary: boolean
isVerified: boolean
verifiedAt?: string
source: string
status: number
remark: string
createdAt: string
updatedAt: string
}
export type CreateCustomerContactPayload = {
customerId: number
contactType: ContactType | string
contactValue: string
isPrimary: boolean
isVerified: boolean
source: string
status: number
remark: string
}
export type UpdateCustomerContactPayload = Omit<
CreateCustomerContactPayload,
"customerId"
> & {
id: number
}
function toQueryString(query?: Record<string, string | number | undefined>) {
if (!query) {
return ""
}
const params = new URLSearchParams()
Object.entries(query).forEach(([key, value]) => {
if (value === undefined || value === "") {
return
}
params.set(key, String(value))
})
const output = params.toString()
return output ? `?${output}` : ""
}
export function fetchCustomerContacts(customerId: number) {
return request<AdminCustomerContact[]>(
`/api/dashboard/customer-contact/list${toQueryString({ customerId })}`
)
}
export function createCustomerContact(payload: CreateCustomerContactPayload) {
return request<AdminCustomerContact>("/api/dashboard/customer-contact/create", {
method: "POST",
body: JSON.stringify(payload),
})
}
export function updateCustomerContact(payload: UpdateCustomerContactPayload) {
return request<void>("/api/dashboard/customer-contact/update", {
method: "POST",
body: JSON.stringify(payload),
})
}
export function deleteCustomerContact(id: number) {
return request<void>("/api/dashboard/customer-contact/delete", {
method: "POST",
body: JSON.stringify({ id }),
})
}
-108
View File
@@ -1,108 +0,0 @@
import { request } from "@/lib/api/client"
import type { PageResult } from "@/lib/api/admin"
import type { ContactType } from "@/lib/generated/enums"
import { AdminCompany } from "./company"
export type AdminCustomer = {
id: number
name: string
gender: number
companyId: number
company?: AdminCompany
lastActiveAt?: string
primaryMobile: string
primaryEmail: string
status: number
remark: string
createdAt: string
updatedAt: string
}
export type CreateAdminCustomerPayload = {
name: string
gender: number
companyId: number
primaryMobile: string
primaryEmail: string
remark: string
}
export type UpdateAdminCustomerPayload = CreateAdminCustomerPayload & {
id: number
}
/** 与 POST /customer/save/profile 请求体一致 */
export type SaveCustomerProfileContactLine = {
id?: number
contactType: ContactType | string
contactValue: string
remark: string
isPrimary: boolean
}
export type SaveCustomerProfilePayload = {
id?: number
name: string
gender: number
companyId: number
remark: string
contacts: SaveCustomerProfileContactLine[]
}
/** 与 POST /customer/list JSON Body 一致 */
export type CustomerListRequest = {
page: number
limit: number
status?: number
gender?: number
companyId?: number
/** 模糊匹配:客户名、主手机、主邮箱、联系方式、公司名称 */
keyword?: string
}
export function fetchCustomers(body: CustomerListRequest) {
return request<PageResult<AdminCustomer>>("/api/dashboard/customer/list", {
method: "POST",
body: JSON.stringify(body),
})
}
export function fetchCustomer(id: number) {
return request<AdminCustomer | null>(`/api/dashboard/customer/${id}`)
}
export function createCustomer(payload: CreateAdminCustomerPayload) {
return request<AdminCustomer>("/api/dashboard/customer/create", {
method: "POST",
body: JSON.stringify(payload),
})
}
/** 单请求 + 单事务保存客户主信息与联系方式全量 */
export function saveCustomerProfile(payload: SaveCustomerProfilePayload) {
return request<AdminCustomer>("/api/dashboard/customer/save_profile", {
method: "POST",
body: JSON.stringify(payload),
})
}
export function updateCustomer(payload: UpdateAdminCustomerPayload) {
return request<void>("/api/dashboard/customer/update", {
method: "POST",
body: JSON.stringify(payload),
})
}
export function updateCustomerStatus(id: number, status: number) {
return request<void>("/api/dashboard/customer/update_status", {
method: "POST",
body: JSON.stringify({ id, status }),
})
}
export function deleteCustomer(id: number) {
return request<void>("/api/dashboard/customer/delete", {
method: "POST",
body: JSON.stringify({ id }),
})
}
-81
View File
@@ -1,81 +0,0 @@
import { request } from "@/lib/api/client"
export type DashboardRange = "today" | "7d" | "30d"
export type DashboardStatusDistributionItem = {
status: number
label: string
count: number
}
export type DashboardTrendItem = {
date: string
newCount: number
closedCount: number
}
export type DashboardTeamLoad = {
teamId: number
teamName: string
totalAgents: number
onlineAgents: number
busyAgents: number
offlineAgents: number
waitingConversations: number
processingConversations: number
maxConcurrentCapacity: number
loadRate: number
hasScheduleNow: boolean
}
export type DashboardAlert = {
id: string
level: "info" | "warning" | "error"
title: string
description: string
count: number
link: string
}
export type DashboardQuickLink = {
title: string
description: string
link: string
}
export type DashboardOverview = {
range: DashboardRange
generatedAt: string
summary: {
todayNewConversations: number
processingConversations: number
pendingDispatchConversations: number
onlineAgents: number
aiServiceRate: number
}
conversationStats: {
statusDistribution: DashboardStatusDistributionItem[]
trend: DashboardTrendItem[]
}
agentStats: {
onlineAgents: number
busyAgents: number
offlineAgents: number
teamLoads: DashboardTeamLoad[]
}
aiStats: {
enabledAiAgents: number
enabledChannels: number
todayKnowledgeRetrieves: number
todayKnowledgeRetrieveFailCount: number
todayKnowledgeRetrieveFailRate: number
todaySkillRunFailCount: number
todayAiHandoffCount: number
}
alerts: DashboardAlert[]
quickLinks: DashboardQuickLink[]
}
export function fetchDashboardOverview(range: DashboardRange) {
return request<DashboardOverview>(`/api/dashboard/dashboard/overview?range=${range}`)
}
-226
View File
@@ -1,226 +0,0 @@
import { request } from "@/lib/api/client"
import { generateUUID } from "@/lib/utils"
import { createWebSocketBaseUrl } from "@/lib/api/websocket"
export type Paging = {
page: number
limit: number
total: number
}
export type PageResult<T> = {
results: T[]
page: Paging
}
export type ImConversationTag = {
id: number
name: string
color: string
}
export type ImConversationParticipant = {
id: number
participantType: string
participantId: number
externalParticipantId?: string
joinedAt?: string
leftAt?: string
status: number
}
export type ImConversation = {
id: number
externalSource: string
externalId: string
subject: string
status: number
serviceMode: number
priority: number
currentAssigneeId: number
currentAssigneeName?: string
lastMessageId: number
lastMessageAt?: string
lastActiveAt?: string
lastMessageSummary?: string
customerUnreadCount: number
agentUnreadCount: number
customerLastReadMessageId: number
customerLastReadSeqNo: number
customerLastReadAt?: string
agentLastReadMessageId: number
agentLastReadSeqNo: number
agentLastReadAt?: string
closedAt?: string
tags?: ImConversationTag[]
participants?: ImConversationParticipant[]
}
export type ImConversationDetail = ImConversation
export type ImMessage = {
id: number
conversationId: number
clientMsgId?: string
senderType: string
senderId: number
senderName?: string
senderAvatar?: string
messageType: string
content: string
payload?: string
seqNo: number
sendStatus: number
sentAt?: string
deliveredAt?: string
readAt?: string
customerRead: boolean
customerReadAt?: string
agentRead: boolean
agentReadAt?: string
recalledAt?: string
quotedMessageId?: number
}
export type ImAsset = {
id: number
assetId: string
provider: string
filename: string
fileSize: number
mimeType: string
status: number
url: string
createdAt: string
updatedAt: string
createUserId: number
createUserName: string
updateUserId: number
updateUserName: string
}
const VISITOR_STORAGE_KEY = "cs_agent_im_visitor_id"
const API_BASE_URL =
process.env.NEXT_PUBLIC_API_BASE_URL?.trim() || ""
const OPEN_IM_CHANNEL_ID =
process.env.NEXT_PUBLIC_OPEN_IM_CHANNEL_ID?.trim() || ""
const OPEN_IM_EXTERNAL_SOURCE =
process.env.NEXT_PUBLIC_OPEN_IM_EXTERNAL_SOURCE?.trim() || "web_chat"
function buildVisitorId() {
return `visitor_${generateUUID()}`
}
export function getImVisitorId() {
if (typeof window === "undefined") {
return "visitor_ssr"
}
const existing = window.localStorage.getItem(VISITOR_STORAGE_KEY)?.trim()
if (existing) {
return existing
}
const visitorId = buildVisitorId()
window.localStorage.setItem(VISITOR_STORAGE_KEY, visitorId)
return visitorId
}
export function createImWebSocketUrl() {
const baseUrl = createWebSocketBaseUrl()
const params = new URLSearchParams({
externalId: getImVisitorId(),
externalSource: OPEN_IM_EXTERNAL_SOURCE,
channelId: OPEN_IM_CHANNEL_ID,
})
return `${baseUrl}/api/open/im/ws?${params.toString()}`
}
function createImHeaders() {
return {
"X-External-Source": OPEN_IM_EXTERNAL_SOURCE,
"X-External-Id": getImVisitorId(),
"X-Channel-Id": OPEN_IM_CHANNEL_ID,
}
}
function toQueryString(query?: Record<string, string | number | undefined>) {
if (!query) {
return ""
}
const params = new URLSearchParams()
Object.entries(query).forEach(([key, value]) => {
if (value === undefined || value === "") {
return
}
params.set(key, String(value))
})
const output = params.toString()
return output ? `?${output}` : ""
}
export function fetchImConversationDetail(id: number) {
return request<ImConversationDetail>(`/api/open/im/conversation/${id}`, {
headers: createImHeaders(),
})
}
export function fetchImMessages(
query?: Record<string, string | number | undefined>
) {
return request<PageResult<ImMessage>>(
`/api/open/im/message/list${toQueryString(query)}`,
{ headers: createImHeaders() }
)
}
/** 外部身份仅通过 createImHeaders()X-External-*)传递,无 JSON body */
export function createOrMatchImConversation() {
return request<ImConversation>("/api/open/im/conversation/create_or_match", {
method: "POST",
headers: createImHeaders(),
})
}
export function sendImMessage(payload: {
conversationId: number
messageType: string
content: string
payload?: string
clientMsgId?: string
}) {
return request<ImMessage>("/api/open/im/message/send", {
method: "POST",
headers: createImHeaders(),
body: JSON.stringify(payload),
})
}
export function markImMessageRead(conversationId: number, messageId = 0) {
return request<void>("/api/open/im/message/read", {
method: "POST",
headers: createImHeaders(),
body: JSON.stringify({ conversationId, messageId }),
})
}
export function uploadImImage(conversationId: number, file: File) {
const formData = new FormData()
formData.set("conversationId", String(conversationId))
formData.set("file", file)
return request<ImAsset>("/api/open/im/message/upload_image", {
method: "POST",
headers: createImHeaders(),
body: formData,
})
}
export function uploadImAttachment(conversationId: number, file: File) {
const formData = new FormData()
formData.set("conversationId", String(conversationId))
formData.set("file", file)
return request<ImAsset>("/api/open/im/message/upload_attachment", {
method: "POST",
headers: createImHeaders(),
body: formData,
})
}
-139
View File
@@ -1,139 +0,0 @@
import { request } from "@/lib/api/client"
export type Paging = {
page: number
limit: number
total: number
}
export type PageResult<T> = {
results: T[]
page: Paging
}
export type TicketResolutionCode = {
id: number
name: string
code: string
sortNo: number
status: number
remark: string
}
export type TicketPriorityConfig = {
id: number
name: string
sortNo: number
firstResponseMinutes: number
resolutionMinutes: number
status: number
remark: string
}
export type CreateTicketResolutionCodePayload = {
name: string
code: string
sortNo: number
status: number
remark: string
}
export type UpdateTicketResolutionCodePayload = CreateTicketResolutionCodePayload & {
id: number
}
export type CreateTicketPriorityConfigPayload = {
name: string
firstResponseMinutes: number
resolutionMinutes: number
status: number
remark: string
}
export type UpdateTicketPriorityConfigPayload = CreateTicketPriorityConfigPayload & {
id: number
}
function toQueryString(query?: Record<string, string | number | undefined>) {
if (!query) {
return ""
}
const params = new URLSearchParams()
Object.entries(query).forEach(([key, value]) => {
if (value === undefined || value === "") {
return
}
params.set(key, String(value))
})
const output = params.toString()
return output ? `?${output}` : ""
}
export function fetchTicketResolutionCodes(query?: Record<string, string | number | undefined>) {
return request<PageResult<TicketResolutionCode>>(
`/api/dashboard/ticket-resolution-code/list${toQueryString(query)}`
)
}
export function fetchTicketResolutionCodesAll() {
return request<TicketResolutionCode[]>("/api/dashboard/ticket-resolution-code/list_all")
}
export function createTicketResolutionCode(payload: CreateTicketResolutionCodePayload) {
return request<TicketResolutionCode>("/api/dashboard/ticket-resolution-code/create", {
method: "POST",
body: JSON.stringify(payload),
})
}
export function updateTicketResolutionCode(payload: UpdateTicketResolutionCodePayload) {
return request<void>("/api/dashboard/ticket-resolution-code/update", {
method: "POST",
body: JSON.stringify(payload),
})
}
export function deleteTicketResolutionCode(id: number) {
return request<void>("/api/dashboard/ticket-resolution-code/delete", {
method: "POST",
body: JSON.stringify({ id }),
})
}
export function fetchTicketPriorityConfigs(query?: Record<string, string | number | undefined>) {
return request<TicketPriorityConfig[]>(
`/api/dashboard/ticket-priority-config/list${toQueryString(query)}`
)
}
export function fetchTicketPriorityConfigsAll() {
return request<TicketPriorityConfig[]>("/api/dashboard/ticket-priority-config/list_all")
}
export function createTicketPriorityConfig(payload: CreateTicketPriorityConfigPayload) {
return request<TicketPriorityConfig>("/api/dashboard/ticket-priority-config/create", {
method: "POST",
body: JSON.stringify(payload),
})
}
export function updateTicketPriorityConfig(payload: UpdateTicketPriorityConfigPayload) {
return request<void>("/api/dashboard/ticket-priority-config/update", {
method: "POST",
body: JSON.stringify(payload),
})
}
export function updateTicketPriorityConfigSort(ids: number[]) {
return request<void>("/api/dashboard/ticket-priority-config/update_sort", {
method: "POST",
body: JSON.stringify(ids),
})
}
export function deleteTicketPriorityConfig(id: number) {
return request<void>("/api/dashboard/ticket-priority-config/delete", {
method: "POST",
body: JSON.stringify({ id }),
})
}
-487
View File
@@ -1,487 +0,0 @@
import { request } from "@/lib/api/client"
import type { Tag } from "@/lib/api/admin"
export type Paging = {
page: number
limit: number
total: number
}
export type PageResult<T> = {
results: T[]
page: Paging
}
export type TicketCustomer = {
id: number
name: string
companyId?: number
company?: {
id: number
name: string
code?: string
remark?: string
createdAt?: string
updatedAt?: string
}
primaryMobile?: string
primaryEmail?: string
}
export type TicketSLA = {
slaType: string
targetMinutes: number
status: string
startedAt?: string
pausedAt?: string
stoppedAt?: string
breachedAt?: string
elapsedMin: number
}
export type TicketComment = {
id: number
ticketId: number
commentType: string
authorType: string
authorId: number
authorName?: string
contentType: string
content: string
payload?: string
createdAt?: string
}
export type TicketEvent = {
id: number
ticketId: number
eventType: string
operatorType: string
operatorId: number
operatorName?: string
oldValue?: string
newValue?: string
content?: string
payload?: string
createdAt?: string
}
export type TicketItem = {
id: number
ticketNo: string
title: string
description: string
source: string
channel: string
customerId: number
conversationId: number
tags?: Tag[]
type: string
priority: number
priorityName?: string
severity: number
status: string
currentTeamId: number
currentTeamName?: string
currentAssigneeId: number
currentAssigneeName?: string
watchedByMe: boolean
pendingReason?: string
closeReason?: string
resolutionCode?: string
resolutionCodeName?: string
resolutionSummary?: string
firstResponseAt?: string
resolvedAt?: string
closedAt?: string
dueAt?: string
nextReplyDeadlineAt?: string
resolveDeadlineAt?: string
reopenedCount: number
createdAt?: string
updatedAt?: string
customer?: TicketCustomer
sla?: TicketSLA[]
}
export type TicketDetail = {
ticket: TicketItem
watchers?: Array<{
id: number
userId: number
userName?: string
}>
collaborators?: TicketCollaborator[]
comments?: TicketComment[]
events?: TicketEvent[]
relatedTickets?: TicketRelation[]
}
export type TicketCollaborator = {
id: number
userId: number
userName?: string
teamName?: string
}
export type TicketRelation = {
id: number
ticketId: number
relatedTicketId: number
relationType: string
relatedTicketNo?: string
relatedTicketTitle?: string
relatedTicketStatus?: string
currentTeamName?: string
currentAssigneeName?: string
updatedAt?: string
}
export type TicketSummary = {
all: number
mine: number
watching: number
collaboration: number
participating: number
mentioned: number
unassigned: number
pendingCustomer: number
pendingInternal: number
overdue: number
}
export type TicketRiskReason = {
code: string
title: string
description: string
count: number
}
export type TicketRiskOverview = {
overdue: number
highRisk: number
unassigned: number
pendingInternal: number
pendingCustomer: number
riskWindowMins: number
reasons?: TicketRiskReason[]
}
export type TicketSavedView = {
id: number
name: string
filters?: Record<string, unknown>
sortNo: number
}
export type TicketListQuery = {
page?: number
limit?: number
keyword?: string
status?: string
priority?: number
severity?: number
tagId?: number
currentTeamId?: number
currentAssigneeId?: number
customerId?: number
conversationId?: number
source?: string
watching?: number
collaboration?: number
collaborating?: number
mentioned?: number
mine?: number
unassigned?: number
overdue?: number
}
export type TicketRiskListQuery = {
riskType: "overdue" | "high_risk" | "unassigned" | "pending_internal" | "pending_customer"
currentTeamId?: number
riskWindowMins?: number
page?: number
limit?: number
}
export type CreateTicketPayload = {
title: string
description: string
source?: string
channel?: string
customerId?: number
conversationId?: number
tagIds?: number[]
type?: string
priority: number
severity: number
currentTeamId?: number
currentAssigneeId?: number
dueAt?: string
customFields?: Record<string, unknown>
}
export type CreateTicketFromConversationPayload = {
conversationId: number
title: string
description: string
tagIds?: number[]
priority: number
severity: number
currentTeamId?: number
currentAssigneeId?: number
syncToConversation: boolean
customFields?: Record<string, unknown>
}
export type UpdateTicketPayload = {
ticketId: number
title: string
description: string
tagIds?: number[]
type?: string
priority: number
severity: number
currentTeamId?: number
currentAssigneeId?: number
dueAt?: string
customFields?: Record<string, unknown>
}
function toQueryString(query?: Record<string, string | number | undefined>) {
if (!query) {
return ""
}
const params = new URLSearchParams()
Object.entries(query).forEach(([key, value]) => {
if (value === undefined || value === "") {
return
}
params.set(key, String(value))
})
const output = params.toString()
return output ? `?${output}` : ""
}
export function fetchTickets(query?: TicketListQuery) {
return request<PageResult<TicketItem>>(`/api/dashboard/ticket/list${toQueryString(query)}`)
}
export function fetchTicketDetail(id: number) {
return request<TicketDetail>(`/api/dashboard/ticket/${id}`)
}
export function fetchTicketSummary() {
return request<TicketSummary>("/api/dashboard/ticket/summary")
}
export function fetchTicketViews() {
return request<TicketSavedView[]>("/api/dashboard/ticket/view_list")
}
export function saveTicketView(payload: {
id?: number
name: string
filters?: Record<string, unknown>
}) {
return request<TicketSavedView>("/api/dashboard/ticket/save_view", {
method: "POST",
body: JSON.stringify(payload),
})
}
export function deleteTicketView(id: number) {
return request<void>("/api/dashboard/ticket/delete_view", {
method: "POST",
body: JSON.stringify({ id }),
})
}
export function fetchTicketRiskOverview(query?: {
currentTeamId?: number
riskWindowMins?: number
}) {
return request<TicketRiskOverview>(`/api/dashboard/ticket/risk_overview${toQueryString(query)}`)
}
export function fetchTicketRiskList(query: TicketRiskListQuery) {
return request<PageResult<TicketItem>>(`/api/dashboard/ticket/risk_list${toQueryString(query)}`)
}
export function createTicket(payload: CreateTicketPayload) {
return request<TicketItem>("/api/dashboard/ticket/create", {
method: "POST",
body: JSON.stringify(payload),
})
}
export function createTicketFromConversation(payload: CreateTicketFromConversationPayload) {
return request<TicketItem>("/api/dashboard/ticket/create_from_conversation", {
method: "POST",
body: JSON.stringify(payload),
})
}
export function updateTicket(payload: UpdateTicketPayload) {
return request<void>("/api/dashboard/ticket/update", {
method: "POST",
body: JSON.stringify(payload),
})
}
export function linkTicketToCustomer(payload: {
ticketId: number
customerId: number
}) {
return request<void>("/api/dashboard/ticket/link_customer", {
method: "POST",
body: JSON.stringify(payload),
})
}
export function assignTicket(payload: {
ticketId: number
toUserId: number
toTeamId?: number
reason?: string
}) {
return request<void>("/api/dashboard/ticket/assign", {
method: "POST",
body: JSON.stringify(payload),
})
}
export function batchAssignTickets(payload: {
ticketIds: number[]
toUserId: number
toTeamId?: number
reason?: string
}) {
return request<void>("/api/dashboard/ticket/batch_assign", {
method: "POST",
body: JSON.stringify(payload),
})
}
export function changeTicketStatus(payload: {
ticketId: number
status: string
pendingReason?: string
closeReason?: string
resolutionCode?: string
resolutionSummary?: string
reason?: string
}) {
return request<void>("/api/dashboard/ticket/change_status", {
method: "POST",
body: JSON.stringify(payload),
})
}
export function batchChangeTicketStatus(payload: {
ticketIds: number[]
status: string
pendingReason?: string
closeReason?: string
resolutionCode?: string
resolutionSummary?: string
reason?: string
}) {
return request<void>("/api/dashboard/ticket/batch_change_status", {
method: "POST",
body: JSON.stringify(payload),
})
}
export function replyTicket(payload: {
ticketId: number
contentType?: string
content: string
payload?: string
}) {
return request<TicketComment>("/api/dashboard/ticket/reply", {
method: "POST",
body: JSON.stringify(payload),
})
}
export function addTicketInternalNote(payload: {
ticketId: number
contentType?: string
content: string
payload?: string
}) {
return request<TicketComment>("/api/dashboard/ticket/internal_note", {
method: "POST",
body: JSON.stringify(payload),
})
}
export function closeTicket(payload: { ticketId: number; closeReason: string }) {
return request<void>("/api/dashboard/ticket/close", {
method: "POST",
body: JSON.stringify(payload),
})
}
export function reopenTicket(payload: { ticketId: number; reason: string }) {
return request<void>("/api/dashboard/ticket/reopen", {
method: "POST",
body: JSON.stringify(payload),
})
}
export function watchTicket(ticketId: number) {
return request<void>("/api/dashboard/ticket/watch", {
method: "POST",
body: JSON.stringify({ ticketId }),
})
}
export function unwatchTicket(ticketId: number) {
return request<void>("/api/dashboard/ticket/unwatch", {
method: "POST",
body: JSON.stringify({ ticketId }),
})
}
export function batchWatchTickets(payload: { ticketIds: number[]; watched: boolean }) {
return request<void>("/api/dashboard/ticket/batch_watch", {
method: "POST",
body: JSON.stringify(payload),
})
}
export function addTicketRelation(payload: {
ticketId: number
relatedTicketId?: number
relatedTicketNo?: string
relationType: string
}) {
return request<void>("/api/dashboard/ticket/add_relation", {
method: "POST",
body: JSON.stringify(payload),
})
}
export function deleteTicketRelation(payload: { ticketId: number; relationId: number }) {
return request<void>("/api/dashboard/ticket/delete_relation", {
method: "POST",
body: JSON.stringify(payload),
})
}
export function addTicketCollaborator(payload: { ticketId: number; userId: number }) {
return request<void>("/api/dashboard/ticket/add_collaborator", {
method: "POST",
body: JSON.stringify(payload),
})
}
export function deleteTicketCollaborator(payload: { ticketId: number; collaboratorId: number }) {
return request<void>("/api/dashboard/ticket/delete_collaborator", {
method: "POST",
body: JSON.stringify(payload),
})
}
-6
View File
@@ -1,6 +0,0 @@
const API_BASE_URL =
process.env.NEXT_PUBLIC_API_BASE_URL?.trim() || "http://127.0.0.1:8083"
export function createWebSocketBaseUrl() {
return API_BASE_URL.replace(/^http/, "ws").replace(/\/$/, "")
}