🚀 feat: Enhance table UX & fix reset actions across Users / Tokens / Redemptions

Users table (UsersColumnDefs.js)
• Merged “Status” into the “Statistics” tag: unified text-color logic, removed duplicate renderStatus / renderOverallStatus helpers.
• Switch now disabled for deleted users.
• Replaced dropdown “More” menu with explicit action buttons (Edit / Promote / Demote / Delete) and set column width to 200 px.
• Deleted unused Dropdown & IconMore imports and tidied redundant code.

Users filters & hooks
• UsersFilters.jsx – store formApi in a ref; reset button clears form then reloads data after 100 ms.
• useUsersData.js – call setLoading(true) at the start of loadUsers so the Query button shows loading on reset / reload.

TokensFilters.jsx & RedemptionsFilters.jsx
• Same ref-based reset pattern with 100 ms debounce to restore working “Reset” buttons.

Other clean-ups
• Removed repeated status strings and unused helper functions.
• Updated import lists to reflect component changes.

Result
– Reset buttons now reliably clear filters and reload data with proper loading feedback.
– Users table shows concise status information and all operation buttons without extra clicks.
This commit is contained in:
t0ng7u
2025-07-20 01:21:06 +08:00
parent 39079e7aff
commit 252fddf3de
6 changed files with 48 additions and 38 deletions

View File

