"use client" import * as React from "react" import Image from "next/image" import { useRouter } from "next/navigation" import { ArrowRightLeftIcon, BanknoteIcon, CreditCardIcon, CopyIcon, EyeIcon, EyeOffIcon, Loader2Icon, MoreHorizontalIcon, PencilIcon, PowerIcon, PowerOffIcon, PlusIcon, SearchIcon, Trash2Icon, WalletCardsIcon, } from "lucide-react" import { type ChannelWithAccountNames, deleteChannelAction, toggleChannelActiveAction, } from "@/lib/actions/channel" import { type AccountWithChannels } from "@/lib/actions/account" import { Badge } from "@/components/ui/badge" import { Button } from "@/components/ui/button" import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, } from "@/components/ui/dialog" import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuSeparator, DropdownMenuTrigger, } from "@/components/ui/dropdown-menu" import { Input } from "@/components/ui/input" import { Select, SelectContent, SelectGroup, SelectItem, SelectTrigger, SelectValue, } from "@/components/ui/select" import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, } from "@/components/ui/tooltip" import { ChannelDialog } from "./channel-dialog" import { CARD_BRAND_LABELS, getCardBrandLogoUrl, normalizeCardBrand, } from "@/lib/payment/card-brand" interface ChannelsViewProps { initialChannels: ChannelWithAccountNames[] initialAccounts: AccountWithChannels[] } const channelTypeLabels = { PAYMENT_CARD: "支付卡", E_WALLET: "电子钱包", CASH: "现金", TRANSFER: "转账", } as const const channelIcon = (type: ChannelWithAccountNames["channelType"]) => type === "PAYMENT_CARD" ? ( ) : type === "E_WALLET" ? ( ) : type === "CASH" ? ( ) : ( ) function paymentCardIcon(channel: ChannelWithAccountNames) { const brand = normalizeCardBrand(channel.cardBrand) const logoUrl = getCardBrandLogoUrl(channel.cardBrand) return logoUrl && brand ? ( {CARD_BRAND_LABELS[brand]} ) : ( channelIcon(channel.channelType) ) } function channelName(channel: ChannelWithAccountNames) { if (channel.channelType === "PAYMENT_CARD") return channel.issuerName || "支付卡" if (channel.channelType === "E_WALLET") return channel.platform || "电子钱包" return channel.channelType === "CASH" ? "现金渠道" : "转账渠道" } function formatCardNumber(cardNumber: string) { return cardNumber .replace(/\D/g, "") .replace(/(.{4})/g, "$1 ") .trim() } export function ChannelsView({ initialChannels, initialAccounts, }: ChannelsViewProps) { const router = useRouter() const [channelsList, setChannelsList] = React.useState(initialChannels) const [searchQuery, setSearchQuery] = React.useState("") const [typeFilter, setTypeFilter] = React.useState("ALL") const [dialogOpen, setDialogOpen] = React.useState(false) const [editingChannel, setEditingChannel] = React.useState(null) const [deletingChannel, setDeletingChannel] = React.useState(null) const [isDeleting, setIsDeleting] = React.useState(false) const [togglingId, setTogglingId] = React.useState(null) const [visibleCardIds, setVisibleCardIds] = React.useState>( () => new Set() ) const [copyStatus, setCopyStatus] = React.useState<{ channelId: string message: string } | null>(null) const filteredChannels = React.useMemo(() => { const query = searchQuery.trim().toLowerCase() return channelsList.filter((item) => { const searchable = [ channelName(item), item.issuerName, item.cardBrand, item.cardNumberSuffix, item.platform, item.platformAccountId, item.region, item.subChannel, item.desc, ...(item.linkedAccounts || []).map((account) => account.name), ] .filter(Boolean) .join(" ") .toLowerCase() return ( (!query || searchable.includes(query)) && (typeFilter === "ALL" || item.channelType === typeFilter) ) }) }, [channelsList, searchQuery, typeFilter]) const openCreate = () => { setEditingChannel(null) setDialogOpen(true) } const confirmDelete = async () => { if (!deletingChannel) return setIsDeleting(true) try { const result = await deleteChannelAction(deletingChannel.id) if (result.success) { setChannelsList((items) => items.filter((item) => item.id !== deletingChannel.id) ) setDeletingChannel(null) router.refresh() } } finally { setIsDeleting(false) } } const toggleActive = async ( channel: ChannelWithAccountNames, isActive: boolean ) => { setTogglingId(channel.id) setChannelsList((items) => items.map((item) => item.id === channel.id ? { ...item, isActive } : item ) ) try { const result = await toggleChannelActiveAction(channel.id, isActive) if (!result.success) setChannelsList((items) => items.map((item) => item.id === channel.id ? { ...item, isActive: !isActive } : item ) ) else router.refresh() } catch { setChannelsList((items) => items.map((item) => item.id === channel.id ? { ...item, isActive: !isActive } : item ) ) } finally { setTogglingId(null) } } const hasFilters = Boolean(searchQuery) || typeFilter !== "ALL" const clearFilters = () => { setSearchQuery("") setTypeFilter("ALL") } const toggleCardVisibility = (channelId: string) => { setVisibleCardIds((ids) => { const nextIds = new Set(ids) if (nextIds.has(channelId)) nextIds.delete(channelId) else nextIds.add(channelId) return nextIds }) } const copyCardNumber = async (channel: ChannelWithAccountNames) => { if (!channel.cardNumberFull) return try { await navigator.clipboard.writeText(channel.cardNumberFull) setCopyStatus({ channelId: channel.id, message: "已复制" }) } catch { setCopyStatus({ channelId: channel.id, message: "复制失败" }) } } return (

支付渠道

集中查看交易工具与归属资金账户。

setSearchQuery(event.target.value)} placeholder="搜索渠道、平台、尾号或账户" className="pl-8" />
{hasFilters && ( )}
{filteredChannels.length > 0 ? (
渠道名称 标识 类型 归属账户
{filteredChannels.map((channel) => { const actionMenu = ( } /> } > 更多操作 toggleActive(channel, !channel.isActive)} > {channel.isActive ? ( ) : ( )} {channel.isActive ? "停用渠道" : "启用渠道"} { setEditingChannel(channel) setDialogOpen(true) }} > 编辑渠道 setDeletingChannel(channel)} > 删除渠道 ) return (
{channel.channelType === "PAYMENT_CARD" ? paymentCardIcon(channel) : channelIcon(channel.channelType)}

{channel.desc || channel.platformAccountId || channel.subChannel || ""}

{!channel.isActive && ( 已停用 )}

{channelName(channel)}

{actionMenu}
标识 {channel.channelType === "PAYMENT_CARD" && (channel.cardNumberSuffix || channel.cardNumberFull) ? ( {visibleCardIds.has(channel.id) && channel.cardNumberFull ? formatCardNumber(channel.cardNumberFull) : `•••• ${ channel.cardNumberSuffix || formatCardNumber( channel.cardNumberFull || "" ).slice(-4) }`} {channel.cardNumberFull && ( <> toggleCardVisibility(channel.id) } /> } > {visibleCardIds.has(channel.id) ? ( ) : ( )} {visibleCardIds.has(channel.id) ? "隐藏完整卡号" : "显示完整卡号"} copyCardNumber(channel)} /> } > 复制卡号 {copyStatus?.channelId === channel.id && ( {copyStatus.message} )} )} ) : channel.channelType === "E_WALLET" ? ( channel.platformAccountId || "" ) : ( "" )}
类型 {channelTypeLabels[channel.channelType]}
归属账户 {channel.linkedAccounts?.length ? ( channel.linkedAccounts .map((account) => account.name) .join("、") ) : ( 未关联 )}
{actionMenu}
) })}
) : (

{hasFilters ? "没有符合条件的渠道" : "还没有支付渠道"}

{hasFilters ? "调整关键词或筛选条件后再试。" : "添加渠道后即可在交易中选择对应的支付工具。"}

{hasFilters && ( )}
)} router.refresh()} /> !open && setDeletingChannel(null)} > 确认删除渠道 将删除「{deletingChannel ? channelName(deletingChannel) : ""} 」。历史流水仍会保留,之后不能再选择此渠道。
) }