2026-06-29 14:26:00 +08:00

201 lines
6.2 KiB
JavaScript

import { useCallback, useEffect, useMemo, useState } from "react";
import { usePaginatedQuery } from "@/shared/hooks/usePaginatedQuery.js";
import { useToast } from "@/shared/ui/ToastProvider.jsx";
import { emptyUserIdentityFilter, userIdentityFilterParams } from "@/shared/ui/userIdentityFilter.js";
import { listResourceGroups } from "@/features/resources/api";
import {
getRegistrationRewardConfig,
listRegistrationRewardClaims,
updateRegistrationRewardConfig,
} from "@/features/registration-reward/api";
import { useRegistrationRewardAbilities } from "@/features/registration-reward/permissions.js";
const pageSize = 50;
const emptyClaims = { items: [], page: 1, pageSize, todayClaimedCount: 0, total: 0 };
const emptyTimeRange = { endMs: "", startMs: "" };
const emptyForm = {
coinAmount: "",
dailyLimit: "0",
enabled: false,
resourceGroupId: "",
rewardType: "coin",
};
export function useRegistrationRewardPage() {
const abilities = useRegistrationRewardAbilities();
const { showToast } = useToast();
const [config, setConfig] = useState(null);
const [configDrawerOpen, setConfigDrawerOpen] = useState(false);
const [form, setForm] = useState(emptyForm);
const [configLoading, setConfigLoading] = useState(false);
const [configSaving, setConfigSaving] = useState(false);
const [resourceGroups, setResourceGroups] = useState([]);
const [userFilter, setUserFilter] = useState(emptyUserIdentityFilter);
const [status, setStatus] = useState("");
const [timeRange, setTimeRange] = useState(emptyTimeRange);
const [page, setPage] = useState(1);
const filters = useMemo(
() => ({
claimed_end_ms: timeRange.endMs || undefined,
claimed_start_ms: timeRange.startMs || undefined,
status,
...userIdentityFilterParams(userFilter),
}),
[status, timeRange, userFilter],
);
const {
data: claims = emptyClaims,
error: claimsError,
loading: claimsLoading,
reload: reloadClaims,
} = usePaginatedQuery({
errorMessage: "加载领取记录失败",
fetcher: listRegistrationRewardClaims,
filters,
page,
pageSize,
queryKey: ["registration-reward-claims", filters, page],
});
const reloadConfig = useCallback(async () => {
setConfigLoading(true);
try {
const [config, groups] = await Promise.all([
getRegistrationRewardConfig(),
listResourceGroups({ page: 1, page_size: 100, status: "active" }),
]);
setConfig(config);
setForm(formFromConfig(config));
setResourceGroups(groups.items || []);
} catch (err) {
showToast(err.message || "加载注册奖励配置失败", "error");
} finally {
setConfigLoading(false);
}
}, [showToast]);
useEffect(() => {
void reloadConfig();
}, [reloadConfig]);
const openConfigDrawer = useCallback(() => {
if (config) {
setForm(formFromConfig(config));
}
setConfigDrawerOpen(true);
}, [config]);
const closeConfigDrawer = useCallback(() => {
setConfigDrawerOpen(false);
if (config) {
setForm(formFromConfig(config));
return;
}
setForm(emptyForm);
}, [config]);
const changeUserFilter = (value) => {
setUserFilter(value);
setPage(1);
};
const changeStatus = (value) => {
setStatus(value);
setPage(1);
};
const changeTimeRange = (value) => {
setTimeRange(value || emptyTimeRange);
setPage(1);
};
const resetFilters = () => {
setUserFilter(emptyUserIdentityFilter);
setStatus("");
setTimeRange(emptyTimeRange);
setPage(1);
};
const submitConfig = async (event) => {
event.preventDefault();
if (!abilities.canUpdate) {
return;
}
setConfigSaving(true);
try {
const saved = await updateRegistrationRewardConfig(payloadFromForm(form));
setConfig(saved);
setForm(formFromConfig(saved));
setConfigDrawerOpen(false);
showToast("注册奖励配置已保存", "success");
await reloadClaims();
} catch (err) {
showToast(err.message || "保存注册奖励配置失败", "error");
} finally {
setConfigSaving(false);
}
};
return {
abilities,
changeStatus,
changeTimeRange,
changeUserFilter,
claims,
claimsError,
claimsLoading,
closeConfigDrawer,
config,
configDrawerOpen,
configLoading,
configSaving,
form,
openConfigDrawer,
page,
reloadClaims,
reloadConfig,
resetFilters,
resourceGroups,
setForm,
setPage,
status,
submitConfig,
timeRange,
userFilter,
};
}
function formFromConfig(config) {
return {
coinAmount: config.coinAmount ? String(config.coinAmount) : "",
dailyLimit: String(config.dailyLimit || 0),
enabled: Boolean(config.enabled),
resourceGroupId: config.resourceGroupId ? String(config.resourceGroupId) : "",
rewardType: config.rewardType || "coin",
};
}
function payloadFromForm(form) {
const rewardType = form.rewardType === "resource_group" ? "resource_group" : "coin";
const coinAmount = Number(form.coinAmount || 0);
const resourceGroupId = Number(form.resourceGroupId || 0);
const dailyLimit = Number(form.dailyLimit || 0);
if (dailyLimit < 0) {
throw new Error("每日限制不能小于 0");
}
if (rewardType === "coin" && coinAmount <= 0) {
throw new Error("金币奖励必须大于 0");
}
if (rewardType === "resource_group" && resourceGroupId <= 0) {
throw new Error("请选择资源组");
}
return {
coin_amount: rewardType === "coin" ? coinAmount : 0,
daily_limit: dailyLimit,
enabled: Boolean(form.enabled),
resource_group_id: rewardType === "resource_group" ? resourceGroupId : 0,
reward_type: rewardType,
};
}