refactor(admin-reports, i18n): remove rebate commission report and enhance localization

Removed the `getAdminReportRebateCommission` function and its references from the admin reports API and localization files. Updated CSS for improved money display handling in admin components. Enhanced localization support by adding new finance and support workspace entries in English, Nepali, and Chinese, improving user experience across the application.
This commit is contained in:
2026-06-16 16:04:03 +08:00
parent d4cf4ff436
commit a020e34a7d
38 changed files with 1259 additions and 353 deletions

View File

@@ -6,7 +6,6 @@ import { useTranslation } from "react-i18next";
import { toast } from "sonner";
import {
CalendarDays,
CircleDollarSign,
Database,
FileDown,
FileSpreadsheet,
@@ -31,7 +30,6 @@ import {
getAdminReportDailyProfit,
getAdminReportPlayDimension,
getAdminReportPlayerWinLoss,
getAdminReportRebateCommission,
} from "@/api/admin-reports";
import {
buildReportJobParameters,
@@ -42,7 +40,13 @@ import { getAdminRiskPoolDetail, getAdminRiskPools } from "@/api/admin-risk";
import { getAdminUsers } from "@/api/admin-users";
import { getAdminTransferOrders } from "@/api/admin-wallet";
import { adminHasAnyPermission } from "@/lib/admin-permissions";
import { PRD_REPORT_EXPORT, PRD_REPORT_VIEW } from "@/lib/admin-prd";
import {
PRD_AUDIT_VIEW,
PRD_REPORT_EXPORT,
PRD_REPORT_VIEW,
PRD_RISK_ACCESS_ANY,
PRD_WALLET_TRANSFER_ACCESS_ANY,
} from "@/lib/admin-prd";
import { useAdminProfile } from "@/stores/admin-session";
import { adminAgentDisplayLabel } from "@/components/admin/admin-agent-columns";
import { AdminNoResourceState, AdminTableNoResourceRow } from "@/components/admin/admin-no-resource-state";
@@ -73,6 +77,7 @@ import { useAdminCurrencyCatalog, getCachedAdminCurrencies } from "@/hooks/use-a
import { useAdminDateTimeFormatter } from "@/hooks/use-admin-datetime-formatter";
import { formatAdminInstant } from "@/lib/admin-datetime";
import { getAdminRequestLocale } from "@/lib/admin-locale";
import { AdminMoneyDisplay } from "@/components/admin/admin-money-display";
import { signedMoneyClass } from "@/lib/admin-signed-money";
import { cn } from "@/lib/utils";
import { formatAdminMinorUnits } from "@/lib/money";
@@ -88,7 +93,6 @@ import type {
AdminReportDailyProfitRow,
AdminReportPlayDimensionRow,
AdminReportPlayerWinLossRow,
AdminReportRebateCommissionRow,
} from "@/types/api/admin-reports";
export type ReportCategory = "profit" | "wallet" | "risk" | "audit";
@@ -107,7 +111,6 @@ type ReportKey =
| "hot_number_risk"
| "play_dimension"
| "sold_out_number"
| "rebate_commission"
| "admin_audit";
type ReportDefinition = {
@@ -118,8 +121,22 @@ type ReportDefinition = {
scope: string;
fields: FieldKey[];
connected: boolean;
requiredAny: readonly string[];
};
const PRD_REPORTS_VIEW_ACCESS_ANY = [PRD_REPORT_VIEW] as const;
const REPORTS: ReportDefinition[] = [
{ key: "draw_profit", category: "profit", icon: Ticket, filterKind: "draw", scope: "drawNo", fields: ["drawNo"], connected: true, requiredAny: PRD_REPORTS_VIEW_ACCESS_ANY },
{ key: "daily_profit", category: "profit", icon: CalendarDays, filterKind: "date", scope: "date", fields: ["period"], connected: true, requiredAny: PRD_REPORTS_VIEW_ACCESS_ANY },
{ key: "player_win_loss", category: "profit", icon: Users, filterKind: "player_period", scope: "playerPeriod", fields: ["player", "period"], connected: true, requiredAny: PRD_REPORTS_VIEW_ACCESS_ANY },
{ key: "player_transfer", category: "wallet", icon: WalletCards, filterKind: "player_period", scope: "playerPeriod", fields: ["player", "period"], connected: true, requiredAny: PRD_WALLET_TRANSFER_ACCESS_ANY },
{ key: "hot_number_risk", category: "risk", icon: ShieldAlert, filterKind: "draw_number", scope: "drawNumber", fields: ["drawNo", "number"], connected: true, requiredAny: PRD_RISK_ACCESS_ANY },
{ key: "play_dimension", category: "profit", icon: ListFilter, filterKind: "play_period", scope: "playPeriod", fields: ["play", "period"], connected: true, requiredAny: PRD_REPORTS_VIEW_ACCESS_ANY },
{ key: "sold_out_number", category: "risk", icon: ShieldCheck, filterKind: "draw", scope: "drawNo", fields: ["drawNo"], connected: true, requiredAny: PRD_RISK_ACCESS_ANY },
{ key: "admin_audit", category: "audit", icon: FileSpreadsheet, filterKind: "operator_period", scope: "operatorPeriod", fields: ["operator", "period"], connected: true, requiredAny: [PRD_AUDIT_VIEW] },
];
type PreviewColumns = {
primary: string;
secondary: string;
@@ -159,7 +176,6 @@ type ReportResult =
| { key: "hot_number_risk"; rows: ExportRow[]; summary: StatCard[]; meta: ReportMeta | null; raw: AdminRiskPoolShowData }
| { key: "play_dimension"; rows: ExportRow[]; summary: StatCard[]; meta: ReportMeta; raw: AdminReportPlayDimensionRow[] }
| { key: "sold_out_number"; rows: ExportRow[]; summary: StatCard[]; meta: ReportMeta; raw: AdminRiskPoolRow[] }
| { key: "rebate_commission"; rows: ExportRow[]; summary: StatCard[]; meta: ReportMeta; raw: AdminReportRebateCommissionRow[] }
| { key: "admin_audit"; rows: ExportRow[]; summary: StatCard[]; meta: ReportMeta; raw: AdminAuditLogRow[] };
type StatCard = {
@@ -182,18 +198,6 @@ type PlayOption = {
label: string;
};
const REPORTS: ReportDefinition[] = [
{ key: "draw_profit", category: "profit", icon: Ticket, filterKind: "draw", scope: "drawNo", fields: ["drawNo"], connected: true },
{ key: "daily_profit", category: "profit", icon: CalendarDays, filterKind: "date", scope: "date", fields: ["period"], connected: true },
{ key: "player_win_loss", category: "profit", icon: Users, filterKind: "player_period", scope: "playerPeriod", fields: ["player", "period"], connected: true },
{ key: "player_transfer", category: "wallet", icon: WalletCards, filterKind: "player_period", scope: "playerPeriod", fields: ["player", "period"], connected: true },
{ key: "hot_number_risk", category: "risk", icon: ShieldAlert, filterKind: "draw_number", scope: "drawNumber", fields: ["drawNo", "number"], connected: true },
{ key: "play_dimension", category: "profit", icon: ListFilter, filterKind: "play_period", scope: "playPeriod", fields: ["play", "period"], connected: true },
{ key: "sold_out_number", category: "risk", icon: ShieldCheck, filterKind: "draw", scope: "drawNo", fields: ["drawNo"], connected: true },
{ key: "rebate_commission", category: "profit", icon: CircleDollarSign, filterKind: "play_period", scope: "playPeriod", fields: ["play", "period"], connected: true },
{ key: "admin_audit", category: "audit", icon: FileSpreadsheet, filterKind: "operator_period", scope: "operatorPeriod", fields: ["operator", "period"], connected: true },
];
const emptyFilters: ReportFilters = {
drawNo: "",
drawId: null,
@@ -414,38 +418,6 @@ function buildPlayDimensionRowsAndSummary(
};
}
function buildRebateCommissionRowsAndSummary(
items: AdminReportRebateCommissionRow[],
total: number,
t: (key: string) => string,
pageScopedLabel: (statKey: string) => string,
currencyCode: string,
): Pick<Extract<ReportResult, { key: "rebate_commission" }>, "rows" | "summary"> {
let totalRebate = 0;
let totalOrders = 0;
const rows = items.map((item) => {
totalRebate += item.total_rebate_minor;
totalOrders += item.order_count;
return {
play_code: item.play_code,
total_rebate_minor: item.total_rebate_minor,
order_count: item.order_count,
ticket_item_count: item.ticket_item_count,
};
});
return {
rows,
summary: [
{ label: t("preview.stats.records"), value: String(total) },
{ label: t("preview.stats.currentPage"), value: String(items.length) },
{ label: pageScopedLabel("rebate"), value: formatPlainMoney(totalRebate, currencyCode) },
{ label: pageScopedLabel("orders"), value: String(totalOrders) },
],
};
}
function metaFromList(meta: { current_page: number; per_page: number; total: number; last_page: number }): ReportMeta {
return {
total: meta.total,
@@ -612,13 +584,6 @@ function defaultSummaryCards(
{ label: t("preview.stats.currency"), value: t("preview.stats.notQueried") },
{ label: t("preview.stats.usage"), value: t("preview.stats.notQueried") },
];
case "rebate_commission":
return [
{ label: t("preview.stats.records"), value: t("preview.stats.notQueried") },
{ label: t("fields.play"), value: filters.play || t("filterAll") },
{ label: t("preview.stats.rebate"), value: t("preview.stats.notQueried") },
{ label: t("preview.stats.orders"), value: t("preview.stats.notQueried") },
];
case "admin_audit":
return [
{ label: t("preview.stats.records"), value: t("preview.stats.notQueried") },
@@ -641,14 +606,15 @@ export function ReportsConsole({ initialCategory }: { initialCategory?: ReportCa
const profile = useAdminProfile();
const canViewReports = adminHasAnyPermission(profile?.permissions, [PRD_REPORT_VIEW]);
const canExportReports = adminHasAnyPermission(profile?.permissions, [PRD_REPORT_EXPORT]);
const permissionSlugs = useMemo(() => profile?.permissions ?? [], [profile?.permissions]);
useAdminCurrencyCatalog();
useAdminPlayTypeCatalog();
const playCodeLabel = useAdminPlayCodeLabel();
const formatTs = useAdminDateTimeFormatter();
const filteredReports = useMemo(
() => (initialCategory ? REPORTS.filter((report) => report.category === initialCategory) : REPORTS),
[initialCategory],
);
const filteredReports = useMemo(() => {
const visible = REPORTS.filter((report) => adminHasAnyPermission(permissionSlugs, report.requiredAny));
return initialCategory ? visible.filter((report) => report.category === initialCategory) : visible;
}, [initialCategory, permissionSlugs]);
const [selectedKey, setSelectedKey] = useState<ReportKey>(
filteredReports[0]?.key ?? REPORTS[0].key,
);
@@ -758,17 +724,6 @@ export function ReportsConsole({ initialCategory }: { initialCategory?: ReportCa
extra: t("preview.columns.soldOut.extra"),
time: t("preview.columns.soldOut.time"),
};
case "rebate_commission":
return {
primary: t("preview.columns.rebateCommission.primary"),
secondary: t("preview.columns.rebateCommission.secondary"),
metricA: t("preview.columns.rebateCommission.metricA"),
metricB: t("preview.columns.rebateCommission.metricB"),
metricC: t("preview.columns.rebateCommission.metricC"),
status: t("preview.columns.rebateCommission.status"),
extra: t("preview.columns.rebateCommission.extra"),
time: t("preview.columns.rebateCommission.time"),
};
case "admin_audit":
return {
primary: t("preview.columns.adminAudit.primary"),
@@ -1057,22 +1012,6 @@ export function ReportsConsole({ initialCategory }: { initialCategory?: ReportCa
});
break;
}
case "rebate_commission": {
const payload = await getAdminReportRebateCommission(
reportListParams(filters, page, perPage),
);
const currencyCode = resolveDisplayCurrency(payload.currency_code);
setDisplayCurrency(currencyCode);
const next = buildRebateCommissionRowsAndSummary(payload.items, payload.meta.total, t, pageScopedLabel, currencyCode);
setResult({
key: "rebate_commission",
raw: payload.items,
rows: next.rows,
meta: metaFromList(payload.meta),
summary: next.summary,
});
break;
}
case "admin_audit": {
const operatorId = filters.operatorId ?? parsePositiveInteger(filters.operator);
const payload = await getAdminAuditLogs({
@@ -1134,10 +1073,10 @@ export function ReportsConsole({ initialCategory }: { initialCategory?: ReportCa
...prev,
drawNo: drawNoFromUrl || prev.drawNo,
}));
if (drawNoFromUrl) {
if (drawNoFromUrl && filteredReports.some((report) => report.key === "draw_profit")) {
setSelectedKey("draw_profit");
}
}, [drawNoFromUrl]);
}, [drawNoFromUrl, filteredReports]);
useEffect(() => {
queueMicrotask(() => {
@@ -1563,21 +1502,6 @@ export function ReportsConsole({ initialCategory }: { initialCategory?: ReportCa
));
}
if (result.key === "rebate_commission") {
return result.raw.map((item) => (
<TableRow key={item.play_code}>
<TableCell className="font-medium">{playCodeLabel(item.play_code)}</TableCell>
<TableCell>{item.order_count}</TableCell>
<TableCell className="text-center">{formatPlainMoney(item.total_rebate_minor, displayCurrency)}</TableCell>
<TableCell className="text-center">{item.ticket_item_count}</TableCell>
<TableCell>-</TableCell>
<TableCell>-</TableCell>
<TableCell>-</TableCell>
<TableCell>-</TableCell>
</TableRow>
));
}
if (result.key === "admin_audit") {
return result.raw.map((item) => (
<TableRow key={item.id}>
@@ -1598,6 +1522,10 @@ export function ReportsConsole({ initialCategory }: { initialCategory?: ReportCa
return (
<div className="mx-auto flex w-full max-w-7xl flex-col gap-4">
{filteredReports.length === 0 ? (
<AdminNoResourceState message={t("empty")} />
) : (
<>
<Card className="admin-list-card">
<CardHeader className="admin-list-header pb-3">
<div className="flex flex-col gap-3">
@@ -1652,11 +1580,13 @@ export function ReportsConsole({ initialCategory }: { initialCategory?: ReportCa
</CardContent>
</Card>
<div className="grid gap-2 md:grid-cols-4">
<div className="grid min-w-0 gap-2 md:grid-cols-4">
{(result?.summary ?? defaultSummaryCards(selectedReport.key, filters, t)).map((item) => (
<div key={item.label} className={cn("rounded-md border px-3 py-2.5", statTone(item.tone))}>
<div key={item.label} className={cn("min-w-0 rounded-md border px-3 py-2.5", statTone(item.tone))}>
<div className="text-xs text-muted-foreground">{item.label}</div>
<div className="mt-0.5 truncate text-base font-semibold tabular-nums">{item.value}</div>
<AdminMoneyDisplay as="div" value={item.value} size="md" className="mt-0.5">
{item.value}
</AdminMoneyDisplay>
</div>
))}
</div>
@@ -1722,6 +1652,8 @@ export function ReportsConsole({ initialCategory }: { initialCategory?: ReportCa
) : null}
</CardContent>
</Card>
</>
)}
</div>
);
}