From cbc499e5b260332484cc17cc73d4a1fe9b490e22 Mon Sep 17 00:00:00 2001 From: kang Date: Thu, 4 Jun 2026 09:17:55 +0800 Subject: [PATCH] feat(api, agents): add agent node profile retrieval and update functionality Implemented new API functions to fetch and update agent node profiles, enhancing the management capabilities for agent data. This addition improves the overall functionality of the admin agents console, allowing for better user interaction with agent profiles. Updated related types for improved type safety and clarity in the codebase. --- src/api/admin-agent-lines.ts | 13 + src/api/admin-agents.ts | 13 + src/app/admin/(shell)/agents/layout.tsx | 14 + src/app/admin/(shell)/agents/list/page.tsx | 5 + src/app/admin/(shell)/agents/page.tsx | 2 +- .../admin/(shell)/agents/provision/page.tsx | 18 + .../(shell)/agents/settlement-bills/page.tsx | 18 + src/app/admin/(shell)/agents/sites/page.tsx | 15 + .../(shell)/config/integration-sites/page.tsx | 20 +- .../(shell)/draws/[drawId]/risk/hot/page.tsx | 14 +- .../draws/[drawId]/risk/pools/page.tsx | 19 +- .../draws/[drawId]/risk/sold-out/page.tsx | 14 +- .../admin/(shell)/players/[playerId]/page.tsx | 28 + .../(shell)/risk/draws/[drawId]/hot/page.tsx | 4 +- .../risk/draws/[drawId]/sold-out/page.tsx | 4 +- .../(shell)/settlement/agent-bills/page.tsx | 5 + src/app/admin/(shell)/wallet/player/page.tsx | 15 +- src/app/globals.css | 2 +- src/components/admin/admin-breadcrumb.tsx | 37 +- src/components/admin/module-scaffold.tsx | 8 +- src/hooks/use-admin-site-code-options.ts | 9 +- src/i18n/locales/en/adminUsers.json | 3 + src/i18n/locales/en/agents.json | 100 +- src/i18n/locales/en/common.json | 4 +- src/i18n/locales/en/config.json | 43 + src/i18n/locales/en/draws.json | 10 + src/i18n/locales/en/jackpot.json | 8 + src/i18n/locales/en/players.json | 20 + src/i18n/locales/ne/adminUsers.json | 3 + src/i18n/locales/ne/agents.json | 99 +- src/i18n/locales/ne/common.json | 2 +- src/i18n/locales/ne/config.json | 43 + src/i18n/locales/ne/draws.json | 10 + src/i18n/locales/ne/jackpot.json | 8 + src/i18n/locales/ne/players.json | 20 + src/i18n/locales/zh/adminUsers.json | 13 +- src/i18n/locales/zh/agents.json | 116 +- src/i18n/locales/zh/common.json | 4 +- src/i18n/locales/zh/config.json | 43 + src/i18n/locales/zh/draws.json | 10 + src/i18n/locales/zh/jackpot.json | 8 + src/i18n/locales/zh/players.json | 20 + src/lib/admin-page-title.ts | 27 +- src/lib/admin-player-paths.ts | 4 + src/lib/admin-prd.ts | 33 +- .../admin-roles/admin-roles-console.tsx | 23 +- .../admin-users/admin-users-console.tsx | 29 +- .../agents/agent-line-provision-wizard.tsx | 239 +++ src/modules/agents/agents-console.tsx | 1522 +++++++---------- src/modules/agents/agents-players-panel.tsx | 257 +++ src/modules/agents/agents-subnav.tsx | 110 ++ src/modules/config/config-hub-screen.tsx | 2 +- .../config/doc/odds-config-doc-screen.tsx | 2 + .../config/doc/play-config-doc-screen.tsx | 3 + .../config/doc/rebate-config-doc-screen.tsx | 3 + .../config/doc/risk-cap-doc-screen.tsx | 3 + src/modules/config/risk-cap-runtime-panel.tsx | 14 +- src/modules/draws/draw-create-dialog.tsx | 2 +- src/modules/draws/draw-detail-console.tsx | 372 ++-- src/modules/draws/draw-edit-dialog.tsx | 1 + src/modules/draws/draw-subnav.tsx | 21 +- .../integration/integration-sites-console.tsx | 26 +- src/modules/jackpot/jackpot-pools-console.tsx | 9 +- src/modules/players/invalid-player-id.tsx | 9 + src/modules/players/player-detail-console.tsx | 562 ++++++ src/modules/players/players-console.tsx | 211 ++- src/modules/risk/risk-lock-logs-console.tsx | 16 +- src/modules/risk/risk-pools-console.tsx | 97 +- src/modules/risk/risk-subnav.tsx | 15 +- .../settings/currency-settings-panel.tsx | 3 + .../panels/currency-format-settings-panel.tsx | 3 + .../settings/panels/draw-settings-panel.tsx | 6 + .../settlement/agent-bills-console.tsx | 79 + .../tickets/player-tickets-console.tsx | 222 +-- src/modules/wallet/wallet-subnav.tsx | 2 - src/types/api/admin-agent-line.ts | 45 + src/types/api/admin-agent.ts | 34 +- src/types/api/admin-player.ts | 2 + src/types/api/admin-user.ts | 2 + 79 files changed, 3468 insertions(+), 1406 deletions(-) create mode 100644 src/api/admin-agent-lines.ts create mode 100644 src/app/admin/(shell)/agents/layout.tsx create mode 100644 src/app/admin/(shell)/agents/list/page.tsx create mode 100644 src/app/admin/(shell)/agents/provision/page.tsx create mode 100644 src/app/admin/(shell)/agents/settlement-bills/page.tsx create mode 100644 src/app/admin/(shell)/agents/sites/page.tsx create mode 100644 src/app/admin/(shell)/players/[playerId]/page.tsx create mode 100644 src/app/admin/(shell)/settlement/agent-bills/page.tsx create mode 100644 src/lib/admin-player-paths.ts create mode 100644 src/modules/agents/agent-line-provision-wizard.tsx create mode 100644 src/modules/agents/agents-players-panel.tsx create mode 100644 src/modules/agents/agents-subnav.tsx create mode 100644 src/modules/players/invalid-player-id.tsx create mode 100644 src/modules/players/player-detail-console.tsx create mode 100644 src/modules/settlement/agent-bills-console.tsx create mode 100644 src/types/api/admin-agent-line.ts diff --git a/src/api/admin-agent-lines.ts b/src/api/admin-agent-lines.ts new file mode 100644 index 0000000..08e2b39 --- /dev/null +++ b/src/api/admin-agent-lines.ts @@ -0,0 +1,13 @@ +import { adminRequest } from "@/lib/admin-http"; +import type { + AdminAgentLineProvisionPayload, + AdminAgentLineProvisionResult, +} from "@/types/api/admin-agent-line"; + +const A = `/admin`; + +export async function postAdminAgentLine( + payload: AdminAgentLineProvisionPayload, +): Promise { + return adminRequest.post(`${A}/agent-lines`, payload); +} diff --git a/src/api/admin-agents.ts b/src/api/admin-agents.ts index e6b845b..9c313b5 100644 --- a/src/api/admin-agents.ts +++ b/src/api/admin-agents.ts @@ -8,6 +8,8 @@ import type { AgentNodeCreatePayload, AgentNodeRow, AgentNodeUpdatePayload, + AgentProfilePayload, + AgentProfileRow, AgentRoleCreatePayload, AgentRoleListData, AgentTreeData, @@ -39,6 +41,17 @@ export async function deleteAgentNode(agentNodeId: number): Promise { return adminRequest.delete(`${A}/agent-nodes/${agentNodeId}`); } +export async function getAgentNodeProfile(agentNodeId: number): Promise { + return adminRequest.get(`${A}/agent-nodes/${agentNodeId}/profile`); +} + +export async function putAgentNodeProfile( + agentNodeId: number, + body: AgentProfilePayload, +): Promise { + return adminRequest.put(`${A}/agent-nodes/${agentNodeId}/profile`, body); +} + export async function getAgentNodeRoles(agentNodeId: number): Promise { return adminRequest.get(`${A}/agent-nodes/${agentNodeId}/roles`); } diff --git a/src/app/admin/(shell)/agents/layout.tsx b/src/app/admin/(shell)/agents/layout.tsx new file mode 100644 index 0000000..69b3b5c --- /dev/null +++ b/src/app/admin/(shell)/agents/layout.tsx @@ -0,0 +1,14 @@ +import type { ReactNode } from "react"; + +import { AgentsSubnav } from "@/modules/agents/agents-subnav"; + +export default function AdminAgentsLayout({ children }: { children: ReactNode }) { + return ( +
+
+ +
+ {children} +
+ ); +} diff --git a/src/app/admin/(shell)/agents/list/page.tsx b/src/app/admin/(shell)/agents/list/page.tsx new file mode 100644 index 0000000..8130be1 --- /dev/null +++ b/src/app/admin/(shell)/agents/list/page.tsx @@ -0,0 +1,5 @@ +import { redirect } from "next/navigation"; + +export default function AgentsListPage() { + redirect("/admin/agents"); +} diff --git a/src/app/admin/(shell)/agents/page.tsx b/src/app/admin/(shell)/agents/page.tsx index 60714f4..d47f6b9 100644 --- a/src/app/admin/(shell)/agents/page.tsx +++ b/src/app/admin/(shell)/agents/page.tsx @@ -9,7 +9,7 @@ export const metadata: Metadata = buildPageMetadata("agents", "title"); export default function AgentsPage() { return ( - + diff --git a/src/app/admin/(shell)/agents/provision/page.tsx b/src/app/admin/(shell)/agents/provision/page.tsx new file mode 100644 index 0000000..228211d --- /dev/null +++ b/src/app/admin/(shell)/agents/provision/page.tsx @@ -0,0 +1,18 @@ +import { ModuleScaffold } from "@/components/admin/module-scaffold"; +import { AdminPermissionGate } from "@/components/admin/admin-permission-gate"; +import { AgentLineProvisionWizard } from "@/modules/agents/agent-line-provision-wizard"; +import { PRD_AGENT_LINE_PROVISION_ACCESS_ANY } from "@/lib/admin-prd"; +import { buildPageMetadata } from "@/lib/page-metadata"; +import type { Metadata } from "next"; + +export const metadata: Metadata = buildPageMetadata("agents", "lineProvision.title"); + +export default function AgentLineProvisionPage(): React.ReactElement { + return ( + + + + + + ); +} diff --git a/src/app/admin/(shell)/agents/settlement-bills/page.tsx b/src/app/admin/(shell)/agents/settlement-bills/page.tsx new file mode 100644 index 0000000..c6a95b2 --- /dev/null +++ b/src/app/admin/(shell)/agents/settlement-bills/page.tsx @@ -0,0 +1,18 @@ +import { ModuleScaffold } from "@/components/admin/module-scaffold"; +import { AdminPermissionGate } from "@/components/admin/admin-permission-gate"; +import { AgentBillsConsole } from "@/modules/settlement/agent-bills-console"; +import { PRD_SETTLEMENT_AGENT_ACCESS_ANY } from "@/lib/admin-prd"; +import { buildPageMetadata } from "@/lib/page-metadata"; +import type { Metadata } from "next"; + +export const metadata: Metadata = buildPageMetadata("agents", "subnav.settlementBills"); + +export default function AgentSettlementBillsPage(): React.ReactElement { + return ( + + + + + + ); +} diff --git a/src/app/admin/(shell)/agents/sites/page.tsx b/src/app/admin/(shell)/agents/sites/page.tsx new file mode 100644 index 0000000..741a001 --- /dev/null +++ b/src/app/admin/(shell)/agents/sites/page.tsx @@ -0,0 +1,15 @@ +import { AdminPermissionGate } from "@/components/admin/admin-permission-gate"; +import { IntegrationSitesConsole } from "@/modules/integration/integration-sites-console"; +import { PRD_AGENT_SITES_ACCESS_ANY } from "@/lib/admin-prd"; +import { buildPageMetadata } from "@/lib/page-metadata"; +import type { Metadata } from "next"; + +export const metadata: Metadata = buildPageMetadata("agents", "sitesTitle"); + +export default function AgentLineSitesPage() { + return ( + + + + ); +} diff --git a/src/app/admin/(shell)/config/integration-sites/page.tsx b/src/app/admin/(shell)/config/integration-sites/page.tsx index 41e61e8..897b1c8 100644 --- a/src/app/admin/(shell)/config/integration-sites/page.tsx +++ b/src/app/admin/(shell)/config/integration-sites/page.tsx @@ -1,18 +1,6 @@ -import { AdminPermissionGate } from "@/components/admin/admin-permission-gate"; -import { ModuleScaffold } from "@/components/admin/module-scaffold"; -import { IntegrationSitesConsole } from "@/modules/integration/integration-sites-console"; -import { buildPageMetadata } from "@/lib/page-metadata"; -import { PRD_INTEGRATION_ACCESS_ANY } from "@/lib/admin-prd"; -import type { Metadata } from "next"; +import { redirect } from "next/navigation"; -export const metadata: Metadata = buildPageMetadata("config", "integrationSites.title"); - -export default function AdminIntegrationSitesPage() { - return ( - - - - - - ); +/** @deprecated 接入配置已并入「代理线路」目录 */ +export default function LegacyIntegrationSitesPage() { + redirect("/admin/agents/sites"); } diff --git a/src/app/admin/(shell)/draws/[drawId]/risk/hot/page.tsx b/src/app/admin/(shell)/draws/[drawId]/risk/hot/page.tsx index 6b1daa3..a6ada7e 100644 --- a/src/app/admin/(shell)/draws/[drawId]/risk/hot/page.tsx +++ b/src/app/admin/(shell)/draws/[drawId]/risk/hot/page.tsx @@ -1,17 +1,9 @@ -import { RiskPoolsConsole } from "@/modules/risk/risk-pools-console"; +import { redirect } from "next/navigation"; +/** 兼容旧链接:热门号码已并入风险池 Tab(筛选 >80%)。 */ export default async function AdminDrawRiskHotPage(props: { params: Promise<{ drawId: string }>; }) { const { drawId } = await props.params; - const id = Number(drawId); - - return ( - - ); + redirect(`/admin/draws/${drawId}/risk/pools?filter=high_risk`); } diff --git a/src/app/admin/(shell)/draws/[drawId]/risk/pools/page.tsx b/src/app/admin/(shell)/draws/[drawId]/risk/pools/page.tsx index 370308b..42b8691 100644 --- a/src/app/admin/(shell)/draws/[drawId]/risk/pools/page.tsx +++ b/src/app/admin/(shell)/draws/[drawId]/risk/pools/page.tsx @@ -1,17 +1,30 @@ -import { RiskPoolsConsole } from "@/modules/risk/risk-pools-console"; +import { + RiskPoolsConsole, + type RiskPoolListFilter, +} from "@/modules/risk/risk-pools-console"; + +function parsePoolFilter(raw: string | undefined): RiskPoolListFilter { + if (raw === "sold_out" || raw === "high_risk") { + return raw; + } + return "all"; +} export default async function AdminDrawRiskPoolsPage(props: { params: Promise<{ drawId: string }>; + searchParams: Promise<{ filter?: string }>; }) { const { drawId } = await props.params; + const { filter: filterRaw } = await props.searchParams; const id = Number(drawId); + const filter = parsePoolFilter(filterRaw); return ( ); diff --git a/src/app/admin/(shell)/draws/[drawId]/risk/sold-out/page.tsx b/src/app/admin/(shell)/draws/[drawId]/risk/sold-out/page.tsx index ea9ac2a..b88ed56 100644 --- a/src/app/admin/(shell)/draws/[drawId]/risk/sold-out/page.tsx +++ b/src/app/admin/(shell)/draws/[drawId]/risk/sold-out/page.tsx @@ -1,17 +1,9 @@ -import { RiskPoolsConsole } from "@/modules/risk/risk-pools-console"; +import { redirect } from "next/navigation"; +/** 兼容旧链接:售罄号码已并入风险池 Tab(筛选售罄)。 */ export default async function AdminDrawRiskSoldOutPage(props: { params: Promise<{ drawId: string }>; }) { const { drawId } = await props.params; - const id = Number(drawId); - - return ( - - ); + redirect(`/admin/draws/${drawId}/risk/pools?filter=sold_out`); } diff --git a/src/app/admin/(shell)/players/[playerId]/page.tsx b/src/app/admin/(shell)/players/[playerId]/page.tsx new file mode 100644 index 0000000..df1fcf1 --- /dev/null +++ b/src/app/admin/(shell)/players/[playerId]/page.tsx @@ -0,0 +1,28 @@ +import { AdminPermissionGate } from "@/components/admin/admin-permission-gate"; +import { ModuleScaffold } from "@/components/admin/module-scaffold"; +import { PRD_PLAYERS_ACCESS_ANY } from "@/lib/admin-prd"; +import { buildPageMetadata } from "@/lib/page-metadata"; +import { InvalidPlayerId } from "@/modules/players/invalid-player-id"; +import { PlayerDetailConsole } from "@/modules/players/player-detail-console"; +import type { Metadata } from "next"; + +export const metadata: Metadata = buildPageMetadata("players", "detailTitle"); + +export default async function AdminPlayerDetailPage(props: { + params: Promise<{ playerId: string }>; +}) { + const { playerId } = await props.params; + const id = Number(playerId); + + return ( + + + {Number.isFinite(id) && id > 0 ? ( + + ) : ( + + )} + + + ); +} diff --git a/src/app/admin/(shell)/risk/draws/[drawId]/hot/page.tsx b/src/app/admin/(shell)/risk/draws/[drawId]/hot/page.tsx index 000e2ee..2f1b0da 100644 --- a/src/app/admin/(shell)/risk/draws/[drawId]/hot/page.tsx +++ b/src/app/admin/(shell)/risk/draws/[drawId]/hot/page.tsx @@ -1,8 +1,8 @@ import { redirect } from "next/navigation"; -export default async function AdminRiskHotPage(props: { +export default async function AdminRiskHotRedirectPage(props: { params: Promise<{ drawId: string }>; }) { const { drawId } = await props.params; - redirect(`/admin/draws/${drawId}/risk/hot`); + redirect(`/admin/draws/${drawId}/risk/pools?filter=high_risk`); } diff --git a/src/app/admin/(shell)/risk/draws/[drawId]/sold-out/page.tsx b/src/app/admin/(shell)/risk/draws/[drawId]/sold-out/page.tsx index e728cc9..14aacbc 100644 --- a/src/app/admin/(shell)/risk/draws/[drawId]/sold-out/page.tsx +++ b/src/app/admin/(shell)/risk/draws/[drawId]/sold-out/page.tsx @@ -1,8 +1,8 @@ import { redirect } from "next/navigation"; -export default async function AdminRiskSoldOutPage(props: { +export default async function AdminRiskSoldOutRedirectPage(props: { params: Promise<{ drawId: string }>; }) { const { drawId } = await props.params; - redirect(`/admin/draws/${drawId}/risk/sold-out`); + redirect(`/admin/draws/${drawId}/risk/pools?filter=sold_out`); } diff --git a/src/app/admin/(shell)/settlement/agent-bills/page.tsx b/src/app/admin/(shell)/settlement/agent-bills/page.tsx new file mode 100644 index 0000000..0cf2463 --- /dev/null +++ b/src/app/admin/(shell)/settlement/agent-bills/page.tsx @@ -0,0 +1,5 @@ +import { redirect } from "next/navigation"; + +export default function LegacyAgentBillsPage() { + redirect("/admin/agents/settlement-bills"); +} diff --git a/src/app/admin/(shell)/wallet/player/page.tsx b/src/app/admin/(shell)/wallet/player/page.tsx index 0f3af86..c3fb1b1 100644 --- a/src/app/admin/(shell)/wallet/player/page.tsx +++ b/src/app/admin/(shell)/wallet/player/page.tsx @@ -1,15 +1,6 @@ -import { AdminPermissionGate } from "@/components/admin/admin-permission-gate"; -import { PRD_WALLET_PLAYER_ACCESS_ANY } from "@/lib/admin-prd"; -import { PlayerWalletPanel } from "@/modules/wallet/wallet-console"; -import { buildPageMetadata } from "@/lib/page-metadata"; -import type { Metadata } from "next"; - -export const metadata: Metadata = buildPageMetadata("wallet", "playerWalletQuery"); +import { redirect } from "next/navigation"; +/** 玩家钱包已并入玩家详情;旧路径重定向到玩家列表 */ export default function AdminWalletPlayerPage() { - return ( - - - - ); + redirect("/admin/players"); } diff --git a/src/app/globals.css b/src/app/globals.css index 15937a2..48df7e2 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -149,7 +149,7 @@ } .admin-list-toolbar { - @apply flex w-full flex-row flex-wrap items-center gap-3 border-t border-border/60 pt-4; + @apply flex w-full flex-row flex-wrap items-center gap-3; } .admin-list-field { diff --git a/src/components/admin/admin-breadcrumb.tsx b/src/components/admin/admin-breadcrumb.tsx index 43e0af8..bdac577 100644 --- a/src/components/admin/admin-breadcrumb.tsx +++ b/src/components/admin/admin-breadcrumb.tsx @@ -12,6 +12,7 @@ import { BreadcrumbSeparator, } from "@/components/ui/breadcrumb"; import { adminNavLabel } from "@/lib/admin-nav-label"; +import { resolveAdminPageTitle } from "@/lib/admin-page-title"; import { ADMIN_BASE } from "@/modules/_config/admin-nav"; import { useAdminProfile } from "@/stores/admin-session"; import React from "react"; @@ -39,6 +40,13 @@ const TOP_ROUTE_LABELS: Record = { agents: "agents.title", }; +const AGENT_ROUTE_LABELS: Record = { + list: "agents.directoryTitle", + provision: "agents.subnav.provision", + sites: "agents.sitesTitle", + "settlement-bills": "agents.subnav.settlementBills", +}; + const CONFIG_ROUTE_LABELS: Record = { "integration-sites": "integrationSites.title", plays: "nav.items.plays", @@ -119,10 +127,15 @@ export function AdminBreadcrumb() { navItem != null && (pathname === navItem.href || pathname.startsWith(`${navItem.href}/`)); - if (segments.length > 2 && !navCoversPath) { + if (segments.length > 2 && (!navCoversPath || businessSegment === "agents")) { const subSegment = segments[2]; let subLabel = ""; - if (businessSegment === "rules" && subSegment) { + if (businessSegment === "agents" && subSegment) { + const key = AGENT_ROUTE_LABELS[subSegment]; + subLabel = key + ? t(key, { ns: "agents", defaultValue: titleCase(subSegment) }) + : titleCase(subSegment); + } else if (businessSegment === "rules" && subSegment) { const key = RULES_ROUTE_LABELS[subSegment]; subLabel = key ? t(key, { ns: "config", defaultValue: titleCase(subSegment) }) @@ -143,12 +156,20 @@ export function AdminBreadcrumb() { ? t(key, { ns: "config", defaultValue: titleCase(subSegment) }) : titleCase(subSegment); } else { - subLabel = subSegment - ? t(`subnav.${subSegment}`, { - ns: "draws", - defaultValue: DRAW_ROUTE_LABELS[subSegment] ?? titleCase(subSegment), - }) - : ""; + const pageTitle = resolveAdminPageTitle(pathname); + if (pageTitle) { + subLabel = t(pageTitle.key, { + ns: pageTitle.ns, + ...(pageTitle.params ?? {}), + }); + } else if (subSegment) { + subLabel = t(`subnav.${subSegment}`, { + ns: "draws", + defaultValue: DRAW_ROUTE_LABELS[subSegment] ?? titleCase(subSegment), + }); + } else { + subLabel = ""; + } } if (subLabel) { breadcrumbs.push({ diff --git a/src/components/admin/module-scaffold.tsx b/src/components/admin/module-scaffold.tsx index e523ac3..a6d0d08 100644 --- a/src/components/admin/module-scaffold.tsx +++ b/src/components/admin/module-scaffold.tsx @@ -5,14 +5,18 @@ import { cn } from "@/lib/utils"; type ModuleScaffoldProps = { children?: ReactNode; className?: string; + /** 已处于带外边距的 layout(如代理线路子导航)内时为 true */ + embedded?: boolean; }; /** 内容区容器;模块标题由侧栏导航体现,此处不再重复大标题与说明。 */ -export function ModuleScaffold({ children, className }: ModuleScaffoldProps) { +export function ModuleScaffold({ children, className, embedded = false }: ModuleScaffoldProps) { return (
diff --git a/src/hooks/use-admin-site-code-options.ts b/src/hooks/use-admin-site-code-options.ts index c22a7e6..d74d8a4 100644 --- a/src/hooks/use-admin-site-code-options.ts +++ b/src/hooks/use-admin-site-code-options.ts @@ -4,11 +4,12 @@ import { useCallback, useEffect, useState } from "react"; import { getAdminIntegrationSites } from "@/api/admin-integration-sites"; import { adminHasAnyPermission } from "@/lib/admin-permissions"; -import { PRD_INTEGRATION_ACCESS_ANY } from "@/lib/admin-prd"; +import { PRD_INTEGRATION_ACCESS_ANY, PRD_USERS_MANAGE } from "@/lib/admin-prd"; import { useAdminProfile } from "@/stores/admin-session"; import { useAsyncEffect } from "@/hooks/use-async-effect"; export type AdminSiteCodeOption = { + id: number; code: string; name: string; }; @@ -32,6 +33,7 @@ async function fetchSiteCodeOptions(): Promise { inflightSites = getAdminIntegrationSites() .then((data) => { cachedSites = data.items.map((row) => ({ + id: row.id, code: row.code, name: row.name, })); @@ -58,7 +60,10 @@ export function useAdminSiteCodeOptions(): { reload: () => Promise; } { const profile = useAdminProfile(); - const canLoad = adminHasAnyPermission(profile?.permissions, PRD_INTEGRATION_ACCESS_ANY); + const canLoad = adminHasAnyPermission(profile?.permissions, [ + ...PRD_INTEGRATION_ACCESS_ANY, + PRD_USERS_MANAGE, + ]); const [sites, setSites] = useState(cachedSites ?? []); const [loading, setLoading] = useState(canLoad && cachedSites === null); diff --git a/src/i18n/locales/en/adminUsers.json b/src/i18n/locales/en/adminUsers.json index 6c92edd..63647ab 100644 --- a/src/i18n/locales/en/adminUsers.json +++ b/src/i18n/locales/en/adminUsers.json @@ -103,8 +103,11 @@ "editTitle": "Edit Role", "description": "Roles group backend function permissions and are then assigned to admin accounts.", "slug": "Role code", + "slugPlaceholder": "Enter role identifier, for example super_admin", "name": "Role name", + "namePlaceholder": "Enter role name", "descriptionLabel": "Role description", + "descriptionPlaceholder": "Enter role description", "status": "Status" }, "accountDialog": { diff --git a/src/i18n/locales/en/agents.json b/src/i18n/locales/en/agents.json index f742409..46e7507 100644 --- a/src/i18n/locales/en/agents.json +++ b/src/i18n/locales/en/agents.json @@ -1,5 +1,36 @@ { - "title": "Agents", + "title": "Agent lines", + "sitesTitle": "Sites", + "sitesListHint": "For the full site table (keys, callbacks, etc.), go to", + "sitesListLink": "Sites", + "subnav": { + "label": "Agent line navigation", + "noPermission": "No permission", + "operations": "Operations", + "provision": "Provision line", + "sites": "Sites", + "settlementBills": "Agent bills" + }, + "includeRoots": "Include root nodes", + "includeRootsHint": "Root nodes represent site boundaries and are excluded from operating agent counts by default.", + "directoryStatus": { + "all": "All statuses", + "enabled": "Enabled only", + "disabled": "Disabled only" + }, + "tabs": { + "subordinates": "Subordinates", + "accounts": "Primary account", + "players": "Players", + "overview": "Overview", + "roles": "Roles", + "users": "Accounts", + "delegation": "Delegation ceiling" + }, + "filterParent": "Parent agent", + "filterParentAll": "All subordinates", + "listFlatHint": "All operating agents in a flat list. Use row actions to add a child under a specific agent.", + "addChildNeedParent": "Select a parent agent before adding a subordinate", "treeTitle": "Agent tree", "detailTitle": "Node details", "selectNode": "Select an agent node from the tree", @@ -18,6 +49,7 @@ }, "code": "Code", "name": "Name", + "namePlaceholder": "Enter agent name", "depth": "Depth", "path": "Path", "status": "Status", @@ -28,11 +60,61 @@ "saveFailed": "Save failed", "codeRequired": "Code and name are required", "modelGuide": "Agent layer controls data scope and delegation ceiling. Account permissions are assigned through roles.", - "tabs": { - "overview": "Overview", - "roles": "Roles", - "users": "Accounts", - "delegation": "Delegation ceiling" + "pageGuide": "Manage the agent tree, agent roles, agent accounts, and delegation ceilings here. Platform accounts and platform roles stay in platform governance pages.", + "summary": { + "currentSiteNodes": "Current site nodes", + "currentSiteAgents": "Current site operating agents", + "visibleList": "Current flat list rows", + "visibleAgents": "Current visible operating agents", + "globalNodes": "All-site node total", + "globalAgents": "All-site operating agents", + "enabledAgents": "Enabled operating agents", + "rootNodes": "Root node count" + }, + "profile": { + "section": "Share & credit", + "totalShareRate": "Share rate (%)", + "creditLimit": "Credit limit", + "rebateLimit": "Rebate ceiling", + "defaultPlayerRebate": "Default player rebate", + "settlementCycle": "Settlement cycle", + "canGrantExtraRebate": "Allow extra rebate", + "canCreatePlayer": "Allow creating players", + "canCreateChildAgent": "Allow creating sub-agents", + "cycleDaily": "Daily", + "cycleWeekly": "Weekly", + "cycleMonthly": "Monthly" + }, + "settlementBills": { + "title": "Agent bills", + "description": "Player/agent bills generated after a period is closed", + "columns": { + "id": "ID", + "type": "Type", + "net": "Net", + "unpaid": "Unpaid", + "status": "Status" + } + }, + "lineProvision": { + "title": "Provision agent line", + "description": "Creates site, root agent, and admin account in one step (site_code matches agent code).", + "code": "Site code", + "name": "Line name", + "username": "Agent login", + "password": "Initial password", + "walletUrl": "Wallet API URL", + "submit": "Provision", + "success": "Line provisioned", + "secretsOnce": "Secrets are shown once — save them now", + "link": "Provision line" + }, + "noAccess": "You do not have permission to manage agents. Contact an administrator.", + "playersPanel": { + "create": "Create player", + "scopedTo": "Direct players: {{agent}}", + "allUnderSite": "Players visible on this site", + "filterHint": "Filter direct players by parent agent." }, "delegation": { "title": "Delegation ceiling", @@ -62,11 +144,15 @@ "title": "Agent accounts", "create": "Create account", "username": "Username", + "email": "Email", "password": "Password", "roles": "Roles", "createSuccess": "Created account {{name}}", "roleSaveSuccess": "Roles updated for {{name}}", "deleteConfirm": "This admin will no longer be able to sign in. This cannot be undone.", "deleteSuccess": "Deleted account {{name}}" - } + }, + "usernamePlaceholder": "Enter login username", + "passwordPlaceholder": "Enter an 8-character password", + "passwordOptionalHint": "Leave empty to keep unchanged, or enter an 8-character password" } diff --git a/src/i18n/locales/en/common.json b/src/i18n/locales/en/common.json index 2f34d49..636f84d 100644 --- a/src/i18n/locales/en/common.json +++ b/src/i18n/locales/en/common.json @@ -160,8 +160,8 @@ "audit": "Audit Logs", "settings": "Settings", "account": "Account settings", - "integration": "Integration sites", - "agents": "Agents", + "integration": "Integration", + "agents": "Agent lines", "config": "Operations config" }, "sidebar": { diff --git a/src/i18n/locales/en/config.json b/src/i18n/locales/en/config.json index b4a6797..6331abd 100644 --- a/src/i18n/locales/en/config.json +++ b/src/i18n/locales/en/config.json @@ -99,6 +99,16 @@ "notes": "Notes", "ssoSecret": "SSO secret", "walletApiKey": "Wallet API key" + }, + "placeholders": { + "code": "Enter site identifier, for example partner-a", + "name": "Enter site name", + "currency": "Enter currency code, for example NPR", + "walletApiUrl": "Enter wallet API URL", + "lotteryH5BaseUrl": "Enter H5 URL", + "iframeOrigins": "Enter allowed origins, for example https://www.example.com", + "notes": "Enter notes", + "connectivityPlayerId": "Enter player ID, for example 10001" } }, "versionStatus": { @@ -211,6 +221,17 @@ "playRulesHtml": "Play rules HTML (i18n)", "playRulesHtmlDesc": "Rendered on the player play-rules page per locale. Leave empty to fall back to another language or the default empty state." }, + "placeholders": { + "defaultCurrency": "Enter default currency code, for example NPR", + "drawIntervalMinutes": "Enter draw interval in minutes", + "drawBettingWindowSeconds": "Enter betting window in seconds", + "drawCloseBeforeDrawSeconds": "Enter seconds to close before draw", + "drawBufferDrawsAhead": "Enter pre-generated draw count", + "cooldownMinutes": "Enter cooldown minutes", + "currencyDisplayDecimals": "Enter display decimal places, for example 2", + "currencyDecimalSeparator": "Enter decimal separator, for example .", + "currencyThousandsSeparator": "Enter thousands separator, for example ," + }, "hints": { "manualReview": "When enabled, RNG draw results enter pending review and must be published manually in admin.", "cooldownMinutes": "How long to wait after publishing before entering settling. Use 0 to settle immediately.", @@ -277,6 +298,9 @@ "code": "Currency code", "name": "Currency name", "decimals": "Decimal places", + "codePlaceholder": "Enter currency code, for example NPR", + "namePlaceholder": "Enter currency name", + "decimalsPlaceholder": "Enter decimal places, for example 2", "enabled": "Enabled status", "enabledHint": "Disabled currencies should not be used for new business.", "bettable": "Allow betting", @@ -347,6 +371,11 @@ "maxBet": "Max bet", "actions": "Actions" }, + "placeholders": { + "displayOrder": "Order", + "minBetAmount": "Minimum amount", + "maxBetAmount": "Maximum amount" + }, "states": { "enabled": "Enabled", "disabled": "Disabled", @@ -411,6 +440,10 @@ "missingScopeRow": "Missing {{scope}} row. Check seed or version data.", "rebateRate": "Rebate rate (%)", "rebateRateHint": "Writes rebate_rate to all prize scopes under this play type.", + "placeholders": { + "multiplier": "Enter odds multiplier", + "rebateRate": "Enter rebate rate" + }, "publishFailed": "Publish failed", "createDraftSuccess": "Created draft v{{version}}", "createDraftFailed": "Failed to create draft", @@ -457,6 +490,11 @@ "d3": "3D rebate rate (%)", "d4": "4D rebate rate (%)" }, + "placeholders": { + "d2": "Enter 2D rebate", + "d3": "Enter 3D rebate", + "d4": "Enter 4D rebate" + }, "winEnjoy": { "label": "Deduct rebate on winning payouts", "description": "Maps to settlement.apply_rebate_to_payout: when enabled, winning payout uses gross win × (1 - rebate_rate_snapshot).", @@ -467,6 +505,11 @@ "effectiveTime": "Effective time (current active odds version)" }, "riskCap": { + "placeholders": { + "defaultCap": "Enter default cap amount", + "number": "4-digit number", + "capAmount": "Enter cap amount" + }, "validation": { "requireAtLeastOne": "At least one cap row is required", "defaultGreaterThanZero": "Default cap amount must be greater than 0", diff --git a/src/i18n/locales/en/draws.json b/src/i18n/locales/en/draws.json index 2b5c80b..6777052 100644 --- a/src/i18n/locales/en/draws.json +++ b/src/i18n/locales/en/draws.json @@ -11,6 +11,7 @@ "title": "Create draw manually", "description": "Enter date and time in {{tz}} (not your browser local zone). If only draw time is set, start/close are derived from server config.", "hint": "Start < close < draw. Draw number optional; sequence auto-assigned by UTC business date.", + "drawNoPlaceholder": "Enter draw number, for example 20260526-008", "drawTimeRequired": "Draw time is required", "submit": "Create", "saving": "Creating…", @@ -34,6 +35,7 @@ "action": "Edit", "title": "Edit draw", "description": "Draw {{drawNo}} · edit times in {{tz}}", + "drawNoPlaceholder": "Enter draw number, for example 20260526-008", "submit": "Save", "saving": "Saving…", "success": "Draw updated", @@ -55,6 +57,14 @@ "invalidDrawId": "Invalid draw ID", "loadFailed": "Failed to load. Check login and API configuration.", "drawDetail": "Draw details", + "detailSubtitle": "{{date}} · Round {{seq}}", + "scheduleTitle": "Schedule", + "resultBatchesTitle": "Result batches", + "batchSummaryTotal": "{{count}} batch(es)", + "batchSummaryPending": "{{count}} pending", + "batchSummaryPublished": "{{count}} published", + "noResultBatchesYet": "No result batches yet.", + "goToReviewTab": "Review & publish", "businessDate": "Business date", "sequenceNo": "Sequence no.", "plannedDraw": "Planned draw", diff --git a/src/i18n/locales/en/jackpot.json b/src/i18n/locales/en/jackpot.json index 7ebba25..ccbb967 100644 --- a/src/i18n/locales/en/jackpot.json +++ b/src/i18n/locales/en/jackpot.json @@ -20,7 +20,9 @@ "adjustmentIncrease": "Increase", "adjustmentDecrease": "Decrease", "adjustmentAmount": "Amount (major units)", + "adjustmentAmountPlaceholder": "Enter adjustment amount", "adjustmentReason": "Reason (required)", + "adjustmentReasonPlaceholder": "Enter adjustment reason", "submitAdjustment": "Submit adjustment", "adjustmentSuccess": "Pool balance adjusted", "adjustmentFailed": "Adjustment failed", @@ -30,11 +32,17 @@ "confirmAdjustmentDescription": "This writes a ledger entry and updates the pool balance. Verify amount and reason.", "recentAdjustments": "Recent adjustments", "contributionRate": "Contribution rate 0-1", + "contributionRatePlaceholder": "Enter contribution rate, for example 0.02", "triggerThreshold": "Burst threshold (minor unit)", + "triggerThresholdPlaceholder": "Enter burst threshold", "payoutRate": "Burst payout rate 0-1", + "payoutRatePlaceholder": "Enter payout rate, for example 0.05", "forceTriggerGap": "Force burst gap (settled draws)", + "forceTriggerGapPlaceholder": "Enter forced burst gap in draws", "minBetAmount": "Minimum bet amount (minor unit)", + "minBetAmountPlaceholder": "Enter minimum bet amount", "comboTriggerPlays": "Combo trigger plays (comma separated)", + "comboTriggerPlaysPlaceholder": "Enter play codes separated by commas, for example straight,ibox", "status": "Status", "disabled": "Disabled", "enabled": "Enabled", diff --git a/src/i18n/locales/en/players.json b/src/i18n/locales/en/players.json index ef2b2d1..dbef32d 100644 --- a/src/i18n/locales/en/players.json +++ b/src/i18n/locales/en/players.json @@ -1,6 +1,22 @@ { "title": "Players", + "detailTitle": "Player details", "listTitle": "Player list", + "viewDetail": "View details", + "backToList": "Back to player list", + "detailSubtitle": "{{site}} · {{sitePlayerId}} · ID {{playerId}}", + "tabOverview": "Overview", + "tabTickets": "Tickets", + "tabWalletTxns": "Wallet transactions", + "tabTransferOrders": "Transfer orders", + "profileSection": "Profile", + "walletsSection": "Wallets", + "createdAt": "Registered at", + "agent": "Agent", + "frozen": "Frozen", + "txnAmount": "Amount", + "balanceAfterTxn": "Balance after", + "invalidPlayerId": "Invalid player ID", "createPlayer": "Create player", "searchPlaceholder": "Search by player ID / username / nickname", "filterSite": "Site", @@ -11,6 +27,10 @@ "siteCodeRequired": "Enter the site code", "sitePlayerIdRequired": "Enter the site player ID", "createFailed": "Failed to create player", + "createAgentRequired": "Your account is not bound to an agent node. Sign in with an agent account, or as super admin pick a valid site and agent.", + "createAgentNode": "Agent node", + "createAgentNodePlaceholder": "Select agent node", + "createAgentAutoHint": "Player will be assigned to your agent: {{name}} ({{code}})", "createSuccess": "Created player {{name}}", "noChanges": "No changes", "updateFailed": "Failed to update player", diff --git a/src/i18n/locales/ne/adminUsers.json b/src/i18n/locales/ne/adminUsers.json index e036dd2..ccb7316 100644 --- a/src/i18n/locales/ne/adminUsers.json +++ b/src/i18n/locales/ne/adminUsers.json @@ -103,8 +103,11 @@ "editTitle": "भूमिका सम्पादन", "description": "भूमिकाले ब्याकएन्ड कार्य अनुमति समेट्छ र पछि प्रशासक खातालाई बाँडिन्छ।", "slug": "भूमिका कोड", + "slugPlaceholder": "भूमिका चिन्ह प्रविष्ट गर्नुहोस्, जस्तै super_admin", "name": "भूमिका नाम", + "namePlaceholder": "भूमिका नाम प्रविष्ट गर्नुहोस्", "descriptionLabel": "भूमिका विवरण", + "descriptionPlaceholder": "भूमिका विवरण प्रविष्ट गर्नुहोस्", "status": "स्थिति" }, "accountDialog": { diff --git a/src/i18n/locales/ne/agents.json b/src/i18n/locales/ne/agents.json index 0eb0d8e..db35d2e 100644 --- a/src/i18n/locales/ne/agents.json +++ b/src/i18n/locales/ne/agents.json @@ -1,5 +1,36 @@ { - "title": "Agents", + "title": "एजेन्ट लाइन", + "sitesTitle": "साइट सूची", + "sitesListHint": "पूर्ण साइट तालिका (कुञ्जी, कलब्याक) को लागि", + "sitesListLink": "साइट सूची", + "subnav": { + "label": "एजेन्ट लाइन नेभ", + "noPermission": "अनुमति छैन", + "operations": "सञ्चालन", + "provision": "लाइन खोल्नुहोस्", + "sites": "साइट सूची", + "settlementBills": "एजेन्ट बिल" + }, + "tabs": { + "subordinates": "अधीनस्थ व्यवस्थापन", + "accounts": "मुख्य खाता", + "players": "प्लेयर व्यवस्थापन", + "overview": "Overview", + "roles": "Roles", + "users": "Accounts", + "delegation": "Delegation ceiling" + }, + "filterParent": "माथिल्लो एजेन्ट", + "filterParentAll": "सबै अधीनस्थ", + "listFlatHint": "सबै सञ्चालन एजेन्ट सूचीमा; अधीनस्थ थप्न पङ्क्ति मेनु प्रयोग गर्नुहोस्।", + "addChildNeedParent": "अधीनस्थ थप्न पहिले माथिल्लो एजेन्ट छान्नुहोस्", + "includeRoots": "रुट नोड समावेश गर्नुहोस्", + "includeRootsHint": "रुट नोडले साइट सिमाना जनाउँछ, त्यसैले सामान्यतया सञ्चालन एजेन्ट गणनामा समावेश हुँदैन।", + "directoryStatus": { + "all": "सबै स्थिति", + "enabled": "सक्रिय मात्र", + "disabled": "निष्क्रिय मात्र" + }, "treeTitle": "Agent tree", "detailTitle": "Node details", "selectNode": "Select an agent node from the tree", @@ -18,6 +49,7 @@ }, "code": "Code", "name": "Name", + "namePlaceholder": "एजेन्ट नाम प्रविष्ट गर्नुहोस्", "depth": "Depth", "path": "Path", "status": "Status", @@ -28,11 +60,61 @@ "saveFailed": "Save failed", "codeRequired": "Code and name are required", "modelGuide": "एजेन्ट तहले डाटा स्कोप र delegation ceiling नियन्त्रण गर्छ; खाताको अनुमति भूमिका मार्फत बाँडिन्छ।", - "tabs": { - "overview": "Overview", - "roles": "Roles", - "users": "Accounts", - "delegation": "Delegation ceiling" + "pageGuide": "यहाँ एजेन्ट ट्री, एजेन्ट भूमिका, एजेन्ट खाता र delegation ceiling व्यवस्थापन गरिन्छ। प्लेटफर्म खाता र प्लेटफर्म भूमिका अलग पृष्ठमा राखिन्छ।", + "summary": { + "currentSiteNodes": "हालको साइट नोड संख्या", + "currentSiteAgents": "हालको साइट सञ्चालन एजेन्ट", + "visibleList": "हालको सूची पंक्ति", + "visibleAgents": "हाल देखिने सञ्चालन एजेन्ट", + "globalNodes": "सबै साइट नोड कुल", + "globalAgents": "सबै साइट सञ्चालन एजेन्ट", + "enabledAgents": "सक्रिय सञ्चालन एजेन्ट", + "rootNodes": "रुट नोड संख्या" + }, + "profile": { + "section": "शेयर र क्रेडिट", + "totalShareRate": "शेयर दर (%)", + "creditLimit": "क्रेडिट सीमा", + "rebateLimit": "रिबेट सीमा", + "defaultPlayerRebate": "प्लेयर पूर्वनिर्धारित रिबेट", + "settlementCycle": "सेटलमेन्ट चक्र", + "canGrantExtraRebate": "अतिरिक्त रिबेट अनुमति", + "canCreatePlayer": "प्लेयर सिर्जना अनुमति", + "canCreateChildAgent": "अधीनस्थ एजेन्ट सिर्जना अनुमति", + "cycleDaily": "दैनिक", + "cycleWeekly": "साप्ताहिक", + "cycleMonthly": "मासिक" + }, + "settlementBills": { + "title": "एजेन्ट बिल", + "description": "अवधि बन्द पछि बनेका प्लेयर/एजेन्ट बिल", + "columns": { + "id": "ID", + "type": "प्रकार", + "net": "नेट", + "unpaid": "बाँकी", + "status": "स्थिति" + } + }, + "lineProvision": { + "title": "एजेन्ट लाइन खोल्नुहोस्", + "description": "एकै चरणमा साइट, रुट एजेन्ट र खाता सिर्जना (site_code = agent code)।", + "code": "साइट code", + "name": "लाइन नाम", + "username": "एजेन्ट लगइन", + "password": "प्रारम्भिक पासवर्ड", + "walletUrl": "वालेट API URL", + "submit": "खोल्नुहोस्", + "success": "लाइन खोलियो", + "secretsOnce": "कुञ्जी एक पटक मात्र देखाइन्छ", + "link": "लाइन खोल्नुहोस्" + }, + "noAccess": "एजेन्ट सञ्चालन अनुमति छैन। प्रशासकलाई सम्पर्क गर्नुहोस्।", + "playersPanel": { + "create": "प्लेयर सिर्जना", + "scopedTo": "प्रत्यक्ष प्लेयर: {{agent}}", + "allUnderSite": "हालको साइटका प्लेयर", + "filterHint": "माथिल्लो एजेन्ट अनुसार प्लेयर हेर्नुहोस्।" }, "delegation": { "title": "Delegation ceiling", @@ -68,5 +150,8 @@ "roleSaveSuccess": "Roles updated for {{name}}", "deleteConfirm": "यो खाता अब लगइन गर्न सक्दैन।", "deleteSuccess": "खाता {{name}} मेटियो" - } + }, + "usernamePlaceholder": "लगइन नाम प्रविष्ट गर्नुहोस्", + "passwordPlaceholder": "८-अक्षरको पासवर्ड प्रविष्ट गर्नुहोस्", + "passwordOptionalHint": "परिवर्तन नगर्ने भए खाली छोड्नुहोस्, परिवर्तन गर्न ८-अक्षरको पासवर्ड प्रविष्ट गर्नुहोस्" } diff --git a/src/i18n/locales/ne/common.json b/src/i18n/locales/ne/common.json index 3a4c730..e2f80ed 100644 --- a/src/i18n/locales/ne/common.json +++ b/src/i18n/locales/ne/common.json @@ -161,7 +161,7 @@ "settings": "सेटिङ", "account": "खाता सेटिङ", "integration": "मुख्य साइट एकीकरण", - "agents": "एजेन्ट व्यवस्थापन", + "agents": "एजेन्ट लाइन", "config": "सञ्चालन कन्फिगरेसन" }, "sidebar": { diff --git a/src/i18n/locales/ne/config.json b/src/i18n/locales/ne/config.json index 2610052..342cd2b 100644 --- a/src/i18n/locales/ne/config.json +++ b/src/i18n/locales/ne/config.json @@ -99,6 +99,16 @@ "notes": "टिप्पणी", "ssoSecret": "SSO गोप्य", "walletApiKey": "वालेट API कुञ्जी" + }, + "placeholders": { + "code": "साइट चिन्ह प्रविष्ट गर्नुहोस्, जस्तै partner-a", + "name": "साइट नाम प्रविष्ट गर्नुहोस्", + "currency": "मुद्रा कोड प्रविष्ट गर्नुहोस्, जस्तै NPR", + "walletApiUrl": "वालेट API ठेगाना प्रविष्ट गर्नुहोस्", + "lotteryH5BaseUrl": "H5 ठेगाना प्रविष्ट गर्नुहोस्", + "iframeOrigins": "अनुमत origin प्रविष्ट गर्नुहोस्, जस्तै https://www.example.com", + "notes": "टिप्पणी प्रविष्ट गर्नुहोस्", + "connectivityPlayerId": "खेलाडी ID प्रविष्ट गर्नुहोस्, जस्तै 10001" } }, "versionStatus": { @@ -200,6 +210,17 @@ "playRulesHtml": "खेल नियम HTML (बहुभाषी)", "playRulesHtmlDesc": "खेलाडीको नियम पृष्ठमा भाषा अनुसार HTML देखिन्छ। खाली छोड्दा अर्को भाषा वा पूर्वनिर्धारित खाली सूचना देखिन्छ।" }, + "placeholders": { + "defaultCurrency": "पूर्वनिर्धारित मुद्रा कोड प्रविष्ट गर्नुहोस्, जस्तै NPR", + "drawIntervalMinutes": "ड्रअ अन्तराल मिनेट प्रविष्ट गर्नुहोस्", + "drawBettingWindowSeconds": "बेटिङ विन्डो सेकेन्ड प्रविष्ट गर्नुहोस्", + "drawCloseBeforeDrawSeconds": "ड्रअ अघि बन्द हुने सेकेन्ड प्रविष्ट गर्नुहोस्", + "drawBufferDrawsAhead": "अग्रिम सिर्जना गरिने ड्रअ संख्या प्रविष्ट गर्नुहोस्", + "cooldownMinutes": "कूलडाउन मिनेट प्रविष्ट गर्नुहोस्", + "currencyDisplayDecimals": "प्रदर्शन दशमलव स्थान प्रविष्ट गर्नुहोस्, जस्तै 2", + "currencyDecimalSeparator": "दशमलव विभाजक प्रविष्ट गर्नुहोस्, जस्तै .", + "currencyThousandsSeparator": "हजार विभाजक प्रविष्ट गर्नुहोस्, जस्तै ," + }, "hints": { "manualReview": "सक्रिय हुँदा RNG ड्रअ परिणाम pending review मा जान्छ र एडमिनबाट म्यानुअल रूपमा प्रकाशित गर्नुपर्छ।", "cooldownMinutes": "प्रकाशनपछि settling मा जानुअघि कति समय पर्खने। 0 राखे तुरुन्त सेटलमेन्ट सुरु हुन्छ।", @@ -260,6 +281,9 @@ "code": "मुद्रा कोड", "name": "मुद्रा नाम", "decimals": "दशमलव स्थान", + "codePlaceholder": "मुद्रा कोड प्रविष्ट गर्नुहोस्, जस्तै NPR", + "namePlaceholder": "मुद्रा नाम प्रविष्ट गर्नुहोस्", + "decimalsPlaceholder": "दशमलव स्थान प्रविष्ट गर्नुहोस्, जस्तै 2", "enabled": "सक्रिय स्थिति", "enabledHint": "निष्क्रिय मुद्रा नयाँ व्यवसायमा प्रयोग गर्नु हुँदैन।", "bettable": "बेटिङ अनुमति", @@ -330,6 +354,11 @@ "maxBet": "अधिकतम बेट", "actions": "कार्य" }, + "placeholders": { + "displayOrder": "क्रम", + "minBetAmount": "न्यूनतम रकम", + "maxBetAmount": "अधिकतम रकम" + }, "states": { "enabled": "सक्रिय", "disabled": "बन्द", @@ -394,6 +423,10 @@ "missingScopeRow": "{{scope}} को row हराइरहेको छ। seed वा version data जाँच गर्नुहोस्।", "rebateRate": "रिबेट दर (%)", "rebateRateHint": "यसले यो खेल प्रकारअन्तर्गत सबै prize scope मा rebate_rate लेख्छ।", + "placeholders": { + "multiplier": "अड्स गुणक प्रविष्ट गर्नुहोस्", + "rebateRate": "रिबेट दर प्रविष्ट गर्नुहोस्" + }, "publishFailed": "प्रकाशन असफल भयो", "createDraftSuccess": "ड्राफ्ट v{{version}} सिर्जना भयो", "createDraftFailed": "ड्राफ्ट सिर्जना असफल भयो", @@ -440,6 +473,11 @@ "d3": "3D रिबेट दर (%)", "d4": "4D रिबेट दर (%)" }, + "placeholders": { + "d2": "2D रिबेट प्रविष्ट गर्नुहोस्", + "d3": "3D रिबेट प्रविष्ट गर्नुहोस्", + "d4": "4D रिबेट प्रविष्ट गर्नुहोस्" + }, "winEnjoy": { "label": "जितेको टिकटको पेआउटमा पुनः रिबेट घटाउने", "description": "settlement.apply_rebate_to_payout सँग जोडिएको: सक्रिय हुँदा जित पेआउटमा rebate_rate_snapshot अनुसार घटाउँछ।", @@ -450,6 +488,11 @@ "effectiveTime": "लागू समय (हाल सक्रिय अड्स संस्करण)" }, "riskCap": { + "placeholders": { + "defaultCap": "पूर्वनिर्धारित सीमा प्रविष्ट गर्नुहोस्", + "number": "४-अङ्कको नम्बर", + "capAmount": "सीमा रकम प्रविष्ट गर्नुहोस्" + }, "validation": { "requireAtLeastOne": "कम्तीमा एक क्याप row आवश्यक छ", "defaultGreaterThanZero": "पूर्वनिर्धारित क्याप रकम 0 भन्दा ठूलो हुनुपर्छ", diff --git a/src/i18n/locales/ne/draws.json b/src/i18n/locales/ne/draws.json index 64556ec..677aeec 100644 --- a/src/i18n/locales/ne/draws.json +++ b/src/i18n/locales/ne/draws.json @@ -11,6 +11,7 @@ "title": "म्यानुअल ड्रअ सिर्जना", "description": "{{tz}} मा मिति र समय प्रविष्ट गर्नुहोस् (ब्राउजर स्थानीय समय होइन)।", "hint": "सुरु < बन्द < ड्रअ। ड्रअ नम्बर वैकल्पिक।", + "drawNoPlaceholder": "ड्रअ नम्बर प्रविष्ट गर्नुहोस्, जस्तै 20260526-008", "drawTimeRequired": "ड्रअ समय आवश्यक छ", "submit": "सिर्जना", "saving": "सिर्जना हुँदैछ…", @@ -34,6 +35,7 @@ "action": "सम्पादन", "title": "ड्रअ सम्पादन", "description": "ड्रअ {{drawNo}} · {{tz}}", + "drawNoPlaceholder": "ड्रअ नम्बर प्रविष्ट गर्नुहोस्, जस्तै 20260526-008", "submit": "सेभ", "saving": "सेभ हुँदैछ…", "success": "ड्रअ अद्यावधिक भयो", @@ -55,6 +57,14 @@ "invalidDrawId": "अवैध ड्रअ ID", "loadFailed": "लोड असफल भयो। लगइन र API कन्फिग जाँच गर्नुहोस्।", "drawDetail": "ड्रअ विवरण", + "detailSubtitle": "{{date}} · राउन्ड {{seq}}", + "scheduleTitle": "तालिका", + "resultBatchesTitle": "नतिजा ब्याच", + "batchSummaryTotal": "जम्मा {{count}}", + "batchSummaryPending": "समीक्षा {{count}}", + "batchSummaryPublished": "प्रकाशित {{count}}", + "noResultBatchesYet": "अहिलेसम्म कुनै नतिजा ब्याच छैन।", + "goToReviewTab": "समीक्षा र प्रकाशन", "businessDate": "व्यवसाय मिति", "sequenceNo": "क्रम संख्या", "plannedDraw": "योजनाबद्ध ड्रअ", diff --git a/src/i18n/locales/ne/jackpot.json b/src/i18n/locales/ne/jackpot.json index 3f5ee86..9945cce 100644 --- a/src/i18n/locales/ne/jackpot.json +++ b/src/i18n/locales/ne/jackpot.json @@ -20,7 +20,9 @@ "adjustmentIncrease": "बढाउनु", "adjustmentDecrease": "घटाउनु", "adjustmentAmount": "समायोजन रकम (मुख्य एकाइ)", + "adjustmentAmountPlaceholder": "समायोजन रकम प्रविष्ट गर्नुहोस्", "adjustmentReason": "कारण (अनिवार्य)", + "adjustmentReasonPlaceholder": "समायोजन कारण प्रविष्ट गर्नुहोस्", "submitAdjustment": "समायोजन पेश गर्नुहोस्", "adjustmentSuccess": "पूल ब्यालेन्स समायोजन भयो", "adjustmentFailed": "समायोजन असफल", @@ -30,11 +32,17 @@ "confirmAdjustmentDescription": "यसले लेजर प्रविष्टि लेख्छ र पूल ब्यालेन्स अद्यावधिक गर्छ। रकम र कारण जाँच गर्नुहोस्।", "recentAdjustments": "भर्खरका समायोजन", "contributionRate": "योगदान अनुपात 0-1", + "contributionRatePlaceholder": "योगदान अनुपात प्रविष्ट गर्नुहोस्, जस्तै 0.02", "triggerThreshold": "बर्स्ट थ्रेसहोल्ड (सानो एकाइ)", + "triggerThresholdPlaceholder": "ट्रिगर थ्रेसहोल्ड प्रविष्ट गर्नुहोस्", "payoutRate": "बर्स्ट भुक्तानी अनुपात 0-1", + "payoutRatePlaceholder": "पेआउट अनुपात प्रविष्ट गर्नुहोस्, जस्तै 0.05", "forceTriggerGap": "बलपूर्वक बर्स्ट अन्तर (सेटल ड्रअ)", + "forceTriggerGapPlaceholder": "बलपूर्वक ट्रिगर अन्तर प्रविष्ट गर्नुहोस्", "minBetAmount": "न्यूनतम बेट रकम (सानो एकाइ)", + "minBetAmountPlaceholder": "न्यूनतम बेट रकम प्रविष्ट गर्नुहोस्", "comboTriggerPlays": "कम्बो ट्रिगर प्ले (comma-separated)", + "comboTriggerPlaysPlaceholder": "प्ले कोडहरू अल्पविरामले छुट्याएर लेख्नुहोस्, जस्तै straight,ibox", "status": "स्थिति", "disabled": "बन्द", "enabled": "खुला", diff --git a/src/i18n/locales/ne/players.json b/src/i18n/locales/ne/players.json index 42105f7..a292bcb 100644 --- a/src/i18n/locales/ne/players.json +++ b/src/i18n/locales/ne/players.json @@ -1,6 +1,22 @@ { "title": "खेलाडी", + "detailTitle": "खेलाडी विवरण", "listTitle": "खेलाडी सूची", + "viewDetail": "विवरण हेर्नुहोस्", + "backToList": "खेलाडी सूचीमा फर्कनुहोस्", + "detailSubtitle": "{{site}} · {{sitePlayerId}} · ID {{playerId}}", + "tabOverview": "सारांश", + "tabTickets": "टिकट", + "tabWalletTxns": "वालेट लेनदेन", + "tabTransferOrders": "ट्रान्सफर अर्डर", + "profileSection": "प्रोफाइल", + "walletsSection": "वालेट", + "createdAt": "दर्ता समय", + "agent": "एजेन्ट", + "frozen": "फ्रोजन", + "txnAmount": "रकम", + "balanceAfterTxn": "पछिको ब्यालेन्स", + "invalidPlayerId": "अवैध खेलाडी ID", "createPlayer": "खेलाडी सिर्जना", "searchPlaceholder": "खेलाडी ID / प्रयोगकर्ता नाम / उपनामबाट खोज्नुहोस्", "search": "खोज", @@ -9,6 +25,10 @@ "siteCodeRequired": "साइट कोड लेख्नुहोस्", "sitePlayerIdRequired": "साइट खेलाडी ID लेख्नुहोस्", "createFailed": "खेलाडी सिर्जना असफल भयो", + "createAgentRequired": "तपाईंको खाता एजेन्ट नोडसँग जोडिएको छैन। एजेन्ट खाताबाट लगइन गर्नुहोस्, वा सुपर एडमिनले मान्य साइट र एजेन्ट छान्नुहोस्।", + "createAgentNode": "एजेन्ट नोड", + "createAgentNodePlaceholder": "एजेन्ट नोड छान्नुहोस्", + "createAgentAutoHint": "खेलाडी तपाईंको एजेन्टमा तोकिनेछ: {{name}} ({{code}})", "createSuccess": "खेलाडी {{name}} सिर्जना भयो", "noChanges": "कुनै परिवर्तन छैन", "updateFailed": "खेलाडी अपडेट असफल भयो", diff --git a/src/i18n/locales/zh/adminUsers.json b/src/i18n/locales/zh/adminUsers.json index f2d6548..3df81bc 100644 --- a/src/i18n/locales/zh/adminUsers.json +++ b/src/i18n/locales/zh/adminUsers.json @@ -1,7 +1,7 @@ { - "title": "管理员", - "listTitle": "管理员用户列表", - "createAdmin": "新建管理员", + "title": "平台账号", + "listTitle": "平台账号列表", + "createAdmin": "新建平台账号", "searchPlaceholder": "按用户名 / 昵称 / 邮箱搜索", "loadFailed": "加载管理员列表失败", "roleLoadFailed": "加载角色列表失败", @@ -15,8 +15,8 @@ "saveAccountFailed": "保存账号失败", "deleteSuccess": "已删除 {{name}}", "deleteFailed": "删除失败", - "roleListTitle": "角色管理", - "createRole": "新增角色", + "roleListTitle": "平台角色管理", + "createRole": "新增平台角色", "roleCreateSuccess": "已创建角色 {{name}}", "roleUpdateSuccess": "已更新角色 {{name}}", "roleSaveFailed": "保存角色失败", @@ -103,8 +103,11 @@ "editTitle": "编辑角色", "description": "角色用于归拢后台功能权限,再分配给管理员账号。", "slug": "角色编码", + "slugPlaceholder": "请输入角色标识,如 super_admin", "name": "角色名称", + "namePlaceholder": "请输入角色名称", "descriptionLabel": "角色说明", + "descriptionPlaceholder": "请输入角色说明", "status": "状态" }, "accountDialog": { diff --git a/src/i18n/locales/zh/agents.json b/src/i18n/locales/zh/agents.json index 6d91d13..e9687d9 100644 --- a/src/i18n/locales/zh/agents.json +++ b/src/i18n/locales/zh/agents.json @@ -1,15 +1,46 @@ { - "title": "代理管理", + "title": "代理线路", + "sitesTitle": "站点列表", + "sitesListHint": "完整站点表格(密钥、回调等)请前往", + "sitesListLink": "站点列表", + "subnav": { + "label": "代理线路导航", + "noPermission": "无权限", + "operations": "代理经营", + "provision": "开通线路", + "sites": "站点列表", + "settlementBills": "代理账单" + }, + "includeRoots": "包含根节点", + "includeRootsHint": "根节点用于表示站点边界,默认不计入经营代理列表。", + "directoryStatus": { + "all": "全部状态", + "enabled": "仅启用", + "disabled": "仅停用" + }, "treeTitle": "代理树", - "detailTitle": "节点详情", - "selectNode": "请从左侧选择代理节点", - "loadFailed": "加载代理树失败", + "tabs": { + "subordinates": "下级管理", + "accounts": "主账号", + "players": "玩家管理", + "overview": "概况", + "roles": "角色", + "users": "账号", + "delegation": "授权上限" + }, + "filterParent": "上级代理", + "filterParentAll": "全部下级", + "listFlatHint": "列表平铺展示所有经营代理;添加下级请使用行内「添加下级代理」。", + "addChildNeedParent": "请先在「上级代理」中选择要为谁添加下级", + "detailTitle": "代理详情", + "selectNode": "请选择代理", + "loadFailed": "加载代理列表失败", "siteLabel": "站点", "createChild": "添加下级代理", - "editNode": "编辑节点", - "deleteNode": "删除节点", - "deleteNodeConfirm": "删除后不可恢复,请确认该节点无下级、无账号、无角色绑定。", - "deleteNodeBlockedHint": "请先删除下级代理、角色与账号后再删除本节点", + "editNode": "编辑代理", + "deleteNode": "删除代理", + "deleteNodeConfirm": "删除后不可恢复,请确认该代理无下级、无账号、无角色绑定。", + "deleteNodeBlockedHint": "请先删除下级代理、角色与账号后再删除该代理", "deleteNodeBlockedPrefix": "暂不可删除:", "deleteBlocked": { "children": "仍有 {{count}} 个下级代理", @@ -18,6 +49,7 @@ }, "code": "编码", "name": "名称", + "namePlaceholder": "请输入代理名称", "depth": "层级", "path": "路径", "status": "状态", @@ -26,13 +58,63 @@ "updateSuccess": "已更新 {{name}}", "deleteSuccess": "已删除代理 {{name}}", "saveFailed": "保存失败", - "codeRequired": "请填写编码与名称", + "codeRequired": "请填写代理名称和登录名", "modelGuide": "代理层负责数据范围(Scope)与授权上限(Ceiling),账号权限请通过角色分配。", - "tabs": { - "overview": "概况", - "roles": "角色", - "users": "账号", - "delegation": "授权上限" + "pageGuide": "这里统一管理代理树、代理角色、代理账号与下放上限。平台账号和平台角色请到各自的平台治理页面维护。", + "summary": { + "currentSiteNodes": "当前站点节点总数", + "currentSiteAgents": "当前站点经营代理数", + "visibleList": "当前平铺列表条数", + "visibleAgents": "当前可见经营代理数", + "globalNodes": "全部站点节点总数", + "globalAgents": "全部站点经营代理数", + "enabledAgents": "启用中的经营代理数", + "rootNodes": "根节点数量" + }, + "profile": { + "section": "占成与授信", + "totalShareRate": "占成比例 (%)", + "creditLimit": "授信额度", + "rebateLimit": "回水上限", + "defaultPlayerRebate": "默认玩家回水", + "settlementCycle": "结算周期", + "canGrantExtraRebate": "允许额外回水", + "canCreatePlayer": "允许创建玩家", + "canCreateChildAgent": "允许创建下级代理", + "cycleDaily": "日结", + "cycleWeekly": "周结", + "cycleMonthly": "月结" + }, + "settlementBills": { + "title": "代理账单", + "description": "账期关闭后生成的玩家/代理账单", + "columns": { + "id": "ID", + "type": "类型", + "net": "净额", + "unpaid": "未结", + "status": "状态" + } + }, + "lineProvision": { + "title": "开通代理线路", + "description": "一次创建站点、根代理与后台账号(site_code 与代理 code 一致)。", + "code": "站点 code", + "name": "线路名称", + "username": "代理账号", + "password": "初始密码", + "walletUrl": "钱包 API URL", + "submit": "开通线路", + "success": "线路已开通", + "secretsOnce": "密钥仅显示一次,请妥善保存", + "link": "开通线路" + }, + "noAccess": "您没有代理经营相关权限,请联系管理员开通。", + "playersPanel": { + "create": "创建玩家", + "scopedTo": "直属玩家:{{agent}}", + "allUnderSite": "当前站点下可见玩家", + "filterHint": "可按上级代理查看其直属玩家。" }, "delegation": { "title": "下放权限上限", @@ -66,11 +148,15 @@ "title": "代理账号", "create": "创建账号", "username": "登录名", + "email": "邮箱", "password": "密码", "roles": "角色", "createSuccess": "已创建账号 {{name}}", "roleSaveSuccess": "已更新 {{name}} 的角色", "deleteConfirm": "删除后该管理员将无法登录,且不可恢复。", "deleteSuccess": "已删除账号 {{name}}" - } + }, + "usernamePlaceholder": "请输入登录名", + "passwordPlaceholder": "请输入8位数密码", + "passwordOptionalHint": "留空则不修改,修改请输入8位数密码" } diff --git a/src/i18n/locales/zh/common.json b/src/i18n/locales/zh/common.json index 3a8b9bb..be6713d 100644 --- a/src/i18n/locales/zh/common.json +++ b/src/i18n/locales/zh/common.json @@ -160,8 +160,8 @@ "audit": "审计日志", "settings": "系统设置", "account": "账号设置", - "integration": "接入站点", - "agents": "代理管理", + "integration": "接入配置", + "agents": "代理线路", "config": "运营配置" }, "sidebar": { diff --git a/src/i18n/locales/zh/config.json b/src/i18n/locales/zh/config.json index bfe0a4e..0e05a91 100644 --- a/src/i18n/locales/zh/config.json +++ b/src/i18n/locales/zh/config.json @@ -99,6 +99,16 @@ "notes": "备注", "ssoSecret": "SSO 密钥", "walletApiKey": "钱包 API 密钥" + }, + "placeholders": { + "code": "请输入站点标识,如 partner-a", + "name": "请输入站点名称", + "currency": "请输入币种代码,如 NPR", + "walletApiUrl": "请输入钱包接口地址", + "lotteryH5BaseUrl": "请输入 H5 地址", + "iframeOrigins": "请输入允许的来源地址,如 https://www.example.com", + "notes": "请输入备注说明", + "connectivityPlayerId": "请输入玩家 ID,如 10001" } }, "versionStatus": { @@ -211,6 +221,17 @@ "playRulesHtml": "玩法规则 HTML(多语言)", "playRulesHtmlDesc": "该内容将直接在玩家端的玩法规则页面作为 HTML 渲染。按语言分别配置;留空则回退其它语言或显示默认提示。" }, + "placeholders": { + "defaultCurrency": "请输入默认币种代码,如 NPR", + "drawIntervalMinutes": "请输入开奖间隔分钟数", + "drawBettingWindowSeconds": "请输入投注窗口秒数", + "drawCloseBeforeDrawSeconds": "请输入封盘提前秒数", + "drawBufferDrawsAhead": "请输入预生成期数", + "cooldownMinutes": "请输入冷却分钟数", + "currencyDisplayDecimals": "请输入显示小数位数,如 2", + "currencyDecimalSeparator": "请输入小数分隔符,如 .", + "currencyThousandsSeparator": "请输入千分位分隔符,如 ," + }, "hints": { "manualReview": "开启后,RNG 开奖结果会先进入待审核,必须由后台人工发布。", "cooldownMinutes": "结果发布后等待多久再进入 settling。填 0 表示发布后直接进入结算。", @@ -277,6 +298,9 @@ "code": "币种代码", "name": "币种名称", "decimals": "小数位", + "codePlaceholder": "请输入币种代码,如 NPR", + "namePlaceholder": "请输入币种名称", + "decimalsPlaceholder": "请输入小数位数,如 2", "enabled": "启用状态", "enabledHint": "关闭后,新业务不应继续使用该币种。", "bettable": "允许下注", @@ -347,6 +371,11 @@ "maxBet": "最大下注", "actions": "操作" }, + "placeholders": { + "displayOrder": "顺序", + "minBetAmount": "最小金额", + "maxBetAmount": "最大金额" + }, "states": { "enabled": "开启", "disabled": "关闭", @@ -411,6 +440,10 @@ "missingScopeRow": "缺少 {{scope}} 对应行,请检查种子或版本数据。", "rebateRate": "回水比例 (%)", "rebateRateHint": "会把 rebate_rate 写入该玩法下所有奖级范围。", + "placeholders": { + "multiplier": "请输入赔率倍数", + "rebateRate": "请输入返点比例" + }, "publishFailed": "发布失败", "createDraftSuccess": "已创建草稿 v{{version}}", "createDraftFailed": "创建草稿失败", @@ -457,6 +490,11 @@ "d3": "3D 回水比例 (%)", "d4": "4D 回水比例 (%)" }, + "placeholders": { + "d2": "请输入 2D 返点", + "d3": "请输入 3D 返点", + "d4": "请输入 4D 返点" + }, "winEnjoy": { "label": "中奖注单结算时再扣回水", "description": "对应系统参数 settlement.apply_rebate_to_payout:开启后中奖派彩在毛赢基础上再乘 (1 - 回水率快照)。", @@ -467,6 +505,11 @@ "effectiveTime": "生效时间(当前赔率生效版本)" }, "riskCap": { + "placeholders": { + "defaultCap": "请输入默认限额", + "number": "4位号码", + "capAmount": "请输入限额" + }, "validation": { "requireAtLeastOne": "至少需要一条封顶配置", "defaultGreaterThanZero": "默认封顶金额必须大于 0", diff --git a/src/i18n/locales/zh/draws.json b/src/i18n/locales/zh/draws.json index ae3e44c..7a565a6 100644 --- a/src/i18n/locales/zh/draws.json +++ b/src/i18n/locales/zh/draws.json @@ -11,6 +11,7 @@ "title": "手动创建期号", "description": "日期与时间按 {{tz}} 填写(勿用浏览器本地时区)。仅填开奖时间时,开始/封盘按系统配置自动推算。", "hint": "开始 < 封盘 < 开奖。期号可留空,将按 UTC 业务日自动生成流水号。", + "drawNoPlaceholder": "请输入期号,如 20260526-008", "drawTimeRequired": "请填写开奖时间", "submit": "创建", "saving": "创建中…", @@ -34,6 +35,7 @@ "action": "编辑", "title": "编辑期号", "description": "期号 {{drawNo}} · 时间按 {{tz}} 编辑", + "drawNoPlaceholder": "请输入期号,如 20260526-008", "submit": "保存", "saving": "保存中…", "success": "期号已更新", @@ -55,6 +57,14 @@ "invalidDrawId": "无效的期号 ID", "loadFailed": "加载失败,请检查登录与 API 配置", "drawDetail": "开奖详情", + "detailSubtitle": "{{date}} · 第 {{seq}} 期", + "scheduleTitle": "时间安排", + "resultBatchesTitle": "开奖批次", + "batchSummaryTotal": "共 {{count}} 批", + "batchSummaryPending": "待审 {{count}}", + "batchSummaryPublished": "已发 {{count}}", + "noResultBatchesYet": "尚无开奖批次。", + "goToReviewTab": "去审核与发布", "businessDate": "业务日", "sequenceNo": "流水序号", "plannedDraw": "计划开奖", diff --git a/src/i18n/locales/zh/jackpot.json b/src/i18n/locales/zh/jackpot.json index 703c9eb..4b69d64 100644 --- a/src/i18n/locales/zh/jackpot.json +++ b/src/i18n/locales/zh/jackpot.json @@ -20,7 +20,9 @@ "adjustmentIncrease": "增加", "adjustmentDecrease": "减少", "adjustmentAmount": "调整金额(主币单位)", + "adjustmentAmountPlaceholder": "请输入调整金额", "adjustmentReason": "调整原因(必填)", + "adjustmentReasonPlaceholder": "请输入调整原因", "submitAdjustment": "提交余额调整", "adjustmentSuccess": "余额调整已入账", "adjustmentFailed": "余额调整失败", @@ -30,11 +32,17 @@ "confirmAdjustmentDescription": "将写入调整流水并更新当前池余额,请确认金额与原因无误。", "recentAdjustments": "最近调整记录", "contributionRate": "蓄水比例 0–1", + "contributionRatePlaceholder": "请输入贡献比例,如 0.02", "triggerThreshold": "爆池阈值(最小单位)", + "triggerThresholdPlaceholder": "请输入触发阈值", "payoutRate": "爆池派彩比例 0–1", + "payoutRatePlaceholder": "请输入派彩比例,如 0.05", "forceTriggerGap": "强制爆池间隔(已结算期数)", + "forceTriggerGapPlaceholder": "请输入强制触发间隔期数", "minBetAmount": "最低下注额(最小单位)", + "minBetAmountPlaceholder": "请输入最低下注金额", "comboTriggerPlays": "组合触发玩法(逗号分隔)", + "comboTriggerPlaysPlaceholder": "请输入玩法编码,多个用逗号分隔,如 straight,ibox", "status": "开关", "disabled": "关闭", "enabled": "开启", diff --git a/src/i18n/locales/zh/players.json b/src/i18n/locales/zh/players.json index fa5089e..c4e4a72 100644 --- a/src/i18n/locales/zh/players.json +++ b/src/i18n/locales/zh/players.json @@ -1,6 +1,22 @@ { "title": "玩家", + "detailTitle": "玩家详情", "listTitle": "玩家列表", + "viewDetail": "查看详情", + "backToList": "返回玩家列表", + "detailSubtitle": "{{site}} · {{sitePlayerId}} · ID {{playerId}}", + "tabOverview": "概览", + "tabTickets": "注单", + "tabWalletTxns": "钱包流水", + "tabTransferOrders": "转账单", + "profileSection": "基本资料", + "walletsSection": "钱包余额", + "createdAt": "注册时间", + "agent": "代理", + "frozen": "冻结", + "txnAmount": "变动金额", + "balanceAfterTxn": "变动后余额", + "invalidPlayerId": "无效的玩家 ID", "createPlayer": "新建玩家", "searchPlaceholder": "按玩家 ID / 用户名 / 昵称搜索", "filterSite": "主站站点", @@ -11,6 +27,10 @@ "siteCodeRequired": "请填写主站编号", "sitePlayerIdRequired": "请填写主站玩家 ID", "createFailed": "创建玩家失败", + "createAgentRequired": "当前账号未绑定代理,无法创建玩家。请使用代理账号登录,或由超管选择有效主站及代理节点。", + "createAgentNode": "归属代理", + "createAgentNodePlaceholder": "选择代理节点", + "createAgentAutoHint": "将归属到您绑定的代理:{{name}}({{code}})", "createSuccess": "已创建玩家 {{name}}", "noChanges": "没有变更", "updateFailed": "更新玩家失败", diff --git a/src/lib/admin-page-title.ts b/src/lib/admin-page-title.ts index e812209..f3de508 100644 --- a/src/lib/admin-page-title.ts +++ b/src/lib/admin-page-title.ts @@ -15,6 +15,12 @@ const EXACT_ROUTES: Record = { "/admin/audit-logs": { ns: "audit", key: "title" }, "/admin/admin-users": { ns: "adminUsers", key: "title" }, "/admin/admin-roles": { ns: "adminRoles", key: "title" }, + "/admin/agents": { ns: "agents", key: "title" }, + "/admin/agents/list": { ns: "agents", key: "directoryTitle" }, + "/admin/agents/provision": { ns: "agents", key: "subnav.provision" }, + "/admin/agents/sites": { ns: "agents", key: "sitesTitle" }, + "/admin/agents/settlement-bills": { ns: "agents", key: "subnav.settlementBills" }, + "/admin/config/integration-sites": { ns: "agents", key: "sitesTitle" }, "/admin/wallet": { ns: "wallet", key: "title" }, "/admin/wallet/transactions": { ns: "wallet", key: "walletTransactions" }, "/admin/wallet/transfer-orders": { ns: "wallet", key: "transferOrders" }, @@ -24,7 +30,6 @@ const EXACT_ROUTES: Record = { "/admin/settings/currencies": { ns: "config", key: "currencies.title" }, "/admin/currencies": { ns: "config", key: "currencies.title" }, "/admin/config": { ns: "config", key: "hub.title" }, - "/admin/config/integration-sites": { ns: "config", key: "integrationSites.title" }, "/admin/rules/plays": { ns: "config", key: "nav.rulesPlaysTitle" }, "/admin/rules/odds": { ns: "config", key: "nav.rulesOddsTitle" }, "/admin/jackpot": { ns: "jackpot", key: "configTitle" }, @@ -39,6 +44,10 @@ type RoutePattern = { }; const ROUTE_PATTERNS: RoutePattern[] = [ + { + test: (p) => /^\/admin\/players\/\d+$/.test(p), + resolve: () => ({ ns: "players", key: "detailTitle" }), + }, { test: (p) => /^\/admin\/draws\/\d+\/finance$/.test(p), resolve: () => ({ ns: "draws", key: "subnav.finance" }), @@ -60,15 +69,13 @@ const ROUTE_PATTERNS: RoutePattern[] = [ resolve: () => ({ ns: "draws", key: "subnav.riskLockLogs" }), }, { - test: (p) => /^\/admin\/draws\/\d+\/risk\/hot$/.test(p) || /^\/admin\/risk\/draws\/\d+\/hot$/.test(p), - resolve: () => ({ ns: "draws", key: "subnav.riskHot" }), - }, - { - test: (p) => /^\/admin\/draws\/\d+\/risk\/sold-out$/.test(p) || /^\/admin\/risk\/draws\/\d+\/sold-out$/.test(p), - resolve: () => ({ ns: "draws", key: "subnav.riskSoldOut" }), - }, - { - test: (p) => /^\/admin\/draws\/\d+\/risk\/pools$/.test(p) || /^\/admin\/risk\/draws\/\d+\/pools$/.test(p), + test: (p) => + /^\/admin\/draws\/\d+\/risk\/pools$/.test(p) + || /^\/admin\/risk\/draws\/\d+\/pools$/.test(p) + || /^\/admin\/draws\/\d+\/risk\/hot$/.test(p) + || /^\/admin\/risk\/draws\/\d+\/hot$/.test(p) + || /^\/admin\/draws\/\d+\/risk\/sold-out$/.test(p) + || /^\/admin\/risk\/draws\/\d+\/sold-out$/.test(p), resolve: () => ({ ns: "draws", key: "subnav.riskPools" }), }, { diff --git a/src/lib/admin-player-paths.ts b/src/lib/admin-player-paths.ts new file mode 100644 index 0000000..4a1823c --- /dev/null +++ b/src/lib/admin-player-paths.ts @@ -0,0 +1,4 @@ +/** 后台玩家详情页(单页聚合资料、注单、钱包)。 */ +export function adminPlayerDetailPath(playerId: number): string { + return `/admin/players/${playerId}`; +} diff --git a/src/lib/admin-prd.ts b/src/lib/admin-prd.ts index 98a9639..684bdd7 100644 --- a/src/lib/admin-prd.ts +++ b/src/lib/admin-prd.ts @@ -10,7 +10,7 @@ export const PRD_PLAYER_FREEZE_MANAGE = "prd.player_freeze.manage" as const; export const PRD_CURRENCY_MANAGE = "prd.currency.manage" as const; -/** 接入站点(integration-sites) */ +/** 接入站点(与 {@link AdminPermissionLanguage} / config admin_permission_language 对齐) */ export const PRD_INTEGRATION_VIEW = "prd.integration.view" as const; export const PRD_INTEGRATION_MANAGE = "prd.integration.manage" as const; @@ -140,6 +140,17 @@ export const PRD_AGENT_ROLE_MANAGE = "prd.agent.role.manage" as const; export const PRD_AGENT_USER_VIEW = "prd.agent.user.view" as const; export const PRD_AGENT_USER_MANAGE = "prd.agent.user.manage" as const; +export const PRD_AGENT_LINE_PROVISION = "prd.agent-line.provision" as const; +export const PRD_AGENT_PROFILE_MANAGE = "prd.agent.profile.manage" as const; + +/** 代理线路内「站点列表」入口(接入权限或线路经营权限) */ +export const PRD_AGENT_SITES_ACCESS_ANY = [ + ...PRD_INTEGRATION_ACCESS_ANY, + PRD_AGENT_LINE_PROVISION, + PRD_AGENT_MANAGE, + PRD_AGENT_VIEW, +] as const; + export const PRD_AGENTS_ACCESS_ANY = [ PRD_AGENT_VIEW, PRD_AGENT_MANAGE, @@ -147,4 +158,24 @@ export const PRD_AGENTS_ACCESS_ANY = [ PRD_AGENT_ROLE_MANAGE, PRD_AGENT_USER_VIEW, PRD_AGENT_USER_MANAGE, + PRD_AGENT_PROFILE_MANAGE, +] as const; + +export const PRD_AGENT_LINE_PROVISION_ACCESS_ANY = [ + PRD_AGENT_LINE_PROVISION, + PRD_AGENT_MANAGE, +] as const; + +export const PRD_SETTLEMENT_AGENT_ACCESS_ANY = [ + "prd.settlement.agent.view", + "prd.settlement.agent.manage", +] as const; + +/** 侧栏「代理线路」分组:含经营、开通、接入配置、代理账单任一权限即可见入口 */ +export const PRD_AGENT_HUB_ACCESS_ANY = [ + ...PRD_AGENTS_ACCESS_ANY, + PRD_AGENT_LINE_PROVISION, + ...PRD_INTEGRATION_ACCESS_ANY, + "prd.settlement.agent.view", + "prd.settlement.agent.manage", ] as const; diff --git a/src/modules/admin-roles/admin-roles-console.tsx b/src/modules/admin-roles/admin-roles-console.tsx index 0cf28b4..e6e8ceb 100644 --- a/src/modules/admin-roles/admin-roles-console.tsx +++ b/src/modules/admin-roles/admin-roles-console.tsx @@ -248,10 +248,10 @@ export function AdminRolesConsole(): React.ReactElement {
- {t("roleListTitle")} + {t("roleListTitle", { defaultValue: "平台角色管理" })} {canManageRoles ? ( ) : null}
@@ -414,15 +414,28 @@ export function AdminRolesConsole(): React.ReactElement {
{t("roleDialog.slug")}
- setRoleSlug(e.target.value)} disabled={roleMode === "edit"} /> + setRoleSlug(e.target.value)} + disabled={roleMode === "edit"} + />
{t("roleDialog.name")}
- setRoleName(e.target.value)} /> + setRoleName(e.target.value)} + />
{t("roleDialog.descriptionLabel")}
- setRoleDescription(e.target.value)} /> + setRoleDescription(e.target.value)} + />
diff --git a/src/modules/admin-users/admin-users-console.tsx b/src/modules/admin-users/admin-users-console.tsx index f145a84..56faf29 100644 --- a/src/modules/admin-users/admin-users-console.tsx +++ b/src/modules/admin-users/admin-users-console.tsx @@ -313,17 +313,19 @@ export function AdminUsersConsole(): React.ReactElement {
- {t("listTitle")} + + {t("listTitle", { defaultValue: "平台账号列表" })} + {canManageUsers ? ( ) : null}
- {t("modelGuide", { + {t("modelGuidePlatform", { defaultValue: - "账号层只绑定角色,不直接分配功能权限;具体权限请到“角色管理”维护。", + "这里只管理平台账号与平台角色。代理账号请到「代理经营」中创建和维护;账号层只绑定角色,不直接分配功能权限。", })}
@@ -545,12 +547,19 @@ export function AdminUsersConsole(): React.ReactElement { - {accountMode === "create" ? t("accountDialog.createTitle") : t("accountDialog.editTitle")} + {accountMode === "create" + ? t("accountDialog.createTitle", { defaultValue: "新建平台账号" }) + : t("accountDialog.editTitle", { defaultValue: "编辑平台账号" })} {accountMode === "create" - ? t("accountDialog.createDescription") - : t("accountDialog.editDescription")} + ? t("accountDialog.createDescriptionPlatform", { + defaultValue: + "须为平台账号指定至少一个平台角色。登录账号仅可使用字母、数字、点、下划线与连字符,保存后为小写。", + }) + : t("accountDialog.editDescriptionPlatform", { + defaultValue: "这里只编辑平台账号。登录账号不可修改,留空密码表示不修改。", + })}
@@ -600,7 +609,11 @@ export function AdminUsersConsole(): React.ReactElement { {accountMode === "create" ? (
{t("accountDialog.rolesRequired")}
-

{t("accountDialog.rolesDescription")}

+

+ {t("accountDialog.rolesDescriptionPlatform", { + defaultValue: "这里只能选择平台角色;代理角色请到「代理经营」中分配。", + })} +

{(catalog?.roles ?? []).length === 0 ? (

diff --git a/src/modules/agents/agent-line-provision-wizard.tsx b/src/modules/agents/agent-line-provision-wizard.tsx new file mode 100644 index 0000000..0170f62 --- /dev/null +++ b/src/modules/agents/agent-line-provision-wizard.tsx @@ -0,0 +1,239 @@ +"use client"; + +import { useState } from "react"; +import { useTranslation } from "react-i18next"; +import { toast } from "sonner"; + +import { postAdminAgentLine } from "@/api/admin-agent-lines"; +import { AdminPageCard } from "@/components/admin/admin-page-card"; +import { Button } from "@/components/ui/button"; +import { Input } from "@/components/ui/input"; +import { Label } from "@/components/ui/label"; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@/components/ui/select"; +import { Switch } from "@/components/ui/switch"; +import { Textarea } from "@/components/ui/textarea"; +import { LotteryApiBizError } from "@/types/api/errors"; + +export function AgentLineProvisionWizard(): React.ReactElement { + const { t } = useTranslation(["agents", "common"]); + const [submitting, setSubmitting] = useState(false); + const [secrets, setSecrets] = useState<{ sso: string; wallet: string } | null>(null); + const [form, setForm] = useState({ + code: "", + name: "", + username: "", + password: "", + currency_code: "NPR", + wallet_api_url: "", + notes: "", + total_share_rate: "0", + credit_limit: "0", + rebate_limit: "0", + default_player_rebate: "0", + settlement_cycle: "weekly" as "daily" | "weekly" | "monthly", + can_grant_extra_rebate: false, + }); + + async function onSubmit(e: React.FormEvent): Promise { + e.preventDefault(); + setSubmitting(true); + setSecrets(null); + try { + const result = await postAdminAgentLine({ + code: form.code.trim().toLowerCase(), + name: form.name.trim(), + username: form.username.trim(), + password: form.password, + currency_code: form.currency_code, + wallet_api_url: form.wallet_api_url.trim() || null, + notes: form.notes.trim() || null, + total_share_rate: Number.parseFloat(form.total_share_rate) || 0, + credit_limit: Number.parseInt(form.credit_limit, 10) || 0, + rebate_limit: Number.parseFloat(form.rebate_limit) || 0, + default_player_rebate: Number.parseFloat(form.default_player_rebate) || 0, + settlement_cycle: form.settlement_cycle, + can_grant_extra_rebate: form.can_grant_extra_rebate, + }); + if (result.secrets) { + setSecrets({ + sso: result.secrets.sso_jwt_secret, + wallet: result.secrets.wallet_api_key, + }); + } + toast.success(t("agents:lineProvision.success", { defaultValue: "线路已开通" })); + } catch (err) { + const msg = + err instanceof LotteryApiBizError ? err.message : t("common:error.generic"); + toast.error(msg); + } finally { + setSubmitting(false); + } + } + + return ( + +

+
+ + setForm((f) => ({ ...f, code: e.target.value }))} + required + pattern="[a-z0-9][a-z0-9_-]*" + /> +
+
+ + setForm((f) => ({ ...f, name: e.target.value }))} + required + /> +
+
+ + setForm((f) => ({ ...f, username: e.target.value }))} + required + /> +
+
+ + setForm((f) => ({ ...f, password: e.target.value }))} + required + minLength={8} + /> +
+
+ + setForm((f) => ({ ...f, wallet_api_url: e.target.value }))} + /> +
+ +

+ {t("agents:profile.section", { defaultValue: "占成与授信" })} +

+
+
+ + setForm((f) => ({ ...f, total_share_rate: e.target.value }))} + /> +
+
+ + setForm((f) => ({ ...f, credit_limit: e.target.value }))} + /> +
+
+ + setForm((f) => ({ ...f, rebate_limit: e.target.value }))} + /> +
+
+ + setForm((f) => ({ ...f, default_player_rebate: e.target.value }))} + /> +
+
+
+ + +
+
+ + setForm((f) => ({ ...f, can_grant_extra_rebate: checked })) + } + /> + +
+ +
+ +