mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-06-04 19:39:35 +00:00
refactor(inbounds): remove column sorter from inbound list
Drop the table header sorter on the inbounds page: the sortKey/sortOrder state, the sortedInbounds memo and onChange handler, the per-column sorterFor spreads, the SORT_FNS comparator map, and the now-unused SortKey/SortOrder types. The list renders in DB order.
This commit is contained in:
@@ -25,11 +25,10 @@ import {
|
||||
|
||||
import { HttpUtil } from '@/utils';
|
||||
|
||||
import { SORT_FNS } from './helpers';
|
||||
import { buildRowActionsMenu } from './RowActions';
|
||||
import { useInboundColumns } from './useInboundColumns';
|
||||
import InboundStatsModal from './InboundStatsModal';
|
||||
import type { DBInboundRecord, GeneralAction, InboundListProps, RowAction, SortKey, SortOrder } from './types';
|
||||
import type { DBInboundRecord, GeneralAction, InboundListProps, RowAction } from './types';
|
||||
import './InboundList.css';
|
||||
|
||||
export default function InboundList({
|
||||
@@ -49,8 +48,6 @@ export default function InboundList({
|
||||
onBulkDelete,
|
||||
}: InboundListProps) {
|
||||
const { t } = useTranslation();
|
||||
const [sortKey, setSortKey] = useState<SortKey | null>(null);
|
||||
const [sortOrder, setSortOrder] = useState<SortOrder>(null);
|
||||
const [statsRecord, setStatsRecord] = useState<DBInboundRecord | null>(null);
|
||||
const [selectedRowKeys, setSelectedRowKeys] = useState<number[]>([]);
|
||||
|
||||
@@ -67,14 +64,6 @@ export default function InboundList({
|
||||
}
|
||||
}, []);
|
||||
|
||||
const sortedInbounds = useMemo(() => {
|
||||
if (!sortKey || !sortOrder) return dbInbounds;
|
||||
const fn = SORT_FNS[sortKey];
|
||||
if (!fn) return dbInbounds;
|
||||
const sorted = [...dbInbounds].sort((a, b) => fn(a, b, { nodesById, clientCount }));
|
||||
return sortOrder === 'descend' ? sorted.reverse() : sorted;
|
||||
}, [dbInbounds, sortKey, sortOrder, nodesById, clientCount]);
|
||||
|
||||
const hasAnyRemark = useMemo(
|
||||
() => dbInbounds.some((i) => typeof i.remark === 'string' && i.remark.trim() !== ''),
|
||||
[dbInbounds],
|
||||
@@ -89,11 +78,11 @@ export default function InboundList({
|
||||
}, []);
|
||||
|
||||
const selectAll = useCallback((checked: boolean) => {
|
||||
setSelectedRowKeys(checked ? sortedInbounds.map((i) => i.id) : []);
|
||||
}, [sortedInbounds]);
|
||||
setSelectedRowKeys(checked ? dbInbounds.map((i) => i.id) : []);
|
||||
}, [dbInbounds]);
|
||||
|
||||
const allSelected = sortedInbounds.length > 0 && selectedRowKeys.length === sortedInbounds.length;
|
||||
const someSelected = selectedRowKeys.length > 0 && selectedRowKeys.length < sortedInbounds.length;
|
||||
const allSelected = dbInbounds.length > 0 && selectedRowKeys.length === dbInbounds.length;
|
||||
const someSelected = selectedRowKeys.length > 0 && selectedRowKeys.length < dbInbounds.length;
|
||||
|
||||
const handleBulkDelete = useCallback(async () => {
|
||||
const ok = await onBulkDelete(selectedRowKeys);
|
||||
@@ -108,8 +97,6 @@ export default function InboundList({
|
||||
subEnable,
|
||||
expireDiff,
|
||||
trafficDiff,
|
||||
sortKey,
|
||||
sortOrder,
|
||||
onRowAction,
|
||||
onSwitchEnable,
|
||||
});
|
||||
@@ -160,7 +147,7 @@ export default function InboundList({
|
||||
<Space orientation="vertical" style={{ width: '100%' }}>
|
||||
{isMobile ? (
|
||||
<div className="inbound-cards">
|
||||
{sortedInbounds.length === 0 ? (
|
||||
{dbInbounds.length === 0 ? (
|
||||
<div className="card-empty">
|
||||
<ImportOutlined style={{ fontSize: 28, opacity: 0.5 }} />
|
||||
<div>{t('noData')}</div>
|
||||
@@ -179,7 +166,7 @@ export default function InboundList({
|
||||
<span className="bulk-count">{selectedRowKeys.length}</span>
|
||||
)}
|
||||
</div>
|
||||
{sortedInbounds.map((record) => (
|
||||
{dbInbounds.map((record) => (
|
||||
<div key={record.id} className={`inbound-card${selectedRowKeys.includes(record.id) ? ' is-selected' : ''}`}>
|
||||
<div className="card-head">
|
||||
<Checkbox
|
||||
@@ -217,13 +204,13 @@ export default function InboundList({
|
||||
) : (
|
||||
<Table
|
||||
columns={columns}
|
||||
dataSource={sortedInbounds}
|
||||
dataSource={dbInbounds}
|
||||
rowKey={(r) => r.id}
|
||||
rowSelection={{
|
||||
selectedRowKeys,
|
||||
onChange: (keys: Key[]) => setSelectedRowKeys(keys as number[]),
|
||||
}}
|
||||
pagination={paginationFor(sortedInbounds)}
|
||||
pagination={paginationFor(dbInbounds)}
|
||||
scroll={{ x: 1000 }}
|
||||
style={{ marginTop: 10 }}
|
||||
size="small"
|
||||
@@ -235,12 +222,6 @@ export default function InboundList({
|
||||
</div>
|
||||
),
|
||||
}}
|
||||
onChange={(_p, _f, sorter) => {
|
||||
const single = Array.isArray(sorter) ? sorter[0] : sorter;
|
||||
const colKey = (single?.columnKey || single?.field) as SortKey | undefined;
|
||||
setSortKey(colKey || null);
|
||||
setSortOrder((single?.order as SortOrder) || null);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</Space>
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import type { NodeRecord } from '@/api/queries/useNodesQuery';
|
||||
import { isSSMultiUser } from '@/lib/xray/protocol-capabilities';
|
||||
import { coerceInboundJsonField } from '@/models/dbinbound';
|
||||
|
||||
import type { ClientCountEntry, DBInboundRecord, SortKey, StreamHints } from './types';
|
||||
import type { DBInboundRecord, StreamHints } from './types';
|
||||
|
||||
export function readStreamHints(streamSettings: unknown): StreamHints {
|
||||
const stream = coerceInboundJsonField(streamSettings) as { network?: string; security?: string };
|
||||
@@ -88,19 +87,3 @@ export function showQrCodeMenu(dbInbound: DBInboundRecord): boolean {
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
export const SORT_FNS: Record<SortKey, (a: DBInboundRecord, b: DBInboundRecord, ctx: { nodesById: Map<number, NodeRecord>; clientCount: Record<number, ClientCountEntry> }) => number> = {
|
||||
id: (a, b) => a.id - b.id,
|
||||
enable: (a, b) => Number(a.enable) - Number(b.enable),
|
||||
remark: (a, b) => (a.remark || '').localeCompare(b.remark || ''),
|
||||
port: (a, b) => a.port - b.port,
|
||||
protocol: (a, b) => a.protocol.localeCompare(b.protocol),
|
||||
traffic: (a, b) => (a.up + a.down) - (b.up + b.down),
|
||||
expiryTime: (a, b) => (a.expiryTime || Infinity) - (b.expiryTime || Infinity),
|
||||
node: (a, b, ctx) => {
|
||||
const nameA = ctx.nodesById.get(a.nodeId ?? -1)?.name ?? (a.nodeId == null ? '' : `node #${a.nodeId}`);
|
||||
const nameB = ctx.nodesById.get(b.nodeId ?? -1)?.name ?? (b.nodeId == null ? '' : `node #${b.nodeId}`);
|
||||
return nameA.localeCompare(nameB);
|
||||
},
|
||||
clients: (a, b, ctx) => (ctx.clientCount[a.id]?.clients || 0) - (ctx.clientCount[b.id]?.clients || 0),
|
||||
};
|
||||
|
||||
@@ -74,16 +74,3 @@ export interface InboundListProps {
|
||||
onRowAction: (action: { key: RowAction; dbInbound: DBInboundRecord }) => void;
|
||||
onBulkDelete: (ids: number[]) => Promise<boolean>;
|
||||
}
|
||||
|
||||
export type SortKey =
|
||||
| 'id'
|
||||
| 'enable'
|
||||
| 'remark'
|
||||
| 'port'
|
||||
| 'protocol'
|
||||
| 'traffic'
|
||||
| 'expiryTime'
|
||||
| 'node'
|
||||
| 'clients';
|
||||
|
||||
export type SortOrder = 'ascend' | 'descend' | null;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useCallback, useMemo, type ReactElement } from 'react';
|
||||
import { useMemo, type ReactElement } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Popover, Switch, Tag, type TableColumnType } from 'antd';
|
||||
|
||||
@@ -16,7 +16,7 @@ import {
|
||||
tunnelNetworkLabel,
|
||||
mixedNetworkLabel,
|
||||
} from './helpers';
|
||||
import type { ClientCountEntry, DBInboundRecord, RowAction, SortKey, SortOrder } from './types';
|
||||
import type { ClientCountEntry, DBInboundRecord, RowAction } from './types';
|
||||
|
||||
interface UseInboundColumnsParams {
|
||||
hasAnyRemark: boolean;
|
||||
@@ -26,8 +26,6 @@ interface UseInboundColumnsParams {
|
||||
subEnable: boolean;
|
||||
expireDiff: number;
|
||||
trafficDiff: number;
|
||||
sortKey: SortKey | null;
|
||||
sortOrder: SortOrder;
|
||||
onRowAction: (action: { key: RowAction; dbInbound: DBInboundRecord }) => void;
|
||||
onSwitchEnable: (dbInbound: DBInboundRecord, next: boolean) => void;
|
||||
}
|
||||
@@ -40,21 +38,12 @@ export function useInboundColumns({
|
||||
subEnable,
|
||||
expireDiff,
|
||||
trafficDiff,
|
||||
sortKey,
|
||||
sortOrder,
|
||||
onRowAction,
|
||||
onSwitchEnable,
|
||||
}: UseInboundColumnsParams): TableColumnType<DBInboundRecord>[] {
|
||||
const { t } = useTranslation();
|
||||
const { datepicker } = useDatepicker();
|
||||
|
||||
const sorterFor = useCallback((key: SortKey) => ({
|
||||
sorter: true as const,
|
||||
showSorterTooltip: false,
|
||||
sortOrder: sortKey === key ? sortOrder : null,
|
||||
sortDirections: ['ascend' as const, 'descend' as const],
|
||||
}), [sortKey, sortOrder]);
|
||||
|
||||
return useMemo(() => {
|
||||
const cols: TableColumnType<DBInboundRecord>[] = [
|
||||
{
|
||||
@@ -63,7 +52,6 @@ export function useInboundColumns({
|
||||
key: 'id',
|
||||
align: 'right',
|
||||
width: 30,
|
||||
...sorterFor('id'),
|
||||
},
|
||||
{
|
||||
title: t('pages.inbounds.operate'),
|
||||
@@ -84,7 +72,6 @@ export function useInboundColumns({
|
||||
key: 'enable',
|
||||
align: 'center',
|
||||
width: 35,
|
||||
...sorterFor('enable'),
|
||||
render: (_, record) => (
|
||||
<Switch
|
||||
checked={record.enable}
|
||||
@@ -101,7 +88,6 @@ export function useInboundColumns({
|
||||
key: 'remark',
|
||||
align: 'center',
|
||||
width: 60,
|
||||
...sorterFor('remark'),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -111,7 +97,6 @@ export function useInboundColumns({
|
||||
key: 'node',
|
||||
align: 'center',
|
||||
width: 60,
|
||||
...sorterFor('node'),
|
||||
render: (_, record) => {
|
||||
if (record.nodeId == null) {
|
||||
return <Tag color="default">{t('pages.inbounds.localPanel')}</Tag>;
|
||||
@@ -134,14 +119,12 @@ export function useInboundColumns({
|
||||
key: 'port',
|
||||
align: 'center',
|
||||
width: 40,
|
||||
...sorterFor('port'),
|
||||
},
|
||||
{
|
||||
title: t('pages.inbounds.protocol'),
|
||||
key: 'protocol',
|
||||
align: 'left',
|
||||
width: 130,
|
||||
...sorterFor('protocol'),
|
||||
render: (_, record) => {
|
||||
const tags: ReactElement[] = [<Tag key="p" color="purple">{record.protocol}</Tag>];
|
||||
if (record.isWireguard || record.isHysteria) {
|
||||
@@ -170,7 +153,6 @@ export function useInboundColumns({
|
||||
key: 'clients',
|
||||
align: 'left',
|
||||
width: 50,
|
||||
...sorterFor('clients'),
|
||||
render: (_, record) => {
|
||||
const cc = clientCount[record.id];
|
||||
if (!cc) return null;
|
||||
@@ -236,7 +218,6 @@ export function useInboundColumns({
|
||||
key: 'traffic',
|
||||
align: 'center',
|
||||
width: 90,
|
||||
...sorterFor('traffic'),
|
||||
render: (_, record) => (
|
||||
<Popover
|
||||
content={(
|
||||
@@ -269,7 +250,6 @@ export function useInboundColumns({
|
||||
key: 'expiryTime',
|
||||
align: 'center',
|
||||
width: 40,
|
||||
...sorterFor('expiryTime'),
|
||||
render: (_, record) => {
|
||||
if (record.expiryTime > 0) {
|
||||
return (
|
||||
@@ -286,5 +266,5 @@ export function useInboundColumns({
|
||||
);
|
||||
|
||||
return cols;
|
||||
}, [t, hasAnyRemark, hasActiveNode, nodesById, clientCount, subEnable, expireDiff, trafficDiff, datepicker, onRowAction, onSwitchEnable, sorterFor]);
|
||||
}, [t, hasAnyRemark, hasActiveNode, nodesById, clientCount, subEnable, expireDiff, trafficDiff, datepicker, onRowAction, onSwitchEnable]);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user