♻️ refactor(users): modularize UsersTable component into microcomponent architecture

BREAKING CHANGE: Removed standalone user edit routes (/console/user/edit, /console/user/edit/:id)

- Decompose 673-line monolithic UsersTable.js into 8 specialized components
- Extract column definitions to UsersColumnDefs.js with render functions
- Create dedicated UsersActions.jsx for action buttons
- Create UsersFilters.jsx for search and filtering logic
- Create UsersDescription.jsx for description area
- Extract all data management logic to useUsersData.js hook
- Move AddUser.js and EditUser.js to users/modals/ folder as modal components
- Create 4 new confirmation modal components (Promote, Demote, EnableDisable, Delete)
- Implement pure UsersTable.jsx component for table rendering only
- Create main container component users/index.jsx to compose all subcomponents
- Update import paths in pages/User/index.js to use new modular structure
- Remove obsolete EditUser imports and routes from App.js
- Delete original monolithic files: UsersTable.js, AddUser.js, EditUser.js

The new architecture follows the same modular pattern as tokens and redemptions modules:
- Consistent file organization across all table modules
- Better separation of concerns and maintainability
- Enhanced reusability and testability
- Unified modal management approach

All existing functionality preserved with improved code organization.
This commit is contained in:
t0ng7u
2025-07-19 00:32:56 +08:00
parent c05d6f7cdf
commit d762da9141
16 changed files with 1099 additions and 699 deletions

View File

@@ -0,0 +1,259 @@
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) => {
const res = await API.post('/api/user/manage', {
id: userId,
action,
});
const { success, message } = res.data;
if (success) {
showSuccess('操作成功完成!');
let user = res.data.data;
let newUsers = [...users];
if (action === 'delete') {
// Mark as deleted
const index = newUsers.findIndex(u => u.id === userId);
if (index > -1) {
newUsers[index].DeletedAt = new Date();
}
} else {
// Update status and role
record.status = user.status;
record.role = user.role;
}
setUsers(newUsers);
} else {
showError(message);
}
};
// 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,
};
};