feat(dashboard, i18n): 增强玩家身份信息展示并完善多语言支持
更新仪表盘相关组件,采用新的玩家身份信息字段(Player Identity Columns),提升数据展示的清晰度与可读性。 优化奖池记录(Jackpot Records)中的玩家信息展示方式,便于快速识别玩家身份。 改进结算明细(Settlement Details)页面的玩家身份展示,提升数据追踪与核对效率。 更新玩家注单(Player Tickets)与钱包交易(Wallet Transactions)相关界面,统一使用新的玩家身份信息展示逻辑。 在英文、尼泊尔语与中文语言包中新增玩家相关术语翻译,增强多语言支持。 提升系统整体用户体验,确保各模块中的玩家信息展示更加一致、直观。
This commit is contained in:
105
src/components/admin/admin-player-identity-columns.tsx
Normal file
105
src/components/admin/admin-player-identity-columns.tsx
Normal file
@@ -0,0 +1,105 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
|
import { TableCell, TableHead } from "@/components/ui/table";
|
||||||
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
|
export type AdminPlayerIdentityFields = {
|
||||||
|
site_code?: string | null;
|
||||||
|
site_player_id?: string | null;
|
||||||
|
username?: string | null;
|
||||||
|
nickname?: string | null;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function adminPlayerDisplayName(row: AdminPlayerIdentityFields): string {
|
||||||
|
const nickname = row.nickname?.trim() ?? "";
|
||||||
|
const username = row.username?.trim() ?? "";
|
||||||
|
if (nickname !== "") {
|
||||||
|
return nickname;
|
||||||
|
}
|
||||||
|
if (username !== "") {
|
||||||
|
return username;
|
||||||
|
}
|
||||||
|
|
||||||
|
return "—";
|
||||||
|
}
|
||||||
|
|
||||||
|
function cellText(value: string | null | undefined): string {
|
||||||
|
const trimmed = value?.trim() ?? "";
|
||||||
|
return trimmed !== "" ? trimmed : "—";
|
||||||
|
}
|
||||||
|
|
||||||
|
type HeadProps = { className?: string };
|
||||||
|
type CellProps = { row: AdminPlayerIdentityFields; className?: string };
|
||||||
|
|
||||||
|
export function AdminPlayerSiteHead({ className }: HeadProps): React.ReactElement {
|
||||||
|
const { t } = useTranslation("common");
|
||||||
|
return (
|
||||||
|
<TableHead className={cn("whitespace-nowrap", className)}>
|
||||||
|
{t("playerColumns.site")}
|
||||||
|
</TableHead>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function AdminPlayerDisplayHead({ className }: HeadProps): React.ReactElement {
|
||||||
|
const { t } = useTranslation("common");
|
||||||
|
return (
|
||||||
|
<TableHead className={cn("whitespace-nowrap", className)}>
|
||||||
|
{t("playerColumns.display")}
|
||||||
|
</TableHead>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function AdminPlayerSiteIdHead({ className }: HeadProps): React.ReactElement {
|
||||||
|
const { t } = useTranslation("common");
|
||||||
|
return (
|
||||||
|
<TableHead className={cn("whitespace-nowrap", className)}>
|
||||||
|
{t("playerColumns.sitePlayerId")}
|
||||||
|
</TableHead>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function AdminPlayerIdentityHeads({ className }: { className?: string }): React.ReactElement {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<AdminPlayerSiteHead className={className} />
|
||||||
|
<AdminPlayerDisplayHead className={className} />
|
||||||
|
<AdminPlayerSiteIdHead className={className} />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function AdminPlayerSiteCell({ row, className }: CellProps): React.ReactElement {
|
||||||
|
return (
|
||||||
|
<TableCell className={cn("text-xs", className)}>
|
||||||
|
<span className="font-mono text-xs">{cellText(row.site_code)}</span>
|
||||||
|
</TableCell>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function AdminPlayerDisplayCell({ row, className }: CellProps): React.ReactElement {
|
||||||
|
return (
|
||||||
|
<TableCell className={cn("text-xs", className)}>
|
||||||
|
{adminPlayerDisplayName(row)}
|
||||||
|
</TableCell>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function AdminPlayerSiteIdCell({ row, className }: CellProps): React.ReactElement {
|
||||||
|
return (
|
||||||
|
<TableCell className={cn("text-xs", className)}>
|
||||||
|
<span className="font-mono text-xs">{cellText(row.site_player_id)}</span>
|
||||||
|
</TableCell>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function AdminPlayerIdentityCells({ row, className }: CellProps): React.ReactElement {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<AdminPlayerSiteCell row={row} className={className} />
|
||||||
|
<AdminPlayerDisplayCell row={row} className={className} />
|
||||||
|
<AdminPlayerSiteIdCell row={row} className={className} />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -120,6 +120,11 @@
|
|||||||
"table": {
|
"table": {
|
||||||
"id": "ID"
|
"id": "ID"
|
||||||
},
|
},
|
||||||
|
"playerColumns": {
|
||||||
|
"site": "Site",
|
||||||
|
"display": "Player",
|
||||||
|
"sitePlayerId": "Player ID"
|
||||||
|
},
|
||||||
"toolbar": {
|
"toolbar": {
|
||||||
"defaultAdmin": "Administrator",
|
"defaultAdmin": "Administrator",
|
||||||
"notifications": "Notifications",
|
"notifications": "Notifications",
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
"lifetime": "All-time totals",
|
"lifetime": "All-time totals",
|
||||||
"currentDraw": "Current draw",
|
"currentDraw": "Current draw",
|
||||||
"currentDrawDetail": "Current draw · {{drawNo}}",
|
"currentDrawDetail": "Current draw · {{drawNo}}",
|
||||||
"operations": "Operations (current draw)",
|
"operations": "Operations (site-wide)",
|
||||||
"snapshot": "Current draw snapshot"
|
"snapshot": "Current draw snapshot"
|
||||||
},
|
},
|
||||||
"countdownToClose": "Time to close",
|
"countdownToClose": "Time to close",
|
||||||
@@ -85,10 +85,15 @@
|
|||||||
"noFinanceActivity": "No bets this draw",
|
"noFinanceActivity": "No bets this draw",
|
||||||
"noPayoutYet": "No payout this draw",
|
"noPayoutYet": "No payout this draw",
|
||||||
"resultBatches": "Result batch progress",
|
"resultBatches": "Result batch progress",
|
||||||
"resultBatchQueueScope": "Site-wide pending batches",
|
"resultBatchQueueScope": "Site-wide result batches",
|
||||||
"batchPending": "Pending review",
|
"batchPending": "Pending review",
|
||||||
"batchPendingDraws": "Draws involved",
|
"batchPendingDraws": "Draws involved",
|
||||||
"batchCurrentDrawPending": "Current draw",
|
"batchPendingDrawsCount": "{{count}} draws pending",
|
||||||
|
"platformLockedAndCap": "Site locked {{locked}} / cap {{cap}}",
|
||||||
|
"platformOrderAndTicket": "Site-wide {{orders}} orders · {{tickets}} lines",
|
||||||
|
"platformBetTotal": "Lifetime bet",
|
||||||
|
"platformNoFinanceActivity": "No bets site-wide yet",
|
||||||
|
"platformNoPayoutYet": "No payouts site-wide yet",
|
||||||
"batchPublished": "Published",
|
"batchPublished": "Published",
|
||||||
"batchTotal": "Total batches",
|
"batchTotal": "Total batches",
|
||||||
"batchOther": "Other statuses",
|
"batchOther": "Other statuses",
|
||||||
|
|||||||
@@ -120,6 +120,11 @@
|
|||||||
"table": {
|
"table": {
|
||||||
"id": "ID"
|
"id": "ID"
|
||||||
},
|
},
|
||||||
|
"playerColumns": {
|
||||||
|
"site": "साइट",
|
||||||
|
"display": "खेलाडी",
|
||||||
|
"sitePlayerId": "खेलाडी ID"
|
||||||
|
},
|
||||||
"toolbar": {
|
"toolbar": {
|
||||||
"defaultAdmin": "प्रशासक",
|
"defaultAdmin": "प्रशासक",
|
||||||
"notifications": "सूचना",
|
"notifications": "सूचना",
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
"lifetime": "ऐतिहासिक कुल",
|
"lifetime": "ऐतिहासिक कुल",
|
||||||
"currentDraw": "हालको ड्रअ",
|
"currentDraw": "हालको ड्रअ",
|
||||||
"currentDrawDetail": "हालको ड्रअ · {{drawNo}}",
|
"currentDrawDetail": "हालको ड्रअ · {{drawNo}}",
|
||||||
"operations": "सञ्चालन (हालको ड्रअ)",
|
"operations": "सञ्चालन (साइटव्यापी)",
|
||||||
"snapshot": "हालको ड्रअ स्न्यापसट"
|
"snapshot": "हालको ड्रअ स्न्यापसट"
|
||||||
},
|
},
|
||||||
"countdownToClose": "बन्द हुन बाँकी",
|
"countdownToClose": "बन्द हुन बाँकी",
|
||||||
@@ -85,10 +85,15 @@
|
|||||||
"noFinanceActivity": "यस ड्रअमा बेट छैन",
|
"noFinanceActivity": "यस ड्रअमा बेट छैन",
|
||||||
"noPayoutYet": "यस ड्रअमा भुक्तानी छैन",
|
"noPayoutYet": "यस ड्रअमा भुक्तानी छैन",
|
||||||
"resultBatches": "परिणाम ब्याच प्रगति",
|
"resultBatches": "परिणाम ब्याच प्रगति",
|
||||||
"resultBatchQueueScope": "साइटव्यापी पेन्डिङ ब्याच",
|
"resultBatchQueueScope": "साइटव्यापी नतिजा ब्याच",
|
||||||
"batchPending": "समीक्षा बाँकी",
|
"batchPending": "समीक्षा बाँकी",
|
||||||
"batchPendingDraws": "सम्बन्धित ड्रअ",
|
"batchPendingDraws": "सम्बन्धित ड्रअ",
|
||||||
"batchCurrentDrawPending": "हालको ड्रअ",
|
"batchPendingDrawsCount": "{{count}} ड्रअ पेन्डिङ",
|
||||||
|
"platformLockedAndCap": "साइट लक {{locked}} / क्याप {{cap}}",
|
||||||
|
"platformOrderAndTicket": "साइटव्यापी {{orders}} अर्डर · {{tickets}} लाइन",
|
||||||
|
"platformBetTotal": "जम्मा बेट",
|
||||||
|
"platformNoFinanceActivity": "साइटव्यापी अहिले बेट छैन",
|
||||||
|
"platformNoPayoutYet": "साइटव्यापी पेआउट छैन",
|
||||||
"batchPublished": "प्रकाशित",
|
"batchPublished": "प्रकाशित",
|
||||||
"batchTotal": "कुल ब्याच",
|
"batchTotal": "कुल ब्याच",
|
||||||
"batchOther": "अन्य स्थिति",
|
"batchOther": "अन्य स्थिति",
|
||||||
|
|||||||
@@ -120,6 +120,11 @@
|
|||||||
"table": {
|
"table": {
|
||||||
"id": "ID"
|
"id": "ID"
|
||||||
},
|
},
|
||||||
|
"playerColumns": {
|
||||||
|
"site": "主站",
|
||||||
|
"display": "玩家",
|
||||||
|
"sitePlayerId": "玩家 ID"
|
||||||
|
},
|
||||||
"toolbar": {
|
"toolbar": {
|
||||||
"defaultAdmin": "管理员",
|
"defaultAdmin": "管理员",
|
||||||
"notifications": "通知",
|
"notifications": "通知",
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
"lifetime": "历史累计",
|
"lifetime": "历史累计",
|
||||||
"currentDraw": "当前期号",
|
"currentDraw": "当前期号",
|
||||||
"currentDrawDetail": "当期明细 · {{drawNo}}",
|
"currentDrawDetail": "当期明细 · {{drawNo}}",
|
||||||
"operations": "运营监控(当期)",
|
"operations": "运营监控(全站)",
|
||||||
"snapshot": "当期快照"
|
"snapshot": "当期快照"
|
||||||
},
|
},
|
||||||
"countdownToClose": "距截止投注",
|
"countdownToClose": "距截止投注",
|
||||||
@@ -85,10 +85,15 @@
|
|||||||
"noFinanceActivity": "本期暂无投注",
|
"noFinanceActivity": "本期暂无投注",
|
||||||
"noPayoutYet": "本期暂无派彩",
|
"noPayoutYet": "本期暂无派彩",
|
||||||
"resultBatches": "开奖批次进度",
|
"resultBatches": "开奖批次进度",
|
||||||
"resultBatchQueueScope": "全站待审核批次",
|
"resultBatchQueueScope": "全站开奖批次",
|
||||||
"batchPending": "待审核",
|
"batchPending": "待审核",
|
||||||
"batchPendingDraws": "涉及期数",
|
"batchPendingDraws": "涉及期数",
|
||||||
"batchCurrentDrawPending": "当前期",
|
"batchPendingDrawsCount": "{{count}} 期待审",
|
||||||
|
"platformLockedAndCap": "全站已占用 {{locked}} / 封顶 {{cap}}",
|
||||||
|
"platformOrderAndTicket": "全站 {{orders}} 单 · {{tickets}} 笔",
|
||||||
|
"platformBetTotal": "累计投注",
|
||||||
|
"platformNoFinanceActivity": "全站暂无投注",
|
||||||
|
"platformNoPayoutYet": "全站暂无派彩记录",
|
||||||
"batchPublished": "已发布",
|
"batchPublished": "已发布",
|
||||||
"batchTotal": "批次合计",
|
"batchTotal": "批次合计",
|
||||||
"batchOther": "其他状态",
|
"batchOther": "其他状态",
|
||||||
|
|||||||
@@ -40,8 +40,8 @@
|
|||||||
"partial_failed": "部分失败",
|
"partial_failed": "部分失败",
|
||||||
"failed": "投注失败",
|
"failed": "投注失败",
|
||||||
"pending_payout": "待派奖",
|
"pending_payout": "待派奖",
|
||||||
"settled_win": "已中奖结算",
|
"settled_win": "已结算(中奖)",
|
||||||
"settled_lose": "已未中奖结算",
|
"settled_lose": "已结算(未中奖)",
|
||||||
"refunded": "已退款"
|
"refunded": "已退款"
|
||||||
},
|
},
|
||||||
"allTickets": "全部注单"
|
"allTickets": "全部注单"
|
||||||
|
|||||||
@@ -41,8 +41,8 @@ import {
|
|||||||
CapUsageBar,
|
CapUsageBar,
|
||||||
FinanceStructureChart,
|
FinanceStructureChart,
|
||||||
HotUsageBars,
|
HotUsageBars,
|
||||||
PayoutPanelSnapshot,
|
|
||||||
ResultBatchQueueSummary,
|
ResultBatchQueueSummary,
|
||||||
|
PlatformLifetimePayoutSnapshot,
|
||||||
DashboardPanelCard,
|
DashboardPanelCard,
|
||||||
SettlementStatusChart,
|
SettlementStatusChart,
|
||||||
} from "@/modules/dashboard/dashboard-visuals";
|
} from "@/modules/dashboard/dashboard-visuals";
|
||||||
@@ -55,6 +55,8 @@ import { cn } from "@/lib/utils";
|
|||||||
import { LotteryApiBizError } from "@/types/api/errors";
|
import { LotteryApiBizError } from "@/types/api/errors";
|
||||||
import type {
|
import type {
|
||||||
AdminDashboardDrawPanel,
|
AdminDashboardDrawPanel,
|
||||||
|
AdminDashboardLifetimeFinance,
|
||||||
|
AdminDashboardPlatformRisk,
|
||||||
AdminDashboardResultBatchQueue,
|
AdminDashboardResultBatchQueue,
|
||||||
} from "@/types/api/admin-dashboard";
|
} from "@/types/api/admin-dashboard";
|
||||||
import type { AdminDrawFinanceSummaryData } from "@/types/api/admin-draw-finance";
|
import type { AdminDrawFinanceSummaryData } from "@/types/api/admin-draw-finance";
|
||||||
@@ -151,6 +153,10 @@ export function DashboardConsole(): ReactElement {
|
|||||||
const [resultBatchQueue, setResultBatchQueue] = useState<AdminDashboardResultBatchQueue | null>(
|
const [resultBatchQueue, setResultBatchQueue] = useState<AdminDashboardResultBatchQueue | null>(
|
||||||
null,
|
null,
|
||||||
);
|
);
|
||||||
|
const [lifetimeFinance, setLifetimeFinance] = useState<AdminDashboardLifetimeFinance | null>(
|
||||||
|
null,
|
||||||
|
);
|
||||||
|
const [platformRisk, setPlatformRisk] = useState<AdminDashboardPlatformRisk | null>(null);
|
||||||
const [riskLocked, setRiskLocked] = useState(0);
|
const [riskLocked, setRiskLocked] = useState(0);
|
||||||
const [riskCap, setRiskCap] = useState(0);
|
const [riskCap, setRiskCap] = useState(0);
|
||||||
const [hotPoolSample, setHotPoolSample] = useState<AdminRiskPoolRow[]>([]);
|
const [hotPoolSample, setHotPoolSample] = useState<AdminRiskPoolRow[]>([]);
|
||||||
@@ -190,6 +196,8 @@ export function DashboardConsole(): ReactElement {
|
|||||||
setCapabilities(null);
|
setCapabilities(null);
|
||||||
setDrawPanel(null);
|
setDrawPanel(null);
|
||||||
setResultBatchQueue(null);
|
setResultBatchQueue(null);
|
||||||
|
setLifetimeFinance(null);
|
||||||
|
setPlatformRisk(null);
|
||||||
setDrawId(null);
|
setDrawId(null);
|
||||||
setRiskLocked(0);
|
setRiskLocked(0);
|
||||||
setRiskCap(0);
|
setRiskCap(0);
|
||||||
@@ -208,9 +216,11 @@ export function DashboardConsole(): ReactElement {
|
|||||||
if (d.finance != null) {
|
if (d.finance != null) {
|
||||||
setFinance(d.finance);
|
setFinance(d.finance);
|
||||||
}
|
}
|
||||||
|
setResultBatchQueue(d.result_batch_queue);
|
||||||
|
setLifetimeFinance(d.lifetime_finance);
|
||||||
|
setPlatformRisk(d.platform_risk);
|
||||||
if (d.draw != null) {
|
if (d.draw != null) {
|
||||||
setDrawPanel(d.draw);
|
setDrawPanel(d.draw);
|
||||||
setResultBatchQueue(d.result_batch_queue);
|
|
||||||
}
|
}
|
||||||
if (d.risk != null) {
|
if (d.risk != null) {
|
||||||
setRiskLocked(d.risk.locked_amount);
|
setRiskLocked(d.risk.locked_amount);
|
||||||
@@ -235,14 +245,16 @@ export function DashboardConsole(): ReactElement {
|
|||||||
return () => window.clearTimeout(timer);
|
return () => window.clearTimeout(timer);
|
||||||
}, [load]);
|
}, [load]);
|
||||||
|
|
||||||
const currency = finance?.currency_code ?? null;
|
const currency =
|
||||||
|
lifetimeFinance?.currency_code ?? finance?.currency_code ?? null;
|
||||||
const canFinance = capabilities?.draw_finance_risk ?? false;
|
const canFinance = capabilities?.draw_finance_risk ?? false;
|
||||||
const usagePct = riskCap > 0 ? (riskLocked / riskCap) * 100 : 0;
|
const platformLocked = platformRisk?.locked_amount ?? 0;
|
||||||
|
const platformCap = platformRisk?.cap_amount ?? 0;
|
||||||
|
const platformUsagePct = platformRisk?.usage_percent ?? 0;
|
||||||
|
|
||||||
const hotRows = useMemo(() => topPoolsForTab(hotPoolSample, hotTab), [hotPoolSample, hotTab]);
|
const hotRows = useMemo(() => topPoolsForTab(hotPoolSample, hotTab), [hotPoolSample, hotTab]);
|
||||||
|
|
||||||
const pendingReviewTotal = resultBatchQueue?.pending_review_total ?? 0;
|
const pendingReviewTotal = resultBatchQueue?.pending_review_total ?? 0;
|
||||||
const currentDrawPending = drawPanel?.result_batch_counts.pending_review ?? 0;
|
|
||||||
|
|
||||||
const analytics = useDashboardAnalytics({ enabled: canFinance, playOptions });
|
const analytics = useDashboardAnalytics({ enabled: canFinance, playOptions });
|
||||||
const showAnalytics = canFinance;
|
const showAnalytics = canFinance;
|
||||||
@@ -322,11 +334,7 @@ export function DashboardConsole(): ReactElement {
|
|||||||
loading={loading}
|
loading={loading}
|
||||||
>
|
>
|
||||||
{resultBatchQueue != null ? (
|
{resultBatchQueue != null ? (
|
||||||
<ResultBatchQueueSummary
|
<ResultBatchQueueSummary queue={resultBatchQueue} compact />
|
||||||
queue={resultBatchQueue}
|
|
||||||
currentDrawPending={currentDrawPending}
|
|
||||||
compact
|
|
||||||
/>
|
|
||||||
) : null}
|
) : null}
|
||||||
</DashboardPanelCard>
|
</DashboardPanelCard>
|
||||||
|
|
||||||
@@ -348,53 +356,62 @@ export function DashboardConsole(): ReactElement {
|
|||||||
</DashboardPanelCard>
|
</DashboardPanelCard>
|
||||||
|
|
||||||
<DashboardPanelCard
|
<DashboardPanelCard
|
||||||
href={drawScopedHref(drawId, "/risk/occupancy", "/admin/risk")}
|
href="/admin/risk"
|
||||||
title={t("riskCapUsage")}
|
title={t("riskCapUsage")}
|
||||||
value={`${usagePct.toFixed(1)}%`}
|
value={`${platformUsagePct.toFixed(1)}%`}
|
||||||
subtitle={t("lockedAndCap", {
|
subtitle={t("platformLockedAndCap", {
|
||||||
locked: formatMoneyMinor(riskLocked, currency),
|
locked: formatMoneyMinor(platformLocked, currency),
|
||||||
cap: formatMoneyMinor(riskCap, currency),
|
cap: formatMoneyMinor(platformCap, currency),
|
||||||
})}
|
})}
|
||||||
actionLabel={t("occupancyDetails")}
|
actionLabel={t("occupancyDetails")}
|
||||||
icon={<Shield className="size-5" aria-hidden />}
|
icon={<Shield className="size-5" aria-hidden />}
|
||||||
accent={
|
accent={
|
||||||
usagePct >= 90 ? "destructive" : usagePct >= 70 ? "primary" : "muted"
|
platformUsagePct >= 90
|
||||||
|
? "destructive"
|
||||||
|
: platformUsagePct >= 70
|
||||||
|
? "primary"
|
||||||
|
: "muted"
|
||||||
}
|
}
|
||||||
loading={loading}
|
loading={loading}
|
||||||
>
|
>
|
||||||
|
{platformRisk != null ? (
|
||||||
<CapUsageBar
|
<CapUsageBar
|
||||||
locked={riskLocked}
|
locked={platformLocked}
|
||||||
cap={riskCap}
|
cap={platformCap}
|
||||||
usagePct={usagePct}
|
usagePct={platformUsagePct}
|
||||||
formatMoney={formatMoneyMinor}
|
formatMoney={formatMoneyMinor}
|
||||||
currency={currency}
|
currency={currency}
|
||||||
compact
|
compact
|
||||||
/>
|
/>
|
||||||
|
) : null}
|
||||||
</DashboardPanelCard>
|
</DashboardPanelCard>
|
||||||
|
|
||||||
<DashboardPanelCard
|
<DashboardPanelCard
|
||||||
href={drawScopedHref(drawId, "/finance")}
|
href="/admin/reports"
|
||||||
title={t("payoutComposition")}
|
title={t("payoutComposition")}
|
||||||
value={
|
value={
|
||||||
finance
|
lifetimeFinance
|
||||||
? formatMoneyMinor(finance.total_payout_minor, currency)
|
? formatMoneyMinor(lifetimeFinance.total_payout_minor, currency)
|
||||||
: "—"
|
: "—"
|
||||||
}
|
}
|
||||||
subtitle={
|
subtitle={
|
||||||
finance
|
lifetimeFinance
|
||||||
? t("orderAndTicket", {
|
? t("platformOrderAndTicket", {
|
||||||
orders: finance.order_count,
|
orders: lifetimeFinance.order_count,
|
||||||
tickets: finance.ticket_item_count,
|
tickets: lifetimeFinance.ticket_item_count,
|
||||||
})
|
})
|
||||||
: t("states.noData", { ns: "common" })
|
: t("states.noData", { ns: "common" })
|
||||||
}
|
}
|
||||||
actionLabel={t("detailsShort")}
|
actionLabel={t("actions.viewAll", { ns: "common" })}
|
||||||
icon={<Wallet className="size-5" aria-hidden />}
|
icon={<Wallet className="size-5" aria-hidden />}
|
||||||
accent="primary"
|
accent="primary"
|
||||||
loading={loading}
|
loading={loading}
|
||||||
>
|
>
|
||||||
{finance ? (
|
{lifetimeFinance ? (
|
||||||
<PayoutPanelSnapshot finance={finance} formatMoney={formatMoneyMinor} />
|
<PlatformLifetimePayoutSnapshot
|
||||||
|
finance={lifetimeFinance}
|
||||||
|
formatMoney={formatMoneyMinor}
|
||||||
|
/>
|
||||||
) : null}
|
) : null}
|
||||||
</DashboardPanelCard>
|
</DashboardPanelCard>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -41,6 +41,7 @@ import {
|
|||||||
} from "@/modules/dashboard/dashboard-chart-config";
|
} from "@/modules/dashboard/dashboard-chart-config";
|
||||||
import { DashboardChartEmpty } from "@/modules/dashboard/dashboard-chart-empty";
|
import { DashboardChartEmpty } from "@/modules/dashboard/dashboard-chart-empty";
|
||||||
import type { AdminDrawFinanceSummaryData } from "@/types/api/admin-draw-finance";
|
import type { AdminDrawFinanceSummaryData } from "@/types/api/admin-draw-finance";
|
||||||
|
import type { AdminDashboardLifetimeFinance } from "@/types/api/admin-dashboard";
|
||||||
import type { AdminRiskPoolRow } from "@/types/api/admin-risk";
|
import type { AdminRiskPoolRow } from "@/types/api/admin-risk";
|
||||||
import type {
|
import type {
|
||||||
AdminDashboardDrawPanel,
|
AdminDashboardDrawPanel,
|
||||||
@@ -976,15 +977,13 @@ export function ResultBatchProgress({
|
|||||||
|
|
||||||
export function ResultBatchQueueSummary({
|
export function ResultBatchQueueSummary({
|
||||||
queue,
|
queue,
|
||||||
currentDrawPending,
|
|
||||||
compact = false,
|
compact = false,
|
||||||
}: {
|
}: {
|
||||||
queue: AdminDashboardResultBatchQueue;
|
queue: AdminDashboardResultBatchQueue;
|
||||||
currentDrawPending: number;
|
|
||||||
compact?: boolean;
|
compact?: boolean;
|
||||||
}): ReactElement {
|
}): ReactElement {
|
||||||
const { t } = useTranslation("dashboard");
|
const { t } = useTranslation("dashboard");
|
||||||
const { pending_review_total, pending_draw_count } = queue;
|
const { pending_review_total, pending_draw_count, published_total, batch_total } = queue;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="grid grid-cols-3 gap-2 text-center">
|
<div className="grid grid-cols-3 gap-2 text-center">
|
||||||
@@ -999,27 +998,89 @@ export function ResultBatchQueueSummary({
|
|||||||
</p>
|
</p>
|
||||||
<p className="mt-0.5 text-[10px] text-muted-foreground">{t("batchPending")}</p>
|
<p className="mt-0.5 text-[10px] text-muted-foreground">{t("batchPending")}</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="rounded-lg bg-sky-500/8 px-2 py-2 ring-1 ring-sky-500/15">
|
<div className="rounded-lg bg-emerald-500/8 px-2 py-2 ring-1 ring-emerald-500/15">
|
||||||
<p
|
<p
|
||||||
className={cn(
|
className={cn(
|
||||||
"font-bold tabular-nums text-sky-800 dark:text-sky-300",
|
"font-bold tabular-nums text-emerald-700 dark:text-emerald-400",
|
||||||
compact ? "text-lg" : "text-2xl",
|
compact ? "text-lg" : "text-2xl",
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{pending_draw_count}
|
{published_total}
|
||||||
</p>
|
</p>
|
||||||
<p className="mt-0.5 text-[10px] text-muted-foreground">{t("batchPendingDraws")}</p>
|
<p className="mt-0.5 text-[10px] text-muted-foreground">{t("batchPublished")}</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="rounded-lg bg-muted/50 px-2 py-2 ring-1 ring-border/60">
|
<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")}>
|
<p className={cn("font-bold tabular-nums text-foreground", compact ? "text-lg" : "text-2xl")}>
|
||||||
{currentDrawPending}
|
{batch_total}
|
||||||
|
</p>
|
||||||
|
<p className="mt-0.5 text-[10px] text-muted-foreground">
|
||||||
|
{pending_draw_count > 0
|
||||||
|
? t("batchPendingDrawsCount", { count: pending_draw_count })
|
||||||
|
: t("batchTotal")}
|
||||||
</p>
|
</p>
|
||||||
<p className="mt-0.5 text-[10px] text-muted-foreground">{t("batchCurrentDrawPending")}</p>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function PlatformLifetimePayoutSnapshot({
|
||||||
|
finance,
|
||||||
|
formatMoney,
|
||||||
|
}: {
|
||||||
|
finance: AdminDashboardLifetimeFinance;
|
||||||
|
formatMoney: MoneyFormatter;
|
||||||
|
}): 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;
|
||||||
|
|
||||||
|
if (bet <= 0 && !hasPayout) {
|
||||||
|
return <DashboardChartEmpty message={t("platformNoFinanceActivity")} compact />;
|
||||||
|
}
|
||||||
|
|
||||||
|
const cells = [
|
||||||
|
{ key: "bet", label: t("platformBetTotal"), amount: bet, emphasize: bet > 0 },
|
||||||
|
{ key: "win", label: t("winPayout"), amount: win, emphasize: win > 0 },
|
||||||
|
{ key: "jackpot", label: t("jackpotPayout"), amount: jackpot, emphasize: jackpot > 0 },
|
||||||
|
] as const;
|
||||||
|
|
||||||
|
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>
|
||||||
|
{!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")}
|
||||||
|
</p>
|
||||||
|
) : null}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
export function SettlementStatusChart({
|
export function SettlementStatusChart({
|
||||||
finance,
|
finance,
|
||||||
}: {
|
}: {
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import { useTranslation } from "react-i18next";
|
|||||||
|
|
||||||
import { getAdminJackpotContributions, getAdminJackpotPayoutLogs } from "@/api/admin-jackpot";
|
import { getAdminJackpotContributions, getAdminJackpotPayoutLogs } from "@/api/admin-jackpot";
|
||||||
import { AdminListPaginationFooter } from "@/components/admin/admin-list-pagination-footer";
|
import { AdminListPaginationFooter } from "@/components/admin/admin-list-pagination-footer";
|
||||||
|
import { AdminPlayerIdentityCells, AdminPlayerIdentityHeads } from "@/components/admin/admin-player-identity-columns";
|
||||||
import { AdminTableExportButton } from "@/components/admin/admin-table-export-button";
|
import { AdminTableExportButton } from "@/components/admin/admin-table-export-button";
|
||||||
import { ModuleScaffold } from "@/components/admin/module-scaffold";
|
import { ModuleScaffold } from "@/components/admin/module-scaffold";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
@@ -289,7 +290,7 @@ export function JackpotRecordsConsole({ embedded = false }: JackpotRecordsConsol
|
|||||||
<TableHead className="w-16 whitespace-nowrap">{t("table.id", { ns: "common" })}</TableHead>
|
<TableHead className="w-16 whitespace-nowrap">{t("table.id", { ns: "common" })}</TableHead>
|
||||||
<TableHead className="whitespace-nowrap">{t("drawNo")}</TableHead>
|
<TableHead className="whitespace-nowrap">{t("drawNo")}</TableHead>
|
||||||
<TableHead className="whitespace-nowrap">{t("ticketNo")}</TableHead>
|
<TableHead className="whitespace-nowrap">{t("ticketNo")}</TableHead>
|
||||||
<TableHead className="max-w-[10rem] whitespace-nowrap">{t("player")}</TableHead>
|
<AdminPlayerIdentityHeads />
|
||||||
<TableHead className="whitespace-nowrap text-right">{t("contributionAmount")}</TableHead>
|
<TableHead className="whitespace-nowrap text-right">{t("contributionAmount")}</TableHead>
|
||||||
<TableHead className="whitespace-nowrap">{t("time")}</TableHead>
|
<TableHead className="whitespace-nowrap">{t("time")}</TableHead>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
@@ -297,7 +298,7 @@ export function JackpotRecordsConsole({ embedded = false }: JackpotRecordsConsol
|
|||||||
<TableBody>
|
<TableBody>
|
||||||
{(contribs?.items ?? []).length === 0 ? (
|
{(contribs?.items ?? []).length === 0 ? (
|
||||||
<TableRow>
|
<TableRow>
|
||||||
<TableCell colSpan={6} className="py-10 text-center text-muted-foreground">
|
<TableCell colSpan={8} className="py-10 text-center text-muted-foreground">
|
||||||
{t("states.noData", { ns: "common" })}
|
{t("states.noData", { ns: "common" })}
|
||||||
</TableCell>
|
</TableCell>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
@@ -307,9 +308,7 @@ export function JackpotRecordsConsole({ embedded = false }: JackpotRecordsConsol
|
|||||||
<TableCell className="font-mono text-xs">{r.id}</TableCell>
|
<TableCell className="font-mono text-xs">{r.id}</TableCell>
|
||||||
<TableCell className="font-mono text-xs">{r.draw_no ?? "—"}</TableCell>
|
<TableCell className="font-mono text-xs">{r.draw_no ?? "—"}</TableCell>
|
||||||
<TableCell className="font-mono text-xs">{r.ticket_no ?? "—"}</TableCell>
|
<TableCell className="font-mono text-xs">{r.ticket_no ?? "—"}</TableCell>
|
||||||
<TableCell className="max-w-[10rem] truncate text-xs" title={r.player_username ?? undefined}>
|
<AdminPlayerIdentityCells row={r} />
|
||||||
{r.player_username ?? "—"}
|
|
||||||
</TableCell>
|
|
||||||
<TableCell className="text-right font-mono text-xs tabular-nums">
|
<TableCell className="text-right font-mono text-xs tabular-nums">
|
||||||
{formatAdminMinorUnits(r.contribution_amount, r.currency_code ?? "NPR")}
|
{formatAdminMinorUnits(r.contribution_amount, r.currency_code ?? "NPR")}
|
||||||
</TableCell>
|
</TableCell>
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import {
|
|||||||
postAdminRejectSettlementBatch,
|
postAdminRejectSettlementBatch,
|
||||||
} from "@/api/admin-settlement";
|
} from "@/api/admin-settlement";
|
||||||
import { AdminListPaginationFooter } from "@/components/admin/admin-list-pagination-footer";
|
import { AdminListPaginationFooter } from "@/components/admin/admin-list-pagination-footer";
|
||||||
|
import { AdminPlayerIdentityCells, AdminPlayerIdentityHeads } from "@/components/admin/admin-player-identity-columns";
|
||||||
import { AdminStatusBadge } from "@/components/admin/admin-status-badge";
|
import { AdminStatusBadge } from "@/components/admin/admin-status-badge";
|
||||||
import { ModuleScaffold } from "@/components/admin/module-scaffold";
|
import { ModuleScaffold } from "@/components/admin/module-scaffold";
|
||||||
import { Button, buttonVariants } from "@/components/ui/button";
|
import { Button, buttonVariants } from "@/components/ui/button";
|
||||||
@@ -336,7 +337,7 @@ export function SettlementBatchDetailsConsole({ batchId }: Props) {
|
|||||||
<TableRow>
|
<TableRow>
|
||||||
<TableHead>{t("ticketNo")}</TableHead>
|
<TableHead>{t("ticketNo")}</TableHead>
|
||||||
<TableHead>{t("playCode")}</TableHead>
|
<TableHead>{t("playCode")}</TableHead>
|
||||||
<TableHead>{t("player")}</TableHead>
|
<AdminPlayerIdentityHeads />
|
||||||
<TableHead>{t("matchedTier")}</TableHead>
|
<TableHead>{t("matchedTier")}</TableHead>
|
||||||
<TableHead className="text-center">{t("regularPayout")}</TableHead>
|
<TableHead className="text-center">{t("regularPayout")}</TableHead>
|
||||||
<TableHead className="text-center">{t("jackpot")}</TableHead>
|
<TableHead className="text-center">{t("jackpot")}</TableHead>
|
||||||
@@ -347,9 +348,7 @@ export function SettlementBatchDetailsConsole({ batchId }: Props) {
|
|||||||
<TableRow key={r.id}>
|
<TableRow key={r.id}>
|
||||||
<TableCell className="font-mono text-xs">{r.ticket_no ?? "—"}</TableCell>
|
<TableCell className="font-mono text-xs">{r.ticket_no ?? "—"}</TableCell>
|
||||||
<TableCell className="text-xs">{playCodeLabel(r.play_code)}</TableCell>
|
<TableCell className="text-xs">{playCodeLabel(r.play_code)}</TableCell>
|
||||||
<TableCell className="max-w-[10rem] truncate text-xs">
|
<AdminPlayerIdentityCells row={r} />
|
||||||
{r.player_username ?? r.site_player_id ?? r.player_id ?? "—"}
|
|
||||||
</TableCell>
|
|
||||||
<TableCell className="text-xs">{r.matched_prize_tier ?? "—"}</TableCell>
|
<TableCell className="text-xs">{r.matched_prize_tier ?? "—"}</TableCell>
|
||||||
<TableCell className="text-center font-mono text-xs tabular-nums">
|
<TableCell className="text-center font-mono text-xs tabular-nums">
|
||||||
{formatAdminMinorUnits(r.win_amount, r.currency_code ?? batchCurrency)}
|
{formatAdminMinorUnits(r.win_amount, r.currency_code ?? batchCurrency)}
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import { getAdminTicketItems } from "@/api/admin-tickets";
|
|||||||
import { useAdminSiteCodeOptions } from "@/hooks/use-admin-site-code-options";
|
import { useAdminSiteCodeOptions } from "@/hooks/use-admin-site-code-options";
|
||||||
import { AdminDateRangeField } from "@/components/admin/admin-date-range-field";
|
import { AdminDateRangeField } from "@/components/admin/admin-date-range-field";
|
||||||
import { AdminListPaginationFooter } from "@/components/admin/admin-list-pagination-footer";
|
import { AdminListPaginationFooter } from "@/components/admin/admin-list-pagination-footer";
|
||||||
|
import { AdminPlayerIdentityCells, AdminPlayerIdentityHeads } from "@/components/admin/admin-player-identity-columns";
|
||||||
import { AdminTableExportButton } from "@/components/admin/admin-table-export-button";
|
import { AdminTableExportButton } from "@/components/admin/admin-table-export-button";
|
||||||
import { AdminStatusBadge } from "@/components/admin/admin-status-badge";
|
import { AdminStatusBadge } from "@/components/admin/admin-status-badge";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
@@ -354,7 +355,7 @@ export function PlayerTicketsConsole(): React.ReactElement {
|
|||||||
<TableHeader>
|
<TableHeader>
|
||||||
<TableRow>
|
<TableRow>
|
||||||
<TableHead>{t("ticketNo")}</TableHead>
|
<TableHead>{t("ticketNo")}</TableHead>
|
||||||
<TableHead>{t("player")}</TableHead>
|
<AdminPlayerIdentityHeads />
|
||||||
<TableHead>{t("orderNo")}</TableHead>
|
<TableHead>{t("orderNo")}</TableHead>
|
||||||
<TableHead>{t("drawNo")}</TableHead>
|
<TableHead>{t("drawNo")}</TableHead>
|
||||||
<TableHead>{t("playCode")}</TableHead>
|
<TableHead>{t("playCode")}</TableHead>
|
||||||
@@ -371,7 +372,7 @@ export function PlayerTicketsConsole(): React.ReactElement {
|
|||||||
<TableBody>
|
<TableBody>
|
||||||
{data.items.length === 0 ? (
|
{data.items.length === 0 ? (
|
||||||
<TableRow>
|
<TableRow>
|
||||||
<TableCell colSpan={13} className="text-muted-foreground">
|
<TableCell colSpan={15} className="text-muted-foreground">
|
||||||
{t("states.noData", { ns: "common" })}
|
{t("states.noData", { ns: "common" })}
|
||||||
</TableCell>
|
</TableCell>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
@@ -380,22 +381,10 @@ export function PlayerTicketsConsole(): React.ReactElement {
|
|||||||
const winLabel = row.jackpot_win_amount > 0
|
const winLabel = row.jackpot_win_amount > 0
|
||||||
? `${row.win_amount_formatted} + ${row.jackpot_win_amount_formatted}`
|
? `${row.win_amount_formatted} + ${row.jackpot_win_amount_formatted}`
|
||||||
: row.win_amount_formatted;
|
: row.win_amount_formatted;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<TableRow key={row.ticket_no}>
|
<TableRow key={row.ticket_no}>
|
||||||
<TableCell className="font-mono text-xs">{row.ticket_no}</TableCell>
|
<TableCell className="font-mono text-xs">{row.ticket_no}</TableCell>
|
||||||
<TableCell className="text-xs">
|
<AdminPlayerIdentityCells row={row} />
|
||||||
<div className="flex flex-col leading-tight">
|
|
||||||
<span className="font-medium">
|
|
||||||
{row.nickname ?? row.username ?? "—"}
|
|
||||||
</span>
|
|
||||||
<span className="font-mono text-[11px] text-muted-foreground">
|
|
||||||
{row.site_code && row.site_player_id
|
|
||||||
? `${row.site_code} / ${row.site_player_id}`
|
|
||||||
: row.site_player_id ?? `#${row.player_id}`}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</TableCell>
|
|
||||||
<TableCell className="font-mono text-xs">{row.order_no ?? "—"}</TableCell>
|
<TableCell className="font-mono text-xs">{row.order_no ?? "—"}</TableCell>
|
||||||
<TableCell className="font-mono text-xs">{row.draw_no ?? "—"}</TableCell>
|
<TableCell className="font-mono text-xs">{row.draw_no ?? "—"}</TableCell>
|
||||||
<TableCell className="text-xs">{playCodeLabel(row.play_code)}</TableCell>
|
<TableCell className="text-xs">{playCodeLabel(row.play_code)}</TableCell>
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import {
|
|||||||
} from "@/api/admin-wallet";
|
} from "@/api/admin-wallet";
|
||||||
import { AdminDateRangeField } from "@/components/admin/admin-date-range-field";
|
import { AdminDateRangeField } from "@/components/admin/admin-date-range-field";
|
||||||
import { AdminListPaginationFooter } from "@/components/admin/admin-list-pagination-footer";
|
import { AdminListPaginationFooter } from "@/components/admin/admin-list-pagination-footer";
|
||||||
|
import { AdminPlayerIdentityCells, AdminPlayerIdentityHeads } from "@/components/admin/admin-player-identity-columns";
|
||||||
import { AdminRowActionsMenu } from "@/components/admin/admin-row-actions-menu";
|
import { AdminRowActionsMenu } from "@/components/admin/admin-row-actions-menu";
|
||||||
import { AdminTableExportButton } from "@/components/admin/admin-table-export-button";
|
import { AdminTableExportButton } from "@/components/admin/admin-table-export-button";
|
||||||
import { AdminStatusBadge } from "@/components/admin/admin-status-badge";
|
import { AdminStatusBadge } from "@/components/admin/admin-status-badge";
|
||||||
@@ -542,7 +543,7 @@ export function TransferOrdersPanel(): React.ReactElement {
|
|||||||
<TableRow>
|
<TableRow>
|
||||||
<TableHead className="min-w-0 max-w-[14rem]">{t("localTransferNo")}</TableHead>
|
<TableHead className="min-w-0 max-w-[14rem]">{t("localTransferNo")}</TableHead>
|
||||||
<TableHead className="min-w-0 max-w-[12rem]">{t("externalRefNo")}</TableHead>
|
<TableHead className="min-w-0 max-w-[12rem]">{t("externalRefNo")}</TableHead>
|
||||||
<TableHead className="whitespace-nowrap">{t("playerAccount")}</TableHead>
|
<AdminPlayerIdentityHeads />
|
||||||
<TableHead className="w-14">{t("direction")}</TableHead>
|
<TableHead className="w-14">{t("direction")}</TableHead>
|
||||||
<TableHead className="whitespace-nowrap">{t("amount")}</TableHead>
|
<TableHead className="whitespace-nowrap">{t("amount")}</TableHead>
|
||||||
<TableHead className="whitespace-nowrap">{t("status")}</TableHead>
|
<TableHead className="whitespace-nowrap">{t("status")}</TableHead>
|
||||||
@@ -555,7 +556,7 @@ export function TransferOrdersPanel(): React.ReactElement {
|
|||||||
<TableBody>
|
<TableBody>
|
||||||
{data.items.length === 0 ? (
|
{data.items.length === 0 ? (
|
||||||
<TableRow>
|
<TableRow>
|
||||||
<TableCell colSpan={10} className="text-muted-foreground">
|
<TableCell colSpan={12} className="text-muted-foreground">
|
||||||
{t("states.noData", { ns: "common" })}
|
{t("states.noData", { ns: "common" })}
|
||||||
</TableCell>
|
</TableCell>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
@@ -568,13 +569,7 @@ export function TransferOrdersPanel(): React.ReactElement {
|
|||||||
<TableCell className="min-w-0 max-w-[12rem] align-top whitespace-normal">
|
<TableCell className="min-w-0 max-w-[12rem] align-top whitespace-normal">
|
||||||
<CellMonoId value={row.external_ref_no} copyHint={t("copyExternalRefNo")} />
|
<CellMonoId value={row.external_ref_no} copyHint={t("copyExternalRefNo")} />
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell className="text-xs">
|
<AdminPlayerIdentityCells row={row} />
|
||||||
#{row.player_id}
|
|
||||||
<br />
|
|
||||||
<span className="text-muted-foreground">
|
|
||||||
{row.site_player_id ?? row.username ?? "—"}
|
|
||||||
</span>
|
|
||||||
</TableCell>
|
|
||||||
<TableCell>{row.direction}</TableCell>
|
<TableCell>{row.direction}</TableCell>
|
||||||
<TableCell className="tabular-nums">
|
<TableCell className="tabular-nums">
|
||||||
{formatAdminMinorUnits(row.amount, row.currency_code)}
|
{formatAdminMinorUnits(row.amount, row.currency_code)}
|
||||||
@@ -852,7 +847,7 @@ export function WalletTxnsPanel(): React.ReactElement {
|
|||||||
<TableRow>
|
<TableRow>
|
||||||
<TableHead className="min-w-0 max-w-[14rem]">{t("txnNo")}</TableHead>
|
<TableHead className="min-w-0 max-w-[14rem]">{t("txnNo")}</TableHead>
|
||||||
<TableHead className="min-w-0 max-w-[12rem]">{t("externalRefNo")}</TableHead>
|
<TableHead className="min-w-0 max-w-[12rem]">{t("externalRefNo")}</TableHead>
|
||||||
<TableHead className="whitespace-nowrap">{t("playerAccount")}</TableHead>
|
<AdminPlayerIdentityHeads />
|
||||||
<TableHead className="whitespace-nowrap">{t("type")}</TableHead>
|
<TableHead className="whitespace-nowrap">{t("type")}</TableHead>
|
||||||
<TableHead className="whitespace-nowrap">{t("amount")}</TableHead>
|
<TableHead className="whitespace-nowrap">{t("amount")}</TableHead>
|
||||||
<TableHead className="whitespace-nowrap">{t("status")}</TableHead>
|
<TableHead className="whitespace-nowrap">{t("status")}</TableHead>
|
||||||
@@ -863,7 +858,7 @@ export function WalletTxnsPanel(): React.ReactElement {
|
|||||||
<TableBody>
|
<TableBody>
|
||||||
{data.items.length === 0 ? (
|
{data.items.length === 0 ? (
|
||||||
<TableRow>
|
<TableRow>
|
||||||
<TableCell colSpan={8} className="text-muted-foreground">
|
<TableCell colSpan={10} className="text-muted-foreground">
|
||||||
{t("states.noData", { ns: "common" })}
|
{t("states.noData", { ns: "common" })}
|
||||||
</TableCell>
|
</TableCell>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
@@ -876,13 +871,7 @@ export function WalletTxnsPanel(): React.ReactElement {
|
|||||||
<TableCell className="min-w-0 max-w-[12rem] align-top whitespace-normal">
|
<TableCell className="min-w-0 max-w-[12rem] align-top whitespace-normal">
|
||||||
<CellMonoId value={row.external_ref_no} copyHint={t("copyExternalTxnRefNo")} />
|
<CellMonoId value={row.external_ref_no} copyHint={t("copyExternalTxnRefNo")} />
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell className="min-w-0 text-xs">
|
<AdminPlayerIdentityCells row={row} />
|
||||||
#{row.player_id}
|
|
||||||
<br />
|
|
||||||
<span className="text-muted-foreground">
|
|
||||||
{row.site_player_id ?? row.username ?? "—"}
|
|
||||||
</span>
|
|
||||||
</TableCell>
|
|
||||||
<TableCell className="min-w-0 text-xs">{row.biz_type}</TableCell>
|
<TableCell className="min-w-0 text-xs">{row.biz_type}</TableCell>
|
||||||
<TableCell className="tabular-nums text-xs">
|
<TableCell className="tabular-nums text-xs">
|
||||||
{row.amount} ({row.direction === 1 ? t("in") : t("out")})
|
{row.amount} ({row.direction === 1 ? t("in") : t("out")})
|
||||||
|
|||||||
@@ -53,10 +53,19 @@ export type AdminDashboardCapabilities = {
|
|||||||
export type AdminDashboardResultBatchQueue = {
|
export type AdminDashboardResultBatchQueue = {
|
||||||
pending_review_total: number;
|
pending_review_total: number;
|
||||||
pending_draw_count: number;
|
pending_draw_count: number;
|
||||||
|
published_total: number;
|
||||||
|
batch_total: number;
|
||||||
first_pending_draw_id: number | null;
|
first_pending_draw_id: number | null;
|
||||||
first_pending_batch_id: number | null;
|
first_pending_batch_id: number | null;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/** 全站风险池占用汇总 */
|
||||||
|
export type AdminDashboardPlatformRisk = {
|
||||||
|
locked_amount: number;
|
||||||
|
cap_amount: number;
|
||||||
|
usage_percent: number;
|
||||||
|
};
|
||||||
|
|
||||||
/** 按业务日汇总的今日投注/派彩/盈亏(与报表 daily-profit 口径一致) */
|
/** 按业务日汇总的今日投注/派彩/盈亏(与报表 daily-profit 口径一致) */
|
||||||
export type AdminDashboardTodayFinance = {
|
export type AdminDashboardTodayFinance = {
|
||||||
business_date: string;
|
business_date: string;
|
||||||
@@ -70,8 +79,12 @@ export type AdminDashboardTodayFinance = {
|
|||||||
export type AdminDashboardLifetimeFinance = {
|
export type AdminDashboardLifetimeFinance = {
|
||||||
currency_code: string | null;
|
currency_code: string | null;
|
||||||
total_bet_minor: number;
|
total_bet_minor: number;
|
||||||
|
total_win_minor: number;
|
||||||
|
total_jackpot_minor: number;
|
||||||
total_payout_minor: number;
|
total_payout_minor: number;
|
||||||
approx_house_gross_minor: number;
|
approx_house_gross_minor: number;
|
||||||
|
order_count: number;
|
||||||
|
ticket_item_count: number;
|
||||||
draw_count: number;
|
draw_count: number;
|
||||||
business_day_count: number;
|
business_day_count: number;
|
||||||
date_from: string | null;
|
date_from: string | null;
|
||||||
@@ -87,6 +100,7 @@ export type AdminDashboardData = {
|
|||||||
finance: AdminDrawFinanceSummaryData | null;
|
finance: AdminDrawFinanceSummaryData | null;
|
||||||
draw: AdminDashboardDrawPanel | null;
|
draw: AdminDashboardDrawPanel | null;
|
||||||
risk: AdminDashboardRiskSnapshot | null;
|
risk: AdminDashboardRiskSnapshot | null;
|
||||||
|
platform_risk: AdminDashboardPlatformRisk | null;
|
||||||
result_batch_queue: AdminDashboardResultBatchQueue | null;
|
result_batch_queue: AdminDashboardResultBatchQueue | null;
|
||||||
abnormal_transfer_total: number | null;
|
abnormal_transfer_total: number | null;
|
||||||
warnings: AdminDashboardWarning[];
|
warnings: AdminDashboardWarning[];
|
||||||
|
|||||||
@@ -47,7 +47,12 @@ export type AdminJackpotContributionRow = {
|
|||||||
jackpot_pool_id: number;
|
jackpot_pool_id: number;
|
||||||
currency_code: string | null;
|
currency_code: string | null;
|
||||||
player_id: number;
|
player_id: number;
|
||||||
player_username: string | null;
|
site_code: string | null;
|
||||||
|
site_player_id: string | null;
|
||||||
|
username: string | null;
|
||||||
|
nickname: string | null;
|
||||||
|
/** @deprecated 使用 username / nickname */
|
||||||
|
player_username?: string | null;
|
||||||
ticket_item_id: number | null;
|
ticket_item_id: number | null;
|
||||||
ticket_no: string | null;
|
ticket_no: string | null;
|
||||||
contribution_amount: number;
|
contribution_amount: number;
|
||||||
|
|||||||
@@ -66,8 +66,11 @@ export type AdminSettlementDetailRow = {
|
|||||||
play_code: string | null;
|
play_code: string | null;
|
||||||
currency_code: string | null;
|
currency_code: string | null;
|
||||||
player_id: number | null;
|
player_id: number | null;
|
||||||
player_username: string | null;
|
site_code: string | null;
|
||||||
site_player_id: string | null;
|
site_player_id: string | null;
|
||||||
|
username: string | null;
|
||||||
|
/** @deprecated 使用 username */
|
||||||
|
player_username?: string | null;
|
||||||
matched_prize_tier: string | null;
|
matched_prize_tier: string | null;
|
||||||
win_amount: number;
|
win_amount: number;
|
||||||
jackpot_allocation_amount: number;
|
jackpot_allocation_amount: number;
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ export type AdminWalletTxnItem = {
|
|||||||
site_code: string | null;
|
site_code: string | null;
|
||||||
site_player_id: string | null;
|
site_player_id: string | null;
|
||||||
username: string | null;
|
username: string | null;
|
||||||
|
nickname: string | null;
|
||||||
wallet_id: number;
|
wallet_id: number;
|
||||||
biz_type: string;
|
biz_type: string;
|
||||||
biz_no: string;
|
biz_no: string;
|
||||||
|
|||||||
Reference in New Issue
Block a user