工资相关

This commit is contained in:
zhx 2026-06-15 10:43:53 +08:00
parent 1447de8563
commit 64cfa472b8
10 changed files with 491 additions and 132 deletions

View File

@ -34,8 +34,23 @@ test("host org list APIs use generated admin paths and filters", async () => {
),
);
await listBDs({ page: 1, page_size: 10, parent_leader_user_id: 21, region_id: 7, status: "active" });
await listAgencies({ keyword: "agency", page: 2, page_size: 10, parent_bd_user_id: 31 });
await listBDs({
page: 1,
page_size: 10,
parent_leader_user_id: 21,
region_id: 7,
sort_by: "salary_wallet",
sort_direction: "asc",
status: "active",
});
await listAgencies({
keyword: "agency",
page: 2,
page_size: 10,
parent_bd_user_id: 31,
sort_by: "salary_wallet",
sort_direction: "desc",
});
await listHosts({ agency_id: 41, page: 1, page_size: 10, sort_by: "diamond", sort_direction: "desc" });
await listCoinSellers({
page: 1,
@ -84,10 +99,14 @@ test("host org list APIs use generated admin paths and filters", async () => {
expect(String(bdUrl)).toContain("/api/v1/admin/bds?");
expect(String(bdUrl)).toContain("parent_leader_user_id=21");
expect(String(bdUrl)).toContain("region_id=7");
expect(String(bdUrl)).toContain("sort_by=salary_wallet");
expect(String(bdUrl)).toContain("sort_direction=asc");
expect(String(bdUrl)).toContain("status=active");
expect(bdInit?.method).toBe("GET");
expect(String(agencyUrl)).toContain("/api/v1/admin/agencies?");
expect(String(agencyUrl)).toContain("parent_bd_user_id=31");
expect(String(agencyUrl)).toContain("sort_by=salary_wallet");
expect(String(agencyUrl)).toContain("sort_direction=desc");
expect(String(hostUrl)).toContain("/api/v1/admin/hosts?");
expect(String(hostUrl)).toContain("agency_id=41");
expect(String(hostUrl)).toContain("sort_by=diamond");

View File

@ -0,0 +1,32 @@
import ArrowDownwardOutlined from "@mui/icons-material/ArrowDownwardOutlined";
import ArrowUpwardOutlined from "@mui/icons-material/ArrowUpwardOutlined";
import SwapVertOutlined from "@mui/icons-material/SwapVertOutlined";
export function HostOrgSortHeader({ field, label, onToggle, sortBy, sortDirection }) {
const active = sortBy === field;
const SortIcon =
active && sortDirection === "asc"
? ArrowUpwardOutlined
: active && sortDirection === "desc"
? ArrowDownwardOutlined
: SwapVertOutlined;
const directionLabel = sortDirection === "asc" ? "升序" : "降序";
const nextDirectionLabel = active && sortDirection === "desc" ? "升序" : "降序";
return (
<button
aria-label={`${label}${nextDirectionLabel}排序`}
className={["admin-cell__head-trigger", active ? "admin-cell__head-trigger--active" : ""]
.filter(Boolean)
.join(" ")}
type="button"
onClick={() => onToggle(field)}
>
<SortIcon className="admin-cell__head-icon" fontSize="inherit" />
<span className="admin-cell__head-label">
{label}
{active ? <span className="admin-cell__head-filter">({directionLabel})</span> : null}
</span>
</button>
);
}

View File

@ -28,6 +28,8 @@ export function useHostAgenciesPage() {
const [status, setStatus] = useState("");
const [regionId, setRegionId] = useState("");
const [parentBdUserId, setParentBdUserId] = useState("");
const [sortBy, setSortBy] = useState("");
const [sortDirection, setSortDirection] = useState("");
const [page, setPage] = useState(1);
const [agencyForm, setAgencyForm] = useState(emptyAgencyForm);
const [rowPatches, setRowPatches] = useState({});
@ -39,9 +41,11 @@ export function useHostAgenciesPage() {
keyword: query,
parent_bd_user_id: parentBdUserId,
region_id: regionId,
sort_by: sortBy,
sort_direction: sortDirection,
status,
}),
[parentBdUserId, query, regionId, status],
[parentBdUserId, query, regionId, sortBy, sortDirection, status],
);
const {
data = emptyData,
@ -97,11 +101,19 @@ export function useHostAgenciesPage() {
setPage(1);
};
const changeSort = (field) => {
setSortDirection((current) => (sortBy === field ? (current === "asc" ? "desc" : "asc") : "desc"));
setSortBy(field);
setPage(1);
};
const resetFilters = () => {
setQuery("");
setStatus("");
setRegionId("");
setParentBdUserId("");
setSortBy("");
setSortDirection("");
setPage(1);
};
@ -212,8 +224,11 @@ export function useHostAgenciesPage() {
removeAgencyFromList,
setAgencyForm,
setPage,
sortBy,
sortDirection,
status,
submitAgency,
changeSort,
toggleAgencyJoinEnabledFromList,
};
}

View File

@ -38,6 +38,8 @@ export function useHostBdsPage({ profileType = "bd" } = {}) {
const [status, setStatus] = useState("");
const [regionId, setRegionId] = useState("");
const [parentLeaderUserId, setParentLeaderUserId] = useState("");
const [sortBy, setSortBy] = useState("");
const [sortDirection, setSortDirection] = useState("");
const [page, setPage] = useState(1);
const [bdLeaderForm, setBDLeaderForm] = useState(emptyBDLeaderForm);
const [bdForm, setBDForm] = useState(emptyBDForm);
@ -54,9 +56,11 @@ export function useHostBdsPage({ profileType = "bd" } = {}) {
keyword: query,
parent_leader_user_id: profileType === "bd" ? parentLeaderUserId : "",
region_id: regionId,
sort_by: sortBy,
sort_direction: sortDirection,
status,
}),
[parentLeaderUserId, profileType, query, regionId, status],
[parentLeaderUserId, profileType, query, regionId, sortBy, sortDirection, status],
);
const fetcher = profileType === "bd" ? listBDs : listBDLeaders;
const {
@ -158,11 +162,19 @@ export function useHostBdsPage({ profileType = "bd" } = {}) {
setPage(1);
};
const changeSort = (field) => {
setSortDirection((current) => (sortBy === field ? (current === "asc" ? "desc" : "asc") : "desc"));
setSortBy(field);
setPage(1);
};
const resetFilters = () => {
setQuery("");
setStatus("");
setRegionId("");
setParentLeaderUserId("");
setSortBy("");
setSortDirection("");
setPage(1);
};
@ -361,10 +373,13 @@ export function useHostBdsPage({ profileType = "bd" } = {}) {
setBDForm,
setBDLeaderForm,
setPage,
sortBy,
sortDirection,
status,
submitBD,
submitBDLeader,
submitLeaderBD,
changeSort,
toggleBD,
toggleBDLeader,
};

View File

@ -14,18 +14,19 @@ export function useHostHostsPage() {
const [status, setStatus] = useState("");
const [regionId, setRegionId] = useState("");
const [agencyId, setAgencyId] = useState("");
const [diamondSortDirection, setDiamondSortDirection] = useState("");
const [sortBy, setSortBy] = useState("");
const [sortDirection, setSortDirection] = useState("");
const [page, setPage] = useState(1);
const filters = useMemo(
() => ({
agency_id: agencyId,
keyword: query,
region_id: regionId,
sort_by: diamondSortDirection ? "diamond" : "",
sort_direction: diamondSortDirection,
sort_by: sortBy,
sort_direction: sortDirection,
status,
}),
[agencyId, diamondSortDirection, query, regionId, status],
[agencyId, query, regionId, sortBy, sortDirection, status],
);
const {
data = emptyData,
@ -61,8 +62,9 @@ export function useHostHostsPage() {
setPage(1);
};
const toggleDiamondSort = () => {
setDiamondSortDirection((current) => (current === "desc" ? "asc" : "desc"));
const changeSort = (field) => {
setSortDirection((current) => (sortBy === field ? (current === "asc" ? "desc" : "asc") : "desc"));
setSortBy(field);
setPage(1);
};
@ -71,7 +73,8 @@ export function useHostHostsPage() {
setStatus("");
setRegionId("");
setAgencyId("");
setDiamondSortDirection("");
setSortBy("");
setSortDirection("");
setPage(1);
};
@ -83,7 +86,7 @@ export function useHostHostsPage() {
changeRegionId,
changeStatus,
data,
diamondSortDirection,
diamondSortDirection: sortBy === "diamond" ? sortDirection : "",
error,
loading,
loadingRegions,
@ -94,7 +97,10 @@ export function useHostHostsPage() {
reload,
resetFilters,
setPage,
sortBy,
sortDirection,
status,
toggleDiamondSort,
changeSort,
toggleDiamondSort: () => changeSort("diamond"),
};
}

View File

@ -7,6 +7,7 @@ import { DataState } from "@/shared/ui/DataState.jsx";
import { agencyStatusFilters } from "@/features/host-org/constants.js";
import { HostOrgActionModal } from "@/features/host-org/components/HostOrgActionModal.jsx";
import { HostOrgEntity, 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 { HostOrgToolbar } from "@/features/host-org/components/HostOrgToolbar.jsx";
@ -31,6 +32,8 @@ const agencyColumns = [
{
key: "salaryWallet",
label: "工资钱包",
sortable: true,
sortField: "salary_wallet",
render: (item) => (
<SalaryWalletCell
role="agency"
@ -121,15 +124,27 @@ export function HostAgenciesPage() {
}),
};
}
if (column.sortable) {
return {
...column,
header: (
<HostOrgSortHeader
field={column.sortField}
label={column.label}
sortBy={page.sortBy}
sortDirection={page.sortDirection}
onToggle={page.changeSort}
/>
),
};
}
return column;
});
return (
<section className={styles.root}>
<div className={styles.contentPanel}>
<HostOrgToolbar
actions={toolbarActions}
/>
<HostOrgToolbar actions={toolbarActions} />
<DataState error={page.error} loading={page.loading} onRetry={page.reload}>
<div className={styles.listBlock}>
@ -191,9 +206,7 @@ export function HostAgenciesPage() {
disabled={createDisabled}
label="入会状态"
uncheckedLabel="禁止"
onChange={(event) =>
page.setAgencyForm({ ...page.agencyForm, joinEnabled: event.target.checked })
}
onChange={(event) => page.setAgencyForm({ ...page.agencyForm, joinEnabled: event.target.checked })}
/>
<TextField
disabled={createDisabled}

View File

@ -10,6 +10,7 @@ import { AdminActionIconButton, AdminRowActions } from "@/shared/ui/AdminListLay
import { AdminUserIdentity } from "@/shared/ui/AdminUserIdentity.jsx";
import { SideDrawer } from "@/shared/ui/SideDrawer.jsx";
import { HostOrgActionModal } from "@/features/host-org/components/HostOrgActionModal.jsx";
import { HostOrgSortHeader } from "@/features/host-org/components/HostOrgSortHeader.jsx";
import { SalaryWalletAdjustButton, SalaryWalletCell } from "@/features/host-org/components/SalaryWalletControls.jsx";
import { HostOrgStatus } from "@/features/host-org/components/HostOrgStatus.jsx";
import { HostOrgTable } from "@/features/host-org/components/HostOrgTable.jsx";
@ -35,6 +36,8 @@ const bdLeaderBaseColumns = [
{
key: "salaryWallet",
label: "工资钱包",
sortable: true,
sortField: "salary_wallet",
render: (item) => (
<SalaryWalletCell
role="bd_leader"
@ -111,6 +114,20 @@ export function HostBdLeadersPage() {
}),
};
}
if (column.sortable) {
return {
...column,
header: (
<HostOrgSortHeader
field={column.sortField}
label={column.label}
sortBy={page.sortBy}
sortDirection={page.sortDirection}
onToggle={page.changeSort}
/>
),
};
}
return column;
});
const toolbarActions = page.abilities.canCreate

View File

@ -6,6 +6,7 @@ import { DataState } from "@/shared/ui/DataState.jsx";
import { bdStatusFilters } from "@/features/host-org/constants.js";
import { HostOrgActionModal } from "@/features/host-org/components/HostOrgActionModal.jsx";
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 { HostOrgToolbar } from "@/features/host-org/components/HostOrgToolbar.jsx";
@ -25,13 +26,10 @@ const bdColumns = [
{
key: "salaryWallet",
label: "工资钱包",
sortable: true,
sortField: "salary_wallet",
render: (item) => (
<SalaryWalletCell
role="bd"
userId={item.userId}
userLabel={bdUserLabel(item)}
wallet={item.salaryWallet}
/>
<SalaryWalletCell role="bd" userId={item.userId} userLabel={bdUserLabel(item)} wallet={item.salaryWallet} />
),
width: "minmax(140px, 0.85fr)",
},
@ -93,47 +91,61 @@ export function HostBdsPage() {
width: "minmax(80px, 0.45fr)",
}
: null,
].filter(Boolean).map((column) => {
if (column.key === "user") {
return {
...column,
filter: createTextColumnFilter({
placeholder: "搜索用户 ID、展示 ID、用户名",
value: page.query,
onChange: page.changeQuery,
}),
};
}
if (column.key === "parentLeaderUserId") {
return {
...column,
filter: createTextColumnFilter({
placeholder: "搜索 Leader 用户 ID",
value: page.parentLeaderUserId,
onChange: page.changeParentLeaderUserId,
}),
};
}
if (column.key === "status") {
return {
...column,
filter: createOptionsColumnFilter({
options: bdStatusFilters,
placeholder: "搜索状态",
value: page.status,
onChange: page.changeStatus,
}),
};
}
return column;
});
]
.filter(Boolean)
.map((column) => {
if (column.key === "user") {
return {
...column,
filter: createTextColumnFilter({
placeholder: "搜索用户 ID、展示 ID、用户名",
value: page.query,
onChange: page.changeQuery,
}),
};
}
if (column.key === "parentLeaderUserId") {
return {
...column,
filter: createTextColumnFilter({
placeholder: "搜索 Leader 用户 ID",
value: page.parentLeaderUserId,
onChange: page.changeParentLeaderUserId,
}),
};
}
if (column.key === "status") {
return {
...column,
filter: createOptionsColumnFilter({
options: bdStatusFilters,
placeholder: "搜索状态",
value: page.status,
onChange: page.changeStatus,
}),
};
}
if (column.sortable) {
return {
...column,
header: (
<HostOrgSortHeader
field={column.sortField}
label={column.label}
sortBy={page.sortBy}
sortDirection={page.sortDirection}
onToggle={page.changeSort}
/>
),
};
}
return column;
});
return (
<section className={styles.root}>
<div className={styles.contentPanel}>
<HostOrgToolbar
actions={toolbarActions}
/>
<HostOrgToolbar actions={toolbarActions} />
<DataState error={page.error} loading={page.loading} onRetry={page.reload}>
<div className={styles.listBlock}>

View File

@ -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) => (
<SalaryWalletCell
role="host"
@ -91,51 +93,70 @@ export function HostHostsPage() {
width: "minmax(80px, 0.45fr)",
}
: null,
].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: (
<DiamondSortHeader
direction={page.diamondSortDirection}
onToggle={page.toggleDiamondSort}
/>
),
};
}
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: (
<HostOrgSortHeader
field={column.sortField}
label={column.label}
sortBy={page.sortBy}
sortDirection={page.sortDirection}
onToggle={page.changeSort}
/>
),
};
}
if (column.sortable) {
return {
...column,
header: (
<HostOrgSortHeader
field={column.sortField}
label={column.label}
sortBy={page.sortBy}
sortDirection={page.sortDirection}
onToggle={page.changeSort}
/>
),
};
}
return column;
});
return (
<section className={styles.root}>
@ -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 (
<button
aria-label={`按钻石${nextDirectionLabel}排序`}
className={["admin-cell__head-trigger", direction ? "admin-cell__head-trigger--active" : ""]
.filter(Boolean)
.join(" ")}
type="button"
onClick={onToggle}
>
<SortIcon className="admin-cell__head-icon" fontSize="inherit" />
<span className="admin-cell__head-label">
钻石{direction ? <span className="admin-cell__head-filter">({directionLabel})</span> : null}
</span>
</button>
);
}
function HostUser({ item }) {
return (
<HostOrgPerson

View File

@ -0,0 +1,231 @@
import { fireEvent, render, screen } from "@testing-library/react";
import { afterEach, expect, test, vi } from "vitest";
import { HostAgenciesPage } from "./HostAgenciesPage.jsx";
import { HostBdLeadersPage } from "./HostBdLeadersPage.jsx";
import { HostBdsPage } from "./HostBdsPage.jsx";
import { HostHostsPage } from "./HostHostsPage.jsx";
import { useHostAgenciesPage } from "@/features/host-org/hooks/useHostAgenciesPage.js";
import { useHostBdsPage } from "@/features/host-org/hooks/useHostBdsPage.js";
import { useHostHostsPage } from "@/features/host-org/hooks/useHostHostsPage.js";
vi.mock("@/features/host-org/hooks/useHostAgenciesPage.js", () => ({
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(<HostAgenciesPage />);
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(<HostBdLeadersPage />);
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(<HostBdsPage />);
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(<HostHostsPage />);
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",
};
}