Compare commits
No commits in common. "0792c6b8a3160f9096468438ce867389dac9cf37" and "e1b410c85072536ec97501b57d53e7d7e219a066" have entirely different histories.
0792c6b8a3
...
e1b410c850
69 changed files with 1109 additions and 2806 deletions
.gitignoretesting.py
backend
Makefilemain.go
docs
internal
database
handlers
global_state.goglobal_state_test.gohandlers_project_related.gohandlers_report_related.gohandlers_user_related.go
projects
AddUserToProject.goCreateProject.goDeleteProject.goGetProject.goGetProjectTimes.goGetUserProject.goIsProjectManager.goListAllUserProjects.goProjectRoleChange.goRemoveProject.go
reports
GetUnsignedReports.goGetWeeklyReport.goGetWeeklyReportsUserHandler.goSignReport.goSubmitWeeklyReport.goUpdateWeeklyReport.go
users
frontend/src
API
Components
AddMember.tsxAddUserToProject.tsxAllTimeReportsInProjectOtherUser.tsxChangeUsername.tsxDeleteUser.tsxDisplayUnsignedReports.tsxDisplayUserProjects.tsxEditWeeklyReport.tsxNewWeeklyReport.tsxOtherUsersTR.tsxProjectInfoModal.tsxProjectListAdmin.tsxProjectMembers.tsxRegister.tsxTimePerActivity.tsxTimePerRole.tsxUserInfoModal.tsxViewOtherTimeReport.tsx
Pages
AdminPages
ProjectManagerPages
PMOtherUsersTR.tsxPMProjectMembers.tsxPMTotalTimeActivity.tsxPMTotalTimeRole.tsxPMUnsignedReports.tsxPMViewOtherUsersTR.tsxPMViewUnsignedReport.tsx
UserPages
Types
main.tsx
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -17,7 +17,6 @@ backend/*.svg
|
||||||
|
|
||||||
/go.work.sum
|
/go.work.sum
|
||||||
/package-lock.json
|
/package-lock.json
|
||||||
/backend/docs/swagger.json
|
|
||||||
|
|
||||||
# Test binary, built with `go test -c`
|
# Test binary, built with `go test -c`
|
||||||
*.test
|
*.test
|
||||||
|
|
|
@ -104,16 +104,6 @@ default: build
|
||||||
docs:
|
docs:
|
||||||
swag init -outputTypes go
|
swag init -outputTypes go
|
||||||
|
|
||||||
api: ./docs/swagger.json
|
|
||||||
npx swagger-typescript-api \
|
|
||||||
--api-class-name GenApi \
|
|
||||||
--path ./docs/swagger.json \
|
|
||||||
--output ../frontend/src/API \
|
|
||||||
--name GenApi.ts \
|
|
||||||
|
|
||||||
./docs/swagger.json:
|
|
||||||
swag init -outputTypes json
|
|
||||||
|
|
||||||
.PHONY: docfmt
|
.PHONY: docfmt
|
||||||
docfmt:
|
docfmt:
|
||||||
swag fmt
|
swag fmt
|
||||||
|
|
|
@ -137,13 +137,13 @@ const docTemplate = `{
|
||||||
],
|
],
|
||||||
"responses": {
|
"responses": {
|
||||||
"200": {
|
"200": {
|
||||||
"description": "Successfully promoted user",
|
"description": "Successfully prometed user",
|
||||||
"schema": {
|
"schema": {
|
||||||
"type": "json"
|
"type": "json"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"400": {
|
"400": {
|
||||||
"description": "Bad request",
|
"description": "bad request",
|
||||||
"schema": {
|
"schema": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
}
|
}
|
||||||
|
|
|
@ -36,13 +36,11 @@ type Database interface {
|
||||||
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)
|
GetWeeklyReportsUser(username string, projectname string) ([]types.WeeklyReportList, 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)
|
||||||
IsProjectManager(username string, projectname string) (bool, error)
|
IsProjectManager(username string, projectname string) (bool, error)
|
||||||
GetProjectTimes(projectName string) (map[string]int, error)
|
GetProjectTimes(projectName string) (map[string]int, error)
|
||||||
UpdateWeeklyReport(projectName string, userName string, week int, developmentTime int, meetingTime int, adminTime int, ownWorkTime int, studyTime int, testingTime int) error
|
UpdateWeeklyReport(projectName string, userName string, week int, developmentTime int, meetingTime int, adminTime int, ownWorkTime int, studyTime int, testingTime int) error
|
||||||
RemoveProject(projectname string) error
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// This struct is a wrapper type that holds the database connection
|
// This struct is a wrapper type that holds the database connection
|
||||||
|
@ -356,51 +354,6 @@ func (d *Db) SignWeeklyReport(reportId int, projectManagerId int) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Db) GetUnsignedWeeklyReports(projectName string) ([]types.WeeklyReport, error) {
|
|
||||||
// Define the SQL query to fetch unsigned reports for a given user
|
|
||||||
query := `
|
|
||||||
SELECT
|
|
||||||
report_id,
|
|
||||||
user_id,
|
|
||||||
project_id,
|
|
||||||
week,
|
|
||||||
development_time,
|
|
||||||
meeting_time,
|
|
||||||
admin_time,
|
|
||||||
own_work_time,
|
|
||||||
study_time,
|
|
||||||
testing_time,
|
|
||||||
signed_by
|
|
||||||
FROM
|
|
||||||
weekly_reports
|
|
||||||
WHERE
|
|
||||||
signed_by IS NULL
|
|
||||||
AND project_id = (SELECT id FROM projects WHERE name = ?)
|
|
||||||
`
|
|
||||||
|
|
||||||
// Execute the query
|
|
||||||
rows, err := d.Queryx(query, projectName)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
defer rows.Close()
|
|
||||||
|
|
||||||
// Iterate over the rows and populate the result slice
|
|
||||||
var reports []types.WeeklyReport
|
|
||||||
for rows.Next() {
|
|
||||||
var report types.WeeklyReport
|
|
||||||
if err := rows.StructScan(&report); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
reports = append(reports, report)
|
|
||||||
}
|
|
||||||
if err := rows.Err(); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return reports, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsSiteAdmin checks if a given username is a site admin
|
// IsSiteAdmin checks if a given username is a site admin
|
||||||
func (d *Db) IsSiteAdmin(username string) (bool, error) {
|
func (d *Db) IsSiteAdmin(username string) (bool, error) {
|
||||||
// Define the SQL query to check if the user is a site admin
|
// Define the SQL query to check if the user is a site admin
|
||||||
|
@ -596,8 +549,3 @@ func (d *Db) GetProjectTimes(projectName string) (map[string]int, error) {
|
||||||
|
|
||||||
return totalTime, nil
|
return totalTime, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Db) RemoveProject(projectname string) error {
|
|
||||||
_, err := d.Exec("DELETE FROM projects WHERE name = ?", projectname)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
|
@ -470,47 +470,6 @@ func TestGetWeeklyReport(t *testing.T) {
|
||||||
// Check other fields similarly
|
// Check other fields similarly
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGetUnsignedWeeklyReports(t *testing.T) {
|
|
||||||
db, err := setupAdvancedState()
|
|
||||||
if err != nil {
|
|
||||||
t.Error("setupState failed:", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = db.AddUser("testuser", "password")
|
|
||||||
if err != nil {
|
|
||||||
t.Error("AddUser failed:", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = db.AddUser("testuser1", "password")
|
|
||||||
if err != nil {
|
|
||||||
t.Error("AddUser failed:", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = db.AddProject("testproject", "description", "testuser")
|
|
||||||
if err != nil {
|
|
||||||
t.Error("AddProject failed:", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = db.AddWeeklyReport("testproject", "testuser", 1, 1, 1, 1, 1, 1, 1)
|
|
||||||
if err != nil {
|
|
||||||
t.Error("AddWeeklyReport failed:", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = db.AddWeeklyReport("testproject", "testuser1", 1, 1, 1, 1, 1, 1, 1)
|
|
||||||
if err != nil {
|
|
||||||
t.Error("AddWeeklyReport failed:", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
reports, err := db.GetUnsignedWeeklyReports("testproject")
|
|
||||||
if err != nil {
|
|
||||||
t.Error("GetUnsignedWeeklyReports failed:", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if reports == nil {
|
|
||||||
t.Error("Expected non-nil reports, got nil")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TestSignWeeklyReport tests SignWeeklyReport function of the database
|
// TestSignWeeklyReport tests SignWeeklyReport function of the database
|
||||||
func TestSignWeeklyReport(t *testing.T) {
|
func TestSignWeeklyReport(t *testing.T) {
|
||||||
db, err := setupState()
|
db, err := setupState()
|
||||||
|
@ -935,33 +894,3 @@ func TestUpdateWeeklyReport(t *testing.T) {
|
||||||
t.Error("UpdateWeeklyReport failed: report not updated correctly")
|
t.Error("UpdateWeeklyReport failed: report not updated correctly")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRemoveProject(t *testing.T) {
|
|
||||||
db, err := setupAdvancedState()
|
|
||||||
if err != nil {
|
|
||||||
t.Error("setupState failed:", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Promote user to Admin
|
|
||||||
err = db.PromoteToAdmin("demouser")
|
|
||||||
if err != nil {
|
|
||||||
t.Error("PromoteToAdmin failed:", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove project
|
|
||||||
err = db.RemoveProject("projecttest")
|
|
||||||
if err != nil {
|
|
||||||
t.Error("RemoveProject failed:", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if the project was removed
|
|
||||||
projects, err := db.GetAllProjects()
|
|
||||||
if err != nil {
|
|
||||||
t.Error("GetAllProjects failed:", err)
|
|
||||||
}
|
|
||||||
if len(projects) != 0 {
|
|
||||||
t.Error("RemoveProject failed: expected 0, got", len(projects))
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,17 +0,0 @@
|
||||||
package database
|
|
||||||
|
|
||||||
import "github.com/gofiber/fiber/v2"
|
|
||||||
|
|
||||||
// Simple middleware that provides a shared database pool as a local key "db"
|
|
||||||
func DbMiddleware(db *Database) func(c *fiber.Ctx) error {
|
|
||||||
return func(c *fiber.Ctx) error {
|
|
||||||
c.Locals("db", db)
|
|
||||||
return c.Next()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Helper function to get the database from the context, without fiddling with casts
|
|
||||||
func GetDb(c *fiber.Ctx) Database {
|
|
||||||
// Dereference a pointer to a local, casted to a pointer to a Database
|
|
||||||
return *c.Locals("db").(*Database)
|
|
||||||
}
|
|
|
@ -7,8 +7,6 @@ VALUES ("user", "123");
|
||||||
INSERT OR IGNORE INTO users(username, password)
|
INSERT OR IGNORE INTO users(username, password)
|
||||||
VALUES ("user2", "123");
|
VALUES ("user2", "123");
|
||||||
|
|
||||||
INSERT OR IGNORE INTO site_admin VALUES (1);
|
|
||||||
|
|
||||||
INSERT OR IGNORE INTO projects(name,description,owner_user_id)
|
INSERT OR IGNORE INTO projects(name,description,owner_user_id)
|
||||||
VALUES ("projecttest","test project", 1);
|
VALUES ("projecttest","test project", 1);
|
||||||
|
|
||||||
|
|
42
backend/internal/handlers/global_state.go
Normal file
42
backend/internal/handlers/global_state.go
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
package handlers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"ttime/internal/database"
|
||||||
|
|
||||||
|
"github.com/gofiber/fiber/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
// The actual interface that we will use
|
||||||
|
type GlobalState interface {
|
||||||
|
Register(c *fiber.Ctx) error // To register a new user
|
||||||
|
UserDelete(c *fiber.Ctx) error // To delete a user
|
||||||
|
Login(c *fiber.Ctx) error // To get the token
|
||||||
|
LoginRenew(c *fiber.Ctx) error // To renew the token
|
||||||
|
CreateProject(c *fiber.Ctx) error // To create a new project
|
||||||
|
GetUserProjects(c *fiber.Ctx) error // To get all projects
|
||||||
|
SubmitWeeklyReport(c *fiber.Ctx) error
|
||||||
|
GetWeeklyReport(c *fiber.Ctx) error
|
||||||
|
SignReport(c *fiber.Ctx) error
|
||||||
|
GetProject(c *fiber.Ctx) error
|
||||||
|
AddUserToProjectHandler(c *fiber.Ctx) error
|
||||||
|
PromoteToAdmin(c *fiber.Ctx) error
|
||||||
|
GetWeeklyReportsUserHandler(c *fiber.Ctx) error
|
||||||
|
IsProjectManagerHandler(c *fiber.Ctx) error
|
||||||
|
DeleteProject(c *fiber.Ctx) error // To delete a project // WIP
|
||||||
|
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
|
||||||
|
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
|
||||||
|
UpdateWeeklyReport(c *fiber.Ctx) error
|
||||||
|
}
|
||||||
|
|
||||||
|
// "Constructor"
|
||||||
|
func NewGlobalState(db database.Database) GlobalState {
|
||||||
|
return &GState{Db: db}
|
||||||
|
}
|
||||||
|
|
||||||
|
// The global state, which implements all the handlers
|
||||||
|
type GState struct {
|
||||||
|
Db database.Database
|
||||||
|
}
|
15
backend/internal/handlers/global_state_test.go
Normal file
15
backend/internal/handlers/global_state_test.go
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
package handlers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
"ttime/internal/database"
|
||||||
|
)
|
||||||
|
|
||||||
|
// The actual interface that we will use
|
||||||
|
func TestGlobalState(t *testing.T) {
|
||||||
|
db := database.DbConnect(":memory:")
|
||||||
|
gs := NewGlobalState(db)
|
||||||
|
if gs == nil {
|
||||||
|
t.Error("NewGlobalState returned nil")
|
||||||
|
}
|
||||||
|
}
|
289
backend/internal/handlers/handlers_project_related.go
Normal file
289
backend/internal/handlers/handlers_project_related.go
Normal file
|
@ -0,0 +1,289 @@
|
||||||
|
package handlers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strconv"
|
||||||
|
"ttime/internal/types"
|
||||||
|
|
||||||
|
"github.com/gofiber/fiber/v2"
|
||||||
|
"github.com/gofiber/fiber/v2/log"
|
||||||
|
"github.com/golang-jwt/jwt/v5"
|
||||||
|
)
|
||||||
|
|
||||||
|
// CreateProject is a simple handler that creates a new project
|
||||||
|
func (gs *GState) CreateProject(c *fiber.Ctx) error {
|
||||||
|
user := c.Locals("user").(*jwt.Token)
|
||||||
|
|
||||||
|
p := new(types.NewProject)
|
||||||
|
if err := c.BodyParser(p); err != nil {
|
||||||
|
return c.Status(400).SendString(err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the username from the token and set it as the owner of the project
|
||||||
|
// This is ugly but
|
||||||
|
claims := user.Claims.(jwt.MapClaims)
|
||||||
|
owner := claims["name"].(string)
|
||||||
|
|
||||||
|
if err := gs.Db.AddProject(p.Name, p.Description, owner); err != nil {
|
||||||
|
return c.Status(500).SendString(err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.Status(200).SendString("Project added")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (gs *GState) DeleteProject(c *fiber.Ctx) error {
|
||||||
|
|
||||||
|
projectID := c.Params("projectID")
|
||||||
|
username := c.Params("username")
|
||||||
|
|
||||||
|
if err := gs.Db.DeleteProject(projectID, username); err != nil {
|
||||||
|
return c.Status(500).SendString((err.Error()))
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.Status(200).SendString("Project deleted")
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetUserProjects returns all projects that the user is a member of
|
||||||
|
func (gs *GState) GetUserProjects(c *fiber.Ctx) error {
|
||||||
|
// First we get the username from the token
|
||||||
|
user := c.Locals("user").(*jwt.Token)
|
||||||
|
claims := user.Claims.(jwt.MapClaims)
|
||||||
|
username := claims["name"].(string)
|
||||||
|
|
||||||
|
// Then dip into the database to get the projects
|
||||||
|
projects, err := gs.Db.GetProjectsForUser(username)
|
||||||
|
if err != nil {
|
||||||
|
return c.Status(500).SendString(err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return a json serialized list of projects
|
||||||
|
return c.JSON(projects)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ProjectRoleChange is a handler that changes a user's role within a project
|
||||||
|
func (gs *GState) ProjectRoleChange(c *fiber.Ctx) error {
|
||||||
|
|
||||||
|
//check token and get username of current user
|
||||||
|
user := c.Locals("user").(*jwt.Token)
|
||||||
|
claims := user.Claims.(jwt.MapClaims)
|
||||||
|
username := claims["name"].(string)
|
||||||
|
|
||||||
|
// Extract the necessary parameters from the request
|
||||||
|
data := new(types.RoleChange)
|
||||||
|
if err := c.BodyParser(data); err != nil {
|
||||||
|
log.Info("error parsing username, project or role")
|
||||||
|
return c.Status(400).SendString(err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Info("Changing role for user: ", username, " in project: ", data.Projectname, " to: ", data.Role)
|
||||||
|
|
||||||
|
// Dubble diping and checcking if current user is
|
||||||
|
if ismanager, err := gs.Db.IsProjectManager(username, data.Projectname); err != nil {
|
||||||
|
log.Warn("Error checking if projectmanager:", err)
|
||||||
|
return c.Status(500).SendString(err.Error())
|
||||||
|
} else if !ismanager {
|
||||||
|
log.Warn("User is not projectmanager")
|
||||||
|
return c.Status(401).SendString("User is not projectmanager")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Change the user's role within the project in the database
|
||||||
|
if err := gs.Db.ChangeUserRole(username, data.Projectname, data.Role); err != nil {
|
||||||
|
return c.Status(500).SendString(err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return a success message
|
||||||
|
return c.SendStatus(fiber.StatusOK)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetProject retrieves a specific project by its ID
|
||||||
|
func (gs *GState) GetProject(c *fiber.Ctx) error {
|
||||||
|
// Extract the project ID from the request parameters or body
|
||||||
|
projectID := c.Params("projectID")
|
||||||
|
if projectID == "" {
|
||||||
|
log.Info("No project ID provided")
|
||||||
|
return c.Status(400).SendString("No project ID provided")
|
||||||
|
}
|
||||||
|
log.Info("Getting project with ID: ", projectID)
|
||||||
|
|
||||||
|
// Parse the project ID into an integer
|
||||||
|
projectIDInt, err := strconv.Atoi(projectID)
|
||||||
|
if err != nil {
|
||||||
|
log.Info("Invalid project ID")
|
||||||
|
return c.Status(400).SendString("Invalid project ID")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the project from the database by its ID
|
||||||
|
project, err := gs.Db.GetProject(projectIDInt)
|
||||||
|
if err != nil {
|
||||||
|
log.Info("Error getting project:", err)
|
||||||
|
return c.Status(500).SendString(err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return the project as JSON
|
||||||
|
log.Info("Returning project: ", project.Name)
|
||||||
|
return c.JSON(project)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (gs *GState) ListAllUsersProject(c *fiber.Ctx) error {
|
||||||
|
// Extract the project name from the request parameters or body
|
||||||
|
projectName := c.Params("projectName")
|
||||||
|
if projectName == "" {
|
||||||
|
log.Info("No project name provided")
|
||||||
|
return c.Status(400).SendString("No project name provided")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the user token
|
||||||
|
userToken := c.Locals("user").(*jwt.Token)
|
||||||
|
claims := userToken.Claims.(jwt.MapClaims)
|
||||||
|
username := claims["name"].(string)
|
||||||
|
|
||||||
|
// Check if the user is a project manager for the specified project
|
||||||
|
isManager, err := gs.Db.IsProjectManager(username, projectName)
|
||||||
|
if err != nil {
|
||||||
|
log.Info("Error checking project manager status:", err)
|
||||||
|
return c.Status(500).SendString(err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the user is not a project manager, check if the user is a site admin
|
||||||
|
if !isManager {
|
||||||
|
isAdmin, err := gs.Db.IsSiteAdmin(username)
|
||||||
|
if err != nil {
|
||||||
|
log.Info("Error checking admin status:", err)
|
||||||
|
return c.Status(500).SendString(err.Error())
|
||||||
|
}
|
||||||
|
if !isAdmin {
|
||||||
|
log.Info("User is neither a project manager nor a site admin:", username)
|
||||||
|
return c.Status(403).SendString("User is neither a project manager nor a site admin")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get all users associated with the project from the database
|
||||||
|
users, err := gs.Db.GetAllUsersProject(projectName)
|
||||||
|
if err != nil {
|
||||||
|
log.Info("Error getting users for project:", err)
|
||||||
|
return c.Status(500).SendString(err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Info("Returning users for project: ", projectName)
|
||||||
|
|
||||||
|
// Return the list of users as JSON
|
||||||
|
return c.JSON(users)
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddUserToProjectHandler is a handler that adds a user to a project with a specified role
|
||||||
|
func (gs *GState) AddUserToProjectHandler(c *fiber.Ctx) error {
|
||||||
|
// Extract necessary parameters from the request
|
||||||
|
var requestData struct {
|
||||||
|
Username string `json:"username"`
|
||||||
|
ProjectName string `json:"projectName"`
|
||||||
|
Role string `json:"role"`
|
||||||
|
}
|
||||||
|
if err := c.BodyParser(&requestData); err != nil {
|
||||||
|
log.Info("Error parsing request body:", err)
|
||||||
|
return c.Status(400).SendString("Bad request")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if the user adding another user to the project is a site admin
|
||||||
|
user := c.Locals("user").(*jwt.Token)
|
||||||
|
claims := user.Claims.(jwt.MapClaims)
|
||||||
|
adminUsername := claims["name"].(string)
|
||||||
|
log.Info("Admin username from claims:", adminUsername)
|
||||||
|
|
||||||
|
isAdmin, err := gs.Db.IsSiteAdmin(adminUsername)
|
||||||
|
if err != nil {
|
||||||
|
log.Info("Error checking admin status:", err)
|
||||||
|
return c.Status(500).SendString(err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
if !isAdmin {
|
||||||
|
log.Info("User is not a site admin:", adminUsername)
|
||||||
|
return c.Status(403).SendString("User is not a site admin")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add the user to the project with the specified role
|
||||||
|
err = gs.Db.AddUserToProject(requestData.Username, requestData.ProjectName, requestData.Role)
|
||||||
|
if err != nil {
|
||||||
|
log.Info("Error adding user to project:", err)
|
||||||
|
return c.Status(500).SendString(err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return success message
|
||||||
|
log.Info("User added to project successfully:", requestData.Username)
|
||||||
|
return c.SendStatus(fiber.StatusOK)
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsProjectManagerHandler is a handler that checks if a user is a project manager for a given project
|
||||||
|
func (gs *GState) IsProjectManagerHandler(c *fiber.Ctx) error {
|
||||||
|
// Get the username from the token
|
||||||
|
user := c.Locals("user").(*jwt.Token)
|
||||||
|
claims := user.Claims.(jwt.MapClaims)
|
||||||
|
username := claims["name"].(string)
|
||||||
|
|
||||||
|
// Extract necessary parameters from the request query string
|
||||||
|
projectName := c.Params("projectName")
|
||||||
|
|
||||||
|
log.Info("Checking if user ", username, " is a project manager for project ", projectName)
|
||||||
|
|
||||||
|
// Check if the user is a project manager for the specified project
|
||||||
|
isManager, err := gs.Db.IsProjectManager(username, projectName)
|
||||||
|
if err != nil {
|
||||||
|
log.Info("Error checking project manager status:", err)
|
||||||
|
return c.Status(500).SendString(err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return the result as JSON
|
||||||
|
return c.JSON(fiber.Map{"isProjectManager": isManager})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (gs *GState) GetProjectTimesHandler(c *fiber.Ctx) error {
|
||||||
|
// Get the username from the token
|
||||||
|
user := c.Locals("user").(*jwt.Token)
|
||||||
|
claims := user.Claims.(jwt.MapClaims)
|
||||||
|
username := claims["name"].(string)
|
||||||
|
|
||||||
|
// Get project
|
||||||
|
projectName := c.Params("projectName")
|
||||||
|
if projectName == "" {
|
||||||
|
log.Info("No project name provided")
|
||||||
|
return c.Status(400).SendString("No project name provided")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get all users in the project and roles
|
||||||
|
userProjects, err := gs.Db.GetAllUsersProject(projectName)
|
||||||
|
if err != nil {
|
||||||
|
log.Info("Error getting users in project:", err)
|
||||||
|
return c.Status(500).SendString(err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the user is member
|
||||||
|
isMember := false
|
||||||
|
for _, userProject := range userProjects {
|
||||||
|
if userProject.Username == username {
|
||||||
|
isMember = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the user is admin
|
||||||
|
if !isMember {
|
||||||
|
isAdmin, err := gs.Db.IsSiteAdmin(username)
|
||||||
|
if err != nil {
|
||||||
|
log.Info("Error checking admin status:", err)
|
||||||
|
return c.Status(500).SendString(err.Error())
|
||||||
|
}
|
||||||
|
if !isAdmin {
|
||||||
|
log.Info("User is neither a project member nor a site admin:", username)
|
||||||
|
return c.Status(403).SendString("User is neither a project member nor a site admin")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get project times
|
||||||
|
projectTimes, err := gs.Db.GetProjectTimes(projectName)
|
||||||
|
if err != nil {
|
||||||
|
log.Info("Error getting project times:", err)
|
||||||
|
return c.Status(500).SendString(err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return project times as JSON
|
||||||
|
log.Info("Returning project times for project:", projectName)
|
||||||
|
return c.JSON(projectTimes)
|
||||||
|
}
|
177
backend/internal/handlers/handlers_report_related.go
Normal file
177
backend/internal/handlers/handlers_report_related.go
Normal file
|
@ -0,0 +1,177 @@
|
||||||
|
package handlers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strconv"
|
||||||
|
"ttime/internal/types"
|
||||||
|
|
||||||
|
"github.com/gofiber/fiber/v2"
|
||||||
|
"github.com/gofiber/fiber/v2/log"
|
||||||
|
"github.com/golang-jwt/jwt/v5"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (gs *GState) SubmitWeeklyReport(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)
|
||||||
|
|
||||||
|
report := new(types.NewWeeklyReport)
|
||||||
|
if err := c.BodyParser(report); err != nil {
|
||||||
|
log.Info("Error parsing weekly report")
|
||||||
|
return c.Status(400).SendString(err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make sure all the fields of the report are valid
|
||||||
|
if report.Week < 1 || report.Week > 52 {
|
||||||
|
log.Info("Invalid week number")
|
||||||
|
return c.Status(400).SendString("Invalid week number")
|
||||||
|
}
|
||||||
|
if report.DevelopmentTime < 0 || report.MeetingTime < 0 || report.AdminTime < 0 || report.OwnWorkTime < 0 || report.StudyTime < 0 || report.TestingTime < 0 {
|
||||||
|
log.Info("Invalid time report")
|
||||||
|
return c.Status(400).SendString("Invalid time report")
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := gs.Db.AddWeeklyReport(report.ProjectName, username, report.Week, report.DevelopmentTime, report.MeetingTime, report.AdminTime, report.OwnWorkTime, report.StudyTime, report.TestingTime); err != nil {
|
||||||
|
log.Info("Error adding weekly report to db:", err)
|
||||||
|
return c.Status(500).SendString(err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Info("Weekly report added")
|
||||||
|
return c.Status(200).SendString("Time report added")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handler for retrieving weekly report
|
||||||
|
func (gs *GState) GetWeeklyReport(c *fiber.Ctx) error {
|
||||||
|
// Extract the necessary parameters from the request
|
||||||
|
user := c.Locals("user").(*jwt.Token)
|
||||||
|
claims := user.Claims.(jwt.MapClaims)
|
||||||
|
username := claims["name"].(string)
|
||||||
|
|
||||||
|
log.Info("Getting weekly report for: ", username)
|
||||||
|
|
||||||
|
// Extract project name and week from query parameters
|
||||||
|
projectName := c.Query("projectName")
|
||||||
|
week := c.Query("week")
|
||||||
|
|
||||||
|
if projectName == "" || week == "" {
|
||||||
|
log.Info("Missing project name or week number")
|
||||||
|
return c.Status(400).SendString("Missing project name or week number")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert week to integer
|
||||||
|
weekInt, err := strconv.Atoi(week)
|
||||||
|
if err != nil {
|
||||||
|
log.Info("Invalid week number")
|
||||||
|
return c.Status(400).SendString("Invalid week number")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Call the database function to get the weekly report
|
||||||
|
report, err := gs.Db.GetWeeklyReport(username, projectName, weekInt)
|
||||||
|
if err != nil {
|
||||||
|
log.Info("Error getting weekly report from db:", err)
|
||||||
|
return c.Status(500).SendString(err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Info("Returning weekly report")
|
||||||
|
// Return the retrieved weekly report
|
||||||
|
return c.JSON(report)
|
||||||
|
}
|
||||||
|
|
||||||
|
type ReportId struct {
|
||||||
|
ReportId int
|
||||||
|
}
|
||||||
|
|
||||||
|
func (gs *GState) SignReport(c *fiber.Ctx) error {
|
||||||
|
// Extract the necessary parameters from the token
|
||||||
|
user := c.Locals("user").(*jwt.Token)
|
||||||
|
claims := user.Claims.(jwt.MapClaims)
|
||||||
|
projectManagerUsername := claims["name"].(string)
|
||||||
|
|
||||||
|
log.Info("Signing report for: ", projectManagerUsername)
|
||||||
|
|
||||||
|
// Extract report ID from the request query parameters
|
||||||
|
// reportID := c.Query("reportId")
|
||||||
|
rid := new(ReportId)
|
||||||
|
if err := c.BodyParser(rid); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
log.Info("Signing report for: ", rid.ReportId)
|
||||||
|
|
||||||
|
// Get the project manager's ID
|
||||||
|
projectManagerID, err := gs.Db.GetUserId(projectManagerUsername)
|
||||||
|
if err != nil {
|
||||||
|
log.Info("Failed to get project manager ID")
|
||||||
|
return c.Status(500).SendString("Failed to get project manager ID")
|
||||||
|
}
|
||||||
|
log.Info("Project manager ID: ", projectManagerID)
|
||||||
|
|
||||||
|
// Call the database function to sign the weekly report
|
||||||
|
err = gs.Db.SignWeeklyReport(rid.ReportId, projectManagerID)
|
||||||
|
if err != nil {
|
||||||
|
log.Info("Error signing weekly report:", err)
|
||||||
|
return c.Status(500).SendString(err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.Status(200).SendString("Weekly report signed successfully")
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetWeeklyReportsUserHandler retrieves all weekly reports for a user in a specific project
|
||||||
|
func (gs *GState) 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 := gs.Db.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)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (gs *GState) UpdateWeeklyReport(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)
|
||||||
|
|
||||||
|
// Parse the request body into an UpdateWeeklyReport struct
|
||||||
|
var updateReport types.UpdateWeeklyReport
|
||||||
|
if err := c.BodyParser(&updateReport); err != nil {
|
||||||
|
log.Info("Error parsing weekly report")
|
||||||
|
return c.Status(400).SendString(err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make sure all the fields of the report are valid
|
||||||
|
if updateReport.Week < 1 || updateReport.Week > 52 {
|
||||||
|
log.Info("Invalid week number")
|
||||||
|
return c.Status(400).SendString("Invalid week number")
|
||||||
|
}
|
||||||
|
|
||||||
|
if updateReport.DevelopmentTime < 0 || updateReport.MeetingTime < 0 || updateReport.AdminTime < 0 || updateReport.OwnWorkTime < 0 || updateReport.StudyTime < 0 || updateReport.TestingTime < 0 {
|
||||||
|
log.Info("Invalid time report")
|
||||||
|
return c.Status(400).SendString("Invalid time report")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update the weekly report in the database
|
||||||
|
if err := gs.Db.UpdateWeeklyReport(updateReport.ProjectName, username, updateReport.Week, updateReport.DevelopmentTime, updateReport.MeetingTime, updateReport.AdminTime, updateReport.OwnWorkTime, updateReport.StudyTime, updateReport.TestingTime); err != nil {
|
||||||
|
log.Info("Error updating weekly report in db:", err)
|
||||||
|
return c.Status(500).SendString(err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Info("Weekly report updated")
|
||||||
|
return c.Status(200).SendString("Weekly report updated")
|
||||||
|
}
|
269
backend/internal/handlers/handlers_user_related.go
Normal file
269
backend/internal/handlers/handlers_user_related.go
Normal file
|
@ -0,0 +1,269 @@
|
||||||
|
package handlers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
"ttime/internal/types"
|
||||||
|
|
||||||
|
"github.com/gofiber/fiber/v2/log"
|
||||||
|
|
||||||
|
"github.com/gofiber/fiber/v2"
|
||||||
|
"github.com/golang-jwt/jwt/v5"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Register is a simple handler that registers a new user
|
||||||
|
//
|
||||||
|
// @Summary Register
|
||||||
|
// @Description Register a new user
|
||||||
|
// @Tags User
|
||||||
|
// @Accept json
|
||||||
|
// @Produce plain
|
||||||
|
// @Param NewUser body types.NewUser true "User to register"
|
||||||
|
// @Success 200 {string} string "User added"
|
||||||
|
// @Failure 400 {string} string "Bad request"
|
||||||
|
// @Failure 500 {string} string "Internal server error"
|
||||||
|
// @Router /register [post]
|
||||||
|
func (gs *GState) Register(c *fiber.Ctx) error {
|
||||||
|
u := new(types.NewUser)
|
||||||
|
if err := c.BodyParser(u); err != nil {
|
||||||
|
log.Warn("Error parsing body")
|
||||||
|
return c.Status(400).SendString(err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Info("Adding user:", u.Username)
|
||||||
|
if err := gs.Db.AddUser(u.Username, u.Password); err != nil {
|
||||||
|
log.Warn("Error adding user:", err)
|
||||||
|
return c.Status(500).SendString(err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Info("User added:", u.Username)
|
||||||
|
return c.Status(200).SendString("User added")
|
||||||
|
}
|
||||||
|
|
||||||
|
// This path should obviously be protected in the future
|
||||||
|
// UserDelete deletes a user from the database
|
||||||
|
//
|
||||||
|
// @Summary UserDelete
|
||||||
|
// @Description UserDelete deletes a user from the database
|
||||||
|
// @Tags User
|
||||||
|
// @Accept json
|
||||||
|
// @Produce plain
|
||||||
|
// @Success 200 {string} string "User deleted"
|
||||||
|
// @Failure 403 {string} string "You can only delete yourself"
|
||||||
|
// @Failure 500 {string} string "Internal server error"
|
||||||
|
// @Failure 401 {string} string "Unauthorized"
|
||||||
|
// @Router /userdelete/{username} [delete]
|
||||||
|
func (gs *GState) UserDelete(c *fiber.Ctx) error {
|
||||||
|
// Read from path parameters
|
||||||
|
username := c.Params("username")
|
||||||
|
|
||||||
|
// Read username from Locals
|
||||||
|
auth_username := c.Locals("user").(*jwt.Token).Claims.(jwt.MapClaims)["name"].(string)
|
||||||
|
|
||||||
|
if username != auth_username {
|
||||||
|
log.Info("User tried to delete another user")
|
||||||
|
return c.Status(403).SendString("You can only delete yourself")
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := gs.Db.RemoveUser(username); err != nil {
|
||||||
|
log.Warn("Error deleting user:", err)
|
||||||
|
return c.Status(500).SendString(err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Info("User deleted:", username)
|
||||||
|
return c.Status(200).SendString("User deleted")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Login is a simple login handler that returns a JWT token
|
||||||
|
//
|
||||||
|
// @Summary login
|
||||||
|
// @Description logs the user in and returns a jwt token
|
||||||
|
// @Tags User
|
||||||
|
// @Accept json
|
||||||
|
// @Param NewUser body types.NewUser true "login info"
|
||||||
|
// @Produce plain
|
||||||
|
// @Success 200 Token types.Token "Successfully signed token for user"
|
||||||
|
// @Failure 400 {string} string "Bad request"
|
||||||
|
// @Failure 401 {string} string "Unauthorized"
|
||||||
|
// @Failure 500 {string} string "Internal server error"
|
||||||
|
// @Router /login [post]
|
||||||
|
func (gs *GState) Login(c *fiber.Ctx) error {
|
||||||
|
// The body type is identical to a NewUser
|
||||||
|
|
||||||
|
u := new(types.NewUser)
|
||||||
|
if err := c.BodyParser(u); err != nil {
|
||||||
|
log.Warn("Error parsing body")
|
||||||
|
return c.Status(400).SendString(err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Info("Username logging in:", u.Username)
|
||||||
|
if !gs.Db.CheckUser(u.Username, u.Password) {
|
||||||
|
log.Info("User not found")
|
||||||
|
return c.SendStatus(fiber.StatusUnauthorized)
|
||||||
|
}
|
||||||
|
|
||||||
|
isAdmin, err := gs.Db.IsSiteAdmin(u.Username)
|
||||||
|
if err != nil {
|
||||||
|
log.Info("Error checking admin status:", err)
|
||||||
|
return c.Status(500).SendString(err.Error())
|
||||||
|
}
|
||||||
|
// Create the Claims
|
||||||
|
claims := jwt.MapClaims{
|
||||||
|
"name": u.Username,
|
||||||
|
"admin": isAdmin,
|
||||||
|
"exp": time.Now().Add(time.Hour * 72).Unix(),
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create token
|
||||||
|
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
|
||||||
|
log.Info("Token created for user:", u.Username)
|
||||||
|
|
||||||
|
// Generate encoded token and send it as response.
|
||||||
|
t, err := token.SignedString([]byte("secret"))
|
||||||
|
if err != nil {
|
||||||
|
log.Warn("Error signing token")
|
||||||
|
return c.SendStatus(fiber.StatusInternalServerError)
|
||||||
|
}
|
||||||
|
|
||||||
|
println("Successfully signed token for user:", u.Username)
|
||||||
|
return c.JSON(types.Token{Token: t})
|
||||||
|
}
|
||||||
|
|
||||||
|
// LoginRenew is a simple handler that renews the token
|
||||||
|
//
|
||||||
|
// @Summary LoginRenews
|
||||||
|
// @Description renews the users token
|
||||||
|
// @Security bererToken
|
||||||
|
// @Tags User
|
||||||
|
// @Accept json
|
||||||
|
// @Produce plain
|
||||||
|
// @Success 200 Token types.Token "Successfully signed token for user"
|
||||||
|
// @Failure 401 {string} string "Unauthorized"
|
||||||
|
// @Failure 500 {string} string "Internal server error"
|
||||||
|
// @Router /loginerenew [post]
|
||||||
|
func (gs *GState) LoginRenew(c *fiber.Ctx) error {
|
||||||
|
user := c.Locals("user").(*jwt.Token)
|
||||||
|
|
||||||
|
log.Info("Renewing token for user:", user.Claims.(jwt.MapClaims)["name"])
|
||||||
|
|
||||||
|
claims := user.Claims.(jwt.MapClaims)
|
||||||
|
claims["exp"] = time.Now().Add(time.Hour * 72).Unix()
|
||||||
|
renewed := jwt.MapClaims{
|
||||||
|
"name": claims["name"],
|
||||||
|
"admin": claims["admin"],
|
||||||
|
"exp": claims["exp"],
|
||||||
|
}
|
||||||
|
token := jwt.NewWithClaims(jwt.SigningMethodHS256, renewed)
|
||||||
|
t, err := token.SignedString([]byte("secret"))
|
||||||
|
if err != nil {
|
||||||
|
log.Warn("Error signing token")
|
||||||
|
return c.SendStatus(fiber.StatusInternalServerError)
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Info("Successfully renewed token for user:", user.Claims.(jwt.MapClaims)["name"])
|
||||||
|
return c.JSON(types.Token{Token: t})
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListAllUsers is a handler that returns a list of all users in the application database
|
||||||
|
//
|
||||||
|
// @Summary ListsAllUsers
|
||||||
|
// @Description lists all users
|
||||||
|
// @Tags User
|
||||||
|
// @Accept json
|
||||||
|
// @Produce plain
|
||||||
|
// @Success 200 {json} json "Successfully signed token for user"
|
||||||
|
// @Failure 401 {string} string "Unauthorized"
|
||||||
|
// @Failure 500 {string} string "Internal server error"
|
||||||
|
// @Router /users/all [get]
|
||||||
|
func (gs *GState) ListAllUsers(c *fiber.Ctx) error {
|
||||||
|
// Get all users from the database
|
||||||
|
users, err := gs.Db.GetAllUsersApplication()
|
||||||
|
if err != nil {
|
||||||
|
log.Info("Error getting users from db:", 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)
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
// @Description promote chosen user to admin
|
||||||
|
// @Tags User
|
||||||
|
// @Accept json
|
||||||
|
// @Produce plain
|
||||||
|
// @Param NewUser body types.NewUser true "user info"
|
||||||
|
// @Success 200 {json} json "Successfully promoted user"
|
||||||
|
// @Failure 400 {string} string "Bad request"
|
||||||
|
// @Failure 401 {string} string "Unauthorized"
|
||||||
|
// @Failure 500 {string} string "Internal server error"
|
||||||
|
// @Router /promoteToAdmin [post]
|
||||||
|
func (gs *GState) PromoteToAdmin(c *fiber.Ctx) error {
|
||||||
|
// Extract the username from the request body
|
||||||
|
var newUser types.NewUser
|
||||||
|
if err := c.BodyParser(&newUser); err != nil {
|
||||||
|
return c.Status(400).SendString("Bad request")
|
||||||
|
}
|
||||||
|
username := newUser.Username
|
||||||
|
|
||||||
|
log.Info("Promoting user to admin:", username) // Debug print
|
||||||
|
|
||||||
|
// Promote the user to a site admin in the database
|
||||||
|
if err := gs.Db.PromoteToAdmin(username); err != nil {
|
||||||
|
log.Info("Error promoting user to admin:", err) // Debug print
|
||||||
|
return c.Status(500).SendString(err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Info("User promoted to admin successfully:", username) // Debug print
|
||||||
|
|
||||||
|
// Return a success message
|
||||||
|
return c.SendStatus(fiber.StatusOK)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ChangeUserName changes a user's username 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)
|
||||||
|
adminUsername := claims["name"].(string)
|
||||||
|
log.Info(adminUsername)
|
||||||
|
|
||||||
|
// Extract the necessary parameters from the request
|
||||||
|
data := new(types.StrNameChange)
|
||||||
|
if err := c.BodyParser(data); err != nil {
|
||||||
|
log.Info("Error parsing username")
|
||||||
|
return c.Status(400).SendString(err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if the current user is an admin
|
||||||
|
isAdmin, err := gs.Db.IsSiteAdmin(adminUsername)
|
||||||
|
if err != nil {
|
||||||
|
log.Warn("Error checking if admin:", err)
|
||||||
|
return c.Status(500).SendString(err.Error())
|
||||||
|
} else if !isAdmin {
|
||||||
|
log.Warn("Tried changing name when not admin")
|
||||||
|
return c.Status(401).SendString("You cannot change name unless you are an admin")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Change the user's name in the database
|
||||||
|
if err := gs.Db.ChangeUserName(data.PrevName, data.NewName); err != nil {
|
||||||
|
return c.Status(500).SendString(err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return a success message
|
||||||
|
return c.SendStatus(fiber.StatusOK)
|
||||||
|
}
|
|
@ -1,51 +0,0 @@
|
||||||
package projects
|
|
||||||
|
|
||||||
import (
|
|
||||||
db "ttime/internal/database"
|
|
||||||
|
|
||||||
"github.com/gofiber/fiber/v2"
|
|
||||||
"github.com/gofiber/fiber/v2/log"
|
|
||||||
"github.com/golang-jwt/jwt/v5"
|
|
||||||
)
|
|
||||||
|
|
||||||
// AddUserToProjectHandler is a handler that adds a user to a project with a specified role
|
|
||||||
func AddUserToProjectHandler(c *fiber.Ctx) error {
|
|
||||||
// Extract necessary parameters from the request
|
|
||||||
var requestData struct {
|
|
||||||
Username string `json:"username"`
|
|
||||||
ProjectName string `json:"projectName"`
|
|
||||||
Role string `json:"role"`
|
|
||||||
}
|
|
||||||
if err := c.BodyParser(&requestData); err != nil {
|
|
||||||
log.Info("Error parsing request body:", err)
|
|
||||||
return c.Status(400).SendString("Bad request")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if the user adding another user to the project is a site admin
|
|
||||||
user := c.Locals("user").(*jwt.Token)
|
|
||||||
claims := user.Claims.(jwt.MapClaims)
|
|
||||||
adminUsername := claims["name"].(string)
|
|
||||||
log.Info("Admin username from claims:", adminUsername)
|
|
||||||
|
|
||||||
isAdmin, err := db.GetDb(c).IsSiteAdmin(adminUsername)
|
|
||||||
if err != nil {
|
|
||||||
log.Info("Error checking admin status:", err)
|
|
||||||
return c.Status(500).SendString(err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
if !isAdmin {
|
|
||||||
log.Info("User is not a site admin:", adminUsername)
|
|
||||||
return c.Status(403).SendString("User is not a site admin")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add the user to the project with the specified role
|
|
||||||
err = db.GetDb(c).AddUserToProject(requestData.Username, requestData.ProjectName, requestData.Role)
|
|
||||||
if err != nil {
|
|
||||||
log.Info("Error adding user to project:", err)
|
|
||||||
return c.Status(500).SendString(err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
// Return success message
|
|
||||||
log.Info("User added to project successfully:", requestData.Username)
|
|
||||||
return c.SendStatus(fiber.StatusOK)
|
|
||||||
}
|
|
|
@ -1,30 +0,0 @@
|
||||||
package projects
|
|
||||||
|
|
||||||
import (
|
|
||||||
db "ttime/internal/database"
|
|
||||||
"ttime/internal/types"
|
|
||||||
|
|
||||||
"github.com/gofiber/fiber/v2"
|
|
||||||
"github.com/golang-jwt/jwt/v5"
|
|
||||||
)
|
|
||||||
|
|
||||||
// CreateProject is a simple handler that creates a new project
|
|
||||||
func CreateProject(c *fiber.Ctx) error {
|
|
||||||
user := c.Locals("user").(*jwt.Token)
|
|
||||||
|
|
||||||
p := new(types.NewProject)
|
|
||||||
if err := c.BodyParser(p); err != nil {
|
|
||||||
return c.Status(400).SendString(err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get the username from the token and set it as the owner of the project
|
|
||||||
// This is ugly but
|
|
||||||
claims := user.Claims.(jwt.MapClaims)
|
|
||||||
owner := claims["name"].(string)
|
|
||||||
|
|
||||||
if err := db.GetDb(c).AddProject(p.Name, p.Description, owner); err != nil {
|
|
||||||
return c.Status(500).SendString(err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
return c.Status(200).SendString("Project added")
|
|
||||||
}
|
|
|
@ -1,19 +0,0 @@
|
||||||
package projects
|
|
||||||
|
|
||||||
import (
|
|
||||||
db "ttime/internal/database"
|
|
||||||
|
|
||||||
"github.com/gofiber/fiber/v2"
|
|
||||||
)
|
|
||||||
|
|
||||||
func DeleteProject(c *fiber.Ctx) error {
|
|
||||||
|
|
||||||
projectID := c.Params("projectID")
|
|
||||||
username := c.Params("username")
|
|
||||||
|
|
||||||
if err := db.GetDb(c).DeleteProject(projectID, username); err != nil {
|
|
||||||
return c.Status(500).SendString((err.Error()))
|
|
||||||
}
|
|
||||||
|
|
||||||
return c.Status(200).SendString("Project deleted")
|
|
||||||
}
|
|
|
@ -1,38 +0,0 @@
|
||||||
package projects
|
|
||||||
|
|
||||||
import (
|
|
||||||
"strconv"
|
|
||||||
db "ttime/internal/database"
|
|
||||||
|
|
||||||
"github.com/gofiber/fiber/v2"
|
|
||||||
"github.com/gofiber/fiber/v2/log"
|
|
||||||
)
|
|
||||||
|
|
||||||
// GetProject retrieves a specific project by its ID
|
|
||||||
func GetProject(c *fiber.Ctx) error {
|
|
||||||
// Extract the project ID from the request parameters or body
|
|
||||||
projectID := c.Params("projectID")
|
|
||||||
if projectID == "" {
|
|
||||||
log.Info("No project ID provided")
|
|
||||||
return c.Status(400).SendString("No project ID provided")
|
|
||||||
}
|
|
||||||
log.Info("Getting project with ID: ", projectID)
|
|
||||||
|
|
||||||
// Parse the project ID into an integer
|
|
||||||
projectIDInt, err := strconv.Atoi(projectID)
|
|
||||||
if err != nil {
|
|
||||||
log.Info("Invalid project ID")
|
|
||||||
return c.Status(400).SendString("Invalid project ID")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get the project from the database by its ID
|
|
||||||
project, err := db.GetDb(c).GetProject(projectIDInt)
|
|
||||||
if err != nil {
|
|
||||||
log.Info("Error getting project:", err)
|
|
||||||
return c.Status(500).SendString(err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
// Return the project as JSON
|
|
||||||
log.Info("Returning project: ", project.Name)
|
|
||||||
return c.JSON(project)
|
|
||||||
}
|
|
|
@ -1,63 +0,0 @@
|
||||||
package projects
|
|
||||||
|
|
||||||
import (
|
|
||||||
db "ttime/internal/database"
|
|
||||||
|
|
||||||
"github.com/gofiber/fiber/v2"
|
|
||||||
"github.com/gofiber/fiber/v2/log"
|
|
||||||
"github.com/golang-jwt/jwt/v5"
|
|
||||||
)
|
|
||||||
|
|
||||||
func GetProjectTimesHandler(c *fiber.Ctx) error {
|
|
||||||
// Get the username from the token
|
|
||||||
user := c.Locals("user").(*jwt.Token)
|
|
||||||
claims := user.Claims.(jwt.MapClaims)
|
|
||||||
username := claims["name"].(string)
|
|
||||||
|
|
||||||
// Get project
|
|
||||||
projectName := c.Params("projectName")
|
|
||||||
if projectName == "" {
|
|
||||||
log.Info("No project name provided")
|
|
||||||
return c.Status(400).SendString("No project name provided")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get all users in the project and roles
|
|
||||||
userProjects, err := db.GetDb(c).GetAllUsersProject(projectName)
|
|
||||||
if err != nil {
|
|
||||||
log.Info("Error getting users in project:", err)
|
|
||||||
return c.Status(500).SendString(err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
// If the user is member
|
|
||||||
isMember := false
|
|
||||||
for _, userProject := range userProjects {
|
|
||||||
if userProject.Username == username {
|
|
||||||
isMember = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// If the user is admin
|
|
||||||
if !isMember {
|
|
||||||
isAdmin, err := db.GetDb(c).IsSiteAdmin(username)
|
|
||||||
if err != nil {
|
|
||||||
log.Info("Error checking admin status:", err)
|
|
||||||
return c.Status(500).SendString(err.Error())
|
|
||||||
}
|
|
||||||
if !isAdmin {
|
|
||||||
log.Info("User is neither a project member nor a site admin:", username)
|
|
||||||
return c.Status(403).SendString("User is neither a project member nor a site admin")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get project times
|
|
||||||
projectTimes, err := db.GetDb(c).GetProjectTimes(projectName)
|
|
||||||
if err != nil {
|
|
||||||
log.Info("Error getting project times:", err)
|
|
||||||
return c.Status(500).SendString(err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
// Return project times as JSON
|
|
||||||
log.Info("Returning project times for project:", projectName)
|
|
||||||
return c.JSON(projectTimes)
|
|
||||||
}
|
|
|
@ -1,25 +0,0 @@
|
||||||
package projects
|
|
||||||
|
|
||||||
import (
|
|
||||||
db "ttime/internal/database"
|
|
||||||
|
|
||||||
"github.com/gofiber/fiber/v2"
|
|
||||||
"github.com/golang-jwt/jwt/v5"
|
|
||||||
)
|
|
||||||
|
|
||||||
// GetUserProjects returns all projects that the user is a member of
|
|
||||||
func GetUserProjects(c *fiber.Ctx) error {
|
|
||||||
// First we get the username from the token
|
|
||||||
user := c.Locals("user").(*jwt.Token)
|
|
||||||
claims := user.Claims.(jwt.MapClaims)
|
|
||||||
username := claims["name"].(string)
|
|
||||||
|
|
||||||
// Then dip into the database to get the projects
|
|
||||||
projects, err := db.GetDb(c).GetProjectsForUser(username)
|
|
||||||
if err != nil {
|
|
||||||
return c.Status(500).SendString(err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
// Return a json serialized list of projects
|
|
||||||
return c.JSON(projects)
|
|
||||||
}
|
|
|
@ -1,32 +0,0 @@
|
||||||
package projects
|
|
||||||
|
|
||||||
import (
|
|
||||||
db "ttime/internal/database"
|
|
||||||
|
|
||||||
"github.com/gofiber/fiber/v2"
|
|
||||||
"github.com/gofiber/fiber/v2/log"
|
|
||||||
"github.com/golang-jwt/jwt/v5"
|
|
||||||
)
|
|
||||||
|
|
||||||
// IsProjectManagerHandler is a handler that checks if a user is a project manager for a given project
|
|
||||||
func IsProjectManagerHandler(c *fiber.Ctx) error {
|
|
||||||
// Get the username from the token
|
|
||||||
user := c.Locals("user").(*jwt.Token)
|
|
||||||
claims := user.Claims.(jwt.MapClaims)
|
|
||||||
username := claims["name"].(string)
|
|
||||||
|
|
||||||
// Extract necessary parameters from the request query string
|
|
||||||
projectName := c.Params("projectName")
|
|
||||||
|
|
||||||
log.Info("Checking if user ", username, " is a project manager for project ", projectName)
|
|
||||||
|
|
||||||
// Check if the user is a project manager for the specified project
|
|
||||||
isManager, err := db.GetDb(c).IsProjectManager(username, projectName)
|
|
||||||
if err != nil {
|
|
||||||
log.Info("Error checking project manager status:", err)
|
|
||||||
return c.Status(500).SendString(err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
// Return the result as JSON
|
|
||||||
return c.JSON(fiber.Map{"isProjectManager": isManager})
|
|
||||||
}
|
|
|
@ -1,55 +0,0 @@
|
||||||
package projects
|
|
||||||
|
|
||||||
import (
|
|
||||||
db "ttime/internal/database"
|
|
||||||
|
|
||||||
"github.com/gofiber/fiber/v2"
|
|
||||||
"github.com/gofiber/fiber/v2/log"
|
|
||||||
"github.com/golang-jwt/jwt/v5"
|
|
||||||
)
|
|
||||||
|
|
||||||
func ListAllUsersProject(c *fiber.Ctx) error {
|
|
||||||
// Extract the project name from the request parameters or body
|
|
||||||
projectName := c.Params("projectName")
|
|
||||||
if projectName == "" {
|
|
||||||
log.Info("No project name provided")
|
|
||||||
return c.Status(400).SendString("No project name provided")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get the user token
|
|
||||||
userToken := c.Locals("user").(*jwt.Token)
|
|
||||||
claims := userToken.Claims.(jwt.MapClaims)
|
|
||||||
username := claims["name"].(string)
|
|
||||||
|
|
||||||
// Check if the user is a project manager for the specified project
|
|
||||||
isManager, err := db.GetDb(c).IsProjectManager(username, projectName)
|
|
||||||
if err != nil {
|
|
||||||
log.Info("Error checking project manager status:", err)
|
|
||||||
return c.Status(500).SendString(err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
// If the user is not a project manager, check if the user is a site admin
|
|
||||||
if !isManager {
|
|
||||||
isAdmin, err := db.GetDb(c).IsSiteAdmin(username)
|
|
||||||
if err != nil {
|
|
||||||
log.Info("Error checking admin status:", err)
|
|
||||||
return c.Status(500).SendString(err.Error())
|
|
||||||
}
|
|
||||||
if !isAdmin {
|
|
||||||
log.Info("User is neither a project manager nor a site admin:", username)
|
|
||||||
return c.Status(403).SendString("User is neither a project manager nor a site admin")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get all users associated with the project from the database
|
|
||||||
users, err := db.GetDb(c).GetAllUsersProject(projectName)
|
|
||||||
if err != nil {
|
|
||||||
log.Info("Error getting users for project:", err)
|
|
||||||
return c.Status(500).SendString(err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Info("Returning users for project: ", projectName)
|
|
||||||
|
|
||||||
// Return the list of users as JSON
|
|
||||||
return c.JSON(users)
|
|
||||||
}
|
|
|
@ -1,45 +0,0 @@
|
||||||
package projects
|
|
||||||
|
|
||||||
import (
|
|
||||||
db "ttime/internal/database"
|
|
||||||
"ttime/internal/types"
|
|
||||||
|
|
||||||
"github.com/gofiber/fiber/v2"
|
|
||||||
"github.com/gofiber/fiber/v2/log"
|
|
||||||
"github.com/golang-jwt/jwt/v5"
|
|
||||||
)
|
|
||||||
|
|
||||||
// ProjectRoleChange is a handler that changes a user's role within a project
|
|
||||||
func ProjectRoleChange(c *fiber.Ctx) error {
|
|
||||||
|
|
||||||
//check token and get username of current user
|
|
||||||
user := c.Locals("user").(*jwt.Token)
|
|
||||||
claims := user.Claims.(jwt.MapClaims)
|
|
||||||
username := claims["name"].(string)
|
|
||||||
|
|
||||||
// Extract the necessary parameters from the request
|
|
||||||
data := new(types.RoleChange)
|
|
||||||
if err := c.BodyParser(data); err != nil {
|
|
||||||
log.Info("error parsing username, project or role")
|
|
||||||
return c.Status(400).SendString(err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Info("Changing role for user: ", username, " in project: ", data.Projectname, " to: ", data.Role)
|
|
||||||
|
|
||||||
// Dubble diping and checcking if current user is
|
|
||||||
if ismanager, err := db.GetDb(c).IsProjectManager(username, data.Projectname); err != nil {
|
|
||||||
log.Warn("Error checking if projectmanager:", err)
|
|
||||||
return c.Status(500).SendString(err.Error())
|
|
||||||
} else if !ismanager {
|
|
||||||
log.Warn("User is not projectmanager")
|
|
||||||
return c.Status(401).SendString("User is not projectmanager")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Change the user's role within the project in the database
|
|
||||||
if err := db.GetDb(c).ChangeUserRole(username, data.Projectname, data.Role); err != nil {
|
|
||||||
return c.Status(500).SendString(err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
// Return a success message
|
|
||||||
return c.SendStatus(fiber.StatusOK)
|
|
||||||
}
|
|
|
@ -1,35 +0,0 @@
|
||||||
package projects
|
|
||||||
|
|
||||||
import (
|
|
||||||
db "ttime/internal/database"
|
|
||||||
|
|
||||||
"github.com/gofiber/fiber/v2"
|
|
||||||
"github.com/gofiber/fiber/v2/log"
|
|
||||||
"github.com/golang-jwt/jwt/v5"
|
|
||||||
)
|
|
||||||
|
|
||||||
func RemoveProject(c *fiber.Ctx) error {
|
|
||||||
user := c.Locals("user").(*jwt.Token)
|
|
||||||
claims := user.Claims.(jwt.MapClaims)
|
|
||||||
username := claims["name"].(string)
|
|
||||||
|
|
||||||
// Check if the user is a site admin
|
|
||||||
isAdmin, err := db.GetDb(c).IsSiteAdmin(username)
|
|
||||||
if err != nil {
|
|
||||||
log.Info("Error checking admin status:", err)
|
|
||||||
return c.Status(500).SendString(err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
if !isAdmin {
|
|
||||||
log.Info("User is not a site admin:", username)
|
|
||||||
return c.Status(403).SendString("User is not a site admin")
|
|
||||||
}
|
|
||||||
|
|
||||||
projectName := c.Params("projectName")
|
|
||||||
|
|
||||||
if err := db.GetDb(c).RemoveProject(projectName); err != nil {
|
|
||||||
return c.Status(500).SendString((err.Error()))
|
|
||||||
}
|
|
||||||
|
|
||||||
return c.Status(200).SendString("Project deleted")
|
|
||||||
}
|
|
|
@ -1,45 +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"
|
|
||||||
)
|
|
||||||
|
|
||||||
func GetUnsignedReports(c *fiber.Ctx) error {
|
|
||||||
// Extract the necessary parameters from the token
|
|
||||||
user := c.Locals("user").(*jwt.Token)
|
|
||||||
claims := user.Claims.(jwt.MapClaims)
|
|
||||||
projectManagerUsername := claims["name"].(string)
|
|
||||||
|
|
||||||
// Extract project name and week from query parameters
|
|
||||||
projectName := c.Params("projectName")
|
|
||||||
|
|
||||||
log.Info("Getting unsigned reports for")
|
|
||||||
|
|
||||||
if projectName == "" {
|
|
||||||
log.Info("Missing project name")
|
|
||||||
return c.Status(400).SendString("Missing project name")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get the project manager's ID
|
|
||||||
isProjectManager, err := db.GetDb(c).IsProjectManager(projectManagerUsername, projectName)
|
|
||||||
if err != nil {
|
|
||||||
log.Info("Failed to get project manager ID")
|
|
||||||
return c.Status(500).SendString("Failed to get project manager ID")
|
|
||||||
}
|
|
||||||
log.Info("User is Project Manager: ", isProjectManager)
|
|
||||||
|
|
||||||
// Call the database function to get the unsigned weekly reports
|
|
||||||
reports, err := db.GetDb(c).GetUnsignedWeeklyReports(projectName)
|
|
||||||
if err != nil {
|
|
||||||
log.Info("Error getting unsigned weekly reports:", err)
|
|
||||||
return c.Status(500).SendString(err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Info("Returning unsigned reports")
|
|
||||||
// Return the list of unsigned reports
|
|
||||||
return c.JSON(reports)
|
|
||||||
}
|
|
|
@ -1,47 +0,0 @@
|
||||||
package reports
|
|
||||||
|
|
||||||
import (
|
|
||||||
"strconv"
|
|
||||||
db "ttime/internal/database"
|
|
||||||
|
|
||||||
"github.com/gofiber/fiber/v2"
|
|
||||||
"github.com/gofiber/fiber/v2/log"
|
|
||||||
"github.com/golang-jwt/jwt/v5"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Handler for retrieving weekly report
|
|
||||||
func GetWeeklyReport(c *fiber.Ctx) error {
|
|
||||||
// Extract the necessary parameters from the request
|
|
||||||
user := c.Locals("user").(*jwt.Token)
|
|
||||||
claims := user.Claims.(jwt.MapClaims)
|
|
||||||
username := claims["name"].(string)
|
|
||||||
|
|
||||||
log.Info("Getting weekly report for: ", username)
|
|
||||||
|
|
||||||
// Extract project name and week from query parameters
|
|
||||||
projectName := c.Query("projectName")
|
|
||||||
week := c.Query("week")
|
|
||||||
|
|
||||||
if projectName == "" || week == "" {
|
|
||||||
log.Info("Missing project name or week number")
|
|
||||||
return c.Status(400).SendString("Missing project name or week number")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Convert week to integer
|
|
||||||
weekInt, err := strconv.Atoi(week)
|
|
||||||
if err != nil {
|
|
||||||
log.Info("Invalid week number")
|
|
||||||
return c.Status(400).SendString("Invalid week number")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Call the database function to get the weekly report
|
|
||||||
report, err := db.GetDb(c).GetWeeklyReport(username, projectName, weekInt)
|
|
||||||
if err != nil {
|
|
||||||
log.Info("Error getting weekly report from db:", err)
|
|
||||||
return c.Status(500).SendString(err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Info("Returning weekly report")
|
|
||||||
// Return the retrieved weekly report
|
|
||||||
return c.JSON(report)
|
|
||||||
}
|
|
|
@ -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)
|
|
||||||
}
|
|
|
@ -1,41 +0,0 @@
|
||||||
package reports
|
|
||||||
|
|
||||||
import (
|
|
||||||
"strconv"
|
|
||||||
db "ttime/internal/database"
|
|
||||||
|
|
||||||
"github.com/gofiber/fiber/v2"
|
|
||||||
"github.com/gofiber/fiber/v2/log"
|
|
||||||
"github.com/golang-jwt/jwt/v5"
|
|
||||||
)
|
|
||||||
|
|
||||||
func SignReport(c *fiber.Ctx) error {
|
|
||||||
// Extract the necessary parameters from the token
|
|
||||||
user := c.Locals("user").(*jwt.Token)
|
|
||||||
claims := user.Claims.(jwt.MapClaims)
|
|
||||||
projectManagerUsername := claims["name"].(string)
|
|
||||||
|
|
||||||
// Extract report ID from the path
|
|
||||||
reportId, err := strconv.Atoi(c.Params("reportId"))
|
|
||||||
if err != nil {
|
|
||||||
log.Info("Invalid report ID")
|
|
||||||
return c.Status(400).SendString("Invalid report ID")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get the project manager's ID
|
|
||||||
projectManagerID, err := db.GetDb(c).GetUserId(projectManagerUsername)
|
|
||||||
if err != nil {
|
|
||||||
log.Info("Failed to get project manager ID for user: ", projectManagerUsername)
|
|
||||||
return c.Status(500).SendString("Failed to get project manager ID")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Call the database function to sign the weekly report
|
|
||||||
err = db.GetDb(c).SignWeeklyReport(reportId, projectManagerID)
|
|
||||||
if err != nil {
|
|
||||||
log.Info("Error signing weekly report:", err)
|
|
||||||
return c.Status(500).SendString(err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Info("Project manager ID: ", projectManagerID, " signed report ID: ", reportId)
|
|
||||||
return c.Status(200).SendString("Weekly report signed successfully")
|
|
||||||
}
|
|
|
@ -1,41 +0,0 @@
|
||||||
package reports
|
|
||||||
|
|
||||||
import (
|
|
||||||
db "ttime/internal/database"
|
|
||||||
"ttime/internal/types"
|
|
||||||
|
|
||||||
"github.com/gofiber/fiber/v2"
|
|
||||||
"github.com/gofiber/fiber/v2/log"
|
|
||||||
"github.com/golang-jwt/jwt/v5"
|
|
||||||
)
|
|
||||||
|
|
||||||
func SubmitWeeklyReport(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)
|
|
||||||
|
|
||||||
report := new(types.NewWeeklyReport)
|
|
||||||
if err := c.BodyParser(report); err != nil {
|
|
||||||
log.Info("Error parsing weekly report")
|
|
||||||
return c.Status(400).SendString(err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
// Make sure all the fields of the report are valid
|
|
||||||
if report.Week < 1 || report.Week > 52 {
|
|
||||||
log.Info("Invalid week number")
|
|
||||||
return c.Status(400).SendString("Invalid week number")
|
|
||||||
}
|
|
||||||
if report.DevelopmentTime < 0 || report.MeetingTime < 0 || report.AdminTime < 0 || report.OwnWorkTime < 0 || report.StudyTime < 0 || report.TestingTime < 0 {
|
|
||||||
log.Info("Invalid time report")
|
|
||||||
return c.Status(400).SendString("Invalid time report")
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := db.GetDb(c).AddWeeklyReport(report.ProjectName, username, report.Week, report.DevelopmentTime, report.MeetingTime, report.AdminTime, report.OwnWorkTime, report.StudyTime, report.TestingTime); err != nil {
|
|
||||||
log.Info("Error adding weekly report to db:", err)
|
|
||||||
return c.Status(500).SendString(err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Info("Weekly report added")
|
|
||||||
return c.Status(200).SendString("Time report added")
|
|
||||||
}
|
|
|
@ -1,44 +0,0 @@
|
||||||
package reports
|
|
||||||
|
|
||||||
import (
|
|
||||||
db "ttime/internal/database"
|
|
||||||
"ttime/internal/types"
|
|
||||||
|
|
||||||
"github.com/gofiber/fiber/v2"
|
|
||||||
"github.com/gofiber/fiber/v2/log"
|
|
||||||
"github.com/golang-jwt/jwt/v5"
|
|
||||||
)
|
|
||||||
|
|
||||||
func UpdateWeeklyReport(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)
|
|
||||||
|
|
||||||
// Parse the request body into an UpdateWeeklyReport struct
|
|
||||||
var updateReport types.UpdateWeeklyReport
|
|
||||||
if err := c.BodyParser(&updateReport); err != nil {
|
|
||||||
log.Info("Error parsing weekly report")
|
|
||||||
return c.Status(400).SendString(err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
// Make sure all the fields of the report are valid
|
|
||||||
if updateReport.Week < 1 || updateReport.Week > 52 {
|
|
||||||
log.Info("Invalid week number")
|
|
||||||
return c.Status(400).SendString("Invalid week number")
|
|
||||||
}
|
|
||||||
|
|
||||||
if updateReport.DevelopmentTime < 0 || updateReport.MeetingTime < 0 || updateReport.AdminTime < 0 || updateReport.OwnWorkTime < 0 || updateReport.StudyTime < 0 || updateReport.TestingTime < 0 {
|
|
||||||
log.Info("Invalid time report")
|
|
||||||
return c.Status(400).SendString("Invalid time report")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update the weekly report in the database
|
|
||||||
if err := db.GetDb(c).UpdateWeeklyReport(updateReport.ProjectName, username, updateReport.Week, updateReport.DevelopmentTime, updateReport.MeetingTime, updateReport.AdminTime, updateReport.OwnWorkTime, updateReport.StudyTime, updateReport.TestingTime); err != nil {
|
|
||||||
log.Info("Error updating weekly report in db:", err)
|
|
||||||
return c.Status(500).SendString(err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Info("Weekly report updated")
|
|
||||||
return c.Status(200).SendString("Weekly report updated")
|
|
||||||
}
|
|
|
@ -1,44 +0,0 @@
|
||||||
package users
|
|
||||||
|
|
||||||
import (
|
|
||||||
db "ttime/internal/database"
|
|
||||||
"ttime/internal/types"
|
|
||||||
|
|
||||||
"github.com/gofiber/fiber/v2"
|
|
||||||
"github.com/gofiber/fiber/v2/log"
|
|
||||||
"github.com/golang-jwt/jwt/v5"
|
|
||||||
)
|
|
||||||
|
|
||||||
// ChangeUserName changes a user's username in the database
|
|
||||||
func ChangeUserName(c *fiber.Ctx) error {
|
|
||||||
// Check token and get username of current user
|
|
||||||
user := c.Locals("user").(*jwt.Token)
|
|
||||||
claims := user.Claims.(jwt.MapClaims)
|
|
||||||
adminUsername := claims["name"].(string)
|
|
||||||
log.Info(adminUsername)
|
|
||||||
|
|
||||||
// Extract the necessary parameters from the request
|
|
||||||
data := new(types.StrNameChange)
|
|
||||||
if err := c.BodyParser(data); err != nil {
|
|
||||||
log.Info("Error parsing username")
|
|
||||||
return c.Status(400).SendString(err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if the current user is an admin
|
|
||||||
isAdmin, err := db.GetDb(c).IsSiteAdmin(adminUsername)
|
|
||||||
if err != nil {
|
|
||||||
log.Warn("Error checking if admin:", err)
|
|
||||||
return c.Status(500).SendString(err.Error())
|
|
||||||
} else if !isAdmin {
|
|
||||||
log.Warn("Tried changing name when not admin")
|
|
||||||
return c.Status(401).SendString("You cannot change name unless you are an admin")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Change the user's name in the database
|
|
||||||
if err := db.GetDb(c).ChangeUserName(data.PrevName, data.NewName); err != nil {
|
|
||||||
return c.Status(500).SendString(err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
// Return a success message
|
|
||||||
return c.SendStatus(fiber.StatusOK)
|
|
||||||
}
|
|
|
@ -1,22 +0,0 @@
|
||||||
package users
|
|
||||||
|
|
||||||
import (
|
|
||||||
db "ttime/internal/database"
|
|
||||||
|
|
||||||
"github.com/gofiber/fiber/v2"
|
|
||||||
"github.com/gofiber/fiber/v2/log"
|
|
||||||
)
|
|
||||||
|
|
||||||
func GetAllUsersProject(c *fiber.Ctx) error {
|
|
||||||
// Get all users from a project
|
|
||||||
projectName := c.Params("projectName")
|
|
||||||
users, err := db.GetDb(c).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)
|
|
||||||
}
|
|
|
@ -1,31 +0,0 @@
|
||||||
package users
|
|
||||||
|
|
||||||
import (
|
|
||||||
db "ttime/internal/database"
|
|
||||||
|
|
||||||
"github.com/gofiber/fiber/v2"
|
|
||||||
"github.com/gofiber/fiber/v2/log"
|
|
||||||
)
|
|
||||||
|
|
||||||
// ListAllUsers is a handler that returns a list of all users in the application database
|
|
||||||
// @Summary ListsAllUsers
|
|
||||||
// @Description lists all users
|
|
||||||
// @Tags User
|
|
||||||
// @Accept json
|
|
||||||
// @Produce plain
|
|
||||||
// @Success 200 {json} json "Successfully signed token for user"
|
|
||||||
// @Failure 401 {string} string "Unauthorized"
|
|
||||||
// @Failure 500 {string} string "Internal server error"
|
|
||||||
// @Router /users/all [get]
|
|
||||||
func ListAllUsers(c *fiber.Ctx) error {
|
|
||||||
// Get all users from the database
|
|
||||||
users, err := db.GetDb(c).GetAllUsersApplication()
|
|
||||||
if err != nil {
|
|
||||||
log.Info("Error getting users from db:", 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)
|
|
||||||
}
|
|
|
@ -1,65 +0,0 @@
|
||||||
package users
|
|
||||||
|
|
||||||
import (
|
|
||||||
"time"
|
|
||||||
db "ttime/internal/database"
|
|
||||||
"ttime/internal/types"
|
|
||||||
|
|
||||||
"github.com/gofiber/fiber/v2"
|
|
||||||
"github.com/gofiber/fiber/v2/log"
|
|
||||||
"github.com/golang-jwt/jwt/v5"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Login is a simple login handler that returns a JWT token
|
|
||||||
// @Summary login
|
|
||||||
// @Description logs the user in and returns a jwt token
|
|
||||||
// @Tags User
|
|
||||||
// @Accept json
|
|
||||||
// @Param NewUser body types.NewUser true "login info"
|
|
||||||
// @Produce plain
|
|
||||||
// @Success 200 Token types.Token "Successfully signed token for user"
|
|
||||||
// @Failure 400 {string} string "Bad request"
|
|
||||||
// @Failure 401 {string} string "Unauthorized"
|
|
||||||
// @Failure 500 {string} string "Internal server error"
|
|
||||||
// @Router /login [post]
|
|
||||||
func Login(c *fiber.Ctx) error {
|
|
||||||
// The body type is identical to a NewUser
|
|
||||||
|
|
||||||
u := new(types.NewUser)
|
|
||||||
if err := c.BodyParser(u); err != nil {
|
|
||||||
log.Warn("Error parsing body")
|
|
||||||
return c.Status(400).SendString(err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Info("Username logging in:", u.Username)
|
|
||||||
if !db.GetDb(c).CheckUser(u.Username, u.Password) {
|
|
||||||
log.Info("User not found")
|
|
||||||
return c.SendStatus(fiber.StatusUnauthorized)
|
|
||||||
}
|
|
||||||
|
|
||||||
isAdmin, err := db.GetDb(c).IsSiteAdmin(u.Username)
|
|
||||||
if err != nil {
|
|
||||||
log.Info("Error checking admin status:", err)
|
|
||||||
return c.Status(500).SendString(err.Error())
|
|
||||||
}
|
|
||||||
// Create the Claims
|
|
||||||
claims := jwt.MapClaims{
|
|
||||||
"name": u.Username,
|
|
||||||
"admin": isAdmin,
|
|
||||||
"exp": time.Now().Add(time.Hour * 72).Unix(),
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create token
|
|
||||||
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
|
|
||||||
log.Info("Token created for user:", u.Username)
|
|
||||||
|
|
||||||
// Generate encoded token and send it as response.
|
|
||||||
t, err := token.SignedString([]byte("secret"))
|
|
||||||
if err != nil {
|
|
||||||
log.Warn("Error signing token")
|
|
||||||
return c.SendStatus(fiber.StatusInternalServerError)
|
|
||||||
}
|
|
||||||
|
|
||||||
println("Successfully signed token for user:", u.Username)
|
|
||||||
return c.JSON(types.Token{Token: t})
|
|
||||||
}
|
|
|
@ -1,44 +0,0 @@
|
||||||
package users
|
|
||||||
|
|
||||||
import (
|
|
||||||
"time"
|
|
||||||
"ttime/internal/types"
|
|
||||||
|
|
||||||
"github.com/gofiber/fiber/v2"
|
|
||||||
"github.com/gofiber/fiber/v2/log"
|
|
||||||
"github.com/golang-jwt/jwt/v5"
|
|
||||||
)
|
|
||||||
|
|
||||||
// LoginRenew is a simple handler that renews the token
|
|
||||||
// @Summary LoginRenews
|
|
||||||
// @Description renews the users token
|
|
||||||
// @Security bererToken
|
|
||||||
// @Tags User
|
|
||||||
// @Accept json
|
|
||||||
// @Produce plain
|
|
||||||
// @Success 200 Token types.Token "Successfully signed token for user"
|
|
||||||
// @Failure 401 {string} string "Unauthorized"
|
|
||||||
// @Failure 500 {string} string "Internal server error"
|
|
||||||
// @Router /loginerenew [post]
|
|
||||||
func LoginRenew(c *fiber.Ctx) error {
|
|
||||||
user := c.Locals("user").(*jwt.Token)
|
|
||||||
|
|
||||||
log.Info("Renewing token for user:", user.Claims.(jwt.MapClaims)["name"])
|
|
||||||
|
|
||||||
claims := user.Claims.(jwt.MapClaims)
|
|
||||||
claims["exp"] = time.Now().Add(time.Hour * 72).Unix()
|
|
||||||
renewed := jwt.MapClaims{
|
|
||||||
"name": claims["name"],
|
|
||||||
"admin": claims["admin"],
|
|
||||||
"exp": claims["exp"],
|
|
||||||
}
|
|
||||||
token := jwt.NewWithClaims(jwt.SigningMethodHS256, renewed)
|
|
||||||
t, err := token.SignedString([]byte("secret"))
|
|
||||||
if err != nil {
|
|
||||||
log.Warn("Error signing token")
|
|
||||||
return c.SendStatus(fiber.StatusInternalServerError)
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Info("Successfully renewed token for user:", user.Claims.(jwt.MapClaims)["name"])
|
|
||||||
return c.JSON(types.Token{Token: t})
|
|
||||||
}
|
|
|
@ -1,42 +0,0 @@
|
||||||
package users
|
|
||||||
|
|
||||||
import (
|
|
||||||
db "ttime/internal/database"
|
|
||||||
"ttime/internal/types"
|
|
||||||
|
|
||||||
"github.com/gofiber/fiber/v2"
|
|
||||||
"github.com/gofiber/fiber/v2/log"
|
|
||||||
)
|
|
||||||
|
|
||||||
// @Summary PromoteToAdmin
|
|
||||||
// @Description promote chosen user to admin
|
|
||||||
// @Tags User
|
|
||||||
// @Accept json
|
|
||||||
// @Produce plain
|
|
||||||
// @Param NewUser body types.NewUser true "user info"
|
|
||||||
// @Success 200 {json} json "Successfully promoted user"
|
|
||||||
// @Failure 400 {string} string "Bad request"
|
|
||||||
// @Failure 401 {string} string "Unauthorized"
|
|
||||||
// @Failure 500 {string} string "Internal server error"
|
|
||||||
// @Router /promoteToAdmin [post]
|
|
||||||
func PromoteToAdmin(c *fiber.Ctx) error {
|
|
||||||
// Extract the username from the request body
|
|
||||||
var newUser types.NewUser
|
|
||||||
if err := c.BodyParser(&newUser); err != nil {
|
|
||||||
return c.Status(400).SendString("Bad request")
|
|
||||||
}
|
|
||||||
username := newUser.Username
|
|
||||||
|
|
||||||
log.Info("Promoting user to admin:", username) // Debug print
|
|
||||||
|
|
||||||
// Promote the user to a site admin in the database
|
|
||||||
if err := db.GetDb(c).PromoteToAdmin(username); err != nil {
|
|
||||||
log.Info("Error promoting user to admin:", err) // Debug print
|
|
||||||
return c.Status(500).SendString(err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Info("User promoted to admin successfully:", username) // Debug print
|
|
||||||
|
|
||||||
// Return a success message
|
|
||||||
return c.SendStatus(fiber.StatusOK)
|
|
||||||
}
|
|
|
@ -1,38 +0,0 @@
|
||||||
package users
|
|
||||||
|
|
||||||
import (
|
|
||||||
db "ttime/internal/database"
|
|
||||||
"ttime/internal/types"
|
|
||||||
|
|
||||||
"github.com/gofiber/fiber/v2"
|
|
||||||
"github.com/gofiber/fiber/v2/log"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Register is a simple handler that registers a new user
|
|
||||||
//
|
|
||||||
// @Summary Register
|
|
||||||
// @Description Register a new user
|
|
||||||
// @Tags User
|
|
||||||
// @Accept json
|
|
||||||
// @Produce plain
|
|
||||||
// @Param NewUser body types.NewUser true "User to register"
|
|
||||||
// @Success 200 {string} string "User added"
|
|
||||||
// @Failure 400 {string} string "Bad request"
|
|
||||||
// @Failure 500 {string} string "Internal server error"
|
|
||||||
// @Router /register [post]
|
|
||||||
func Register(c *fiber.Ctx) error {
|
|
||||||
u := new(types.NewUser)
|
|
||||||
if err := c.BodyParser(u); err != nil {
|
|
||||||
log.Warn("Error parsing body")
|
|
||||||
return c.Status(400).SendString(err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Info("Adding user:", u.Username)
|
|
||||||
if err := db.GetDb(c).AddUser(u.Username, u.Password); err != nil {
|
|
||||||
log.Warn("Error adding user:", err)
|
|
||||||
return c.Status(500).SendString(err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Info("User added:", u.Username)
|
|
||||||
return c.Status(200).SendString("User added")
|
|
||||||
}
|
|
|
@ -1,43 +0,0 @@
|
||||||
package users
|
|
||||||
|
|
||||||
import (
|
|
||||||
db "ttime/internal/database"
|
|
||||||
|
|
||||||
"github.com/gofiber/fiber/v2"
|
|
||||||
"github.com/gofiber/fiber/v2/log"
|
|
||||||
"github.com/golang-jwt/jwt/v5"
|
|
||||||
)
|
|
||||||
|
|
||||||
// This path should obviously be protected in the future
|
|
||||||
// UserDelete deletes a user from the database
|
|
||||||
//
|
|
||||||
// @Summary UserDelete
|
|
||||||
// @Description UserDelete deletes a user from the database
|
|
||||||
// @Tags User
|
|
||||||
// @Accept json
|
|
||||||
// @Produce plain
|
|
||||||
// @Success 200 {string} string "User deleted"
|
|
||||||
// @Failure 403 {string} string "You can only delete yourself"
|
|
||||||
// @Failure 500 {string} string "Internal server error"
|
|
||||||
// @Failure 401 {string} string "Unauthorized"
|
|
||||||
// @Router /userdelete/{username} [delete]
|
|
||||||
func UserDelete(c *fiber.Ctx) error {
|
|
||||||
// Read from path parameters
|
|
||||||
username := c.Params("username")
|
|
||||||
|
|
||||||
// Read username from Locals
|
|
||||||
auth_username := c.Locals("user").(*jwt.Token).Claims.(jwt.MapClaims)["name"].(string)
|
|
||||||
|
|
||||||
if username == auth_username {
|
|
||||||
log.Info("User tried to delete itself")
|
|
||||||
return c.Status(403).SendString("You can't delete yourself")
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := db.GetDb(c).RemoveUser(username); err != nil {
|
|
||||||
log.Warn("Error deleting user:", err)
|
|
||||||
return c.Status(500).SendString(err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Info("User deleted:", username)
|
|
||||||
return c.Status(200).SendString("User deleted")
|
|
||||||
}
|
|
|
@ -6,9 +6,7 @@ import (
|
||||||
_ "ttime/docs"
|
_ "ttime/docs"
|
||||||
"ttime/internal/config"
|
"ttime/internal/config"
|
||||||
"ttime/internal/database"
|
"ttime/internal/database"
|
||||||
"ttime/internal/handlers/projects"
|
"ttime/internal/handlers"
|
||||||
"ttime/internal/handlers/reports"
|
|
||||||
"ttime/internal/handlers/users"
|
|
||||||
|
|
||||||
"github.com/BurntSushi/toml"
|
"github.com/BurntSushi/toml"
|
||||||
"github.com/gofiber/fiber/v2"
|
"github.com/gofiber/fiber/v2"
|
||||||
|
@ -56,28 +54,24 @@ func main() {
|
||||||
|
|
||||||
// Connect to the database
|
// Connect to the database
|
||||||
db := database.DbConnect(conf.DbPath)
|
db := database.DbConnect(conf.DbPath)
|
||||||
|
|
||||||
// Migrate the database
|
// Migrate the database
|
||||||
if err = db.Migrate(); err != nil {
|
if err = db.Migrate(); err != nil {
|
||||||
fmt.Println("Error migrating database: ", err)
|
fmt.Println("Error migrating database: ", err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Migrate sample data, should not be used in production
|
|
||||||
if err = db.MigrateSampleData(); err != nil {
|
if err = db.MigrateSampleData(); err != nil {
|
||||||
fmt.Println("Error migrating sample data: ", err)
|
fmt.Println("Error migrating sample data: ", err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Get our global state
|
||||||
|
gs := handlers.NewGlobalState(db)
|
||||||
// Create the server
|
// Create the server
|
||||||
server := fiber.New()
|
server := fiber.New()
|
||||||
|
|
||||||
// We want some logs
|
|
||||||
server.Use(logger.New())
|
server.Use(logger.New())
|
||||||
|
|
||||||
// Sets up db middleware, accessed as Local "db" key
|
|
||||||
server.Use(database.DbMiddleware(&db))
|
|
||||||
|
|
||||||
// Mounts the swagger documentation, this is available at /swagger/index.html
|
// Mounts the swagger documentation, this is available at /swagger/index.html
|
||||||
server.Get("/swagger/*", swagger.HandlerDefault)
|
server.Get("/swagger/*", swagger.HandlerDefault)
|
||||||
|
|
||||||
|
@ -85,50 +79,35 @@ func main() {
|
||||||
// This will likely be replaced by an embedded filesystem in the future
|
// This will likely be replaced by an embedded filesystem in the future
|
||||||
server.Static("/", "./static")
|
server.Static("/", "./static")
|
||||||
|
|
||||||
// Create a group for our API
|
|
||||||
api := server.Group("/api")
|
|
||||||
|
|
||||||
// Register our unprotected routes
|
// Register our unprotected routes
|
||||||
api.Post("/register", users.Register)
|
server.Post("/api/register", gs.Register)
|
||||||
api.Post("/login", users.Login)
|
server.Post("/api/login", gs.Login)
|
||||||
|
|
||||||
// Every route from here on will require a valid
|
// Every route from here on will require a valid JWT
|
||||||
// JWT bearer token authentication in the header
|
|
||||||
server.Use(jwtware.New(jwtware.Config{
|
server.Use(jwtware.New(jwtware.Config{
|
||||||
SigningKey: jwtware.SigningKey{Key: []byte("secret")},
|
SigningKey: jwtware.SigningKey{Key: []byte("secret")},
|
||||||
}))
|
}))
|
||||||
|
|
||||||
// All user related routes
|
// Protected routes (require a valid JWT bearer token authentication header)
|
||||||
// userGroup := api.Group("/user") // Not currently in use
|
server.Post("/api/submitWeeklyReport", gs.SubmitWeeklyReport)
|
||||||
api.Get("/users/all", users.ListAllUsers)
|
server.Get("/api/getUserProjects", gs.GetUserProjects)
|
||||||
api.Get("/project/getAllUsers", users.GetAllUsersProject)
|
server.Post("/api/loginrenew", gs.LoginRenew)
|
||||||
api.Post("/login", users.Login)
|
server.Delete("/api/userdelete/:username", gs.UserDelete) // Perhaps just use POST to avoid headaches
|
||||||
api.Post("/register", users.Register)
|
server.Delete("api/project/:projectID", gs.DeleteProject) // WIP
|
||||||
api.Post("/loginrenew", users.LoginRenew)
|
server.Post("/api/project", gs.CreateProject) // WIP
|
||||||
api.Post("/promoteToAdmin", users.PromoteToAdmin)
|
server.Get("/api/project/:projectId", gs.GetProject)
|
||||||
api.Put("/changeUserName", users.ChangeUserName)
|
server.Get("/api/project/getAllUsers", gs.GetAllUsersProject)
|
||||||
api.Delete("/userdelete/:username", users.UserDelete) // Perhaps just use POST to avoid headaches
|
server.Get("/api/getWeeklyReport", gs.GetWeeklyReport)
|
||||||
|
server.Post("/api/signReport", gs.SignReport)
|
||||||
// All project related routes
|
server.Put("/api/addUserToProject", gs.AddUserToProjectHandler)
|
||||||
// projectGroup := api.Group("/project") // Not currently in use
|
server.Put("/api/changeUserName", gs.ChangeUserName)
|
||||||
api.Get("/getUserProjects", projects.GetUserProjects)
|
server.Post("/api/promoteToAdmin", gs.PromoteToAdmin)
|
||||||
api.Get("/project/:projectId", projects.GetProject)
|
server.Get("/api/users/all", gs.ListAllUsers)
|
||||||
api.Get("/checkIfProjectManager/:projectName", projects.IsProjectManagerHandler)
|
server.Get("/api/getWeeklyReportsUser/:projectName", gs.GetWeeklyReportsUserHandler)
|
||||||
api.Get("/getUsersProject/:projectName", projects.ListAllUsersProject)
|
server.Get("/api/checkIfProjectManager/:projectName", gs.IsProjectManagerHandler)
|
||||||
api.Post("/project", projects.CreateProject)
|
server.Post("/api/ProjectRoleChange", gs.ProjectRoleChange)
|
||||||
api.Post("/ProjectRoleChange", projects.ProjectRoleChange)
|
server.Get("/api/getUsersProject/:projectName", gs.ListAllUsersProject)
|
||||||
api.Delete("/removeProject/:projectName", projects.RemoveProject)
|
server.Put("/api/updateWeeklyReport", gs.UpdateWeeklyReport)
|
||||||
api.Delete("/project/:projectID", projects.DeleteProject)
|
|
||||||
|
|
||||||
// All report related routes
|
|
||||||
// reportGroup := api.Group("/report") // Not currently in use
|
|
||||||
api.Get("/getWeeklyReport", reports.GetWeeklyReport)
|
|
||||||
api.Get("/getUnsignedReports/:projectName", reports.GetUnsignedReports)
|
|
||||||
api.Get("/getWeeklyReportsUser/:projectName", reports.GetWeeklyReportsUserHandler)
|
|
||||||
api.Post("/submitWeeklyReport", reports.SubmitWeeklyReport)
|
|
||||||
api.Put("/signReport/:reportId", reports.SignReport)
|
|
||||||
api.Put("/addUserToProject", projects.AddUserToProjectHandler)
|
|
||||||
api.Put("/updateWeeklyReport", reports.UpdateWeeklyReport)
|
|
||||||
|
|
||||||
// Announce the port we are listening on and start the server
|
// Announce the port we are listening on and start the server
|
||||||
err = server.Listen(fmt.Sprintf(":%d", conf.Port))
|
err = server.Listen(fmt.Sprintf(":%d", conf.Port))
|
||||||
|
|
|
@ -6,8 +6,6 @@ import {
|
||||||
NewProject,
|
NewProject,
|
||||||
UserProjectMember,
|
UserProjectMember,
|
||||||
WeeklyReport,
|
WeeklyReport,
|
||||||
StrNameChange,
|
|
||||||
NewProjMember,
|
|
||||||
} from "../Types/goTypes";
|
} from "../Types/goTypes";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -49,6 +47,7 @@ interface API {
|
||||||
* @returns {Promise<APIResponse<boolean>>} A promise containing the API response indicating if the user is a project manager.
|
* @returns {Promise<APIResponse<boolean>>} A promise containing the API response indicating if the user is a project manager.
|
||||||
*/
|
*/
|
||||||
checkIfProjectManager(
|
checkIfProjectManager(
|
||||||
|
username: string,
|
||||||
projectName: string,
|
projectName: string,
|
||||||
token: string,
|
token: string,
|
||||||
): Promise<APIResponse<boolean>>;
|
): Promise<APIResponse<boolean>>;
|
||||||
|
@ -134,37 +133,6 @@ interface API {
|
||||||
projectName: string,
|
projectName: string,
|
||||||
token: string,
|
token: string,
|
||||||
): Promise<APIResponse<UserProjectMember[]>>;
|
): Promise<APIResponse<UserProjectMember[]>>;
|
||||||
/**
|
|
||||||
* Changes the username of a user in the database.
|
|
||||||
* @param {StrNameChange} data 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.
|
|
||||||
*/
|
|
||||||
changeUserName(
|
|
||||||
data: StrNameChange,
|
|
||||||
token: string,
|
|
||||||
): Promise<APIResponse<void>>;
|
|
||||||
addUserToProject(
|
|
||||||
user: NewProjMember,
|
|
||||||
token: string,
|
|
||||||
): Promise<APIResponse<NewProjMember>>;
|
|
||||||
|
|
||||||
removeProject(
|
|
||||||
projectName: string,
|
|
||||||
token: string,
|
|
||||||
): Promise<APIResponse<string>>;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Signs a report. Keep in mind that the user which the token belongs to must be
|
|
||||||
* the project manager of the project the report belongs to.
|
|
||||||
*
|
|
||||||
* @param {number} reportId The id of the report to sign
|
|
||||||
* @param {string} token The authentication token
|
|
||||||
*/
|
|
||||||
signReport(
|
|
||||||
reportId: number,
|
|
||||||
token: string,
|
|
||||||
): Promise<APIResponse<string>>;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** An instance of the API */
|
/** An instance of the API */
|
||||||
|
@ -202,17 +170,19 @@ export const api: API = {
|
||||||
): Promise<APIResponse<User>> {
|
): Promise<APIResponse<User>> {
|
||||||
try {
|
try {
|
||||||
const response = await fetch(`/api/userdelete/${username}`, {
|
const response = await fetch(`/api/userdelete/${username}`, {
|
||||||
method: "DELETE",
|
method: "POST",
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
Authorization: "Bearer " + token,
|
Authorization: "Bearer " + token,
|
||||||
},
|
},
|
||||||
body: JSON.stringify(username),
|
body: JSON.stringify(username),
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
return { success: false, message: "Could not remove user" };
|
return { success: false, message: "Failed to remove user" };
|
||||||
} else {
|
} else {
|
||||||
return { success: true };
|
const data = (await response.json()) as User;
|
||||||
|
return { success: true, data };
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return { success: false, message: "Failed to remove user" };
|
return { success: false, message: "Failed to remove user" };
|
||||||
|
@ -220,20 +190,19 @@ export const api: API = {
|
||||||
},
|
},
|
||||||
|
|
||||||
async checkIfProjectManager(
|
async checkIfProjectManager(
|
||||||
|
username: string,
|
||||||
projectName: string,
|
projectName: string,
|
||||||
token: string,
|
token: string,
|
||||||
): Promise<APIResponse<boolean>> {
|
): Promise<APIResponse<boolean>> {
|
||||||
try {
|
try {
|
||||||
const response = await fetch(
|
const response = await fetch("/api/checkIfProjectManager", {
|
||||||
`/api/checkIfProjectManager/${projectName}`,
|
|
||||||
{
|
|
||||||
method: "GET",
|
method: "GET",
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
Authorization: "Bearer " + token,
|
Authorization: "Bearer " + token,
|
||||||
},
|
},
|
||||||
},
|
body: JSON.stringify({ username, projectName }),
|
||||||
);
|
});
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
return {
|
return {
|
||||||
|
@ -245,7 +214,7 @@ export const api: API = {
|
||||||
return { success: true, data };
|
return { success: true, data };
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return { success: false, message: "Failed to check if project manager" };
|
return { success: false, message: "fuck" };
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -274,30 +243,6 @@ export const api: API = {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
async addUserToProject(
|
|
||||||
user: NewProjMember,
|
|
||||||
token: string,
|
|
||||||
): Promise<APIResponse<NewProjMember>> {
|
|
||||||
try {
|
|
||||||
const response = await fetch("/api/addUserToProject", {
|
|
||||||
method: "PUT",
|
|
||||||
headers: {
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
Authorization: "Bearer " + token,
|
|
||||||
},
|
|
||||||
body: JSON.stringify(user),
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!response.ok) {
|
|
||||||
return { success: false, message: "Failed to add member" };
|
|
||||||
} else {
|
|
||||||
return { success: true, message: "Added member" };
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
return { success: false, message: "Failed to add 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", {
|
||||||
|
@ -539,81 +484,4 @@ export const api: API = {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
async changeUserName(
|
|
||||||
data: StrNameChange,
|
|
||||||
token: string,
|
|
||||||
): Promise<APIResponse<void>> {
|
|
||||||
try {
|
|
||||||
const response = await fetch("/api/changeUserName", {
|
|
||||||
method: "PUT",
|
|
||||||
headers: {
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
Authorization: "Bearer " + token,
|
|
||||||
},
|
|
||||||
body: JSON.stringify(data),
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!response.ok) {
|
|
||||||
return { success: false, message: "Failed to change username" };
|
|
||||||
} else {
|
|
||||||
return { success: true };
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
return { success: false, message: "Failed to change username" };
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
async removeProject(
|
|
||||||
projectName: string,
|
|
||||||
token: string,
|
|
||||||
): Promise<APIResponse<string>> {
|
|
||||||
try {
|
|
||||||
const response = await fetch(`/api/projectdelete/${projectName}`, {
|
|
||||||
method: "DELETE",
|
|
||||||
headers: {
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
Authorization: "Bearer " + token,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!response.ok) {
|
|
||||||
return Promise.resolve({
|
|
||||||
success: false,
|
|
||||||
message: "Failed to remove project",
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
const data = await response.text();
|
|
||||||
return Promise.resolve({ success: true, message: data });
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
return Promise.resolve({
|
|
||||||
success: false,
|
|
||||||
message: "Failed to remove project",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
async signReport(
|
|
||||||
reportId: number,
|
|
||||||
token: string,
|
|
||||||
): Promise<APIResponse<string>> {
|
|
||||||
try {
|
|
||||||
const response = await fetch(`/api/signReport/${reportId}`, {
|
|
||||||
method: "PUT",
|
|
||||||
headers: {
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
Authorization: "Bearer " + token,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!response.ok) {
|
|
||||||
return { success: false, message: "Failed to sign report" };
|
|
||||||
} else {
|
|
||||||
return { success: true, message: "Report signed" };
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
return { success: false, message: "Failed to sign report" };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,39 +0,0 @@
|
||||||
import { APIResponse, api } from "../API/API";
|
|
||||||
import { NewProjMember } from "../Types/goTypes";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Tries to add a member to a project
|
|
||||||
* @param {Object} props - A NewProjMember
|
|
||||||
* @returns {boolean} True if added, false if not
|
|
||||||
*/
|
|
||||||
function AddMember(props: { memberToAdd: NewProjMember }): boolean {
|
|
||||||
let added = false;
|
|
||||||
if (
|
|
||||||
props.memberToAdd.username === "" ||
|
|
||||||
props.memberToAdd.role === "" ||
|
|
||||||
props.memberToAdd.projectname === ""
|
|
||||||
) {
|
|
||||||
alert("All fields must be filled before adding");
|
|
||||||
return added;
|
|
||||||
}
|
|
||||||
api
|
|
||||||
.addUserToProject(
|
|
||||||
props.memberToAdd,
|
|
||||||
localStorage.getItem("accessToken") ?? "",
|
|
||||||
)
|
|
||||||
.then((response: APIResponse<NewProjMember>) => {
|
|
||||||
if (response.success) {
|
|
||||||
alert("Member added");
|
|
||||||
added = true;
|
|
||||||
} else {
|
|
||||||
alert("Member not added");
|
|
||||||
console.error(response.message);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch((error) => {
|
|
||||||
console.error("An error occurred during member add:", error);
|
|
||||||
});
|
|
||||||
return added;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default AddMember;
|
|
|
@ -1,92 +0,0 @@
|
||||||
import { useState } from "react";
|
|
||||||
import { NewProjMember } from "../Types/goTypes";
|
|
||||||
import Button from "./Button";
|
|
||||||
import GetAllUsers from "./GetAllUsers";
|
|
||||||
import AddMember from "./AddMember";
|
|
||||||
import BackButton from "./BackButton";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Provides UI for adding a member to a project.
|
|
||||||
* @returns {JSX.Element} - Returns the component UI for adding a member
|
|
||||||
*/
|
|
||||||
function AddUserToProject(): JSX.Element {
|
|
||||||
const [name, setName] = useState("");
|
|
||||||
const [users, setUsers] = useState<string[]>([]);
|
|
||||||
const [role, setRole] = useState("");
|
|
||||||
GetAllUsers({ setUsersProp: setUsers });
|
|
||||||
|
|
||||||
const handleClick = (): boolean => {
|
|
||||||
const newMember: NewProjMember = {
|
|
||||||
username: name,
|
|
||||||
projectname: localStorage.getItem("projectName") ?? "",
|
|
||||||
role: role,
|
|
||||||
};
|
|
||||||
return AddMember({ memberToAdd: newMember });
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="border-4 border-black bg-white flex flex-col items-center justify-center rounded-3xl content-center pl-20 pr-20 h-[75vh] w-[50vh]">
|
|
||||||
<p className="pb-4 mb-2 text-center font-bold text-[18px]">
|
|
||||||
User chosen: [{name}]
|
|
||||||
</p>
|
|
||||||
<p className="pb-4 mb-2 text-center font-bold text-[18px]">
|
|
||||||
Role chosen: [{role}]
|
|
||||||
</p>
|
|
||||||
<p className="pb-4 mb-2 text-center font-bold text-[18px]">
|
|
||||||
Project chosen: [{localStorage.getItem("projectName") ?? ""}]
|
|
||||||
</p>
|
|
||||||
<p className="p-1">Choose role:</p>
|
|
||||||
<div className="border-2 border-black p-2 rounded-xl text-center h-[10h] w-[16vh]">
|
|
||||||
<ul className="text-center items-center font-medium space-y-2">
|
|
||||||
<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"
|
|
||||||
onClick={() => {
|
|
||||||
setRole("member");
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{"Member"}
|
|
||||||
</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"
|
|
||||||
onClick={() => {
|
|
||||||
setRole("project_manager");
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{"Project manager"}
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
<p className="p-1">Choose user:</p>
|
|
||||||
<div className="border-2 border-black p-2 rounded-xl text-center overflow-scroll h-[26vh] w-[26vh]">
|
|
||||||
<ul className="text-center font-medium space-y-2">
|
|
||||||
<div></div>
|
|
||||||
{users.map((user) => (
|
|
||||||
<li
|
|
||||||
className="items-start p-1 border-2 border-black rounded-full bg-orange-200 hover:bg-orange-600 hover:text-slate-100 hover:cursor-pointer"
|
|
||||||
key={user}
|
|
||||||
value={user}
|
|
||||||
onClick={() => {
|
|
||||||
setName(user);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<span>{user}</span>
|
|
||||||
</li>
|
|
||||||
))}
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
<div className="flex space-x-5 items-center justify-between">
|
|
||||||
<Button
|
|
||||||
text="Add"
|
|
||||||
onClick={(): void => {
|
|
||||||
handleClick();
|
|
||||||
}}
|
|
||||||
type="submit"
|
|
||||||
/>
|
|
||||||
<BackButton />
|
|
||||||
</div>
|
|
||||||
<p className="text-center text-gray-500 text-xs"></p>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default AddUserToProject;
|
|
|
@ -1,103 +0,0 @@
|
||||||
//Info: This component is used to display all the time reports for a project. It will display the week number,
|
|
||||||
//total time spent, and if the report has been signed or not. The user can click on a report to edit it.
|
|
||||||
import { useEffect, useState } from "react";
|
|
||||||
import { NewWeeklyReport } from "../Types/goTypes";
|
|
||||||
import { Link, useParams } from "react-router-dom";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Renders a component that displays all the time reports for a specific project.
|
|
||||||
* @returns {JSX.Element} representing the component.
|
|
||||||
*/
|
|
||||||
function AllTimeReportsInProject(): JSX.Element {
|
|
||||||
const { username } = useParams();
|
|
||||||
const { projectName } = useParams();
|
|
||||||
const [weeklyReports, setWeeklyReports] = useState<NewWeeklyReport[]>([]);
|
|
||||||
|
|
||||||
/* // Call getProjects when the component mounts
|
|
||||||
useEffect(() => {
|
|
||||||
const getWeeklyReports = async (): Promise<void> => {
|
|
||||||
const token = localStorage.getItem("accessToken") ?? "";
|
|
||||||
const response = await api.getWeeklyReportsForUser(
|
|
||||||
projectName ?? "",
|
|
||||||
token,
|
|
||||||
);
|
|
||||||
console.log(response);
|
|
||||||
if (response.success) {
|
|
||||||
setWeeklyReports(response.data ?? []);
|
|
||||||
} else {
|
|
||||||
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();
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<h1 className="text-[30px] font-bold">{username}'s Time Reports</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] text-[30px]">
|
|
||||||
{weeklyReports.map((newWeeklyReport, index) => (
|
|
||||||
<Link
|
|
||||||
to={`/editOthersTR/${projectName}/${username}/${newWeeklyReport.week}`}
|
|
||||||
key={index}
|
|
||||||
className="border-b-2 border-black w-full"
|
|
||||||
>
|
|
||||||
<div className="flex justify-between">
|
|
||||||
<h1>
|
|
||||||
<span className="font-bold">{"Week: "}</span>
|
|
||||||
{newWeeklyReport.week}
|
|
||||||
</h1>
|
|
||||||
<h1>
|
|
||||||
<span className="font-bold">{"Total Time: "}</span>
|
|
||||||
{newWeeklyReport.developmentTime +
|
|
||||||
newWeeklyReport.meetingTime +
|
|
||||||
newWeeklyReport.adminTime +
|
|
||||||
newWeeklyReport.ownWorkTime +
|
|
||||||
newWeeklyReport.studyTime +
|
|
||||||
newWeeklyReport.testingTime}{" "}
|
|
||||||
min
|
|
||||||
</h1>
|
|
||||||
<h1>
|
|
||||||
<span className="font-bold">{"Signed: "}</span>
|
|
||||||
NO
|
|
||||||
</h1>
|
|
||||||
</div>
|
|
||||||
</Link>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default AllTimeReportsInProject;
|
|
|
@ -1,48 +1,23 @@
|
||||||
import React, { useState } from "react";
|
import React, { useState } from "react";
|
||||||
import InputField from "./InputField";
|
import InputField from "./InputField";
|
||||||
import { api } from "../API/API";
|
|
||||||
|
|
||||||
function ChangeUsername(): JSX.Element {
|
function ChangeUsername(): JSX.Element {
|
||||||
const [newUsername, setNewUsername] = useState("");
|
const [newUsername, setNewUsername] = useState("");
|
||||||
const [errorMessage, setErrorMessage] = useState("");
|
|
||||||
|
|
||||||
const handleChange = (e: React.ChangeEvent<HTMLInputElement>): void => {
|
const handleChange = (e: React.ChangeEvent<HTMLInputElement>): void => {
|
||||||
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 change the username
|
// // Call the API function to update the username
|
||||||
const token = localStorage.getItem("accessToken");
|
// await api.updateUsername(newUsername);
|
||||||
if (!token) {
|
// // Optionally, add a success message or redirect the user
|
||||||
throw new Error("Access token not found");
|
// } catch (error) {
|
||||||
}
|
// console.error("Error updating username:", error);
|
||||||
|
// // Optionally, handle the error
|
||||||
const response = await api.changeUserName(
|
// }
|
||||||
{ prevName: "currentName", newName: newUsername },
|
// };
|
||||||
token,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (response.success) {
|
|
||||||
// Optionally, add a success message or redirect the user
|
|
||||||
console.log("Username changed successfully");
|
|
||||||
} else {
|
|
||||||
// Handle the error message
|
|
||||||
console.error("Failed to change username:", response.message);
|
|
||||||
setErrorMessage(response.message ?? "Failed to change username");
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error("Error changing username:", error);
|
|
||||||
// Optionally, handle the error
|
|
||||||
setErrorMessage("Failed to change username");
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleButtonClick = (): void => {
|
|
||||||
handleSubmit().catch((error) => {
|
|
||||||
console.error("Error in handleSubmit:", error);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
|
@ -52,8 +27,6 @@ function ChangeUsername(): JSX.Element {
|
||||||
value={newUsername}
|
value={newUsername}
|
||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
/>
|
/>
|
||||||
{errorMessage && <div>{errorMessage}</div>}
|
|
||||||
<button onClick={handleButtonClick}>Update Username</button>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,6 +11,7 @@ import { api, APIResponse } from "../API/API";
|
||||||
*/
|
*/
|
||||||
|
|
||||||
function DeleteUser(props: { usernameToDelete: string }): boolean {
|
function DeleteUser(props: { usernameToDelete: string }): boolean {
|
||||||
|
//console.log(props.usernameToDelete); FOR DEBUG
|
||||||
let removed = false;
|
let removed = false;
|
||||||
api
|
api
|
||||||
.removeUser(
|
.removeUser(
|
||||||
|
@ -19,16 +20,12 @@ function DeleteUser(props: { usernameToDelete: string }): boolean {
|
||||||
)
|
)
|
||||||
.then((response: APIResponse<User>) => {
|
.then((response: APIResponse<User>) => {
|
||||||
if (response.success) {
|
if (response.success) {
|
||||||
alert("User has been deleted!");
|
|
||||||
location.reload();
|
|
||||||
removed = true;
|
removed = true;
|
||||||
} else {
|
} else {
|
||||||
alert("User has not been deleted");
|
|
||||||
console.error(response.message);
|
console.error(response.message);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
alert("User has not been deleted");
|
|
||||||
console.error("An error occurred during creation:", error);
|
console.error("An error occurred during creation:", error);
|
||||||
});
|
});
|
||||||
return removed;
|
return removed;
|
||||||
|
|
|
@ -1,129 +0,0 @@
|
||||||
import { useState, useEffect } from "react";
|
|
||||||
import { Link, useParams } from "react-router-dom";
|
|
||||||
|
|
||||||
interface UnsignedReports {
|
|
||||||
projectName: string;
|
|
||||||
username: string;
|
|
||||||
week: number;
|
|
||||||
signed: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Renders a component that displays the projects a user is a part of and links to the projects start-page.
|
|
||||||
* @returns The JSX element representing the component.
|
|
||||||
*/
|
|
||||||
function DisplayUserProject(): JSX.Element {
|
|
||||||
const { projectName } = useParams();
|
|
||||||
const [unsignedReports, setUnsignedReports] = useState<UnsignedReports[]>([]);
|
|
||||||
//const navigate = useNavigate();
|
|
||||||
|
|
||||||
// const getUnsignedReports = async (): Promise<void> => {
|
|
||||||
// const token = localStorage.getItem("accessToken") ?? "";
|
|
||||||
// const response = await api.getUserProjects(token);
|
|
||||||
// console.log(response);
|
|
||||||
// if (response.success) {
|
|
||||||
// setUnsignedReports(response.data ?? []);
|
|
||||||
// } else {
|
|
||||||
// console.error(response.message);
|
|
||||||
// }
|
|
||||||
// };
|
|
||||||
|
|
||||||
// const handleReportClick = async (projectName: string): Promise<void> => {
|
|
||||||
// const username = localStorage.getItem("username") ?? "";
|
|
||||||
// const token = localStorage.getItem("accessToken") ?? "";
|
|
||||||
// const response = await api.checkIfProjectManager(
|
|
||||||
// username,
|
|
||||||
// projectName,
|
|
||||||
// token,
|
|
||||||
// );
|
|
||||||
// if (response.success) {
|
|
||||||
// if (response.data) {
|
|
||||||
// navigate(`/PMProjectPage/${projectName}`);
|
|
||||||
// } else {
|
|
||||||
// navigate(`/project/${projectName}`);
|
|
||||||
// }
|
|
||||||
// } else {
|
|
||||||
// // handle error
|
|
||||||
// console.error(response.message);
|
|
||||||
// }
|
|
||||||
// };
|
|
||||||
|
|
||||||
const getUnsignedReports = async (): Promise<void> => {
|
|
||||||
// Simulate a delay
|
|
||||||
await Promise.resolve();
|
|
||||||
|
|
||||||
// Use mock data
|
|
||||||
const reports: UnsignedReports[] = [
|
|
||||||
{
|
|
||||||
projectName: "projecttest",
|
|
||||||
username: "user1",
|
|
||||||
week: 2,
|
|
||||||
signed: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
projectName: "projecttest",
|
|
||||||
username: "user2",
|
|
||||||
week: 2,
|
|
||||||
signed: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
projectName: "projecttest",
|
|
||||||
username: "user3",
|
|
||||||
week: 2,
|
|
||||||
signed: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
projectName: "projecttest",
|
|
||||||
username: "user4",
|
|
||||||
week: 2,
|
|
||||||
signed: false,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
// Set the state with the mock data
|
|
||||||
setUnsignedReports(reports);
|
|
||||||
};
|
|
||||||
|
|
||||||
// Call getProjects when the component mounts
|
|
||||||
useEffect(() => {
|
|
||||||
void getUnsignedReports();
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<h1 className="font-bold text-[30px] mb-[20px]">
|
|
||||||
All Unsigned Reports In: {projectName}{" "}
|
|
||||||
</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]">
|
|
||||||
{unsignedReports.map(
|
|
||||||
(unsignedReport: UnsignedReports, index: number) => (
|
|
||||||
<h1 key={index} className="border-b-2 border-black w-full">
|
|
||||||
<div className="flex justify-between">
|
|
||||||
<div className="flex">
|
|
||||||
<h1>{unsignedReport.username}</h1>
|
|
||||||
<span className="ml-6 mr-2 font-bold">Week:</span>
|
|
||||||
<h1>{unsignedReport.week}</h1>
|
|
||||||
<span className="ml-6 mr-2 font-bold">Signed:</span>
|
|
||||||
<h1>NO</h1>
|
|
||||||
</div>
|
|
||||||
<div className="flex">
|
|
||||||
<div className="ml-auto flex space-x-4">
|
|
||||||
<Link
|
|
||||||
to={`/PMViewUnsignedReport/${projectName}/${unsignedReport.username}/${unsignedReport.week}`}
|
|
||||||
>
|
|
||||||
<h1 className="underline cursor-pointer font-bold">
|
|
||||||
View Report
|
|
||||||
</h1>
|
|
||||||
</Link>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</h1>
|
|
||||||
),
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default DisplayUserProject;
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { useState, useEffect } from "react";
|
import { useState, useEffect } from "react";
|
||||||
import { Project } from "../Types/goTypes";
|
import { Project } from "../Types/goTypes";
|
||||||
import { useNavigate } from "react-router-dom";
|
import { Link } from "react-router-dom";
|
||||||
import { api } from "../API/API";
|
import { api } from "../API/API";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -9,7 +9,6 @@ import { api } from "../API/API";
|
||||||
*/
|
*/
|
||||||
function DisplayUserProject(): JSX.Element {
|
function DisplayUserProject(): JSX.Element {
|
||||||
const [projects, setProjects] = useState<Project[]>([]);
|
const [projects, setProjects] = useState<Project[]>([]);
|
||||||
const navigate = useNavigate();
|
|
||||||
|
|
||||||
const getProjects = async (): Promise<void> => {
|
const getProjects = async (): Promise<void> => {
|
||||||
const token = localStorage.getItem("accessToken") ?? "";
|
const token = localStorage.getItem("accessToken") ?? "";
|
||||||
|
@ -22,21 +21,6 @@ function DisplayUserProject(): JSX.Element {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleProjectClick = async (projectName: string): Promise<void> => {
|
|
||||||
const token = localStorage.getItem("accessToken") ?? "";
|
|
||||||
const response = await api.checkIfProjectManager(projectName, token);
|
|
||||||
if (response.success) {
|
|
||||||
if (response.data) {
|
|
||||||
navigate(`/PMProjectPage/${projectName}`);
|
|
||||||
} else {
|
|
||||||
navigate(`/project/${projectName}`);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// handle error
|
|
||||||
console.error(response.message);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Call getProjects when the component mounts
|
// Call getProjects when the component mounts
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
void getProjects();
|
void getProjects();
|
||||||
|
@ -46,15 +30,12 @@ function DisplayUserProject(): JSX.Element {
|
||||||
<>
|
<>
|
||||||
<h1 className="font-bold text-[30px] mb-[20px]">Your Projects</h1>
|
<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]">
|
<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) => (
|
{projects.map((project, index) => (
|
||||||
<div
|
<Link to={`/project/${project.name}`} key={index}>
|
||||||
onClick={() => void handleProjectClick(project.name)}
|
|
||||||
key={project.id}
|
|
||||||
>
|
|
||||||
<h1 className="font-bold underline text-[30px] cursor-pointer">
|
<h1 className="font-bold underline text-[30px] cursor-pointer">
|
||||||
{project.name}
|
{project.name}
|
||||||
</h1>
|
</h1>
|
||||||
</div>
|
</Link>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
|
|
|
@ -18,17 +18,13 @@ export default function GetWeeklyReport(): JSX.Element {
|
||||||
const [testingTime, setTestingTime] = useState(0);
|
const [testingTime, setTestingTime] = useState(0);
|
||||||
|
|
||||||
const token = localStorage.getItem("accessToken") ?? "";
|
const token = localStorage.getItem("accessToken") ?? "";
|
||||||
const { projectName, fetchedWeek } = useParams<{
|
const { projectName } = useParams();
|
||||||
projectName: string;
|
const { fetchedWeek } = useParams();
|
||||||
fetchedWeek: string;
|
|
||||||
}>();
|
|
||||||
console.log(projectName, fetchedWeek);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const fetchWeeklyReport = async (): Promise<void> => {
|
const fetchWeeklyReport = async (): Promise<void> => {
|
||||||
const response = await api.getWeeklyReport(
|
const response = await api.getWeeklyReport(
|
||||||
projectName ?? "",
|
projectName ?? "",
|
||||||
fetchedWeek ?? "",
|
fetchedWeek?.toString() ?? "0",
|
||||||
token,
|
token,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -57,8 +53,9 @@ export default function GetWeeklyReport(): JSX.Element {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
void fetchWeeklyReport();
|
void fetchWeeklyReport();
|
||||||
}, [projectName, fetchedWeek, token]);
|
});
|
||||||
|
|
||||||
const handleNewWeeklyReport = async (): Promise<void> => {
|
const handleNewWeeklyReport = async (): Promise<void> => {
|
||||||
const newWeeklyReport: NewWeeklyReport = {
|
const newWeeklyReport: NewWeeklyReport = {
|
||||||
|
@ -79,7 +76,6 @@ export default function GetWeeklyReport(): JSX.Element {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<h1 className="font-bold text-[30px] mb-[20px]">Edit Time 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) => {
|
||||||
|
@ -94,10 +90,24 @@ export default function GetWeeklyReport(): JSX.Element {
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div className="flex flex-col items-center">
|
<div className="flex flex-col items-center">
|
||||||
<div className="flex flex-col w-1/2 border-b-2 border-black items-center justify-center">
|
<input
|
||||||
<h1 className="font-bold text-[30px]"> Week: {week}</h1>
|
className="w-fill h-[5vh] font-sans text-[3vh] pl-[1vw] rounded-full text-center pt-[1vh] pb-[1vh] border-2 border-black"
|
||||||
</div>
|
type="week"
|
||||||
|
placeholder="Week"
|
||||||
|
value={
|
||||||
|
week === 0 ? "" : `2024-W${week.toString().padStart(2, "0")}`
|
||||||
|
}
|
||||||
|
onChange={(e) => {
|
||||||
|
const weekNumber = parseInt(e.target.value.split("-W")[1]);
|
||||||
|
setWeek(weekNumber);
|
||||||
|
}}
|
||||||
|
onKeyDown={(event) => {
|
||||||
|
event.preventDefault();
|
||||||
|
}}
|
||||||
|
onPaste={(event) => {
|
||||||
|
event.preventDefault();
|
||||||
|
}}
|
||||||
|
/>
|
||||||
<table className="w-full text-center divide-y divide-x divide-white text-[30px]">
|
<table className="w-full text-center divide-y divide-x divide-white text-[30px]">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
|
@ -117,14 +127,9 @@ export default function GetWeeklyReport(): JSX.Element {
|
||||||
type="number"
|
type="number"
|
||||||
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}
|
value={developmentTime}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
if (e.target.value === "") {
|
|
||||||
setDevelopmentTime(0);
|
|
||||||
return;
|
|
||||||
} else {
|
|
||||||
setDevelopmentTime(parseInt(e.target.value));
|
setDevelopmentTime(parseInt(e.target.value));
|
||||||
}
|
|
||||||
}}
|
}}
|
||||||
onKeyDown={(event) => {
|
onKeyDown={(event) => {
|
||||||
const keyValue = event.key;
|
const keyValue = event.key;
|
||||||
|
@ -141,14 +146,9 @@ export default function GetWeeklyReport(): JSX.Element {
|
||||||
type="number"
|
type="number"
|
||||||
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}
|
value={meetingTime}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
if (e.target.value === "") {
|
|
||||||
setMeetingTime(0);
|
|
||||||
return;
|
|
||||||
} else {
|
|
||||||
setMeetingTime(parseInt(e.target.value));
|
setMeetingTime(parseInt(e.target.value));
|
||||||
}
|
|
||||||
}}
|
}}
|
||||||
onKeyDown={(event) => {
|
onKeyDown={(event) => {
|
||||||
const keyValue = event.key;
|
const keyValue = event.key;
|
||||||
|
@ -165,14 +165,9 @@ export default function GetWeeklyReport(): JSX.Element {
|
||||||
type="number"
|
type="number"
|
||||||
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}
|
value={adminTime}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
if (e.target.value === "") {
|
|
||||||
setAdminTime(0);
|
|
||||||
return;
|
|
||||||
} else {
|
|
||||||
setAdminTime(parseInt(e.target.value));
|
setAdminTime(parseInt(e.target.value));
|
||||||
}
|
|
||||||
}}
|
}}
|
||||||
onKeyDown={(event) => {
|
onKeyDown={(event) => {
|
||||||
const keyValue = event.key;
|
const keyValue = event.key;
|
||||||
|
@ -189,14 +184,9 @@ export default function GetWeeklyReport(): JSX.Element {
|
||||||
type="number"
|
type="number"
|
||||||
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}
|
value={ownWorkTime}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
if (e.target.value === "") {
|
|
||||||
setOwnWorkTime(0);
|
|
||||||
return;
|
|
||||||
} else {
|
|
||||||
setOwnWorkTime(parseInt(e.target.value));
|
setOwnWorkTime(parseInt(e.target.value));
|
||||||
}
|
|
||||||
}}
|
}}
|
||||||
onKeyDown={(event) => {
|
onKeyDown={(event) => {
|
||||||
const keyValue = event.key;
|
const keyValue = event.key;
|
||||||
|
@ -213,14 +203,9 @@ export default function GetWeeklyReport(): JSX.Element {
|
||||||
type="number"
|
type="number"
|
||||||
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}
|
value={studyTime}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
if (e.target.value === "") {
|
|
||||||
setStudyTime(0);
|
|
||||||
return;
|
|
||||||
} else {
|
|
||||||
setStudyTime(parseInt(e.target.value));
|
setStudyTime(parseInt(e.target.value));
|
||||||
}
|
|
||||||
}}
|
}}
|
||||||
onKeyDown={(event) => {
|
onKeyDown={(event) => {
|
||||||
const keyValue = event.key;
|
const keyValue = event.key;
|
||||||
|
@ -237,14 +222,9 @@ export default function GetWeeklyReport(): JSX.Element {
|
||||||
type="number"
|
type="number"
|
||||||
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}
|
value={testingTime}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
if (e.target.value === "") {
|
|
||||||
setTestingTime(0);
|
|
||||||
return;
|
|
||||||
} else {
|
|
||||||
setTestingTime(parseInt(e.target.value));
|
setTestingTime(parseInt(e.target.value));
|
||||||
}
|
|
||||||
}}
|
}}
|
||||||
onKeyDown={(event) => {
|
onKeyDown={(event) => {
|
||||||
const keyValue = event.key;
|
const keyValue = event.key;
|
||||||
|
|
|
@ -12,74 +12,55 @@ import Button from "./Button";
|
||||||
*/
|
*/
|
||||||
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>(0);
|
const [developmentTime, setDevelopmentTime] = useState<number>();
|
||||||
const [meetingTime, setMeetingTime] = useState<number>(0);
|
const [meetingTime, setMeetingTime] = useState<number>();
|
||||||
const [adminTime, setAdminTime] = useState<number>(0);
|
const [adminTime, setAdminTime] = useState<number>();
|
||||||
const [ownWorkTime, setOwnWorkTime] = useState<number>(0);
|
const [ownWorkTime, setOwnWorkTime] = useState<number>();
|
||||||
const [studyTime, setStudyTime] = useState<number>(0);
|
const [studyTime, setStudyTime] = useState<number>();
|
||||||
const [testingTime, setTestingTime] = useState<number>(0);
|
const [testingTime, setTestingTime] = useState<number>();
|
||||||
|
|
||||||
const { projectName } = useParams();
|
const { projectName } = useParams();
|
||||||
const token = localStorage.getItem("accessToken") ?? "";
|
const token = localStorage.getItem("accessToken") ?? "";
|
||||||
|
|
||||||
const handleNewWeeklyReport = async (): Promise<boolean> => {
|
const handleNewWeeklyReport = async (): Promise<void> => {
|
||||||
const newWeeklyReport: NewWeeklyReport = {
|
const newWeeklyReport: NewWeeklyReport = {
|
||||||
projectName: projectName ?? "",
|
projectName: projectName ?? "",
|
||||||
week: week,
|
week: week,
|
||||||
developmentTime: developmentTime,
|
developmentTime: developmentTime ?? 0,
|
||||||
meetingTime: meetingTime,
|
meetingTime: meetingTime ?? 0,
|
||||||
adminTime: adminTime,
|
adminTime: adminTime ?? 0,
|
||||||
ownWorkTime: ownWorkTime,
|
ownWorkTime: ownWorkTime ?? 0,
|
||||||
studyTime: studyTime,
|
studyTime: studyTime ?? 0,
|
||||||
testingTime: testingTime,
|
testingTime: testingTime ?? 0,
|
||||||
};
|
};
|
||||||
|
|
||||||
const response = await api.submitWeeklyReport(newWeeklyReport, token);
|
await api.submitWeeklyReport(newWeeklyReport, token);
|
||||||
console.log(response);
|
|
||||||
if (response.success) {
|
|
||||||
return true;
|
|
||||||
} else {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
// Check if the browser is Chrome or Edge
|
|
||||||
const isChromeOrEdge = /Chrome|Edg/.test(navigator.userAgent);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<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) => {
|
||||||
|
if (week === 0) {
|
||||||
|
alert("Please enter a week number");
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
void (async (): Promise<void> => {
|
|
||||||
if (week === 0 || week > 53 || week < 1) {
|
|
||||||
alert("Please enter a valid week number");
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
e.preventDefault();
|
||||||
const success = await handleNewWeeklyReport();
|
void handleNewWeeklyReport();
|
||||||
if (!success) {
|
|
||||||
alert(
|
|
||||||
"A Time Report for this week already exists, please go to the edit page to edit it or change week number.",
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
alert("Weekly report submitted successfully");
|
|
||||||
navigate(-1);
|
navigate(-1);
|
||||||
})();
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div className="flex flex-col items-center">
|
<div className="flex flex-col items-center">
|
||||||
{isChromeOrEdge ? (
|
|
||||||
<input
|
<input
|
||||||
className="w-fill h-[5vh] font-sans text-[3vh] pl-[1vw] rounded-full text-center pt-[1vh] pb-[1vh] border-2 border-black"
|
className="w-fill h-[5vh] font-sans text-[3vh] pl-[1vw] rounded-full text-center pt-[1vh] pb-[1vh] border-2 border-black"
|
||||||
type="week"
|
type="week"
|
||||||
placeholder="Week"
|
placeholder="Week"
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
const weekNumber = parseInt(e.target.value.split("-W")[1]);
|
setWeek(parseInt(e.target.value));
|
||||||
setWeek(weekNumber);
|
|
||||||
}}
|
}}
|
||||||
onKeyDown={(event) => {
|
onKeyDown={(event) => {
|
||||||
const keyValue = event.key;
|
const keyValue = event.key;
|
||||||
|
@ -90,25 +71,6 @@ export default function NewWeeklyReport(): JSX.Element {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
) : (
|
|
||||||
<input
|
|
||||||
className="w-fill h-[5vh] font-sans text-[3vh] pl-[1vw] rounded-full text-center pt-[1vh] pb-[1vh] border-2 border-black"
|
|
||||||
type="text"
|
|
||||||
placeholder="Week (Numbers Only)"
|
|
||||||
onChange={(e) => {
|
|
||||||
const weekNumber = parseInt(e.target.value);
|
|
||||||
setWeek(weekNumber);
|
|
||||||
}}
|
|
||||||
onKeyDown={(event) => {
|
|
||||||
const keyValue = event.key;
|
|
||||||
if (!/\d/.test(keyValue) && keyValue !== "Backspace")
|
|
||||||
event.preventDefault();
|
|
||||||
}}
|
|
||||||
onPaste={(event) => {
|
|
||||||
event.preventDefault();
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
<table className="w-full text-center divide-y divide-x divide-white text-[30px]">
|
<table className="w-full text-center divide-y divide-x divide-white text-[30px]">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
|
@ -128,14 +90,9 @@ export default function NewWeeklyReport(): JSX.Element {
|
||||||
type="number"
|
type="number"
|
||||||
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}
|
value={developmentTime}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
if (e.target.value === "") {
|
|
||||||
setDevelopmentTime(0);
|
|
||||||
return;
|
|
||||||
} else {
|
|
||||||
setDevelopmentTime(parseInt(e.target.value));
|
setDevelopmentTime(parseInt(e.target.value));
|
||||||
}
|
|
||||||
}}
|
}}
|
||||||
onKeyDown={(event) => {
|
onKeyDown={(event) => {
|
||||||
const keyValue = event.key;
|
const keyValue = event.key;
|
||||||
|
@ -152,14 +109,9 @@ export default function NewWeeklyReport(): JSX.Element {
|
||||||
type="number"
|
type="number"
|
||||||
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}
|
value={meetingTime}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
if (e.target.value === "") {
|
|
||||||
setMeetingTime(0);
|
|
||||||
return;
|
|
||||||
} else {
|
|
||||||
setMeetingTime(parseInt(e.target.value));
|
setMeetingTime(parseInt(e.target.value));
|
||||||
}
|
|
||||||
}}
|
}}
|
||||||
onKeyDown={(event) => {
|
onKeyDown={(event) => {
|
||||||
const keyValue = event.key;
|
const keyValue = event.key;
|
||||||
|
@ -176,14 +128,9 @@ export default function NewWeeklyReport(): JSX.Element {
|
||||||
type="number"
|
type="number"
|
||||||
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}
|
value={adminTime}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
if (e.target.value === "") {
|
|
||||||
setAdminTime(0);
|
|
||||||
return;
|
|
||||||
} else {
|
|
||||||
setAdminTime(parseInt(e.target.value));
|
setAdminTime(parseInt(e.target.value));
|
||||||
}
|
|
||||||
}}
|
}}
|
||||||
onKeyDown={(event) => {
|
onKeyDown={(event) => {
|
||||||
const keyValue = event.key;
|
const keyValue = event.key;
|
||||||
|
@ -200,14 +147,9 @@ export default function NewWeeklyReport(): JSX.Element {
|
||||||
type="number"
|
type="number"
|
||||||
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}
|
value={ownWorkTime}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
if (e.target.value === "") {
|
|
||||||
setOwnWorkTime(0);
|
|
||||||
return;
|
|
||||||
} else {
|
|
||||||
setOwnWorkTime(parseInt(e.target.value));
|
setOwnWorkTime(parseInt(e.target.value));
|
||||||
}
|
|
||||||
}}
|
}}
|
||||||
onKeyDown={(event) => {
|
onKeyDown={(event) => {
|
||||||
const keyValue = event.key;
|
const keyValue = event.key;
|
||||||
|
@ -224,14 +166,9 @@ export default function NewWeeklyReport(): JSX.Element {
|
||||||
type="number"
|
type="number"
|
||||||
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}
|
value={studyTime}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
if (e.target.value === "") {
|
|
||||||
setStudyTime(0);
|
|
||||||
return;
|
|
||||||
} else {
|
|
||||||
setStudyTime(parseInt(e.target.value));
|
setStudyTime(parseInt(e.target.value));
|
||||||
}
|
|
||||||
}}
|
}}
|
||||||
onKeyDown={(event) => {
|
onKeyDown={(event) => {
|
||||||
const keyValue = event.key;
|
const keyValue = event.key;
|
||||||
|
@ -248,14 +185,9 @@ export default function NewWeeklyReport(): JSX.Element {
|
||||||
type="number"
|
type="number"
|
||||||
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}
|
value={testingTime}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
if (e.target.value === "") {
|
|
||||||
setTestingTime(0);
|
|
||||||
return;
|
|
||||||
} else {
|
|
||||||
setTestingTime(parseInt(e.target.value));
|
setTestingTime(parseInt(e.target.value));
|
||||||
}
|
|
||||||
}}
|
}}
|
||||||
onKeyDown={(event) => {
|
onKeyDown={(event) => {
|
||||||
const keyValue = event.key;
|
const keyValue = event.key;
|
||||||
|
|
|
@ -1,153 +0,0 @@
|
||||||
import { useState, useEffect } from "react";
|
|
||||||
import { WeeklyReport } from "../Types/goTypes";
|
|
||||||
import { api } from "../API/API";
|
|
||||||
import { useParams } from "react-router-dom";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Renders the component for editing a weekly report.
|
|
||||||
* @returns JSX.Element
|
|
||||||
*/
|
|
||||||
|
|
||||||
//This component does not yet work as intended. It is supposed to display the weekly report of a user in a project.
|
|
||||||
export default function OtherUsersTR(): JSX.Element {
|
|
||||||
const [week, setWeek] = useState(0);
|
|
||||||
const [developmentTime, setDevelopmentTime] = useState(0);
|
|
||||||
const [meetingTime, setMeetingTime] = useState(0);
|
|
||||||
const [adminTime, setAdminTime] = useState(0);
|
|
||||||
const [ownWorkTime, setOwnWorkTime] = useState(0);
|
|
||||||
const [studyTime, setStudyTime] = useState(0);
|
|
||||||
const [testingTime, setTestingTime] = useState(0);
|
|
||||||
|
|
||||||
const token = localStorage.getItem("accessToken") ?? "";
|
|
||||||
const { projectName } = useParams();
|
|
||||||
const { username } = useParams();
|
|
||||||
const { fetchedWeek } = useParams();
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const fetchUsersWeeklyReport = async (): Promise<void> => {
|
|
||||||
const response = await api.getWeeklyReport(
|
|
||||||
projectName ?? "",
|
|
||||||
fetchedWeek?.toString() ?? "0",
|
|
||||||
token,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (response.success) {
|
|
||||||
const report: WeeklyReport = response.data ?? {
|
|
||||||
reportId: 0,
|
|
||||||
userId: 0,
|
|
||||||
projectId: 0,
|
|
||||||
week: 0,
|
|
||||||
developmentTime: 0,
|
|
||||||
meetingTime: 0,
|
|
||||||
adminTime: 0,
|
|
||||||
ownWorkTime: 0,
|
|
||||||
studyTime: 0,
|
|
||||||
testingTime: 0,
|
|
||||||
};
|
|
||||||
setWeek(report.week);
|
|
||||||
setDevelopmentTime(report.developmentTime);
|
|
||||||
setMeetingTime(report.meetingTime);
|
|
||||||
setAdminTime(report.adminTime);
|
|
||||||
setOwnWorkTime(report.ownWorkTime);
|
|
||||||
setStudyTime(report.studyTime);
|
|
||||||
setTestingTime(report.testingTime);
|
|
||||||
} else {
|
|
||||||
console.error("Failed to fetch weekly report:", response.message);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
void fetchUsersWeeklyReport();
|
|
||||||
});
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<h1 className="text-[30px] font-bold">{username}'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="flex flex-col items-center">
|
|
||||||
<div className="flex flex-col w-1/2 border-b-2 border-black items-center justify-center">
|
|
||||||
<h1 className="font-bold text-[30px]"> Week: {week}</h1>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<table className="w-full text-center divide-y divide-x divide-white text-[30px]">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th className="w-1/2 py-2 border-b-2 border-black">Activity</th>
|
|
||||||
<th className="w-1/2 py-2 border-b-2 border-black">
|
|
||||||
Total Time (min)
|
|
||||||
</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody className="divide-y divide-black">
|
|
||||||
<tr className="h-[10vh]">
|
|
||||||
<td>Development</td>
|
|
||||||
<td>
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
min="0"
|
|
||||||
className="border-2 border-black rounded-md text-center w-1/2"
|
|
||||||
value={developmentTime === 0 ? "" : developmentTime}
|
|
||||||
/>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr className="h-[10vh]">
|
|
||||||
<td>Meeting</td>
|
|
||||||
<td>
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
min="0"
|
|
||||||
className="border-2 border-black rounded-md text-center w-1/2"
|
|
||||||
value={meetingTime === 0 ? "" : meetingTime}
|
|
||||||
/>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr className="h-[10vh]">
|
|
||||||
<td>Administration</td>
|
|
||||||
<td>
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
min="0"
|
|
||||||
className="border-2 border-black rounded-md text-center w-1/2"
|
|
||||||
value={adminTime === 0 ? "" : adminTime}
|
|
||||||
/>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr className="h-[10vh]">
|
|
||||||
<td>Own Work</td>
|
|
||||||
<td>
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
min="0"
|
|
||||||
className="border-2 border-black rounded-md text-center w-1/2"
|
|
||||||
value={ownWorkTime === 0 ? "" : ownWorkTime}
|
|
||||||
/>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr className="h-[10vh]">
|
|
||||||
<td>Studies</td>
|
|
||||||
<td>
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
min="0"
|
|
||||||
className="border-2 border-black rounded-md text-center w-1/2"
|
|
||||||
value={studyTime === 0 ? "" : studyTime}
|
|
||||||
/>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr className="h-[10vh]">
|
|
||||||
<td>Testing</td>
|
|
||||||
<td>
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
min="0"
|
|
||||||
className="border-2 border-black rounded-md text-center w-1/2"
|
|
||||||
value={testingTime === 0 ? "" : testingTime}
|
|
||||||
/>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
|
@ -2,7 +2,6 @@ import { useState } from "react";
|
||||||
import Button from "./Button";
|
import Button from "./Button";
|
||||||
import { UserProjectMember } from "../Types/goTypes";
|
import { UserProjectMember } from "../Types/goTypes";
|
||||||
import GetUsersInProject from "./GetUsersInProject";
|
import GetUsersInProject from "./GetUsersInProject";
|
||||||
import { Link } from "react-router-dom";
|
|
||||||
|
|
||||||
function ProjectInfoModal(props: {
|
function ProjectInfoModal(props: {
|
||||||
isVisible: boolean;
|
isVisible: boolean;
|
||||||
|
@ -19,12 +18,9 @@ function ProjectInfoModal(props: {
|
||||||
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-[41vh] w-[40vw] flex flex-col">
|
||||||
<div className="pl-20 pr-20">
|
<div className="pl-20 pr-20">
|
||||||
<h1 className="font-bold text-[32px] mb-[20px]">
|
<h1 className="font-bold text-[32px] mb-[20px]">Project members:</h1>
|
||||||
{localStorage.getItem("projectName") ?? ""}
|
|
||||||
</h1>
|
|
||||||
<h2 className="font-bold text-[24px] mb-[20px]">Project members:</h2>
|
|
||||||
<div className="border-2 border-black p-2 rounded-lg text-center overflow-scroll h-[26vh]">
|
<div className="border-2 border-black p-2 rounded-lg text-center overflow-scroll h-[26vh]">
|
||||||
<ul className="text-left font-medium space-y-2">
|
<ul className="text-left font-medium space-y-2">
|
||||||
<div></div>
|
<div></div>
|
||||||
|
@ -54,15 +50,6 @@ function ProjectInfoModal(props: {
|
||||||
}}
|
}}
|
||||||
type="button"
|
type="button"
|
||||||
/>
|
/>
|
||||||
<Link to={"/adminProjectAddMember"}>
|
|
||||||
<Button
|
|
||||||
text={"Add Member"}
|
|
||||||
onClick={function (): void {
|
|
||||||
return;
|
|
||||||
}}
|
|
||||||
type="button"
|
|
||||||
/>
|
|
||||||
</Link>
|
|
||||||
<Button
|
<Button
|
||||||
text={"Close"}
|
text={"Close"}
|
||||||
onClick={function (): void {
|
onClick={function (): void {
|
||||||
|
|
|
@ -2,6 +2,7 @@ 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 UserInfoModal from "./UserInfoModal";
|
||||||
|
import DeleteUser from "./DeleteUser";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 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
|
||||||
|
@ -27,9 +28,8 @@ export function ProjectListAdmin(props: {
|
||||||
setUserModalVisible(true);
|
setUserModalVisible(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleClickProject = (projectname: string): void => {
|
const handleClickProject = (username: string): void => {
|
||||||
setProjectname(projectname);
|
setProjectname(username);
|
||||||
localStorage.setItem("projectName", projectname);
|
|
||||||
setProjectModalVisible(true);
|
setProjectModalVisible(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -55,9 +55,7 @@ export function ProjectListAdmin(props: {
|
||||||
manageMember={true}
|
manageMember={true}
|
||||||
onClose={handleCloseUser}
|
onClose={handleCloseUser}
|
||||||
//TODO: CHANGE TO REMOVE USER FROM PROJECT
|
//TODO: CHANGE TO REMOVE USER FROM PROJECT
|
||||||
onDelete={() => {
|
onDelete={() => DeleteUser}
|
||||||
return;
|
|
||||||
}}
|
|
||||||
isVisible={userModalVisible}
|
isVisible={userModalVisible}
|
||||||
username={username}
|
username={username}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -1,55 +1,91 @@
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { Link, useParams } from "react-router-dom";
|
import { Link, useParams } from "react-router-dom";
|
||||||
import { api } from "../API/API";
|
|
||||||
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(() => {
|
// const getProjectMembers = async (): Promise<void> => {
|
||||||
const getProjectMembers = async (): Promise<void> => {
|
// const token = localStorage.getItem("accessToken") ?? "";
|
||||||
const token = localStorage.getItem("accessToken") ?? "";
|
// const response = await api.getProjectMembers(projectName ?? "", token);
|
||||||
const response = await api.getAllUsersProject(projectName ?? "", token);
|
// console.log(response);
|
||||||
console.log(response);
|
// if (response.success) {
|
||||||
if (response.success) {
|
// setProjectMembers(response.data ?? []);
|
||||||
setProjectMembers(response.data ?? []);
|
// } else {
|
||||||
} else {
|
// console.error(response.message);
|
||||||
console.error(response.message);
|
// }
|
||||||
}
|
// };
|
||||||
};
|
|
||||||
|
|
||||||
void getProjectMembers();
|
|
||||||
}, [projectName]);
|
|
||||||
|
|
||||||
interface ProjectMember {
|
interface ProjectMember {
|
||||||
Username: string;
|
username: string;
|
||||||
UserRole: 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 (
|
return (
|
||||||
<>
|
<>
|
||||||
<h1 className="font-bold text-[30px] mb-[20px]">
|
|
||||||
All Members In: {projectName}{" "}
|
|
||||||
</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]">
|
||||||
{projectMembers.map((projectMember: ProjectMember, index: number) => (
|
{projectMembers.map((projectMember, index) => (
|
||||||
<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>{projectMember.Username}</h1>
|
<h1>{projectMember.username}</h1>
|
||||||
<span className="ml-6 mr-2 font-bold">Role:</span>
|
<span className="ml-6 mr-2 font-bold">Role:</span>
|
||||||
<h1>{projectMember.UserRole}</h1>
|
<h1>{projectMember.role}</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={`/otherUsersTimeReports/${projectName}/${projectMember.Username}`}
|
to={`/viewReports/${projectName}/${projectMember.username}`}
|
||||||
>
|
>
|
||||||
<h1 className="underline cursor-pointer font-bold">
|
<h1 className="underline cursor-pointer font-bold">
|
||||||
View Reports
|
View Reports
|
||||||
</h1>
|
</h1>
|
||||||
</Link>
|
</Link>
|
||||||
|
<Link
|
||||||
|
to={`/changeRole/${projectName}/${projectMember.username}`}
|
||||||
|
>
|
||||||
|
<h1 className="underline cursor-pointer font-bold">
|
||||||
|
Change Role
|
||||||
|
</h1>
|
||||||
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -4,6 +4,7 @@ import { api } from "../API/API";
|
||||||
import Logo from "../assets/Logo.svg";
|
import Logo from "../assets/Logo.svg";
|
||||||
import Button from "./Button";
|
import Button from "./Button";
|
||||||
import InputField from "./InputField";
|
import InputField from "./InputField";
|
||||||
|
import { useNavigate } from "react-router-dom";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Renders a registration form for the admin to add new users in.
|
* Renders a registration form for the admin to add new users in.
|
||||||
|
@ -14,6 +15,8 @@ export default function Register(): JSX.Element {
|
||||||
const [password, setPassword] = useState<string>();
|
const [password, setPassword] = useState<string>();
|
||||||
const [errMessage, setErrMessage] = useState<string>();
|
const [errMessage, setErrMessage] = useState<string>();
|
||||||
|
|
||||||
|
const nav = useNavigate();
|
||||||
|
|
||||||
const handleRegister = async (): Promise<void> => {
|
const handleRegister = async (): Promise<void> => {
|
||||||
const newUser: NewUser = {
|
const newUser: NewUser = {
|
||||||
username: username ?? "",
|
username: username ?? "",
|
||||||
|
@ -21,9 +24,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!");
|
nav("/"); // Instantly navigate to the login page
|
||||||
} else {
|
} else {
|
||||||
alert("User not added");
|
|
||||||
setErrMessage(response.message ?? "Unknown error");
|
setErrMessage(response.message ?? "Unknown error");
|
||||||
console.error(errMessage);
|
console.error(errMessage);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,175 +0,0 @@
|
||||||
import { useState, useEffect } from "react";
|
|
||||||
import { useParams } from "react-router-dom";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Renders the component for showing total time per role in a project.
|
|
||||||
* @returns JSX.Element
|
|
||||||
*/
|
|
||||||
export default function TimePerRole(): JSX.Element {
|
|
||||||
const [developmentTime, setDevelopmentTime] = useState<number>();
|
|
||||||
const [meetingTime, setMeetingTime] = useState<number>();
|
|
||||||
const [adminTime, setAdminTime] = useState<number>();
|
|
||||||
const [ownWorkTime, setOwnWorkTime] = useState<number>();
|
|
||||||
const [studyTime, setStudyTime] = useState<number>();
|
|
||||||
const [testingTime, setTestingTime] = useState<number>();
|
|
||||||
|
|
||||||
// const token = localStorage.getItem("accessToken") ?? "";
|
|
||||||
// const username = localStorage.getItem("username") ?? "";
|
|
||||||
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> => {
|
|
||||||
// Use mock data
|
|
||||||
const report: TimePerActivity = {
|
|
||||||
developmentTime: 100,
|
|
||||||
meetingTime: 200,
|
|
||||||
adminTime: 300,
|
|
||||||
ownWorkTime: 50,
|
|
||||||
studyTime: 75,
|
|
||||||
testingTime: 110,
|
|
||||||
};
|
|
||||||
|
|
||||||
// Set the state with the mock data
|
|
||||||
setDevelopmentTime(report.developmentTime);
|
|
||||||
setMeetingTime(report.meetingTime);
|
|
||||||
setAdminTime(report.adminTime);
|
|
||||||
setOwnWorkTime(report.ownWorkTime);
|
|
||||||
setStudyTime(report.studyTime);
|
|
||||||
setTestingTime(report.testingTime);
|
|
||||||
|
|
||||||
await Promise.resolve();
|
|
||||||
};
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
void fetchTimePerActivity();
|
|
||||||
});
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<h1 className="font-bold text-[30px] mb-[20px]">
|
|
||||||
Total Time Per Activity In: {projectName}{" "}
|
|
||||||
</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="flex flex-col items-center">
|
|
||||||
<table className="w-full text-center divide-y divide-x divide-white text-[30px]">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th className="w-1/2 py-2 border-b-2 border-black">Activity</th>
|
|
||||||
<th className="w-1/2 py-2 border-b-2 border-black">
|
|
||||||
Total Time (min)
|
|
||||||
</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody className="divide-y divide-black">
|
|
||||||
<tr className="h-[10vh]">
|
|
||||||
<td>Development</td>
|
|
||||||
<td>
|
|
||||||
<input
|
|
||||||
type="string"
|
|
||||||
className="border-2 border-black rounded-md text-center w-1/2"
|
|
||||||
value={developmentTime}
|
|
||||||
onKeyDown={(event) => {
|
|
||||||
event.preventDefault();
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr className="h-[10vh]">
|
|
||||||
<td>Meeting</td>
|
|
||||||
<td>
|
|
||||||
<input
|
|
||||||
type="string"
|
|
||||||
className="border-2 border-black rounded-md text-center w-1/2"
|
|
||||||
value={meetingTime}
|
|
||||||
onKeyDown={(event) => {
|
|
||||||
event.preventDefault();
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr className="h-[10vh]">
|
|
||||||
<td>Administration</td>
|
|
||||||
<td>
|
|
||||||
<input
|
|
||||||
type="string"
|
|
||||||
className="border-2 border-black rounded-md text-center w-1/2"
|
|
||||||
value={adminTime}
|
|
||||||
onKeyDown={(event) => {
|
|
||||||
event.preventDefault();
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr className="h-[10vh]">
|
|
||||||
<td>Own Work</td>
|
|
||||||
<td>
|
|
||||||
<input
|
|
||||||
type="string"
|
|
||||||
className="border-2 border-black rounded-md text-center w-1/2"
|
|
||||||
value={ownWorkTime}
|
|
||||||
onKeyDown={(event) => {
|
|
||||||
event.preventDefault();
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr className="h-[10vh]">
|
|
||||||
<td>Studies</td>
|
|
||||||
<td>
|
|
||||||
<input
|
|
||||||
type="string"
|
|
||||||
className="border-2 border-black rounded-md text-center w-1/2"
|
|
||||||
value={studyTime}
|
|
||||||
onKeyDown={(event) => {
|
|
||||||
event.preventDefault();
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr className="h-[10vh]">
|
|
||||||
<td>Testing</td>
|
|
||||||
<td>
|
|
||||||
<input
|
|
||||||
type="string"
|
|
||||||
className="border-2 border-black rounded-md text-center w-1/2"
|
|
||||||
value={testingTime}
|
|
||||||
onKeyDown={(event) => {
|
|
||||||
event.preventDefault();
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
|
@ -1,141 +0,0 @@
|
||||||
import { useState, useEffect } from "react";
|
|
||||||
import { useParams } from "react-router-dom";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Renders the component for showing total time per role in a project.
|
|
||||||
* @returns JSX.Element
|
|
||||||
*/
|
|
||||||
export default function TimePerRole(): JSX.Element {
|
|
||||||
const [PManagerTime, setPManagerTime] = useState(0);
|
|
||||||
const [SManagerTime, setSManagerTime] = useState(0);
|
|
||||||
const [DeveloperTime, setDeveloperTime] = useState(0);
|
|
||||||
const [TesterTime, setTesterTime] = useState(0);
|
|
||||||
|
|
||||||
// const token = localStorage.getItem("accessToken") ?? "";
|
|
||||||
// const username = localStorage.getItem("username") ?? "";
|
|
||||||
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 TimePerRole {
|
|
||||||
PManager: number;
|
|
||||||
SManager: number;
|
|
||||||
Developer: number;
|
|
||||||
Tester: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
const fetchTimePerRole = async (): Promise<void> => {
|
|
||||||
// Use mock data
|
|
||||||
const report: TimePerRole = {
|
|
||||||
PManager: 120,
|
|
||||||
SManager: 80,
|
|
||||||
Developer: 200,
|
|
||||||
Tester: 150,
|
|
||||||
};
|
|
||||||
|
|
||||||
// Set the state with the mock data
|
|
||||||
setPManagerTime(report.PManager);
|
|
||||||
setSManagerTime(report.SManager);
|
|
||||||
setDeveloperTime(report.Developer);
|
|
||||||
setTesterTime(report.Tester);
|
|
||||||
|
|
||||||
await Promise.resolve();
|
|
||||||
};
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
void fetchTimePerRole();
|
|
||||||
});
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<h1 className="font-bold text-[30px] mb-[20px]">
|
|
||||||
Total Time Per Role In: {projectName}{" "}
|
|
||||||
</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="flex flex-col items-center">
|
|
||||||
<table className="w-full text-center divide-y divide-x divide-white text-[30px]">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th className="w-1/2 py-2 border-b-2 border-black">Role</th>
|
|
||||||
<th className="w-1/2 py-2 border-b-2 border-black">
|
|
||||||
Total Time (min)
|
|
||||||
</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody className="divide-y divide-black">
|
|
||||||
<tr className="h-[10vh]">
|
|
||||||
<td>Project Manager</td>
|
|
||||||
<td>
|
|
||||||
<input
|
|
||||||
type="string"
|
|
||||||
className="border-2 border-black rounded-md text-center w-1/2"
|
|
||||||
value={PManagerTime}
|
|
||||||
onKeyDown={(event) => {
|
|
||||||
event.preventDefault();
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr className="h-[10vh]">
|
|
||||||
<td>System Manager</td>
|
|
||||||
<td>
|
|
||||||
<input
|
|
||||||
type="string"
|
|
||||||
className="border-2 border-black rounded-md text-center w-1/2"
|
|
||||||
value={SManagerTime}
|
|
||||||
onKeyDown={(event) => {
|
|
||||||
event.preventDefault();
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr className="h-[10vh]">
|
|
||||||
<td>Administration</td>
|
|
||||||
<td>
|
|
||||||
<input
|
|
||||||
type="string"
|
|
||||||
className="border-2 border-black rounded-md text-center w-1/2"
|
|
||||||
value={DeveloperTime}
|
|
||||||
onKeyDown={(event) => {
|
|
||||||
event.preventDefault();
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr className="h-[10vh]">
|
|
||||||
<td>Own Work</td>
|
|
||||||
<td>
|
|
||||||
<input
|
|
||||||
type="string"
|
|
||||||
className="border-2 border-black rounded-md text-center w-1/2"
|
|
||||||
value={TesterTime}
|
|
||||||
onKeyDown={(event) => {
|
|
||||||
event.preventDefault();
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
|
@ -49,13 +49,7 @@ function UserInfoModal(props: {
|
||||||
<Button
|
<Button
|
||||||
text={"Delete"}
|
text={"Delete"}
|
||||||
onClick={function (): void {
|
onClick={function (): void {
|
||||||
if (
|
DeleteUser({ usernameToDelete: props.username });
|
||||||
window.confirm("Are you sure you want to delete this user?")
|
|
||||||
) {
|
|
||||||
DeleteUser({
|
|
||||||
usernameToDelete: props.username,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}}
|
}}
|
||||||
type="button"
|
type="button"
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -1,188 +0,0 @@
|
||||||
import { useState, useEffect } from "react";
|
|
||||||
import { WeeklyReport, NewWeeklyReport } from "../Types/goTypes";
|
|
||||||
import { api } from "../API/API";
|
|
||||||
import { useNavigate, useParams } from "react-router-dom";
|
|
||||||
import Button from "./Button";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Renders the component for editing a weekly report.
|
|
||||||
* @returns JSX.Element
|
|
||||||
*/
|
|
||||||
|
|
||||||
//This component does not yet work as intended. It is supposed to display the weekly report of a user in a project.
|
|
||||||
export default function GetOtherUsersReport(): JSX.Element {
|
|
||||||
const [week, setWeek] = useState(0);
|
|
||||||
const [developmentTime, setDevelopmentTime] = useState(0);
|
|
||||||
const [meetingTime, setMeetingTime] = useState(0);
|
|
||||||
const [adminTime, setAdminTime] = useState(0);
|
|
||||||
const [ownWorkTime, setOwnWorkTime] = useState(0);
|
|
||||||
const [studyTime, setStudyTime] = useState(0);
|
|
||||||
const [testingTime, setTestingTime] = useState(0);
|
|
||||||
|
|
||||||
const token = localStorage.getItem("accessToken") ?? "";
|
|
||||||
const { projectName } = useParams();
|
|
||||||
const { username } = useParams();
|
|
||||||
const { fetchedWeek } = useParams();
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const fetchUsersWeeklyReport = async (): Promise<void> => {
|
|
||||||
const response = await api.getWeeklyReport(
|
|
||||||
projectName ?? "",
|
|
||||||
fetchedWeek?.toString() ?? "0",
|
|
||||||
token,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (response.success) {
|
|
||||||
const report: WeeklyReport = response.data ?? {
|
|
||||||
reportId: 0,
|
|
||||||
userId: 0,
|
|
||||||
projectId: 0,
|
|
||||||
week: 0,
|
|
||||||
developmentTime: 0,
|
|
||||||
meetingTime: 0,
|
|
||||||
adminTime: 0,
|
|
||||||
ownWorkTime: 0,
|
|
||||||
studyTime: 0,
|
|
||||||
testingTime: 0,
|
|
||||||
};
|
|
||||||
setWeek(report.week);
|
|
||||||
setDevelopmentTime(report.developmentTime);
|
|
||||||
setMeetingTime(report.meetingTime);
|
|
||||||
setAdminTime(report.adminTime);
|
|
||||||
setOwnWorkTime(report.ownWorkTime);
|
|
||||||
setStudyTime(report.studyTime);
|
|
||||||
setTestingTime(report.testingTime);
|
|
||||||
} else {
|
|
||||||
console.error("Failed to fetch weekly report:", response.message);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
void fetchUsersWeeklyReport();
|
|
||||||
});
|
|
||||||
|
|
||||||
const handleSignWeeklyReport = async (): Promise<void> => {
|
|
||||||
const newWeeklyReport: NewWeeklyReport = {
|
|
||||||
projectName: projectName ?? "",
|
|
||||||
week,
|
|
||||||
developmentTime,
|
|
||||||
meetingTime,
|
|
||||||
adminTime,
|
|
||||||
ownWorkTime,
|
|
||||||
studyTime,
|
|
||||||
testingTime,
|
|
||||||
};
|
|
||||||
|
|
||||||
await api.submitWeeklyReport(newWeeklyReport, token);
|
|
||||||
};
|
|
||||||
|
|
||||||
const navigate = useNavigate();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<h1 className="text-[30px] font-bold">{username}'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">
|
|
||||||
<form
|
|
||||||
onSubmit={(e) => {
|
|
||||||
e.preventDefault();
|
|
||||||
void handleSignWeeklyReport();
|
|
||||||
navigate(-1);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<div className="flex flex-col items-center">
|
|
||||||
<div className="flex flex-col w-1/2 border-b-2 border-black items-center justify-center">
|
|
||||||
<h1 className="font-bold text-[30px]"> Week: {week}</h1>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<table className="w-full text-center divide-y divide-x divide-white text-[30px]">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th className="w-1/2 py-2 border-b-2 border-black">
|
|
||||||
Activity
|
|
||||||
</th>
|
|
||||||
<th className="w-1/2 py-2 border-b-2 border-black">
|
|
||||||
Total Time (min)
|
|
||||||
</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody className="divide-y divide-black">
|
|
||||||
<tr className="h-[10vh]">
|
|
||||||
<td>Development</td>
|
|
||||||
<td>
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
min="0"
|
|
||||||
className="border-2 border-black rounded-md text-center w-1/2"
|
|
||||||
value={developmentTime === 0 ? "" : developmentTime}
|
|
||||||
/>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr className="h-[10vh]">
|
|
||||||
<td>Meeting</td>
|
|
||||||
<td>
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
min="0"
|
|
||||||
className="border-2 border-black rounded-md text-center w-1/2"
|
|
||||||
value={meetingTime === 0 ? "" : meetingTime}
|
|
||||||
/>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr className="h-[10vh]">
|
|
||||||
<td>Administration</td>
|
|
||||||
<td>
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
min="0"
|
|
||||||
className="border-2 border-black rounded-md text-center w-1/2"
|
|
||||||
value={adminTime === 0 ? "" : adminTime}
|
|
||||||
/>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr className="h-[10vh]">
|
|
||||||
<td>Own Work</td>
|
|
||||||
<td>
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
min="0"
|
|
||||||
className="border-2 border-black rounded-md text-center w-1/2"
|
|
||||||
value={ownWorkTime === 0 ? "" : ownWorkTime}
|
|
||||||
/>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr className="h-[10vh]">
|
|
||||||
<td>Studies</td>
|
|
||||||
<td>
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
min="0"
|
|
||||||
className="border-2 border-black rounded-md text-center w-1/2"
|
|
||||||
value={studyTime === 0 ? "" : studyTime}
|
|
||||||
/>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr className="h-[10vh]">
|
|
||||||
<td>Testing</td>
|
|
||||||
<td>
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
min="0"
|
|
||||||
className="border-2 border-black rounded-md text-center w-1/2"
|
|
||||||
value={testingTime === 0 ? "" : testingTime}
|
|
||||||
/>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
<Button
|
|
||||||
text="Sign Report"
|
|
||||||
onClick={(): void => {
|
|
||||||
return;
|
|
||||||
}}
|
|
||||||
type="submit"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
|
@ -1,10 +1,22 @@
|
||||||
import AddUserToProject from "../../Components/AddUserToProject";
|
import BackButton from "../../Components/BackButton";
|
||||||
import BasicWindow from "../../Components/BasicWindow";
|
import BasicWindow from "../../Components/BasicWindow";
|
||||||
|
import Button from "../../Components/Button";
|
||||||
|
|
||||||
function AdminProjectAddMember(): JSX.Element {
|
function AdminProjectAddMember(): JSX.Element {
|
||||||
const content = <AddUserToProject />;
|
const content = <></>;
|
||||||
|
|
||||||
const buttons = <></>;
|
const buttons = (
|
||||||
|
<>
|
||||||
|
<Button
|
||||||
|
text="Add"
|
||||||
|
onClick={(): void => {
|
||||||
|
return;
|
||||||
|
}}
|
||||||
|
type="button"
|
||||||
|
/>
|
||||||
|
<BackButton />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
|
||||||
return <BasicWindow content={content} buttons={buttons} />;
|
return <BasicWindow content={content} buttons={buttons} />;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,26 +1,17 @@
|
||||||
import { useParams } from "react-router-dom";
|
|
||||||
import { api } from "../../API/API";
|
|
||||||
import BackButton from "../../Components/BackButton";
|
import BackButton from "../../Components/BackButton";
|
||||||
import BasicWindow from "../../Components/BasicWindow";
|
import BasicWindow from "../../Components/BasicWindow";
|
||||||
import Button from "../../Components/Button";
|
import Button from "../../Components/Button";
|
||||||
|
|
||||||
async function handleDeleteProject(
|
|
||||||
projectName: string,
|
|
||||||
token: string,
|
|
||||||
): Promise<void> {
|
|
||||||
await api.removeProject(projectName, token);
|
|
||||||
}
|
|
||||||
|
|
||||||
function AdminProjectPage(): JSX.Element {
|
function AdminProjectPage(): JSX.Element {
|
||||||
const content = <></>;
|
const content = <></>;
|
||||||
const { projectName } = useParams();
|
|
||||||
const token = localStorage.getItem("accessToken");
|
|
||||||
|
|
||||||
const buttons = (
|
const buttons = (
|
||||||
<>
|
<>
|
||||||
<Button
|
<Button
|
||||||
text="Delete"
|
text="Delete"
|
||||||
onClick={() => handleDeleteProject(projectName, token)}
|
onClick={(): void => {
|
||||||
|
return;
|
||||||
|
}}
|
||||||
type="button"
|
type="button"
|
||||||
/>
|
/>
|
||||||
<BackButton />
|
<BackButton />
|
||||||
|
@ -29,5 +20,4 @@ function AdminProjectPage(): JSX.Element {
|
||||||
|
|
||||||
return <BasicWindow content={content} buttons={buttons} />;
|
return <BasicWindow content={content} buttons={buttons} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default AdminProjectPage;
|
export default AdminProjectPage;
|
||||||
|
|
|
@ -1,13 +1,8 @@
|
||||||
import BasicWindow from "../../Components/BasicWindow";
|
import BasicWindow from "../../Components/BasicWindow";
|
||||||
import BackButton from "../../Components/BackButton";
|
import BackButton from "../../Components/BackButton";
|
||||||
import AllTimeReportsInProjectOtherUser from "../../Components/AllTimeReportsInProjectOtherUser";
|
|
||||||
|
|
||||||
function PMOtherUsersTR(): JSX.Element {
|
function PMOtherUsersTR(): JSX.Element {
|
||||||
const content = (
|
const content = <></>;
|
||||||
<>
|
|
||||||
<AllTimeReportsInProjectOtherUser />
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
|
|
||||||
const buttons = (
|
const buttons = (
|
||||||
<>
|
<>
|
||||||
|
|
|
@ -8,13 +8,16 @@ function PMProjectMembers(): JSX.Element {
|
||||||
const { projectName } = useParams();
|
const { projectName } = useParams();
|
||||||
const content = (
|
const content = (
|
||||||
<>
|
<>
|
||||||
|
<h1 className="font-bold text-[30px] mb-[20px]">
|
||||||
|
All Members In: {projectName}{" "}
|
||||||
|
</h1>
|
||||||
<ProjectMembers />
|
<ProjectMembers />
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|
||||||
const buttons = (
|
const buttons = (
|
||||||
<>
|
<>
|
||||||
<Link to={`/PMtimeactivity/${projectName}`}>
|
<Link to="/PM-time-activity">
|
||||||
<Button
|
<Button
|
||||||
text="Time / Activity"
|
text="Time / Activity"
|
||||||
onClick={(): void => {
|
onClick={(): void => {
|
||||||
|
@ -23,6 +26,15 @@ function PMProjectMembers(): JSX.Element {
|
||||||
type={"button"}
|
type={"button"}
|
||||||
/>
|
/>
|
||||||
</Link>
|
</Link>
|
||||||
|
<Link to="/PM-time-role">
|
||||||
|
<Button
|
||||||
|
text="Time / Role"
|
||||||
|
onClick={(): void => {
|
||||||
|
return;
|
||||||
|
}}
|
||||||
|
type={"button"}
|
||||||
|
/>
|
||||||
|
</Link>
|
||||||
<BackButton />
|
<BackButton />
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,11 +1,14 @@
|
||||||
import BackButton from "../../Components/BackButton";
|
import BackButton from "../../Components/BackButton";
|
||||||
import BasicWindow from "../../Components/BasicWindow";
|
import BasicWindow from "../../Components/BasicWindow";
|
||||||
import TimePerActivity from "../../Components/TimePerActivity";
|
import TimeReport from "../../Components/NewWeeklyReport";
|
||||||
|
|
||||||
function PMTotalTimeActivity(): JSX.Element {
|
function PMTotalTimeActivity(): JSX.Element {
|
||||||
const content = (
|
const content = (
|
||||||
<>
|
<>
|
||||||
<TimePerActivity />
|
<h1 className="font-bold text-[30px] mb-[20px]">
|
||||||
|
Total Time Per Activity
|
||||||
|
</h1>
|
||||||
|
<TimeReport />
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -1,13 +1,8 @@
|
||||||
import BasicWindow from "../../Components/BasicWindow";
|
import BasicWindow from "../../Components/BasicWindow";
|
||||||
import BackButton from "../../Components/BackButton";
|
import BackButton from "../../Components/BackButton";
|
||||||
import TimePerRole from "../../Components/TimePerRole";
|
|
||||||
|
|
||||||
function PMTotalTimeRole(): JSX.Element {
|
function PMTotalTimeRole(): JSX.Element {
|
||||||
const content = (
|
const content = <></>;
|
||||||
<>
|
|
||||||
<TimePerRole />
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
|
|
||||||
const buttons = (
|
const buttons = (
|
||||||
<>
|
<>
|
||||||
|
|
|
@ -1,13 +1,8 @@
|
||||||
import BasicWindow from "../../Components/BasicWindow";
|
import BasicWindow from "../../Components/BasicWindow";
|
||||||
import BackButton from "../../Components/BackButton";
|
import BackButton from "../../Components/BackButton";
|
||||||
import DisplayUnsignedReports from "../../Components/DisplayUnsignedReports";
|
|
||||||
|
|
||||||
function PMUnsignedReports(): JSX.Element {
|
function PMUnsignedReports(): JSX.Element {
|
||||||
const content = (
|
const content = <></>;
|
||||||
<>
|
|
||||||
<DisplayUnsignedReports />
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
|
|
||||||
const buttons = (
|
const buttons = (
|
||||||
<>
|
<>
|
||||||
|
|
|
@ -1,20 +0,0 @@
|
||||||
import BasicWindow from "../../Components/BasicWindow";
|
|
||||||
import BackButton from "../../Components/BackButton";
|
|
||||||
import OtherUsersTR from "../../Components/OtherUsersTR";
|
|
||||||
|
|
||||||
function PMViewOtherUsersTR(): JSX.Element {
|
|
||||||
const content = (
|
|
||||||
<>
|
|
||||||
<OtherUsersTR />
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
|
|
||||||
const buttons = (
|
|
||||||
<>
|
|
||||||
<BackButton />
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
|
|
||||||
return <BasicWindow content={content} buttons={buttons} />;
|
|
||||||
}
|
|
||||||
export default PMViewOtherUsersTR;
|
|
|
@ -1,16 +1,34 @@
|
||||||
import BackButton from "../../Components/BackButton";
|
import BackButton from "../../Components/BackButton";
|
||||||
import BasicWindow from "../../Components/BasicWindow";
|
import BasicWindow from "../../Components/BasicWindow";
|
||||||
import ViewOtherTimeReport from "../../Components/ViewOtherTimeReport";
|
import Button from "../../Components/Button";
|
||||||
|
import TimeReport from "../../Components/NewWeeklyReport";
|
||||||
|
|
||||||
function PMViewUnsignedReport(): JSX.Element {
|
function PMViewUnsignedReport(): JSX.Element {
|
||||||
const content = (
|
const content = (
|
||||||
<>
|
<>
|
||||||
<ViewOtherTimeReport />
|
<h1 className="font-bold text-[30px] mb-[20px]">
|
||||||
|
Username's Time Report
|
||||||
|
</h1>
|
||||||
|
<TimeReport />
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|
||||||
const buttons = (
|
const buttons = (
|
||||||
<>
|
<>
|
||||||
|
<Button
|
||||||
|
text="Sign"
|
||||||
|
onClick={(): void => {
|
||||||
|
return;
|
||||||
|
}}
|
||||||
|
type="button"
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
text="Save"
|
||||||
|
onClick={(): void => {
|
||||||
|
return;
|
||||||
|
}}
|
||||||
|
type="button"
|
||||||
|
/>
|
||||||
<BackButton />
|
<BackButton />
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|
|
@ -5,6 +5,7 @@ import EditWeeklyReport from "../../Components/EditWeeklyReport";
|
||||||
function UserEditTimeReportPage(): JSX.Element {
|
function UserEditTimeReportPage(): JSX.Element {
|
||||||
const content = (
|
const content = (
|
||||||
<>
|
<>
|
||||||
|
<h1 className="font-bold text-[30px] mb-[20px]">Edit Time Report</h1>
|
||||||
<EditWeeklyReport />
|
<EditWeeklyReport />
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|
|
@ -151,16 +151,9 @@ export interface NewProject {
|
||||||
*/
|
*/
|
||||||
export interface RoleChange {
|
export interface RoleChange {
|
||||||
username: string;
|
username: string;
|
||||||
role: "project_manager" | "user";
|
role: 'project_manager' | 'user';
|
||||||
projectname: string;
|
projectname: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface NewProjMember {
|
|
||||||
username: string;
|
|
||||||
projectname: string;
|
|
||||||
role: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface NameChange {
|
export interface NameChange {
|
||||||
id: number /* int */;
|
id: number /* int */;
|
||||||
name: string;
|
name: string;
|
||||||
|
|
|
@ -31,7 +31,6 @@ import AdminProjectViewMemberInfo from "./Pages/AdminPages/AdminProjectViewMembe
|
||||||
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";
|
import UnauthorizedPage from "./Pages/UnauthorizedPage.tsx";
|
||||||
import PMViewOtherUsersTR from "./Pages/ProjectManagerPages/PMViewOtherUsersTR.tsx";
|
|
||||||
|
|
||||||
// This is where the routes are mounted
|
// This is where the routes are mounted
|
||||||
const router = createBrowserRouter([
|
const router = createBrowserRouter([
|
||||||
|
@ -61,7 +60,7 @@ const router = createBrowserRouter([
|
||||||
element: <UserViewTimeReportsPage />,
|
element: <UserViewTimeReportsPage />,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "/editTimeReport/:projectName/:fetchedWeek",
|
path: "/editTimeReport/:projectName/:weekNumber",
|
||||||
element: <UserEditTimeReportPage />,
|
element: <UserEditTimeReportPage />,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -69,13 +68,9 @@ const router = createBrowserRouter([
|
||||||
element: <PMChangeRole />,
|
element: <PMChangeRole />,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "/otherUsersTimeReports/:projectName/:username",
|
path: "/otherUsersTimeReports",
|
||||||
element: <PMOtherUsersTR />,
|
element: <PMOtherUsersTR />,
|
||||||
},
|
},
|
||||||
{
|
|
||||||
path: "/editOthersTR/:projectName/:username/:fetchedWeek",
|
|
||||||
element: <PMViewOtherUsersTR />,
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
path: "/projectMembers/:projectName",
|
path: "/projectMembers/:projectName",
|
||||||
element: <PMProjectMembers />,
|
element: <PMProjectMembers />,
|
||||||
|
@ -85,11 +80,11 @@ const router = createBrowserRouter([
|
||||||
element: <PMProjectPage />,
|
element: <PMProjectPage />,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "/PMTimeActivity/:projectName",
|
path: "/PMTimeActivity",
|
||||||
element: <PMTotalTimeActivity />,
|
element: <PMTotalTimeActivity />,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "/PMTimeRole/:projectName",
|
path: "/PMTimeRole",
|
||||||
element: <PMTotalTimeRole />,
|
element: <PMTotalTimeRole />,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -97,7 +92,7 @@ const router = createBrowserRouter([
|
||||||
element: <PMUnsignedReports />,
|
element: <PMUnsignedReports />,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "/PMViewUnsignedReport/:projectName/:username/:fetchedWeek",
|
path: "/PMViewUnsignedReport",
|
||||||
element: <PMViewUnsignedReport />,
|
element: <PMViewUnsignedReport />,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
64
testing.py
64
testing.py
|
@ -2,7 +2,7 @@ import requests
|
||||||
import string
|
import string
|
||||||
import random
|
import random
|
||||||
|
|
||||||
debug_output = True
|
debug_output = False
|
||||||
|
|
||||||
def gprint(*args, **kwargs):
|
def gprint(*args, **kwargs):
|
||||||
print("\033[92m", *args, "\033[00m", **kwargs)
|
print("\033[92m", *args, "\033[00m", **kwargs)
|
||||||
|
@ -41,10 +41,8 @@ getWeeklyReportsUserPath = base_url + "/api/getWeeklyReportsUser"
|
||||||
checkIfProjectManagerPath = base_url + "/api/checkIfProjectManager"
|
checkIfProjectManagerPath = base_url + "/api/checkIfProjectManager"
|
||||||
ProjectRoleChangePath = base_url + "/api/ProjectRoleChange"
|
ProjectRoleChangePath = base_url + "/api/ProjectRoleChange"
|
||||||
getUsersProjectPath = base_url + "/api/getUsersProject"
|
getUsersProjectPath = base_url + "/api/getUsersProject"
|
||||||
getUnsignedReportsPath = base_url + "/api/getUnsignedReports"
|
|
||||||
getChangeUserNamePath = base_url + "/api/changeUserName"
|
getChangeUserNamePath = base_url + "/api/changeUserName"
|
||||||
getUpdateWeeklyReportPath = base_url + "/api/updateWeeklyReport"
|
getUpdateWeeklyReportPath = base_url + "/api/updateWeeklyReport"
|
||||||
removeProjectPath = base_url + "/api/removeProject"
|
|
||||||
|
|
||||||
#ta bort auth i handlern för att få testet att gå igenom
|
#ta bort auth i handlern för att få testet att gå igenom
|
||||||
def test_ProjectRoleChange():
|
def test_ProjectRoleChange():
|
||||||
|
@ -301,8 +299,9 @@ def test_sign_report():
|
||||||
report_id = response.json()["reportId"]
|
report_id = response.json()["reportId"]
|
||||||
|
|
||||||
# Sign the report as the project manager
|
# Sign the report as the project manager
|
||||||
response = requests.put(
|
response = requests.post(
|
||||||
signReportPath + "/" + str(report_id),
|
signReportPath,
|
||||||
|
json={"reportId": report_id},
|
||||||
headers={"Authorization": "Bearer " + project_manager_token},
|
headers={"Authorization": "Bearer " + project_manager_token},
|
||||||
)
|
)
|
||||||
assert response.status_code == 200, "Sign report failed"
|
assert response.status_code == 200, "Sign report failed"
|
||||||
|
@ -332,8 +331,6 @@ def test_get_weekly_reports_user():
|
||||||
assert response.status_code == 200, "Get weekly reports for user failed"
|
assert response.status_code == 200, "Get weekly reports for user failed"
|
||||||
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
|
||||||
|
@ -465,58 +462,7 @@ def test_update_weekly_report():
|
||||||
assert response.status_code == 200, "Update weekly report failed"
|
assert response.status_code == 200, "Update weekly report failed"
|
||||||
gprint("test_update_weekly_report successful")
|
gprint("test_update_weekly_report successful")
|
||||||
|
|
||||||
|
|
||||||
def test_remove_project():
|
|
||||||
admin_username = randomString()
|
|
||||||
admin_password = "admin_password2"
|
|
||||||
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
|
|
||||||
admin_token = login(admin_username, admin_password).json()["token"]
|
|
||||||
response = requests.post(
|
|
||||||
promoteToAdminPath,
|
|
||||||
json={"username": admin_username},
|
|
||||||
headers={"Authorization": "Bearer " + admin_token},
|
|
||||||
)
|
|
||||||
|
|
||||||
# Create a new project
|
|
||||||
new_project = randomString()
|
|
||||||
response = requests.post(
|
|
||||||
addProjectPath,
|
|
||||||
json={"name": new_project, "description": "This is a project"},
|
|
||||||
headers={"Authorization": "Bearer " + admin_token},
|
|
||||||
)
|
|
||||||
assert response.status_code == 200, "Add project failed"
|
|
||||||
|
|
||||||
# Remove the project
|
|
||||||
response = requests.delete(
|
|
||||||
removeProjectPath + "/" + new_project,
|
|
||||||
headers={"Authorization": "Bearer " + admin_token},
|
|
||||||
)
|
|
||||||
assert response.status_code == 200, "Remove project failed"
|
|
||||||
gprint("test_remove_project successful")
|
|
||||||
|
|
||||||
def test_get_unsigned_reports():
|
|
||||||
# Log in as the user
|
|
||||||
token = login("user2", "123").json()["token"]
|
|
||||||
|
|
||||||
# Make a request to get all unsigned reports
|
|
||||||
response = requests.get(
|
|
||||||
getUnsignedReportsPath + "/" + projectName,
|
|
||||||
headers={"Authorization": "Bearer " + token},
|
|
||||||
)
|
|
||||||
assert response.status_code == 200, "Get unsigned reports failed"
|
|
||||||
gprint("test_get_unsigned_reports successful")
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
test_remove_project()
|
|
||||||
test_get_user_projects()
|
test_get_user_projects()
|
||||||
test_create_user()
|
test_create_user()
|
||||||
test_login()
|
test_login()
|
||||||
|
@ -530,8 +476,6 @@ if __name__ == "__main__":
|
||||||
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()
|
||||||
test_get_unsigned_reports()
|
|
||||||
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()
|
||||||
|
|
Loading…
Add table
Reference in a new issue