import { useCallback, useMemo, useState } from "react"; import { parseForm } from "@/shared/forms/validation"; import { useAdminQuery } from "@/shared/hooks/useAdminQuery.js"; import { useToast } from "@/shared/ui/ToastProvider.jsx"; import { createCountry, disableCountry, enableCountry, getJob, listCountries, renameCountryCode, updateCountry, } from "@/features/host-org/api"; import { useCountryAbilities } from "@/features/host-org/permissions.js"; import { countryCreateSchema, countryRenameCodeSchema, countryUpdateSchema } from "@/features/host-org/schema"; const emptyCountryForm = () => ({ countryCode: "", countryDisplayName: "", countryName: "", enabled: true, flag: "", isoAlpha3: "", isoNumeric: "", phoneCountryCode: "", sortOrder: 0, }); const emptyData = { items: [], total: 0 }; export function useHostCountriesPage() { const abilities = useCountryAbilities(); const { showToast } = useToast(); const [query, setQuery] = useState(""); const [enabledStatus, setEnabledStatus] = useState(""); const [countryForm, setCountryForm] = useState(emptyCountryForm); const [editingCountry, setEditingCountry] = useState(null); const [loadingAction, setLoadingAction] = useState(""); const [activeAction, setActiveAction] = useState(""); const filters = useMemo( () => ({ enabled: enabledStatus === "enabled" ? true : enabledStatus === "disabled" ? false : undefined, }), [enabledStatus], ); const queryFn = useCallback(() => listCountries(filters), [filters]); const { data = emptyData, error, loading, reload, } = useAdminQuery(queryFn, { errorMessage: "加载国家列表失败", initialData: emptyData, keepPreviousData: true, queryKey: ["host-org", "countries", filters], }); const items = useMemo(() => filterCountries(data?.items || [], query), [data?.items, query]); const changeQuery = (value) => { setQuery(value); }; const changeEnabledStatus = (value) => { setEnabledStatus(value); }; const resetFilters = () => { setQuery(""); setEnabledStatus(""); }; const openCreateCountry = () => { setEditingCountry(null); setCountryForm(emptyCountryForm()); setActiveAction("create"); }; const openEditCountry = (country) => { setEditingCountry(country); setCountryForm({ countryCode: country.countryCode || "", countryDisplayName: country.countryDisplayName || "", countryName: country.countryName || "", enabled: Boolean(country.enabled), flag: country.flag || "", isoAlpha3: country.isoAlpha3 || "", isoNumeric: country.isoNumeric || "", phoneCountryCode: country.phoneCountryCode || "", sortOrder: country.sortOrder || 0, }); setActiveAction("edit"); }; const submitCountry = async (event) => { event.preventDefault(); const isEdit = activeAction === "edit"; if (!isEdit) { await runAction("country-create", "国家已创建", async () => { const payload = parseForm(countryCreateSchema, countryForm); const data = await createCountry(payload); setCountryForm(emptyCountryForm()); setEditingCountry(null); setActiveAction(""); await reload(); return data; }); return; } const currentCountryCode = normalizeCountryCode(editingCountry?.countryCode); const nextCountryCode = normalizeCountryCode(countryForm.countryCode); if (currentCountryCode !== nextCountryCode) { await runCountryCodeRename(); return; } await runAction("country-edit", "国家已更新", async () => { const { countryCode: _countryCode, enabled: _enabled, ...formWithoutImmutableFields } = countryForm; const payload = parseForm(countryUpdateSchema, formWithoutImmutableFields); const data = await updateCountry(editingCountry.countryId, payload); setCountryForm(emptyCountryForm()); setEditingCountry(null); setActiveAction(""); await reload(); return data; }); }; const runCountryCodeRename = async () => { setLoadingAction("country-rename"); try { const payload = parseForm(countryRenameCodeSchema, countryForm); const job = await renameCountryCode(editingCountry.countryId, payload); showToast(`国家码重命名任务已创建:#${job.jobId}`, "success"); setCountryForm(emptyCountryForm()); setEditingCountry(null); setActiveAction(""); // 国家码会同步 user/admin/room 多个当前态,只有 job succeeded 后才刷新列表,避免短暂显示新旧码混杂。 await waitCountryCodeRenameJob(job.jobId); await reload(); showToast("国家码重命名完成", "success"); } catch (err) { showToast(err.message || "国家码重命名失败", "error"); } finally { setLoadingAction(""); } }; const toggleCountry = async (country, nextEnabled = !country.enabled) => { if (!abilities.canStatus || Boolean(country.enabled) === nextEnabled) { return; } await runAction(`country-status-${country.countryId}`, nextEnabled ? "国家已启用" : "国家已禁用", async () => { const data = nextEnabled ? await enableCountry(country.countryId) : await disableCountry(country.countryId); await reload(); return data; }); }; const runAction = async (action, successMessage, submitter) => { setLoadingAction(action); try { await submitter(); showToast(successMessage, "success"); } catch (err) { showToast(err.message || "操作失败", "error"); } finally { setLoadingAction(""); } }; return { abilities, activeAction, changeEnabledStatus, changeQuery, closeAction: () => setActiveAction(""), countryForm, data: { ...data, items, total: items.length }, editingCountry, enabledStatus, error, loading, loadingAction, openCreateCountry, openEditCountry, query, reload, resetFilters, setCountryForm, submitCountry, toggleCountry, }; } async function waitCountryCodeRenameJob(jobId) { const deadline = Date.now() + 120000; while (Date.now() < deadline) { const job = await getJob(jobId); if (job.status === "succeeded") { return job; } if (job.status === "failed" || job.status === "canceled") { throw new Error(job.error || "国家码重命名任务失败"); } await delay(1500); } throw new Error("国家码重命名任务仍在处理中,请稍后刷新查看"); } function delay(ms) { return new Promise((resolve) => { window.setTimeout(resolve, ms); }); } function normalizeCountryCode(value) { return String(value || "") .trim() .toUpperCase(); } function filterCountries(items, query) { const keyword = query.trim().toLowerCase(); if (!keyword) { return items; } return items.filter((item) => { return [ item.countryCode, item.countryDisplayName, item.countryName, item.isoAlpha3, item.isoNumeric, item.phoneCountryCode, ] .filter(Boolean) .some((value) => String(value).toLowerCase().includes(keyword)); }); }