refactor: 合并多语言支持的显示名称字段,优化奖池手动爆发功能的返回数据结构,增强管理端权限控制
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
"use client";
|
||||
|
||||
import { useCallback, useEffect, useMemo, useState } from "react";
|
||||
import { useConfirmAction } from "@/hooks/use-confirm-action";
|
||||
import { useExportLabels } from "@/hooks/use-export-labels";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { toast } from "sonner";
|
||||
@@ -9,6 +10,8 @@ import {
|
||||
deleteAdminPlayer,
|
||||
getAdminPlayers,
|
||||
postAdminPlayer,
|
||||
postAdminPlayerFreeze,
|
||||
postAdminPlayerUnfreeze,
|
||||
putAdminPlayer,
|
||||
} from "@/api/admin-player";
|
||||
import { AdminListPaginationFooter } from "@/components/admin/admin-list-pagination-footer";
|
||||
@@ -27,6 +30,7 @@ import {
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import { adminHasAnyPermission } from "@/lib/admin-permissions";
|
||||
import { PRD_PLAYER_FREEZE_MANAGE, PRD_USERS_MANAGE } from "@/lib/admin-prd";
|
||||
import { useAdminProfile } from "@/stores/admin-session";
|
||||
import {
|
||||
Select,
|
||||
@@ -63,10 +67,12 @@ const PLAYER_STATUS_OPTIONS = [
|
||||
|
||||
export function PlayersConsole(): React.ReactElement {
|
||||
const { t } = useTranslation(["players", "common"]);
|
||||
const { request: requestConfirm, ConfirmDialog } = useConfirmAction();
|
||||
const exportLabels = useExportLabels("players");
|
||||
const profile = useAdminProfile();
|
||||
useAdminCurrencyCatalog();
|
||||
const canManagePlayers = adminHasAnyPermission(profile?.permissions, ["prd.users.manage"]);
|
||||
const canManagePlayers = adminHasAnyPermission(profile?.permissions, [PRD_USERS_MANAGE]);
|
||||
const canFreezePlayers = adminHasAnyPermission(profile?.permissions, [PRD_PLAYER_FREEZE_MANAGE]);
|
||||
const [page, setPage] = useState(1);
|
||||
const [perPage, setPerPage] = useState(10);
|
||||
const [keyword, setKeyword] = useState("");
|
||||
@@ -91,6 +97,7 @@ export function PlayersConsole(): React.ReactElement {
|
||||
|
||||
const [deleteTarget, setDeleteTarget] = useState<AdminPlayerRow | null>(null);
|
||||
const [deleteBusy, setDeleteBusy] = useState(false);
|
||||
const [freezeBusyId, setFreezeBusyId] = useState<number | null>(null);
|
||||
|
||||
const editingPlayer = useMemo(
|
||||
() => items.find((p) => p.id === editingAccountId) ?? null,
|
||||
@@ -226,6 +233,28 @@ export function PlayersConsole(): React.ReactElement {
|
||||
}
|
||||
}
|
||||
|
||||
async function toggleFreeze(row: AdminPlayerRow, freeze: boolean): Promise<void> {
|
||||
setFreezeBusyId(row.id);
|
||||
try {
|
||||
const updated = freeze
|
||||
? await postAdminPlayerFreeze(row.id)
|
||||
: await postAdminPlayerUnfreeze(row.id);
|
||||
setItems((prev) => prev.map((r) => (r.id === updated.id ? updated : r)));
|
||||
const name = updated.username ?? updated.site_player_id;
|
||||
toast.success(freeze ? t("freezeSuccess", { name }) : t("unfreezeSuccess", { name }));
|
||||
} catch (e) {
|
||||
const msg =
|
||||
e instanceof LotteryApiBizError
|
||||
? e.message
|
||||
: freeze
|
||||
? t("freezeFailed")
|
||||
: t("unfreezeFailed");
|
||||
toast.error(msg);
|
||||
} finally {
|
||||
setFreezeBusyId(null);
|
||||
}
|
||||
}
|
||||
|
||||
async function confirmDelete(): Promise<void> {
|
||||
if (!deleteTarget) return;
|
||||
setDeleteBusy(true);
|
||||
@@ -364,26 +393,66 @@ export function PlayersConsole(): React.ReactElement {
|
||||
: "—"}
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
{canManagePlayers ? (
|
||||
{canManagePlayers || canFreezePlayers ? (
|
||||
<div className="flex flex-wrap gap-1">
|
||||
<Button
|
||||
type="button"
|
||||
size="sm"
|
||||
variant={
|
||||
accountOpen && editingAccountId === row.id ? "secondary" : "outline"
|
||||
}
|
||||
onClick={() => openEditAccount(row)}
|
||||
>
|
||||
{t("edit")}
|
||||
</Button>
|
||||
<Button
|
||||
type="button"
|
||||
size="sm"
|
||||
variant="destructive"
|
||||
onClick={() => setDeleteTarget(row)}
|
||||
>
|
||||
{t("delete")}
|
||||
</Button>
|
||||
{canFreezePlayers && row.status === 0 ? (
|
||||
<Button
|
||||
type="button"
|
||||
size="sm"
|
||||
variant="outline"
|
||||
disabled={freezeBusyId === row.id}
|
||||
onClick={() => {
|
||||
const name = row.username ?? row.site_player_id;
|
||||
requestConfirm({
|
||||
title: t("confirmFreezeTitle"),
|
||||
description: t("confirmFreezeDescription", { name }),
|
||||
onConfirm: () => toggleFreeze(row, true),
|
||||
});
|
||||
}}
|
||||
>
|
||||
{freezeBusyId === row.id ? t("saving") : t("freeze")}
|
||||
</Button>
|
||||
) : null}
|
||||
{canFreezePlayers && row.status === 1 ? (
|
||||
<Button
|
||||
type="button"
|
||||
size="sm"
|
||||
variant="outline"
|
||||
disabled={freezeBusyId === row.id}
|
||||
onClick={() => {
|
||||
const name = row.username ?? row.site_player_id;
|
||||
requestConfirm({
|
||||
title: t("confirmUnfreezeTitle"),
|
||||
description: t("confirmUnfreezeDescription", { name }),
|
||||
onConfirm: () => toggleFreeze(row, false),
|
||||
});
|
||||
}}
|
||||
>
|
||||
{freezeBusyId === row.id ? t("saving") : t("unfreeze")}
|
||||
</Button>
|
||||
) : null}
|
||||
{canManagePlayers ? (
|
||||
<>
|
||||
<Button
|
||||
type="button"
|
||||
size="sm"
|
||||
variant={
|
||||
accountOpen && editingAccountId === row.id ? "secondary" : "outline"
|
||||
}
|
||||
onClick={() => openEditAccount(row)}
|
||||
>
|
||||
{t("edit")}
|
||||
</Button>
|
||||
<Button
|
||||
type="button"
|
||||
size="sm"
|
||||
variant="destructive"
|
||||
onClick={() => setDeleteTarget(row)}
|
||||
>
|
||||
{t("delete")}
|
||||
</Button>
|
||||
</>
|
||||
) : null}
|
||||
</div>
|
||||
) : (
|
||||
<span className="text-xs text-muted-foreground">—</span>
|
||||
@@ -554,6 +623,7 @@ export function PlayersConsole(): React.ReactElement {
|
||||
</div>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
<ConfirmDialog />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user