Fix pagination component flickering issue across multiple table views by initializing count states to 0 instead of ITEMS_PER_PAGE. This prevents the pagination component from briefly appearing and then disappearing when tables are empty. Changes: - usage-logs: logCount initial value 0 (was ITEMS_PER_PAGE) - users: userCount initial value 0 (was ITEMS_PER_PAGE) - tokens: tokenCount initial value 0 (was ITEMS_PER_PAGE) - channels: channelCount initial value 0 (was ITEMS_PER_PAGE) - redemptions: tokenCount initial value 0 (was ITEMS_PER_PAGE) The createCardProPagination function already handles total <= 0 by returning null, so this ensures consistent behavior across all table components and improves user experience by eliminating visual flicker. Affected files: - web/src/hooks/usage-logs/useUsageLogsData.js - web/src/hooks/users/useUsersData.js - web/src/hooks/tokens/useTokensData.js - web/src/hooks/channels/useChannelsData.js - web/src/hooks/redemptions/useRedemptionsData.js
285 lines
7.2 KiB
JavaScript
285 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(0);
|
|
|
|
// 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) => {
|
|
setLoading(true);
|
|
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,
|
|
};
|
|
};
|