"use client" import { useCallback, useEffect, useState } from "react" import { Building2Icon, MessagesSquareIcon, MessageSquareMoreIcon, MoreHorizontalIcon, PlusIcon, RefreshCwIcon, SearchIcon, Trash2Icon, } from "lucide-react" import { toast } from "sonner" import { createChannel, deleteChannel, fetchChannels, updateChannel, updateChannelStatus, type AdminChannel, type CreateAdminChannelPayload, type PageResult, } from "@/lib/api/admin" import { OptionCombobox } from "@/components/option-combobox" import { EditDialog } from "./_components/edit" import { Badge } from "@/components/ui/badge" import { Button } from "@/components/ui/button" import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger, } from "@/components/ui/dropdown-menu" import { Input } from "@/components/ui/input" import { ListPagination } from "@/components/list-pagination" import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from "@/components/ui/table" import { Status, StatusLabels } from "@/lib/generated/enums" import { getEnumLabel, getEnumOptions } from "@/lib/enums" import { ButtonGroup } from "@/components/ui/button-group" import { Switch } from "@/components/ui/switch" const statusOptions = [ { value: "all", label: "全部状态" }, ...getEnumOptions(StatusLabels).map((option) => ({ value: String(option.value), label: option.label, })), ] as const const channelTypeOptions = [ { value: "all", label: "全部类型" }, { value: "web", label: "Web 站点" }, { value: "wechat_mp", label: "微信公众号" }, { value: "wxwork_kf", label: "企业微信客服" }, ] as const function getChannelTypeLabel(channelType: string) { if (channelType === "wechat_mp") { return "微信公众号" } if (channelType === "wxwork_kf") { return "企业微信客服" } return "Web 站点" } function ChannelIcon({ channelType }: { channelType: string }) { if (channelType === "wechat_mp") { return } if (channelType === "wxwork_kf") { return } return } export default function DashboardChannelsPage() { const [nameInput, setNameInput] = useState("") const [channelIdInput, setChannelIdInput] = useState("") const [channelTypeInput, setChannelTypeInput] = useState("all") const [statusInput, setStatusInput] = useState("all") const [name, setName] = useState("") const [channelId, setChannelId] = useState("") const [channelType, setChannelType] = useState("all") const [status, setStatus] = useState("all") const [page, setPage] = useState(1) const [limit, setLimit] = useState(20) const [loading, setLoading] = useState(true) const [saving, setSaving] = useState(false) const [actionLoadingId, setActionLoadingId] = useState(null) const [dialogOpen, setDialogOpen] = useState(false) const [editingItem, setEditingItem] = useState(null) const [result, setResult] = useState>({ results: [], page: { page: 1, limit: 20, total: 0 }, }) const loadData = useCallback(async () => { setLoading(true) try { const data = await fetchChannels({ name: name.trim() || undefined, channelId: channelId.trim() || undefined, channelType: channelType === "all" ? undefined : channelType, status: status === "all" ? undefined : status, page, limit, }) setResult(data) } catch (error) { toast.error(error instanceof Error ? error.message : "加载接入渠道失败") } finally { setLoading(false) } }, [channelId, channelType, limit, name, page, status]) useEffect(() => { void loadData() }, [loadData]) function applyFilters() { setName(nameInput) setChannelId(channelIdInput) setChannelType(channelTypeInput) setStatus(statusInput) setPage(1) } function handleFilterKeyDown(event: React.KeyboardEvent) { if (event.key !== "Enter") { return } event.preventDefault() applyFilters() } function openCreateDialog() { setEditingItem(null) setDialogOpen(true) } function openEditDialog(item: AdminChannel) { setEditingItem(item) setDialogOpen(true) } async function handleSubmit(payload: CreateAdminChannelPayload) { if (saving) { return } setSaving(true) try { if (editingItem) { await updateChannel({ id: editingItem.id, ...payload }) toast.success(`已更新接入渠道:${payload.name}`) } else { const created = await createChannel(payload) toast.success(`已创建接入渠道:${created.name}`) } setDialogOpen(false) setEditingItem(null) await loadData() } catch (error) { toast.error(error instanceof Error ? error.message : "保存接入渠道失败") } finally { setSaving(false) } } async function handleToggleStatus(item: AdminChannel) { setActionLoadingId(item.id) try { const nextStatus = item.status === Status.Ok ? Status.Disabled : Status.Ok await updateChannelStatus(item.id, nextStatus) toast.success(`已${nextStatus === Status.Ok ? "启用" : "禁用"}:${item.name}`) await loadData() } catch (error) { toast.error(error instanceof Error ? error.message : "更新渠道状态失败") } finally { setActionLoadingId(null) } } async function handleDelete(item: AdminChannel) { setActionLoadingId(item.id) try { await deleteChannel(item.id) toast.success(`已删除接入渠道:${item.name}`) await loadData() } catch (error) { toast.error(error instanceof Error ? error.message : "删除接入渠道失败") } finally { setActionLoadingId(null) } } return ( <> setNameInput(event.target.value)} onKeyDown={handleFilterKeyDown} placeholder="按渠道名称筛选" className="w-full xl:w-56" /> setChannelIdInput(event.target.value)} onKeyDown={handleFilterKeyDown} placeholder="按 channelId 筛选" className="pl-9" /> 查询 void loadData()} disabled={loading}> 刷新列表 新建渠道 渠道 类型 ChannelID 接待 Agent 状态 操作 {result.results.length === 0 ? ( {loading ? "正在加载接入渠道..." : "暂无接入渠道"} ) : null} {result.results.map((item) => ( {item.name} {getChannelTypeLabel(item.channelType)} {getChannelTypeLabel(item.channelType)} {item.channelId || "-"} {item.aiAgentName || "-"} void handleToggleStatus(item)} aria-label={`${item.name} 状态切换`} /> {getEnumLabel(StatusLabels, item.status as Status)} openEditDialog(item)}> 编辑 } aria-label={`更多操作 ${item.name}`} > void handleDelete(item)} > 删除 ))} setPage(nextPage)} onLimitChange={(nextLimit) => { setLimit(nextLimit) setPage(1) }} /> > ) }