diff --git a/backend/controllers/project.controller.js b/backend/controllers/project.controller.js index 37c15025b..7af1e99c1 100644 --- a/backend/controllers/project.controller.js +++ b/backend/controllers/project.controller.js @@ -13,15 +13,15 @@ ProjectController.project_list = async function (req, res) { } }; -ProjectController.pm_filtered_projects = async function(req, res) { +ProjectController.pm_filtered_projects = async function (req, res) { try { - const projectList = await Project.find({}) - const projects = projectList.filter(proj => req.body.includes(proj._id.toString())) - return res.status(200).send(projects) - } catch(e) { - return res.sendStatus(400) + const projectList = await Project.find({}); + const projects = projectList.filter((proj) => req.body.includes(proj._id.toString())); + return res.status(200).send(projects); + } catch (e) { + return res.sendStatus(400); } -} +}; ProjectController.create = async function (req, res) { const { body } = req; @@ -48,7 +48,7 @@ ProjectController.project_by_id = async function (req, res) { ProjectController.update = async function (req, res) { const { ProjectId } = req.params; try { - const project = await Project.findOneAndUpdate({_id: ProjectId}, req.body, {new: true}); + const project = await Project.findOneAndUpdate({ _id: ProjectId }, req.body, { new: true }); return res.status(200).send(project); } catch (err) { return res.sendStatus(400); @@ -66,5 +66,4 @@ ProjectController.destroy = async function (req, res) { } }; - module.exports = ProjectController; diff --git a/backend/controllers/user.controller.js b/backend/controllers/user.controller.js index e9764765d..897496e75 100644 --- a/backend/controllers/user.controller.js +++ b/backend/controllers/user.controller.js @@ -3,7 +3,7 @@ const jwt = require('jsonwebtoken'); const EmailController = require('./email.controller'); const { CONFIG_AUTH } = require('../config'); -const { User } = require('../models'); +const { User, Project } = require('../models'); const expectedHeader = process.env.CUSTOM_REQUEST_HEADER; @@ -26,6 +26,63 @@ UserController.user_list = async function (req, res) { } }; +// Get list of Users with accessLevel 'admin' or 'superadmin' with GET +UserController.admin_list = async function (req, res) { + const { headers } = req; + + if (headers['x-customrequired-header'] !== expectedHeader) { + return res.sendStatus(403); + } + + try { + const admins = await User.find({ accessLevel: { $in: ['admin', 'superadmin'] } }); + return res.status(200).send(admins); + } catch (err) { + return res.sendStatus(400); + } +}; + +// Get list of Users with accessLevel 'admin' or 'superadmin' and also managed projects with GET +UserController.projectLead_list = async function (req, res) { + const { headers } = req; + + if (headers['x-customrequired-header'] !== expectedHeader) { + return res.sendStatus(403); + } + + try { + const projectManagers = await User.find({ + $and: [ + { accessLevel: { $in: ['admin', 'superadmin'] } }, + { managedProjects: { $exists: true, $type: 'array', $ne: [] } }, + ], + }); + + const updatedProjectManagers = []; + + for (const projectManager of projectManagers) { + const projectManagerObj = projectManager.toObject(); + projectManagerObj.isProjectLead = true; + const projectNames = []; + + for (const projectId of projectManagerObj.managedProjects) { + const projectDetail = await Project.findById(projectId); + if (projectDetail && projectDetail.name) { + projectNames.push(projectDetail.name); + } else { + console.warn('Project detail is null, cannot access name'); + } + } + projectManagerObj.managedProjectNames = projectNames; + + updatedProjectManagers.push(projectManagerObj); + } + return res.status(200).send(updatedProjectManagers); + } catch (err) { + return res.sendStatus(400); + } +}; + // Get User by id with GET UserController.user_by_id = async function (req, res) { const { headers } = req; @@ -51,12 +108,11 @@ UserController.create = async function (req, res) { return res.sendStatus(403); } - try { - const newUser = { - ...req.body, - email: req.body.email.toLowerCase() - } + const newUser = { + ...req.body, + email: req.body.email.toLowerCase(), + }; const user = await User.create(newUser); return res.status(201).send(user); } catch (error) { @@ -80,7 +136,7 @@ UserController.update = async function (req, res) { } try { - const user = await User.findOneAndUpdate({_id: UserId}, req.body, { new: true }); + const user = await User.findOneAndUpdate({ _id: UserId }, req.body, { new: true }); return res.status(200).send(user); } catch (err) { return res.sendStatus(400); @@ -192,10 +248,7 @@ UserController.verifyMe = async function (req, res) { }; UserController.logout = async function (req, res) { - return res - .clearCookie('token') - .status(200) - .send('Successfully logged out.'); -} + return res.clearCookie('token').status(200).send('Successfully logged out.'); +}; module.exports = UserController; diff --git a/backend/routers/projects.router.js b/backend/routers/projects.router.js index 533e300b8..16aed3728 100644 --- a/backend/routers/projects.router.js +++ b/backend/routers/projects.router.js @@ -1,8 +1,8 @@ -const express = require("express"); +const express = require('express'); const router = express.Router(); const { ProjectController } = require('../controllers'); -const { AuthUtil } = require("../middleware"); +const { AuthUtil } = require('../middleware'); // The base is /api/projects router.get('/', ProjectController.project_list); @@ -18,5 +18,4 @@ router.put('/:ProjectId', AuthUtil.verifyCookie, ProjectController.update); router.patch('/:ProjectId', AuthUtil.verifyCookie, ProjectController.update); - module.exports = router; diff --git a/backend/routers/users.router.js b/backend/routers/users.router.js index db4efb231..034c12dac 100644 --- a/backend/routers/users.router.js +++ b/backend/routers/users.router.js @@ -6,6 +6,10 @@ const { UserController } = require('../controllers'); // The base is /api/users router.get('/', UserController.user_list); +router.get('/admins', UserController.admin_list); + +router.get('/projectManagers', UserController.projectLead_list); + router.post('/', UserController.create); router.get('/:UserId', UserController.user_by_id); diff --git a/client/src/App.jsx b/client/src/App.jsx index 82fdf5baa..947e77d22 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,9 @@ import addProject from './components/manageProjects/addProject'; import HealthCheck from './pages/HealthCheck'; import SecretPassword from './pages/SecretPassword'; import UserWelcome from './pages/UserWelcome'; +// Added User Permission Search component +import UserPermissionSearch from './pages/UserPermissionSearch'; +import UserPermission from './pages/UserPermission'; import { ThemeProvider } from '@mui/material'; import theme from './theme'; @@ -46,9 +50,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/api/ProjectApiService.js b/client/src/api/ProjectApiService.js index 7a863683f..b2e692462 100644 --- a/client/src/api/ProjectApiService.js +++ b/client/src/api/ProjectApiService.js @@ -53,9 +53,9 @@ class ProjectApiService { console.log('THIS BASEPROJECT URL', this.baseProjectUrl); try { - const proj = await fetch(this.baseProjectUrl, requestOptions); - const projectDetails = await proj.json() - return projectDetails._id + const proj = await fetch(this.baseProjectUrl, requestOptions); + const projectDetails = await proj.json(); + return projectDetails._id; } catch (error) { console.error(`Add project error: `, error); alert('Server not responding. Please try again.'); @@ -87,13 +87,13 @@ class ProjectApiService { async fetchPMProjects(projects) { const requestOptions = { headers: this.headers, - method: "PUT", - body: JSON.stringify(projects) - } + method: 'PUT', + body: JSON.stringify(projects), + }; try { - const res = await fetch(this.baseProjectUrl, requestOptions); - return await res.json(); - } catch(e) { + const res = await fetch(this.baseProjectUrl, requestOptions); + return await res.json(); + } catch (e) { console.error(e); return undefined; } diff --git a/client/src/api/UserApiService.js b/client/src/api/UserApiService.js index 8d6c475f4..3ef938f02 100644 --- a/client/src/api/UserApiService.js +++ b/client/src/api/UserApiService.js @@ -22,6 +22,34 @@ class UserApiService { return []; } + async fetchAdmins() { + try { + const route = this.baseUserUrl + '/admins'; + const res = await fetch(route, { + headers: this.headers, + }); + return await res.json(); + } catch (error) { + console.error(`fetchAdmins error: ${error}`); + alert('Server not responding. Please refresh the page.'); + } + return []; + } + + async fetchProjectsManagers() { + try { + const route = this.baseUserUrl + '/projectManagers'; + const res = await fetch(route, { + headers: this.headers, + }); + return await res.json(); + } catch (error) { + console.error(`fetchProjectsManagers error: ${error}`); + alert('Server not responding. Please refresh the page.'); + } + return []; + } + // Updates user projects in db async updateUserDbProjects(userToEdit, managedProjects) { // eslint-disable-next-line no-underscore-dangle diff --git a/client/src/components/Navbar.jsx b/client/src/components/Navbar.jsx index 08ba4dc62..68d39e454 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 e3681928a..1ead7238c 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 000000000..f3cc5894e --- /dev/null +++ b/client/src/components/user-admin/UserPermissionSearch.jsx @@ -0,0 +1,283 @@ +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 DummyComponent = ({ data, isProjectLead, setUserToEdit }) => { + return ( + + {data.map((u, idx) => { + // Destructure user object + const { _id, name, email } = u; + // return projects.length === 0 ? + return !isProjectLead ? ( + + setUserToEdit(u)} + > + + + + {`${name.firstName.toUpperCase()} ${name.lastName.toUpperCase()} ( ${email.toUpperCase()} )`} + + + + + + ) : ( + + setUserToEdit(u)} + > + + + + {name.firstName.toUpperCase() + + ' ' + + name.lastName.toUpperCase()} + + + + + {u.managedProjectName} + + + + + + ); + })} + + ); +}; + +const UserPermissionSearch = ({ admins, projectLeads, setUserToEdit }) => { + const [userType, setUserType] = useState('admin'); // Which results will display + const [searchText, setSearchText] = useState(''); // Search term for the admin/PM search + const [isProjectLead, setIsProjectLead] = useState(false); + + const location = useLocation(); + + const resultData = [...admins, ...projectLeads]; + + 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 = () => + isProjectLead ? setIsProjectLead(false) : setIsProjectLead(true); + + // Handle change on input in search form + const handleChange = (event) => { + setSearchText(event.target.value); + }; + + const getFilteredData = (resultData, searchText, isProjectLead) => { + const searchTextLowerCase = searchText.trim().toLowerCase(); + + let filteredData = resultData + .filter((user) => + isProjectLead + ? user.isProjectLead === true + : user.isProjectLead === undefined + ) + .flatMap((user) => + isProjectLead && user.managedProjectNames.length > 0 + ? user.managedProjectNames.map((managedProjectName) => ({ + ...user, + managedProjectName, + })) + : [{ ...user }] + ) + .filter((user) => { + const fullName = + `${user.name.firstName} ${user.name.lastName}`.toLowerCase(); + const projectName = user.managedProjectName + ? user.managedProjectName.toLowerCase() + : ''; + return ( + fullName.includes(searchTextLowerCase) || + (isProjectLead && projectName.includes(searchTextLowerCase)) + ); + }); + + return filteredData.sort((a, b) => { + if (isProjectLead) { + return ( + a.managedProjectName.localeCompare(b.managedProjectName) || + a.name.firstName.localeCompare(b.name.firstName) + ); + } + return a.name.firstName.localeCompare(b.name.firstName); + }); + }; + + // Filtering logic + let filteredData; + if (!searchText) { + filteredData = resultData.filter((user) => + isProjectLead + ? user.isProjectLead === true + : user.isProjectLead === undefined + ); + + if (!isProjectLead) { + // 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.managedProjectNames.forEach((managedProjectName) => { + tempFilter.push({ ...user, managedProjectName }); + }); + }); + tempFilter.sort( + (u1, u2) => + u1.managedProjectName.localeCompare(u2.managedProjectName) || + 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(resultData, searchText, isProjectLead); + } + + 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 6271a374d..0fcf458b1 100644 --- a/client/src/pages/UserAdmin.jsx +++ b/client/src/pages/UserAdmin.jsx @@ -3,9 +3,9 @@ import { Redirect } from 'react-router-dom'; import '../sass/UserAdmin.scss'; import useAuth from '../hooks/useAuth'; import EditUsers from '../components/user-admin/EditUsers'; -import UserManagement from '../components/user-admin/UserManagement'; import UserApiService from '../api/UserApiService'; import ProjectApiService from '../api/ProjectApiService'; +import UserManagement from '../components/user-admin/UserManagement'; const UserAdmin = () => { // Initialize state hooks @@ -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 000000000..af38752a6 --- /dev/null +++ b/client/src/pages/UserPermission.jsx @@ -0,0 +1,97 @@ +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'; + +const UserPermission = () => { + // Initialize state hooks + const { auth } = useAuth(); + const [admins, setAdmins] = useState([]); // All admins pulled from database + const [projects, setProjects] = useState([]); // All projects pulled from db + const [projectManagers, setProjectManagers] = useState([]); //All project managers pulled from db + const [userToEdit, setUserToEdit] = useState({}); // The selected user that is being edited + + const [userApiService] = useState(new UserApiService()); + const [projectApiService] = useState(new ProjectApiService()); + + const fetchAdmins = useCallback(async () => { + const userRes = await userApiService.fetchAdmins(); + setAdmins(userRes); + }, [userApiService]); + + const fetchProjectsManagers = useCallback(async () => { + const pmRes = await userApiService.fetchProjectsManagers(); + setProjectManagers(pmRes); + }, [userApiService]); + + const updateUserDb = useCallback( + async (user, managedProjects) => { + await userApiService.updateUserDbProjects(user, managedProjects); + fetchAdmins(); + }, + [userApiService, fetchAdmins] + ); + + const updateUserActiveStatus = useCallback( + async (user, isActive) => { + await userApiService.updateUserDbIsActive(admins, isActive); + fetchAdmins(); + }, + [userApiService, fetchAdmins] + ); + + // Update user's access level (admin/user) + const updateUserAccessLevel = useCallback( + async (admin, newAccessLevel) => { + await userApiService.updateUserAccessLevel(admin, newAccessLevel); + fetchAdmins(); + }, + [userApiService, fetchAdmins] + ); + + const fetchProjects = useCallback(async () => { + const projectRes = await projectApiService.fetchProjects(); + setProjects(projectRes); + }, [projectApiService]); + + useEffect(() => { + fetchAdmins(); + fetchProjects(); + fetchProjectsManagers(); + }, [fetchAdmins, fetchProjects, fetchProjectsManagers]); + + 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/UserPermissionSearch.jsx b/client/src/pages/UserPermissionSearch.jsx new file mode 100644 index 000000000..fd559ff31 --- /dev/null +++ b/client/src/pages/UserPermissionSearch.jsx @@ -0,0 +1,287 @@ +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 }) => { + return ( + + {data.map((user, index) => { + // Destructure user object + const { _id, name, email } = user; + return type === 'admin' ? + ( + + setUserToEdit(user)} + > + + + + {`${name.firstName.toUpperCase()} ${name.lastName.toUpperCase()} ( ${email.toUpperCase()} )`} + + + + + + ) : + + setUserToEdit(user)} + > + + + {name.firstName.toUpperCase() + " " + name.lastName.toUpperCase()} + + + {user.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(() => { + // Edits 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); + }; + + // 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]; + } + } + + 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/Users.jsx b/client/src/pages/Users.jsx new file mode 100644 index 000000000..2ec4aad01 --- /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 000000000..aa69313ef --- /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; +}