Compare commits
No commits in common. "master" and "refactor" have entirely different histories.
100 changed files with 2399 additions and 5709 deletions
.gitignore
backend
Makefilemain.go
docs
go.modgo.suminternal
database
handlers
projects
AddUserToProject.goChangeProjectName.goGetUserProject.goProjectRoleChange.goPromoteToPm.goRemoveUserFromProject.go
reports
DeleteReport.goGetAllWeeklyReports.goGetWeeklyReport.goGetWeeklyReportsUserHandler.goStatistics.goUnsignReport.go
users
types
frontend
.prettierignorepackage-lock.json
testing.pysrc
API
Components
AddMember.tsxAddProject.tsxAddUserToProject.tsxAllTimeReportsInProject.tsxAllTimeReportsInProjectOtherUser.tsxChangeProjectName.tsxChangeRole.tsxChangeRoleView.tsxChangeUserPassword.tsxChangeUsername.tsxDeleteProject.tsxDeleteUser.tsxDisplayUnsignedReports.tsxDisplayUserProjects.tsxEditWeeklyReport.tsxGetProjectTimes.tsxGetProjects.tsxGetUsersInProject.tsxHeader.tsxInputField.tsx
Inputs
LoginCheck.tsxLoginField.tsxMemberInfoModal.tsxNavButton.tsxNewWeeklyReport.tsxOtherUsersTR.tsxPMProjectMenu.tsxProjectInfoModal.tsxProjectListAdmin.tsxProjectMembers.tsxRegister.tsxRemoveUserFromProj.tsxTimePerActivity.tsxUserInfoModal.tsxUserListAdmin.tsxUserProjectListAdmin.tsxUserProjectMenu.tsxUserStatistics.tsxViewOtherTimeReport.tsxContainers
Data
Pages
AdminPages
AdminChangeUsername.tsxAdminManageProjects.tsxAdminManageUsers.tsxAdminMenuPage.tsxAdminProjectAddMember.tsxAdminProjectChangeUserRole.tsxAdminProjectManageMembers.tsxAdminProjectPage.tsxAdminProjectStatistics.tsxAdminProjectViewMemberInfo.tsxAdminViewUserInfo.tsx
ProjectManagerPages
UserPages
Types
main.tsxtesting
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -14,7 +14,6 @@ diagram.puml
|
||||||
backend/*.png
|
backend/*.png
|
||||||
backend/*.jpg
|
backend/*.jpg
|
||||||
backend/*.svg
|
backend/*.svg
|
||||||
__pycache__
|
|
||||||
|
|
||||||
/go.work.sum
|
/go.work.sum
|
||||||
/package-lock.json
|
/package-lock.json
|
||||||
|
|
|
@ -34,7 +34,6 @@ clean:
|
||||||
rm -f plantuml.jar
|
rm -f plantuml.jar
|
||||||
rm -f erd.png
|
rm -f erd.png
|
||||||
rm -f config.toml
|
rm -f config.toml
|
||||||
rm -f database.txt
|
|
||||||
|
|
||||||
# Test target
|
# Test target
|
||||||
test: db.sqlite3
|
test: db.sqlite3
|
||||||
|
@ -47,7 +46,7 @@ itest:
|
||||||
make build
|
make build
|
||||||
./bin/$(PROC_NAME) >/dev/null 2>&1 &
|
./bin/$(PROC_NAME) >/dev/null 2>&1 &
|
||||||
sleep 1 # Adjust if needed
|
sleep 1 # Adjust if needed
|
||||||
python ../testing/testing.py || pkill $(PROC_NAME)
|
python ../testing.py
|
||||||
pkill $(PROC_NAME)
|
pkill $(PROC_NAME)
|
||||||
|
|
||||||
# Get dependencies target
|
# Get dependencies target
|
||||||
|
@ -106,7 +105,6 @@ docs:
|
||||||
swag init -outputTypes go
|
swag init -outputTypes go
|
||||||
|
|
||||||
api: ./docs/swagger.json
|
api: ./docs/swagger.json
|
||||||
rm ../frontend/src/API/GenApi.ts
|
|
||||||
npx swagger-typescript-api \
|
npx swagger-typescript-api \
|
||||||
--api-class-name GenApi \
|
--api-class-name GenApi \
|
||||||
--path ./docs/swagger.json \
|
--path ./docs/swagger.json \
|
||||||
|
|
|
@ -21,21 +21,21 @@ const docTemplate = `{
|
||||||
"paths": {
|
"paths": {
|
||||||
"/login": {
|
"/login": {
|
||||||
"post": {
|
"post": {
|
||||||
"description": "Logs in a user and returns a JWT token",
|
"description": "logs the user in and returns a jwt token",
|
||||||
"consumes": [
|
"consumes": [
|
||||||
"application/json"
|
"application/json"
|
||||||
],
|
],
|
||||||
"produces": [
|
"produces": [
|
||||||
"application/json"
|
"text/plain"
|
||||||
],
|
],
|
||||||
"tags": [
|
"tags": [
|
||||||
"Auth"
|
"User"
|
||||||
],
|
],
|
||||||
"summary": "Login",
|
"summary": "login",
|
||||||
"parameters": [
|
"parameters": [
|
||||||
{
|
{
|
||||||
"description": "User credentials",
|
"description": "login info",
|
||||||
"name": "body",
|
"name": "NewUser",
|
||||||
"in": "body",
|
"in": "body",
|
||||||
"required": true,
|
"required": true,
|
||||||
"schema": {
|
"schema": {
|
||||||
|
@ -45,9 +45,9 @@ const docTemplate = `{
|
||||||
],
|
],
|
||||||
"responses": {
|
"responses": {
|
||||||
"200": {
|
"200": {
|
||||||
"description": "JWT token",
|
"description": "Successfully signed token for user",
|
||||||
"schema": {
|
"schema": {
|
||||||
"$ref": "#/definitions/types.Token"
|
"type": "Token"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"400": {
|
"400": {
|
||||||
|
@ -71,26 +71,29 @@ const docTemplate = `{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"/loginrenew": {
|
"/loginerenew": {
|
||||||
"post": {
|
"post": {
|
||||||
"security": [
|
"security": [
|
||||||
{
|
{
|
||||||
"JWT": []
|
"bererToken": []
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"description": "Renews the users token.",
|
"description": "renews the users token",
|
||||||
"produces": [
|
"consumes": [
|
||||||
"application/json"
|
"application/json"
|
||||||
],
|
],
|
||||||
|
"produces": [
|
||||||
|
"text/plain"
|
||||||
|
],
|
||||||
"tags": [
|
"tags": [
|
||||||
"Auth"
|
"User"
|
||||||
],
|
],
|
||||||
"summary": "LoginRenews",
|
"summary": "LoginRenews",
|
||||||
"responses": {
|
"responses": {
|
||||||
"200": {
|
"200": {
|
||||||
"description": "Successfully signed token for user",
|
"description": "Successfully signed token for user",
|
||||||
"schema": {
|
"schema": {
|
||||||
"$ref": "#/definitions/types.Token"
|
"type": "Token"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"401": {
|
"401": {
|
||||||
|
@ -108,64 +111,9 @@ const docTemplate = `{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"/promote/{projectName}": {
|
|
||||||
"put": {
|
|
||||||
"security": [
|
|
||||||
{
|
|
||||||
"JWT": []
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"description": "Promote a user to project manager",
|
|
||||||
"consumes": [
|
|
||||||
"text/plain"
|
|
||||||
],
|
|
||||||
"produces": [
|
|
||||||
"text/plain"
|
|
||||||
],
|
|
||||||
"tags": [
|
|
||||||
"Auth"
|
|
||||||
],
|
|
||||||
"summary": "Promote to project manager",
|
|
||||||
"parameters": [
|
|
||||||
{
|
|
||||||
"type": "string",
|
|
||||||
"description": "Project name",
|
|
||||||
"name": "projectName",
|
|
||||||
"in": "path",
|
|
||||||
"required": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "string",
|
|
||||||
"description": "User name",
|
|
||||||
"name": "userName",
|
|
||||||
"in": "query",
|
|
||||||
"required": true
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"responses": {
|
|
||||||
"403": {
|
|
||||||
"description": "Forbidden",
|
|
||||||
"schema": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"500": {
|
|
||||||
"description": "Internal server error",
|
|
||||||
"schema": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"/promoteToAdmin": {
|
"/promoteToAdmin": {
|
||||||
"post": {
|
"post": {
|
||||||
"security": [
|
"description": "promote chosen user to admin",
|
||||||
{
|
|
||||||
"JWT": []
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"description": "Promote chosen user to site admin",
|
|
||||||
"consumes": [
|
"consumes": [
|
||||||
"application/json"
|
"application/json"
|
||||||
],
|
],
|
||||||
|
@ -191,7 +139,7 @@ const docTemplate = `{
|
||||||
"200": {
|
"200": {
|
||||||
"description": "Successfully promoted user",
|
"description": "Successfully promoted user",
|
||||||
"schema": {
|
"schema": {
|
||||||
"$ref": "#/definitions/types.Token"
|
"type": "json"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"400": {
|
"400": {
|
||||||
|
@ -225,7 +173,7 @@ const docTemplate = `{
|
||||||
"text/plain"
|
"text/plain"
|
||||||
],
|
],
|
||||||
"tags": [
|
"tags": [
|
||||||
"Auth"
|
"User"
|
||||||
],
|
],
|
||||||
"summary": "Register",
|
"summary": "Register",
|
||||||
"parameters": [
|
"parameters": [
|
||||||
|
@ -263,11 +211,6 @@ const docTemplate = `{
|
||||||
},
|
},
|
||||||
"/userdelete/{username}": {
|
"/userdelete/{username}": {
|
||||||
"delete": {
|
"delete": {
|
||||||
"security": [
|
|
||||||
{
|
|
||||||
"JWT": []
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"description": "UserDelete deletes a user from the database",
|
"description": "UserDelete deletes a user from the database",
|
||||||
"consumes": [
|
"consumes": [
|
||||||
"application/json"
|
"application/json"
|
||||||
|
@ -309,27 +252,22 @@ const docTemplate = `{
|
||||||
},
|
},
|
||||||
"/users/all": {
|
"/users/all": {
|
||||||
"get": {
|
"get": {
|
||||||
"security": [
|
|
||||||
{
|
|
||||||
"JWT": []
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"description": "lists all users",
|
"description": "lists all users",
|
||||||
"produces": [
|
"consumes": [
|
||||||
"application/json"
|
"application/json"
|
||||||
],
|
],
|
||||||
|
"produces": [
|
||||||
|
"text/plain"
|
||||||
|
],
|
||||||
"tags": [
|
"tags": [
|
||||||
"User"
|
"User"
|
||||||
],
|
],
|
||||||
"summary": "ListsAllUsers",
|
"summary": "ListsAllUsers",
|
||||||
"responses": {
|
"responses": {
|
||||||
"200": {
|
"200": {
|
||||||
"description": "Successfully returned all users",
|
"description": "Successfully signed token for user",
|
||||||
"schema": {
|
"schema": {
|
||||||
"type": "array",
|
"type": "json"
|
||||||
"items": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"401": {
|
"401": {
|
||||||
|
@ -353,27 +291,16 @@ const docTemplate = `{
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
"password": {
|
"password": {
|
||||||
"type": "string",
|
"type": "string"
|
||||||
"example": "password123"
|
|
||||||
},
|
},
|
||||||
"username": {
|
"username": {
|
||||||
"type": "string",
|
|
||||||
"example": "username123"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"types.Token": {
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"token": {
|
|
||||||
"type": "string"
|
"type": "string"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"securityDefinitions": {
|
"securityDefinitions": {
|
||||||
"JWT": {
|
"bererToken": {
|
||||||
"description": "Use the JWT token provided by the login endpoint to authenticate requests. **Prefix the token with \"Bearer \".**",
|
|
||||||
"type": "apiKey",
|
"type": "apiKey",
|
||||||
"name": "Authorization",
|
"name": "Authorization",
|
||||||
"in": "header"
|
"in": "header"
|
||||||
|
|
|
@ -13,10 +13,10 @@ require (
|
||||||
require (
|
require (
|
||||||
github.com/KyleBanks/depth v1.2.1 // indirect
|
github.com/KyleBanks/depth v1.2.1 // indirect
|
||||||
github.com/dustin/go-humanize v1.0.1 // indirect
|
github.com/dustin/go-humanize v1.0.1 // indirect
|
||||||
github.com/go-openapi/jsonpointer v0.21.0 // indirect
|
github.com/go-openapi/jsonpointer v0.20.3 // indirect
|
||||||
github.com/go-openapi/jsonreference v0.21.0 // indirect
|
github.com/go-openapi/jsonreference v0.20.5 // indirect
|
||||||
github.com/go-openapi/spec v0.21.0 // indirect
|
github.com/go-openapi/spec v0.20.15 // indirect
|
||||||
github.com/go-openapi/swag v0.23.0 // indirect
|
github.com/go-openapi/swag v0.22.10 // indirect
|
||||||
github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect
|
github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect
|
||||||
github.com/josharian/intern v1.0.0 // indirect
|
github.com/josharian/intern v1.0.0 // indirect
|
||||||
github.com/mailru/easyjson v0.7.7 // indirect
|
github.com/mailru/easyjson v0.7.7 // indirect
|
||||||
|
@ -25,10 +25,10 @@ require (
|
||||||
github.com/swaggo/files/v2 v2.0.0 // indirect
|
github.com/swaggo/files/v2 v2.0.0 // indirect
|
||||||
golang.org/x/tools v0.19.0 // indirect
|
golang.org/x/tools v0.19.0 // indirect
|
||||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
modernc.org/gc/v3 v3.0.0-20240304020402-f0dba7c97c2b // indirect
|
modernc.org/gc/v3 v3.0.0-20240107210532-573471604cb6 // indirect
|
||||||
modernc.org/libc v1.49.1 // indirect
|
modernc.org/libc v1.41.0 // indirect
|
||||||
modernc.org/mathutil v1.6.0 // indirect
|
modernc.org/mathutil v1.6.0 // indirect
|
||||||
modernc.org/memory v1.8.0 // indirect
|
modernc.org/memory v1.7.2 // indirect
|
||||||
modernc.org/strutil v1.2.0 // indirect
|
modernc.org/strutil v1.2.0 // indirect
|
||||||
modernc.org/token v1.1.0 // indirect
|
modernc.org/token v1.1.0 // indirect
|
||||||
)
|
)
|
||||||
|
@ -42,7 +42,7 @@ require (
|
||||||
// These are all for fiber
|
// These are all for fiber
|
||||||
require (
|
require (
|
||||||
github.com/andybalholm/brotli v1.1.0 // indirect
|
github.com/andybalholm/brotli v1.1.0 // indirect
|
||||||
github.com/gofiber/fiber/v2 v2.52.4
|
github.com/gofiber/fiber/v2 v2.52.2
|
||||||
github.com/google/uuid v1.6.0 // indirect
|
github.com/google/uuid v1.6.0 // indirect
|
||||||
github.com/klauspost/compress v1.17.7 // indirect
|
github.com/klauspost/compress v1.17.7 // indirect
|
||||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||||
|
@ -52,5 +52,5 @@ require (
|
||||||
github.com/valyala/bytebufferpool v1.0.0 // indirect
|
github.com/valyala/bytebufferpool v1.0.0 // indirect
|
||||||
github.com/valyala/fasthttp v1.52.0 // indirect
|
github.com/valyala/fasthttp v1.52.0 // indirect
|
||||||
github.com/valyala/tcplisten v1.0.0 // indirect
|
github.com/valyala/tcplisten v1.0.0 // indirect
|
||||||
golang.org/x/sys v0.19.0 // indirect
|
golang.org/x/sys v0.18.0 // indirect
|
||||||
)
|
)
|
||||||
|
|
|
@ -10,20 +10,20 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
|
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
|
||||||
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
||||||
github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ=
|
github.com/go-openapi/jsonpointer v0.20.3 h1:jykzYWS/kyGtsHfRt6aV8JTB9pcQAXPIA7qlZ5aRlyk=
|
||||||
github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY=
|
github.com/go-openapi/jsonpointer v0.20.3/go.mod h1:c7l0rjoouAuIxCm8v/JWKRgMjDG/+/7UBWsXMrv6PsM=
|
||||||
github.com/go-openapi/jsonreference v0.21.0 h1:Rs+Y7hSXT83Jacb7kFyjn4ijOuVGSvOdF2+tg1TRrwQ=
|
github.com/go-openapi/jsonreference v0.20.5 h1:hutI+cQI+HbSQaIGSfsBsYI0pHk+CATf8Fk5gCSj0yI=
|
||||||
github.com/go-openapi/jsonreference v0.21.0/go.mod h1:LmZmgsrTkVg9LG4EaHeY8cBDslNPMo06cago5JNLkm4=
|
github.com/go-openapi/jsonreference v0.20.5/go.mod h1:thAqAp31UABtI+FQGKAQfmv7DbFpKNUlva2UPCxKu2Y=
|
||||||
github.com/go-openapi/spec v0.21.0 h1:LTVzPc3p/RzRnkQqLRndbAzjY0d0BCL72A6j3CdL9ZY=
|
github.com/go-openapi/spec v0.20.15 h1:8bDcVxF607pTh9NpPwgsH4J5Uhh5mV5XoWnkurdiY+U=
|
||||||
github.com/go-openapi/spec v0.21.0/go.mod h1:78u6VdPw81XU44qEWGhtr982gJ5BWg2c0I5XwVMotYk=
|
github.com/go-openapi/spec v0.20.15/go.mod h1:o0upgqg5uYFG7O5mADrDVmSG3Wa6y6OLhwiCqQ+sTv4=
|
||||||
github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE=
|
github.com/go-openapi/swag v0.22.10 h1:4y86NVn7Z2yYd6pfS4Z+Nyh3aAUL3Nul+LMbhFKy0gA=
|
||||||
github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ=
|
github.com/go-openapi/swag v0.22.10/go.mod h1:Cnn8BYtRlx6BNE3DPN86f/xkapGIcLWzh3CLEb4C1jI=
|
||||||
github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE=
|
github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE=
|
||||||
github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
|
github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
|
||||||
github.com/gofiber/contrib/jwt v1.0.8 h1:/GeOsm/Mr1OGr0GTy+RIVSz5VgNNyP3ZgK4wdqxF/WY=
|
github.com/gofiber/contrib/jwt v1.0.8 h1:/GeOsm/Mr1OGr0GTy+RIVSz5VgNNyP3ZgK4wdqxF/WY=
|
||||||
github.com/gofiber/contrib/jwt v1.0.8/go.mod h1:gWWBtBiLmKXRN7xy6a96QO0KGvPEyxdh8x496Ujtg84=
|
github.com/gofiber/contrib/jwt v1.0.8/go.mod h1:gWWBtBiLmKXRN7xy6a96QO0KGvPEyxdh8x496Ujtg84=
|
||||||
github.com/gofiber/fiber/v2 v2.52.4 h1:P+T+4iK7VaqUsq2PALYEfBBo6bJZ4q3FP8cZ84EggTM=
|
github.com/gofiber/fiber/v2 v2.52.2 h1:b0rYH6b06Df+4NyrbdptQL8ifuxw/Tf2DgfkZkDaxEo=
|
||||||
github.com/gofiber/fiber/v2 v2.52.4/go.mod h1:KEOE+cXMhXG0zHc9d8+E38hoX+ZN7bhOtgeF2oT6jrQ=
|
github.com/gofiber/fiber/v2 v2.52.2/go.mod h1:KEOE+cXMhXG0zHc9d8+E38hoX+ZN7bhOtgeF2oT6jrQ=
|
||||||
github.com/gofiber/swagger v1.0.0 h1:BzUzDS9ZT6fDUa692kxmfOjc1DZiloLiPK/W5z1H1tc=
|
github.com/gofiber/swagger v1.0.0 h1:BzUzDS9ZT6fDUa692kxmfOjc1DZiloLiPK/W5z1H1tc=
|
||||||
github.com/gofiber/swagger v1.0.0/go.mod h1:QrYNF1Yrc7ggGK6ATsJ6yfH/8Zi5bu9lA7wB8TmCecg=
|
github.com/gofiber/swagger v1.0.0/go.mod h1:QrYNF1Yrc7ggGK6ATsJ6yfH/8Zi5bu9lA7wB8TmCecg=
|
||||||
github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk=
|
github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk=
|
||||||
|
@ -85,8 +85,8 @@ golang.org/x/mod v0.16.0 h1:QX4fJ0Rr5cPQCF7O9lh9Se4pmwfwskqZfq5moyldzic=
|
||||||
golang.org/x/mod v0.16.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
golang.org/x/mod v0.16.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o=
|
golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4=
|
||||||
golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
golang.org/x/tools v0.19.0 h1:tfGCXNR1OsFG+sVdLAitlpjAvD/I6dHDKnYrpEZUHkw=
|
golang.org/x/tools v0.19.0 h1:tfGCXNR1OsFG+sVdLAitlpjAvD/I6dHDKnYrpEZUHkw=
|
||||||
golang.org/x/tools v0.19.0/go.mod h1:qoJWxmGSIBmAeriMx19ogtrEPrGtDbPK634QFIcLAhc=
|
golang.org/x/tools v0.19.0/go.mod h1:qoJWxmGSIBmAeriMx19ogtrEPrGtDbPK634QFIcLAhc=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
@ -94,26 +94,16 @@ gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntN
|
||||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
modernc.org/cc/v4 v4.20.0 h1:45Or8mQfbUqJOG9WaxvlFYOAQO0lQ5RvqBcFCXngjxk=
|
|
||||||
modernc.org/cc/v4 v4.20.0/go.mod h1:HM7VJTZbUCR3rV8EYBi9wxnJ0ZBRiGE5OeGXNA0IsLQ=
|
|
||||||
modernc.org/ccgo/v4 v4.15.0 h1:uwfCZOkKhaNNotgYW7kxkJwrkQC1HfGitt/7ousudJE=
|
|
||||||
modernc.org/ccgo/v4 v4.15.0/go.mod h1:XVITcYGiI+O97UNDLMsnZ9ZjJOhC+ACX+TfxpsWWyRc=
|
|
||||||
modernc.org/fileutil v1.3.0 h1:gQ5SIzK3H9kdfai/5x41oQiKValumqNTDXMvKo62HvE=
|
modernc.org/fileutil v1.3.0 h1:gQ5SIzK3H9kdfai/5x41oQiKValumqNTDXMvKo62HvE=
|
||||||
modernc.org/fileutil v1.3.0/go.mod h1:XatxS8fZi3pS8/hKG2GH/ArUogfxjpEKs3Ku3aK4JyQ=
|
modernc.org/fileutil v1.3.0/go.mod h1:XatxS8fZi3pS8/hKG2GH/ArUogfxjpEKs3Ku3aK4JyQ=
|
||||||
modernc.org/gc/v2 v2.4.1 h1:9cNzOqPyMJBvrUipmynX0ZohMhcxPtMccYgGOJdOiBw=
|
modernc.org/gc/v3 v3.0.0-20240107210532-573471604cb6 h1:5D53IMaUuA5InSeMu9eJtlQXS2NxAhyWQvkKEgXZhHI=
|
||||||
modernc.org/gc/v2 v2.4.1/go.mod h1:wzN5dK1AzVGoH6XOzc3YZ+ey/jPgYHLuVckd62P0GYU=
|
modernc.org/gc/v3 v3.0.0-20240107210532-573471604cb6/go.mod h1:Qz0X07sNOR1jWYCrJMEnbW/X55x206Q7Vt4mz6/wHp4=
|
||||||
modernc.org/gc/v3 v3.0.0-20240304020402-f0dba7c97c2b h1:BnN1t+pb1cy61zbvSUV7SeI0PwosMhlAEi/vBY4qxp8=
|
modernc.org/libc v1.41.0 h1:g9YAc6BkKlgORsUWj+JwqoB1wU3o4DE3bM3yvA3k+Gk=
|
||||||
modernc.org/gc/v3 v3.0.0-20240304020402-f0dba7c97c2b/go.mod h1:Qz0X07sNOR1jWYCrJMEnbW/X55x206Q7Vt4mz6/wHp4=
|
modernc.org/libc v1.41.0/go.mod h1:w0eszPsiXoOnoMJgrXjglgLuDy/bt5RR4y3QzUUeodY=
|
||||||
modernc.org/libc v1.49.1 h1:r4UaWllkYXRPA7Mq/KzmassZBvNJiH9egF4O/KV/gdE=
|
|
||||||
modernc.org/libc v1.49.1/go.mod h1:Hx2rWfza47GSzCluTU7Vf0Qx3z9rWCVORL6RNgq+Xog=
|
|
||||||
modernc.org/mathutil v1.6.0 h1:fRe9+AmYlaej+64JsEEhoWuAYBkOtQiMEU7n/XgfYi4=
|
modernc.org/mathutil v1.6.0 h1:fRe9+AmYlaej+64JsEEhoWuAYBkOtQiMEU7n/XgfYi4=
|
||||||
modernc.org/mathutil v1.6.0/go.mod h1:Ui5Q9q1TR2gFm0AQRqQUaBWFLAhQpCwNcuhBOSedWPo=
|
modernc.org/mathutil v1.6.0/go.mod h1:Ui5Q9q1TR2gFm0AQRqQUaBWFLAhQpCwNcuhBOSedWPo=
|
||||||
modernc.org/memory v1.8.0 h1:IqGTL6eFMaDZZhEWwcREgeMXYwmW83LYW8cROZYkg+E=
|
modernc.org/memory v1.7.2 h1:Klh90S215mmH8c9gO98QxQFsY+W451E8AnzjoE2ee1E=
|
||||||
modernc.org/memory v1.8.0/go.mod h1:XPZ936zp5OMKGWPqbD3JShgd/ZoQ7899TUuQqxY+peU=
|
modernc.org/memory v1.7.2/go.mod h1:NO4NVCQy0N7ln+T9ngWqOQfi7ley4vpwvARR+Hjw95E=
|
||||||
modernc.org/opt v0.1.3 h1:3XOZf2yznlhC+ibLltsDGzABUGVx8J6pnFMS3E4dcq4=
|
|
||||||
modernc.org/opt v0.1.3/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0=
|
|
||||||
modernc.org/sortutil v1.2.0 h1:jQiD3PfS2REGJNzNCMMaLSp/wdMNieTbKX920Cqdgqc=
|
|
||||||
modernc.org/sortutil v1.2.0/go.mod h1:TKU2s7kJMf1AE84OoiGppNHJwvB753OYfNl2WRb++Ss=
|
|
||||||
modernc.org/sqlite v1.29.5 h1:8l/SQKAjDtZFo9lkJLdk8g9JEOeYRG4/ghStDCCTiTE=
|
modernc.org/sqlite v1.29.5 h1:8l/SQKAjDtZFo9lkJLdk8g9JEOeYRG4/ghStDCCTiTE=
|
||||||
modernc.org/sqlite v1.29.5/go.mod h1:S02dvcmm7TnTRvGhv8IGYyLnIt7AS2KPaB1F/71p75U=
|
modernc.org/sqlite v1.29.5/go.mod h1:S02dvcmm7TnTRvGhv8IGYyLnIt7AS2KPaB1F/71p75U=
|
||||||
modernc.org/strutil v1.2.0 h1:agBi9dp1I+eOnxXeiZawM8F4LawKv4NzGWSaLfyeNZA=
|
modernc.org/strutil v1.2.0 h1:agBi9dp1I+eOnxXeiZawM8F4LawKv4NzGWSaLfyeNZA=
|
||||||
|
|
|
@ -2,12 +2,11 @@ package database
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"embed"
|
"embed"
|
||||||
"encoding/json"
|
|
||||||
"errors"
|
"errors"
|
||||||
|
"fmt"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"ttime/internal/types"
|
"ttime/internal/types"
|
||||||
|
|
||||||
"github.com/gofiber/fiber/v2/log"
|
|
||||||
"github.com/jmoiron/sqlx"
|
"github.com/jmoiron/sqlx"
|
||||||
_ "modernc.org/sqlite"
|
_ "modernc.org/sqlite"
|
||||||
)
|
)
|
||||||
|
@ -18,11 +17,12 @@ type Database interface {
|
||||||
AddUser(username string, password string) error
|
AddUser(username string, password string) error
|
||||||
CheckUser(username string, password string) bool
|
CheckUser(username string, password string) bool
|
||||||
RemoveUser(username string) error
|
RemoveUser(username string) error
|
||||||
RemoveUserFromProject(username string, projectname string) error
|
|
||||||
PromoteToAdmin(username string) error
|
PromoteToAdmin(username string) error
|
||||||
GetUserId(username string) (int, error)
|
GetUserId(username string) (int, error)
|
||||||
AddProject(name string, description string, username string) error
|
AddProject(name string, description string, username string) error
|
||||||
DeleteProject(name string, username string) error
|
DeleteProject(name string, username string) error
|
||||||
|
Migrate() error
|
||||||
|
MigrateSampleData() error
|
||||||
GetProjectId(projectname string) (int, error)
|
GetProjectId(projectname string) (int, error)
|
||||||
AddWeeklyReport(projectName string, userName string, week int, developmentTime int, meetingTime int, adminTime int, ownWorkTime int, studyTime int, testingTime int) error
|
AddWeeklyReport(projectName string, userName string, week int, developmentTime int, meetingTime int, adminTime int, ownWorkTime int, studyTime int, testingTime int) error
|
||||||
AddUserToProject(username string, projectname string, role string) error
|
AddUserToProject(username string, projectname string, role string) error
|
||||||
|
@ -35,26 +35,20 @@ type Database interface {
|
||||||
GetProject(projectId int) (types.Project, error)
|
GetProject(projectId int) (types.Project, error)
|
||||||
GetUserRole(username string, projectname string) (string, error)
|
GetUserRole(username string, projectname string) (string, error)
|
||||||
GetWeeklyReport(username string, projectName string, week int) (types.WeeklyReport, error)
|
GetWeeklyReport(username string, projectName string, week int) (types.WeeklyReport, error)
|
||||||
GetAllWeeklyReports(username string, projectname string) ([]types.WeeklyReportList, error)
|
GetWeeklyReportsUser(username string, projectname string) ([]types.WeeklyReportList, error)
|
||||||
GetUnsignedWeeklyReports(projectName string) ([]types.WeeklyReport, error)
|
GetUnsignedWeeklyReports(projectName string) ([]types.WeeklyReport, error)
|
||||||
SignWeeklyReport(reportId int, projectManagerId int) error
|
SignWeeklyReport(reportId int, projectManagerId int) error
|
||||||
IsSiteAdmin(username string) (bool, error)
|
IsSiteAdmin(username string) (bool, error)
|
||||||
IsProjectManager(username string, projectname string) (bool, error)
|
IsProjectManager(username string, projectname string) (bool, error)
|
||||||
ReportStatistics(username string, projectName string) (*types.Statistics, 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
|
RemoveProject(projectname string) error
|
||||||
GetUserName(id int) (string, error)
|
|
||||||
UnsignWeeklyReport(reportId int, projectManagerId int) error
|
|
||||||
DeleteReport(reportID int) error
|
|
||||||
ChangeProjectName(projectName string, newProjectName string) error
|
|
||||||
ChangeUserPassword(username string, password string) error
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// This struct is a wrapper type that holds the database connection
|
// This struct is a wrapper type that holds the database connection
|
||||||
// Internally DB holds a connection pool, so it's safe for concurrent use
|
// Internally DB holds a connection pool, so it's safe for concurrent use
|
||||||
type Db struct {
|
type Db struct {
|
||||||
*sqlx.Tx
|
*sqlx.DB
|
||||||
}
|
}
|
||||||
|
|
||||||
type UserProjectMember struct {
|
type UserProjectMember struct {
|
||||||
|
@ -92,23 +86,8 @@ const isProjectManagerQuery = `SELECT COUNT(*) > 0 FROM user_roles
|
||||||
JOIN projects ON user_roles.project_id = projects.id
|
JOIN projects ON user_roles.project_id = projects.id
|
||||||
WHERE users.username = ? AND projects.name = ? AND user_roles.p_role = 'project_manager'`
|
WHERE users.username = ? AND projects.name = ? AND user_roles.p_role = 'project_manager'`
|
||||||
|
|
||||||
const removeUserFromProjectQuery = `DELETE FROM user_roles
|
|
||||||
WHERE user_id = (SELECT id FROM users WHERE username = ?)
|
|
||||||
AND project_id = (SELECT id FROM projects WHERE name = ?)`
|
|
||||||
|
|
||||||
const reportStatistics = `SELECT SUM(development_time) AS total_development_time,
|
|
||||||
SUM(meeting_time) AS total_meeting_time,
|
|
||||||
SUM(admin_time) AS total_admin_time,
|
|
||||||
SUM(own_work_time) AS total_own_work_time,
|
|
||||||
SUM(study_time) AS total_study_time,
|
|
||||||
SUM(testing_time) AS total_testing_time
|
|
||||||
FROM weekly_reports
|
|
||||||
WHERE user_id = (SELECT id FROM users WHERE username = ?)
|
|
||||||
AND project_id = (SELECT id FROM projects WHERE name = ?)
|
|
||||||
GROUP BY user_id, project_id`
|
|
||||||
|
|
||||||
// DbConnect connects to the database
|
// DbConnect connects to the database
|
||||||
func DbConnect(dbpath string) sqlx.DB {
|
func DbConnect(dbpath string) Database {
|
||||||
// Open the database
|
// Open the database
|
||||||
db, err := sqlx.Connect("sqlite", dbpath)
|
db, err := sqlx.Connect("sqlite", dbpath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -121,25 +100,7 @@ func DbConnect(dbpath string) sqlx.DB {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return *db
|
return &Db{db}
|
||||||
}
|
|
||||||
|
|
||||||
func (d *Db) ReportStatistics(username string, projectName string) (*types.Statistics, error) {
|
|
||||||
var result types.Statistics
|
|
||||||
|
|
||||||
err := d.Get(&result, reportStatistics, username, projectName)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
serialized, err := json.Marshal(result)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Info(string(serialized))
|
|
||||||
|
|
||||||
return &result, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Db) CheckUser(username string, password string) bool {
|
func (d *Db) CheckUser(username string, password string) bool {
|
||||||
|
@ -186,11 +147,6 @@ func (d *Db) AddUserToProject(username string, projectname string, role string)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Db) RemoveUserFromProject(username string, projectname string) error {
|
|
||||||
_, err := d.Exec(removeUserFromProjectQuery, username, projectname)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// ChangeUserRole changes the role of a user within a project.
|
// ChangeUserRole changes the role of a user within a project.
|
||||||
func (d *Db) ChangeUserRole(username string, projectname string, role string) error {
|
func (d *Db) ChangeUserRole(username string, projectname string, role string) error {
|
||||||
// Execute the SQL query to change the user's role
|
// Execute the SQL query to change the user's role
|
||||||
|
@ -243,15 +199,25 @@ func (d *Db) GetProjectId(projectname string) (int, error) {
|
||||||
|
|
||||||
// Creates a new project in the database, associated with a user
|
// Creates a new project in the database, associated with a user
|
||||||
func (d *Db) AddProject(name string, description string, username string) error {
|
func (d *Db) AddProject(name string, description string, username string) error {
|
||||||
|
tx := d.MustBegin()
|
||||||
// Insert the project into the database
|
// Insert the project into the database
|
||||||
_, err := d.Exec(projectInsert, name, description, username)
|
_, err := tx.Exec(projectInsert, name, description, username)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
if err := tx.Rollback(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add creator to project as project manager
|
// Add creator to project as project manager
|
||||||
_, err = d.Exec(addUserToProject, username, name, "project_manager")
|
_, err = tx.Exec(addUserToProject, username, name, "project_manager")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
if err := tx.Rollback(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := tx.Commit(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -259,7 +225,16 @@ func (d *Db) AddProject(name string, description string, username string) error
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Db) DeleteProject(projectID string, username string) error {
|
func (d *Db) DeleteProject(projectID string, username string) error {
|
||||||
_, err := d.Exec(deleteProject, projectID, username)
|
tx := d.MustBegin()
|
||||||
|
|
||||||
|
_, err := tx.Exec(deleteProject, projectID, username)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
if rollbackErr := tx.Rollback(); rollbackErr != nil {
|
||||||
|
return fmt.Errorf("error rolling back transaction: %v, delete error: %v", rollbackErr, err)
|
||||||
|
}
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -364,14 +339,9 @@ func (d *Db) SignWeeklyReport(reportId int, projectManagerId int) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
managerQuery := `SELECT project_id FROM user_roles
|
|
||||||
WHERE user_id = ?
|
|
||||||
AND project_id = (SELECT project_id FROM weekly_reports WHERE report_id = ?)
|
|
||||||
AND p_role = 'project_manager'`
|
|
||||||
|
|
||||||
// Retrieve the project ID associated with the project manager
|
// Retrieve the project ID associated with the project manager
|
||||||
var managerProjectID int
|
var managerProjectID int
|
||||||
err = d.Get(&managerProjectID, managerQuery, projectManagerId, reportId)
|
err = d.Get(&managerProjectID, "SELECT project_id FROM user_roles WHERE user_id = ? AND p_role = 'project_manager'", projectManagerId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -386,36 +356,6 @@ func (d *Db) SignWeeklyReport(reportId int, projectManagerId int) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Db) UnsignWeeklyReport(reportId int, projectManagerId int) error {
|
|
||||||
// Retrieve the project ID associated with the report
|
|
||||||
var reportProjectID int
|
|
||||||
err := d.Get(&reportProjectID, "SELECT project_id FROM weekly_reports WHERE report_id = ?", reportId)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
managerQuery := `SELECT project_id FROM user_roles
|
|
||||||
WHERE user_id = ?
|
|
||||||
AND project_id = (SELECT project_id FROM weekly_reports WHERE report_id = ?)
|
|
||||||
AND p_role = 'project_manager'`
|
|
||||||
|
|
||||||
// Retrieve the project ID associated with the project manager
|
|
||||||
var managerProjectID int
|
|
||||||
err = d.Get(&managerProjectID, managerQuery, projectManagerId, reportId)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if the project manager is in the same project as the report
|
|
||||||
if reportProjectID != managerProjectID {
|
|
||||||
return errors.New("project manager doesn't have permission to unsign the report")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update the signed_by field of the specified report
|
|
||||||
_, err = d.Exec("UPDATE weekly_reports SET signed_by = NULL WHERE report_id = ?;", reportId)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *Db) GetUnsignedWeeklyReports(projectName string) ([]types.WeeklyReport, error) {
|
func (d *Db) GetUnsignedWeeklyReports(projectName string) ([]types.WeeklyReport, error) {
|
||||||
// Define the SQL query to fetch unsigned reports for a given user
|
// Define the SQL query to fetch unsigned reports for a given user
|
||||||
query := `
|
query := `
|
||||||
|
@ -483,7 +423,7 @@ func (d *Db) IsSiteAdmin(username string) (bool, error) {
|
||||||
|
|
||||||
// Reads a directory of migration files and applies them to the database.
|
// Reads a directory of migration files and applies them to the database.
|
||||||
// This will eventually be used on an embedded directory
|
// This will eventually be used on an embedded directory
|
||||||
func Migrate(db sqlx.DB) error {
|
func (d *Db) Migrate() error {
|
||||||
// Read the embedded scripts directory
|
// Read the embedded scripts directory
|
||||||
files, err := scripts.ReadDir("migrations")
|
files, err := scripts.ReadDir("migrations")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -495,7 +435,7 @@ func Migrate(db sqlx.DB) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
tr := db.MustBegin()
|
tr := d.MustBegin()
|
||||||
|
|
||||||
// Iterate over each SQL file and execute it
|
// Iterate over each SQL file and execute it
|
||||||
for _, file := range files {
|
for _, file := range files {
|
||||||
|
@ -523,8 +463,8 @@ func Migrate(db sqlx.DB) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetAllWeeklyReports retrieves weekly reports for a specific user and project.
|
// GetWeeklyReportsUser retrieves weekly reports for a specific user and project.
|
||||||
func (d *Db) GetAllWeeklyReports(username string, projectName string) ([]types.WeeklyReportList, error) {
|
func (d *Db) GetWeeklyReportsUser(username string, projectName string) ([]types.WeeklyReportList, error) {
|
||||||
query := `
|
query := `
|
||||||
SELECT
|
SELECT
|
||||||
wr.week,
|
wr.week,
|
||||||
|
@ -581,7 +521,7 @@ func (d *Db) UpdateWeeklyReport(projectName string, userName string, week int, d
|
||||||
}
|
}
|
||||||
|
|
||||||
// MigrateSampleData applies sample data to the database.
|
// MigrateSampleData applies sample data to the database.
|
||||||
func MigrateSampleData(db sqlx.DB) error {
|
func (d *Db) MigrateSampleData() error {
|
||||||
// Insert sample data
|
// Insert sample data
|
||||||
files, err := sampleData.ReadDir("sample_data")
|
files, err := sampleData.ReadDir("sample_data")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -591,7 +531,7 @@ func MigrateSampleData(db sqlx.DB) error {
|
||||||
if len(files) == 0 {
|
if len(files) == 0 {
|
||||||
println("No sample data files found")
|
println("No sample data files found")
|
||||||
}
|
}
|
||||||
tr := db.MustBegin()
|
tr := d.MustBegin()
|
||||||
|
|
||||||
// Iterate over each SQL file and execute it
|
// Iterate over each SQL file and execute it
|
||||||
for _, file := range files {
|
for _, file := range files {
|
||||||
|
@ -628,7 +568,7 @@ func (d *Db) GetProjectTimes(projectName string) (map[string]int, error) {
|
||||||
WHERE projects.name = ?
|
WHERE projects.name = ?
|
||||||
`
|
`
|
||||||
|
|
||||||
rows, err := d.Query(query, projectName)
|
rows, err := d.DB.Query(query, projectName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -661,25 +601,3 @@ func (d *Db) RemoveProject(projectname string) error {
|
||||||
_, err := d.Exec("DELETE FROM projects WHERE name = ?", projectname)
|
_, err := d.Exec("DELETE FROM projects WHERE name = ?", projectname)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Db) GetUserName(id int) (string, error) {
|
|
||||||
var username string
|
|
||||||
err := d.Get(&username, "SELECT username FROM users WHERE id = ?", id)
|
|
||||||
return username, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *Db) DeleteReport(reportID int) error {
|
|
||||||
_, err := d.Exec("DELETE FROM weekly_reports WHERE report_id = ?", reportID)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// ChangeProjectName is a handler that changes the name of a project
|
|
||||||
func (d *Db) ChangeProjectName(projectName string, newProjectName string) error {
|
|
||||||
_, err := d.Exec("UPDATE projects SET name = ? WHERE name = ?", newProjectName, projectName)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *Db) ChangeUserPassword(username string, password string) error {
|
|
||||||
_, err := d.Exec("UPDATE users SET password = ? WHERE username = ?", password, username)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
|
@ -9,13 +9,11 @@ import (
|
||||||
// setupState initializes a database instance with necessary setup for testing
|
// setupState initializes a database instance with necessary setup for testing
|
||||||
func setupState() (Database, error) {
|
func setupState() (Database, error) {
|
||||||
db := DbConnect(":memory:")
|
db := DbConnect(":memory:")
|
||||||
err := Migrate(db)
|
err := db.Migrate()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
return db, nil
|
||||||
db_iface := Db{db.MustBegin()}
|
|
||||||
return &db_iface, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// This is a more advanced setup that includes more data in the database.
|
// This is a more advanced setup that includes more data in the database.
|
||||||
|
@ -585,94 +583,6 @@ func TestSignWeeklyReport(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestUnsignWeeklyReport(t *testing.T) {
|
|
||||||
db, err := setupState()
|
|
||||||
if err != nil {
|
|
||||||
t.Error("setupState failed:", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add project manager
|
|
||||||
err = db.AddUser("projectManager", "password")
|
|
||||||
if err != nil {
|
|
||||||
t.Error("AddUser failed:", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add a regular user
|
|
||||||
err = db.AddUser("testuser", "password")
|
|
||||||
if err != nil {
|
|
||||||
t.Error("AddUser failed:", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add project
|
|
||||||
err = db.AddProject("testproject", "description", "projectManager")
|
|
||||||
if err != nil {
|
|
||||||
t.Error("AddProject failed:", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add both regular users as members to the project
|
|
||||||
err = db.AddUserToProject("testuser", "testproject", "member")
|
|
||||||
if err != nil {
|
|
||||||
t.Error("AddUserToProject failed:", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = db.AddUserToProject("projectManager", "testproject", "project_manager")
|
|
||||||
if err != nil {
|
|
||||||
t.Error("AddUserToProject failed:", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add a weekly report for one of the regular users
|
|
||||||
err = db.AddWeeklyReport("testproject", "testuser", 1, 1, 1, 1, 1, 1, 1)
|
|
||||||
if err != nil {
|
|
||||||
t.Error("AddWeeklyReport failed:", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Retrieve the added report
|
|
||||||
report, err := db.GetWeeklyReport("testuser", "testproject", 1)
|
|
||||||
if err != nil {
|
|
||||||
t.Error("GetWeeklyReport failed:", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Print project manager's ID
|
|
||||||
projectManagerID, err := db.GetUserId("projectManager")
|
|
||||||
if err != nil {
|
|
||||||
t.Error("GetUserId failed:", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sign the report with the project manager
|
|
||||||
err = db.SignWeeklyReport(report.ReportId, projectManagerID)
|
|
||||||
if err != nil {
|
|
||||||
t.Error("SignWeeklyReport failed:", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Retrieve the report again to check if it's signed
|
|
||||||
signedReport, err := db.GetWeeklyReport("testuser", "testproject", 1)
|
|
||||||
if err != nil {
|
|
||||||
t.Error("GetWeeklyReport failed:", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ensure the report is signed by the project manager
|
|
||||||
if *signedReport.SignedBy != projectManagerID {
|
|
||||||
t.Errorf("Expected SignedBy to be %d, got %d", projectManagerID, *signedReport.SignedBy)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Unsign the report
|
|
||||||
err = db.UnsignWeeklyReport(report.ReportId, projectManagerID)
|
|
||||||
if err != nil {
|
|
||||||
t.Error("UnsignWeeklyReport failed:", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Retrieve the report again to check if it's unsigned
|
|
||||||
unsignedReport, err := db.GetWeeklyReport("testuser", "testproject", 1)
|
|
||||||
if err != nil {
|
|
||||||
t.Error("GetWeeklyReport failed:", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ensure the report is unsigned
|
|
||||||
if unsignedReport.SignedBy != nil {
|
|
||||||
t.Error("Expected SignedBy to be nil, got", unsignedReport.SignedBy)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TestSignWeeklyReportByAnotherProjectManager tests the scenario where a project manager attempts to sign a weekly report for a user who is not assigned to their project
|
// TestSignWeeklyReportByAnotherProjectManager tests the scenario where a project manager attempts to sign a weekly report for a user who is not assigned to their project
|
||||||
func TestSignWeeklyReportByAnotherProjectManager(t *testing.T) {
|
func TestSignWeeklyReportByAnotherProjectManager(t *testing.T) {
|
||||||
db, err := setupState()
|
db, err := setupState()
|
||||||
|
@ -795,7 +705,7 @@ func TestGetWeeklyReportsUser(t *testing.T) {
|
||||||
t.Error("AddWeeklyReport failed:", err)
|
t.Error("AddWeeklyReport failed:", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
reports, err := db.GetAllWeeklyReports("testuser", "testproject")
|
reports, err := db.GetWeeklyReportsUser("testuser", "testproject")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Error("GetWeeklyReportsUser failed:", err)
|
t.Error("GetWeeklyReportsUser failed:", err)
|
||||||
}
|
}
|
||||||
|
@ -1055,90 +965,3 @@ func TestRemoveProject(t *testing.T) {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestDeleteReport(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)
|
|
||||||
}
|
|
||||||
|
|
||||||
// create a weekly report
|
|
||||||
err = db.AddWeeklyReport("projecttest", "demouser", 16, 1, 1, 1, 1, 1, 1)
|
|
||||||
if err != nil {
|
|
||||||
t.Error("AddWeeklyReport failed:", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if the report was added
|
|
||||||
report, err := db.GetWeeklyReport("demouser", "projecttest", 16)
|
|
||||||
if err != nil {
|
|
||||||
t.Error("GetWeeklyReport failed:", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove report
|
|
||||||
err = db.DeleteReport(report.ReportId)
|
|
||||||
if err != nil {
|
|
||||||
t.Error("RemoveReport failed:", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if the report was removed
|
|
||||||
report, err = db.GetWeeklyReport("demouser", "projecttest", 16)
|
|
||||||
if err == nil {
|
|
||||||
t.Error("RemoveReport failed: report not removed")
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestChangeProjectName(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)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Change project name
|
|
||||||
err = db.ChangeProjectName("projecttest", "newprojectname")
|
|
||||||
if err != nil {
|
|
||||||
t.Error("ChangeProjectName failed:", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if the project name was changed
|
|
||||||
projects, err := db.GetAllProjects()
|
|
||||||
if err != nil {
|
|
||||||
t.Error("GetAllProjects failed:", err)
|
|
||||||
}
|
|
||||||
if projects[0].Name != "newprojectname" {
|
|
||||||
t.Error("ChangeProjectName failed: expected newprojectname, got", projects[0].Name)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestChangeUserPassword(t *testing.T) {
|
|
||||||
db, err := setupState()
|
|
||||||
if err != nil {
|
|
||||||
t.Error("setupState failed:", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add a user
|
|
||||||
_ = db.AddUser("testuser", "password")
|
|
||||||
|
|
||||||
// Change user password
|
|
||||||
err = db.ChangeUserPassword("testuser", "newpassword")
|
|
||||||
if err != nil {
|
|
||||||
t.Error("ChangeUserPassword failed:", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if the password was changed
|
|
||||||
if !db.CheckUser("testuser", "newpassword") {
|
|
||||||
t.Error("ChangeUserPassword failed: password not changed")
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,28 +1,11 @@
|
||||||
package database
|
package database
|
||||||
|
|
||||||
import (
|
import "github.com/gofiber/fiber/v2"
|
||||||
"github.com/gofiber/fiber/v2"
|
|
||||||
"github.com/gofiber/fiber/v2/log"
|
|
||||||
"github.com/jmoiron/sqlx"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Simple middleware that provides a transaction as a local key "db"
|
// Simple middleware that provides a shared database pool as a local key "db"
|
||||||
func DbMiddleware(db *sqlx.DB) func(c *fiber.Ctx) error {
|
func DbMiddleware(db *Database) func(c *fiber.Ctx) error {
|
||||||
return func(c *fiber.Ctx) error {
|
return func(c *fiber.Ctx) error {
|
||||||
tx := db.MustBegin()
|
c.Locals("db", db)
|
||||||
|
|
||||||
defer func() {
|
|
||||||
if err := tx.Commit(); err != nil {
|
|
||||||
if err = tx.Rollback(); err != nil {
|
|
||||||
log.Error("Failed to rollback transaction: ", err)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
var db_iface Database = &Db{tx}
|
|
||||||
|
|
||||||
c.Locals("db", &db_iface)
|
|
||||||
return c.Next()
|
return c.Next()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,220 +1,52 @@
|
||||||
INSERT OR IGNORE INTO users(username, password)
|
INSERT OR IGNORE INTO users(username, password)
|
||||||
VALUES ("admin", "123"),
|
VALUES ("admin", "123");
|
||||||
("user", "123"),
|
|
||||||
("user2", "123"),
|
|
||||||
("John", "123"),
|
|
||||||
("Emma", "123"),
|
|
||||||
("Michael", "123"),
|
|
||||||
("Liam", "123"),
|
|
||||||
("Oliver", "123"),
|
|
||||||
("Amelia", "123"),
|
|
||||||
("Benjamin", "123"),
|
|
||||||
("Mia", "123"),
|
|
||||||
("Elijah", "123"),
|
|
||||||
("Charlotte", "123"),
|
|
||||||
("Henry", "123"),
|
|
||||||
("Harper", "123"),
|
|
||||||
("Lucas", "123"),
|
|
||||||
("Emily", "123"),
|
|
||||||
("Alexander", "123"),
|
|
||||||
("Daniel", "123"),
|
|
||||||
("Ella", "123"),
|
|
||||||
("Matthew", "123"),
|
|
||||||
("Madison", "123"),
|
|
||||||
("Samuel", "123"),
|
|
||||||
("Avery", "123"),
|
|
||||||
("Sofia", "123"),
|
|
||||||
("David", "123"),
|
|
||||||
("Victoria", "123"),
|
|
||||||
("Jackson", "123"),
|
|
||||||
("Abigail", "123"),
|
|
||||||
("Gabriel", "123"),
|
|
||||||
("Luna", "123"),
|
|
||||||
("Wyatt", "123"),
|
|
||||||
("Chloe", "123"),
|
|
||||||
("Nora", "123"),
|
|
||||||
("Joshua", "123"),
|
|
||||||
("Hazel", "123"),
|
|
||||||
("Riley", "123"),
|
|
||||||
("Scarlett", "123"),
|
|
||||||
("Aria", "123"),
|
|
||||||
("Carter", "123"),
|
|
||||||
("Grace", "123"),
|
|
||||||
("Jayden", "123"),
|
|
||||||
("Hannah", "123"),
|
|
||||||
("Zoe", "123"),
|
|
||||||
("Luke", "123"),
|
|
||||||
("Sophia", "123"),
|
|
||||||
("Jack", "123"),
|
|
||||||
("Isabella", "123"),
|
|
||||||
("William", "123"),
|
|
||||||
("Mason", "123"),
|
|
||||||
("Evelyn", "123"),
|
|
||||||
("James", "123"),
|
|
||||||
("Cynthia", "123"),
|
|
||||||
("Abraham", "123"),
|
|
||||||
("Ava", "123"),
|
|
||||||
("Aiden", "123"),
|
|
||||||
("Natalie", "123"),
|
|
||||||
("Lily", "123"),
|
|
||||||
("Olivia", "123"),
|
|
||||||
("Alexander", "123"),
|
|
||||||
("Ethan", "123"),
|
|
||||||
("Mila", "123"),
|
|
||||||
("Evelyn", "123"),
|
|
||||||
("Logan", "123"),
|
|
||||||
("Riley", "123"),
|
|
||||||
("Grace", "123"),
|
|
||||||
("Arnold", "123"),
|
|
||||||
("Connor", "123"),
|
|
||||||
("Samantha", "123"),
|
|
||||||
("Emma", "123"),
|
|
||||||
("Sarah", "123"),
|
|
||||||
("Nathan", "123"),
|
|
||||||
("Layla", "123"),
|
|
||||||
("Ryan", "123"),
|
|
||||||
("Zoey", "123"),
|
|
||||||
("Megan", "123"),
|
|
||||||
("Christian", "123"),
|
|
||||||
("Eva", "123"),
|
|
||||||
("Isaac", "123"),
|
|
||||||
("Michaela", "123"),
|
|
||||||
("Caroline", "123"),
|
|
||||||
("Elijah", "123"),
|
|
||||||
("Elena", "123"),
|
|
||||||
("Julian", "123"),
|
|
||||||
("Sophie", "123"),
|
|
||||||
("Gabriella", "123"),
|
|
||||||
("Cole", "123"),
|
|
||||||
("Hannah", "123"),
|
|
||||||
("Lucy", "123"),
|
|
||||||
("Katherine", "123"),
|
|
||||||
("Benjamin", "123"),
|
|
||||||
("Ella", "123"),
|
|
||||||
("Evan", "123");
|
|
||||||
|
|
||||||
INSERT OR IGNORE INTO projects(name, description, owner_user_id)
|
INSERT OR IGNORE INTO users(username, password)
|
||||||
VALUES ("projecttest1", "Description for projecttest1", 1),
|
VALUES ("user", "123");
|
||||||
("projecttest2", "Description for projecttest2", 1),
|
|
||||||
("projecttest3", "Description for projecttest3", 1),
|
|
||||||
("projecttest4", "Description for projecttest4", 1),
|
|
||||||
("projecttest5", "Description for projecttest5", 1),
|
|
||||||
("projecttest6", "Description for projecttest6", 1),
|
|
||||||
("projecttest7", "Description for projecttest7", 1),
|
|
||||||
("projecttest8", "Description for projecttest8", 1),
|
|
||||||
("projecttest9", "Description for projecttest9", 1),
|
|
||||||
("projecttest10", "Description for projecttest10", 1),
|
|
||||||
("projecttest11", "Description for projecttest11", 1),
|
|
||||||
("projecttest12", "Description for projecttest12", 1),
|
|
||||||
("projecttest13", "Description for projecttest13", 1),
|
|
||||||
("projecttest14", "Description for projecttest14", 1),
|
|
||||||
("projecttest15", "Description for projecttest15", 1);
|
|
||||||
|
|
||||||
INSERT OR IGNORE INTO user_roles(user_id,project_id,p_role)
|
INSERT OR IGNORE INTO users(username, password)
|
||||||
VALUES (1,1,"project_manager"),
|
VALUES ("user2", "123");
|
||||||
(1,2,"project_manager"),
|
|
||||||
(1,3,"project_manager"),
|
|
||||||
(1,4,"project_manager"),
|
|
||||||
(1,5,"project_manager"),
|
|
||||||
(1,6,"project_manager"),
|
|
||||||
(1,7,"project_manager"),
|
|
||||||
(1,8,"project_manager"),
|
|
||||||
(1,9,"project_manager"),
|
|
||||||
(1,10,"project_manager"),
|
|
||||||
(1,11,"project_manager"),
|
|
||||||
(1,12,"project_manager"),
|
|
||||||
(1,13,"project_manager"),
|
|
||||||
(1,14,"project_manager"),
|
|
||||||
(1,15,"project_manager"),
|
|
||||||
(2,1,"project_manager"),
|
|
||||||
(2,2,"member"),
|
|
||||||
(2,3,"member"),
|
|
||||||
(2,4,"member"),
|
|
||||||
(2,5,"member"),
|
|
||||||
(2,6,"member"),
|
|
||||||
(2,7,"member"),
|
|
||||||
(2,8,"member"),
|
|
||||||
(2,9,"member"),
|
|
||||||
(2,10,"member"),
|
|
||||||
(2,11,"member"),
|
|
||||||
(2,12,"member"),
|
|
||||||
(2,13,"member"),
|
|
||||||
(2,14,"member"),
|
|
||||||
(2,15,"member"),
|
|
||||||
(3,1,"member"),
|
|
||||||
(3,2,"member"),
|
|
||||||
(3,3,"member"),
|
|
||||||
(3,4,"member"),
|
|
||||||
(3,5,"member"),
|
|
||||||
(3,6,"member"),
|
|
||||||
(3,7,"member"),
|
|
||||||
(3,8,"member"),
|
|
||||||
(3,9,"member"),
|
|
||||||
(3,10,"member"),
|
|
||||||
(3,11,"member"),
|
|
||||||
(3,12,"member"),
|
|
||||||
(3,13,"member"),
|
|
||||||
(3,14,"member"),
|
|
||||||
(3,15,"member"),
|
|
||||||
(4,1,"member"),
|
|
||||||
(4,2,"member"),
|
|
||||||
(4,3,"member"),
|
|
||||||
(4,4,"member"),
|
|
||||||
(4,5,"member"),
|
|
||||||
(4,6,"member"),
|
|
||||||
(4,7,"member"),
|
|
||||||
(4,8,"member"),
|
|
||||||
(4,9,"member"),
|
|
||||||
(4,10,"member"),
|
|
||||||
(4,11,"member"),
|
|
||||||
(4,12,"member"),
|
|
||||||
(4,13,"member"),
|
|
||||||
(4,14,"member"),
|
|
||||||
(4,15,"member"),
|
|
||||||
(5,1,"member"),
|
|
||||||
(5,2,"member"),
|
|
||||||
(5,3,"member"),
|
|
||||||
(5,4,"member"),
|
|
||||||
(5,5,"member"),
|
|
||||||
(5,6,"member"),
|
|
||||||
(5,7,"member"),
|
|
||||||
(5,8,"member"),
|
|
||||||
(5,9,"member"),
|
|
||||||
(5,10,"member"),
|
|
||||||
(5,11,"member"),
|
|
||||||
(5,12,"member"),
|
|
||||||
(5,13,"member"),
|
|
||||||
(5,14,"member"),
|
|
||||||
(5,15,"member");
|
|
||||||
|
|
||||||
INSERT OR IGNORE INTO weekly_reports (user_id, project_id, week, development_time, meeting_time, admin_time, own_work_time, study_time, testing_time, signed_by)
|
|
||||||
VALUES (2, 1, 12, 100, 50, 30, 150, 80, 20, NULL),
|
|
||||||
(3, 1, 12, 200, 80, 20, 200, 100, 30, NULL),
|
|
||||||
(3, 1, 14, 150, 70, 40, 180, 90, 25, NULL),
|
|
||||||
(3, 2, 12, 120, 60, 35, 160, 85, 15, NULL),
|
|
||||||
(3, 3, 12, 180, 90, 25, 190, 110, 40, NULL),
|
|
||||||
(2, 1, 13, 130, 70, 40, 170, 95, 35, NULL),
|
|
||||||
(3, 1, 15, 140, 60, 50, 200, 120, 30, NULL),
|
|
||||||
(2, 2, 11, 110, 50, 45, 140, 70, 25, NULL),
|
|
||||||
(3, 3, 14, 170, 80, 30, 180, 100, 35, NULL),
|
|
||||||
(3, 3, 15, 200, 100, 20, 220, 130, 45, NULL),
|
|
||||||
(2, 4, 12, 120, 60, 40, 160, 80, 30, NULL),
|
|
||||||
(3, 5, 14, 150, 70, 30, 180, 90, 25, NULL),
|
|
||||||
(3, 5, 15, 180, 90, 20, 190, 110, 35, NULL),
|
|
||||||
(2, 6, 11, 100, 50, 35, 130, 60, 20, NULL),
|
|
||||||
(3, 7, 14, 170, 80, 25, 180, 100, 30, NULL),
|
|
||||||
(2, 8, 12, 130, 70, 30, 170, 90, 25, NULL),
|
|
||||||
(2, 8, 13, 150, 80, 20, 180, 110, 35, NULL),
|
|
||||||
(3, 9, 12, 140, 60, 40, 180, 100, 30, NULL),
|
|
||||||
(3, 10, 11, 120, 50, 45, 150, 70, 25, NULL),
|
|
||||||
(2, 11, 13, 110, 60, 35, 140, 80, 30, NULL),
|
|
||||||
(3, 12, 12, 160, 70, 30, 180, 100, 35, NULL),
|
|
||||||
(3, 12, 13, 180, 90, 25, 190, 110, 40, NULL),
|
|
||||||
(3, 12, 14, 200, 100, 20, 220, 130, 45, NULL),
|
|
||||||
(2, 13, 11, 100, 50, 45, 130, 60, 20, NULL),
|
|
||||||
(2, 13, 12, 120, 60, 40, 160, 80, 30, NULL),
|
|
||||||
(3, 14, 13, 140, 70, 30, 160, 90, 35, NULL),
|
|
||||||
(3, 15, 12, 150, 80, 25, 180, 100, 30, NULL),
|
|
||||||
(3, 15, 13, 170, 90, 20, 190, 110, 35, NULL);
|
|
||||||
|
|
||||||
INSERT OR IGNORE INTO site_admin VALUES (1);
|
INSERT OR IGNORE INTO site_admin VALUES (1);
|
||||||
|
|
||||||
|
INSERT OR IGNORE INTO projects(name,description,owner_user_id)
|
||||||
|
VALUES ("projecttest","test project", 1);
|
||||||
|
|
||||||
|
INSERT OR IGNORE INTO projects(name,description,owner_user_id)
|
||||||
|
VALUES ("projecttest2","test project2", 1);
|
||||||
|
|
||||||
|
INSERT OR IGNORE INTO projects(name,description,owner_user_id)
|
||||||
|
VALUES ("projecttest3","test project3", 1);
|
||||||
|
|
||||||
|
INSERT OR IGNORE INTO user_roles(user_id,project_id,p_role)
|
||||||
|
VALUES (1,1,"project_manager");
|
||||||
|
|
||||||
|
INSERT OR IGNORE INTO user_roles(user_id,project_id,p_role)
|
||||||
|
VALUES (2,1,"member");
|
||||||
|
|
||||||
|
INSERT OR IGNORE INTO user_roles(user_id,project_id,p_role)
|
||||||
|
VALUES (3,1,"member");
|
||||||
|
|
||||||
|
INSERT OR IGNORE INTO user_roles(user_id,project_id,p_role)
|
||||||
|
VALUES (3,2,"member");
|
||||||
|
|
||||||
|
INSERT OR IGNORE INTO user_roles(user_id,project_id,p_role)
|
||||||
|
VALUES (3,3,"member");
|
||||||
|
|
||||||
|
INSERT OR IGNORE INTO user_roles(user_id,project_id,p_role)
|
||||||
|
VALUES (2,1,"project_manager");
|
||||||
|
|
||||||
|
INSERT OR IGNORE INTO weekly_reports (user_id, project_id, week, development_time, meeting_time, admin_time, own_work_time, study_time, testing_time, signed_by)
|
||||||
|
VALUES (2, 1, 12, 20, 10, 5, 30, 15, 10, NULL);
|
||||||
|
|
||||||
|
INSERT OR IGNORE INTO weekly_reports (user_id, project_id, week, development_time, meeting_time, admin_time, own_work_time, study_time, testing_time, signed_by)
|
||||||
|
VALUES (3, 1, 12, 20, 10, 5, 30, 15, 10, NULL);
|
||||||
|
|
||||||
|
INSERT OR IGNORE INTO weekly_reports (user_id, project_id, week, development_time, meeting_time, admin_time, own_work_time, study_time, testing_time, signed_by)
|
||||||
|
VALUES (3, 1, 14, 20, 10, 5, 30, 15, 10, NULL);
|
||||||
|
|
||||||
|
INSERT OR IGNORE INTO weekly_reports (user_id, project_id, week, development_time, meeting_time, admin_time, own_work_time, study_time, testing_time, signed_by)
|
||||||
|
VALUES (3, 2, 12, 20, 10, 5, 30, 15, 10, NULL);
|
||||||
|
|
||||||
|
INSERT OR IGNORE INTO weekly_reports (user_id, project_id, week, development_time, meeting_time, admin_time, own_work_time, study_time, testing_time, signed_by)
|
||||||
|
VALUES (3, 3, 12, 20, 10, 5, 30, 15, 10, NULL);
|
||||||
|
|
|
@ -10,33 +10,42 @@ import (
|
||||||
|
|
||||||
// AddUserToProjectHandler is a handler that adds a user to a project with a specified role
|
// AddUserToProjectHandler is a handler that adds a user to a project with a specified role
|
||||||
func AddUserToProjectHandler(c *fiber.Ctx) error {
|
func AddUserToProjectHandler(c *fiber.Ctx) error {
|
||||||
|
// Extract necessary parameters from the request
|
||||||
|
var requestData struct {
|
||||||
|
Username string `json:"username"`
|
||||||
|
ProjectName string `json:"projectName"`
|
||||||
|
Role string `json:"role"`
|
||||||
|
}
|
||||||
|
if err := c.BodyParser(&requestData); err != nil {
|
||||||
|
log.Info("Error parsing request body:", err)
|
||||||
|
return c.Status(400).SendString("Bad request")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if the user adding another user to the project is a site admin
|
||||||
user := c.Locals("user").(*jwt.Token)
|
user := c.Locals("user").(*jwt.Token)
|
||||||
claims := user.Claims.(jwt.MapClaims)
|
claims := user.Claims.(jwt.MapClaims)
|
||||||
pm_name := claims["name"].(string)
|
adminUsername := claims["name"].(string)
|
||||||
|
log.Info("Admin username from claims:", adminUsername)
|
||||||
|
|
||||||
project := c.Params("projectName")
|
isAdmin, err := db.GetDb(c).IsSiteAdmin(adminUsername)
|
||||||
username := c.Query("userName")
|
|
||||||
|
|
||||||
// Check if the user is a project manager
|
|
||||||
isPM, err := db.GetDb(c).IsProjectManager(pm_name, project)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Info("Error checking if user is project manager:", err)
|
log.Info("Error checking admin status:", err)
|
||||||
return c.Status(500).SendString(err.Error())
|
return c.Status(500).SendString(err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
if !isPM {
|
if !isAdmin {
|
||||||
log.Info("User: ", pm_name, " is not a project manager in project: ", project)
|
log.Info("User is not a site admin:", adminUsername)
|
||||||
return c.Status(403).SendString("User is not a project manager")
|
return c.Status(403).SendString("User is not a site admin")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add the user to the project with the specified role
|
// Add the user to the project with the specified role
|
||||||
err = db.GetDb(c).AddUserToProject(username, project, "member")
|
err = db.GetDb(c).AddUserToProject(requestData.Username, requestData.ProjectName, requestData.Role)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Info("Error adding user to project:", err)
|
log.Info("Error adding user to project:", err)
|
||||||
return c.Status(500).SendString(err.Error())
|
return c.Status(500).SendString(err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
// Return success message
|
// Return success message
|
||||||
log.Info("User : ", username, " added to project: ", project)
|
log.Info("User added to project successfully:", requestData.Username)
|
||||||
return c.SendStatus(fiber.StatusOK)
|
return c.SendStatus(fiber.StatusOK)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,43 +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"
|
|
||||||
)
|
|
||||||
|
|
||||||
// ChangeProjectName is a handler that changes the name of a project
|
|
||||||
func ChangeProjectName(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
|
|
||||||
projectName := c.Params("projectName")
|
|
||||||
newProjectName := c.Query("newProjectName")
|
|
||||||
|
|
||||||
// Check if user is site admin
|
|
||||||
issiteadmin, err := db.GetDb(c).IsSiteAdmin(username)
|
|
||||||
if err != nil {
|
|
||||||
log.Warn("Error checking if siteadmin:", err)
|
|
||||||
return c.Status(500).SendString(err.Error())
|
|
||||||
} else if !issiteadmin {
|
|
||||||
log.Warn("User is not siteadmin")
|
|
||||||
return c.Status(401).SendString("User is not siteadmin")
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// Perform the project name change
|
|
||||||
err = db.GetDb(c).ChangeProjectName(projectName, newProjectName)
|
|
||||||
if err != nil {
|
|
||||||
log.Warn("Error changing project name:", err)
|
|
||||||
return c.Status(500).SendString(err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
// Return a success message
|
|
||||||
return c.Status(200).SendString("Project name changed successfully")
|
|
||||||
}
|
|
|
@ -4,16 +4,15 @@ import (
|
||||||
db "ttime/internal/database"
|
db "ttime/internal/database"
|
||||||
|
|
||||||
"github.com/gofiber/fiber/v2"
|
"github.com/gofiber/fiber/v2"
|
||||||
"github.com/gofiber/fiber/v2/log"
|
"github.com/golang-jwt/jwt/v5"
|
||||||
)
|
)
|
||||||
|
|
||||||
// GetUserProjects returns all projects that the user is a member of
|
// GetUserProjects returns all projects that the user is a member of
|
||||||
func GetUserProjects(c *fiber.Ctx) error {
|
func GetUserProjects(c *fiber.Ctx) error {
|
||||||
username := c.Params("username")
|
// First we get the username from the token
|
||||||
if username == "" {
|
user := c.Locals("user").(*jwt.Token)
|
||||||
log.Info("No username provided")
|
claims := user.Claims.(jwt.MapClaims)
|
||||||
return c.Status(400).SendString("No username provided")
|
username := claims["name"].(string)
|
||||||
}
|
|
||||||
|
|
||||||
// Then dip into the database to get the projects
|
// Then dip into the database to get the projects
|
||||||
projects, err := db.GetDb(c).GetProjectsForUser(username)
|
projects, err := db.GetDb(c).GetProjectsForUser(username)
|
||||||
|
|
|
@ -24,13 +24,7 @@ func ProjectRoleChange(c *fiber.Ctx) error {
|
||||||
return c.Status(400).SendString(err.Error())
|
return c.Status(400).SendString(err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if user is trying to change its own role
|
log.Info("Changing role for user: ", username, " in project: ", data.Projectname, " to: ", data.Role)
|
||||||
if username == data.UserName {
|
|
||||||
log.Info("Can't change your own role")
|
|
||||||
return c.Status(403).SendString("Can't change your own role")
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Info("Changing role for user: ", data.UserName, " in project: ", data.Projectname, " to: ", data.Role)
|
|
||||||
|
|
||||||
// Dubble diping and checcking if current user is
|
// Dubble diping and checcking if current user is
|
||||||
if ismanager, err := db.GetDb(c).IsProjectManager(username, data.Projectname); err != nil {
|
if ismanager, err := db.GetDb(c).IsProjectManager(username, data.Projectname); err != nil {
|
||||||
|
@ -42,7 +36,7 @@ func ProjectRoleChange(c *fiber.Ctx) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Change the user's role within the project in the database
|
// Change the user's role within the project in the database
|
||||||
if err := db.GetDb(c).ChangeUserRole(data.UserName, data.Projectname, data.Role); err != nil {
|
if err := db.GetDb(c).ChangeUserRole(username, data.Projectname, data.Role); err != nil {
|
||||||
return c.Status(500).SendString(err.Error())
|
return c.Status(500).SendString(err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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"
|
|
||||||
)
|
|
||||||
|
|
||||||
// @Summary Promote to project manager
|
|
||||||
// @Description Promote a user to project manager
|
|
||||||
// @Tags Auth
|
|
||||||
// @Security JWT
|
|
||||||
// @Accept plain
|
|
||||||
// @Produce plain
|
|
||||||
// @Param projectName path string true "Project name"
|
|
||||||
// @Param userName query string true "User name"
|
|
||||||
// @Failure 500 {string} string "Internal server error"
|
|
||||||
// @Failure 403 {string} string "Forbidden"
|
|
||||||
// @Router /promote/{projectName} [put]
|
|
||||||
//
|
|
||||||
// Login logs in a user and returns a JWT token
|
|
||||||
// Promote to project manager
|
|
||||||
func PromoteToPm(c *fiber.Ctx) error {
|
|
||||||
user := c.Locals("user").(*jwt.Token)
|
|
||||||
claims := user.Claims.(jwt.MapClaims)
|
|
||||||
pm_name := claims["name"].(string)
|
|
||||||
|
|
||||||
project := c.Params("projectName")
|
|
||||||
new_pm_name := c.Query("userName")
|
|
||||||
|
|
||||||
// Check if the user is a project manager
|
|
||||||
isPM, err := db.GetDb(c).IsProjectManager(pm_name, project)
|
|
||||||
if err != nil {
|
|
||||||
log.Info("Error checking if user is project manager:", err)
|
|
||||||
return c.Status(500).SendString(err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
if !isPM {
|
|
||||||
log.Info("User: ", pm_name, " is not a project manager in project: ", project)
|
|
||||||
return c.Status(403).SendString("User is not a project manager")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add the user to the project with the specified role
|
|
||||||
err = db.GetDb(c).ChangeUserRole(new_pm_name, project, "project_manager")
|
|
||||||
if err != nil {
|
|
||||||
log.Info("Error promoting user to project manager:", err)
|
|
||||||
return c.Status(500).SendString(err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
// Return success message
|
|
||||||
log.Info("User : ", new_pm_name, " promoted to project manager in project: ", project)
|
|
||||||
return c.SendStatus(fiber.StatusOK)
|
|
||||||
}
|
|
|
@ -1,40 +0,0 @@
|
||||||
package projects
|
|
||||||
|
|
||||||
import (
|
|
||||||
db "ttime/internal/database"
|
|
||||||
|
|
||||||
"github.com/gofiber/fiber/v2"
|
|
||||||
"github.com/gofiber/fiber/v2/log"
|
|
||||||
"github.com/golang-jwt/jwt/v5"
|
|
||||||
)
|
|
||||||
|
|
||||||
func RemoveUserFromProject(c *fiber.Ctx) error {
|
|
||||||
user := c.Locals("user").(*jwt.Token)
|
|
||||||
claims := user.Claims.(jwt.MapClaims)
|
|
||||||
pm_name := claims["name"].(string)
|
|
||||||
|
|
||||||
project := c.Params("projectName")
|
|
||||||
username := c.Query("userName")
|
|
||||||
|
|
||||||
// Check if the user is a project manager
|
|
||||||
isPM, err := db.GetDb(c).IsProjectManager(pm_name, project)
|
|
||||||
if err != nil {
|
|
||||||
log.Info("Error checking if user is project manager:", err)
|
|
||||||
return c.Status(500).SendString(err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
if !isPM {
|
|
||||||
log.Info("User: ", pm_name, " is not a project manager in project: ", project)
|
|
||||||
return c.Status(403).SendString("User is not a project manager")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove the user from the project
|
|
||||||
if err = db.GetDb(c).RemoveUserFromProject(username, project); err != nil {
|
|
||||||
log.Info("Error removing user from project:", err)
|
|
||||||
return c.Status(500).SendString(err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
// Return success message
|
|
||||||
log.Info("User : ", username, " removed from project: ", project)
|
|
||||||
return c.SendStatus(fiber.StatusOK)
|
|
||||||
}
|
|
|
@ -1,22 +0,0 @@
|
||||||
package reports
|
|
||||||
|
|
||||||
import (
|
|
||||||
"strconv"
|
|
||||||
db "ttime/internal/database"
|
|
||||||
|
|
||||||
"github.com/gofiber/fiber/v2"
|
|
||||||
)
|
|
||||||
|
|
||||||
func DeleteReport(c *fiber.Ctx) error {
|
|
||||||
reportID := c.Params("reportID")
|
|
||||||
reportIDInt, err := strconv.Atoi(reportID)
|
|
||||||
if err != nil {
|
|
||||||
return c.Status(400).SendString("Invalid report ID")
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := db.GetDb(c).DeleteReport(reportIDInt); err != nil {
|
|
||||||
return c.Status(500).SendString((err.Error()))
|
|
||||||
}
|
|
||||||
|
|
||||||
return c.Status(200).SendString("Weekly report deleted")
|
|
||||||
}
|
|
|
@ -1,56 +0,0 @@
|
||||||
package reports
|
|
||||||
|
|
||||||
import (
|
|
||||||
db "ttime/internal/database"
|
|
||||||
|
|
||||||
"github.com/gofiber/fiber/v2"
|
|
||||||
"github.com/gofiber/fiber/v2/log"
|
|
||||||
"github.com/golang-jwt/jwt/v5"
|
|
||||||
)
|
|
||||||
|
|
||||||
// GetAllWeeklyReports retrieves all weekly reports for a user in a specific project
|
|
||||||
func GetAllWeeklyReports(c *fiber.Ctx) error {
|
|
||||||
// Extract the necessary parameters from the token
|
|
||||||
user := c.Locals("user").(*jwt.Token)
|
|
||||||
claims := user.Claims.(jwt.MapClaims)
|
|
||||||
username := claims["name"].(string)
|
|
||||||
|
|
||||||
// Extract project name and week from query parameters
|
|
||||||
projectName := c.Params("projectName")
|
|
||||||
target_user := c.Query("targetUser") // The user whose reports are being requested
|
|
||||||
|
|
||||||
// If the target user is not empty, use it as the username
|
|
||||||
if target_user == "" {
|
|
||||||
target_user = username
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Info(username, " trying to get all weekly reports for: ", target_user)
|
|
||||||
|
|
||||||
if projectName == "" {
|
|
||||||
log.Info("Missing project name")
|
|
||||||
return c.Status(400).SendString("Missing project name")
|
|
||||||
}
|
|
||||||
|
|
||||||
// If the user is not a project manager, they can only view their own reports
|
|
||||||
pm, err := db.GetDb(c).IsProjectManager(username, projectName)
|
|
||||||
if err != nil {
|
|
||||||
log.Info("Error checking if user is project manager:", err)
|
|
||||||
return c.Status(500).SendString(err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
if !(pm || target_user == username) {
|
|
||||||
log.Info("Unauthorized access")
|
|
||||||
return c.Status(403).SendString("Unauthorized access")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Retrieve weekly reports for the user in the project from the database
|
|
||||||
reports, err := db.GetDb(c).GetAllWeeklyReports(target_user, projectName)
|
|
||||||
if err != nil {
|
|
||||||
log.Error("Error getting weekly reports for user:", target_user, "in project:", projectName, ":", err)
|
|
||||||
return c.Status(500).SendString(err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Info("Returning weekly report")
|
|
||||||
// Return the retrieved weekly report
|
|
||||||
return c.JSON(reports)
|
|
||||||
}
|
|
|
@ -16,17 +16,11 @@ func GetWeeklyReport(c *fiber.Ctx) error {
|
||||||
claims := user.Claims.(jwt.MapClaims)
|
claims := user.Claims.(jwt.MapClaims)
|
||||||
username := claims["name"].(string)
|
username := claims["name"].(string)
|
||||||
|
|
||||||
|
log.Info("Getting weekly report for: ", username)
|
||||||
|
|
||||||
// Extract project name and week from query parameters
|
// Extract project name and week from query parameters
|
||||||
projectName := c.Query("projectName")
|
projectName := c.Query("projectName")
|
||||||
week := c.Query("week")
|
week := c.Query("week")
|
||||||
target_user := c.Query("targetUser") // The user whose report is being requested
|
|
||||||
|
|
||||||
// If the target user is not empty, use it as the username
|
|
||||||
if target_user == "" {
|
|
||||||
target_user = username
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Info(username, " trying to get weekly report for: ", target_user)
|
|
||||||
|
|
||||||
if projectName == "" || week == "" {
|
if projectName == "" || week == "" {
|
||||||
log.Info("Missing project name or week number")
|
log.Info("Missing project name or week number")
|
||||||
|
@ -40,20 +34,8 @@ func GetWeeklyReport(c *fiber.Ctx) error {
|
||||||
return c.Status(400).SendString("Invalid week number")
|
return c.Status(400).SendString("Invalid week number")
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the token user is not an admin, check if the target user is the same as the token user
|
|
||||||
pm, err := db.GetDb(c).IsProjectManager(username, projectName)
|
|
||||||
if err != nil {
|
|
||||||
log.Info("Error checking if user is project manager:", err)
|
|
||||||
return c.Status(500).SendString(err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
if !(pm || target_user == username) {
|
|
||||||
log.Info("Unauthorized access")
|
|
||||||
return c.Status(403).SendString("Unauthorized access")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Call the database function to get the weekly report
|
// Call the database function to get the weekly report
|
||||||
report, err := db.GetDb(c).GetWeeklyReport(target_user, projectName, weekInt)
|
report, err := db.GetDb(c).GetWeeklyReport(username, projectName, weekInt)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Info("Error getting weekly report from db:", err)
|
log.Info("Error getting weekly report from db:", err)
|
||||||
return c.Status(500).SendString(err.Error())
|
return c.Status(500).SendString(err.Error())
|
||||||
|
|
|
@ -0,0 +1,36 @@
|
||||||
|
package reports
|
||||||
|
|
||||||
|
import (
|
||||||
|
db "ttime/internal/database"
|
||||||
|
|
||||||
|
"github.com/gofiber/fiber/v2"
|
||||||
|
"github.com/gofiber/fiber/v2/log"
|
||||||
|
"github.com/golang-jwt/jwt/v5"
|
||||||
|
)
|
||||||
|
|
||||||
|
// GetWeeklyReportsUserHandler retrieves all weekly reports for a user in a specific project
|
||||||
|
func GetWeeklyReportsUserHandler(c *fiber.Ctx) error {
|
||||||
|
// Extract the necessary parameters from the token
|
||||||
|
user := c.Locals("user").(*jwt.Token)
|
||||||
|
claims := user.Claims.(jwt.MapClaims)
|
||||||
|
username := claims["name"].(string)
|
||||||
|
|
||||||
|
// Extract necessary (path) parameters from the request
|
||||||
|
projectName := c.Params("projectName")
|
||||||
|
|
||||||
|
// TODO: Here we need to check whether the user is a member of the project
|
||||||
|
// If not, we should return an error. On the other hand, if the user not a member,
|
||||||
|
// the returned list of reports will (should) allways be empty.
|
||||||
|
|
||||||
|
// Retrieve weekly reports for the user in the project from the database
|
||||||
|
reports, err := db.GetDb(c).GetWeeklyReportsUser(username, projectName)
|
||||||
|
if err != nil {
|
||||||
|
log.Error("Error getting weekly reports for user:", username, "in project:", projectName, ":", err)
|
||||||
|
return c.Status(500).SendString(err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Info("Returning weekly reports for user:", username, "in project:", projectName)
|
||||||
|
|
||||||
|
// Return the list of reports as JSON
|
||||||
|
return c.JSON(reports)
|
||||||
|
}
|
|
@ -1,56 +0,0 @@
|
||||||
package reports
|
|
||||||
|
|
||||||
import (
|
|
||||||
db "ttime/internal/database"
|
|
||||||
|
|
||||||
"github.com/gofiber/fiber/v2"
|
|
||||||
"github.com/gofiber/fiber/v2/log"
|
|
||||||
"github.com/golang-jwt/jwt/v5"
|
|
||||||
)
|
|
||||||
|
|
||||||
func GetStatistics(c *fiber.Ctx) error {
|
|
||||||
// Extract the necessary parameters from the token
|
|
||||||
user := c.Locals("user").(*jwt.Token)
|
|
||||||
claims := user.Claims.(jwt.MapClaims)
|
|
||||||
username := claims["name"].(string)
|
|
||||||
|
|
||||||
// Extract project name from query parameters
|
|
||||||
projectName := c.Query("projectName")
|
|
||||||
userNameParam := c.Query("userName")
|
|
||||||
|
|
||||||
log.Info(username, " trying to get statistics for project: ", projectName)
|
|
||||||
|
|
||||||
if projectName == "" {
|
|
||||||
log.Info("Missing project name")
|
|
||||||
return c.Status(400).SendString("Missing project name")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if the user is a project manager
|
|
||||||
pm, err := db.GetDb(c).IsProjectManager(username, projectName)
|
|
||||||
if err != nil {
|
|
||||||
log.Info("Error checking if user is project manager:", err)
|
|
||||||
return c.Status(500).SendString(err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
// Bail if the user is not a PM or checking its own statistics
|
|
||||||
if !pm && userNameParam != "" && userNameParam != username {
|
|
||||||
log.Info("Unauthorized access for user: ", username, "trying to access project: ", projectName, "statistics for user: ", userNameParam)
|
|
||||||
return c.Status(403).SendString("Unauthorized access")
|
|
||||||
}
|
|
||||||
|
|
||||||
if pm && userNameParam != "" {
|
|
||||||
username = userNameParam
|
|
||||||
}
|
|
||||||
|
|
||||||
// Retrieve statistics for the project from the database
|
|
||||||
statistics, err := db.GetDb(c).ReportStatistics(username, projectName)
|
|
||||||
if err != nil {
|
|
||||||
log.Error("Error getting statistics for project:", projectName, ":", err)
|
|
||||||
return c.Status(500).SendString(err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Info("Returning statistics")
|
|
||||||
// Return the retrieved statistics
|
|
||||||
return c.JSON(statistics)
|
|
||||||
|
|
||||||
}
|
|
|
@ -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 UnsignReport(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).UnsignWeeklyReport(reportId, projectManagerID)
|
|
||||||
if err != nil {
|
|
||||||
log.Info("Error Unsigning weekly report:", err)
|
|
||||||
return c.Status(500).SendString(err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Info("Project manager ID: ", projectManagerID, " unsigned report ID: ", reportId)
|
|
||||||
return c.Status(200).SendString("Weekly report unsigned successfully")
|
|
||||||
}
|
|
|
@ -1,42 +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"
|
|
||||||
)
|
|
||||||
|
|
||||||
// ChangeUserPassword is a handler that changes the password of a user
|
|
||||||
func ChangeUserPassword(c *fiber.Ctx) error {
|
|
||||||
|
|
||||||
//Check token and get username of current user
|
|
||||||
user := c.Locals("user").(*jwt.Token)
|
|
||||||
claims := user.Claims.(jwt.MapClaims)
|
|
||||||
admin := claims["name"].(string)
|
|
||||||
|
|
||||||
// Extract the necessary parameters from the request
|
|
||||||
username := c.Params("username")
|
|
||||||
newPassword := c.Query("newPassword")
|
|
||||||
|
|
||||||
// Check if user is site admin
|
|
||||||
issiteadmin, err := db.GetDb(c).IsSiteAdmin(admin)
|
|
||||||
if err != nil {
|
|
||||||
log.Warn("Error checking if siteadmin:", err)
|
|
||||||
return c.Status(500).SendString(err.Error())
|
|
||||||
} else if !issiteadmin {
|
|
||||||
log.Warn("User is not siteadmin")
|
|
||||||
return c.Status(401).SendString("User is not siteadmin")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Perform the password change
|
|
||||||
err = db.GetDb(c).ChangeUserPassword(username, newPassword)
|
|
||||||
if err != nil {
|
|
||||||
log.Warn("Error changing password:", err)
|
|
||||||
return c.Status(500).SendString(err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
// Return a success message
|
|
||||||
return c.Status(200).SendString("Password changed successfully")
|
|
||||||
}
|
|
|
@ -1,32 +0,0 @@
|
||||||
package users
|
|
||||||
|
|
||||||
import (
|
|
||||||
"strconv"
|
|
||||||
db "ttime/internal/database"
|
|
||||||
|
|
||||||
"github.com/gofiber/fiber/v2"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Return the username of a user given their user id
|
|
||||||
func GetUserName(c *fiber.Ctx) error {
|
|
||||||
// Check the query params for userId
|
|
||||||
user_id_string := c.Query("userId")
|
|
||||||
if user_id_string == "" {
|
|
||||||
return c.Status(400).SendString("Missing user id")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Convert to int
|
|
||||||
user_id, err := strconv.Atoi(user_id_string)
|
|
||||||
if err != nil {
|
|
||||||
return c.Status(400).SendString("Invalid user id")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get the username from the database
|
|
||||||
username, err := db.GetDb(c).GetUserName(user_id)
|
|
||||||
if err != nil {
|
|
||||||
return c.Status(500).SendString(err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
// Send the nuclear launch codes to north korea
|
|
||||||
return c.JSON(fiber.Map{"username": username})
|
|
||||||
}
|
|
|
@ -7,17 +7,16 @@ import (
|
||||||
"github.com/gofiber/fiber/v2/log"
|
"github.com/gofiber/fiber/v2/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// ListAllUsers is a handler that returns a list of all users in the application database
|
||||||
// @Summary ListsAllUsers
|
// @Summary ListsAllUsers
|
||||||
// @Description lists all users
|
// @Description lists all users
|
||||||
// @Tags User
|
// @Tags User
|
||||||
// @Produce json
|
// @Accept json
|
||||||
// @Security JWT
|
// @Produce plain
|
||||||
// @Success 200 {array} string "Successfully returned all users"
|
// @Success 200 {json} json "Successfully signed token for user"
|
||||||
// @Failure 401 {string} string "Unauthorized"
|
// @Failure 401 {string} string "Unauthorized"
|
||||||
// @Failure 500 {string} string "Internal server error"
|
// @Failure 500 {string} string "Internal server error"
|
||||||
// @Router /users/all [get]
|
// @Router /users/all [get]
|
||||||
//
|
|
||||||
// ListAllUsers returns a list of all users in the application database
|
|
||||||
func ListAllUsers(c *fiber.Ctx) error {
|
func ListAllUsers(c *fiber.Ctx) error {
|
||||||
// Get all users from the database
|
// Get all users from the database
|
||||||
users, err := db.GetDb(c).GetAllUsersApplication()
|
users, err := db.GetDb(c).GetAllUsersApplication()
|
||||||
|
|
|
@ -10,19 +10,18 @@ import (
|
||||||
"github.com/golang-jwt/jwt/v5"
|
"github.com/golang-jwt/jwt/v5"
|
||||||
)
|
)
|
||||||
|
|
||||||
// @Summary Login
|
// Login is a simple login handler that returns a JWT token
|
||||||
// @Description Logs in a user and returns a JWT token
|
// @Summary login
|
||||||
// @Tags Auth
|
// @Description logs the user in and returns a jwt token
|
||||||
|
// @Tags User
|
||||||
// @Accept json
|
// @Accept json
|
||||||
// @Produce json
|
// @Param NewUser body types.NewUser true "login info"
|
||||||
// @Param body body types.NewUser true "User credentials"
|
// @Produce plain
|
||||||
// @Success 200 {object} types.Token "JWT token"
|
// @Success 200 Token types.Token "Successfully signed token for user"
|
||||||
// @Failure 400 {string} string "Bad request"
|
// @Failure 400 {string} string "Bad request"
|
||||||
// @Failure 401 {string} string "Unauthorized"
|
// @Failure 401 {string} string "Unauthorized"
|
||||||
// @Failure 500 {string} string "Internal server error"
|
// @Failure 500 {string} string "Internal server error"
|
||||||
// @Router /login [post]
|
// @Router /login [post]
|
||||||
//
|
|
||||||
// Login logs in a user and returns a JWT token
|
|
||||||
func Login(c *fiber.Ctx) error {
|
func Login(c *fiber.Ctx) error {
|
||||||
// The body type is identical to a NewUser
|
// The body type is identical to a NewUser
|
||||||
|
|
||||||
|
|
|
@ -9,40 +9,34 @@ import (
|
||||||
"github.com/golang-jwt/jwt/v5"
|
"github.com/golang-jwt/jwt/v5"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// LoginRenew is a simple handler that renews the token
|
||||||
// @Summary LoginRenews
|
// @Summary LoginRenews
|
||||||
// @Description Renews the users token.
|
// @Description renews the users token
|
||||||
// @Tags Auth
|
// @Security bererToken
|
||||||
// @Produce json
|
// @Tags User
|
||||||
// @Security JWT
|
// @Accept json
|
||||||
// @Success 200 {object} types.Token "Successfully signed token for user"
|
// @Produce plain
|
||||||
|
// @Success 200 Token types.Token "Successfully signed token for user"
|
||||||
// @Failure 401 {string} string "Unauthorized"
|
// @Failure 401 {string} string "Unauthorized"
|
||||||
// @Failure 500 {string} string "Internal server error"
|
// @Failure 500 {string} string "Internal server error"
|
||||||
// @Router /loginrenew [post]
|
// @Router /loginerenew [post]
|
||||||
//
|
|
||||||
// LoginRenew renews the users token
|
|
||||||
func LoginRenew(c *fiber.Ctx) error {
|
func LoginRenew(c *fiber.Ctx) error {
|
||||||
user := c.Locals("user").(*jwt.Token)
|
user := c.Locals("user").(*jwt.Token)
|
||||||
|
|
||||||
log.Info("Renewing token for user:", user.Claims.(jwt.MapClaims)["name"])
|
log.Info("Renewing token for user:", user.Claims.(jwt.MapClaims)["name"])
|
||||||
|
|
||||||
// Renewing the token means we trust whatever is already in the token
|
|
||||||
claims := user.Claims.(jwt.MapClaims)
|
claims := user.Claims.(jwt.MapClaims)
|
||||||
|
|
||||||
// 72 hour expiration time
|
|
||||||
claims["exp"] = time.Now().Add(time.Hour * 72).Unix()
|
claims["exp"] = time.Now().Add(time.Hour * 72).Unix()
|
||||||
|
renewed := jwt.MapClaims{
|
||||||
// Create token with old claims, but new expiration time
|
|
||||||
token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
|
|
||||||
"name": claims["name"],
|
"name": claims["name"],
|
||||||
"admin": claims["admin"],
|
"admin": claims["admin"],
|
||||||
"exp": claims["exp"],
|
"exp": claims["exp"],
|
||||||
})
|
}
|
||||||
|
token := jwt.NewWithClaims(jwt.SigningMethodHS256, renewed)
|
||||||
// Sign it with top secret key
|
|
||||||
t, err := token.SignedString([]byte("secret"))
|
t, err := token.SignedString([]byte("secret"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Warn("Error signing token")
|
log.Warn("Error signing token")
|
||||||
return c.SendStatus(fiber.StatusInternalServerError) // 500
|
return c.SendStatus(fiber.StatusInternalServerError)
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Info("Successfully renewed token for user:", user.Claims.(jwt.MapClaims)["name"])
|
log.Info("Successfully renewed token for user:", user.Claims.(jwt.MapClaims)["name"])
|
||||||
|
|
|
@ -9,19 +9,16 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
// @Summary PromoteToAdmin
|
// @Summary PromoteToAdmin
|
||||||
// @Description Promote chosen user to site admin
|
// @Description promote chosen user to admin
|
||||||
// @Tags User
|
// @Tags User
|
||||||
// @Accept json
|
// @Accept json
|
||||||
// @Produce plain
|
// @Produce plain
|
||||||
// @Security JWT
|
|
||||||
// @Param NewUser body types.NewUser true "user info"
|
// @Param NewUser body types.NewUser true "user info"
|
||||||
// @Success 200 {object} types.Token "Successfully promoted user"
|
// @Success 200 {json} json "Successfully promoted user"
|
||||||
// @Failure 400 {string} string "Bad request"
|
// @Failure 400 {string} string "Bad request"
|
||||||
// @Failure 401 {string} string "Unauthorized"
|
// @Failure 401 {string} string "Unauthorized"
|
||||||
// @Failure 500 {string} string "Internal server error"
|
// @Failure 500 {string} string "Internal server error"
|
||||||
// @Router /promoteToAdmin [post]
|
// @Router /promoteToAdmin [post]
|
||||||
//
|
|
||||||
// PromoteToAdmin promotes a user to a site admin
|
|
||||||
func PromoteToAdmin(c *fiber.Ctx) error {
|
func PromoteToAdmin(c *fiber.Ctx) error {
|
||||||
// Extract the username from the request body
|
// Extract the username from the request body
|
||||||
var newUser types.NewUser
|
var newUser types.NewUser
|
||||||
|
|
|
@ -8,9 +8,11 @@ import (
|
||||||
"github.com/gofiber/fiber/v2/log"
|
"github.com/gofiber/fiber/v2/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Register is a simple handler that registers a new user
|
||||||
|
//
|
||||||
// @Summary Register
|
// @Summary Register
|
||||||
// @Description Register a new user
|
// @Description Register a new user
|
||||||
// @Tags Auth
|
// @Tags User
|
||||||
// @Accept json
|
// @Accept json
|
||||||
// @Produce plain
|
// @Produce plain
|
||||||
// @Param NewUser body types.NewUser true "User to register"
|
// @Param NewUser body types.NewUser true "User to register"
|
||||||
|
@ -18,8 +20,6 @@ import (
|
||||||
// @Failure 400 {string} string "Bad request"
|
// @Failure 400 {string} string "Bad request"
|
||||||
// @Failure 500 {string} string "Internal server error"
|
// @Failure 500 {string} string "Internal server error"
|
||||||
// @Router /register [post]
|
// @Router /register [post]
|
||||||
//
|
|
||||||
// Register is a simple handler that registers a new user
|
|
||||||
func Register(c *fiber.Ctx) error {
|
func Register(c *fiber.Ctx) error {
|
||||||
u := new(types.NewUser)
|
u := new(types.NewUser)
|
||||||
if err := c.BodyParser(u); err != nil {
|
if err := c.BodyParser(u); err != nil {
|
||||||
|
|
|
@ -8,19 +8,19 @@ import (
|
||||||
"github.com/golang-jwt/jwt/v5"
|
"github.com/golang-jwt/jwt/v5"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// This path should obviously be protected in the future
|
||||||
|
// UserDelete deletes a user from the database
|
||||||
|
//
|
||||||
// @Summary UserDelete
|
// @Summary UserDelete
|
||||||
// @Description UserDelete deletes a user from the database
|
// @Description UserDelete deletes a user from the database
|
||||||
// @Tags User
|
// @Tags User
|
||||||
// @Accept json
|
// @Accept json
|
||||||
// @Produce plain
|
// @Produce plain
|
||||||
// @Security JWT
|
|
||||||
// @Success 200 {string} string "User deleted"
|
// @Success 200 {string} string "User deleted"
|
||||||
// @Failure 403 {string} string "You can only delete yourself"
|
// @Failure 403 {string} string "You can only delete yourself"
|
||||||
// @Failure 500 {string} string "Internal server error"
|
// @Failure 500 {string} string "Internal server error"
|
||||||
// @Failure 401 {string} string "Unauthorized"
|
// @Failure 401 {string} string "Unauthorized"
|
||||||
// @Router /userdelete/{username} [delete]
|
// @Router /userdelete/{username} [delete]
|
||||||
//
|
|
||||||
// UserDelete deletes a user from the database
|
|
||||||
func UserDelete(c *fiber.Ctx) error {
|
func UserDelete(c *fiber.Ctx) error {
|
||||||
// Read from path parameters
|
// Read from path parameters
|
||||||
username := c.Params("username")
|
username := c.Params("username")
|
||||||
|
|
|
@ -66,15 +66,6 @@ type WeeklyReport struct {
|
||||||
SignedBy *int `json:"signedBy" db:"signed_by"`
|
SignedBy *int `json:"signedBy" db:"signed_by"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Statistics struct {
|
|
||||||
TotalDevelopmentTime int `json:"totalDevelopmentTime" db:"total_development_time"`
|
|
||||||
TotalMeetingTime int `json:"totalMeetingTime" db:"total_meeting_time"`
|
|
||||||
TotalAdminTime int `json:"totalAdminTime" db:"total_admin_time"`
|
|
||||||
TotalOwnWorkTime int `json:"totalOwnWorkTime" db:"total_own_work_time"`
|
|
||||||
TotalStudyTime int `json:"totalStudyTime" db:"total_study_time"`
|
|
||||||
TotalTestingTime int `json:"totalTestingTime" db:"total_testing_time"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type UpdateWeeklyReport struct {
|
type UpdateWeeklyReport struct {
|
||||||
// The name of the project, as it appears in the database
|
// The name of the project, as it appears in the database
|
||||||
ProjectName string `json:"projectName"`
|
ProjectName string `json:"projectName"`
|
||||||
|
|
|
@ -18,8 +18,8 @@ func (u *User) ToPublicUser() (*PublicUser, error) {
|
||||||
|
|
||||||
// Should be used when registering, for example
|
// Should be used when registering, for example
|
||||||
type NewUser struct {
|
type NewUser struct {
|
||||||
Username string `json:"username" example:"username123"`
|
Username string `json:"username"`
|
||||||
Password string `json:"password" example:"password123"`
|
Password string `json:"password"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// PublicUser represents a user that is safe to send over the API (no password)
|
// PublicUser represents a user that is safe to send over the API (no password)
|
||||||
|
|
|
@ -25,10 +25,9 @@ import (
|
||||||
// @license.name AGPL
|
// @license.name AGPL
|
||||||
// @license.url https://www.gnu.org/licenses/agpl-3.0.html
|
// @license.url https://www.gnu.org/licenses/agpl-3.0.html
|
||||||
|
|
||||||
// @securityDefinitions.apikey JWT
|
//@securityDefinitions.apikey bererToken
|
||||||
//@in header
|
//@in header
|
||||||
//@name Authorization
|
//@name Authorization
|
||||||
// @description Use the JWT token provided by the login endpoint to authenticate requests. **Prefix the token with "Bearer ".**
|
|
||||||
|
|
||||||
// @host localhost:8080
|
// @host localhost:8080
|
||||||
// @BasePath /api
|
// @BasePath /api
|
||||||
|
@ -59,13 +58,13 @@ func main() {
|
||||||
db := database.DbConnect(conf.DbPath)
|
db := database.DbConnect(conf.DbPath)
|
||||||
|
|
||||||
// Migrate the database
|
// Migrate the database
|
||||||
if err = database.Migrate(db); 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
|
// Migrate sample data, should not be used in production
|
||||||
if err = database.MigrateSampleData(db); 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)
|
||||||
}
|
}
|
||||||
|
@ -103,42 +102,33 @@ func main() {
|
||||||
// userGroup := api.Group("/user") // Not currently in use
|
// userGroup := api.Group("/user") // Not currently in use
|
||||||
api.Get("/users/all", users.ListAllUsers)
|
api.Get("/users/all", users.ListAllUsers)
|
||||||
api.Get("/project/getAllUsers", users.GetAllUsersProject)
|
api.Get("/project/getAllUsers", users.GetAllUsersProject)
|
||||||
api.Get("/username", users.GetUserName)
|
|
||||||
api.Post("/login", users.Login)
|
api.Post("/login", users.Login)
|
||||||
api.Post("/register", users.Register)
|
api.Post("/register", users.Register)
|
||||||
api.Post("/loginrenew", users.LoginRenew)
|
api.Post("/loginrenew", users.LoginRenew)
|
||||||
api.Post("/promoteToAdmin", users.PromoteToAdmin)
|
api.Post("/promoteToAdmin", users.PromoteToAdmin)
|
||||||
api.Put("/changeUserName", users.ChangeUserName)
|
api.Put("/changeUserName", users.ChangeUserName)
|
||||||
api.Delete("/userdelete/:username", users.UserDelete) // Perhaps just use POST to avoid headaches
|
api.Delete("/userdelete/:username", users.UserDelete) // Perhaps just use POST to avoid headaches
|
||||||
api.Put("/changeUserPassword/:username", users.ChangeUserPassword)
|
|
||||||
|
|
||||||
// All project related routes
|
// All project related routes
|
||||||
// projectGroup := api.Group("/project") // Not currently in use
|
// projectGroup := api.Group("/project") // Not currently in use
|
||||||
api.Get("/getProjectTimes/:projectName", projects.GetProjectTimesHandler)
|
api.Get("/getUserProjects", projects.GetUserProjects)
|
||||||
api.Get("/getUserProjects/:username", projects.GetUserProjects)
|
|
||||||
api.Get("/project/:projectId", projects.GetProject)
|
api.Get("/project/:projectId", projects.GetProject)
|
||||||
api.Get("/checkIfProjectManager/:projectName", projects.IsProjectManagerHandler)
|
api.Get("/checkIfProjectManager/:projectName", projects.IsProjectManagerHandler)
|
||||||
api.Get("/getUsersProject/:projectName", projects.ListAllUsersProject)
|
api.Get("/getUsersProject/:projectName", projects.ListAllUsersProject)
|
||||||
api.Post("/project", projects.CreateProject)
|
api.Post("/project", projects.CreateProject)
|
||||||
api.Post("/ProjectRoleChange", projects.ProjectRoleChange)
|
api.Post("/ProjectRoleChange", projects.ProjectRoleChange)
|
||||||
api.Put("/promoteToPm/:projectName", projects.PromoteToPm)
|
|
||||||
api.Put("/addUserToProject/:projectName", projects.AddUserToProjectHandler)
|
|
||||||
api.Delete("/removeUserFromProject/:projectName", projects.RemoveUserFromProject)
|
|
||||||
api.Delete("/removeProject/:projectName", projects.RemoveProject)
|
api.Delete("/removeProject/:projectName", projects.RemoveProject)
|
||||||
api.Delete("/project/:projectID", projects.DeleteProject)
|
api.Delete("/project/:projectID", projects.DeleteProject)
|
||||||
api.Put("/changeProjectName/:projectName", projects.ChangeProjectName)
|
|
||||||
|
|
||||||
// All report related routes
|
// All report related routes
|
||||||
// reportGroup := api.Group("/report") // Not currently in use
|
// reportGroup := api.Group("/report") // Not currently in use
|
||||||
api.Get("/getWeeklyReport", reports.GetWeeklyReport)
|
api.Get("/getWeeklyReport", reports.GetWeeklyReport)
|
||||||
api.Get("/getUnsignedReports/:projectName", reports.GetUnsignedReports)
|
api.Get("/getUnsignedReports/:projectName", reports.GetUnsignedReports)
|
||||||
api.Get("/getAllWeeklyReports/:projectName", reports.GetAllWeeklyReports)
|
api.Get("/getWeeklyReportsUser/:projectName", reports.GetWeeklyReportsUserHandler)
|
||||||
api.Get("/getStatistics", reports.GetStatistics)
|
|
||||||
api.Post("/submitWeeklyReport", reports.SubmitWeeklyReport)
|
api.Post("/submitWeeklyReport", reports.SubmitWeeklyReport)
|
||||||
api.Put("/signReport/:reportId", reports.SignReport)
|
api.Put("/signReport/:reportId", reports.SignReport)
|
||||||
|
api.Put("/addUserToProject", projects.AddUserToProjectHandler)
|
||||||
api.Put("/updateWeeklyReport", reports.UpdateWeeklyReport)
|
api.Put("/updateWeeklyReport", reports.UpdateWeeklyReport)
|
||||||
api.Put("/unsignReport/:reportId", reports.UnsignReport)
|
|
||||||
api.Delete("/deleteReport/:reportId", reports.DeleteReport)
|
|
||||||
|
|
||||||
// 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))
|
||||||
|
|
|
@ -1,2 +0,0 @@
|
||||||
goTypes.ts
|
|
||||||
GenApi.ts
|
|
1790
frontend/package-lock.json
generated
1790
frontend/package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
@ -1,17 +1,13 @@
|
||||||
import { AddMemberInfo } from "../Components/AddMember";
|
|
||||||
import { ProjectRoleChange } from "../Components/ChangeRole";
|
|
||||||
import { projectTimes } from "../Components/GetProjectTimes";
|
|
||||||
import { ProjectMember } from "../Components/GetUsersInProject";
|
|
||||||
import {
|
import {
|
||||||
UpdateWeeklyReport,
|
|
||||||
NewWeeklyReport,
|
NewWeeklyReport,
|
||||||
NewUser,
|
NewUser,
|
||||||
User,
|
User,
|
||||||
Project,
|
Project,
|
||||||
NewProject,
|
NewProject,
|
||||||
|
UserProjectMember,
|
||||||
WeeklyReport,
|
WeeklyReport,
|
||||||
StrNameChange,
|
StrNameChange,
|
||||||
Statistics,
|
NewProjMember,
|
||||||
} from "../Types/goTypes";
|
} from "../Types/goTypes";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -77,7 +73,10 @@ interface API {
|
||||||
* @param {string} token The authentication token.
|
* @param {string} token The authentication token.
|
||||||
* @returns {Promise<APIResponse<Project>>} A promise resolving to an API response with the created project.
|
* @returns {Promise<APIResponse<Project>>} A promise resolving to an API response with the created project.
|
||||||
*/
|
*/
|
||||||
createProject(project: NewProject, token: string): Promise<APIResponse<void>>;
|
createProject(
|
||||||
|
project: NewProject,
|
||||||
|
token: string,
|
||||||
|
): Promise<APIResponse<Project>>;
|
||||||
|
|
||||||
/** Submits a weekly report
|
/** Submits a weekly report
|
||||||
* @param {NewWeeklyReport} weeklyReport The weekly report object.
|
* @param {NewWeeklyReport} weeklyReport The weekly report object.
|
||||||
|
@ -89,31 +88,16 @@ interface API {
|
||||||
token: string,
|
token: string,
|
||||||
): Promise<APIResponse<string>>;
|
): Promise<APIResponse<string>>;
|
||||||
|
|
||||||
/**
|
/** Gets a weekly report for a specific user, project and week
|
||||||
* Updates a weekly report.
|
|
||||||
* @param {UpdateWeeklyReport} weeklyReport The updated weekly report object.
|
|
||||||
* @param {string} token The authentication token.
|
|
||||||
* @returns {Promise<APIResponse<string>>} A promise containing the API response with the updated report.
|
|
||||||
*/
|
|
||||||
updateWeeklyReport(
|
|
||||||
weeklyReport: UpdateWeeklyReport,
|
|
||||||
token: string,
|
|
||||||
): Promise<APIResponse<string>>;
|
|
||||||
|
|
||||||
/** Gets a weekly report for a specific user, project and week.
|
|
||||||
* Keep in mind that the user within the token needs to be PM
|
|
||||||
* of the project to get the report, unless the user is the target user.
|
|
||||||
* @param {string} projectName The name of the project.
|
* @param {string} projectName The name of the project.
|
||||||
* @param {string} week The week number.
|
* @param {string} week The week number.
|
||||||
* @param {string} token The authentication token.
|
* @param {string} token The authentication token.
|
||||||
* @param {string} targetUser The username of the target user. Defaults to token user.
|
|
||||||
* @returns {Promise<APIResponse<WeeklyReport>>} A promise resolving to an API response with the retrieved report.
|
* @returns {Promise<APIResponse<WeeklyReport>>} A promise resolving to an API response with the retrieved report.
|
||||||
*/
|
*/
|
||||||
getWeeklyReport(
|
getWeeklyReport(
|
||||||
projectName: string,
|
projectName: string,
|
||||||
week: string,
|
week: string,
|
||||||
token: string,
|
token: string,
|
||||||
targetUser?: string,
|
|
||||||
): Promise<APIResponse<WeeklyReport>>;
|
): Promise<APIResponse<WeeklyReport>>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -123,21 +107,16 @@ interface API {
|
||||||
* @param {string} token The token of the user
|
* @param {string} token The token of the user
|
||||||
* @returns {APIResponse<WeeklyReport[]>} A list of weekly reports
|
* @returns {APIResponse<WeeklyReport[]>} A list of weekly reports
|
||||||
*/
|
*/
|
||||||
getAllWeeklyReportsForUser(
|
getWeeklyReportsForUser(
|
||||||
projectName: string,
|
projectName: string,
|
||||||
token: string,
|
token: string,
|
||||||
targetUser?: string,
|
|
||||||
): Promise<APIResponse<WeeklyReport[]>>;
|
): Promise<APIResponse<WeeklyReport[]>>;
|
||||||
|
|
||||||
/** Gets all the projects of a user
|
/** Gets all the projects of a user
|
||||||
* @param {string} username - The authentication token.
|
|
||||||
* @param {string} token - The authentication token.
|
* @param {string} token - The authentication token.
|
||||||
* @returns {Promise<APIResponse<Project[]>>} A promise containing the API response with the user's projects.
|
* @returns {Promise<APIResponse<Project[]>>} A promise containing the API response with the user's projects.
|
||||||
*/
|
*/
|
||||||
getUserProjects(
|
getUserProjects(token: string): Promise<APIResponse<Project[]>>;
|
||||||
username: string,
|
|
||||||
token: string,
|
|
||||||
): Promise<APIResponse<Project[]>>;
|
|
||||||
|
|
||||||
/** Gets a project by its id.
|
/** Gets a project by its id.
|
||||||
* @param {number} id The id of the project to retrieve.
|
* @param {number} id The id of the project to retrieve.
|
||||||
|
@ -145,16 +124,6 @@ interface API {
|
||||||
*/
|
*/
|
||||||
getProject(id: number): Promise<APIResponse<Project>>;
|
getProject(id: number): Promise<APIResponse<Project>>;
|
||||||
|
|
||||||
/** Gets a projects reported time
|
|
||||||
* @param {string} projectName The name of the project.
|
|
||||||
* @param {string} token The usertoken.
|
|
||||||
* @returns {Promise<APIResponse<Times>>} A promise resolving to an API response containing the project times.
|
|
||||||
*/
|
|
||||||
getProjectTimes(
|
|
||||||
projectName: string,
|
|
||||||
token: string,
|
|
||||||
): Promise<APIResponse<projectTimes>>;
|
|
||||||
|
|
||||||
/** Gets a list of all users.
|
/** Gets a list of all users.
|
||||||
* @param {string} token The authentication token of the requesting user.
|
* @param {string} token The authentication token of the requesting user.
|
||||||
* @returns {Promise<APIResponse<string[]>>} A promise resolving to an API response containing the list of users.
|
* @returns {Promise<APIResponse<string[]>>} A promise resolving to an API response containing the list of users.
|
||||||
|
@ -164,18 +133,7 @@ interface API {
|
||||||
getAllUsersProject(
|
getAllUsersProject(
|
||||||
projectName: string,
|
projectName: string,
|
||||||
token: string,
|
token: string,
|
||||||
): Promise<APIResponse<ProjectMember[]>>;
|
): Promise<APIResponse<UserProjectMember[]>>;
|
||||||
|
|
||||||
/** Gets all unsigned reports in a project.
|
|
||||||
* @param {string} projectName The name of the project.
|
|
||||||
* @param {string} token The authentication token.
|
|
||||||
* @returns {Promise<APIResponse<WeeklyReport[]>>} A promise resolving to an API response containing the list of unsigned reports.
|
|
||||||
*/
|
|
||||||
getUnsignedReportsInProject(
|
|
||||||
projectName: string,
|
|
||||||
token: string,
|
|
||||||
): Promise<APIResponse<WeeklyReport[]>>;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Changes the username of a user in the database.
|
* Changes the username of a user in the database.
|
||||||
* @param {StrNameChange} data The object containing the previous and new username.
|
* @param {StrNameChange} data The object containing the previous and new username.
|
||||||
|
@ -186,27 +144,10 @@ interface API {
|
||||||
data: StrNameChange,
|
data: StrNameChange,
|
||||||
token: string,
|
token: string,
|
||||||
): Promise<APIResponse<void>>;
|
): Promise<APIResponse<void>>;
|
||||||
/**
|
|
||||||
* Changes the role of a user in the database.
|
|
||||||
* @param {RoleChange} roleInfo The object containing the previous and new username.
|
|
||||||
* @param {string} token The authentication token.
|
|
||||||
* @returns {Promise<APIResponse<void>>} A promise resolving to an API response.
|
|
||||||
*/
|
|
||||||
changeUserRole(
|
|
||||||
roleInfo: ProjectRoleChange,
|
|
||||||
token: string,
|
|
||||||
): Promise<APIResponse<void>>;
|
|
||||||
|
|
||||||
addUserToProject(
|
addUserToProject(
|
||||||
addMemberInfo: AddMemberInfo,
|
user: NewProjMember,
|
||||||
token: string,
|
token: string,
|
||||||
): Promise<APIResponse<void>>;
|
): Promise<APIResponse<NewProjMember>>;
|
||||||
|
|
||||||
removeUserFromProject(
|
|
||||||
user: string,
|
|
||||||
project: string,
|
|
||||||
token: string,
|
|
||||||
): Promise<APIResponse<void>>;
|
|
||||||
|
|
||||||
removeProject(
|
removeProject(
|
||||||
projectName: string,
|
projectName: string,
|
||||||
|
@ -220,81 +161,10 @@ interface API {
|
||||||
* @param {number} reportId The id of the report to sign
|
* @param {number} reportId The id of the report to sign
|
||||||
* @param {string} token The authentication token
|
* @param {string} token The authentication token
|
||||||
*/
|
*/
|
||||||
signReport(reportId: number, token: string): Promise<APIResponse<string>>;
|
signReport(
|
||||||
|
|
||||||
/**
|
|
||||||
* Unsigns 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
|
|
||||||
*/
|
|
||||||
unsignReport(reportId: number, token: string): Promise<APIResponse<string>>;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Promotes a user to project manager within a project.
|
|
||||||
*
|
|
||||||
* @param {string} userName The username of the user to promote
|
|
||||||
* @param {string} projectName The name of the project to promote the user in
|
|
||||||
* @returns {Promise<APIResponse<string>} A promise resolving to an API response.
|
|
||||||
*/
|
|
||||||
promoteToPm(
|
|
||||||
userName: string,
|
|
||||||
projectName: string,
|
|
||||||
token: string,
|
|
||||||
): Promise<APIResponse<string>>;
|
|
||||||
/**
|
|
||||||
* Get the username from the id
|
|
||||||
* @param {number} id The id of the user
|
|
||||||
* @param {string} token Your token
|
|
||||||
*/
|
|
||||||
getUsername(id: number, token: string): Promise<APIResponse<string>>;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Deletes a WeeklyReport from the database
|
|
||||||
* @param {number} reportId The id of the report to delete
|
|
||||||
* @param {string} token The authentication token
|
|
||||||
*/
|
|
||||||
deleteWeeklyReport(
|
|
||||||
reportId: number,
|
reportId: number,
|
||||||
token: string,
|
token: string,
|
||||||
): Promise<APIResponse<string>>;
|
): Promise<APIResponse<string>>;
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieves the total time spent on a project for a particular user (the user is determined by the token)
|
|
||||||
*
|
|
||||||
* @param {string} projectName The name of the project
|
|
||||||
* @param {string} token The authentication token
|
|
||||||
*/
|
|
||||||
getStatistics(
|
|
||||||
projectName: string,
|
|
||||||
token: string,
|
|
||||||
userName?: string,
|
|
||||||
): Promise<APIResponse<Statistics>>;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Changes the name of a project
|
|
||||||
* @param {string} projectName The name of the project
|
|
||||||
* @param {string} newProjectName The new name of the project
|
|
||||||
* @param {string} token The authentication token
|
|
||||||
*/
|
|
||||||
changeProjectName(
|
|
||||||
projectName: string,
|
|
||||||
newProjectName: string,
|
|
||||||
token: string,
|
|
||||||
): Promise<APIResponse<string>>;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Changes the password of a user
|
|
||||||
* @param {string} username The username of the user
|
|
||||||
* @param {string} newPassword The new password
|
|
||||||
* @param {string} token The authentication token
|
|
||||||
*/
|
|
||||||
changeUserPassword(
|
|
||||||
username: string,
|
|
||||||
newPassword: string,
|
|
||||||
token: string,
|
|
||||||
): Promise<APIResponse<string>>;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** An instance of the API */
|
/** An instance of the API */
|
||||||
|
@ -382,7 +252,7 @@ export const api: API = {
|
||||||
async createProject(
|
async createProject(
|
||||||
project: NewProject,
|
project: NewProject,
|
||||||
token: string,
|
token: string,
|
||||||
): Promise<APIResponse<void>> {
|
): Promise<APIResponse<Project>> {
|
||||||
try {
|
try {
|
||||||
const response = await fetch("/api/project", {
|
const response = await fetch("/api/project", {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
|
@ -396,28 +266,27 @@ export const api: API = {
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
return { success: false, message: "Failed to create project" };
|
return { success: false, message: "Failed to create project" };
|
||||||
} else {
|
} else {
|
||||||
return { success: true };
|
const data = (await response.json()) as Project;
|
||||||
|
return { success: true, data };
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return { success: false, message: "Failed to create project!" };
|
return { success: false, message: "Failed to create project" };
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
async addUserToProject(
|
async addUserToProject(
|
||||||
addMemberInfo: AddMemberInfo,
|
user: NewProjMember,
|
||||||
token: string,
|
token: string,
|
||||||
): Promise<APIResponse<void>> {
|
): Promise<APIResponse<NewProjMember>> {
|
||||||
try {
|
try {
|
||||||
const response = await fetch(
|
const response = await fetch("/api/addUserToProject", {
|
||||||
`/api/addUserToProject/${addMemberInfo.projectName}/?userName=${addMemberInfo.userName}`,
|
|
||||||
{
|
|
||||||
method: "PUT",
|
method: "PUT",
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
Authorization: "Bearer " + token,
|
Authorization: "Bearer " + token,
|
||||||
},
|
},
|
||||||
},
|
body: JSON.stringify(user),
|
||||||
);
|
});
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
return { success: false, message: "Failed to add member" };
|
return { success: false, message: "Failed to add member" };
|
||||||
|
@ -429,31 +298,6 @@ export const api: API = {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
async removeUserFromProject(
|
|
||||||
user: string,
|
|
||||||
project: string,
|
|
||||||
token: string,
|
|
||||||
): Promise<APIResponse<void>> {
|
|
||||||
try {
|
|
||||||
const response = await fetch(
|
|
||||||
`/api/removeUserFromProject/${project}?userName=${user}`,
|
|
||||||
{
|
|
||||||
method: "DELETE",
|
|
||||||
headers: {
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
Authorization: "Bearer " + token,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
);
|
|
||||||
if (!response.ok) {
|
|
||||||
return { success: false, message: "Failed to remove member" };
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
return { success: false, message: "Failed to remove member" };
|
|
||||||
}
|
|
||||||
return { success: true, message: "Removed member" };
|
|
||||||
},
|
|
||||||
|
|
||||||
async renewToken(token: string): Promise<APIResponse<string>> {
|
async renewToken(token: string): Promise<APIResponse<string>> {
|
||||||
try {
|
try {
|
||||||
const response = await fetch("/api/loginrenew", {
|
const response = await fetch("/api/loginrenew", {
|
||||||
|
@ -475,39 +319,9 @@ export const api: API = {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
async changeUserRole(
|
async getUserProjects(token: string): Promise<APIResponse<Project[]>> {
|
||||||
roleInfo: ProjectRoleChange,
|
|
||||||
token: string,
|
|
||||||
): Promise<APIResponse<void>> {
|
|
||||||
try {
|
try {
|
||||||
const response = await fetch("/api/ProjectRoleChange", {
|
const response = await fetch("/api/getUserProjects", {
|
||||||
method: "POST",
|
|
||||||
headers: {
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
Authorization: "Bearer " + token,
|
|
||||||
},
|
|
||||||
body: JSON.stringify(roleInfo),
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!response.ok) {
|
|
||||||
if (response.status === 403) {
|
|
||||||
return { success: false, message: "Cannot change your own role" };
|
|
||||||
}
|
|
||||||
return { success: false, message: "Could not change role" };
|
|
||||||
} else {
|
|
||||||
return { success: true };
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
return { success: false, message: "Could not change role" };
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
async getUserProjects(
|
|
||||||
username: string,
|
|
||||||
token: string,
|
|
||||||
): Promise<APIResponse<Project[]>> {
|
|
||||||
try {
|
|
||||||
const response = await fetch(`/api/getUserProjects/${username}`, {
|
|
||||||
method: "GET",
|
method: "GET",
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
|
@ -532,37 +346,6 @@ export const api: API = {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
async getProjectTimes(
|
|
||||||
projectName: string,
|
|
||||||
token: string,
|
|
||||||
): Promise<APIResponse<projectTimes>> {
|
|
||||||
try {
|
|
||||||
const response = await fetch(`/api/getProjectTimes/${projectName}`, {
|
|
||||||
method: "GET",
|
|
||||||
headers: {
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
Authorization: "Bearer " + token,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!response.ok) {
|
|
||||||
return Promise.resolve({
|
|
||||||
success: false,
|
|
||||||
message:
|
|
||||||
"Fetch error: " + response.status + ", failed to get project times",
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
const data = (await response.json()) as projectTimes;
|
|
||||||
return Promise.resolve({ success: true, data });
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
return Promise.resolve({
|
|
||||||
success: false,
|
|
||||||
message: "API error! Could not get times.",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
async submitWeeklyReport(
|
async submitWeeklyReport(
|
||||||
weeklyReport: NewWeeklyReport,
|
weeklyReport: NewWeeklyReport,
|
||||||
token: string,
|
token: string,
|
||||||
|
@ -594,46 +377,14 @@ export const api: API = {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
async updateWeeklyReport(
|
|
||||||
weeklyReport: UpdateWeeklyReport,
|
|
||||||
token: string,
|
|
||||||
): Promise<APIResponse<string>> {
|
|
||||||
try {
|
|
||||||
const response = await fetch("/api/updateWeeklyReport", {
|
|
||||||
method: "PUT",
|
|
||||||
headers: {
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
Authorization: "Bearer " + token,
|
|
||||||
},
|
|
||||||
body: JSON.stringify(weeklyReport),
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!response.ok) {
|
|
||||||
return {
|
|
||||||
success: false,
|
|
||||||
message: "Failed to update weekly report",
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const data = await response.text();
|
|
||||||
return { success: true, message: data };
|
|
||||||
} catch (e) {
|
|
||||||
return {
|
|
||||||
success: false,
|
|
||||||
message: "Failed to update weekly report",
|
|
||||||
};
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
async getWeeklyReport(
|
async getWeeklyReport(
|
||||||
projectName: string,
|
projectName: string,
|
||||||
week: string,
|
week: string,
|
||||||
token: string,
|
token: string,
|
||||||
targetUser?: string,
|
|
||||||
): Promise<APIResponse<WeeklyReport>> {
|
): Promise<APIResponse<WeeklyReport>> {
|
||||||
try {
|
try {
|
||||||
const response = await fetch(
|
const response = await fetch(
|
||||||
`/api/getWeeklyReport?projectName=${projectName}&week=${week}&targetUser=${targetUser ?? ""}`,
|
`/api/getWeeklyReport?projectName=${projectName}&week=${week}`,
|
||||||
{
|
{
|
||||||
method: "GET",
|
method: "GET",
|
||||||
headers: {
|
headers: {
|
||||||
|
@ -654,22 +405,18 @@ export const api: API = {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
async getAllWeeklyReportsForUser(
|
async getWeeklyReportsForUser(
|
||||||
projectName: string,
|
projectName: string,
|
||||||
token: string,
|
token: string,
|
||||||
targetUser?: string,
|
|
||||||
): Promise<APIResponse<WeeklyReport[]>> {
|
): Promise<APIResponse<WeeklyReport[]>> {
|
||||||
try {
|
try {
|
||||||
const response = await fetch(
|
const response = await fetch(`/api/getWeeklyReportsUser/${projectName}`, {
|
||||||
`/api/getAllWeeklyReports/${projectName}?targetUser=${targetUser ?? ""}`,
|
|
||||||
{
|
|
||||||
method: "GET",
|
method: "GET",
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
Authorization: "Bearer " + token,
|
Authorization: "Bearer " + token,
|
||||||
},
|
},
|
||||||
},
|
});
|
||||||
);
|
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
return {
|
return {
|
||||||
|
@ -701,11 +448,7 @@ export const api: API = {
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
return {
|
return { success: false, message: "Failed to login" };
|
||||||
success: false,
|
|
||||||
data: `${response.status}`,
|
|
||||||
message: "Failed to login",
|
|
||||||
};
|
|
||||||
} else {
|
} else {
|
||||||
const data = (await response.json()) as { token: string }; // Update the type of 'data'
|
const data = (await response.json()) as { token: string }; // Update the type of 'data'
|
||||||
return { success: true, data: data.token };
|
return { success: true, data: data.token };
|
||||||
|
@ -770,7 +513,7 @@ export const api: API = {
|
||||||
async getAllUsersProject(
|
async getAllUsersProject(
|
||||||
projectName: string,
|
projectName: string,
|
||||||
token: string,
|
token: string,
|
||||||
): Promise<APIResponse<ProjectMember[]>> {
|
): Promise<APIResponse<UserProjectMember[]>> {
|
||||||
try {
|
try {
|
||||||
const response = await fetch(`/api/getUsersProject/${projectName}`, {
|
const response = await fetch(`/api/getUsersProject/${projectName}`, {
|
||||||
method: "GET",
|
method: "GET",
|
||||||
|
@ -786,7 +529,7 @@ export const api: API = {
|
||||||
message: "Failed to get users",
|
message: "Failed to get users",
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
const data = (await response.json()) as ProjectMember[];
|
const data = (await response.json()) as UserProjectMember[];
|
||||||
return Promise.resolve({ success: true, data });
|
return Promise.resolve({ success: true, data });
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
@ -797,38 +540,6 @@ export const api: API = {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
async getUnsignedReportsInProject(
|
|
||||||
projectName: string,
|
|
||||||
token: string,
|
|
||||||
): Promise<APIResponse<WeeklyReport[]>> {
|
|
||||||
try {
|
|
||||||
const response = await fetch(`/api/getUnsignedReports/${projectName}`, {
|
|
||||||
method: "GET",
|
|
||||||
headers: {
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
Authorization: "Bearer " + token,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!response.ok) {
|
|
||||||
return {
|
|
||||||
success: false,
|
|
||||||
message:
|
|
||||||
"Failed to get unsigned reports for project: Response code " +
|
|
||||||
response.status,
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
const data = (await response.json()) as WeeklyReport[];
|
|
||||||
return { success: true, data };
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
return {
|
|
||||||
success: false,
|
|
||||||
message: "Failed to get unsigned reports for project, unknown error",
|
|
||||||
};
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
async changeUserName(
|
async changeUserName(
|
||||||
data: StrNameChange,
|
data: StrNameChange,
|
||||||
token: string,
|
token: string,
|
||||||
|
@ -858,7 +569,7 @@ export const api: API = {
|
||||||
token: string,
|
token: string,
|
||||||
): Promise<APIResponse<string>> {
|
): Promise<APIResponse<string>> {
|
||||||
try {
|
try {
|
||||||
const response = await fetch(`/api/removeProject/${projectName}`, {
|
const response = await fetch(`/api/projectdelete/${projectName}`, {
|
||||||
method: "DELETE",
|
method: "DELETE",
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
|
@ -904,184 +615,5 @@ export const api: API = {
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return { success: false, message: "Failed to sign report" };
|
return { success: false, message: "Failed to sign report" };
|
||||||
}
|
}
|
||||||
},
|
|
||||||
|
|
||||||
async unsignReport(
|
|
||||||
reportId: number,
|
|
||||||
token: string,
|
|
||||||
): Promise<APIResponse<string>> {
|
|
||||||
try {
|
|
||||||
const response = await fetch(`/api/unsignReport/${reportId}`, {
|
|
||||||
method: "PUT",
|
|
||||||
headers: {
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
Authorization: "Bearer " + token,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!response.ok) {
|
|
||||||
return { success: false, message: "Failed to unsign report" };
|
|
||||||
} else {
|
|
||||||
return { success: true, message: "Report unsigned" };
|
|
||||||
}
|
}
|
||||||
} catch (e) {
|
|
||||||
return { success: false, message: "Failed to unsign report" };
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
async promoteToPm(
|
|
||||||
userName: string,
|
|
||||||
projectName: string,
|
|
||||||
token: string,
|
|
||||||
): Promise<APIResponse<string>> {
|
|
||||||
try {
|
|
||||||
const response = await fetch(
|
|
||||||
`/api/promoteToPm/${projectName}?userName=${userName}`,
|
|
||||||
{
|
|
||||||
method: "PUT",
|
|
||||||
headers: {
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
Authorization: "Bearer " + token,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
);
|
|
||||||
if (!response.ok) {
|
|
||||||
return {
|
|
||||||
success: false,
|
|
||||||
message: "Failed to promote user to project manager",
|
|
||||||
};
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
return {
|
|
||||||
success: false,
|
|
||||||
message: "Failed to promote user to project manager",
|
|
||||||
};
|
|
||||||
}
|
|
||||||
return { success: true, message: "User promoted to project manager" };
|
|
||||||
},
|
|
||||||
|
|
||||||
async getUsername(id: number, token: string): Promise<APIResponse<string>> {
|
|
||||||
try {
|
|
||||||
const response = await fetch(`/api/username?userId=${id}`, {
|
|
||||||
method: "GET",
|
|
||||||
headers: {
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
Authorization: "Bearer " + token,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!response.ok) {
|
|
||||||
return { success: false, message: "Failed to get username" };
|
|
||||||
} else {
|
|
||||||
const data = (await response.json()) as string;
|
|
||||||
return { success: true, data };
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
return { success: false, message: "Failed to get username" };
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
async deleteWeeklyReport(
|
|
||||||
reportId: number,
|
|
||||||
token: string,
|
|
||||||
): Promise<APIResponse<string>> {
|
|
||||||
try {
|
|
||||||
const response = await fetch(`/api/deleteReport/${reportId}`, {
|
|
||||||
method: "DELETE",
|
|
||||||
headers: {
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
Authorization: "Bearer " + token,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!response.ok) {
|
|
||||||
return { success: false, message: "Failed to delete report" };
|
|
||||||
} else {
|
|
||||||
return { success: true, message: "Report deleted" };
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
return { success: false, message: "Failed to delete report" };
|
|
||||||
}
|
|
||||||
},
|
|
||||||
async getStatistics(
|
|
||||||
projectName: string,
|
|
||||||
token: string,
|
|
||||||
userName?: string,
|
|
||||||
): Promise<APIResponse<Statistics>> {
|
|
||||||
try {
|
|
||||||
const response = await fetch(
|
|
||||||
`/api/getStatistics/?projectName=${projectName}&userName=${userName ?? ""}`,
|
|
||||||
{
|
|
||||||
method: "GET",
|
|
||||||
headers: {
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
Authorization: "Bearer " + token,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!response.ok) {
|
|
||||||
return { success: false, message: "Failed to get statistics" };
|
|
||||||
} else {
|
|
||||||
const data = (await response.json()) as Statistics;
|
|
||||||
return { success: true, data };
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
return { success: false, message: "Failed to get statistics" };
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
async changeProjectName(
|
|
||||||
projectName: string,
|
|
||||||
newProjectName: string,
|
|
||||||
token: string,
|
|
||||||
): Promise<APIResponse<string>> {
|
|
||||||
try {
|
|
||||||
const response = await fetch(
|
|
||||||
`/api/changeProjectName/${projectName}?newProjectName=${newProjectName}`,
|
|
||||||
{
|
|
||||||
method: "PUT",
|
|
||||||
headers: {
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
Authorization: "Bearer " + token,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!response.ok) {
|
|
||||||
return { success: false, message: "Failed to change project name" };
|
|
||||||
} else {
|
|
||||||
return { success: true, message: "Project name changed" };
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
return { success: false, message: "Failed to change project name" };
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
async changeUserPassword(
|
|
||||||
username: string,
|
|
||||||
newPassword: string,
|
|
||||||
token: string,
|
|
||||||
): Promise<APIResponse<string>> {
|
|
||||||
try {
|
|
||||||
const response = await fetch(
|
|
||||||
`/api/changeUserPassword/${username}?newPassword=${newPassword}`,
|
|
||||||
{
|
|
||||||
method: "PUT",
|
|
||||||
headers: {
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
Authorization: "Bearer " + token,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!response.ok) {
|
|
||||||
return { success: false, message: "Failed to change password" };
|
|
||||||
} else {
|
|
||||||
return { success: true, message: "Password changed" };
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
return { success: false, message: "Failed to change password" };
|
|
||||||
}
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,358 +0,0 @@
|
||||||
/* eslint-disable */
|
|
||||||
/* tslint:disable */
|
|
||||||
/*
|
|
||||||
* ---------------------------------------------------------------
|
|
||||||
* ## THIS FILE WAS GENERATED VIA SWAGGER-TYPESCRIPT-API ##
|
|
||||||
* ## ##
|
|
||||||
* ## AUTHOR: acacode ##
|
|
||||||
* ## SOURCE: https://github.com/acacode/swagger-typescript-api ##
|
|
||||||
* ---------------------------------------------------------------
|
|
||||||
*/
|
|
||||||
|
|
||||||
export interface TypesNewUser {
|
|
||||||
/** @example "password123" */
|
|
||||||
password?: string;
|
|
||||||
/** @example "username123" */
|
|
||||||
username?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface TypesToken {
|
|
||||||
token?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export type QueryParamsType = Record<string | number, any>;
|
|
||||||
export type ResponseFormat = keyof Omit<Body, "body" | "bodyUsed">;
|
|
||||||
|
|
||||||
export interface FullRequestParams extends Omit<RequestInit, "body"> {
|
|
||||||
/** set parameter to `true` for call `securityWorker` for this request */
|
|
||||||
secure?: boolean;
|
|
||||||
/** request path */
|
|
||||||
path: string;
|
|
||||||
/** content type of request body */
|
|
||||||
type?: ContentType;
|
|
||||||
/** query params */
|
|
||||||
query?: QueryParamsType;
|
|
||||||
/** format of response (i.e. response.json() -> format: "json") */
|
|
||||||
format?: ResponseFormat;
|
|
||||||
/** request body */
|
|
||||||
body?: unknown;
|
|
||||||
/** base url */
|
|
||||||
baseUrl?: string;
|
|
||||||
/** request cancellation token */
|
|
||||||
cancelToken?: CancelToken;
|
|
||||||
}
|
|
||||||
|
|
||||||
export type RequestParams = Omit<FullRequestParams, "body" | "method" | "query" | "path">;
|
|
||||||
|
|
||||||
export interface ApiConfig<SecurityDataType = unknown> {
|
|
||||||
baseUrl?: string;
|
|
||||||
baseApiParams?: Omit<RequestParams, "baseUrl" | "cancelToken" | "signal">;
|
|
||||||
securityWorker?: (securityData: SecurityDataType | null) => Promise<RequestParams | void> | RequestParams | void;
|
|
||||||
customFetch?: typeof fetch;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface HttpResponse<D extends unknown, E extends unknown = unknown> extends Response {
|
|
||||||
data: D;
|
|
||||||
error: E;
|
|
||||||
}
|
|
||||||
|
|
||||||
type CancelToken = Symbol | string | number;
|
|
||||||
|
|
||||||
export enum ContentType {
|
|
||||||
Json = "application/json",
|
|
||||||
FormData = "multipart/form-data",
|
|
||||||
UrlEncoded = "application/x-www-form-urlencoded",
|
|
||||||
Text = "text/plain",
|
|
||||||
}
|
|
||||||
|
|
||||||
export class HttpClient<SecurityDataType = unknown> {
|
|
||||||
public baseUrl: string = "//localhost:8080/api";
|
|
||||||
private securityData: SecurityDataType | null = null;
|
|
||||||
private securityWorker?: ApiConfig<SecurityDataType>["securityWorker"];
|
|
||||||
private abortControllers = new Map<CancelToken, AbortController>();
|
|
||||||
private customFetch = (...fetchParams: Parameters<typeof fetch>) => fetch(...fetchParams);
|
|
||||||
|
|
||||||
private baseApiParams: RequestParams = {
|
|
||||||
credentials: "same-origin",
|
|
||||||
headers: {},
|
|
||||||
redirect: "follow",
|
|
||||||
referrerPolicy: "no-referrer",
|
|
||||||
};
|
|
||||||
|
|
||||||
constructor(apiConfig: ApiConfig<SecurityDataType> = {}) {
|
|
||||||
Object.assign(this, apiConfig);
|
|
||||||
}
|
|
||||||
|
|
||||||
public setSecurityData = (data: SecurityDataType | null) => {
|
|
||||||
this.securityData = data;
|
|
||||||
};
|
|
||||||
|
|
||||||
protected encodeQueryParam(key: string, value: any) {
|
|
||||||
const encodedKey = encodeURIComponent(key);
|
|
||||||
return `${encodedKey}=${encodeURIComponent(typeof value === "number" ? value : `${value}`)}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected addQueryParam(query: QueryParamsType, key: string) {
|
|
||||||
return this.encodeQueryParam(key, query[key]);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected addArrayQueryParam(query: QueryParamsType, key: string) {
|
|
||||||
const value = query[key];
|
|
||||||
return value.map((v: any) => this.encodeQueryParam(key, v)).join("&");
|
|
||||||
}
|
|
||||||
|
|
||||||
protected toQueryString(rawQuery?: QueryParamsType): string {
|
|
||||||
const query = rawQuery || {};
|
|
||||||
const keys = Object.keys(query).filter((key) => "undefined" !== typeof query[key]);
|
|
||||||
return keys
|
|
||||||
.map((key) => (Array.isArray(query[key]) ? this.addArrayQueryParam(query, key) : this.addQueryParam(query, key)))
|
|
||||||
.join("&");
|
|
||||||
}
|
|
||||||
|
|
||||||
protected addQueryParams(rawQuery?: QueryParamsType): string {
|
|
||||||
const queryString = this.toQueryString(rawQuery);
|
|
||||||
return queryString ? `?${queryString}` : "";
|
|
||||||
}
|
|
||||||
|
|
||||||
private contentFormatters: Record<ContentType, (input: any) => any> = {
|
|
||||||
[ContentType.Json]: (input: any) =>
|
|
||||||
input !== null && (typeof input === "object" || typeof input === "string") ? JSON.stringify(input) : input,
|
|
||||||
[ContentType.Text]: (input: any) => (input !== null && typeof input !== "string" ? JSON.stringify(input) : input),
|
|
||||||
[ContentType.FormData]: (input: any) =>
|
|
||||||
Object.keys(input || {}).reduce((formData, key) => {
|
|
||||||
const property = input[key];
|
|
||||||
formData.append(
|
|
||||||
key,
|
|
||||||
property instanceof Blob
|
|
||||||
? property
|
|
||||||
: typeof property === "object" && property !== null
|
|
||||||
? JSON.stringify(property)
|
|
||||||
: `${property}`,
|
|
||||||
);
|
|
||||||
return formData;
|
|
||||||
}, new FormData()),
|
|
||||||
[ContentType.UrlEncoded]: (input: any) => this.toQueryString(input),
|
|
||||||
};
|
|
||||||
|
|
||||||
protected mergeRequestParams(params1: RequestParams, params2?: RequestParams): RequestParams {
|
|
||||||
return {
|
|
||||||
...this.baseApiParams,
|
|
||||||
...params1,
|
|
||||||
...(params2 || {}),
|
|
||||||
headers: {
|
|
||||||
...(this.baseApiParams.headers || {}),
|
|
||||||
...(params1.headers || {}),
|
|
||||||
...((params2 && params2.headers) || {}),
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
protected createAbortSignal = (cancelToken: CancelToken): AbortSignal | undefined => {
|
|
||||||
if (this.abortControllers.has(cancelToken)) {
|
|
||||||
const abortController = this.abortControllers.get(cancelToken);
|
|
||||||
if (abortController) {
|
|
||||||
return abortController.signal;
|
|
||||||
}
|
|
||||||
return void 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
const abortController = new AbortController();
|
|
||||||
this.abortControllers.set(cancelToken, abortController);
|
|
||||||
return abortController.signal;
|
|
||||||
};
|
|
||||||
|
|
||||||
public abortRequest = (cancelToken: CancelToken) => {
|
|
||||||
const abortController = this.abortControllers.get(cancelToken);
|
|
||||||
|
|
||||||
if (abortController) {
|
|
||||||
abortController.abort();
|
|
||||||
this.abortControllers.delete(cancelToken);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
public request = async <T = any, E = any>({
|
|
||||||
body,
|
|
||||||
secure,
|
|
||||||
path,
|
|
||||||
type,
|
|
||||||
query,
|
|
||||||
format,
|
|
||||||
baseUrl,
|
|
||||||
cancelToken,
|
|
||||||
...params
|
|
||||||
}: FullRequestParams): Promise<HttpResponse<T, E>> => {
|
|
||||||
const secureParams =
|
|
||||||
((typeof secure === "boolean" ? secure : this.baseApiParams.secure) &&
|
|
||||||
this.securityWorker &&
|
|
||||||
(await this.securityWorker(this.securityData))) ||
|
|
||||||
{};
|
|
||||||
const requestParams = this.mergeRequestParams(params, secureParams);
|
|
||||||
const queryString = query && this.toQueryString(query);
|
|
||||||
const payloadFormatter = this.contentFormatters[type || ContentType.Json];
|
|
||||||
const responseFormat = format || requestParams.format;
|
|
||||||
|
|
||||||
return this.customFetch(`${baseUrl || this.baseUrl || ""}${path}${queryString ? `?${queryString}` : ""}`, {
|
|
||||||
...requestParams,
|
|
||||||
headers: {
|
|
||||||
...(requestParams.headers || {}),
|
|
||||||
...(type && type !== ContentType.FormData ? { "Content-Type": type } : {}),
|
|
||||||
},
|
|
||||||
signal: (cancelToken ? this.createAbortSignal(cancelToken) : requestParams.signal) || null,
|
|
||||||
body: typeof body === "undefined" || body === null ? null : payloadFormatter(body),
|
|
||||||
}).then(async (response) => {
|
|
||||||
const r = response as HttpResponse<T, E>;
|
|
||||||
r.data = null as unknown as T;
|
|
||||||
r.error = null as unknown as E;
|
|
||||||
|
|
||||||
const data = !responseFormat
|
|
||||||
? r
|
|
||||||
: await response[responseFormat]()
|
|
||||||
.then((data) => {
|
|
||||||
if (r.ok) {
|
|
||||||
r.data = data;
|
|
||||||
} else {
|
|
||||||
r.error = data;
|
|
||||||
}
|
|
||||||
return r;
|
|
||||||
})
|
|
||||||
.catch((e) => {
|
|
||||||
r.error = e;
|
|
||||||
return r;
|
|
||||||
});
|
|
||||||
|
|
||||||
if (cancelToken) {
|
|
||||||
this.abortControllers.delete(cancelToken);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!response.ok) throw data;
|
|
||||||
return data;
|
|
||||||
});
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @title TTime API
|
|
||||||
* @version 0.0.1
|
|
||||||
* @license AGPL (https://www.gnu.org/licenses/agpl-3.0.html)
|
|
||||||
* @baseUrl //localhost:8080/api
|
|
||||||
* @externalDocs https://swagger.io/resources/open-api/
|
|
||||||
* @contact
|
|
||||||
*
|
|
||||||
* This is the API for TTime, a time tracking application.
|
|
||||||
*/
|
|
||||||
export class GenApi<SecurityDataType extends unknown> extends HttpClient<SecurityDataType> {
|
|
||||||
login = {
|
|
||||||
/**
|
|
||||||
* @description Logs in a user and returns a JWT token
|
|
||||||
*
|
|
||||||
* @tags Auth
|
|
||||||
* @name LoginCreate
|
|
||||||
* @summary Login
|
|
||||||
* @request POST:/login
|
|
||||||
*/
|
|
||||||
loginCreate: (body: TypesNewUser, params: RequestParams = {}) =>
|
|
||||||
this.request<TypesToken, string>({
|
|
||||||
path: `/login`,
|
|
||||||
method: "POST",
|
|
||||||
body: body,
|
|
||||||
type: ContentType.Json,
|
|
||||||
format: "json",
|
|
||||||
...params,
|
|
||||||
}),
|
|
||||||
};
|
|
||||||
loginrenew = {
|
|
||||||
/**
|
|
||||||
* @description Renews the users token.
|
|
||||||
*
|
|
||||||
* @tags Auth
|
|
||||||
* @name LoginrenewCreate
|
|
||||||
* @summary LoginRenews
|
|
||||||
* @request POST:/loginrenew
|
|
||||||
* @secure
|
|
||||||
*/
|
|
||||||
loginrenewCreate: (params: RequestParams = {}) =>
|
|
||||||
this.request<TypesToken, string>({
|
|
||||||
path: `/loginrenew`,
|
|
||||||
method: "POST",
|
|
||||||
secure: true,
|
|
||||||
format: "json",
|
|
||||||
...params,
|
|
||||||
}),
|
|
||||||
};
|
|
||||||
promoteToAdmin = {
|
|
||||||
/**
|
|
||||||
* @description Promote chosen user to site admin
|
|
||||||
*
|
|
||||||
* @tags User
|
|
||||||
* @name PromoteToAdminCreate
|
|
||||||
* @summary PromoteToAdmin
|
|
||||||
* @request POST:/promoteToAdmin
|
|
||||||
* @secure
|
|
||||||
*/
|
|
||||||
promoteToAdminCreate: (NewUser: TypesNewUser, params: RequestParams = {}) =>
|
|
||||||
this.request<TypesToken, string>({
|
|
||||||
path: `/promoteToAdmin`,
|
|
||||||
method: "POST",
|
|
||||||
body: NewUser,
|
|
||||||
secure: true,
|
|
||||||
type: ContentType.Json,
|
|
||||||
...params,
|
|
||||||
}),
|
|
||||||
};
|
|
||||||
register = {
|
|
||||||
/**
|
|
||||||
* @description Register a new user
|
|
||||||
*
|
|
||||||
* @tags Auth
|
|
||||||
* @name RegisterCreate
|
|
||||||
* @summary Register
|
|
||||||
* @request POST:/register
|
|
||||||
*/
|
|
||||||
registerCreate: (NewUser: TypesNewUser, params: RequestParams = {}) =>
|
|
||||||
this.request<string, string>({
|
|
||||||
path: `/register`,
|
|
||||||
method: "POST",
|
|
||||||
body: NewUser,
|
|
||||||
type: ContentType.Json,
|
|
||||||
...params,
|
|
||||||
}),
|
|
||||||
};
|
|
||||||
userdelete = {
|
|
||||||
/**
|
|
||||||
* @description UserDelete deletes a user from the database
|
|
||||||
*
|
|
||||||
* @tags User
|
|
||||||
* @name UserdeleteDelete
|
|
||||||
* @summary UserDelete
|
|
||||||
* @request DELETE:/userdelete/{username}
|
|
||||||
* @secure
|
|
||||||
*/
|
|
||||||
userdeleteDelete: (username: string, params: RequestParams = {}) =>
|
|
||||||
this.request<string, string>({
|
|
||||||
path: `/userdelete/${username}`,
|
|
||||||
method: "DELETE",
|
|
||||||
secure: true,
|
|
||||||
type: ContentType.Json,
|
|
||||||
...params,
|
|
||||||
}),
|
|
||||||
};
|
|
||||||
users = {
|
|
||||||
/**
|
|
||||||
* @description lists all users
|
|
||||||
*
|
|
||||||
* @tags User
|
|
||||||
* @name GetUsers
|
|
||||||
* @summary ListsAllUsers
|
|
||||||
* @request GET:/users/all
|
|
||||||
* @secure
|
|
||||||
*/
|
|
||||||
getUsers: (params: RequestParams = {}) =>
|
|
||||||
this.request<string[], string>({
|
|
||||||
path: `/users/all`,
|
|
||||||
method: "GET",
|
|
||||||
secure: true,
|
|
||||||
format: "json",
|
|
||||||
...params,
|
|
||||||
}),
|
|
||||||
};
|
|
||||||
}
|
|
|
@ -1,35 +1,39 @@
|
||||||
import { api } from "../API/API";
|
import { APIResponse, api } from "../API/API";
|
||||||
|
import { NewProjMember } from "../Types/goTypes";
|
||||||
export interface AddMemberInfo {
|
|
||||||
userName: string;
|
|
||||||
projectName: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tries to add a member to a project
|
* Tries to add a member to a project
|
||||||
* @param {AddMemberInfo} props.membertoAdd - Contains user's name and project's name
|
* @param {Object} props - A NewProjMember
|
||||||
* @returns {Promise<void>}
|
* @returns {boolean} True if added, false if not
|
||||||
*/
|
*/
|
||||||
async function AddMember(props: { memberToAdd: AddMemberInfo }): Promise<void> {
|
function AddMember(props: { memberToAdd: NewProjMember }): boolean {
|
||||||
if (props.memberToAdd.userName === "") {
|
let added = false;
|
||||||
alert("You must choose at least one user to add");
|
if (
|
||||||
return;
|
props.memberToAdd.username === "" ||
|
||||||
|
props.memberToAdd.role === "" ||
|
||||||
|
props.memberToAdd.projectname === ""
|
||||||
|
) {
|
||||||
|
alert("All fields must be filled before adding");
|
||||||
|
return added;
|
||||||
}
|
}
|
||||||
try {
|
api
|
||||||
const response = await api.addUserToProject(
|
.addUserToProject(
|
||||||
props.memberToAdd,
|
props.memberToAdd,
|
||||||
localStorage.getItem("accessToken") ?? "",
|
localStorage.getItem("accessToken") ?? "",
|
||||||
);
|
)
|
||||||
|
.then((response: APIResponse<NewProjMember>) => {
|
||||||
if (response.success) {
|
if (response.success) {
|
||||||
alert(`[${props.memberToAdd.userName}] added`);
|
alert("Member added");
|
||||||
|
added = true;
|
||||||
} else {
|
} else {
|
||||||
alert(`[${props.memberToAdd.userName}] not added`);
|
alert("Member not added");
|
||||||
console.error(response.message);
|
console.error(response.message);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
})
|
||||||
alert(`[${props.memberToAdd.userName}] not added`);
|
.catch((error) => {
|
||||||
console.error("An error occurred during member add:", error);
|
console.error("An error occurred during member add:", error);
|
||||||
}
|
});
|
||||||
|
return added;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default AddMember;
|
export default AddMember;
|
||||||
|
|
|
@ -1,13 +1,37 @@
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { api } from "../API/API";
|
import { APIResponse, api } from "../API/API";
|
||||||
import { NewProject } from "../Types/goTypes";
|
import { NewProject, Project } from "../Types/goTypes";
|
||||||
|
import InputField from "./InputField";
|
||||||
import Logo from "../assets/Logo.svg";
|
import Logo from "../assets/Logo.svg";
|
||||||
import Button from "./Button";
|
import Button from "./Button";
|
||||||
import { useNavigate } from "react-router-dom";
|
|
||||||
import ProjectNameInput from "./Inputs/ProjectNameInput";
|
/**
|
||||||
import DescriptionInput from "./Inputs/DescriptionInput";
|
* Tries to add a project to the system
|
||||||
import { alphanumeric } from "../Data/regex";
|
* @param {Object} props - Project name and description
|
||||||
import { projNameHighLimit, projNameLowLimit } from "../Data/constants";
|
* @returns {boolean} True if created, false if not
|
||||||
|
*/
|
||||||
|
function CreateProject(props: { name: string; description: string }): boolean {
|
||||||
|
const project: NewProject = {
|
||||||
|
name: props.name,
|
||||||
|
description: props.description,
|
||||||
|
};
|
||||||
|
|
||||||
|
let created = false;
|
||||||
|
|
||||||
|
api
|
||||||
|
.createProject(project, localStorage.getItem("accessToken") ?? "")
|
||||||
|
.then((response: APIResponse<Project>) => {
|
||||||
|
if (response.success) {
|
||||||
|
created = true;
|
||||||
|
} else {
|
||||||
|
console.error(response.message);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
console.error("An error occurred during creation:", error);
|
||||||
|
});
|
||||||
|
return created;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Provides UI for adding a project to the system.
|
* Provides UI for adding a project to the system.
|
||||||
|
@ -16,85 +40,42 @@ import { projNameHighLimit, projNameLowLimit } from "../Data/constants";
|
||||||
function AddProject(): JSX.Element {
|
function AddProject(): JSX.Element {
|
||||||
const [name, setName] = useState("");
|
const [name, setName] = useState("");
|
||||||
const [description, setDescription] = useState("");
|
const [description, setDescription] = useState("");
|
||||||
const navigate = useNavigate();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Tries to add a project to the system
|
|
||||||
*/
|
|
||||||
const handleCreateProject = async (): Promise<void> => {
|
|
||||||
if (
|
|
||||||
!alphanumeric.test(name) ||
|
|
||||||
name.length > projNameHighLimit ||
|
|
||||||
name.length < projNameLowLimit
|
|
||||||
) {
|
|
||||||
alert(
|
|
||||||
"Please provide valid project name: \n-Between 10-99 characters \n-No special characters (.-!?/*)",
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (description.length > projNameHighLimit) {
|
|
||||||
alert("Please provide valid description: \n-Max 100 characters");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const project: NewProject = {
|
|
||||||
name: name.replace(/ /g, ""),
|
|
||||||
description: description.trim(),
|
|
||||||
};
|
|
||||||
try {
|
|
||||||
const response = await api.createProject(
|
|
||||||
project,
|
|
||||||
localStorage.getItem("accessToken") ?? "",
|
|
||||||
);
|
|
||||||
if (response.success) {
|
|
||||||
alert(`${project.name} added!`);
|
|
||||||
setDescription("");
|
|
||||||
setName("");
|
|
||||||
navigate("/admin");
|
|
||||||
} else {
|
|
||||||
alert("Project not added, name could be taken");
|
|
||||||
console.error(response.message);
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
alert("Project not added");
|
|
||||||
console.error(error);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col h-fit w-screen items-center justify-center">
|
<div className="flex flex-col h-fit w-screen items-center justify-center">
|
||||||
<div className="border-4 border-black bg-white flex flex-col items-center justify-center h-fit w-fit rounded-3xl content-center pl-20 pr-20">
|
<div className="border-4 border-black bg-white flex flex-col items-center justify-center h-fit w-fit rounded-3xl content-center pl-20 pr-20">
|
||||||
<form
|
<form
|
||||||
className="bg-white rounded px-8 pt-6 pb-8 mb-4 justify-center flex flex-col w-fit h-fit"
|
className="bg-white rounded px-8 pt-6 pb-8 mb-4 items-center justify-center flex flex-col w-fit h-fit"
|
||||||
onSubmit={(e) => {
|
onSubmit={(e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
void handleCreateProject();
|
CreateProject({ name: name, description: description });
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<img
|
<img
|
||||||
src={Logo}
|
src={Logo}
|
||||||
className="logo w-[7vw] self-center mb-10 mt-10"
|
className="logo w-[7vw] mb-10 mt-10"
|
||||||
alt="TTIME Logo"
|
alt="TTIME Logo"
|
||||||
/>
|
/>
|
||||||
<h3 className="pb-4 mb-2 text-center font-bold text-[18px]">
|
<h3 className="pb-4 mb-2 text-center font-bold text-[18px]">
|
||||||
Create a new project
|
Create a new project
|
||||||
</h3>
|
</h3>
|
||||||
<ProjectNameInput
|
<InputField
|
||||||
name={name}
|
label="Name"
|
||||||
|
type="text"
|
||||||
|
value={name}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
e.preventDefault();
|
|
||||||
setName(e.target.value);
|
setName(e.target.value);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<div className="p-2"></div>
|
<InputField
|
||||||
<DescriptionInput
|
label="Description"
|
||||||
desc={description}
|
type="text"
|
||||||
|
value={description}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
e.preventDefault();
|
|
||||||
setDescription(e.target.value);
|
setDescription(e.target.value);
|
||||||
}}
|
}}
|
||||||
placeholder={"Description (Optional)"}
|
|
||||||
/>
|
/>
|
||||||
<div className="flex self-center mt-4 justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<Button
|
<Button
|
||||||
text="Create"
|
text="Create"
|
||||||
onClick={(): void => {
|
onClick={(): void => {
|
||||||
|
|
|
@ -1,104 +1,72 @@
|
||||||
import { useEffect, useState } from "react";
|
import { useState } from "react";
|
||||||
|
import { NewProjMember } from "../Types/goTypes";
|
||||||
import Button from "./Button";
|
import Button from "./Button";
|
||||||
import AddMember, { AddMemberInfo } from "./AddMember";
|
|
||||||
import GetUsersInProject, { ProjectMember } from "./GetUsersInProject";
|
|
||||||
import GetAllUsers from "./GetAllUsers";
|
import GetAllUsers from "./GetAllUsers";
|
||||||
import InputField from "./InputField";
|
import AddMember from "./AddMember";
|
||||||
|
import BackButton from "./BackButton";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Provides UI for adding a member to a project.
|
* Provides UI for adding a member to a project.
|
||||||
* @returns {JSX.Element} - Returns the component UI for adding a member
|
* @returns {JSX.Element} - Returns the component UI for adding a member
|
||||||
*/
|
*/
|
||||||
function AddUserToProject(props: { projectName: string }): JSX.Element {
|
function AddUserToProject(): JSX.Element {
|
||||||
const [names, setNames] = useState<string[]>([]);
|
const [name, setName] = useState("");
|
||||||
const [users, setUsers] = useState<string[]>([]);
|
const [users, setUsers] = useState<string[]>([]);
|
||||||
const [usersProj, setUsersProj] = useState<ProjectMember[]>([]);
|
const [role, setRole] = useState("");
|
||||||
const [search, setSearch] = useState("");
|
|
||||||
|
|
||||||
// Gets all users and project members for filtering
|
|
||||||
GetAllUsers({ setUsersProp: setUsers });
|
GetAllUsers({ setUsersProp: setUsers });
|
||||||
GetUsersInProject({
|
|
||||||
setUsersProp: setUsersProj,
|
|
||||||
projectName: props.projectName,
|
|
||||||
});
|
|
||||||
/*
|
|
||||||
* Filters the members from users so that users who are already
|
|
||||||
* members are not shown
|
|
||||||
*/
|
|
||||||
useEffect(() => {
|
|
||||||
setUsers((prevUsers) => {
|
|
||||||
const filteredUsers = prevUsers.filter(
|
|
||||||
(user) =>
|
|
||||||
!usersProj.some((projectUser) => projectUser.Username === user),
|
|
||||||
);
|
|
||||||
return filteredUsers;
|
|
||||||
});
|
|
||||||
}, [usersProj]);
|
|
||||||
|
|
||||||
// Attempts to add all of the selected users to the project
|
const handleClick = (): boolean => {
|
||||||
const handleAddClick = async (): Promise<void> => {
|
const newMember: NewProjMember = {
|
||||||
if (names.length === 0) {
|
username: name,
|
||||||
alert("You have to choose at least one user to add");
|
projectname: localStorage.getItem("projectName") ?? "",
|
||||||
return;
|
role: role,
|
||||||
}
|
|
||||||
for (const name of names) {
|
|
||||||
const newMember: AddMemberInfo = {
|
|
||||||
userName: name,
|
|
||||||
projectName: props.projectName,
|
|
||||||
};
|
};
|
||||||
await AddMember({ memberToAdd: newMember });
|
return AddMember({ memberToAdd: newMember });
|
||||||
}
|
|
||||||
setNames([]);
|
|
||||||
location.reload();
|
|
||||||
};
|
|
||||||
|
|
||||||
// Updates the names that have been selected
|
|
||||||
const handleUserClick = (user: string): void => {
|
|
||||||
setNames((prevNames): string[] => {
|
|
||||||
if (!prevNames.includes(user)) {
|
|
||||||
return [...prevNames, user];
|
|
||||||
}
|
|
||||||
return prevNames.filter((name) => name !== user);
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="border-4 border-black bg-white flex flex-col items-center py-10 px-20 rounded-3xl content-center overflow-auto">
|
<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]">
|
||||||
<h1 className="text-center font-bold text-[36px] pb-10">
|
<p className="pb-4 mb-2 text-center font-bold text-[18px]">
|
||||||
{props.projectName}
|
User chosen: [{name}]
|
||||||
</h1>
|
|
||||||
<p className="p-1 text-center font-bold text-[26px]">
|
|
||||||
Choose users to add:
|
|
||||||
</p>
|
</p>
|
||||||
|
<p className="pb-4 mb-2 text-center font-bold text-[18px]">
|
||||||
<div>
|
Role chosen: [{role}]
|
||||||
<InputField
|
</p>
|
||||||
placeholder={"Search users"}
|
<p className="pb-4 mb-2 text-center font-bold text-[18px]">
|
||||||
type={"Text"}
|
Project chosen: [{localStorage.getItem("projectName") ?? ""}]
|
||||||
value={search}
|
</p>
|
||||||
onChange={(e) => {
|
<p className="p-1">Choose role:</p>
|
||||||
setSearch(e.target.value);
|
<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">
|
||||||
/>
|
|
||||||
<ul className="font-medium space-y-2 border-2 border-black mt-2 px-2 pb-2 rounded-2xl text-center overflow-auto h-[26vh] w-[34vh]">
|
|
||||||
<div></div>
|
|
||||||
{users
|
|
||||||
.filter((user) => {
|
|
||||||
return search.toLowerCase() === ""
|
|
||||||
? user
|
|
||||||
: user.toLowerCase().includes(search.toLowerCase());
|
|
||||||
})
|
|
||||||
.map((user) => (
|
|
||||||
<li
|
<li
|
||||||
className={
|
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"
|
||||||
names.includes(user)
|
onClick={() => {
|
||||||
? "items-start p-1 border-2 border-transparent rounded-full bg-orange-500 transition-all hover:bg-orange-600 text-white hover:cursor-pointer ring-2 ring-black"
|
setRole("member");
|
||||||
: "items-start p-1 border-2 border-black rounded-full bg-orange-200 hover:bg-orange-400 transition-all hover:text-white hover:cursor-pointer"
|
}}
|
||||||
}
|
>
|
||||||
|
{"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}
|
key={user}
|
||||||
value={user}
|
value={user}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
handleUserClick(user);
|
setName(user);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<span>{user}</span>
|
<span>{user}</span>
|
||||||
|
@ -106,18 +74,17 @@ function AddUserToProject(props: { projectName: string }): JSX.Element {
|
||||||
))}
|
))}
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
<p className="pt-10 pb-5 underline text-center font-bold text-[18px]">
|
<div className="flex space-x-5 items-center justify-between">
|
||||||
Number of users to be added: {names.length}
|
|
||||||
</p>
|
|
||||||
<div className="space-x-10 items-center">
|
|
||||||
<Button
|
<Button
|
||||||
text="Add"
|
text="Add"
|
||||||
onClick={(): void => {
|
onClick={(): void => {
|
||||||
void handleAddClick();
|
handleClick();
|
||||||
}}
|
}}
|
||||||
type="button"
|
type="submit"
|
||||||
/>
|
/>
|
||||||
|
<BackButton />
|
||||||
</div>
|
</div>
|
||||||
|
<p className="text-center text-gray-500 text-xs"></p>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,7 +17,7 @@ function AllTimeReportsInProject(): JSX.Element {
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const getWeeklyReports = async (): Promise<void> => {
|
const getWeeklyReports = async (): Promise<void> => {
|
||||||
const token = localStorage.getItem("accessToken") ?? "";
|
const token = localStorage.getItem("accessToken") ?? "";
|
||||||
const response = await api.getAllWeeklyReportsForUser(
|
const response = await api.getWeeklyReportsForUser(
|
||||||
projectName ?? "",
|
projectName ?? "",
|
||||||
token,
|
token,
|
||||||
);
|
);
|
||||||
|
@ -37,9 +37,9 @@ function AllTimeReportsInProject(): JSX.Element {
|
||||||
<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]">
|
<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) => (
|
{weeklyReports.map((newWeeklyReport, index) => (
|
||||||
<Link
|
<Link
|
||||||
to={`/editTimeReport/${projectName}/${newWeeklyReport.week}/${newWeeklyReport.signedBy ? "signed" : "unsigned"}`}
|
to={`/editTimeReport/${projectName}/${newWeeklyReport.week}`}
|
||||||
key={index}
|
key={index}
|
||||||
className="border-b-2 border-black w-full cursor-pointer hover:font-extrabold"
|
className="border-b-2 border-black w-full"
|
||||||
>
|
>
|
||||||
<div className="flex justify-between">
|
<div className="flex justify-between">
|
||||||
<h1>
|
<h1>
|
||||||
|
|
|
@ -1,9 +1,8 @@
|
||||||
//Info: This component is used to display all the time reports for a project. It will display the week number,
|
//Info: This component is used to display all the time reports for a project. It will display the week number,
|
||||||
//total time spent, and if the report has been signed or not. The user can click on a report to edit it.
|
//total time spent, and if the report has been signed or not. The user can click on a report to edit it.
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { WeeklyReport } from "../Types/goTypes";
|
import { NewWeeklyReport } from "../Types/goTypes";
|
||||||
import { Link, useParams } from "react-router-dom";
|
import { Link, useParams } from "react-router-dom";
|
||||||
import { api } from "../API/API";
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Renders a component that displays all the time reports for a specific project.
|
* Renders a component that displays all the time reports for a specific project.
|
||||||
|
@ -12,15 +11,15 @@ import { api } from "../API/API";
|
||||||
function AllTimeReportsInProject(): JSX.Element {
|
function AllTimeReportsInProject(): JSX.Element {
|
||||||
const { username } = useParams();
|
const { username } = useParams();
|
||||||
const { projectName } = useParams();
|
const { projectName } = useParams();
|
||||||
const [weeklyReports, setWeeklyReports] = useState<WeeklyReport[]>([]);
|
const [weeklyReports, setWeeklyReports] = useState<NewWeeklyReport[]>([]);
|
||||||
|
|
||||||
|
/* // Call getProjects when the component mounts
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const getWeeklyReports = async (): Promise<void> => {
|
const getWeeklyReports = async (): Promise<void> => {
|
||||||
const token = localStorage.getItem("accessToken") ?? "";
|
const token = localStorage.getItem("accessToken") ?? "";
|
||||||
const response = await api.getAllWeeklyReportsForUser(
|
const response = await api.getWeeklyReportsForUser(
|
||||||
projectName ?? "",
|
projectName ?? "",
|
||||||
token,
|
token,
|
||||||
username ?? "",
|
|
||||||
);
|
);
|
||||||
console.log(response);
|
console.log(response);
|
||||||
if (response.success) {
|
if (response.success) {
|
||||||
|
@ -28,10 +27,41 @@ function AllTimeReportsInProject(): JSX.Element {
|
||||||
} else {
|
} else {
|
||||||
console.error(response.message);
|
console.error(response.message);
|
||||||
}
|
}
|
||||||
};
|
}; */
|
||||||
|
// Mock data
|
||||||
|
const getWeeklyReports = async (): Promise<void> => {
|
||||||
|
// Simulate a delay
|
||||||
|
await Promise.resolve();
|
||||||
|
const mockWeeklyReports: NewWeeklyReport[] = [
|
||||||
|
{
|
||||||
|
projectName: "Project 1",
|
||||||
|
week: 1,
|
||||||
|
developmentTime: 10,
|
||||||
|
meetingTime: 2,
|
||||||
|
adminTime: 1,
|
||||||
|
ownWorkTime: 3,
|
||||||
|
studyTime: 4,
|
||||||
|
testingTime: 5,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
projectName: "Project 1",
|
||||||
|
week: 2,
|
||||||
|
developmentTime: 8,
|
||||||
|
meetingTime: 2,
|
||||||
|
adminTime: 1,
|
||||||
|
ownWorkTime: 3,
|
||||||
|
studyTime: 4,
|
||||||
|
testingTime: 5,
|
||||||
|
},
|
||||||
|
// Add more reports as needed
|
||||||
|
];
|
||||||
|
|
||||||
|
// Use the mock data instead of the real data
|
||||||
|
setWeeklyReports(mockWeeklyReports);
|
||||||
|
};
|
||||||
|
useEffect(() => {
|
||||||
void getWeeklyReports();
|
void getWeeklyReports();
|
||||||
}, [projectName, username]);
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
@ -39,9 +69,9 @@ function AllTimeReportsInProject(): JSX.Element {
|
||||||
<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]">
|
<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) => (
|
{weeklyReports.map((newWeeklyReport, index) => (
|
||||||
<Link
|
<Link
|
||||||
to={`/editOthersTR/${projectName}/${username}/${newWeeklyReport.week}/${newWeeklyReport.signedBy ? "signed" : "unsigned"}`}
|
to={`/editOthersTR/${projectName}/${username}/${newWeeklyReport.week}`}
|
||||||
key={index}
|
key={index}
|
||||||
className="border-b-2 border-black w-full hover:font-extrabold"
|
className="border-b-2 border-black w-full"
|
||||||
>
|
>
|
||||||
<div className="flex justify-between">
|
<div className="flex justify-between">
|
||||||
<h1>
|
<h1>
|
||||||
|
@ -60,7 +90,7 @@ function AllTimeReportsInProject(): JSX.Element {
|
||||||
</h1>
|
</h1>
|
||||||
<h1>
|
<h1>
|
||||||
<span className="font-bold">{"Signed: "}</span>
|
<span className="font-bold">{"Signed: "}</span>
|
||||||
{newWeeklyReport.signedBy ? "YES" : "NO"}
|
NO
|
||||||
</h1>
|
</h1>
|
||||||
</div>
|
</div>
|
||||||
</Link>
|
</Link>
|
||||||
|
|
|
@ -1,36 +0,0 @@
|
||||||
import { APIResponse, api } from "../API/API";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Changes the name of a project
|
|
||||||
* @param {string} props.projectName - Current project name
|
|
||||||
* @param {string} props.newProjectName - New project name
|
|
||||||
* @returns {void} - Nothing
|
|
||||||
*/
|
|
||||||
export default function ChangeProjectName(props: {
|
|
||||||
projectName: string;
|
|
||||||
newProjectName: string;
|
|
||||||
}): void {
|
|
||||||
if (props.projectName === "" || props.projectName === props.newProjectName) {
|
|
||||||
alert("You have to give a new name\n\nName not changed");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
api
|
|
||||||
.changeProjectName(
|
|
||||||
props.projectName,
|
|
||||||
props.newProjectName,
|
|
||||||
localStorage.getItem("accessToken") ?? "",
|
|
||||||
)
|
|
||||||
.then((response: APIResponse<string>) => {
|
|
||||||
if (response.success) {
|
|
||||||
alert("Name changed successfully");
|
|
||||||
location.reload();
|
|
||||||
} else {
|
|
||||||
alert("Name not changed, name could be taken");
|
|
||||||
console.error(response.message);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch((error) => {
|
|
||||||
alert("Name not changed");
|
|
||||||
console.error("An error occurred during change:", error);
|
|
||||||
});
|
|
||||||
}
|
|
|
@ -1,37 +0,0 @@
|
||||||
import { APIResponse, api } from "../API/API";
|
|
||||||
|
|
||||||
export interface ProjectRoleChange {
|
|
||||||
username: string;
|
|
||||||
role: "project_manager" | "member" | "";
|
|
||||||
projectname: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function ChangeRole(roleChangeInfo: ProjectRoleChange): void {
|
|
||||||
if (
|
|
||||||
roleChangeInfo.username === "" ||
|
|
||||||
roleChangeInfo.role === "" ||
|
|
||||||
roleChangeInfo.projectname === ""
|
|
||||||
) {
|
|
||||||
// FOR DEBUG
|
|
||||||
// console.log(roleChangeInfo.role + ": Role");
|
|
||||||
// console.log(roleChangeInfo.projectname + ": P-Name");
|
|
||||||
// console.log(roleChangeInfo.username + ": U-name");
|
|
||||||
alert("You have to select a role");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
api
|
|
||||||
.changeUserRole(roleChangeInfo, localStorage.getItem("accessToken") ?? "")
|
|
||||||
.then((response: APIResponse<void>) => {
|
|
||||||
if (response.success) {
|
|
||||||
alert("Role changed successfully");
|
|
||||||
location.reload();
|
|
||||||
} else {
|
|
||||||
alert(response.message);
|
|
||||||
console.error(response.message);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch((error) => {
|
|
||||||
alert(error);
|
|
||||||
console.error("An error occurred during change:", error);
|
|
||||||
});
|
|
||||||
}
|
|
|
@ -1,76 +0,0 @@
|
||||||
import { useState } from "react";
|
|
||||||
import Button from "./Button";
|
|
||||||
import ChangeRole, { ProjectRoleChange } from "./ChangeRole";
|
|
||||||
|
|
||||||
export default function ChangeRoleView(props: {
|
|
||||||
projectName: string;
|
|
||||||
username: string;
|
|
||||||
currentRole: string;
|
|
||||||
}): JSX.Element {
|
|
||||||
const [selectedRole, setSelectedRole] = useState<
|
|
||||||
"project_manager" | "member" | ""
|
|
||||||
>("");
|
|
||||||
|
|
||||||
const handleRoleChange = (
|
|
||||||
event: React.ChangeEvent<HTMLInputElement>,
|
|
||||||
): void => {
|
|
||||||
if (event.target.value === "member") {
|
|
||||||
setSelectedRole(event.target.value);
|
|
||||||
} else if (event.target.value === "project_manager") {
|
|
||||||
setSelectedRole(event.target.value);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleSubmit = (event: React.FormEvent<HTMLFormElement>): void => {
|
|
||||||
console.log("Cur: " + props.currentRole + " " + "new: " + selectedRole);
|
|
||||||
event.preventDefault();
|
|
||||||
if (selectedRole === props.currentRole) {
|
|
||||||
alert(`Already ${props.currentRole}, nothing changed`);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const roleChangeInfo: ProjectRoleChange = {
|
|
||||||
username: props.username,
|
|
||||||
projectname: props.projectName,
|
|
||||||
role: selectedRole,
|
|
||||||
};
|
|
||||||
ChangeRole(roleChangeInfo);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="overflow-auto">
|
|
||||||
<h1 className="font-bold text-[20px]">Select role:</h1>
|
|
||||||
<form onSubmit={handleSubmit}>
|
|
||||||
<div className="py-1 px-1 w-full self-start text-left font-medium overflow-auto border-2 border-black rounded-2xl">
|
|
||||||
<label className="hover:cursor-pointer hover:font-bold">
|
|
||||||
<input
|
|
||||||
type="radio"
|
|
||||||
value="project_manager"
|
|
||||||
checked={selectedRole === "project_manager"}
|
|
||||||
onChange={handleRoleChange}
|
|
||||||
className="m-2"
|
|
||||||
/>
|
|
||||||
Project manager
|
|
||||||
</label>
|
|
||||||
<br />
|
|
||||||
<label className="hover:cursor-pointer hover:font-bold">
|
|
||||||
<input
|
|
||||||
type="radio"
|
|
||||||
value="member"
|
|
||||||
checked={selectedRole === "member"}
|
|
||||||
onChange={handleRoleChange}
|
|
||||||
className="m-2 hover:cursor-pointer"
|
|
||||||
/>
|
|
||||||
Member
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
<Button
|
|
||||||
text="Change"
|
|
||||||
onClick={(): void => {
|
|
||||||
return;
|
|
||||||
}}
|
|
||||||
type="submit"
|
|
||||||
/>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
|
@ -1,36 +0,0 @@
|
||||||
import { APIResponse, api } from "../API/API";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Changes the password of a user
|
|
||||||
* @param {string} props.username - The username of the user
|
|
||||||
* @param {string} props.newPassword - The new password
|
|
||||||
* @returns {void} - Nothing
|
|
||||||
*/
|
|
||||||
export default function ChangeUserPassword(props: {
|
|
||||||
username: string;
|
|
||||||
newPassword: string;
|
|
||||||
}): void {
|
|
||||||
if (props.username === localStorage.getItem("username")) {
|
|
||||||
alert("You cannot change admin password");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
api
|
|
||||||
.changeUserPassword(
|
|
||||||
props.username,
|
|
||||||
props.newPassword,
|
|
||||||
localStorage.getItem("accessToken") ?? "",
|
|
||||||
)
|
|
||||||
.then((response: APIResponse<string>) => {
|
|
||||||
if (response.success) {
|
|
||||||
alert("Password changed successfully");
|
|
||||||
location.reload();
|
|
||||||
} else {
|
|
||||||
alert("Password not changed");
|
|
||||||
console.error(response.message);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch((error) => {
|
|
||||||
alert("Password not changed");
|
|
||||||
console.error("An error occurred during change:", error);
|
|
||||||
});
|
|
||||||
}
|
|
|
@ -1,33 +1,61 @@
|
||||||
import { APIResponse, api } from "../API/API";
|
import React, { useState } from "react";
|
||||||
import { StrNameChange } from "../Types/goTypes";
|
import InputField from "./InputField";
|
||||||
|
import { api } from "../API/API";
|
||||||
|
|
||||||
function ChangeUsername(props: { nameChange: StrNameChange }): void {
|
function ChangeUsername(): JSX.Element {
|
||||||
if (
|
const [newUsername, setNewUsername] = useState("");
|
||||||
props.nameChange.newName === "" ||
|
const [errorMessage, setErrorMessage] = useState("");
|
||||||
props.nameChange.newName === props.nameChange.prevName
|
|
||||||
) {
|
const handleChange = (e: React.ChangeEvent<HTMLInputElement>): void => {
|
||||||
alert("You have to give a new name\n\nName not changed");
|
setNewUsername(e.target.value);
|
||||||
return;
|
};
|
||||||
|
|
||||||
|
const handleSubmit = async (): Promise<void> => {
|
||||||
|
try {
|
||||||
|
// Call the API function to change the username
|
||||||
|
const token = localStorage.getItem("accessToken");
|
||||||
|
if (!token) {
|
||||||
|
throw new Error("Access token not found");
|
||||||
}
|
}
|
||||||
if (props.nameChange.prevName === localStorage.getItem("username")) {
|
|
||||||
alert("You cannot change admin name");
|
const response = await api.changeUserName(
|
||||||
return;
|
{ prevName: "currentName", newName: newUsername },
|
||||||
}
|
token,
|
||||||
api
|
);
|
||||||
.changeUserName(props.nameChange, localStorage.getItem("accessToken") ?? "")
|
|
||||||
.then((response: APIResponse<void>) => {
|
|
||||||
if (response.success) {
|
if (response.success) {
|
||||||
alert("Name changed successfully");
|
// Optionally, add a success message or redirect the user
|
||||||
location.reload();
|
console.log("Username changed successfully");
|
||||||
} else {
|
} else {
|
||||||
alert("Name not changed, name could be taken");
|
// Handle the error message
|
||||||
console.error(response.message);
|
console.error("Failed to change username:", response.message);
|
||||||
|
setErrorMessage(response.message ?? "Failed to change username");
|
||||||
}
|
}
|
||||||
})
|
} catch (error) {
|
||||||
.catch((error) => {
|
console.error("Error changing username:", error);
|
||||||
alert("Name not changed");
|
// Optionally, handle the error
|
||||||
console.error("An error occurred during change:", error);
|
setErrorMessage("Failed to change username");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleButtonClick = (): void => {
|
||||||
|
handleSubmit().catch((error) => {
|
||||||
|
console.error("Error in handleSubmit:", error);
|
||||||
});
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<InputField
|
||||||
|
label="New Username"
|
||||||
|
type="text"
|
||||||
|
value={newUsername}
|
||||||
|
onChange={handleChange}
|
||||||
|
/>
|
||||||
|
{errorMessage && <div>{errorMessage}</div>}
|
||||||
|
<button onClick={handleButtonClick}>Update Username</button>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default ChangeUsername;
|
export default ChangeUsername;
|
||||||
|
|
|
@ -1,33 +0,0 @@
|
||||||
import { api, APIResponse } from "../API/API";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Use to delete a project from the system
|
|
||||||
* @param {string} props.projectToDelete - The projectname of project to delete
|
|
||||||
* @returns {void} Nothing
|
|
||||||
* @example
|
|
||||||
* const exampleProjectName = "project";
|
|
||||||
* DeleteProject({ projectToDelete: exampleProjectName });
|
|
||||||
*/
|
|
||||||
|
|
||||||
function DeleteProject(props: { projectToDelete: string }): void {
|
|
||||||
api
|
|
||||||
.removeProject(
|
|
||||||
props.projectToDelete,
|
|
||||||
localStorage.getItem("accessToken") ?? "",
|
|
||||||
)
|
|
||||||
.then((response: APIResponse<string>) => {
|
|
||||||
if (response.success) {
|
|
||||||
alert("Project has been deleted!");
|
|
||||||
location.reload();
|
|
||||||
} else {
|
|
||||||
alert("Project has not been deleted");
|
|
||||||
console.error(response.message);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch((error) => {
|
|
||||||
alert("project has not been deleted");
|
|
||||||
console.error("An error occurred during deletion:", error);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export default DeleteProject;
|
|
|
@ -3,7 +3,7 @@ import { api, APIResponse } from "../API/API";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Use to remove a user from the system
|
* Use to remove a user from the system
|
||||||
* @param {string} props.usernameToDelete - The username of user to remove
|
* @param props - The username of user to remove
|
||||||
* @returns {boolean} True if removed, false if not
|
* @returns {boolean} True if removed, false if not
|
||||||
* @example
|
* @example
|
||||||
* const exampleUsername = "user";
|
* const exampleUsername = "user";
|
||||||
|
@ -29,7 +29,7 @@ function DeleteUser(props: { usernameToDelete: string }): boolean {
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
alert("User has not been deleted");
|
alert("User has not been deleted");
|
||||||
console.error("An error occurred during deletion:", error);
|
console.error("An error occurred during creation:", error);
|
||||||
});
|
});
|
||||||
return removed;
|
return removed;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,38 +1,93 @@
|
||||||
import { useState, useEffect } from "react";
|
import { useState, useEffect } from "react";
|
||||||
import { Link, useParams } from "react-router-dom";
|
import { Link, useParams } from "react-router-dom";
|
||||||
import { api } from "../API/API";
|
|
||||||
import { WeeklyReport } from "../Types/goTypes";
|
|
||||||
|
|
||||||
|
interface UnsignedReports {
|
||||||
|
projectName: string;
|
||||||
|
username: string;
|
||||||
|
week: number;
|
||||||
|
signed: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Renders a component that displays the projects a user is a part of and links to the projects start-page.
|
||||||
|
* @returns The JSX element representing the component.
|
||||||
|
*/
|
||||||
function DisplayUserProject(): JSX.Element {
|
function DisplayUserProject(): JSX.Element {
|
||||||
const { projectName } = useParams();
|
const { projectName } = useParams();
|
||||||
const [unsignedReports, setUnsignedReports] = useState<WeeklyReport[]>([]);
|
const [unsignedReports, setUnsignedReports] = useState<UnsignedReports[]>([]);
|
||||||
const [usernames, setUsernames] = useState<string[]>([]);
|
//const navigate = useNavigate();
|
||||||
const token = localStorage.getItem("accessToken") ?? "";
|
|
||||||
|
// 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);
|
||||||
|
// }
|
||||||
|
// };
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const getUnsignedReports = async (): Promise<void> => {
|
const getUnsignedReports = async (): Promise<void> => {
|
||||||
const response = await api.getUnsignedReportsInProject(
|
// Simulate a delay
|
||||||
projectName ?? "",
|
await Promise.resolve();
|
||||||
token,
|
|
||||||
);
|
// Use mock data
|
||||||
console.log(response);
|
const reports: UnsignedReports[] = [
|
||||||
if (response.success) {
|
{
|
||||||
setUnsignedReports(response.data ?? []);
|
projectName: "projecttest",
|
||||||
const usernamesPromises = (response.data ?? []).map((report) =>
|
username: "user1",
|
||||||
api.getUsername(report.userId, token),
|
week: 2,
|
||||||
);
|
signed: false,
|
||||||
const usernamesResponses = await Promise.all(usernamesPromises);
|
},
|
||||||
const usernames = usernamesResponses.map(
|
{
|
||||||
(res) => (res.data as { username?: string }).username ?? "",
|
projectName: "projecttest",
|
||||||
);
|
username: "user2",
|
||||||
setUsernames(usernames);
|
week: 2,
|
||||||
} else {
|
signed: false,
|
||||||
console.error(response.message);
|
},
|
||||||
}
|
{
|
||||||
|
projectName: "projecttest",
|
||||||
|
username: "user3",
|
||||||
|
week: 2,
|
||||||
|
signed: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
projectName: "projecttest",
|
||||||
|
username: "user4",
|
||||||
|
week: 2,
|
||||||
|
signed: false,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
// Set the state with the mock data
|
||||||
|
setUnsignedReports(reports);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Call getProjects when the component mounts
|
||||||
|
useEffect(() => {
|
||||||
void getUnsignedReports();
|
void getUnsignedReports();
|
||||||
}, [projectName, token]);
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
@ -40,32 +95,23 @@ function DisplayUserProject(): JSX.Element {
|
||||||
All Unsigned Reports In: {projectName}{" "}
|
All Unsigned Reports In: {projectName}{" "}
|
||||||
</h1>
|
</h1>
|
||||||
<div className="border-4 border-black bg-white flex flex-col items-center justify-center min-h-[65vh] h-fit w-[70vw] rounded-3xl content-center overflow-scroll space-y-[10vh] p-[30px] text-[20px]">
|
<div className="border-4 border-black bg-white flex flex-col items-center justify-center min-h-[65vh] h-fit w-[70vw] rounded-3xl content-center overflow-scroll space-y-[10vh] p-[30px] text-[20px]">
|
||||||
{unsignedReports.map((unsignedReport: WeeklyReport, index: number) => (
|
{unsignedReports.map(
|
||||||
|
(unsignedReport: UnsignedReports, index: number) => (
|
||||||
<h1 key={index} className="border-b-2 border-black w-full">
|
<h1 key={index} className="border-b-2 border-black w-full">
|
||||||
<div className="flex justify-between">
|
<div className="flex justify-between">
|
||||||
<div className="flex">
|
<div className="flex">
|
||||||
<span className="ml-6 mr-2 font-bold">Username:</span>
|
<h1>{unsignedReport.username}</h1>
|
||||||
<h1>{usernames[index]}</h1>{" "}
|
|
||||||
<span className="ml-6 mr-2 font-bold">Week:</span>
|
<span className="ml-6 mr-2 font-bold">Week:</span>
|
||||||
<h1>{unsignedReport.week}</h1>
|
<h1>{unsignedReport.week}</h1>
|
||||||
<span className="ml-6 mr-2 font-bold">Total Time:</span>
|
|
||||||
<h1>
|
|
||||||
{unsignedReport.developmentTime +
|
|
||||||
unsignedReport.meetingTime +
|
|
||||||
unsignedReport.adminTime +
|
|
||||||
unsignedReport.ownWorkTime +
|
|
||||||
unsignedReport.studyTime +
|
|
||||||
unsignedReport.testingTime}
|
|
||||||
</h1>
|
|
||||||
<span className="ml-6 mr-2 font-bold">Signed:</span>
|
<span className="ml-6 mr-2 font-bold">Signed:</span>
|
||||||
<h1>NO</h1>
|
<h1>NO</h1>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex">
|
<div className="flex">
|
||||||
<div className="ml-auto flex space-x-4">
|
<div className="ml-auto flex space-x-4">
|
||||||
<Link
|
<Link
|
||||||
to={`/PMViewUnsignedReport/${projectName}/${usernames[index]}/${unsignedReport.week}`}
|
to={`/PMViewUnsignedReport/${projectName}/${unsignedReport.username}/${unsignedReport.week}`}
|
||||||
>
|
>
|
||||||
<h1 className="cursor-pointer font-bold hover:font-extrabold hover:underline">
|
<h1 className="underline cursor-pointer font-bold">
|
||||||
View Report
|
View Report
|
||||||
</h1>
|
</h1>
|
||||||
</Link>
|
</Link>
|
||||||
|
@ -73,7 +119,8 @@ function DisplayUserProject(): JSX.Element {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</h1>
|
</h1>
|
||||||
))}
|
),
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
import { useState } from "react";
|
import { useState, useEffect } from "react";
|
||||||
import { Project } from "../Types/goTypes";
|
import { Project } from "../Types/goTypes";
|
||||||
import { useNavigate } from "react-router-dom";
|
import { useNavigate } from "react-router-dom";
|
||||||
import GetProjects from "./GetProjects";
|
|
||||||
import { api } from "../API/API";
|
import { api } from "../API/API";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -12,20 +11,22 @@ function DisplayUserProject(): JSX.Element {
|
||||||
const [projects, setProjects] = useState<Project[]>([]);
|
const [projects, setProjects] = useState<Project[]>([]);
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
GetProjects({
|
const getProjects = async (): Promise<void> => {
|
||||||
setProjectsProp: setProjects,
|
const token = localStorage.getItem("accessToken") ?? "";
|
||||||
username: localStorage.getItem("username") ?? "",
|
const response = await api.getUserProjects(token);
|
||||||
});
|
console.log(response);
|
||||||
|
if (response.success) {
|
||||||
|
setProjects(response.data ?? []);
|
||||||
|
} else {
|
||||||
|
console.error(response.message);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const handleProjectClick = async (projectName: string): Promise<void> => {
|
const handleProjectClick = async (projectName: string): Promise<void> => {
|
||||||
const token = localStorage.getItem("accessToken") ?? "";
|
const token = localStorage.getItem("accessToken") ?? "";
|
||||||
const response = await api.checkIfProjectManager(projectName, token);
|
const response = await api.checkIfProjectManager(projectName, token);
|
||||||
console.log(response.data);
|
|
||||||
if (response.success) {
|
if (response.success) {
|
||||||
if (
|
if (response.data) {
|
||||||
(response.data as unknown as { isProjectManager: boolean })
|
|
||||||
.isProjectManager
|
|
||||||
) {
|
|
||||||
navigate(`/PMProjectPage/${projectName}`);
|
navigate(`/PMProjectPage/${projectName}`);
|
||||||
} else {
|
} else {
|
||||||
navigate(`/project/${projectName}`);
|
navigate(`/project/${projectName}`);
|
||||||
|
@ -36,6 +37,11 @@ function DisplayUserProject(): JSX.Element {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Call getProjects when the component mounts
|
||||||
|
useEffect(() => {
|
||||||
|
void getProjects();
|
||||||
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<h1 className="font-bold text-[30px] mb-[20px]">Your Projects</h1>
|
<h1 className="font-bold text-[30px] mb-[20px]">Your Projects</h1>
|
||||||
|
@ -45,7 +51,7 @@ function DisplayUserProject(): JSX.Element {
|
||||||
onClick={() => void handleProjectClick(project.name)}
|
onClick={() => void handleProjectClick(project.name)}
|
||||||
key={project.id}
|
key={project.id}
|
||||||
>
|
>
|
||||||
<h1 className="font-bold hover:underline text-[30px] cursor-pointer hover:font-extrabold">
|
<h1 className="font-bold underline text-[30px] cursor-pointer">
|
||||||
{project.name}
|
{project.name}
|
||||||
</h1>
|
</h1>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { useState, useEffect } from "react";
|
import { useState, useEffect } from "react";
|
||||||
import { WeeklyReport, UpdateWeeklyReport } from "../Types/goTypes";
|
import { WeeklyReport, NewWeeklyReport } from "../Types/goTypes";
|
||||||
import { api } from "../API/API";
|
import { api } from "../API/API";
|
||||||
import { useNavigate, useParams } from "react-router-dom";
|
import { useNavigate, useParams } from "react-router-dom";
|
||||||
import Button from "./Button";
|
import Button from "./Button";
|
||||||
|
@ -18,13 +18,11 @@ 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, signedOrUnsigned } = useParams<{
|
const { projectName, fetchedWeek } = useParams<{
|
||||||
projectName: string;
|
projectName: string;
|
||||||
fetchedWeek: string;
|
fetchedWeek: string;
|
||||||
signedOrUnsigned: string;
|
|
||||||
}>();
|
}>();
|
||||||
const username = localStorage.getItem("userName") ?? "";
|
console.log(projectName, fetchedWeek);
|
||||||
console.log(projectName, fetchedWeek, signedOrUnsigned);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const fetchWeeklyReport = async (): Promise<void> => {
|
const fetchWeeklyReport = async (): Promise<void> => {
|
||||||
|
@ -60,11 +58,10 @@ export default function GetWeeklyReport(): JSX.Element {
|
||||||
};
|
};
|
||||||
|
|
||||||
void fetchWeeklyReport();
|
void fetchWeeklyReport();
|
||||||
}, [projectName, fetchedWeek, signedOrUnsigned, token]);
|
}, [projectName, fetchedWeek, token]);
|
||||||
|
|
||||||
const handleUpdateWeeklyReport = async (): Promise<void> => {
|
const handleNewWeeklyReport = async (): Promise<void> => {
|
||||||
const updateWeeklyReport: UpdateWeeklyReport = {
|
const newWeeklyReport: NewWeeklyReport = {
|
||||||
userName: username,
|
|
||||||
projectName: projectName ?? "",
|
projectName: projectName ?? "",
|
||||||
week,
|
week,
|
||||||
developmentTime,
|
developmentTime,
|
||||||
|
@ -75,7 +72,7 @@ export default function GetWeeklyReport(): JSX.Element {
|
||||||
testingTime,
|
testingTime,
|
||||||
};
|
};
|
||||||
|
|
||||||
await api.updateWeeklyReport(updateWeeklyReport, token);
|
await api.submitWeeklyReport(newWeeklyReport, token);
|
||||||
};
|
};
|
||||||
|
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
@ -92,8 +89,7 @@ export default function GetWeeklyReport(): JSX.Element {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
void handleUpdateWeeklyReport();
|
void handleNewWeeklyReport();
|
||||||
alert("Changes submitted");
|
|
||||||
navigate(-1);
|
navigate(-1);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
@ -132,20 +128,9 @@ export default function GetWeeklyReport(): JSX.Element {
|
||||||
}}
|
}}
|
||||||
onKeyDown={(event) => {
|
onKeyDown={(event) => {
|
||||||
const keyValue = event.key;
|
const keyValue = event.key;
|
||||||
if (
|
if (!/\d/.test(keyValue) && keyValue !== "Backspace")
|
||||||
!/\d/.test(keyValue) &&
|
|
||||||
keyValue !== "Backspace" &&
|
|
||||||
keyValue !== "ArrowLeft" &&
|
|
||||||
keyValue !== "ArrowRight"
|
|
||||||
)
|
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
}}
|
}}
|
||||||
onClick={() => {
|
|
||||||
if (signedOrUnsigned === "signed") {
|
|
||||||
alert("You cannot edit a signed report.");
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
readOnly={signedOrUnsigned === "signed"}
|
|
||||||
/>
|
/>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
@ -167,20 +152,9 @@ export default function GetWeeklyReport(): JSX.Element {
|
||||||
}}
|
}}
|
||||||
onKeyDown={(event) => {
|
onKeyDown={(event) => {
|
||||||
const keyValue = event.key;
|
const keyValue = event.key;
|
||||||
if (
|
if (!/\d/.test(keyValue) && keyValue !== "Backspace")
|
||||||
!/\d/.test(keyValue) &&
|
|
||||||
keyValue !== "Backspace" &&
|
|
||||||
keyValue !== "ArrowLeft" &&
|
|
||||||
keyValue !== "ArrowRight"
|
|
||||||
)
|
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
}}
|
}}
|
||||||
onClick={() => {
|
|
||||||
if (signedOrUnsigned === "signed") {
|
|
||||||
alert("You cannot edit a signed report.");
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
readOnly={signedOrUnsigned === "signed"}
|
|
||||||
/>
|
/>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
@ -202,20 +176,9 @@ export default function GetWeeklyReport(): JSX.Element {
|
||||||
}}
|
}}
|
||||||
onKeyDown={(event) => {
|
onKeyDown={(event) => {
|
||||||
const keyValue = event.key;
|
const keyValue = event.key;
|
||||||
if (
|
if (!/\d/.test(keyValue) && keyValue !== "Backspace")
|
||||||
!/\d/.test(keyValue) &&
|
|
||||||
keyValue !== "Backspace" &&
|
|
||||||
keyValue !== "ArrowLeft" &&
|
|
||||||
keyValue !== "ArrowRight"
|
|
||||||
)
|
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
}}
|
}}
|
||||||
onClick={() => {
|
|
||||||
if (signedOrUnsigned === "signed") {
|
|
||||||
alert("You cannot edit a signed report.");
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
readOnly={signedOrUnsigned === "signed"}
|
|
||||||
/>
|
/>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
@ -237,20 +200,9 @@ export default function GetWeeklyReport(): JSX.Element {
|
||||||
}}
|
}}
|
||||||
onKeyDown={(event) => {
|
onKeyDown={(event) => {
|
||||||
const keyValue = event.key;
|
const keyValue = event.key;
|
||||||
if (
|
if (!/\d/.test(keyValue) && keyValue !== "Backspace")
|
||||||
!/\d/.test(keyValue) &&
|
|
||||||
keyValue !== "Backspace" &&
|
|
||||||
keyValue !== "ArrowLeft" &&
|
|
||||||
keyValue !== "ArrowRight"
|
|
||||||
)
|
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
}}
|
}}
|
||||||
onClick={() => {
|
|
||||||
if (signedOrUnsigned === "signed") {
|
|
||||||
alert("You cannot edit a signed report.");
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
readOnly={signedOrUnsigned === "signed"}
|
|
||||||
/>
|
/>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
@ -272,20 +224,9 @@ export default function GetWeeklyReport(): JSX.Element {
|
||||||
}}
|
}}
|
||||||
onKeyDown={(event) => {
|
onKeyDown={(event) => {
|
||||||
const keyValue = event.key;
|
const keyValue = event.key;
|
||||||
if (
|
if (!/\d/.test(keyValue) && keyValue !== "Backspace")
|
||||||
!/\d/.test(keyValue) &&
|
|
||||||
keyValue !== "Backspace" &&
|
|
||||||
keyValue !== "ArrowLeft" &&
|
|
||||||
keyValue !== "ArrowRight"
|
|
||||||
)
|
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
}}
|
}}
|
||||||
onClick={() => {
|
|
||||||
if (signedOrUnsigned === "signed") {
|
|
||||||
alert("You cannot edit a signed report.");
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
readOnly={signedOrUnsigned === "signed"}
|
|
||||||
/>
|
/>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
@ -307,26 +248,14 @@ export default function GetWeeklyReport(): JSX.Element {
|
||||||
}}
|
}}
|
||||||
onKeyDown={(event) => {
|
onKeyDown={(event) => {
|
||||||
const keyValue = event.key;
|
const keyValue = event.key;
|
||||||
if (
|
if (!/\d/.test(keyValue) && keyValue !== "Backspace")
|
||||||
!/\d/.test(keyValue) &&
|
|
||||||
keyValue !== "Backspace" &&
|
|
||||||
keyValue !== "ArrowLeft" &&
|
|
||||||
keyValue !== "ArrowRight"
|
|
||||||
)
|
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
}}
|
}}
|
||||||
onClick={() => {
|
|
||||||
if (signedOrUnsigned === "signed") {
|
|
||||||
alert("You cannot edit a signed report.");
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
readOnly={signedOrUnsigned === "signed"}
|
|
||||||
/>
|
/>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
{signedOrUnsigned !== "signed" && (
|
|
||||||
<Button
|
<Button
|
||||||
text="Submit changes"
|
text="Submit changes"
|
||||||
onClick={(): void => {
|
onClick={(): void => {
|
||||||
|
@ -334,7 +263,6 @@ export default function GetWeeklyReport(): JSX.Element {
|
||||||
}}
|
}}
|
||||||
type="submit"
|
type="submit"
|
||||||
/>
|
/>
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,59 +0,0 @@
|
||||||
import { Dispatch, SetStateAction, useEffect } from "react";
|
|
||||||
import { api } from "../API/API";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Interface for reported time per category + total time reported
|
|
||||||
*/
|
|
||||||
export interface projectTimes {
|
|
||||||
admin: number;
|
|
||||||
development: number;
|
|
||||||
meeting: number;
|
|
||||||
own_work: number;
|
|
||||||
study: number;
|
|
||||||
testing: number;
|
|
||||||
totalTime?: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets all reported times for this project
|
|
||||||
* @param {Dispatch} props.setTimesProp - A setStateAction for the map you want to put times in
|
|
||||||
* @param {string} props.projectName - Username
|
|
||||||
* @returns {void} Nothing
|
|
||||||
* @example
|
|
||||||
* const projectName = "Example";
|
|
||||||
* const [times, setTimes] = useState<Times>();
|
|
||||||
* GetProjectTimes({ setTimesProp: setTimes, projectName: projectName });
|
|
||||||
*/
|
|
||||||
function GetProjectTimes(props: {
|
|
||||||
setTimesProp: Dispatch<SetStateAction<projectTimes | undefined>>;
|
|
||||||
projectName: string;
|
|
||||||
}): void {
|
|
||||||
const setTimes: Dispatch<SetStateAction<projectTimes | undefined>> =
|
|
||||||
props.setTimesProp;
|
|
||||||
useEffect(() => {
|
|
||||||
const fetchUsers = async (): Promise<void> => {
|
|
||||||
try {
|
|
||||||
const token = localStorage.getItem("accessToken") ?? "";
|
|
||||||
const response = await api.getProjectTimes(props.projectName, token);
|
|
||||||
if (response.success && response.data) {
|
|
||||||
// Calculates total time reported
|
|
||||||
response.data.totalTime = response.data.admin;
|
|
||||||
response.data.totalTime += response.data.development;
|
|
||||||
response.data.totalTime += response.data.meeting;
|
|
||||||
response.data.totalTime += response.data.own_work;
|
|
||||||
response.data.totalTime += response.data.study;
|
|
||||||
response.data.totalTime += response.data.testing;
|
|
||||||
setTimes(response.data);
|
|
||||||
} else {
|
|
||||||
console.error("Failed to fetch project times:", response.message);
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error("Error fetching times:", error);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
void fetchUsers();
|
|
||||||
}, [props.projectName, setTimes]);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default GetProjectTimes;
|
|
|
@ -4,17 +4,14 @@ import { api } from "../API/API";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets all projects that user is a member of
|
* Gets all projects that user is a member of
|
||||||
* @param {Dispatch} props.setProjectsProp - A setStateAction for the array you want to put projects in
|
* @param props - A setStateAction for the array you want to put projects in
|
||||||
* @param {string} props.username - Username
|
|
||||||
* @returns {void} Nothing
|
* @returns {void} Nothing
|
||||||
* @example
|
* @example
|
||||||
* const username = "Example";
|
|
||||||
* const [projects, setProjects] = useState<Project[]>([]);
|
* const [projects, setProjects] = useState<Project[]>([]);
|
||||||
* GetProjects({ setProjectsProp: setProjects, username: username });
|
* GetAllUsers({ setProjectsProp: setProjects });
|
||||||
*/
|
*/
|
||||||
function GetProjects(props: {
|
function GetProjects(props: {
|
||||||
setProjectsProp: Dispatch<React.SetStateAction<Project[]>>;
|
setProjectsProp: Dispatch<React.SetStateAction<Project[]>>;
|
||||||
username: string;
|
|
||||||
}): void {
|
}): void {
|
||||||
const setProjects: Dispatch<React.SetStateAction<Project[]>> =
|
const setProjects: Dispatch<React.SetStateAction<Project[]>> =
|
||||||
props.setProjectsProp;
|
props.setProjectsProp;
|
||||||
|
@ -22,7 +19,7 @@ function GetProjects(props: {
|
||||||
const fetchUsers = async (): Promise<void> => {
|
const fetchUsers = async (): Promise<void> => {
|
||||||
try {
|
try {
|
||||||
const token = localStorage.getItem("accessToken") ?? "";
|
const token = localStorage.getItem("accessToken") ?? "";
|
||||||
const response = await api.getUserProjects(props.username, token);
|
const response = await api.getUserProjects(token);
|
||||||
if (response.success) {
|
if (response.success) {
|
||||||
setProjects(response.data ?? []);
|
setProjects(response.data ?? []);
|
||||||
} else {
|
} else {
|
||||||
|
@ -34,7 +31,7 @@ function GetProjects(props: {
|
||||||
};
|
};
|
||||||
|
|
||||||
void fetchUsers();
|
void fetchUsers();
|
||||||
}, [props.username, setProjects]);
|
}, [setProjects]);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default GetProjects;
|
export default GetProjects;
|
||||||
|
|
|
@ -1,25 +1,20 @@
|
||||||
import { Dispatch, useEffect } from "react";
|
import { Dispatch, useEffect } from "react";
|
||||||
|
import { UserProjectMember } from "../Types/goTypes";
|
||||||
import { api } from "../API/API";
|
import { api } from "../API/API";
|
||||||
|
|
||||||
export interface ProjectMember {
|
|
||||||
Username: string;
|
|
||||||
UserRole: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets all members of a project
|
* Gets all projects that user is a member of
|
||||||
* @param string - The project's name
|
* @param props - A setStateAction for the array you want to put projects in
|
||||||
* @param Dispatch - A setStateAction for the array you want to put members in
|
|
||||||
* @returns {void} Nothing
|
* @returns {void} Nothing
|
||||||
* @example
|
* @example
|
||||||
* const [users, setUsers] = useState<User[]>([]);
|
* const [projects, setProjects] = useState<Project[]>([]);
|
||||||
* GetUsersInProject({ projectName: props.projectname, setUsersProp: setUsers });
|
* GetAllUsers({ setProjectsProp: setProjects });
|
||||||
*/
|
*/
|
||||||
function GetUsersInProject(props: {
|
function GetUsersInProject(props: {
|
||||||
projectName: string;
|
projectName: string;
|
||||||
setUsersProp: Dispatch<React.SetStateAction<ProjectMember[]>>;
|
setUsersProp: Dispatch<React.SetStateAction<UserProjectMember[]>>;
|
||||||
}): void {
|
}): void {
|
||||||
const setUsers: Dispatch<React.SetStateAction<ProjectMember[]>> =
|
const setUsers: Dispatch<React.SetStateAction<UserProjectMember[]>> =
|
||||||
props.setUsersProp;
|
props.setUsersProp;
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const fetchUsers = async (): Promise<void> => {
|
const fetchUsers = async (): Promise<void> => {
|
||||||
|
@ -29,10 +24,10 @@ function GetUsersInProject(props: {
|
||||||
if (response.success) {
|
if (response.success) {
|
||||||
setUsers(response.data ?? []);
|
setUsers(response.data ?? []);
|
||||||
} else {
|
} else {
|
||||||
console.error("Failed to fetch members:", response.message);
|
console.error("Failed to fetch projects:", response.message);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error fetching members:", error);
|
console.error("Error fetching projects:", error);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
void fetchUsers();
|
void fetchUsers();
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
//info: Header component to display the header of the page including the logo and user information where thr user can logout
|
//info: Header component to display the header of the page including the logo and user information where thr user can logout
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { Link, useNavigate } from "react-router-dom";
|
import { Link } from "react-router-dom";
|
||||||
import backgroundImage from "../assets/1.jpg";
|
import backgroundImage from "../assets/1.jpg";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -9,33 +9,23 @@ import backgroundImage from "../assets/1.jpg";
|
||||||
*/
|
*/
|
||||||
function Header(): JSX.Element {
|
function Header(): JSX.Element {
|
||||||
const [isOpen, setIsOpen] = useState(false);
|
const [isOpen, setIsOpen] = useState(false);
|
||||||
const username = localStorage.getItem("username");
|
|
||||||
const navigate = useNavigate();
|
|
||||||
|
|
||||||
const handleLogout = (): void => {
|
const handleLogout = (): void => {
|
||||||
localStorage.clear();
|
localStorage.clear();
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleNavigation = (): void => {
|
|
||||||
if (username === "admin") {
|
|
||||||
navigate("/admin");
|
|
||||||
} else {
|
|
||||||
navigate("/yourProjects");
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<header
|
<header
|
||||||
className="fixed top-0 left-0 right-0 border-[1.75px] border-black text-black p-3 pl-5 flex items-center justify-between bg-cover"
|
className="fixed top-0 left-0 right-0 border-[1.75px] border-black text-black p-3 pl-5 flex items-center justify-between bg-cover"
|
||||||
style={{ backgroundImage: `url(${backgroundImage})` }}
|
style={{ backgroundImage: `url(${backgroundImage})` }}
|
||||||
>
|
>
|
||||||
<div onClick={handleNavigation}>
|
<Link to="/your-projects">
|
||||||
<img
|
<img
|
||||||
src="/src/assets/Logo.svg"
|
src="/src/assets/Logo.svg"
|
||||||
alt="TTIME Logo"
|
alt="TTIME Logo"
|
||||||
className="w-11 h-14 cursor-pointer"
|
className="w-11 h-14 cursor-pointer"
|
||||||
/>
|
/>
|
||||||
</div>
|
</Link>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
className="relative"
|
className="relative"
|
||||||
|
|
|
@ -4,24 +4,22 @@
|
||||||
* @returns {JSX.Element} The input field
|
* @returns {JSX.Element} The input field
|
||||||
* @example
|
* @example
|
||||||
* <InputField
|
* <InputField
|
||||||
* label="Example"
|
|
||||||
* placeholder="New placeholder"
|
|
||||||
* type="text"
|
* type="text"
|
||||||
* value={example}
|
* label="Example"
|
||||||
* onChange={(e) => {
|
* onChange={(e) => {
|
||||||
* setExample(e.target.value);
|
* setExample(e.target.value);
|
||||||
* }}
|
* }}
|
||||||
|
* value={example}
|
||||||
* />
|
* />
|
||||||
*/
|
*/
|
||||||
function InputField(props: {
|
function InputField(props: {
|
||||||
label?: string;
|
label: string;
|
||||||
placeholder?: string;
|
type: string;
|
||||||
type?: string;
|
value: string;
|
||||||
value?: string;
|
onChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
|
||||||
onChange?: (e: React.ChangeEvent<HTMLInputElement>) => void;
|
|
||||||
}): JSX.Element {
|
}): JSX.Element {
|
||||||
return (
|
return (
|
||||||
<div className="">
|
<div className="mb-4">
|
||||||
<label
|
<label
|
||||||
className="block text-gray-700 text-sm font-sans font-bold mb-2"
|
className="block text-gray-700 text-sm font-sans font-bold mb-2"
|
||||||
htmlFor={props.label}
|
htmlFor={props.label}
|
||||||
|
@ -32,7 +30,7 @@ function InputField(props: {
|
||||||
className="appearance-none border-2 border-black rounded-2xl w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline"
|
className="appearance-none border-2 border-black rounded-2xl w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline"
|
||||||
id={props.label}
|
id={props.label}
|
||||||
type={props.type}
|
type={props.type}
|
||||||
placeholder={props.placeholder}
|
placeholder={props.label}
|
||||||
value={props.value}
|
value={props.value}
|
||||||
onChange={props.onChange}
|
onChange={props.onChange}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -1,38 +0,0 @@
|
||||||
import { projDescHighLimit, projDescLowLimit } from "../../Data/constants";
|
|
||||||
|
|
||||||
export default function DescriptionInput(props: {
|
|
||||||
desc: string;
|
|
||||||
placeholder: string;
|
|
||||||
onChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
|
|
||||||
}): JSX.Element {
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<input
|
|
||||||
className={
|
|
||||||
props.desc.length <= 100
|
|
||||||
? "border-2 border-green-500 dark:border-green-500 focus-visible:border-green-500 outline-none rounded-2xl w-full py-2 px-3 text-gray-700 leading-tight"
|
|
||||||
: "border-2 border-red-600 dark:border-red-600 focus:border-red-600 outline-none rounded-2xl w-full py-2 px-3 text-gray-700 leading-tight"
|
|
||||||
}
|
|
||||||
spellCheck="true"
|
|
||||||
id="New desc"
|
|
||||||
type="text"
|
|
||||||
placeholder={props.placeholder}
|
|
||||||
value={props.desc}
|
|
||||||
onChange={props.onChange}
|
|
||||||
/>
|
|
||||||
<div className="my-1">
|
|
||||||
{props.desc.length > projDescHighLimit && (
|
|
||||||
<p className="text-red-600 pl-2 text-[13px] text-left">
|
|
||||||
Description must be under 100 characters
|
|
||||||
</p>
|
|
||||||
)}
|
|
||||||
{props.desc.length <= projDescHighLimit &&
|
|
||||||
props.desc.length > projDescLowLimit && (
|
|
||||||
<p className="text-green-500 pl-2 text-[13px] text-left">
|
|
||||||
Valid project description!
|
|
||||||
</p>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
|
@ -1,44 +0,0 @@
|
||||||
import { passwordLength } from "../../Data/constants";
|
|
||||||
import { lowercase } from "../../Data/regex";
|
|
||||||
|
|
||||||
export default function PasswordInput(props: {
|
|
||||||
password: string;
|
|
||||||
onChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
|
|
||||||
}): JSX.Element {
|
|
||||||
const password = props.password;
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<input
|
|
||||||
className={
|
|
||||||
password.length === passwordLength && lowercase.test(password)
|
|
||||||
? "border-2 border-green-500 dark:border-green-500 focus-visible:border-green-500 outline-none rounded-2xl w-full py-2 px-3 text-gray-700 leading-tight"
|
|
||||||
: "border-2 border-red-600 dark:border-red-600 focus:border-red-600 outline-none rounded-2xl w-full py-2 px-3 text-gray-700 leading-tight"
|
|
||||||
}
|
|
||||||
spellCheck="false"
|
|
||||||
id="New password"
|
|
||||||
type="password"
|
|
||||||
placeholder="Password"
|
|
||||||
value={password}
|
|
||||||
onChange={props.onChange}
|
|
||||||
/>
|
|
||||||
<div className="my-1">
|
|
||||||
{password.length === passwordLength &&
|
|
||||||
lowercase.test(props.password) && (
|
|
||||||
<p className="text-green-500 pl-2 text-[13px] text-left">
|
|
||||||
Valid password!
|
|
||||||
</p>
|
|
||||||
)}
|
|
||||||
{password.length !== passwordLength && (
|
|
||||||
<p className="text-red-600 pl-2 text-[13px] text-left">
|
|
||||||
Password must be 6 characters
|
|
||||||
</p>
|
|
||||||
)}
|
|
||||||
{!lowercase.test(password) && password !== "" && (
|
|
||||||
<p className="text-red-600 pl-2 text-[13px] text-left">
|
|
||||||
No number, uppercase or special <br /> characters allowed
|
|
||||||
</p>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
|
@ -1,48 +0,0 @@
|
||||||
import { projNameHighLimit, projNameLowLimit } from "../../Data/constants";
|
|
||||||
import { alphanumeric } from "../../Data/regex";
|
|
||||||
|
|
||||||
export default function ProjectNameInput(props: {
|
|
||||||
name: string;
|
|
||||||
onChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
|
|
||||||
}): JSX.Element {
|
|
||||||
const name = props.name;
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<input
|
|
||||||
className={
|
|
||||||
name.length >= projNameLowLimit &&
|
|
||||||
name.length <= projNameHighLimit &&
|
|
||||||
alphanumeric.test(name)
|
|
||||||
? "border-2 border-green-500 dark:border-green-500 focus-visible:border-green-500 outline-none rounded-2xl w-full py-2 px-3 text-gray-700 leading-tight"
|
|
||||||
: "border-2 border-red-600 dark:border-red-600 focus:border-red-600 outline-none rounded-2xl w-full py-2 px-3 text-gray-700 leading-tight"
|
|
||||||
}
|
|
||||||
spellCheck="false"
|
|
||||||
id="New name"
|
|
||||||
type="text"
|
|
||||||
placeholder="Project name"
|
|
||||||
value={name}
|
|
||||||
onChange={props.onChange}
|
|
||||||
/>
|
|
||||||
<div className="my-1">
|
|
||||||
{!alphanumeric.test(name) && name !== "" && (
|
|
||||||
<p className="text-red-600 pl-2 text-[13px] text-left">
|
|
||||||
No special characters allowed
|
|
||||||
</p>
|
|
||||||
)}
|
|
||||||
{(name.length < projNameLowLimit ||
|
|
||||||
name.length > projNameHighLimit) && (
|
|
||||||
<p className="text-red-600 pl-2 text-[13px] text-left">
|
|
||||||
Project name must be 10-99 characters
|
|
||||||
</p>
|
|
||||||
)}
|
|
||||||
{alphanumeric.test(props.name) &&
|
|
||||||
name.length >= projNameLowLimit &&
|
|
||||||
name.length <= projNameHighLimit && (
|
|
||||||
<p className="text-green-500 pl-2 text-[13px] text-left">
|
|
||||||
Valid project name!
|
|
||||||
</p>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
|
@ -1,50 +0,0 @@
|
||||||
import { usernameLowLimit, usernameUpLimit } from "../../Data/constants";
|
|
||||||
import { alphanumeric } from "../../Data/regex";
|
|
||||||
|
|
||||||
export default function UsernameInput(props: {
|
|
||||||
username: string;
|
|
||||||
onChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
|
|
||||||
}): JSX.Element {
|
|
||||||
const username = props.username;
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<input
|
|
||||||
className={
|
|
||||||
username.length >= usernameLowLimit &&
|
|
||||||
username.length <= usernameUpLimit &&
|
|
||||||
alphanumeric.test(props.username)
|
|
||||||
? "border-2 border-green-500 dark:border-green-500 focus-visible:border-green-500 outline-none rounded-2xl w-full py-2 px-3 text-gray-700 leading-tight"
|
|
||||||
: "border-2 border-red-600 dark:border-red-600 focus:border-red-600 outline-none rounded-2xl w-full py-2 px-3 text-gray-700 leading-tight"
|
|
||||||
}
|
|
||||||
spellCheck="false"
|
|
||||||
id="New username"
|
|
||||||
type="text"
|
|
||||||
placeholder="Username"
|
|
||||||
value={username}
|
|
||||||
onChange={props.onChange}
|
|
||||||
/>
|
|
||||||
<div className="my-1">
|
|
||||||
{alphanumeric.test(username) &&
|
|
||||||
username.length >= usernameLowLimit &&
|
|
||||||
username.length <= usernameUpLimit && (
|
|
||||||
<p className="text-green-500 pl-2 text-[13px] text-left">
|
|
||||||
Valid username!
|
|
||||||
</p>
|
|
||||||
)}
|
|
||||||
{!alphanumeric.test(username) && username !== "" && (
|
|
||||||
<p className="text-red-600 pl-2 text-[13px] text-left">
|
|
||||||
No special characters allowed
|
|
||||||
</p>
|
|
||||||
)}
|
|
||||||
{!(
|
|
||||||
username.length >= usernameLowLimit &&
|
|
||||||
username.length <= usernameUpLimit
|
|
||||||
) && (
|
|
||||||
<p className="text-red-600 pl-2 text-[13px] text-left">
|
|
||||||
Username must be 5-10 characters
|
|
||||||
</p>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
|
@ -11,10 +11,6 @@ function LoginCheck(props: {
|
||||||
password: string;
|
password: string;
|
||||||
setAuthority: Dispatch<SetStateAction<number>>;
|
setAuthority: Dispatch<SetStateAction<number>>;
|
||||||
}): void {
|
}): void {
|
||||||
if (props.username === "" || props.password === "") {
|
|
||||||
alert("Please enter username and password to login");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const user: NewUser = {
|
const user: NewUser = {
|
||||||
username: props.username,
|
username: props.username,
|
||||||
password: props.password,
|
password: props.password,
|
||||||
|
@ -46,15 +42,7 @@ function LoginCheck(props: {
|
||||||
console.error("Token was undefined");
|
console.error("Token was undefined");
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (response.data === "500") {
|
console.error("Token could not be fetched/No such user");
|
||||||
console.error(response.message);
|
|
||||||
alert("No connection/Error");
|
|
||||||
} else {
|
|
||||||
console.error(
|
|
||||||
"Token could not be fetched/No such user" + response.message,
|
|
||||||
);
|
|
||||||
alert("Incorrect login information");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
|
|
|
@ -25,7 +25,6 @@ function Login(props: {
|
||||||
}): JSX.Element {
|
}): JSX.Element {
|
||||||
return (
|
return (
|
||||||
<form className="flex flex-col items-center" onSubmit={props.handleSubmit}>
|
<form className="flex flex-col items-center" onSubmit={props.handleSubmit}>
|
||||||
<div className="space-y-3">
|
|
||||||
<InputField
|
<InputField
|
||||||
type="text"
|
type="text"
|
||||||
label="Username"
|
label="Username"
|
||||||
|
@ -33,7 +32,6 @@ function Login(props: {
|
||||||
props.setUsername(e.target.value);
|
props.setUsername(e.target.value);
|
||||||
}}
|
}}
|
||||||
value={props.username}
|
value={props.username}
|
||||||
placeholder={"Username"}
|
|
||||||
/>
|
/>
|
||||||
<InputField
|
<InputField
|
||||||
type="password"
|
type="password"
|
||||||
|
@ -42,9 +40,7 @@ function Login(props: {
|
||||||
props.setPassword(e.target.value);
|
props.setPassword(e.target.value);
|
||||||
}}
|
}}
|
||||||
value={props.password}
|
value={props.password}
|
||||||
placeholder={"Password"}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
|
||||||
<Button
|
<Button
|
||||||
text="Login"
|
text="Login"
|
||||||
onClick={(): void => {
|
onClick={(): void => {
|
||||||
|
|
|
@ -1,78 +0,0 @@
|
||||||
import Button from "./Button";
|
|
||||||
import UserProjectListAdmin from "./UserProjectListAdmin";
|
|
||||||
import { useState } from "react";
|
|
||||||
import RemoveUserFromProj from "./RemoveUserFromProj";
|
|
||||||
import ChangeRoleInput from "./ChangeRoleView";
|
|
||||||
|
|
||||||
function MemberInfoModal(props: {
|
|
||||||
projectName: string;
|
|
||||||
username: string;
|
|
||||||
role: string;
|
|
||||||
onClose: () => void;
|
|
||||||
}): JSX.Element {
|
|
||||||
const [showRoles, setShowRoles] = useState(false);
|
|
||||||
|
|
||||||
const handleChangeRole = (): void => {
|
|
||||||
if (showRoles) {
|
|
||||||
setShowRoles(false);
|
|
||||||
} else {
|
|
||||||
setShowRoles(true);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
className="fixed inset-0 bg-opacity-30 backdrop-blur-sm
|
|
||||||
flex justify-center items-center"
|
|
||||||
>
|
|
||||||
<div className="border-4 border-black bg-white rounded-2xl text-center flex flex-col">
|
|
||||||
<div className="mx-10">
|
|
||||||
<p className="font-bold text-[30px]">{props.username}</p>
|
|
||||||
<p className="font-bold text-[20px]">{props.role}</p>
|
|
||||||
<p
|
|
||||||
className="hover:font-bold hover:cursor-pointer underline mb-2 mt-1"
|
|
||||||
onClick={handleChangeRole}
|
|
||||||
>
|
|
||||||
(Change Role)
|
|
||||||
</p>
|
|
||||||
{showRoles && (
|
|
||||||
<ChangeRoleInput
|
|
||||||
projectName={props.projectName}
|
|
||||||
username={props.username}
|
|
||||||
currentRole={props.role}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
<h2 className="font-bold text-[20px]">Member of these projects:</h2>
|
|
||||||
<UserProjectListAdmin username={props.username} />
|
|
||||||
<div className="items-center space-x-6">
|
|
||||||
<Button
|
|
||||||
text={"Remove"}
|
|
||||||
onClick={function (): void {
|
|
||||||
if (
|
|
||||||
window.confirm(
|
|
||||||
"Are you sure you want to remove this user from the project?",
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
RemoveUserFromProj({
|
|
||||||
userToRemove: props.username,
|
|
||||||
projectName: props.projectName,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
type="button"
|
|
||||||
/>
|
|
||||||
<Button
|
|
||||||
text={"Close"}
|
|
||||||
onClick={function (): void {
|
|
||||||
setShowRoles(false);
|
|
||||||
props.onClose();
|
|
||||||
}}
|
|
||||||
type="button"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default MemberInfoModal;
|
|
|
@ -1,24 +0,0 @@
|
||||||
import { useNavigate } from "react-router-dom";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Renders a navigation button component for navigating to
|
|
||||||
* different page
|
|
||||||
* @returns The JSX element representing the navigation button.
|
|
||||||
*/
|
|
||||||
export default function NavButton(props: {
|
|
||||||
navTo: string;
|
|
||||||
label: string;
|
|
||||||
}): JSX.Element {
|
|
||||||
const navigate = useNavigate();
|
|
||||||
const goBack = (): void => {
|
|
||||||
navigate(props.navTo);
|
|
||||||
};
|
|
||||||
return (
|
|
||||||
<button
|
|
||||||
onClick={goBack}
|
|
||||||
className="inline-block py-1 px-8 font-bold bg-orange-500 text-white border-2 border-black rounded-full cursor-pointer mt-5 mb-5 transition-colors duration-10 hover:bg-orange-600 hover:text-gray-300 font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; font-size: 4vh;"
|
|
||||||
>
|
|
||||||
{props.label}
|
|
||||||
</button>
|
|
||||||
);
|
|
||||||
}
|
|
|
@ -62,7 +62,7 @@ export default function NewWeeklyReport(): JSX.Element {
|
||||||
const success = await handleNewWeeklyReport();
|
const success = await handleNewWeeklyReport();
|
||||||
if (!success) {
|
if (!success) {
|
||||||
alert(
|
alert(
|
||||||
"Error occurred! Your connection to the server might be lost or a time report for this week already exists, please check your connection or go to the edit page to edit your report or change week number.",
|
"A Time Report for this week already exists, please go to the edit page to edit it or change week number.",
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -139,12 +139,7 @@ export default function NewWeeklyReport(): JSX.Element {
|
||||||
}}
|
}}
|
||||||
onKeyDown={(event) => {
|
onKeyDown={(event) => {
|
||||||
const keyValue = event.key;
|
const keyValue = event.key;
|
||||||
if (
|
if (!/\d/.test(keyValue) && keyValue !== "Backspace")
|
||||||
!/\d/.test(keyValue) &&
|
|
||||||
keyValue !== "Backspace" &&
|
|
||||||
keyValue !== "ArrowLeft" &&
|
|
||||||
keyValue !== "ArrowRight"
|
|
||||||
)
|
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
@ -168,12 +163,7 @@ export default function NewWeeklyReport(): JSX.Element {
|
||||||
}}
|
}}
|
||||||
onKeyDown={(event) => {
|
onKeyDown={(event) => {
|
||||||
const keyValue = event.key;
|
const keyValue = event.key;
|
||||||
if (
|
if (!/\d/.test(keyValue) && keyValue !== "Backspace")
|
||||||
!/\d/.test(keyValue) &&
|
|
||||||
keyValue !== "Backspace" &&
|
|
||||||
keyValue !== "ArrowLeft" &&
|
|
||||||
keyValue !== "ArrowRight"
|
|
||||||
)
|
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
@ -197,12 +187,7 @@ export default function NewWeeklyReport(): JSX.Element {
|
||||||
}}
|
}}
|
||||||
onKeyDown={(event) => {
|
onKeyDown={(event) => {
|
||||||
const keyValue = event.key;
|
const keyValue = event.key;
|
||||||
if (
|
if (!/\d/.test(keyValue) && keyValue !== "Backspace")
|
||||||
!/\d/.test(keyValue) &&
|
|
||||||
keyValue !== "Backspace" &&
|
|
||||||
keyValue !== "ArrowLeft" &&
|
|
||||||
keyValue !== "ArrowRight"
|
|
||||||
)
|
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
@ -226,12 +211,7 @@ export default function NewWeeklyReport(): JSX.Element {
|
||||||
}}
|
}}
|
||||||
onKeyDown={(event) => {
|
onKeyDown={(event) => {
|
||||||
const keyValue = event.key;
|
const keyValue = event.key;
|
||||||
if (
|
if (!/\d/.test(keyValue) && keyValue !== "Backspace")
|
||||||
!/\d/.test(keyValue) &&
|
|
||||||
keyValue !== "Backspace" &&
|
|
||||||
keyValue !== "ArrowLeft" &&
|
|
||||||
keyValue !== "ArrowRight"
|
|
||||||
)
|
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
@ -255,12 +235,7 @@ export default function NewWeeklyReport(): JSX.Element {
|
||||||
}}
|
}}
|
||||||
onKeyDown={(event) => {
|
onKeyDown={(event) => {
|
||||||
const keyValue = event.key;
|
const keyValue = event.key;
|
||||||
if (
|
if (!/\d/.test(keyValue) && keyValue !== "Backspace")
|
||||||
!/\d/.test(keyValue) &&
|
|
||||||
keyValue !== "Backspace" &&
|
|
||||||
keyValue !== "ArrowLeft" &&
|
|
||||||
keyValue !== "ArrowRight"
|
|
||||||
)
|
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
@ -284,12 +259,7 @@ export default function NewWeeklyReport(): JSX.Element {
|
||||||
}}
|
}}
|
||||||
onKeyDown={(event) => {
|
onKeyDown={(event) => {
|
||||||
const keyValue = event.key;
|
const keyValue = event.key;
|
||||||
if (
|
if (!/\d/.test(keyValue) && keyValue !== "Backspace")
|
||||||
!/\d/.test(keyValue) &&
|
|
||||||
keyValue !== "Backspace" &&
|
|
||||||
keyValue !== "ArrowLeft" &&
|
|
||||||
keyValue !== "ArrowRight"
|
|
||||||
)
|
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -1,8 +1,7 @@
|
||||||
import { useState, useEffect } from "react";
|
import { useState, useEffect } from "react";
|
||||||
import { WeeklyReport } from "../Types/goTypes";
|
import { WeeklyReport } from "../Types/goTypes";
|
||||||
import { api } from "../API/API";
|
import { api } from "../API/API";
|
||||||
import { useParams, useNavigate } from "react-router-dom";
|
import { useParams } from "react-router-dom";
|
||||||
import Button from "./Button";
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Renders the component for editing a weekly report.
|
* Renders the component for editing a weekly report.
|
||||||
|
@ -18,14 +17,11 @@ export default function OtherUsersTR(): JSX.Element {
|
||||||
const [ownWorkTime, setOwnWorkTime] = useState(0);
|
const [ownWorkTime, setOwnWorkTime] = useState(0);
|
||||||
const [studyTime, setStudyTime] = useState(0);
|
const [studyTime, setStudyTime] = useState(0);
|
||||||
const [testingTime, setTestingTime] = useState(0);
|
const [testingTime, setTestingTime] = useState(0);
|
||||||
const [reportId, setReportId] = useState(0);
|
|
||||||
|
|
||||||
const token = localStorage.getItem("accessToken") ?? "";
|
const token = localStorage.getItem("accessToken") ?? "";
|
||||||
const { projectName } = useParams();
|
const { projectName } = useParams();
|
||||||
const { username } = useParams();
|
const { username } = useParams();
|
||||||
const { fetchedWeek } = useParams();
|
const { fetchedWeek } = useParams();
|
||||||
const { signedOrUnsigned } = useParams();
|
|
||||||
console.log(projectName, username, fetchedWeek, signedOrUnsigned);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const fetchUsersWeeklyReport = async (): Promise<void> => {
|
const fetchUsersWeeklyReport = async (): Promise<void> => {
|
||||||
|
@ -33,7 +29,6 @@ export default function OtherUsersTR(): JSX.Element {
|
||||||
projectName ?? "",
|
projectName ?? "",
|
||||||
fetchedWeek?.toString() ?? "0",
|
fetchedWeek?.toString() ?? "0",
|
||||||
token,
|
token,
|
||||||
username ?? "",
|
|
||||||
);
|
);
|
||||||
|
|
||||||
if (response.success) {
|
if (response.success) {
|
||||||
|
@ -49,7 +44,6 @@ export default function OtherUsersTR(): JSX.Element {
|
||||||
studyTime: 0,
|
studyTime: 0,
|
||||||
testingTime: 0,
|
testingTime: 0,
|
||||||
};
|
};
|
||||||
setReportId(report.reportId);
|
|
||||||
setWeek(report.week);
|
setWeek(report.week);
|
||||||
setDevelopmentTime(report.developmentTime);
|
setDevelopmentTime(report.developmentTime);
|
||||||
setMeetingTime(report.meetingTime);
|
setMeetingTime(report.meetingTime);
|
||||||
|
@ -65,27 +59,6 @@ export default function OtherUsersTR(): JSX.Element {
|
||||||
void fetchUsersWeeklyReport();
|
void fetchUsersWeeklyReport();
|
||||||
});
|
});
|
||||||
|
|
||||||
const handleUnsignWeeklyReport = async (): Promise<boolean> => {
|
|
||||||
const response = await api.unsignReport(reportId, token);
|
|
||||||
console.log(response);
|
|
||||||
console.log(reportId);
|
|
||||||
if (response.success) {
|
|
||||||
return true;
|
|
||||||
} else {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleDeleteWeeklyReport = async (): Promise<boolean> => {
|
|
||||||
const response = await api.deleteWeeklyReport(reportId, token);
|
|
||||||
console.log(response);
|
|
||||||
if (response.success) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
};
|
|
||||||
const navigate = useNavigate();
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<h1 className="text-[30px] font-bold">{username}'s Report</h1>
|
<h1 className="text-[30px] font-bold">{username}'s Report</h1>
|
||||||
|
@ -113,7 +86,6 @@ export default function OtherUsersTR(): JSX.Element {
|
||||||
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 === 0 ? "" : developmentTime}
|
||||||
readOnly
|
|
||||||
/>
|
/>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
@ -125,7 +97,6 @@ export default function OtherUsersTR(): JSX.Element {
|
||||||
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 === 0 ? "" : meetingTime}
|
||||||
readOnly
|
|
||||||
/>
|
/>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
@ -137,7 +108,6 @@ export default function OtherUsersTR(): JSX.Element {
|
||||||
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 === 0 ? "" : adminTime}
|
||||||
readOnly
|
|
||||||
/>
|
/>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
@ -149,7 +119,6 @@ export default function OtherUsersTR(): JSX.Element {
|
||||||
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 === 0 ? "" : ownWorkTime}
|
||||||
readOnly
|
|
||||||
/>
|
/>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
@ -161,7 +130,6 @@ export default function OtherUsersTR(): JSX.Element {
|
||||||
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 === 0 ? "" : studyTime}
|
||||||
readOnly
|
|
||||||
/>
|
/>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
@ -173,54 +141,11 @@ export default function OtherUsersTR(): JSX.Element {
|
||||||
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 === 0 ? "" : testingTime}
|
||||||
readOnly
|
|
||||||
/>
|
/>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
<div className="flex space-x-4">
|
|
||||||
{signedOrUnsigned === "signed" && (
|
|
||||||
<Button
|
|
||||||
text="Unsign Report"
|
|
||||||
onClick={(): void => {
|
|
||||||
void (async (): Promise<void> => {
|
|
||||||
const success = await handleUnsignWeeklyReport();
|
|
||||||
if (success) {
|
|
||||||
alert("Report successfully unsigned!");
|
|
||||||
navigate(-1);
|
|
||||||
} else {
|
|
||||||
alert("Failed to unsign report");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
})();
|
|
||||||
}}
|
|
||||||
type={"button"}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
<Button
|
|
||||||
text="Delete Time Report"
|
|
||||||
onClick={(): void => {
|
|
||||||
void (async (): Promise<void> => {
|
|
||||||
const confirmDelete = window.confirm(
|
|
||||||
"Are you sure you want to delete this report? This action cannot be undone.",
|
|
||||||
);
|
|
||||||
if (!confirmDelete) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const success = await handleDeleteWeeklyReport();
|
|
||||||
if (success) {
|
|
||||||
alert("Report successfully deleted!");
|
|
||||||
navigate(-1);
|
|
||||||
} else {
|
|
||||||
alert("Failed to delete report");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
})();
|
|
||||||
}}
|
|
||||||
type={"button"}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
|
|
|
@ -8,22 +8,22 @@ function PMProjectMenu(): JSX.Element {
|
||||||
<h1 className="font-bold text-[30px] mb-[20px]">{projectName}</h1>
|
<h1 className="font-bold text-[30px] mb-[20px]">{projectName}</h1>
|
||||||
<div className="border-4 border-black bg-white flex flex-col items-center justify-center min-h-[65vh] h-fit w-[50vw] rounded-3xl content-center overflow-scroll space-y-[5vh] p-[30px]">
|
<div className="border-4 border-black bg-white flex flex-col items-center justify-center min-h-[65vh] h-fit w-[50vw] rounded-3xl content-center overflow-scroll space-y-[5vh] p-[30px]">
|
||||||
<Link to={`/timeReports/${projectName}/`}>
|
<Link to={`/timeReports/${projectName}/`}>
|
||||||
<h1 className="font-bold hover:underline text-[30px] cursor-pointer hover:font-extrabold">
|
<h1 className="font-bold underline text-[30px] cursor-pointer">
|
||||||
Your Time Reports
|
Your Time Reports
|
||||||
</h1>
|
</h1>
|
||||||
</Link>
|
</Link>
|
||||||
<Link to={`/newTimeReport/${projectName}`}>
|
<Link to={`/newTimeReport/${projectName}`}>
|
||||||
<h1 className="font-bold hover:underline text-[30px] cursor-pointer hover:font-extrabold">
|
<h1 className="font-bold underline text-[30px] cursor-pointer">
|
||||||
New Time Report
|
New Time Report
|
||||||
</h1>
|
</h1>
|
||||||
</Link>
|
</Link>
|
||||||
<Link to={`/projectMembers/${projectName}`}>
|
<Link to={`/projectMembers/${projectName}`}>
|
||||||
<h1 className="font-bold hover:underline text-[30px] cursor-pointer hover:font-extrabold">
|
<h1 className="font-bold underline text-[30px] cursor-pointer">
|
||||||
Statistics
|
Statistics
|
||||||
</h1>
|
</h1>
|
||||||
</Link>
|
</Link>
|
||||||
<Link to={`/unsignedReports/${projectName}`}>
|
<Link to={`/unsignedReports/${projectName}`}>
|
||||||
<h1 className="font-bold hover:underline text-[30px] cursor-pointer hover:font-extrabold">
|
<h1 className="font-bold underline text-[30px] cursor-pointer">
|
||||||
Unsigned Time Reports
|
Unsigned Time Reports
|
||||||
</h1>
|
</h1>
|
||||||
</Link>
|
</Link>
|
||||||
|
|
|
@ -1,149 +1,39 @@
|
||||||
import { useEffect, useRef, useState } from "react";
|
import { useState } from "react";
|
||||||
import Button from "./Button";
|
import Button from "./Button";
|
||||||
import GetUsersInProject, { ProjectMember } from "./GetUsersInProject";
|
import { UserProjectMember } from "../Types/goTypes";
|
||||||
|
import GetUsersInProject from "./GetUsersInProject";
|
||||||
import { Link } from "react-router-dom";
|
import { Link } from "react-router-dom";
|
||||||
import GetProjectTimes, { projectTimes } from "./GetProjectTimes";
|
|
||||||
import DeleteProject from "./DeleteProject";
|
|
||||||
import InputField from "./InputField";
|
|
||||||
import ProjectNameInput from "./Inputs/ProjectNameInput";
|
|
||||||
import { alphanumeric } from "../Data/regex";
|
|
||||||
import { projNameHighLimit, projNameLowLimit } from "../Data/constants";
|
|
||||||
import ChangeProjectName from "./ChangeProjectName";
|
|
||||||
|
|
||||||
function ProjectInfoModal(props: {
|
function ProjectInfoModal(props: {
|
||||||
|
isVisible: boolean;
|
||||||
projectname: string;
|
projectname: string;
|
||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
onClick: (username: string, userRole: string) => void;
|
onClick: (username: string) => void;
|
||||||
}): JSX.Element {
|
}): JSX.Element {
|
||||||
const [showInput, setShowInput] = useState(false);
|
const [users, setUsers] = useState<UserProjectMember[]>([]);
|
||||||
const [users, setUsers] = useState<ProjectMember[]>([]);
|
|
||||||
const [times, setTimes] = useState<projectTimes>();
|
|
||||||
const [search, setSearch] = useState("");
|
|
||||||
const [newProjName, setNewProjName] = useState("");
|
|
||||||
const totalTime = useRef(0);
|
|
||||||
GetUsersInProject({ projectName: props.projectname, setUsersProp: setUsers });
|
GetUsersInProject({ projectName: props.projectname, setUsersProp: setUsers });
|
||||||
|
if (!props.isVisible) return <></>;
|
||||||
GetProjectTimes({ setTimesProp: setTimes, projectName: props.projectname });
|
|
||||||
|
|
||||||
const handleChangeNameView = (): void => {
|
|
||||||
if (showInput) {
|
|
||||||
setNewProjName("");
|
|
||||||
setShowInput(false);
|
|
||||||
} else {
|
|
||||||
setShowInput(true);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleClickChangeName = (): void => {
|
|
||||||
if (
|
|
||||||
newProjName.length > projNameHighLimit ||
|
|
||||||
newProjName.length < projNameLowLimit ||
|
|
||||||
!alphanumeric.test(newProjName)
|
|
||||||
) {
|
|
||||||
alert(
|
|
||||||
"Please provide valid project name: \n-Between 10-99 characters \n-No special characters (.-!?/*)",
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
|
||||||
confirm(
|
|
||||||
`Are you sure you want to change name of ${props.projectname} to ${newProjName}?`,
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
ChangeProjectName({
|
|
||||||
projectName: props.projectname,
|
|
||||||
newProjectName: newProjName,
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
alert("Name was not changed!");
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (times?.totalTime !== undefined) {
|
|
||||||
totalTime.current = times.totalTime;
|
|
||||||
}
|
|
||||||
}, [times]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className="fixed inset-0 bg-black bg-opacity-30 backdrop-blur-sm
|
className="fixed inset-0 bg-black bg-opacity-30 backdrop-blur-sm
|
||||||
flex justify-center items-center"
|
flex justify-center items-center"
|
||||||
>
|
>
|
||||||
<div className="border-4 border-black bg-white p-2 rounded-2xl text-center h-[64vh] w-[40] overflow-auto">
|
<div className="border-4 border-black bg-white p-2 rounded-2xl text-center h-[47vh] w-[40] flex flex-col">
|
||||||
<div className="pl-10 pr-10">
|
<div className="pl-20 pr-20">
|
||||||
<h1 className="font-bold text-[32px]">{props.projectname}</h1>
|
<h1 className="font-bold text-[32px] mb-[20px]">
|
||||||
<p
|
{localStorage.getItem("projectName") ?? ""}
|
||||||
className="mb-4 hover:font-bold hover:cursor-pointer hover:underline"
|
</h1>
|
||||||
onClick={handleChangeNameView}
|
<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]">
|
||||||
(Change project name)
|
<ul className="text-left font-medium space-y-2">
|
||||||
</p>
|
<div></div>
|
||||||
{showInput && (
|
{users.map((user) => (
|
||||||
<>
|
|
||||||
<h2 className="text-[20px] font-bold pb-2">Change name:</h2>
|
|
||||||
<div className="border-2 rounded-2xl border-black px-6 pt-6 pb-1 mb-7">
|
|
||||||
<ProjectNameInput
|
|
||||||
name={newProjName}
|
|
||||||
onChange={function (e): void {
|
|
||||||
setNewProjName(e.target.value);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<div className="px-6 grid grid-cols-2 gap-10">
|
|
||||||
<Button
|
|
||||||
text={"Change"}
|
|
||||||
onClick={function (): void {
|
|
||||||
handleClickChangeName();
|
|
||||||
}}
|
|
||||||
type={"submit"}
|
|
||||||
/>
|
|
||||||
<Button
|
|
||||||
text={"Close"}
|
|
||||||
onClick={function (): void {
|
|
||||||
handleChangeNameView();
|
|
||||||
}}
|
|
||||||
type={"submit"}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
<h2 className="text-[20px] font-bold pb-2">Statistics:</h2>
|
|
||||||
<div className="border-2 border-black rounded-2xl px-2 py-1 text-left divide-y-2 flex flex-col overflow-auto">
|
|
||||||
<p>Number of members: {users.length}</p>
|
|
||||||
<p>
|
|
||||||
Total time reported:{" "}
|
|
||||||
{Math.floor(totalTime.current / 60 / 24) + " d "}
|
|
||||||
{Math.floor((totalTime.current / 60) % 24) + " h "}
|
|
||||||
{(totalTime.current % 60) + " m "}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<h3 className="pt-7 text-[20px] font-bold">Project members:</h3>
|
|
||||||
<div className="">
|
|
||||||
<InputField
|
|
||||||
placeholder={"Search member"}
|
|
||||||
type={"Text"}
|
|
||||||
value={search}
|
|
||||||
onChange={(e) => {
|
|
||||||
setSearch(e.target.value);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<ul className="border-2 border-black mt-2 p-2 rounded-2xl text-left overflow-auto h-[24vh] font-medium space-y-2">
|
|
||||||
{users
|
|
||||||
.filter((user) => {
|
|
||||||
return search.toLowerCase() === ""
|
|
||||||
? user.Username
|
|
||||||
: user.Username.toLowerCase().includes(
|
|
||||||
search.toLowerCase(),
|
|
||||||
);
|
|
||||||
})
|
|
||||||
.map((user) => (
|
|
||||||
<li
|
<li
|
||||||
className="items-start px-2 py-1 border-2 border-black rounded-2xl bg-orange-200 transition-all hover:bg-orange-600 hover:text-white hover:cursor-pointer"
|
className="items-start p-1 border-2 border-black rounded-lg bg-orange-200 hover:bg-orange-600 hover:text-slate-100 hover:cursor-pointer"
|
||||||
key={user.Username}
|
key={user.Username}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
props.onClick(user.Username, user.UserRole);
|
props.onClick(user.Username);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<span>
|
<span>
|
||||||
|
@ -155,28 +45,16 @@ function ProjectInfoModal(props: {
|
||||||
))}
|
))}
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
<div className="space-x-5 my-2">
|
</div>
|
||||||
|
<div className="space-x-16">
|
||||||
<Button
|
<Button
|
||||||
text={"Delete"}
|
text={"Delete"}
|
||||||
onClick={function (): void {
|
onClick={function (): void {
|
||||||
if (
|
//DELETE PROJECT
|
||||||
window.confirm(
|
|
||||||
"Are you sure you want to delete this project?",
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
DeleteProject({
|
|
||||||
projectToDelete: props.projectname,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}}
|
}}
|
||||||
type="button"
|
type="button"
|
||||||
/>
|
/>
|
||||||
<Link
|
<Link to={"/adminProjectAddMember"}>
|
||||||
to={{
|
|
||||||
pathname: "/adminProjectAddMember",
|
|
||||||
search: props.projectname,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Button
|
<Button
|
||||||
text={"Add Member"}
|
text={"Add Member"}
|
||||||
onClick={function (): void {
|
onClick={function (): void {
|
||||||
|
@ -195,7 +73,6 @@ function ProjectInfoModal(props: {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,8 +1,7 @@
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { NewProject } from "../Types/goTypes";
|
import { NewProject } from "../Types/goTypes";
|
||||||
import ProjectInfoModal from "./ProjectInfoModal";
|
import ProjectInfoModal from "./ProjectInfoModal";
|
||||||
import MemberInfoModal from "./MemberInfoModal";
|
import UserInfoModal from "./UserInfoModal";
|
||||||
import InputField from "./InputField";
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 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
|
||||||
|
@ -19,71 +18,54 @@ export function ProjectListAdmin(props: {
|
||||||
projects: NewProject[];
|
projects: NewProject[];
|
||||||
}): JSX.Element {
|
}): JSX.Element {
|
||||||
const [projectModalVisible, setProjectModalVisible] = useState(false);
|
const [projectModalVisible, setProjectModalVisible] = useState(false);
|
||||||
const [projectName, setProjectName] = useState("");
|
const [projectname, setProjectname] = useState("");
|
||||||
const [userModalVisible, setUserModalVisible] = useState(false);
|
const [userModalVisible, setUserModalVisible] = useState(false);
|
||||||
const [username, setUsername] = useState("");
|
const [username, setUsername] = useState("");
|
||||||
const [userRole, setUserRole] = useState("");
|
|
||||||
const [search, setSearch] = useState("");
|
|
||||||
|
|
||||||
const handleClickUser = (username: string, userRole: string): void => {
|
const handleClickUser = (username: string): void => {
|
||||||
setUsername(username);
|
setUsername(username);
|
||||||
setUserRole(userRole);
|
|
||||||
setUserModalVisible(true);
|
setUserModalVisible(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleClickProject = (projectname: string): void => {
|
const handleClickProject = (projectname: string): void => {
|
||||||
setProjectName(projectname);
|
setProjectname(projectname);
|
||||||
|
localStorage.setItem("projectName", projectname);
|
||||||
setProjectModalVisible(true);
|
setProjectModalVisible(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleCloseProject = (): void => {
|
const handleCloseProject = (): void => {
|
||||||
setProjectName("");
|
setProjectname("");
|
||||||
setProjectModalVisible(false);
|
setProjectModalVisible(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleCloseUser = (): void => {
|
const handleCloseUser = (): void => {
|
||||||
setUsername("");
|
setProjectname("");
|
||||||
setUserRole("");
|
|
||||||
setUserModalVisible(false);
|
setUserModalVisible(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<h1 className="font-bold text-[30px] mb-[20px]">Manage Projects</h1>
|
|
||||||
{projectModalVisible && (
|
|
||||||
<ProjectInfoModal
|
<ProjectInfoModal
|
||||||
onClose={handleCloseProject}
|
onClose={handleCloseProject}
|
||||||
onClick={handleClickUser}
|
onClick={handleClickUser}
|
||||||
projectname={projectName}
|
isVisible={projectModalVisible}
|
||||||
|
projectname={projectname}
|
||||||
/>
|
/>
|
||||||
)}
|
<UserInfoModal
|
||||||
{userModalVisible && (
|
manageMember={true}
|
||||||
<MemberInfoModal
|
|
||||||
onClose={handleCloseUser}
|
onClose={handleCloseUser}
|
||||||
username={username}
|
//TODO: CHANGE TO REMOVE USER FROM PROJECT
|
||||||
projectName={projectName}
|
onDelete={() => {
|
||||||
role={userRole}
|
return;
|
||||||
/>
|
|
||||||
)}
|
|
||||||
<div>
|
|
||||||
<InputField
|
|
||||||
placeholder={"Search"}
|
|
||||||
type={"Text"}
|
|
||||||
value={search}
|
|
||||||
onChange={(e) => {
|
|
||||||
setSearch(e.target.value);
|
|
||||||
}}
|
}}
|
||||||
|
isVisible={userModalVisible}
|
||||||
|
username={username}
|
||||||
/>
|
/>
|
||||||
<ul className="mt-3 border-2 text-left border-black rounded-2xl px-2 divide-y divide-gray-300 font-semibold text-[30px] cursor-pointer overflow-auto h-[60vh] w-[40vw]">
|
<div>
|
||||||
{props.projects
|
<ul className="font-bold underline text-[30px] cursor-pointer padding">
|
||||||
.filter((project) => {
|
{props.projects.map((project) => (
|
||||||
return search.toLowerCase() === ""
|
|
||||||
? project.name
|
|
||||||
: project.name.toLowerCase().includes(search.toLowerCase());
|
|
||||||
})
|
|
||||||
.map((project) => (
|
|
||||||
<li
|
<li
|
||||||
className="hover:font-extrabold hover:underline p-1"
|
className="pt-5"
|
||||||
key={project.name}
|
key={project.name}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
handleClickProject(project.name);
|
handleClickProject(project.name);
|
||||||
|
|
|
@ -1,43 +1,39 @@
|
||||||
import { useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { Link, useParams } from "react-router-dom";
|
import { Link, useParams } from "react-router-dom";
|
||||||
import GetUsersInProject, { ProjectMember } from "./GetUsersInProject";
|
|
||||||
import { api } from "../API/API";
|
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<ProjectMember[]>([]);
|
const [projectMembers, setProjectMembers] = useState<UserProjectMember[]>([]);
|
||||||
|
|
||||||
GetUsersInProject({
|
useEffect(() => {
|
||||||
projectName: projectName ?? "",
|
const getProjectMembers = async (): Promise<void> => {
|
||||||
setUsersProp: setProjectMembers,
|
|
||||||
});
|
|
||||||
|
|
||||||
const handleUserDeleteClick = async (username: string): Promise<void> => {
|
|
||||||
const token = localStorage.getItem("accessToken") ?? "";
|
const token = localStorage.getItem("accessToken") ?? "";
|
||||||
const response = await api.removeUserFromProject(
|
const response = await api.getAllUsersProject(projectName ?? "", token);
|
||||||
username,
|
console.log(response);
|
||||||
projectName ?? "",
|
if (response.success) {
|
||||||
token,
|
setProjectMembers(response.data ?? []);
|
||||||
);
|
} else {
|
||||||
console.log(response.data);
|
console.error(response.message);
|
||||||
|
}
|
||||||
// Remove the deleted user from the state
|
|
||||||
setProjectMembers((prevMembers) =>
|
|
||||||
prevMembers.filter((member) => member.Username !== username),
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
void getProjectMembers();
|
||||||
|
}, [projectName]);
|
||||||
|
|
||||||
|
interface ProjectMember {
|
||||||
|
Username: string;
|
||||||
|
UserRole: string;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<h1 className="font-bold text-[30px] mb-[20px]">
|
<h1 className="font-bold text-[30px] mb-[20px]">
|
||||||
All Members In: {projectName}{" "}
|
All Members In: {projectName}{" "}
|
||||||
</h1>
|
</h1>
|
||||||
<div className="border-4 border-black bg-white flex flex-col items-center justify-center min-h-[65vh] h-fit w-[70vw] rounded-3xl content-center overflow-scroll space-y-[10vh] p-[30px] text-[20px]">
|
<div className="border-4 border-black bg-white flex flex-col items-center justify-center min-h-[65vh] h-fit w-[70vw] rounded-3xl content-center overflow-scroll space-y-[10vh] p-[30px] text-[20px]">
|
||||||
{projectMembers.map((projectMember: ProjectMember, index: number) => {
|
{projectMembers.map((projectMember: ProjectMember, index: number) => (
|
||||||
if (projectMember.Username === "admin") {
|
|
||||||
return null; // Skip rendering for admin user
|
|
||||||
}
|
|
||||||
return (
|
|
||||||
<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">
|
||||||
|
@ -47,24 +43,10 @@ function ProjectMembers(): JSX.Element {
|
||||||
</div>
|
</div>
|
||||||
<div className="flex">
|
<div className="flex">
|
||||||
<div className="ml-auto flex space-x-4">
|
<div className="ml-auto flex space-x-4">
|
||||||
{projectMember.Username !==
|
|
||||||
localStorage.getItem("username") && (
|
|
||||||
<h1
|
|
||||||
className="cursor-pointer font-bold hover:font-extrabold hover:underline"
|
|
||||||
onClick={() => {
|
|
||||||
confirm(
|
|
||||||
"Are you sure you want to delete this user? This action cannot be undone.",
|
|
||||||
) &&
|
|
||||||
void handleUserDeleteClick(projectMember.Username);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Delete User
|
|
||||||
</h1>
|
|
||||||
)}
|
|
||||||
<Link
|
<Link
|
||||||
to={`/otherUsersTimeReports/${projectName}/${projectMember.Username}`}
|
to={`/otherUsersTimeReports/${projectName}/${projectMember.Username}`}
|
||||||
>
|
>
|
||||||
<h1 className="cursor-pointer font-bold hover:font-extrabold hover:underline">
|
<h1 className="underline cursor-pointer font-bold">
|
||||||
View Reports
|
View Reports
|
||||||
</h1>
|
</h1>
|
||||||
</Link>
|
</Link>
|
||||||
|
@ -72,8 +54,7 @@ function ProjectMembers(): JSX.Element {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</h1>
|
</h1>
|
||||||
);
|
))}
|
||||||
})}
|
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|
|
@ -3,52 +3,27 @@ import { NewUser } from "../Types/goTypes";
|
||||||
import { api } from "../API/API";
|
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 UsernameInput from "./Inputs/UsernameInput";
|
import InputField from "./InputField";
|
||||||
import PasswordInput from "./Inputs/PasswordInput";
|
|
||||||
import { alphanumeric, lowercase } from "../Data/regex";
|
|
||||||
import {
|
|
||||||
passwordLength,
|
|
||||||
usernameLowLimit,
|
|
||||||
usernameUpLimit,
|
|
||||||
} from "../Data/constants";
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Renders a registration form for the admin to add new users in.
|
* Renders a registration form for the admin to add new users in.
|
||||||
* @returns The JSX element representing the registration form.
|
* @returns The JSX element representing the registration form.
|
||||||
*/
|
*/
|
||||||
export default function Register(): JSX.Element {
|
export default function Register(): JSX.Element {
|
||||||
const [username, setUsername] = useState<string>("");
|
const [username, setUsername] = useState<string>();
|
||||||
const [password, setPassword] = useState<string>("");
|
const [password, setPassword] = useState<string>();
|
||||||
const [errMessage, setErrMessage] = useState<string>("");
|
const [errMessage, setErrMessage] = useState<string>();
|
||||||
|
|
||||||
const handleRegister = async (): Promise<void> => {
|
const handleRegister = async (): Promise<void> => {
|
||||||
if (
|
|
||||||
username.length > usernameUpLimit ||
|
|
||||||
username.length < usernameLowLimit ||
|
|
||||||
!alphanumeric.test(username)
|
|
||||||
) {
|
|
||||||
alert(
|
|
||||||
"Please provide valid username: \n-Between 5-10 characters \n-No special characters (.-!?/*)",
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (password.length !== passwordLength || !lowercase.test(password)) {
|
|
||||||
alert(
|
|
||||||
"Please provide valid password: \n-Exactly 6 characters \n-No uppercase letters \n-No numbers \n-No special characters (.-!?/*)",
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const newUser: NewUser = {
|
const newUser: NewUser = {
|
||||||
username: username,
|
username: username ?? "",
|
||||||
password: password,
|
password: password ?? "",
|
||||||
};
|
};
|
||||||
const response = await api.registerUser(newUser);
|
const response = await api.registerUser(newUser);
|
||||||
if (response.success) {
|
if (response.success) {
|
||||||
alert(`${newUser.username} added!`);
|
alert("User added!");
|
||||||
setPassword("");
|
|
||||||
setUsername("");
|
|
||||||
} else {
|
} else {
|
||||||
alert("User not added, name could be taken");
|
alert("User not added");
|
||||||
setErrMessage(response.message ?? "Unknown error");
|
setErrMessage(response.message ?? "Unknown error");
|
||||||
console.error(errMessage);
|
console.error(errMessage);
|
||||||
}
|
}
|
||||||
|
@ -58,7 +33,7 @@ export default function Register(): JSX.Element {
|
||||||
<div className="flex flex-col h-fit w-screen items-center justify-center">
|
<div className="flex flex-col h-fit w-screen items-center justify-center">
|
||||||
<div className="border-4 border-black bg-white flex flex-col items-center justify-center h-fit w-fit rounded-3xl content-center pl-20 pr-20">
|
<div className="border-4 border-black bg-white flex flex-col items-center justify-center h-fit w-fit rounded-3xl content-center pl-20 pr-20">
|
||||||
<form
|
<form
|
||||||
className="bg-white rounded px-8 pt-6 pb-8 mb-4 justify-center flex flex-col w-fit h-fit"
|
className="bg-white rounded px-8 pt-6 pb-8 mb-4 items-center justify-center flex flex-col w-fit h-fit"
|
||||||
onSubmit={(e) => {
|
onSubmit={(e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
void handleRegister();
|
void handleRegister();
|
||||||
|
@ -66,28 +41,29 @@ export default function Register(): JSX.Element {
|
||||||
>
|
>
|
||||||
<img
|
<img
|
||||||
src={Logo}
|
src={Logo}
|
||||||
className="logo self-center w-[7vw] mb-10 mt-10"
|
className="logo w-[7vw] mb-10 mt-10"
|
||||||
alt="TTIME Logo"
|
alt="TTIME Logo"
|
||||||
/>
|
/>
|
||||||
<h3 className="pb-4 mb-2 text-center font-bold text-[18px]">
|
<h3 className="pb-4 mb-2 text-center font-bold text-[18px]">
|
||||||
Register New User
|
Register New User
|
||||||
</h3>
|
</h3>
|
||||||
|
<InputField
|
||||||
<UsernameInput
|
label="Username"
|
||||||
username={username}
|
type="text"
|
||||||
|
value={username ?? ""}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
setUsername(e.target.value);
|
setUsername(e.target.value);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<div className="py-2" />
|
<InputField
|
||||||
<PasswordInput
|
label="Password"
|
||||||
password={password}
|
type="password"
|
||||||
|
value={password ?? ""}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
setPassword(e.target.value);
|
setPassword(e.target.value);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
<div className="flex self-center justify-between">
|
|
||||||
<Button
|
<Button
|
||||||
text="Register"
|
text="Register"
|
||||||
onClick={(): void => {
|
onClick={(): void => {
|
||||||
|
|
|
@ -1,41 +0,0 @@
|
||||||
import { api, APIResponse } from "../API/API";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Removes a user from a project
|
|
||||||
* @param {string} props.usernameToDelete - The username of user to remove
|
|
||||||
* @param {string} props.projectName - Project to remove user from
|
|
||||||
* @returns {void}
|
|
||||||
* @example
|
|
||||||
* const exampleUsername = "user";
|
|
||||||
* const exampleProjectName "project";
|
|
||||||
* RemoveUserFromProj({ userToRemove: exampleUsername, projectName: exampleProjectName });
|
|
||||||
*/
|
|
||||||
|
|
||||||
export default function RemoveUserFromProj(props: {
|
|
||||||
userToRemove: string;
|
|
||||||
projectName: string;
|
|
||||||
}): void {
|
|
||||||
if (props.userToRemove === localStorage.getItem("username")) {
|
|
||||||
alert("Cannot remove yourself");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
api
|
|
||||||
.removeUserFromProject(
|
|
||||||
props.userToRemove,
|
|
||||||
props.projectName,
|
|
||||||
localStorage.getItem("accessToken") ?? "",
|
|
||||||
)
|
|
||||||
.then((response: APIResponse<void>) => {
|
|
||||||
if (response.success) {
|
|
||||||
alert(`${props.userToRemove} has been removed!`);
|
|
||||||
location.reload();
|
|
||||||
} else {
|
|
||||||
alert(`${props.userToRemove} has not been removed due to an error`);
|
|
||||||
console.error(response.message);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch((error) => {
|
|
||||||
alert(`${props.userToRemove} has not been removed due to an error`);
|
|
||||||
console.error("An error occurred during deletion:", error);
|
|
||||||
});
|
|
||||||
}
|
|
|
@ -1,46 +1,70 @@
|
||||||
import { useState, useEffect } from "react";
|
import { useState, useEffect } from "react";
|
||||||
import { useParams } from "react-router-dom";
|
import { useParams } from "react-router-dom";
|
||||||
import { api } from "../API/API";
|
|
||||||
import { projectTimes } from "./GetProjectTimes";
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Renders the component for showing total time per role in a project.
|
* Renders the component for showing total time per role in a project.
|
||||||
* @returns JSX.Element
|
* @returns JSX.Element
|
||||||
*/
|
*/
|
||||||
export default function TimePerRole(): JSX.Element {
|
export default function TimePerRole(): JSX.Element {
|
||||||
const [development, setDevelopment] = useState<number>(0);
|
const [developmentTime, setDevelopmentTime] = useState<number>();
|
||||||
const [meeting, setMeeting] = useState<number>(0);
|
const [meetingTime, setMeetingTime] = useState<number>();
|
||||||
const [admin, setAdmin] = useState<number>(0);
|
const [adminTime, setAdminTime] = useState<number>();
|
||||||
const [own_work, setOwnWork] = useState<number>(0);
|
const [ownWorkTime, setOwnWorkTime] = useState<number>();
|
||||||
const [study, setStudy] = useState<number>(0);
|
const [studyTime, setStudyTime] = useState<number>();
|
||||||
const [testing, setTesting] = useState<number>(0);
|
const [testingTime, setTestingTime] = useState<number>();
|
||||||
const total = development + meeting + admin + own_work + study + testing;
|
|
||||||
|
|
||||||
const token = localStorage.getItem("accessToken") ?? "";
|
// const token = localStorage.getItem("accessToken") ?? "";
|
||||||
|
// const username = localStorage.getItem("username") ?? "";
|
||||||
const { projectName } = useParams();
|
const { projectName } = useParams();
|
||||||
|
|
||||||
|
// const fetchTimePerRole = async (): Promise<void> => {
|
||||||
|
// const response = await api.getTimePerRole(
|
||||||
|
// username,
|
||||||
|
// projectName ?? "",
|
||||||
|
// token,
|
||||||
|
// );
|
||||||
|
// {
|
||||||
|
// if (response.success) {
|
||||||
|
// const report: TimePerRole = response.data ?? {
|
||||||
|
// PManagerTime: 0,
|
||||||
|
// SManagerTime: 0,
|
||||||
|
// DeveloperTime: 0,
|
||||||
|
// TesterTime: 0,
|
||||||
|
// };
|
||||||
|
// } else {
|
||||||
|
// console.error("Failed to fetch weekly report:", response.message);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
interface TimePerActivity {
|
||||||
|
developmentTime: number;
|
||||||
|
meetingTime: number;
|
||||||
|
adminTime: number;
|
||||||
|
ownWorkTime: number;
|
||||||
|
studyTime: number;
|
||||||
|
testingTime: number;
|
||||||
|
}
|
||||||
|
|
||||||
const fetchTimePerActivity = async (): Promise<void> => {
|
const fetchTimePerActivity = async (): Promise<void> => {
|
||||||
const response = await api.getProjectTimes(projectName ?? "", token);
|
// Use mock data
|
||||||
{
|
const report: TimePerActivity = {
|
||||||
if (response.success) {
|
developmentTime: 100,
|
||||||
const report: projectTimes = response.data ?? {
|
meetingTime: 200,
|
||||||
development: 0,
|
adminTime: 300,
|
||||||
meeting: 0,
|
ownWorkTime: 50,
|
||||||
admin: 0,
|
studyTime: 75,
|
||||||
own_work: 0,
|
testingTime: 110,
|
||||||
study: 0,
|
|
||||||
testing: 0,
|
|
||||||
};
|
};
|
||||||
setDevelopment(report.development);
|
|
||||||
setMeeting(report.meeting);
|
// Set the state with the mock data
|
||||||
setAdmin(report.admin);
|
setDevelopmentTime(report.developmentTime);
|
||||||
setOwnWork(report.own_work);
|
setMeetingTime(report.meetingTime);
|
||||||
setStudy(report.study);
|
setAdminTime(report.adminTime);
|
||||||
setTesting(report.testing);
|
setOwnWorkTime(report.ownWorkTime);
|
||||||
} else {
|
setStudyTime(report.studyTime);
|
||||||
console.error("Failed to fetch weekly report:", response.message);
|
setTestingTime(report.testingTime);
|
||||||
}
|
|
||||||
}
|
await Promise.resolve();
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
@ -50,7 +74,7 @@ export default function TimePerRole(): JSX.Element {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<h1 className="font-bold text-[30px] mb-[20px]">
|
<h1 className="font-bold text-[30px] mb-[20px]">
|
||||||
Total Time Per Activity For All Members In: {projectName}{" "}
|
Total Time Per Activity In: {projectName}{" "}
|
||||||
</h1>
|
</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">
|
||||||
<div className="flex flex-col items-center">
|
<div className="flex flex-col items-center">
|
||||||
|
@ -70,8 +94,10 @@ export default function TimePerRole(): JSX.Element {
|
||||||
<input
|
<input
|
||||||
type="string"
|
type="string"
|
||||||
className="border-2 border-black rounded-md text-center w-1/2"
|
className="border-2 border-black rounded-md text-center w-1/2"
|
||||||
value={development}
|
value={developmentTime}
|
||||||
readOnly
|
onKeyDown={(event) => {
|
||||||
|
event.preventDefault();
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
@ -81,8 +107,10 @@ export default function TimePerRole(): JSX.Element {
|
||||||
<input
|
<input
|
||||||
type="string"
|
type="string"
|
||||||
className="border-2 border-black rounded-md text-center w-1/2"
|
className="border-2 border-black rounded-md text-center w-1/2"
|
||||||
value={meeting}
|
value={meetingTime}
|
||||||
readOnly
|
onKeyDown={(event) => {
|
||||||
|
event.preventDefault();
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
@ -92,8 +120,10 @@ export default function TimePerRole(): JSX.Element {
|
||||||
<input
|
<input
|
||||||
type="string"
|
type="string"
|
||||||
className="border-2 border-black rounded-md text-center w-1/2"
|
className="border-2 border-black rounded-md text-center w-1/2"
|
||||||
value={admin}
|
value={adminTime}
|
||||||
readOnly
|
onKeyDown={(event) => {
|
||||||
|
event.preventDefault();
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
@ -103,8 +133,10 @@ export default function TimePerRole(): JSX.Element {
|
||||||
<input
|
<input
|
||||||
type="string"
|
type="string"
|
||||||
className="border-2 border-black rounded-md text-center w-1/2"
|
className="border-2 border-black rounded-md text-center w-1/2"
|
||||||
value={own_work}
|
value={ownWorkTime}
|
||||||
readOnly
|
onKeyDown={(event) => {
|
||||||
|
event.preventDefault();
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
@ -114,8 +146,10 @@ export default function TimePerRole(): JSX.Element {
|
||||||
<input
|
<input
|
||||||
type="string"
|
type="string"
|
||||||
className="border-2 border-black rounded-md text-center w-1/2"
|
className="border-2 border-black rounded-md text-center w-1/2"
|
||||||
value={study}
|
value={studyTime}
|
||||||
readOnly
|
onKeyDown={(event) => {
|
||||||
|
event.preventDefault();
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
@ -125,17 +159,13 @@ export default function TimePerRole(): JSX.Element {
|
||||||
<input
|
<input
|
||||||
type="string"
|
type="string"
|
||||||
className="border-2 border-black rounded-md text-center w-1/2"
|
className="border-2 border-black rounded-md text-center w-1/2"
|
||||||
value={testing}
|
value={testingTime}
|
||||||
readOnly
|
onKeyDown={(event) => {
|
||||||
|
event.preventDefault();
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr className="h-[10vh] font-bold font-">
|
|
||||||
<td>In Total:</td>
|
|
||||||
<td>
|
|
||||||
<h1>{total}</h1>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,177 +1,51 @@
|
||||||
|
import { Link } from "react-router-dom";
|
||||||
import Button from "./Button";
|
import Button from "./Button";
|
||||||
import DeleteUser from "./DeleteUser";
|
import DeleteUser from "./DeleteUser";
|
||||||
import UserProjectListAdmin from "./UserProjectListAdmin";
|
import UserProjectListAdmin from "./UserProjectListAdmin";
|
||||||
import { useState } from "react";
|
|
||||||
import ChangeUsername from "./ChangeUsername";
|
|
||||||
import { StrNameChange } from "../Types/goTypes";
|
|
||||||
import UsernameInput from "./Inputs/UsernameInput";
|
|
||||||
import PasswordInput from "./Inputs/PasswordInput";
|
|
||||||
import { alphanumeric, lowercase } from "../Data/regex";
|
|
||||||
import {
|
|
||||||
passwordLength,
|
|
||||||
usernameLowLimit,
|
|
||||||
usernameUpLimit,
|
|
||||||
} from "../Data/constants";
|
|
||||||
import ChangeUserPassword from "./ChangeUserPassword";
|
|
||||||
|
|
||||||
function UserInfoModal(props: {
|
function UserInfoModal(props: {
|
||||||
isVisible: boolean;
|
isVisible: boolean;
|
||||||
|
manageMember: boolean;
|
||||||
username: string;
|
username: string;
|
||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
|
onDelete: (username: string) => void;
|
||||||
}): JSX.Element {
|
}): JSX.Element {
|
||||||
const [showNameInput, setShowNameInput] = useState(false);
|
if (!props.isVisible) return <></>;
|
||||||
const [showPwordInput, setShowPwordInput] = useState(false);
|
const ManageUserOrMember = (check: boolean): JSX.Element => {
|
||||||
const [newUsername, setNewUsername] = useState("");
|
if (check) {
|
||||||
const [newPassword, setNewPassword] = useState("");
|
return (
|
||||||
if (!props.isVisible) {
|
<Link to="/AdminChangeRole">
|
||||||
return <></>;
|
<p className="mb-[20px] hover:font-bold hover:cursor-pointer underline">
|
||||||
}
|
(Change Role)
|
||||||
|
</p>
|
||||||
/*
|
</Link>
|
||||||
* Switches name input between visible/invisible
|
|
||||||
* and makes password input invisible
|
|
||||||
*/
|
|
||||||
const handleShowNameInput = (): void => {
|
|
||||||
if (showPwordInput) setShowPwordInput(false);
|
|
||||||
if (showNameInput) {
|
|
||||||
setShowNameInput(false);
|
|
||||||
setNewUsername("");
|
|
||||||
} else {
|
|
||||||
setShowNameInput(true);
|
|
||||||
setNewPassword("");
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Switches password input between visible/invisible
|
|
||||||
* and makes username input invisible
|
|
||||||
*/
|
|
||||||
const handleShowPwordInput = (): void => {
|
|
||||||
if (showNameInput) setShowNameInput(false);
|
|
||||||
if (showPwordInput) {
|
|
||||||
setShowPwordInput(false);
|
|
||||||
setNewPassword("");
|
|
||||||
} else {
|
|
||||||
setShowPwordInput(true);
|
|
||||||
setNewUsername("");
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Handles name change and checks if new name meets requirements
|
|
||||||
const handleClickChangeName = (): void => {
|
|
||||||
if (
|
|
||||||
!alphanumeric.test(newUsername) ||
|
|
||||||
newUsername.length > usernameUpLimit ||
|
|
||||||
newUsername.length < usernameLowLimit
|
|
||||||
) {
|
|
||||||
alert(
|
|
||||||
"Please provide valid username: \n-Between 5-10 characters \n-No special characters (.-!?/*)",
|
|
||||||
);
|
);
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
if (
|
return (
|
||||||
confirm(
|
<Link to="/AdminChangeUserName">
|
||||||
`Do you really want to change username of ${props.username} to ${newUsername}?`,
|
<p className="mb-[20px] hover:font-bold hover:cursor-pointer underline">
|
||||||
)
|
(Change Username)
|
||||||
) {
|
</p>
|
||||||
const nameChange: StrNameChange = {
|
</Link>
|
||||||
prevName: props.username,
|
|
||||||
newName: newUsername.replace(/ /g, ""),
|
|
||||||
};
|
|
||||||
ChangeUsername({ nameChange: nameChange });
|
|
||||||
} else {
|
|
||||||
alert("Name was not changed!");
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Handles password change and checks if new password meets requirements
|
|
||||||
const handleClickChangePassword = (): void => {
|
|
||||||
if (newPassword.length !== passwordLength || !lowercase.test(newPassword)) {
|
|
||||||
alert(
|
|
||||||
"Please provide valid password: \n-Exactly 6 characters \n-No uppercase letters \n-No numbers \n-No special characters (.-!?/*)",
|
|
||||||
);
|
);
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (
|
|
||||||
confirm(`Are you sure you want to change password of ${props.username}?`)
|
|
||||||
) {
|
|
||||||
ChangeUserPassword({
|
|
||||||
username: props.username,
|
|
||||||
newPassword: newPassword,
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
alert("Password was not changed!");
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className="fixed inset-0 bg-black bg-opacity-30 backdrop-blur-sm
|
className="fixed inset-0 bg-black bg-opacity-30 backdrop-blur-sm
|
||||||
flex justify-center items-center"
|
flex justify-center items-center"
|
||||||
>
|
>
|
||||||
<div className="border-4 border-black bg-white rounded-2xl text-center flex flex-col">
|
<div className="border-4 border-black bg-white p-2 rounded-lg text-center flex flex-col">
|
||||||
<div className="mx-10">
|
|
||||||
<p className="font-bold text-[30px]">{props.username}</p>
|
<p className="font-bold text-[30px]">{props.username}</p>
|
||||||
<p className="mt-2 font-bold text-[20px]">Change:</p>
|
{ManageUserOrMember(props.manageMember)}
|
||||||
<p className="mt-2 space-x-3 mb-[10px]">
|
<div>
|
||||||
<span
|
<h2 className="font-bold text-[22px] mb-[20px]">
|
||||||
className={
|
Member of these projects:
|
||||||
showNameInput
|
</h2>
|
||||||
? "items-start font-semibold py-1 px-2 border-2 border-transparent rounded-full bg-orange-500 transition-all hover:bg-orange-600 text-white hover:cursor-pointer ring-2 ring-black"
|
<div className="pr-6 pl-6">
|
||||||
: "items-start font-medium py-1 px-2 border-2 border-gray-500 text-white rounded-full bg-orange-300 hover:bg-orange-400 transition-all hover:text-gray-100 hover:border-gray-600 hover:cursor-pointer"
|
<UserProjectListAdmin />
|
||||||
}
|
|
||||||
onClick={handleShowNameInput}
|
|
||||||
>
|
|
||||||
Username
|
|
||||||
</span>{" "}
|
|
||||||
<span
|
|
||||||
className={
|
|
||||||
showPwordInput
|
|
||||||
? "items-start font-semibold py-1 px-2 border-2 border-transparent rounded-full bg-orange-500 transition-all hover:bg-orange-600 text-white hover:cursor-pointer ring-2 ring-black"
|
|
||||||
: "items-start font-medium py-1 px-2 border-2 border-gray-500 text-white rounded-full bg-orange-300 hover:bg-orange-400 transition-all hover:text-gray-100 hover:border-gray-600 hover:cursor-pointer"
|
|
||||||
}
|
|
||||||
onClick={handleShowPwordInput}
|
|
||||||
>
|
|
||||||
Password
|
|
||||||
</span>
|
|
||||||
</p>
|
|
||||||
{showNameInput && (
|
|
||||||
<div className="mt-7">
|
|
||||||
<UsernameInput
|
|
||||||
username={newUsername}
|
|
||||||
onChange={(e) => {
|
|
||||||
setNewUsername(e.target.value);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<Button
|
|
||||||
text={"Change"}
|
|
||||||
onClick={function (): void {
|
|
||||||
handleClickChangeName();
|
|
||||||
}}
|
|
||||||
type={"submit"}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
|
||||||
{showPwordInput && (
|
|
||||||
<div className="mt-7">
|
|
||||||
<PasswordInput
|
|
||||||
password={newPassword}
|
|
||||||
onChange={(e) => {
|
|
||||||
setNewPassword(e.target.value);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<Button
|
|
||||||
text={"Change"}
|
|
||||||
onClick={function (): void {
|
|
||||||
handleClickChangePassword();
|
|
||||||
}}
|
|
||||||
type={"submit"}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
<div className="items-center space-x-6 pr-6 pl-6">
|
||||||
<h2 className="font-bold text-[20px]">Member of these projects:</h2>
|
|
||||||
<UserProjectListAdmin username={props.username} />
|
|
||||||
<div className="items-center space-x-6">
|
|
||||||
<Button
|
<Button
|
||||||
text={"Delete"}
|
text={"Delete"}
|
||||||
onClick={function (): void {
|
onClick={function (): void {
|
||||||
|
@ -188,10 +62,6 @@ function UserInfoModal(props: {
|
||||||
<Button
|
<Button
|
||||||
text={"Close"}
|
text={"Close"}
|
||||||
onClick={function (): void {
|
onClick={function (): void {
|
||||||
setNewUsername("");
|
|
||||||
setNewPassword("");
|
|
||||||
setShowNameInput(false);
|
|
||||||
setShowPwordInput(false);
|
|
||||||
props.onClose();
|
props.onClose();
|
||||||
}}
|
}}
|
||||||
type="button"
|
type="button"
|
||||||
|
@ -199,7 +69,6 @@ function UserInfoModal(props: {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import UserInfoModal from "./UserInfoModal";
|
import UserInfoModal from "./UserInfoModal";
|
||||||
import InputField from "./InputField";
|
import DeleteUser from "./DeleteUser";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A list of users for admin manage users page, that sets an onClick
|
* A list of users for admin manage users page, that sets an onClick
|
||||||
|
@ -16,7 +16,6 @@ import InputField from "./InputField";
|
||||||
export function UserListAdmin(props: { users: string[] }): JSX.Element {
|
export function UserListAdmin(props: { users: string[] }): JSX.Element {
|
||||||
const [modalVisible, setModalVisible] = useState(false);
|
const [modalVisible, setModalVisible] = useState(false);
|
||||||
const [username, setUsername] = useState("");
|
const [username, setUsername] = useState("");
|
||||||
const [search, setSearch] = useState("");
|
|
||||||
|
|
||||||
const handleClick = (username: string): void => {
|
const handleClick = (username: string): void => {
|
||||||
setUsername(username);
|
setUsername(username);
|
||||||
|
@ -30,31 +29,18 @@ export function UserListAdmin(props: { users: string[] }): JSX.Element {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<h1 className="font-bold text-[30px] mb-[20px]">Manage Users</h1>
|
|
||||||
<UserInfoModal
|
<UserInfoModal
|
||||||
|
manageMember={false}
|
||||||
onClose={handleClose}
|
onClose={handleClose}
|
||||||
|
onDelete={() => DeleteUser}
|
||||||
isVisible={modalVisible}
|
isVisible={modalVisible}
|
||||||
username={username}
|
username={username}
|
||||||
/>
|
/>
|
||||||
<div>
|
<div>
|
||||||
<InputField
|
<ul className="font-bold underline text-[30px] cursor-pointer padding">
|
||||||
placeholder={"Search"}
|
{props.users.map((user) => (
|
||||||
type={"Text"}
|
|
||||||
value={search}
|
|
||||||
onChange={(e) => {
|
|
||||||
setSearch(e.target.value);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<ul className="mt-3 border-2 text-left border-black rounded-2xl px-2 divide-y divide-gray-300 font-semibold text-[30px] transition-all cursor-pointer overflow-auto h-[60vh] w-[40vw]">
|
|
||||||
{props.users
|
|
||||||
.filter((user) => {
|
|
||||||
return search.toLowerCase() === ""
|
|
||||||
? user
|
|
||||||
: user.toLowerCase().includes(search.toLowerCase());
|
|
||||||
})
|
|
||||||
.map((user) => (
|
|
||||||
<li
|
<li
|
||||||
className="hover:font-extrabold hover:underline p-1"
|
className="pt-5"
|
||||||
key={user}
|
key={user}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
handleClick(user);
|
handleClick(user);
|
||||||
|
|
|
@ -1,17 +1,35 @@
|
||||||
import { useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
|
import { api } from "../API/API";
|
||||||
import { Project } from "../Types/goTypes";
|
import { Project } from "../Types/goTypes";
|
||||||
import GetProjects from "./GetProjects";
|
|
||||||
|
|
||||||
function UserProjectListAdmin(props: { username: string }): JSX.Element {
|
function UserProjectListAdmin(): JSX.Element {
|
||||||
const [projects, setProjects] = useState<Project[]>([]);
|
const [projects, setProjects] = useState<Project[]>([]);
|
||||||
|
|
||||||
GetProjects({ setProjectsProp: setProjects, username: props.username });
|
useEffect(() => {
|
||||||
|
const fetchProjects = async (): Promise<void> => {
|
||||||
|
try {
|
||||||
|
const token = localStorage.getItem("accessToken") ?? "";
|
||||||
|
// const username = props.username;
|
||||||
|
|
||||||
|
const response = await api.getUserProjects(token);
|
||||||
|
if (response.success) {
|
||||||
|
setProjects(response.data ?? []);
|
||||||
|
} else {
|
||||||
|
console.error("Failed to fetch projects:", response.message);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error fetching projects:", error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
void fetchProjects();
|
||||||
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="border-2 border-black bg-white rounded-2xl text-left overflow-auto h-[15vh] font-medium">
|
<div className="border-2 border-black bg-white p-2 rounded-lg text-center">
|
||||||
<ul className="divide-y-2">
|
<ul>
|
||||||
{projects.map((project) => (
|
{projects.map((project) => (
|
||||||
<li className="mx-2 my-1" key={project.id}>
|
<li key={project.id}>
|
||||||
<span>{project.name}</span>
|
<span>{project.name}</span>
|
||||||
</li>
|
</li>
|
||||||
))}
|
))}
|
||||||
|
|
|
@ -16,12 +16,12 @@ function UserProjectMenu(): JSX.Element {
|
||||||
<h1 className="font-bold text-[30px] mb-[20px]">{projectName}</h1>
|
<h1 className="font-bold text-[30px] mb-[20px]">{projectName}</h1>
|
||||||
<div className="border-4 border-black bg-white flex flex-col items-center justify-center min-h-[65vh] h-fit w-[50vw] rounded-3xl content-center overflow-scroll space-y-[10vh] p-[30px]">
|
<div className="border-4 border-black bg-white flex flex-col items-center justify-center min-h-[65vh] h-fit w-[50vw] rounded-3xl content-center overflow-scroll space-y-[10vh] p-[30px]">
|
||||||
<Link to={`/timeReports/${projectName}/`}>
|
<Link to={`/timeReports/${projectName}/`}>
|
||||||
<h1 className="font-bold hover:underline text-[30px] cursor-pointer hover:font-extrabold">
|
<h1 className="font-bold underline text-[30px] cursor-pointer">
|
||||||
Your Time Reports
|
Your Time Reports
|
||||||
</h1>
|
</h1>
|
||||||
</Link>
|
</Link>
|
||||||
<Link to={`/newTimeReport/${projectName}`}>
|
<Link to={`/newTimeReport/${projectName}`}>
|
||||||
<h1 className="font-bold hover:underline text-[30px] cursor-pointer hover:font-extrabold">
|
<h1 className="font-bold underline text-[30px] cursor-pointer">
|
||||||
New Time Report
|
New Time Report
|
||||||
</h1>
|
</h1>
|
||||||
</Link>
|
</Link>
|
||||||
|
|
|
@ -1,150 +0,0 @@
|
||||||
import { useState, useEffect } from "react";
|
|
||||||
import { useParams } from "react-router-dom";
|
|
||||||
import { api } from "../API/API";
|
|
||||||
import { Statistics } from "../Types/goTypes";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Renders the component for showing total time per role in a project.
|
|
||||||
* @returns JSX.Element
|
|
||||||
*/
|
|
||||||
export default function UserStatistics(): JSX.Element {
|
|
||||||
const [development, setDevelopment] = useState<number>(0);
|
|
||||||
const [meeting, setMeeting] = useState<number>(0);
|
|
||||||
const [admin, setAdmin] = useState<number>(0);
|
|
||||||
const [own_work, setOwnWork] = useState<number>(0);
|
|
||||||
const [study, setStudy] = useState<number>(0);
|
|
||||||
const [testing, setTesting] = useState<number>(0);
|
|
||||||
const total = development + meeting + admin + own_work + study + testing;
|
|
||||||
|
|
||||||
const token = localStorage.getItem("accessToken") ?? "";
|
|
||||||
const { projectName } = useParams();
|
|
||||||
const { username } = useParams();
|
|
||||||
|
|
||||||
const fetchTimePerActivity = async (): Promise<void> => {
|
|
||||||
const response = await api.getStatistics(
|
|
||||||
projectName ?? "",
|
|
||||||
token,
|
|
||||||
username ?? "",
|
|
||||||
);
|
|
||||||
{
|
|
||||||
if (response.success) {
|
|
||||||
const statistics: Statistics = response.data ?? {
|
|
||||||
totalDevelopmentTime: 0,
|
|
||||||
totalMeetingTime: 0,
|
|
||||||
totalAdminTime: 0,
|
|
||||||
totalOwnWorkTime: 0,
|
|
||||||
totalStudyTime: 0,
|
|
||||||
totalTestingTime: 0,
|
|
||||||
};
|
|
||||||
setDevelopment(statistics.totalDevelopmentTime);
|
|
||||||
setMeeting(statistics.totalMeetingTime);
|
|
||||||
setAdmin(statistics.totalAdminTime);
|
|
||||||
setOwnWork(statistics.totalOwnWorkTime);
|
|
||||||
setStudy(statistics.totalStudyTime);
|
|
||||||
setTesting(statistics.totalTestingTime);
|
|
||||||
} else {
|
|
||||||
console.error("Failed to fetch weekly report:", response.message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
void fetchTimePerActivity();
|
|
||||||
});
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<h1 className="font-bold text-[30px] mb-[20px]">
|
|
||||||
Total Time 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={development}
|
|
||||||
readOnly
|
|
||||||
/>
|
|
||||||
</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={meeting}
|
|
||||||
readOnly
|
|
||||||
/>
|
|
||||||
</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={admin}
|
|
||||||
readOnly
|
|
||||||
/>
|
|
||||||
</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={own_work}
|
|
||||||
readOnly
|
|
||||||
/>
|
|
||||||
</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={study}
|
|
||||||
readOnly
|
|
||||||
/>
|
|
||||||
</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={testing}
|
|
||||||
readOnly
|
|
||||||
/>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr className="h-[10vh] font-bold font-">
|
|
||||||
<td>In Total:</td>
|
|
||||||
<td>
|
|
||||||
<h1>{total}</h1>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { useState, useEffect } from "react";
|
import { useState, useEffect } from "react";
|
||||||
import { WeeklyReport } from "../Types/goTypes";
|
import { WeeklyReport, NewWeeklyReport } from "../Types/goTypes";
|
||||||
import { api } from "../API/API";
|
import { api } from "../API/API";
|
||||||
import { useNavigate, useParams } from "react-router-dom";
|
import { useNavigate, useParams } from "react-router-dom";
|
||||||
import Button from "./Button";
|
import Button from "./Button";
|
||||||
|
@ -18,7 +18,6 @@ export default function GetOtherUsersReport(): JSX.Element {
|
||||||
const [ownWorkTime, setOwnWorkTime] = useState(0);
|
const [ownWorkTime, setOwnWorkTime] = useState(0);
|
||||||
const [studyTime, setStudyTime] = useState(0);
|
const [studyTime, setStudyTime] = useState(0);
|
||||||
const [testingTime, setTestingTime] = useState(0);
|
const [testingTime, setTestingTime] = useState(0);
|
||||||
const [reportId, setReportId] = useState(0);
|
|
||||||
|
|
||||||
const token = localStorage.getItem("accessToken") ?? "";
|
const token = localStorage.getItem("accessToken") ?? "";
|
||||||
const { projectName } = useParams();
|
const { projectName } = useParams();
|
||||||
|
@ -31,9 +30,8 @@ export default function GetOtherUsersReport(): JSX.Element {
|
||||||
projectName ?? "",
|
projectName ?? "",
|
||||||
fetchedWeek?.toString() ?? "0",
|
fetchedWeek?.toString() ?? "0",
|
||||||
token,
|
token,
|
||||||
username ?? "",
|
|
||||||
);
|
);
|
||||||
console.log(response);
|
|
||||||
if (response.success) {
|
if (response.success) {
|
||||||
const report: WeeklyReport = response.data ?? {
|
const report: WeeklyReport = response.data ?? {
|
||||||
reportId: 0,
|
reportId: 0,
|
||||||
|
@ -47,7 +45,6 @@ export default function GetOtherUsersReport(): JSX.Element {
|
||||||
studyTime: 0,
|
studyTime: 0,
|
||||||
testingTime: 0,
|
testingTime: 0,
|
||||||
};
|
};
|
||||||
setReportId(report.reportId);
|
|
||||||
setWeek(report.week);
|
setWeek(report.week);
|
||||||
setDevelopmentTime(report.developmentTime);
|
setDevelopmentTime(report.developmentTime);
|
||||||
setMeetingTime(report.meetingTime);
|
setMeetingTime(report.meetingTime);
|
||||||
|
@ -63,13 +60,19 @@ export default function GetOtherUsersReport(): JSX.Element {
|
||||||
void fetchUsersWeeklyReport();
|
void fetchUsersWeeklyReport();
|
||||||
});
|
});
|
||||||
|
|
||||||
const handleSignWeeklyReport = async (): Promise<boolean> => {
|
const handleSignWeeklyReport = async (): Promise<void> => {
|
||||||
const response = await api.signReport(reportId, token);
|
const newWeeklyReport: NewWeeklyReport = {
|
||||||
if (response.success) {
|
projectName: projectName ?? "",
|
||||||
return true;
|
week,
|
||||||
} else {
|
developmentTime,
|
||||||
return false;
|
meetingTime,
|
||||||
}
|
adminTime,
|
||||||
|
ownWorkTime,
|
||||||
|
studyTime,
|
||||||
|
testingTime,
|
||||||
|
};
|
||||||
|
|
||||||
|
await api.submitWeeklyReport(newWeeklyReport, token);
|
||||||
};
|
};
|
||||||
|
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
@ -81,15 +84,8 @@ export default function GetOtherUsersReport(): JSX.Element {
|
||||||
<form
|
<form
|
||||||
onSubmit={(e) => {
|
onSubmit={(e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
void (async (): Promise<void> => {
|
void handleSignWeeklyReport();
|
||||||
const success = await handleSignWeeklyReport();
|
|
||||||
if (!success) {
|
|
||||||
alert("Failed to sign report!");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
alert("Report successfully signed!");
|
|
||||||
navigate(-1);
|
navigate(-1);
|
||||||
})();
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div className="flex flex-col items-center">
|
<div className="flex flex-col items-center">
|
||||||
|
@ -116,10 +112,7 @@ export default function GetOtherUsersReport(): JSX.Element {
|
||||||
type="text"
|
type="text"
|
||||||
min="0"
|
min="0"
|
||||||
className="border-2 border-black rounded-md text-center w-1/2"
|
className="border-2 border-black rounded-md text-center w-1/2"
|
||||||
defaultValue={
|
value={developmentTime === 0 ? "" : developmentTime}
|
||||||
developmentTime === 0 ? "" : developmentTime
|
|
||||||
}
|
|
||||||
readOnly
|
|
||||||
/>
|
/>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
@ -130,8 +123,7 @@ export default function GetOtherUsersReport(): JSX.Element {
|
||||||
type="text"
|
type="text"
|
||||||
min="0"
|
min="0"
|
||||||
className="border-2 border-black rounded-md text-center w-1/2"
|
className="border-2 border-black rounded-md text-center w-1/2"
|
||||||
defaultValue={meetingTime === 0 ? "" : meetingTime}
|
value={meetingTime === 0 ? "" : meetingTime}
|
||||||
readOnly
|
|
||||||
/>
|
/>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
@ -142,8 +134,7 @@ export default function GetOtherUsersReport(): JSX.Element {
|
||||||
type="text"
|
type="text"
|
||||||
min="0"
|
min="0"
|
||||||
className="border-2 border-black rounded-md text-center w-1/2"
|
className="border-2 border-black rounded-md text-center w-1/2"
|
||||||
defaultValue={adminTime === 0 ? "" : adminTime}
|
value={adminTime === 0 ? "" : adminTime}
|
||||||
readOnly
|
|
||||||
/>
|
/>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
@ -154,8 +145,7 @@ export default function GetOtherUsersReport(): JSX.Element {
|
||||||
type="text"
|
type="text"
|
||||||
min="0"
|
min="0"
|
||||||
className="border-2 border-black rounded-md text-center w-1/2"
|
className="border-2 border-black rounded-md text-center w-1/2"
|
||||||
defaultValue={ownWorkTime === 0 ? "" : ownWorkTime}
|
value={ownWorkTime === 0 ? "" : ownWorkTime}
|
||||||
readOnly
|
|
||||||
/>
|
/>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
@ -166,8 +156,7 @@ export default function GetOtherUsersReport(): JSX.Element {
|
||||||
type="text"
|
type="text"
|
||||||
min="0"
|
min="0"
|
||||||
className="border-2 border-black rounded-md text-center w-1/2"
|
className="border-2 border-black rounded-md text-center w-1/2"
|
||||||
defaultValue={studyTime === 0 ? "" : studyTime}
|
value={studyTime === 0 ? "" : studyTime}
|
||||||
readOnly
|
|
||||||
/>
|
/>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
@ -178,8 +167,7 @@ export default function GetOtherUsersReport(): JSX.Element {
|
||||||
type="text"
|
type="text"
|
||||||
min="0"
|
min="0"
|
||||||
className="border-2 border-black rounded-md text-center w-1/2"
|
className="border-2 border-black rounded-md text-center w-1/2"
|
||||||
defaultValue={testingTime === 0 ? "" : testingTime}
|
value={testingTime === 0 ? "" : testingTime}
|
||||||
readOnly
|
|
||||||
/>
|
/>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
|
@ -1,23 +0,0 @@
|
||||||
import { useEffect } from "react";
|
|
||||||
import { GenApi } from "../API/GenApi";
|
|
||||||
|
|
||||||
// Instantiation of the API
|
|
||||||
const api = new GenApi();
|
|
||||||
|
|
||||||
export function GenApiDemo(): JSX.Element {
|
|
||||||
// Sync wrapper around the loginCreate method
|
|
||||||
const register = async (): Promise<void> => {
|
|
||||||
const response = await api.login.loginCreate({
|
|
||||||
username: "admin",
|
|
||||||
password: "admin",
|
|
||||||
});
|
|
||||||
console.log(response.data); // This should be the inner type of the response
|
|
||||||
};
|
|
||||||
|
|
||||||
// Call the wrapper
|
|
||||||
useEffect(() => {
|
|
||||||
void register();
|
|
||||||
});
|
|
||||||
|
|
||||||
return <></>;
|
|
||||||
}
|
|
|
@ -1,36 +0,0 @@
|
||||||
//Different character limits certain strings
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Allowed character length for password
|
|
||||||
*/
|
|
||||||
export const passwordLength = 6;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Lower limit for username length
|
|
||||||
*/
|
|
||||||
export const usernameLowLimit = 5;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Upper limit for password length
|
|
||||||
*/
|
|
||||||
export const usernameUpLimit = 10;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Lower limit for project name length
|
|
||||||
*/
|
|
||||||
export const projNameLowLimit = 10;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Upper limit for project name length
|
|
||||||
*/
|
|
||||||
export const projNameHighLimit = 99;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Upper limit for project description length
|
|
||||||
*/
|
|
||||||
export const projDescLowLimit = 0;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Upper limit for project description length
|
|
||||||
*/
|
|
||||||
export const projDescHighLimit = 99;
|
|
|
@ -1,9 +0,0 @@
|
||||||
/**
|
|
||||||
* Only alphanumerical characters
|
|
||||||
*/
|
|
||||||
export const alphanumeric = /^[a-zA-Z0-9]+$/;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Only lowercase letters
|
|
||||||
*/
|
|
||||||
export const lowercase = /^[a-z]+$/;
|
|
28
frontend/src/Pages/AdminPages/AdminChangeUsername.tsx
Normal file
28
frontend/src/Pages/AdminPages/AdminChangeUsername.tsx
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
import BackButton from "../../Components/BackButton";
|
||||||
|
import BasicWindow from "../../Components/BasicWindow";
|
||||||
|
import Button from "../../Components/Button";
|
||||||
|
import ChangeUsername from "../../Components/ChangeUsername";
|
||||||
|
|
||||||
|
function AdminChangeUsername(): JSX.Element {
|
||||||
|
const content = (
|
||||||
|
<>
|
||||||
|
<ChangeUsername />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
|
||||||
|
const buttons = (
|
||||||
|
<>
|
||||||
|
<Button
|
||||||
|
text="Finish"
|
||||||
|
onClick={(): void => {
|
||||||
|
return;
|
||||||
|
}}
|
||||||
|
type="button"
|
||||||
|
/>
|
||||||
|
<BackButton />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
|
||||||
|
return <BasicWindow content={content} buttons={buttons} />;
|
||||||
|
}
|
||||||
|
export default AdminChangeUsername;
|
|
@ -1,19 +1,23 @@
|
||||||
import { Link } from "react-router-dom";
|
import { Link } from "react-router-dom";
|
||||||
|
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";
|
||||||
import { ProjectListAdmin } from "../../Components/ProjectListAdmin";
|
import { ProjectListAdmin } from "../../Components/ProjectListAdmin";
|
||||||
import { Project } from "../../Types/goTypes";
|
import { Project } from "../../Types/goTypes";
|
||||||
import GetProjects from "../../Components/GetProjects";
|
import GetProjects from "../../Components/GetProjects";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import NavButton from "../../Components/NavButton";
|
|
||||||
|
|
||||||
function AdminManageProjects(): JSX.Element {
|
function AdminManageProjects(): JSX.Element {
|
||||||
const [projects, setProjects] = useState<Project[]>([]);
|
const [projects, setProjects] = useState<Project[]>([]);
|
||||||
GetProjects({
|
GetProjects({ setProjectsProp: setProjects });
|
||||||
setProjectsProp: setProjects,
|
const content = (
|
||||||
username: localStorage.getItem("username") ?? "",
|
<>
|
||||||
});
|
<h1 className="font-bold text-[30px] mb-[20px]">Manage Projects</h1>
|
||||||
const content = <ProjectListAdmin projects={projects} />;
|
<div className="border-4 border-black bg-white flex flex-col items-center h-[65vh] w-[50vw] rounded-3xl content-center overflow-scroll space-y-[10vh] p-[30px]">
|
||||||
|
<ProjectListAdmin projects={projects} />
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
|
||||||
const buttons = (
|
const buttons = (
|
||||||
<>
|
<>
|
||||||
|
@ -26,7 +30,7 @@ function AdminManageProjects(): JSX.Element {
|
||||||
type="button"
|
type="button"
|
||||||
/>
|
/>
|
||||||
</Link>
|
</Link>
|
||||||
<NavButton navTo="/admin" label={"Back"} />
|
<BackButton />
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -12,7 +12,14 @@ function AdminManageUsers(): JSX.Element {
|
||||||
|
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
const content = <UserListAdmin users={users} />;
|
const content = (
|
||||||
|
<>
|
||||||
|
<h1 className="font-bold text-[30px] mb-[20px]">Manage Users</h1>
|
||||||
|
<div className="border-4 border-black bg-white flex flex-col items-center h-[65vh] w-[50vw] rounded-3xl content-center overflow-scroll space-y-[10vh] p-[30px]">
|
||||||
|
<UserListAdmin users={users} />
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
|
||||||
const buttons = (
|
const buttons = (
|
||||||
<>
|
<>
|
||||||
|
|
|
@ -5,14 +5,14 @@ function AdminMenuPage(): JSX.Element {
|
||||||
const content = (
|
const content = (
|
||||||
<>
|
<>
|
||||||
<h1 className="font-bold text-[30px] mb-[20px]">Administrator Menu</h1>
|
<h1 className="font-bold text-[30px] mb-[20px]">Administrator Menu</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-auto 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]">
|
||||||
<Link to="/adminManageUser">
|
<Link to="/adminManageUser">
|
||||||
<h1 className="font-bold hover:underline text-[30px] cursor-pointer hover:font-extrabold">
|
<h1 className="font-bold underline text-[30px] cursor-pointer">
|
||||||
Manage Users
|
Manage Users
|
||||||
</h1>
|
</h1>
|
||||||
</Link>
|
</Link>
|
||||||
<Link to="/adminManageProject">
|
<Link to="/adminManageProject">
|
||||||
<h1 className="font-bold hover:underline text-[30px] cursor-pointer hover:font-extrabold">
|
<h1 className="font-bold underline text-[30px] cursor-pointer">
|
||||||
Manage Projects
|
Manage Projects
|
||||||
</h1>
|
</h1>
|
||||||
</Link>
|
</Link>
|
||||||
|
|
|
@ -1,12 +1,11 @@
|
||||||
import { useLocation } from "react-router-dom";
|
|
||||||
import AddUserToProject from "../../Components/AddUserToProject";
|
import AddUserToProject from "../../Components/AddUserToProject";
|
||||||
import BasicWindow from "../../Components/BasicWindow";
|
import BasicWindow from "../../Components/BasicWindow";
|
||||||
import BackButton from "../../Components/BackButton";
|
|
||||||
|
|
||||||
function AdminProjectAddMember(): JSX.Element {
|
function AdminProjectAddMember(): JSX.Element {
|
||||||
const projectName = useLocation().search.slice(1);
|
const content = <AddUserToProject />;
|
||||||
const content = <AddUserToProject projectName={projectName} />;
|
|
||||||
const buttons = <BackButton />;
|
const buttons = <></>;
|
||||||
|
|
||||||
return <BasicWindow content={content} buttons={buttons} />;
|
return <BasicWindow content={content} buttons={buttons} />;
|
||||||
}
|
}
|
||||||
export default AdminProjectAddMember;
|
export default AdminProjectAddMember;
|
||||||
|
|
23
frontend/src/Pages/AdminPages/AdminProjectChangeUserRole.tsx
Normal file
23
frontend/src/Pages/AdminPages/AdminProjectChangeUserRole.tsx
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
import BackButton from "../../Components/BackButton";
|
||||||
|
import BasicWindow from "../../Components/BasicWindow";
|
||||||
|
import Button from "../../Components/Button";
|
||||||
|
|
||||||
|
function AdminProjectChangeUserRole(): JSX.Element {
|
||||||
|
const content = <></>;
|
||||||
|
|
||||||
|
const buttons = (
|
||||||
|
<>
|
||||||
|
<Button
|
||||||
|
text="Change"
|
||||||
|
onClick={(): void => {
|
||||||
|
return;
|
||||||
|
}}
|
||||||
|
type="button"
|
||||||
|
/>
|
||||||
|
<BackButton />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
|
||||||
|
return <BasicWindow content={content} buttons={buttons} />;
|
||||||
|
}
|
||||||
|
export default AdminProjectChangeUserRole;
|
23
frontend/src/Pages/AdminPages/AdminProjectManageMembers.tsx
Normal file
23
frontend/src/Pages/AdminPages/AdminProjectManageMembers.tsx
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
import BackButton from "../../Components/BackButton";
|
||||||
|
import BasicWindow from "../../Components/BasicWindow";
|
||||||
|
import Button from "../../Components/Button";
|
||||||
|
|
||||||
|
function AdminProjectManageMembers(): JSX.Element {
|
||||||
|
const content = <></>;
|
||||||
|
|
||||||
|
const buttons = (
|
||||||
|
<>
|
||||||
|
<Button
|
||||||
|
text="Add Member"
|
||||||
|
onClick={(): void => {
|
||||||
|
return;
|
||||||
|
}}
|
||||||
|
type="button"
|
||||||
|
/>
|
||||||
|
<BackButton />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
|
||||||
|
return <BasicWindow content={content} buttons={buttons} />;
|
||||||
|
}
|
||||||
|
export default AdminProjectManageMembers;
|
33
frontend/src/Pages/AdminPages/AdminProjectPage.tsx
Normal file
33
frontend/src/Pages/AdminPages/AdminProjectPage.tsx
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
import { useParams } from "react-router-dom";
|
||||||
|
import { api } from "../../API/API";
|
||||||
|
import BackButton from "../../Components/BackButton";
|
||||||
|
import BasicWindow from "../../Components/BasicWindow";
|
||||||
|
import Button from "../../Components/Button";
|
||||||
|
|
||||||
|
async function handleDeleteProject(
|
||||||
|
projectName: string,
|
||||||
|
token: string,
|
||||||
|
): Promise<void> {
|
||||||
|
await api.removeProject(projectName, token);
|
||||||
|
}
|
||||||
|
|
||||||
|
function AdminProjectPage(): JSX.Element {
|
||||||
|
const content = <></>;
|
||||||
|
const { projectName } = useParams();
|
||||||
|
const token = localStorage.getItem("accessToken");
|
||||||
|
|
||||||
|
const buttons = (
|
||||||
|
<>
|
||||||
|
<Button
|
||||||
|
text="Delete"
|
||||||
|
onClick={() => handleDeleteProject(projectName, token)}
|
||||||
|
type="button"
|
||||||
|
/>
|
||||||
|
<BackButton />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
|
||||||
|
return <BasicWindow content={content} buttons={buttons} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default AdminProjectPage;
|
|
@ -1,13 +1,8 @@
|
||||||
import BackButton from "../../Components/BackButton";
|
import BackButton from "../../Components/BackButton";
|
||||||
import BasicWindow from "../../Components/BasicWindow";
|
import BasicWindow from "../../Components/BasicWindow";
|
||||||
import UserStatistics from "../../Components/UserStatistics";
|
|
||||||
|
|
||||||
function UserNewTimeReportPage(): JSX.Element {
|
function AdminProjectStatistics(): JSX.Element {
|
||||||
const content = (
|
const content = <></>;
|
||||||
<>
|
|
||||||
<UserStatistics />
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
|
|
||||||
const buttons = (
|
const buttons = (
|
||||||
<>
|
<>
|
||||||
|
@ -17,4 +12,4 @@ function UserNewTimeReportPage(): JSX.Element {
|
||||||
|
|
||||||
return <BasicWindow content={content} buttons={buttons} />;
|
return <BasicWindow content={content} buttons={buttons} />;
|
||||||
}
|
}
|
||||||
export default UserNewTimeReportPage;
|
export default AdminProjectStatistics;
|
23
frontend/src/Pages/AdminPages/AdminProjectViewMemberInfo.tsx
Normal file
23
frontend/src/Pages/AdminPages/AdminProjectViewMemberInfo.tsx
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
import BackButton from "../../Components/BackButton";
|
||||||
|
import BasicWindow from "../../Components/BasicWindow";
|
||||||
|
import Button from "../../Components/Button";
|
||||||
|
|
||||||
|
function AdminProjectViewMemberInfo(): JSX.Element {
|
||||||
|
const content = <></>;
|
||||||
|
|
||||||
|
const buttons = (
|
||||||
|
<>
|
||||||
|
<Button
|
||||||
|
text="Remove"
|
||||||
|
onClick={(): void => {
|
||||||
|
return;
|
||||||
|
}}
|
||||||
|
type="button"
|
||||||
|
/>
|
||||||
|
<BackButton />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
|
||||||
|
return <BasicWindow content={content} buttons={buttons} />;
|
||||||
|
}
|
||||||
|
export default AdminProjectViewMemberInfo;
|
28
frontend/src/Pages/AdminPages/AdminViewUserInfo.tsx
Normal file
28
frontend/src/Pages/AdminPages/AdminViewUserInfo.tsx
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
import BasicWindow from "../../Components/BasicWindow";
|
||||||
|
import Button from "../../Components/Button";
|
||||||
|
import BackButton from "../../Components/BackButton";
|
||||||
|
import UserProjectListAdmin from "../../Components/UserProjectListAdmin";
|
||||||
|
|
||||||
|
function AdminViewUserInfo(): JSX.Element {
|
||||||
|
const content = (
|
||||||
|
<>
|
||||||
|
<UserProjectListAdmin />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
|
||||||
|
const buttons = (
|
||||||
|
<>
|
||||||
|
<Button
|
||||||
|
text="Delete"
|
||||||
|
onClick={(): void => {
|
||||||
|
return;
|
||||||
|
}}
|
||||||
|
type="button"
|
||||||
|
/>
|
||||||
|
<BackButton />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
|
||||||
|
return <BasicWindow content={content} buttons={buttons} />;
|
||||||
|
}
|
||||||
|
export default AdminViewUserInfo;
|
|
@ -1,12 +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";
|
import AllTimeReportsInProjectOtherUser from "../../Components/AllTimeReportsInProjectOtherUser";
|
||||||
import Button from "../../Components/Button";
|
|
||||||
import { useParams, Link } from "react-router-dom";
|
|
||||||
|
|
||||||
function PMOtherUsersTR(): JSX.Element {
|
function PMOtherUsersTR(): JSX.Element {
|
||||||
const { projectName } = useParams();
|
|
||||||
const { username } = useParams();
|
|
||||||
const content = (
|
const content = (
|
||||||
<>
|
<>
|
||||||
<AllTimeReportsInProjectOtherUser />
|
<AllTimeReportsInProjectOtherUser />
|
||||||
|
@ -15,15 +11,6 @@ function PMOtherUsersTR(): JSX.Element {
|
||||||
|
|
||||||
const buttons = (
|
const buttons = (
|
||||||
<>
|
<>
|
||||||
<Link to={`/viewStatistics/${projectName}/${username}`}>
|
|
||||||
<Button
|
|
||||||
text={`Statistics: ${username}`}
|
|
||||||
onClick={(): void => {
|
|
||||||
return;
|
|
||||||
}}
|
|
||||||
type={"button"}
|
|
||||||
/>
|
|
||||||
</Link>
|
|
||||||
<BackButton />
|
<BackButton />
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|
|
@ -16,7 +16,7 @@ function PMProjectMembers(): JSX.Element {
|
||||||
<>
|
<>
|
||||||
<Link to={`/PMtimeactivity/${projectName}`}>
|
<Link to={`/PMtimeactivity/${projectName}`}>
|
||||||
<Button
|
<Button
|
||||||
text="Statistics"
|
text="Time / Activity"
|
||||||
onClick={(): void => {
|
onClick={(): void => {
|
||||||
return;
|
return;
|
||||||
}}
|
}}
|
||||||
|
|
|
@ -1,12 +1,10 @@
|
||||||
import BasicWindow from "../../Components/BasicWindow";
|
import BasicWindow from "../../Components/BasicWindow";
|
||||||
import BackButton from "../../Components/BackButton";
|
import BackButton from "../../Components/BackButton";
|
||||||
import { useParams, Link } from "react-router-dom";
|
import { useParams } from "react-router-dom";
|
||||||
import AllTimeReportsInProject from "../../Components/AllTimeReportsInProject";
|
import AllTimeReportsInProject from "../../Components/AllTimeReportsInProject";
|
||||||
import Button from "../../Components/Button";
|
|
||||||
|
|
||||||
function UserViewTimeReportsPage(): JSX.Element {
|
function UserViewTimeReportsPage(): JSX.Element {
|
||||||
const { projectName } = useParams();
|
const { projectName } = useParams();
|
||||||
const username = localStorage.getItem("username");
|
|
||||||
|
|
||||||
const content = (
|
const content = (
|
||||||
<>
|
<>
|
||||||
|
@ -19,15 +17,6 @@ function UserViewTimeReportsPage(): JSX.Element {
|
||||||
|
|
||||||
const buttons = (
|
const buttons = (
|
||||||
<>
|
<>
|
||||||
<Link to={`/viewStatistics/${projectName}/${username}`}>
|
|
||||||
<Button
|
|
||||||
text="Statistics"
|
|
||||||
onClick={(): void => {
|
|
||||||
return;
|
|
||||||
}}
|
|
||||||
type={"button"}
|
|
||||||
/>
|
|
||||||
</Link>
|
|
||||||
<BackButton />
|
<BackButton />
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|
|
@ -124,52 +124,6 @@ export interface WeeklyReport {
|
||||||
*/
|
*/
|
||||||
signedBy?: number /* int */;
|
signedBy?: number /* int */;
|
||||||
}
|
}
|
||||||
export interface Statistics {
|
|
||||||
totalDevelopmentTime: number /* int */;
|
|
||||||
totalMeetingTime: number /* int */;
|
|
||||||
totalAdminTime: number /* int */;
|
|
||||||
totalOwnWorkTime: number /* int */;
|
|
||||||
totalStudyTime: number /* int */;
|
|
||||||
totalTestingTime: number /* int */;
|
|
||||||
}
|
|
||||||
export interface UpdateWeeklyReport {
|
|
||||||
/**
|
|
||||||
* The name of the project, as it appears in the database
|
|
||||||
*/
|
|
||||||
projectName: string;
|
|
||||||
/**
|
|
||||||
* The name of the user
|
|
||||||
*/
|
|
||||||
userName: string;
|
|
||||||
/**
|
|
||||||
* The week number
|
|
||||||
*/
|
|
||||||
week: number /* int */;
|
|
||||||
/**
|
|
||||||
* Total time spent on development
|
|
||||||
*/
|
|
||||||
developmentTime: number /* int */;
|
|
||||||
/**
|
|
||||||
* Total time spent in meetings
|
|
||||||
*/
|
|
||||||
meetingTime: number /* int */;
|
|
||||||
/**
|
|
||||||
* Total time spent on administrative tasks
|
|
||||||
*/
|
|
||||||
adminTime: number /* int */;
|
|
||||||
/**
|
|
||||||
* Total time spent on personal projects
|
|
||||||
*/
|
|
||||||
ownWorkTime: number /* int */;
|
|
||||||
/**
|
|
||||||
* Total time spent on studying
|
|
||||||
*/
|
|
||||||
studyTime: number /* int */;
|
|
||||||
/**
|
|
||||||
* Total time spent on testing
|
|
||||||
*/
|
|
||||||
testingTime: number /* int */;
|
|
||||||
}
|
|
||||||
|
|
||||||
//////////
|
//////////
|
||||||
// source: project.go
|
// source: project.go
|
||||||
|
@ -197,9 +151,16 @@ 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;
|
||||||
|
@ -230,6 +191,11 @@ export interface PublicUser {
|
||||||
userId: string;
|
userId: string;
|
||||||
username: string;
|
username: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface UserProjectMember {
|
||||||
|
Username: string;
|
||||||
|
UserRole: string;
|
||||||
|
}
|
||||||
/**
|
/**
|
||||||
* wrapper type for token
|
* wrapper type for token
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -9,7 +9,6 @@ import AdminMenuPage from "./Pages/AdminPages/AdminMenuPage.tsx";
|
||||||
import UserEditTimeReportPage from "./Pages/UserPages/UserEditTimeReportPage.tsx";
|
import UserEditTimeReportPage from "./Pages/UserPages/UserEditTimeReportPage.tsx";
|
||||||
import UserNewTimeReportPage from "./Pages/UserPages/UserNewTimeReportPage.tsx";
|
import UserNewTimeReportPage from "./Pages/UserPages/UserNewTimeReportPage.tsx";
|
||||||
import UserViewTimeReportsPage from "./Pages/UserPages/UserViewTimeReportsPage.tsx";
|
import UserViewTimeReportsPage from "./Pages/UserPages/UserViewTimeReportsPage.tsx";
|
||||||
import UserViewStatistics from "./Pages/UserPages/UserViewStatistics.tsx";
|
|
||||||
import PMChangeRole from "./Pages/ProjectManagerPages/PMChangeRole.tsx";
|
import PMChangeRole from "./Pages/ProjectManagerPages/PMChangeRole.tsx";
|
||||||
import PMOtherUsersTR from "./Pages/ProjectManagerPages/PMOtherUsersTR.tsx";
|
import PMOtherUsersTR from "./Pages/ProjectManagerPages/PMOtherUsersTR.tsx";
|
||||||
import PMProjectMembers from "./Pages/ProjectManagerPages/PMProjectMembers.tsx";
|
import PMProjectMembers from "./Pages/ProjectManagerPages/PMProjectMembers.tsx";
|
||||||
|
@ -19,10 +18,17 @@ import PMTotalTimeRole from "./Pages/ProjectManagerPages/PMTotalTimeRole.tsx";
|
||||||
import PMUnsignedReports from "./Pages/ProjectManagerPages/PMUnsignedReports.tsx";
|
import PMUnsignedReports from "./Pages/ProjectManagerPages/PMUnsignedReports.tsx";
|
||||||
import PMViewUnsignedReport from "./Pages/ProjectManagerPages/PMViewUnsignedReport.tsx";
|
import PMViewUnsignedReport from "./Pages/ProjectManagerPages/PMViewUnsignedReport.tsx";
|
||||||
import AdminManageUsers from "./Pages/AdminPages/AdminManageUsers.tsx";
|
import AdminManageUsers from "./Pages/AdminPages/AdminManageUsers.tsx";
|
||||||
|
import AdminViewUserInfo from "./Pages/AdminPages/AdminViewUserInfo.tsx";
|
||||||
import AdminManageProjects from "./Pages/AdminPages/AdminManageProjects.tsx";
|
import AdminManageProjects from "./Pages/AdminPages/AdminManageProjects.tsx";
|
||||||
import AdminAddProject from "./Pages/AdminPages/AdminAddProject.tsx";
|
import AdminAddProject from "./Pages/AdminPages/AdminAddProject.tsx";
|
||||||
import AdminAddUser from "./Pages/AdminPages/AdminAddUser.tsx";
|
import AdminAddUser from "./Pages/AdminPages/AdminAddUser.tsx";
|
||||||
|
import AdminChangeUsername from "./Pages/AdminPages/AdminChangeUsername.tsx";
|
||||||
import AdminProjectAddMember from "./Pages/AdminPages/AdminProjectAddMember.tsx";
|
import AdminProjectAddMember from "./Pages/AdminPages/AdminProjectAddMember.tsx";
|
||||||
|
import AdminProjectChangeUserRole from "./Pages/AdminPages/AdminProjectChangeUserRole.tsx";
|
||||||
|
import AdminProjectManageMembers from "./Pages/AdminPages/AdminProjectManageMembers.tsx";
|
||||||
|
import AdminProjectStatistics from "./Pages/AdminPages/AdminProjectStatistics.tsx";
|
||||||
|
import AdminProjectViewMemberInfo from "./Pages/AdminPages/AdminProjectViewMemberInfo.tsx";
|
||||||
|
import AdminProjectPage from "./Pages/AdminPages/AdminProjectPage.tsx";
|
||||||
import NotFoundPage from "./Pages/NotFoundPage.tsx";
|
import NotFoundPage from "./Pages/NotFoundPage.tsx";
|
||||||
import UnauthorizedPage from "./Pages/UnauthorizedPage.tsx";
|
import UnauthorizedPage from "./Pages/UnauthorizedPage.tsx";
|
||||||
import PMViewOtherUsersTR from "./Pages/ProjectManagerPages/PMViewOtherUsersTR.tsx";
|
import PMViewOtherUsersTR from "./Pages/ProjectManagerPages/PMViewOtherUsersTR.tsx";
|
||||||
|
@ -55,13 +61,9 @@ const router = createBrowserRouter([
|
||||||
element: <UserViewTimeReportsPage />,
|
element: <UserViewTimeReportsPage />,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "/editTimeReport/:projectName/:fetchedWeek/:signedOrUnsigned",
|
path: "/editTimeReport/:projectName/:fetchedWeek",
|
||||||
element: <UserEditTimeReportPage />,
|
element: <UserEditTimeReportPage />,
|
||||||
},
|
},
|
||||||
{
|
|
||||||
path: "/viewStatistics/:projectName/:username",
|
|
||||||
element: <UserViewStatistics />,
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
path: "/changeRole/:projectName/:username",
|
path: "/changeRole/:projectName/:username",
|
||||||
element: <PMChangeRole />,
|
element: <PMChangeRole />,
|
||||||
|
@ -71,7 +73,7 @@ const router = createBrowserRouter([
|
||||||
element: <PMOtherUsersTR />,
|
element: <PMOtherUsersTR />,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "/editOthersTR/:projectName/:username/:fetchedWeek/:signedOrUnsigned",
|
path: "/editOthersTR/:projectName/:username/:fetchedWeek",
|
||||||
element: <PMViewOtherUsersTR />,
|
element: <PMViewOtherUsersTR />,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -98,10 +100,34 @@ const router = createBrowserRouter([
|
||||||
path: "/PMViewUnsignedReport/:projectName/:username/:fetchedWeek",
|
path: "/PMViewUnsignedReport/:projectName/:username/:fetchedWeek",
|
||||||
element: <PMViewUnsignedReport />,
|
element: <PMViewUnsignedReport />,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: "/adminChangeUsername",
|
||||||
|
element: <AdminChangeUsername />,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: "/adminProjectAddMember",
|
path: "/adminProjectAddMember",
|
||||||
element: <AdminProjectAddMember />,
|
element: <AdminProjectAddMember />,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: "/adminProjectChangeUserRole",
|
||||||
|
element: <AdminProjectChangeUserRole />,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "/adminProjectManageMembers",
|
||||||
|
element: <AdminProjectManageMembers />,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "/adminProjectPage",
|
||||||
|
element: <AdminProjectPage />,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "/adminProjectStatistics",
|
||||||
|
element: <AdminProjectStatistics />,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "/adminProjectViewMembers",
|
||||||
|
element: <AdminProjectViewMemberInfo />,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: "/addProject",
|
path: "/addProject",
|
||||||
element: <AdminAddProject />,
|
element: <AdminAddProject />,
|
||||||
|
@ -110,6 +136,10 @@ const router = createBrowserRouter([
|
||||||
path: "/adminAddUser",
|
path: "/adminAddUser",
|
||||||
element: <AdminAddUser />,
|
element: <AdminAddUser />,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: "/adminUserInfo",
|
||||||
|
element: <AdminViewUserInfo />,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: "/adminManageProject",
|
path: "/adminManageProject",
|
||||||
element: <AdminManageProjects />,
|
element: <AdminManageProjects />,
|
||||||
|
|
|
@ -1,14 +1,50 @@
|
||||||
import requests
|
import requests
|
||||||
|
import string
|
||||||
|
import random
|
||||||
|
|
||||||
# This modules contains helper functions for the tests
|
debug_output = True
|
||||||
from helpers import *
|
|
||||||
|
def gprint(*args, **kwargs):
|
||||||
|
print("\033[92m", *args, "\033[00m", **kwargs)
|
||||||
|
|
||||||
print("Running Tests...")
|
print("Running Tests...")
|
||||||
|
|
||||||
|
def dprint(*args, **kwargs):
|
||||||
|
if debug_output:
|
||||||
|
print(*args, **kwargs)
|
||||||
|
|
||||||
|
def randomString(len=10):
|
||||||
|
"""Generate a random string of fixed length"""
|
||||||
|
letters = string.ascii_lowercase
|
||||||
|
return "".join(random.choice(letters) for i in range(len))
|
||||||
|
|
||||||
|
|
||||||
# Defined once per test run
|
# Defined once per test run
|
||||||
username = "user_" + randomString()
|
username = "user_" + randomString()
|
||||||
projectName = "project_" + randomString()
|
projectName = "project_" + randomString()
|
||||||
|
|
||||||
|
# The base URL of the API
|
||||||
|
base_url = "http://localhost:8080"
|
||||||
|
|
||||||
|
# Endpoint to test
|
||||||
|
registerPath = base_url + "/api/register"
|
||||||
|
loginPath = base_url + "/api/login"
|
||||||
|
addProjectPath = base_url + "/api/project"
|
||||||
|
submitReportPath = base_url + "/api/submitWeeklyReport"
|
||||||
|
getWeeklyReportPath = base_url + "/api/getWeeklyReport"
|
||||||
|
getProjectPath = base_url + "/api/project"
|
||||||
|
signReportPath = base_url + "/api/signReport"
|
||||||
|
addUserToProjectPath = base_url + "/api/addUserToProject"
|
||||||
|
promoteToAdminPath = base_url + "/api/promoteToAdmin"
|
||||||
|
getUserProjectsPath = base_url + "/api/getUserProjects"
|
||||||
|
getWeeklyReportsUserPath = base_url + "/api/getWeeklyReportsUser"
|
||||||
|
checkIfProjectManagerPath = base_url + "/api/checkIfProjectManager"
|
||||||
|
ProjectRoleChangePath = base_url + "/api/ProjectRoleChange"
|
||||||
|
getUsersProjectPath = base_url + "/api/getUsersProject"
|
||||||
|
getUnsignedReportsPath = base_url + "/api/getUnsignedReports"
|
||||||
|
getChangeUserNamePath = base_url + "/api/changeUserName"
|
||||||
|
getUpdateWeeklyReportPath = base_url + "/api/updateWeeklyReport"
|
||||||
|
removeProjectPath = base_url + "/api/removeProject"
|
||||||
|
|
||||||
#ta bort auth i handlern för att få testet att gå igenom
|
#ta bort auth i handlern för att få testet att gå igenom
|
||||||
def test_ProjectRoleChange():
|
def test_ProjectRoleChange():
|
||||||
|
@ -17,7 +53,9 @@ def test_ProjectRoleChange():
|
||||||
localProjectName = randomString()
|
localProjectName = randomString()
|
||||||
register(localUsername, "username_password")
|
register(localUsername, "username_password")
|
||||||
|
|
||||||
token = login(localUsername, "username_password").json()["token"]
|
token = login(localUsername, "username_password").json()[
|
||||||
|
"token"
|
||||||
|
]
|
||||||
|
|
||||||
# Just checking since this test is built somewhat differently than the others
|
# Just checking since this test is built somewhat differently than the others
|
||||||
assert token != None, "Login failed"
|
assert token != None, "Login failed"
|
||||||
|
@ -45,14 +83,13 @@ def test_ProjectRoleChange():
|
||||||
|
|
||||||
|
|
||||||
def test_get_user_projects():
|
def test_get_user_projects():
|
||||||
username = "user2"
|
|
||||||
password = "123"
|
|
||||||
|
|
||||||
dprint("Testing get user projects")
|
dprint("Testing get user projects")
|
||||||
loginResponse = login(username, password)
|
loginResponse = login("user2", "123")
|
||||||
# Check if the user is added to the project
|
# Check if the user is added to the project
|
||||||
response = requests.get(
|
response = requests.get(
|
||||||
getUserProjectsPath + "/" + username,
|
getUserProjectsPath,
|
||||||
|
json={"username": "user2"},
|
||||||
headers={"Authorization": "Bearer " + loginResponse.json()["token"]},
|
headers={"Authorization": "Bearer " + loginResponse.json()["token"]},
|
||||||
)
|
)
|
||||||
dprint(response.text)
|
dprint(response.text)
|
||||||
|
@ -61,6 +98,26 @@ def test_get_user_projects():
|
||||||
gprint("test_get_user_projects successful")
|
gprint("test_get_user_projects successful")
|
||||||
|
|
||||||
|
|
||||||
|
# Posts the username and password to the register endpoint
|
||||||
|
def register(username: string, password: string):
|
||||||
|
dprint("Registering with username: ", username, " and password: ", password)
|
||||||
|
response = requests.post(
|
||||||
|
registerPath, json={"username": username, "password": password}
|
||||||
|
)
|
||||||
|
dprint(response.text)
|
||||||
|
return response
|
||||||
|
|
||||||
|
|
||||||
|
# Posts the username and password to the login endpoint
|
||||||
|
def login(username: string, password: string):
|
||||||
|
dprint("Logging in with username: ", username, " and password: ", password)
|
||||||
|
response = requests.post(
|
||||||
|
loginPath, json={"username": username, "password": password}
|
||||||
|
)
|
||||||
|
dprint(response.text)
|
||||||
|
return response
|
||||||
|
|
||||||
|
|
||||||
# Test function to login
|
# Test function to login
|
||||||
def test_login():
|
def test_login():
|
||||||
response = login(username, "always_same")
|
response = login(username, "always_same")
|
||||||
|
@ -76,7 +133,6 @@ def test_create_user():
|
||||||
assert response.status_code == 200, "Registration failed"
|
assert response.status_code == 200, "Registration failed"
|
||||||
gprint("test_create_user successful")
|
gprint("test_create_user successful")
|
||||||
|
|
||||||
|
|
||||||
# Test function to add a project
|
# Test function to add a project
|
||||||
def test_add_project():
|
def test_add_project():
|
||||||
loginResponse = login(username, "always_same")
|
loginResponse = login(username, "always_same")
|
||||||
|
@ -90,7 +146,6 @@ def test_add_project():
|
||||||
assert response.status_code == 200, "Add project failed"
|
assert response.status_code == 200, "Add project failed"
|
||||||
gprint("test_add_project successful")
|
gprint("test_add_project successful")
|
||||||
|
|
||||||
|
|
||||||
# Test function to submit a report
|
# Test function to submit a report
|
||||||
def test_submit_report():
|
def test_submit_report():
|
||||||
token = login(username, "always_same").json()["token"]
|
token = login(username, "always_same").json()["token"]
|
||||||
|
@ -112,7 +167,6 @@ def test_submit_report():
|
||||||
assert response.status_code == 200, "Submit report failed"
|
assert response.status_code == 200, "Submit report failed"
|
||||||
gprint("test_submit_report successful")
|
gprint("test_submit_report successful")
|
||||||
|
|
||||||
|
|
||||||
# Test function to get a weekly report
|
# Test function to get a weekly report
|
||||||
def test_get_weekly_report():
|
def test_get_weekly_report():
|
||||||
token = login(username, "always_same").json()["token"]
|
token = login(username, "always_same").json()["token"]
|
||||||
|
@ -140,58 +194,91 @@ def test_get_project():
|
||||||
|
|
||||||
# Test function to add a user to a project
|
# Test function to add a user to a project
|
||||||
def test_add_user_to_project():
|
def test_add_user_to_project():
|
||||||
# User to create
|
# Log in as a site admin
|
||||||
pm_user = "user" + randomString()
|
admin_username = randomString()
|
||||||
pm_passwd = "password"
|
admin_password = "admin_password"
|
||||||
|
dprint(
|
||||||
|
"Registering with username: ", admin_username, " and password: ", admin_password
|
||||||
|
)
|
||||||
|
response = requests.post(
|
||||||
|
registerPath, json={"username": admin_username, "password": admin_password}
|
||||||
|
)
|
||||||
|
dprint(response.text)
|
||||||
|
|
||||||
# User to add
|
admin_token = login(admin_username, admin_password).json()["token"]
|
||||||
member_user = "member" + randomString()
|
response = requests.post(
|
||||||
member_passwd = "password"
|
promoteToAdminPath,
|
||||||
|
json={"username": admin_username},
|
||||||
|
headers={"Authorization": "Bearer " + admin_token},
|
||||||
|
)
|
||||||
|
dprint(response.text)
|
||||||
|
assert response.status_code == 200, "Promote to site admin failed"
|
||||||
|
dprint("Admin promoted to site admin successfully")
|
||||||
|
|
||||||
# Name of the project to be created
|
# Create a new user to add to the project
|
||||||
project_name = "project" + randomString()
|
new_user = randomString()
|
||||||
|
register(new_user, "new_user_password")
|
||||||
|
|
||||||
pm_token = register_and_login(pm_user, pm_passwd)
|
# Add the new user to the project as a member
|
||||||
register(member_user, member_passwd)
|
response = requests.put(
|
||||||
|
addUserToProjectPath,
|
||||||
|
json={"projectName": projectName, "username": new_user, "role": "member"},
|
||||||
|
headers={"Authorization": "Bearer " + admin_token},
|
||||||
|
)
|
||||||
|
|
||||||
response = create_project(pm_token, project_name)
|
dprint(response.text)
|
||||||
assert response.status_code == 200, "Create project failed"
|
|
||||||
|
|
||||||
# Promote the user to project manager
|
|
||||||
response = addToProject(pm_token, member_user, project_name)
|
|
||||||
assert response.status_code == 200, "Add user to project failed"
|
assert response.status_code == 200, "Add user to project failed"
|
||||||
|
gprint("test_add_user_to_project successful")
|
||||||
|
|
||||||
# Test function to sign a report
|
# Test function to sign a report
|
||||||
def test_sign_report():
|
def test_sign_report():
|
||||||
# Pm user
|
# Create a project manager user
|
||||||
pm_username = "pm" + randomString()
|
project_manager = randomString()
|
||||||
pm_password = "admin_password2"
|
register(project_manager, "project_manager_password")
|
||||||
|
|
||||||
# User to add
|
# Register an admin
|
||||||
member_user = "member" + randomString()
|
admin_username = randomString()
|
||||||
member_passwd = "password"
|
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)
|
||||||
|
|
||||||
# Name of the project to be created
|
# Log in as the admin
|
||||||
project_name = "project" + randomString()
|
admin_token = login(admin_username, admin_password).json()["token"]
|
||||||
|
response = requests.post(
|
||||||
|
promoteToAdminPath,
|
||||||
|
json={"username": admin_username},
|
||||||
|
headers={"Authorization": "Bearer " + admin_token},
|
||||||
|
)
|
||||||
|
|
||||||
# Register and get the tokens for both users
|
response = requests.put(
|
||||||
pm_token = register_and_login(pm_username, pm_password)
|
addUserToProjectPath,
|
||||||
member_token = register_and_login(member_user, member_passwd)
|
json={
|
||||||
|
"projectName": projectName,
|
||||||
|
"username": project_manager,
|
||||||
|
"role": "project_manager",
|
||||||
|
},
|
||||||
|
headers={"Authorization": "Bearer " + admin_token},
|
||||||
|
)
|
||||||
|
assert response.status_code == 200, "Add project manager to project failed"
|
||||||
|
dprint("Project manager added to project successfully")
|
||||||
|
|
||||||
# Create the project
|
# Log in as the project manager
|
||||||
response = create_project(pm_token, project_name)
|
project_manager_token = login(project_manager, "project_manager_password").json()[
|
||||||
assert response.status_code == 200, "Create project failed"
|
"token"
|
||||||
|
]
|
||||||
# Add the user to the project
|
|
||||||
response = addToProject(pm_token, member_user, project_name)
|
|
||||||
|
|
||||||
# Submit a report for the project
|
# Submit a report for the project
|
||||||
response = submitReport(
|
token = login(username, "always_same").json()["token"]
|
||||||
member_token,
|
response = requests.post(
|
||||||
{
|
submitReportPath,
|
||||||
"projectName": project_name,
|
json={
|
||||||
"week": 1,
|
"projectName": projectName,
|
||||||
|
"week": 2,
|
||||||
"developmentTime": 10,
|
"developmentTime": 10,
|
||||||
"meetingTime": 5,
|
"meetingTime": 5,
|
||||||
"adminTime": 5,
|
"adminTime": 5,
|
||||||
|
@ -199,99 +286,46 @@ def test_sign_report():
|
||||||
"studyTime": 10,
|
"studyTime": 10,
|
||||||
"testingTime": 10,
|
"testingTime": 10,
|
||||||
},
|
},
|
||||||
|
headers={"Authorization": "Bearer " + token},
|
||||||
)
|
)
|
||||||
assert response.status_code == 200, "Submit report failed"
|
assert response.status_code == 200, "Submit report failed"
|
||||||
|
dprint("Submit report successful")
|
||||||
|
|
||||||
# Retrieve the report ID
|
# Retrieve the report ID
|
||||||
report_id = getReport(member_token, member_user, project_name)["reportId"]
|
response = requests.get(
|
||||||
|
getWeeklyReportPath,
|
||||||
|
headers={"Authorization": "Bearer " + token},
|
||||||
|
params={"username": username, "projectName": projectName, "week": 1},
|
||||||
|
)
|
||||||
|
dprint(response.text)
|
||||||
|
report_id = response.json()["reportId"]
|
||||||
|
|
||||||
# Sign the report as the project manager
|
# Sign the report as the project manager
|
||||||
response = signReport(pm_token, report_id)
|
response = requests.put(
|
||||||
|
signReportPath + "/" + str(report_id),
|
||||||
|
headers={"Authorization": "Bearer " + project_manager_token},
|
||||||
|
)
|
||||||
assert response.status_code == 200, "Sign report failed"
|
assert response.status_code == 200, "Sign report failed"
|
||||||
dprint("Sign report successful")
|
dprint("Sign report successful")
|
||||||
|
|
||||||
# Retrieve the report ID again for confirmation
|
# Retrieve the report ID again for confirmation
|
||||||
report_id = getReport(member_token, member_user, project_name)["reportId"]
|
response = requests.get(
|
||||||
assert report_id != None, "Get report failed"
|
getWeeklyReportPath,
|
||||||
|
headers={"Authorization": "Bearer " + token},
|
||||||
|
params={"username": username, "projectName": projectName, "week": 1},
|
||||||
|
)
|
||||||
|
dprint(response.text)
|
||||||
gprint("test_sign_report successful")
|
gprint("test_sign_report successful")
|
||||||
|
|
||||||
# Test function to unsign a report
|
|
||||||
def test_unsign_report():
|
|
||||||
# Pm user
|
|
||||||
pm_username = "pm" + randomString()
|
|
||||||
pm_password = "admin_password2"
|
|
||||||
|
|
||||||
# User to add
|
|
||||||
member_user = "member" + randomString()
|
|
||||||
member_passwd = "password"
|
|
||||||
|
|
||||||
# Name of the project to be created
|
|
||||||
project_name = "project" + randomString()
|
|
||||||
|
|
||||||
# Register and get the tokens for both users
|
|
||||||
pm_token = register_and_login(pm_username, pm_password)
|
|
||||||
member_token = register_and_login(member_user, member_passwd)
|
|
||||||
|
|
||||||
# Create the project
|
|
||||||
response = create_project(pm_token, project_name)
|
|
||||||
assert response.status_code == 200, "Create project failed"
|
|
||||||
|
|
||||||
# Add the user to the project
|
|
||||||
response = addToProject(pm_token, member_user, project_name)
|
|
||||||
|
|
||||||
# Submit a report for the project
|
|
||||||
response = submitReport(
|
|
||||||
member_token,
|
|
||||||
{
|
|
||||||
"projectName": project_name,
|
|
||||||
"week": 1,
|
|
||||||
"developmentTime": 10,
|
|
||||||
"meetingTime": 5,
|
|
||||||
"adminTime": 5,
|
|
||||||
"ownWorkTime": 10,
|
|
||||||
"studyTime": 10,
|
|
||||||
"testingTime": 10,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
assert response.status_code == 200, "Submit report failed"
|
|
||||||
|
|
||||||
# Retrieve the report ID
|
|
||||||
report_id = getReport(member_token, member_user, project_name)["reportId"]
|
|
||||||
|
|
||||||
# Sign the report as the project manager
|
|
||||||
response = signReport(pm_token, report_id)
|
|
||||||
assert response.status_code == 200, "Sign report failed"
|
|
||||||
dprint("Sign report successful")
|
|
||||||
|
|
||||||
# Retrieve the report ID again for confirmation
|
|
||||||
report = getReport(member_token, member_user, project_name)
|
|
||||||
dprint(report)
|
|
||||||
report_id = report["reportId"]
|
|
||||||
assert report_id != None, "Get report failed"
|
|
||||||
|
|
||||||
# Unsign the report as the project manager
|
|
||||||
response = unsignReport(pm_token, report_id)
|
|
||||||
assert response.status_code == 200, "Unsign report failed"
|
|
||||||
dprint("Unsign report successful")
|
|
||||||
|
|
||||||
# Retrieve the report ID again for confirmation
|
|
||||||
report = getReport(member_token, member_user, project_name)
|
|
||||||
assert report_id != None, "Get report failed"
|
|
||||||
dprint(report)
|
|
||||||
assert report["signedBy"] == None, "Report was not unsigned"
|
|
||||||
gprint("test_unsign_report successful")
|
|
||||||
|
|
||||||
|
|
||||||
# Test function to get weekly reports for a user in a project
|
# Test function to get weekly reports for a user in a project
|
||||||
def test_get_all_weekly_reports():
|
def test_get_weekly_reports_user():
|
||||||
# Log in as the user
|
# Log in as the user
|
||||||
token = login(username, "always_same").json()["token"]
|
token = login(username, "always_same").json()["token"]
|
||||||
|
|
||||||
# Get weekly reports for the user in the project
|
# Get weekly reports for the user in the project
|
||||||
response = requests.get(
|
response = requests.get(
|
||||||
getAllWeeklyReportsPath + "/" + projectName,
|
getWeeklyReportsUserPath + "/" + projectName,
|
||||||
headers={"Authorization": "Bearer " + token},
|
headers={"Authorization": "Bearer " + token},
|
||||||
params={"targetUser": username},
|
|
||||||
)
|
)
|
||||||
|
|
||||||
dprint(response.text)
|
dprint(response.text)
|
||||||
|
@ -299,6 +333,7 @@ def test_get_all_weekly_reports():
|
||||||
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
|
||||||
|
@ -314,7 +349,6 @@ def test_check_if_project_manager():
|
||||||
assert response.status_code == 200, "Check if project manager failed"
|
assert response.status_code == 200, "Check if project manager failed"
|
||||||
gprint("test_check_if_project_manager successful")
|
gprint("test_check_if_project_manager successful")
|
||||||
|
|
||||||
|
|
||||||
def test_ensure_manager_of_created_project():
|
def test_ensure_manager_of_created_project():
|
||||||
# Create a new user to add to the project
|
# Create a new user to add to the project
|
||||||
newUser = "karen_" + randomString()
|
newUser = "karen_" + randomString()
|
||||||
|
@ -338,7 +372,6 @@ def test_ensure_manager_of_created_project():
|
||||||
assert response.json()["isProjectManager"] == True, "User is not project manager"
|
assert response.json()["isProjectManager"] == True, "User is not project manager"
|
||||||
gprint("test_ensure_admin_of_created_project successful")
|
gprint("test_ensure_admin_of_created_project successful")
|
||||||
|
|
||||||
|
|
||||||
def test_change_user_name():
|
def test_change_user_name():
|
||||||
# Register a new user
|
# Register a new user
|
||||||
new_user = randomString()
|
new_user = randomString()
|
||||||
|
@ -376,7 +409,6 @@ def test_change_user_name():
|
||||||
assert response.status_code == 200, "Change user name failed"
|
assert response.status_code == 200, "Change user name failed"
|
||||||
gprint("test_change_user_name successful")
|
gprint("test_change_user_name successful")
|
||||||
|
|
||||||
|
|
||||||
def test_list_all_users_project():
|
def test_list_all_users_project():
|
||||||
# Log in as a user who is a member of the project
|
# Log in as a user who is a member of the project
|
||||||
admin_username = randomString()
|
admin_username = randomString()
|
||||||
|
@ -405,7 +437,6 @@ def test_list_all_users_project():
|
||||||
assert response.status_code == 200, "List all users project failed"
|
assert response.status_code == 200, "List all users project failed"
|
||||||
gprint("test_list_all_users_project sucessful")
|
gprint("test_list_all_users_project sucessful")
|
||||||
|
|
||||||
|
|
||||||
def test_update_weekly_report():
|
def test_update_weekly_report():
|
||||||
# Log in as the user
|
# Log in as the user
|
||||||
token = login(username, "always_same").json()["token"]
|
token = login(username, "always_same").json()["token"]
|
||||||
|
@ -471,7 +502,6 @@ def test_remove_project():
|
||||||
assert response.status_code == 200, "Remove project failed"
|
assert response.status_code == 200, "Remove project failed"
|
||||||
gprint("test_remove_project successful")
|
gprint("test_remove_project successful")
|
||||||
|
|
||||||
|
|
||||||
def test_get_unsigned_reports():
|
def test_get_unsigned_reports():
|
||||||
# Log in as the user
|
# Log in as the user
|
||||||
token = login("user2", "123").json()["token"]
|
token = login("user2", "123").json()["token"]
|
||||||
|
@ -485,266 +515,7 @@ def test_get_unsigned_reports():
|
||||||
gprint("test_get_unsigned_reports successful")
|
gprint("test_get_unsigned_reports successful")
|
||||||
|
|
||||||
|
|
||||||
def test_get_other_users_report_as_pm():
|
|
||||||
# Create user
|
|
||||||
user = randomString()
|
|
||||||
register(user, "password")
|
|
||||||
|
|
||||||
# Create project
|
|
||||||
project = randomString()
|
|
||||||
pm_token = login(user, "password").json()["token"]
|
|
||||||
response = requests.post(
|
|
||||||
addProjectPath,
|
|
||||||
json={"name": project, "description": "This is a project"},
|
|
||||||
headers={"Authorization": "Bearer " + pm_token},
|
|
||||||
)
|
|
||||||
assert response.status_code == 200, "Add project failed"
|
|
||||||
|
|
||||||
# Create other user
|
|
||||||
other_user = randomString()
|
|
||||||
register(other_user, "password")
|
|
||||||
user_token = login(other_user, "password").json()["token"]
|
|
||||||
|
|
||||||
# Add other user to project
|
|
||||||
response = requests.put(
|
|
||||||
addUserToProjectPath + "/" + project,
|
|
||||||
headers={"Authorization": "Bearer " + pm_token}, # note pm_token
|
|
||||||
params={"userName": other_user},
|
|
||||||
)
|
|
||||||
assert response.status_code == 200, "Add user to project failed"
|
|
||||||
|
|
||||||
# Submit report as other user
|
|
||||||
response = requests.post(
|
|
||||||
submitReportPath,
|
|
||||||
json={
|
|
||||||
"projectName": project,
|
|
||||||
"week": 1,
|
|
||||||
"developmentTime": 10,
|
|
||||||
"meetingTime": 5,
|
|
||||||
"adminTime": 5,
|
|
||||||
"ownWorkTime": 10,
|
|
||||||
"studyTime": 10,
|
|
||||||
"testingTime": 10,
|
|
||||||
},
|
|
||||||
headers={"Authorization": "Bearer " + user_token},
|
|
||||||
)
|
|
||||||
assert response.status_code == 200, "Submit report failed"
|
|
||||||
|
|
||||||
# Get report as project manager
|
|
||||||
response = requests.get(
|
|
||||||
getWeeklyReportPath,
|
|
||||||
headers={"Authorization": "Bearer " + pm_token},
|
|
||||||
params={"targetUser": other_user, "projectName": project, "week": 1},
|
|
||||||
)
|
|
||||||
assert response.status_code == 200, "Get weekly report failed"
|
|
||||||
|
|
||||||
|
|
||||||
def test_promote_to_manager():
|
|
||||||
# User to create
|
|
||||||
pm_user = "user" + randomString()
|
|
||||||
pm_passwd = "password"
|
|
||||||
|
|
||||||
# User to promote
|
|
||||||
member_user = "member" + randomString()
|
|
||||||
member_passwd = "password"
|
|
||||||
|
|
||||||
# Name of the project to be created
|
|
||||||
project_name = "project" + randomString()
|
|
||||||
|
|
||||||
pm_token = register_and_login(pm_user, pm_passwd)
|
|
||||||
member_token = register_and_login(member_user, member_passwd)
|
|
||||||
|
|
||||||
response = create_project(pm_token, project_name)
|
|
||||||
assert response.status_code == 200, "Create project failed"
|
|
||||||
|
|
||||||
# Promote the user to project manager
|
|
||||||
response = promoteToManager(pm_token, member_user, project_name)
|
|
||||||
assert response.status_code == 200, "Promote to manager failed"
|
|
||||||
|
|
||||||
def test_delete_report():
|
|
||||||
# Create admin
|
|
||||||
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"
|
|
||||||
|
|
||||||
# Create a new report
|
|
||||||
new_report = {
|
|
||||||
"projectName": new_project,
|
|
||||||
"week": 1,
|
|
||||||
"developmentTime": 10,
|
|
||||||
"meetingTime": 5,
|
|
||||||
"adminTime": 5,
|
|
||||||
"ownWorkTime": 10,
|
|
||||||
"studyTime": 10,
|
|
||||||
"testingTime": 10,
|
|
||||||
}
|
|
||||||
response = submitReport(admin_token, new_report);
|
|
||||||
assert response.status_code == 200, "Submit report failed"
|
|
||||||
|
|
||||||
# Get the report ID
|
|
||||||
report_id = getReport(admin_token, admin_username, new_project)["reportId"]
|
|
||||||
assert report_id != None, "Get report failed"
|
|
||||||
|
|
||||||
# Delete the report
|
|
||||||
response = requests.delete(
|
|
||||||
deleteReportPath + "/" + str(report_id),
|
|
||||||
headers={"Authorization": "Bearer " + admin_token},
|
|
||||||
)
|
|
||||||
assert response.status_code == 200, "Delete report failed"
|
|
||||||
|
|
||||||
# Check if the report was deleted
|
|
||||||
response = requests.get(
|
|
||||||
getWeeklyReportPath,
|
|
||||||
headers={"Authorization": "Bearer " + admin_token},
|
|
||||||
params={"username": admin_username, "projectName": new_project, "week": 1},
|
|
||||||
)
|
|
||||||
assert response.status_code == 500, "Report was not deleted"
|
|
||||||
|
|
||||||
gprint("test_delete_report successful")
|
|
||||||
|
|
||||||
def test_get_statistics():
|
|
||||||
# Create admin
|
|
||||||
admin_username = randomString()
|
|
||||||
admin_password = randomString()
|
|
||||||
|
|
||||||
project_name = "project" + randomString()
|
|
||||||
|
|
||||||
token = register_and_login(admin_username, admin_password)
|
|
||||||
|
|
||||||
response = create_project(token, project_name)
|
|
||||||
assert response.status_code == 200, "Create project failed"
|
|
||||||
|
|
||||||
response = submitReport(token, {
|
|
||||||
"projectName": project_name,
|
|
||||||
"week": 1,
|
|
||||||
"developmentTime": 10,
|
|
||||||
"meetingTime": 5,
|
|
||||||
"adminTime": 5,
|
|
||||||
"ownWorkTime": 10,
|
|
||||||
"studyTime": 10,
|
|
||||||
"testingTime": 10,
|
|
||||||
})
|
|
||||||
|
|
||||||
response = submitReport(token, {
|
|
||||||
"projectName": project_name,
|
|
||||||
"week": 2,
|
|
||||||
"developmentTime": 10,
|
|
||||||
"meetingTime": 5,
|
|
||||||
"adminTime": 5,
|
|
||||||
"ownWorkTime": 10,
|
|
||||||
"studyTime": 10,
|
|
||||||
"testingTime": 10,
|
|
||||||
})
|
|
||||||
|
|
||||||
assert response.status_code == 200, "Submit report failed"
|
|
||||||
|
|
||||||
stats = getStatistics(token, project_name)
|
|
||||||
|
|
||||||
assert stats["totalDevelopmentTime"] == 20, "Total development time is not correct"
|
|
||||||
gprint("test_get_statistics successful")
|
|
||||||
|
|
||||||
def test_project_name_change():
|
|
||||||
# Create admin
|
|
||||||
admin_username = randomString()
|
|
||||||
admin_password = randomString()
|
|
||||||
|
|
||||||
project_name = "project" + randomString()
|
|
||||||
|
|
||||||
token = register_and_login(admin_username, admin_password)
|
|
||||||
|
|
||||||
# Promote to admin
|
|
||||||
response = requests.post(
|
|
||||||
promoteToAdminPath,
|
|
||||||
json={"username": admin_username},
|
|
||||||
headers={"Authorization": "Bearer " + token},
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
response = create_project(token, project_name)
|
|
||||||
assert response.status_code == 200, "Create project failed"
|
|
||||||
|
|
||||||
response = requests.get(
|
|
||||||
getUserProjectsPath + "/" + admin_username,
|
|
||||||
headers={"Authorization": "Bearer " + token},
|
|
||||||
)
|
|
||||||
|
|
||||||
dprint(response.json())
|
|
||||||
|
|
||||||
new_project_name = "new project name " + randomString()
|
|
||||||
dprint("Changing project name from ", project_name, " to ", new_project_name)
|
|
||||||
response = changeProjectName(token, project_name, new_project_name)
|
|
||||||
|
|
||||||
response = requests.get(
|
|
||||||
getUserProjectsPath + "/" + admin_username,
|
|
||||||
headers={"Authorization": "Bearer " + token},
|
|
||||||
)
|
|
||||||
|
|
||||||
dprint(response.json())
|
|
||||||
|
|
||||||
if (response.json()[0]["name"] != new_project_name):
|
|
||||||
assert False, "Project name change failed"
|
|
||||||
|
|
||||||
|
|
||||||
assert response.status_code == 200, "Project name change failed"
|
|
||||||
gprint("test_projectNameChange successful")
|
|
||||||
|
|
||||||
def test_change_user_password():
|
|
||||||
# Create admin
|
|
||||||
admin_username = randomString()
|
|
||||||
admin_password = randomString()
|
|
||||||
|
|
||||||
user = randomString()
|
|
||||||
password = randomString()
|
|
||||||
|
|
||||||
token = register_and_login(admin_username, admin_password)
|
|
||||||
|
|
||||||
# Promote to admin
|
|
||||||
response = requests.post(
|
|
||||||
promoteToAdminPath,
|
|
||||||
json={"username": admin_username},
|
|
||||||
headers={"Authorization": "Bearer " + token},
|
|
||||||
)
|
|
||||||
|
|
||||||
_ = register_and_login(user, password)
|
|
||||||
|
|
||||||
response = changeUserPassword(token, user, "new_password")
|
|
||||||
assert response.status_code == 200, "Change user password failed"
|
|
||||||
|
|
||||||
response = login(user, "new_password")
|
|
||||||
assert response.status_code == 200, "Login failed with new password"
|
|
||||||
|
|
||||||
gprint("test_change_user_password successful")
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
test_change_user_password()
|
|
||||||
test_project_name_change();
|
|
||||||
test_delete_report()
|
|
||||||
test_unsign_report()
|
|
||||||
test_promote_to_manager()
|
|
||||||
test_remove_project()
|
test_remove_project()
|
||||||
test_get_user_projects()
|
test_get_user_projects()
|
||||||
test_create_user()
|
test_create_user()
|
||||||
|
@ -755,7 +526,7 @@ if __name__ == "__main__":
|
||||||
test_get_project()
|
test_get_project()
|
||||||
test_sign_report()
|
test_sign_report()
|
||||||
test_add_user_to_project()
|
test_add_user_to_project()
|
||||||
test_get_all_weekly_reports()
|
test_get_weekly_reports_user()
|
||||||
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()
|
||||||
|
@ -763,5 +534,4 @@ if __name__ == "__main__":
|
||||||
test_list_all_users_project()
|
test_list_all_users_project()
|
||||||
test_change_user_name()
|
test_change_user_name()
|
||||||
test_update_weekly_report()
|
test_update_weekly_report()
|
||||||
test_get_other_users_report_as_pm()
|
|
||||||
test_get_statistics()
|
|
|
@ -1,191 +0,0 @@
|
||||||
import requests
|
|
||||||
import string
|
|
||||||
import random
|
|
||||||
import json
|
|
||||||
|
|
||||||
# Helper function for the TTime API testing suite
|
|
||||||
|
|
||||||
# For style guide, see:
|
|
||||||
# https://peps.python.org/pep-0008/#function-and-variable-names
|
|
||||||
# https://google.github.io/styleguide/pyguide.html#316-naming
|
|
||||||
|
|
||||||
##################
|
|
||||||
## Static Paths ##
|
|
||||||
##################
|
|
||||||
|
|
||||||
base_url = "http://localhost:8080"
|
|
||||||
|
|
||||||
registerPath = base_url + "/api/register"
|
|
||||||
loginPath = base_url + "/api/login"
|
|
||||||
addProjectPath = base_url + "/api/project"
|
|
||||||
submitReportPath = base_url + "/api/submitWeeklyReport"
|
|
||||||
getWeeklyReportPath = base_url + "/api/getWeeklyReport"
|
|
||||||
getProjectPath = base_url + "/api/project"
|
|
||||||
signReportPath = base_url + "/api/signReport"
|
|
||||||
addUserToProjectPath = base_url + "/api/addUserToProject"
|
|
||||||
promoteToAdminPath = base_url + "/api/promoteToAdmin"
|
|
||||||
getUserProjectsPath = base_url + "/api/getUserProjects"
|
|
||||||
getAllWeeklyReportsPath = base_url + "/api/getAllWeeklyReports"
|
|
||||||
checkIfProjectManagerPath = base_url + "/api/checkIfProjectManager"
|
|
||||||
ProjectRoleChangePath = base_url + "/api/ProjectRoleChange"
|
|
||||||
getUsersProjectPath = base_url + "/api/getUsersProject"
|
|
||||||
getUnsignedReportsPath = base_url + "/api/getUnsignedReports"
|
|
||||||
getChangeUserNamePath = base_url + "/api/changeUserName"
|
|
||||||
getUpdateWeeklyReportPath = base_url + "/api/updateWeeklyReport"
|
|
||||||
removeProjectPath = base_url + "/api/removeProject"
|
|
||||||
promoteToPmPath = base_url + "/api/promoteToPm"
|
|
||||||
unsignReportPath = base_url + "/api/unsignReport"
|
|
||||||
deleteReportPath = base_url + "/api/deleteReport"
|
|
||||||
getStatisticsPath = base_url + "/api/getStatistics"
|
|
||||||
changeProjectNamePath = base_url + "/api/changeProjectName"
|
|
||||||
changeUserPasswordPath = base_url + "/api/changeUserPassword"
|
|
||||||
|
|
||||||
debug_output = False
|
|
||||||
|
|
||||||
|
|
||||||
def gprint(*args, **kwargs):
|
|
||||||
print("\033[92m", *args, "\033[00m", **kwargs)
|
|
||||||
|
|
||||||
|
|
||||||
def dprint(*args, **kwargs):
|
|
||||||
if debug_output:
|
|
||||||
print(*args, **kwargs)
|
|
||||||
|
|
||||||
|
|
||||||
def randomString(len=10):
|
|
||||||
"""Generate a random string of fixed length"""
|
|
||||||
letters = string.ascii_lowercase
|
|
||||||
return "".join(random.choice(letters) for i in range(len))
|
|
||||||
|
|
||||||
|
|
||||||
############ ############ ############ ############ ############
|
|
||||||
|
|
||||||
|
|
||||||
# Posts the username and password to the register endpoint
|
|
||||||
def register(username: string, password: string):
|
|
||||||
dprint("Registering with username: ", username, " and password: ", password)
|
|
||||||
response = requests.post(
|
|
||||||
registerPath, json={"username": username, "password": password}
|
|
||||||
)
|
|
||||||
dprint(response.text)
|
|
||||||
return response
|
|
||||||
|
|
||||||
|
|
||||||
# Posts the username and password to the login endpoint
|
|
||||||
def login(username: string, password: string):
|
|
||||||
dprint("Logging in with username: ", username, " and password: ", password)
|
|
||||||
response = requests.post(
|
|
||||||
loginPath, json={"username": username, "password": password}
|
|
||||||
)
|
|
||||||
dprint(response.text)
|
|
||||||
return response
|
|
||||||
|
|
||||||
|
|
||||||
# Register a user and return the token
|
|
||||||
def register_and_login(username: string, password: string) -> string:
|
|
||||||
register(username, password)
|
|
||||||
response = login(username, password)
|
|
||||||
return response.json()["token"]
|
|
||||||
|
|
||||||
|
|
||||||
def create_project(
|
|
||||||
token: string, project_name: string, description: string = "Test description"
|
|
||||||
):
|
|
||||||
dprint("Creating project with name: ", project_name)
|
|
||||||
response = requests.post(
|
|
||||||
addProjectPath,
|
|
||||||
headers={"Authorization": "Bearer " + token},
|
|
||||||
json={"name": project_name, "description": description},
|
|
||||||
)
|
|
||||||
dprint(response.text)
|
|
||||||
return response
|
|
||||||
|
|
||||||
|
|
||||||
# Add a user to a project, requires the user withing the token to be a project manager of said project
|
|
||||||
def addToProject(token: string, username: string, project_name: string):
|
|
||||||
dprint("Adding user with username: ", username, " to project: ", project_name)
|
|
||||||
response = requests.put(
|
|
||||||
addUserToProjectPath + "/" + project_name,
|
|
||||||
headers={"Authorization": "Bearer " + token},
|
|
||||||
params={"userName": username},
|
|
||||||
)
|
|
||||||
dprint(response.text)
|
|
||||||
return response
|
|
||||||
|
|
||||||
|
|
||||||
def promoteToManager(token: string, username: string, project_name: string):
|
|
||||||
dprint(
|
|
||||||
"Promoting user with username: ",
|
|
||||||
username,
|
|
||||||
" to project manager of project: ",
|
|
||||||
project_name,
|
|
||||||
)
|
|
||||||
response = requests.put(
|
|
||||||
promoteToPmPath + "/" + project_name,
|
|
||||||
headers={"Authorization": "Bearer " + token},
|
|
||||||
params={"userName": username},
|
|
||||||
)
|
|
||||||
dprint(response.text)
|
|
||||||
return response
|
|
||||||
|
|
||||||
|
|
||||||
def submitReport(token: string, report):
|
|
||||||
dprint("Submitting report: ", report)
|
|
||||||
response = requests.post(
|
|
||||||
submitReportPath,
|
|
||||||
json=report,
|
|
||||||
headers={"Authorization": "Bearer " + token},
|
|
||||||
)
|
|
||||||
return response
|
|
||||||
|
|
||||||
|
|
||||||
def getReport(token: string, username: string, projectName: string):
|
|
||||||
# Retrieve the report ID
|
|
||||||
response = requests.get(
|
|
||||||
getWeeklyReportPath,
|
|
||||||
headers={"Authorization": "Bearer " + token},
|
|
||||||
params={"username": username, "projectName": projectName, "week": 1},
|
|
||||||
)
|
|
||||||
return response.json()
|
|
||||||
|
|
||||||
|
|
||||||
def signReport(project_manager_token: string, report_id: int):
|
|
||||||
return requests.put(
|
|
||||||
signReportPath + "/" + str(report_id),
|
|
||||||
headers={"Authorization": "Bearer " + project_manager_token},
|
|
||||||
)
|
|
||||||
|
|
||||||
def unsignReport(project_manager_token: string, report_id: int):
|
|
||||||
return requests.put(
|
|
||||||
unsignReportPath + "/" + str(report_id),
|
|
||||||
headers={"Authorization": "Bearer " + project_manager_token},
|
|
||||||
)
|
|
||||||
|
|
||||||
def deleteReport(report_id: int):
|
|
||||||
return requests.delete(
|
|
||||||
deleteReportPath + "/" + str(report_id),
|
|
||||||
)
|
|
||||||
|
|
||||||
def getStatistics(token: string, projectName: string):
|
|
||||||
response = requests.get(
|
|
||||||
getStatisticsPath,
|
|
||||||
headers = {"Authorization": "Bearer " + token},
|
|
||||||
params={"projectName": projectName}
|
|
||||||
)
|
|
||||||
return response.json()
|
|
||||||
|
|
||||||
def changeProjectName(token: string, projectName: string, newProjectName: string):
|
|
||||||
response = requests.put(
|
|
||||||
changeProjectNamePath + "/" + projectName,
|
|
||||||
headers = {"Authorization": "Bearer " + token},
|
|
||||||
params={"newProjectName": newProjectName}
|
|
||||||
)
|
|
||||||
return response
|
|
||||||
|
|
||||||
def changeUserPassword(token: string, username: string, newPassword: string):
|
|
||||||
response = requests.put(
|
|
||||||
changeUserPasswordPath + "/" + username,
|
|
||||||
headers = {"Authorization": "Bearer " + token},
|
|
||||||
params={"newPassword": newPassword}
|
|
||||||
)
|
|
||||||
return response
|
|
Loading…
Add table
Reference in a new issue