feat(agents, config, dashboard, i18n): add agent line provision wizard, site deletion, and site dashboard with multi-language support

Added agent line provision wizard page with permission gating, replacing redirect placeholder. Introduced site deletion API and UI with confirmation dialog in integration sites management. Added new site-scoped dashboard panel showing bet metrics, P/L trends, active players, and quick links. Enhanced chart tooltip to support custom formatters and fix indicator color
This commit is contained in:
2026-06-12 20:47:53 +08:00
parent 24fd7c10bd
commit 6ea0a6feec
48 changed files with 1573 additions and 629 deletions

View File

@@ -3,7 +3,9 @@
import { AdminNoResourceState, AdminTableNoResourceRow } from "@/components/admin/admin-no-resource-state";
import { useTranslation } from "react-i18next";
import { SignedMoney } from "@/lib/admin-signed-money";
import { formatDashboardCreditMajor, formatDashboardMoneyMinor } from "@/modules/dashboard/use-dashboard-analytics";
import { formatSignedSettlementMoney } from "@/modules/settlement/settlement-signed-money";
import {
Table,
TableBody,
@@ -165,15 +167,19 @@ export function AgentSettlementReportView({
</p>
);
}
const stats = [
{ label: t("settlementReports.platformPnl.billNet", { defaultValue: "平台账单净额" }), value: money(root.platform_bill_net, currencyCode) },
const stats: { label: string; amount: number; signed?: boolean }[] = [
{
label: t("settlementReports.platformPnl.billNet", { defaultValue: "平台账单净额" }),
amount: Number(root.platform_bill_net ?? 0),
},
{
label: t("settlementReports.platformPnl.rounding", { defaultValue: "尾差调整" }),
value: money(root.platform_rounding_adjustment, currencyCode),
amount: Number(root.platform_rounding_adjustment ?? 0),
},
{
label: t("settlementReports.platformPnl.shareProfit", { defaultValue: "占成利润(元数据)" }),
value: money(root.share_profit_meta, currencyCode),
amount: Number(root.share_profit_meta ?? 0),
signed: true,
},
];
return (
@@ -181,7 +187,15 @@ export function AgentSettlementReportView({
{stats.map((item) => (
<div key={item.label} className="rounded-md border border-border/60 px-3 py-2">
<div className="text-xs text-muted-foreground">{item.label}</div>
<div className="mt-1 text-sm font-semibold tabular-nums">{item.value}</div>
<div className="mt-1 text-sm font-semibold tabular-nums">
{item.signed ? (
<SignedMoney amount={item.amount} emphasize>
{formatSignedSettlementMoney(item.amount, currencyCode)}
</SignedMoney>
) : (
money(item.amount, currencyCode)
)}
</div>
</div>
))}
</div>
@@ -190,16 +204,16 @@ export function AgentSettlementReportView({
const items = asRows(root?.items ?? (reportType === "player_win_loss" || reportType === "agent_share" || reportType === "unpaid_bills" || reportType === "overdue" || reportType === "draw_period" ? data : null));
const columnSets: Record<string, { key: string; header: string; money?: boolean }[]> = {
const columnSets: Record<string, { key: string; header: string; money?: boolean; signed?: boolean }[]> = {
player_win_loss: [
{ key: "username", header: t("settlementReports.columns.player", { defaultValue: "玩家" }) },
{ key: "game_type", header: t("settlementReports.columns.gameType", { defaultValue: "玩法" }) },
{ key: "game_win_loss", header: t("settlementReports.columns.grossWinLoss", { defaultValue: "输赢" }), money: true },
{ key: "game_win_loss", header: t("settlementReports.columns.grossWinLoss", { defaultValue: "输赢" }), money: true, signed: true },
{ key: "basic_rebate", header: t("settlementReports.columns.rebate", { defaultValue: "回水" }), money: true },
],
agent_share: [
{ key: "agent_node_id", header: t("settlementReports.columns.agentId", { defaultValue: "代理 ID" }) },
{ key: "game_win_loss", header: t("settlementReports.columns.grossWinLoss", { defaultValue: "输赢" }), money: true },
{ key: "game_win_loss", header: t("settlementReports.columns.grossWinLoss", { defaultValue: "输赢" }), money: true, signed: true },
{ key: "basic_rebate", header: t("settlementReports.columns.rebate", { defaultValue: "回水" }), money: true },
{ key: "entry_count", header: t("settlementReports.columns.count", { defaultValue: "笔数" }) },
],
@@ -216,7 +230,7 @@ export function AgentSettlementReportView({
],
draw_period: [
{ key: "draw_no", header: t("settlementReports.columns.drawNo", { defaultValue: "期号" }) },
{ key: "game_win_loss", header: t("settlementReports.columns.grossWinLoss", { defaultValue: "输赢" }), money: true },
{ key: "game_win_loss", header: t("settlementReports.columns.grossWinLoss", { defaultValue: "输赢" }), money: true, signed: true },
{ key: "basic_rebate", header: t("settlementReports.columns.rebate", { defaultValue: "回水" }), money: true },
{ key: "ticket_count", header: t("settlementReports.columns.count", { defaultValue: "笔数" }) },
],
@@ -238,7 +252,7 @@ function ReportTable({
currencyCode,
}: {
rows: Record<string, unknown>[];
columns: { key: string; header: string; money?: boolean; creditMajor?: boolean }[];
columns: { key: string; header: string; money?: boolean; signed?: boolean; creditMajor?: boolean }[];
currencyCode: string;
}): React.ReactElement {
const { t } = useTranslation("common");
@@ -272,9 +286,15 @@ function ReportTable({
>
{col.creditMajor
? creditMoney(row[col.key], currencyCode)
: col.money
? money(row[col.key], currencyCode)
: String(row[col.key] ?? "—")}
: col.money && col.signed
? (
<SignedMoney amount={Number(row[col.key] ?? 0)} emphasize>
{formatSignedSettlementMoney(Number(row[col.key] ?? 0), currencyCode)}
</SignedMoney>
)
: col.money
? money(row[col.key], currencyCode)
: String(row[col.key] ?? "—")}
</TableCell>
))}
</TableRow>