Compare commits

..

192 commits

Author SHA1 Message Date
al8763be
ddea76e24a fixed AllTimeReportsInProject 2024-03-20 22:47:02 +01:00
al8763be
c4b8bef7f8 fix database 2024-03-20 22:37:15 +01:00
Imbus
1a7bc9c73c Merge branch 'imbs' of git.silversoft.se:Imbus/TTime into imbs 2024-03-20 22:04:42 +01:00
Imbus
b00ce7a0ed Typescript type regeneration 2024-03-20 22:04:36 +01:00
Imbus
ec138163c6 Very large changes related to database and its interface 2024-03-20 22:04:36 +01:00
al8763be
a51d984229 Merge branch 'dev' of https://github.com/imbus64/TTime into dev 2024-03-20 22:03:25 +01:00
al8763be
70cf40feb5 fixed getWeeklyReport 2024-03-20 22:02:34 +01:00
Imbus
0dc7e3bf99 Deleted sqlite3-journal 2024-03-20 21:56:03 +01:00
Imbus
65101384e2 Typescript type regeneration 2024-03-20 21:54:13 +01:00
al8763be
4288b064f3 fixade dålig stämmning igen 2024-03-20 21:53:08 +01:00
Imbus
cdea2dce1c Very large changes related to database and its interface 2024-03-20 21:51:36 +01:00
al8763be
49ca043157 ingen dålig stämmning 2024-03-20 21:51:02 +01:00
al8763be
69d07624ce Merge remote-tracking branch 'origin/dev' into BumBranch 2024-03-20 20:55:53 +01:00
Imbus
22f4fba36a Merge 2024-03-20 20:46:27 +01:00
Johanna
ae77bcf0bb Added comments to TypeScript API, merge 2024-03-20 20:44:11 +01:00
Johanna
9f61a36a68 Added comments to TypeScript API, merge 2024-03-20 20:36:34 +01:00
al8763be
a9e515925e getWeeklyReportsForUser fixed 2024-03-20 20:24:56 +01:00
al8763be
91c9f50491 sampledata now has weeklyReports for user & user2 2024-03-20 20:08:00 +01:00
al8763be
e8c660705f sampledata now has weeklyReports for user & user2 2024-03-20 20:05:04 +01:00
al8763be
be3adb09ae Merge branch 'dev' into BumBranch 2024-03-20 19:39:05 +01:00
al8763be
8fbb4be0ad merge again 2024-03-20 19:38:52 +01:00
al8763be
eff14516b4 merge 2024-03-20 19:37:07 +01:00
Imbus
9bf00e8dce Merge branch 'dev' of github.com:imbus64/TTime into dev 2024-03-20 18:47:47 +01:00
Imbus
3525598df2 Tests for AddProject to ensure creator is Manager 2024-03-20 18:47:37 +01:00
Melker
bef8a6af85 GetTotalTimePerActivity och ett halvt test för den 2024-03-20 18:20:14 +01:00
Imbus
45761dadcb Fix for checkProjectManager endpoint and test_check_if_project_manager 2024-03-20 17:50:51 +01:00
Imbus
0017e9b9ef Fixing error in ts API, fixing AllTimeReportsInProject 2024-03-20 17:31:59 +01:00
Imbus
933191b076 Generate typescript types 2024-03-20 17:20:47 +01:00
Imbus
b405b44460 Merge frontend -> dev 2024-03-20 17:19:09 +01:00
Imbus
cc013632a7 Updated python testing script to pass test_get_weekly_reports_user 2024-03-20 17:14:57 +01:00
Imbus
154c2240c8 Merge branch 'dev' of github.com:imbus64/TTime into dev 2024-03-20 17:10:16 +01:00
Imbus
d19372d66b Frontend API interface fixed (untested) for getWeeklyReportsForUser 2024-03-20 17:10:11 +01:00
borean
1147a216ce typo 2024-03-20 16:55:55 +01:00
Imbus
649a4ba1fd Merge branch 'dev' of github.com:imbus64/TTime into dev 2024-03-20 16:50:09 +01:00
Imbus
49e3542dce Merge branch 'BumBranch' into dev 2024-03-20 16:49:59 +01:00
dDogge
88f232e21b ChangeUserName function in db.go fixed, corresponding test added 2024-03-20 16:46:48 +01:00
Johanna
ef6c3951fd Merge branch 'johanna-testing' into dev 2024-03-20 16:28:10 +01:00
Johanna
67680e2616 Added comments to db_test.go test functions 2024-03-20 16:13:53 +01:00
Imbus
0f4c3ac5ec Reworking endpoint getWeeklyReportsUser 2024-03-20 16:05:50 +01:00
al8763be
af5813681d Jag avskyr denna handlern 2024-03-20 15:17:00 +01:00
al8763be
7d30165465 Merge remote-tracking branch 'origin/dev' into BumBranch 2024-03-20 15:13:05 +01:00
al8763be
771118f85e Update API.ts 2024-03-20 15:13:00 +01:00
Imbus
7799eb09e9 Delete unused handler prototype 2024-03-20 15:11:25 +01:00
Imbus
a84c2c6129 Rename GetWeeklyReportsForProject -> GetWeeklyReportsForUser 2024-03-20 14:54:22 +01:00
Mattias
14b68e4463 Formatting 2024-03-20 14:20:09 +01:00
Mattias
47ce986834 Minor fixes 2024-03-20 14:17:33 +01:00
borean
c6969b9503 updated api path for DeleteProject 2024-03-20 14:16:47 +01:00
Davenludd
0fb06790af Fix useEffect dependency array in ProjectMembers component 2024-03-20 14:09:16 +01:00
Davenludd
89cb3d23b7 Fix missing comma in getWeeklyReportsForProject function signature 2024-03-20 14:07:12 +01:00
Davenludd
adf9500bbf Fix useEffect dependency in AllTimeReportsInProject component 2024-03-20 14:06:54 +01:00
Johanna
00f72df65f Added comments 2024-03-20 14:06:15 +01:00
al8763be
74e1347162 Changed error message 2024-03-20 14:02:34 +01:00
Davenludd
4574af25cb Merge branch 'BumBranch' into gruppDM 2024-03-20 14:02:11 +01:00
al8763be
d41bfcf888 Fix for getWeeklyReportsForUser 2024-03-20 14:00:00 +01:00
Davenludd
29d893362c Docs/Comments to user project menu component 2024-03-20 13:57:02 +01:00
Davenludd
c7c38d4201 Docs/Comments to registration form component 2024-03-20 13:57:02 +01:00
Davenludd
d9068949cd Docs/Comments to new weekly report form component 2024-03-20 13:57:02 +01:00
Davenludd
7e7dbe2cc7 Docs/Comments to header component 2024-03-20 13:57:02 +01:00
Davenludd
cf6c8cd34c Docs/Comments to Footer component 2024-03-20 13:57:02 +01:00
Davenludd
dbeeb609b3 Docs/Comments to GetWeeklyReport component 2024-03-20 13:57:02 +01:00
Davenludd
aa5c4017bb Docs/Comments to DisplayUserProjects component 2024-03-20 13:57:02 +01:00
Davenludd
4212dae5bf Delete CountButton component 2024-03-20 13:57:02 +01:00
Davenludd
bd27b549aa Add JSDoc comments to Button component 2024-03-20 13:57:02 +01:00
Davenludd
af92f4faee Docs/Comments to BasicWindow component 2024-03-20 13:57:02 +01:00
Davenludd
2dffffde50 Docs/coments to background animation component 2024-03-20 13:57:02 +01:00
Davenludd
c0e03d7d0d Doc/Comments to BackButton comp 2024-03-20 13:57:02 +01:00
Davenludd
ff4ea47846 Doc/Comments in AllTimeReportsInProject component 2024-03-20 13:57:02 +01:00
Mattias
d3bb6f49e5 New comp changeRoles 2024-03-20 13:51:49 +01:00
borean
a18ce465de Added a handler for getting all users for a specific project, WIP and untested 2024-03-20 13:03:43 +01:00
borean
3515a86bbb Added ChangeUserName handler, untested and WIP, 2024-03-20 12:50:04 +01:00
Mattias
1b21b2574a Comp for displaying projectmembers and changed path in main 2024-03-20 12:36:30 +01:00
borean
1919b7cb99 Added ChangeUserName in db 2024-03-20 12:11:05 +01:00
borean
bb93cef1d3 New deleteproject handler, WIP 2024-03-20 11:43:47 +01:00
borean
c01dd21b0a uncommented 2024-03-20 11:23:24 +01:00
borean
dce91943b3 Added DeleteProject handler, untested 2024-03-20 11:22:33 +01:00
Davenludd
7ed9398bcb Merge branch 'frontend' into gruppDM 2024-03-20 11:19:02 +01:00
borean
cd74758b2f DeleteProject added in db.go, untested 2024-03-20 11:09:48 +01:00
Peter KW
6c433208ea Merge branch 'frontend' into gruppPP 2024-03-20 10:54:38 +01:00
borean
a468b896db WIP for funcs that need to be implemented 2024-03-20 10:41:16 +01:00
Davenludd
033268f4ef Added info to component 2024-03-20 08:47:11 +01:00
al8763be
7b78767f50 restore 2024-03-20 01:14:30 +01:00
dDogge
b13a8b5432 Merge 2024-03-20 00:35:37 +01:00
Peter KW
4630fb336f Merge branch 'dev' into gruppPP 2024-03-20 00:32:15 +01:00
Peter KW
4e100229f5 Now uses GetAllUsers 2024-03-20 00:16:56 +01:00
Peter KW
b20817ec8a Small fix to props and imports 2024-03-20 00:16:30 +01:00
Peter KW
cc231dbfaa Component for getting all users 2024-03-20 00:14:23 +01:00
Peter KW
8300fb3a6f Added getAllUsers to API 2024-03-20 00:10:24 +01:00
Melker
b7b831d869 Lagt till om Admin=True/false 2024-03-19 23:54:54 +01:00
Davenludd
b5d3ab7cb7 Refactor AllTimeReportsInProject component to use API for fetching weekly reports 2024-03-19 23:44:44 +01:00
Davenludd
863a14c316 Add WeeklyReport type and update getWeeklyReportsForProject method 2024-03-19 23:44:44 +01:00
Samuel Högbom Aronson
cb44954477 -added auth for rolechange, endpoint and test 2024-03-19 23:08:14 +01:00
Mattias
ffaa661628 Minor fix 2024-03-19 22:51:54 +01:00
Mattias
900c1614e9 minor fix 2024-03-19 22:50:57 +01:00
Mattias
cd2a81e7f2 Added unauthorizedpage and path in main 2024-03-19 22:50:40 +01:00
Mattias
cc9678f375 Created component for authorized route 2024-03-19 22:50:23 +01:00
Mattias
fe89ae0970 Added backbutton 2024-03-19 22:13:06 +01:00
Mattias
55a2dc7fac Add PMProjectMenu component and refactor PMProjectPage 2024-03-19 22:03:45 +01:00
Mattias
d355b55634 Add UserProjectMenu component and refactor UserProjectPage 2024-03-19 21:59:37 +01:00
Davenludd
08a0ebb533 Merge branch 'BumBranch' into gruppDM 2024-03-19 21:51:07 +01:00
Mattias
dc6ce4b725 Additional minor fix 2024-03-19 21:31:20 +01:00
Mattias
05b9740c36 Fixed links and changed path in main for pm 2024-03-19 21:29:37 +01:00
Mattias
3fea87a88a New component for displaying userprojects 2024-03-19 21:22:49 +01:00
al8763be
0b6edd359e Bumfix for BumCode on checkIfProjectManager 2024-03-19 20:29:36 +01:00
al8763be
e48bf5d98c Added checkIfProjectManager, hope it works 2024-03-19 20:25:26 +01:00
Imbus
88c53ae4b6 Golds automatic documentation generator 2024-03-19 20:21:38 +01:00
al8763be
382ea38558 Merge branch 'dev' into BumBranch 2024-03-19 20:15:20 +01:00
al8763be
74285dc1cf Before fuck up 2024-03-19 19:45:56 +01:00
dDogge
ce4cf788ae Added handler and corresponding test for the IsProjectManager function 2024-03-19 19:30:01 +01:00
dDogge
5778d6e892 Added isProjectManager function to db.go and corresponding test to db_test.go 2024-03-19 19:14:55 +01:00
dDogge
2b41085865 Added GetWeeklyReportsUser function and handler, also corresponding tests to both GetWeeklyReportsUser and handler added 2024-03-19 19:04:45 +01:00
Peter KW
a72aea1382 Removed unused authority check 2024-03-19 17:12:38 +01:00
Peter KW
cd84a2bb21 Everyone except admin has authority = 2 2024-03-19 17:12:09 +01:00
Peter KW
8dfe9dac96 Removed unused path 2024-03-19 17:09:45 +01:00
Davenludd
4568d68746 Merge branch 'frontend' into gruppDM 2024-03-19 17:02:50 +01:00
Peter KW
4bcbf162f9 Merge branch 'frontend' into gruppPP 2024-03-19 17:00:04 +01:00
Imbus
9c4ce0e733 Merge branch 'dev' into frontend 2024-03-19 16:57:49 +01:00
Imbus
2f3730ca90 New typescript types from tygo 2024-03-19 16:50:16 +01:00
Mattias
5803c7b29b Refactor fetchWeeklyReport function, updated submit button text, week fetched by params 2024-03-19 11:41:50 +01:00
Davenludd
d0d0e311e5 Add project name to UserViewTimeReportsPage and add AllTimeReportsInProject component 2024-03-19 11:10:47 +01:00
Davenludd
03819aff44 Update link to time reports in UserProjectPage 2024-03-19 11:10:02 +01:00
Davenludd
7fd128e1f3 Add AllTimeReportsInProject component 2024-03-19 11:09:52 +01:00
Davenludd
09a7cbced1 Update routes in main.tsx 2024-03-19 11:09:35 +01:00
Davenludd
ee2fb6d543 Minor change UserProjectPage 2024-03-19 09:41:24 +01:00
Davenludd
a30a6a4988 Fix button UserNewTimeReportPage 2024-03-19 09:37:13 +01:00
Mattias
cda2b72d08 projectName is now fetched from params 2024-03-19 09:35:15 +01:00
Davenludd
4f53f9de94 Fix initial value of week state in NewWeeklyReport component for error message 2024-03-19 09:33:20 +01:00
Davenludd
53f468d7c8 Update navigation in EditWeeklyReport component 2024-03-19 09:23:09 +01:00
Davenludd
d775a6e381 Update navigation in NewWeeklyReport component 2024-03-19 09:21:33 +01:00
Davenludd
08532444f0 Cleanup YourProjectsPage 2024-03-19 09:14:30 +01:00
Imbus
5f6977354f Justfile save-release target now saves image with name containing commit hash and date 2024-03-19 04:02:04 +01:00
al8763be
3b8b9bb3f2 Merge branch 'dev' into frontend 2024-03-19 03:44:23 +01:00
al8763be
3df9ddcd4b Merge remote-tracking branch 'origin/gruppPP' into frontend 2024-03-19 03:44:02 +01:00
Imbus
ab313551c9 Merge branch 'frontend' into dev 2024-03-19 03:40:24 +01:00
pavel Hamawand
cbb62438c8 implementing AdminChangeUsername 2024-03-19 03:40:17 +01:00
pavel Hamawand
8b7ad8911b implementing ChangeUser 2024-03-19 03:39:05 +01:00
Imbus
2ce1837223 Merge branch 'frontend' into dev 2024-03-19 03:36:42 +01:00
pavel Hamawand
4df8d3f858 new component 2024-03-19 03:22:54 +01:00
pavel Hamawand
52aecd14d4 minor fix 2024-03-19 03:14:14 +01:00
al8763be
502cd67b4c Merge branch 'dev' into BumBranch 2024-03-19 02:15:33 +01:00
Imbus
4dbbee3249 Checking errors from transactions in go 2024-03-19 02:14:09 +01:00
al8763be
e3fd9f52ca Merge branch 'gruppPP' into BumBranch 2024-03-19 02:12:37 +01:00
al8763be
8081f289b5 fixed NewWeeklyReport 2024-03-19 02:11:47 +01:00
Peter KW
cdbd6ca0ce Small fixes to design 2024-03-19 01:48:01 +01:00
Imbus
7db03e8dbd Merge branch 'imbs' into dev 2024-03-19 01:43:10 +01:00
Imbus
5f88e92415 Merge branch 'imbs' of git.silversoft.se:Imbus/TTime into imbs 2024-03-19 01:40:36 +01:00
Imbus
09014c6659 Better test feedback in python script 2024-03-19 01:38:39 +01:00
Imbus
e498f0ed63 Silencing python testing, optional verbose output 2024-03-19 01:38:39 +01:00
al8763be
77a24421e9 Merge branch 'gruppdm' into BumBranch 2024-03-19 01:24:55 +01:00
al8763be
de234c12f2 Merge branch 'imbs' into BumBranch 2024-03-19 01:24:38 +01:00
Imbus
5bcca0202b Better test feedback in python script 2024-03-19 01:12:14 +01:00
pavel Hamawand
6a84b1c14d fix backbutton 2024-03-19 01:03:38 +01:00
pavel Hamawand
c072aff9da added change username button 2024-03-19 01:02:39 +01:00
al8763be
9434c31013 Merge remote-tracking branch 'origin/dev' into frontend 2024-03-19 00:48:13 +01:00
Davenludd
ba2bb1fc5e Merge branch 'frontend' into gruppDM 2024-03-19 00:43:25 +01:00
al8763be
847427a543 Merge remote-tracking branch 'origin/dev' into frontend 2024-03-19 00:42:32 +01:00
Davenludd
b8c69fabf5 Remove unused variable and update API call in YourProjectsPage.tsx 2024-03-19 00:35:27 +01:00
pavel Hamawand
d2a8399bde minor fix 2024-03-19 00:35:15 +01:00
Peter KW
b3dfbc47a4 Merge branch 'frontend' into gruppPP 2024-03-19 00:33:30 +01:00
Imbus
7e4e35f597 Silencing python testing, optional verbose output 2024-03-19 00:31:15 +01:00
Davenludd
17c30f5dd9 Merge branch 'frontend' into gruppDM 2024-03-19 00:28:12 +01:00
al8763be
d7e14f1886 quick fix for getUserProjects API 2024-03-19 00:27:35 +01:00
al8763be
59c4dab2e2 quick fix for getUserProjects API 2024-03-19 00:26:17 +01:00
Peter KW
d2ff2428cd Some corrections 2024-03-19 00:26:05 +01:00
Peter KW
36524e5cbb Changed so that it makes a modal for each user instead of a link 2024-03-19 00:25:37 +01:00
Davenludd
a2bc13ec22 Merge branch 'frontend' into gruppDM 2024-03-19 00:20:43 +01:00
al8763be
83f8097c2b API getUserProjects Fucked 2024-03-19 00:20:08 +01:00
Peter KW
a0759b099a Modul for viewing user info in admin manage users page 2024-03-19 00:17:29 +01:00
Peter KW
db4f869712 Delete user component 2024-03-19 00:15:42 +01:00
Davenludd
652f74884f Merge branch 'frontend' into gruppDM 2024-03-19 00:15:41 +01:00
al8763be
3e1f899414 Merge branch 'dev' into frontend 2024-03-19 00:14:55 +01:00
Peter KW
e55b380bb4 Should be able to delete users except for self now 2024-03-19 00:14:55 +01:00
Davenludd
2eab030212 Merge branch 'frontend' into gruppDM 2024-03-18 23:37:48 +01:00
Davenludd
ff9eba039f Minor fixes YourProjectsPage 2024-03-18 23:36:59 +01:00
al8763be
2cd2ef9ef5 Merge branch 'frontend' of https://github.com/imbus64/TTime into frontend 2024-03-18 23:36:50 +01:00
Davenludd
cc09eb0ead Remove duplicate import statements 2024-03-18 23:01:30 +01:00
Davenludd
8df3311f5a Remove duplicate code in UserProjectPage 2024-03-18 22:59:16 +01:00
Davenludd
f5a4c3d0e5 Merge branch 'frontend' into gruppDM 2024-03-18 22:05:50 +01:00
Davenludd
55fd42090d Remove username prop from BasicWindow component on all pages 2024-03-18 22:00:58 +01:00
Davenludd
5a4049eaf3 Remove username prop from BasicWindow component 2024-03-18 21:56:37 +01:00
Davenludd
59add3b6b3 Remove username prop from BasicWindow component 2024-03-18 21:55:47 +01:00
Davenludd
6982d21016 Add project name to URL in UserProjectPage 2024-03-18 21:55:15 +01:00
Davenludd
f16dc1722c Update project name in YourProjectsPage.tsx URL 2024-03-18 21:53:16 +01:00
Davenludd
31c5a78dae Refactor routing paths in main.tsx 2024-03-18 21:52:34 +01:00
Davenludd
93addc9870 Fixes in NewWeeklyReport component 2024-03-18 21:40:07 +01:00
Davenludd
847180cf75 Update user navigation route 2024-03-18 21:37:52 +01:00
Davenludd
b9d7e57f2c Update background image in Header component 2024-03-18 21:37:31 +01:00
Davenludd
25713443e2 Remove username prop from BasicWindow component 2024-03-18 21:33:20 +01:00
Davenludd
d64ec708a1 Minor fixes 2024-03-18 19:37:37 +01:00
Davenludd
3e9dc87100 Add NotFoundPage to handle 404 errors 2024-03-18 19:36:28 +01:00
Davenludd
a2ad2913e4 Add NotFoundPage component 2024-03-18 19:34:15 +01:00
Peter KW
83e781c877 Fixed getting the username and removed comment 2024-03-18 18:31:58 +01:00
Davenludd
531e9a0535 Fix links in UserProjectPage component 2024-03-18 17:38:45 +01:00
58 changed files with 1918 additions and 449 deletions

