diff --git a/backend/controllers/user.controller.js b/backend/controllers/user.controller.js index 783f3a9d0..ff9e421a4 100644 --- a/backend/controllers/user.controller.js +++ b/backend/controllers/user.controller.js @@ -42,8 +42,7 @@ UserController.admin_list = async function (req, res) { } }; -// Get list of Users with accessLevel 'admin' or 'superadmin' and also managed projects with GET -UserController.projectLead_list = async function (req, res) { +UserController.projectManager_list = async function (req, res) { const { headers } = req; if (headers['x-customrequired-header'] !== expectedHeader) { @@ -52,33 +51,68 @@ UserController.projectLead_list = async function (req, res) { try { const projectManagers = await User.find({ - $and: [ - { accessLevel: { $in: ['admin', 'superadmin'] } }, - { managedProjects: { $exists: true, $type: 'array', $ne: [] } }, - ], + managedProjects: { $exists: true, $type: 'array', $not: { $size: 0 } }, + }); + + // Filter out managers with empty arrays early + const validProjectManagers = projectManagers.filter( + (manager) => manager.managedProjects && manager.managedProjects.length > 0, + ); + + if (validProjectManagers.length === 0) { + return res.status(200).send([]); + } + + // Collect all unique project IDs to fetch in one query + const allProjectIds = new Set(); + validProjectManagers.forEach((manager) => { + manager.managedProjects.forEach((projectId) => { + // Filter out invalid project IDs (non-string or obviously invalid values) + if (typeof projectId === 'string' && projectId !== 'false' && projectId.length > 0) { + allProjectIds.add(projectId); + } + }); + }); + + // Fetch all projects in a single query + const projects = await Project.find({ _id: { $in: Array.from(allProjectIds) } }); + + // Create a map for O(1) lookup + const projectMap = new Map(); + projects.forEach((project) => { + projectMap.set(project._id.toString(), project.name); }); const updatedProjectManagers = []; - for (const projectManager of projectManagers) { + for (const projectManager of validProjectManagers) { const projectManagerObj = projectManager.toObject(); - projectManagerObj.isProjectLead = true; + projectManagerObj.isProjectMember = 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'); + // using try-catch block because old user data had invalid strings (aka 'false') for ProjectIds + try { + const projectName = projectMap.get(projectId.toString()); + if (projectName) { + projectNames.push(projectName); + } else { + console.warn('Project detail is null, cannot access name'); + } + } catch (error) { + console.warn('Failed to fetch project details for ID:', projectId, error); } } - projectManagerObj.managedProjectNames = projectNames; - updatedProjectManagers.push(projectManagerObj); + if (projectNames.length) { + projectManagerObj.managedProjectNames = projectNames; + updatedProjectManagers.push(projectManagerObj); + } } + return res.status(200).send(updatedProjectManagers); } catch (err) { + console.log('Projectlead error', err); return res.sendStatus(400); } }; diff --git a/backend/models/project.model.js b/backend/models/project.model.js index 47421aa47..2f996b02a 100644 --- a/backend/models/project.model.js +++ b/backend/models/project.model.js @@ -16,7 +16,7 @@ Idea for the future: programmingLanguages, numberGithubContributions (pull these */ const projectSchema = mongoose.Schema({ - name: { type: String, trim: true }, + name: { type: String, trim: true, required: true }, description: { type: String, trim: true }, githubIdentifier: { type: String, trim: true }, projectStatus: { type: String }, // Active, Completed, or Paused diff --git a/backend/models/user.model.js b/backend/models/user.model.js index 4c2005cc6..8541dbde2 100644 --- a/backend/models/user.model.js +++ b/backend/models/user.model.js @@ -1,4 +1,4 @@ -const mongoose = require("mongoose"); +const mongoose = require('mongoose'); // const bcrypt = require('bcrypt-nodejs'); mongoose.Promise = global.Promise; @@ -9,10 +9,10 @@ const userSchema = mongoose.Schema({ lastName: { type: String }, }, email: { type: String, unique: true }, - accessLevel: { - type: String, - enum: ["user", "admin", "superadmin"], // restricts values to "user", "admin" and "superadmin" - default: "user" + accessLevel: { + type: String, + enum: ['user', 'admin', 'superadmin'], // restricts values to "user", "admin" and "superadmin" + default: 'user', }, createdDate: { type: Date, default: Date.now }, currentRole: { type: String }, // will remove but need to update check-in form @@ -27,7 +27,7 @@ const userSchema = mongoose.Schema({ projects: [ { type: mongoose.Schema.Types.ObjectId, - ref: "Project", + ref: 'Project', }, ], githubHandle: { type: String }, // handle not including @, not the URL @@ -37,10 +37,15 @@ const userSchema = mongoose.Schema({ isHflaGithubMember: { type: Boolean }, // pull from API once github handle in place? githubPublic2FA: { type: Boolean }, // does the user have 2FA enabled on their github and membership set to public? availability: { type: String }, // availability to meet outside of hacknight times; string for now, more structured in future - managedProjects: [{ type: String}], // Which projects managed by user. + managedProjects: [ + { + type: mongoose.Schema.Types.ObjectId, + ref: 'Project', + }, + ], // Which projects managed by user. //currentProject: { type: String } // no longer need this as we can get it from Project Team Member table // password: { type: String, required: true } - isActive: { type: Boolean, default: true } + isActive: { type: Boolean, default: true }, }); userSchema.methods.serialize = function () { @@ -71,10 +76,10 @@ userSchema.methods.serialize = function () { githubPublic2FA: this.githubPublic2FA, availability: this.availability, managedProjects: this.managedProjects, - isActive: this.isActive + isActive: this.isActive, }; }; -const User = mongoose.model("User", userSchema); +const User = mongoose.model('User', userSchema); module.exports = { User }; diff --git a/backend/routers/users.router.js b/backend/routers/users.router.js index 034c12dac..a20da8f5a 100644 --- a/backend/routers/users.router.js +++ b/backend/routers/users.router.js @@ -8,7 +8,7 @@ router.get('/', UserController.user_list); router.get('/admins', UserController.admin_list); -router.get('/projectManagers', UserController.projectLead_list); +router.get('/projectManagers', UserController.projectManager_list); router.post('/', UserController.create); diff --git a/backend/routers/users.router.test.js b/backend/routers/users.router.test.js index b8f65e201..124e67678 100644 --- a/backend/routers/users.router.test.js +++ b/backend/routers/users.router.test.js @@ -15,206 +15,201 @@ testapp.use('/api/users', usersRouter); const request = supertest(testapp); describe('Unit Tests for userRouter', () => { - // Mocked user data - const mockUser = { - name: { - firstName: 'test', - lastName: 'user', - }, - email: 'newtest@test.com', - }; - const mockId = '12345'; - const mockUpdatedEmail = { - email: 'newtest@test.com', - }; - - afterEach(() => { - jest.clearAllMocks(); + // Mocked user data + const mockUser = { + name: { + firstName: 'test', + lastName: 'user', + }, + email: 'newtest@test.com', + managedProjects: [ + { + projectId: 'project123', + projectName: 'Test Project', + }, + ], + }; + const mockId = '12345'; + const mockUpdatedEmail = { + email: 'newtest@test.com', + }; + + afterEach(() => { + jest.clearAllMocks(); + }); + + describe('CREATE', () => { + it('should create a User through the UserController', async (done) => { + //Setup + //Mock the UserController function that this route calls with expected results + UserController.create.mockImplementationOnce((req, res) => { + return res.status(201).send(mockUser); + }); + + //Functionality + //Post mockUser to CREATE API Endpoint + const response = await request.post('/api/users/').send(mockUser); + + //Test + expect(UserController.create).toHaveBeenCalledWith( + expect.objectContaining({ body: mockUser }), + expect.anything(), // Mock the response object + expect.anything(), // Mock the next function + ); + expect(response.status).toBe(201); + expect(response.body).toEqual(mockUser); + + done(); }); + }); + + describe('READ', () => { + it('should get a list of Users with with GET to /api/users/ through UserController', async (done) => { + //Setup + //Mock the UserController function that this route calls with expected results + UserController.user_list.mockImplementationOnce((req, res) => { + return res.status(200).send([mockUser]); + }); + + //Functionality + //Get list of all users from READ API Endpoint + const response = await request.get('/api/users/'); + + //Test + expect(UserController.user_list).toHaveBeenCalled(); + expect(response.status).toBe(200); + expect(response.body[0]).toEqual(mockUser); + + done(); + }); + + it('should get a specific User by param with GET to /api/users?email= through UserController', async (done) => { + //Setup + //Mock the UserController function that this route calls with expected results + UserController.user_list.mockImplementationOnce((req, res) => { + return res.status(200).send([mockUser]); + }); + + //Functionality + //Get a user with a specific email using a query param to READ API Endpoint + const response = await request.get('/api/users?email=newtest@test.com'); + + //Test + expect(UserController.user_list).toHaveBeenCalled(); + expect(response.status).toBe(200); + expect(response.body[0]).toEqual(mockUser); - describe('CREATE', () => { - it('should create a User through the UserController', async (done) => { - //Setup - //Mock the UserController function that this route calls with expected results - UserController.create.mockImplementationOnce( - (req, res) => { return res.status(201).send(mockUser) } - ); - - //Functionality - //Post mockUser to CREATE API Endpoint - const response = await request - .post('/api/users/') - .send(mockUser); - - //Test - expect(UserController.create).toHaveBeenCalledWith( - expect.objectContaining({body: mockUser}), - expect.anything(), // Mock the response object - expect.anything() // Mock the next function - ); - expect(response.status).toBe(201); - expect(response.body).toEqual(mockUser); - - done(); - }); + done(); }); - - describe('READ', () => { - it('should get a list of Users with with GET to /api/users/ through UserController', async (done) => { - //Setup - //Mock the UserController function that this route calls with expected results - UserController.user_list.mockImplementationOnce( - (req, res) => { return res.status(200).send([mockUser]) } - ); - - //Functionality - //Get list of all users from READ API Endpoint - const response = await request - .get('/api/users/'); - - //Test - expect(UserController.user_list).toHaveBeenCalled(); - expect(response.status).toBe(200); - expect(response.body[0]).toEqual(mockUser); - - done(); - }); - - it('should get a specific User by param with GET to /api/users?email= through UserController', async (done) => { - //Setup - //Mock the UserController function that this route calls with expected results - UserController.user_list.mockImplementationOnce( - (req, res) => { return res.status(200).send([mockUser]) } - ); - - //Functionality - //Get a user with a specific email using a query param to READ API Endpoint - const response = await request - .get('/api/users?email=newtest@test.com'); - - //Test - expect(UserController.user_list).toHaveBeenCalled(); - expect(response.status).toBe(200); - expect(response.body[0]).toEqual(mockUser); - - done(); - }); - - it('should get a list of Users with accessLevel of admin or superadmin with GET to /api/users/admins through UserController', async (done) => { - //Setup - //Mock the UserController function that this route calls with expected results - UserController.admin_list.mockImplementationOnce( - (req, res) => { return res.status(200).send([mockUser]) } - ); - - //Functionality - //Get a list of admins and superadmins from READ API Endpoint for admins - const response = await request - .get('/api/users/admins'); - - //Test - expect(UserController.admin_list).toHaveBeenCalled(); - expect(response.status).toBe(200); - expect(response.body[0]).toEqual(mockUser); - - done(); - }); - - it('should get a list of Users with the ability to manage projects with GET to /api/users/projectManagers through UserController', async (done) => { - //Setup - //Mock the UserController function that this route calls with expected results - UserController.projectLead_list.mockImplementationOnce( - (req, res) => { return res.status(200).send([mockUser]) } - ); - - //Functionality - //Get a list of project leads and admins from READ API Endpoint for project leads - const response = await request - .get('/api/users/projectManagers'); - - //Test - expect(UserController.projectLead_list).toHaveBeenCalled(); - expect(response.status).toBe(200); - expect(response.body[0]).toEqual(mockUser); - - done(); - }); - - it('should get a specific User by UserId with GET to /api/users/:UserId through UserController', async (done) => { - //Setup - //Mock the UserController function that this route calls with expected results - UserController.user_by_id.mockImplementationOnce( - (req, res) => { return res.status(200).send(mockUser) } - ); - - //Functionality - //Get a specific user from READ API Endpoint for specific UUIDs - const response = await request - .get(`/api/users/${mockId}`); - - //Test - expect(UserController.user_by_id).toHaveBeenCalledWith( - expect.objectContaining({params: {UserId: mockId}}), - expect.anything(), // Mock the response object - expect.anything() // Mock the next function - ); - expect(response.status).toBe(200); - expect(response.body).toEqual(mockUser); - - done(); - }); + + it('should get a list of Users with accessLevel of admin or superadmin with GET to /api/users/admins through UserController', async (done) => { + //Setup + //Mock the UserController function that this route calls with expected results + UserController.admin_list.mockImplementationOnce((req, res) => { + return res.status(200).send([mockUser]); + }); + + //Functionality + //Get a list of admins and superadmins from READ API Endpoint for admins + const response = await request.get('/api/users/admins'); + + //Test + expect(UserController.admin_list).toHaveBeenCalled(); + expect(response.status).toBe(200); + expect(response.body[0]).toEqual(mockUser); + + done(); + }); + + it('should get a list of Users who manage projects with GET to /api/users/projectManagers through UserController', async (done) => { + //Setup + //Mock the UserController function that this route calls with expected results + UserController.projectManager_list.mockImplementationOnce((req, res) => { + return res.status(200).send([mockUser]); + }); + + //Functionality + //Get a list of project leads and admins from READ API Endpoint for project leads + const response = await request.get('/api/users/projectManagers'); + + //Test + expect(UserController.projectManager_list).toHaveBeenCalled(); + expect(response.status).toBe(200); + expect(response.body[0]).toEqual(mockUser); + + done(); + }); + + it('should get a specific User by UserId with GET to /api/users/:UserId through UserController', async (done) => { + //Setup + //Mock the UserController function that this route calls with expected results + UserController.user_by_id.mockImplementationOnce((req, res) => { + return res.status(200).send(mockUser); + }); + + //Functionality + //Get a specific user from READ API Endpoint for specific UUIDs + const response = await request.get(`/api/users/${mockId}`); + + //Test + expect(UserController.user_by_id).toHaveBeenCalledWith( + expect.objectContaining({ params: { UserId: mockId } }), + expect.anything(), // Mock the response object + expect.anything(), // Mock the next function + ); + expect(response.status).toBe(200); + expect(response.body).toEqual(mockUser); + + done(); }); - - describe('UPDATE', () => { - it('should update a User with PATCH to /api/users/:UserId through UserController', async (done) => { - //Setup - //Mock the UserController function that this route calls with expected results - UserController.update.mockImplementationOnce( - (req, res) => { return res.status(200).send(mockUser) } - ); - - //Functionality - //Patch a user with a specific id by sending new user data to UPDATE API Endpoint - const response = await request - .patch(`/api/users/${mockId}`) - .send(mockUpdatedEmail); - - //Test - expect(UserController.update).toHaveBeenCalledWith( - expect.objectContaining({params: {UserId: mockId}}), - expect.anything(), // Mock the response object - expect.anything() // Mock the next function - ); - expect(response.status).toBe(200); - expect(response.body).toEqual(mockUser); - - done(); - }); + }); + + describe('UPDATE', () => { + it('should update a User with PATCH to /api/users/:UserId through UserController', async (done) => { + //Setup + //Mock the UserController function that this route calls with expected results + UserController.update.mockImplementationOnce((req, res) => { + return res.status(200).send(mockUser); + }); + + //Functionality + //Patch a user with a specific id by sending new user data to UPDATE API Endpoint + const response = await request.patch(`/api/users/${mockId}`).send(mockUpdatedEmail); + + //Test + expect(UserController.update).toHaveBeenCalledWith( + expect.objectContaining({ params: { UserId: mockId } }), + expect.anything(), // Mock the response object + expect.anything(), // Mock the next function + ); + expect(response.status).toBe(200); + expect(response.body).toEqual(mockUser); + + done(); }); - - describe('DELETE', () => { - it('should delete a specific user by Id with DELETE /api/users/:UserId through UserController', async (done) => { - //Setup - //Mock the UserController function that this route calls with expected results - UserController.delete.mockImplementationOnce( - (req, res) => { return res.status(200).send(mockUser) } - ); - - //Delete user with a specific id via a request to DELETE API Endpoint - const response = await request - .delete(`/api/users/${mockId}`) - .send(mockUpdatedEmail); - - //Test - expect(UserController.delete).toHaveBeenCalledWith( - expect.objectContaining({params: {UserId: mockId}}), - expect.anything(), // Mock the response object - expect.anything() // Mock the next function - ); - expect(response.status).toBe(200); - expect(response.body).toEqual(mockUser); - - done(); - }); + }); + + describe('DELETE', () => { + it('should delete a specific user by Id with DELETE /api/users/:UserId through UserController', async (done) => { + //Setup + //Mock the UserController function that this route calls with expected results + UserController.delete.mockImplementationOnce((req, res) => { + return res.status(200).send(mockUser); + }); + + //Delete user with a specific id via a request to DELETE API Endpoint + const response = await request.delete(`/api/users/${mockId}`).send(mockUpdatedEmail); + + //Test + expect(UserController.delete).toHaveBeenCalledWith( + expect.objectContaining({ params: { UserId: mockId } }), + expect.anything(), // Mock the response object + expect.anything(), // Mock the next function + ); + expect(response.status).toBe(200); + expect(response.body).toEqual(mockUser); + + done(); }); -}); \ No newline at end of file + }); +}); diff --git a/client/src/App.jsx b/client/src/App.jsx index 5be55b97a..e9261d4f2 100644 --- a/client/src/App.jsx +++ b/client/src/App.jsx @@ -26,15 +26,10 @@ 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 { Box, ThemeProvider } from '@mui/material'; import theme from './theme'; - import './App.scss'; - /* withAuth Hook Wraps component with withAuth hook to manage automatic redirect to login page if user is not logged in diff --git a/client/src/components/user-admin/UserPermissionSearch.jsx b/client/src/components/user-admin/UserPermissionSearch.jsx index f3cc5894e..bb52a3180 100644 --- a/client/src/components/user-admin/UserPermissionSearch.jsx +++ b/client/src/components/user-admin/UserPermissionSearch.jsx @@ -11,22 +11,27 @@ import { ListItemButton, } from '@mui/material'; import { useLocation } from 'react-router-dom'; - import '../../sass/UserAdmin.scss'; -const Buttonsx = { - px: 2, - py: 0.5, +const buttonSX = { + adminButton: { + px: 2, + py: 0.5, + }, + projMemsButton: { + px: 6, + py: 0.5, + } }; -const DummyComponent = ({ data, isProjectLead, setUserToEdit }) => { +const ListComponent = ({ data, isProjectMember, setUserToEdit }) => { return ( - {data.map((u, idx) => { + {data.map((user, idx) => { // Destructure user object - const { _id, name, email } = u; + const { _id, name, email } = user; // return projects.length === 0 ? - return !isProjectLead ? ( + return !isProjectMember ? ( { }} className="search-results-button" type="button" - onClick={() => setUserToEdit(u)} + onClick={() => setUserToEdit(user)} > @@ -76,7 +81,7 @@ const DummyComponent = ({ data, isProjectLead, setUserToEdit }) => { }} className="search-results-button" type="button" - onClick={() => setUserToEdit(u)} + onClick={() => setUserToEdit(user)} > @@ -88,7 +93,7 @@ const DummyComponent = ({ data, isProjectLead, setUserToEdit }) => { - {u.managedProjectName} + {user.managedProjectName} @@ -101,9 +106,8 @@ const DummyComponent = ({ data, isProjectLead, setUserToEdit }) => { }; 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 [isProjectMember, setIsProjectMember] = useState(false); const location = useLocation(); @@ -112,34 +116,34 @@ const UserPermissionSearch = ({ admins, projectLeads, setUserToEdit }) => { useEffect(() => { // Edit url by adding '/admin' upon loading let editURL = ''; - if (userType === 'admin') { + if (!isProjectMember) { editURL = location.pathname + '/admin'; } else { editURL = location.pathname + '/projects'; } window.history.replaceState({}, '', editURL); - }, [userType]); + }, [isProjectMember]); // Swaps the buttons and displayed panels for the search results, by email or by name const buttonSwap = () => - isProjectLead ? setIsProjectLead(false) : setIsProjectLead(true); + isProjectMember ? setIsProjectMember(false) : setIsProjectMember(true); // Handle change on input in search form const handleChange = (event) => { setSearchText(event.target.value); }; - const getFilteredData = (resultData, searchText, isProjectLead) => { + const getFilteredData = (resultData, searchText, isProjectMember) => { const searchTextLowerCase = searchText.trim().toLowerCase(); - let filteredData = resultData + let filteredUsers = resultData .filter((user) => - isProjectLead - ? user.isProjectLead === true - : user.isProjectLead === undefined + isProjectMember + ? user.isProjectMember === true + : user.isProjectMember === undefined ) .flatMap((user) => - isProjectLead && user.managedProjectNames.length > 0 + isProjectMember && user.managedProjectNames.length > 0 ? user.managedProjectNames.map((managedProjectName) => ({ ...user, managedProjectName, @@ -154,12 +158,12 @@ const UserPermissionSearch = ({ admins, projectLeads, setUserToEdit }) => { : ''; return ( fullName.includes(searchTextLowerCase) || - (isProjectLead && projectName.includes(searchTextLowerCase)) + (isProjectMember && projectName.includes(searchTextLowerCase)) ); }); - return filteredData.sort((a, b) => { - if (isProjectLead) { + return filteredUsers.sort((a, b) => { + if (isProjectMember) { return ( a.managedProjectName.localeCompare(b.managedProjectName) || a.name.firstName.localeCompare(b.name.firstName) @@ -173,12 +177,12 @@ const UserPermissionSearch = ({ admins, projectLeads, setUserToEdit }) => { let filteredData; if (!searchText) { filteredData = resultData.filter((user) => - isProjectLead - ? user.isProjectLead === true - : user.isProjectLead === undefined + isProjectMember + ? user.isProjectMember === true + : user.isProjectMember === undefined ); - if (!isProjectLead) { + if (!isProjectMember) { // Default display for admins, sorted ASC based on first name filteredData.sort((u1, u2) => u1.name?.firstName.localeCompare(u2.name?.firstName) @@ -200,7 +204,7 @@ const UserPermissionSearch = ({ admins, projectLeads, setUserToEdit }) => { } } else { // NOTE: Using "users" instead of "dummyData" to check the link to user profile - filteredData = getFilteredData(resultData, searchText, isProjectLead); + filteredData = getFilteredData(resultData, searchText, isProjectMember); } return ( @@ -233,26 +237,26 @@ const UserPermissionSearch = ({ admins, projectLeads, setUserToEdit }) => { }} > { > {/*Component to render admins and PMs*/} - diff --git a/client/src/pages/UserPermissionSearch.jsx b/client/src/pages/UserPermissionSearch.jsx deleted file mode 100644 index fd559ff31..000000000 --- a/client/src/pages/UserPermissionSearch.jsx +++ /dev/null @@ -1,287 +0,0 @@ -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;