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/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/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..6e65386 100644 --- a/backend/main.go +++ b/backend/main.go @@ -119,9 +119,6 @@ func main() { 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 +126,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 cf3d1eb..890a7e6 100644 --- a/frontend/src/API/API.ts +++ b/frontend/src/API/API.ts @@ -1,9 +1,8 @@ -import { AddMemberInfo } from "../Components/AddMember"; +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, @@ -88,31 +87,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,10 +106,9 @@ 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 @@ -164,17 +147,6 @@ interface API { 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>; - /** * Changes the username of a user in the database. * @param {StrNameChange} data The object containing the previous and new username. @@ -197,13 +169,7 @@ interface API { ): Promise>; addUserToProject( - addMemberInfo: AddMemberInfo, - token: string, - ): Promise>; - - removeUserFromProject( - user: string, - project: string, + user: NewProjMember, token: string, ): Promise>; @@ -220,19 +186,6 @@ interface API { * @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, - token: string, - ): Promise>; } /** An instance of the API */ @@ -342,20 +295,18 @@ export const api: API = { }, async addUserToProject( - addMemberInfo: AddMemberInfo, + user: NewProjMember, token: string, ): Promise> { try { - const response = await fetch( - `/api/addUserToProject/${addMemberInfo.projectName}/?userName=${addMemberInfo.userName}`, - { - method: "PUT", - headers: { - "Content-Type": "application/json", - Authorization: "Bearer " + token, - }, + const response = await fetch("/api/addUserToProject", { + method: "PUT", + headers: { + "Content-Type": "application/json", + Authorization: "Bearer " + token, }, - ); + body: JSON.stringify(user), + }); if (!response.ok) { return { success: false, message: "Failed to add member" }; @@ -367,31 +318,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", { @@ -532,46 +458,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: { @@ -592,22 +486,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 { @@ -731,38 +621,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, @@ -839,35 +697,4 @@ export const api: API = { 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 d8036b7..194afe8 100644 --- a/frontend/src/Components/AddMember.tsx +++ b/frontend/src/Components/AddMember.tsx @@ -1,35 +1,44 @@ -import { api } from "../API/API"; +import { APIResponse, api } from "../API/API"; -export interface AddMemberInfo { - userName: string; - projectName: string; +export interface NewProjMember { + username: string; + role: string; + projectname: string; } /** * Tries to add a member to a project - * @param {AddMemberInfo} props.membertoAdd - Contains user's name and project's name - * @returns {Promise} + * @param {Object} props - A NewProjMember + * @returns {boolean} True if added, false if not */ -async function AddMember(props: { memberToAdd: AddMemberInfo }): Promise { - if (props.memberToAdd.userName === "") { - alert("You must choose at least one user to add"); - return; +function AddMember(props: { memberToAdd: NewProjMember }): boolean { + let added = false; + if ( + props.memberToAdd.username === "" || + props.memberToAdd.role === "" || + props.memberToAdd.projectname === "" + ) { + alert("All fields must be filled before adding"); + return added; } - try { - const response = await api.addUserToProject( + api + .addUserToProject( props.memberToAdd, localStorage.getItem("accessToken") ?? "", - ); - if (response.success) { - alert(`[${props.memberToAdd.userName}] added`); - } else { - alert(`[${props.memberToAdd.userName}] not added`); - console.error(response.message); - } - } catch (error) { - alert(`[${props.memberToAdd.userName}] not added`); - console.error("An error occurred during member add:", error); - } + ) + .then((response: APIResponse) => { + if (response.success) { + alert("Member added"); + added = true; + } else { + alert("Member not added"); + console.error(response.message); + } + }) + .catch((error) => { + console.error("An error occurred during member add:", error); + }); + return added; } export default AddMember; diff --git a/frontend/src/Components/AddProject.tsx b/frontend/src/Components/AddProject.tsx index c8a1c66..e2ad8b9 100644 --- a/frontend/src/Components/AddProject.tsx +++ b/frontend/src/Components/AddProject.tsx @@ -1,10 +1,37 @@ import { useState } from "react"; -import { api } from "../API/API"; +import { APIResponse, api } from "../API/API"; import { NewProject } from "../Types/goTypes"; import InputField from "./InputField"; import Logo from "../assets/Logo.svg"; import Button from "./Button"; +/** + * Tries to add a project to the system + * @param {Object} props - Project name and description + * @returns {boolean} True if created, false if not + */ +function CreateProject(props: { name: string; description: string }): void { + const project: NewProject = { + name: props.name, + description: props.description, + }; + + api + .createProject(project, localStorage.getItem("accessToken") ?? "") + .then((response: APIResponse) => { + if (response.success) { + alert("Project added!"); + } else { + alert("Project NOT added!"); + console.error(response.message); + } + }) + .catch((error) => { + alert("Project NOT added!"); + console.error("An error occurred during creation:", error); + }); +} + /** * Provides UI for adding a project to the system. * @returns {JSX.Element} - Returns the component UI for adding a project @@ -13,33 +40,6 @@ function AddProject(): JSX.Element { const [name, setName] = useState(""); const [description, setDescription] = useState(""); - /** - * Tries to add a project to the system - */ - const handleCreateProject = async (): Promise => { - const project: NewProject = { - name: name.replace(/ /g, ""), - description: description.trim(), - }; - try { - const response = await api.createProject( - project, - localStorage.getItem("accessToken") ?? "", - ); - if (response.success) { - alert(`${project.name} added!`); - setDescription(""); - setName(""); - } else { - alert("Project not added, name could be taken"); - console.error(response.message); - } - } catch (error) { - alert("Project not added"); - console.error(error); - } - }; - return (
@@ -47,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" onSubmit={(e) => { e.preventDefault(); - void handleCreateProject(); + CreateProject({ + name: name, + description: description, + }); }} > ([]); + const [name, setName] = useState(""); const [users, setUsers] = useState([]); - const [usersProj, setUsersProj] = useState([]); - - // Gets all users and project members for filtering + const [role, setRole] = useState(""); GetAllUsers({ setUsersProp: setUsers }); - GetUsersInProject({ - setUsersProp: setUsersProj, - projectName: props.projectName, - }); - /* - * Filters the members from users so that users who are already - * members are not shown - */ - useEffect(() => { - setUsers((prevUsers) => { - const filteredUsers = prevUsers.filter( - (user) => - !usersProj.some((projectUser) => projectUser.Username === user), - ); - return filteredUsers; - }); - }, [usersProj]); - // Attempts to add all of the selected users to the project - const handleAddClick = async (): Promise => { - if (names.length === 0) - alert("You have to choose at least one user to add"); - for (const name of names) { - const newMember: AddMemberInfo = { - userName: name, - projectName: props.projectName, - }; - await AddMember({ memberToAdd: newMember }); - } - setNames([]); - location.reload(); - }; - - // Updates the names that have been selected - const handleUserClick = (user: string): void => { - setNames((prevNames): string[] => { - if (!prevNames.includes(user)) { - return [...prevNames, user]; - } - return prevNames.filter((name) => name !== user); - }); + const handleClick = (): boolean => { + const newMember: NewProjMember = { + username: name, + projectname: props.projectName, + role: role, + }; + return AddMember({ memberToAdd: newMember }); }; return ( -
-

- {props.projectName} -

-

- Choose users to add: +

+

+ User chosen: [{name}]

-
+

+ Role chosen: [{role}] +

+

+ Project chosen: [{props.projectName}] +

+

Choose role:

+
+
    +
  • { + setRole("member"); + }} + > + {"Member"} +
  • +
  • { + setRole("project_manager"); + }} + > + {"Project manager"} +
  • +
+
+

Choose user:

+
    {users.map((user) => (
  • { - handleUserClick(user); + setName(user); }} > {user} @@ -88,16 +73,13 @@ function AddUserToProject(props: { projectName: string }): JSX.Element { ))}
-

- Number of users to be added: {names.length} -

-
+
diff --git a/frontend/src/Components/AllTimeReportsInProject.tsx b/frontend/src/Components/AllTimeReportsInProject.tsx index 0d5916b..4fa9ad8 100644 --- a/frontend/src/Components/AllTimeReportsInProject.tsx +++ b/frontend/src/Components/AllTimeReportsInProject.tsx @@ -17,7 +17,7 @@ function AllTimeReportsInProject(): JSX.Element { useEffect(() => { const getWeeklyReports = async (): Promise => { const token = localStorage.getItem("accessToken") ?? ""; - const response = await api.getAllWeeklyReportsForUser( + const response = await api.getWeeklyReportsForUser( projectName ?? "", token, ); diff --git a/frontend/src/Components/AllTimeReportsInProjectOtherUser.tsx b/frontend/src/Components/AllTimeReportsInProjectOtherUser.tsx index cde9fa7..09ca6dc 100644 --- a/frontend/src/Components/AllTimeReportsInProjectOtherUser.tsx +++ b/frontend/src/Components/AllTimeReportsInProjectOtherUser.tsx @@ -1,9 +1,8 @@ //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. import { useEffect, useState } from "react"; -import { WeeklyReport } from "../Types/goTypes"; +import { NewWeeklyReport } from "../Types/goTypes"; 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. @@ -12,15 +11,15 @@ import { api } from "../API/API"; function AllTimeReportsInProject(): JSX.Element { const { username } = useParams(); const { projectName } = useParams(); - const [weeklyReports, setWeeklyReports] = useState([]); + const [weeklyReports, setWeeklyReports] = useState([]); + /* // Call getProjects when the component mounts useEffect(() => { const getWeeklyReports = async (): Promise => { const token = localStorage.getItem("accessToken") ?? ""; - const response = await api.getAllWeeklyReportsForUser( + const response = await api.getWeeklyReportsForUser( projectName ?? "", token, - username ?? "", ); console.log(response); if (response.success) { @@ -28,10 +27,41 @@ function AllTimeReportsInProject(): JSX.Element { } else { console.error(response.message); } - }; + }; */ + // Mock data + const getWeeklyReports = async (): Promise => { + // 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(); - }, [projectName, username]); + }, []); return ( <> diff --git a/frontend/src/Components/ChangeRoleView.tsx b/frontend/src/Components/ChangeRoleView.tsx index 782ad8d..30dce3c 100644 --- a/frontend/src/Components/ChangeRoleView.tsx +++ b/frontend/src/Components/ChangeRoleView.tsx @@ -2,7 +2,7 @@ import { useState } from "react"; import Button from "./Button"; import ChangeRole, { ProjectRoleChange } from "./ChangeRole"; -export default function ChangeRoleView(props: { +export default function ChangeRoles(props: { projectName: string; username: string; }): JSX.Element { diff --git a/frontend/src/Components/ChangeUsername.tsx b/frontend/src/Components/ChangeUsername.tsx index 2f73bb6..78d7da9 100644 --- a/frontend/src/Components/ChangeUsername.tsx +++ b/frontend/src/Components/ChangeUsername.tsx @@ -2,11 +2,8 @@ import { APIResponse, api } from "../API/API"; import { StrNameChange } from "../Types/goTypes"; function ChangeUsername(props: { nameChange: StrNameChange }): void { - if ( - props.nameChange.newName === "" || - props.nameChange.newName === props.nameChange.prevName - ) { - alert("You have to give a new name\n\nName not changed"); + if (props.nameChange.newName === "") { + alert("You have to select a new name"); return; } api @@ -16,7 +13,7 @@ function ChangeUsername(props: { nameChange: StrNameChange }): void { alert("Name changed successfully"); location.reload(); } else { - alert("Name not changed, name could be taken"); + alert("Name not changed"); console.error(response.message); } }) 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/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/MemberInfoModal.tsx b/frontend/src/Components/MemberInfoModal.tsx index dac11f8..8b32367 100644 --- a/frontend/src/Components/MemberInfoModal.tsx +++ b/frontend/src/Components/MemberInfoModal.tsx @@ -1,8 +1,8 @@ import Button from "./Button"; +import DeleteUser from "./DeleteUser"; import UserProjectListAdmin from "./UserProjectListAdmin"; import { useState } from "react"; import ChangeRoleView from "./ChangeRoleView"; -import RemoveUserFromProj from "./RemoveUserFromProj"; function MemberInfoModal(props: { projectName: string; @@ -20,7 +20,7 @@ function MemberInfoModal(props: { }; return (
@@ -42,16 +42,13 @@ function MemberInfoModal(props: {