Summary of changes
1. UI clean-up
• Removed all `prefixIcon` props from `Tag` components in `UsersColumnDefs.js`.
• Corrected i18n string in invite info (`${t('邀请人')}: …`).
2. “Statistics” column overhaul
• Added a Switch (enable / disable) and quota Progress bar, mirroring the Tokens table design.
• Moved enable / disable action out of the “More” dropdown; user status is now toggled directly via the Switch.
• Disabled the Switch for deleted (注销) users.
• Restored column title to “Statistics” to avoid duplication.
3. State consistency / refresh
• Updated `manageUser` in `useUsersData.js` to:
– set `loading` while processing actions;
– update users list immutably (new objects & array) to trigger React re-render.
4. Imports / plumbing
• Added `Progress` and `Switch` to UI imports in `UsersColumnDefs.js`.
These changes streamline the user table’s appearance, align interaction patterns with the token table, and ensure immediate visual feedback after user status changes.
284 lines
7.2 KiB
JavaScript
284 lines
7.2 KiB
JavaScript
/*
|
|
Copyright (C) 2025 QuantumNous
|
|
|
|
This program is free software: you can redistribute it and/or modify
|
|
it under the terms of the GNU Affero General Public License as
|
|
published by the Free Software Foundation, either version 3 of the
|
|
License, or (at your option) any later version.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU Affero General Public License for more details.
|
|
|
|
You should have received a copy of the GNU Affero General Public License
|
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
|
|
For commercial licensing, please contact support@quantumnous.com
|
|
*/
|
|
|
|
import { useState, useEffect } from 'react';
|
|
import { useTranslation } from 'react-i18next';
|
|
import { API, showError, showSuccess } from '../../helpers';
|
|
import { ITEMS_PER_PAGE } from '../../constants';
|
|
import { useTableCompactMode } from '../common/useTableCompactMode';
|
|
|
|
export const useUsersData = () => {
|
|
const { t } = useTranslation();
|
|
const [compactMode, setCompactMode] = useTableCompactMode('users');
|
|
|
|
// State management
|
|
const [users, setUsers] = useState([]);
|
|
const [loading, setLoading] = useState(true);
|
|
const [activePage, setActivePage] = useState(1);
|
|
const [pageSize, setPageSize] = useState(ITEMS_PER_PAGE);
|
|
const [searching, setSearching] = useState(false);
|
|
const [groupOptions, setGroupOptions] = useState([]);
|
|
const [userCount, setUserCount] = useState(ITEMS_PER_PAGE);
|
|
|
|
// Modal states
|
|
const [showAddUser, setShowAddUser] = useState(false);
|
|
const [showEditUser, setShowEditUser] = useState(false);
|
|
const [editingUser, setEditingUser] = useState({
|
|
id: undefined,
|
|
});
|
|
|
|
// Form initial values
|
|
const formInitValues = {
|
|
searchKeyword: '',
|
|
searchGroup: '',
|
|
};
|
|
|
|
// Form API reference
|
|
const [formApi, setFormApi] = useState(null);
|
|
|
|
// Get form values helper function
|
|
const getFormValues = () => {
|
|
const formValues = formApi ? formApi.getValues() : {};
|
|
return {
|
|
searchKeyword: formValues.searchKeyword || '',
|
|
searchGroup: formValues.searchGroup || '',
|
|
};
|
|
};
|
|
|
|
// Set user format with key field
|
|
const setUserFormat = (users) => {
|
|
for (let i = 0; i < users.length; i++) {
|
|
users[i].key = users[i].id;
|
|
}
|
|
setUsers(users);
|
|
};
|
|
|
|
// Load users data
|
|
const loadUsers = async (startIdx, pageSize) => {
|
|
const res = await API.get(`/api/user/?p=${startIdx}&page_size=${pageSize}`);
|
|
const { success, message, data } = res.data;
|
|
if (success) {
|
|
const newPageData = data.items;
|
|
setActivePage(data.page);
|
|
setUserCount(data.total);
|
|
setUserFormat(newPageData);
|
|
} else {
|
|
showError(message);
|
|
}
|
|
setLoading(false);
|
|
};
|
|
|
|
// Search users with keyword and group
|
|
const searchUsers = async (
|
|
startIdx,
|
|
pageSize,
|
|
searchKeyword = null,
|
|
searchGroup = null,
|
|
) => {
|
|
// If no parameters passed, get values from form
|
|
if (searchKeyword === null || searchGroup === null) {
|
|
const formValues = getFormValues();
|
|
searchKeyword = formValues.searchKeyword;
|
|
searchGroup = formValues.searchGroup;
|
|
}
|
|
|
|
if (searchKeyword === '' && searchGroup === '') {
|
|
// If keyword is blank, load files instead
|
|
await loadUsers(startIdx, pageSize);
|
|
return;
|
|
}
|
|
setSearching(true);
|
|
const res = await API.get(
|
|
`/api/user/search?keyword=${searchKeyword}&group=${searchGroup}&p=${startIdx}&page_size=${pageSize}`,
|
|
);
|
|
const { success, message, data } = res.data;
|
|
if (success) {
|
|
const newPageData = data.items;
|
|
setActivePage(data.page);
|
|
setUserCount(data.total);
|
|
setUserFormat(newPageData);
|
|
} else {
|
|
showError(message);
|
|
}
|
|
setSearching(false);
|
|
};
|
|
|
|
// Manage user operations (promote, demote, enable, disable, delete)
|
|
const manageUser = async (userId, action, record) => {
|
|
// Trigger loading state to force table re-render
|
|
setLoading(true);
|
|
|
|
const res = await API.post('/api/user/manage', {
|
|
id: userId,
|
|
action,
|
|
});
|
|
|
|
const { success, message } = res.data;
|
|
if (success) {
|
|
showSuccess('操作成功完成!');
|
|
const user = res.data.data;
|
|
|
|
// Create a new array and new object to ensure React detects changes
|
|
const newUsers = users.map((u) => {
|
|
if (u.id === userId) {
|
|
if (action === 'delete') {
|
|
return { ...u, DeletedAt: new Date() };
|
|
}
|
|
return { ...u, status: user.status, role: user.role };
|
|
}
|
|
return u;
|
|
});
|
|
|
|
setUsers(newUsers);
|
|
} else {
|
|
showError(message);
|
|
}
|
|
|
|
setLoading(false);
|
|
};
|
|
|
|
// Handle page change
|
|
const handlePageChange = (page) => {
|
|
setActivePage(page);
|
|
const { searchKeyword, searchGroup } = getFormValues();
|
|
if (searchKeyword === '' && searchGroup === '') {
|
|
loadUsers(page, pageSize).then();
|
|
} else {
|
|
searchUsers(page, pageSize, searchKeyword, searchGroup).then();
|
|
}
|
|
};
|
|
|
|
// Handle page size change
|
|
const handlePageSizeChange = async (size) => {
|
|
localStorage.setItem('page-size', size + '');
|
|
setPageSize(size);
|
|
setActivePage(1);
|
|
loadUsers(activePage, size)
|
|
.then()
|
|
.catch((reason) => {
|
|
showError(reason);
|
|
});
|
|
};
|
|
|
|
// Handle table row styling for disabled/deleted users
|
|
const handleRow = (record, index) => {
|
|
if (record.DeletedAt !== null || record.status !== 1) {
|
|
return {
|
|
style: {
|
|
background: 'var(--semi-color-disabled-border)',
|
|
},
|
|
};
|
|
} else {
|
|
return {};
|
|
}
|
|
};
|
|
|
|
// Refresh data
|
|
const refresh = async (page = activePage) => {
|
|
const { searchKeyword, searchGroup } = getFormValues();
|
|
if (searchKeyword === '' && searchGroup === '') {
|
|
await loadUsers(page, pageSize);
|
|
} else {
|
|
await searchUsers(page, pageSize, searchKeyword, searchGroup);
|
|
}
|
|
};
|
|
|
|
// Fetch groups data
|
|
const fetchGroups = async () => {
|
|
try {
|
|
let res = await API.get(`/api/group/`);
|
|
if (res === undefined) {
|
|
return;
|
|
}
|
|
setGroupOptions(
|
|
res.data.data.map((group) => ({
|
|
label: group,
|
|
value: group,
|
|
})),
|
|
);
|
|
} catch (error) {
|
|
showError(error.message);
|
|
}
|
|
};
|
|
|
|
// Modal control functions
|
|
const closeAddUser = () => {
|
|
setShowAddUser(false);
|
|
};
|
|
|
|
const closeEditUser = () => {
|
|
setShowEditUser(false);
|
|
setEditingUser({
|
|
id: undefined,
|
|
});
|
|
};
|
|
|
|
// Initialize data on component mount
|
|
useEffect(() => {
|
|
loadUsers(0, pageSize)
|
|
.then()
|
|
.catch((reason) => {
|
|
showError(reason);
|
|
});
|
|
fetchGroups().then();
|
|
}, []);
|
|
|
|
return {
|
|
// Data state
|
|
users,
|
|
loading,
|
|
activePage,
|
|
pageSize,
|
|
userCount,
|
|
searching,
|
|
groupOptions,
|
|
|
|
// Modal state
|
|
showAddUser,
|
|
showEditUser,
|
|
editingUser,
|
|
setShowAddUser,
|
|
setShowEditUser,
|
|
setEditingUser,
|
|
|
|
// Form state
|
|
formInitValues,
|
|
formApi,
|
|
setFormApi,
|
|
|
|
// UI state
|
|
compactMode,
|
|
setCompactMode,
|
|
|
|
// Actions
|
|
loadUsers,
|
|
searchUsers,
|
|
manageUser,
|
|
handlePageChange,
|
|
handlePageSizeChange,
|
|
handleRow,
|
|
refresh,
|
|
closeAddUser,
|
|
closeEditUser,
|
|
getFormValues,
|
|
|
|
// Translation
|
|
t,
|
|
};
|
|
};
|