feat(agents, players): enhance agent console and players panel functionality
Some checks failed
lotteryadmin CI / build (push) Has been cancelled

Updated the AgentsConsole to improve node ID handling from URL parameters, ensuring better validation and selection logic. Enhanced the AgentsPlayersPanel by introducing new permissions for managing bills and refining the conditions for displaying bill actions based on user roles. Improved overall code clarity and maintainability through refactoring and added checks for selected bills.
This commit is contained in:
2026-06-17 15:23:46 +08:00
parent 7224d044ae
commit d6c7d2c361
4 changed files with 66 additions and 26 deletions

View File

@@ -160,7 +160,10 @@ export function AgentsConsole(): React.ReactElement {
isSuperAdmin ||
adminHasAnyPermission(profile?.permissions, [PRD_USERS_MANAGE]);
const [rootProfile, setRootProfile] = useState<AgentProfileRow | null>(null);
const selectedNodeIdFromUrl = Number.parseInt(searchParams.get("agent_node_id") ?? "", 10);
const selectedNodeIdFromUrl = Number.parseInt(
searchParams.get("agent_node_id") ?? searchParams.get("node") ?? "",
10,
);
const resetProfileForm = (mode: "create" | "edit" = "create") => {
setProfileShareRate("0");
@@ -417,14 +420,31 @@ export function AgentsConsole(): React.ReactElement {
]);
useEffect(() => {
if (!Number.isInteger(selectedNodeIdFromUrl) || selectedNodeIdFromUrl <= 0) {
if (visibleAgentRows.length === 0) {
setSelectedNodeId(null);
return;
}
if (!flatNodes.some((node) => node.id === selectedNodeIdFromUrl)) {
return;
}
setSelectedNodeId(selectedNodeIdFromUrl);
}, [flatNodes, selectedNodeIdFromUrl]);
const urlId =
Number.isInteger(selectedNodeIdFromUrl) && selectedNodeIdFromUrl > 0
? selectedNodeIdFromUrl
: null;
const urlValid =
urlId !== null && visibleAgentRows.some((row) => row.id === urlId);
setSelectedNodeId((current) => {
if (urlValid) {
return urlId;
}
if (
current !== null &&
visibleAgentRows.some((row) => row.id === current)
) {
return current;
}
return visibleAgentRows[0]?.id ?? null;
});
}, [visibleAgentRows, selectedNodeIdFromUrl]);
useAsyncEffect(() => {
if (selectedNode === null) {
@@ -433,16 +453,34 @@ export function AgentsConsole(): React.ReactElement {
return;
}
const nodeId = selectedNode.id;
let cancelled = false;
setSelectedProfileLoading(true);
void getAgentNodeProfile(selectedNode.id)
void getAgentNodeProfile(nodeId)
.then((row) => {
if (cancelled) {
return;
}
setSelectedProfile(row);
if (!nodeDialogOpen) {
applyProfileRowToForm(row);
}
})
.catch(() => setSelectedProfile(null))
.finally(() => setSelectedProfileLoading(false));
.catch(() => {
if (!cancelled) {
setSelectedProfile(null);
}
})
.finally(() => {
if (!cancelled) {
setSelectedProfileLoading(false);
}
});
return () => {
cancelled = true;
};
}, [selectedNode?.id, nodeDialogOpen]);
useAsyncEffect(() => {
@@ -532,17 +570,6 @@ export function AgentsConsole(): React.ReactElement {
selectedProfileLoading,
]);
useAsyncEffect(() => {
if (visibleAgentRows.length === 0) {
setSelectedNodeId(null);
return;
}
if (selectedNodeId === null || !visibleAgentRows.some((row) => row.id === selectedNodeId)) {
setSelectedNodeId(visibleAgentRows[0]?.id ?? null);
}
}, [visibleAgentRows, selectedNodeId]);
useEffect(() => {
setDetailTab("overview");
}, [selectedNodeId]);

View File

@@ -66,8 +66,9 @@ import {
validateNativePlayerUsername,
} from "@/lib/admin-input-validation";
import { adminHasAnyPermission } from "@/lib/admin-permissions";
import { PRD_USERS_MANAGE } from "@/lib/admin-prd";
import { PRD_SETTLEMENT_AGENT_MANAGE, PRD_USERS_MANAGE } from "@/lib/admin-prd";
import { isSiteAdminOperator } from "@/lib/admin-session-variants";
import { settlementBillOperableByBoundAgent } from "@/modules/settlement/settlement-bill-operable";
import { resolveRoleStatusTone } from "@/lib/admin-status-tone";
import { useAdminProfile } from "@/stores/admin-session";
import { LotteryApiBizError } from "@/types/api/errors";
@@ -182,6 +183,10 @@ export function AgentsPlayersPanel({
(profileAllowsCreate &&
adminHasAnyPermission(profile?.permissions, [PRD_USERS_MANAGE]));
const canManagePlayerRows = canCreatePlayer;
const canOperateBills =
isSuperAdmin ||
adminHasAnyPermission(profile?.permissions, [PRD_SETTLEMENT_AGENT_MANAGE]);
const canFinanceAdjustments = canOperateBills && boundAgent === null;
const effectiveAgentId = useMemo(() => {
if (agentNodeId !== null) {
@@ -597,6 +602,10 @@ export function AgentsPlayersPanel({
() => billingBills.find((bill) => bill.id === selectedBillId) ?? null,
[billingBills, selectedBillId],
);
const canOperateSelectedBill =
selectedBill !== null &&
canOperateBills &&
settlementBillOperableByBoundAgent(selectedBill, boundAgent);
const billingCurrency = billingPlayer?.default_currency ?? "NPR";
function resetBillingForm(): void {
@@ -1163,13 +1172,13 @@ export function AgentsPlayersPanel({
</div>
</div>
{selectedBill.status === "pending_confirm" ? (
{canOperateSelectedBill && selectedBill.status === "pending_confirm" ? (
<Button type="button" className="w-full" disabled={billingBusy || confirmBusy} onClick={requestConfirmBillAction}>
{t("agents:settlementBills.confirm", { defaultValue: "确认账单" })}
</Button>
) : null}
{selectedBill.status !== "pending_confirm" && Number(selectedBill.unpaid_amount ?? 0) > 0 ? (
{canOperateSelectedBill && selectedBill.status !== "pending_confirm" && Number(selectedBill.unpaid_amount ?? 0) > 0 ? (
<div className="space-y-3">
<div className="space-y-1">
<Label>{t("agents:settlementBills.paymentAmount", { defaultValue: "收付金额" })}</Label>
@@ -1207,7 +1216,10 @@ export function AgentsPlayersPanel({
{t("agents:settlementBills.paid", { defaultValue: "登记收付" })}
</Button>
{boundAgent === null ? (
{canFinanceAdjustments &&
["confirmed", "partial_paid", "overdue"].includes(selectedBill.status) &&
Number(selectedBill.unpaid_amount ?? 0) > 0 &&
!["adjustment", "reversal", "bad_debt"].includes(selectedBill.bill_type) ? (
<>
<div className="space-y-1 pt-2">
<Label>{t("agents:settlementBills.badDebtReason", { defaultValue: "核销原因" })}</Label>