feat(admin, i18n): enhance reports, draws, config, and player workflows
This commit is contained in:
@@ -12,6 +12,7 @@ import { getAdminPlayerTicketItems } from "@/api/admin-player-tickets";
|
||||
import { getAdminTransferOrders, getAdminWalletTransactions } from "@/api/admin-wallet";
|
||||
import { AdminListPaginationFooter } from "@/components/admin/admin-list-pagination-footer";
|
||||
import { AdminNoResourceState, AdminTableNoResourceRow } from "@/components/admin/admin-no-resource-state";
|
||||
import { AdminRowActionsMenu } from "@/components/admin/admin-row-actions-menu";
|
||||
import { AdminStatusBadge } from "@/components/admin/admin-status-badge";
|
||||
import { AdminLoadingState, AdminTableLoadingRow } from "@/components/admin/admin-loading-state";
|
||||
import { buttonVariants } from "@/components/ui/button";
|
||||
@@ -42,6 +43,7 @@ import { LotteryApiBizError } from "@/types/api/errors";
|
||||
import type { AdminPlayerRow, AdminPlayerWalletRow } from "@/types/api/admin-player";
|
||||
import type { AdminPlayerTicketItemRow } from "@/types/api/admin-player-tickets";
|
||||
import type { AdminTransferOrderItem, AdminWalletTxnItem } from "@/types/api/admin-wallet";
|
||||
import { Eye } from "lucide-react";
|
||||
|
||||
function playerStatusLabel(status: number, t: (key: string) => string): string {
|
||||
if (status === 0) return t("statusNormal");
|
||||
@@ -309,6 +311,9 @@ export function PlayerDetailConsole({ playerId }: { playerId: number }) {
|
||||
<ProfileField label={t("authSource")}>
|
||||
{playerAuthSourceLabel(player, t)}
|
||||
</ProfileField>
|
||||
<ProfileField label={t("riskTags", { defaultValue: "风控标签" })}>
|
||||
{player.risk_tags && player.risk_tags.length > 0 ? player.risk_tags.join(", ") : "—"}
|
||||
</ProfileField>
|
||||
<ProfileField label={t("status")}>
|
||||
<PlayerStatusBadge status={player.status} t={t} />
|
||||
</ProfileField>
|
||||
@@ -408,7 +413,10 @@ export function PlayerDetailConsole({ playerId }: { playerId: number }) {
|
||||
<TabsContent value="tickets" className="mt-0">
|
||||
<Card className="admin-list-card">
|
||||
<CardHeader className="admin-list-header">
|
||||
<CardTitle className="admin-list-title">{t("tabTickets")}</CardTitle>
|
||||
<div className="space-y-1">
|
||||
<CardTitle className="admin-list-title">{t("tabTickets")}</CardTitle>
|
||||
<p className="text-sm text-muted-foreground">{t("ticketTableHint")}</p>
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent className="admin-list-content">
|
||||
<div className="admin-table-shell">
|
||||
@@ -416,21 +424,29 @@ export function PlayerDetailConsole({ playerId }: { playerId: number }) {
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead>{t("ticketNo", { ns: "tickets" })}</TableHead>
|
||||
<TableHead>{t("orderNo", { ns: "tickets" })}</TableHead>
|
||||
<TableHead>{t("drawNo", { ns: "tickets" })}</TableHead>
|
||||
<TableHead>{t("playCode", { ns: "tickets" })}</TableHead>
|
||||
<TableHead>{t("number", { ns: "tickets" })}</TableHead>
|
||||
<TableHead className="text-center">{t("actualDeduct", { ns: "tickets" })}</TableHead>
|
||||
<TableHead>{t("status", { ns: "tickets" })}</TableHead>
|
||||
<TableHead>{t("failReason", { ns: "tickets" })}</TableHead>
|
||||
<TableHead className="text-center">{t("winAmount", { ns: "tickets" })}</TableHead>
|
||||
<TableHead>{t("placedAt", { ns: "tickets" })}</TableHead>
|
||||
<TableHead>{t("updatedAt", { ns: "tickets" })}</TableHead>
|
||||
<TableHead className="sticky right-0 z-20 w-12 bg-muted text-center shadow-[-1px_0_0_rgba(203,213,225,0.7)]">
|
||||
{t("table.actions", { ns: "common" })}
|
||||
</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{ticketsLoading && tickets.length === 0 ? (
|
||||
<AdminTableLoadingRow colSpan={7} />
|
||||
<AdminTableLoadingRow colSpan={11} />
|
||||
) : null}
|
||||
{tickets.map((row) => (
|
||||
<TableRow key={row.ticket_no}>
|
||||
<TableCell className="font-mono text-xs">{row.ticket_no}</TableCell>
|
||||
<TableCell className="font-mono text-xs">{row.order_no ?? "—"}</TableCell>
|
||||
<TableCell className="font-mono text-xs">{row.draw_no ?? "—"}</TableCell>
|
||||
<TableCell className="text-xs">{playCodeLabel(row.play_code)}</TableCell>
|
||||
<TableCell className="font-mono text-xs">{row.original_number ?? "—"}</TableCell>
|
||||
@@ -442,13 +458,36 @@ export function PlayerDetailConsole({ playerId }: { playerId: number }) {
|
||||
{ticketStatusText(row.status, t)}
|
||||
</AdminStatusBadge>
|
||||
</TableCell>
|
||||
<TableCell className="max-w-[14rem] text-xs text-muted-foreground">
|
||||
{row.fail_reason_text ?? row.fail_reason_code ?? "—"}
|
||||
</TableCell>
|
||||
<TableCell className="text-center text-xs tabular-nums">
|
||||
{row.jackpot_win_amount_minor > 0
|
||||
? `${row.win_amount_formatted} + ${row.jackpot_win_amount_formatted}`
|
||||
: row.win_amount_formatted}
|
||||
</TableCell>
|
||||
<TableCell className="text-xs text-muted-foreground">
|
||||
{row.placed_at ? formatDt(row.placed_at) : "—"}
|
||||
</TableCell>
|
||||
<TableCell className="text-xs text-muted-foreground">
|
||||
{row.updated_at ? formatDt(row.updated_at) : "—"}
|
||||
</TableCell>
|
||||
<TableCell className="sticky right-0 z-10 bg-card text-center shadow-[-1px_0_0_rgba(203,213,225,0.7)]">
|
||||
<AdminRowActionsMenu
|
||||
actions={[
|
||||
{
|
||||
key: "view-ticket-in-list",
|
||||
label: t("viewTicketInList", { ns: "tickets" }),
|
||||
icon: Eye,
|
||||
href: `/admin/tickets?player_id=${player.id}&number=${encodeURIComponent(row.ticket_no)}${row.draw_no ? `&draw_no=${encodeURIComponent(row.draw_no)}` : ""}`,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
{!ticketsLoading && tickets.length === 0 ? (
|
||||
<AdminTableNoResourceRow colSpan={7} className="text-muted-foreground" />
|
||||
<AdminTableNoResourceRow colSpan={11} className="text-muted-foreground" />
|
||||
) : null}
|
||||
</TableBody>
|
||||
</Table>
|
||||
|
||||
Reference in New Issue
Block a user