feat(agents, players): enhance agent console and players panel functionality
Some checks failed
lotteryadmin CI / build (push) Has been cancelled
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:
@@ -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]);
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user