@@ -128,13 +128,13 @@ const SiderBar = ({ onNavigate = () => { } }) => {
const adminItems = useMemo( const adminItems = useMemo(
() => [ () => [
{ {
text: t('渠道'), text: t('渠道管理'),
itemKey: 'channel', itemKey: 'channel',
to: '/channel', to: '/channel',
className: isAdmin() ? '' : 'tableHiddle', className: isAdmin() ? '' : 'tableHiddle',
}, },
{ {
text: t('兑换码'), text: t('兑换码管理'),
itemKey: 'redemption', itemKey: 'redemption',
to: '/redemption', to: '/redemption',
className: isAdmin() ? '' : 'tableHiddle', className: isAdmin() ? '' : 'tableHiddle',

View File

@@ -17,7 +17,7 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
For commercial licensing, please contact support@quantumnous.com For commercial licensing, please contact support@quantumnous.com
*/ */
import React from 'react'; import React, { useRef } from 'react';
import { Form, Button } from '@douyinfe/semi-ui'; import { Form, Button } from '@douyinfe/semi-ui';
import { IconSearch } from '@douyinfe/semi-icons'; import { IconSearch } from '@douyinfe/semi-icons';
@@ -31,20 +31,23 @@ const RedemptionsFilters = ({
}) => { }) => {
// Handle form reset and immediate search // Handle form reset and immediate search
const handleReset = (formApi) => { const formApiRef = useRef(null);
if (formApi) {
formApi.reset(); const handleReset = () => {
// Reset and search immediately if (!formApiRef.current) return;
setTimeout(() => { formApiRef.current.reset();
searchRedemptions(); setTimeout(() => {
}, 100); searchRedemptions();
} }, 100);
}; };
return ( return (
<Form <Form
initValues={formInitValues} initValues={formInitValues}
getFormApi={(api) => setFormApi(api)} getFormApi={(api) => {
setFormApi(api);
formApiRef.current = api;
}}
onSubmit={searchRedemptions} onSubmit={searchRedemptions}
allowEmpty={true} allowEmpty={true}
autoComplete="off" autoComplete="off"
@@ -76,7 +79,7 @@ const RedemptionsFilters = ({
</Button> </Button>
<Button <Button
type="tertiary" type="tertiary"
onClick={(_, formApi) => handleReset(formApi)} onClick={handleReset}
className="flex-1 md:flex-initial md:w-auto" className="flex-1 md:flex-initial md:w-auto"
size="small" size="small"
> >

View File

@@ -17,7 +17,7 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
For commercial licensing, please contact support@quantumnous.com For commercial licensing, please contact support@quantumnous.com
*/ */
import React from 'react'; import React, { useRef } from 'react';
import { Form, Button } from '@douyinfe/semi-ui'; import { Form, Button } from '@douyinfe/semi-ui';
import { IconSearch } from '@douyinfe/semi-icons'; import { IconSearch } from '@douyinfe/semi-icons';
@@ -30,20 +30,23 @@ const TokensFilters = ({
t, t,
}) => { }) => {
// Handle form reset and immediate search // Handle form reset and immediate search
const handleReset = (formApi) => { const formApiRef = useRef(null);
if (formApi) {
formApi.reset(); const handleReset = () => {
// Reset and search immediately if (!formApiRef.current) return;
setTimeout(() => { formApiRef.current.reset();
searchTokens(); setTimeout(() => {
}, 100); searchTokens();
} }, 100);
}; };
return ( return (
<Form <Form
initValues={formInitValues} initValues={formInitValues}
getFormApi={(api) => setFormApi(api)} getFormApi={(api) => {
setFormApi(api);
formApiRef.current = api;
}}
onSubmit={searchTokens} onSubmit={searchTokens}
allowEmpty={true} allowEmpty={true}
autoComplete="off" autoComplete="off"
@@ -88,7 +91,7 @@ const TokensFilters = ({
<Button <Button
type='tertiary' type='tertiary'
onClick={(_, formApi) => handleReset(formApi)} onClick={handleReset}
className="flex-1 md:flex-initial md:w-auto" className="flex-1 md:flex-initial md:w-auto"
size="small" size="small"
> >

View File

@@ -17,7 +17,7 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
For commercial licensing, please contact support@quantumnous.com For commercial licensing, please contact support@quantumnous.com
*/ */
import React from 'react'; import React, { useRef } from 'react';
import { Form, Button } from '@douyinfe/semi-ui'; import { Form, Button } from '@douyinfe/semi-ui';
import { IconSearch } from '@douyinfe/semi-icons'; import { IconSearch } from '@douyinfe/semi-icons';
@@ -34,21 +34,23 @@ const UsersFilters = ({
t t
}) => { }) => {
// Handle form reset and immediate search const formApiRef = useRef(null);
const handleReset = (formApi) => {
if (formApi) { const handleReset = () => {
formApi.reset(); if (!formApiRef.current) return;
// Reset and search immediately formApiRef.current.reset();
setTimeout(() => { setTimeout(() => {
loadUsers(1, pageSize); loadUsers(1, pageSize);
}, 100); }, 100);
}
}; };
return ( return (
<Form <Form
initValues={formInitValues} initValues={formInitValues}
getFormApi={(api) => setFormApi(api)} getFormApi={(api) => {
setFormApi(api);
formApiRef.current = api;
}}
onSubmit={() => { onSubmit={() => {
searchUsers(1, pageSize); searchUsers(1, pageSize);
}} }}
@@ -99,7 +101,7 @@ const UsersFilters = ({
</Button> </Button>
<Button <Button
type='tertiary' type='tertiary'
onClick={(_, formApi) => handleReset(formApi)} onClick={handleReset}
className="flex-1 md:flex-initial md:w-auto" className="flex-1 md:flex-initial md:w-auto"
size="small" size="small"
> >

View File

@@ -71,6 +71,7 @@ export const useUsersData = () => {
// Load users data // Load users data
const loadUsers = async (startIdx, pageSize) => { const loadUsers = async (startIdx, pageSize) => {
setLoading(true);
const res = await API.get(`/api/user/?p=${startIdx}&page_size=${pageSize}`); const res = await API.get(`/api/user/?p=${startIdx}&page_size=${pageSize}`);
const { success, message, data } = res.data; const { success, message, data } = res.data;
if (success) { if (success) {

View File

@@ -165,7 +165,8 @@
"操作失败,重定向至登录界面中...": "Operation failed, redirecting to login page...", "操作失败,重定向至登录界面中...": "Operation failed, redirecting to login page...",
"出现错误,第 ${count} 次重试中...": "Error occurred, retry attempt ${count}...", "出现错误,第 ${count} 次重试中...": "Error occurred, retry attempt ${count}...",
"首页": "Home", "首页": "Home",
"渠道": "Channels", "渠道": "Channel",
"渠道管理": "Channels",
"令牌": "Tokens", "令牌": "Tokens",
"兑换": "Redeem", "兑换": "Redeem",
"充值": "Recharge", "充值": "Recharge",
@@ -1487,7 +1488,7 @@
"收益": "Earnings", "收益": "Earnings",
"无邀请人": "No Inviter", "无邀请人": "No Inviter",
"邀请人": "Inviter", "邀请人": "Inviter",
"兑换码管理": "Redemption Code Management", "兑换码管理": "Redemption Code",
"设置兑换码的基本信息": "Set redemption code basic information", "设置兑换码的基本信息": "Set redemption code basic information",
"设置兑换码的额度和数量": "Set redemption code quota and quantity", "设置兑换码的额度和数量": "Set redemption code quota and quantity",
"编辑用户": "Edit User", "编辑用户": "Edit User",