Merge branch 'frontend' into gruppDM
This commit is contained in:
		
						commit
						544383809b
					
				
					 14 changed files with 346 additions and 138 deletions
				
			
		|  | @ -17,6 +17,7 @@ type Database interface { | ||||||
| 	AddUser(username string, password string) error | 	AddUser(username string, password string) error | ||||||
| 	CheckUser(username string, password string) bool | 	CheckUser(username string, password string) bool | ||||||
| 	RemoveUser(username string) error | 	RemoveUser(username string) error | ||||||
|  | 	RemoveUserFromProject(username string, projectname string) error | ||||||
| 	PromoteToAdmin(username string) error | 	PromoteToAdmin(username string) error | ||||||
| 	GetUserId(username string) (int, error) | 	GetUserId(username string) (int, error) | ||||||
| 	AddProject(name string, description string, username string) error | 	AddProject(name string, description string, username string) error | ||||||
|  | @ -43,6 +44,7 @@ type Database interface { | ||||||
| 	GetProjectTimes(projectName string) (map[string]int, error) | 	GetProjectTimes(projectName string) (map[string]int, error) | ||||||
| 	UpdateWeeklyReport(projectName string, userName string, week int, developmentTime int, meetingTime int, adminTime int, ownWorkTime int, studyTime int, testingTime int) error | 	UpdateWeeklyReport(projectName string, userName string, week int, developmentTime int, meetingTime int, adminTime int, ownWorkTime int, studyTime int, testingTime int) error | ||||||
| 	RemoveProject(projectname string) error | 	RemoveProject(projectname string) error | ||||||
|  | 	GetUserName(id int) (string, error) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // This struct is a wrapper type that holds the database connection | // This struct is a wrapper type that holds the database connection | ||||||
|  | @ -86,6 +88,10 @@ const isProjectManagerQuery = `SELECT COUNT(*) > 0 FROM user_roles | ||||||
| 								JOIN projects ON user_roles.project_id = projects.id | 								JOIN projects ON user_roles.project_id = projects.id | ||||||
| 								WHERE users.username = ? AND projects.name = ? AND user_roles.p_role = 'project_manager'` | 								WHERE users.username = ? AND projects.name = ? AND user_roles.p_role = 'project_manager'` | ||||||
| 
 | 
 | ||||||
|  | const removeUserFromProjectQuery = `DELETE FROM user_roles  | ||||||
|  | 									WHERE user_id = (SELECT id FROM users WHERE username = ?)  | ||||||
|  | 									AND project_id = (SELECT id FROM projects WHERE name = ?)` | ||||||
|  | 
 | ||||||
| // DbConnect connects to the database | // DbConnect connects to the database | ||||||
| func DbConnect(dbpath string) Database { | func DbConnect(dbpath string) Database { | ||||||
| 	// Open the database | 	// Open the database | ||||||
|  | @ -147,6 +153,11 @@ func (d *Db) AddUserToProject(username string, projectname string, role string) | ||||||
| 	return err | 	return err | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | func (d *Db) RemoveUserFromProject(username string, projectname string) error { | ||||||
|  | 	_, err := d.Exec(removeUserFromProjectQuery, username, projectname) | ||||||
|  | 	return err | ||||||
|  | } | ||||||
|  | 
 | ||||||
| // ChangeUserRole changes the role of a user within a project. | // ChangeUserRole changes the role of a user within a project. | ||||||
| func (d *Db) ChangeUserRole(username string, projectname string, role string) error { | func (d *Db) ChangeUserRole(username string, projectname string, role string) error { | ||||||
| 	// Execute the SQL query to change the user's role | 	// Execute the SQL query to change the user's role | ||||||
|  | @ -601,3 +612,9 @@ func (d *Db) RemoveProject(projectname string) error { | ||||||
| 	_, err := d.Exec("DELETE FROM projects WHERE name = ?", projectname) | 	_, err := d.Exec("DELETE FROM projects WHERE name = ?", projectname) | ||||||
| 	return err | 	return err | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | func (d *Db) GetUserName(id int) (string, error) { | ||||||
|  | 	var username string | ||||||
|  | 	err := d.Get(&username, "SELECT username FROM users WHERE id = ?", id) | ||||||
|  | 	return username, err | ||||||
|  | } | ||||||
|  |  | ||||||
							
								
								
									
										40
									
								
								backend/internal/handlers/projects/RemoveUserFromProject.go
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										40
									
								
								backend/internal/handlers/projects/RemoveUserFromProject.go
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,40 @@ | ||||||
|  | package projects | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	db "ttime/internal/database" | ||||||
|  | 
 | ||||||
|  | 	"github.com/gofiber/fiber/v2" | ||||||
|  | 	"github.com/gofiber/fiber/v2/log" | ||||||
|  | 	"github.com/golang-jwt/jwt/v5" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | func RemoveUserFromProject(c *fiber.Ctx) error { | ||||||
|  | 	user := c.Locals("user").(*jwt.Token) | ||||||
|  | 	claims := user.Claims.(jwt.MapClaims) | ||||||
|  | 	pm_name := claims["name"].(string) | ||||||
|  | 
 | ||||||
|  | 	project := c.Params("projectName") | ||||||
|  | 	username := c.Query("userName") | ||||||
|  | 
 | ||||||
|  | 	// Check if the user is a project manager | ||||||
|  | 	isPM, err := db.GetDb(c).IsProjectManager(pm_name, project) | ||||||
|  | 	if err != nil { | ||||||
|  | 		log.Info("Error checking if user is project manager:", err) | ||||||
|  | 		return c.Status(500).SendString(err.Error()) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if !isPM { | ||||||
|  | 		log.Info("User: ", pm_name, " is not a project manager in project: ", project) | ||||||
|  | 		return c.Status(403).SendString("User is not a project manager") | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// Remove the user from the project | ||||||
|  | 	if err = db.GetDb(c).RemoveUserFromProject(username, project); err != nil { | ||||||
|  | 		log.Info("Error removing user from project:", err) | ||||||
|  | 		return c.Status(500).SendString(err.Error()) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// Return success message | ||||||
|  | 	log.Info("User : ", username, " removed from project: ", project) | ||||||
|  | 	return c.SendStatus(fiber.StatusOK) | ||||||
|  | } | ||||||
							
								
								
									
										32
									
								
								backend/internal/handlers/users/GetUserName.go
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										32
									
								
								backend/internal/handlers/users/GetUserName.go
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,32 @@ | ||||||
|  | package users | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"strconv" | ||||||
|  | 	db "ttime/internal/database" | ||||||
|  | 
 | ||||||
|  | 	"github.com/gofiber/fiber/v2" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | // Return the username of a user given their user id | ||||||
|  | func GetUserName(c *fiber.Ctx) error { | ||||||
|  | 	// Check the query params for userId | ||||||
|  | 	user_id_string := c.Query("userId") | ||||||
|  | 	if user_id_string == "" { | ||||||
|  | 		return c.Status(400).SendString("Missing user id") | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// Convert to int | ||||||
|  | 	user_id, err := strconv.Atoi(user_id_string) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return c.Status(400).SendString("Invalid user id") | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// Get the username from the database | ||||||
|  | 	username, err := db.GetDb(c).GetUserName(user_id) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return c.Status(500).SendString(err.Error()) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// Send the nuclear launch codes to north korea | ||||||
|  | 	return c.JSON(fiber.Map{"username": username}) | ||||||
|  | } | ||||||
|  | @ -103,6 +103,7 @@ func main() { | ||||||
| 	// userGroup := api.Group("/user") // Not currently in use | 	// userGroup := api.Group("/user") // Not currently in use | ||||||
| 	api.Get("/users/all", users.ListAllUsers) | 	api.Get("/users/all", users.ListAllUsers) | ||||||
| 	api.Get("/project/getAllUsers", users.GetAllUsersProject) | 	api.Get("/project/getAllUsers", users.GetAllUsersProject) | ||||||
|  | 	api.Get("/username", users.GetUserName) | ||||||
| 	api.Post("/login", users.Login) | 	api.Post("/login", users.Login) | ||||||
| 	api.Post("/register", users.Register) | 	api.Post("/register", users.Register) | ||||||
| 	api.Post("/loginrenew", users.LoginRenew) | 	api.Post("/loginrenew", users.LoginRenew) | ||||||
|  | @ -121,6 +122,7 @@ func main() { | ||||||
| 	api.Post("/ProjectRoleChange", projects.ProjectRoleChange) | 	api.Post("/ProjectRoleChange", projects.ProjectRoleChange) | ||||||
| 	api.Put("/promoteToPm/:projectName", projects.PromoteToPm) | 	api.Put("/promoteToPm/:projectName", projects.PromoteToPm) | ||||||
| 	api.Put("/addUserToProject/:projectName", projects.AddUserToProjectHandler) | 	api.Put("/addUserToProject/:projectName", projects.AddUserToProjectHandler) | ||||||
|  | 	api.Delete("/removeUserFromProject/:projectName", projects.RemoveUserFromProject) | ||||||
| 	api.Delete("/removeProject/:projectName", projects.RemoveProject) | 	api.Delete("/removeProject/:projectName", projects.RemoveProject) | ||||||
| 	api.Delete("/project/:projectID", projects.DeleteProject) | 	api.Delete("/project/:projectID", projects.DeleteProject) | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -1,4 +1,4 @@ | ||||||
| import { NewProjMember } from "../Components/AddMember"; | import { AddMemberInfo } from "../Components/AddMember"; | ||||||
| import { ProjectRoleChange } from "../Components/ChangeRole"; | import { ProjectRoleChange } from "../Components/ChangeRole"; | ||||||
| import { projectTimes } from "../Components/GetProjectTimes"; | import { projectTimes } from "../Components/GetProjectTimes"; | ||||||
| import { ProjectMember } from "../Components/GetUsersInProject"; | import { ProjectMember } from "../Components/GetUsersInProject"; | ||||||
|  | @ -197,7 +197,13 @@ interface API { | ||||||
|   ): Promise<APIResponse<void>>; |   ): Promise<APIResponse<void>>; | ||||||
| 
 | 
 | ||||||
|   addUserToProject( |   addUserToProject( | ||||||
|     user: NewProjMember, |     addMemberInfo: AddMemberInfo, | ||||||
|  |     token: string, | ||||||
|  |   ): Promise<APIResponse<void>>; | ||||||
|  | 
 | ||||||
|  |   removeUserFromProject( | ||||||
|  |     user: string, | ||||||
|  |     project: string, | ||||||
|     token: string, |     token: string, | ||||||
|   ): Promise<APIResponse<void>>; |   ): Promise<APIResponse<void>>; | ||||||
| 
 | 
 | ||||||
|  | @ -227,6 +233,12 @@ interface API { | ||||||
|     projectName: string, |     projectName: string, | ||||||
|     token: string, |     token: string, | ||||||
|   ): Promise<APIResponse<string>>; |   ): Promise<APIResponse<string>>; | ||||||
|  |   /** | ||||||
|  |    * Get the username from the id | ||||||
|  |    * @param {number} id The id of the user | ||||||
|  |    * @param {string} token Your token | ||||||
|  |    */ | ||||||
|  |   getUsername(id: number, token: string): Promise<APIResponse<string>>; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| /** An instance of the API */ | /** An instance of the API */ | ||||||
|  | @ -336,18 +348,20 @@ export const api: API = { | ||||||
|   }, |   }, | ||||||
| 
 | 
 | ||||||
|   async addUserToProject( |   async addUserToProject( | ||||||
|     user: NewProjMember, |     addMemberInfo: AddMemberInfo, | ||||||
|     token: string, |     token: string, | ||||||
|   ): Promise<APIResponse<void>> { |   ): Promise<APIResponse<void>> { | ||||||
|     try { |     try { | ||||||
|       const response = await fetch("/api/addUserToProject", { |       const response = await fetch( | ||||||
|  |         `/api/addUserToProject/${addMemberInfo.projectName}/?userName=${addMemberInfo.userName}`, | ||||||
|  |         { | ||||||
|           method: "PUT", |           method: "PUT", | ||||||
|           headers: { |           headers: { | ||||||
|             "Content-Type": "application/json", |             "Content-Type": "application/json", | ||||||
|             Authorization: "Bearer " + token, |             Authorization: "Bearer " + token, | ||||||
|           }, |           }, | ||||||
|         body: JSON.stringify(user), |         }, | ||||||
|       }); |       ); | ||||||
| 
 | 
 | ||||||
|       if (!response.ok) { |       if (!response.ok) { | ||||||
|         return { success: false, message: "Failed to add member" }; |         return { success: false, message: "Failed to add member" }; | ||||||
|  | @ -359,6 +373,31 @@ export const api: API = { | ||||||
|     } |     } | ||||||
|   }, |   }, | ||||||
| 
 | 
 | ||||||
|  |   async removeUserFromProject( | ||||||
|  |     user: string, | ||||||
|  |     project: string, | ||||||
|  |     token: string, | ||||||
|  |   ): Promise<APIResponse<void>> { | ||||||
|  |     try { | ||||||
|  |       const response = await fetch( | ||||||
|  |         `/api/removeUserFromProject/${project}?userName=${user}`, | ||||||
|  |         { | ||||||
|  |           method: "DELETE", | ||||||
|  |           headers: { | ||||||
|  |             "Content-Type": "application/json", | ||||||
|  |             Authorization: "Bearer " + token, | ||||||
|  |           }, | ||||||
|  |         }, | ||||||
|  |       ); | ||||||
|  |       if (!response.ok) { | ||||||
|  |         return { success: false, message: "Failed to remove member" }; | ||||||
|  |       } | ||||||
|  |     } catch (e) { | ||||||
|  |       return { success: false, message: "Failed to remove member" }; | ||||||
|  |     } | ||||||
|  |     return { success: true, message: "Removed member" }; | ||||||
|  |   }, | ||||||
|  | 
 | ||||||
|   async renewToken(token: string): Promise<APIResponse<string>> { |   async renewToken(token: string): Promise<APIResponse<string>> { | ||||||
|     try { |     try { | ||||||
|       const response = await fetch("/api/loginrenew", { |       const response = await fetch("/api/loginrenew", { | ||||||
|  | @ -837,4 +876,25 @@ export const api: API = { | ||||||
|     } |     } | ||||||
|     return { success: true, message: "User promoted to project manager" }; |     return { success: true, message: "User promoted to project manager" }; | ||||||
|   }, |   }, | ||||||
|  | 
 | ||||||
|  |   async getUsername(id: number, token: string): Promise<APIResponse<string>> { | ||||||
|  |     try { | ||||||
|  |       const response = await fetch(`/api/username?userId=${id}`, { | ||||||
|  |         method: "GET", | ||||||
|  |         headers: { | ||||||
|  |           "Content-Type": "application/json", | ||||||
|  |           Authorization: "Bearer " + token, | ||||||
|  |         }, | ||||||
|  |       }); | ||||||
|  | 
 | ||||||
|  |       if (!response.ok) { | ||||||
|  |         return { success: false, message: "Failed to get username" }; | ||||||
|  |       } else { | ||||||
|  |         const data = (await response.json()) as string; | ||||||
|  |         return { success: true, data }; | ||||||
|  |       } | ||||||
|  |     } catch (e) { | ||||||
|  |       return { success: false, message: "Failed to get username" }; | ||||||
|  |     } | ||||||
|  |   }, | ||||||
| }; | }; | ||||||
|  |  | ||||||
|  | @ -1,44 +1,35 @@ | ||||||
| import { APIResponse, api } from "../API/API"; | import { api } from "../API/API"; | ||||||
| 
 | 
 | ||||||
| export interface NewProjMember { | export interface AddMemberInfo { | ||||||
|   username: string; |   userName: string; | ||||||
|   role: string; |   projectName: string; | ||||||
|   projectname: string; |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
|  * Tries to add a member to a project |  * Tries to add a member to a project | ||||||
|  * @param {Object} props - A NewProjMember |  * @param {AddMemberInfo} props.membertoAdd - Contains user's name and project's name | ||||||
|  * @returns {boolean} True if added, false if not |  * @returns {Promise<void>} | ||||||
|  */ |  */ | ||||||
| function AddMember(props: { memberToAdd: NewProjMember }): boolean { | async function AddMember(props: { memberToAdd: AddMemberInfo }): Promise<void> { | ||||||
|   let added = false; |   if (props.memberToAdd.userName === "") { | ||||||
|   if ( |     alert("You must choose at least one user to add"); | ||||||
|     props.memberToAdd.username === "" || |     return; | ||||||
|     props.memberToAdd.role === "" || |  | ||||||
|     props.memberToAdd.projectname === "" |  | ||||||
|   ) { |  | ||||||
|     alert("All fields must be filled before adding"); |  | ||||||
|     return added; |  | ||||||
|   } |   } | ||||||
|   api |   try { | ||||||
|     .addUserToProject( |     const response = await api.addUserToProject( | ||||||
|       props.memberToAdd, |       props.memberToAdd, | ||||||
|       localStorage.getItem("accessToken") ?? "", |       localStorage.getItem("accessToken") ?? "", | ||||||
|     ) |     ); | ||||||
|     .then((response: APIResponse<void>) => { |  | ||||||
|     if (response.success) { |     if (response.success) { | ||||||
|         alert("Member added"); |       alert(`[${props.memberToAdd.userName}] added`); | ||||||
|         added = true; |  | ||||||
|     } else { |     } else { | ||||||
|         alert("Member not added"); |       alert(`[${props.memberToAdd.userName}] not added`); | ||||||
|       console.error(response.message); |       console.error(response.message); | ||||||
|     } |     } | ||||||
|     }) |   } catch (error) { | ||||||
|     .catch((error) => { |     alert(`[${props.memberToAdd.userName}] not added`); | ||||||
|     console.error("An error occurred during member add:", error); |     console.error("An error occurred during member add:", error); | ||||||
|     }); |   } | ||||||
|   return added; |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export default AddMember; | export default AddMember; | ||||||
|  |  | ||||||
|  | @ -1,37 +1,10 @@ | ||||||
| import { useState } from "react"; | import { useState } from "react"; | ||||||
| import { APIResponse, api } from "../API/API"; | import { api } from "../API/API"; | ||||||
| import { NewProject } from "../Types/goTypes"; | import { NewProject } from "../Types/goTypes"; | ||||||
| import InputField from "./InputField"; | import InputField from "./InputField"; | ||||||
| import Logo from "../assets/Logo.svg"; | import Logo from "../assets/Logo.svg"; | ||||||
| import Button from "./Button"; | import Button from "./Button"; | ||||||
| 
 | 
 | ||||||
| /** |  | ||||||
|  * Tries to add a project to the system |  | ||||||
|  * @param {Object} props - Project name and description |  | ||||||
|  * @returns {boolean} True if created, false if not |  | ||||||
|  */ |  | ||||||
| function CreateProject(props: { name: string; description: string }): void { |  | ||||||
|   const project: NewProject = { |  | ||||||
|     name: props.name, |  | ||||||
|     description: props.description, |  | ||||||
|   }; |  | ||||||
| 
 |  | ||||||
|   api |  | ||||||
|     .createProject(project, localStorage.getItem("accessToken") ?? "") |  | ||||||
|     .then((response: APIResponse<void>) => { |  | ||||||
|       if (response.success) { |  | ||||||
|         alert("Project added!"); |  | ||||||
|       } else { |  | ||||||
|         alert("Project NOT added!"); |  | ||||||
|         console.error(response.message); |  | ||||||
|       } |  | ||||||
|     }) |  | ||||||
|     .catch((error) => { |  | ||||||
|       alert("Project NOT added!"); |  | ||||||
|       console.error("An error occurred during creation:", error); |  | ||||||
|     }); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| /** | /** | ||||||
|  * Provides UI for adding a project to the system. |  * Provides UI for adding a project to the system. | ||||||
|  * @returns {JSX.Element} - Returns the component UI for adding a project |  * @returns {JSX.Element} - Returns the component UI for adding a project | ||||||
|  | @ -40,6 +13,33 @@ function AddProject(): JSX.Element { | ||||||
|   const [name, setName] = useState(""); |   const [name, setName] = useState(""); | ||||||
|   const [description, setDescription] = useState(""); |   const [description, setDescription] = useState(""); | ||||||
| 
 | 
 | ||||||
|  |   /** | ||||||
|  |    * Tries to add a project to the system | ||||||
|  |    */ | ||||||
|  |   const handleCreateProject = async (): Promise<void> => { | ||||||
|  |     const project: NewProject = { | ||||||
|  |       name: name.replace(/ /g, ""), | ||||||
|  |       description: description.trim(), | ||||||
|  |     }; | ||||||
|  |     try { | ||||||
|  |       const response = await api.createProject( | ||||||
|  |         project, | ||||||
|  |         localStorage.getItem("accessToken") ?? "", | ||||||
|  |       ); | ||||||
|  |       if (response.success) { | ||||||
|  |         alert(`${project.name} added!`); | ||||||
|  |         setDescription(""); | ||||||
|  |         setName(""); | ||||||
|  |       } else { | ||||||
|  |         alert("Project not added, name could be taken"); | ||||||
|  |         console.error(response.message); | ||||||
|  |       } | ||||||
|  |     } catch (error) { | ||||||
|  |       alert("Project not added"); | ||||||
|  |       console.error(error); | ||||||
|  |     } | ||||||
|  |   }; | ||||||
|  | 
 | ||||||
|   return ( |   return ( | ||||||
|     <div className="flex flex-col h-fit w-screen items-center justify-center"> |     <div className="flex flex-col h-fit w-screen items-center justify-center"> | ||||||
|       <div className="border-4 border-black bg-white flex flex-col items-center justify-center h-fit w-fit rounded-3xl content-center pl-20 pr-20"> |       <div className="border-4 border-black bg-white flex flex-col items-center justify-center h-fit w-fit rounded-3xl content-center pl-20 pr-20"> | ||||||
|  | @ -47,10 +47,7 @@ function AddProject(): JSX.Element { | ||||||
|           className="bg-white rounded px-8 pt-6 pb-8 mb-4 items-center justify-center flex flex-col w-fit h-fit" |           className="bg-white rounded px-8 pt-6 pb-8 mb-4 items-center justify-center flex flex-col w-fit h-fit" | ||||||
|           onSubmit={(e) => { |           onSubmit={(e) => { | ||||||
|             e.preventDefault(); |             e.preventDefault(); | ||||||
|             CreateProject({ |             void handleCreateProject(); | ||||||
|               name: name, |  | ||||||
|               description: description, |  | ||||||
|             }); |  | ||||||
|           }} |           }} | ||||||
|         > |         > | ||||||
|           <img |           <img | ||||||
|  |  | ||||||
|  | @ -1,71 +1,86 @@ | ||||||
| import { useState } from "react"; | import { useEffect, useState } from "react"; | ||||||
| import Button from "./Button"; | import Button from "./Button"; | ||||||
| import GetAllUsers from "./GetAllUsers"; | import AddMember, { AddMemberInfo } from "./AddMember"; | ||||||
| import AddMember, { NewProjMember } from "./AddMember"; |  | ||||||
| import BackButton from "./BackButton"; | import BackButton from "./BackButton"; | ||||||
|  | import GetUsersInProject, { ProjectMember } from "./GetUsersInProject"; | ||||||
|  | import GetAllUsers from "./GetAllUsers"; | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
|  * Provides UI for adding a member to a project. |  * Provides UI for adding a member to a project. | ||||||
|  * @returns {JSX.Element} - Returns the component UI for adding a member |  * @returns {JSX.Element} - Returns the component UI for adding a member | ||||||
|  */ |  */ | ||||||
| function AddUserToProject(props: { projectName: string }): JSX.Element { | function AddUserToProject(props: { projectName: string }): JSX.Element { | ||||||
|   const [name, setName] = useState(""); |   const [names, setNames] = useState<string[]>([]); | ||||||
|   const [users, setUsers] = useState<string[]>([]); |   const [users, setUsers] = useState<string[]>([]); | ||||||
|   const [role, setRole] = useState(""); |   const [usersProj, setUsersProj] = useState<ProjectMember[]>([]); | ||||||
|   GetAllUsers({ setUsersProp: setUsers }); |  | ||||||
| 
 | 
 | ||||||
|   const handleClick = (): boolean => { |   // Gets all users and project members for filtering
 | ||||||
|     const newMember: NewProjMember = { |   GetAllUsers({ setUsersProp: setUsers }); | ||||||
|       username: name, |   GetUsersInProject({ | ||||||
|       projectname: props.projectName, |     setUsersProp: setUsersProj, | ||||||
|       role: role, |     projectName: props.projectName, | ||||||
|  |   }); | ||||||
|  |   /* | ||||||
|  |    * Filters the members from users so that users who are already | ||||||
|  |    * members are not shown | ||||||
|  |    */ | ||||||
|  |   useEffect(() => { | ||||||
|  |     setUsers((prevUsers) => { | ||||||
|  |       const filteredUsers = prevUsers.filter( | ||||||
|  |         (user) => | ||||||
|  |           !usersProj.some((projectUser) => projectUser.Username === user), | ||||||
|  |       ); | ||||||
|  |       return filteredUsers; | ||||||
|  |     }); | ||||||
|  |   }, [usersProj]); | ||||||
|  | 
 | ||||||
|  |   // Attempts to add all of the selected users to the project
 | ||||||
|  |   const handleAddClick = async (): Promise<void> => { | ||||||
|  |     if (names.length === 0) | ||||||
|  |       alert("You have to choose at least one user to add"); | ||||||
|  |     for (const name of names) { | ||||||
|  |       const newMember: AddMemberInfo = { | ||||||
|  |         userName: name, | ||||||
|  |         projectName: props.projectName, | ||||||
|       }; |       }; | ||||||
|     return AddMember({ memberToAdd: newMember }); |       await AddMember({ memberToAdd: newMember }); | ||||||
|  |     } | ||||||
|  |     setNames([]); | ||||||
|  |     location.reload(); | ||||||
|  |   }; | ||||||
|  | 
 | ||||||
|  |   // Updates the names that have been selected
 | ||||||
|  |   const handleUserClick = (user: string): void => { | ||||||
|  |     setNames((prevNames): string[] => { | ||||||
|  |       if (!prevNames.includes(user)) { | ||||||
|  |         return [...prevNames, user]; | ||||||
|  |       } | ||||||
|  |       return prevNames.filter((name) => name !== user); | ||||||
|  |     }); | ||||||
|   }; |   }; | ||||||
| 
 | 
 | ||||||
|   return ( |   return ( | ||||||
|     <div className="border-4 border-black bg-white flex flex-col items-center justify-center rounded-3xl content-center pl-20 pr-20  h-[75vh] w-[50vh]"> |     <div className="border-4 border-black bg-white flex flex-col items-center pt-10 rounded-3xl content-center pl-20 pr-20  h-[63vh] w-[50] overflow-auto"> | ||||||
|       <p className="pb-4 mb-2 text-center font-bold text-[18px]"> |       <h1 className="text-center font-bold text-[36px] pb-10"> | ||||||
|         User chosen: [{name}] |         {props.projectName} | ||||||
|  |       </h1> | ||||||
|  |       <p className="p-1 text-center font-bold text-[26px]"> | ||||||
|  |         Choose users to add: | ||||||
|       </p> |       </p> | ||||||
|       <p className="pb-4 mb-2 text-center font-bold text-[18px]"> |       <div className="border-2 border-black pl-2 pr-2 pb-2 rounded-xl text-center overflow-auto h-[26vh] w-[26vh]"> | ||||||
|         Role chosen: [{role}] |  | ||||||
|       </p> |  | ||||||
|       <p className="pb-4 mb-2 text-center font-bold text-[18px]"> |  | ||||||
|         Project chosen: [{props.projectName}] |  | ||||||
|       </p> |  | ||||||
|       <p className="p-1">Choose role:</p> |  | ||||||
|       <div className="border-2 border-black p-2 rounded-xl text-center h-[10h] w-[16] overflow-auto"> |  | ||||||
|         <ul className="text-center items-center font-medium space-y-2"> |  | ||||||
|           <li |  | ||||||
|             className="h-[10] w-[14] items-start px-2 py-1 border-2 border-black rounded-full bg-orange-200 hover:bg-orange-600 hover:text-slate-100 hover:cursor-pointer" |  | ||||||
|             onClick={() => { |  | ||||||
|               setRole("member"); |  | ||||||
|             }} |  | ||||||
|           > |  | ||||||
|             {"Member"} |  | ||||||
|           </li> |  | ||||||
|           <li |  | ||||||
|             className="h-[10] w-[14] items-start px-2 py-1 border-2 border-black rounded-full bg-orange-200 hover:bg-orange-600 hover:text-slate-100 hover:cursor-pointer" |  | ||||||
|             onClick={() => { |  | ||||||
|               setRole("project_manager"); |  | ||||||
|             }} |  | ||||||
|           > |  | ||||||
|             {"Project manager"} |  | ||||||
|           </li> |  | ||||||
|         </ul> |  | ||||||
|       </div> |  | ||||||
|       <p className="p-1">Choose user:</p> |  | ||||||
|       <div className="border-2 border-black p-2 rounded-xl text-center overflow-scroll h-[26vh] w-[26vh]"> |  | ||||||
|         <ul className="text-center font-medium space-y-2"> |         <ul className="text-center font-medium space-y-2"> | ||||||
|           <div></div> |           <div></div> | ||||||
|           {users.map((user) => ( |           {users.map((user) => ( | ||||||
|             <li |             <li | ||||||
|               className="items-start p-1 border-2 border-black rounded-full bg-orange-200 hover:bg-orange-600 hover:text-slate-100 hover:cursor-pointer" |               className={ | ||||||
|  |                 names.includes(user) | ||||||
|  |                   ? "items-start p-1 border-2 border-transparent rounded-full bg-orange-500 hover:bg-orange-600 text-white hover:cursor-pointer ring-2 ring-black" | ||||||
|  |                   : "items-start p-1 border-2 border-black rounded-full bg-orange-200 hover:bg-orange-400 hover:text-slate-100 hover:cursor-pointer" | ||||||
|  |               } | ||||||
|               key={user} |               key={user} | ||||||
|               value={user} |               value={user} | ||||||
|               onClick={() => { |               onClick={() => { | ||||||
|                 setName(user); |                 handleUserClick(user); | ||||||
|               }} |               }} | ||||||
|             > |             > | ||||||
|               <span>{user}</span> |               <span>{user}</span> | ||||||
|  | @ -73,13 +88,16 @@ function AddUserToProject(props: { projectName: string }): JSX.Element { | ||||||
|           ))} |           ))} | ||||||
|         </ul> |         </ul> | ||||||
|       </div> |       </div> | ||||||
|       <div className="flex space-x-5 items-center justify-between"> |       <p className="pt-10 pb-5 underline text-center font-bold text-[18px]"> | ||||||
|  |         Number of users to be added: {names.length} | ||||||
|  |       </p> | ||||||
|  |       <div className="space-x-10 items-center"> | ||||||
|         <Button |         <Button | ||||||
|           text="Add" |           text="Add" | ||||||
|           onClick={(): void => { |           onClick={(): void => { | ||||||
|             handleClick(); |             void handleAddClick(); | ||||||
|           }} |           }} | ||||||
|           type="submit" |           type="button" | ||||||
|         /> |         /> | ||||||
|         <BackButton /> |         <BackButton /> | ||||||
|       </div> |       </div> | ||||||
|  |  | ||||||
|  | @ -2,7 +2,7 @@ import { useState } from "react"; | ||||||
| import Button from "./Button"; | import Button from "./Button"; | ||||||
| import ChangeRole, { ProjectRoleChange } from "./ChangeRole"; | import ChangeRole, { ProjectRoleChange } from "./ChangeRole"; | ||||||
| 
 | 
 | ||||||
| export default function ChangeRoles(props: { | export default function ChangeRoleView(props: { | ||||||
|   projectName: string; |   projectName: string; | ||||||
|   username: string; |   username: string; | ||||||
| }): JSX.Element { | }): JSX.Element { | ||||||
|  |  | ||||||
|  | @ -2,8 +2,11 @@ import { APIResponse, api } from "../API/API"; | ||||||
| import { StrNameChange } from "../Types/goTypes"; | import { StrNameChange } from "../Types/goTypes"; | ||||||
| 
 | 
 | ||||||
| function ChangeUsername(props: { nameChange: StrNameChange }): void { | function ChangeUsername(props: { nameChange: StrNameChange }): void { | ||||||
|   if (props.nameChange.newName === "") { |   if ( | ||||||
|     alert("You have to select a new name"); |     props.nameChange.newName === "" || | ||||||
|  |     props.nameChange.newName === props.nameChange.prevName | ||||||
|  |   ) { | ||||||
|  |     alert("You have to give a new name\n\nName not changed"); | ||||||
|     return; |     return; | ||||||
|   } |   } | ||||||
|   api |   api | ||||||
|  | @ -13,7 +16,7 @@ function ChangeUsername(props: { nameChange: StrNameChange }): void { | ||||||
|         alert("Name changed successfully"); |         alert("Name changed successfully"); | ||||||
|         location.reload(); |         location.reload(); | ||||||
|       } else { |       } else { | ||||||
|         alert("Name not changed"); |         alert("Name not changed, name could be taken"); | ||||||
|         console.error(response.message); |         console.error(response.message); | ||||||
|       } |       } | ||||||
|     }) |     }) | ||||||
|  |  | ||||||
|  | @ -1,8 +1,8 @@ | ||||||
| import Button from "./Button"; | import Button from "./Button"; | ||||||
| import DeleteUser from "./DeleteUser"; |  | ||||||
| import UserProjectListAdmin from "./UserProjectListAdmin"; | import UserProjectListAdmin from "./UserProjectListAdmin"; | ||||||
| import { useState } from "react"; | import { useState } from "react"; | ||||||
| import ChangeRoleView from "./ChangeRoleView"; | import ChangeRoleView from "./ChangeRoleView"; | ||||||
|  | import RemoveUserFromProj from "./RemoveUserFromProj"; | ||||||
| 
 | 
 | ||||||
| function MemberInfoModal(props: { | function MemberInfoModal(props: { | ||||||
|   projectName: string; |   projectName: string; | ||||||
|  | @ -20,7 +20,7 @@ function MemberInfoModal(props: { | ||||||
|   }; |   }; | ||||||
|   return ( |   return ( | ||||||
|     <div |     <div | ||||||
|       className="fixed inset-0 bg-black bg-opacity-30 backdrop-blur-sm  |       className="fixed inset-10 bg-opacity-30 backdrop-blur-sm  | ||||||
|       flex justify-center items-center" |       flex justify-center items-center" | ||||||
|     > |     > | ||||||
|       <div className="border-4 border-black bg-white rounded-lg text-center flex flex-col"> |       <div className="border-4 border-black bg-white rounded-lg text-center flex flex-col"> | ||||||
|  | @ -42,13 +42,16 @@ function MemberInfoModal(props: { | ||||||
|           <UserProjectListAdmin username={props.username} /> |           <UserProjectListAdmin username={props.username} /> | ||||||
|           <div className="items-center space-x-6"> |           <div className="items-center space-x-6"> | ||||||
|             <Button |             <Button | ||||||
|               text={"Delete"} |               text={"Remove"} | ||||||
|               onClick={function (): void { |               onClick={function (): void { | ||||||
|                 if ( |                 if ( | ||||||
|                   window.confirm("Are you sure you want to delete this user?") |                   window.confirm( | ||||||
|  |                     "Are you sure you want to remove this user from the project?", | ||||||
|  |                   ) | ||||||
|                 ) { |                 ) { | ||||||
|                   DeleteUser({ |                   RemoveUserFromProj({ | ||||||
|                     usernameToDelete: props.username, |                     userToRemove: props.username, | ||||||
|  |                     projectName: props.projectName, | ||||||
|                   }); |                   }); | ||||||
|                 } |                 } | ||||||
|               }} |               }} | ||||||
|  |  | ||||||
|  | @ -15,17 +15,21 @@ export default function Register(): JSX.Element { | ||||||
|   const [errMessage, setErrMessage] = useState<string>(); |   const [errMessage, setErrMessage] = useState<string>(); | ||||||
| 
 | 
 | ||||||
|   const handleRegister = async (): Promise<void> => { |   const handleRegister = async (): Promise<void> => { | ||||||
|  |     if (username === "" || password === "") { | ||||||
|  |       alert("Must provide username and password"); | ||||||
|  |       return; | ||||||
|  |     } | ||||||
|     const newUser: NewUser = { |     const newUser: NewUser = { | ||||||
|       username: username ?? "", |       username: username?.replace(/ /g, "") ?? "", | ||||||
|       password: password ?? "", |       password: password ?? "", | ||||||
|     }; |     }; | ||||||
|     const response = await api.registerUser(newUser); |     const response = await api.registerUser(newUser); | ||||||
|     if (response.success) { |     if (response.success) { | ||||||
|       alert("User added!"); |       alert(`${newUser.username} added!`); | ||||||
|       setPassword(""); |       setPassword(""); | ||||||
|       setUsername(""); |       setUsername(""); | ||||||
|     } else { |     } else { | ||||||
|       alert("User not added"); |       alert("User not added, name could be taken"); | ||||||
|       setErrMessage(response.message ?? "Unknown error"); |       setErrMessage(response.message ?? "Unknown error"); | ||||||
|       console.error(errMessage); |       console.error(errMessage); | ||||||
|     } |     } | ||||||
|  |  | ||||||
							
								
								
									
										41
									
								
								frontend/src/Components/RemoveUserFromProj.tsx
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										41
									
								
								frontend/src/Components/RemoveUserFromProj.tsx
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,41 @@ | ||||||
|  | import { api, APIResponse } from "../API/API"; | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Removes a user from a project | ||||||
|  |  * @param {string} props.usernameToDelete - The username of user to remove | ||||||
|  |  * @param {string} props.projectName - Project to remove user from | ||||||
|  |  * @returns {void} | ||||||
|  |  * @example | ||||||
|  |  * const exampleUsername = "user"; | ||||||
|  |  * const exampleProjectName "project"; | ||||||
|  |  * RemoveUserFromProj({ userToRemove: exampleUsername, projectName: exampleProjectName }); | ||||||
|  |  */ | ||||||
|  | 
 | ||||||
|  | export default function RemoveUserFromProj(props: { | ||||||
|  |   userToRemove: string; | ||||||
|  |   projectName: string; | ||||||
|  | }): void { | ||||||
|  |   if (props.userToRemove === localStorage.getItem("username")) { | ||||||
|  |     alert("Cannot remove yourself"); | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|  |   api | ||||||
|  |     .removeUserFromProject( | ||||||
|  |       props.userToRemove, | ||||||
|  |       props.projectName, | ||||||
|  |       localStorage.getItem("accessToken") ?? "", | ||||||
|  |     ) | ||||||
|  |     .then((response: APIResponse<void>) => { | ||||||
|  |       if (response.success) { | ||||||
|  |         alert(`${props.userToRemove} has been removed!`); | ||||||
|  |         location.reload(); | ||||||
|  |       } else { | ||||||
|  |         alert(`${props.userToRemove} has not been removed due to an error`); | ||||||
|  |         console.error(response.message); | ||||||
|  |       } | ||||||
|  |     }) | ||||||
|  |     .catch((error) => { | ||||||
|  |       alert(`${props.userToRemove} has not been removed due to an error`); | ||||||
|  |       console.error("An error occurred during deletion:", error); | ||||||
|  |     }); | ||||||
|  | } | ||||||
|  | @ -28,7 +28,7 @@ function UserInfoModal(props: { | ||||||
|   const handleClickChangeName = (): void => { |   const handleClickChangeName = (): void => { | ||||||
|     const nameChange: StrNameChange = { |     const nameChange: StrNameChange = { | ||||||
|       prevName: props.username, |       prevName: props.username, | ||||||
|       newName: newUsername, |       newName: newUsername.replace(/ /g, ""), | ||||||
|     }; |     }; | ||||||
|     ChangeUsername({ nameChange: nameChange }); |     ChangeUsername({ nameChange: nameChange }); | ||||||
|   }; |   }; | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Davenludd
						Davenludd