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:
@@ -29,6 +29,11 @@ import {
|
||||
ChartTooltipContent,
|
||||
type ChartConfig,
|
||||
} from "@/components/ui/chart";
|
||||
import {
|
||||
coerceAdminMinor,
|
||||
formatAdminMinorDecimal,
|
||||
getAdminCurrencyDecimalPlaces,
|
||||
} from "@/lib/money";
|
||||
import { cn } from "@/lib/utils";
|
||||
import {
|
||||
buildBatchProgressConfig,
|
||||
@@ -53,6 +58,74 @@ export type SoldOutBuckets = AdminDashboardSoldOutBuckets;
|
||||
|
||||
type MoneyFormatter = (minor: number, currency: string | null) => string;
|
||||
|
||||
type DashboardFinanceMetricCell = {
|
||||
key: string;
|
||||
label: string;
|
||||
amount: number;
|
||||
emphasize: boolean;
|
||||
};
|
||||
|
||||
/** KPI 卡片底部三列:仅数字(币种见卡片主值),过长时省略号 + hover 看全称 */
|
||||
function formatDashboardMetricAmount(
|
||||
minor: number,
|
||||
currencyCode: string | null,
|
||||
formatMoney: MoneyFormatter,
|
||||
): { display: string; title: string } {
|
||||
const safeMinor = coerceAdminMinor(minor);
|
||||
const code = (currencyCode ?? "NPR").toUpperCase();
|
||||
const decimals = getAdminCurrencyDecimalPlaces(code);
|
||||
return {
|
||||
display: formatAdminMinorDecimal(safeMinor, code, decimals),
|
||||
title: formatMoney(safeMinor, currencyCode),
|
||||
};
|
||||
}
|
||||
|
||||
function DashboardFinanceMetricCells({
|
||||
cells,
|
||||
currency,
|
||||
formatMoney,
|
||||
}: {
|
||||
cells: readonly DashboardFinanceMetricCell[];
|
||||
currency: string | null;
|
||||
formatMoney: MoneyFormatter;
|
||||
}): ReactElement {
|
||||
return (
|
||||
<div className="grid grid-cols-3 gap-1.5">
|
||||
{cells.map((cell) => {
|
||||
const { display, title } = formatDashboardMetricAmount(
|
||||
cell.amount,
|
||||
currency,
|
||||
formatMoney,
|
||||
);
|
||||
return (
|
||||
<div
|
||||
key={cell.key}
|
||||
className={cn(
|
||||
"min-w-0 rounded-lg px-1 py-2 ring-1",
|
||||
cell.emphasize
|
||||
? "bg-primary/6 ring-primary/15"
|
||||
: "bg-muted/30 ring-border/50",
|
||||
)}
|
||||
>
|
||||
<p className="line-clamp-2 text-center text-[10px] leading-tight text-muted-foreground">
|
||||
{cell.label}
|
||||
</p>
|
||||
<p
|
||||
className={cn(
|
||||
"mt-1 truncate text-center text-[10px] font-bold tabular-nums leading-tight",
|
||||
cell.emphasize ? "text-foreground" : "text-muted-foreground",
|
||||
)}
|
||||
title={title}
|
||||
>
|
||||
{display}
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function usageBarFill(pct: number): string {
|
||||
if (pct >= 95) {
|
||||
return DASHBOARD_CHART_COLORS.rose;
|
||||
@@ -485,10 +558,11 @@ export function PayoutPanelSnapshot({
|
||||
}): ReactElement {
|
||||
const { t } = useTranslation("dashboard");
|
||||
const currency = finance.currency_code;
|
||||
const bet = finance.total_bet_minor;
|
||||
const win = finance.total_win_payout_minor;
|
||||
const jackpot = finance.total_jackpot_win_minor;
|
||||
const hasPayout = win + jackpot > 0;
|
||||
const bet = coerceAdminMinor(finance.total_bet_minor);
|
||||
const win = coerceAdminMinor(finance.total_win_payout_minor);
|
||||
const jackpot = coerceAdminMinor(finance.total_jackpot_win_minor);
|
||||
const payout = coerceAdminMinor(finance.total_payout_minor);
|
||||
const hasPayout = payout > 0 || win + jackpot > 0;
|
||||
|
||||
if (bet <= 0 && !hasPayout) {
|
||||
return <DashboardChartEmpty message={t("noFinanceActivity")} compact />;
|
||||
@@ -502,29 +576,7 @@ export function PayoutPanelSnapshot({
|
||||
|
||||
return (
|
||||
<div className="space-y-3">
|
||||
<div className="grid grid-cols-3 gap-2 text-center">
|
||||
{cells.map((cell) => (
|
||||
<div
|
||||
key={cell.key}
|
||||
className={cn(
|
||||
"rounded-lg px-1.5 py-2 ring-1",
|
||||
cell.emphasize
|
||||
? "bg-primary/6 ring-primary/15"
|
||||
: "bg-muted/30 ring-border/50",
|
||||
)}
|
||||
>
|
||||
<p className="text-[10px] leading-tight text-muted-foreground">{cell.label}</p>
|
||||
<p
|
||||
className={cn(
|
||||
"mt-1 text-[11px] font-bold tabular-nums leading-tight",
|
||||
cell.emphasize ? "text-foreground" : "text-muted-foreground",
|
||||
)}
|
||||
>
|
||||
{formatMoney(cell.amount, currency)}
|
||||
</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<DashboardFinanceMetricCells cells={cells} currency={currency} formatMoney={formatMoney} />
|
||||
{hasPayout ? (
|
||||
<PayoutCompositionChart finance={finance} formatMoney={formatMoney} compact />
|
||||
) : (
|
||||
@@ -983,7 +1035,10 @@ export function ResultBatchQueueSummary({
|
||||
compact?: boolean;
|
||||
}): ReactElement {
|
||||
const { t } = useTranslation("dashboard");
|
||||
const { pending_review_total, pending_draw_count, published_total, batch_total } = queue;
|
||||
const pendingReviewTotal = coerceAdminMinor(queue.pending_review_total);
|
||||
const pendingDrawCount = coerceAdminMinor(queue.pending_draw_count);
|
||||
const publishedTotal = coerceAdminMinor(queue.published_total);
|
||||
const batchTotal = coerceAdminMinor(queue.batch_total);
|
||||
|
||||
return (
|
||||
<div className="grid grid-cols-3 gap-2 text-center">
|
||||
@@ -994,7 +1049,7 @@ export function ResultBatchQueueSummary({
|
||||
compact ? "text-lg" : "text-2xl",
|
||||
)}
|
||||
>
|
||||
{pending_review_total}
|
||||
{pendingReviewTotal}
|
||||
</p>
|
||||
<p className="mt-0.5 text-[10px] text-muted-foreground">{t("batchPending")}</p>
|
||||
</div>
|
||||
@@ -1005,18 +1060,16 @@ export function ResultBatchQueueSummary({
|
||||
compact ? "text-lg" : "text-2xl",
|
||||
)}
|
||||
>
|
||||
{published_total}
|
||||
{publishedTotal}
|
||||
</p>
|
||||
<p className="mt-0.5 text-[10px] text-muted-foreground">{t("batchPublished")}</p>
|
||||
</div>
|
||||
<div className="rounded-lg bg-muted/50 px-2 py-2 ring-1 ring-border/60">
|
||||
<p className={cn("font-bold tabular-nums text-foreground", compact ? "text-lg" : "text-2xl")}>
|
||||
{batch_total}
|
||||
{pendingDrawCount > 0 ? pendingDrawCount : batchTotal}
|
||||
</p>
|
||||
<p className="mt-0.5 text-[10px] text-muted-foreground">
|
||||
{pending_draw_count > 0
|
||||
? t("batchPendingDrawsCount", { count: pending_draw_count })
|
||||
: t("batchTotal")}
|
||||
{pendingDrawCount > 0 ? t("batchPendingDraws") : t("batchTotal")}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1032,10 +1085,14 @@ export function PlatformLifetimePayoutSnapshot({
|
||||
}): ReactElement {
|
||||
const { t } = useTranslation("dashboard");
|
||||
const currency = finance.currency_code;
|
||||
const bet = finance.total_bet_minor;
|
||||
const win = finance.total_win_minor;
|
||||
const jackpot = finance.total_jackpot_minor;
|
||||
const hasPayout = win + jackpot > 0;
|
||||
const bet = coerceAdminMinor(finance.total_bet_minor);
|
||||
const payout = coerceAdminMinor(finance.total_payout_minor);
|
||||
let win = coerceAdminMinor(finance.total_win_minor);
|
||||
let jackpot = coerceAdminMinor(finance.total_jackpot_minor);
|
||||
if (payout > 0 && win + jackpot === 0) {
|
||||
win = payout;
|
||||
}
|
||||
const hasPayout = payout > 0 || win + jackpot > 0;
|
||||
|
||||
if (bet <= 0 && !hasPayout) {
|
||||
return <DashboardChartEmpty message={t("platformNoFinanceActivity")} compact />;
|
||||
@@ -1049,29 +1106,7 @@ export function PlatformLifetimePayoutSnapshot({
|
||||
|
||||
return (
|
||||
<div className="space-y-3">
|
||||
<div className="grid grid-cols-3 gap-2 text-center">
|
||||
{cells.map((cell) => (
|
||||
<div
|
||||
key={cell.key}
|
||||
className={cn(
|
||||
"rounded-lg px-1.5 py-2 ring-1",
|
||||
cell.emphasize
|
||||
? "bg-primary/6 ring-primary/15"
|
||||
: "bg-muted/30 ring-border/50",
|
||||
)}
|
||||
>
|
||||
<p className="text-[10px] leading-tight text-muted-foreground">{cell.label}</p>
|
||||
<p
|
||||
className={cn(
|
||||
"mt-1 text-[11px] font-bold tabular-nums leading-tight",
|
||||
cell.emphasize ? "text-foreground" : "text-muted-foreground",
|
||||
)}
|
||||
>
|
||||
{formatMoney(cell.amount, currency)}
|
||||
</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<DashboardFinanceMetricCells cells={cells} currency={currency} formatMoney={formatMoney} />
|
||||
{!hasPayout ? (
|
||||
<p className="rounded-lg bg-muted/25 px-2 py-2 text-center text-[11px] text-muted-foreground ring-1 ring-border/40">
|
||||
{t("platformNoPayoutYet")}
|
||||
|
||||
Reference in New Issue
Block a user