diff --git a/client/src/App.jsx b/client/src/App.jsx index 82fdf5ba..4c7d590f 100644 --- a/client/src/App.jsx +++ b/client/src/App.jsx @@ -18,6 +18,7 @@ import HandleAuth from './components/auth/HandleAuth'; import EmailSent from './pages/EmailSent'; import Events from './pages/Events'; import ProjectLeaderDashboard from './pages/ProjectLeaderDashboard'; +import Users from './pages/Users'; import UserAdmin from './pages/UserAdmin'; import ProjectList from './pages/ProjectList'; import ManageProjects from './pages/ManageProjects'; @@ -25,6 +26,7 @@ import addProject from './components/manageProjects/addProject'; import HealthCheck from './pages/HealthCheck'; import SecretPassword from './pages/SecretPassword'; import UserWelcome from './pages/UserWelcome'; +import UserPermission from './pages/UserPermission'; import { ThemeProvider } from '@mui/material'; import theme from './theme'; @@ -46,9 +48,16 @@ const routes = [ { path: '/handleauth', name: 'handleauth', Component: HandleAuth }, { path: '/emailsent', name: 'emailsent', Component: EmailSent }, { path: '/events', name: 'events', Component: Events }, - { path: '/useradmin', name: 'useradmin', Component: UserAdmin }, + { path: '/users', name: 'users', Component: Users }, + + { path: '/users/user-search', name: 'useradmin', Component: UserAdmin }, + { + path: '/users/permission-search', + name: 'useradmin', + Component: UserPermission, + }, { path: '/projects', name: 'projects', Component: ProjectList }, - { path: '/projects/create', name: 'projectform', Component: addProject}, + { path: '/projects/create', name: 'projectform', Component: addProject }, { path: '/projects/:projectId', name: 'project', diff --git a/client/src/components/Navbar.jsx b/client/src/components/Navbar.jsx index 08ba4dc6..68d39e45 100644 --- a/client/src/components/Navbar.jsx +++ b/client/src/components/Navbar.jsx @@ -61,7 +61,7 @@ const Navbar = (props) => { {/* Admin auth -> Displays 2 links -> 'Users' and 'Projects'. */} {auth?.user?.accessLevel === 'admin' && ( <> - + USERS diff --git a/client/src/components/user-admin/UserManagement.jsx b/client/src/components/user-admin/UserManagement.jsx index e3681928..1ead7238 100644 --- a/client/src/components/user-admin/UserManagement.jsx +++ b/client/src/components/user-admin/UserManagement.jsx @@ -1,13 +1,21 @@ import React, { useState } from 'react'; -import {Box, Button, ButtonGroup, TextField, Typography, List, ListItem, ListItemButton} from '@mui/material'; - +import { + Box, + Button, + ButtonGroup, + TextField, + Typography, + List, + ListItem, + ListItemButton, +} from '@mui/material'; import '../../sass/UserAdmin.scss'; const Buttonsx = { px: 2, py: 0.5, -} +}; const UserManagement = ({ users, setUserToEdit }) => { let searchResults = []; @@ -50,97 +58,96 @@ const UserManagement = ({ users, setUserToEdit }) => { ); } return ( - - - User Management - + + + User Search + - - - + + + - 0? '#F5F5F5': 'transparent', - my: 1.2, - borderRadius: 1, - flexGrow: 1, - width: 1/1, - }}> + 0 ? '#F5F5F5' : 'transparent', + my: 1.2, + borderRadius: 1, + flexGrow: 1, + width: 1 / 1, + }} + > - - {searchResults.map((u) => { - return ( - // eslint-disable-next-line no-underscore-dangle - + {searchResults.map((u) => { + return ( + // eslint-disable-next-line no-underscore-dangle + - setUserToEdit(u)} - > - {searchResultType === 'name' - ? `${u.name?.firstName} ${u.name?.lastName} ( ${u.email} )` - : `${u.email} ( ${u.name?.firstName} ${u.name?.lastName} )`} - - - ); - })} - + }} + key={`result_${u._id}`} + > + setUserToEdit(u)} + > + {searchResultType === 'name' + ? `${u.name?.firstName} ${u.name?.lastName} ( ${u.email} )` + : `${u.email} ( ${u.name?.firstName} ${u.name?.lastName} )`} + + + ); + })} + diff --git a/client/src/components/user-admin/UserPermissionSearch.jsx b/client/src/components/user-admin/UserPermissionSearch.jsx new file mode 100644 index 00000000..53c77382 --- /dev/null +++ b/client/src/components/user-admin/UserPermissionSearch.jsx @@ -0,0 +1,354 @@ +import React, { useState, useEffect } from 'react'; +import { + Box, + Button, + ButtonGroup, + Grid, + TextField, + Typography, + List, + ListItem, + ListItemButton, +} from '@mui/material'; +import { useLocation } from 'react-router-dom'; + +import '../../sass/UserAdmin.scss'; + +const Buttonsx = { + px: 2, + py: 0.5, +}; + +const dummyData = [ + { + _id: 1, + name: { + firstName: 'John', + lastName: 'Doe', + }, + accessLevel: 'admin', + email: 'johndoe@hackforla.org', + projects: [], + }, + { + _id: 2, + name: { + firstName: 'Vinny', + lastName: 'Harris', + }, + accessLevel: 'admin', + email: 'vinnyharris@hackforla.org', + projects: [], + }, + { + _id: 3, + name: { + firstName: 'Gary', + lastName: 'Jones', + }, + accessLevel: 'admin', + email: 'garyjones@hackforla.org', + projects: [], + }, + { + _id: 4, + name: { + firstName: 'Jane', + lastName: 'Smith', + }, + accessLevel: 'projectLead', + email: 'janesmith@hackforla.org', + projects: ['VRMS', 'Mobile'], + }, + { + _id: 5, + name: { + firstName: 'Bonnie', + lastName: 'Wolfe', + }, + accessLevel: 'projectLead', + email: 'bonnie@hackforla.org', + projects: ['Home Unite Us'], + }, + { + _id: 6, + name: { + firstName: 'Diana', + lastName: 'Loeb', + }, + accessLevel: 'projectLead', + email: 'dianaloeb@hackforla.org', + projects: ['HackforLA Mobile', 'LA TDM Calculator'], + }, + { + _id: 7, + name: { + firstName: 'Zack', + lastName: 'Cruz', + }, + accessLevel: 'projectLead', + email: 'dianaloeb@hackforla.org', + projects: ['LA TDM Calculator', 'VRMS backend'], + }, + { + _id: 8, + name: { + firstName: 'Iris', + lastName: 'Sosa', + }, + accessLevel: 'projectLead', + email: 'irissosa@hackforla.org', + projects: ['Home Unite Us', 'VRMS Support'], + }, +]; + +const DummyComponent = ({ data, type, setUserToEdit }) => { + return ( + + {data.map((u, idx) => { + // Destructure user object + const { _id, name, email } = u; + // return projects.length === 0 ? + return type === 'admin' ? ( + + setUserToEdit(u)} + > + + + + {`${name.firstName.toUpperCase()} ${name.lastName.toUpperCase()} ( ${email.toUpperCase()} )`} + + + + + + ) : ( + + setUserToEdit(u)} + > + + + + {name.firstName.toUpperCase() + + ' ' + + name.lastName.toUpperCase()} + + + + + {u.project} + + + + + + ); + })} + + ); +}; + +const UserPermissionSearch = ({ users, setUserToEdit }) => { + const [userType, setUserType] = useState('admin'); // Which results will display + const [searchText, setSearchText] = useState(''); // Search term for the admin/PM search + + const location = useLocation(); + + useEffect(() => { + // Edit url by adding '/admin' upon loading + let editURL = ''; + if (userType === 'admin') { + editURL = location.pathname + '/admin'; + } else { + editURL = location.pathname + '/projects'; + } + window.history.replaceState({}, '', editURL); + }, [userType]); + + // Swaps the buttons and displayed panels for the search results, by email or by name + const buttonSwap = () => + userType === 'projectLead' + ? setUserType('admin') + : setUserType('projectLead'); + + // Handle change on input in search form + const handleChange = (event) => { + setSearchText(event.target.value); + }; + + const getFilteredData = (dummyData, searchText, userType) => { + const searchTextLowerCase = searchText.trim().toLowerCase(); + + let filteredData = dummyData + .filter((user) => user.accessLevel === userType) + .flatMap((user) => + userType === 'projectLead' && user.projects.length > 0 + ? user.projects.map((project) => ({ ...user, project })) + : [{ ...user }] + ) + .filter((user) => { + const fullName = + `${user.name.firstName} ${user.name.lastName}`.toLowerCase(); + const projectName = user.project ? user.project.toLowerCase() : ''; + return ( + fullName.includes(searchTextLowerCase) || + (userType === 'projectLead' && + projectName.includes(searchTextLowerCase)) + ); + }); + + return filteredData.sort((a, b) => { + if (userType === 'projectLead') { + return ( + a.project.localeCompare(b.project) || + a.name.firstName.localeCompare(b.name.firstName) + ); + } + return a.name.firstName.localeCompare(b.name.firstName); + }); + }; + + // Filtering logic + let filteredData; + if (!searchText) { + filteredData = dummyData.filter((user) => user.accessLevel === userType); + if (userType === 'admin') { + // Default display for admins, sorted ASC based on first name + filteredData.sort((u1, u2) => + u1.name?.firstName.localeCompare(u2.name?.firstName) + ); + } else { + // Default display of all PMs, sorted ASC based on project name, then first name + let tempFilter = []; + filteredData.forEach((user) => { + user.projects.forEach((project) => { + tempFilter.push({ ...user, project }); + }); + }); + tempFilter.sort( + (u1, u2) => + u1.project.localeCompare(u2.project) || + u1.name?.firstName.localeCompare(u2.name?.firstName) + ); + filteredData = [...tempFilter]; + } + } else { + // NOTE: Using "users" instead of "dummyData" to check the link to user profile + filteredData = getFilteredData(users, searchText, userType); + } + + return ( + + + + User Permission Search + + + + + + + + + 0 ? '#F5F5F5' : 'transparent', + my: 1.2, + borderRadius: 1, + flexGrow: 1, + width: 1 / 1, + }} + > + + {/*Component to render admins and PMs*/} + + + + + + ); +}; + +export default UserPermissionSearch; diff --git a/client/src/pages/UserAdmin.jsx b/client/src/pages/UserAdmin.jsx index 6271a374..b8d6df07 100644 --- a/client/src/pages/UserAdmin.jsx +++ b/client/src/pages/UserAdmin.jsx @@ -33,9 +33,10 @@ const UserAdmin = () => { const updateUserActiveStatus = useCallback( async (user, isActive) => { await userApiService.updateUserDbIsActive(user, isActive); - fetchUsers() - }, [userApiService, fetchUsers] - ) + fetchUsers(); + }, + [userApiService, fetchUsers] + ); // Update user's access level (admin/user) const updateUserAccessLevel = useCallback( diff --git a/client/src/pages/UserPermission.jsx b/client/src/pages/UserPermission.jsx new file mode 100644 index 00000000..0fc07f2b --- /dev/null +++ b/client/src/pages/UserPermission.jsx @@ -0,0 +1,87 @@ +import React, { useState, useEffect, useCallback } from 'react'; +import { Redirect } from 'react-router-dom'; +import '../sass/UserAdmin.scss'; +import useAuth from '../hooks/useAuth'; +import EditUsers from '../components/user-admin/EditUsers'; +import UserPermissionSearch from '../components/user-admin/UserPermissionSearch'; +import UserApiService from '../api/UserApiService'; +import ProjectApiService from '../api/ProjectApiService'; + +//NOTE: This page is based off of "UserAdmin.jsx" for now. It should be update as part of #1801. + +const UserPermission = () => { + // Initialize state hooks + const { auth } = useAuth(); + const [users, setUsers] = useState([]); // All users pulled from database + const [projects, setProjects] = useState([]); // All projects pulled from db + const [userToEdit, setUserToEdit] = useState({}); // The selected user that is being edited + + const [userApiService] = useState(new UserApiService()); + const [projectApiService] = useState(new ProjectApiService()); + + // NOTE: will have to be updated as part of #1801 + const fetchUsers = useCallback(async () => { + const userRes = await userApiService.fetchUsers(); + setUsers(userRes); + }, [userApiService]); + + const updateUserDb = useCallback( + async (user, managedProjects) => { + await userApiService.updateUserDbProjects(user, managedProjects); + fetchUsers(); + }, + [userApiService, fetchUsers] + ); + + const updateUserActiveStatus = useCallback( + async (user, isActive) => { + await userApiService.updateUserDbIsActive(user, isActive); + fetchUsers(); + }, + [userApiService, fetchUsers] + ); + + // Update user's access level (admin/user) + const updateUserAccessLevel = useCallback( + async (user, newAccessLevel) => { + await userApiService.updateUserAccessLevel(user, newAccessLevel); + fetchUsers(); + }, + [userApiService, fetchUsers] + ); + + const fetchProjects = useCallback(async () => { + const projectRes = await projectApiService.fetchProjects(); + setProjects(projectRes); + }, [projectApiService]); + + useEffect(() => { + fetchUsers(); + fetchProjects(); + }, [fetchUsers, fetchProjects]); + + const backToSearch = () => { + setUserToEdit({}); + }; + + if (!auth && !auth?.user) { + return ; + } + + if (Object.keys(userToEdit).length === 0) { + return ; + } else { + return ( + + ); + } +}; + +export default UserPermission; diff --git a/client/src/pages/Users.jsx b/client/src/pages/Users.jsx new file mode 100644 index 00000000..2ec4aad0 --- /dev/null +++ b/client/src/pages/Users.jsx @@ -0,0 +1,34 @@ +import React, { useState } from 'react'; +import { Link } from 'react-router-dom'; +import { Button, Box, Container } from '@mui/material'; + +import '../sass/Users.scss'; + +const Users = () => { + return ( + + + + + + + + + ); +}; + +export default Users; diff --git a/client/src/sass/Users.scss b/client/src/sass/Users.scss new file mode 100644 index 00000000..aa69313e --- /dev/null +++ b/client/src/sass/Users.scss @@ -0,0 +1,21 @@ +.container--users { + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + margin-top: 50px; +} + +.center { + display: flex; + justify-content: center; +} + +.button { + width: 250px; + border-radius: 8px; +} + +.margin-bottom { + margin-bottom: 15px; +}