diff --git a/netmanager/src/redux/AccessControl/operations.js b/netmanager/src/redux/AccessControl/operations.js index 5ad144250b..6d167b452d 100644 --- a/netmanager/src/redux/AccessControl/operations.js +++ b/netmanager/src/redux/AccessControl/operations.js @@ -14,7 +14,6 @@ import { LOAD_CURRENT_USER_ROLE_SUCCESS, LOAD_NETWORK_USERS_FAILURE, LOAD_NETWORK_USERS_SUCCESS, - LOAD_ROLES_SUMMARY_FAILURE, LOAD_ROLES_SUMMARY_SUCCESS, LOAD_GROUPS_SUMMARY_SUCCESS } from './actions'; @@ -79,6 +78,7 @@ export const addActiveNetwork = (data) => (dispatch) => { export const fetchNetworkUsers = (networkId, params) => async (dispatch) => { try { + dispatch({ type: 'SET_NETWORK_USERS_LOADING', payload: true }); const resData = await getNetworkUsersListApi(networkId, params); dispatch({ type: LOAD_NETWORK_USERS_SUCCESS, @@ -94,11 +94,14 @@ export const fetchNetworkUsers = (networkId, params) => async (dispatch) => { payload: err }); throw err; + } finally { + dispatch({ type: 'SET_NETWORK_USERS_LOADING', payload: false }); } }; export const fetchAvailableNetworkUsers = (networkId, params) => async (dispatch) => { try { + dispatch({ type: 'SET_AVAILABLE_USERS_LOADING', payload: true }); const resData = await getAvailableNetworkUsersListApi(networkId, params); dispatch({ type: LOAD_AVAILABLE_USERS_SUCCESS, @@ -114,6 +117,8 @@ export const fetchAvailableNetworkUsers = (networkId, params) => async (dispatch payload: err }); throw err; + } finally { + dispatch({ type: 'SET_AVAILABLE_USERS_LOADING', payload: false }); } }; diff --git a/netmanager/src/redux/AccessControl/reducers.js b/netmanager/src/redux/AccessControl/reducers.js index f0986304f5..8207632a03 100644 --- a/netmanager/src/redux/AccessControl/reducers.js +++ b/netmanager/src/redux/AccessControl/reducers.js @@ -17,12 +17,14 @@ const initialState = { activeNetwork: {}, networkUsers: { users: null, - total: 0 + total: 0, + loading: false }, rolesSummary: null, availableUsers: { users: null, - total: 0 + total: 0, + loading: false }, groupsSummary: null }; @@ -57,6 +59,22 @@ export default function accessControlReducer(state = initialState, action) { }; case LOAD_GROUPS_SUMMARY_SUCCESS: return { ...state, groupsSummary: action.payload }; + case 'SET_NETWORK_USERS_LOADING': + return { + ...state, + networkUsers: { + ...state.networkUsers, + loading: action.payload + } + }; + case 'SET_AVAILABLE_USERS_LOADING': + return { + ...state, + availableUsers: { + ...state.availableUsers, + loading: action.payload + } + }; default: return state; } diff --git a/netmanager/src/views/pages/UserList/AvailableUserList.js b/netmanager/src/views/pages/UserList/AvailableUserList.js index 5ee1b51ff3..961426d2b0 100644 --- a/netmanager/src/views/pages/UserList/AvailableUserList.js +++ b/netmanager/src/views/pages/UserList/AvailableUserList.js @@ -9,6 +9,7 @@ import { withPermission } from '../../containers/PageAccess'; import AvailableUsersTable from './components/UsersTable/AvailableUsersTable'; import { getAvailableNetworkUsersListApi } from 'views/apis/accessControl'; import { updateMainAlert } from '../../../redux/MainAlert/operations'; +import { fetchAvailableNetworkUsers } from '../../../redux/AccessControl/operations'; const useStyles = makeStyles((theme) => ({ root: { @@ -22,53 +23,42 @@ const useStyles = makeStyles((theme) => ({ const AvailableUserList = (props) => { const classes = useStyles(); const dispatch = useDispatch(); - const [loading, setLoading] = useState(false); - const [users, setUsers] = useState([]); - const [totalCount, setTotalCount] = useState(0); - const [limit, setLimit] = useState(10); - const [skip, setSkip] = useState(0); - const activeNetwork = useSelector((state) => state.accessControl.activeNetwork); + const [page, setPage] = useState(0); + const [pageSize, setPageSize] = useState(100); - const fetchUsers = async (skipCount, limitCount) => { - if (!activeNetwork) return; - setLoading(true); - try { - const res = await getAvailableNetworkUsersListApi(activeNetwork._id, { - skip: skipCount, - limit: limitCount - }); - setUsers(res.available_users); - setTotalCount(res.total || 0); - } catch (error) { - let errorMessage = 'An error occurred'; - if (error.response && error.response.status >= 500) { - errorMessage = 'An error occurred. Please try again later'; - } else if (error.response && error.response.data && error.response.data.message) { - errorMessage = error.response.data.message; - } else if (error.message) { - errorMessage = error.message; - } - dispatch( - updateMainAlert({ - message: errorMessage, - show: true, - severity: 'error' - }) - ); - } finally { - setLoading(false); - } - }; + // Use Redux state instead of local state + const { users, total, loading } = useSelector((state) => state.accessControl.availableUsers); + const activeNetwork = useSelector((state) => state.accessControl.activeNetwork); useEffect(() => { - fetchUsers(skip, limit); + if (!activeNetwork) return; + dispatch( + fetchAvailableNetworkUsers(activeNetwork._id, { + skip: page * pageSize, + limit: pageSize + }) + ); }, [activeNetwork]); - const handlePageChange = (page, pageSize) => { - const newSkip = page * pageSize; - setSkip(newSkip); - setLimit(pageSize); - fetchUsers(newSkip, pageSize); + const handlePageChange = (event, newPage) => { + setPage(newPage); + dispatch( + fetchAvailableNetworkUsers(activeNetwork._id, { + skip: newPage * pageSize, + limit: pageSize + }) + ); + }; + + const handleRowsPerPageChange = (newPageSize) => { + setPageSize(newPageSize); + setPage(0); + dispatch( + fetchAvailableNetworkUsers(activeNetwork._id, { + skip: 0, + limit: newPageSize + }) + ); }; return ( @@ -76,12 +66,13 @@ const AvailableUserList = (props) => {
diff --git a/netmanager/src/views/pages/UserList/UserList.js b/netmanager/src/views/pages/UserList/UserList.js index 18708e8a20..9999517e49 100644 --- a/netmanager/src/views/pages/UserList/UserList.js +++ b/netmanager/src/views/pages/UserList/UserList.js @@ -12,6 +12,7 @@ import { loadRolesSummary } from 'redux/AccessControl/operations'; import { withPermission } from '../../containers/PageAccess'; import { getNetworkUsersListApi } from 'views/apis/accessControl'; import { updateMainAlert } from 'redux/MainAlert/operations'; +import { fetchNetworkUsers } from '../../../redux/AccessControl/operations'; const useStyles = makeStyles((theme) => ({ root: { @@ -25,60 +26,56 @@ const useStyles = makeStyles((theme) => ({ const UserList = (props) => { const classes = useStyles(); const dispatch = useDispatch(); - const [loading, setLoading] = useState(false); - const [users, setUsers] = useState([]); - const [totalCount, setTotalCount] = useState(0); - const [limit, setLimit] = useState(10); - const [skip, setSkip] = useState(0); + const [page, setPage] = useState(0); + const [pageSize, setPageSize] = useState(100); + + // Use Redux state instead of local state + const { users, total, loading } = useSelector((state) => state.accessControl.networkUsers); const roles = useSelector((state) => state.accessControl.rolesSummary); const activeNetwork = useSelector((state) => state.accessControl.activeNetwork); useEffect(() => { if (!activeNetwork) return; - dispatch(loadRolesSummary(activeNetwork._id)); - }, []); - - const fetchUsers = async (skipCount, limitCount) => { - if (!activeNetwork) return; - setLoading(true); try { - const res = await getNetworkUsersListApi(activeNetwork._id, { - skip: skipCount, - limit: limitCount - }); - setUsers(res.assigned_users); - setTotalCount(res.total || 0); + dispatch(loadRolesSummary(activeNetwork._id)); + dispatch( + fetchNetworkUsers(activeNetwork._id, { + skip: page * pageSize, + limit: pageSize + }) + ); } catch (error) { - let errorMessage = 'An error occurred'; - if (error.response && error.response.status >= 500) { - errorMessage = 'An error occurred. Please try again later'; - } else if (error.response && error.response.data && error.response.data.message) { - errorMessage = error.response.data.message; - } else if (error.message) { - errorMessage = error.message; - } + console.error('Error fetching network users:', error); + } + }, [activeNetwork]); + + const handlePageChange = (event, newPage) => { + setPage(newPage); + try { dispatch( - updateMainAlert({ - message: errorMessage, - show: true, - severity: 'error' + fetchNetworkUsers(activeNetwork._id, { + skip: newPage * pageSize, + limit: pageSize }) ); - } finally { - setLoading(false); + } catch (error) { + console.error('Error fetching network users:', error); } }; - // Initial load - useEffect(() => { - fetchUsers(skip, limit); - }, [activeNetwork]); - - const handlePageChange = (page, pageSize) => { - const newSkip = page * pageSize; - setSkip(newSkip); - setLimit(pageSize); - fetchUsers(newSkip, pageSize); + const handleRowsPerPageChange = (newPageSize) => { + setPageSize(newPageSize); + setPage(0); + try { + dispatch( + fetchNetworkUsers(activeNetwork._id, { + skip: 0, + limit: newPageSize + }) + ); + } catch (error) { + console.error('Error fetching network users:', error); + } }; return ( @@ -88,12 +85,13 @@ const UserList = (props) => {
diff --git a/netmanager/src/views/pages/UserList/components/UsersTable/AvailableUsersTable.js b/netmanager/src/views/pages/UserList/components/UsersTable/AvailableUsersTable.js index de25cc08e2..a80bda425d 100644 --- a/netmanager/src/views/pages/UserList/components/UsersTable/AvailableUsersTable.js +++ b/netmanager/src/views/pages/UserList/components/UsersTable/AvailableUsersTable.js @@ -3,11 +3,22 @@ import React, { useState } from 'react'; import clsx from 'clsx'; import PropTypes from 'prop-types'; import { makeStyles } from '@material-ui/styles'; -import { Card, Avatar, Typography, Button } from '@material-ui/core'; +import { + Card, + Avatar, + Typography, + Button, + Table, + TableBody, + TableCell, + TableContainer, + TableHead, + TablePagination, + TableRow, + Paper +} from '@material-ui/core'; import { getInitials } from 'utils/users'; import { formatDateString } from 'utils/dateTime'; -import CustomMaterialTable from 'views/components/Table/CustomMaterialTable'; -import usersStateConnector from 'views/stateConnectors/usersStateConnector'; import { isEmpty } from 'underscore'; import { useDispatch, useSelector } from 'react-redux'; import { assignUserNetworkApi } from '../../../../apis/accessControl'; @@ -15,6 +26,14 @@ import { updateMainAlert } from 'redux/MainAlert/operations'; import { fetchAvailableNetworkUsers } from 'redux/AccessControl/operations'; import { createAlertBarExtraContentFromObject } from 'utils/objectManipulators'; import UsersListBreadCrumb from '../Breadcrumb'; +import usersStateConnector from '../../../../stateConnectors/usersStateConnector'; +import { + FirstPage as FirstPageIcon, + KeyboardArrowLeft, + KeyboardArrowRight, + LastPage as LastPageIcon +} from '@material-ui/icons'; +import { IconButton } from '@material-ui/core'; const useStyles = makeStyles((theme) => ({ root: {}, @@ -33,12 +52,24 @@ const useStyles = makeStyles((theme) => ({ }, actions: { justifyContent: 'flex-end' + }, + tableContainer: { + maxHeight: 440 } })); const AvailableUsersTable = (props) => { - const { className, users, loadData, totalCount, pageSize, currentPage, onPageChange, ...rest } = - props; + const { + className, + users, + loadData, + totalCount, + pageSize, + currentPage, + onPageChange, + onChangeRowsPerPage, + ...rest + } = props; const dispatch = useDispatch(); const [isLoading, setLoading] = useState(false); const classes = useStyles(); @@ -81,81 +112,165 @@ const AvailableUsersTable = (props) => { <> - { - return ( -
- - {getInitials(`${rowData.firstName + ' ' + rowData.lastName}`)} - - - {' '} - {rowData.firstName + ' ' + rowData.lastName} - -
- ); - } - }, - { - title: 'Email', - field: 'email' - }, - { - title: 'Username', - field: 'userName' - }, - { - title: 'Joined', - field: 'createdAt', - render: (candidate) => ( - {candidate.createdAt ? formatDateString(candidate.createdAt) : '---'} - ) - }, - { - title: 'Action', - render: (user) => { - return ( -
- -
- ); - } - } - ]} - options={{ - search: true, - searchFieldAlignment: 'left', - showTitle: false, - serverSide: true, - pageSize: pageSize, - page: currentPage, - totalCount: totalCount, - onPageChange: (page, pageSize) => { - onPageChange(page, pageSize); - } - }} - /> + + + + + + Full Name + Email + Username + Joined + Action + + + + {loadData ? ( + + + Loading... + + + ) : !isEmpty(users) ? ( + users.map((user) => ( + + +
+ + {getInitials(`${user.firstName} ${user.lastName}`)} + + + {user.firstName} {user.lastName} + +
+
+ {user.email} + {user.userName} + + {user.createdAt ? formatDateString(user.createdAt) : '---'} + + + + +
+ )) + ) : ( + + + No users available + + + )} +
+
+
+ ( + + )} + /> +
); }; +function TablePaginationActions(props) { + const { count, page, rowsPerPage, onPageChange } = props; + console.log('TablePaginationActions props:', { + count, + page, + rowsPerPage, + hasOnPageChange: !!onPageChange + }); + + const handleFirstPageButtonClick = (event) => { + if (onPageChange) { + onPageChange(event, 0); + } + }; + + const handleBackButtonClick = (event) => { + if (onPageChange) { + onPageChange(event, page - 1); + } + }; + + const handleNextButtonClick = (event) => { + console.log('Next button clicked, current page:', page); + console.log('onPageChange exists:', !!onPageChange); + if (onPageChange) { + console.log('Calling onPageChange with:', page + 1); + onPageChange(event, page + 1); + } + }; + + const handleLastPageButtonClick = (event) => { + if (onPageChange) { + onPageChange(event, Math.max(0, Math.ceil(count / rowsPerPage) - 1)); + } + }; + + return ( +
+ + + + + + + = Math.ceil(count / rowsPerPage) - 1} + aria-label="next page" + > + + + = Math.ceil(count / rowsPerPage) - 1} + aria-label="last page" + > + + +
+ ); +} + +TablePaginationActions.propTypes = { + count: PropTypes.number.isRequired, + onPageChange: PropTypes.func.isRequired, + page: PropTypes.number.isRequired, + rowsPerPage: PropTypes.number.isRequired +}; + AvailableUsersTable.propTypes = { className: PropTypes.string, - auth: PropTypes.object.isRequired + auth: PropTypes.object.isRequired, + users: PropTypes.array.isRequired, + loadData: PropTypes.bool, + totalCount: PropTypes.number, + pageSize: PropTypes.number, + currentPage: PropTypes.number, + onPageChange: PropTypes.func, + onChangeRowsPerPage: PropTypes.func }; export default usersStateConnector(AvailableUsersTable); diff --git a/netmanager/src/views/pages/UserList/components/UsersTable/UsersTable.js b/netmanager/src/views/pages/UserList/components/UsersTable/UsersTable.js index 77ee8b4b77..f5b3c7c676 100644 --- a/netmanager/src/views/pages/UserList/components/UsersTable/UsersTable.js +++ b/netmanager/src/views/pages/UserList/components/UsersTable/UsersTable.js @@ -15,9 +15,24 @@ import { DialogActions, ListItemText, Divider, - Select + Select, + Table, + TableBody, + TableCell, + TableContainer, + TableHead, + TablePagination, + TableRow, + Paper, + IconButton } from '@material-ui/core'; -import { RemoveRedEye } from '@material-ui/icons'; +import { + FirstPage as FirstPageIcon, + KeyboardArrowLeft, + KeyboardArrowRight, + LastPage as LastPageIcon, + RemoveRedEye +} from '@material-ui/icons'; import { getInitials } from 'utils/users'; import { formatDateString } from 'utils/dateTime'; @@ -88,6 +103,79 @@ const customStyles = { }) }; +function TablePaginationActions(props) { + const { count, page, rowsPerPage, onPageChange } = props; + console.log('TablePaginationActions props:', { + count, + page, + rowsPerPage, + hasOnPageChange: !!onPageChange + }); + + const handleFirstPageButtonClick = (event) => { + if (onPageChange) { + onPageChange(event, 0); + } + }; + + const handleBackButtonClick = (event) => { + if (onPageChange) { + onPageChange(event, page - 1); + } + }; + + const handleNextButtonClick = (event) => { + console.log('Next button clicked, current page:', page); + console.log('onPageChange exists:', !!onPageChange); + if (onPageChange) { + console.log('Calling onPageChange with:', page + 1); + onPageChange(event, page + 1); + } + }; + + const handleLastPageButtonClick = (event) => { + if (onPageChange) { + onPageChange(event, Math.max(0, Math.ceil(count / rowsPerPage) - 1)); + } + }; + + return ( +
+ + + + + + + = Math.ceil(count / rowsPerPage) - 1} + aria-label="next page" + > + + + = Math.ceil(count / rowsPerPage) - 1} + aria-label="last page" + > + + +
+ ); +} + +TablePaginationActions.propTypes = { + count: PropTypes.number.isRequired, + onPageChange: PropTypes.func.isRequired, + page: PropTypes.number.isRequired, + rowsPerPage: PropTypes.number.isRequired +}; + const UsersTable = (props) => { const { className, @@ -99,6 +187,7 @@ const UsersTable = (props) => { pageSize, currentPage, onPageChange, + onChangeRowsPerPage, ...rest } = props; const [userDelState, setUserDelState] = useState({ open: false, user: {} }); @@ -249,293 +338,300 @@ const UsersTable = (props) => { <> - { - return ( -
- - {getInitials(`${rowData.firstName + ' ' + rowData.lastName}`)} - - - {' '} - {rowData.firstName + ' ' + rowData.lastName} - -
- ); - } - }, - { - title: 'Email', - field: 'email' - }, - { - title: 'Username', - field: 'userName' - }, - { - title: 'Role', - render: (user) => { - return {user.role ? user.role.role_name : '---'}; - } - }, - { - title: 'Joined', - field: 'createdAt', - render: (candidate) => ( - {candidate.createdAt ? formatDateString(candidate.createdAt) : '---'} - ) - }, - { - title: 'More Details', - render: (user) => ( - showMoreDetails(user)} /> - ) - }, - { - title: 'Action', - render: (user) => { - return ( -
- - - -
- ); - } - } - ]} - options={{ - search: true, - searchFieldAlignment: 'left', - showTitle: false, - serverSide: true, - pageSize: pageSize, - page: currentPage, - totalCount: totalCount, - onPageChange: (page, pageSize) => { - onPageChange(page, pageSize); - } - }} - /> - - {/*************************** the more details dialog **********************************************/} - {editUser && ( - - User request details - -
- - - - - - - - - -
-
- -
- -
-
-
- )} - - {/*************************** the edit dialog **********************************************/} - {editUser && ( - - Edit User - -
- - - - - - {/* dropdown */} - - - - - - - - -
-
- -
- - -
-
-
- )} - {/***************************************** deleting a user ***********************************/} - - Are you sure you want to delete this user — - {userDelState.user.firstName}? - - } - confirm={deleteUser} - close={hideDeleteDialog} - error - /> + + + + + + Full Name + Email + Username + Role + Joined + More Details + Action + + + + {loadData ? ( + + + Loading... + + + ) : !isEmpty(users) ? ( + users.map((user) => ( + + +
+ + {getInitials(`${user.firstName} ${user.lastName}`)} + + + {user.firstName} {user.lastName} + +
+
+ {user.email} + {user.userName} + {user.role ? user.role.role_name : '---'} + + {user.createdAt ? formatDateString(user.createdAt) : '---'} + + + showMoreDetails(user)} + /> + + + + + +
+ )) + ) : ( + + + No users available + + + )} +
+
+
+ ( + + )} + /> +
+ + {/*************************** the more details dialog **********************************************/} + {editUser && ( + + User request details + +
+ + + + + + + + + +
+
+ +
+ +
+
+
+ )} + + {/*************************** the edit dialog **********************************************/} + {editUser && ( + + Edit User + +
+ + + + + + {/* dropdown */} + + + + + + + + +
+
+ +
+ + +
+
+
+ )} + {/***************************************** deleting a user ***********************************/} + + Are you sure you want to delete this user — + {userDelState.user.firstName}? + + } + confirm={deleteUser} + close={hideDeleteDialog} + error + /> ); }; UsersTable.propTypes = { className: PropTypes.string, - auth: PropTypes.object.isRequired + auth: PropTypes.object.isRequired, + users: PropTypes.array.isRequired, + loadData: PropTypes.bool, + totalCount: PropTypes.number, + pageSize: PropTypes.number, + currentPage: PropTypes.number, + onPageChange: PropTypes.func, + onChangeRowsPerPage: PropTypes.func }; export default usersStateConnector(UsersTable);