hyapp-admin-platform/src/features/host-org/pages/HostWithdrawalsPage.jsx
2026-07-02 14:21:32 +08:00

277 lines
8.7 KiB
JavaScript

import {
AdminFilterResetButton,
AdminListBody,
AdminListPage,
AdminListToolbar,
} from "@/shared/ui/AdminListLayout.jsx";
import { AdminUserIdentity } from "@/shared/ui/AdminUserIdentity.jsx";
import { DataState } from "@/shared/ui/DataState.jsx";
import { DataTable } from "@/shared/ui/DataTable.jsx";
import { TimeRangeFilter } from "@/shared/ui/TimeRangeFilter.jsx";
import { createOptionsColumnFilter, createTextColumnFilter } from "@/shared/ui/tableFilters.js";
import { formatMillis } from "@/shared/utils/time.js";
import { useHostWithdrawalsPage } from "@/features/host-org/hooks/useHostWithdrawalsPage.js";
import styles from "@/features/operations/operations.module.css";
const recordTypeOptions = [
["", "全部"],
["salary_transfer", "工资转币商"],
["withdrawal", "提现申请"],
];
const identityOptions = [
["", "全部"],
["host", "Host"],
["agency", "Agency"],
["bd", "BD"],
["bd_leader_admin", "BD Leader/Admin"],
];
const statusOptions = [
["", "全部"],
["completed", "已完成"],
["pending", "待审核"],
["approved", "已通过"],
["rejected", "已拒绝"],
];
const recordTypeLabels = Object.fromEntries(recordTypeOptions.filter(([value]) => value));
const identityLabels = Object.fromEntries(identityOptions.filter(([value]) => value));
const statusLabels = Object.fromEntries(statusOptions.filter(([value]) => value));
const baseColumns = [
{
key: "createdAtMs",
label: "时间",
render: (item) => formatMillis(item.createdAtMs),
width: "minmax(170px, 0.8fr)",
},
{
key: "recordType",
label: "类型",
render: (item) => recordTypeLabel(item.recordType),
width: "minmax(130px, 0.6fr)",
},
{
key: "identity",
label: "身份",
render: (item) => identityLabel(item.identity, item.salaryAssetType),
width: "minmax(150px, 0.65fr)",
},
{
key: "sourceUser",
label: "发起用户",
render: (item) => <AdminUserIdentity openInAppUserDetail user={item.sourceUser} userId={item.sourceUserId} />,
width: "minmax(230px, 1fr)",
},
{
key: "receiver",
label: "币商/收款",
render: (item) => <ReceiverCell item={item} />,
width: "minmax(240px, 1.05fr)",
},
{
key: "usdMinorAmount",
label: "USD金额",
render: (item) => <AmountText value={`$${formatUSDMinor(item.usdMinorAmount)}`} />,
width: "minmax(120px, 0.55fr)",
},
{
key: "coinAmount",
label: "币商金币",
render: (item) => formatCoinAmount(item),
width: "minmax(130px, 0.6fr)",
},
{
key: "status",
label: "状态",
render: (item) => <StatusBadge status={item.status} />,
width: "minmax(110px, 0.55fr)",
},
{
key: "recordId",
label: "交易/申请ID",
render: (item) => <RecordIdCell item={item} />,
width: "minmax(260px, 1.1fr)",
},
];
export function HostWithdrawalsPage() {
const page = useHostWithdrawalsPage();
const data = page.data || { items: [], page: 1, pageSize: 50, total: 0 };
const items = data.items || [];
const total = Number(data.total || 0);
const tableColumns = baseColumns.map((column) => {
if (column.key === "recordType") {
return {
...column,
filter: createOptionsColumnFilter({
emptyLabel: "全部",
options: recordTypeOptions,
placeholder: "类型",
value: page.recordType,
onChange: page.changeRecordType,
}),
};
}
if (column.key === "identity") {
return {
...column,
filter: createOptionsColumnFilter({
emptyLabel: "全部",
options: identityOptions,
placeholder: "身份",
value: page.identity,
onChange: page.changeIdentity,
}),
};
}
if (column.key === "sourceUser") {
return {
...column,
filter: createTextColumnFilter({
activeLabel: page.keyword ? `用户 ${page.keyword}` : "",
placeholder: "用户/交易/地址",
value: page.keyword,
onChange: page.changeKeyword,
}),
};
}
if (column.key === "status") {
return {
...column,
filter: createOptionsColumnFilter({
emptyLabel: "全部",
options: statusOptions,
placeholder: "状态",
value: page.status,
onChange: page.changeStatus,
}),
};
}
return column;
});
return (
<AdminListPage>
<AdminListToolbar
filters={
<>
<TimeRangeFilter value={page.timeRange} onChange={page.changeTimeRange} />
<AdminFilterResetButton disabled={!page.hasActiveFilters} onClick={page.resetFilters} />
</>
}
/>
<DataState error={page.error} loading={page.loading} onRetry={page.reload}>
<AdminListBody>
<DataTable
columns={tableColumns}
emptyLabel="当前无主播提现记录"
items={items}
minWidth="1510px"
pagination={
total > 0
? {
page: page.page,
pageSize: data.pageSize || 50,
total,
onPageChange: page.setPage,
}
: undefined
}
rowKey={(item) => item.id}
/>
</AdminListBody>
</DataState>
</AdminListPage>
);
}
function ReceiverCell({ item }) {
if (item.recordType === "salary_transfer") {
return <AdminUserIdentity openInAppUserDetail user={item.sellerUser} userId={item.sellerUserId} />;
}
const method = String(item.withdrawMethod || "").trim();
const address = String(item.withdrawAddress || "").trim();
if (!method && !address) {
return "-";
}
return (
<span className={styles.identityText}>
<span className={styles.primaryText}>{method || "-"}</span>
{address ? <span className={styles.meta}>{address}</span> : null}
</span>
);
}
function RecordIdCell({ item }) {
const primary = item.transactionId || item.applicationId || item.id || "-";
const secondary = item.commandId || item.freezeTransactionId || item.auditTransactionId || "";
return (
<span className={styles.identityText}>
<span className={styles.primaryText}>{primary}</span>
{secondary ? <span className={styles.meta}>{secondary}</span> : null}
</span>
);
}
function StatusBadge({ status }) {
const normalized = String(status || "").trim();
return (
<span className={`status-badge status-badge--${statusTone(normalized)}`}>
<span className="status-point" />
{statusLabel(normalized)}
</span>
);
}
function AmountText({ value }) {
return <span className={styles.amount}>{value}</span>;
}
function recordTypeLabel(value) {
return recordTypeLabels[value] || value || "-";
}
function identityLabel(identity, salaryAssetType) {
return identityLabels[identity] || salaryAssetLabel(salaryAssetType) || identity || "-";
}
function salaryAssetLabel(assetType) {
const labels = {
AGENCY_SALARY_USD: "Agency",
ADMIN_SALARY_USD: "BD Leader/Admin",
BD_SALARY_USD: "BD",
HOST_SALARY_USD: "Host",
};
return labels[assetType] || "";
}
function statusLabel(status) {
return statusLabels[status] || status || "-";
}
function statusTone(status) {
if (status === "completed" || status === "approved") {
return "succeeded";
}
if (status === "pending") {
return "warning";
}
if (status === "rejected") {
return "danger";
}
return "stopped";
}
function formatCoinAmount(item) {
if (item.recordType !== "salary_transfer") {
return "-";
}
return Number(item.coinAmount || 0).toLocaleString("zh-CN");
}
function formatUSDMinor(value) {
return (Number(value || 0) / 100).toFixed(2);
}