Compare commits

...

67 commits

Author SHA1 Message Date
Imbus
12810075f9 Logic error in getAllWeeklyReports fixed 2024-04-03 17:44:50 +02:00
Imbus
12a2691d55 Rename 2024-04-03 17:40:48 +02:00
Imbus
9c5aa10414 Tests for getAllWeeklyReports 2024-04-03 17:32:50 +02:00
Imbus
fcd035fe6e TS Api for getAllWeeklyReport 2024-04-03 17:32:07 +02:00
Imbus
a39cfedad3 Rename, fix and testing for getAllWeeklyReports path 2024-04-03 17:31:39 +02:00
Imbus
a1d2520d88 Updating typescript api 2024-04-03 17:12:29 +02:00
Imbus
903132e56d Polishing tests 2024-04-03 17:06:08 +02:00
Imbus
7c973009ac Adding pycache to gitignore 2024-04-03 15:54:27 +02:00
Imbus
ffe5d53625 Splitting test script 2024-04-03 15:53:52 +02:00
Imbus
8ea6dec346 Fixes for various paths 2024-04-03 15:53:36 +02:00
Imbus
61a2d1ce0c PromoteToPm handler 2024-04-03 15:53:15 +02:00
Imbus
9cca8edd9d Additional tests 2024-04-03 14:50:04 +02:00
Imbus
1f9ccca9bf Fixing integration test script 2024-04-03 14:21:55 +02:00
Davenludd
6b880244a1 Merge branch 'gruppDM' into frontend 2024-04-03 14:00:14 +02:00
Davenludd
4d7b3e0d57 Refactor API call and types in AllTimeReportsInProjectOtherUser component 2024-04-03 13:59:44 +02:00
Peter KW
46eb3c76a8 Small fix to statistics 2024-04-02 19:18:05 +02:00
Peter KW
632676e3d2 Added admin to all projects in sample_data 2024-04-02 19:16:02 +02:00
Mattias
1b4e521508 Update variable names in TimePerActivity component 2024-04-02 18:02:40 +02:00
Mattias
a7cc48d392 Refactor TimePerRole component to use API response for time per activity 2024-04-02 18:02:04 +02:00
Davenludd
ff37236cf6 Minor fixes TimePerActivity component to use readOnly input fields 2024-04-02 17:35:02 +02:00
Davenludd
eb741ba20d Add total time calculation to DisplayUnsignedReports component 2024-04-02 17:33:20 +02:00
Davenludd
00ca5514e5 Added sign functionality to component 2024-04-02 17:27:17 +02:00
Mattias
7c7755085e Add comments for getUnsignedReportsInProject in API 2024-04-02 17:11:30 +02:00
Mattias
1e1677fc57 Refactor getUnsignedReports in DisplayUnsignedReports component 2024-04-02 17:07:47 +02:00
Mattias
0b8b430f38 Refactor DisplayUnsignedReports component to use API and WeeklyReport type 2024-04-02 17:05:55 +02:00
Mattias
93659a72dc Add getUnsignedReportsInProject API method 2024-04-02 17:05:46 +02:00
Mattias
8d6da684bf Add username retrieval from local storage 2024-04-02 16:21:23 +02:00
Davenludd
762a1b7368 Merge branch 'frontend' into gruppDM 2024-04-02 15:51:11 +02:00
Mattias
398305d3ed Fix input validation in NewWeeklyReport component 2024-04-02 15:43:18 +02:00
Mattias
6c2213b488 Update handleUpdateWeeklyReport function and fix input validation 2024-04-02 15:43:12 +02:00
Mattias
b3e363f391 Add updateWeeklyReport function to API.ts 2024-04-02 15:43:05 +02:00
Peter KW
51a4d2a0b7 Fix to comments 2024-04-02 13:45:03 +02:00
Peter KW
524bd6c691 Can now delete project 2024-04-02 13:44:31 +02:00
Peter KW
b50d88f670 DeleteProject component 2024-04-02 13:44:06 +02:00
Peter KW
3ed4393c77 Fixed fetch path in removeProject 2024-04-02 13:43:32 +02:00
Peter KW
75876e43da Minor design fix 2024-04-02 13:22:46 +02:00
Peter KW
ea5bbf5f0a Merge branch 'frontend' into gruppPP 2024-04-02 13:18:25 +02:00
Peter KW
948dcce1ca Some fixes to design and comments 2024-04-02 13:14:08 +02:00
Peter KW
cb68a6323b Now shows project statistics 2024-04-02 13:11:33 +02:00
Peter KW
ca88daf493 GetProjectTimes component 2024-04-02 13:10:04 +02:00
Peter KW
644d0ee12c getProjectTimes API 2024-04-02 13:09:28 +02:00
Peter KW
6efc961774 Added getProjectTimes 2024-04-02 13:08:55 +02:00
Davenludd
7df1654bdc Merge branch 'frontend' into gruppDM 2024-04-02 11:49:46 +02:00
Davenludd
6dfa917cf0 Fix in DisplayUserProjects component 2024-04-02 11:37:53 +02:00
Peter KW
17a571fd7c Uses component to get projects now 2024-04-01 02:25:12 +02:00
Peter KW
f3466854c7 Removed unused pages and paths to them in main 2024-04-01 02:24:26 +02:00
Peter KW
1212b3c5ef Removed some stuff 2024-04-01 02:20:48 +02:00
Peter KW
dc98fb510e Clears username+password fields on successful register 2024-04-01 02:17:57 +02:00
Peter KW
58deef400a Removed unused code 2024-04-01 02:17:02 +02:00
Peter KW
cc039d27ae New modal for member info 2024-04-01 02:16:23 +02:00
Peter KW
3981190c7a Can now change username in this modal + moved some stuff to a separate modal 2024-04-01 02:16:06 +02:00
Peter KW
9b0a231701 Some fixes to ChangeUsername 2024-04-01 02:14:44 +02:00
Peter KW
e06aced6dd ChangeRole component to change role and a view for it 2024-04-01 02:13:24 +02:00
Peter KW
68fbbb4b19 Added some alerts + removed unused code 2024-04-01 02:09:28 +02:00
Peter KW
378dd99592 Changed so that you can only change other users role 2024-04-01 02:08:19 +02:00
Peter KW
6fa8135e32 ChangeUserRole API added + bugfix 2024-04-01 02:02:22 +02:00
Peter KW
60fb333090 Fix to paths 2024-03-31 21:04:58 +02:00
Peter KW
e7911574be Removed unused files 2024-03-31 20:56:40 +02:00
Peter KW
0c8a394f74 Small fix so that it uses component for getting users in a proj 2024-03-31 20:56:08 +02:00
Peter KW
5f42fa7818 Fixed types and imports of types 2024-03-31 20:54:00 +02:00
Peter KW
8b6462abee Changed so that username is required to get projects, so that you can get another user's projects (for admin stuff) 2024-03-29 20:19:22 +01:00
Peter KW
4ab23b3c3c Merge branch 'dev' into gruppPP 2024-03-29 20:05:10 +01:00
Davenludd
c1f49915ba Refactor signReport method signature 2024-03-29 18:29:38 +01:00
Davenludd
2aaa327a01 Merge branch 'dev' into gruppDM 2024-03-29 17:55:32 +01:00
Davenludd
05545f6f88 Minor fixes 2024-03-29 17:53:37 +01:00
Peter KW
b036ef906c Small fixes in all files that fetches user projects, so that they pass username as argument to GetProjects-function 2024-03-28 21:31:30 +01:00
Peter KW
85795f5406 Changed GetUserProjects so that you have to get username from params. Now admin can choose a user and see what projects the user belongs to 2024-03-28 21:25:59 +01:00
52 changed files with 1525 additions and 1034 deletions

1
.gitignore vendored
View file

