工资相关
This commit is contained in:
parent
1447de8563
commit
64cfa472b8
@ -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");
|
||||
|
||||
32
src/features/host-org/components/HostOrgSortHeader.jsx
Normal file
32
src/features/host-org/components/HostOrgSortHeader.jsx
Normal 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>
|
||||
);
|
||||
}
|
||||
@ -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,
|
||||
};
|
||||
}
|
||||
|
||||
@ -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,
|
||||
};
|
||||
|
||||
@ -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"),
|
||||
};
|
||||
}
|
||||
|
||||
@ -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}
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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}>
|
||||
|
||||
@ -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
|
||||
|
||||
231
src/features/host-org/pages/HostSalaryWalletSortHeaders.test.jsx
Normal file
231
src/features/host-org/pages/HostSalaryWalletSortHeaders.test.jsx
Normal 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",
|
||||
};
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user