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 && (
-
- )}
-
- {/*************************** the edit dialog **********************************************/}
- {editUser && (
-
- )}
- {/***************************************** 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 && (
+
+ )}
+
+ {/*************************** the edit dialog **********************************************/}
+ {editUser && (
+
+ )}
+ {/***************************************** 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);