"use client"; import { Eye, Pencil, Plus, ReceiptText, Trash2 } from "lucide-react"; import { useCallback, useEffect, useMemo, useRef, useState } from "react"; import { useTranslation } from "react-i18next"; import { toast } from "sonner"; import { getAgentNodeProfile } from "@/api/admin-agents"; import { getSettlementBills, postSettlementBillBadDebtWriteOff, postSettlementBillConfirm, postSettlementBillPayment, type SettlementBillRow, } from "@/api/admin-agent-settlement"; import { deleteAdminPlayer, getAdminPlayer, getAdminPlayers, postAdminPlayer, putAdminPlayer, } from "@/api/admin-player"; import { formatCredit } from "@/modules/agents/agent-line-sidebar"; import { AdminRowActionsMenu } from "@/components/admin/admin-row-actions-menu"; import { AdminTableNoResourceRow } from "@/components/admin/admin-no-resource-state"; import { AdminListPaginationFooter } from "@/components/admin/admin-list-pagination-footer"; import { AdminLoadingState } from "@/components/admin/admin-loading-state"; import { AdminStatusBadge } from "@/components/admin/admin-status-badge"; import { Button } from "@/components/ui/button"; import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle, } from "@/components/ui/dialog"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@/components/ui/select"; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from "@/components/ui/table"; import { useAsyncEffect } from "@/hooks/use-async-effect"; import { useConfirmAction } from "@/hooks/use-confirm-action"; import { useAdminDateTimeFormatter } from "@/hooks/use-admin-datetime-formatter"; import { PlayerFundingModeBadge } from "@/components/admin/player-funding-badges"; import { formatPlayerCreditAmount, playerBalanceCells } from "@/lib/admin-player-display"; import { formatAdminMinorDecimal, formatAdminMinorUnits, parseAdminMajorToMinor } from "@/lib/money"; import { parsePercentUi, percentValueToUi } from "@/lib/admin-rate-percent"; import { adminPlayerDetailPath } from "@/lib/admin-player-paths"; import { adminHasAnyPermission } from "@/lib/admin-permissions"; import { PRD_USERS_MANAGE } from "@/lib/admin-prd"; import { resolveRoleStatusTone } from "@/lib/admin-status-tone"; import { useAdminProfile } from "@/stores/admin-session"; import { LotteryApiBizError } from "@/types/api/errors"; import type { AdminPlayerRow } from "@/types/api/admin-player"; const PLAYER_STATUS_OPTIONS = [ { value: 0, labelKey: "players:statusNormal" as const }, { value: 1, labelKey: "players:statusFrozen" as const }, { value: 2, labelKey: "players:statusBanned" as const }, ]; function playerStatusLabel( status: number, t: (key: string, opts?: { defaultValue?: string }) => string, ): string { const hit = PLAYER_STATUS_OPTIONS.find((opt) => opt.value === status); if (hit) { return t(hit.labelKey, { defaultValue: status === 0 ? "正常" : status === 1 ? "冻结" : "封禁", }); } return String(status); } function creditAdjustModeLabel( mode: "increase" | "decrease", t: (key: string, opts?: { defaultValue?: string }) => string, ): string { return mode === "increase" ? t("playersPanel.creditIncrease", { defaultValue: "增加授信" }) : t("playersPanel.creditDecrease", { defaultValue: "减少授信" }); } function resolvePlayerRebateRate(row: AdminPlayerRow): number | null { if (row.rebate_rate != null) { return row.rebate_rate; } const defaultProfile = row.rebate_profiles?.find((p) => p.game_type === "*"); if (defaultProfile && !defaultProfile.inherit_from_agent) { return defaultProfile.rebate_rate; } return null; } function parseRiskTagsInput(text: string): string[] { return Array.from( new Set( text .split(/[,,\s]+/) .map((tag) => tag.trim()) .filter((tag) => tag.length > 0), ), ); } function fillEditFormFromPlayer(row: AdminPlayerRow): { username: string; nickname: string; currency: string; status: number; creditLimit: number; rebateRate: string; riskTags: string; } { const rebate = resolvePlayerRebateRate(row); return { username: row.username ?? "", nickname: row.nickname ?? "", currency: row.default_currency ?? "", status: row.status, creditLimit: row.credit_limit ?? 0, rebateRate: rebate != null ? percentValueToUi(rebate) : "", riskTags: (row.risk_tags ?? []).join(", "), }; } type AgentsPlayersPanelProps = { siteCode: string; /** 筛选直属玩家时的代理节点;null 表示当前登录代理或不过滤 */ agentNodeId: number | null; /** 当前代理 profile 是否允许创建玩家;未传时沿用登录代理能力 */ allowCreatePlayer?: boolean; /** 嵌入代理线路详情 Tab 时使用紧凑顶栏 */ embedded?: boolean; /** 外部触发创建直属玩家的计数器 */ createRequestKey?: number; }; export function AgentsPlayersPanel({ siteCode, agentNodeId, allowCreatePlayer, embedded = false, createRequestKey = 0, }: AgentsPlayersPanelProps): React.ReactElement { const { t } = useTranslation(["agents", "players", "common"]); const formatDt = useAdminDateTimeFormatter(); const createPlayerLabel = embedded ? t("playersPanel.createDirect", { defaultValue: "创建直属玩家" }) : t("playersPanel.create", { defaultValue: "创建玩家" }); const viewPlayerLabel = t("players:viewDetail", { defaultValue: "查看玩家详情" }); const editPlayerLabel = t("players:editPlayer", { defaultValue: "编辑玩家" }); const deletePlayerLabel = t("players:deletePlayer", { defaultValue: "删除玩家" }); const profile = useAdminProfile(); const boundAgent = profile?.agent ?? null; const isSuperAdmin = profile?.is_super_admin === true; const { request: requestConfirm, ConfirmDialog, busy: confirmBusy } = useConfirmAction(); const profileAllowsCreate = allowCreatePlayer === undefined ? boundAgent?.can_create_player !== false : allowCreatePlayer === true; const canCreatePlayer = isSuperAdmin || (profileAllowsCreate && adminHasAnyPermission(profile?.permissions, [PRD_USERS_MANAGE])); const canManagePlayerRows = canCreatePlayer; const effectiveAgentId = useMemo(() => { if (agentNodeId !== null) { return agentNodeId; } return boundAgent?.id ?? null; }, [agentNodeId, boundAgent?.id]); const [page, setPage] = useState(1); const [perPage, setPerPage] = useState(20); const [items, setItems] = useState>["items"]>([]); const [total, setTotal] = useState(0); const [lastPage, setLastPage] = useState(1); const [loading, setLoading] = useState(true); const [dialogOpen, setDialogOpen] = useState(false); const [saving, setSaving] = useState(false); const [username, setUsername] = useState(""); const [password, setPassword] = useState(""); const [nickname, setNickname] = useState(""); const [creditLimit, setCreditLimit] = useState(""); const [rebateRate, setRebateRate] = useState(""); const [parentAvailableCredit, setParentAvailableCredit] = useState(null); const [editDialogOpen, setEditDialogOpen] = useState(false); const [editSaving, setEditSaving] = useState(false); const [editingPlayer, setEditingPlayer] = useState>["items"][number] | null>(null); const [editUsername, setEditUsername] = useState(""); const [editNickname, setEditNickname] = useState(""); const [editDefaultCurrency, setEditDefaultCurrency] = useState(""); const [editStatus, setEditStatus] = useState(0); const [editCreditBase, setEditCreditBase] = useState(0); const [editCreditAdjustMode, setEditCreditAdjustMode] = useState<"increase" | "decrease">("increase"); const [editCreditDelta, setEditCreditDelta] = useState(""); const [editRebateRate, setEditRebateRate] = useState(""); const [editRiskTags, setEditRiskTags] = useState(""); const [editDetailLoading, setEditDetailLoading] = useState(false); const [billingDialogOpen, setBillingDialogOpen] = useState(false); const [billingPlayer, setBillingPlayer] = useState(null); const [billingBills, setBillingBills] = useState([]); const [billingLoading, setBillingLoading] = useState(false); const [billingBusy, setBillingBusy] = useState(false); const [selectedBillId, setSelectedBillId] = useState(null); const [payAmount, setPayAmount] = useState(""); const [payMethod, setPayMethod] = useState(""); const [payProof, setPayProof] = useState(""); const [badDebtReason, setBadDebtReason] = useState(""); const lastCreateRequestKeyRef = useRef(createRequestKey); const load = useCallback(async () => { if (siteCode.trim() === "") { setItems([]); setTotal(0); setLastPage(1); setLoading(false); return; } setLoading(true); try { const data = await getAdminPlayers({ page, per_page: perPage, site_code: siteCode.trim(), ...(effectiveAgentId !== null ? { agent_node_id: effectiveAgentId } : {}), }); setItems(data.items); setTotal(data.meta.total); setLastPage(Math.max(1, data.meta.last_page)); } catch { setItems([]); setTotal(0); setLastPage(1); } finally { setLoading(false); } }, [effectiveAgentId, page, perPage, siteCode]); useAsyncEffect(() => { void load(); }, [load]); async function savePlayer(): Promise { if (siteCode.trim() === "") { toast.error(t("players:siteCodeRequired", { defaultValue: "请填写主站编号" })); return; } if (username.trim() === "" || password.trim() === "") { toast.error(t("playersPanel.loginRequired", { defaultValue: "请填写登录账号与初始密码" })); return; } if (password.trim().length < 8) { toast.error( t("playersPanel.passwordMinLength", { defaultValue: "初始密码至少 8 位" }), ); return; } const parsedCreditLimit = creditLimit.trim() === "" ? 0 : Number.parseInt(creditLimit, 10); if ( Number.isNaN(parsedCreditLimit) || parsedCreditLimit < 0 || !Number.isInteger(parsedCreditLimit) ) { toast.error( t("playersPanel.creditLimitInvalid", { defaultValue: "授信额度必须为不小于 0 的整数" }), ); return; } if ( parentAvailableCredit !== null && parsedCreditLimit > parentAvailableCredit ) { toast.error( t("playersPanel.creditLimitExceeded", { defaultValue: "授信额度不能超过当前代理可下发额度", }), ); return; } const parsedRebateRate = rebateRate.trim() === "" ? null : parsePercentUi(rebateRate); if (rebateRate.trim() !== "" && (parsedRebateRate === null || parsedRebateRate < 0 || parsedRebateRate > 100)) { toast.error( t("playersPanel.rebateRateInvalid", { defaultValue: "回水比例须在 0–100% 之间", }), ); return; } setSaving(true); try { await postAdminPlayer({ site_code: siteCode.trim(), username: username.trim(), password: password, nickname: nickname.trim() || null, ...(isSuperAdmin && effectiveAgentId ? { agent_node_id: effectiveAgentId } : {}), credit_limit: parsedCreditLimit, ...(parsedRebateRate !== null ? { rebate_rate: parsedRebateRate } : {}), }); toast.success( t("playersPanel.createSuccessNative", { name: username.trim(), defaultValue: "玩家 {{name}} 已创建,请使用彩票端登录", }), ); setDialogOpen(false); setUsername(""); setPassword(""); setNickname(""); setCreditLimit(""); setRebateRate(""); await load(); } catch (e) { toast.error(e instanceof LotteryApiBizError ? e.message : t("players:createFailed")); } finally { setSaving(false); } } function openCreateDialog(): void { setDialogOpen(true); setUsername(""); setPassword(""); setNickname(""); setCreditLimit(""); setRebateRate(""); if (effectiveAgentId !== null) { void getAgentNodeProfile(effectiveAgentId) .then((p) => setParentAvailableCredit(p.available_credit ?? null)) .catch(() => setParentAvailableCredit(null)); } else { setParentAvailableCredit(null); } } useEffect(() => { if (createRequestKey === 0 || createRequestKey === lastCreateRequestKeyRef.current) { return; } lastCreateRequestKeyRef.current = createRequestKey; if (canCreatePlayer) { openCreateDialog(); } }, [canCreatePlayer, createRequestKey]); const applyEditForm = (row: AdminPlayerRow): void => { const form = fillEditFormFromPlayer(row); setEditUsername(form.username); setEditNickname(form.nickname); setEditDefaultCurrency(form.currency); setEditStatus(form.status); setEditCreditBase(form.creditLimit); setEditCreditAdjustMode("increase"); setEditCreditDelta(""); setEditRebateRate(form.rebateRate); setEditRiskTags(form.riskTags); }; const openEditPlayer = (row: AdminPlayerRow): void => { setEditingPlayer(row); applyEditForm(row); setEditDialogOpen(true); setEditDetailLoading(true); void getAdminPlayer(row.id) .then((full) => { setEditingPlayer(full); applyEditForm(full); }) .catch(() => { toast.error(t("players:loadFailed", { defaultValue: "加载玩家详情失败" })); }) .finally(() => { setEditDetailLoading(false); }); }; function handleEditDialogOpenChange(open: boolean): void { setEditDialogOpen(open); if (!open) { setEditingPlayer(null); } } async function saveEditedPlayer(): Promise { if (!editingPlayer) { return; } const body: Parameters[1] = {}; if (editUsername.trim() !== "" && editUsername.trim() !== (editingPlayer.username ?? "")) { body.username = editUsername.trim(); } if (editNickname.trim() !== (editingPlayer.nickname ?? "")) { body.nickname = editNickname.trim() || null; } const nextCurrency = editDefaultCurrency.trim().toUpperCase(); if (nextCurrency !== editingPlayer.default_currency) { body.default_currency = nextCurrency; } if (editStatus !== editingPlayer.status) { body.status = editStatus; } const creditDelta = editCreditDelta.trim() === "" ? 0 : Number.parseInt(editCreditDelta, 10); if (!Number.isNaN(creditDelta) && creditDelta > 0) { const signedDelta = editCreditAdjustMode === "increase" ? creditDelta : -creditDelta; const nextCredit = Math.max(0, (editingPlayer.credit_limit ?? 0) + signedDelta); if (nextCredit !== (editingPlayer.credit_limit ?? 0)) { body.credit_limit = nextCredit; } } const prevRebate = resolvePlayerRebateRate(editingPlayer); const nextPercent = parsePercentUi(editRebateRate); const nextRebate = nextPercent === null ? null : nextPercent; if (nextRebate !== null && nextRebate !== (prevRebate ?? 0)) { body.rebate_rate = nextRebate; } const nextRiskTags = parseRiskTagsInput(editRiskTags); const prevRiskTags = editingPlayer.risk_tags ?? []; if (JSON.stringify(nextRiskTags) !== JSON.stringify(prevRiskTags)) { body.risk_tags = nextRiskTags; } if (Object.keys(body).length === 0) { toast.success(t("players:noChanges", { defaultValue: "没有变更" })); handleEditDialogOpenChange(false); return; } setEditSaving(true); try { const updated = await putAdminPlayer(editingPlayer.id, body); setItems((prev) => prev.map((row) => (row.id === updated.id ? updated : row))); toast.success( t("players:updateSuccess", { name: updated.username ?? updated.site_player_id, defaultValue: "已更新 {{name}}", }), ); handleEditDialogOpenChange(false); } catch (e) { toast.error( e instanceof LotteryApiBizError ? e.message : t("players:updateFailed", { defaultValue: "更新玩家失败" }), ); } finally { setEditSaving(false); } } async function confirmDeletePlayer(row: Awaited>["items"][number]): Promise { try { await deleteAdminPlayer(row.id); setItems((prev) => prev.filter((item) => item.id !== row.id)); setTotal((current) => Math.max(0, current - 1)); toast.success(t("deleteSuccess", { name: row.username ?? row.site_player_id })); } catch (e) { if (e instanceof LotteryApiBizError) { const needsSettlement = e.message.includes("已占用信用额度"); toast.error( needsSettlement ? t("playersPanel.deleteBlockedByCreditHint", { defaultValue: "该玩家仍有已占用信用额度,请先到结算中心结清或核销相关账单后再删除。", }) : e.message, ); return; } toast.error(t("deleteFailed")); } } const selectedBill = useMemo( () => billingBills.find((bill) => bill.id === selectedBillId) ?? null, [billingBills, selectedBillId], ); const billingCurrency = selectedBill?.currency_code ?? billingPlayer?.default_currency ?? "NPR"; const projectedCreditLimit = useMemo(() => { const delta = editCreditDelta.trim() === "" ? 0 : Number.parseInt(editCreditDelta, 10); if (Number.isNaN(delta) || delta <= 0) { return editCreditBase; } return Math.max(0, editCreditBase + (editCreditAdjustMode === "increase" ? delta : -delta)); }, [editCreditAdjustMode, editCreditBase, editCreditDelta]); function resetBillingForm(): void { setPayAmount(""); setPayMethod(""); setPayProof(""); setBadDebtReason(""); } async function openBillingDialog(row: AdminPlayerRow): Promise { setBillingDialogOpen(true); setBillingPlayer(row); setBillingBills([]); setSelectedBillId(null); resetBillingForm(); setBillingLoading(true); try { const data = await getSettlementBills({ bill_type: "player", keyword: row.site_player_id, per_page: 20, }); const items = (data.items ?? []).filter( (bill) => bill.bill_type === "player" && bill.owner_id === row.id && (bill.status === "pending_confirm" || Number(bill.unpaid_amount ?? 0) > 0), ); setBillingBills(items); const first = items[0] ?? null; setSelectedBillId(first?.id ?? null); setPayAmount( first ? formatAdminMinorDecimal(first.unpaid_amount ?? 0, row.default_currency ?? "NPR") : "", ); } catch (e) { toast.error( e instanceof LotteryApiBizError ? e.message : t("playersPanel.billingLoadFailed", { defaultValue: "加载账单失败" }), ); } finally { setBillingLoading(false); } } async function handleConfirmBill(): Promise { if (selectedBill === null) return; setBillingBusy(true); try { await postSettlementBillConfirm(selectedBill.id); toast.success( t("playersPanel.billConfirmed", { defaultValue: "账单已确认,请继续登记收付或核销" }), ); if (billingPlayer) { await openBillingDialog(billingPlayer); } } catch (e) { toast.error( e instanceof LotteryApiBizError ? e.message : t("playersPanel.billConfirmFailed", { defaultValue: "确认账单失败" }), ); } finally { setBillingBusy(false); } } async function handlePayBill(): Promise { if (selectedBill === null) return; const fallbackAmount = formatAdminMinorDecimal( selectedBill.unpaid_amount ?? 0, billingCurrency, ); const amount = parseAdminMajorToMinor(payAmount || fallbackAmount, billingCurrency); if (amount === null || amount <= 0 || amount > Number(selectedBill.unpaid_amount ?? 0)) { toast.error(t("playersPanel.paymentAmountInvalid", { defaultValue: "请输入有效的收付金额" })); return; } setBillingBusy(true); try { await postSettlementBillPayment(selectedBill.id, { amount, method: payMethod.trim() || undefined, proof: payProof.trim() || undefined, }); toast.success(t("playersPanel.billPaid", { defaultValue: "已登记收付" })); await load(); if (billingPlayer) { await openBillingDialog(billingPlayer); } } catch (e) { toast.error( e instanceof LotteryApiBizError ? e.message : t("playersPanel.billPayFailed", { defaultValue: "登记收付失败" }), ); } finally { setBillingBusy(false); } } async function handleWriteOffBill(): Promise { if (selectedBill === null) return; const reason = badDebtReason.trim(); if (!reason) { toast.error(t("playersPanel.badDebtReasonRequired", { defaultValue: "请填写核销原因" })); return; } setBillingBusy(true); try { await postSettlementBillBadDebtWriteOff(selectedBill.id, { reason, }); toast.success(t("playersPanel.billWrittenOff", { defaultValue: "已核销坏账" })); await load(); if (billingPlayer) { await openBillingDialog(billingPlayer); } } catch (e) { toast.error( e instanceof LotteryApiBizError ? e.message : t("playersPanel.billWriteOffFailed", { defaultValue: "核销坏账失败" }), ); } finally { setBillingBusy(false); } } function requestConfirmBillAction(): void { if (selectedBill === null) return; requestConfirm({ title: t("playersPanel.confirmBillTitle", { defaultValue: "确认账单?" }), description: t("playersPanel.confirmBillDescription", { defaultValue: "确认后账单会进入待收付状态,请确认金额与玩家无误。", }), confirmLabel: t("agents:settlementBills.confirm", { defaultValue: "确认账单" }), confirmVariant: "default", onConfirm: handleConfirmBill, }); } function requestPayBillAction(): void { if (selectedBill === null) return; const fallbackAmount = formatAdminMinorDecimal( selectedBill.unpaid_amount ?? 0, billingCurrency, ); const amount = parseAdminMajorToMinor(payAmount || fallbackAmount, billingCurrency); if (amount === null || amount <= 0) { toast.error(t("playersPanel.paymentAmountInvalid", { defaultValue: "请输入大于 0 的有效金额" })); return; } if (amount > Number(selectedBill.unpaid_amount ?? 0)) { toast.error(t("playersPanel.paymentAmountTooLarge", { defaultValue: "收付金额不能超过未结金额" })); return; } requestConfirm({ title: t("playersPanel.payBillConfirmTitle", { defaultValue: "确认登记收付?" }), description: t("playersPanel.payBillConfirmDescription", { defaultValue: "这会写入收付记录并更新玩家账单金额,请确认金额与凭证无误。", }), confirmLabel: t("agents:settlementBills.paid", { defaultValue: "登记收付" }), confirmVariant: "default", onConfirm: handlePayBill, }); } function requestWriteOffBillAction(): void { if (selectedBill === null) return; if (!badDebtReason.trim()) { toast.error(t("playersPanel.badDebtReasonRequired", { defaultValue: "请填写核销原因" })); return; } requestConfirm({ title: t("playersPanel.writeOffBillConfirmTitle", { defaultValue: "确认核销坏账?" }), description: t("playersPanel.writeOffBillConfirmDescription", { defaultValue: "核销会把该玩家账单未结金额归档为坏账记录,请确认已无法收回。", }), confirmLabel: t("agents:settlementBills.confirmBadDebt", { defaultValue: "确认核销" }), confirmVariant: "destructive", onConfirm: handleWriteOffBill, }); } return (
{!embedded ? (

{t("playersPanel.creditListHint", { defaultValue: "信用占成盘:下列为玩家授信额度与可用信用,非主站钱包余额。", })}

) : (
)} {canCreatePlayer && !embedded ? ( ) : null}
{loading ? ( ) : ( <>
{t("common:table.id", { defaultValue: "ID" })} {t("playersPanel.playerRef", { defaultValue: "玩家标识" })} {t("playersPanel.usernameNickname", { defaultValue: "用户名 / 昵称" })} {t("players:riskTags", { defaultValue: "风控标签" })} {t("players:fundingMode", { defaultValue: "资金模式" })} {t("players:currency", { defaultValue: "币种" })} {t("playersPanel.creditLimitAvailable", { defaultValue: "授信 / 可用" })} {t("players:rebateRate", { defaultValue: "回水" })} {t("players:lastLogin", { defaultValue: "最后登录" })} {!embedded ? ( {t("players:status", { defaultValue: "状态" })} ) : null} {t("common:table.actions", { defaultValue: "操作" })} {items.length === 0 ? ( ) : ( items.map((row) => { const balances = playerBalanceCells(row, formatAdminMinorUnits); const rebate = resolvePlayerRebateRate(row); const riskTags = row.risk_tags ?? []; return ( #{row.id} {row.site_player_id} {row.username ?? "—"} / {row.nickname ?? "—"} {riskTags.length > 0 ? (
{riskTags.map((tag) => ( {tag} ))}
) : ( )}
{row.default_currency} {balances.balance} / {balances.available} {rebate != null ? `${percentValueToUi(rebate)}%` : "—"} {row.last_login_at ? formatDt(row.last_login_at) : "—"} {!embedded ? ( {playerStatusLabel(row.status, t)} ) : null} e.stopPropagation()} > void openBillingDialog(row), }, ] : []), ...(canManagePlayerRows ? [ { key: "edit", label: editPlayerLabel, icon: Pencil, onClick: () => openEditPlayer(row), }, { key: "delete", label: deletePlayerLabel, icon: Trash2, destructive: true, onClick: () => requestConfirm({ title: t("players:confirmDelete", { defaultValue: "确认删除", }), description: t("players:confirmDeleteDesc", { name: row.username ?? row.site_player_id, defaultValue: "确定要删除玩家 {{name}} 吗?此操作不可恢复。", }), confirmVariant: "destructive", onConfirm: () => void confirmDeletePlayer(row), }), }, ] : []), ]} />
); }) )}
{ setPerPage(value); setPage(1); }} onPageChange={setPage} /> )} {createPlayerLabel}
setUsername(e.target.value)} autoComplete="off" className="bg-background/50 transition-colors focus:bg-background" />
setPassword(e.target.value)} autoComplete="new-password" className="bg-background/50 transition-colors focus:bg-background" />

