feat(integration): 为集成站点与开奖管理新增 AdminPermissionGate 权限控制
使用 AdminPermissionGate 包裹集成站点与开奖相关组件,根据权限进行访问控制。 新增集成管理与开奖管理相关权限常量。 更新相关 UI 组件以适配权限校验逻辑,提升系统安全性与用户体验。 增强国际化支持,在英文、尼泊尔语与中文语言包中新增集成相关文案。
This commit is contained in:
@@ -1,6 +1,8 @@
|
|||||||
|
import { AdminPermissionGate } from "@/components/admin/admin-permission-gate";
|
||||||
import { ModuleScaffold } from "@/components/admin/module-scaffold";
|
import { ModuleScaffold } from "@/components/admin/module-scaffold";
|
||||||
import { IntegrationSitesConsole } from "@/modules/integration/integration-sites-console";
|
import { IntegrationSitesConsole } from "@/modules/integration/integration-sites-console";
|
||||||
import { buildPageMetadata } from "@/lib/page-metadata";
|
import { buildPageMetadata } from "@/lib/page-metadata";
|
||||||
|
import { PRD_INTEGRATION_ACCESS_ANY } from "@/lib/admin-prd";
|
||||||
import type { Metadata } from "next";
|
import type { Metadata } from "next";
|
||||||
|
|
||||||
export const metadata: Metadata = buildPageMetadata("config", "integrationSites.title");
|
export const metadata: Metadata = buildPageMetadata("config", "integrationSites.title");
|
||||||
@@ -8,7 +10,9 @@ export const metadata: Metadata = buildPageMetadata("config", "integrationSites.
|
|||||||
export default function AdminIntegrationSitesPage() {
|
export default function AdminIntegrationSitesPage() {
|
||||||
return (
|
return (
|
||||||
<ModuleScaffold>
|
<ModuleScaffold>
|
||||||
<IntegrationSitesConsole />
|
<AdminPermissionGate requiredAny={PRD_INTEGRATION_ACCESS_ANY}>
|
||||||
|
<IntegrationSitesConsole />
|
||||||
|
</AdminPermissionGate>
|
||||||
</ModuleScaffold>
|
</ModuleScaffold>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
|
import { AdminPermissionGate } from "@/components/admin/admin-permission-gate";
|
||||||
import { DrawFinanceConsole } from "@/modules/draws/draw-finance-console";
|
import { DrawFinanceConsole } from "@/modules/draws/draw-finance-console";
|
||||||
|
import { PRD_DRAW_ACCESS_ANY } from "@/lib/admin-prd";
|
||||||
import { buildPageMetadata } from "@/lib/page-metadata";
|
import { buildPageMetadata } from "@/lib/page-metadata";
|
||||||
import type { Metadata } from "next";
|
import type { Metadata } from "next";
|
||||||
|
|
||||||
@@ -8,5 +10,9 @@ export default async function AdminDrawFinancePage(props: {
|
|||||||
params: Promise<{ drawId: string }>;
|
params: Promise<{ drawId: string }>;
|
||||||
}) {
|
}) {
|
||||||
const { drawId } = await props.params;
|
const { drawId } = await props.params;
|
||||||
return <DrawFinanceConsole drawId={drawId} />;
|
return (
|
||||||
|
<AdminPermissionGate requiredAny={PRD_DRAW_ACCESS_ANY}>
|
||||||
|
<DrawFinanceConsole drawId={drawId} />
|
||||||
|
</AdminPermissionGate>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,14 @@
|
|||||||
|
import { AdminPermissionGate } from "@/components/admin/admin-permission-gate";
|
||||||
import { DrawDetailConsole } from "@/modules/draws/draw-detail-console";
|
import { DrawDetailConsole } from "@/modules/draws/draw-detail-console";
|
||||||
|
import { PRD_DRAW_ACCESS_ANY } from "@/lib/admin-prd";
|
||||||
|
|
||||||
export default async function AdminDrawDetailPage(props: {
|
export default async function AdminDrawDetailPage(props: {
|
||||||
params: Promise<{ drawId: string }>;
|
params: Promise<{ drawId: string }>;
|
||||||
}) {
|
}) {
|
||||||
const { drawId } = await props.params;
|
const { drawId } = await props.params;
|
||||||
return <DrawDetailConsole drawId={drawId} />;
|
return (
|
||||||
|
<AdminPermissionGate requiredAny={PRD_DRAW_ACCESS_ANY}>
|
||||||
|
<DrawDetailConsole drawId={drawId} />
|
||||||
|
</AdminPermissionGate>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,14 @@
|
|||||||
|
import { AdminPermissionGate } from "@/components/admin/admin-permission-gate";
|
||||||
import { DrawResultsConsole } from "@/modules/draws/draw-results-console";
|
import { DrawResultsConsole } from "@/modules/draws/draw-results-console";
|
||||||
|
import { PRD_DRAW_ACCESS_ANY } from "@/lib/admin-prd";
|
||||||
|
|
||||||
export default async function AdminDrawResultsPage(props: {
|
export default async function AdminDrawResultsPage(props: {
|
||||||
params: Promise<{ drawId: string }>;
|
params: Promise<{ drawId: string }>;
|
||||||
}) {
|
}) {
|
||||||
const { drawId } = await props.params;
|
const { drawId } = await props.params;
|
||||||
return <DrawResultsConsole drawId={drawId} />;
|
return (
|
||||||
|
<AdminPermissionGate requiredAny={PRD_DRAW_ACCESS_ANY}>
|
||||||
|
<DrawResultsConsole drawId={drawId} />
|
||||||
|
</AdminPermissionGate>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,14 @@
|
|||||||
|
import { AdminPermissionGate } from "@/components/admin/admin-permission-gate";
|
||||||
import { DrawReviewConsole } from "@/modules/draws/draw-review-console";
|
import { DrawReviewConsole } from "@/modules/draws/draw-review-console";
|
||||||
|
import { PRD_DRAW_ACCESS_ANY } from "@/lib/admin-prd";
|
||||||
|
|
||||||
export default async function AdminDrawReviewPage(props: {
|
export default async function AdminDrawReviewPage(props: {
|
||||||
params: Promise<{ drawId: string }>;
|
params: Promise<{ drawId: string }>;
|
||||||
}) {
|
}) {
|
||||||
const { drawId } = await props.params;
|
const { drawId } = await props.params;
|
||||||
return <DrawReviewConsole drawId={drawId} />;
|
return (
|
||||||
|
<AdminPermissionGate requiredAny={PRD_DRAW_ACCESS_ANY}>
|
||||||
|
<DrawReviewConsole drawId={drawId} />
|
||||||
|
</AdminPermissionGate>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
import { ModuleScaffold } from "@/components/admin/module-scaffold";
|
import { ModuleScaffold } from "@/components/admin/module-scaffold";
|
||||||
|
import { AdminPermissionGate } from "@/components/admin/admin-permission-gate";
|
||||||
import { DrawsIndexConsole } from "@/modules/draws/draws-index-console";
|
import { DrawsIndexConsole } from "@/modules/draws/draws-index-console";
|
||||||
|
import { PRD_DRAW_ACCESS_ANY } from "@/lib/admin-prd";
|
||||||
import { buildPageMetadata } from "@/lib/page-metadata";
|
import { buildPageMetadata } from "@/lib/page-metadata";
|
||||||
import type { Metadata } from "next";
|
import type { Metadata } from "next";
|
||||||
|
|
||||||
@@ -7,8 +9,10 @@ export const metadata: Metadata = buildPageMetadata("draws", "statusListTitle");
|
|||||||
|
|
||||||
export default function AdminDrawsPage() {
|
export default function AdminDrawsPage() {
|
||||||
return (
|
return (
|
||||||
<ModuleScaffold>
|
<AdminPermissionGate requiredAny={PRD_DRAW_ACCESS_ANY}>
|
||||||
<DrawsIndexConsole />
|
<ModuleScaffold>
|
||||||
</ModuleScaffold>
|
<DrawsIndexConsole />
|
||||||
|
</ModuleScaffold>
|
||||||
|
</AdminPermissionGate>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
|
import { AdminPermissionGate } from "@/components/admin/admin-permission-gate";
|
||||||
import { InvalidSettlementBatchId } from "@/modules/settlement/invalid-settlement-batch-id";
|
import { InvalidSettlementBatchId } from "@/modules/settlement/invalid-settlement-batch-id";
|
||||||
import { SettlementBatchDetailsConsole } from "@/modules/settlement/settlement-batch-details-console";
|
import { SettlementBatchDetailsConsole } from "@/modules/settlement/settlement-batch-details-console";
|
||||||
|
import { PRD_PAYOUT_ACCESS_ANY } from "@/lib/admin-prd";
|
||||||
import { buildPageMetadata } from "@/lib/page-metadata";
|
import { buildPageMetadata } from "@/lib/page-metadata";
|
||||||
import type { Metadata } from "next";
|
import type { Metadata } from "next";
|
||||||
|
|
||||||
@@ -14,5 +16,9 @@ export default async function AdminSettlementBatchDetailsPage(props: {
|
|||||||
return <InvalidSettlementBatchId />;
|
return <InvalidSettlementBatchId />;
|
||||||
}
|
}
|
||||||
|
|
||||||
return <SettlementBatchDetailsConsole batchId={id} />;
|
return (
|
||||||
|
<AdminPermissionGate requiredAny={PRD_PAYOUT_ACCESS_ANY}>
|
||||||
|
<SettlementBatchDetailsConsole batchId={id} />
|
||||||
|
</AdminPermissionGate>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,15 @@
|
|||||||
|
import { AdminPermissionGate } from "@/components/admin/admin-permission-gate";
|
||||||
import { SettlementBatchesConsole } from "@/modules/settlement/settlement-batches-console";
|
import { SettlementBatchesConsole } from "@/modules/settlement/settlement-batches-console";
|
||||||
|
import { PRD_PAYOUT_ACCESS_ANY } from "@/lib/admin-prd";
|
||||||
import { buildPageMetadata } from "@/lib/page-metadata";
|
import { buildPageMetadata } from "@/lib/page-metadata";
|
||||||
import type { Metadata } from "next";
|
import type { Metadata } from "next";
|
||||||
|
|
||||||
export const metadata: Metadata = buildPageMetadata("settlement", "batchList");
|
export const metadata: Metadata = buildPageMetadata("settlement", "batchList");
|
||||||
|
|
||||||
export default function AdminSettlementBatchesPage() {
|
export default function AdminSettlementBatchesPage() {
|
||||||
return <SettlementBatchesConsole />;
|
return (
|
||||||
|
<AdminPermissionGate requiredAny={PRD_PAYOUT_ACCESS_ANY}>
|
||||||
|
<SettlementBatchesConsole />
|
||||||
|
</AdminPermissionGate>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -40,6 +40,8 @@ const NAV_TRANSLATION_KEYS: Record<string, string> = {
|
|||||||
tickets: "tickets",
|
tickets: "tickets",
|
||||||
audit: "audit",
|
audit: "audit",
|
||||||
settings: "settings",
|
settings: "settings",
|
||||||
|
integration: "integration",
|
||||||
|
config: "config",
|
||||||
};
|
};
|
||||||
|
|
||||||
const RULES_ROUTE_LABELS: Record<string, string> = {
|
const RULES_ROUTE_LABELS: Record<string, string> = {
|
||||||
@@ -55,6 +57,16 @@ const SETTINGS_ROUTE_LABELS: Record<string, string> = {
|
|||||||
currencies: "currencies.title",
|
currencies: "currencies.title",
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const CONFIG_ROUTE_LABELS: Record<string, string> = {
|
||||||
|
"integration-sites": "integrationSites.title",
|
||||||
|
plays: "nav.items.plays",
|
||||||
|
odds: "nav.items.odds",
|
||||||
|
rebate: "nav.items.rebate",
|
||||||
|
jackpot: "nav.items.jackpot",
|
||||||
|
"risk-cap": "nav.items.risk-cap",
|
||||||
|
wallet: "wallet.title",
|
||||||
|
};
|
||||||
|
|
||||||
function titleCase(value: string): string {
|
function titleCase(value: string): string {
|
||||||
return value
|
return value
|
||||||
.split("-")
|
.split("-")
|
||||||
@@ -146,6 +158,11 @@ export function AdminBreadcrumb() {
|
|||||||
ns: "config",
|
ns: "config",
|
||||||
defaultValue: titleCase(subSegment),
|
defaultValue: titleCase(subSegment),
|
||||||
});
|
});
|
||||||
|
} else if (businessSegment === "config" && subSegment) {
|
||||||
|
const key = CONFIG_ROUTE_LABELS[subSegment];
|
||||||
|
subLabel = key
|
||||||
|
? t(key, { ns: "config", defaultValue: titleCase(subSegment) })
|
||||||
|
: titleCase(subSegment);
|
||||||
} else {
|
} else {
|
||||||
subLabel = subSegment
|
subLabel = subSegment
|
||||||
? t(`subnav.${subSegment}`, {
|
? t(`subnav.${subSegment}`, {
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { useCallback, useEffect, useState } from "react";
|
|||||||
|
|
||||||
import { getAdminIntegrationSites } from "@/api/admin-integration-sites";
|
import { getAdminIntegrationSites } from "@/api/admin-integration-sites";
|
||||||
import { adminHasAnyPermission } from "@/lib/admin-permissions";
|
import { adminHasAnyPermission } from "@/lib/admin-permissions";
|
||||||
|
import { PRD_INTEGRATION_ACCESS_ANY } from "@/lib/admin-prd";
|
||||||
import { useAdminProfile } from "@/stores/admin-session";
|
import { useAdminProfile } from "@/stores/admin-session";
|
||||||
|
|
||||||
export type AdminSiteCodeOption = {
|
export type AdminSiteCodeOption = {
|
||||||
@@ -21,10 +22,7 @@ export function useAdminSiteCodeOptions(): {
|
|||||||
reload: () => Promise<void>;
|
reload: () => Promise<void>;
|
||||||
} {
|
} {
|
||||||
const profile = useAdminProfile();
|
const profile = useAdminProfile();
|
||||||
const canLoad = adminHasAnyPermission(profile?.permissions, [
|
const canLoad = adminHasAnyPermission(profile?.permissions, PRD_INTEGRATION_ACCESS_ANY);
|
||||||
"prd.integration.view",
|
|
||||||
"prd.integration.manage",
|
|
||||||
]);
|
|
||||||
|
|
||||||
const [sites, setSites] = useState<AdminSiteCodeOption[]>([]);
|
const [sites, setSites] = useState<AdminSiteCodeOption[]>([]);
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
|
|||||||
@@ -147,7 +147,9 @@
|
|||||||
"tickets": "Ticket list",
|
"tickets": "Ticket list",
|
||||||
"audit": "Audit Logs",
|
"audit": "Audit Logs",
|
||||||
"settings": "Settings",
|
"settings": "Settings",
|
||||||
"account": "Account settings"
|
"account": "Account settings",
|
||||||
|
"integration": "Integration sites",
|
||||||
|
"config": "Operations config"
|
||||||
},
|
},
|
||||||
"sidebar": {
|
"sidebar": {
|
||||||
"workspace": "Workspace"
|
"workspace": "Workspace"
|
||||||
|
|||||||
@@ -147,7 +147,9 @@
|
|||||||
"tickets": "टिकट सूची",
|
"tickets": "टिकट सूची",
|
||||||
"audit": "अडिट लग",
|
"audit": "अडिट लग",
|
||||||
"settings": "सेटिङ",
|
"settings": "सेटिङ",
|
||||||
"account": "खाता सेटिङ"
|
"account": "खाता सेटिङ",
|
||||||
|
"integration": "मुख्य साइट एकीकरण",
|
||||||
|
"config": "सञ्चालन कन्फिगरेसन"
|
||||||
},
|
},
|
||||||
"sidebar": {
|
"sidebar": {
|
||||||
"workspace": "कार्यस्थान"
|
"workspace": "कार्यस्थान"
|
||||||
|
|||||||
@@ -29,7 +29,77 @@
|
|||||||
"jackpotTitle": "Jackpot",
|
"jackpotTitle": "Jackpot",
|
||||||
"jackpotDesc": "पूल प्यारामिटर र लेजर",
|
"jackpotDesc": "पूल प्यारामिटर र लेजर",
|
||||||
"riskCapTitle": "जोखिम क्याप",
|
"riskCapTitle": "जोखिम क्याप",
|
||||||
"riskCapDesc": "नम्बर क्याप र ओगट उपस्थिति"
|
"riskCapDesc": "नम्बर क्याप र ओगट उपस्थिति",
|
||||||
|
"integrationTitle": "मुख्य साइट एकीकरण",
|
||||||
|
"integrationDesc": "site_code, JWT गोप्य, पार्टनर वालेट URL र iframe श्वेतसूची"
|
||||||
|
},
|
||||||
|
"integrationSites": {
|
||||||
|
"title": "मुख्य साइट एकीकरण साइटहरू",
|
||||||
|
"description": "एडमिनमा पार्टनर एकीकरण सेटिङ मिलाउनुहोस्। site_code सिर्जना पछि परिवर्तन गर्न मिल्दैन।",
|
||||||
|
"create": "नयाँ साइट",
|
||||||
|
"edit": "सम्पादन",
|
||||||
|
"save": "बचत",
|
||||||
|
"saving": "बचत हुँदैछ…",
|
||||||
|
"cancel": "रद्द",
|
||||||
|
"copy": "प्रतिलिपि",
|
||||||
|
"loading": "लोड हुँदैछ…",
|
||||||
|
"empty": "कुनै एकीकरण साइट छैन",
|
||||||
|
"loadFailed": "एकीकरण साइट लोड असफल",
|
||||||
|
"saveFailed": "बचत असफल",
|
||||||
|
"createSuccess": "साइट {{code}} सिर्जना भयो",
|
||||||
|
"updateSuccess": "साइट {{code}} अद्यावधिक भयो",
|
||||||
|
"connectivityTest": "जडान परीक्षण",
|
||||||
|
"connectivityTitle": "पार्टनर वालेट जडान परीक्षण",
|
||||||
|
"connectivityDescription": "परीक्षण खेलाडीबाट साइट {{code}} को balance API कल गर्नुहोस्।",
|
||||||
|
"connectivityPlayerId": "परीक्षण site_player_id",
|
||||||
|
"connectivityRun": "परीक्षण सुरु",
|
||||||
|
"connectivityRunning": "परीक्षण हुँदैछ…",
|
||||||
|
"connectivitySuccess": "जडान सफल",
|
||||||
|
"connectivityFailed": "जडान असफल",
|
||||||
|
"exportParams": "प्यारामिटर निर्यात",
|
||||||
|
"exportSuccess": "{{code}} को प्यारामिटर चिट्ठा निर्यात भयो",
|
||||||
|
"exportFailed": "निर्यात असफल",
|
||||||
|
"rotateSecrets": "गोप्य कुञ्जी पुनः सिर्जना",
|
||||||
|
"rotateSuccess": "साइट {{code}} का गोप्य कुञ्जी पुनः सिर्जना भयो",
|
||||||
|
"rotateFailed": "गोप्य कुञ्जी पुनः सिर्जना असफल",
|
||||||
|
"rotateConfirmTitle": "गोप्य कुञ्जी पुनः सिर्जना गर्ने?",
|
||||||
|
"rotateConfirmDescription": "साइट {{code}} का नयाँ SSO र वालेट कुञ्जी सिर्जना हुन्छ। पुराना कुञ्जी तुरुन्त अमान्य हुन्छन्।",
|
||||||
|
"rotateConfirm": "पुष्टि",
|
||||||
|
"secretsTitle": "गोप्य कुञ्जी अहिले नै सुरक्षित राख्नुहोस्",
|
||||||
|
"secretsDescription": "साइट {{code}} का गोप्य कुञ्जी एक पटक मात्र देखिन्छ।",
|
||||||
|
"secretsDismiss": "सुरक्षित गरिसके",
|
||||||
|
"copied": "{{field}} प्रतिलिपि भयो",
|
||||||
|
"copyFailed": "प्रतिलिपि असफल",
|
||||||
|
"noPermission": "एकीकरण साइट हेर्ने अनुमति छैन",
|
||||||
|
"codeImmutable": "site_code सिर्जना पछि परिवर्तन गर्न मिल्दैन",
|
||||||
|
"statusEnabled": "सक्रिय",
|
||||||
|
"statusDisabled": "निष्क्रिय",
|
||||||
|
"dialogCreateTitle": "नयाँ एकीकरण साइट",
|
||||||
|
"dialogEditTitle": "एकीकरण साइट सम्पादन",
|
||||||
|
"dialogDescription": "पार्टनरले अनुकूल URL नभएसम्म पूर्वनिर्धारित वालेट path प्रयोग गर्न सकिन्छ।",
|
||||||
|
"form": {
|
||||||
|
"required": "साइट नाम अनिवार्य छ",
|
||||||
|
"codeRequired": "site_code अनिवार्य छ"
|
||||||
|
},
|
||||||
|
"columns": {
|
||||||
|
"code": "site_code",
|
||||||
|
"name": "नाम",
|
||||||
|
"status": "स्थिति",
|
||||||
|
"walletUrl": "वालेट API",
|
||||||
|
"actions": "कार्य"
|
||||||
|
},
|
||||||
|
"fields": {
|
||||||
|
"code": "site_code",
|
||||||
|
"name": "साइट नाम",
|
||||||
|
"currency": "पूर्वनिर्धारित मुद्रा",
|
||||||
|
"status": "स्थिति",
|
||||||
|
"walletApiUrl": "पार्टनर वालेट आधार URL",
|
||||||
|
"lotteryH5BaseUrl": "लटरी H5 आधार URL (वैकल्पिक)",
|
||||||
|
"iframeOrigins": "iframe श्वेतसूची (प्रति लाइन एक origin)",
|
||||||
|
"notes": "टिप्पणी",
|
||||||
|
"ssoSecret": "SSO गोप्य",
|
||||||
|
"walletApiKey": "वालेट API कुञ्जी"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"versionStatus": {
|
"versionStatus": {
|
||||||
"active": "सक्रिय",
|
"active": "सक्रिय",
|
||||||
|
|||||||
@@ -147,7 +147,9 @@
|
|||||||
"tickets": "注单列表",
|
"tickets": "注单列表",
|
||||||
"audit": "审计日志",
|
"audit": "审计日志",
|
||||||
"settings": "系统设置",
|
"settings": "系统设置",
|
||||||
"account": "账号设置"
|
"account": "账号设置",
|
||||||
|
"integration": "主站接入站点",
|
||||||
|
"config": "运营配置"
|
||||||
},
|
},
|
||||||
"sidebar": {
|
"sidebar": {
|
||||||
"workspace": "工作台"
|
"workspace": "工作台"
|
||||||
|
|||||||
41
src/lib/admin-permission-bundles.ts
Normal file
41
src/lib/admin-permission-bundles.ts
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
import { PRD_INTEGRATION_MANAGE, PRD_INTEGRATION_VIEW } from "@/lib/admin-prd";
|
||||||
|
|
||||||
|
export type AdminPermissionBundleKey = "view" | "manage" | "audit" | "export" | "privilege";
|
||||||
|
|
||||||
|
export type AdminPageKey = "integration-sites";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* “页面权限包”是把运营/管理员能理解的词汇(查看/管理/审核/导出/特权)
|
||||||
|
* 映射到系统真实用的 `prd.*` 权限 slug。
|
||||||
|
*
|
||||||
|
* 目前只落地 integration-sites;其它页面按同样方式逐步接入。
|
||||||
|
*/
|
||||||
|
export const ADMIN_PERMISSION_BUNDLES = {
|
||||||
|
"integration-sites": {
|
||||||
|
view: [PRD_INTEGRATION_VIEW] as const,
|
||||||
|
manage: [PRD_INTEGRATION_MANAGE] as const,
|
||||||
|
audit: [] as const,
|
||||||
|
// 导出接口的资源鉴权仍落在 view/manage,因此这里复用 view。
|
||||||
|
export: [PRD_INTEGRATION_VIEW] as const,
|
||||||
|
privilege: [] as const,
|
||||||
|
},
|
||||||
|
} satisfies Record<AdminPageKey, Record<AdminPermissionBundleKey, readonly string[]>>;
|
||||||
|
|
||||||
|
export const ADMIN_PAGE_REQUIRED_ANY = {
|
||||||
|
"integration-sites": [
|
||||||
|
...ADMIN_PERMISSION_BUNDLES["integration-sites"].view,
|
||||||
|
...ADMIN_PERMISSION_BUNDLES["integration-sites"].manage,
|
||||||
|
] as const,
|
||||||
|
} satisfies Record<AdminPageKey, readonly string[]>;
|
||||||
|
|
||||||
|
export function getAdminPageRequiredAny(page: AdminPageKey): readonly string[] {
|
||||||
|
return ADMIN_PAGE_REQUIRED_ANY[page];
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getAdminPageBundle(
|
||||||
|
page: AdminPageKey,
|
||||||
|
bundle: AdminPermissionBundleKey,
|
||||||
|
): readonly string[] {
|
||||||
|
return ADMIN_PERMISSION_BUNDLES[page][bundle] ?? [];
|
||||||
|
}
|
||||||
|
|
||||||
@@ -10,6 +10,10 @@ export const PRD_PLAYER_FREEZE_MANAGE = "prd.player_freeze.manage" as const;
|
|||||||
|
|
||||||
export const PRD_CURRENCY_MANAGE = "prd.currency.manage" as const;
|
export const PRD_CURRENCY_MANAGE = "prd.currency.manage" as const;
|
||||||
|
|
||||||
|
/** 接入站点(integration-sites) */
|
||||||
|
export const PRD_INTEGRATION_VIEW = "prd.integration.view" as const;
|
||||||
|
export const PRD_INTEGRATION_MANAGE = "prd.integration.manage" as const;
|
||||||
|
|
||||||
export const PRD_WALLET_RECONCILE_MANAGE = "prd.wallet_reconcile.manage" as const;
|
export const PRD_WALLET_RECONCILE_MANAGE = "prd.wallet_reconcile.manage" as const;
|
||||||
export const PRD_WALLET_RECONCILE_VIEW = "prd.wallet_reconcile.view" as const;
|
export const PRD_WALLET_RECONCILE_VIEW = "prd.wallet_reconcile.view" as const;
|
||||||
export const PRD_WALLET_RECONCILE_VIEW_CS = "prd.wallet_reconcile.view_cs" as const;
|
export const PRD_WALLET_RECONCILE_VIEW_CS = "prd.wallet_reconcile.view_cs" as const;
|
||||||
@@ -105,8 +109,25 @@ export const PRD_RULES_ODDS_ACCESS_ANY = [
|
|||||||
PRD_REBATE_VIEW,
|
PRD_REBATE_VIEW,
|
||||||
] as const;
|
] as const;
|
||||||
|
|
||||||
|
/** 开奖页面入口 */
|
||||||
|
export const PRD_DRAW_ACCESS_ANY = [
|
||||||
|
PRD_DRAW_RESULT_VIEW,
|
||||||
|
PRD_DRAW_RESULT_MANAGE,
|
||||||
|
PRD_DRAW_REOPEN_MANAGE,
|
||||||
|
] as const;
|
||||||
|
|
||||||
/** 封顶配置页 */
|
/** 封顶配置页 */
|
||||||
export const PRD_RISK_CAP_ACCESS_ANY = [PRD_RISK_CAP_MANAGE, PRD_RISK_CAP_VIEW] as const;
|
export const PRD_RISK_CAP_ACCESS_ANY = [PRD_RISK_CAP_MANAGE, PRD_RISK_CAP_VIEW] as const;
|
||||||
|
|
||||||
/** Jackpot 配置页 */
|
/** Jackpot 配置页 */
|
||||||
export const PRD_JACKPOT_ACCESS_ANY = [PRD_JACKPOT_MANAGE, PRD_JACKPOT_VIEW] as const;
|
export const PRD_JACKPOT_ACCESS_ANY = [PRD_JACKPOT_MANAGE, PRD_JACKPOT_VIEW] as const;
|
||||||
|
|
||||||
|
/** 派彩 / 结算页面入口 */
|
||||||
|
export const PRD_PAYOUT_ACCESS_ANY = [
|
||||||
|
PRD_PAYOUT_VIEW,
|
||||||
|
PRD_PAYOUT_REVIEW,
|
||||||
|
PRD_PAYOUT_MANAGE,
|
||||||
|
] as const;
|
||||||
|
|
||||||
|
/** 接入站点配置页 */
|
||||||
|
export const PRD_INTEGRATION_ACCESS_ANY = [PRD_INTEGRATION_VIEW, PRD_INTEGRATION_MANAGE] as const;
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import { ChevronRight } from "lucide-react";
|
|||||||
|
|
||||||
import { ModuleScaffold } from "@/components/admin/module-scaffold";
|
import { ModuleScaffold } from "@/components/admin/module-scaffold";
|
||||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
|
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
|
||||||
|
import { PRD_INTEGRATION_ACCESS_ANY } from "@/lib/admin-prd";
|
||||||
import { useAdminProfile } from "@/stores/admin-session";
|
import { useAdminProfile } from "@/stores/admin-session";
|
||||||
import { adminHasAnyPermission } from "@/lib/admin-permissions";
|
import { adminHasAnyPermission } from "@/lib/admin-permissions";
|
||||||
|
|
||||||
@@ -45,7 +46,7 @@ const HUB_CARDS: HubCard[] = [
|
|||||||
href: "/admin/config/integration-sites",
|
href: "/admin/config/integration-sites",
|
||||||
titleKey: "hub.integrationTitle",
|
titleKey: "hub.integrationTitle",
|
||||||
descKey: "hub.integrationDesc",
|
descKey: "hub.integrationDesc",
|
||||||
requiredAny: ["prd.integration.view", "prd.integration.manage"],
|
requiredAny: PRD_INTEGRATION_ACCESS_ANY,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ import type { AdminDrawFinanceSummaryData } from "@/types/api/admin-draw-finance
|
|||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
|
|
||||||
import { useAdminCurrencyCatalog } from "@/hooks/use-admin-currency-catalog";
|
import { useAdminCurrencyCatalog } from "@/hooks/use-admin-currency-catalog";
|
||||||
|
import { useAdminDateTimeFormatter } from "@/hooks/use-admin-datetime-formatter";
|
||||||
import { useConfirmAction } from "@/hooks/use-confirm-action";
|
import { useConfirmAction } from "@/hooks/use-confirm-action";
|
||||||
import { useExportLabels } from "@/hooks/use-export-labels";
|
import { useExportLabels } from "@/hooks/use-export-labels";
|
||||||
import { formatAdminMinorUnits } from "@/lib/money";
|
import { formatAdminMinorUnits } from "@/lib/money";
|
||||||
@@ -44,6 +45,7 @@ export function DrawFinanceConsole({ drawId }: { drawId: string }): React.ReactE
|
|||||||
PRD_PAYOUT_REVIEW,
|
PRD_PAYOUT_REVIEW,
|
||||||
]);
|
]);
|
||||||
const [data, setData] = useState<AdminDrawFinanceSummaryData | null>(null);
|
const [data, setData] = useState<AdminDrawFinanceSummaryData | null>(null);
|
||||||
|
const formatTs = useAdminDateTimeFormatter();
|
||||||
const exportLabels = useExportLabels("drawFinance", { drawNo: data?.draw_no ?? drawId });
|
const exportLabels = useExportLabels("drawFinance", { drawNo: data?.draw_no ?? drawId });
|
||||||
const [err, setErr] = useState<string | null>(null);
|
const [err, setErr] = useState<string | null>(null);
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
@@ -219,8 +221,8 @@ export function DrawFinanceConsole({ drawId }: { drawId: string }): React.ReactE
|
|||||||
<TableCell className="text-center tabular-nums text-xs">
|
<TableCell className="text-center tabular-nums text-xs">
|
||||||
{formatMoney(b.total_jackpot_payout_amount)}
|
{formatMoney(b.total_jackpot_payout_amount)}
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell className="font-mono text-[11px] text-muted-foreground">
|
<TableCell className="whitespace-nowrap text-xs text-muted-foreground">
|
||||||
{b.finished_at ?? "—"}
|
{formatTs(b.finished_at)}
|
||||||
</TableCell>
|
</TableCell>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
))}
|
))}
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import {
|
|||||||
TableHeader,
|
TableHeader,
|
||||||
TableRow,
|
TableRow,
|
||||||
} from "@/components/ui/table";
|
} from "@/components/ui/table";
|
||||||
|
import { useAdminDateTimeFormatter } from "@/hooks/use-admin-datetime-formatter";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import { adminHasAnyPermission } from "@/lib/admin-permissions";
|
import { adminHasAnyPermission } from "@/lib/admin-permissions";
|
||||||
import { useAdminProfile } from "@/stores/admin-session";
|
import { useAdminProfile } from "@/stores/admin-session";
|
||||||
@@ -103,6 +104,7 @@ export function DrawResultsConsole({ drawId }: { drawId: string }) {
|
|||||||
|
|
||||||
function BatchTable({ batch }: { batch: AdminDrawBatchRow }) {
|
function BatchTable({ batch }: { batch: AdminDrawBatchRow }) {
|
||||||
const { t } = useTranslation("draws");
|
const { t } = useTranslation("draws");
|
||||||
|
const formatDt = useAdminDateTimeFormatter();
|
||||||
return (
|
return (
|
||||||
<Card>
|
<Card>
|
||||||
<CardHeader className="pb-2">
|
<CardHeader className="pb-2">
|
||||||
@@ -112,7 +114,7 @@ function BatchTable({ batch }: { batch: AdminDrawBatchRow }) {
|
|||||||
source: batch.source_type === "manual" ? t("manualEntry") : t("rng"),
|
source: batch.source_type === "manual" ? t("manualEntry") : t("rng"),
|
||||||
})}{" "}
|
})}{" "}
|
||||||
· {t("rngSummary", { hash: batch.rng_seed_hash ?? "—" })} ·{" "}
|
· {t("rngSummary", { hash: batch.rng_seed_hash ?? "—" })} ·{" "}
|
||||||
{t("confirmedAt", { time: batch.confirmed_at ?? "—" })}
|
{t("confirmedAt", { time: formatDt(batch.confirmed_at) })}
|
||||||
</p>
|
</p>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent className="overflow-x-auto pt-0">
|
<CardContent className="overflow-x-auto pt-0">
|
||||||
|
|||||||
@@ -36,11 +36,15 @@ import {
|
|||||||
} from "@/components/ui/table";
|
} from "@/components/ui/table";
|
||||||
import { Textarea } from "@/components/ui/textarea";
|
import { Textarea } from "@/components/ui/textarea";
|
||||||
import { adminHasAnyPermission } from "@/lib/admin-permissions";
|
import { adminHasAnyPermission } from "@/lib/admin-permissions";
|
||||||
|
import { getAdminPageBundle } from "@/lib/admin-permission-bundles";
|
||||||
import { useAdminProfile } from "@/stores/admin-session";
|
import { useAdminProfile } from "@/stores/admin-session";
|
||||||
import { LotteryApiBizError } from "@/types/api/errors";
|
import { LotteryApiBizError } from "@/types/api/errors";
|
||||||
import type {
|
import type {
|
||||||
|
AdminIntegrationSiteCreatePayload,
|
||||||
AdminIntegrationSiteConnectivityResult,
|
AdminIntegrationSiteConnectivityResult,
|
||||||
|
AdminIntegrationSiteUpdatePayload,
|
||||||
AdminIntegrationSiteRow,
|
AdminIntegrationSiteRow,
|
||||||
|
AdminIntegrationSiteDetail,
|
||||||
AdminIntegrationSiteSecrets,
|
AdminIntegrationSiteSecrets,
|
||||||
AdminIntegrationSiteWithSecrets,
|
AdminIntegrationSiteWithSecrets,
|
||||||
} from "@/types/api/admin-integration-site";
|
} from "@/types/api/admin-integration-site";
|
||||||
@@ -86,7 +90,7 @@ function textToOrigins(text: string): string[] {
|
|||||||
.filter(Boolean);
|
.filter(Boolean);
|
||||||
}
|
}
|
||||||
|
|
||||||
function rowToForm(row: AdminIntegrationSiteRow & Partial<FormState>): FormState {
|
function rowToForm(row: AdminIntegrationSiteDetail): FormState {
|
||||||
return {
|
return {
|
||||||
code: row.code,
|
code: row.code,
|
||||||
name: row.name,
|
name: row.name,
|
||||||
@@ -103,7 +107,12 @@ function rowToForm(row: AdminIntegrationSiteRow & Partial<FormState>): FormState
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function formToPayload(form: FormState, includeCode: boolean) {
|
function formToPayload(form: FormState, includeCode: true): AdminIntegrationSiteCreatePayload;
|
||||||
|
function formToPayload(form: FormState, includeCode: false): AdminIntegrationSiteUpdatePayload;
|
||||||
|
function formToPayload(
|
||||||
|
form: FormState,
|
||||||
|
includeCode: boolean,
|
||||||
|
): AdminIntegrationSiteCreatePayload | AdminIntegrationSiteUpdatePayload {
|
||||||
const base = {
|
const base = {
|
||||||
name: form.name.trim(),
|
name: form.name.trim(),
|
||||||
currency_code: form.currency_code.trim() || "NPR",
|
currency_code: form.currency_code.trim() || "NPR",
|
||||||
@@ -128,11 +137,10 @@ function formToPayload(form: FormState, includeCode: boolean) {
|
|||||||
export function IntegrationSitesConsole() {
|
export function IntegrationSitesConsole() {
|
||||||
const { t } = useTranslation("config");
|
const { t } = useTranslation("config");
|
||||||
const profile = useAdminProfile();
|
const profile = useAdminProfile();
|
||||||
const canView = adminHasAnyPermission(profile?.permissions, [
|
const canManage = adminHasAnyPermission(
|
||||||
"prd.integration.view",
|
profile?.permissions,
|
||||||
"prd.integration.manage",
|
getAdminPageBundle("integration-sites", "manage"),
|
||||||
]);
|
);
|
||||||
const canManage = adminHasAnyPermission(profile?.permissions, ["prd.integration.manage"]);
|
|
||||||
|
|
||||||
const [items, setItems] = useState<AdminIntegrationSiteRow[]>([]);
|
const [items, setItems] = useState<AdminIntegrationSiteRow[]>([]);
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
@@ -158,12 +166,6 @@ export function IntegrationSitesConsole() {
|
|||||||
const [exportBusyId, setExportBusyId] = useState<number | null>(null);
|
const [exportBusyId, setExportBusyId] = useState<number | null>(null);
|
||||||
|
|
||||||
const load = useCallback(async () => {
|
const load = useCallback(async () => {
|
||||||
if (!canView) {
|
|
||||||
setItems([]);
|
|
||||||
setLoading(false);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
try {
|
try {
|
||||||
const data = await getAdminIntegrationSites();
|
const data = await getAdminIntegrationSites();
|
||||||
@@ -175,7 +177,7 @@ export function IntegrationSitesConsole() {
|
|||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
}, [canView, t]);
|
}, [t]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
queueMicrotask(() => {
|
queueMicrotask(() => {
|
||||||
@@ -334,14 +336,6 @@ export function IntegrationSitesConsole() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!canView) {
|
|
||||||
return (
|
|
||||||
<AdminPageCard title={t("integrationSites.title")}>
|
|
||||||
<p className="text-sm text-muted-foreground">{t("integrationSites.noPermission")}</p>
|
|
||||||
</AdminPageCard>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<AdminPageCard
|
<AdminPageCard
|
||||||
@@ -377,13 +371,12 @@ export function IntegrationSitesConsole() {
|
|||||||
<TableCell>{row.name}</TableCell>
|
<TableCell>{row.name}</TableCell>
|
||||||
<TableCell>
|
<TableCell>
|
||||||
<AdminStatusBadge
|
<AdminStatusBadge
|
||||||
tone={row.status === 1 ? "success" : "muted"}
|
tone={row.status === 1 ? "success" : "neutral"}
|
||||||
label={
|
>
|
||||||
row.status === 1
|
{row.status === 1
|
||||||
? t("integrationSites.statusEnabled")
|
? t("integrationSites.statusEnabled")
|
||||||
: t("integrationSites.statusDisabled")
|
: t("integrationSites.statusDisabled")}
|
||||||
}
|
</AdminStatusBadge>
|
||||||
/>
|
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell className="max-w-[240px] truncate text-xs text-muted-foreground">
|
<TableCell className="max-w-[240px] truncate text-xs text-muted-foreground">
|
||||||
{row.wallet_api_url ?? "—"}
|
{row.wallet_api_url ?? "—"}
|
||||||
@@ -407,9 +400,16 @@ export function IntegrationSitesConsole() {
|
|||||||
>
|
>
|
||||||
{t("integrationSites.exportParams")}
|
{t("integrationSites.exportParams")}
|
||||||
</Button>
|
</Button>
|
||||||
<Button type="button" variant="outline" size="sm" onClick={() => void openEdit(row)}>
|
{canManage ? (
|
||||||
{t("integrationSites.edit")}
|
<Button
|
||||||
</Button>
|
type="button"
|
||||||
|
variant="outline"
|
||||||
|
size="sm"
|
||||||
|
onClick={() => void openEdit(row)}
|
||||||
|
>
|
||||||
|
{t("integrationSites.edit")}
|
||||||
|
</Button>
|
||||||
|
) : null}
|
||||||
{canManage ? (
|
{canManage ? (
|
||||||
<Button
|
<Button
|
||||||
type="button"
|
type="button"
|
||||||
|
|||||||
@@ -286,9 +286,10 @@ export function JackpotPoolsConsole({ embedded = false }: JackpotPoolsConsolePro
|
|||||||
<Label>{t("adjustmentDirection")}</Label>
|
<Label>{t("adjustmentDirection")}</Label>
|
||||||
<Select
|
<Select
|
||||||
value={adj.direction}
|
value={adj.direction}
|
||||||
onValueChange={(value: "increase" | "decrease") =>
|
onValueChange={(value: "increase" | "decrease" | null) => {
|
||||||
updateAdjustmentDraft(p.id, { direction: value })
|
if (value === null) return;
|
||||||
}
|
updateAdjustmentDraft(p.id, { direction: value });
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<SelectTrigger className="w-full min-w-0 sm:max-w-[12rem]">
|
<SelectTrigger className="w-full min-w-0 sm:max-w-[12rem]">
|
||||||
<SelectValue>
|
<SelectValue>
|
||||||
|
|||||||
@@ -305,7 +305,10 @@ export function PlayersConsole(): React.ReactElement {
|
|||||||
{canChooseSite ? (
|
{canChooseSite ? (
|
||||||
<div className="admin-list-field">
|
<div className="admin-list-field">
|
||||||
<Label className="sm:w-20 sm:shrink-0">{t("filterSite")}</Label>
|
<Label className="sm:w-20 sm:shrink-0">{t("filterSite")}</Label>
|
||||||
<Select value={siteCode || "__all__"} onValueChange={(v) => setSiteCode(v === "__all__" ? "" : v)}>
|
<Select
|
||||||
|
value={siteCode || "__all__"}
|
||||||
|
onValueChange={(v) => setSiteCode(v === "__all__" ? "" : v ?? "")}
|
||||||
|
>
|
||||||
<SelectTrigger className="w-full sm:w-[12rem]">
|
<SelectTrigger className="w-full sm:w-[12rem]">
|
||||||
<SelectValue placeholder={t("filterAllSites")} />
|
<SelectValue placeholder={t("filterAllSites")} />
|
||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
|
|||||||
@@ -73,6 +73,8 @@ import {
|
|||||||
} from "@/components/ui/table";
|
} from "@/components/ui/table";
|
||||||
import { useAdminCurrencyCatalog } from "@/hooks/use-admin-currency-catalog";
|
import { useAdminCurrencyCatalog } from "@/hooks/use-admin-currency-catalog";
|
||||||
import { useAdminDateTimeFormatter } from "@/hooks/use-admin-datetime-formatter";
|
import { useAdminDateTimeFormatter } from "@/hooks/use-admin-datetime-formatter";
|
||||||
|
import { formatAdminInstant } from "@/lib/admin-datetime";
|
||||||
|
import { getAdminRequestLocale } from "@/lib/admin-locale";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import { formatAdminMinorUnits } from "@/lib/money";
|
import { formatAdminMinorUnits } from "@/lib/money";
|
||||||
import { LotteryApiBizError } from "@/types/api/errors";
|
import { LotteryApiBizError } from "@/types/api/errors";
|
||||||
@@ -249,6 +251,10 @@ function normalizeFilenamePart(value: string): string {
|
|||||||
return value.trim().replace(/[\\/:*?"<>|\s]+/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "");
|
return value.trim().replace(/[\\/:*?"<>|\s]+/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function formatExportInstant(iso: string | null | undefined): ExportCell {
|
||||||
|
return formatAdminInstant(iso, { locale: getAdminRequestLocale() });
|
||||||
|
}
|
||||||
|
|
||||||
function toCsvValue(value: ExportCell): string {
|
function toCsvValue(value: ExportCell): string {
|
||||||
if (value == null) {
|
if (value == null) {
|
||||||
return "";
|
return "";
|
||||||
@@ -369,7 +375,7 @@ function drawRowsFromSummary(summary: AdminDrawFinanceSummaryData): ExportRow[]
|
|||||||
total_win_count: batch.total_win_count,
|
total_win_count: batch.total_win_count,
|
||||||
total_payout_amount: batch.total_payout_amount,
|
total_payout_amount: batch.total_payout_amount,
|
||||||
total_jackpot_payout_amount: batch.total_jackpot_payout_amount,
|
total_jackpot_payout_amount: batch.total_jackpot_payout_amount,
|
||||||
finished_at: batch.finished_at,
|
finished_at: formatExportInstant(batch.finished_at),
|
||||||
})),
|
})),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
@@ -575,8 +581,8 @@ export function ReportsConsole() {
|
|||||||
status: item.status,
|
status: item.status,
|
||||||
external_ref_no: item.external_ref_no,
|
external_ref_no: item.external_ref_no,
|
||||||
fail_reason: item.fail_reason,
|
fail_reason: item.fail_reason,
|
||||||
created_at: item.created_at,
|
created_at: formatExportInstant(item.created_at),
|
||||||
finished_at: item.finished_at,
|
finished_at: formatExportInstant(item.finished_at),
|
||||||
}));
|
}));
|
||||||
setResult({
|
setResult({
|
||||||
key: "player_transfer",
|
key: "player_transfer",
|
||||||
@@ -625,7 +631,7 @@ export function ReportsConsole() {
|
|||||||
ticket_no: item.ticket_no,
|
ticket_no: item.ticket_no,
|
||||||
play_code: item.play_code,
|
play_code: item.play_code,
|
||||||
player_id: item.player_id,
|
player_id: item.player_id,
|
||||||
created_at: item.created_at,
|
created_at: formatExportInstant(item.created_at),
|
||||||
})),
|
})),
|
||||||
];
|
];
|
||||||
setResult({
|
setResult({
|
||||||
@@ -737,7 +743,7 @@ export function ReportsConsole() {
|
|||||||
target_id: item.target_id,
|
target_id: item.target_id,
|
||||||
ip: item.ip,
|
ip: item.ip,
|
||||||
user_agent: item.user_agent,
|
user_agent: item.user_agent,
|
||||||
created_at: item.created_at,
|
created_at: formatExportInstant(item.created_at),
|
||||||
}));
|
}));
|
||||||
setResult({
|
setResult({
|
||||||
key: "admin_audit",
|
key: "admin_audit",
|
||||||
|
|||||||
@@ -194,7 +194,7 @@ export function PlayerTicketsConsole(): React.ReactElement {
|
|||||||
onValueChange={(v) =>
|
onValueChange={(v) =>
|
||||||
setDraft((current) => ({
|
setDraft((current) => ({
|
||||||
...current,
|
...current,
|
||||||
siteCode: v === "__all__" ? "" : v,
|
siteCode: v === "__all__" ? "" : (v ?? ""),
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
|
|||||||
Reference in New Issue
Block a user