import AccountBalanceWalletOutlined from "@mui/icons-material/AccountBalanceWalletOutlined"; import PaidOutlined from "@mui/icons-material/PaidOutlined"; import MenuItem from "@mui/material/MenuItem"; import TextField from "@mui/material/TextField"; import { useCallback, useEffect, useState } from "react"; import { adjustSalaryWallet, listSalaryWalletHistory } from "@/features/host-org/api"; import styles from "@/features/host-org/host-org.module.css"; import { AdminFormAmountField, AdminFormDialog, AdminFormFieldGrid, AdminFormReadOnlyField, AdminFormSection, } from "@/shared/ui/AdminFormDialog.jsx"; import { AdminActionIconButton } from "@/shared/ui/AdminListLayout.jsx"; import { DataState } from "@/shared/ui/DataState.jsx"; import { DataTable } from "@/shared/ui/DataTable.jsx"; import { SideDrawer } from "@/shared/ui/SideDrawer.jsx"; import { useToast } from "@/shared/ui/ToastProvider.jsx"; import { formatMillis } from "@/shared/utils/time.js"; const historyPageSize = 20; const emptyHistory = { items: [], page: 1, pageSize: historyPageSize, total: 0 }; const emptyAdjustForm = () => ({ amount: "", reason: "", type: "increase", }); const historyColumns = [ { key: "createdAtMs", label: "时间", render: (item) => formatMillis(item.createdAtMs), width: "minmax(170px, 0.9fr)", }, { key: "amountMinor", label: "金额", render: (item) => , width: "minmax(120px, 0.65fr)", }, { key: "balance", label: "余额", render: (item) => formatSalaryWalletAmount(item.availableAfter), width: "minmax(120px, 0.65fr)", }, { key: "bizType", label: "类型", render: (item) => salaryWalletBizLabel(item.bizType), width: "minmax(150px, 0.8fr)", }, { key: "operator", label: "操作人", render: (item) => salaryWalletOperatorName(item), width: "minmax(150px, 0.8fr)", }, { key: "reason", label: "原因", render: (item) => item.reason || "-", width: "minmax(220px, 1.15fr)", }, ]; export function SalaryWalletCell({ role, userId, userLabel, wallet }) { const [open, setOpen] = useState(false); const balance = Number(wallet?.availableAmount || 0); return ( <> setOpen(false)} /> ); } export function SalaryWalletAdjustButton({ disabled = false, onAdjusted, role, userId, userLabel }) { const { showToast } = useToast(); const [open, setOpen] = useState(false); const [submitting, setSubmitting] = useState(false); const [form, setForm] = useState(emptyAdjustForm); const close = () => { if (submitting) { return; } setOpen(false); setForm(emptyAdjustForm()); }; const submit = async (event) => { event.preventDefault(); let amountMinor; try { amountMinor = parseSalaryUSDMinor(form.amount); if (!String(form.reason || "").trim()) { throw new Error("请填写原因"); } } catch (err) { showToast(err.message || "工资操作参数不正确", "error"); return; } setSubmitting(true); try { const result = await adjustSalaryWallet({ amountMinor, commandId: makeSalaryWalletCommandId(role, userId), reason: String(form.reason || "").trim(), role, targetUserId: userId, type: form.type, }); showToast("工资钱包已更新", "success"); setOpen(false); setForm(emptyAdjustForm()); await onAdjusted?.(result); } catch (err) { showToast(err.message || "工资钱包操作失败", "error"); } finally { setSubmitting(false); } }; return ( <> setOpen(true)} > setForm((current) => ({ ...current, type: event.target.value }))} > 工资增加 工资减少 setForm((current) => ({ ...current, amount: event.target.value }))} /> setForm((current) => ({ ...current, reason: event.target.value }))} /> ); } function SalaryWalletHistoryDrawer({ onClose, open, role, userId, userLabel }) { const [page, setPage] = useState(1); const [data, setData] = useState(emptyHistory); const [error, setError] = useState(""); const [loading, setLoading] = useState(false); const loadHistory = useCallback(async () => { if (!open || !role || !userId) { return; } setError(""); setLoading(true); try { const nextData = await listSalaryWalletHistory({ page, page_size: historyPageSize, role, user_id: userId, }); setData(nextData || emptyHistory); } catch (err) { setError(err.message || "加载工资钱包历史失败"); } finally { setLoading(false); } }, [open, page, role, userId]); useEffect(() => { if (!open) { return; } void loadHistory(); }, [loadHistory, open]); useEffect(() => { if (open) { setPage(1); } }, [open, role, userId]); const total = data.total || 0; return (
{salaryWalletRoleLabel(role)}
0 ? { page, pageSize: data.pageSize || historyPageSize, total, onPageChange: setPage, } : undefined } rowKey={(item) => item.entryId || item.transactionId} />
); } function SalaryHistoryAmount({ item }) { const delta = Number(item.availableDelta ?? item.amountMinor ?? 0); const expense = delta < 0; return ( {expense ? "-" : "+"} {formatSalaryWalletAmount(Math.abs(delta))} ); } export function formatSalaryWalletAmount(value) { const minor = Number(value || 0); return `$${(minor / 100).toLocaleString("en-US", { maximumFractionDigits: 2, minimumFractionDigits: 2, })}`; } function parseSalaryUSDMinor(value) { const raw = String(value || "").trim(); if (!/^\d+(\.\d{1,2})?$/.test(raw)) { throw new Error("请输入正确的 USD 金额,最多两位小数"); } const [integerPart, decimalPart = ""] = raw.split("."); const amountMinor = Number(integerPart) * 100 + Number(decimalPart.padEnd(2, "0")); if (!Number.isSafeInteger(amountMinor) || amountMinor <= 0) { throw new Error("请输入大于 0 的工资金额"); } return amountMinor; } function salaryWalletRoleLabel(role) { switch (role) { case "host": return "Host"; case "agency": return "Agency"; case "bd": return "BD"; case "bd_leader": return "BD Leader"; default: return role || "-"; } } function salaryWalletBizLabel(value) { switch (value) { case "manual_credit": return "手工调整"; case "salary_settlement": case "host_salary_settlement": case "team_salary_settlement": return "工资结算"; case "transfer": return "转账"; case "withdraw": return "提现"; default: return value || "-"; } } function salaryWalletOperatorName(item) { const operator = item.operator || {}; return operator.name || operator.username || item.operatorUserId || "-"; } function makeSalaryWalletCommandId(role, userId) { return `salary-wallet-${role || "role"}-${userId || "user"}-${Date.now()}`; }