Compare commits
36 commits
12810075f9
...
7635791f09
Author | SHA1 | Date | |
---|---|---|---|
![]() |
7635791f09 | ||
![]() |
68e6f94d13 | ||
![]() |
91bccba871 | ||
![]() |
a6f3fc4a1c | ||
![]() |
544383809b | ||
![]() |
abfb79b991 | ||
![]() |
db6fdf3c29 | ||
![]() |
5bc7aadfa3 | ||
![]() |
2d8d200340 | ||
![]() |
0cb6af03e4 | ||
![]() |
f75e6e4a6e | ||
![]() |
10d06767c4 | ||
![]() |
ee49bbde69 | ||
![]() |
266aaa482c | ||
![]() |
194e1d52a8 | ||
![]() |
82e72432f6 | ||
![]() |
620073f688 | ||
![]() |
7313a27b31 | ||
![]() |
d7789ab844 | ||
![]() |
4319f30799 | ||
![]() |
2ca8c60418 | ||
![]() |
0a951ecd2b | ||
![]() |
13eb6597a7 | ||
![]() |
9a0f855d2b | ||
![]() |
7c995573ef | ||
![]() |
a5ea74c996 | ||
![]() |
a0ff329845 | ||
![]() |
e67c54540c | ||
![]() |
91671b2b63 | ||
![]() |
76ae587116 | ||
![]() |
66a064c9f8 | ||
![]() |
be963b2281 | ||
![]() |
c0d36f8df1 | ||
![]() |
f9c30c4951 | ||
![]() |
520e2f74f0 | ||
![]() |
8cc0458e1b |
23 changed files with 469 additions and 175 deletions
|
@ -108,6 +108,56 @@ const docTemplate = `{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"/promote/{projectName}": {
|
||||||
|
"put": {
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"JWT": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Promote a user to project manager",
|
||||||
|
"consumes": [
|
||||||
|
"text/plain"
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"text/plain"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"Auth"
|
||||||
|
],
|
||||||
|
"summary": "Promote to project manager",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "Project name",
|
||||||
|
"name": "projectName",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "User name",
|
||||||
|
"name": "userName",
|
||||||
|
"in": "query",
|
||||||
|
"required": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"403": {
|
||||||
|
"description": "Forbidden",
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"500": {
|
||||||
|
"description": "Internal server error",
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"/promoteToAdmin": {
|
"/promoteToAdmin": {
|
||||||
"post": {
|
"post": {
|
||||||
"security": [
|
"security": [
|
||||||
|
|
|
@ -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
|
||||||
|
@ -339,9 +350,14 @@ func (d *Db) SignWeeklyReport(reportId int, projectManagerId int) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
managerQuery := `SELECT project_id FROM user_roles
|
||||||
|
WHERE user_id = ?
|
||||||
|
AND project_id = (SELECT project_id FROM weekly_reports WHERE report_id = ?)
|
||||||
|
AND p_role = 'project_manager'`
|
||||||
|
|
||||||
// Retrieve the project ID associated with the project manager
|
// Retrieve the project ID associated with the project manager
|
||||||
var managerProjectID int
|
var managerProjectID int
|
||||||
err = d.Get(&managerProjectID, "SELECT project_id FROM user_roles WHERE user_id = ? AND p_role = 'project_manager'", projectManagerId)
|
err = d.Get(&managerProjectID, managerQuery, projectManagerId, reportId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -601,3 +617,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", {
|
||||||
|
@ -538,7 +577,7 @@ export const api: API = {
|
||||||
): Promise<APIResponse<WeeklyReport>> {
|
): Promise<APIResponse<WeeklyReport>> {
|
||||||
try {
|
try {
|
||||||
const response = await fetch(
|
const response = await fetch(
|
||||||
`/api/getWeeklyReport?projectName=${projectName}&week=${week}&targetUser=${targetUser}`,
|
`/api/getWeeklyReport?projectName=${projectName}&week=${week}&targetUser=${targetUser ?? ""}`,
|
||||||
{
|
{
|
||||||
method: "GET",
|
method: "GET",
|
||||||
headers: {
|
headers: {
|
||||||
|
@ -566,7 +605,7 @@ export const api: API = {
|
||||||
): Promise<APIResponse<WeeklyReport[]>> {
|
): Promise<APIResponse<WeeklyReport[]>> {
|
||||||
try {
|
try {
|
||||||
const response = await fetch(
|
const response = await fetch(
|
||||||
`/api/getAllWeeklyReports/${projectName}?targetUser=${targetUser}`,
|
`/api/getAllWeeklyReports/${projectName}?targetUser=${targetUser ?? ""}`,
|
||||||
{
|
{
|
||||||
method: "GET",
|
method: "GET",
|
||||||
headers: {
|
headers: {
|
||||||
|
@ -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>
|
||||||
|
|
|
@ -17,7 +17,7 @@ function AllTimeReportsInProject(): JSX.Element {
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const getWeeklyReports = async (): Promise<void> => {
|
const getWeeklyReports = async (): Promise<void> => {
|
||||||
const token = localStorage.getItem("accessToken") ?? "";
|
const token = localStorage.getItem("accessToken") ?? "";
|
||||||
const response = await api.getWeeklyReportsForUser(
|
const response = await api.getAllWeeklyReportsForUser(
|
||||||
projectName ?? "",
|
projectName ?? "",
|
||||||
token,
|
token,
|
||||||
);
|
);
|
||||||
|
|
|
@ -17,10 +17,10 @@ function AllTimeReportsInProject(): JSX.Element {
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const getWeeklyReports = async (): Promise<void> => {
|
const getWeeklyReports = async (): Promise<void> => {
|
||||||
const token = localStorage.getItem("accessToken") ?? "";
|
const token = localStorage.getItem("accessToken") ?? "";
|
||||||
const response = await api.getWeeklyReportsForDifferentUser(
|
const response = await api.getAllWeeklyReportsForUser(
|
||||||
projectName ?? "",
|
projectName ?? "",
|
||||||
username ?? "",
|
|
||||||
token,
|
token,
|
||||||
|
username ?? "",
|
||||||
);
|
);
|
||||||
console.log(response);
|
console.log(response);
|
||||||
if (response.success) {
|
if (response.success) {
|
||||||
|
@ -31,7 +31,7 @@ function AllTimeReportsInProject(): JSX.Element {
|
||||||
};
|
};
|
||||||
|
|
||||||
void getWeeklyReports();
|
void getWeeklyReports();
|
||||||
}, []);
|
}, [projectName, username]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
|
@ -3,17 +3,14 @@ import { Link, useParams } from "react-router-dom";
|
||||||
import { api } from "../API/API";
|
import { api } from "../API/API";
|
||||||
import { WeeklyReport } from "../Types/goTypes";
|
import { WeeklyReport } from "../Types/goTypes";
|
||||||
|
|
||||||
/**
|
|
||||||
* Renders a component that displays the projects a user is a part of and links to the projects start-page.
|
|
||||||
* @returns The JSX element representing the component.
|
|
||||||
*/
|
|
||||||
function DisplayUserProject(): JSX.Element {
|
function DisplayUserProject(): JSX.Element {
|
||||||
const { projectName } = useParams();
|
const { projectName } = useParams();
|
||||||
const [unsignedReports, setUnsignedReports] = useState<WeeklyReport[]>([]);
|
const [unsignedReports, setUnsignedReports] = useState<WeeklyReport[]>([]);
|
||||||
//const navigate = useNavigate();
|
const [usernames, setUsernames] = useState<string[]>([]);
|
||||||
|
const token = localStorage.getItem("accessToken") ?? "";
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const getUnsignedReports = async (): Promise<void> => {
|
const getUnsignedReports = async (): Promise<void> => {
|
||||||
const token = localStorage.getItem("accessToken") ?? "";
|
|
||||||
const response = await api.getUnsignedReportsInProject(
|
const response = await api.getUnsignedReportsInProject(
|
||||||
projectName ?? "",
|
projectName ?? "",
|
||||||
token,
|
token,
|
||||||
|
@ -21,13 +18,21 @@ function DisplayUserProject(): JSX.Element {
|
||||||
console.log(response);
|
console.log(response);
|
||||||
if (response.success) {
|
if (response.success) {
|
||||||
setUnsignedReports(response.data ?? []);
|
setUnsignedReports(response.data ?? []);
|
||||||
|
const usernamesPromises = (response.data ?? []).map((report) =>
|
||||||
|
api.getUsername(report.userId, token),
|
||||||
|
);
|
||||||
|
const usernamesResponses = await Promise.all(usernamesPromises);
|
||||||
|
const usernames = usernamesResponses.map(
|
||||||
|
(res) => (res.data as { username?: string }).username ?? "",
|
||||||
|
);
|
||||||
|
setUsernames(usernames);
|
||||||
} else {
|
} else {
|
||||||
console.error(response.message);
|
console.error(response.message);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
void getUnsignedReports();
|
void getUnsignedReports();
|
||||||
}, [projectName]); // Include 'projectName' in the dependency array
|
}, [projectName, token]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
@ -39,8 +44,8 @@ function DisplayUserProject(): JSX.Element {
|
||||||
<h1 key={index} className="border-b-2 border-black w-full">
|
<h1 key={index} className="border-b-2 border-black w-full">
|
||||||
<div className="flex justify-between">
|
<div className="flex justify-between">
|
||||||
<div className="flex">
|
<div className="flex">
|
||||||
<span className="ml-6 mr-2 font-bold">UserID:</span>
|
<span className="ml-6 mr-2 font-bold">Username:</span>
|
||||||
<h1>{unsignedReport.userId}</h1>
|
<h1>{usernames[index]}</h1>{" "}
|
||||||
<span className="ml-6 mr-2 font-bold">Week:</span>
|
<span className="ml-6 mr-2 font-bold">Week:</span>
|
||||||
<h1>{unsignedReport.week}</h1>
|
<h1>{unsignedReport.week}</h1>
|
||||||
<span className="ml-6 mr-2 font-bold">Total Time:</span>
|
<span className="ml-6 mr-2 font-bold">Total Time:</span>
|
||||||
|
@ -58,7 +63,7 @@ function DisplayUserProject(): JSX.Element {
|
||||||
<div className="flex">
|
<div className="flex">
|
||||||
<div className="ml-auto flex space-x-4">
|
<div className="ml-auto flex space-x-4">
|
||||||
<Link
|
<Link
|
||||||
to={`/PMViewUnsignedReport/${projectName}/${unsignedReport.userId}/${unsignedReport.week}`}
|
to={`/PMViewUnsignedReport/${projectName}/${usernames[index]}/${unsignedReport.week}`}
|
||||||
>
|
>
|
||||||
<h1 className="underline cursor-pointer font-bold">
|
<h1 className="underline cursor-pointer font-bold">
|
||||||
View Report
|
View Report
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
//info: Header component to display the header of the page including the logo and user information where thr user can logout
|
//info: Header component to display the header of the page including the logo and user information where thr user can logout
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { Link } from "react-router-dom";
|
import { Link, useNavigate } from "react-router-dom";
|
||||||
import backgroundImage from "../assets/1.jpg";
|
import backgroundImage from "../assets/1.jpg";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -9,23 +9,33 @@ import backgroundImage from "../assets/1.jpg";
|
||||||
*/
|
*/
|
||||||
function Header(): JSX.Element {
|
function Header(): JSX.Element {
|
||||||
const [isOpen, setIsOpen] = useState(false);
|
const [isOpen, setIsOpen] = useState(false);
|
||||||
|
const username = localStorage.getItem("username");
|
||||||
|
const navigate = useNavigate();
|
||||||
|
|
||||||
const handleLogout = (): void => {
|
const handleLogout = (): void => {
|
||||||
localStorage.clear();
|
localStorage.clear();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleNavigation = (): void => {
|
||||||
|
if (username === "admin") {
|
||||||
|
navigate("/admin");
|
||||||
|
} else {
|
||||||
|
navigate("/yourProjects");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<header
|
<header
|
||||||
className="fixed top-0 left-0 right-0 border-[1.75px] border-black text-black p-3 pl-5 flex items-center justify-between bg-cover"
|
className="fixed top-0 left-0 right-0 border-[1.75px] border-black text-black p-3 pl-5 flex items-center justify-between bg-cover"
|
||||||
style={{ backgroundImage: `url(${backgroundImage})` }}
|
style={{ backgroundImage: `url(${backgroundImage})` }}
|
||||||
>
|
>
|
||||||
<Link to="/your-projects">
|
<div onClick={handleNavigation}>
|
||||||
<img
|
<img
|
||||||
src="/src/assets/Logo.svg"
|
src="/src/assets/Logo.svg"
|
||||||
alt="TTIME Logo"
|
alt="TTIME Logo"
|
||||||
className="w-11 h-14 cursor-pointer"
|
className="w-11 h-14 cursor-pointer"
|
||||||
/>
|
/>
|
||||||
</Link>
|
</div>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
className="relative"
|
className="relative"
|
||||||
|
|
|
@ -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,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
|
|
|
@ -62,7 +62,7 @@ export default function NewWeeklyReport(): JSX.Element {
|
||||||
const success = await handleNewWeeklyReport();
|
const success = await handleNewWeeklyReport();
|
||||||
if (!success) {
|
if (!success) {
|
||||||
alert(
|
alert(
|
||||||
"A Time Report for this week already exists, please go to the edit page to edit it or change week number.",
|
"Error occurred! Your connection to the server might be lost or a time report for this week already exists, please check your connection or go to the edit page to edit your report or change week number.",
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,6 +29,7 @@ export default function OtherUsersTR(): JSX.Element {
|
||||||
projectName ?? "",
|
projectName ?? "",
|
||||||
fetchedWeek?.toString() ?? "0",
|
fetchedWeek?.toString() ?? "0",
|
||||||
token,
|
token,
|
||||||
|
username ?? "",
|
||||||
);
|
);
|
||||||
|
|
||||||
if (response.success) {
|
if (response.success) {
|
||||||
|
@ -86,6 +87,7 @@ export default function OtherUsersTR(): JSX.Element {
|
||||||
min="0"
|
min="0"
|
||||||
className="border-2 border-black rounded-md text-center w-1/2"
|
className="border-2 border-black rounded-md text-center w-1/2"
|
||||||
value={developmentTime === 0 ? "" : developmentTime}
|
value={developmentTime === 0 ? "" : developmentTime}
|
||||||
|
readOnly
|
||||||
/>
|
/>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
@ -97,6 +99,7 @@ export default function OtherUsersTR(): JSX.Element {
|
||||||
min="0"
|
min="0"
|
||||||
className="border-2 border-black rounded-md text-center w-1/2"
|
className="border-2 border-black rounded-md text-center w-1/2"
|
||||||
value={meetingTime === 0 ? "" : meetingTime}
|
value={meetingTime === 0 ? "" : meetingTime}
|
||||||
|
readOnly
|
||||||
/>
|
/>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
@ -108,6 +111,7 @@ export default function OtherUsersTR(): JSX.Element {
|
||||||
min="0"
|
min="0"
|
||||||
className="border-2 border-black rounded-md text-center w-1/2"
|
className="border-2 border-black rounded-md text-center w-1/2"
|
||||||
value={adminTime === 0 ? "" : adminTime}
|
value={adminTime === 0 ? "" : adminTime}
|
||||||
|
readOnly
|
||||||
/>
|
/>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
@ -119,6 +123,7 @@ export default function OtherUsersTR(): JSX.Element {
|
||||||
min="0"
|
min="0"
|
||||||
className="border-2 border-black rounded-md text-center w-1/2"
|
className="border-2 border-black rounded-md text-center w-1/2"
|
||||||
value={ownWorkTime === 0 ? "" : ownWorkTime}
|
value={ownWorkTime === 0 ? "" : ownWorkTime}
|
||||||
|
readOnly
|
||||||
/>
|
/>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
@ -130,6 +135,7 @@ export default function OtherUsersTR(): JSX.Element {
|
||||||
min="0"
|
min="0"
|
||||||
className="border-2 border-black rounded-md text-center w-1/2"
|
className="border-2 border-black rounded-md text-center w-1/2"
|
||||||
value={studyTime === 0 ? "" : studyTime}
|
value={studyTime === 0 ? "" : studyTime}
|
||||||
|
readOnly
|
||||||
/>
|
/>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
@ -141,6 +147,7 @@ export default function OtherUsersTR(): JSX.Element {
|
||||||
min="0"
|
min="0"
|
||||||
className="border-2 border-black rounded-md text-center w-1/2"
|
className="border-2 border-black rounded-md text-center w-1/2"
|
||||||
value={testingTime === 0 ? "" : testingTime}
|
value={testingTime === 0 ? "" : testingTime}
|
||||||
|
readOnly
|
||||||
/>
|
/>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
|
@ -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);
|
||||||
|
});
|
||||||
|
}
|
|
@ -8,12 +8,12 @@ import { projectTimes } from "./GetProjectTimes";
|
||||||
* @returns JSX.Element
|
* @returns JSX.Element
|
||||||
*/
|
*/
|
||||||
export default function TimePerRole(): JSX.Element {
|
export default function TimePerRole(): JSX.Element {
|
||||||
const [development, setDevelopment] = useState<number>();
|
const [development, setDevelopment] = useState<number>(0);
|
||||||
const [meeting, setMeeting] = useState<number>();
|
const [meeting, setMeeting] = useState<number>(0);
|
||||||
const [admin, setAdmin] = useState<number>();
|
const [admin, setAdmin] = useState<number>(0);
|
||||||
const [own_work, setOwnWork] = useState<number>();
|
const [own_work, setOwnWork] = useState<number>(0);
|
||||||
const [study, setStudy] = useState<number>();
|
const [study, setStudy] = useState<number>(0);
|
||||||
const [testing, setTesting] = useState<number>();
|
const [testing, setTesting] = useState<number>(0);
|
||||||
|
|
||||||
const token = localStorage.getItem("accessToken") ?? "";
|
const token = localStorage.getItem("accessToken") ?? "";
|
||||||
const { projectName } = useParams();
|
const { projectName } = useParams();
|
||||||
|
|
|
@ -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 });
|
||||||
};
|
};
|
||||||
|
|
|
@ -31,8 +31,9 @@ export default function GetOtherUsersReport(): JSX.Element {
|
||||||
projectName ?? "",
|
projectName ?? "",
|
||||||
fetchedWeek?.toString() ?? "0",
|
fetchedWeek?.toString() ?? "0",
|
||||||
token,
|
token,
|
||||||
|
username ?? "",
|
||||||
);
|
);
|
||||||
|
console.log(response);
|
||||||
if (response.success) {
|
if (response.success) {
|
||||||
const report: WeeklyReport = response.data ?? {
|
const report: WeeklyReport = response.data ?? {
|
||||||
reportId: 0,
|
reportId: 0,
|
||||||
|
@ -62,25 +63,33 @@ export default function GetOtherUsersReport(): JSX.Element {
|
||||||
void fetchUsersWeeklyReport();
|
void fetchUsersWeeklyReport();
|
||||||
});
|
});
|
||||||
|
|
||||||
const handleSignWeeklyReport = async (): Promise<void> => {
|
const handleSignWeeklyReport = async (): Promise<boolean> => {
|
||||||
await api.signReport(reportId, token);
|
const response = await api.signReport(reportId, token);
|
||||||
|
if (response.success) {
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<h1 className="text-[30px] font-bold">
|
<h1 className="text-[30px] font-bold"> {username}'s Report</h1>
|
||||||
{" "}
|
|
||||||
UserId: {username}'s Report
|
|
||||||
</h1>
|
|
||||||
<div className="border-4 border-black bg-white flex flex-col justify-start min-h-[65vh] h-fit w-[50vw] rounded-3xl overflow-scroll space-y-[2vh] p-[30px] items-center">
|
<div className="border-4 border-black bg-white flex flex-col justify-start min-h-[65vh] h-fit w-[50vw] rounded-3xl overflow-scroll space-y-[2vh] p-[30px] items-center">
|
||||||
<form
|
<form
|
||||||
onSubmit={(e) => {
|
onSubmit={(e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
void handleSignWeeklyReport();
|
void (async (): Promise<void> => {
|
||||||
|
const success = await handleSignWeeklyReport();
|
||||||
|
if (!success) {
|
||||||
|
alert("Failed to sign report!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
alert("Report successfully signed!");
|
alert("Report successfully signed!");
|
||||||
navigate(-1);
|
navigate(-1);
|
||||||
|
})();
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div className="flex flex-col items-center">
|
<div className="flex flex-col items-center">
|
||||||
|
|
Loading…
Reference in a new issue