diff --git a/src/features/host-org/pages/HostHostsPage.jsx b/src/features/host-org/pages/HostHostsPage.jsx
index d331644..153b44f 100644
--- a/src/features/host-org/pages/HostHostsPage.jsx
+++ b/src/features/host-org/pages/HostHostsPage.jsx
@@ -1,11 +1,9 @@
-import ArrowDownwardOutlined from "@mui/icons-material/ArrowDownwardOutlined";
-import ArrowUpwardOutlined from "@mui/icons-material/ArrowUpwardOutlined";
-import SwapVertOutlined from "@mui/icons-material/SwapVertOutlined";
import { AdminSwitch } from "@/shared/ui/AdminSwitch.jsx";
import { formatMillis } from "@/shared/utils/time.js";
import { DataState } from "@/shared/ui/DataState.jsx";
import { hostSourceLabels, hostStatusFilters } from "@/features/host-org/constants.js";
import { HostOrgPerson, hostOrgRegionName } from "@/features/host-org/components/HostOrgIdentity.jsx";
+import { HostOrgSortHeader } from "@/features/host-org/components/HostOrgSortHeader.jsx";
import { SalaryWalletAdjustButton, SalaryWalletCell } from "@/features/host-org/components/SalaryWalletControls.jsx";
import { HostOrgTable } from "@/features/host-org/components/HostOrgTable.jsx";
import { useHostHostsPage } from "@/features/host-org/hooks/useHostHostsPage.js";
@@ -29,12 +27,16 @@ const hostColumns = [
{
key: "diamond",
label: "钻石",
+ sortable: true,
+ sortField: "diamond",
render: (item) => formatNumber(item.diamond),
width: "minmax(110px, 0.7fr)",
},
{
key: "salaryWallet",
label: "工资钱包",
+ sortable: true,
+ sortField: "salary_wallet",
render: (item) => (
{
- if (column.key === "user") {
- return {
- ...column,
- filter: createTextColumnFilter({
- placeholder: "搜索用户 ID、展示 ID、用户名、Agency",
- value: page.query,
- onChange: page.changeQuery,
- }),
- };
- }
- if (column.key === "currentAgencyId") {
- return {
- ...column,
- filter: createTextColumnFilter({
- placeholder: "搜索 Agency ID",
- value: page.agencyId,
- onChange: page.changeAgencyId,
- }),
- };
- }
- if (column.key === "status") {
- return {
- ...column,
- filter: createOptionsColumnFilter({
- options: hostStatusFilters,
- placeholder: "搜索状态",
- value: page.status,
- onChange: page.changeStatus,
- }),
- };
- }
- if (column.key === "diamond") {
- return {
- ...column,
- header: (
-
- ),
- };
- }
- return column;
- });
+ ]
+ .filter(Boolean)
+ .map((column) => {
+ if (column.key === "user") {
+ return {
+ ...column,
+ filter: createTextColumnFilter({
+ placeholder: "搜索用户 ID、展示 ID、用户名、Agency",
+ value: page.query,
+ onChange: page.changeQuery,
+ }),
+ };
+ }
+ if (column.key === "currentAgencyId") {
+ return {
+ ...column,
+ filter: createTextColumnFilter({
+ placeholder: "搜索 Agency ID",
+ value: page.agencyId,
+ onChange: page.changeAgencyId,
+ }),
+ };
+ }
+ if (column.key === "status") {
+ return {
+ ...column,
+ filter: createOptionsColumnFilter({
+ options: hostStatusFilters,
+ placeholder: "搜索状态",
+ value: page.status,
+ onChange: page.changeStatus,
+ }),
+ };
+ }
+ if (column.key === "diamond") {
+ return {
+ ...column,
+ header: (
+
+ ),
+ };
+ }
+ if (column.sortable) {
+ return {
+ ...column,
+ header: (
+
+ ),
+ };
+ }
+ return column;
+ });
return (
@@ -170,28 +191,6 @@ function formatNumber(value) {
return Number(value || 0).toLocaleString("zh-CN");
}
-function DiamondSortHeader({ direction, onToggle }) {
- const SortIcon =
- direction === "asc" ? ArrowUpwardOutlined : direction === "desc" ? ArrowDownwardOutlined : SwapVertOutlined;
- const directionLabel = direction === "asc" ? "升序" : "降序";
- const nextDirectionLabel = direction === "desc" ? "升序" : "降序";
- return (
-
- );
-}
-
function HostUser({ item }) {
return (
({
+ useHostAgenciesPage: vi.fn(),
+}));
+
+vi.mock("@/features/host-org/hooks/useHostBdsPage.js", () => ({
+ useHostBdsPage: vi.fn(),
+}));
+
+vi.mock("@/features/host-org/hooks/useHostHostsPage.js", () => ({
+ useHostHostsPage: vi.fn(),
+}));
+
+afterEach(() => {
+ vi.clearAllMocks();
+});
+
+test("agency list salary wallet header requests salary wallet sorting", () => {
+ const changeSort = vi.fn();
+ vi.mocked(useHostAgenciesPage).mockReturnValue(agencyPageFixture({ changeSort }));
+
+ render();
+ fireEvent.click(screen.getByLabelText("按工资钱包降序排序"));
+
+ expect(changeSort).toHaveBeenCalledWith("salary_wallet");
+});
+
+test("bd leader list salary wallet header requests salary wallet sorting", () => {
+ const changeSort = vi.fn();
+ vi.mocked(useHostBdsPage).mockReturnValue(bdPageFixture({ changeSort, profileType: "leader" }));
+
+ render();
+ fireEvent.click(screen.getByLabelText("按工资钱包降序排序"));
+
+ expect(changeSort).toHaveBeenCalledWith("salary_wallet");
+});
+
+test("bd list salary wallet header requests salary wallet sorting", () => {
+ const changeSort = vi.fn();
+ vi.mocked(useHostBdsPage).mockReturnValue(bdPageFixture({ changeSort, profileType: "bd" }));
+
+ render();
+ fireEvent.click(screen.getByLabelText("按工资钱包降序排序"));
+
+ expect(changeSort).toHaveBeenCalledWith("salary_wallet");
+});
+
+test("host list salary wallet header requests salary wallet sorting", () => {
+ const changeSort = vi.fn();
+ vi.mocked(useHostHostsPage).mockReturnValue(hostPageFixture({ changeSort }));
+
+ render();
+ fireEvent.click(screen.getByLabelText("按工资钱包降序排序"));
+
+ expect(changeSort).toHaveBeenCalledWith("salary_wallet");
+});
+
+function agencyPageFixture(patch = {}) {
+ return {
+ abilities: { canCreate: false, canDelete: false, canSalaryAdjust: false, canStatus: false },
+ activeAction: "",
+ agencyForm: { joinEnabled: true, name: "", ownerUserId: "", parentBdUserId: "", reason: "" },
+ changeParentBdUserId: vi.fn(),
+ changeQuery: vi.fn(),
+ changeRegionId: vi.fn(),
+ changeSort: vi.fn(),
+ changeStatus: vi.fn(),
+ closeAction: vi.fn(),
+ closeAgencyFromList: vi.fn(),
+ data: { items: [agencyFixture()], page: 1, pageSize: 50, total: 1 },
+ error: null,
+ loading: false,
+ loadingAction: "",
+ loadingRegions: false,
+ openAgencyForm: vi.fn(),
+ page: 1,
+ parentBdUserId: "",
+ query: "",
+ regionId: "",
+ regionOptions: [],
+ reload: vi.fn(),
+ removeAgencyFromList: vi.fn(),
+ resetFilters: vi.fn(),
+ setAgencyForm: vi.fn(),
+ setPage: vi.fn(),
+ sortBy: "",
+ sortDirection: "",
+ status: "",
+ submitAgency: vi.fn(),
+ toggleAgencyJoinEnabledFromList: vi.fn(),
+ ...patch,
+ };
+}
+
+function bdPageFixture(patch = {}) {
+ return {
+ abilities: { canCreate: false, canSalaryAdjust: false, canUpdate: false, canView: true },
+ activeAction: "",
+ bdForm: { parentLeaderUserId: "", reason: "", targetUserId: "" },
+ bdLeaderForm: { positionAlias: "", reason: "", targetUserId: "" },
+ bdUserLookup: { error: "", loading: false, user: null },
+ changeParentLeaderUserId: vi.fn(),
+ changeQuery: vi.fn(),
+ changeRegionId: vi.fn(),
+ changeSort: vi.fn(),
+ changeStatus: vi.fn(),
+ closeAction: vi.fn(),
+ data: { items: [bdFixture()], page: 1, pageSize: 50, total: 1 },
+ error: null,
+ leaderBDs: { items: [], page: 1, pageSize: 50, total: 0 },
+ leaderBDsError: "",
+ leaderBDsLoading: false,
+ loadLeaderBDs: vi.fn(),
+ loading: false,
+ loadingAction: "",
+ loadingRegions: false,
+ openAddLeaderBD: vi.fn(),
+ openBDForm: vi.fn(),
+ openBDLeaderForm: vi.fn(),
+ openLeaderBDs: vi.fn(),
+ page: 1,
+ parentLeaderUserId: "",
+ profileType: "bd",
+ query: "",
+ regionId: "",
+ regionOptions: [],
+ reload: vi.fn(),
+ removeBDLeader: vi.fn(),
+ resetFilters: vi.fn(),
+ saveBDLeaderPositionAlias: vi.fn(),
+ selectedLeader: null,
+ setBDForm: vi.fn(),
+ setBDLeaderForm: vi.fn(),
+ setPage: vi.fn(),
+ sortBy: "",
+ sortDirection: "",
+ status: "",
+ submitBD: vi.fn(),
+ submitBDLeader: vi.fn(),
+ submitLeaderBD: vi.fn(),
+ toggleBD: vi.fn(),
+ toggleBDLeader: vi.fn(),
+ ...patch,
+ };
+}
+
+function hostPageFixture(patch = {}) {
+ return {
+ abilities: { canSalaryAdjust: false, canView: true },
+ agencyId: "",
+ changeAgencyId: vi.fn(),
+ changeQuery: vi.fn(),
+ changeRegionId: vi.fn(),
+ changeSort: vi.fn(),
+ changeStatus: vi.fn(),
+ data: { items: [hostFixture()], page: 1, pageSize: 50, total: 1 },
+ diamondSortDirection: "",
+ error: null,
+ loading: false,
+ loadingRegions: false,
+ page: 1,
+ query: "",
+ regionId: "",
+ regionOptions: [],
+ reload: vi.fn(),
+ resetFilters: vi.fn(),
+ setPage: vi.fn(),
+ sortBy: "",
+ sortDirection: "",
+ status: "",
+ toggleDiamondSort: vi.fn(),
+ ...patch,
+ };
+}
+
+function agencyFixture() {
+ return {
+ agencyId: "9001",
+ joinEnabled: true,
+ name: "Agency One",
+ ownerDisplayUserId: "160001",
+ ownerUserId: "1001",
+ ownerUsername: "agency_owner",
+ parentBdUserId: "2001",
+ regionName: "中东区",
+ salaryWallet: { availableAmount: 1200 },
+ status: "active",
+ updatedAtMs: 1760000000000,
+ };
+}
+
+function bdFixture() {
+ return {
+ createdAtMs: 1760000000000,
+ displayUserId: "160002",
+ parentLeaderUserId: "3001",
+ regionName: "中东区",
+ role: "bd",
+ salaryWallet: { availableAmount: 1200 },
+ status: "active",
+ subBdCount: 0,
+ updatedAtMs: 1760000000000,
+ userId: "2001",
+ username: "bd_user",
+ };
+}
+
+function hostFixture() {
+ return {
+ currentAgencyId: "9001",
+ diamond: 100,
+ displayUserId: "160003",
+ firstBecameHostAtMs: 1760000000000,
+ regionName: "中东区",
+ salaryWallet: { availableAmount: 1200 },
+ source: "agency_invite",
+ status: "active",
+ updatedAtMs: 1760000000000,
+ userId: "3001",
+ username: "host_user",
+ };
+}