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 })) + } + /> + +
+ +
+ +