{t("playersPanel.passwordHint", { defaultValue: "至少 8 位" })}

setNickname(e.target.value)} className="bg-background/50 transition-colors focus:bg-background" />
setCreditLimit(e.target.value)} className="bg-background/50 transition-colors focus:bg-background" /> {parentAvailableCredit !== null ? (

{t("playersPanel.availableToGrant", { defaultValue: "代理剩余可下发:{{amount}}", amount: formatCredit(parentAvailableCredit), })}

) : null}
setRebateRate(e.target.value)} className="bg-background/50 transition-colors focus:bg-background" />

{t("playersPanel.rebateRateHint", { defaultValue: "填写百分比,如 5 表示 5%" })}

{ setBillingDialogOpen(open); if (!open) { setBillingPlayer(null); setBillingBills([]); setSelectedBillId(null); resetBillingForm(); } }} > {t("playersPanel.manageSettlement", { defaultValue: "处理账单" })}
{billingLoading ? (

{t("playersPanel.billingLoading", { defaultValue: "正在加载账单…" })}

) : billingBills.length === 0 ? (

{t("playersPanel.noPendingBills", { defaultValue: "当前没有可处理的未结账单。" })}

) : ( <>
{selectedBill ? (
{t("playersPanel.billStatus", { defaultValue: "状态" })}: {" "} {selectedBill.status}
{t("playersPanel.billUnpaid", { defaultValue: "未结" })}: {" "} {formatAdminMinorUnits(selectedBill.unpaid_amount ?? 0, billingCurrency)}
{selectedBill.status === "pending_confirm" ? ( ) : null} {selectedBill.status !== "pending_confirm" && Number(selectedBill.unpaid_amount ?? 0) > 0 ? (
setPayAmount(e.target.value)} inputMode="decimal" placeholder={formatAdminMinorDecimal( selectedBill.unpaid_amount ?? 0, billingCurrency, )} />
setPayMethod(e.target.value)} placeholder={t("agents:settlementBills.paymentMethodPlaceholder", { defaultValue: "例如:现金 / 银行转账", })} />
setPayProof(e.target.value)} placeholder={t("agents:settlementBills.paymentProofPlaceholder", { defaultValue: "可填写流水号、截图说明或备注", })} />
{boundAgent === null ? ( <>
setBadDebtReason(e.target.value)} placeholder={t("agents:settlementBills.badDebtReasonPlaceholder", { defaultValue: "例如:客户失联、确认坏账", })} />
) : null}
) : null}
) : null} )}
{t("players:editDialogTitle", { defaultValue: "编辑玩家" })} {editDetailLoading ? : null}
setEditUsername(e.target.value)} autoComplete="off" />
setEditNickname(e.target.value)} autoComplete="off" />
setEditDefaultCurrency(e.target.value)} autoComplete="off" />
{t("playersPanel.currentCredit", { defaultValue: "当前授信" })} {formatPlayerCreditAmount(editCreditBase, editDefaultCurrency || "NPR")}
setEditCreditDelta(e.target.value)} placeholder="0" />

{t("playersPanel.creditProjected", { defaultValue: "调整后授信:{{amount}}", amount: formatPlayerCreditAmount(projectedCreditLimit, editDefaultCurrency || "NPR"), })}

setEditRebateRate(e.target.value)} placeholder="0" />

{t("playersPanel.rebateRateHint", { defaultValue: "填写百分比,如 5 表示 5%" })}

setEditRiskTags(e.target.value)} placeholder={t("playersPanel.riskTagsPlaceholder", { defaultValue: "逗号分隔", })} />
); }