Merge remote-tracking branch 'origin/dev' into borean

This commit is contained in:
thugborean 2024-04-03 19:02:01 +02:00
commit 745786b7fd
54 changed files with 1657 additions and 1034 deletions

1
.gitignore vendored
View file

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

View file

@ -108,6 +108,56 @@ const docTemplate = `{
} }
} }
}, },
"/promote/{projectName}": {
"put": {
"security": [
{
"JWT": []
}
],
"description": "Promote a user to project manager",
"consumes": [
"text/plain"
],
"produces": [
"text/plain"
],
"tags": [
"Auth"
],
"summary": "Promote to project manager",
"parameters": [
{
"type": "string",
"description": "Project name",
"name": "projectName",
"in": "path",
"required": true
},
{
"type": "string",
"description": "User name",
"name": "userName",
"in": "query",
"required": true
}
],
"responses": {
"403": {
"description": "Forbidden",
"schema": {
"type": "string"
}
},
"500": {
"description": "Internal server error",
"schema": {
"type": "string"
}
}
}
}
},
"/promoteToAdmin": { "/promoteToAdmin": {
"post": { "post": {
"security": [ "security": [

View file

@ -17,6 +17,7 @@ type Database interface {
AddUser(username string, password string) error AddUser(username string, password string) error
CheckUser(username string, password string) bool CheckUser(username string, password string) bool
RemoveUser(username string) error RemoveUser(username string) error
RemoveUserFromProject(username string, projectname string) error
PromoteToAdmin(username string) error PromoteToAdmin(username string) error
GetUserId(username string) (int, error) GetUserId(username string) (int, error)
AddProject(name string, description string, username string) error AddProject(name string, description string, username string) error
@ -35,7 +36,7 @@ type Database interface {
GetProject(projectId int) (types.Project, error) GetProject(projectId int) (types.Project, error)
GetUserRole(username string, projectname string) (string, error) GetUserRole(username string, projectname string) (string, error)
GetWeeklyReport(username string, projectName string, week int) (types.WeeklyReport, error) GetWeeklyReport(username string, projectName string, week int) (types.WeeklyReport, error)
GetWeeklyReportsUser(username string, projectname string) ([]types.WeeklyReportList, error) GetAllWeeklyReports(username string, projectname string) ([]types.WeeklyReportList, error)
GetUnsignedWeeklyReports(projectName string) ([]types.WeeklyReport, error) GetUnsignedWeeklyReports(projectName string) ([]types.WeeklyReport, error)
SignWeeklyReport(reportId int, projectManagerId int) error SignWeeklyReport(reportId int, projectManagerId int) error
IsSiteAdmin(username string) (bool, error) IsSiteAdmin(username string) (bool, error)
@ -86,6 +87,10 @@ const isProjectManagerQuery = `SELECT COUNT(*) > 0 FROM user_roles
JOIN projects ON user_roles.project_id = projects.id JOIN projects ON user_roles.project_id = projects.id
WHERE users.username = ? AND projects.name = ? AND user_roles.p_role = 'project_manager'` WHERE users.username = ? AND projects.name = ? AND user_roles.p_role = 'project_manager'`
const removeUserFromProjectQuery = `DELETE FROM user_roles
WHERE user_id = (SELECT id FROM users WHERE username = ?)
AND project_id = (SELECT id FROM projects WHERE name = ?)`
// DbConnect connects to the database // DbConnect connects to the database
func DbConnect(dbpath string) Database { func DbConnect(dbpath string) Database {
// Open the database // Open the database
@ -147,6 +152,11 @@ func (d *Db) AddUserToProject(username string, projectname string, role string)
return err return err
} }
func (d *Db) RemoveUserFromProject(username string, projectname string) error {
_, err := d.Exec(removeUserFromProjectQuery, username, projectname)
return err
}
// ChangeUserRole changes the role of a user within a project. // ChangeUserRole changes the role of a user within a project.
func (d *Db) ChangeUserRole(username string, projectname string, role string) error { func (d *Db) ChangeUserRole(username string, projectname string, role string) error {
// Execute the SQL query to change the user's role // Execute the SQL query to change the user's role
@ -463,8 +473,8 @@ func (d *Db) Migrate() error {
return nil return nil
} }
// GetWeeklyReportsUser retrieves weekly reports for a specific user and project. // GetAllWeeklyReports retrieves weekly reports for a specific user and project.
func (d *Db) GetWeeklyReportsUser(username string, projectName string) ([]types.WeeklyReportList, error) { func (d *Db) GetAllWeeklyReports(username string, projectName string) ([]types.WeeklyReportList, error) {
query := ` query := `
SELECT SELECT
wr.week, wr.week,

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -0,0 +1,40 @@
package projects
import (
db "ttime/internal/database"
"github.com/gofiber/fiber/v2"
"github.com/gofiber/fiber/v2/log"
"github.com/golang-jwt/jwt/v5"
)
func RemoveUserFromProject(c *fiber.Ctx) error {
user := c.Locals("user").(*jwt.Token)
claims := user.Claims.(jwt.MapClaims)
pm_name := claims["name"].(string)
project := c.Params("projectName")
username := c.Query("userName")
// Check if the user is a project manager
isPM, err := db.GetDb(c).IsProjectManager(pm_name, project)
if err != nil {
log.Info("Error checking if user is project manager:", err)
return c.Status(500).SendString(err.Error())
}
if !isPM {
log.Info("User: ", pm_name, " is not a project manager in project: ", project)
return c.Status(403).SendString("User is not a project manager")
}
// Remove the user from the project
if err = db.GetDb(c).RemoveUserFromProject(username, project); err != nil {
log.Info("Error removing user from project:", err)
return c.Status(500).SendString(err.Error())
}
// Return success message
log.Info("User : ", username, " removed from project: ", project)
return c.SendStatus(fiber.StatusOK)
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,6 +1,6 @@
import { useState } from "react"; import { useState } from "react";
import { APIResponse, api } from "../API/API"; import { APIResponse, api } from "../API/API";
import { NewProject, Project } from "../Types/goTypes"; import { NewProject } from "../Types/goTypes";
import InputField from "./InputField"; import InputField from "./InputField";
import Logo from "../assets/Logo.svg"; import Logo from "../assets/Logo.svg";
import Button from "./Button"; import Button from "./Button";
@ -10,27 +10,26 @@ import Button from "./Button";
* @param {Object} props - Project name and description * @param {Object} props - Project name and description
* @returns {boolean} True if created, false if not * @returns {boolean} True if created, false if not
*/ */
function CreateProject(props: { name: string; description: string }): boolean { function CreateProject(props: { name: string; description: string }): void {
const project: NewProject = { const project: NewProject = {
name: props.name, name: props.name,
description: props.description, description: props.description,
}; };
let created = false;
api api
.createProject(project, localStorage.getItem("accessToken") ?? "") .createProject(project, localStorage.getItem("accessToken") ?? "")
.then((response: APIResponse<Project>) => { .then((response: APIResponse<void>) => {
if (response.success) { if (response.success) {
created = true; alert("Project added!");
} else { } else {
alert("Project NOT added!");
console.error(response.message); console.error(response.message);
} }
}) })
.catch((error) => { .catch((error) => {
alert("Project NOT added!");
console.error("An error occurred during creation:", error); console.error("An error occurred during creation:", error);
}); });
return created;
} }
/** /**
@ -49,7 +48,10 @@ 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" className="bg-white rounded px-8 pt-6 pb-8 mb-4 items-center justify-center flex flex-col w-fit h-fit"
onSubmit={(e) => { onSubmit={(e) => {
e.preventDefault(); e.preventDefault();
CreateProject({ name: name, description: description }); CreateProject({
name: name,
description: description,
});
}} }}
> >
<img <img
@ -60,11 +62,13 @@ function AddProject() {
<h3 className="pb-4 mb-2 text-center font-bold text-[18px]"> <h3 className="pb-4 mb-2 text-center font-bold text-[18px]">
Create a new project Create a new project
</h3> </h3>
<div className="space-y-3">
<InputField <InputField
label="Name" label="Name"
type="text" type="text"
value={name} value={name}
onChange={(e) => { onChange={(e) => {
e.preventDefault();
setName(e.target.value); setName(e.target.value);
}} }}
/> />
@ -73,9 +77,11 @@ function AddProject() {
type="text" type="text"
value={description} value={description}
onChange={(e) => { onChange={(e) => {
e.preventDefault();
setDescription(e.target.value); setDescription(e.target.value);
}} }}
/> />
</div>
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
<Button <Button
text="Create" text="Create"

View file

@ -1,15 +1,14 @@
import { useState } from "react"; import { useState } from "react";
import { NewProjMember } from "../Types/goTypes";
import Button from "./Button"; import Button from "./Button";
import GetAllUsers from "./GetAllUsers"; import GetAllUsers from "./GetAllUsers";
import AddMember from "./AddMember"; import AddMember, { NewProjMember } from "./AddMember";
import BackButton from "./BackButton"; import BackButton from "./BackButton";
/** /**
* Provides UI for adding a member to a project. * Provides UI for adding a member to a project.
* @returns {JSX.Element} - Returns the component UI for adding a member * @returns {JSX.Element} - Returns the component UI for adding a member
*/ */
function AddUserToProject(): JSX.Element { function AddUserToProject(props: { projectName: string }): JSX.Element {
const [name, setName] = useState(""); const [name, setName] = useState("");
const [users, setUsers] = useState<string[]>([]); const [users, setUsers] = useState<string[]>([]);
const [role, setRole] = useState(""); const [role, setRole] = useState("");
@ -18,7 +17,7 @@ function AddUserToProject(): JSX.Element {
const handleClick = (): boolean => { const handleClick = (): boolean => {
const newMember: NewProjMember = { const newMember: NewProjMember = {
username: name, username: name,
projectname: localStorage.getItem("projectName") ?? "", projectname: props.projectName,
role: role, role: role,
}; };
return AddMember({ memberToAdd: newMember }); return AddMember({ memberToAdd: newMember });
@ -33,13 +32,13 @@ function AddUserToProject(): JSX.Element {
Role chosen: [{role}] Role chosen: [{role}]
</p> </p>
<p className="pb-4 mb-2 text-center font-bold text-[18px]"> <p className="pb-4 mb-2 text-center font-bold text-[18px]">
Project chosen: [{localStorage.getItem("projectName") ?? ""}] Project chosen: [{props.projectName}]
</p> </p>
<p className="p-1">Choose role:</p> <p className="p-1">Choose role:</p>
<div className="border-2 border-black p-2 rounded-xl text-center h-[10h] w-[16vh]"> <div className="border-2 border-black p-2 rounded-xl text-center h-[10h] w-[16] overflow-auto">
<ul className="text-center items-center font-medium space-y-2"> <ul className="text-center items-center font-medium space-y-2">
<li <li
className="h-[10h] w-[14vh] items-start p-1 border-2 border-black rounded-full bg-orange-200 hover:bg-orange-600 hover:text-slate-100 hover:cursor-pointer" className="h-[10] w-[14] items-start px-2 py-1 border-2 border-black rounded-full bg-orange-200 hover:bg-orange-600 hover:text-slate-100 hover:cursor-pointer"
onClick={() => { onClick={() => {
setRole("member"); setRole("member");
}} }}
@ -47,7 +46,7 @@ function AddUserToProject(): JSX.Element {
{"Member"} {"Member"}
</li> </li>
<li <li
className="h-[10h] w-[14vh] items-start p-1 border-2 border-black rounded-full bg-orange-200 hover:bg-orange-600 hover:text-slate-100 hover:cursor-pointer" className="h-[10] w-[14] items-start px-2 py-1 border-2 border-black rounded-full bg-orange-200 hover:bg-orange-600 hover:text-slate-100 hover:cursor-pointer"
onClick={() => { onClick={() => {
setRole("project_manager"); setRole("project_manager");
}} }}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -25,6 +25,7 @@ function Login(props: {
}): JSX.Element { }): JSX.Element {
return ( return (
<form className="flex flex-col items-center" onSubmit={props.handleSubmit}> <form className="flex flex-col items-center" onSubmit={props.handleSubmit}>
<div className="space-y-3">
<InputField <InputField
type="text" type="text"
label="Username" label="Username"
@ -41,6 +42,7 @@ function Login(props: {
}} }}
value={props.password} value={props.password}
/> />
</div>
<Button <Button
text="Login" text="Login"
onClick={(): void => { onClick={(): void => {

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,5 +1,5 @@
import { useState, useEffect } from "react"; import { useState, useEffect } from "react";
import { WeeklyReport, NewWeeklyReport } from "../Types/goTypes"; import { WeeklyReport } from "../Types/goTypes";
import { api } from "../API/API"; import { api } from "../API/API";
import { useNavigate, useParams } from "react-router-dom"; import { useNavigate, useParams } from "react-router-dom";
import Button from "./Button"; import Button from "./Button";
@ -18,6 +18,7 @@ export default function GetOtherUsersReport(): JSX.Element {
const [ownWorkTime, setOwnWorkTime] = useState(0); const [ownWorkTime, setOwnWorkTime] = useState(0);
const [studyTime, setStudyTime] = useState(0); const [studyTime, setStudyTime] = useState(0);
const [testingTime, setTestingTime] = useState(0); const [testingTime, setTestingTime] = useState(0);
const [reportId, setReportId] = useState(0);
const token = localStorage.getItem("accessToken") ?? ""; const token = localStorage.getItem("accessToken") ?? "";
const { projectName } = useParams(); const { projectName } = useParams();
@ -45,6 +46,7 @@ export default function GetOtherUsersReport(): JSX.Element {
studyTime: 0, studyTime: 0,
testingTime: 0, testingTime: 0,
}; };
setReportId(report.reportId);
setWeek(report.week); setWeek(report.week);
setDevelopmentTime(report.developmentTime); setDevelopmentTime(report.developmentTime);
setMeetingTime(report.meetingTime); setMeetingTime(report.meetingTime);
@ -61,30 +63,23 @@ export default function GetOtherUsersReport(): JSX.Element {
}); });
const handleSignWeeklyReport = async (): Promise<void> => { const handleSignWeeklyReport = async (): Promise<void> => {
const newWeeklyReport: NewWeeklyReport = { await api.signReport(reportId, token);
projectName: projectName ?? "",
week,
developmentTime,
meetingTime,
adminTime,
ownWorkTime,
studyTime,
testingTime,
};
await api.submitWeeklyReport(newWeeklyReport, token);
}; };
const navigate = useNavigate(); const navigate = useNavigate();
return ( return (
<> <>
<h1 className="text-[30px] font-bold">{username}&apos;s Report</h1> <h1 className="text-[30px] font-bold">
{" "}
UserId: {username}&apos;s Report
</h1>
<div className="border-4 border-black bg-white flex flex-col justify-start min-h-[65vh] h-fit w-[50vw] rounded-3xl overflow-scroll space-y-[2vh] p-[30px] items-center"> <div className="border-4 border-black bg-white flex flex-col justify-start min-h-[65vh] h-fit w-[50vw] rounded-3xl overflow-scroll space-y-[2vh] p-[30px] items-center">
<form <form
onSubmit={(e) => { onSubmit={(e) => {
e.preventDefault(); e.preventDefault();
void handleSignWeeklyReport(); void handleSignWeeklyReport();
alert("Report successfully signed!");
navigate(-1); navigate(-1);
}} }}
> >
@ -112,7 +107,10 @@ export default function GetOtherUsersReport(): JSX.Element {
type="text" type="text"
min="0" min="0"
className="border-2 border-black rounded-md text-center w-1/2" className="border-2 border-black rounded-md text-center w-1/2"
value={developmentTime === 0 ? "" : developmentTime} defaultValue={
developmentTime === 0 ? "" : developmentTime
}
readOnly
/> />
</td> </td>
</tr> </tr>
@ -123,7 +121,8 @@ export default function GetOtherUsersReport(): JSX.Element {
type="text" type="text"
min="0" min="0"
className="border-2 border-black rounded-md text-center w-1/2" className="border-2 border-black rounded-md text-center w-1/2"
value={meetingTime === 0 ? "" : meetingTime} defaultValue={meetingTime === 0 ? "" : meetingTime}
readOnly
/> />
</td> </td>
</tr> </tr>
@ -134,7 +133,8 @@ export default function GetOtherUsersReport(): JSX.Element {
type="text" type="text"
min="0" min="0"
className="border-2 border-black rounded-md text-center w-1/2" className="border-2 border-black rounded-md text-center w-1/2"
value={adminTime === 0 ? "" : adminTime} defaultValue={adminTime === 0 ? "" : adminTime}
readOnly
/> />
</td> </td>
</tr> </tr>
@ -145,7 +145,8 @@ export default function GetOtherUsersReport(): JSX.Element {
type="text" type="text"
min="0" min="0"
className="border-2 border-black rounded-md text-center w-1/2" className="border-2 border-black rounded-md text-center w-1/2"
value={ownWorkTime === 0 ? "" : ownWorkTime} defaultValue={ownWorkTime === 0 ? "" : ownWorkTime}
readOnly
/> />
</td> </td>
</tr> </tr>
@ -156,7 +157,8 @@ export default function GetOtherUsersReport(): JSX.Element {
type="text" type="text"
min="0" min="0"
className="border-2 border-black rounded-md text-center w-1/2" className="border-2 border-black rounded-md text-center w-1/2"
value={studyTime === 0 ? "" : studyTime} defaultValue={studyTime === 0 ? "" : studyTime}
readOnly
/> />
</td> </td>
</tr> </tr>
@ -167,7 +169,8 @@ export default function GetOtherUsersReport(): JSX.Element {
type="text" type="text"
min="0" min="0"
className="border-2 border-black rounded-md text-center w-1/2" className="border-2 border-black rounded-md text-center w-1/2"
value={testingTime === 0 ? "" : testingTime} defaultValue={testingTime === 0 ? "" : testingTime}
readOnly
/> />
</td> </td>
</tr> </tr>

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

151
testing/helpers.py Normal file
View file

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

View file

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