@ -14,6 +14,7 @@ diagram.puml
backend/*.png backend/*.png
backend/*.jpg backend/*.jpg
backend/*.svg backend/*.svg
__pycache__
/go.work.sum /go.work.sum
/package-lock.json /package-lock.json

View file

@ -35,7 +35,7 @@ type Database interface {
GetProject(projectId int) (types.Project, error) GetProject(projectId int) (types.Project, error)
GetUserRole(username string, projectname string) (string, error) GetUserRole(username string, projectname string) (string, error)
GetWeeklyReport(username string, projectName string, week int) (types.WeeklyReport, error) GetWeeklyReport(username string, projectName string, week int) (types.WeeklyReport, error)
GetWeeklyReportsUser(username string, projectname string) ([]types.WeeklyReportList, error) GetAllWeeklyReports(username string, projectname string) ([]types.WeeklyReportList, error)
GetUnsignedWeeklyReports(projectName string) ([]types.WeeklyReport, error) GetUnsignedWeeklyReports(projectName string) ([]types.WeeklyReport, error)
SignWeeklyReport(reportId int, projectManagerId int) error SignWeeklyReport(reportId int, projectManagerId int) error
IsSiteAdmin(username string) (bool, error) IsSiteAdmin(username string) (bool, error)
@ -463,8 +463,8 @@ func (d *Db) Migrate() error {
return nil return nil
} }
// GetWeeklyReportsUser retrieves weekly reports for a specific user and project. // GetAllWeeklyReports retrieves weekly reports for a specific user and project.
func (d *Db) GetWeeklyReportsUser(username string, projectName string) ([]types.WeeklyReportList, error) { func (d *Db) GetAllWeeklyReports(username string, projectName string) ([]types.WeeklyReportList, error) {
query := ` query := `
SELECT SELECT
wr.week, wr.week,

View file

@ -705,7 +705,7 @@ func TestGetWeeklyReportsUser(t *testing.T) {
t.Error("AddWeeklyReport failed:", err) t.Error("AddWeeklyReport failed:", err)
} }
reports, err := db.GetWeeklyReportsUser("testuser", "testproject") reports, err := db.GetAllWeeklyReports("testuser", "testproject")
if err != nil { if err != nil {
t.Error("GetWeeklyReportsUser failed:", err) t.Error("GetWeeklyReportsUser failed:", err)
} }
@ -964,4 +964,3 @@ func TestRemoveProject(t *testing.T) {
} }
} }

View file

@ -21,6 +21,12 @@ VALUES ("projecttest3","test project3", 1);
INSERT OR IGNORE INTO user_roles(user_id,project_id,p_role) INSERT OR IGNORE INTO user_roles(user_id,project_id,p_role)
VALUES (1,1,"project_manager"); VALUES (1,1,"project_manager");
INSERT OR IGNORE INTO user_roles(user_id,project_id,p_role)
VALUES (1,2,"project_manager");
INSERT OR IGNORE INTO user_roles(user_id,project_id,p_role)
VALUES (1,3,"project_manager");
INSERT OR IGNORE INTO user_roles(user_id,project_id,p_role) INSERT OR IGNORE INTO user_roles(user_id,project_id,p_role)
VALUES (2,1,"member"); VALUES (2,1,"member");

View file

@ -10,42 +10,33 @@ import (
// AddUserToProjectHandler is a handler that adds a user to a project with a specified role // AddUserToProjectHandler is a handler that adds a user to a project with a specified role
func AddUserToProjectHandler(c *fiber.Ctx) error { func AddUserToProjectHandler(c *fiber.Ctx) error {
// Extract necessary parameters from the request
var requestData struct {
Username string `json:"username"`
ProjectName string `json:"projectName"`
Role string `json:"role"`
}
if err := c.BodyParser(&requestData); err != nil {
log.Info("Error parsing request body:", err)
return c.Status(400).SendString("Bad request")
}
// Check if the user adding another user to the project is a site admin
user := c.Locals("user").(*jwt.Token) user := c.Locals("user").(*jwt.Token)
claims := user.Claims.(jwt.MapClaims) claims := user.Claims.(jwt.MapClaims)
adminUsername := claims["name"].(string) pm_name := claims["name"].(string)
log.Info("Admin username from claims:", adminUsername)
isAdmin, err := db.GetDb(c).IsSiteAdmin(adminUsername) 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 { if err != nil {
log.Info("Error checking admin status:", err) log.Info("Error checking if user is project manager:", err)
return c.Status(500).SendString(err.Error()) return c.Status(500).SendString(err.Error())
} }
if !isAdmin { if !isPM {
log.Info("User is not a site admin:", adminUsername) log.Info("User: ", pm_name, " is not a project manager in project: ", project)
return c.Status(403).SendString("User is not a site admin") return c.Status(403).SendString("User is not a project manager")
} }
// Add the user to the project with the specified role // Add the user to the project with the specified role
err = db.GetDb(c).AddUserToProject(requestData.Username, requestData.ProjectName, requestData.Role) err = db.GetDb(c).AddUserToProject(username, project, "member")
if err != nil { if err != nil {
log.Info("Error adding user to project:", err) log.Info("Error adding user to project:", err)
return c.Status(500).SendString(err.Error()) return c.Status(500).SendString(err.Error())
} }
// Return success message // Return success message
log.Info("User added to project successfully:", requestData.Username) log.Info("User : ", username, " added to project: ", project)
return c.SendStatus(fiber.StatusOK) return c.SendStatus(fiber.StatusOK)
} }

View file

@ -4,15 +4,16 @@ import (
db "ttime/internal/database" db "ttime/internal/database"
"github.com/gofiber/fiber/v2" "github.com/gofiber/fiber/v2"
"github.com/golang-jwt/jwt/v5" "github.com/gofiber/fiber/v2/log"
) )
// GetUserProjects returns all projects that the user is a member of // GetUserProjects returns all projects that the user is a member of
func GetUserProjects(c *fiber.Ctx) error { func GetUserProjects(c *fiber.Ctx) error {
// First we get the username from the token username := c.Params("username")
user := c.Locals("user").(*jwt.Token) if username == "" {
claims := user.Claims.(jwt.MapClaims) log.Info("No username provided")
username := claims["name"].(string) return c.Status(400).SendString("No username provided")
}
// Then dip into the database to get the projects // Then dip into the database to get the projects
projects, err := db.GetDb(c).GetProjectsForUser(username) projects, err := db.GetDb(c).GetProjectsForUser(username)

View file

@ -24,7 +24,13 @@ func ProjectRoleChange(c *fiber.Ctx) error {
return c.Status(400).SendString(err.Error()) return c.Status(400).SendString(err.Error())
} }
log.Info("Changing role for user: ", username, " in project: ", data.Projectname, " to: ", data.Role) // Check if user is trying to change its own role
if username == data.UserName {
log.Info("Can't change your own role")
return c.Status(403).SendString("Can't change your own role")
}
log.Info("Changing role for user: ", data.UserName, " in project: ", data.Projectname, " to: ", data.Role)
// Dubble diping and checcking if current user is // Dubble diping and checcking if current user is
if ismanager, err := db.GetDb(c).IsProjectManager(username, data.Projectname); err != nil { if ismanager, err := db.GetDb(c).IsProjectManager(username, data.Projectname); err != nil {
@ -36,7 +42,7 @@ func ProjectRoleChange(c *fiber.Ctx) error {
} }
// Change the user's role within the project in the database // Change the user's role within the project in the database
if err := db.GetDb(c).ChangeUserRole(username, data.Projectname, data.Role); err != nil { if err := db.GetDb(c).ChangeUserRole(data.UserName, data.Projectname, data.Role); err != nil {
return c.Status(500).SendString(err.Error()) return c.Status(500).SendString(err.Error())
} }

View file

@ -0,0 +1,51 @@
package projects
import (
db "ttime/internal/database"
"github.com/gofiber/fiber/v2"
"github.com/gofiber/fiber/v2/log"
"github.com/golang-jwt/jwt/v5"
)
// @Summary Promote to project manager
// @Description Promote a user to project manager
// @Tags Auth
// @Security JWT
// @Accept plain
// @Produce plain
// @Param projectName path string true "Project name"
// @Param userName query string true "User name"
// @Failure 500 {string} string "Internal server error"
// @Failure 403 {string} string "Forbidden"
// @Router /promote/{projectName} [put]
//
// Login logs in a user and returns a JWT token
// Promote to project manager
func PromoteToPm(c *fiber.Ctx) error {
user := c.Locals("user").(*jwt.Token)
claims := user.Claims.(jwt.MapClaims)
pm_name := claims["name"].(string)
project := c.Params("projectName")
new_pm_name := 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")
}
// Add the user to the project with the specified role
err = db.GetDb(c).ChangeUserRole(new_pm_name, project, "project_manager")
// Return success message
log.Info("User : ", new_pm_name, " promoted to project manager in project: ", project)
return c.SendStatus(fiber.StatusOK)
}

View file

@ -0,0 +1,56 @@
package reports
import (
db "ttime/internal/database"
"github.com/gofiber/fiber/v2"
"github.com/gofiber/fiber/v2/log"
"github.com/golang-jwt/jwt/v5"
)
// GetAllWeeklyReports retrieves all weekly reports for a user in a specific project
func GetAllWeeklyReports(c *fiber.Ctx) error {
// Extract the necessary parameters from the token
user := c.Locals("user").(*jwt.Token)
claims := user.Claims.(jwt.MapClaims)
username := claims["name"].(string)
// Extract project name and week from query parameters
projectName := c.Params("projectName")
target_user := c.Query("targetUser") // The user whose reports are being requested
// If the target user is not empty, use it as the username
if target_user == "" {
target_user = username
}
log.Info(username, " trying to get all weekly reports for: ", target_user)
if projectName == "" {
log.Info("Missing project name")
return c.Status(400).SendString("Missing project name")
}
// If the user is not a project manager, they can only view their own reports
pm, err := db.GetDb(c).IsProjectManager(username, projectName)
if err != nil {
log.Info("Error checking if user is project manager:", err)
return c.Status(500).SendString(err.Error())
}
if pm == false && target_user != username {
log.Info("Unauthorized access")
return c.Status(403).SendString("Unauthorized access")
}
// Retrieve weekly reports for the user in the project from the database
reports, err := db.GetDb(c).GetAllWeeklyReports(target_user, projectName)
if err != nil {
log.Error("Error getting weekly reports for user:", target_user, "in project:", projectName, ":", err)
return c.Status(500).SendString(err.Error())
}
log.Info("Returning weekly report")
// Return the retrieved weekly report
return c.JSON(reports)
}

View file

@ -16,11 +16,17 @@ func GetWeeklyReport(c *fiber.Ctx) error {
claims := user.Claims.(jwt.MapClaims) claims := user.Claims.(jwt.MapClaims)
username := claims["name"].(string) username := claims["name"].(string)
log.Info("Getting weekly report for: ", username)
// Extract project name and week from query parameters // Extract project name and week from query parameters
projectName := c.Query("projectName") projectName := c.Query("projectName")
week := c.Query("week") week := c.Query("week")
target_user := c.Query("targetUser") // The user whose report is being requested
// If the target user is not empty, use it as the username
if target_user == "" {
target_user = username
}
log.Info(username, " trying to get weekly report for: ", target_user)
if projectName == "" || week == "" { if projectName == "" || week == "" {
log.Info("Missing project name or week number") log.Info("Missing project name or week number")
@ -34,8 +40,20 @@ func GetWeeklyReport(c *fiber.Ctx) error {
return c.Status(400).SendString("Invalid week number") return c.Status(400).SendString("Invalid week number")
} }
// If the token user is not an admin, check if the target user is the same as the token user
pm, err := db.GetDb(c).IsProjectManager(username, projectName)
if err != nil {
log.Info("Error checking if user is project manager:", err)
return c.Status(500).SendString(err.Error())
}
if pm == false && target_user != username {
log.Info("Unauthorized access")
return c.Status(403).SendString("Unauthorized access")
}
// Call the database function to get the weekly report // Call the database function to get the weekly report
report, err := db.GetDb(c).GetWeeklyReport(username, projectName, weekInt) report, err := db.GetDb(c).GetWeeklyReport(target_user, projectName, weekInt)
if err != nil { if err != nil {
log.Info("Error getting weekly report from db:", err) log.Info("Error getting weekly report from db:", err)
return c.Status(500).SendString(err.Error()) return c.Status(500).SendString(err.Error())

View file

@ -1,36 +0,0 @@
package reports
import (
db "ttime/internal/database"
"github.com/gofiber/fiber/v2"
"github.com/gofiber/fiber/v2/log"
"github.com/golang-jwt/jwt/v5"
)
// GetWeeklyReportsUserHandler retrieves all weekly reports for a user in a specific project
func GetWeeklyReportsUserHandler(c *fiber.Ctx) error {
// Extract the necessary parameters from the token
user := c.Locals("user").(*jwt.Token)
claims := user.Claims.(jwt.MapClaims)
username := claims["name"].(string)
// Extract necessary (path) parameters from the request
projectName := c.Params("projectName")
// TODO: Here we need to check whether the user is a member of the project
// If not, we should return an error. On the other hand, if the user not a member,
// the returned list of reports will (should) allways be empty.
// Retrieve weekly reports for the user in the project from the database
reports, err := db.GetDb(c).GetWeeklyReportsUser(username, projectName)
if err != nil {
log.Error("Error getting weekly reports for user:", username, "in project:", projectName, ":", err)
return c.Status(500).SendString(err.Error())
}
log.Info("Returning weekly reports for user:", username, "in project:", projectName)
// Return the list of reports as JSON
return c.JSON(reports)
}

View file

@ -112,12 +112,15 @@ func main() {
// All project related routes // All project related routes
// projectGroup := api.Group("/project") // Not currently in use // projectGroup := api.Group("/project") // Not currently in use
api.Get("/getUserProjects", projects.GetUserProjects) api.Get("/getProjectTimes/:projectName", projects.GetProjectTimesHandler)
api.Get("/getUserProjects/:username", projects.GetUserProjects)
api.Get("/project/:projectId", projects.GetProject) api.Get("/project/:projectId", projects.GetProject)
api.Get("/checkIfProjectManager/:projectName", projects.IsProjectManagerHandler) api.Get("/checkIfProjectManager/:projectName", projects.IsProjectManagerHandler)
api.Get("/getUsersProject/:projectName", projects.ListAllUsersProject) api.Get("/getUsersProject/:projectName", projects.ListAllUsersProject)
api.Post("/project", projects.CreateProject) api.Post("/project", projects.CreateProject)
api.Post("/ProjectRoleChange", projects.ProjectRoleChange) api.Post("/ProjectRoleChange", projects.ProjectRoleChange)
api.Put("/promoteToPm/:projectName", projects.PromoteToPm)
api.Put("/addUserToProject/:projectName", projects.AddUserToProjectHandler)
api.Delete("/removeProject/:projectName", projects.RemoveProject) api.Delete("/removeProject/:projectName", projects.RemoveProject)
api.Delete("/project/:projectID", projects.DeleteProject) api.Delete("/project/:projectID", projects.DeleteProject)
@ -125,10 +128,9 @@ func main() {
// reportGroup := api.Group("/report") // Not currently in use // reportGroup := api.Group("/report") // Not currently in use
api.Get("/getWeeklyReport", reports.GetWeeklyReport) api.Get("/getWeeklyReport", reports.GetWeeklyReport)
api.Get("/getUnsignedReports/:projectName", reports.GetUnsignedReports) api.Get("/getUnsignedReports/:projectName", reports.GetUnsignedReports)
api.Get("/getWeeklyReportsUser/:projectName", reports.GetWeeklyReportsUserHandler) api.Get("/getAllWeeklyReports/:projectName", reports.GetAllWeeklyReports)
api.Post("/submitWeeklyReport", reports.SubmitWeeklyReport) api.Post("/submitWeeklyReport", reports.SubmitWeeklyReport)
api.Put("/signReport/:reportId", reports.SignReport) api.Put("/signReport/:reportId", reports.SignReport)
api.Put("/addUserToProject", projects.AddUserToProjectHandler)
api.Put("/updateWeeklyReport", reports.UpdateWeeklyReport) api.Put("/updateWeeklyReport", reports.UpdateWeeklyReport)
// Announce the port we are listening on and start the server // Announce the port we are listening on and start the server

View file

@ -1,13 +1,16 @@
import { NewProjMember } from "../Components/AddMember";
import { ProjectRoleChange } from "../Components/ChangeRole";
import { projectTimes } from "../Components/GetProjectTimes";
import { ProjectMember } from "../Components/GetUsersInProject";
import { import {
UpdateWeeklyReport,
NewWeeklyReport, NewWeeklyReport,
NewUser, NewUser,
User, User,
Project, Project,
NewProject, NewProject,
UserProjectMember,
WeeklyReport, WeeklyReport,
StrNameChange, StrNameChange,
NewProjMember,
} from "../Types/goTypes"; } from "../Types/goTypes";
/** /**
@ -73,10 +76,7 @@ interface API {
* @param {string} token The authentication token. * @param {string} token The authentication token.
* @returns {Promise<APIResponse<Project>>} A promise resolving to an API response with the created project. * @returns {Promise<APIResponse<Project>>} A promise resolving to an API response with the created project.
*/ */
createProject( createProject(project: NewProject, token: string): Promise<APIResponse<void>>;
project: NewProject,
token: string,
): Promise<APIResponse<Project>>;
/** Submits a weekly report /** Submits a weekly report
* @param {NewWeeklyReport} weeklyReport The weekly report object. * @param {NewWeeklyReport} weeklyReport The weekly report object.
@ -88,16 +88,31 @@ interface API {
token: string, token: string,
): Promise<APIResponse<string>>; ): Promise<APIResponse<string>>;
/** Gets a weekly report for a specific user, project and week /**
* Updates a weekly report.
* @param {UpdateWeeklyReport} weeklyReport The updated weekly report object.
* @param {string} token The authentication token.
* @returns {Promise<APIResponse<string>>} A promise containing the API response with the updated report.
*/
updateWeeklyReport(
weeklyReport: UpdateWeeklyReport,
token: string,
): Promise<APIResponse<string>>;
/** Gets a weekly report for a specific user, project and week.
* Keep in mind that the user within the token needs to be PM
* of the project to get the report, unless the user is the target user.
* @param {string} projectName The name of the project. * @param {string} projectName The name of the project.
* @param {string} week The week number. * @param {string} week The week number.
* @param {string} token The authentication token. * @param {string} token The authentication token.
* @param {string} targetUser The username of the target user. Defaults to token user.
* @returns {Promise<APIResponse<WeeklyReport>>} A promise resolving to an API response with the retrieved report. * @returns {Promise<APIResponse<WeeklyReport>>} A promise resolving to an API response with the retrieved report.
*/ */
getWeeklyReport( getWeeklyReport(
projectName: string, projectName: string,
week: string, week: string,
token: string, token: string,
targetUser?: string,
): Promise<APIResponse<WeeklyReport>>; ): Promise<APIResponse<WeeklyReport>>;
/** /**
@ -107,16 +122,21 @@ interface API {
* @param {string} token The token of the user * @param {string} token The token of the user
* @returns {APIResponse<WeeklyReport[]>} A list of weekly reports * @returns {APIResponse<WeeklyReport[]>} A list of weekly reports
*/ */
getWeeklyReportsForUser( getAllWeeklyReportsForUser(
projectName: string, projectName: string,
token: string, token: string,
targetUser?: string,
): Promise<APIResponse<WeeklyReport[]>>; ): Promise<APIResponse<WeeklyReport[]>>;
/** Gets all the projects of a user /** Gets all the projects of a user
* @param {string} username - The authentication token.
* @param {string} token - The authentication token. * @param {string} token - The authentication token.
* @returns {Promise<APIResponse<Project[]>>} A promise containing the API response with the user's projects. * @returns {Promise<APIResponse<Project[]>>} A promise containing the API response with the user's projects.
*/ */
getUserProjects(token: string): Promise<APIResponse<Project[]>>; getUserProjects(
username: string,
token: string,
): Promise<APIResponse<Project[]>>;
/** Gets a project by its id. /** Gets a project by its id.
* @param {number} id The id of the project to retrieve. * @param {number} id The id of the project to retrieve.
@ -124,6 +144,16 @@ interface API {
*/ */
getProject(id: number): Promise<APIResponse<Project>>; getProject(id: number): Promise<APIResponse<Project>>;
/** Gets a projects reported time
* @param {string} projectName The name of the project.
* @param {string} token The usertoken.
* @returns {Promise<APIResponse<Times>>} A promise resolving to an API response containing the project times.
*/
getProjectTimes(
projectName: string,
token: string,
): Promise<APIResponse<projectTimes>>;
/** Gets a list of all users. /** Gets a list of all users.
* @param {string} token The authentication token of the requesting user. * @param {string} token The authentication token of the requesting user.
* @returns {Promise<APIResponse<string[]>>} A promise resolving to an API response containing the list of users. * @returns {Promise<APIResponse<string[]>>} A promise resolving to an API response containing the list of users.
@ -133,7 +163,18 @@ interface API {
getAllUsersProject( getAllUsersProject(
projectName: string, projectName: string,
token: string, token: string,
): Promise<APIResponse<UserProjectMember[]>>; ): Promise<APIResponse<ProjectMember[]>>;
/** Gets all unsigned reports in a project.
* @param {string} projectName The name of the project.
* @param {string} token The authentication token.
* @returns {Promise<APIResponse<WeeklyReport[]>>} A promise resolving to an API response containing the list of unsigned reports.
*/
getUnsignedReportsInProject(
projectName: string,
token: string,
): Promise<APIResponse<WeeklyReport[]>>;
/** /**
* Changes the username of a user in the database. * Changes the username of a user in the database.
* @param {StrNameChange} data The object containing the previous and new username. * @param {StrNameChange} data The object containing the previous and new username.
@ -144,10 +185,21 @@ interface API {
data: StrNameChange, data: StrNameChange,
token: string, token: string,
): Promise<APIResponse<void>>; ): Promise<APIResponse<void>>;
/**
* Changes the role of a user in the database.
* @param {RoleChange} roleInfo The object containing the previous and new username.
* @param {string} token The authentication token.
* @returns {Promise<APIResponse<void>>} A promise resolving to an API response.
*/
changeUserRole(
roleInfo: ProjectRoleChange,
token: string,
): Promise<APIResponse<void>>;
addUserToProject( addUserToProject(
user: NewProjMember, user: NewProjMember,
token: string, token: string,
): Promise<APIResponse<NewProjMember>>; ): Promise<APIResponse<void>>;
removeProject( removeProject(
projectName: string, projectName: string,
@ -161,8 +213,18 @@ interface API {
* @param {number} reportId The id of the report to sign * @param {number} reportId The id of the report to sign
* @param {string} token The authentication token * @param {string} token The authentication token
*/ */
signReport( signReport(reportId: number, token: string): Promise<APIResponse<string>>;
reportId: number,
/**
* Promotes a user to project manager within a project.
*
* @param {string} userName The username of the user to promote
* @param {string} projectName The name of the project to promote the user in
* @returns {Promise<APIResponse<string>} A promise resolving to an API response.
*/
promoteToPm(
userName: string,
projectName: string,
token: string, token: string,
): Promise<APIResponse<string>>; ): Promise<APIResponse<string>>;
} }
@ -252,7 +314,7 @@ export const api: API = {
async createProject( async createProject(
project: NewProject, project: NewProject,
token: string, token: string,
): Promise<APIResponse<Project>> { ): Promise<APIResponse<void>> {
try { try {
const response = await fetch("/api/project", { const response = await fetch("/api/project", {
method: "POST", method: "POST",
@ -266,18 +328,17 @@ export const api: API = {
if (!response.ok) { if (!response.ok) {
return { success: false, message: "Failed to create project" }; return { success: false, message: "Failed to create project" };
} else { } else {
const data = (await response.json()) as Project; return { success: true };
return { success: true, data };
} }
} catch (e) { } catch (e) {
return { success: false, message: "Failed to create project" }; return { success: false, message: "Failed to create project!" };
} }
}, },
async addUserToProject( async addUserToProject(
user: NewProjMember, user: NewProjMember,
token: string, token: string,
): Promise<APIResponse<NewProjMember>> { ): Promise<APIResponse<void>> {
try { try {
const response = await fetch("/api/addUserToProject", { const response = await fetch("/api/addUserToProject", {
method: "PUT", method: "PUT",
@ -319,9 +380,39 @@ export const api: API = {
} }
}, },
async getUserProjects(token: string): Promise<APIResponse<Project[]>> { async changeUserRole(
roleInfo: ProjectRoleChange,
token: string,
): Promise<APIResponse<void>> {
try { try {
const response = await fetch("/api/getUserProjects", { const response = await fetch("/api/ProjectRoleChange", {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: "Bearer " + token,
},
body: JSON.stringify(roleInfo),
});
if (!response.ok) {
if (response.status === 403) {
return { success: false, message: "Cannot change your own role" };
}
return { success: false, message: "Could not change role" };
} else {
return { success: true };
}
} catch (e) {
return { success: false, message: "Could not change role" };
}
},
async getUserProjects(
username: string,
token: string,
): Promise<APIResponse<Project[]>> {
try {
const response = await fetch(`/api/getUserProjects/${username}`, {
method: "GET", method: "GET",
headers: { headers: {
"Content-Type": "application/json", "Content-Type": "application/json",
@ -346,6 +437,37 @@ export const api: API = {
} }
}, },
async getProjectTimes(
projectName: string,
token: string,
): Promise<APIResponse<projectTimes>> {
try {
const response = await fetch(`/api/getProjectTimes/${projectName}`, {
method: "GET",
headers: {
"Content-Type": "application/json",
Authorization: "Bearer " + token,
},
});
if (!response.ok) {
return Promise.resolve({
success: false,
message:
"Fetch error: " + response.status + ", failed to get project times",
});
} else {
const data = (await response.json()) as projectTimes;
return Promise.resolve({ success: true, data });
}
} catch (e) {
return Promise.resolve({
success: false,
message: "API error! Could not get times.",
});
}
},
async submitWeeklyReport( async submitWeeklyReport(
weeklyReport: NewWeeklyReport, weeklyReport: NewWeeklyReport,
token: string, token: string,
@ -377,14 +499,46 @@ export const api: API = {
} }
}, },
async updateWeeklyReport(
weeklyReport: UpdateWeeklyReport,
token: string,
): Promise<APIResponse<string>> {
try {
const response = await fetch("/api/updateWeeklyReport", {
method: "PUT",
headers: {
"Content-Type": "application/json",
Authorization: "Bearer " + token,
},
body: JSON.stringify(weeklyReport),
});
if (!response.ok) {
return {
success: false,
message: "Failed to update weekly report",
};
}
const data = await response.text();
return { success: true, message: data };
} catch (e) {
return {
success: false,
message: "Failed to update weekly report",
};
}
},
async getWeeklyReport( async getWeeklyReport(
projectName: string, projectName: string,
week: string, week: string,
token: string, token: string,
targetUser?: string,
): Promise<APIResponse<WeeklyReport>> { ): Promise<APIResponse<WeeklyReport>> {
try { try {
const response = await fetch( const response = await fetch(
`/api/getWeeklyReport?projectName=${projectName}&week=${week}`, `/api/getWeeklyReport?projectName=${projectName}&week=${week}&targetUser=${targetUser}`,
{ {
method: "GET", method: "GET",
headers: { headers: {
@ -405,18 +559,22 @@ export const api: API = {
} }
}, },
async getWeeklyReportsForUser( async getAllWeeklyReportsForUser(
projectName: string, projectName: string,
token: string, token: string,
targetUser?: string,
): Promise<APIResponse<WeeklyReport[]>> { ): Promise<APIResponse<WeeklyReport[]>> {
try { try {
const response = await fetch(`/api/getWeeklyReportsUser/${projectName}`, { const response = await fetch(
method: "GET", `/api/getAllWeeklyReports/${projectName}?targetUser=${targetUser}`,
headers: { {
"Content-Type": "application/json", method: "GET",
Authorization: "Bearer " + token, headers: {
"Content-Type": "application/json",
Authorization: "Bearer " + token,
},
}, },
}); );
if (!response.ok) { if (!response.ok) {
return { return {
@ -513,7 +671,7 @@ export const api: API = {
async getAllUsersProject( async getAllUsersProject(
projectName: string, projectName: string,
token: string, token: string,
): Promise<APIResponse<UserProjectMember[]>> { ): Promise<APIResponse<ProjectMember[]>> {
try { try {
const response = await fetch(`/api/getUsersProject/${projectName}`, { const response = await fetch(`/api/getUsersProject/${projectName}`, {
method: "GET", method: "GET",
@ -529,7 +687,7 @@ export const api: API = {
message: "Failed to get users", message: "Failed to get users",
}); });
} else { } else {
const data = (await response.json()) as UserProjectMember[]; const data = (await response.json()) as ProjectMember[];
return Promise.resolve({ success: true, data }); return Promise.resolve({ success: true, data });
} }
} catch (e) { } catch (e) {
@ -540,6 +698,38 @@ export const api: API = {
} }
}, },
async getUnsignedReportsInProject(
projectName: string,
token: string,
): Promise<APIResponse<WeeklyReport[]>> {
try {
const response = await fetch(`/api/getUnsignedReports/${projectName}`, {
method: "GET",
headers: {
"Content-Type": "application/json",
Authorization: "Bearer " + token,
},
});
if (!response.ok) {
return {
success: false,
message:
"Failed to get unsigned reports for project: Response code " +
response.status,
};
} else {
const data = (await response.json()) as WeeklyReport[];
return { success: true, data };
}
} catch (e) {
return {
success: false,
message: "Failed to get unsigned reports for project, unknown error",
};
}
},
async changeUserName( async changeUserName(
data: StrNameChange, data: StrNameChange,
token: string, token: string,
@ -569,7 +759,7 @@ export const api: API = {
token: string, token: string,
): Promise<APIResponse<string>> { ): Promise<APIResponse<string>> {
try { try {
const response = await fetch(`/api/projectdelete/${projectName}`, { const response = await fetch(`/api/removeProject/${projectName}`, {
method: "DELETE", method: "DELETE",
headers: { headers: {
"Content-Type": "application/json", "Content-Type": "application/json",
@ -615,5 +805,36 @@ export const api: API = {
} catch (e) { } catch (e) {
return { success: false, message: "Failed to sign report" }; return { success: false, message: "Failed to sign report" };
} }
} },
async promoteToPm(
userName: string,
projectName: string,
token: string,
): Promise<APIResponse<string>> {
try {
const response = await fetch(
`/api/promoteToPm/${projectName}?userName=${userName}`,
{
method: "PUT",
headers: {
"Content-Type": "application/json",
Authorization: "Bearer " + token,
},
},
);
if (!response.ok) {
return {
success: false,
message: "Failed to promote user to project manager",
};
}
} catch (e) {
return {
success: false,
message: "Failed to promote user to project manager",
};
}
return { success: true, message: "User promoted to project manager" };
},
}; };

View file

@ -1,5 +1,10 @@
import { APIResponse, api } from "../API/API"; import { APIResponse, api } from "../API/API";
import { NewProjMember } from "../Types/goTypes";
export interface NewProjMember {
username: string;
role: string;
projectname: string;
}
/** /**
* Tries to add a member to a project * Tries to add a member to a project
@ -21,7 +26,7 @@ function AddMember(props: { memberToAdd: NewProjMember }): boolean {
props.memberToAdd, props.memberToAdd,
localStorage.getItem("accessToken") ?? "", localStorage.getItem("accessToken") ?? "",
) )
.then((response: APIResponse<NewProjMember>) => { .then((response: APIResponse<void>) => {
if (response.success) { if (response.success) {
alert("Member added"); alert("Member added");
added = true; added = true;

View file

@ -1,6 +1,6 @@
import { useState } from "react"; import { useState } from "react";
import { APIResponse, api } from "../API/API"; import { APIResponse, api } from "../API/API";
import { NewProject, Project } 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";
@ -10,27 +10,26 @@ import Button from "./Button";
* @param {Object} props - Project name and description * @param {Object} props - Project name and description
* @returns {boolean} True if created, false if not * @returns {boolean} True if created, false if not
*/ */
function CreateProject(props: { name: string; description: string }): boolean { function CreateProject(props: { name: string; description: string }): void {
const project: NewProject = { const project: NewProject = {
name: props.name, name: props.name,
description: props.description, description: props.description,
}; };
let created = false;
api api
.createProject(project, localStorage.getItem("accessToken") ?? "") .createProject(project, localStorage.getItem("accessToken") ?? "")
.then((response: APIResponse<Project>) => { .then((response: APIResponse<void>) => {
if (response.success) { if (response.success) {
created = true; alert("Project added!");
} else { } else {
alert("Project NOT added!");
console.error(response.message); console.error(response.message);
} }
}) })
.catch((error) => { .catch((error) => {
alert("Project NOT added!");
console.error("An error occurred during creation:", error); console.error("An error occurred during creation:", error);
}); });
return created;
} }
/** /**
@ -48,7 +47,10 @@ 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({ name: name, description: description }); CreateProject({
name: name,
description: description,
});
}} }}
> >
<img <img
@ -59,22 +61,26 @@ function AddProject(): JSX.Element {
<h3 className="pb-4 mb-2 text-center font-bold text-[18px]"> <h3 className="pb-4 mb-2 text-center font-bold text-[18px]">
Create a new project Create a new project
</h3> </h3>
<InputField <div className="space-y-3">
label="Name" <InputField
type="text" label="Name"
value={name} type="text"
onChange={(e) => { value={name}
setName(e.target.value); onChange={(e) => {
}} e.preventDefault();
/> setName(e.target.value);
<InputField }}
label="Description" />
type="text" <InputField
value={description} label="Description"
onChange={(e) => { type="text"
setDescription(e.target.value); value={description}
}} onChange={(e) => {
/> e.preventDefault();
setDescription(e.target.value);
}}
/>
</div>
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
<Button <Button
text="Create" text="Create"

View file

@ -1,15 +1,14 @@
import { useState } from "react"; import { useState } from "react";
import { NewProjMember } from "../Types/goTypes";
import Button from "./Button"; import Button from "./Button";
import GetAllUsers from "./GetAllUsers"; import GetAllUsers from "./GetAllUsers";
import AddMember from "./AddMember"; import AddMember, { NewProjMember } from "./AddMember";
import BackButton from "./BackButton"; import BackButton from "./BackButton";
/** /**
* 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(): JSX.Element { function AddUserToProject(props: { projectName: string }): JSX.Element {
const [name, setName] = useState(""); const [name, setName] = useState("");
const [users, setUsers] = useState<string[]>([]); const [users, setUsers] = useState<string[]>([]);
const [role, setRole] = useState(""); const [role, setRole] = useState("");
@ -18,7 +17,7 @@ function AddUserToProject(): JSX.Element {
const handleClick = (): boolean => { const handleClick = (): boolean => {
const newMember: NewProjMember = { const newMember: NewProjMember = {
username: name, username: name,
projectname: localStorage.getItem("projectName") ?? "", projectname: props.projectName,
role: role, role: role,
}; };
return AddMember({ memberToAdd: newMember }); return AddMember({ memberToAdd: newMember });
@ -33,13 +32,13 @@ function AddUserToProject(): JSX.Element {
Role chosen: [{role}] Role chosen: [{role}]
</p> </p>
<p className="pb-4 mb-2 text-center font-bold text-[18px]"> <p className="pb-4 mb-2 text-center font-bold text-[18px]">
Project chosen: [{localStorage.getItem("projectName") ?? ""}] Project chosen: [{props.projectName}]
</p> </p>
<p className="p-1">Choose role:</p> <p className="p-1">Choose role:</p>
<div className="border-2 border-black p-2 rounded-xl text-center h-[10h] w-[16vh]"> <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"> <ul className="text-center items-center font-medium space-y-2">
<li <li
className="h-[10h] w-[14vh] 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="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={() => { onClick={() => {
setRole("member"); setRole("member");
}} }}
@ -47,7 +46,7 @@ function AddUserToProject(): JSX.Element {
{"Member"} {"Member"}
</li> </li>
<li <li
className="h-[10h] w-[14vh] 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="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={() => { onClick={() => {
setRole("project_manager"); setRole("project_manager");
}} }}

View file

@ -1,8 +1,9 @@
//Info: This component is used to display all the time reports for a project. It will display the week number, //Info: This component is used to display all the time reports for a project. It will display the week number,
//total time spent, and if the report has been signed or not. The user can click on a report to edit it. //total time spent, and if the report has been signed or not. The user can click on a report to edit it.
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import { NewWeeklyReport } from "../Types/goTypes"; import { WeeklyReport } from "../Types/goTypes";
import { Link, useParams } from "react-router-dom"; import { Link, useParams } from "react-router-dom";
import { api } from "../API/API";
/** /**
* Renders a component that displays all the time reports for a specific project. * Renders a component that displays all the time reports for a specific project.
@ -11,14 +12,14 @@ import { Link, useParams } from "react-router-dom";
function AllTimeReportsInProject(): JSX.Element { function AllTimeReportsInProject(): JSX.Element {
const { username } = useParams(); const { username } = useParams();
const { projectName } = useParams(); const { projectName } = useParams();
const [weeklyReports, setWeeklyReports] = useState<NewWeeklyReport[]>([]); const [weeklyReports, setWeeklyReports] = useState<WeeklyReport[]>([]);
/* // Call getProjects when the component mounts
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.getWeeklyReportsForDifferentUser(
projectName ?? "", projectName ?? "",
username ?? "",
token, token,
); );
console.log(response); console.log(response);
@ -27,39 +28,8 @@ function AllTimeReportsInProject(): JSX.Element {
} else { } else {
console.error(response.message); console.error(response.message);
} }
}; */ };
// Mock data
const getWeeklyReports = async (): Promise<void> => {
// Simulate a delay
await Promise.resolve();
const mockWeeklyReports: NewWeeklyReport[] = [
{
projectName: "Project 1",
week: 1,
developmentTime: 10,
meetingTime: 2,
adminTime: 1,
ownWorkTime: 3,
studyTime: 4,
testingTime: 5,
},
{
projectName: "Project 1",
week: 2,
developmentTime: 8,
meetingTime: 2,
adminTime: 1,
ownWorkTime: 3,
studyTime: 4,
testingTime: 5,
},
// Add more reports as needed
];
// Use the mock data instead of the real data
setWeeklyReports(mockWeeklyReports);
};
useEffect(() => {
void getWeeklyReports(); void getWeeklyReports();
}, []); }, []);

View file

@ -0,0 +1,37 @@
import { APIResponse, api } from "../API/API";
export interface ProjectRoleChange {
username: string;
role: "project_manager" | "member" | "";
projectname: string;
}
export default function ChangeRole(roleChangeInfo: ProjectRoleChange): void {
if (
roleChangeInfo.username === "" ||
roleChangeInfo.role === "" ||
roleChangeInfo.projectname === ""
) {
// FOR DEBUG
// console.log(roleChangeInfo.role + ": Role");
// console.log(roleChangeInfo.projectname + ": P-Name");
// console.log(roleChangeInfo.username + ": U-name");
alert("You have to select a role");
return;
}
api
.changeUserRole(roleChangeInfo, localStorage.getItem("accessToken") ?? "")
.then((response: APIResponse<void>) => {
if (response.success) {
alert("Role changed successfully");
location.reload();
} else {
alert(response.message);
console.error(response.message);
}
})
.catch((error) => {
alert(error);
console.error("An error occurred during change:", error);
});
}

View file

@ -0,0 +1,73 @@
import { useState } from "react";
import Button from "./Button";
import ChangeRole, { ProjectRoleChange } from "./ChangeRole";
export default function ChangeRoles(props: {
projectName: string;
username: string;
}): JSX.Element {
const [selectedRole, setSelectedRole] = useState<
"project_manager" | "member" | ""
>("");
const handleRoleChange = (
event: React.ChangeEvent<HTMLInputElement>,
): void => {
if (event.target.value === "member") {
setSelectedRole(event.target.value);
} else if (event.target.value === "project_manager") {
setSelectedRole(event.target.value);
}
};
const handleSubmit = (event: React.FormEvent<HTMLFormElement>): void => {
event.preventDefault();
const roleChangeInfo: ProjectRoleChange = {
username: props.username,
projectname: props.projectName,
role: selectedRole,
};
ChangeRole(roleChangeInfo);
};
return (
<div className="overflow-auto rounded-lg">
<h1 className="font-bold text-[20px]">Select role:</h1>
<form onSubmit={handleSubmit}>
<div className="h-[7vh] self-start text-left font-medium overflow-auto border-2 border-black rounded-lg p-2">
<div className="hover:font-bold">
<label>
<input
type="radio"
value="project_manager"
checked={selectedRole === "project_manager"}
onChange={handleRoleChange}
className="ml-2 mr-2 mb-3"
/>
Project manager
</label>
</div>
<div className="hover:font-bold">
<label>
<input
type="radio"
value="member"
checked={selectedRole === "member"}
onChange={handleRoleChange}
className="ml-2 mr-2"
/>
Member
</label>
</div>
</div>
<Button
text="Change"
onClick={(): void => {
return;
}}
type="submit"
/>
</form>
</div>
);
}

View file

@ -1,61 +1,26 @@
import React, { useState } from "react"; import { APIResponse, api } from "../API/API";
import InputField from "./InputField"; import { StrNameChange } from "../Types/goTypes";
import { api } from "../API/API";
function ChangeUsername(): JSX.Element {
const [newUsername, setNewUsername] = useState("");
const [errorMessage, setErrorMessage] = useState("");
const handleChange = (e: React.ChangeEvent<HTMLInputElement>): void => {
setNewUsername(e.target.value);
};
const handleSubmit = async (): Promise<void> => {
try {
// Call the API function to change the username
const token = localStorage.getItem("accessToken");
if (!token) {
throw new Error("Access token not found");
}
const response = await api.changeUserName(
{ prevName: "currentName", newName: newUsername },
token,
);
function ChangeUsername(props: { nameChange: StrNameChange }): void {
if (props.nameChange.newName === "") {
alert("You have to select a new name");
return;
}
api
.changeUserName(props.nameChange, localStorage.getItem("accessToken") ?? "")
.then((response: APIResponse<void>) => {
if (response.success) { if (response.success) {
// Optionally, add a success message or redirect the user alert("Name changed successfully");
console.log("Username changed successfully"); location.reload();
} else { } else {
// Handle the error message alert("Name not changed");
console.error("Failed to change username:", response.message); console.error(response.message);
setErrorMessage(response.message ?? "Failed to change username");
} }
} catch (error) { })
console.error("Error changing username:", error); .catch((error) => {
// Optionally, handle the error alert("Name not changed");
setErrorMessage("Failed to change username"); console.error("An error occurred during change:", error);
}
};
const handleButtonClick = (): void => {
handleSubmit().catch((error) => {
console.error("Error in handleSubmit:", error);
}); });
};
return (
<div>
<InputField
label="New Username"
type="text"
value={newUsername}
onChange={handleChange}
/>
{errorMessage && <div>{errorMessage}</div>}
<button onClick={handleButtonClick}>Update Username</button>
</div>
);
} }
export default ChangeUsername; export default ChangeUsername;

View file

@ -0,0 +1,33 @@
import { api, APIResponse } from "../API/API";
/**
* Use to delete a project from the system
* @param {string} props.projectToDelete - The projectname of project to delete
* @returns {void} Nothing
* @example
* const exampleProjectName = "project";
* DeleteProject({ projectToDelete: exampleProjectName });
*/
function DeleteProject(props: { projectToDelete: string }): void {
api
.removeProject(
props.projectToDelete,
localStorage.getItem("accessToken") ?? "",
)
.then((response: APIResponse<string>) => {
if (response.success) {
alert("Project has been deleted!");
location.reload();
} else {
alert("Project has not been deleted");
console.error(response.message);
}
})
.catch((error) => {
alert("project has not been deleted");
console.error("An error occurred during deletion:", error);
});
}
export default DeleteProject;

View file

@ -3,7 +3,7 @@ import { api, APIResponse } from "../API/API";
/** /**
* Use to remove a user from the system * Use to remove a user from the system
* @param props - The username of user to remove * @param {string} props.usernameToDelete - The username of user to remove
* @returns {boolean} True if removed, false if not * @returns {boolean} True if removed, false if not
* @example * @example
* const exampleUsername = "user"; * const exampleUsername = "user";
@ -29,7 +29,7 @@ function DeleteUser(props: { usernameToDelete: string }): boolean {
}) })
.catch((error) => { .catch((error) => {
alert("User has not been deleted"); alert("User has not been deleted");
console.error("An error occurred during creation:", error); console.error("An error occurred during deletion:", error);
}); });
return removed; return removed;
} }

View file

@ -1,12 +1,7 @@
import { useState, useEffect } from "react"; import { useState, useEffect } from "react";
import { Link, useParams } from "react-router-dom"; import { Link, useParams } from "react-router-dom";
import { api } from "../API/API";
interface UnsignedReports { import { WeeklyReport } from "../Types/goTypes";
projectName: string;
username: string;
week: number;
signed: boolean;
}
/** /**
* Renders a component that displays the projects a user is a part of and links to the projects start-page. * Renders a component that displays the projects a user is a part of and links to the projects start-page.
@ -14,80 +9,25 @@ interface UnsignedReports {
*/ */
function DisplayUserProject(): JSX.Element { function DisplayUserProject(): JSX.Element {
const { projectName } = useParams(); const { projectName } = useParams();
const [unsignedReports, setUnsignedReports] = useState<UnsignedReports[]>([]); const [unsignedReports, setUnsignedReports] = useState<WeeklyReport[]>([]);
//const navigate = useNavigate(); //const navigate = useNavigate();
// const getUnsignedReports = async (): Promise<void> => {
// const token = localStorage.getItem("accessToken") ?? "";
// const response = await api.getUserProjects(token);
// console.log(response);
// if (response.success) {
// setUnsignedReports(response.data ?? []);
// } else {
// console.error(response.message);
// }
// };
// const handleReportClick = async (projectName: string): Promise<void> => {
// const username = localStorage.getItem("username") ?? "";
// const token = localStorage.getItem("accessToken") ?? "";
// const response = await api.checkIfProjectManager(
// username,
// projectName,
// token,
// );
// if (response.success) {
// if (response.data) {
// navigate(`/PMProjectPage/${projectName}`);
// } else {
// navigate(`/project/${projectName}`);
// }
// } else {
// // handle error
// console.error(response.message);
// }
// };
const getUnsignedReports = async (): Promise<void> => {
// Simulate a delay
await Promise.resolve();
// Use mock data
const reports: UnsignedReports[] = [
{
projectName: "projecttest",
username: "user1",
week: 2,
signed: false,
},
{
projectName: "projecttest",
username: "user2",
week: 2,
signed: false,
},
{
projectName: "projecttest",
username: "user3",
week: 2,
signed: false,
},
{
projectName: "projecttest",
username: "user4",
week: 2,
signed: false,
},
];
// Set the state with the mock data
setUnsignedReports(reports);
};
// Call getProjects when the component mounts
useEffect(() => { useEffect(() => {
const getUnsignedReports = async (): Promise<void> => {
const token = localStorage.getItem("accessToken") ?? "";
const response = await api.getUnsignedReportsInProject(
projectName ?? "",
token,
);
console.log(response);
if (response.success) {
setUnsignedReports(response.data ?? []);
} else {
console.error(response.message);
}
};
void getUnsignedReports(); void getUnsignedReports();
}, []); }, [projectName]); // Include 'projectName' in the dependency array
return ( return (
<> <>
@ -95,32 +35,40 @@ function DisplayUserProject(): JSX.Element {
All Unsigned Reports In: {projectName}{" "} All Unsigned Reports In: {projectName}{" "}
</h1> </h1>
<div className="border-4 border-black bg-white flex flex-col items-center justify-center min-h-[65vh] h-fit w-[70vw] rounded-3xl content-center overflow-scroll space-y-[10vh] p-[30px] text-[20px]"> <div className="border-4 border-black bg-white flex flex-col items-center justify-center min-h-[65vh] h-fit w-[70vw] rounded-3xl content-center overflow-scroll space-y-[10vh] p-[30px] text-[20px]">
{unsignedReports.map( {unsignedReports.map((unsignedReport: WeeklyReport, index: number) => (
(unsignedReport: UnsignedReports, index: number) => ( <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>
<h1>{unsignedReport.username}</h1> <h1>{unsignedReport.userId}</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">Signed:</span> <span className="ml-6 mr-2 font-bold">Total Time:</span>
<h1>NO</h1> <h1>
</div> {unsignedReport.developmentTime +
<div className="flex"> unsignedReport.meetingTime +
<div className="ml-auto flex space-x-4"> unsignedReport.adminTime +
<Link unsignedReport.ownWorkTime +
to={`/PMViewUnsignedReport/${projectName}/${unsignedReport.username}/${unsignedReport.week}`} unsignedReport.studyTime +
> unsignedReport.testingTime}
<h1 className="underline cursor-pointer font-bold"> </h1>
View Report <span className="ml-6 mr-2 font-bold">Signed:</span>
</h1> <h1>NO</h1>
</Link> </div>
</div> <div className="flex">
<div className="ml-auto flex space-x-4">
<Link
to={`/PMViewUnsignedReport/${projectName}/${unsignedReport.userId}/${unsignedReport.week}`}
>
<h1 className="underline cursor-pointer font-bold">
View Report
</h1>
</Link>
</div> </div>
</div> </div>
</h1> </div>
), </h1>
)} ))}
</div> </div>
</> </>
); );

View file

@ -1,6 +1,7 @@
import { useState, useEffect } from "react"; import { useState } from "react";
import { Project } from "../Types/goTypes"; import { Project } from "../Types/goTypes";
import { useNavigate } from "react-router-dom"; import { useNavigate } from "react-router-dom";
import GetProjects from "./GetProjects";
import { api } from "../API/API"; import { api } from "../API/API";
/** /**
@ -11,22 +12,20 @@ function DisplayUserProject(): JSX.Element {
const [projects, setProjects] = useState<Project[]>([]); const [projects, setProjects] = useState<Project[]>([]);
const navigate = useNavigate(); const navigate = useNavigate();
const getProjects = async (): Promise<void> => { GetProjects({
const token = localStorage.getItem("accessToken") ?? ""; setProjectsProp: setProjects,
const response = await api.getUserProjects(token); username: localStorage.getItem("username") ?? "",
console.log(response); });
if (response.success) {
setProjects(response.data ?? []);
} else {
console.error(response.message);
}
};
const handleProjectClick = async (projectName: string): Promise<void> => { const handleProjectClick = async (projectName: string): Promise<void> => {
const token = localStorage.getItem("accessToken") ?? ""; const token = localStorage.getItem("accessToken") ?? "";
const response = await api.checkIfProjectManager(projectName, token); const response = await api.checkIfProjectManager(projectName, token);
console.log(response.data);
if (response.success) { if (response.success) {
if (response.data) { if (
(response.data as unknown as { isProjectManager: boolean })
.isProjectManager
) {
navigate(`/PMProjectPage/${projectName}`); navigate(`/PMProjectPage/${projectName}`);
} else { } else {
navigate(`/project/${projectName}`); navigate(`/project/${projectName}`);
@ -37,11 +36,6 @@ function DisplayUserProject(): JSX.Element {
} }
}; };
// Call getProjects when the component mounts
useEffect(() => {
void getProjects();
}, []);
return ( return (
<> <>
<h1 className="font-bold text-[30px] mb-[20px]">Your Projects</h1> <h1 className="font-bold text-[30px] mb-[20px]">Your Projects</h1>

View file

@ -1,5 +1,5 @@
import { useState, useEffect } from "react"; import { useState, useEffect } from "react";
import { WeeklyReport, NewWeeklyReport } from "../Types/goTypes"; import { WeeklyReport, UpdateWeeklyReport } from "../Types/goTypes";
import { api } from "../API/API"; import { api } from "../API/API";
import { useNavigate, useParams } from "react-router-dom"; import { useNavigate, useParams } from "react-router-dom";
import Button from "./Button"; import Button from "./Button";
@ -22,6 +22,7 @@ export default function GetWeeklyReport(): JSX.Element {
projectName: string; projectName: string;
fetchedWeek: string; fetchedWeek: string;
}>(); }>();
const username = localStorage.getItem("userName") ?? "";
console.log(projectName, fetchedWeek); console.log(projectName, fetchedWeek);
useEffect(() => { useEffect(() => {
@ -60,8 +61,9 @@ export default function GetWeeklyReport(): JSX.Element {
void fetchWeeklyReport(); void fetchWeeklyReport();
}, [projectName, fetchedWeek, token]); }, [projectName, fetchedWeek, token]);
const handleNewWeeklyReport = async (): Promise<void> => { const handleUpdateWeeklyReport = async (): Promise<void> => {
const newWeeklyReport: NewWeeklyReport = { const updateWeeklyReport: UpdateWeeklyReport = {
userName: username,
projectName: projectName ?? "", projectName: projectName ?? "",
week, week,
developmentTime, developmentTime,
@ -72,7 +74,7 @@ export default function GetWeeklyReport(): JSX.Element {
testingTime, testingTime,
}; };
await api.submitWeeklyReport(newWeeklyReport, token); await api.updateWeeklyReport(updateWeeklyReport, token);
}; };
const navigate = useNavigate(); const navigate = useNavigate();
@ -89,7 +91,8 @@ export default function GetWeeklyReport(): JSX.Element {
return; return;
} }
e.preventDefault(); e.preventDefault();
void handleNewWeeklyReport(); void handleUpdateWeeklyReport();
alert("Changes submitted");
navigate(-1); navigate(-1);
}} }}
> >
@ -128,7 +131,12 @@ export default function GetWeeklyReport(): JSX.Element {
}} }}
onKeyDown={(event) => { onKeyDown={(event) => {
const keyValue = event.key; const keyValue = event.key;
if (!/\d/.test(keyValue) && keyValue !== "Backspace") if (
!/\d/.test(keyValue) &&
keyValue !== "Backspace" &&
keyValue !== "ArrowLeft" &&
keyValue !== "ArrowRight"
)
event.preventDefault(); event.preventDefault();
}} }}
/> />
@ -152,7 +160,12 @@ export default function GetWeeklyReport(): JSX.Element {
}} }}
onKeyDown={(event) => { onKeyDown={(event) => {
const keyValue = event.key; const keyValue = event.key;
if (!/\d/.test(keyValue) && keyValue !== "Backspace") if (
!/\d/.test(keyValue) &&
keyValue !== "Backspace" &&
keyValue !== "ArrowLeft" &&
keyValue !== "ArrowRight"
)
event.preventDefault(); event.preventDefault();
}} }}
/> />
@ -176,7 +189,12 @@ export default function GetWeeklyReport(): JSX.Element {
}} }}
onKeyDown={(event) => { onKeyDown={(event) => {
const keyValue = event.key; const keyValue = event.key;
if (!/\d/.test(keyValue) && keyValue !== "Backspace") if (
!/\d/.test(keyValue) &&
keyValue !== "Backspace" &&
keyValue !== "ArrowLeft" &&
keyValue !== "ArrowRight"
)
event.preventDefault(); event.preventDefault();
}} }}
/> />
@ -200,7 +218,12 @@ export default function GetWeeklyReport(): JSX.Element {
}} }}
onKeyDown={(event) => { onKeyDown={(event) => {
const keyValue = event.key; const keyValue = event.key;
if (!/\d/.test(keyValue) && keyValue !== "Backspace") if (
!/\d/.test(keyValue) &&
keyValue !== "Backspace" &&
keyValue !== "ArrowLeft" &&
keyValue !== "ArrowRight"
)
event.preventDefault(); event.preventDefault();
}} }}
/> />
@ -224,7 +247,12 @@ export default function GetWeeklyReport(): JSX.Element {
}} }}
onKeyDown={(event) => { onKeyDown={(event) => {
const keyValue = event.key; const keyValue = event.key;
if (!/\d/.test(keyValue) && keyValue !== "Backspace") if (
!/\d/.test(keyValue) &&
keyValue !== "Backspace" &&
keyValue !== "ArrowLeft" &&
keyValue !== "ArrowRight"
)
event.preventDefault(); event.preventDefault();
}} }}
/> />
@ -248,7 +276,12 @@ export default function GetWeeklyReport(): JSX.Element {
}} }}
onKeyDown={(event) => { onKeyDown={(event) => {
const keyValue = event.key; const keyValue = event.key;
if (!/\d/.test(keyValue) && keyValue !== "Backspace") if (
!/\d/.test(keyValue) &&
keyValue !== "Backspace" &&
keyValue !== "ArrowLeft" &&
keyValue !== "ArrowRight"
)
event.preventDefault(); event.preventDefault();
}} }}
/> />

View file

@ -0,0 +1,59 @@
import { Dispatch, SetStateAction, useEffect } from "react";
import { api } from "../API/API";
/**
* Interface for reported time per category + total time reported
*/
export interface projectTimes {
admin: number;
development: number;
meeting: number;
own_work: number;
study: number;
testing: number;
totalTime?: number;
}
/**
* Gets all reported times for this project
* @param {Dispatch} props.setTimesProp - A setStateAction for the map you want to put times in
* @param {string} props.projectName - Username
* @returns {void} Nothing
* @example
* const projectName = "Example";
* const [times, setTimes] = useState<Times>();
* GetProjectTimes({ setTimesProp: setTimes, projectName: projectName });
*/
function GetProjectTimes(props: {
setTimesProp: Dispatch<SetStateAction<projectTimes | undefined>>;
projectName: string;
}): void {
const setTimes: Dispatch<SetStateAction<projectTimes | undefined>> =
props.setTimesProp;
useEffect(() => {
const fetchUsers = async (): Promise<void> => {
try {
const token = localStorage.getItem("accessToken") ?? "";
const response = await api.getProjectTimes(props.projectName, token);
if (response.success && response.data) {
// Calculates total time reported
response.data.totalTime = response.data.admin;
response.data.totalTime += response.data.development;
response.data.totalTime += response.data.meeting;
response.data.totalTime += response.data.own_work;
response.data.totalTime += response.data.study;
response.data.totalTime += response.data.testing;
setTimes(response.data);
} else {
console.error("Failed to fetch project times:", response.message);
}
} catch (error) {
console.error("Error fetching times:", error);
}
};
void fetchUsers();
}, [props.projectName, setTimes]);
}
export default GetProjectTimes;

View file

@ -4,14 +4,17 @@ import { api } from "../API/API";
/** /**
* Gets all projects that user is a member of * Gets all projects that user is a member of
* @param props - A setStateAction for the array you want to put projects in * @param {Dispatch} props.setProjectsProp - A setStateAction for the array you want to put projects in
* @param {string} props.username - Username
* @returns {void} Nothing * @returns {void} Nothing
* @example * @example
* const username = "Example";
* const [projects, setProjects] = useState<Project[]>([]); * const [projects, setProjects] = useState<Project[]>([]);
* GetAllUsers({ setProjectsProp: setProjects }); * GetProjects({ setProjectsProp: setProjects, username: username });
*/ */
function GetProjects(props: { function GetProjects(props: {
setProjectsProp: Dispatch<React.SetStateAction<Project[]>>; setProjectsProp: Dispatch<React.SetStateAction<Project[]>>;
username: string;
}): void { }): void {
const setProjects: Dispatch<React.SetStateAction<Project[]>> = const setProjects: Dispatch<React.SetStateAction<Project[]>> =
props.setProjectsProp; props.setProjectsProp;
@ -19,7 +22,7 @@ function GetProjects(props: {
const fetchUsers = async (): Promise<void> => { const fetchUsers = async (): Promise<void> => {
try { try {
const token = localStorage.getItem("accessToken") ?? ""; const token = localStorage.getItem("accessToken") ?? "";
const response = await api.getUserProjects(token); const response = await api.getUserProjects(props.username, token);
if (response.success) { if (response.success) {
setProjects(response.data ?? []); setProjects(response.data ?? []);
} else { } else {
@ -31,7 +34,7 @@ function GetProjects(props: {
}; };
void fetchUsers(); void fetchUsers();
}, [setProjects]); }, [props.username, setProjects]);
} }
export default GetProjects; export default GetProjects;

View file

@ -1,20 +1,25 @@
import { Dispatch, useEffect } from "react"; import { Dispatch, useEffect } from "react";
import { UserProjectMember } from "../Types/goTypes";
import { api } from "../API/API"; import { api } from "../API/API";
export interface ProjectMember {
Username: string;
UserRole: string;
}
/** /**
* Gets all projects that user is a member of * Gets all members of a project
* @param props - A setStateAction for the array you want to put projects in * @param string - The project's name
* @param Dispatch - A setStateAction for the array you want to put members in
* @returns {void} Nothing * @returns {void} Nothing
* @example * @example
* const [projects, setProjects] = useState<Project[]>([]); * const [users, setUsers] = useState<User[]>([]);
* GetAllUsers({ setProjectsProp: setProjects }); * GetUsersInProject({ projectName: props.projectname, setUsersProp: setUsers });
*/ */
function GetUsersInProject(props: { function GetUsersInProject(props: {
projectName: string; projectName: string;
setUsersProp: Dispatch<React.SetStateAction<UserProjectMember[]>>; setUsersProp: Dispatch<React.SetStateAction<ProjectMember[]>>;
}): void { }): void {
const setUsers: Dispatch<React.SetStateAction<UserProjectMember[]>> = const setUsers: Dispatch<React.SetStateAction<ProjectMember[]>> =
props.setUsersProp; props.setUsersProp;
useEffect(() => { useEffect(() => {
const fetchUsers = async (): Promise<void> => { const fetchUsers = async (): Promise<void> => {
@ -24,10 +29,10 @@ function GetUsersInProject(props: {
if (response.success) { if (response.success) {
setUsers(response.data ?? []); setUsers(response.data ?? []);
} else { } else {
console.error("Failed to fetch projects:", response.message); console.error("Failed to fetch members:", response.message);
} }
} catch (error) { } catch (error) {
console.error("Error fetching projects:", error); console.error("Error fetching members:", error);
} }
}; };
void fetchUsers(); void fetchUsers();

View file

@ -19,7 +19,7 @@ function InputField(props: {
onChange: (e: React.ChangeEvent<HTMLInputElement>) => void; onChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
}): JSX.Element { }): JSX.Element {
return ( return (
<div className="mb-4"> <div className="">
<label <label
className="block text-gray-700 text-sm font-sans font-bold mb-2" className="block text-gray-700 text-sm font-sans font-bold mb-2"
htmlFor={props.label} htmlFor={props.label}

View file

@ -25,22 +25,24 @@ function Login(props: {
}): JSX.Element { }): JSX.Element {
return ( return (
<form className="flex flex-col items-center" onSubmit={props.handleSubmit}> <form className="flex flex-col items-center" onSubmit={props.handleSubmit}>
<InputField <div className="space-y-3">
type="text" <InputField
label="Username" type="text"
onChange={(e) => { label="Username"
props.setUsername(e.target.value); onChange={(e) => {
}} props.setUsername(e.target.value);
value={props.username} }}
/> value={props.username}
<InputField />
type="password" <InputField
label="Password" type="password"
onChange={(e) => { label="Password"
props.setPassword(e.target.value); onChange={(e) => {
}} props.setPassword(e.target.value);
value={props.password} }}
/> value={props.password}
/>
</div>
<Button <Button
text="Login" text="Login"
onClick={(): void => { onClick={(): void => {

View file

@ -0,0 +1,72 @@
import Button from "./Button";
import DeleteUser from "./DeleteUser";
import UserProjectListAdmin from "./UserProjectListAdmin";
import { useState } from "react";
import ChangeRoleView from "./ChangeRoleView";
function MemberInfoModal(props: {
projectName: string;
username: string;
onClose: () => void;
}): JSX.Element {
const [showRoles, setShowRoles] = useState(false);
const handleChangeRole = (): void => {
if (showRoles) {
setShowRoles(false);
} else {
setShowRoles(true);
}
};
return (
<div
className="fixed inset-0 bg-black bg-opacity-30 backdrop-blur-sm
flex justify-center items-center"
>
<div className="border-4 border-black bg-white rounded-lg text-center flex flex-col">
<div className="mx-10">
<p className="font-bold text-[30px]">{props.username}</p>
<p
className="hover:font-bold hover:cursor-pointer underline"
onClick={handleChangeRole}
>
(Change Role)
</p>
{showRoles && (
<ChangeRoleView
projectName={props.projectName}
username={props.username}
/>
)}
<h2 className="font-bold text-[20px]">Member of these projects:</h2>
<UserProjectListAdmin username={props.username} />
<div className="items-center space-x-6">
<Button
text={"Delete"}
onClick={function (): void {
if (
window.confirm("Are you sure you want to delete this user?")
) {
DeleteUser({
usernameToDelete: props.username,
});
}
}}
type="button"
/>
<Button
text={"Close"}
onClick={function (): void {
setShowRoles(false);
props.onClose();
}}
type="button"
/>
</div>
</div>
</div>
</div>
);
}
export default MemberInfoModal;

View file

@ -139,7 +139,12 @@ export default function NewWeeklyReport(): JSX.Element {
}} }}
onKeyDown={(event) => { onKeyDown={(event) => {
const keyValue = event.key; const keyValue = event.key;
if (!/\d/.test(keyValue) && keyValue !== "Backspace") if (
!/\d/.test(keyValue) &&
keyValue !== "Backspace" &&
keyValue !== "ArrowLeft" &&
keyValue !== "ArrowRight"
)
event.preventDefault(); event.preventDefault();
}} }}
/> />
@ -163,7 +168,12 @@ export default function NewWeeklyReport(): JSX.Element {
}} }}
onKeyDown={(event) => { onKeyDown={(event) => {
const keyValue = event.key; const keyValue = event.key;
if (!/\d/.test(keyValue) && keyValue !== "Backspace") if (
!/\d/.test(keyValue) &&
keyValue !== "Backspace" &&
keyValue !== "ArrowLeft" &&
keyValue !== "ArrowRight"
)
event.preventDefault(); event.preventDefault();
}} }}
/> />
@ -187,7 +197,12 @@ export default function NewWeeklyReport(): JSX.Element {
}} }}
onKeyDown={(event) => { onKeyDown={(event) => {
const keyValue = event.key; const keyValue = event.key;
if (!/\d/.test(keyValue) && keyValue !== "Backspace") if (
!/\d/.test(keyValue) &&
keyValue !== "Backspace" &&
keyValue !== "ArrowLeft" &&
keyValue !== "ArrowRight"
)
event.preventDefault(); event.preventDefault();
}} }}
/> />
@ -211,7 +226,12 @@ export default function NewWeeklyReport(): JSX.Element {
}} }}
onKeyDown={(event) => { onKeyDown={(event) => {
const keyValue = event.key; const keyValue = event.key;
if (!/\d/.test(keyValue) && keyValue !== "Backspace") if (
!/\d/.test(keyValue) &&
keyValue !== "Backspace" &&
keyValue !== "ArrowLeft" &&
keyValue !== "ArrowRight"
)
event.preventDefault(); event.preventDefault();
}} }}
/> />
@ -235,7 +255,12 @@ export default function NewWeeklyReport(): JSX.Element {
}} }}
onKeyDown={(event) => { onKeyDown={(event) => {
const keyValue = event.key; const keyValue = event.key;
if (!/\d/.test(keyValue) && keyValue !== "Backspace") if (
!/\d/.test(keyValue) &&
keyValue !== "Backspace" &&
keyValue !== "ArrowLeft" &&
keyValue !== "ArrowRight"
)
event.preventDefault(); event.preventDefault();
}} }}
/> />
@ -259,7 +284,12 @@ export default function NewWeeklyReport(): JSX.Element {
}} }}
onKeyDown={(event) => { onKeyDown={(event) => {
const keyValue = event.key; const keyValue = event.key;
if (!/\d/.test(keyValue) && keyValue !== "Backspace") if (
!/\d/.test(keyValue) &&
keyValue !== "Backspace" &&
keyValue !== "ArrowLeft" &&
keyValue !== "ArrowRight"
)
event.preventDefault(); event.preventDefault();
}} }}
/> />

View file

@ -1,31 +1,54 @@
import { useState } from "react"; import { useEffect, useRef, useState } from "react";
import Button from "./Button"; import Button from "./Button";
import { UserProjectMember } from "../Types/goTypes"; import GetUsersInProject, { ProjectMember } from "./GetUsersInProject";
import GetUsersInProject from "./GetUsersInProject";
import { Link } from "react-router-dom"; import { Link } from "react-router-dom";
import GetProjectTimes, { projectTimes } from "./GetProjectTimes";
import DeleteProject from "./DeleteProject";
function ProjectInfoModal(props: { function ProjectInfoModal(props: {
isVisible: boolean;
projectname: string; projectname: string;
onClose: () => void; onClose: () => void;
onClick: (username: string) => void; onClick: (username: string) => void;
}): JSX.Element { }): JSX.Element {
const [users, setUsers] = useState<UserProjectMember[]>([]); const [users, setUsers] = useState<ProjectMember[]>([]);
const [times, setTimes] = useState<projectTimes>();
const totalTime = useRef(0);
GetUsersInProject({ projectName: props.projectname, setUsersProp: setUsers }); GetUsersInProject({ projectName: props.projectname, setUsersProp: setUsers });
if (!props.isVisible) return <></>;
GetProjectTimes({ setTimesProp: setTimes, projectName: props.projectname });
useEffect(() => {
if (times?.totalTime !== undefined) {
totalTime.current = times.totalTime;
}
}, [times]);
return ( return (
<div <div
className="fixed inset-0 bg-black bg-opacity-30 backdrop-blur-sm className="fixed inset-0 bg-black bg-opacity-30 backdrop-blur-sm
flex justify-center items-center" flex justify-center items-center"
> >
<div className="border-4 border-black bg-white p-2 rounded-2xl text-center h-[47vh] w-[40] flex flex-col"> <div className="border-4 border-black bg-white p-2 rounded-2xl text-center h-[61vh] w-[40] overflow-auto">
<div className="pl-20 pr-20"> <div className="pl-10 pr-10">
<h1 className="font-bold text-[32px] mb-[20px]"> <h1 className="font-bold text-[32px] mb-[20px]">
{localStorage.getItem("projectName") ?? ""} {props.projectname}
</h1> </h1>
<h2 className="font-bold text-[24px] mb-[20px]">Project members:</h2> <div className="p-1 text-center">
<div className="border-2 border-black p-2 rounded-lg text-center overflow-scroll h-[26vh]"> <h2 className="text-[20px] font-bold">Statistics:</h2>
</div>
<div className="border-2 border-black rounded-lg h-[8vh] text-left divide-y-2 flex flex-col overflow-auto mx-10">
<p className="p-2">Number of members: {users.length}</p>
<p className="p-2">
Total time reported:{" "}
{Math.floor(totalTime.current / 60 / 24) + " d "}
{Math.floor((totalTime.current / 60) % 24) + " h "}
{(totalTime.current % 60) + " m "}
</p>
</div>
<div className="h-[6vh] p-7 text-center">
<h2 className="text-[20px] font-bold">Project members:</h2>
</div>
<div className="border-2 border-black p-2 rounded-lg text-center overflow-auto h-[24vh] mx-10">
<ul className="text-left font-medium space-y-2"> <ul className="text-left font-medium space-y-2">
<div></div> <div></div>
{users.map((user) => ( {users.map((user) => (
@ -45,31 +68,44 @@ function ProjectInfoModal(props: {
))} ))}
</ul> </ul>
</div> </div>
</div> <div className="space-x-5 my-2">
<div className="space-x-16">
<Button
text={"Delete"}
onClick={function (): void {
//DELETE PROJECT
}}
type="button"
/>
<Link to={"/adminProjectAddMember"}>
<Button <Button
text={"Add Member"} text={"Delete"}
onClick={function (): void { onClick={function (): void {
return; if (
window.confirm(
"Are you sure you want to delete this project?",
)
) {
DeleteProject({
projectToDelete: props.projectname,
});
}
}} }}
type="button" type="button"
/> />
</Link> <Link
<Button to={{
text={"Close"} pathname: "/adminProjectAddMember",
onClick={function (): void { search: props.projectname,
props.onClose(); }}
}} >
type="button" <Button
/> text={"Add Member"}
onClick={function (): void {
return;
}}
type="button"
/>
</Link>
<Button
text={"Close"}
onClick={function (): void {
props.onClose();
}}
type="button"
/>
</div>
</div> </div>
</div> </div>
</div> </div>

View file

@ -1,7 +1,7 @@
import { useState } from "react"; import { useState } from "react";
import { NewProject } from "../Types/goTypes"; import { NewProject } from "../Types/goTypes";
import ProjectInfoModal from "./ProjectInfoModal"; import ProjectInfoModal from "./ProjectInfoModal";
import UserInfoModal from "./UserInfoModal"; import MemberInfoModal from "./MemberInfoModal";
/** /**
* A list of projects for admin manage projects page, that sets an onClick * A list of projects for admin manage projects page, that sets an onClick
@ -18,7 +18,7 @@ export function ProjectListAdmin(props: {
projects: NewProject[]; projects: NewProject[];
}): JSX.Element { }): JSX.Element {
const [projectModalVisible, setProjectModalVisible] = useState(false); const [projectModalVisible, setProjectModalVisible] = useState(false);
const [projectname, setProjectname] = useState(""); const [projectName, setProjectName] = useState("");
const [userModalVisible, setUserModalVisible] = useState(false); const [userModalVisible, setUserModalVisible] = useState(false);
const [username, setUsername] = useState(""); const [username, setUsername] = useState("");
@ -28,39 +28,36 @@ export function ProjectListAdmin(props: {
}; };
const handleClickProject = (projectname: string): void => { const handleClickProject = (projectname: string): void => {
setProjectname(projectname); setProjectName(projectname);
localStorage.setItem("projectName", projectname);
setProjectModalVisible(true); setProjectModalVisible(true);
}; };
const handleCloseProject = (): void => { const handleCloseProject = (): void => {
setProjectname(""); setProjectName("");
setProjectModalVisible(false); setProjectModalVisible(false);
}; };
const handleCloseUser = (): void => { const handleCloseUser = (): void => {
setProjectname(""); setUsername("");
setUserModalVisible(false); setUserModalVisible(false);
}; };
return ( return (
<> <>
<ProjectInfoModal {projectModalVisible && (
onClose={handleCloseProject} <ProjectInfoModal
onClick={handleClickUser} onClose={handleCloseProject}
isVisible={projectModalVisible} onClick={handleClickUser}
projectname={projectname} projectname={projectName}
/> />
<UserInfoModal )}
manageMember={true} {userModalVisible && (
onClose={handleCloseUser} <MemberInfoModal
//TODO: CHANGE TO REMOVE USER FROM PROJECT onClose={handleCloseUser}
onDelete={() => { username={username}
return; projectName={projectName}
}} />
isVisible={userModalVisible} )}
username={username}
/>
<div> <div>
<ul className="font-bold underline text-[30px] cursor-pointer padding"> <ul className="font-bold underline text-[30px] cursor-pointer padding">
{props.projects.map((project) => ( {props.projects.map((project) => (

View file

@ -1,31 +1,15 @@
import { useEffect, useState } from "react"; import { useState } from "react";
import { Link, useParams } from "react-router-dom"; import { Link, useParams } from "react-router-dom";
import { api } from "../API/API"; import GetUsersInProject, { ProjectMember } from "./GetUsersInProject";
import { UserProjectMember } from "../Types/goTypes";
function ProjectMembers(): JSX.Element { function ProjectMembers(): JSX.Element {
const { projectName } = useParams(); const { projectName } = useParams();
const [projectMembers, setProjectMembers] = useState<UserProjectMember[]>([]); const [projectMembers, setProjectMembers] = useState<ProjectMember[]>([]);
useEffect(() => { GetUsersInProject({
const getProjectMembers = async (): Promise<void> => { projectName: projectName ?? "",
const token = localStorage.getItem("accessToken") ?? ""; setUsersProp: setProjectMembers,
const response = await api.getAllUsersProject(projectName ?? "", token); });
console.log(response);
if (response.success) {
setProjectMembers(response.data ?? []);
} else {
console.error(response.message);
}
};
void getProjectMembers();
}, [projectName]);
interface ProjectMember {
Username: string;
UserRole: string;
}
return ( return (
<> <>

View file

@ -22,6 +22,8 @@ export default function Register(): JSX.Element {
const response = await api.registerUser(newUser); const response = await api.registerUser(newUser);
if (response.success) { if (response.success) {
alert("User added!"); alert("User added!");
setPassword("");
setUsername("");
} else { } else {
alert("User not added"); alert("User not added");
setErrMessage(response.message ?? "Unknown error"); setErrMessage(response.message ?? "Unknown error");
@ -47,22 +49,24 @@ export default function Register(): JSX.Element {
<h3 className="pb-4 mb-2 text-center font-bold text-[18px]"> <h3 className="pb-4 mb-2 text-center font-bold text-[18px]">
Register New User Register New User
</h3> </h3>
<InputField <div className="space-y-3">
label="Username" <InputField
type="text" label="Username"
value={username ?? ""} type="text"
onChange={(e) => { value={username ?? ""}
setUsername(e.target.value); onChange={(e) => {
}} setUsername(e.target.value);
/> }}
<InputField />
label="Password" <InputField
type="password" label="Password"
value={password ?? ""} type="password"
onChange={(e) => { value={password ?? ""}
setPassword(e.target.value); onChange={(e) => {
}} setPassword(e.target.value);
/> }}
/>
</div>
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
<Button <Button
text="Register" text="Register"

View file

@ -1,70 +1,45 @@
import { useState, useEffect } from "react"; import { useState, useEffect } from "react";
import { useParams } from "react-router-dom"; import { useParams } from "react-router-dom";
import { api } from "../API/API";
import { projectTimes } from "./GetProjectTimes";
/** /**
* Renders the component for showing total time per role in a project. * Renders the component for showing total time per role in a project.
* @returns JSX.Element * @returns JSX.Element
*/ */
export default function TimePerRole(): JSX.Element { export default function TimePerRole(): JSX.Element {
const [developmentTime, setDevelopmentTime] = useState<number>(); const [development, setDevelopment] = useState<number>();
const [meetingTime, setMeetingTime] = useState<number>(); const [meeting, setMeeting] = useState<number>();
const [adminTime, setAdminTime] = useState<number>(); const [admin, setAdmin] = useState<number>();
const [ownWorkTime, setOwnWorkTime] = useState<number>(); const [own_work, setOwnWork] = useState<number>();
const [studyTime, setStudyTime] = useState<number>(); const [study, setStudy] = useState<number>();
const [testingTime, setTestingTime] = useState<number>(); const [testing, setTesting] = useState<number>();
// const token = localStorage.getItem("accessToken") ?? ""; const token = localStorage.getItem("accessToken") ?? "";
// const username = localStorage.getItem("username") ?? "";
const { projectName } = useParams(); const { projectName } = useParams();
// const fetchTimePerRole = async (): Promise<void> => {
// const response = await api.getTimePerRole(
// username,
// projectName ?? "",
// token,
// );
// {
// if (response.success) {
// const report: TimePerRole = response.data ?? {
// PManagerTime: 0,
// SManagerTime: 0,
// DeveloperTime: 0,
// TesterTime: 0,
// };
// } else {
// console.error("Failed to fetch weekly report:", response.message);
// }
// }
interface TimePerActivity {
developmentTime: number;
meetingTime: number;
adminTime: number;
ownWorkTime: number;
studyTime: number;
testingTime: number;
}
const fetchTimePerActivity = async (): Promise<void> => { const fetchTimePerActivity = async (): Promise<void> => {
// Use mock data const response = await api.getProjectTimes(projectName ?? "", token);
const report: TimePerActivity = { {
developmentTime: 100, if (response.success) {
meetingTime: 200, const report: projectTimes = response.data ?? {
adminTime: 300, development: 0,
ownWorkTime: 50, meeting: 0,
studyTime: 75, admin: 0,
testingTime: 110, own_work: 0,
}; study: 0,
testing: 0,
// Set the state with the mock data };
setDevelopmentTime(report.developmentTime); setDevelopment(report.development);
setMeetingTime(report.meetingTime); setMeeting(report.meeting);
setAdminTime(report.adminTime); setAdmin(report.admin);
setOwnWorkTime(report.ownWorkTime); setOwnWork(report.own_work);
setStudyTime(report.studyTime); setStudy(report.study);
setTestingTime(report.testingTime); setTesting(report.testing);
} else {
await Promise.resolve(); console.error("Failed to fetch weekly report:", response.message);
}
}
}; };
useEffect(() => { useEffect(() => {
@ -94,10 +69,8 @@ export default function TimePerRole(): JSX.Element {
<input <input
type="string" type="string"
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} value={development}
onKeyDown={(event) => { readOnly
event.preventDefault();
}}
/> />
</td> </td>
</tr> </tr>
@ -107,10 +80,8 @@ export default function TimePerRole(): JSX.Element {
<input <input
type="string" type="string"
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} value={meeting}
onKeyDown={(event) => { readOnly
event.preventDefault();
}}
/> />
</td> </td>
</tr> </tr>
@ -120,10 +91,8 @@ export default function TimePerRole(): JSX.Element {
<input <input
type="string" type="string"
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} value={admin}
onKeyDown={(event) => { readOnly
event.preventDefault();
}}
/> />
</td> </td>
</tr> </tr>
@ -133,10 +102,8 @@ export default function TimePerRole(): JSX.Element {
<input <input
type="string" type="string"
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} value={own_work}
onKeyDown={(event) => { readOnly
event.preventDefault();
}}
/> />
</td> </td>
</tr> </tr>
@ -146,10 +113,8 @@ export default function TimePerRole(): JSX.Element {
<input <input
type="string" type="string"
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} value={study}
onKeyDown={(event) => { readOnly
event.preventDefault();
}}
/> />
</td> </td>
</tr> </tr>
@ -159,10 +124,8 @@ export default function TimePerRole(): JSX.Element {
<input <input
type="string" type="string"
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} value={testing}
onKeyDown={(event) => { readOnly
event.preventDefault();
}}
/> />
</td> </td>
</tr> </tr>

View file

@ -1,72 +1,99 @@
import { Link } from "react-router-dom";
import Button from "./Button"; import Button from "./Button";
import DeleteUser from "./DeleteUser"; import DeleteUser from "./DeleteUser";
import UserProjectListAdmin from "./UserProjectListAdmin"; import UserProjectListAdmin from "./UserProjectListAdmin";
import { useState } from "react";
import InputField from "./InputField";
import ChangeUsername from "./ChangeUsername";
import { StrNameChange } from "../Types/goTypes";
function UserInfoModal(props: { function UserInfoModal(props: {
isVisible: boolean; isVisible: boolean;
manageMember: boolean;
username: string; username: string;
onClose: () => void; onClose: () => void;
onDelete: (username: string) => void;
}): JSX.Element { }): JSX.Element {
if (!props.isVisible) return <></>; const [showInput, setShowInput] = useState(false);
const ManageUserOrMember = (check: boolean): JSX.Element => { const [newUsername, setNewUsername] = useState("");
if (check) { if (!props.isVisible) {
return ( return <></>;
<Link to="/AdminChangeRole"> }
<p className="mb-[20px] hover:font-bold hover:cursor-pointer underline">
(Change Role) const handleChangeNameView = (): void => {
</p> if (showInput) {
</Link> setShowInput(false);
); } else {
setShowInput(true);
} }
return (
<Link to="/AdminChangeUserName">
<p className="mb-[20px] hover:font-bold hover:cursor-pointer underline">
(Change Username)
</p>
</Link>
);
}; };
const handleClickChangeName = (): void => {
const nameChange: StrNameChange = {
prevName: props.username,
newName: newUsername,
};
ChangeUsername({ nameChange: nameChange });
};
return ( return (
<div <div
className="fixed inset-0 bg-black bg-opacity-30 backdrop-blur-sm className="fixed inset-0 bg-black bg-opacity-30 backdrop-blur-sm
flex justify-center items-center" flex justify-center items-center"
> >
<div className="border-4 border-black bg-white p-2 rounded-lg text-center flex flex-col"> <div className="border-4 border-black bg-white rounded-lg text-center flex flex-col">
<p className="font-bold text-[30px]">{props.username}</p> <div className="mx-10">
{ManageUserOrMember(props.manageMember)} <p className="font-bold text-[30px]">{props.username}</p>
<div> <p
<h2 className="font-bold text-[22px] mb-[20px]"> className="mb-[10px] hover:font-bold hover:cursor-pointer underline"
Member of these projects: onClick={handleChangeNameView}
</h2> >
<div className="pr-6 pl-6"> (Change Username)
<UserProjectListAdmin /> </p>
{showInput && (
<div>
<InputField
label={"New username"}
type={"text"}
value={newUsername}
onChange={function (e): void {
e.defaultPrevented;
setNewUsername(e.target.value);
}}
/>
<Button
text={"Change"}
onClick={function (): void {
handleClickChangeName();
}}
type={"submit"}
/>
</div>
)}
<h2 className="font-bold text-[20px]">Member of these projects:</h2>
<UserProjectListAdmin username={props.username} />
<div className="items-center space-x-6">
<Button
text={"Delete"}
onClick={function (): void {
if (
window.confirm("Are you sure you want to delete this user?")
) {
DeleteUser({
usernameToDelete: props.username,
});
}
}}
type="button"
/>
<Button
text={"Close"}
onClick={function (): void {
setNewUsername("");
setShowInput(false);
props.onClose();
}}
type="button"
/>
</div> </div>
</div> </div>
<div className="items-center space-x-6 pr-6 pl-6">
<Button
text={"Delete"}
onClick={function (): void {
if (
window.confirm("Are you sure you want to delete this user?")
) {
DeleteUser({
usernameToDelete: props.username,
});
}
}}
type="button"
/>
<Button
text={"Close"}
onClick={function (): void {
props.onClose();
}}
type="button"
/>
</div>
</div> </div>
</div> </div>
); );

View file

@ -1,6 +1,5 @@
import { useState } from "react"; import { useState } from "react";
import UserInfoModal from "./UserInfoModal"; import UserInfoModal from "./UserInfoModal";
import DeleteUser from "./DeleteUser";
/** /**
* A list of users for admin manage users page, that sets an onClick * A list of users for admin manage users page, that sets an onClick
@ -30,9 +29,7 @@ export function UserListAdmin(props: { users: string[] }): JSX.Element {
return ( return (
<> <>
<UserInfoModal <UserInfoModal
manageMember={false}
onClose={handleClose} onClose={handleClose}
onDelete={() => DeleteUser}
isVisible={modalVisible} isVisible={modalVisible}
username={username} username={username}
/> />

View file

@ -1,35 +1,17 @@
import { useEffect, useState } from "react"; import { useState } from "react";
import { api } from "../API/API";
import { Project } from "../Types/goTypes"; import { Project } from "../Types/goTypes";
import GetProjects from "./GetProjects";
function UserProjectListAdmin(): JSX.Element { function UserProjectListAdmin(props: { username: string }): JSX.Element {
const [projects, setProjects] = useState<Project[]>([]); const [projects, setProjects] = useState<Project[]>([]);
useEffect(() => { GetProjects({ setProjectsProp: setProjects, username: props.username });
const fetchProjects = async (): Promise<void> => {
try {
const token = localStorage.getItem("accessToken") ?? "";
// const username = props.username;
const response = await api.getUserProjects(token);
if (response.success) {
setProjects(response.data ?? []);
} else {
console.error("Failed to fetch projects:", response.message);
}
} catch (error) {
console.error("Error fetching projects:", error);
}
};
void fetchProjects();
}, []);
return ( return (
<div className="border-2 border-black bg-white p-2 rounded-lg text-center"> <div className="border-2 border-black bg-white rounded-lg text-left overflow-auto h-[15vh] font-medium">
<ul> <ul className="divide-y-2">
{projects.map((project) => ( {projects.map((project) => (
<li key={project.id}> <li className="mx-2 my-1" key={project.id}>
<span>{project.name}</span> <span>{project.name}</span>
</li> </li>
))} ))}

View file

@ -1,5 +1,5 @@
import { useState, useEffect } from "react"; import { useState, useEffect } from "react";
import { WeeklyReport, NewWeeklyReport } from "../Types/goTypes"; import { WeeklyReport } from "../Types/goTypes";
import { api } from "../API/API"; import { api } from "../API/API";
import { useNavigate, useParams } from "react-router-dom"; import { useNavigate, useParams } from "react-router-dom";
import Button from "./Button"; import Button from "./Button";
@ -18,6 +18,7 @@ export default function GetOtherUsersReport(): JSX.Element {
const [ownWorkTime, setOwnWorkTime] = useState(0); const [ownWorkTime, setOwnWorkTime] = useState(0);
const [studyTime, setStudyTime] = useState(0); const [studyTime, setStudyTime] = useState(0);
const [testingTime, setTestingTime] = useState(0); const [testingTime, setTestingTime] = useState(0);
const [reportId, setReportId] = useState(0);
const token = localStorage.getItem("accessToken") ?? ""; const token = localStorage.getItem("accessToken") ?? "";
const { projectName } = useParams(); const { projectName } = useParams();
@ -45,6 +46,7 @@ export default function GetOtherUsersReport(): JSX.Element {
studyTime: 0, studyTime: 0,
testingTime: 0, testingTime: 0,
}; };
setReportId(report.reportId);
setWeek(report.week); setWeek(report.week);
setDevelopmentTime(report.developmentTime); setDevelopmentTime(report.developmentTime);
setMeetingTime(report.meetingTime); setMeetingTime(report.meetingTime);
@ -61,30 +63,23 @@ export default function GetOtherUsersReport(): JSX.Element {
}); });
const handleSignWeeklyReport = async (): Promise<void> => { const handleSignWeeklyReport = async (): Promise<void> => {
const newWeeklyReport: NewWeeklyReport = { await api.signReport(reportId, token);
projectName: projectName ?? "",
week,
developmentTime,
meetingTime,
adminTime,
ownWorkTime,
studyTime,
testingTime,
};
await api.submitWeeklyReport(newWeeklyReport, token);
}; };
const navigate = useNavigate(); const navigate = useNavigate();
return ( return (
<> <>
<h1 className="text-[30px] font-bold">{username}&apos;s Report</h1> <h1 className="text-[30px] font-bold">
{" "}
UserId: {username}&apos;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 handleSignWeeklyReport();
alert("Report successfully signed!");
navigate(-1); navigate(-1);
}} }}
> >
@ -112,7 +107,10 @@ export default function GetOtherUsersReport(): JSX.Element {
type="text" type="text"
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} defaultValue={
developmentTime === 0 ? "" : developmentTime
}
readOnly
/> />
</td> </td>
</tr> </tr>
@ -123,7 +121,8 @@ export default function GetOtherUsersReport(): JSX.Element {
type="text" type="text"
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} defaultValue={meetingTime === 0 ? "" : meetingTime}
readOnly
/> />
</td> </td>
</tr> </tr>
@ -134,7 +133,8 @@ export default function GetOtherUsersReport(): JSX.Element {
type="text" type="text"
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} defaultValue={adminTime === 0 ? "" : adminTime}
readOnly
/> />
</td> </td>
</tr> </tr>
@ -145,7 +145,8 @@ export default function GetOtherUsersReport(): JSX.Element {
type="text" type="text"
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} defaultValue={ownWorkTime === 0 ? "" : ownWorkTime}
readOnly
/> />
</td> </td>
</tr> </tr>
@ -156,7 +157,8 @@ export default function GetOtherUsersReport(): JSX.Element {
type="text" type="text"
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} defaultValue={studyTime === 0 ? "" : studyTime}
readOnly
/> />
</td> </td>
</tr> </tr>
@ -167,7 +169,8 @@ export default function GetOtherUsersReport(): JSX.Element {
type="text" type="text"
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} defaultValue={testingTime === 0 ? "" : testingTime}
readOnly
/> />
</td> </td>
</tr> </tr>

View file

@ -1,28 +0,0 @@
import BackButton from "../../Components/BackButton";
import BasicWindow from "../../Components/BasicWindow";
import Button from "../../Components/Button";
import ChangeUsername from "../../Components/ChangeUsername";
function AdminChangeUsername(): JSX.Element {
const content = (
<>
<ChangeUsername />
</>
);
const buttons = (
<>
<Button
text="Finish"
onClick={(): void => {
return;
}}
type="button"
/>
<BackButton />
</>
);
return <BasicWindow content={content} buttons={buttons} />;
}
export default AdminChangeUsername;

View file

@ -9,7 +9,10 @@ import { useState } from "react";
function AdminManageProjects(): JSX.Element { function AdminManageProjects(): JSX.Element {
const [projects, setProjects] = useState<Project[]>([]); const [projects, setProjects] = useState<Project[]>([]);
GetProjects({ setProjectsProp: setProjects }); GetProjects({
setProjectsProp: setProjects,
username: localStorage.getItem("username") ?? "",
});
const content = ( const content = (
<> <>
<h1 className="font-bold text-[30px] mb-[20px]">Manage Projects</h1> <h1 className="font-bold text-[30px] mb-[20px]">Manage Projects</h1>

View file

@ -1,11 +1,11 @@
import { useLocation } from "react-router-dom";
import AddUserToProject from "../../Components/AddUserToProject"; import AddUserToProject from "../../Components/AddUserToProject";
import BasicWindow from "../../Components/BasicWindow"; import BasicWindow from "../../Components/BasicWindow";
function AdminProjectAddMember(): JSX.Element { function AdminProjectAddMember(): JSX.Element {
const content = <AddUserToProject />; const projectName = useLocation().search.slice(1);
const content = <AddUserToProject projectName={projectName} />;
const buttons = <></>; const buttons = <></>;
return <BasicWindow content={content} buttons={buttons} />; return <BasicWindow content={content} buttons={buttons} />;
} }
export default AdminProjectAddMember; export default AdminProjectAddMember;

View file

@ -1,23 +0,0 @@
import BackButton from "../../Components/BackButton";
import BasicWindow from "../../Components/BasicWindow";
import Button from "../../Components/Button";
function AdminProjectChangeUserRole(): JSX.Element {
const content = <></>;
const buttons = (
<>
<Button
text="Change"
onClick={(): void => {
return;
}}
type="button"
/>
<BackButton />
</>
);
return <BasicWindow content={content} buttons={buttons} />;
}
export default AdminProjectChangeUserRole;

View file

@ -1,23 +0,0 @@
import BackButton from "../../Components/BackButton";
import BasicWindow from "../../Components/BasicWindow";
import Button from "../../Components/Button";
function AdminProjectManageMembers(): JSX.Element {
const content = <></>;
const buttons = (
<>
<Button
text="Add Member"
onClick={(): void => {
return;
}}
type="button"
/>
<BackButton />
</>
);
return <BasicWindow content={content} buttons={buttons} />;
}
export default AdminProjectManageMembers;

View file

@ -1,33 +0,0 @@
import { useParams } from "react-router-dom";
import { api } from "../../API/API";
import BackButton from "../../Components/BackButton";
import BasicWindow from "../../Components/BasicWindow";
import Button from "../../Components/Button";
async function handleDeleteProject(
projectName: string,
token: string,
): Promise<void> {
await api.removeProject(projectName, token);
}
function AdminProjectPage(): JSX.Element {
const content = <></>;
const { projectName } = useParams();
const token = localStorage.getItem("accessToken");
const buttons = (
<>
<Button
text="Delete"
onClick={() => handleDeleteProject(projectName, token)}
type="button"
/>
<BackButton />
</>
);
return <BasicWindow content={content} buttons={buttons} />;
}
export default AdminProjectPage;

View file

@ -1,23 +0,0 @@
import BackButton from "../../Components/BackButton";
import BasicWindow from "../../Components/BasicWindow";
import Button from "../../Components/Button";
function AdminProjectViewMemberInfo(): JSX.Element {
const content = <></>;
const buttons = (
<>
<Button
text="Remove"
onClick={(): void => {
return;
}}
type="button"
/>
<BackButton />
</>
);
return <BasicWindow content={content} buttons={buttons} />;
}
export default AdminProjectViewMemberInfo;

View file

@ -1,28 +0,0 @@
import BasicWindow from "../../Components/BasicWindow";
import Button from "../../Components/Button";
import BackButton from "../../Components/BackButton";
import UserProjectListAdmin from "../../Components/UserProjectListAdmin";
function AdminViewUserInfo(): JSX.Element {
const content = (
<>
<UserProjectListAdmin />
</>
);
const buttons = (
<>
<Button
text="Delete"
onClick={(): void => {
return;
}}
type="button"
/>
<BackButton />
</>
);
return <BasicWindow content={content} buttons={buttons} />;
}
export default AdminViewUserInfo;

View file

@ -18,17 +18,11 @@ import PMTotalTimeRole from "./Pages/ProjectManagerPages/PMTotalTimeRole.tsx";
import PMUnsignedReports from "./Pages/ProjectManagerPages/PMUnsignedReports.tsx"; import PMUnsignedReports from "./Pages/ProjectManagerPages/PMUnsignedReports.tsx";
import PMViewUnsignedReport from "./Pages/ProjectManagerPages/PMViewUnsignedReport.tsx"; import PMViewUnsignedReport from "./Pages/ProjectManagerPages/PMViewUnsignedReport.tsx";
import AdminManageUsers from "./Pages/AdminPages/AdminManageUsers.tsx"; import AdminManageUsers from "./Pages/AdminPages/AdminManageUsers.tsx";
import AdminViewUserInfo from "./Pages/AdminPages/AdminViewUserInfo.tsx";
import AdminManageProjects from "./Pages/AdminPages/AdminManageProjects.tsx"; import AdminManageProjects from "./Pages/AdminPages/AdminManageProjects.tsx";
import AdminAddProject from "./Pages/AdminPages/AdminAddProject.tsx"; import AdminAddProject from "./Pages/AdminPages/AdminAddProject.tsx";
import AdminAddUser from "./Pages/AdminPages/AdminAddUser.tsx"; import AdminAddUser from "./Pages/AdminPages/AdminAddUser.tsx";
import AdminChangeUsername from "./Pages/AdminPages/AdminChangeUsername.tsx";
import AdminProjectAddMember from "./Pages/AdminPages/AdminProjectAddMember.tsx"; import AdminProjectAddMember from "./Pages/AdminPages/AdminProjectAddMember.tsx";
import AdminProjectChangeUserRole from "./Pages/AdminPages/AdminProjectChangeUserRole.tsx";
import AdminProjectManageMembers from "./Pages/AdminPages/AdminProjectManageMembers.tsx";
import AdminProjectStatistics from "./Pages/AdminPages/AdminProjectStatistics.tsx"; import AdminProjectStatistics from "./Pages/AdminPages/AdminProjectStatistics.tsx";
import AdminProjectViewMemberInfo from "./Pages/AdminPages/AdminProjectViewMemberInfo.tsx";
import AdminProjectPage from "./Pages/AdminPages/AdminProjectPage.tsx";
import NotFoundPage from "./Pages/NotFoundPage.tsx"; import NotFoundPage from "./Pages/NotFoundPage.tsx";
import UnauthorizedPage from "./Pages/UnauthorizedPage.tsx"; import UnauthorizedPage from "./Pages/UnauthorizedPage.tsx";
import PMViewOtherUsersTR from "./Pages/ProjectManagerPages/PMViewOtherUsersTR.tsx"; import PMViewOtherUsersTR from "./Pages/ProjectManagerPages/PMViewOtherUsersTR.tsx";
@ -100,34 +94,14 @@ const router = createBrowserRouter([
path: "/PMViewUnsignedReport/:projectName/:username/:fetchedWeek", path: "/PMViewUnsignedReport/:projectName/:username/:fetchedWeek",
element: <PMViewUnsignedReport />, element: <PMViewUnsignedReport />,
}, },
{
path: "/adminChangeUsername",
element: <AdminChangeUsername />,
},
{ {
path: "/adminProjectAddMember", path: "/adminProjectAddMember",
element: <AdminProjectAddMember />, element: <AdminProjectAddMember />,
}, },
{
path: "/adminProjectChangeUserRole",
element: <AdminProjectChangeUserRole />,
},
{
path: "/adminProjectManageMembers",
element: <AdminProjectManageMembers />,
},
{
path: "/adminProjectPage",
element: <AdminProjectPage />,
},
{ {
path: "/adminProjectStatistics", path: "/adminProjectStatistics",
element: <AdminProjectStatistics />, element: <AdminProjectStatistics />,
}, },
{
path: "/adminProjectViewMembers",
element: <AdminProjectViewMemberInfo />,
},
{ {
path: "/addProject", path: "/addProject",
element: <AdminAddProject />, element: <AdminAddProject />,
@ -136,10 +110,6 @@ const router = createBrowserRouter([
path: "/adminAddUser", path: "/adminAddUser",
element: <AdminAddUser />, element: <AdminAddUser />,
}, },
{
path: "/adminUserInfo",
element: <AdminViewUserInfo />,
},
{ {
path: "/adminManageProject", path: "/adminManageProject",
element: <AdminManageProjects />, element: <AdminManageProjects />,

151
testing/helpers.py Normal file
View file

@ -0,0 +1,151 @@
import requests
import string
import random
import json
# Helper function for the TTime API testing suite
# For style guide, see:
# https://peps.python.org/pep-0008/#function-and-variable-names
# https://google.github.io/styleguide/pyguide.html#316-naming
##################
## Static Paths ##
##################
base_url = "http://localhost:8080"
registerPath = base_url + "/api/register"
loginPath = base_url + "/api/login"
addProjectPath = base_url + "/api/project"
submitReportPath = base_url + "/api/submitWeeklyReport"
getWeeklyReportPath = base_url + "/api/getWeeklyReport"
getProjectPath = base_url + "/api/project"
signReportPath = base_url + "/api/signReport"
addUserToProjectPath = base_url + "/api/addUserToProject"
promoteToAdminPath = base_url + "/api/promoteToAdmin"
getUserProjectsPath = base_url + "/api/getUserProjects"
getAllWeeklyReportsPath = base_url + "/api/getAllWeeklyReports"
checkIfProjectManagerPath = base_url + "/api/checkIfProjectManager"
ProjectRoleChangePath = base_url + "/api/ProjectRoleChange"
getUsersProjectPath = base_url + "/api/getUsersProject"
getUnsignedReportsPath = base_url + "/api/getUnsignedReports"
getChangeUserNamePath = base_url + "/api/changeUserName"
getUpdateWeeklyReportPath = base_url + "/api/updateWeeklyReport"
removeProjectPath = base_url + "/api/removeProject"
promoteToPmPath = base_url + "/api/promoteToPm"
debug_output = True
def gprint(*args, **kwargs):
print("\033[92m", *args, "\033[00m", **kwargs)
def dprint(*args, **kwargs):
if debug_output:
print(*args, **kwargs)
def randomString(len=10):
"""Generate a random string of fixed length"""
letters = string.ascii_lowercase
return "".join(random.choice(letters) for i in range(len))
############ ############ ############ ############ ############
# Posts the username and password to the register endpoint
def register(username: string, password: string):
dprint("Registering with username: ", username, " and password: ", password)
response = requests.post(
registerPath, json={"username": username, "password": password}
)
dprint(response.text)
return response
# Posts the username and password to the login endpoint
def login(username: string, password: string):
dprint("Logging in with username: ", username, " and password: ", password)
response = requests.post(
loginPath, json={"username": username, "password": password}
)
dprint(response.text)
return response
# Register a user and return the token
def register_and_login(username: string, password: string) -> string:
register(username, password)
response = login(username, password)
return response.json()["token"]
def create_project(
token: string, project_name: string, description: string = "Test description"
):
dprint("Creating project with name: ", project_name)
response = requests.post(
addProjectPath,
headers={"Authorization": "Bearer " + token},
json={"name": project_name, "description": description},
)
dprint(response.text)
return response
# Add a user to a project, requires the user withing the token to be a project manager of said project
def addToProject(token: string, username: string, project_name: string):
dprint("Adding user with username: ", username, " to project: ", project_name)
response = requests.put(
addUserToProjectPath + "/" + project_name,
headers={"Authorization": "Bearer " + token},
params={"userName": username},
)
dprint(response.text)
return response
def promoteToManager(token: string, username: string, project_name: string):
dprint(
"Promoting user with username: ",
username,
" to project manager of project: ",
project_name,
)
response = requests.put(
promoteToPmPath + "/" + project_name,
headers={"Authorization": "Bearer " + token},
params={"userName": username},
)
dprint(response.text)
return response
def submitReport(token: string, report):
dprint("Submitting report: ", report)
response = requests.post(
submitReportPath,
json=report,
headers={"Authorization": "Bearer " + token},
)
return response
def getReport(token: string, username: string, projectName: string):
# Retrieve the report ID
response = requests.get(
getWeeklyReportPath,
headers={"Authorization": "Bearer " + token},
params={"username": username, "projectName": projectName, "week": 1},
)
return response.json()
def signReport(project_manager_token: string, report_id: int):
return requests.put(
signReportPath + "/" + str(report_id),
headers={"Authorization": "Bearer " + project_manager_token},
)

View file

@ -1,61 +1,23 @@
import requests import requests
import string
import random
debug_output = True # This modules contains helper functions for the tests
from helpers import *
def gprint(*args, **kwargs):
print("\033[92m", *args, "\033[00m", **kwargs)
print("Running Tests...") print("Running Tests...")
def dprint(*args, **kwargs):
if debug_output:
print(*args, **kwargs)
def randomString(len=10):
"""Generate a random string of fixed length"""
letters = string.ascii_lowercase
return "".join(random.choice(letters) for i in range(len))
# Defined once per test run # Defined once per test run
username = "user_" + randomString() username = "user_" + randomString()
projectName = "project_" + randomString() projectName = "project_" + randomString()
# The base URL of the API
base_url = "http://localhost:8080"
# Endpoint to test # ta bort auth i handlern för att få testet att gå igenom
registerPath = base_url + "/api/register"
loginPath = base_url + "/api/login"
addProjectPath = base_url + "/api/project"
submitReportPath = base_url + "/api/submitWeeklyReport"
getWeeklyReportPath = base_url + "/api/getWeeklyReport"
getProjectPath = base_url + "/api/project"
signReportPath = base_url + "/api/signReport"
addUserToProjectPath = base_url + "/api/addUserToProject"
promoteToAdminPath = base_url + "/api/promoteToAdmin"
getUserProjectsPath = base_url + "/api/getUserProjects"
getWeeklyReportsUserPath = base_url + "/api/getWeeklyReportsUser"
checkIfProjectManagerPath = base_url + "/api/checkIfProjectManager"
ProjectRoleChangePath = base_url + "/api/ProjectRoleChange"
getUsersProjectPath = base_url + "/api/getUsersProject"
getUnsignedReportsPath = base_url + "/api/getUnsignedReports"
getChangeUserNamePath = base_url + "/api/changeUserName"
getUpdateWeeklyReportPath = base_url + "/api/updateWeeklyReport"
removeProjectPath = base_url + "/api/removeProject"
#ta bort auth i handlern för att få testet att gå igenom
def test_ProjectRoleChange(): def test_ProjectRoleChange():
dprint("Testing ProjectRoleChange") dprint("Testing ProjectRoleChange")
localUsername = randomString() localUsername = randomString()
localProjectName = randomString() localProjectName = randomString()
register(localUsername, "username_password") register(localUsername, "username_password")
token = login(localUsername, "username_password").json()[ token = login(localUsername, "username_password").json()["token"]
"token"
]
# Just checking since this test is built somewhat differently than the others # Just checking since this test is built somewhat differently than the others
assert token != None, "Login failed" assert token != None, "Login failed"
@ -83,13 +45,14 @@ def test_ProjectRoleChange():
def test_get_user_projects(): def test_get_user_projects():
username = "user2"
password = "123"
dprint("Testing get user projects") dprint("Testing get user projects")
loginResponse = login("user2", "123") loginResponse = login(username, password)
# Check if the user is added to the project # Check if the user is added to the project
response = requests.get( response = requests.get(
getUserProjectsPath, getUserProjectsPath + "/" + username,
json={"username": "user2"},
headers={"Authorization": "Bearer " + loginResponse.json()["token"]}, headers={"Authorization": "Bearer " + loginResponse.json()["token"]},
) )
dprint(response.text) dprint(response.text)
@ -98,26 +61,6 @@ def test_get_user_projects():
gprint("test_get_user_projects successful") gprint("test_get_user_projects successful")
# Posts the username and password to the register endpoint
def register(username: string, password: string):
dprint("Registering with username: ", username, " and password: ", password)
response = requests.post(
registerPath, json={"username": username, "password": password}
)
dprint(response.text)
return response
# Posts the username and password to the login endpoint
def login(username: string, password: string):
dprint("Logging in with username: ", username, " and password: ", password)
response = requests.post(
loginPath, json={"username": username, "password": password}
)
dprint(response.text)
return response
# Test function to login # Test function to login
def test_login(): def test_login():
response = login(username, "always_same") response = login(username, "always_same")
@ -133,6 +76,7 @@ def test_create_user():
assert response.status_code == 200, "Registration failed" assert response.status_code == 200, "Registration failed"
gprint("test_create_user successful") gprint("test_create_user successful")
# Test function to add a project # Test function to add a project
def test_add_project(): def test_add_project():
loginResponse = login(username, "always_same") loginResponse = login(username, "always_same")
@ -146,6 +90,7 @@ def test_add_project():
assert response.status_code == 200, "Add project failed" assert response.status_code == 200, "Add project failed"
gprint("test_add_project successful") gprint("test_add_project successful")
# Test function to submit a report # Test function to submit a report
def test_submit_report(): def test_submit_report():
token = login(username, "always_same").json()["token"] token = login(username, "always_same").json()["token"]
@ -167,6 +112,7 @@ def test_submit_report():
assert response.status_code == 200, "Submit report failed" assert response.status_code == 200, "Submit report failed"
gprint("test_submit_report successful") gprint("test_submit_report successful")
# Test function to get a weekly report # Test function to get a weekly report
def test_get_weekly_report(): def test_get_weekly_report():
token = login(username, "always_same").json()["token"] token = login(username, "always_same").json()["token"]
@ -194,91 +140,58 @@ def test_get_project():
# Test function to add a user to a project # Test function to add a user to a project
def test_add_user_to_project(): def test_add_user_to_project():
# Log in as a site admin # User to create
admin_username = randomString() pm_user = "user" + randomString()
admin_password = "admin_password" pm_passwd = "password"
dprint(
"Registering with username: ", admin_username, " and password: ", admin_password
)
response = requests.post(
registerPath, json={"username": admin_username, "password": admin_password}
)
dprint(response.text)
admin_token = login(admin_username, admin_password).json()["token"] # User to add
response = requests.post( member_user = "member" + randomString()
promoteToAdminPath, member_passwd = "password"
json={"username": admin_username},
headers={"Authorization": "Bearer " + admin_token},
)
dprint(response.text)
assert response.status_code == 200, "Promote to site admin failed"
dprint("Admin promoted to site admin successfully")
# Create a new user to add to the project # Name of the project to be created
new_user = randomString() project_name = "project" + randomString()
register(new_user, "new_user_password")
# Add the new user to the project as a member pm_token = register_and_login(pm_user, pm_passwd)
response = requests.put( register(member_user, member_passwd)
addUserToProjectPath,
json={"projectName": projectName, "username": new_user, "role": "member"},
headers={"Authorization": "Bearer " + admin_token},
)
dprint(response.text) response = create_project(pm_token, project_name)
assert response.status_code == 200, "Create project failed"
# Promote the user to project manager
response = addToProject(pm_token, member_user, project_name)
assert response.status_code == 200, "Add user to project failed" assert response.status_code == 200, "Add user to project failed"
gprint("test_add_user_to_project successful")
# Test function to sign a report # Test function to sign a report
def test_sign_report(): def test_sign_report():
# Create a project manager user # Pm user
project_manager = randomString() pm_username = "pm" + randomString()
register(project_manager, "project_manager_password") pm_password = "admin_password2"
# Register an admin # User to add
admin_username = randomString() member_user = "member" + randomString()
admin_password = "admin_password2" member_passwd = "password"
dprint(
"Registering with username: ", admin_username, " and password: ", admin_password
)
response = requests.post(
registerPath, json={"username": admin_username, "password": admin_password}
)
dprint(response.text)
# Log in as the admin # Name of the project to be created
admin_token = login(admin_username, admin_password).json()["token"] project_name = "project" + randomString()
response = requests.post(
promoteToAdminPath,
json={"username": admin_username},
headers={"Authorization": "Bearer " + admin_token},
)
response = requests.put( # Register and get the tokens for both users
addUserToProjectPath, pm_token = register_and_login(pm_username, pm_password)
json={ member_token = register_and_login(member_user, member_passwd)
"projectName": projectName,
"username": project_manager,
"role": "project_manager",
},
headers={"Authorization": "Bearer " + admin_token},
)
assert response.status_code == 200, "Add project manager to project failed"
dprint("Project manager added to project successfully")
# Log in as the project manager # Create the project
project_manager_token = login(project_manager, "project_manager_password").json()[ response = create_project(pm_token, project_name)
"token" assert response.status_code == 200, "Create project failed"
]
# Add the user to the project
response = addToProject(pm_token, member_user, project_name)
# Submit a report for the project # Submit a report for the project
token = login(username, "always_same").json()["token"] response = submitReport(
response = requests.post( member_token,
submitReportPath, {
json={ "projectName": project_name,
"projectName": projectName, "week": 1,
"week": 2,
"developmentTime": 10, "developmentTime": 10,
"meetingTime": 5, "meetingTime": 5,
"adminTime": 5, "adminTime": 5,
@ -286,46 +199,33 @@ def test_sign_report():
"studyTime": 10, "studyTime": 10,
"testingTime": 10, "testingTime": 10,
}, },
headers={"Authorization": "Bearer " + token},
) )
assert response.status_code == 200, "Submit report failed" assert response.status_code == 200, "Submit report failed"
dprint("Submit report successful")
# Retrieve the report ID # Retrieve the report ID
response = requests.get( report_id = getReport(member_token, member_user, project_name)["reportId"]
getWeeklyReportPath,
headers={"Authorization": "Bearer " + token},
params={"username": username, "projectName": projectName, "week": 1},
)
dprint(response.text)
report_id = response.json()["reportId"]
# Sign the report as the project manager # Sign the report as the project manager
response = requests.put( response = signReport(pm_token, report_id)
signReportPath + "/" + str(report_id),
headers={"Authorization": "Bearer " + project_manager_token},
)
assert response.status_code == 200, "Sign report failed" assert response.status_code == 200, "Sign report failed"
dprint("Sign report successful") dprint("Sign report successful")
# Retrieve the report ID again for confirmation # Retrieve the report ID again for confirmation
response = requests.get( report_id = getReport(member_token, member_user, project_name)["reportId"]
getWeeklyReportPath, assert report_id != None, "Get report failed"
headers={"Authorization": "Bearer " + token},
params={"username": username, "projectName": projectName, "week": 1},
)
dprint(response.text)
gprint("test_sign_report successful") gprint("test_sign_report successful")
# Test function to get weekly reports for a user in a project # Test function to get weekly reports for a user in a project
def test_get_weekly_reports_user(): def test_get_all_weekly_reports():
# Log in as the user # Log in as the user
token = login(username, "always_same").json()["token"] token = login(username, "always_same").json()["token"]
# Get weekly reports for the user in the project # Get weekly reports for the user in the project
response = requests.get( response = requests.get(
getWeeklyReportsUserPath + "/" + projectName, getAllWeeklyReportsPath + "/" + projectName,
headers={"Authorization": "Bearer " + token}, headers={"Authorization": "Bearer " + token},
params={"targetUser": username},
) )
dprint(response.text) dprint(response.text)
@ -333,7 +233,6 @@ def test_get_weekly_reports_user():
gprint("test_get_weekly_reports_user successful") gprint("test_get_weekly_reports_user successful")
# Test function to check if a user is a project manager # Test function to check if a user is a project manager
def test_check_if_project_manager(): def test_check_if_project_manager():
# Log in as the user # Log in as the user
@ -349,6 +248,7 @@ def test_check_if_project_manager():
assert response.status_code == 200, "Check if project manager failed" assert response.status_code == 200, "Check if project manager failed"
gprint("test_check_if_project_manager successful") gprint("test_check_if_project_manager successful")
def test_ensure_manager_of_created_project(): def test_ensure_manager_of_created_project():
# Create a new user to add to the project # Create a new user to add to the project
newUser = "karen_" + randomString() newUser = "karen_" + randomString()
@ -372,6 +272,7 @@ def test_ensure_manager_of_created_project():
assert response.json()["isProjectManager"] == True, "User is not project manager" assert response.json()["isProjectManager"] == True, "User is not project manager"
gprint("test_ensure_admin_of_created_project successful") gprint("test_ensure_admin_of_created_project successful")
def test_change_user_name(): def test_change_user_name():
# Register a new user # Register a new user
new_user = randomString() new_user = randomString()
@ -409,6 +310,7 @@ def test_change_user_name():
assert response.status_code == 200, "Change user name failed" assert response.status_code == 200, "Change user name failed"
gprint("test_change_user_name successful") gprint("test_change_user_name successful")
def test_list_all_users_project(): def test_list_all_users_project():
# Log in as a user who is a member of the project # Log in as a user who is a member of the project
admin_username = randomString() admin_username = randomString()
@ -437,6 +339,7 @@ def test_list_all_users_project():
assert response.status_code == 200, "List all users project failed" assert response.status_code == 200, "List all users project failed"
gprint("test_list_all_users_project sucessful") gprint("test_list_all_users_project sucessful")
def test_update_weekly_report(): def test_update_weekly_report():
# Log in as the user # Log in as the user
token = login(username, "always_same").json()["token"] token = login(username, "always_same").json()["token"]
@ -502,20 +405,99 @@ def test_remove_project():
assert response.status_code == 200, "Remove project failed" assert response.status_code == 200, "Remove project failed"
gprint("test_remove_project successful") gprint("test_remove_project successful")
def test_get_unsigned_reports(): def test_get_unsigned_reports():
# Log in as the user # Log in as the user
token = login("user2", "123").json()["token"] token = login("user2", "123").json()["token"]
# Make a request to get all unsigned reports # Make a request to get all unsigned reports
response = requests.get( response = requests.get(
getUnsignedReportsPath + "/" + projectName, getUnsignedReportsPath + "/" + projectName,
headers={"Authorization": "Bearer " + token}, headers={"Authorization": "Bearer " + token},
) )
assert response.status_code == 200, "Get unsigned reports failed" assert response.status_code == 200, "Get unsigned reports failed"
gprint("test_get_unsigned_reports successful") gprint("test_get_unsigned_reports successful")
def test_get_other_users_report_as_pm():
# Create user
user = randomString()
register(user, "password")
# Create project
project = randomString()
pm_token = login(user, "password").json()["token"]
response = requests.post(
addProjectPath,
json={"name": project, "description": "This is a project"},
headers={"Authorization": "Bearer " + pm_token},
)
assert response.status_code == 200, "Add project failed"
# Create other user
other_user = randomString()
register(other_user, "password")
user_token = login(other_user, "password").json()["token"]
# Add other user to project
response = requests.put(
addUserToProjectPath + "/" + project,
headers={"Authorization": "Bearer " + pm_token}, # note pm_token
params={"userName": other_user},
)
assert response.status_code == 200, "Add user to project failed"
# Submit report as other user
response = requests.post(
submitReportPath,
json={
"projectName": project,
"week": 1,
"developmentTime": 10,
"meetingTime": 5,
"adminTime": 5,
"ownWorkTime": 10,
"studyTime": 10,
"testingTime": 10,
},
headers={"Authorization": "Bearer " + user_token},
)
assert response.status_code == 200, "Submit report failed"
# Get report as project manager
response = requests.get(
getWeeklyReportPath,
headers={"Authorization": "Bearer " + pm_token},
params={"targetUser": other_user, "projectName": project, "week": 1},
)
assert response.status_code == 200, "Get weekly report failed"
def test_promote_to_manager():
# User to create
pm_user = "user" + randomString()
pm_passwd = "password"
# User to promote
member_user = "member" + randomString()
member_passwd = "password"
# Name of the project to be created
project_name = "project" + randomString()
pm_token = register_and_login(pm_user, pm_passwd)
member_token = register_and_login(member_user, member_passwd)
response = create_project(pm_token, project_name)
assert response.status_code == 200, "Create project failed"
# Promote the user to project manager
response = promoteToManager(pm_token, member_user, project_name)
assert response.status_code == 200, "Promote to manager failed"
if __name__ == "__main__": if __name__ == "__main__":
test_promote_to_manager()
test_remove_project() test_remove_project()
test_get_user_projects() test_get_user_projects()
test_create_user() test_create_user()
@ -526,7 +508,7 @@ if __name__ == "__main__":
test_get_project() test_get_project()
test_sign_report() test_sign_report()
test_add_user_to_project() test_add_user_to_project()
test_get_weekly_reports_user() test_get_all_weekly_reports()
test_check_if_project_manager() test_check_if_project_manager()
test_ProjectRoleChange() test_ProjectRoleChange()
test_ensure_manager_of_created_project() test_ensure_manager_of_created_project()
@ -534,4 +516,4 @@ if __name__ == "__main__":
test_list_all_users_project() test_list_all_users_project()
test_change_user_name() test_change_user_name()
test_update_weekly_report() test_update_weekly_report()
test_get_other_users_report_as_pm()