diff --git a/backend/controllers/user.controller.js b/backend/controllers/user.controller.js index ff9e421a4..783f3a9d0 100644 --- a/backend/controllers/user.controller.js +++ b/backend/controllers/user.controller.js @@ -42,7 +42,8 @@ UserController.admin_list = async function (req, res) { } }; -UserController.projectManager_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) { const { headers } = req; if (headers['x-customrequired-header'] !== expectedHeader) { @@ -51,68 +52,33 @@ UserController.projectManager_list = async function (req, res) { try { const projectManagers = await User.find({ - 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); + $and: [ + { accessLevel: { $in: ['admin', 'superadmin'] } }, + { managedProjects: { $exists: true, $type: 'array', $ne: [] } }, + ], }); const updatedProjectManagers = []; - for (const projectManager of validProjectManagers) { + for (const projectManager of projectManagers) { const projectManagerObj = projectManager.toObject(); - projectManagerObj.isProjectMember = true; + projectManagerObj.isProjectLead = true; const projectNames = []; for (const projectId of projectManagerObj.managedProjects) { - // 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); + 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; - if (projectNames.length) { - projectManagerObj.managedProjectNames = projectNames; - updatedProjectManagers.push(projectManagerObj); - } + 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 2f996b02a..47421aa47 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, required: true }, + name: { type: String, trim: 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 8541dbde2..4c2005cc6 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,15 +37,10 @@ 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: mongoose.Schema.Types.ObjectId, - ref: 'Project', - }, - ], // Which projects managed by user. + managedProjects: [{ type: String}], // 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 () { @@ -76,10 +71,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 a20da8f5a..034c12dac 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.projectManager_list); +router.get('/projectManagers', UserController.projectLead_list); router.post('/', UserController.create); diff --git a/backend/routers/users.router.test.js b/backend/routers/users.router.test.js index 124e67678..b8f65e201 100644 --- a/backend/routers/users.router.test.js +++ b/backend/routers/users.router.test.js @@ -15,201 +15,206 @@ 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', - 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(); + // 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(); }); - }); - - 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(); + 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(); + }); }); - - 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('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(); + }); }); - }); - - 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 e9261d4f2..5be55b97a 100644 --- a/client/src/App.jsx +++ b/client/src/App.jsx @@ -26,10 +26,15 @@ 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 bb52a3180..f3cc5894e 100644 --- a/client/src/components/user-admin/UserPermissionSearch.jsx +++ b/client/src/components/user-admin/UserPermissionSearch.jsx @@ -11,27 +11,22 @@ import { ListItemButton, } from '@mui/material'; import { useLocation } from 'react-router-dom'; + import '../../sass/UserAdmin.scss'; -const buttonSX = { - adminButton: { - px: 2, - py: 0.5, - }, - projMemsButton: { - px: 6, - py: 0.5, - } +const Buttonsx = { + px: 2, + py: 0.5, }; -const ListComponent = ({ data, isProjectMember, setUserToEdit }) => { +const DummyComponent = ({ data, isProjectLead, setUserToEdit }) => { return ( - {data.map((user, idx) => { + {data.map((u, idx) => { // Destructure user object - const { _id, name, email } = user; + const { _id, name, email } = u; // return projects.length === 0 ? - return !isProjectMember ? ( + return !isProjectLead ? ( { }} className="search-results-button" type="button" - onClick={() => setUserToEdit(user)} + onClick={() => setUserToEdit(u)} > @@ -81,7 +76,7 @@ const ListComponent = ({ data, isProjectMember, setUserToEdit }) => { }} className="search-results-button" type="button" - onClick={() => setUserToEdit(user)} + onClick={() => setUserToEdit(u)} > @@ -93,7 +88,7 @@ const ListComponent = ({ data, isProjectMember, setUserToEdit }) => { - {user.managedProjectName} + {u.managedProjectName} @@ -106,8 +101,9 @@ const ListComponent = ({ data, isProjectMember, 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 [isProjectMember, setIsProjectMember] = useState(false); + const [isProjectLead, setIsProjectLead] = useState(false); const location = useLocation(); @@ -116,34 +112,34 @@ const UserPermissionSearch = ({ admins, projectLeads, setUserToEdit }) => { useEffect(() => { // Edit url by adding '/admin' upon loading let editURL = ''; - if (!isProjectMember) { + if (userType === 'admin') { editURL = location.pathname + '/admin'; } else { editURL = location.pathname + '/projects'; } window.history.replaceState({}, '', editURL); - }, [isProjectMember]); + }, [userType]); // Swaps the buttons and displayed panels for the search results, by email or by name const buttonSwap = () => - isProjectMember ? setIsProjectMember(false) : setIsProjectMember(true); + isProjectLead ? setIsProjectLead(false) : setIsProjectLead(true); // Handle change on input in search form const handleChange = (event) => { setSearchText(event.target.value); }; - const getFilteredData = (resultData, searchText, isProjectMember) => { + const getFilteredData = (resultData, searchText, isProjectLead) => { const searchTextLowerCase = searchText.trim().toLowerCase(); - let filteredUsers = resultData + let filteredData = resultData .filter((user) => - isProjectMember - ? user.isProjectMember === true - : user.isProjectMember === undefined + isProjectLead + ? user.isProjectLead === true + : user.isProjectLead === undefined ) .flatMap((user) => - isProjectMember && user.managedProjectNames.length > 0 + isProjectLead && user.managedProjectNames.length > 0 ? user.managedProjectNames.map((managedProjectName) => ({ ...user, managedProjectName, @@ -158,12 +154,12 @@ const UserPermissionSearch = ({ admins, projectLeads, setUserToEdit }) => { : ''; return ( fullName.includes(searchTextLowerCase) || - (isProjectMember && projectName.includes(searchTextLowerCase)) + (isProjectLead && projectName.includes(searchTextLowerCase)) ); }); - return filteredUsers.sort((a, b) => { - if (isProjectMember) { + return filteredData.sort((a, b) => { + if (isProjectLead) { return ( a.managedProjectName.localeCompare(b.managedProjectName) || a.name.firstName.localeCompare(b.name.firstName) @@ -177,12 +173,12 @@ const UserPermissionSearch = ({ admins, projectLeads, setUserToEdit }) => { let filteredData; if (!searchText) { filteredData = resultData.filter((user) => - isProjectMember - ? user.isProjectMember === true - : user.isProjectMember === undefined + isProjectLead + ? user.isProjectLead === true + : user.isProjectLead === undefined ); - if (!isProjectMember) { + if (!isProjectLead) { // Default display for admins, sorted ASC based on first name filteredData.sort((u1, u2) => u1.name?.firstName.localeCompare(u2.name?.firstName) @@ -204,7 +200,7 @@ const UserPermissionSearch = ({ admins, projectLeads, setUserToEdit }) => { } } else { // NOTE: Using "users" instead of "dummyData" to check the link to user profile - filteredData = getFilteredData(resultData, searchText, isProjectMember); + filteredData = getFilteredData(resultData, searchText, isProjectLead); } return ( @@ -237,26 +233,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 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;