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…
Reference in a new issue