diff --git a/.gitignore b/.gitignore index c50fe24..281e866 100644 --- a/.gitignore +++ b/.gitignore @@ -14,7 +14,6 @@ diagram.puml backend/*.png backend/*.jpg backend/*.svg -__pycache__ /go.work.sum /package-lock.json diff --git a/backend/docs/docs.go b/backend/docs/docs.go index c8b020d..7a08b0e 100644 --- a/backend/docs/docs.go +++ b/backend/docs/docs.go @@ -108,56 +108,6 @@ const docTemplate = `{ } } }, - "/promote/{projectName}": { - "put": { - "security": [ - { - "JWT": [] - } - ], - "description": "Promote a user to project manager", - "consumes": [ - "text/plain" - ], - "produces": [ - "text/plain" - ], - "tags": [ - "Auth" - ], - "summary": "Promote to project manager", - "parameters": [ - { - "type": "string", - "description": "Project name", - "name": "projectName", - "in": "path", - "required": true - }, - { - "type": "string", - "description": "User name", - "name": "userName", - "in": "query", - "required": true - } - ], - "responses": { - "403": { - "description": "Forbidden", - "schema": { - "type": "string" - } - }, - "500": { - "description": "Internal server error", - "schema": { - "type": "string" - } - } - } - } - }, "/promoteToAdmin": { "post": { "security": [ diff --git a/backend/internal/database/db.go b/backend/internal/database/db.go index 0bd67bc..f4c0f6e 100644 --- a/backend/internal/database/db.go +++ b/backend/internal/database/db.go @@ -17,7 +17,6 @@ type Database interface { AddUser(username string, password string) error CheckUser(username string, password string) bool RemoveUser(username string) error - RemoveUserFromProject(username string, projectname string) error PromoteToAdmin(username string) error GetUserId(username string) (int, error) AddProject(name string, description string, username string) error @@ -36,7 +35,7 @@ type Database interface { GetProject(projectId int) (types.Project, error) GetUserRole(username string, projectname string) (string, error) GetWeeklyReport(username string, projectName string, week int) (types.WeeklyReport, error) - GetAllWeeklyReports(username string, projectname string) ([]types.WeeklyReportList, error) + GetWeeklyReportsUser(username string, projectname string) ([]types.WeeklyReportList, error) GetUnsignedWeeklyReports(projectName string) ([]types.WeeklyReport, error) SignWeeklyReport(reportId int, projectManagerId int) error IsSiteAdmin(username string) (bool, error) @@ -87,10 +86,6 @@ const isProjectManagerQuery = `SELECT COUNT(*) > 0 FROM user_roles JOIN projects ON user_roles.project_id = projects.id WHERE users.username = ? AND projects.name = ? AND user_roles.p_role = 'project_manager'` -const removeUserFromProjectQuery = `DELETE FROM user_roles - WHERE user_id = (SELECT id FROM users WHERE username = ?) - AND project_id = (SELECT id FROM projects WHERE name = ?)` - // DbConnect connects to the database func DbConnect(dbpath string) Database { // Open the database @@ -152,11 +147,6 @@ func (d *Db) AddUserToProject(username string, projectname string, role string) return err } -func (d *Db) RemoveUserFromProject(username string, projectname string) error { - _, err := d.Exec(removeUserFromProjectQuery, username, projectname) - return err -} - // ChangeUserRole changes the role of a user within a project. func (d *Db) ChangeUserRole(username string, projectname string, role string) error { // Execute the SQL query to change the user's role @@ -473,8 +463,8 @@ func (d *Db) Migrate() error { return nil } -// GetAllWeeklyReports retrieves weekly reports for a specific user and project. -func (d *Db) GetAllWeeklyReports(username string, projectName string) ([]types.WeeklyReportList, error) { +// GetWeeklyReportsUser retrieves weekly reports for a specific user and project. +func (d *Db) GetWeeklyReportsUser(username string, projectName string) ([]types.WeeklyReportList, error) { query := ` SELECT wr.week, diff --git a/backend/internal/database/db_test.go b/backend/internal/database/db_test.go index a691a4d..fe3e6cd 100644 --- a/backend/internal/database/db_test.go +++ b/backend/internal/database/db_test.go @@ -705,7 +705,7 @@ func TestGetWeeklyReportsUser(t *testing.T) { t.Error("AddWeeklyReport failed:", err) } - reports, err := db.GetAllWeeklyReports("testuser", "testproject") + reports, err := db.GetWeeklyReportsUser("testuser", "testproject") if err != nil { t.Error("GetWeeklyReportsUser failed:", err) } @@ -962,5 +962,6 @@ func TestRemoveProject(t *testing.T) { if len(projects) != 0 { t.Error("RemoveProject failed: expected 0, got", len(projects)) } - + } + \ No newline at end of file diff --git a/backend/internal/database/sample_data/0010_sample_data.sql b/backend/internal/database/sample_data/0010_sample_data.sql index 70499b0..ab74f1a 100644 --- a/backend/internal/database/sample_data/0010_sample_data.sql +++ b/backend/internal/database/sample_data/0010_sample_data.sql @@ -21,12 +21,6 @@ VALUES ("projecttest3","test project3", 1); INSERT OR IGNORE INTO user_roles(user_id,project_id,p_role) 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) VALUES (2,1,"member"); diff --git a/backend/internal/handlers/projects/AddUserToProject.go b/backend/internal/handlers/projects/AddUserToProject.go index 3195314..702b7dd 100644 --- a/backend/internal/handlers/projects/AddUserToProject.go +++ b/backend/internal/handlers/projects/AddUserToProject.go @@ -10,33 +10,42 @@ import ( // AddUserToProjectHandler is a handler that adds a user to a project with a specified role 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) claims := user.Claims.(jwt.MapClaims) - pm_name := claims["name"].(string) + adminUsername := claims["name"].(string) + log.Info("Admin username from claims:", 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) + isAdmin, err := db.GetDb(c).IsSiteAdmin(adminUsername) if err != nil { - log.Info("Error checking if user is project manager:", err) + log.Info("Error checking admin status:", 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") + if !isAdmin { + log.Info("User is not a site admin:", adminUsername) + return c.Status(403).SendString("User is not a site admin") } // Add the user to the project with the specified role - err = db.GetDb(c).AddUserToProject(username, project, "member") + err = db.GetDb(c).AddUserToProject(requestData.Username, requestData.ProjectName, requestData.Role) if err != nil { log.Info("Error adding user to project:", err) return c.Status(500).SendString(err.Error()) } // Return success message - log.Info("User : ", username, " added to project: ", project) + log.Info("User added to project successfully:", requestData.Username) return c.SendStatus(fiber.StatusOK) } diff --git a/backend/internal/handlers/projects/GetUserProject.go b/backend/internal/handlers/projects/GetUserProject.go index 6c80515..99ed63b 100644 --- a/backend/internal/handlers/projects/GetUserProject.go +++ b/backend/internal/handlers/projects/GetUserProject.go @@ -4,16 +4,15 @@ import ( db "ttime/internal/database" "github.com/gofiber/fiber/v2" - "github.com/gofiber/fiber/v2/log" + "github.com/golang-jwt/jwt/v5" ) // GetUserProjects returns all projects that the user is a member of func GetUserProjects(c *fiber.Ctx) error { - username := c.Params("username") - if username == "" { - log.Info("No username provided") - return c.Status(400).SendString("No username provided") - } + // First we get the username from the token + user := c.Locals("user").(*jwt.Token) + claims := user.Claims.(jwt.MapClaims) + username := claims["name"].(string) // Then dip into the database to get the projects projects, err := db.GetDb(c).GetProjectsForUser(username) diff --git a/backend/internal/handlers/projects/ProjectRoleChange.go b/backend/internal/handlers/projects/ProjectRoleChange.go index 6c5d455..266127d 100644 --- a/backend/internal/handlers/projects/ProjectRoleChange.go +++ b/backend/internal/handlers/projects/ProjectRoleChange.go @@ -24,13 +24,7 @@ func ProjectRoleChange(c *fiber.Ctx) error { return c.Status(400).SendString(err.Error()) } - // 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) + log.Info("Changing role for user: ", username, " in project: ", data.Projectname, " to: ", data.Role) // Dubble diping and checcking if current user is if ismanager, err := db.GetDb(c).IsProjectManager(username, data.Projectname); err != nil { @@ -42,7 +36,7 @@ func ProjectRoleChange(c *fiber.Ctx) error { } // Change the user's role within the project in the database - if err := db.GetDb(c).ChangeUserRole(data.UserName, data.Projectname, data.Role); err != nil { + if err := db.GetDb(c).ChangeUserRole(username, data.Projectname, data.Role); err != nil { return c.Status(500).SendString(err.Error()) } diff --git a/backend/internal/handlers/projects/PromoteToPm.go b/backend/internal/handlers/projects/PromoteToPm.go deleted file mode 100644 index ffe2215..0000000 --- a/backend/internal/handlers/projects/PromoteToPm.go +++ /dev/null @@ -1,51 +0,0 @@ -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) -} diff --git a/backend/internal/handlers/projects/RemoveUserFromProject.go b/backend/internal/handlers/projects/RemoveUserFromProject.go deleted file mode 100644 index 7aefcf8..0000000 --- a/backend/internal/handlers/projects/RemoveUserFromProject.go +++ /dev/null @@ -1,40 +0,0 @@ -package projects - -import ( - db "ttime/internal/database" - - "github.com/gofiber/fiber/v2" - "github.com/gofiber/fiber/v2/log" - "github.com/golang-jwt/jwt/v5" -) - -func RemoveUserFromProject(c *fiber.Ctx) error { - user := c.Locals("user").(*jwt.Token) - claims := user.Claims.(jwt.MapClaims) - pm_name := claims["name"].(string) - - project := c.Params("projectName") - username := c.Query("userName") - - // Check if the user is a project manager - isPM, err := db.GetDb(c).IsProjectManager(pm_name, project) - if err != nil { - log.Info("Error checking if user is project manager:", err) - return c.Status(500).SendString(err.Error()) - } - - if !isPM { - log.Info("User: ", pm_name, " is not a project manager in project: ", project) - return c.Status(403).SendString("User is not a project manager") - } - - // Remove the user from the project - if err = db.GetDb(c).RemoveUserFromProject(username, project); err != nil { - log.Info("Error removing user from project:", err) - return c.Status(500).SendString(err.Error()) - } - - // Return success message - log.Info("User : ", username, " removed from project: ", project) - return c.SendStatus(fiber.StatusOK) -} diff --git a/backend/internal/handlers/reports/GetAllWeeklyReports.go b/backend/internal/handlers/reports/GetAllWeeklyReports.go deleted file mode 100644 index ee81c82..0000000 --- a/backend/internal/handlers/reports/GetAllWeeklyReports.go +++ /dev/null @@ -1,56 +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" -) - -// 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) -} diff --git a/backend/internal/handlers/reports/GetWeeklyReport.go b/backend/internal/handlers/reports/GetWeeklyReport.go index 04bdc0d..422bc0b 100644 --- a/backend/internal/handlers/reports/GetWeeklyReport.go +++ b/backend/internal/handlers/reports/GetWeeklyReport.go @@ -16,17 +16,11 @@ func GetWeeklyReport(c *fiber.Ctx) error { claims := user.Claims.(jwt.MapClaims) username := claims["name"].(string) + log.Info("Getting weekly report for: ", username) + // Extract project name and week from query parameters projectName := c.Query("projectName") 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 == "" { log.Info("Missing project name or week number") @@ -40,20 +34,8 @@ func GetWeeklyReport(c *fiber.Ctx) error { 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 - report, err := db.GetDb(c).GetWeeklyReport(target_user, projectName, weekInt) + report, err := db.GetDb(c).GetWeeklyReport(username, projectName, weekInt) if err != nil { log.Info("Error getting weekly report from db:", err) return c.Status(500).SendString(err.Error()) diff --git a/backend/internal/handlers/reports/GetWeeklyReportsUserHandler.go b/backend/internal/handlers/reports/GetWeeklyReportsUserHandler.go new file mode 100644 index 0000000..da8a90b --- /dev/null +++ b/backend/internal/handlers/reports/GetWeeklyReportsUserHandler.go @@ -0,0 +1,36 @@ +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) +} diff --git a/backend/main.go b/backend/main.go index 42daa5c..cf58280 100644 --- a/backend/main.go +++ b/backend/main.go @@ -112,16 +112,12 @@ func main() { // All project related routes // projectGroup := api.Group("/project") // Not currently in use - api.Get("/getProjectTimes/:projectName", projects.GetProjectTimesHandler) - api.Get("/getUserProjects/:username", projects.GetUserProjects) + api.Get("/getUserProjects", projects.GetUserProjects) api.Get("/project/:projectId", projects.GetProject) api.Get("/checkIfProjectManager/:projectName", projects.IsProjectManagerHandler) api.Get("/getUsersProject/:projectName", projects.ListAllUsersProject) api.Post("/project", projects.CreateProject) api.Post("/ProjectRoleChange", projects.ProjectRoleChange) - api.Put("/promoteToPm/:projectName", projects.PromoteToPm) - api.Put("/addUserToProject/:projectName", projects.AddUserToProjectHandler) - api.Delete("/removeUserFromProject/:projectName", projects.RemoveUserFromProject) api.Delete("/removeProject/:projectName", projects.RemoveProject) api.Delete("/project/:projectID", projects.DeleteProject) @@ -129,9 +125,10 @@ func main() { // reportGroup := api.Group("/report") // Not currently in use api.Get("/getWeeklyReport", reports.GetWeeklyReport) api.Get("/getUnsignedReports/:projectName", reports.GetUnsignedReports) - api.Get("/getAllWeeklyReports/:projectName", reports.GetAllWeeklyReports) + api.Get("/getWeeklyReportsUser/:projectName", reports.GetWeeklyReportsUserHandler) api.Post("/submitWeeklyReport", reports.SubmitWeeklyReport) api.Put("/signReport/:reportId", reports.SignReport) + api.Put("/addUserToProject", projects.AddUserToProjectHandler) api.Put("/updateWeeklyReport", reports.UpdateWeeklyReport) // Announce the port we are listening on and start the server diff --git a/frontend/src/API/API.ts b/frontend/src/API/API.ts index 3c0f0e9..886c957 100644 --- a/frontend/src/API/API.ts +++ b/frontend/src/API/API.ts @@ -1,16 +1,13 @@ -import { NewProjMember } from "../Components/AddMember"; -import { ProjectRoleChange } from "../Components/ChangeRole"; -import { projectTimes } from "../Components/GetProjectTimes"; -import { ProjectMember } from "../Components/GetUsersInProject"; import { - UpdateWeeklyReport, NewWeeklyReport, NewUser, User, Project, NewProject, + UserProjectMember, WeeklyReport, StrNameChange, + NewProjMember, } from "../Types/goTypes"; /** @@ -76,7 +73,10 @@ interface API { * @param {string} token The authentication token. * @returns {Promise>} A promise resolving to an API response with the created project. */ - createProject(project: NewProject, token: string): Promise>; + createProject( + project: NewProject, + token: string, + ): Promise>; /** Submits a weekly report * @param {NewWeeklyReport} weeklyReport The weekly report object. @@ -88,31 +88,16 @@ interface API { token: string, ): Promise>; - /** - * Updates a weekly report. - * @param {UpdateWeeklyReport} weeklyReport The updated weekly report object. - * @param {string} token The authentication token. - * @returns {Promise>} A promise containing the API response with the updated report. - */ - updateWeeklyReport( - weeklyReport: UpdateWeeklyReport, - token: string, - ): Promise>; - - /** 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. + /** Gets a weekly report for a specific user, project and week * @param {string} projectName The name of the project. * @param {string} week The week number. * @param {string} token The authentication token. - * @param {string} targetUser The username of the target user. Defaults to token user. * @returns {Promise>} A promise resolving to an API response with the retrieved report. */ getWeeklyReport( projectName: string, week: string, token: string, - targetUser?: string, ): Promise>; /** @@ -122,21 +107,16 @@ interface API { * @param {string} token The token of the user * @returns {APIResponse} A list of weekly reports */ - getAllWeeklyReportsForUser( + getWeeklyReportsForUser( projectName: string, token: string, - targetUser?: string, ): Promise>; /** Gets all the projects of a user - * @param {string} username - The authentication token. * @param {string} token - The authentication token. * @returns {Promise>} A promise containing the API response with the user's projects. */ - getUserProjects( - username: string, - token: string, - ): Promise>; + getUserProjects(token: string): Promise>; /** Gets a project by its id. * @param {number} id The id of the project to retrieve. @@ -144,16 +124,6 @@ interface API { */ getProject(id: number): Promise>; - /** Gets a projects reported time - * @param {string} projectName The name of the project. - * @param {string} token The usertoken. - * @returns {Promise>} A promise resolving to an API response containing the project times. - */ - getProjectTimes( - projectName: string, - token: string, - ): Promise>; - /** Gets a list of all users. * @param {string} token The authentication token of the requesting user. * @returns {Promise>} A promise resolving to an API response containing the list of users. @@ -163,18 +133,7 @@ interface API { getAllUsersProject( projectName: string, token: string, - ): Promise>; - - /** Gets all unsigned reports in a project. - * @param {string} projectName The name of the project. - * @param {string} token The authentication token. - * @returns {Promise>} A promise resolving to an API response containing the list of unsigned reports. - */ - getUnsignedReportsInProject( - projectName: string, - token: string, - ): Promise>; - + ): Promise>; /** * Changes the username of a user in the database. * @param {StrNameChange} data The object containing the previous and new username. @@ -185,27 +144,10 @@ interface API { data: StrNameChange, token: string, ): Promise>; - /** - * 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>} A promise resolving to an API response. - */ - changeUserRole( - roleInfo: ProjectRoleChange, - token: string, - ): Promise>; - addUserToProject( user: NewProjMember, token: string, - ): Promise>; - - removeUserFromProject( - user: string, - project: string, - token: string, - ): Promise>; + ): Promise>; removeProject( projectName: string, @@ -219,18 +161,8 @@ interface API { * @param {number} reportId The id of the report to sign * @param {string} token The authentication token */ - signReport(reportId: number, token: string): Promise>; - - /** - * 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} A promise resolving to an API response. - */ - promoteToPm( - userName: string, - projectName: string, + signReport( + reportId: number, token: string, ): Promise>; } @@ -320,7 +252,7 @@ export const api: API = { async createProject( project: NewProject, token: string, - ): Promise> { + ): Promise> { try { const response = await fetch("/api/project", { method: "POST", @@ -334,17 +266,18 @@ export const api: API = { if (!response.ok) { return { success: false, message: "Failed to create project" }; } else { - return { success: true }; + const data = (await response.json()) as Project; + return { success: true, data }; } } catch (e) { - return { success: false, message: "Failed to create project!" }; + return { success: false, message: "Failed to create project" }; } }, async addUserToProject( user: NewProjMember, token: string, - ): Promise> { + ): Promise> { try { const response = await fetch("/api/addUserToProject", { method: "PUT", @@ -365,31 +298,6 @@ export const api: API = { } }, - async removeUserFromProject( - user: string, - project: string, - token: string, - ): Promise> { - try { - const response = await fetch( - `/api/removeUserFromProject/${project}?userName=${user}`, - { - method: "DELETE", - headers: { - "Content-Type": "application/json", - Authorization: "Bearer " + token, - }, - }, - ); - if (!response.ok) { - return { success: false, message: "Failed to remove member" }; - } - } catch (e) { - return { success: false, message: "Failed to remove member" }; - } - return { success: true, message: "Removed member" }; - }, - async renewToken(token: string): Promise> { try { const response = await fetch("/api/loginrenew", { @@ -411,39 +319,9 @@ export const api: API = { } }, - async changeUserRole( - roleInfo: ProjectRoleChange, - token: string, - ): Promise> { + async getUserProjects(token: string): Promise> { try { - 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> { - try { - const response = await fetch(`/api/getUserProjects/${username}`, { + const response = await fetch("/api/getUserProjects", { method: "GET", headers: { "Content-Type": "application/json", @@ -468,37 +346,6 @@ export const api: API = { } }, - async getProjectTimes( - projectName: string, - token: string, - ): Promise> { - 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( weeklyReport: NewWeeklyReport, token: string, @@ -530,46 +377,14 @@ export const api: API = { } }, - async updateWeeklyReport( - weeklyReport: UpdateWeeklyReport, - token: string, - ): Promise> { - 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( projectName: string, week: string, token: string, - targetUser?: string, ): Promise> { try { const response = await fetch( - `/api/getWeeklyReport?projectName=${projectName}&week=${week}&targetUser=${targetUser ?? ""}`, + `/api/getWeeklyReport?projectName=${projectName}&week=${week}`, { method: "GET", headers: { @@ -590,22 +405,18 @@ export const api: API = { } }, - async getAllWeeklyReportsForUser( + async getWeeklyReportsForUser( projectName: string, token: string, - targetUser?: string, ): Promise> { try { - const response = await fetch( - `/api/getAllWeeklyReports/${projectName}?targetUser=${targetUser ?? ""}`, - { - method: "GET", - headers: { - "Content-Type": "application/json", - Authorization: "Bearer " + token, - }, + const response = await fetch(`/api/getWeeklyReportsUser/${projectName}`, { + method: "GET", + headers: { + "Content-Type": "application/json", + Authorization: "Bearer " + token, }, - ); + }); if (!response.ok) { return { @@ -702,7 +513,7 @@ export const api: API = { async getAllUsersProject( projectName: string, token: string, - ): Promise> { + ): Promise> { try { const response = await fetch(`/api/getUsersProject/${projectName}`, { method: "GET", @@ -718,7 +529,7 @@ export const api: API = { message: "Failed to get users", }); } else { - const data = (await response.json()) as ProjectMember[]; + const data = (await response.json()) as UserProjectMember[]; return Promise.resolve({ success: true, data }); } } catch (e) { @@ -729,38 +540,6 @@ export const api: API = { } }, - async getUnsignedReportsInProject( - projectName: string, - token: string, - ): Promise> { - 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( data: StrNameChange, token: string, @@ -790,7 +569,7 @@ export const api: API = { token: string, ): Promise> { try { - const response = await fetch(`/api/removeProject/${projectName}`, { + const response = await fetch(`/api/projectdelete/${projectName}`, { method: "DELETE", headers: { "Content-Type": "application/json", @@ -836,36 +615,5 @@ export const api: API = { } catch (e) { return { success: false, message: "Failed to sign report" }; } - }, - - async promoteToPm( - userName: string, - projectName: string, - token: string, - ): Promise> { - 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" }; - }, + } }; diff --git a/frontend/src/Components/AddMember.tsx b/frontend/src/Components/AddMember.tsx index 194afe8..d29be68 100644 --- a/frontend/src/Components/AddMember.tsx +++ b/frontend/src/Components/AddMember.tsx @@ -1,10 +1,5 @@ import { APIResponse, api } from "../API/API"; - -export interface NewProjMember { - username: string; - role: string; - projectname: string; -} +import { NewProjMember } from "../Types/goTypes"; /** * Tries to add a member to a project @@ -26,7 +21,7 @@ function AddMember(props: { memberToAdd: NewProjMember }): boolean { props.memberToAdd, localStorage.getItem("accessToken") ?? "", ) - .then((response: APIResponse) => { + .then((response: APIResponse) => { if (response.success) { alert("Member added"); added = true; diff --git a/frontend/src/Components/AddProject.tsx b/frontend/src/Components/AddProject.tsx index e838d14..839cfa7 100644 --- a/frontend/src/Components/AddProject.tsx +++ b/frontend/src/Components/AddProject.tsx @@ -1,6 +1,6 @@ import { useState } from "react"; import { APIResponse, api } from "../API/API"; -import { NewProject } from "../Types/goTypes"; +import { NewProject, Project } from "../Types/goTypes"; import InputField from "./InputField"; import Logo from "../assets/Logo.svg"; import Button from "./Button"; @@ -10,26 +10,27 @@ import Button from "./Button"; * @param {Object} props - Project name and description * @returns {boolean} True if created, false if not */ -function CreateProject(props: { name: string; description: string }): void { +function CreateProject(props: { name: string; description: string }): boolean { const project: NewProject = { name: props.name, description: props.description, }; + let created = false; + api .createProject(project, localStorage.getItem("accessToken") ?? "") - .then((response: APIResponse) => { + .then((response: APIResponse) => { if (response.success) { - alert("Project added!"); + created = true; } else { - alert("Project NOT added!"); console.error(response.message); } }) .catch((error) => { - alert("Project NOT added!"); console.error("An error occurred during creation:", error); }); + return created; } /** @@ -48,10 +49,7 @@ function AddProject() { 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) => { e.preventDefault(); - CreateProject({ - name: name, - description: description, - }); + CreateProject({ name: name, description: description }); }} > Create a new project -
- { - e.preventDefault(); - setName(e.target.value); - }} - /> - { - e.preventDefault(); - setDescription(e.target.value); - }} - /> -
+ { + setName(e.target.value); + }} + /> + { + setDescription(e.target.value); + }} + />
- ); -} diff --git a/frontend/src/Components/ChangeUsername.tsx b/frontend/src/Components/ChangeUsername.tsx index 78d7da9..e297a04 100644 --- a/frontend/src/Components/ChangeUsername.tsx +++ b/frontend/src/Components/ChangeUsername.tsx @@ -1,26 +1,61 @@ -import { APIResponse, api } from "../API/API"; -import { StrNameChange } from "../Types/goTypes"; +import React, { useState } from "react"; +import InputField from "./InputField"; +import { api } from "../API/API"; -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) => { - if (response.success) { - alert("Name changed successfully"); - location.reload(); - } else { - alert("Name not changed"); - console.error(response.message); +function ChangeUsername(): JSX.Element { + const [newUsername, setNewUsername] = useState(""); + const [errorMessage, setErrorMessage] = useState(""); + + const handleChange = (e: React.ChangeEvent): void => { + setNewUsername(e.target.value); + }; + + const handleSubmit = async (): Promise => { + try { + // Call the API function to change the username + const token = localStorage.getItem("accessToken"); + if (!token) { + throw new Error("Access token not found"); } - }) - .catch((error) => { - alert("Name not changed"); - console.error("An error occurred during change:", error); + + const response = await api.changeUserName( + { prevName: "currentName", newName: newUsername }, + token, + ); + + if (response.success) { + // Optionally, add a success message or redirect the user + console.log("Username changed successfully"); + } else { + // Handle the error message + console.error("Failed to change username:", response.message); + setErrorMessage(response.message ?? "Failed to change username"); + } + } catch (error) { + console.error("Error changing username:", error); + // Optionally, handle the error + setErrorMessage("Failed to change username"); + } + }; + + const handleButtonClick = (): void => { + handleSubmit().catch((error) => { + console.error("Error in handleSubmit:", error); }); + }; + + return ( +
+ + {errorMessage &&
{errorMessage}
} + +
+ ); } export default ChangeUsername; diff --git a/frontend/src/Components/DeleteProject.tsx b/frontend/src/Components/DeleteProject.tsx deleted file mode 100644 index 4add857..0000000 --- a/frontend/src/Components/DeleteProject.tsx +++ /dev/null @@ -1,33 +0,0 @@ -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) => { - 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; diff --git a/frontend/src/Components/DeleteUser.tsx b/frontend/src/Components/DeleteUser.tsx index 7c5e8e8..d1dbc7f 100644 --- a/frontend/src/Components/DeleteUser.tsx +++ b/frontend/src/Components/DeleteUser.tsx @@ -3,7 +3,7 @@ import { api, APIResponse } from "../API/API"; /** * Use to remove a user from the system - * @param {string} props.usernameToDelete - The username of user to remove + * @param props - The username of user to remove * @returns {boolean} True if removed, false if not * @example * const exampleUsername = "user"; @@ -29,7 +29,7 @@ function DeleteUser(props: { usernameToDelete: string }): boolean { }) .catch((error) => { alert("User has not been deleted"); - console.error("An error occurred during deletion:", error); + console.error("An error occurred during creation:", error); }); return removed; } diff --git a/frontend/src/Components/DisplayUnsignedReports.tsx b/frontend/src/Components/DisplayUnsignedReports.tsx index 232cb31..780f20c 100644 --- a/frontend/src/Components/DisplayUnsignedReports.tsx +++ b/frontend/src/Components/DisplayUnsignedReports.tsx @@ -1,7 +1,12 @@ import { useState, useEffect } from "react"; import { Link, useParams } from "react-router-dom"; -import { api } from "../API/API"; -import { WeeklyReport } from "../Types/goTypes"; + +interface UnsignedReports { + 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. @@ -9,25 +14,80 @@ import { WeeklyReport } from "../Types/goTypes"; */ function DisplayUserProject(): JSX.Element { const { projectName } = useParams(); - const [unsignedReports, setUnsignedReports] = useState([]); + const [unsignedReports, setUnsignedReports] = useState([]); //const navigate = useNavigate(); - useEffect(() => { - const getUnsignedReports = async (): Promise => { - 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); - } - }; + // const getUnsignedReports = async (): Promise => { + // 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 => { + // 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 => { + // 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(() => { void getUnsignedReports(); - }, [projectName]); // Include 'projectName' in the dependency array + }, []); return ( <> @@ -35,40 +95,32 @@ function DisplayUserProject(): JSX.Element { All Unsigned Reports In: {projectName}{" "}
- {unsignedReports.map((unsignedReport: WeeklyReport, index: number) => ( -

-
-
- UserID: -

{unsignedReport.userId}

- Week: -

{unsignedReport.week}

- Total Time: -

- {unsignedReport.developmentTime + - unsignedReport.meetingTime + - unsignedReport.adminTime + - unsignedReport.ownWorkTime + - unsignedReport.studyTime + - unsignedReport.testingTime} -

- Signed: -

NO

-
-
-
- -

- View Report -

- + {unsignedReports.map( + (unsignedReport: UnsignedReports, index: number) => ( +

+
+
+

{unsignedReport.username}

+ Week: +

{unsignedReport.week}

+ Signed: +

NO

+
+
+
+ +

+ View Report +

+ +
-

-

- ))} + + ), + )}
); diff --git a/frontend/src/Components/DisplayUserProjects.tsx b/frontend/src/Components/DisplayUserProjects.tsx index 65bd4f5..29e4bcb 100644 --- a/frontend/src/Components/DisplayUserProjects.tsx +++ b/frontend/src/Components/DisplayUserProjects.tsx @@ -1,7 +1,6 @@ -import { useState } from "react"; +import { useState, useEffect } from "react"; import { Project } from "../Types/goTypes"; import { useNavigate } from "react-router-dom"; -import GetProjects from "./GetProjects"; import { api } from "../API/API"; /** @@ -12,20 +11,22 @@ function DisplayUserProject(): JSX.Element { const [projects, setProjects] = useState([]); const navigate = useNavigate(); - GetProjects({ - setProjectsProp: setProjects, - username: localStorage.getItem("username") ?? "", - }); + const getProjects = async (): Promise => { + const token = localStorage.getItem("accessToken") ?? ""; + const response = await api.getUserProjects(token); + console.log(response); + if (response.success) { + setProjects(response.data ?? []); + } else { + console.error(response.message); + } + }; const handleProjectClick = async (projectName: string): Promise => { const token = localStorage.getItem("accessToken") ?? ""; const response = await api.checkIfProjectManager(projectName, token); - console.log(response.data); if (response.success) { - if ( - (response.data as unknown as { isProjectManager: boolean }) - .isProjectManager - ) { + if (response.data) { navigate(`/PMProjectPage/${projectName}`); } else { navigate(`/project/${projectName}`); @@ -36,6 +37,11 @@ function DisplayUserProject(): JSX.Element { } }; + // Call getProjects when the component mounts + useEffect(() => { + void getProjects(); + }, []); + return ( <>

Your Projects

diff --git a/frontend/src/Components/EditWeeklyReport.tsx b/frontend/src/Components/EditWeeklyReport.tsx index d56ee42..384359e 100644 --- a/frontend/src/Components/EditWeeklyReport.tsx +++ b/frontend/src/Components/EditWeeklyReport.tsx @@ -1,5 +1,5 @@ import { useState, useEffect } from "react"; -import { WeeklyReport, UpdateWeeklyReport } from "../Types/goTypes"; +import { WeeklyReport, NewWeeklyReport } from "../Types/goTypes"; import { api } from "../API/API"; import { useNavigate, useParams } from "react-router-dom"; import Button from "./Button"; @@ -22,7 +22,6 @@ export default function GetWeeklyReport(): JSX.Element { projectName: string; fetchedWeek: string; }>(); - const username = localStorage.getItem("userName") ?? ""; console.log(projectName, fetchedWeek); useEffect(() => { @@ -61,9 +60,8 @@ export default function GetWeeklyReport(): JSX.Element { void fetchWeeklyReport(); }, [projectName, fetchedWeek, token]); - const handleUpdateWeeklyReport = async (): Promise => { - const updateWeeklyReport: UpdateWeeklyReport = { - userName: username, + const handleNewWeeklyReport = async (): Promise => { + const newWeeklyReport: NewWeeklyReport = { projectName: projectName ?? "", week, developmentTime, @@ -74,7 +72,7 @@ export default function GetWeeklyReport(): JSX.Element { testingTime, }; - await api.updateWeeklyReport(updateWeeklyReport, token); + await api.submitWeeklyReport(newWeeklyReport, token); }; const navigate = useNavigate(); @@ -91,8 +89,7 @@ export default function GetWeeklyReport(): JSX.Element { return; } e.preventDefault(); - void handleUpdateWeeklyReport(); - alert("Changes submitted"); + void handleNewWeeklyReport(); navigate(-1); }} > @@ -131,12 +128,7 @@ export default function GetWeeklyReport(): JSX.Element { }} onKeyDown={(event) => { const keyValue = event.key; - if ( - !/\d/.test(keyValue) && - keyValue !== "Backspace" && - keyValue !== "ArrowLeft" && - keyValue !== "ArrowRight" - ) + if (!/\d/.test(keyValue) && keyValue !== "Backspace") event.preventDefault(); }} /> @@ -160,12 +152,7 @@ export default function GetWeeklyReport(): JSX.Element { }} onKeyDown={(event) => { const keyValue = event.key; - if ( - !/\d/.test(keyValue) && - keyValue !== "Backspace" && - keyValue !== "ArrowLeft" && - keyValue !== "ArrowRight" - ) + if (!/\d/.test(keyValue) && keyValue !== "Backspace") event.preventDefault(); }} /> @@ -189,12 +176,7 @@ export default function GetWeeklyReport(): JSX.Element { }} onKeyDown={(event) => { const keyValue = event.key; - if ( - !/\d/.test(keyValue) && - keyValue !== "Backspace" && - keyValue !== "ArrowLeft" && - keyValue !== "ArrowRight" - ) + if (!/\d/.test(keyValue) && keyValue !== "Backspace") event.preventDefault(); }} /> @@ -218,12 +200,7 @@ export default function GetWeeklyReport(): JSX.Element { }} onKeyDown={(event) => { const keyValue = event.key; - if ( - !/\d/.test(keyValue) && - keyValue !== "Backspace" && - keyValue !== "ArrowLeft" && - keyValue !== "ArrowRight" - ) + if (!/\d/.test(keyValue) && keyValue !== "Backspace") event.preventDefault(); }} /> @@ -247,12 +224,7 @@ export default function GetWeeklyReport(): JSX.Element { }} onKeyDown={(event) => { const keyValue = event.key; - if ( - !/\d/.test(keyValue) && - keyValue !== "Backspace" && - keyValue !== "ArrowLeft" && - keyValue !== "ArrowRight" - ) + if (!/\d/.test(keyValue) && keyValue !== "Backspace") event.preventDefault(); }} /> @@ -276,12 +248,7 @@ export default function GetWeeklyReport(): JSX.Element { }} onKeyDown={(event) => { const keyValue = event.key; - if ( - !/\d/.test(keyValue) && - keyValue !== "Backspace" && - keyValue !== "ArrowLeft" && - keyValue !== "ArrowRight" - ) + if (!/\d/.test(keyValue) && keyValue !== "Backspace") event.preventDefault(); }} /> diff --git a/frontend/src/Components/GetProjectTimes.tsx b/frontend/src/Components/GetProjectTimes.tsx deleted file mode 100644 index 38288ec..0000000 --- a/frontend/src/Components/GetProjectTimes.tsx +++ /dev/null @@ -1,59 +0,0 @@ -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(); - * GetProjectTimes({ setTimesProp: setTimes, projectName: projectName }); - */ -function GetProjectTimes(props: { - setTimesProp: Dispatch>; - projectName: string; -}): void { - const setTimes: Dispatch> = - props.setTimesProp; - useEffect(() => { - const fetchUsers = async (): Promise => { - 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; diff --git a/frontend/src/Components/GetProjects.tsx b/frontend/src/Components/GetProjects.tsx index bd6c303..d6ab1f7 100644 --- a/frontend/src/Components/GetProjects.tsx +++ b/frontend/src/Components/GetProjects.tsx @@ -4,17 +4,14 @@ import { api } from "../API/API"; /** * Gets all projects that user is a member of - * @param {Dispatch} props.setProjectsProp - A setStateAction for the array you want to put projects in - * @param {string} props.username - Username + * @param props - A setStateAction for the array you want to put projects in * @returns {void} Nothing * @example - * const username = "Example"; * const [projects, setProjects] = useState([]); - * GetProjects({ setProjectsProp: setProjects, username: username }); + * GetAllUsers({ setProjectsProp: setProjects }); */ function GetProjects(props: { setProjectsProp: Dispatch>; - username: string; }): void { const setProjects: Dispatch> = props.setProjectsProp; @@ -22,7 +19,7 @@ function GetProjects(props: { const fetchUsers = async (): Promise => { try { const token = localStorage.getItem("accessToken") ?? ""; - const response = await api.getUserProjects(props.username, token); + const response = await api.getUserProjects(token); if (response.success) { setProjects(response.data ?? []); } else { @@ -34,7 +31,7 @@ function GetProjects(props: { }; void fetchUsers(); - }, [props.username, setProjects]); + }, [setProjects]); } export default GetProjects; diff --git a/frontend/src/Components/GetUsersInProject.tsx b/frontend/src/Components/GetUsersInProject.tsx index eb32e9b..acdd965 100644 --- a/frontend/src/Components/GetUsersInProject.tsx +++ b/frontend/src/Components/GetUsersInProject.tsx @@ -1,25 +1,20 @@ import { Dispatch, useEffect } from "react"; +import { UserProjectMember } from "../Types/goTypes"; import { api } from "../API/API"; -export interface ProjectMember { - Username: string; - UserRole: string; -} - /** - * Gets all members of a project - * @param string - The project's name - * @param Dispatch - A setStateAction for the array you want to put members in + * Gets all projects that user is a member of + * @param props - A setStateAction for the array you want to put projects in * @returns {void} Nothing * @example - * const [users, setUsers] = useState([]); - * GetUsersInProject({ projectName: props.projectname, setUsersProp: setUsers }); + * const [projects, setProjects] = useState([]); + * GetAllUsers({ setProjectsProp: setProjects }); */ function GetUsersInProject(props: { projectName: string; - setUsersProp: Dispatch>; + setUsersProp: Dispatch>; }): void { - const setUsers: Dispatch> = + const setUsers: Dispatch> = props.setUsersProp; useEffect(() => { const fetchUsers = async (): Promise => { @@ -29,10 +24,10 @@ function GetUsersInProject(props: { if (response.success) { setUsers(response.data ?? []); } else { - console.error("Failed to fetch members:", response.message); + console.error("Failed to fetch projects:", response.message); } } catch (error) { - console.error("Error fetching members:", error); + console.error("Error fetching projects:", error); } }; void fetchUsers(); diff --git a/frontend/src/Components/InputField.tsx b/frontend/src/Components/InputField.tsx index 699d8fa..639b4ca 100644 --- a/frontend/src/Components/InputField.tsx +++ b/frontend/src/Components/InputField.tsx @@ -19,7 +19,7 @@ function InputField(props: { onChange: (e: React.ChangeEvent) => void; }): JSX.Element { return ( -
+
-
- - - ); -} - -export default MemberInfoModal; diff --git a/frontend/src/Components/NewWeeklyReport.tsx b/frontend/src/Components/NewWeeklyReport.tsx index 1bb5cd4..f684b0c 100644 --- a/frontend/src/Components/NewWeeklyReport.tsx +++ b/frontend/src/Components/NewWeeklyReport.tsx @@ -139,12 +139,7 @@ export default function NewWeeklyReport(): JSX.Element { }} onKeyDown={(event) => { const keyValue = event.key; - if ( - !/\d/.test(keyValue) && - keyValue !== "Backspace" && - keyValue !== "ArrowLeft" && - keyValue !== "ArrowRight" - ) + if (!/\d/.test(keyValue) && keyValue !== "Backspace") event.preventDefault(); }} /> @@ -168,12 +163,7 @@ export default function NewWeeklyReport(): JSX.Element { }} onKeyDown={(event) => { const keyValue = event.key; - if ( - !/\d/.test(keyValue) && - keyValue !== "Backspace" && - keyValue !== "ArrowLeft" && - keyValue !== "ArrowRight" - ) + if (!/\d/.test(keyValue) && keyValue !== "Backspace") event.preventDefault(); }} /> @@ -197,12 +187,7 @@ export default function NewWeeklyReport(): JSX.Element { }} onKeyDown={(event) => { const keyValue = event.key; - if ( - !/\d/.test(keyValue) && - keyValue !== "Backspace" && - keyValue !== "ArrowLeft" && - keyValue !== "ArrowRight" - ) + if (!/\d/.test(keyValue) && keyValue !== "Backspace") event.preventDefault(); }} /> @@ -226,12 +211,7 @@ export default function NewWeeklyReport(): JSX.Element { }} onKeyDown={(event) => { const keyValue = event.key; - if ( - !/\d/.test(keyValue) && - keyValue !== "Backspace" && - keyValue !== "ArrowLeft" && - keyValue !== "ArrowRight" - ) + if (!/\d/.test(keyValue) && keyValue !== "Backspace") event.preventDefault(); }} /> @@ -255,12 +235,7 @@ export default function NewWeeklyReport(): JSX.Element { }} onKeyDown={(event) => { const keyValue = event.key; - if ( - !/\d/.test(keyValue) && - keyValue !== "Backspace" && - keyValue !== "ArrowLeft" && - keyValue !== "ArrowRight" - ) + if (!/\d/.test(keyValue) && keyValue !== "Backspace") event.preventDefault(); }} /> @@ -284,12 +259,7 @@ export default function NewWeeklyReport(): JSX.Element { }} onKeyDown={(event) => { const keyValue = event.key; - if ( - !/\d/.test(keyValue) && - keyValue !== "Backspace" && - keyValue !== "ArrowLeft" && - keyValue !== "ArrowRight" - ) + if (!/\d/.test(keyValue) && keyValue !== "Backspace") event.preventDefault(); }} /> diff --git a/frontend/src/Components/ProjectInfoModal.tsx b/frontend/src/Components/ProjectInfoModal.tsx index 1f98d79..3075b19 100644 --- a/frontend/src/Components/ProjectInfoModal.tsx +++ b/frontend/src/Components/ProjectInfoModal.tsx @@ -1,54 +1,31 @@ -import { useEffect, useRef, useState } from "react"; +import { useState } from "react"; import Button from "./Button"; -import GetUsersInProject, { ProjectMember } from "./GetUsersInProject"; +import { UserProjectMember } from "../Types/goTypes"; +import GetUsersInProject from "./GetUsersInProject"; import { Link } from "react-router-dom"; -import GetProjectTimes, { projectTimes } from "./GetProjectTimes"; -import DeleteProject from "./DeleteProject"; function ProjectInfoModal(props: { + isVisible: boolean; projectname: string; onClose: () => void; onClick: (username: string) => void; }): JSX.Element { - const [users, setUsers] = useState([]); - const [times, setTimes] = useState(); - const totalTime = useRef(0); + const [users, setUsers] = useState([]); GetUsersInProject({ projectName: props.projectname, setUsersProp: setUsers }); - - GetProjectTimes({ setTimesProp: setTimes, projectName: props.projectname }); - - useEffect(() => { - if (times?.totalTime !== undefined) { - totalTime.current = times.totalTime; - } - }, [times]); + if (!props.isVisible) return <>; return (
-
-
+
+

- {props.projectname} + {localStorage.getItem("projectName") ?? ""}

-
-

Statistics:

-
-
-

Number of members: {users.length}

-

- Total time reported:{" "} - {Math.floor(totalTime.current / 60 / 24) + " d "} - {Math.floor((totalTime.current / 60) % 24) + " h "} - {(totalTime.current % 60) + " m "} -

-
-
-

Project members:

-
-
+

Project members:

+
    {users.map((user) => ( @@ -68,44 +45,31 @@ function ProjectInfoModal(props: { ))}
-
+
+
+
+ +
diff --git a/frontend/src/Components/ProjectListAdmin.tsx b/frontend/src/Components/ProjectListAdmin.tsx index 294a131..f25ee47 100644 --- a/frontend/src/Components/ProjectListAdmin.tsx +++ b/frontend/src/Components/ProjectListAdmin.tsx @@ -1,7 +1,7 @@ import { useState } from "react"; import { NewProject } from "../Types/goTypes"; import ProjectInfoModal from "./ProjectInfoModal"; -import MemberInfoModal from "./MemberInfoModal"; +import UserInfoModal from "./UserInfoModal"; /** * A list of projects for admin manage projects page, that sets an onClick @@ -18,7 +18,7 @@ export function ProjectListAdmin(props: { projects: NewProject[]; }): JSX.Element { const [projectModalVisible, setProjectModalVisible] = useState(false); - const [projectName, setProjectName] = useState(""); + const [projectname, setProjectname] = useState(""); const [userModalVisible, setUserModalVisible] = useState(false); const [username, setUsername] = useState(""); @@ -28,36 +28,39 @@ export function ProjectListAdmin(props: { }; const handleClickProject = (projectname: string): void => { - setProjectName(projectname); + setProjectname(projectname); + localStorage.setItem("projectName", projectname); setProjectModalVisible(true); }; const handleCloseProject = (): void => { - setProjectName(""); + setProjectname(""); setProjectModalVisible(false); }; const handleCloseUser = (): void => { - setUsername(""); + setProjectname(""); setUserModalVisible(false); }; return ( <> - {projectModalVisible && ( - - )} - {userModalVisible && ( - - )} + + { + return; + }} + isVisible={userModalVisible} + username={username} + />
    {props.projects.map((project) => ( diff --git a/frontend/src/Components/ProjectMembers.tsx b/frontend/src/Components/ProjectMembers.tsx index 52e8559..60ffcd9 100644 --- a/frontend/src/Components/ProjectMembers.tsx +++ b/frontend/src/Components/ProjectMembers.tsx @@ -1,15 +1,31 @@ -import { useState } from "react"; +import { useEffect, useState } from "react"; import { Link, useParams } from "react-router-dom"; -import GetUsersInProject, { ProjectMember } from "./GetUsersInProject"; +import { api } from "../API/API"; +import { UserProjectMember } from "../Types/goTypes"; function ProjectMembers(): JSX.Element { const { projectName } = useParams(); - const [projectMembers, setProjectMembers] = useState([]); + const [projectMembers, setProjectMembers] = useState([]); - GetUsersInProject({ - projectName: projectName ?? "", - setUsersProp: setProjectMembers, - }); + useEffect(() => { + const getProjectMembers = async (): Promise => { + const token = localStorage.getItem("accessToken") ?? ""; + 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 ( <> diff --git a/frontend/src/Components/Register.tsx b/frontend/src/Components/Register.tsx index 68e0979..6192637 100644 --- a/frontend/src/Components/Register.tsx +++ b/frontend/src/Components/Register.tsx @@ -22,8 +22,6 @@ export default function Register(): JSX.Element { const response = await api.registerUser(newUser); if (response.success) { alert("User added!"); - setPassword(""); - setUsername(""); } else { alert("User not added"); setErrMessage(response.message ?? "Unknown error"); @@ -49,24 +47,22 @@ export default function Register(): JSX.Element {

    Register New User

    -
    - { - setUsername(e.target.value); - }} - /> - { - setPassword(e.target.value); - }} - /> -
    + { + setUsername(e.target.value); + }} + /> + { + setPassword(e.target.value); + }} + />
    - )} -

    Member of these projects:

    - -
    -
); diff --git a/frontend/src/Components/UserListAdmin.tsx b/frontend/src/Components/UserListAdmin.tsx index 76cae9f..c08b05c 100644 --- a/frontend/src/Components/UserListAdmin.tsx +++ b/frontend/src/Components/UserListAdmin.tsx @@ -1,5 +1,6 @@ import { useState } from "react"; import UserInfoModal from "./UserInfoModal"; +import DeleteUser from "./DeleteUser"; /** * A list of users for admin manage users page, that sets an onClick @@ -29,7 +30,9 @@ export function UserListAdmin(props: { users: string[] }): JSX.Element { return ( <> DeleteUser} isVisible={modalVisible} username={username} /> diff --git a/frontend/src/Components/UserProjectListAdmin.tsx b/frontend/src/Components/UserProjectListAdmin.tsx index bc85c5b..1b7b923 100644 --- a/frontend/src/Components/UserProjectListAdmin.tsx +++ b/frontend/src/Components/UserProjectListAdmin.tsx @@ -1,17 +1,35 @@ -import { useState } from "react"; +import { useEffect, useState } from "react"; +import { api } from "../API/API"; import { Project } from "../Types/goTypes"; -import GetProjects from "./GetProjects"; -function UserProjectListAdmin(props: { username: string }): JSX.Element { +function UserProjectListAdmin(): JSX.Element { const [projects, setProjects] = useState([]); - GetProjects({ setProjectsProp: setProjects, username: props.username }); + useEffect(() => { + const fetchProjects = async (): Promise => { + 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 ( -
-
    +
    +
      {projects.map((project) => ( -
    • +
    • {project.name}
    • ))} diff --git a/frontend/src/Components/ViewOtherTimeReport.tsx b/frontend/src/Components/ViewOtherTimeReport.tsx index bde0529..32e0716 100644 --- a/frontend/src/Components/ViewOtherTimeReport.tsx +++ b/frontend/src/Components/ViewOtherTimeReport.tsx @@ -1,5 +1,5 @@ import { useState, useEffect } from "react"; -import { WeeklyReport } from "../Types/goTypes"; +import { WeeklyReport, NewWeeklyReport } from "../Types/goTypes"; import { api } from "../API/API"; import { useNavigate, useParams } from "react-router-dom"; import Button from "./Button"; @@ -18,7 +18,6 @@ export default function GetOtherUsersReport(): JSX.Element { const [ownWorkTime, setOwnWorkTime] = useState(0); const [studyTime, setStudyTime] = useState(0); const [testingTime, setTestingTime] = useState(0); - const [reportId, setReportId] = useState(0); const token = localStorage.getItem("accessToken") ?? ""; const { projectName } = useParams(); @@ -46,7 +45,6 @@ export default function GetOtherUsersReport(): JSX.Element { studyTime: 0, testingTime: 0, }; - setReportId(report.reportId); setWeek(report.week); setDevelopmentTime(report.developmentTime); setMeetingTime(report.meetingTime); @@ -63,23 +61,30 @@ export default function GetOtherUsersReport(): JSX.Element { }); const handleSignWeeklyReport = async (): Promise => { - await api.signReport(reportId, token); + const newWeeklyReport: NewWeeklyReport = { + projectName: projectName ?? "", + week, + developmentTime, + meetingTime, + adminTime, + ownWorkTime, + studyTime, + testingTime, + }; + + await api.submitWeeklyReport(newWeeklyReport, token); }; const navigate = useNavigate(); return ( <> -

      - {" "} - UserId: {username}'s Report -

      +

      {username}'s Report

      { e.preventDefault(); void handleSignWeeklyReport(); - alert("Report successfully signed!"); navigate(-1); }} > @@ -107,10 +112,7 @@ export default function GetOtherUsersReport(): JSX.Element { type="text" min="0" className="border-2 border-black rounded-md text-center w-1/2" - defaultValue={ - developmentTime === 0 ? "" : developmentTime - } - readOnly + value={developmentTime === 0 ? "" : developmentTime} /> @@ -121,8 +123,7 @@ export default function GetOtherUsersReport(): JSX.Element { type="text" min="0" className="border-2 border-black rounded-md text-center w-1/2" - defaultValue={meetingTime === 0 ? "" : meetingTime} - readOnly + value={meetingTime === 0 ? "" : meetingTime} /> @@ -133,8 +134,7 @@ export default function GetOtherUsersReport(): JSX.Element { type="text" min="0" className="border-2 border-black rounded-md text-center w-1/2" - defaultValue={adminTime === 0 ? "" : adminTime} - readOnly + value={adminTime === 0 ? "" : adminTime} /> @@ -145,8 +145,7 @@ export default function GetOtherUsersReport(): JSX.Element { type="text" min="0" className="border-2 border-black rounded-md text-center w-1/2" - defaultValue={ownWorkTime === 0 ? "" : ownWorkTime} - readOnly + value={ownWorkTime === 0 ? "" : ownWorkTime} /> @@ -157,8 +156,7 @@ export default function GetOtherUsersReport(): JSX.Element { type="text" min="0" className="border-2 border-black rounded-md text-center w-1/2" - defaultValue={studyTime === 0 ? "" : studyTime} - readOnly + value={studyTime === 0 ? "" : studyTime} /> @@ -169,8 +167,7 @@ export default function GetOtherUsersReport(): JSX.Element { type="text" min="0" className="border-2 border-black rounded-md text-center w-1/2" - defaultValue={testingTime === 0 ? "" : testingTime} - readOnly + value={testingTime === 0 ? "" : testingTime} /> diff --git a/frontend/src/Pages/AdminPages/AdminChangeUsername.tsx b/frontend/src/Pages/AdminPages/AdminChangeUsername.tsx new file mode 100644 index 0000000..b130fae --- /dev/null +++ b/frontend/src/Pages/AdminPages/AdminChangeUsername.tsx @@ -0,0 +1,28 @@ +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 = ( + <> + + + ); + + const buttons = ( + <> +