feat(api, i18n): add agent_node_id to various admin queries and enhance multi-language support
Introduced the agent_node_id field in AdminDrawListQuery, AdminPlayerListQuery, AdminSettlementBatchListQuery, TicketItemsListQuery, and TransferOrderListQuery to improve filtering capabilities. Updated the admin-breadcrumb and admin-sidebar components to include new translations for agent-related terms in English, Nepali, and Chinese, enhancing the overall user experience and multi-language support across the admin interface.
This commit is contained in:
@@ -21,6 +21,7 @@ import { Skeleton } from "@/components/ui/skeleton";
|
||||
import { getAdminRequestLocale } from "@/lib/admin-locale";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { DashboardKpiCard } from "@/modules/dashboard/dashboard-visuals";
|
||||
import { DASHBOARD_CHART_COLORS } from "@/modules/dashboard/dashboard-chart-config";
|
||||
import {
|
||||
DailyTrendChart,
|
||||
PlayBreakdownChart,
|
||||
@@ -356,6 +357,112 @@ export function DashboardPlayRankingCard({
|
||||
);
|
||||
}
|
||||
|
||||
export function DashboardAgentRankingCard({
|
||||
analytics,
|
||||
}: {
|
||||
analytics: DashboardAnalyticsState;
|
||||
}): ReactNode {
|
||||
const { t } = useTranslation(["dashboard", "common"]);
|
||||
const {
|
||||
enabled,
|
||||
rankingMetric,
|
||||
loading,
|
||||
topAgentRows,
|
||||
currency,
|
||||
formatMoney,
|
||||
formatSignedMoney,
|
||||
} = analytics;
|
||||
|
||||
if (!enabled) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const metricValue = (row: (typeof topAgentRows)[number]): number => {
|
||||
if (rankingMetric === "payout") {
|
||||
return row.total_payout_minor;
|
||||
}
|
||||
if (rankingMetric === "profit") {
|
||||
return row.approx_house_gross_minor;
|
||||
}
|
||||
return row.total_bet_minor;
|
||||
};
|
||||
|
||||
const maxAbs = Math.max(1, ...topAgentRows.map((r) => Math.abs(metricValue(r))));
|
||||
|
||||
const formatRowValue = (row: (typeof topAgentRows)[number]): string => {
|
||||
const v = metricValue(row);
|
||||
if (rankingMetric === "profit") {
|
||||
return formatSignedMoney(v, currency);
|
||||
}
|
||||
return formatMoney(v, currency);
|
||||
};
|
||||
|
||||
const barColor = (row: (typeof topAgentRows)[number]): string => {
|
||||
if (rankingMetric === "bet") {
|
||||
return DASHBOARD_CHART_COLORS.primary;
|
||||
}
|
||||
if (rankingMetric === "payout") {
|
||||
return DASHBOARD_CHART_COLORS.rose;
|
||||
}
|
||||
return row.approx_house_gross_minor >= 0 ? DASHBOARD_CHART_COLORS.success : DASHBOARD_CHART_COLORS.warning;
|
||||
};
|
||||
|
||||
return (
|
||||
<Card className="admin-list-card flex min-w-0 flex-col overflow-hidden py-0">
|
||||
<CardHeader className="space-y-2 border-b border-border/60 px-4 py-3">
|
||||
<CardTitle className="text-sm font-semibold">{t("analytics.agentRanking")}</CardTitle>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
{t(`analytics.rankingMetrics.${rankingMetric}`)}
|
||||
</p>
|
||||
</CardHeader>
|
||||
<CardContent className="min-w-0 flex-1 overflow-hidden px-3 py-3">
|
||||
{loading ? (
|
||||
<Skeleton className="h-[210px] w-full" />
|
||||
) : topAgentRows.length > 0 ? (
|
||||
<div className="space-y-1.5">
|
||||
{topAgentRows.map((row, idx) => {
|
||||
const v = metricValue(row);
|
||||
const pct = (Math.abs(v) / maxAbs) * 100;
|
||||
const color = barColor(row);
|
||||
return (
|
||||
<div key={row.agent_node_id} className="rounded-lg bg-muted/20 px-2 py-2">
|
||||
<div className="flex items-start justify-between gap-2">
|
||||
<div className="flex min-w-0 items-start gap-2">
|
||||
<span className="mt-0.5 w-5 shrink-0 text-center text-[11px] font-semibold text-muted-foreground">
|
||||
#{idx + 1}
|
||||
</span>
|
||||
<div className="min-w-0">
|
||||
<p className="truncate text-xs font-medium">{row.agent_name || "-"}</p>
|
||||
<p className="truncate text-[11px] text-muted-foreground">{row.agent_code || ""}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="shrink-0 text-right text-xs font-semibold tabular-nums">
|
||||
{formatRowValue(row)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mt-2 h-2 overflow-hidden rounded-full bg-muted/30">
|
||||
<div
|
||||
className="h-full rounded-full"
|
||||
style={{
|
||||
width: `${Math.max(2, pct)}%`,
|
||||
backgroundColor: color,
|
||||
opacity: 0.35,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
) : (
|
||||
<p className="py-10 text-center text-sm text-muted-foreground">{t("analytics.noAgentData")}</p>
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
/** 单列堆叠布局(兼容旧用法) */
|
||||
export function DashboardAnalyticsPanel({
|
||||
enabled,
|
||||
|
||||
Reference in New Issue
Block a user