Merge branch 'frontend' into gruppPP

This commit is contained in:
Peter KW 2024-03-20 18:41:01 +01:00
commit e4a0246b84
37 changed files with 772 additions and 240 deletions

View file

@ -5,8 +5,12 @@ import (
"testing" "testing"
) )
// TestNewConfig tests the creation of a new configuration object
func TestNewConfig(t *testing.T) { func TestNewConfig(t *testing.T) {
// Arrange
c := NewConfig() c := NewConfig()
// Act & Assert
if c.Port != 8080 { if c.Port != 8080 {
t.Errorf("Expected port to be 8080, got %d", c.Port) t.Errorf("Expected port to be 8080, got %d", c.Port)
} }
@ -24,9 +28,15 @@ func TestNewConfig(t *testing.T) {
} }
} }
// TestWriteConfig tests the function to write the configuration to a file
func TestWriteConfig(t *testing.T) { func TestWriteConfig(t *testing.T) {
// Arrange
c := NewConfig() c := NewConfig()
//Act
err := c.WriteConfigToFile("test.toml") err := c.WriteConfigToFile("test.toml")
// Assert
if err != nil { if err != nil {
t.Errorf("Expected no error, got %s", err) t.Errorf("Expected no error, got %s", err)
} }
@ -35,14 +45,23 @@ func TestWriteConfig(t *testing.T) {
_ = os.Remove("test.toml") _ = os.Remove("test.toml")
} }
// TestReadConfig tests the function to read the configuration from a file
func TestReadConfig(t *testing.T) { func TestReadConfig(t *testing.T) {
// Arrange
c := NewConfig() c := NewConfig()
// Act
err := c.WriteConfigToFile("test.toml") err := c.WriteConfigToFile("test.toml")
// Assert
if err != nil { if err != nil {
t.Errorf("Expected no error, got %s", err) t.Errorf("Expected no error, got %s", err)
} }
// Act
c2, err := ReadConfigFromFile("test.toml") c2, err := ReadConfigFromFile("test.toml")
// Assert
if err != nil { if err != nil {
t.Errorf("Expected no error, got %s", err) t.Errorf("Expected no error, got %s", err)
} }

View file

@ -27,6 +27,7 @@ type Database interface {
AddWeeklyReport(projectName string, userName string, week int, developmentTime int, meetingTime int, adminTime int, ownWorkTime int, studyTime int, testingTime int) error AddWeeklyReport(projectName string, userName string, week int, developmentTime int, meetingTime int, adminTime int, ownWorkTime int, studyTime int, testingTime int) error
AddUserToProject(username string, projectname string, role string) error AddUserToProject(username string, projectname string, role string) error
ChangeUserRole(username string, projectname string, role string) error ChangeUserRole(username string, projectname string, role string) error
ChangeUserName(username string, newname string) error
GetAllUsersProject(projectname string) ([]UserProjectMember, error) GetAllUsersProject(projectname string) ([]UserProjectMember, error)
GetAllUsersApplication() ([]string, error) GetAllUsersApplication() ([]string, error)
GetProjectsForUser(username string) ([]types.Project, error) GetProjectsForUser(username string) ([]types.Project, error)
@ -65,9 +66,8 @@ const addWeeklyReport = `WITH UserLookup AS (SELECT id FROM users WHERE username
ProjectLookup AS (SELECT id FROM projects WHERE name = ?) ProjectLookup AS (SELECT id FROM projects WHERE name = ?)
INSERT INTO weekly_reports (project_id, user_id, week, development_time, meeting_time, admin_time, own_work_time, study_time, testing_time) INSERT INTO weekly_reports (project_id, user_id, week, development_time, meeting_time, admin_time, own_work_time, study_time, testing_time)
VALUES ((SELECT id FROM ProjectLookup), (SELECT id FROM UserLookup),?, ?, ?, ?, ?, ?, ?);` VALUES ((SELECT id FROM ProjectLookup), (SELECT id FROM UserLookup),?, ?, ?, ?, ?, ?, ?);`
const addUserToProject = "INSERT INTO user_roles (user_id, project_id, p_role) VALUES (?, ?, ?)" // WIP const addUserToProject = "INSERT INTO user_roles (user_id, project_id, p_role) VALUES (?, ?, ?)"
const changeUserRole = "UPDATE user_roles SET p_role = ? WHERE user_id = ? AND project_id = ?" const changeUserRole = "UPDATE user_roles SET p_role = ? WHERE user_id = ? AND project_id = ?"
const getProjectsForUser = `SELECT p.id, p.name, p.description FROM projects p const getProjectsForUser = `SELECT p.id, p.name, p.description FROM projects p
JOIN user_roles ur ON p.id = ur.project_id JOIN user_roles ur ON p.id = ur.project_id
JOIN users u ON ur.user_id = u.id JOIN users u ON ur.user_id = u.id
@ -131,7 +131,7 @@ func (d *Db) AddWeeklyReport(projectName string, userName string, week int, deve
} }
// AddUserToProject adds a user to a project with a specified role. // AddUserToProject adds a user to a project with a specified role.
func (d *Db) AddUserToProject(username string, projectname string, role string) error { // WIP func (d *Db) AddUserToProject(username string, projectname string, role string) error {
var userid int var userid int
userid, err := d.GetUserId(username) userid, err := d.GetUserId(username)
if err != nil { if err != nil {
@ -169,6 +169,13 @@ func (d *Db) ChangeUserRole(username string, projectname string, role string) er
return err3 return err3
} }
// ChangeUserName changes the username of a user.
func (d *Db) ChangeUserName(username string, newname string) error {
// Execute the SQL query to update the username
_, err := d.Exec("UPDATE users SET username = ? WHERE username = ?", newname, username)
return err
}
// GetUserRole retrieves the role of a user within a project. // GetUserRole retrieves the role of a user within a project.
func (d *Db) GetUserRole(username string, projectname string) (string, error) { func (d *Db) GetUserRole(username string, projectname string) (string, error) {
var role string var role string

View file

@ -7,6 +7,7 @@ import (
// Tests are not guaranteed to be sequential // Tests are not guaranteed to be sequential
// setupState initializes a database instance with necessary setup for testing
func setupState() (Database, error) { func setupState() (Database, error) {
db := DbConnect(":memory:") db := DbConnect(":memory:")
err := db.Migrate() err := db.Migrate()
@ -16,11 +17,13 @@ func setupState() (Database, error) {
return db, nil return db, nil
} }
// TestDbConnect tests the connection to the database
func TestDbConnect(t *testing.T) { func TestDbConnect(t *testing.T) {
db := DbConnect(":memory:") db := DbConnect(":memory:")
_ = db _ = db
} }
// TestDbAddUser tests the AddUser function of the database
func TestDbAddUser(t *testing.T) { func TestDbAddUser(t *testing.T) {
db, err := setupState() db, err := setupState()
if err != nil { if err != nil {
@ -32,6 +35,7 @@ func TestDbAddUser(t *testing.T) {
} }
} }
// TestDbGetUserId tests the GetUserID function of the database
func TestDbGetUserId(t *testing.T) { func TestDbGetUserId(t *testing.T) {
db, err := setupState() db, err := setupState()
if err != nil { if err != nil {
@ -52,6 +56,7 @@ func TestDbGetUserId(t *testing.T) {
} }
} }
// TestDbAddProject tests the AddProject function of the database
func TestDbAddProject(t *testing.T) { func TestDbAddProject(t *testing.T) {
db, err := setupState() db, err := setupState()
if err != nil { if err != nil {
@ -64,6 +69,7 @@ func TestDbAddProject(t *testing.T) {
} }
} }
// TestDbRemoveUser tests the RemoveUser function of the database
func TestDbRemoveUser(t *testing.T) { func TestDbRemoveUser(t *testing.T) {
db, err := setupState() db, err := setupState()
if err != nil { if err != nil {
@ -76,6 +82,7 @@ func TestDbRemoveUser(t *testing.T) {
} }
} }
// TestPromoteToAdmin tests the PromoteToAdmin function of the database
func TestPromoteToAdmin(t *testing.T) { func TestPromoteToAdmin(t *testing.T) {
db, err := setupState() db, err := setupState()
if err != nil { if err != nil {
@ -93,6 +100,7 @@ func TestPromoteToAdmin(t *testing.T) {
} }
} }
// TestAddWeeklyReport tests the AddWeeklyReport function of the database
func TestAddWeeklyReport(t *testing.T) { func TestAddWeeklyReport(t *testing.T) {
db, err := setupState() db, err := setupState()
if err != nil { if err != nil {
@ -115,6 +123,7 @@ func TestAddWeeklyReport(t *testing.T) {
} }
} }
// TestAddUserToProject tests the AddUseToProject function of the database
func TestAddUserToProject(t *testing.T) { func TestAddUserToProject(t *testing.T) {
db, err := setupState() db, err := setupState()
if err != nil { if err != nil {
@ -142,6 +151,7 @@ func TestAddUserToProject(t *testing.T) {
} }
} }
// TestChangeUserRole tests the ChangeUserRole function of the database
func TestChangeUserRole(t *testing.T) { func TestChangeUserRole(t *testing.T) {
db, err := setupState() db, err := setupState()
if err != nil { if err != nil {
@ -186,6 +196,7 @@ func TestChangeUserRole(t *testing.T) {
} }
// TestGetAllUsersProject tests the GetAllUsersProject function of the database
func TestGetAllUsersProject(t *testing.T) { func TestGetAllUsersProject(t *testing.T) {
db, err := setupState() db, err := setupState()
if err != nil { if err != nil {
@ -252,6 +263,7 @@ func TestGetAllUsersProject(t *testing.T) {
} }
} }
// TestGetAllUsersApplication tests the GetAllUsersApplicsation function of the database
func TestGetAllUsersApplication(t *testing.T) { func TestGetAllUsersApplication(t *testing.T) {
db, err := setupState() db, err := setupState()
if err != nil { if err != nil {
@ -298,6 +310,7 @@ func TestGetAllUsersApplication(t *testing.T) {
} }
} }
// TestGetProjectsForUser tests the GetProjectsForUser function of the database
func TestGetProjectsForUser(t *testing.T) { func TestGetProjectsForUser(t *testing.T) {
db, err := setupState() db, err := setupState()
if err != nil { if err != nil {
@ -338,6 +351,7 @@ func TestGetProjectsForUser(t *testing.T) {
} }
} }
// TestAddProject tests AddProject function of the database
func TestAddProject(t *testing.T) { func TestAddProject(t *testing.T) {
db, err := setupState() db, err := setupState()
if err != nil { if err != nil {
@ -373,6 +387,7 @@ func TestAddProject(t *testing.T) {
} }
} }
// TestGetWeeklyReport tests GetWeeklyReport function of the database
func TestGetWeeklyReport(t *testing.T) { func TestGetWeeklyReport(t *testing.T) {
db, err := setupState() db, err := setupState()
if err != nil { if err != nil {
@ -412,6 +427,7 @@ func TestGetWeeklyReport(t *testing.T) {
// Check other fields similarly // Check other fields similarly
} }
// TestSignWeeklyReport tests SignWeeklyReport function of the database
func TestSignWeeklyReport(t *testing.T) { func TestSignWeeklyReport(t *testing.T) {
db, err := setupState() db, err := setupState()
if err != nil { if err != nil {
@ -484,6 +500,7 @@ func TestSignWeeklyReport(t *testing.T) {
} }
} }
// TestSignWeeklyReportByAnotherProjectManager tests the scenario where a project manager attempts to sign a weekly report for a user who is not assigned to their project
func TestSignWeeklyReportByAnotherProjectManager(t *testing.T) { func TestSignWeeklyReportByAnotherProjectManager(t *testing.T) {
db, err := setupState() db, err := setupState()
if err != nil { if err != nil {
@ -537,6 +554,7 @@ func TestSignWeeklyReportByAnotherProjectManager(t *testing.T) {
} }
} }
// TestGetProject tests GetProject function of the database
func TestGetProject(t *testing.T) { func TestGetProject(t *testing.T) {
db, err := setupState() db, err := setupState()
if err != nil { if err != nil {
@ -657,3 +675,39 @@ func TestIsProjectManager(t *testing.T) {
t.Error("Expected projectManager to be a project manager, but it's not.") t.Error("Expected projectManager to be a project manager, but it's not.")
} }
} }
func TestChangeUserName(t *testing.T) {
db, err := setupState()
if err != nil {
t.Error("setupState failed:", err)
}
// Add a user
err = db.AddUser("testuser", "password")
if err != nil {
t.Error("AddUser failed:", err)
}
// Change the user's name
err = db.ChangeUserName("testuser", "newname")
if err != nil {
t.Error("ChangeUserName failed:", err)
}
// Retrieve the user's ID
userID, err := db.GetUserId("newname")
if err != nil {
t.Error("GetUserId failed:", err)
}
// Ensure the user's ID matches the expected value
if userID != 1 {
t.Errorf("Expected user ID to be 1, got %d", userID)
}
// Attempt to retrieve the user by the old name
_, err = db.GetUserId("testuser")
if err == nil {
t.Error("Expected GetUserId to fail for the old name, but it didn't")
}
}

View file

@ -22,22 +22,12 @@ type GlobalState interface {
PromoteToAdmin(c *fiber.Ctx) error PromoteToAdmin(c *fiber.Ctx) error
GetWeeklyReportsUserHandler(c *fiber.Ctx) error GetWeeklyReportsUserHandler(c *fiber.Ctx) error
IsProjectManagerHandler(c *fiber.Ctx) error IsProjectManagerHandler(c *fiber.Ctx) error
// UpdateProject(c *fiber.Ctx) error // To update a project // WIP
DeleteProject(c *fiber.Ctx) error // To delete a project // WIP DeleteProject(c *fiber.Ctx) error // To delete a project // WIP
// CreateTask(c *fiber.Ctx) error // To create a new task // WIP
// GetTasks(c *fiber.Ctx) error // To get all tasks // WIP
// GetTask(c *fiber.Ctx) error // To get a specific task // WIP
// UpdateTask(c *fiber.Ctx) error // To update a task // WIP
// DeleteTask(c *fiber.Ctx) error // To delete a task // WIP
// CreateCollection(c *fiber.Ctx) error // To create a new collection // WIP
// GetCollections(c *fiber.Ctx) error // To get all collections // WIP
// GetCollection(c *fiber.Ctx) error // To get a specific collection // WIP
// UpdateCollection(c *fiber.Ctx) error // To update a collection // WIP
// DeleteCollection(c *fiber.Ctx) error // To delete a collection // WIP
// SignCollection(c *fiber.Ctx) error // To sign a collection // WIP
ListAllUsers(c *fiber.Ctx) error // To get a list of all users in the application database ListAllUsers(c *fiber.Ctx) error // To get a list of all users in the application database
ListAllUsersProject(c *fiber.Ctx) error // To get a list of all users for a specific project ListAllUsersProject(c *fiber.Ctx) error // To get a list of all users for a specific project
ProjectRoleChange(c *fiber.Ctx) error // To change a users role in a project ProjectRoleChange(c *fiber.Ctx) error // To change a users role in a project
ChangeUserName(c *fiber.Ctx) error // WIP
GetAllUsersProject(c *fiber.Ctx) error // WIP
} }
// "Constructor" // "Constructor"

View file

@ -117,14 +117,22 @@ func (gs *GState) SignReport(c *fiber.Ctx) error {
// GetWeeklyReportsUserHandler retrieves all weekly reports for a user in a specific project // GetWeeklyReportsUserHandler retrieves all weekly reports for a user in a specific project
func (gs *GState) GetWeeklyReportsUserHandler(c *fiber.Ctx) error { func (gs *GState) GetWeeklyReportsUserHandler(c *fiber.Ctx) error {
// Extract necessary parameters from the request // Extract the necessary parameters from the token
username := c.Params("username") 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") 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 // Retrieve weekly reports for the user in the project from the database
reports, err := gs.Db.GetWeeklyReportsUser(username, projectName) reports, err := gs.Db.GetWeeklyReportsUser(username, projectName)
if err != nil { if err != nil {
log.Info("Error getting weekly reports for user:", err) log.Error("Error getting weekly reports for user:", username, "in project:", projectName, ":", err)
return c.Status(500).SendString(err.Error()) return c.Status(500).SendString(err.Error())
} }

View file

@ -105,7 +105,7 @@ func (gs *GState) Login(c *fiber.Ctx) error {
if err != nil { if err != nil {
log.Info("Error checking admin status:", err) log.Info("Error checking admin status:", err)
return c.Status(500).SendString(err.Error()) return c.Status(500).SendString(err.Error())
} }
// Create the Claims // Create the Claims
claims := jwt.MapClaims{ claims := jwt.MapClaims{
"name": u.Username, "name": u.Username,
@ -187,6 +187,20 @@ func (gs *GState) ListAllUsers(c *fiber.Ctx) error {
return c.JSON(users) return c.JSON(users)
} }
func (gs *GState) GetAllUsersProject(c *fiber.Ctx) error {
// Get all users from a project
projectName := c.Params("projectName")
users, err := gs.Db.GetAllUsersProject(projectName)
if err != nil {
log.Info("Error getting users from project:", err) // Debug print
return c.Status(500).SendString(err.Error())
}
log.Info("Returning all users")
// Return the list of users as JSON
return c.JSON(users)
}
// @Summary PromoteToAdmin // @Summary PromoteToAdmin
// @Description promote chosen user to admin // @Description promote chosen user to admin
// @Tags User // @Tags User
@ -219,3 +233,37 @@ func (gs *GState) PromoteToAdmin(c *fiber.Ctx) error {
// Return a success message // Return a success message
return c.SendStatus(fiber.StatusOK) return c.SendStatus(fiber.StatusOK)
} }
// Changes a users name in the database
func (gs *GState) ChangeUserName(c *fiber.Ctx) error {
//check token and get username of current user
user := c.Locals("user").(*jwt.Token)
claims := user.Claims.(jwt.MapClaims)
projectManagerUsername := claims["name"].(string)
log.Info(projectManagerUsername)
// Extract the necessary parameters from the request
data := new(types.NameChange)
if err := c.BodyParser(data); err != nil {
log.Info("error parsing username, project or role")
return c.Status(400).SendString(err.Error())
}
// dubble diping and checcking if current user is
if ismanager, err := gs.Db.IsProjectManager(projectManagerUsername, c.Params(data.Name)); err != nil {
log.Warn("Error checking if projectmanager:", err)
return c.Status(500).SendString(err.Error())
} else if !ismanager {
log.Warn("tried changing name when not projectmanager:", err)
return c.Status(401).SendString("you can not change name when not projectmanager")
}
// Change the user's name within the project in the database
if err := gs.Db.ChangeUserName(projectManagerUsername, data.Name); err != nil {
return c.Status(500).SendString(err.Error())
}
// Return a success message
return c.SendStatus(fiber.StatusOK)
}

View file

@ -19,3 +19,8 @@ type RoleChange struct {
Username string `json:"username"` Username string `json:"username"`
Projectname string `json:"projectname"` Projectname string `json:"projectname"`
} }
type NameChange struct {
ID int `json:"id" db:"id"`
Name string `json:"name" db:"name"`
}

View file

@ -32,3 +32,8 @@ type PublicUser struct {
type Token struct { type Token struct {
Token string `json:"token"` Token string `json:"token"`
} }
type StrNameChange struct {
PrevName string `json:"prevName" db:"prevName"`
NewName string `json:"newName" db:"newName"`
}

View file

@ -87,15 +87,17 @@ func main() {
server.Get("/api/getUserProjects", gs.GetUserProjects) server.Get("/api/getUserProjects", gs.GetUserProjects)
server.Post("/api/loginrenew", gs.LoginRenew) server.Post("/api/loginrenew", gs.LoginRenew)
server.Delete("/api/userdelete/:username", gs.UserDelete) // Perhaps just use POST to avoid headaches server.Delete("/api/userdelete/:username", gs.UserDelete) // Perhaps just use POST to avoid headaches
server.Delete("api/project", gs.DeleteProject) // WIP server.Delete("api/project/:projectID", gs.DeleteProject) // WIP
server.Post("/api/project", gs.CreateProject) server.Post("/api/project", gs.CreateProject) // WIP
server.Get("/api/project/:projectId", gs.GetProject) server.Get("/api/project/:projectId", gs.GetProject)
server.Get("/api/project/getAllUsers", gs.GetAllUsersProject)
server.Get("/api/getWeeklyReport", gs.GetWeeklyReport) server.Get("/api/getWeeklyReport", gs.GetWeeklyReport)
server.Post("/api/signReport", gs.SignReport) server.Post("/api/signReport", gs.SignReport)
server.Put("/api/addUserToProject", gs.AddUserToProjectHandler) server.Put("/api/addUserToProject", gs.AddUserToProjectHandler)
server.Put("/api/changeUserName", gs.ChangeUserName)
server.Post("/api/promoteToAdmin", gs.PromoteToAdmin) server.Post("/api/promoteToAdmin", gs.PromoteToAdmin)
server.Get("/api/users/all", gs.ListAllUsers) server.Get("/api/users/all", gs.ListAllUsers)
server.Get("/api/getWeeklyReportsUser", gs.GetWeeklyReportsUserHandler) server.Get("/api/getWeeklyReportsUser/:projectName", gs.GetWeeklyReportsUserHandler)
server.Get("api/checkIfProjectManager", gs.IsProjectManagerHandler) server.Get("api/checkIfProjectManager", gs.IsProjectManagerHandler)
server.Post("/api/ProjectRoleChange", gs.ProjectRoleChange) server.Post("/api/ProjectRoleChange", gs.ProjectRoleChange)
server.Get("/api/getUsersProject/:projectName", gs.ListAllUsersProject) server.Get("/api/getUsersProject/:projectName", gs.ListAllUsersProject)

View file

@ -5,6 +5,7 @@ import {
Project, Project,
NewProject, NewProject,
UserProjectMember, UserProjectMember,
WeeklyReport,
} from "../Types/goTypes"; } from "../Types/goTypes";
// This type of pattern should be hard to misuse // This type of pattern should be hard to misuse
@ -21,10 +22,17 @@ interface API {
registerUser(user: NewUser): Promise<APIResponse<User>>; registerUser(user: NewUser): Promise<APIResponse<User>>;
/** Remove a user */ /** Remove a user */
removeUser(username: string, token: string): Promise<APIResponse<User>>; removeUser(username: string, token: string): Promise<APIResponse<User>>;
/** Check if user is project manager */
checkIfProjectManager(
username: string,
projectName: string,
token: string,
): Promise<APIResponse<boolean>>;
/** Login */ /** Login */
login(NewUser: NewUser): Promise<APIResponse<string>>; login(NewUser: NewUser): Promise<APIResponse<string>>;
/** Renew the token */ /** Renew the token */
renewToken(token: string): Promise<APIResponse<string>>; renewToken(token: string): Promise<APIResponse<string>>;
/** Promote user to admin */
/** Create a project */ /** Create a project */
createProject( createProject(
project: NewProject, project: NewProject,
@ -41,7 +49,19 @@ interface API {
projectName: string, projectName: string,
week: string, week: string,
token: string, token: string,
): Promise<APIResponse<NewWeeklyReport>>; ): Promise<APIResponse<WeeklyReport>>;
/**
* Returns all the weekly reports for a user in a particular project
* The username is derived from the token
*
* @param {string} projectName The name of the project
* @param {string} token The token of the user
* @returns {APIResponse<WeeklyReport[]>} A list of weekly reports
*/
getWeeklyReportsForUser(
projectName: string,
token: string,
): Promise<APIResponse<WeeklyReport[]>>;
/** Gets all the projects of a user*/ /** Gets all the projects of a user*/
getUserProjects(token: string): Promise<APIResponse<Project[]>>; getUserProjects(token: string): Promise<APIResponse<Project[]>>;
/** Gets a project from id*/ /** Gets a project from id*/
@ -109,6 +129,35 @@ export const api: API = {
} }
}, },
async checkIfProjectManager(
username: string,
projectName: string,
token: string,
): Promise<APIResponse<boolean>> {
try {
const response = await fetch("/api/checkIfProjectManager", {
method: "GET",
headers: {
"Content-Type": "application/json",
Authorization: "Bearer " + token,
},
body: JSON.stringify({ username, projectName }),
});
if (!response.ok) {
return {
success: false,
message: "Failed to check if project manager",
};
} else {
const data = (await response.json()) as boolean;
return { success: true, data };
}
} catch (e) {
return { success: false, message: "fuck" };
}
},
async createProject( async createProject(
project: NewProject, project: NewProject,
token: string, token: string,
@ -218,7 +267,7 @@ export const api: API = {
projectName: string, projectName: string,
week: string, week: string,
token: string, token: string,
): Promise<APIResponse<NewWeeklyReport>> { ): Promise<APIResponse<WeeklyReport>> {
try { try {
const response = await fetch("/api/getWeeklyReport", { const response = await fetch("/api/getWeeklyReport", {
method: "GET", method: "GET",
@ -232,7 +281,7 @@ export const api: API = {
if (!response.ok) { if (!response.ok) {
return { success: false, message: "Failed to get weekly report" }; return { success: false, message: "Failed to get weekly report" };
} else { } else {
const data = (await response.json()) as NewWeeklyReport; const data = (await response.json()) as WeeklyReport;
return { success: true, data }; return { success: true, data };
} }
} catch (e) { } catch (e) {
@ -240,6 +289,38 @@ export const api: API = {
} }
}, },
async getWeeklyReportsForUser(
projectName: string,
token: string,
): Promise<APIResponse<WeeklyReport[]>> {
try {
const response = await fetch(`/api/getWeeklyReportsUser/${projectName}`, {
method: "GET",
headers: {
"Content-Type": "application/json",
Authorization: "Bearer " + token,
},
});
if (!response.ok) {
return {
success: false,
message:
"Failed to get weekly 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 weekly reports for project, unknown error",
};
}
},
async login(NewUser: NewUser): Promise<APIResponse<string>> { async login(NewUser: NewUser): Promise<APIResponse<string>> {
try { try {
const response = await fetch("/api/login", { const response = await fetch("/api/login", {

View file

@ -1,74 +1,36 @@
import React, { useEffect, useState } from "react"; //Info: This component is used to display all the time reports for a project. It will display the week number,
import { NewWeeklyReport } from "../Types/goTypes"; //total time spent, and if the report has been signed or not. The user can click on a report to edit it.
import { useEffect, useState } from "react";
import { WeeklyReport } from "../Types/goTypes";
import { 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.
* @returns JSX.Element representing the component.
*/
function AllTimeReportsInProject(): JSX.Element { function AllTimeReportsInProject(): JSX.Element {
const { projectName } = useParams(); const { projectName } = useParams();
const [weeklyReports, setWeeklyReports] = useState<NewWeeklyReport[]>([]); const [weeklyReports, setWeeklyReports] = useState<WeeklyReport[]>([]);
/* const getWeeklyReports = async (): Promise<void> => { const getWeeklyReports = async (): Promise<void> => {
const token = localStorage.getItem("accessToken") ?? ""; const token = localStorage.getItem("accessToken") ?? "";
const response = await api.getWeeklyReports(token); const response = await api.getWeeklyReportsForUser(
token,
projectName ?? "",
);
console.log(response); console.log(response);
if (response.success) { if (response.success) {
setWeeklyReports(response.data ?? []); setWeeklyReports(response.data ?? []);
} else { } else {
console.error(response.message); console.error(response.message);
} }
}; */
const getWeeklyReports = async (): Promise<void> => {
const report: NewWeeklyReport[] = [
{
projectName: projectName ?? "",
week: 10,
developmentTime: 1,
meetingTime: 1,
adminTime: 1,
ownWorkTime: 1,
studyTime: 1,
testingTime: 1,
},
{
projectName: projectName ?? "",
week: 11,
developmentTime: 1,
meetingTime: 1,
adminTime: 1,
ownWorkTime: 100,
studyTime: 1,
testingTime: 1,
},
{
projectName: projectName ?? "",
week: 12,
developmentTime: 1,
meetingTime: 1,
adminTime: 1,
ownWorkTime: 1,
studyTime: 1,
testingTime: 1000,
},
{
projectName: projectName ?? "",
week: 20,
developmentTime: 1,
meetingTime: 1,
adminTime: 1,
ownWorkTime: 1,
studyTime: 1,
testingTime: 10000,
},
// Add more reports as needed
];
setWeeklyReports(report);
await Promise.resolve();
}; };
// Call getProjects when the component mounts // Call getProjects when the component mounts
useEffect(() => { useEffect(() => {
void getWeeklyReports(); void getWeeklyReports();
}, []); });
return ( return (
<> <>
@ -96,7 +58,7 @@ function AllTimeReportsInProject(): JSX.Element {
</h1> </h1>
<h1> <h1>
<span className="font-bold">{"Signed: "}</span> <span className="font-bold">{"Signed: "}</span>
YES {newWeeklyReport.signedBy ? "YES" : "NO"}
</h1> </h1>
</div> </div>
</Link> </Link>

View file

@ -0,0 +1,18 @@
import { Navigate } from "react-router-dom";
import React from "react";
interface AuthorizedRouteProps {
children: React.ReactNode;
isAuthorized: boolean;
}
export function AuthorizedRoute({
children,
isAuthorized,
}: AuthorizedRouteProps): JSX.Element {
if (!isAuthorized) {
return <Navigate to="/unauthorized" />;
}
return children as React.ReactElement;
}

View file

@ -1,5 +1,11 @@
//info: Back button component to navigate back to the previous page
import { useNavigate } from "react-router-dom"; import { useNavigate } from "react-router-dom";
/**
* Renders a back button component.
*
* @returns The JSX element representing the back button.
*/
function BackButton(): JSX.Element { function BackButton(): JSX.Element {
const navigate = useNavigate(); const navigate = useNavigate();
const goBack = (): void => { const goBack = (): void => {

View file

@ -1,5 +1,10 @@
//info: Background animation component to animate the background of loginpage
import { useEffect } from "react"; import { useEffect } from "react";
/**
* Renders a background animation component.
* This component pre-loads images and starts a background transition animation.
*/
const BackgroundAnimation = (): JSX.Element => { const BackgroundAnimation = (): JSX.Element => {
useEffect(() => { useEffect(() => {
const images = [ const images = [

View file

@ -1,6 +1,16 @@
//info: Basic window component to display content and buttons of a page, inclduing header and footer
//content to insert is placed in the content prop, and buttons in the buttons prop
import Header from "./Header"; import Header from "./Header";
import Footer from "./Footer"; import Footer from "./Footer";
/**
* Renders a basic window component with a header, content, and footer.
*
* @param {Object} props - The component props.
* @param {React.ReactNode} props.content - The content to be rendered in the window.
* @param {React.ReactNode} props.buttons - The buttons to be rendered in the footer.
* @returns {JSX.Element} The rendered basic window component.
*/
function BasicWindow({ function BasicWindow({
content, content,
buttons, buttons,

View file

@ -1,3 +1,12 @@
/**
* Button component to display a button with text and onClick function.
*
* @param {Object} props - The component props.
* @param {string} props.text - The text to display on the button.
* @param {Function} props.onClick - The function to run when the button is clicked.
* @param {"submit" | "button" | "reset"} props.type - The type of button.
* @returns {JSX.Element} The rendered Button component.
*/
function Button({ function Button({
text, text,
onClick, onClick,

View file

@ -0,0 +1,83 @@
import { useState } from "react";
import { useParams } from "react-router-dom";
import Button from "./Button";
export default function ChangeRoles(): JSX.Element {
const [selectedRole, setSelectedRole] = useState("");
const { username } = useParams();
const handleRoleChange = (
event: React.ChangeEvent<HTMLInputElement>,
): void => {
setSelectedRole(event.target.value);
};
// const handleSubmit = async (event: React.FormEvent<HTMLFormElement>) => {
// event.preventDefault();
// const response = await api.changeRole(username, selectedRole, token);
// if (response.success) {
// console.log("Role changed successfully");
// } else {
// console.error("Failed to change role:", response.message);
// }
// };
return (
<>
<h1 className="font-bold text-[30px] mb-[20px]">
Change roll for: {username}
</h1>
<form
className="text-[20px] font-bold border-4 border-black bg-white flex flex-col items-center justify-center min-h-[50vh] h-fit w-[30vw] rounded-3xl content-center overflow-scroll space-y-[10vh] p-[30px]"
onSubmit={undefined}
>
<div className="self-start">
<div>
<label>
<input
type="radio"
value="System Manager"
checked={selectedRole === "System Manager"}
onChange={handleRoleChange}
className="ml-2 mr-2 mb-6"
/>
System Manager
</label>
</div>
<div>
<label>
<input
type="radio"
value="Developer"
checked={selectedRole === "Developer"}
onChange={handleRoleChange}
className="ml-2 mr-2 mb-6"
/>
Developer
</label>
</div>
<div>
<label>
<input
type="radio"
value="Tester"
checked={selectedRole === "Tester"}
onChange={handleRoleChange}
className="ml-2 mr-2 mb-6"
/>
Tester
</label>
</div>
</div>
<Button
text="Save"
onClick={(): void => {
return;
}}
type="submit"
/>
</form>
</>
);
}

View file

@ -1,5 +1,4 @@
import React, { useState } from "react"; import React, { useState } from "react";
import { api } from "../API/API";
import InputField from "./InputField"; import InputField from "./InputField";
function ChangeUsername(): JSX.Element { function ChangeUsername(): JSX.Element {
@ -9,16 +8,16 @@ function ChangeUsername(): JSX.Element {
setNewUsername(e.target.value); setNewUsername(e.target.value);
}; };
const handleSubmit = async (): Promise<void> => { // const handleSubmit = async (): Promise<void> => {
try { // try {
// Call the API function to update the username // // Call the API function to update the username
await api.updateUsername(newUsername); // await api.updateUsername(newUsername);
// Optionally, add a success message or redirect the user // // Optionally, add a success message or redirect the user
} catch (error) { // } catch (error) {
console.error("Error updating username:", error); // console.error("Error updating username:", error);
// Optionally, handle the error // // Optionally, handle the error
} // }
}; // };
return ( return (
<div> <div>

View file

@ -1,38 +0,0 @@
import { useState, useEffect } from "react";
// Interface for the response from the server
// This should eventually reside in a dedicated file
interface CountResponse {
pressCount: number;
}
// Some constants for the button
const BUTTON_ENDPOINT = "/api/button";
// A simple button that counts how many times it's been pressed
export function CountButton(): JSX.Element {
const [count, setCount] = useState<number>(NaN);
// useEffect with a [] dependency array runs only once
useEffect(() => {
async function getCount(): Promise<void> {
const response = await fetch(BUTTON_ENDPOINT);
const data = (await response.json()) as CountResponse;
setCount(data.pressCount);
}
void getCount();
}, []);
// This is what runs on every button click
function press(): void {
async function pressPost(): Promise<void> {
const response = await fetch(BUTTON_ENDPOINT, { method: "POST" });
const data = (await response.json()) as CountResponse;
setCount(data.pressCount);
}
void pressPost();
}
// Return some JSX with the button and associated handler
return <button onClick={press}>count is {count}</button>;
}

View file

@ -0,0 +1,45 @@
import { useState, useEffect } from "react";
import { Project } from "../Types/goTypes";
import { Link } from "react-router-dom";
import { api } from "../API/API";
/**
* Renders a component that displays the projects a user is a part of and links to the projects start-page.
* @returns The JSX element representing the component.
*/
function DisplayUserProject(): JSX.Element {
const [projects, setProjects] = useState<Project[]>([]);
const getProjects = async (): Promise<void> => {
const token = localStorage.getItem("accessToken") ?? "";
const response = await api.getUserProjects(token);
console.log(response);
if (response.success) {
setProjects(response.data ?? []);
} else {
console.error(response.message);
}
};
// Call getProjects when the component mounts
useEffect(() => {
void getProjects();
}, []);
return (
<>
<h1 className="font-bold text-[30px] mb-[20px]">Your Projects</h1>
<div className="border-4 border-black bg-white flex flex-col items-center justify-center min-h-[65vh] h-fit w-[50vw] rounded-3xl content-center overflow-scroll space-y-[10vh] p-[30px]">
{projects.map((project, index) => (
<Link to={`/project/${project.name}`} key={index}>
<h1 className="font-bold underline text-[30px] cursor-pointer">
{project.name}
</h1>
</Link>
))}
</div>
</>
);
}
export default DisplayUserProject;

View file

@ -1,9 +1,13 @@
import { useState, useEffect } from "react"; import { useState, useEffect } from "react";
import { NewWeeklyReport } from "../Types/goTypes"; import { WeeklyReport, NewWeeklyReport } 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";
/**
* Renders the component for editing a weekly report.
* @returns JSX.Element
*/
export default function GetWeeklyReport(): JSX.Element { export default function GetWeeklyReport(): JSX.Element {
const [week, setWeek] = useState(0); const [week, setWeek] = useState(0);
const [developmentTime, setDevelopmentTime] = useState(0); const [developmentTime, setDevelopmentTime] = useState(0);
@ -27,8 +31,10 @@ export default function GetWeeklyReport(): JSX.Element {
); );
if (response.success) { if (response.success) {
const report: NewWeeklyReport = response.data ?? { const report: WeeklyReport = response.data ?? {
projectName: "", reportId: 0,
userId: 0,
projectId: 0,
week: 0, week: 0,
developmentTime: 0, developmentTime: 0,
meetingTime: 0, meetingTime: 0,
@ -37,7 +43,6 @@ export default function GetWeeklyReport(): JSX.Element {
studyTime: 0, studyTime: 0,
testingTime: 0, testingTime: 0,
}; };
setWeek(report.week); setWeek(report.week);
setDevelopmentTime(report.developmentTime); setDevelopmentTime(report.developmentTime);
setMeetingTime(report.meetingTime); setMeetingTime(report.meetingTime);

View file

@ -1,5 +1,13 @@
//info: Footer component to display the footer of a page where the buttons are placed
import React from "react"; import React from "react";
/**
* Footer component.
*
* @param {Object} props - The component props.
* @param {React.ReactNode} props.children - The children elements to render inside the footer (buttons).
* @returns {JSX.Element} The rendered footer component.
*/
function Footer({ children }: { children: React.ReactNode }): JSX.Element { function Footer({ children }: { children: React.ReactNode }): JSX.Element {
return ( return (
<footer className="bg-white"> <footer className="bg-white">

View file

@ -1,7 +1,12 @@
//info: Header component to display the header of the page including the logo and user information where thr user can logout
import { useState } from "react"; import { useState } from "react";
import { Link } from "react-router-dom"; import { Link } from "react-router-dom";
import backgroundImage from "../assets/1.jpg"; import backgroundImage from "../assets/1.jpg";
/**
* Renders the header component.
* @returns JSX.Element representing the header component.
*/
function Header(): JSX.Element { function Header(): JSX.Element {
const [isOpen, setIsOpen] = useState(false); const [isOpen, setIsOpen] = useState(false);

View file

@ -1,9 +1,15 @@
//info: New weekly report form component to create a new weekly report to
//sumbit development time, meeting time, admin time, own work time, study time and testing time
import { useState } from "react"; import { useState } from "react";
import type { NewWeeklyReport } from "../Types/goTypes"; import type { NewWeeklyReport } 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";
/**
* Renders a form for creating a new weekly report.
* @returns The JSX element representing the new weekly report form.
*/
export default function NewWeeklyReport(): JSX.Element { export default function NewWeeklyReport(): JSX.Element {
const [week, setWeek] = useState<number>(0); const [week, setWeek] = useState<number>(0);
const [developmentTime, setDevelopmentTime] = useState<number>(); const [developmentTime, setDevelopmentTime] = useState<number>();

View file

@ -0,0 +1,34 @@
import { Link, useParams } from "react-router-dom";
import { JSX } from "react/jsx-runtime";
function PMProjectMenu(): JSX.Element {
const { projectName } = useParams();
return (
<>
<h1 className="font-bold text-[30px] mb-[20px]">{projectName}</h1>
<div className="border-4 border-black bg-white flex flex-col items-center justify-center min-h-[65vh] h-fit w-[50vw] rounded-3xl content-center overflow-scroll space-y-[5vh] p-[30px]">
<Link to={`/timeReports/${projectName}/`}>
<h1 className="font-bold underline text-[30px] cursor-pointer">
Your Time Reports
</h1>
</Link>
<Link to={`/newTimeReport/${projectName}`}>
<h1 className="font-bold underline text-[30px] cursor-pointer">
New Time Report
</h1>
</Link>
<Link to={`/projectMembers/${projectName}`}>
<h1 className="font-bold underline text-[30px] cursor-pointer">
Statistics
</h1>
</Link>
<Link to={`/unsignedReports/${projectName}`}>
<h1 className="font-bold underline text-[30px] cursor-pointer">
Unsigned Time Reports
</h1>
</Link>
</div>
</>
);
}
export default PMProjectMenu;

View file

@ -0,0 +1,99 @@
import { useEffect, useState } from "react";
import { Link, useParams } from "react-router-dom";
function ProjectMembers(): JSX.Element {
const { projectName } = useParams();
const [projectMembers, setProjectMembers] = useState<ProjectMember[]>([]);
// const getProjectMembers = async (): Promise<void> => {
// const token = localStorage.getItem("accessToken") ?? "";
// const response = await api.getProjectMembers(projectName ?? "", token);
// console.log(response);
// if (response.success) {
// setProjectMembers(response.data ?? []);
// } else {
// console.error(response.message);
// }
// };
interface ProjectMember {
username: string;
role: string;
}
const mockProjectMembers = [
{
username: "username1",
role: "Project Manager",
},
{
username: "username2",
role: "System Manager",
},
{
username: "username3",
role: "Developer",
},
{
username: "username4",
role: "Tester",
},
{
username: "username5",
role: "Tester",
},
{
username: "username6",
role: "Tester",
},
];
const getProjectMembers = async (): Promise<void> => {
// Use the mock data
setProjectMembers(mockProjectMembers);
await Promise.resolve();
};
useEffect(() => {
void getProjectMembers();
});
return (
<>
<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]">
{projectMembers.map((projectMember, index) => (
<h1 key={index} className="border-b-2 border-black w-full">
<div className="flex justify-between">
<div className="flex">
<h1>{projectMember.username}</h1>
<span className="ml-6 mr-2 font-bold">Role:</span>
<h1>{projectMember.role}</h1>
</div>
<div className="flex">
<div className="ml-auto flex space-x-4">
<Link
to={`/viewReports/${projectName}/${projectMember.username}`}
>
<h1 className="underline cursor-pointer font-bold">
View Reports
</h1>
</Link>
<Link
to={`/changeRole/${projectName}/${projectMember.username}`}
>
<h1 className="underline cursor-pointer font-bold">
Change Role
</h1>
</Link>
</div>
</div>
</div>
</h1>
))}
</div>
</>
);
}
export default ProjectMembers;

View file

@ -6,6 +6,10 @@ import Button from "./Button";
import InputField from "./InputField"; import InputField from "./InputField";
import { useNavigate } from "react-router-dom"; import { useNavigate } from "react-router-dom";
/**
* Renders a registration form for the admin to add new users in.
* @returns The JSX element representing the registration form.
*/
export default function Register(): JSX.Element { export default function Register(): JSX.Element {
const [username, setUsername] = useState<string>(); const [username, setUsername] = useState<string>();
const [password, setPassword] = useState<string>(); const [password, setPassword] = useState<string>();

View file

@ -0,0 +1,32 @@
//info: User project menu component to display the user project menu where the user can navigate to
//existing time reports in a project and create a new time report
import { useParams, Link } from "react-router-dom";
import { JSX } from "react/jsx-runtime";
/**
* Renders the user project menu component.
*
* @returns JSX.Element representing the user project menu.
*/
function UserProjectMenu(): JSX.Element {
const { projectName } = useParams();
return (
<>
<h1 className="font-bold text-[30px] mb-[20px]">{projectName}</h1>
<div className="border-4 border-black bg-white flex flex-col items-center justify-center min-h-[65vh] h-fit w-[50vw] rounded-3xl content-center overflow-scroll space-y-[10vh] p-[30px]">
<Link to={`/timeReports/${projectName}/`}>
<h1 className="font-bold underline text-[30px] cursor-pointer">
Your Time Reports
</h1>
</Link>
<Link to={`/newTimeReport/${projectName}`}>
<h1 className="font-bold underline text-[30px] cursor-pointer">
New Time Report
</h1>
</Link>
</div>
</>
);
}
export default UserProjectMenu;

View file

@ -1,19 +1,16 @@
import BasicWindow from "../../Components/BasicWindow"; import BasicWindow from "../../Components/BasicWindow";
import Button from "../../Components/Button";
import BackButton from "../../Components/BackButton"; import BackButton from "../../Components/BackButton";
import ChangeRoles from "../../Components/ChangeRoles";
function ChangeRole(): JSX.Element { function ChangeRole(): JSX.Element {
const content = <></>; const content = (
<>
<ChangeRoles />
</>
);
const buttons = ( const buttons = (
<> <>
<Button
text="Save"
onClick={(): void => {
return;
}}
type="button"
/>
<BackButton /> <BackButton />
</> </>
); );

View file

@ -1,10 +1,19 @@
import BasicWindow from "../../Components/BasicWindow"; import BasicWindow from "../../Components/BasicWindow";
import Button from "../../Components/Button"; import Button from "../../Components/Button";
import BackButton from "../../Components/BackButton"; import BackButton from "../../Components/BackButton";
import { Link } from "react-router-dom"; import { Link, useParams } from "react-router-dom";
import ProjectMembers from "../../Components/ProjectMembers";
function PMProjectMembers(): JSX.Element { function PMProjectMembers(): JSX.Element {
const content = <></>; const { projectName } = useParams();
const content = (
<>
<h1 className="font-bold text-[30px] mb-[20px]">
All Members In: {projectName}{" "}
</h1>
<ProjectMembers />
</>
);
const buttons = ( const buttons = (
<> <>

View file

@ -1,36 +1,21 @@
import { Link } from "react-router-dom";
import BasicWindow from "../../Components/BasicWindow"; import BasicWindow from "../../Components/BasicWindow";
import { JSX } from "react/jsx-runtime"; import { JSX } from "react/jsx-runtime";
import PMProjectMenu from "../../Components/PMProjectMenu";
import BackButton from "../../Components/BackButton";
function PMProjectPage(): JSX.Element { function PMProjectPage(): JSX.Element {
const content = ( const content = (
<> <>
<h1 className="font-bold text-[30px] mb-[20px]">ProjectNameExample</h1> <PMProjectMenu />
<div className="border-4 border-black bg-white flex flex-col items-center justify-center min-h-[65vh] h-fit w-[50vw] rounded-3xl content-center overflow-scroll space-y-[5vh] p-[30px]">
<Link to="/project-page">
<h1 className="font-bold underline text-[30px] cursor-pointer">
Your Time Reports
</h1>
</Link>
<Link to="/new-time-report">
<h1 className="font-bold underline text-[30px] cursor-pointer">
New Time Report
</h1>
</Link>
<Link to="/project-members">
<h1 className="font-bold underline text-[30px] cursor-pointer">
Statistics
</h1>
</Link>
<Link to="/PM-unsigned-reports">
<h1 className="font-bold underline text-[30px] cursor-pointer">
Unsigned Time Reports
</h1>
</Link>
</div>
</> </>
); );
return <BasicWindow content={content} buttons={undefined} />; const buttons = (
<>
<BackButton />
</>
);
return <BasicWindow content={content} buttons={buttons} />;
} }
export default PMProjectPage; export default PMProjectPage;

View file

@ -0,0 +1,18 @@
import Button from "../Components/Button";
export default function UnauthorizedPage(): JSX.Element {
return (
<div className="flex flex-col items-center justify-center min-h-screen bg-white">
<h1 className="text-[30px]">Unauthorized</h1>
<a href="/">
<Button
text="Go to Home Page"
onClick={(): void => {
localStorage.clear();
}}
type="button"
/>
</a>
</div>
);
}

View file

@ -1,25 +1,11 @@
import { Link, useParams } from "react-router-dom";
import BasicWindow from "../../Components/BasicWindow"; import BasicWindow from "../../Components/BasicWindow";
import BackButton from "../../Components/BackButton"; import BackButton from "../../Components/BackButton";
import UserProjectMenu from "../../Components/UserProjectMenu";
function UserProjectPage(): JSX.Element { function UserProjectPage(): JSX.Element {
const { projectName } = useParams();
const content = ( const content = (
<> <>
<h1 className="font-bold text-[40px] mb-[20px]">{projectName}</h1> <UserProjectMenu />
<div className="border-4 border-black bg-white flex flex-col items-center justify-center min-h-[65vh] h-fit w-[50vw] rounded-3xl content-center overflow-scroll space-y-[10vh] p-[30px]">
<Link to={`/timeReports/${projectName}/`}>
<h1 className="font-bold underline text-[30px] cursor-pointer">
Your Time Reports
</h1>
</Link>
<Link to={`/newTimeReport/${projectName}`}>
<h1 className="font-bold underline text-[30px] cursor-pointer">
New Time Report
</h1>
</Link>
</div>
</> </>
); );

View file

@ -1,39 +1,10 @@
import { useState, useEffect } from "react";
import { Project } from "../Types/goTypes";
import { Link } from "react-router-dom";
import BasicWindow from "../Components/BasicWindow"; import BasicWindow from "../Components/BasicWindow";
import { api } from "../API/API"; import DisplayUserProjects from "../Components/DisplayUserProjects";
function UserProjectPage(): JSX.Element { function UserProjectPage(): JSX.Element {
const [projects, setProjects] = useState<Project[]>([]);
const getProjects = async (): Promise<void> => {
const token = localStorage.getItem("accessToken") ?? "";
const response = await api.getUserProjects(token);
console.log(response);
if (response.success) {
setProjects(response.data ?? []);
} else {
console.error(response.message);
}
};
// Call getProjects when the component mounts
useEffect(() => {
void getProjects();
}, []);
const content = ( const content = (
<> <>
<h1 className="font-bold text-[30px] mb-[20px]">Your Projects</h1> <DisplayUserProjects />
<div className="border-4 border-black bg-white flex flex-col items-center justify-center min-h-[65vh] h-fit w-[50vw] rounded-3xl content-center overflow-scroll space-y-[10vh] p-[30px]">
{projects.map((project, index) => (
<Link to={`/project/${project.name}`} key={index}>
<h1 className="font-bold underline text-[30px] cursor-pointer">
{project.name}
</h1>
</Link>
))}
</div>
</> </>
); );

View file

@ -40,6 +40,44 @@ export interface NewWeeklyReport {
*/ */
testingTime: number /* int */; testingTime: number /* int */;
} }
export interface WeeklyReportList {
/**
* The name of the project, as it appears in the database
*/
projectName: string;
/**
* The week number
*/
week: number /* int */;
/**
* Total time spent on development
*/
developmentTime: number /* int */;
/**
* Total time spent in meetings
*/
meetingTime: number /* int */;
/**
* Total time spent on administrative tasks
*/
adminTime: number /* int */;
/**
* Total time spent on personal projects
*/
ownWorkTime: number /* int */;
/**
* Total time spent on studying
*/
studyTime: number /* int */;
/**
* Total time spent on testing
*/
testingTime: number /* int */;
/**
* The project manager who signed it
*/
signedBy?: number /* int */;
}
export interface WeeklyReport { export interface WeeklyReport {
/** /**
* The ID of the report * The ID of the report
@ -106,6 +144,15 @@ export interface NewProject {
name: string; name: string;
description: string; description: string;
} }
export interface RoleChange {
role: 'project_manager' | 'user';
username: string;
projectname: string;
}
export interface NameChange {
id: number /* int */;
name: string;
}
////////// //////////
// source: users.go // source: users.go
@ -143,3 +190,7 @@ export interface UserProjectMember {
export interface Token { export interface Token {
token: string; token: string;
} }
export interface StrNameChange {
prevName: string;
newName: string;
}

View file

@ -30,6 +30,7 @@ import AdminProjectStatistics from "./Pages/AdminPages/AdminProjectStatistics.ts
import AdminProjectViewMemberInfo from "./Pages/AdminPages/AdminProjectViewMemberInfo.tsx"; import AdminProjectViewMemberInfo from "./Pages/AdminPages/AdminProjectViewMemberInfo.tsx";
import AdminProjectPage from "./Pages/AdminPages/AdminProjectPage.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";
// This is where the routes are mounted // This is where the routes are mounted
const router = createBrowserRouter([ const router = createBrowserRouter([
@ -63,7 +64,7 @@ const router = createBrowserRouter([
element: <UserEditTimeReportPage />, element: <UserEditTimeReportPage />,
}, },
{ {
path: "/changeRole", path: "/changeRole/:projectName/:username",
element: <PMChangeRole />, element: <PMChangeRole />,
}, },
{ {
@ -71,11 +72,11 @@ const router = createBrowserRouter([
element: <PMOtherUsersTR />, element: <PMOtherUsersTR />,
}, },
{ {
path: "/projectMembers", path: "/projectMembers/:projectName",
element: <PMProjectMembers />, element: <PMProjectMembers />,
}, },
{ {
path: "/PMProjectPage", path: "/PMProjectPage/:projectName",
element: <PMProjectPage />, element: <PMProjectPage />,
}, },
{ {
@ -87,7 +88,7 @@ const router = createBrowserRouter([
element: <PMTotalTimeRole />, element: <PMTotalTimeRole />,
}, },
{ {
path: "/PMUnsignedReports", path: "/unsignedReports/:projectName",
element: <PMUnsignedReports />, element: <PMUnsignedReports />,
}, },
{ {
@ -142,6 +143,10 @@ const router = createBrowserRouter([
path: "/adminManageUser", path: "/adminManageUser",
element: <AdminManageUsers />, element: <AdminManageUsers />,
}, },
{
path: "/unauthorized",
element: <UnauthorizedPage />,
},
]); ]);
// Semi-hacky way to get the root element // Semi-hacky way to get the root element

View file

@ -314,9 +314,8 @@ def test_get_weekly_reports_user():
# 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, getWeeklyReportsUserPath + "/" + projectName,
headers={"Authorization": "Bearer " + token}, headers={"Authorization": "Bearer " + token},
params={"username": username, "projectName": projectName},
) )
dprint(response.text) dprint(response.text)