import { useCallback, useMemo, useRef, useState } from "react";
import { AdminPrettyDisplayID, AdminUserIdentity } from "@/shared/ui/AdminUserIdentity.jsx";
import { SideDrawer } from "@/shared/ui/SideDrawer.jsx";
import { useToast } from "@/shared/ui/ToastProvider.jsx";
import { formatMillis } from "@/shared/utils/time.js";
import { getAppUser } from "@/features/app-users/api";
import { appUserStatusLabels } from "@/features/app-users/constants.js";
import { AppUserDetailContext } from "@/features/app-users/components/AppUserDetailContext.jsx";
import {
BanCountdown,
GenderValue,
LevelValue,
OperatorValue,
RolesValue,
formatUsdMinor,
} from "@/features/app-users/components/AppUserValues.jsx";
import styles from "@/features/app-users/app-users.module.css";
export function AppUserDetailProvider({ children }) {
const { showToast } = useToast();
const requestSeqRef = useRef(0);
const [detailUser, setDetailUser] = useState(null);
const [loadingUserId, setLoadingUserId] = useState("");
const closeAppUserDetail = useCallback(() => {
requestSeqRef.current += 1;
setDetailUser(null);
setLoadingUserId("");
}, []);
const openAppUserDetail = useCallback(
async (source) => {
const seedUser = normalizeDetailSeed(source);
if (!seedUser.userId) {
showToast("缺少用户 ID,无法打开用户详情", "error");
return;
}
const requestSeq = requestSeqRef.current + 1;
requestSeqRef.current = requestSeq;
setDetailUser(seedUser);
setLoadingUserId(seedUser.userId);
try {
const latestUser = await getAppUser(seedUser.userId);
if (requestSeqRef.current === requestSeq) {
setDetailUser({ ...seedUser, ...latestUser });
}
} catch (err) {
if (requestSeqRef.current === requestSeq) {
showToast(err.message || "加载用户详情失败", "error");
}
} finally {
if (requestSeqRef.current === requestSeq) {
setLoadingUserId("");
}
}
},
[showToast],
);
const value = useMemo(() => ({ closeAppUserDetail, openAppUserDetail }), [closeAppUserDetail, openAppUserDetail]);
return (
{children}
);
}
function AppUserDetailDrawer({ loading, onClose, open, user }) {
if (!open || !user) {
return null;
}
const balances = user.balances || [];
const equippedResources = user.equippedResources || [];
const resources = user.resources || [];
return (
{loading ? 正在刷新用户详情...
: null}
);
}
function DetailItem({ label, value }) {
const isElement = value && typeof value === "object";
return (
{label}
{isElement ? value : value === undefined || value === null || value === "" ? "-" : value}
);
}
function PrettyValue({ user }) {
if (!user.prettyId || !user.prettyDisplayUserId) {
return "-";
}
return ;
}
function VipDetailValue({ vip }) {
const level = Number(vip?.level || 0);
if (level <= 0 || !vip?.active) {
return "VIP 0";
}
return vip.name ? `VIP ${level} · ${vip.name}` : `VIP ${level}`;
}
function AssetBalanceList({ balances }) {
if (!balances.length) {
return 当前无数据
;
}
return (
{balances.map((balance) => (
{formatAssetType(balance.assetType)}
冻结 {formatNumber(balance.frozenAmount)}
{formatNumber(balance.availableAmount)}
))}
);
}
function ResourceList({ resources }) {
if (!resources.length) {
return 当前无数据
;
}
return (
{resources.map((resource) => (
-
{resource.name || resource.resourceCode || `资源 ${resource.resourceId}`}
{resource.equipped ? 佩戴中 : null}
{formatResourceType(resource.resourceType)} · 剩余{" "}
{formatNumber(resource.remainingQuantity)} / {formatNumber(resource.quantity)}
到期 {formatExpireTime(resource.expiresAtMs)}
))}
);
}
function ResourceThumb({ resource }) {
const imageUrl = resource.previewUrl || resource.assetUrl || resource.animationUrl;
if (!imageUrl) {
return {resource.name?.slice(0, 1) || "资"};
}
return
;
}
function AppUserStatus({ status }) {
const tone = status === "active" ? "running" : "danger";
return (
{appUserStatusLabels[status] || status || "-"}
);
}
function normalizeDetailSeed(source) {
if (source === undefined || source === null) {
return {};
}
if (typeof source === "string" || typeof source === "number") {
return { userId: String(source).trim() };
}
const userId = firstNonEmpty(
source.userId,
source.user_id,
source.assignedUserId,
source.assigned_user_id,
source.targetUserId,
source.target_user_id,
);
return {
...source,
avatar: firstNonEmpty(source.avatar),
defaultDisplayUserId: firstNonEmpty(source.defaultDisplayUserId, source.default_display_user_id),
displayUserId: firstNonEmpty(source.displayUserId, source.display_user_id),
prettyDisplayUserId: firstNonEmpty(source.prettyDisplayUserId, source.pretty_display_user_id),
prettyId: firstNonEmpty(source.prettyId, source.pretty_id),
userId,
username: firstNonEmpty(source.username, source.name, source.account),
};
}
function formatCountry(user) {
return user.countryDisplayName || user.countryName || user.country || "-";
}
function formatNumber(value) {
const number = Number(value || 0);
return Number.isFinite(number) ? number.toLocaleString() : "-";
}
function formatAge(value) {
if (value === undefined || value === null || value === "") {
return "-";
}
const age = Number(value);
return Number.isFinite(age) && age >= 0 ? `${Math.trunc(age)} 岁` : "-";
}
function formatVipExpire(vip) {
if (Number(vip?.level || 0) <= 0) {
return "-";
}
return formatExpireTime(vip?.expiresAtMs);
}
function formatAssetType(value) {
const normalized = String(value || "").toUpperCase();
const labels = {
COIN: "金币",
DIAMOND: "钻石",
};
return labels[normalized] || normalized || "-";
}
function formatResourceType(value) {
const labels = {
avatar_frame: "头像框",
badge: "徽章",
chat_bubble: "聊天气泡",
emoji_pack: "表情包",
floating_screen: "飘屏",
gift: "礼物",
mic_seat_animation: "麦位动画",
mic_seat_icon: "麦位图标",
profile_card: "资料卡",
vehicle: "座驾",
};
return labels[value] || value || "-";
}
function formatExpireTime(value) {
const expiresAtMs = Number(value || 0);
if (!Number.isFinite(expiresAtMs) || expiresAtMs <= 0) {
return "永久";
}
return formatMillis(expiresAtMs);
}
function firstNonEmpty(...values) {
for (const value of values) {
const normalized = String(value ?? "").trim();
if (normalized) {
return normalized;
}
}
return "";
}