1
.gitignore vendored
View file

@ -9,6 +9,7 @@ bin
database.txt
plantuml.jar
db.sqlite3
db.sqlite3-journal
diagram.puml
backend/*.png
backend/*.jpg

View file

@ -15,7 +15,7 @@ remove-podman-containers:
# Saves the release container to a tarball, pigz is just gzip but multithreaded
save-release: build-container-release
podman save --format=oci-archive ttime-server | pigz -9 > ttime-server.tar.gz
podman save --format=oci-archive ttime-server | pigz -9 > ttime-server_`date -I`_`git rev-parse --short HEAD`.tar.gz
# Loads the release container from a tarball
load-release file:

View file

@ -130,4 +130,12 @@ install-just:
.PHONY: types
types:
tygo generate
tygo generate
.PHONY: install-golds
install-golds:
go install go101.org/golds@latest
.PHONY: golds
golds:
golds -port 6060 -nouses -plainsrc -wdpkgs-listing=promoted ./...

View file

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

View file

@ -3,6 +3,7 @@ package database
import (
"embed"
"errors"
"fmt"
"path/filepath"
"ttime/internal/types"
@ -19,12 +20,14 @@ type Database interface {
PromoteToAdmin(username string) error
GetUserId(username string) (int, error)
AddProject(name string, description string, username string) error
DeleteProject(name string, username string) error
Migrate() error
MigrateSampleData() 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
AddUserToProject(username string, projectname string, role string) error
ChangeUserRole(username string, projectname string, role string) error
ChangeUserName(username string, newname string) error
GetAllUsersProject(projectname string) ([]UserProjectMember, error)
GetAllUsersApplication() ([]string, error)
GetProjectsForUser(username string) ([]types.Project, error)
@ -32,8 +35,11 @@ type Database interface {
GetProject(projectId int) (types.Project, error)
GetUserRole(username string, projectname string) (string, error)
GetWeeklyReport(username string, projectName string, week int) (types.WeeklyReport, error)
GetWeeklyReportsUser(username string, projectname string) ([]types.WeeklyReportList, error)
SignWeeklyReport(reportId int, projectManagerId int) error
IsSiteAdmin(username string) (bool, error)
IsProjectManager(username string, projectname string) (bool, error)
GetTotalTimePerActivity(projectName string) (map[string]int, error)
}
// This struct is a wrapper type that holds the database connection
@ -55,19 +61,27 @@ var sampleData embed.FS
// TODO: Possibly break these out into separate files bundled with the embed package?
const userInsert = "INSERT INTO users (username, password) VALUES (?, ?)"
const projectInsert = "INSERT INTO projects (name, description, owner_user_id) SELECT ?, ?, id FROM users WHERE username = ?"
const projectInsert = "INSERT INTO projects (name, description, owner_user_id) VALUES (?, ?, (SELECT id FROM users WHERE username = ?))"
const promoteToAdmin = "INSERT INTO site_admin (admin_id) SELECT id FROM users WHERE username = ?"
const addWeeklyReport = `WITH UserLookup AS (SELECT id FROM users WHERE username = ?),
ProjectLookup AS (SELECT id FROM projects WHERE name = ?)
INSERT INTO weekly_reports (project_id, user_id, week, development_time, meeting_time, admin_time, own_work_time, study_time, testing_time)
VALUES ((SELECT id FROM ProjectLookup), (SELECT id FROM UserLookup),?, ?, ?, ?, ?, ?, ?);`
const addUserToProject = "INSERT INTO user_roles (user_id, project_id, p_role) VALUES (?, ?, ?)" // WIP
const changeUserRole = "UPDATE user_roles SET p_role = ? WHERE user_id = ? AND project_id = ?"
const addUserToProject = `INSERT OR IGNORE INTO user_roles (user_id, project_id, p_role)
VALUES ((SELECT id FROM users WHERE username = ?),
(SELECT id FROM projects WHERE name = ?), ?)`
const changeUserRole = "UPDATE user_roles SET p_role = ? WHERE user_id = (SELECT id FROM users WHERE username = ?) AND project_id = (SELECT id FROM projects WHERE name = ?)"
const getProjectsForUser = `SELECT p.id, p.name, p.description FROM projects p
JOIN user_roles ur ON p.id = ur.project_id
JOIN users u ON ur.user_id = u.id
WHERE u.username = ?`
const deleteProject = `DELETE FROM projects
WHERE id = ? AND owner_username = ?`
const isProjectManagerQuery = `SELECT COUNT(*) > 0 FROM user_roles
JOIN users ON user_roles.user_id = users.id
JOIN projects ON user_roles.project_id = projects.id
WHERE users.username = ? AND projects.name = ? AND user_roles.p_role = 'project_manager'`
// DbConnect connects to the database
func DbConnect(dbpath string) Database {
@ -125,42 +139,23 @@ func (d *Db) AddWeeklyReport(projectName string, userName string, week int, deve
}
// AddUserToProject adds a user to a project with a specified role.
func (d *Db) AddUserToProject(username string, projectname string, role string) error { // WIP
var userid int
userid, err := d.GetUserId(username)
if err != nil {
panic(err)
}
var projectid int
projectid, err2 := d.GetProjectId(projectname)
if err2 != nil {
panic(err2)
}
_, err3 := d.Exec(addUserToProject, userid, projectid, role)
return err3
func (d *Db) AddUserToProject(username string, projectname string, role string) error {
_, err := d.Exec(addUserToProject, username, projectname, role)
return err
}
// ChangeUserRole changes the role of a user within a project.
func (d *Db) ChangeUserRole(username string, projectname string, role string) error {
// Get the user ID
var userid int
userid, err := d.GetUserId(username)
if err != nil {
panic(err)
}
// Get the project ID
var projectid int
projectid, err2 := d.GetProjectId(projectname)
if err2 != nil {
panic(err2)
}
// Execute the SQL query to change the user's role
_, err3 := d.Exec(changeUserRole, role, userid, projectid)
return err3
_, err := d.Exec(changeUserRole, role, username, projectname)
return err
}
// ChangeUserName changes the username of a user.
func (d *Db) ChangeUserName(username string, newname string) error {
// Execute the SQL query to update the username
_, err := d.Exec("UPDATE users SET username = ? WHERE username = ?", newname, username)
return err
}
// GetUserRole retrieves the role of a user within a project.
@ -202,17 +197,42 @@ func (d *Db) GetProjectId(projectname string) (int, error) {
// Creates a new project in the database, associated with a user
func (d *Db) AddProject(name string, description string, username string) error {
tx := d.MustBegin()
// Insert the project into the database
_, err := tx.Exec(projectInsert, name, description, username)
if err != nil {
tx.Rollback()
if err := tx.Rollback(); err != nil {
return err
}
return err
}
_, err = tx.Exec(changeUserRole, "project_manager", username, name)
// Add creator to project as project manager
_, err = tx.Exec(addUserToProject, username, name, "project_manager")
if err != nil {
tx.Rollback()
if err := tx.Rollback(); err != nil {
return err
}
return err
}
tx.Commit()
if err := tx.Commit(); err != nil {
return err
}
return err
}
func (d *Db) DeleteProject(projectID string, username string) error {
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
}
@ -395,6 +415,43 @@ func (d *Db) Migrate() error {
return nil
}
// GetWeeklyReportsUser retrieves weekly reports for a specific user and project.
func (d *Db) GetWeeklyReportsUser(username string, projectName string) ([]types.WeeklyReportList, error) {
query := `
SELECT
wr.week,
wr.development_time,
wr.meeting_time,
wr.admin_time,
wr.own_work_time,
wr.study_time,
wr.testing_time,
wr.signed_by
FROM
weekly_reports wr
INNER JOIN
users u ON wr.user_id = u.id
INNER JOIN
projects p ON wr.project_id = p.id
WHERE
u.username = ? AND p.name = ?
`
var reports []types.WeeklyReportList
if err := d.Select(&reports, query, username, projectName); err != nil {
return nil, err
}
return reports, nil
}
// IsProjectManager checks if a given username is a project manager for the specified project
func (d *Db) IsProjectManager(username string, projectname string) (bool, error) {
var manager bool
err := d.Get(&manager, isProjectManagerQuery, username, projectname)
return manager, err
}
// MigrateSampleData applies sample data to the database.
func (d *Db) MigrateSampleData() error {
// Insert sample data
@ -433,3 +490,41 @@ func (d *Db) MigrateSampleData() error {
return nil
}
func (d *Db) GetTotalTimePerActivity(projectName string) (map[string]int, error) {
query := `
SELECT development_time, meeting_time, admin_time, own_work_time, study_time, testing_time
FROM weekly_reports
JOIN projects ON weekly_reports.project_id = projects.id
WHERE projects.name = ?
`
rows, err := d.DB.Query(query, projectName)
if err != nil {
return nil, err
}
defer rows.Close()
totalTime := make(map[string]int)
for rows.Next() {
var developmentTime, meetingTime, adminTime, ownWorkTime, studyTime, testingTime int
if err := rows.Scan(&developmentTime, &meetingTime, &adminTime, &ownWorkTime, &studyTime, &testingTime); err != nil {
return nil, err
}
totalTime["development"] += developmentTime
totalTime["meeting"] += meetingTime
totalTime["admin"] += adminTime
totalTime["own_work"] += ownWorkTime
totalTime["study"] += studyTime
totalTime["testing"] += testingTime
}
if err := rows.Err(); err != nil {
return nil, err
}
return totalTime, nil
}

View file

@ -1,12 +1,12 @@
package database
import (
"fmt"
"testing"
)
// Tests are not guaranteed to be sequential
// setupState initializes a database instance with necessary setup for testing
func setupState() (Database, error) {
db := DbConnect(":memory:")
err := db.Migrate()
@ -16,11 +16,62 @@ func setupState() (Database, error) {
return db, nil
}
// This is a more advanced setup that includes more data in the database.
// This is useful for more complex testing scenarios.
func setupAdvancedState() (Database, error) {
db, err := setupState()
if err != nil {
return nil, err
}
// Add a user
if err = db.AddUser("demouser", "password"); err != nil {
return nil, err
}
// Add a project
if err = db.AddProject("projecttest", "description", "demouser"); err != nil {
return nil, err
}
// Add a weekly report
if err = db.AddWeeklyReport("projecttest", "demouser", 1, 1, 1, 1, 1, 1, 1); err != nil {
return nil, err
}
return db, nil
}
// TestDbConnect tests the connection to the database
func TestDbConnect(t *testing.T) {
db := DbConnect(":memory:")
_ = db
}
func TestSetupAdvancedState(t *testing.T) {
db, err := setupAdvancedState()
if err != nil {
t.Error("setupAdvancedState failed:", err)
}
// Check if the user was added
if _, err = db.GetUserId("demouser"); err != nil {
t.Error("GetUserId failed:", err)
}
// Check if the project was added
projects, err := db.GetAllProjects()
if err != nil {
t.Error("GetAllProjects failed:", err)
}
if len(projects) != 1 {
t.Error("GetAllProjects failed: expected 1, got", len(projects))
}
// To be continued...
}
// TestDbAddUser tests the AddUser function of the database
func TestDbAddUser(t *testing.T) {
db, err := setupState()
if err != nil {
@ -32,6 +83,7 @@ func TestDbAddUser(t *testing.T) {
}
}
// TestDbGetUserId tests the GetUserID function of the database
func TestDbGetUserId(t *testing.T) {
db, err := setupState()
if err != nil {
@ -52,18 +104,20 @@ func TestDbGetUserId(t *testing.T) {
}
}
// TestDbAddProject tests the AddProject function of the database
func TestDbAddProject(t *testing.T) {
db, err := setupState()
db, err := setupAdvancedState()
if err != nil {
t.Error("setupState failed:", err)
}
err = db.AddProject("test", "description", "test")
err = db.AddProject("test", "description", "demouser")
if err != nil {
t.Error("AddProject failed:", err)
}
}
// TestDbRemoveUser tests the RemoveUser function of the database
func TestDbRemoveUser(t *testing.T) {
db, err := setupState()
if err != nil {
@ -76,6 +130,7 @@ func TestDbRemoveUser(t *testing.T) {
}
}
// TestPromoteToAdmin tests the PromoteToAdmin function of the database
func TestPromoteToAdmin(t *testing.T) {
db, err := setupState()
if err != nil {
@ -93,6 +148,7 @@ func TestPromoteToAdmin(t *testing.T) {
}
}
// TestAddWeeklyReport tests the AddWeeklyReport function of the database
func TestAddWeeklyReport(t *testing.T) {
db, err := setupState()
if err != nil {
@ -115,6 +171,7 @@ func TestAddWeeklyReport(t *testing.T) {
}
}
// TestAddUserToProject tests the AddUseToProject function of the database
func TestAddUserToProject(t *testing.T) {
db, err := setupState()
if err != nil {
@ -142,6 +199,7 @@ func TestAddUserToProject(t *testing.T) {
}
}
// TestChangeUserRole tests the ChangeUserRole function of the database
func TestChangeUserRole(t *testing.T) {
db, err := setupState()
if err != nil {
@ -158,20 +216,15 @@ func TestChangeUserRole(t *testing.T) {
t.Error("AddProject failed:", err)
}
err = db.AddUserToProject("testuser", "testproject", "user")
if err != nil {
t.Error("AddUserToProject failed:", err)
}
role, err := db.GetUserRole("testuser", "testproject")
if err != nil {
t.Error("GetUserRole failed:", err)
}
if role != "user" {
t.Error("GetUserRole failed: expected user, got", role)
if role != "project_manager" {
t.Error("GetUserRole failed: expected project_manager, got", role)
}
err = db.ChangeUserRole("testuser", "testproject", "admin")
err = db.ChangeUserRole("testuser", "testproject", "member")
if err != nil {
t.Error("ChangeUserRole failed:", err)
}
@ -180,12 +233,13 @@ func TestChangeUserRole(t *testing.T) {
if err != nil {
t.Error("GetUserRole failed:", err)
}
if role != "admin" {
t.Error("GetUserRole failed: expected admin, got", role)
if role != "member" {
t.Error("GetUserRole failed: expected member, got", role)
}
}
// TestGetAllUsersProject tests the GetAllUsersProject function of the database
func TestGetAllUsersProject(t *testing.T) {
db, err := setupState()
if err != nil {
@ -252,6 +306,7 @@ func TestGetAllUsersProject(t *testing.T) {
}
}
// TestGetAllUsersApplication tests the GetAllUsersApplicsation function of the database
func TestGetAllUsersApplication(t *testing.T) {
db, err := setupState()
if err != nil {
@ -298,6 +353,7 @@ func TestGetAllUsersApplication(t *testing.T) {
}
}
// TestGetProjectsForUser tests the GetProjectsForUser function of the database
func TestGetProjectsForUser(t *testing.T) {
db, err := setupState()
if err != nil {
@ -338,6 +394,7 @@ func TestGetProjectsForUser(t *testing.T) {
}
}
// TestAddProject tests AddProject function of the database
func TestAddProject(t *testing.T) {
db, err := setupState()
if err != nil {
@ -373,6 +430,7 @@ func TestAddProject(t *testing.T) {
}
}
// TestGetWeeklyReport tests GetWeeklyReport function of the database
func TestGetWeeklyReport(t *testing.T) {
db, err := setupState()
if err != nil {
@ -412,6 +470,7 @@ func TestGetWeeklyReport(t *testing.T) {
// Check other fields similarly
}
// TestSignWeeklyReport tests SignWeeklyReport function of the database
func TestSignWeeklyReport(t *testing.T) {
db, err := setupState()
if err != nil {
@ -464,7 +523,6 @@ func TestSignWeeklyReport(t *testing.T) {
if err != nil {
t.Error("GetUserId failed:", err)
}
fmt.Println("Project Manager's ID:", projectManagerID)
// Sign the report with the project manager
err = db.SignWeeklyReport(report.ReportId, projectManagerID)
@ -484,6 +542,7 @@ func TestSignWeeklyReport(t *testing.T) {
}
}
// TestSignWeeklyReportByAnotherProjectManager tests the scenario where a project manager attempts to sign a weekly report for a user who is not assigned to their project
func TestSignWeeklyReportByAnotherProjectManager(t *testing.T) {
db, err := setupState()
if err != nil {
@ -502,7 +561,7 @@ func TestSignWeeklyReportByAnotherProjectManager(t *testing.T) {
t.Error("AddUser failed:", err)
}
// Add project
// Add project, projectManager is the owner
err = db.AddProject("testproject", "description", "projectManager")
if err != nil {
t.Error("AddProject failed:", err)
@ -526,17 +585,29 @@ func TestSignWeeklyReportByAnotherProjectManager(t *testing.T) {
t.Error("GetWeeklyReport failed:", err)
}
anotherManagerID, err := db.GetUserId("projectManager")
managerID, err := db.GetUserId("projectManager")
if err != nil {
t.Error("GetUserId failed:", err)
}
err = db.SignWeeklyReport(report.ReportId, anotherManagerID)
if err == nil {
t.Error("Expected SignWeeklyReport to fail with a project manager who is not in the project, but it didn't")
err = db.SignWeeklyReport(report.ReportId, managerID)
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 != managerID {
t.Errorf("Expected SignedBy to be %d, got %d", managerID, *signedReport.SignedBy)
}
}
// TestGetProject tests GetProject function of the database
func TestGetProject(t *testing.T) {
db, err := setupState()
if err != nil {
@ -566,3 +637,149 @@ func TestGetProject(t *testing.T) {
t.Errorf("Expected Name to be testproject, got %s", project.Name)
}
}
func TestGetWeeklyReportsUser(t *testing.T) {
db, err := setupState()
if err != nil {
t.Error("setupState failed:", err)
}
err = db.AddUser("testuser", "password")
if err != nil {
t.Error("AddUser failed:", err)
}
err = db.AddProject("testproject", "description", "testuser")
if err != nil {
t.Error("AddProject failed:", err)
}
err = db.AddWeeklyReport("testproject", "testuser", 1, 1, 1, 1, 1, 1, 1)
if err != nil {
t.Error("AddWeeklyReport failed:", err)
}
err = db.AddWeeklyReport("testproject", "testuser", 2, 1, 1, 1, 1, 1, 1)
if err != nil {
t.Error("AddWeeklyReport failed:", err)
}
reports, err := db.GetWeeklyReportsUser("testuser", "testproject")
if err != nil {
t.Error("GetWeeklyReportsUser failed:", err)
}
// Check if the retrieved reports match the expected values
if len(reports) != 2 {
t.Errorf("Expected 1 report, got %d", len(reports))
}
}
func TestIsProjectManager(t *testing.T) {
db, err := setupState()
if err != nil {
t.Error("setupState failed:", err)
}
// Add a 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)
}
// Check if the regular user is not a project manager
isManager, err := db.IsProjectManager("testuser", "testproject")
if err != nil {
t.Error("IsProjectManager failed:", err)
}
if isManager {
t.Error("Expected testuser not to be a project manager, but it is.")
}
// Check if the project manager is indeed a project manager
isManager, err = db.IsProjectManager("projectManager", "testproject")
if err != nil {
t.Error("IsProjectManager failed:", err)
}
if !isManager {
t.Error("Expected projectManager to be a project manager, but it's not.")
}
}
func TestGetTotalTimePerActivity(t *testing.T) {
// Initialize your test database connection
db, err := setupState()
if err != nil {
t.Error("setupState failed:", err)
}
// Run the query to get total time per activity
totalTime, err := db.GetTotalTimePerActivity("projecttest")
if err != nil {
t.Error("GetTotalTimePerActivity failed:", err)
}
// Check if the totalTime map is not nil
if totalTime == nil {
t.Error("Expected non-nil totalTime map, got nil")
}
// ska lägga till fler assertions
}
func TestEnsureManagerOfCreatedProject(t *testing.T) {
db, err := setupState()
if err != nil {
t.Error("setupState failed:", err)
}
// Add a user
err = db.AddUser("testuser", "password")
if err != nil {
t.Error("AddUser failed:", err)
}
// Add a project
err = db.AddProject("testproject", "description", "testuser")
if err != nil {
t.Error("AddProject failed:", err)
}
// Set user to a project manager
// err = db.AddUserToProject("testuser", "testproject", "project_manager")
// if err != nil {
// t.Error("AddUserToProject failed:", err)
// }
managerState, err := db.IsProjectManager("testuser", "testproject")
if err != nil {
t.Error("IsProjectManager failed:", err)
}
if !managerState {
t.Error("Expected testuser to be a project manager, but it's not.")
}
}

View file

@ -10,6 +10,7 @@ CREATE TABLE IF NOT EXISTS weekly_reports (
study_time INTEGER,
testing_time INTEGER,
signed_by INTEGER,
UNIQUE(user_id, project_id, week),
FOREIGN KEY (user_id) REFERENCES users(id),
FOREIGN KEY (project_id) REFERENCES projects(id),
FOREIGN KEY (signed_by) REFERENCES users(id)

View file

@ -33,3 +33,18 @@ 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);

View file

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

View file

@ -30,6 +30,18 @@ func (gs *GState) CreateProject(c *fiber.Ctx) error {
return c.Status(200).SendString("Project added")
}
func (gs *GState) DeleteProject(c *fiber.Ctx) error {
projectID := c.Params("projectID")
username := c.Params("username")
if err := gs.Db.DeleteProject(projectID, username); err != nil {
return c.Status(500).SendString((err.Error()))
}
return c.Status(200).SendString("Project deleted")
}
// GetUserProjects returns all projects that the user is a member of
func (gs *GState) GetUserProjects(c *fiber.Ctx) error {
// First we get the username from the token
@ -49,13 +61,32 @@ func (gs *GState) GetUserProjects(c *fiber.Ctx) error {
// ProjectRoleChange is a handler that changes a user's role within a project
func (gs *GState) ProjectRoleChange(c *fiber.Ctx) error {
//check token and get username of current user
user := c.Locals("user").(*jwt.Token)
claims := user.Claims.(jwt.MapClaims)
username := claims["name"].(string)
// Extract the necessary parameters from the request
username := c.Params("username")
projectName := c.Params("projectName")
role := c.Params("role")
data := new(types.RoleChange)
if err := c.BodyParser(data); err != nil {
log.Info("error parsing username, project or role")
return c.Status(400).SendString(err.Error())
}
log.Info("Changing role for user: ", username, " in project: ", data.Projectname, " to: ", data.Role)
// Dubble diping and checcking if current user is
if ismanager, err := gs.Db.IsProjectManager(username, data.Projectname); err != nil {
log.Warn("Error checking if projectmanager:", err)
return c.Status(500).SendString(err.Error())
} else if !ismanager {
log.Warn("User is not projectmanager")
return c.Status(401).SendString("User is not projectmanager")
}
// Change the user's role within the project in the database
if err := gs.Db.ChangeUserRole(username, projectName, role); err != nil {
if err := gs.Db.ChangeUserRole(username, data.Projectname, data.Role); err != nil {
return c.Status(500).SendString(err.Error())
}
@ -100,6 +131,31 @@ func (gs *GState) ListAllUsersProject(c *fiber.Ctx) error {
return c.Status(400).SendString("No project name provided")
}
// Get the user token
userToken := c.Locals("user").(*jwt.Token)
claims := userToken.Claims.(jwt.MapClaims)
username := claims["name"].(string)
// Check if the user is a project manager for the specified project
isManager, err := gs.Db.IsProjectManager(username, projectName)
if err != nil {
log.Info("Error checking project manager status:", err)
return c.Status(500).SendString(err.Error())
}
// If the user is not a project manager, check if the user is a site admin
if !isManager {
isAdmin, err := gs.Db.IsSiteAdmin(username)
if err != nil {
log.Info("Error checking admin status:", err)
return c.Status(500).SendString(err.Error())
}
if !isAdmin {
log.Info("User is neither a project manager nor a site admin:", username)
return c.Status(403).SendString("User is neither a project manager nor a site admin")
}
}
// Get all users associated with the project from the database
users, err := gs.Db.GetAllUsersProject(projectName)
if err != nil {
@ -154,3 +210,26 @@ func (gs *GState) AddUserToProjectHandler(c *fiber.Ctx) error {
log.Info("User added to project successfully:", requestData.Username)
return c.SendStatus(fiber.StatusOK)
}
// IsProjectManagerHandler is a handler that checks if a user is a project manager for a given project
func (gs *GState) IsProjectManagerHandler(c *fiber.Ctx) error {
// Get the username from the token
user := c.Locals("user").(*jwt.Token)
claims := user.Claims.(jwt.MapClaims)
username := claims["name"].(string)
// Extract necessary parameters from the request query string
projectName := c.Params("projectName")
log.Info("Checking if user ", username, " is a project manager for project ", projectName)
// Check if the user is a project manager for the specified project
isManager, err := gs.Db.IsProjectManager(username, projectName)
if err != nil {
log.Info("Error checking project manager status:", err)
return c.Status(500).SendString(err.Error())
}
// Return the result as JSON
return c.JSON(fiber.Map{"isProjectManager": isManager})
}

View file

@ -114,3 +114,30 @@ func (gs *GState) SignReport(c *fiber.Ctx) error {
return c.Status(200).SendString("Weekly report signed successfully")
}
// GetWeeklyReportsUserHandler retrieves all weekly reports for a user in a specific project
func (gs *GState) GetWeeklyReportsUserHandler(c *fiber.Ctx) error {
// Extract the necessary parameters from the token
user := c.Locals("user").(*jwt.Token)
claims := user.Claims.(jwt.MapClaims)
username := claims["name"].(string)
// Extract necessary (path) parameters from the request
projectName := c.Params("projectName")
// TODO: Here we need to check whether the user is a member of the project
// If not, we should return an error. On the other hand, if the user not a member,
// the returned list of reports will (should) allways be empty.
// Retrieve weekly reports for the user in the project from the database
reports, err := gs.Db.GetWeeklyReportsUser(username, projectName)
if err != nil {
log.Error("Error getting weekly reports for user:", username, "in project:", projectName, ":", err)
return c.Status(500).SendString(err.Error())
}
log.Info("Returning weekly reports for user:", username, "in project:", projectName)
// Return the list of reports as JSON
return c.JSON(reports)
}

View file

@ -101,10 +101,15 @@ func (gs *GState) Login(c *fiber.Ctx) error {
return c.SendStatus(fiber.StatusUnauthorized)
}
isAdmin, err := gs.Db.IsSiteAdmin(u.Username)
if err != nil {
log.Info("Error checking admin status:", err)
return c.Status(500).SendString(err.Error())
}
// Create the Claims
claims := jwt.MapClaims{
"name": u.Username,
"admin": false,
"admin": isAdmin,
"exp": time.Now().Add(time.Hour * 72).Unix(),
}
@ -182,17 +187,31 @@ func (gs *GState) ListAllUsers(c *fiber.Ctx) error {
return c.JSON(users)
}
// @Summary PromoteToAdmin
// @Description promote chosen user to admin
// @Tags User
// @Accept json
// @Produce plain
// @Param NewUser body types.NewUser true "user info"
// @Success 200 {json} json "Successfully prometed user"
// @Failure 400 {string} string "bad request"
// @Failure 401 {string} string "Unauthorized"
// @Failure 500 {string} string "Internal server error"
// @Router /promoteToAdmin [post]
func (gs *GState) GetAllUsersProject(c *fiber.Ctx) error {
// Get all users from a project
projectName := c.Params("projectName")
users, err := gs.Db.GetAllUsersProject(projectName)
if err != nil {
log.Info("Error getting users from project:", err) // Debug print
return c.Status(500).SendString(err.Error())
}
log.Info("Returning all users")
// Return the list of users as JSON
return c.JSON(users)
}
// @Summary PromoteToAdmin
// @Description promote chosen user to admin
// @Tags User
// @Accept json
// @Produce plain
// @Param NewUser body types.NewUser true "user info"
// @Success 200 {json} json "Successfully prometed user"
// @Failure 400 {string} string "bad request"
// @Failure 401 {string} string "Unauthorized"
// @Failure 500 {string} string "Internal server error"
// @Router /promoteToAdmin [post]
func (gs *GState) PromoteToAdmin(c *fiber.Ctx) error {
// Extract the username from the request body
var newUser types.NewUser
@ -214,3 +233,37 @@ func (gs *GState) PromoteToAdmin(c *fiber.Ctx) error {
// Return a success message
return c.SendStatus(fiber.StatusOK)
}
// Changes a users name in the database
func (gs *GState) ChangeUserName(c *fiber.Ctx) error {
//check token and get username of current user
user := c.Locals("user").(*jwt.Token)
claims := user.Claims.(jwt.MapClaims)
projectManagerUsername := claims["name"].(string)
log.Info(projectManagerUsername)
// Extract the necessary parameters from the request
data := new(types.NameChange)
if err := c.BodyParser(data); err != nil {
log.Info("error parsing username, project or role")
return c.Status(400).SendString(err.Error())
}
// dubble diping and checcking if current user is
if ismanager, err := gs.Db.IsProjectManager(projectManagerUsername, c.Params(data.Name)); err != nil {
log.Warn("Error checking if projectmanager:", err)
return c.Status(500).SendString(err.Error())
} else if !ismanager {
log.Warn("tried changing name when not projectmanager:", err)
return c.Status(401).SendString("you can not change name when not projectmanager")
}
// Change the user's name within the project in the database
if err := gs.Db.ChangeUserName(projectManagerUsername, data.Name); err != nil {
return c.Status(500).SendString(err.Error())
}
// Return a success message
return c.SendStatus(fiber.StatusOK)
}

View file

@ -20,6 +20,27 @@ type NewWeeklyReport struct {
TestingTime int `json:"testingTime"`
}
type WeeklyReportList struct {
// The name of the project, as it appears in the database
ProjectName string `json:"projectName" db:"project_name"`
// The week number
Week int `json:"week" db:"week"`
// Total time spent on development
DevelopmentTime int `json:"developmentTime" db:"development_time"`
// Total time spent in meetings
MeetingTime int `json:"meetingTime" db:"meeting_time"`
// Total time spent on administrative tasks
AdminTime int `json:"adminTime" db:"admin_time"`
// Total time spent on personal projects
OwnWorkTime int `json:"ownWorkTime" db:"own_work_time"`
// Total time spent on studying
StudyTime int `json:"studyTime" db:"study_time"`
// Total time spent on testing
TestingTime int `json:"testingTime" db:"testing_time"`
// The project manager who signed it
SignedBy *int `json:"signedBy" db:"signed_by"`
}
type WeeklyReport struct {
// The ID of the report
ReportId int `json:"reportId" db:"report_id"`

View file

@ -13,3 +13,17 @@ type NewProject struct {
Name string `json:"name"`
Description string `json:"description"`
}
// Used to change the role of a user in a project.
// If name is identical to the name contained in the token, the role can be changed.
// If the name is different, only a project manager can change the role.
type RoleChange struct {
UserName string `json:"username"`
Role string `json:"role" tstype:"'project_manager' | 'user'"`
Projectname string `json:"projectname"`
}
type NameChange struct {
ID int `json:"id" db:"id"`
Name string `json:"name" db:"name"`
}

View file

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

View file

@ -87,13 +87,21 @@ func main() {
server.Get("/api/getUserProjects", gs.GetUserProjects)
server.Post("/api/loginrenew", gs.LoginRenew)
server.Delete("/api/userdelete/:username", gs.UserDelete) // Perhaps just use POST to avoid headaches
server.Post("/api/project", gs.CreateProject)
server.Delete("api/project/:projectID", gs.DeleteProject) // WIP
server.Post("/api/project", gs.CreateProject) // WIP
server.Get("/api/project/:projectId", gs.GetProject)
server.Get("/api/project/getAllUsers", gs.GetAllUsersProject)
server.Get("/api/getWeeklyReport", gs.GetWeeklyReport)
server.Post("/api/signReport", gs.SignReport)
server.Put("/api/addUserToProject", gs.AddUserToProjectHandler)
server.Put("/api/changeUserName", gs.ChangeUserName)
server.Post("/api/promoteToAdmin", gs.PromoteToAdmin)
server.Get("/api/users/all", gs.ListAllUsers)
server.Get("/api/getWeeklyReportsUser/:projectName", gs.GetWeeklyReportsUserHandler)
server.Get("/api/checkIfProjectManager/:projectName", gs.IsProjectManagerHandler)
server.Post("/api/ProjectRoleChange", gs.ProjectRoleChange)
server.Get("/api/getUsersProject/:projectName", gs.ListAllUsersProject)
// Announce the port we are listening on and start the server
err = server.Listen(fmt.Sprintf(":%d", conf.Port))
if err != nil {

View file

@ -4,53 +4,132 @@ import {
User,
Project,
NewProject,
WeeklyReport,
} from "../Types/goTypes";
// This type of pattern should be hard to misuse
/**
* Response object returned by API methods.
*/
export interface APIResponse<T> {
/** Indicates whether the API call was successful */
success: boolean;
/** Optional message providing additional information or error description */
message?: string;
/** Optional data returned by the API method */
data?: T;
}
// Note that all protected routes also require a token
// Defines all the methods that an instance of the API must implement
/**
* Interface defining methods that an instance of the API must implement.
*/
interface API {
/** Register a new user */
/**
* Register a new user
* @param {NewUser} user The user object to be registered
* @returns {Promise<APIResponse<User>>} A promise containing the API response with the user data.
*/
registerUser(user: NewUser): Promise<APIResponse<User>>;
/** Remove a user */
/**
* Removes a user.
* @param {string} username The username of the user to be removed.
* @param {string} token The authentication token.
* @returns {Promise<APIResponse<User>>} A promise containing the API response with the removed user data.
*/
removeUser(username: string, token: string): Promise<APIResponse<User>>;
/** Login */
/**
* Check if user is project manager.
* @param {string} username The username of the user.
* @param {string} projectName The name of the project.
* @param {string} token The authentication token.
* @returns {Promise<APIResponse<boolean>>} A promise containing the API response indicating if the user is a project manager.
*/
checkIfProjectManager(
username: string,
projectName: string,
token: string,
): Promise<APIResponse<boolean>>;
/** Logs in a user with the provided credentials.
* @param {NewUser} NewUser The user object containing username and password.
* @returns {Promise<APIResponse<string>>} A promise resolving to an API response with a token.
*/
login(NewUser: NewUser): Promise<APIResponse<string>>;
/** Renew the token */
/**
* Renew the token
* @param {string} token The current authentication token.
* @returns {Promise<APIResponse<string>>} A promise resolving to an API response with a renewed token.
*/
renewToken(token: string): Promise<APIResponse<string>>;
/** Create a project */
/** Promote user to admin */
/** Creates a new project.
* @param {NewProject} project The project object containing name and description.
* @param {string} token The authentication token.
* @returns {Promise<APIResponse<Project>>} A promise resolving to an API response with the created project.
*/
createProject(
project: NewProject,
token: string,
): Promise<APIResponse<Project>>;
/** Submit a weekly report */
/** Submits a weekly report
* @param {NewWeeklyReport} weeklyReport The weekly report object.
* @param {string} token The authentication token.
* @returns {Promise<APIResponse<NewWeeklyReport>>} A promise resolving to an API response with the submitted report.
*/
submitWeeklyReport(
project: NewWeeklyReport,
weeklyReport: NewWeeklyReport,
token: string,
): Promise<APIResponse<NewWeeklyReport>>;
/**Gets a weekly report*/
/** Gets a weekly report for a specific user, project and week
* @param {string} projectName The name of the project.
* @param {string} week The week number.
* @param {string} token The authentication token.
* @returns {Promise<APIResponse<WeeklyReport>>} A promise resolving to an API response with the retrieved report.
*/
getWeeklyReport(
username: string,
projectName: string,
week: string,
token: string,
): Promise<APIResponse<NewWeeklyReport>>;
/** Gets all the projects of a user*/
getUserProjects(
username: string,
): Promise<APIResponse<WeeklyReport>>;
/**
* Returns all the weekly reports for a user in a particular project
* The username is derived from the token
* @param {string} projectName The name of the project
* @param {string} token The token of the user
* @returns {APIResponse<WeeklyReport[]>} A list of weekly reports
*/
getWeeklyReportsForUser(
projectName: string,
token: string,
): Promise<APIResponse<Project[]>>;
/** Gets a project from id*/
): Promise<APIResponse<WeeklyReport[]>>;
/** Gets all the projects of a user
* @param {string} token - The authentication token.
* @returns {Promise<APIResponse<Project[]>>} A promise containing the API response with the user's projects.
*/
getUserProjects(token: string): Promise<APIResponse<Project[]>>;
/** Gets a project by its id.
* @param {number} id The id of the project to retrieve.
* @returns {Promise<APIResponse<Project>>} A promise resolving to an API response containing the project data.
*/
getProject(id: number): Promise<APIResponse<Project>>;
/** Gets a list of all users.
* @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.
*/
getAllUsers(token: string): Promise<APIResponse<string[]>>;
}
// Export an instance of the API
/** An instance of the API */
export const api: API = {
async registerUser(user: NewUser): Promise<APIResponse<User>> {
try {
@ -84,7 +163,7 @@ export const api: API = {
token: string,
): Promise<APIResponse<User>> {
try {
const response = await fetch("/api/userdelete", {
const response = await fetch(`/api/userdelete/${username}`, {
method: "POST",
headers: {
"Content-Type": "application/json",
@ -104,6 +183,35 @@ export const api: API = {
}
},
async checkIfProjectManager(
username: string,
projectName: string,
token: string,
): Promise<APIResponse<boolean>> {
try {
const response = await fetch("/api/checkIfProjectManager", {
method: "GET",
headers: {
"Content-Type": "application/json",
Authorization: "Bearer " + token,
},
body: JSON.stringify({ username, projectName }),
});
if (!response.ok) {
return {
success: false,
message: "Failed to check if project manager",
};
} else {
const data = (await response.json()) as boolean;
return { success: true, data };
}
} catch (e) {
return { success: false, message: "fuck" };
}
},
async createProject(
project: NewProject,
token: string,
@ -150,10 +258,7 @@ export const api: API = {
}
},
async getUserProjects(
username: string,
token: string,
): Promise<APIResponse<Project[]>> {
async getUserProjects(token: string): Promise<APIResponse<Project[]>> {
try {
const response = await fetch("/api/getUserProjects", {
method: "GET",
@ -161,7 +266,6 @@ export const api: API = {
"Content-Type": "application/json",
Authorization: "Bearer " + token,
},
body: JSON.stringify({ username }),
});
if (!response.ok) {
@ -176,7 +280,7 @@ export const api: API = {
} catch (e) {
return Promise.resolve({
success: false,
message: "Failed to get user projects",
message: "API fucked",
});
}
},
@ -213,29 +317,62 @@ export const api: API = {
},
async getWeeklyReport(
username: string,
projectName: string,
week: string,
token: string,
): Promise<APIResponse<NewWeeklyReport>> {
): Promise<APIResponse<WeeklyReport>> {
try {
const response = await fetch("/api/getWeeklyReport", {
const response = await fetch(
`/api/getWeeklyReport?projectName=${projectName}&week=${week}`,
{
method: "GET",
headers: {
"Content-Type": "application/json",
Authorization: "Bearer " + token,
},
},
);
if (!response.ok) {
return { success: false, message: "Failed to get weekly report" };
} else {
const data = (await response.json()) as WeeklyReport;
return { success: true, data };
}
} catch (e) {
return { success: false, message: "Failed to get weekly report" };
}
},
async getWeeklyReportsForUser(
projectName: string,
token: string,
): Promise<APIResponse<WeeklyReport[]>> {
try {
const response = await fetch(`/api/getWeeklyReportsUser/${projectName}`, {
method: "GET",
headers: {
"Content-Type": "application/json",
Authorization: "Bearer " + token,
},
body: JSON.stringify({ username, projectName, week }),
});
if (!response.ok) {
return { success: false, message: "Failed to get weekly report" };
return {
success: false,
message:
"Failed to get weekly reports for project: Response code " +
response.status,
};
} else {
const data = (await response.json()) as NewWeeklyReport;
const data = (await response.json()) as WeeklyReport[];
return { success: true, data };
}
} catch (e) {
return { success: false, message: "Failed to get weekly report" };
return {
success: false,
message: "Failed to get weekly reports for project, unknown error",
};
}
},
@ -260,7 +397,6 @@ export const api: API = {
}
},
// Gets a projet by id, currently untested since we have no javascript-based tests
async getProject(id: number): Promise<APIResponse<Project>> {
try {
const response = await fetch(`/api/project/${id}`, {
@ -285,4 +421,31 @@ export const api: API = {
};
}
},
async getAllUsers(token: string): Promise<APIResponse<string[]>> {
try {
const response = await fetch("/api/users/all", {
method: "GET",
headers: {
"Content-Type": "application/json",
Authorization: "Bearer " + token,
},
});
if (!response.ok) {
return Promise.resolve({
success: false,
message: "Failed to get users",
});
} else {
const data = (await response.json()) as string[];
return Promise.resolve({ success: true, data });
}
} catch (e) {
return Promise.resolve({
success: false,
message: "API is not ok",
});
}
},
};

View file

@ -7,7 +7,7 @@ import Button from "./Button";
/**
* Tries to add a project to the system
* @param props - Project name and description
* @param {Object} props - Project name and description
* @returns {boolean} True if created, false if not
*/
function CreateProject(props: { name: string; description: string }): boolean {
@ -34,8 +34,8 @@ function CreateProject(props: { name: string; description: string }): boolean {
}
/**
* Tries to add a project to the system
* @returns {JSX.Element} UI for project adding
* Provides UI for adding a project to the system.
* @returns {JSX.Element} - Returns the component UI for adding a project
*/
function AddProject(): JSX.Element {
const [name, setName] = useState("");

View file

@ -0,0 +1,71 @@
//Info: This component is used to display all the time reports for a project. It will display the week number,
//total time spent, and if the report has been signed or not. The user can click on a report to edit it.
import { useEffect, useState } from "react";
import { WeeklyReport } from "../Types/goTypes";
import { Link, useParams } from "react-router-dom";
import { api } from "../API/API";
/**
* Renders a component that displays all the time reports for a specific project.
* @returns {JSX.Element} representing the component.
*/
function AllTimeReportsInProject(): JSX.Element {
const { projectName } = useParams();
const [weeklyReports, setWeeklyReports] = useState<WeeklyReport[]>([]);
const getWeeklyReports = async (): Promise<void> => {
const token = localStorage.getItem("accessToken") ?? "";
const response = await api.getWeeklyReportsForUser(
projectName ?? "",
token,
);
console.log(response);
if (response.success) {
setWeeklyReports(response.data ?? []);
} else {
console.error(response.message);
}
};
// Call getProjects when the component mounts
useEffect(() => {
void getWeeklyReports();
}, []);
return (
<>
<div className="border-4 border-black bg-white flex flex-col items-center justify-center min-h-[65vh] h-fit w-[50vw] rounded-3xl content-center overflow-scroll space-y-[10vh] p-[30px] text-[30px]">
{weeklyReports.map((newWeeklyReport, index) => (
<Link
to={`/editTimeReport/${projectName}/${newWeeklyReport.week}`}
key={index}
className="border-b-2 border-black w-full"
>
<div className="flex justify-between">
<h1>
<span className="font-bold">{"Week: "}</span>
{newWeeklyReport.week}
</h1>
<h1>
<span className="font-bold">{"Total Time: "}</span>
{newWeeklyReport.developmentTime +
newWeeklyReport.meetingTime +
newWeeklyReport.adminTime +
newWeeklyReport.ownWorkTime +
newWeeklyReport.studyTime +
newWeeklyReport.testingTime}{" "}
min
</h1>
<h1>
<span className="font-bold">{"Signed: "}</span>
{newWeeklyReport.signedBy ? "YES" : "NO"}
</h1>
</div>
</Link>
))}
</div>
</>
);
}
export default AllTimeReportsInProject;

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -0,0 +1,34 @@
import React, { useState } from "react";
import InputField from "./InputField";
function ChangeUsername(): JSX.Element {
const [newUsername, setNewUsername] = useState("");
const handleChange = (e: React.ChangeEvent<HTMLInputElement>): void => {
setNewUsername(e.target.value);
};
// const handleSubmit = async (): Promise<void> => {
// try {
// // Call the API function to update the username
// await api.updateUsername(newUsername);
// // Optionally, add a success message or redirect the user
// } catch (error) {
// console.error("Error updating username:", error);
// // Optionally, handle the error
// }
// };
return (
<div>
<InputField
label="New Username"
type="text"
value={newUsername}
onChange={handleChange}
/>
</div>
);
}
export default ChangeUsername;

View file

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

View file

@ -0,0 +1,34 @@
import { User } from "../Types/goTypes";
import { api, APIResponse } from "../API/API";
/**
* Use to remove a user from the system
* @param props - The username of user to remove
* @returns {boolean} True if removed, false if not
* @example
* const exampleUsername = "user";
* DeleteUser({ usernameToDelete: exampleUsername });
*/
function DeleteUser(props: { usernameToDelete: string }): boolean {
//console.log(props.usernameToDelete); FOR DEBUG
let removed = false;
api
.removeUser(
props.usernameToDelete,
localStorage.getItem("accessToken") ?? "",
)
.then((response: APIResponse<User>) => {
if (response.success) {
removed = true;
} else {
console.error(response.message);
}
})
.catch((error) => {
console.error("An error occurred during creation:", error);
});
return removed;
}
export default DeleteUser;

View file

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

View file

@ -1,11 +1,14 @@
import { useState, useEffect } from "react";
import { NewWeeklyReport } from "../Types/goTypes";
import { WeeklyReport, NewWeeklyReport } from "../Types/goTypes";
import { api } from "../API/API";
import { useNavigate } from "react-router-dom";
import { useNavigate, useParams } from "react-router-dom";
import Button from "./Button";
/**
* Renders the component for editing a weekly report.
* @returns JSX.Element
*/
export default function GetWeeklyReport(): JSX.Element {
const [projectName, setProjectName] = useState("");
const [week, setWeek] = useState(0);
const [developmentTime, setDevelopmentTime] = useState(0);
const [meetingTime, setMeetingTime] = useState(0);
@ -16,46 +19,49 @@ export default function GetWeeklyReport(): JSX.Element {
const token = localStorage.getItem("accessToken") ?? "";
const username = localStorage.getItem("username") ?? "";
const { projectName } = useParams();
const { fetchedWeek } = useParams();
const fetchWeeklyReport = async (): Promise<void> => {
const response = await api.getWeeklyReport(
username,
projectName ?? "",
fetchedWeek?.toString() ?? "0",
token,
);
if (response.success) {
const report: WeeklyReport = response.data ?? {
reportId: 0,
userId: 0,
projectId: 0,
week: 0,
developmentTime: 0,
meetingTime: 0,
adminTime: 0,
ownWorkTime: 0,
studyTime: 0,
testingTime: 0,
};
setWeek(report.week);
setDevelopmentTime(report.developmentTime);
setMeetingTime(report.meetingTime);
setAdminTime(report.adminTime);
setOwnWorkTime(report.ownWorkTime);
setStudyTime(report.studyTime);
setTestingTime(report.testingTime);
} else {
console.error("Failed to fetch weekly report:", response.message);
}
};
useEffect(() => {
const fetchWeeklyReport = async (): Promise<void> => {
const response = await api.getWeeklyReport(
username,
projectName,
week.toString(),
token,
);
if (response.success) {
const report: NewWeeklyReport = response.data ?? {
projectName: "",
week: 0,
developmentTime: 0,
meetingTime: 0,
adminTime: 0,
ownWorkTime: 0,
studyTime: 0,
testingTime: 0,
};
setProjectName(report.projectName);
setWeek(report.week);
setDevelopmentTime(report.developmentTime);
setMeetingTime(report.meetingTime);
setAdminTime(report.adminTime);
setOwnWorkTime(report.ownWorkTime);
setStudyTime(report.studyTime);
setTestingTime(report.testingTime);
} else {
console.error("Failed to fetch weekly report:", response.message);
}
};
void fetchWeeklyReport();
}, [projectName, token, username, week]);
});
const handleNewWeeklyReport = async (): Promise<void> => {
const newWeeklyReport: NewWeeklyReport = {
projectName,
projectName: projectName ?? "",
week,
developmentTime,
meetingTime,
@ -82,7 +88,7 @@ export default function GetWeeklyReport(): JSX.Element {
}
e.preventDefault();
void handleNewWeeklyReport();
navigate("/project");
navigate(-1);
}}
>
<div className="flex flex-col items-center">
@ -233,7 +239,7 @@ export default function GetWeeklyReport(): JSX.Element {
</tbody>
</table>
<Button
text="Submit"
text="Submit changes"
onClick={(): void => {
return;
}}

View file

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

View file

@ -0,0 +1,35 @@
import { Dispatch, useEffect } from "react";
import { api } from "../API/API";
/**
* Gets all usernames in the system and puts them in an array
* @param props - A setStateAction for the array you want to put users in
* @returns {void} Nothing
* @example
* const [users, setUsers] = useState<string[]>([]);
* GetAllUsers({ setUsersProp: setUsers });
*/
function GetAllUsers(props: {
setUsersProp: Dispatch<React.SetStateAction<string[]>>;
}): void {
const setUsers: Dispatch<React.SetStateAction<string[]>> = props.setUsersProp;
useEffect(() => {
const fetchUsers = async (): Promise<void> => {
try {
const token = localStorage.getItem("accessToken") ?? "";
const response = await api.getAllUsers(token);
if (response.success) {
setUsers(response.data ?? []);
} else {
console.error("Failed to fetch users:", response.message);
}
} catch (error) {
console.error("Error fetching users:", error);
}
};
void fetchUsers();
}, [setUsers]);
}
export default GetAllUsers;

View file

@ -1,6 +1,12 @@
//info: Header component to display the header of the page including the logo and user information where thr user can logout
import { useState } from "react";
import { Link } from "react-router-dom";
import backgroundImage from "../assets/1.jpg";
/**
* Renders the header component.
* @returns JSX.Element representing the header component.
*/
function Header(): JSX.Element {
const [isOpen, setIsOpen] = useState(false);
@ -11,7 +17,7 @@ function Header(): JSX.Element {
return (
<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"
style={{ backgroundImage: `url('src/assets/1.jpg')` }}
style={{ backgroundImage: `url(${backgroundImage})` }}
>
<Link to="/your-projects">
<img

View file

@ -32,16 +32,11 @@ function LoginCheck(props: {
prevAuth = 1;
return prevAuth;
});
} else if (token !== "" && props.username === "pm") {
} else if (token !== "") {
props.setAuthority((prevAuth) => {
prevAuth = 2;
return prevAuth;
});
} else if (token !== "" && props.username === "user") {
props.setAuthority((prevAuth) => {
prevAuth = 3;
return prevAuth;
});
}
} else {
console.error("Token was undefined");

View file

@ -1,32 +1,37 @@
import { useState, useContext } from "react";
//info: New weekly report form component to create a new weekly report to
//sumbit development time, meeting time, admin time, own work time, study time and testing time
import { useState } from "react";
import type { NewWeeklyReport } from "../Types/goTypes";
import { api } from "../API/API";
import { useNavigate } from "react-router-dom";
import { useNavigate, useParams } from "react-router-dom";
import Button from "./Button";
import { ProjectNameContext } from "../Pages/YourProjectsPage";
/**
* Renders a form for creating a new weekly report.
* @returns The JSX element representing the new weekly report form.
*/
export default function NewWeeklyReport(): JSX.Element {
const [week, setWeek] = useState(0);
const [developmentTime, setDevelopmentTime] = useState(0);
const [meetingTime, setMeetingTime] = useState(0);
const [adminTime, setAdminTime] = useState(0);
const [ownWorkTime, setOwnWorkTime] = useState(0);
const [studyTime, setStudyTime] = useState(0);
const [testingTime, setTestingTime] = useState(0);
const [week, setWeek] = useState<number>(0);
const [developmentTime, setDevelopmentTime] = useState<number>();
const [meetingTime, setMeetingTime] = useState<number>();
const [adminTime, setAdminTime] = useState<number>();
const [ownWorkTime, setOwnWorkTime] = useState<number>();
const [studyTime, setStudyTime] = useState<number>();
const [testingTime, setTestingTime] = useState<number>();
const projectName = useContext(ProjectNameContext);
const { projectName } = useParams();
const token = localStorage.getItem("accessToken") ?? "";
const handleNewWeeklyReport = async (): Promise<void> => {
const newWeeklyReport: NewWeeklyReport = {
projectName,
week,
developmentTime,
meetingTime,
adminTime,
ownWorkTime,
studyTime,
testingTime,
projectName: projectName ?? "",
week: week,
developmentTime: developmentTime ?? 0,
meetingTime: meetingTime ?? 0,
adminTime: adminTime ?? 0,
ownWorkTime: ownWorkTime ?? 0,
studyTime: studyTime ?? 0,
testingTime: testingTime ?? 0,
};
await api.submitWeeklyReport(newWeeklyReport, token);
@ -46,7 +51,7 @@ export default function NewWeeklyReport(): JSX.Element {
}
e.preventDefault();
void handleNewWeeklyReport();
navigate("/project");
navigate(-1);
}}
>
<div className="flex flex-col items-center">
@ -59,7 +64,9 @@ export default function NewWeeklyReport(): JSX.Element {
setWeek(weekNumber);
}}
onKeyDown={(event) => {
event.preventDefault();
const keyValue = event.key;
if (!/\d/.test(keyValue) && keyValue !== "Backspace")
event.preventDefault();
}}
onPaste={(event) => {
event.preventDefault();

View file

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

View file

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

View file

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

View file

@ -0,0 +1,54 @@
import { Link } from "react-router-dom";
import Button from "./Button";
import DeleteUser from "./DeleteUser";
import UserProjectListAdmin from "./UserProjectListAdmin";
function UserInfoModal(props: {
isVisible: boolean;
username: string;
onClose: () => void;
}): JSX.Element {
if (!props.isVisible) return <></>;
return (
<div
className="fixed inset-0 bg-black bg-opacity-30 backdrop-blur-sm
flex justify-center items-center"
>
<div className="border-4 border-black bg-white p-2 rounded-lg text-center">
<p className="font-bold text-[30px]">{props.username}</p>
<Link to="/AdminChangeUserName">
<p className="mb-[20px] hover:font-bold hover:cursor-pointer underline">
(Change Username)
</p>
</Link>
<div>
<h2 className="font-bold text-[22px] mb-[20px]">
Member of these projects:
</h2>
<div className="pr-6 pl-6">
<UserProjectListAdmin />
</div>
</div>
<div className="items-center space-x-6 pr-6 pl-6">
<Button
text={"Delete"}
onClick={function (): void {
DeleteUser({ usernameToDelete: props.username });
}}
type="button"
/>
<Button
text={"Close"}
onClick={function (): void {
props.onClose();
}}
type="button"
/>
</div>
</div>
</div>
);
}
export default UserInfoModal;

View file

@ -1,35 +1,53 @@
import { Link } from "react-router-dom";
import { PublicUser } from "../Types/goTypes";
import { useState } from "react";
import UserInfoModal from "./UserInfoModal";
/**
* The props for the UserProps component
*/
interface UserProps {
users: PublicUser[];
}
/**
* A list of users for admin manage users page, that links admin to the right user page
* thanks to the state property
* @param props - The users to display
* A list of users for admin manage users page, that sets an onClick
* function for eact user <li> element, which displays a modul with
* user info.
* @param props - An array of users users to display
* @returns {JSX.Element} The user list
* @example
* const users = [{ id: 1, userName: "Random name" }];
* const users = [{ id: 1, userName: "ExampleName" }];
* return <UserList users={users} />;
*/
export function UserListAdmin(props: UserProps): JSX.Element {
export function UserListAdmin(props: { users: string[] }): JSX.Element {
const [modalVisible, setModalVisible] = useState(false);
const [username, setUsername] = useState("");
const handleClick = (username: string): void => {
setUsername(username);
setModalVisible(true);
};
const handleClose = (): void => {
setUsername("");
setModalVisible(false);
};
return (
<div>
<ul className="font-bold underline text-[30px] cursor-pointer padding">
{props.users.map((user) => (
<Link to="/adminUserInfo" key={user.userId} state={user.username}>
<li className="pt-5" key={user.userId}>
{user.username}
<>
<UserInfoModal
onClose={handleClose}
isVisible={modalVisible}
username={username}
/>
<div>
<ul className="font-bold underline text-[30px] cursor-pointer padding">
{props.users.map((user) => (
<li
className="pt-5"
key={user}
onClick={() => {
handleClick(user);
}}
>
{user}
</li>
</Link>
))}
</ul>
</div>
))}
</ul>
</div>
</>
);
}

View file

@ -1,17 +1,17 @@
import React, { useEffect, useState } from "react";
import { useEffect, useState } from "react";
import { api } from "../API/API";
import { Project } from "../Types/goTypes";
const UserProjectListAdmin: React.FC = () => {
function UserProjectListAdmin(): JSX.Element {
const [projects, setProjects] = useState<Project[]>([]);
useEffect(() => {
const fetchProjects = async (): Promise<void> => {
try {
const token = localStorage.getItem("accessToken") ?? "";
const username = "NoUser"; // getUsernameFromContext(); // Assuming you have a function to get the username from your context
// const username = props.username;
const response = await api.getUserProjects(username, token);
const response = await api.getUserProjects(token);
if (response.success) {
setProjects(response.data ?? []);
} else {
@ -26,18 +26,16 @@ const UserProjectListAdmin: React.FC = () => {
}, []);
return (
<div>
<h2>User Projects</h2>
<div className="border-2 border-black bg-white p-2 rounded-lg text-center">
<ul>
{projects.map((project) => (
<li key={project.id}>
<span>{project.name}</span>
{/* Add any additional project details you want to display */}
</li>
))}
</ul>
</div>
);
};
}
export default UserProjectListAdmin;

View file

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

View file

@ -1,8 +1,14 @@
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 = <></>;
const content = (
<>
<ChangeUsername />
</>
);
const buttons = (
<>
@ -13,13 +19,7 @@ function AdminChangeUsername(): JSX.Element {
}}
type="button"
/>
<Button
text="Back"
onClick={(): void => {
return;
}}
type="button"
/>
<BackButton />
</>
);

View file

@ -2,15 +2,13 @@ import BasicWindow from "../../Components/BasicWindow";
import Button from "../../Components/Button";
import BackButton from "../../Components/BackButton";
import { UserListAdmin } from "../../Components/UserListAdmin";
import { PublicUser } from "../../Types/goTypes";
import { useNavigate } from "react-router-dom";
import GetAllUsers from "../../Components/GetAllUsers";
import { useState } from "react";
function AdminManageUsers(): JSX.Element {
//TODO: Change so that it reads users from database
const users: PublicUser[] = [];
for (let i = 1; i <= 20; i++) {
users.push({ userId: "id" + i, username: "Example User " + i });
}
const [users, setUsers] = useState<string[]>([]);
GetAllUsers({ setUsersProp: setUsers });
const navigate = useNavigate();

View file

@ -11,9 +11,7 @@ function App(): JSX.Element {
if (authority === 1) {
navigate("/admin");
} else if (authority === 2) {
navigate("/pm");
} else if (authority === 3) {
navigate("/user");
navigate("/yourProjects");
}
}, [authority, navigate]);

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,7 +1,6 @@
import BackButton from "../../Components/BackButton";
import BasicWindow from "../../Components/BasicWindow";
import Button from "../../Components/Button";
import NewWeeklyReport from "../../Components/NewWeeklyReport";
import { Link } from "react-router-dom";
function UserNewTimeReportPage(): JSX.Element {
const content = (
@ -13,15 +12,7 @@ function UserNewTimeReportPage(): JSX.Element {
const buttons = (
<>
<Link to="/project">
<Button
text="Back"
onClick={(): void => {
return;
}}
type="button"
/>
</Link>
<BackButton />
</>
);

View file

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

View file

@ -1,11 +1,17 @@
import BasicWindow from "../../Components/BasicWindow";
import BackButton from "../../Components/BackButton";
import { useParams } from "react-router-dom";
import AllTimeReportsInProject from "../../Components/AllTimeReportsInProject";
function UserViewTimeReportsPage(): JSX.Element {
const { projectName } = useParams();
const content = (
<>
<h1 className="font-bold text-[30px] mb-[20px]">Your Time Reports</h1>
{/* Här kan du inkludera logiken för att visa användarens tidrapporter */}
<h1 className="font-bold text-[30px] mb-[20px]">
Your Time Reports In: {projectName}
</h1>
<AllTimeReportsInProject />
</>
);

View file

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

View file

@ -40,6 +40,90 @@ export interface NewWeeklyReport {
*/
testingTime: number /* int */;
}
export interface WeeklyReportList {
/**
* The name of the project, as it appears in the database
*/
projectName: string;
/**
* The week number
*/
week: number /* int */;
/**
* Total time spent on development
*/
developmentTime: number /* int */;
/**
* Total time spent in meetings
*/
meetingTime: number /* int */;
/**
* Total time spent on administrative tasks
*/
adminTime: number /* int */;
/**
* Total time spent on personal projects
*/
ownWorkTime: number /* int */;
/**
* Total time spent on studying
*/
studyTime: number /* int */;
/**
* Total time spent on testing
*/
testingTime: number /* int */;
/**
* The project manager who signed it
*/
signedBy?: number /* int */;
}
export interface WeeklyReport {
/**
* The ID of the report
*/
reportId: number /* int */;
/**
* The user id of the user who submitted the report
*/
userId: number /* int */;
/**
* The name of the project, as it appears in the database
*/
projectId: number /* int */;
/**
* The week number
*/
week: number /* int */;
/**
* Total time spent on development
*/
developmentTime: number /* int */;
/**
* Total time spent in meetings
*/
meetingTime: number /* int */;
/**
* Total time spent on administrative tasks
*/
adminTime: number /* int */;
/**
* Total time spent on personal projects
*/
ownWorkTime: number /* int */;
/**
* Total time spent on studying
*/
studyTime: number /* int */;
/**
* Total time spent on testing
*/
testingTime: number /* int */;
/**
* The project manager who signed it
*/
signedBy?: number /* int */;
}
//////////
// source: project.go
@ -60,6 +144,20 @@ export interface NewProject {
name: string;
description: string;
}
/**
* Used to change the role of a user in a project.
* If name is identical to the name contained in the token, the role can be changed.
* If the name is different, only a project manager can change the role.
*/
export interface RoleChange {
username: string;
role: 'project_manager' | 'user';
projectname: string;
}
export interface NameChange {
id: number /* int */;
name: string;
}
//////////
// source: users.go
@ -86,3 +184,13 @@ export interface PublicUser {
userId: string;
username: string;
}
/**
* wrapper type for token
*/
export interface Token {
token: string;
}
export interface StrNameChange {
prevName: string;
newName: string;
}

View file

@ -29,47 +29,42 @@ import AdminProjectManageMembers from "./Pages/AdminPages/AdminProjectManageMemb
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 UnauthorizedPage from "./Pages/UnauthorizedPage.tsx";
// This is where the routes are mounted
const router = createBrowserRouter([
{
path: "/",
element: <App />,
errorElement: <NotFoundPage />,
},
{
path: "/admin",
element: <AdminMenuPage />,
},
{
path: "/pm",
element: <YourProjectsPage />,
},
{
path: "/user",
element: <YourProjectsPage />,
},
{
path: "/yourProjects",
element: <YourProjectsPage />,
},
{
path: "/editTimeReport",
element: <UserEditTimeReportPage />,
},
{
path: "/newTimeReport",
element: <UserNewTimeReportPage />,
},
{
path: "/project",
path: "/project/:projectName",
element: <UserProjectPage />,
},
{
path: "/projectPage",
path: "/newTimeReport/:projectName",
element: <UserNewTimeReportPage />,
},
{
path: "/timeReports/:projectName",
element: <UserViewTimeReportsPage />,
},
{
path: "/changeRole",
path: "/editTimeReport/:projectName/:weekNumber",
element: <UserEditTimeReportPage />,
},
{
path: "/changeRole/:projectName/:username",
element: <PMChangeRole />,
},
{
@ -77,11 +72,11 @@ const router = createBrowserRouter([
element: <PMOtherUsersTR />,
},
{
path: "/projectMembers",
path: "/projectMembers/:projectName",
element: <PMProjectMembers />,
},
{
path: "/PMProjectPage",
path: "/PMProjectPage/:projectName",
element: <PMProjectPage />,
},
{
@ -93,7 +88,7 @@ const router = createBrowserRouter([
element: <PMTotalTimeRole />,
},
{
path: "/PMUnsignedReports",
path: "/unsignedReports/:projectName",
element: <PMUnsignedReports />,
},
{
@ -148,6 +143,10 @@ const router = createBrowserRouter([
path: "/adminManageUser",
element: <AdminManageUsers />,
},
{
path: "/unauthorized",
element: <UnauthorizedPage />,
},
]);
// Semi-hacky way to get the root element

View file

@ -2,6 +2,16 @@ import requests
import string
import random
debug_output = False
def gprint(*args, **kwargs):
print("\033[92m", *args, "\033[00m", **kwargs)
print("Running Tests...")
def dprint(*args, **kwargs):
if debug_output:
print(*args, **kwargs)
def randomString(len=10):
"""Generate a random string of fixed length"""
@ -10,8 +20,8 @@ def randomString(len=10):
# Defined once per test run
username = randomString()
projectName = randomString()
username = "user_" + randomString()
projectName = "project_" + randomString()
# The base URL of the API
base_url = "http://localhost:8080"
@ -27,11 +37,50 @@ 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"
#ta bort auth i handlern för att få testet att gå igenom
def test_ProjectRoleChange():
dprint("Testing ProjectRoleChange")
localUsername = randomString()
localProjectName = randomString()
register(localUsername, "username_password")
token = login(localUsername, "username_password").json()[
"token"
]
# Just checking since this test is built somewhat differently than the others
assert token != None, "Login failed"
response = requests.post(
addProjectPath,
json={"name": localProjectName, "description": "This is a project"},
headers={"Authorization": "Bearer " + token},
)
if response.status_code != 200:
print("Add project failed")
response = requests.post(
ProjectRoleChangePath,
headers={"Authorization": "Bearer " + token},
json={
"projectName": localProjectName,
"role": "project_manager",
},
)
assert response.status_code == 200, "ProjectRoleChange failed"
gprint("test_ProjectRoleChange successful")
def test_get_user_projects():
print("Testing get user projects")
dprint("Testing get user projects")
loginResponse = login("user2", "123")
# Check if the user is added to the project
response = requests.get(
@ -39,29 +88,29 @@ def test_get_user_projects():
json={"username": "user2"},
headers={"Authorization": "Bearer " + loginResponse.json()["token"]},
)
print(response.text)
print(response.json())
dprint(response.text)
dprint(response.json())
assert response.status_code == 200, "Get user projects failed"
print("got user projects successfully")
gprint("test_get_user_projects successful")
# Posts the username and password to the register endpoint
def register(username: string, password: string):
print("Registering with username: ", username, " and password: ", password)
dprint("Registering with username: ", username, " and password: ", password)
response = requests.post(
registerPath, json={"username": username, "password": password}
)
print(response.text)
dprint(response.text)
return response
# Posts the username and password to the login endpoint
def login(username: string, password: string):
print("Logging in with username: ", username, " and password: ", password)
dprint("Logging in with username: ", username, " and password: ", password)
response = requests.post(
loginPath, json={"username": username, "password": password}
)
print(response.text)
dprint(response.text)
return response
@ -69,7 +118,8 @@ def login(username: string, password: string):
def test_login():
response = login(username, "always_same")
assert response.status_code == 200, "Login failed"
print("Login successful")
dprint("Login successful")
gprint("test_login successful")
return response.json()["token"]
@ -77,8 +127,7 @@ def test_login():
def test_create_user():
response = register(username, "always_same")
assert response.status_code == 200, "Registration failed"
print("Registration successful")
gprint("test_create_user successful")
# Test function to add a project
def test_add_project():
@ -89,10 +138,9 @@ def test_add_project():
json={"name": projectName, "description": "This is a project"},
headers={"Authorization": "Bearer " + token},
)
print(response.text)
dprint(response.text)
assert response.status_code == 200, "Add project failed"
print("Add project successful")
gprint("test_add_project successful")
# Test function to submit a report
def test_submit_report():
@ -111,10 +159,9 @@ def test_submit_report():
},
headers={"Authorization": "Bearer " + token},
)
print(response.text)
dprint(response.text)
assert response.status_code == 200, "Submit report failed"
print("Submit report successful")
gprint("test_submit_report successful")
# Test function to get a weekly report
def test_get_weekly_report():
@ -124,8 +171,9 @@ def test_get_weekly_report():
headers={"Authorization": "Bearer " + token},
params={"username": username, "projectName": projectName, "week": 1},
)
print(response.text)
dprint(response.text)
assert response.status_code == 200, "Get weekly report failed"
gprint("test_get_weekly_report successful")
# Tests getting a project by id
@ -135,8 +183,9 @@ def test_get_project():
getProjectPath + "/1", # Assumes that the project with id 1 exists
headers={"Authorization": "Bearer " + token},
)
print(response.text)
dprint(response.text)
assert response.status_code == 200, "Get project failed"
gprint("test_get_project successful")
# Test function to add a user to a project
@ -144,13 +193,13 @@ def test_add_user_to_project():
# Log in as a site admin
admin_username = randomString()
admin_password = "admin_password"
print(
dprint(
"Registering with username: ", admin_username, " and password: ", admin_password
)
response = requests.post(
registerPath, json={"username": admin_username, "password": admin_password}
)
print(response.text)
dprint(response.text)
admin_token = login(admin_username, admin_password).json()["token"]
response = requests.post(
@ -158,9 +207,9 @@ def test_add_user_to_project():
json={"username": admin_username},
headers={"Authorization": "Bearer " + admin_token},
)
print(response.text)
dprint(response.text)
assert response.status_code == 200, "Promote to site admin failed"
print("Admin promoted to site admin successfully")
dprint("Admin promoted to site admin successfully")
# Create a new user to add to the project
new_user = randomString()
@ -173,10 +222,9 @@ def test_add_user_to_project():
headers={"Authorization": "Bearer " + admin_token},
)
print(response.text)
dprint(response.text)
assert response.status_code == 200, "Add user to project failed"
print("Add user to project successful")
gprint("test_add_user_to_project successful")
# Test function to sign a report
def test_sign_report():
@ -187,13 +235,13 @@ def test_sign_report():
# Register an admin
admin_username = randomString()
admin_password = "admin_password2"
print(
dprint(
"Registering with username: ", admin_username, " and password: ", admin_password
)
response = requests.post(
registerPath, json={"username": admin_username, "password": admin_password}
)
print(response.text)
dprint(response.text)
# Log in as the admin
admin_token = login(admin_username, admin_password).json()["token"]
@ -213,7 +261,7 @@ def test_sign_report():
headers={"Authorization": "Bearer " + admin_token},
)
assert response.status_code == 200, "Add project manager to project failed"
print("Project manager added to project successfully")
dprint("Project manager added to project successfully")
# Log in as the project manager
project_manager_token = login(project_manager, "project_manager_password").json()[
@ -237,7 +285,7 @@ def test_sign_report():
headers={"Authorization": "Bearer " + token},
)
assert response.status_code == 200, "Submit report failed"
print("Submit report successful")
dprint("Submit report successful")
# Retrieve the report ID
response = requests.get(
@ -245,7 +293,7 @@ def test_sign_report():
headers={"Authorization": "Bearer " + token},
params={"username": username, "projectName": projectName, "week": 1},
)
print(response.text)
dprint(response.text)
report_id = response.json()["reportId"]
# Sign the report as the project manager
@ -255,7 +303,7 @@ def test_sign_report():
headers={"Authorization": "Bearer " + project_manager_token},
)
assert response.status_code == 200, "Sign report failed"
print("Sign report successful")
dprint("Sign report successful")
# Retrieve the report ID again for confirmation
response = requests.get(
@ -263,7 +311,61 @@ def test_sign_report():
headers={"Authorization": "Bearer " + token},
params={"username": username, "projectName": projectName, "week": 1},
)
print(response.text)
dprint(response.text)
gprint("test_sign_report successful")
# Test function to get weekly reports for a user in a project
def test_get_weekly_reports_user():
# Log in as the user
token = login(username, "always_same").json()["token"]
# Get weekly reports for the user in the project
response = requests.get(
getWeeklyReportsUserPath + "/" + projectName,
headers={"Authorization": "Bearer " + token},
)
dprint(response.text)
assert response.status_code == 200, "Get weekly reports for user failed"
gprint("test_get_weekly_reports_user successful")
# Test function to check if a user is a project manager
def test_check_if_project_manager():
# Log in as the user
token = login(username, "always_same").json()["token"]
# Check if the user is a project manager for the project
response = requests.get(
checkIfProjectManagerPath + "/" + projectName,
headers={"Authorization": "Bearer " + token},
)
dprint(response.text)
assert response.status_code == 200, "Check if project manager failed"
gprint("test_check_if_project_manager successful")
def test_ensure_manager_of_created_project():
# Create a new user to add to the project
newUser = "karen_" + randomString()
newProject = "HR_" + randomString()
register(newUser, "new_user_password")
token = login(newUser, "new_user_password").json()["token"]
# Create a new project
response = requests.post(
addProjectPath,
json={"name": newProject, "description": "This is a project"},
headers={"Authorization": "Bearer " + token},
)
assert response.status_code == 200, "Add project failed"
response = requests.get(
checkIfProjectManagerPath + "/" + newProject,
headers={"Authorization": "Bearer " + token},
)
assert response.status_code == 200, "Check if project manager failed"
assert response.json()["isProjectManager"] == True, "User is not project manager"
gprint("test_ensure_admin_of_created_project successful")
if __name__ == "__main__":
@ -276,3 +378,8 @@ if __name__ == "__main__":
test_get_project()
test_sign_report()
test_add_user_to_project()
test_get_weekly_reports_user()
test_check_if_project_manager()
test_ProjectRoleChange()
#test_list_all_users_project()
test_ensure_manager_of_created_project()