Compare commits

...

139 commits

Author SHA1 Message Date
al8763be
c466a98b15 test for getUnsigned 2024-03-29 12:43:35 +01:00
Imbus
9edcc74ee2 Fix for IsProjectManagerHandler 2024-03-29 12:41:51 +01:00
al8763be
8d7d815745 Merge remote-tracking branch 'origin/gruppDM' into BumBranch 2024-03-28 16:46:29 +01:00
al8763be
09f2a2202f Merge remote-tracking branch 'origin/frontend' into BumBranch 2024-03-28 16:45:39 +01:00
al8763be
5d714bbacf Merge remote-tracking branch 'origin/AlexTester' into BumBranch 2024-03-28 16:44:29 +01:00
al8763be
9a54175d49 Co-authored-by: Imbus <imbus64@users.noreply.github.com> 2024-03-28 16:43:34 +01:00
al8763be
f6dcdcc376 GetUnsignedReports 2024-03-28 16:40:55 +01:00
Peter KW
218b0b3ab7 Merge branch 'frontend' into gruppPP 2024-03-28 12:41:43 +01:00
Peter KW
a96f0fb488 Can now delete user, and also asks user to confirm action 2024-03-28 12:39:23 +01:00
Peter KW
48e53b8768 Added some alerts 2024-03-28 12:38:40 +01:00
Peter KW
3d9bd2ef96 Fixed bugs in removeUser api 2024-03-28 12:38:12 +01:00
Peter KW
50042ded41 Fix in handler 2024-03-28 12:37:04 +01:00
Davenludd
4a71078f2a Fix API endpoint for checking project manager 2024-03-28 12:25:24 +01:00
Davenludd
c002f0e530 Merge remote-tracking branch 'origin/AlexTester' into gruppDM 2024-03-28 12:19:45 +01:00
al8763be
6a7bb9ab26 fix ifPM igen 2024-03-28 12:19:11 +01:00
Davenludd
cc186f8ad0 Refactor handleProjectClick function to remove unused username parameter 2024-03-28 12:16:35 +01:00
Davenludd
e589726ab2 Merge remote-tracking branch 'origin/AlexTester' into gruppDM 2024-03-28 12:10:53 +01:00
Davenludd
37d06b10be Fix typo in route parameter name 2024-03-28 12:09:16 +01:00
Davenludd
48e4d1a8df Refactor fetchWeeklyReport in EditWeeklyReport component 2024-03-28 12:09:09 +01:00
al8763be
6a78e67e7e lol 2024-03-28 12:08:14 +01:00
al8763be
7c46797634 ifPM fix 2024-03-28 12:06:59 +01:00
Peter KW
cef7b34092 Small change to newprojmember type 2024-03-28 01:36:48 +01:00
Peter KW
9052babaae Now uses addUserToProject 2024-03-28 01:35:36 +01:00
Peter KW
752d366cf7 Added some alerts 2024-03-28 01:35:16 +01:00
Peter KW
19cb8dbd94 Small fixes 2024-03-28 01:34:49 +01:00
Peter KW
b6d1a2926e Moved MemberAdd to own component and changed design a little 2024-03-28 01:33:03 +01:00
Peter KW
dc3f91eee1 Add member component 2024-03-28 01:31:53 +01:00
Peter KW
0299bb4779 Removed unused path 2024-03-28 01:31:35 +01:00
Peter KW
99a1430188 Fix to addUserToProject API 2024-03-28 00:57:16 +01:00
Peter KW
36c158b54b Insert user "admin" to site_admin in sample_data 2024-03-28 00:27:07 +01:00
Alexander Ek
d4cc556366 Co-authored-by: al8763be <al8763be@users.noreply.github.com> 2024-03-27 21:18:44 +01:00
Davenludd
7c73a01d4c Add success alert message after submitting weekly report 2024-03-27 20:47:24 +01:00
Davenludd
0fc8957e55 Merge branch 'master' into gruppDM 2024-03-27 20:38:31 +01:00
Imbus
e1b410c850 Merge branch 'dev' 2024-03-26 04:19:27 +01:00
Imbus
94f36d4e06 Cleaning 2024-03-23 20:32:00 +01:00
Imbus
96238ceb2e Fix asset resolution at build time 2024-03-23 20:29:08 +01:00
Alexander Ek
42a2ad02e4 Description comment in main.go file 2024-03-23 15:22:58 +01:00
Davenludd
492cfed08c Merge branch 'dev' into gruppDM 2024-03-21 18:09:17 +01:00
Davenludd
6ed53fe94a retract changes made in error handling in SubmitWeeklyReport handler 2024-03-21 18:07:56 +01:00
Imbus
dbbe4da401 Fix for submitWeeklyReport ts API 2024-03-21 18:05:41 +01:00
Davenludd
8d739396a1 Change input types from number to text in ViewOtherTimeReport component 2024-03-21 14:01:33 +01:00
Davenludd
689daf4e1f Updated Link in ProjectMembers component 2024-03-21 14:01:19 +01:00
Davenludd
1d054e660c Fixes for PMOtherUsersTR 2024-03-21 14:00:47 +01:00
Davenludd
589a135bb5 New Page for PM 2024-03-21 13:59:57 +01:00
Davenludd
4c22ba478d Add OtherUsersTR component for displaying weekly report of a user in a project 2024-03-21 13:59:24 +01:00
Davenludd
deeff6c3c2 Added AllTimeReportsInProjectOtherUser component 2024-03-21 13:59:10 +01:00
Davenludd
e47f12c6d7 Add new route for PM components 2024-03-21 13:58:45 +01:00
Davenludd
b656204457 Added component for PM to view other users time reports 2024-03-21 12:46:05 +01:00
Mattias
9f931a2643 Add heading to Edit Time Report page 2024-03-21 12:37:44 +01:00
Mattias
a31a50965f Update routes and components for PMViewUnsignedReport 2024-03-21 12:36:14 +01:00
Davenludd
ebc59e0c11 Refactor handleNewWeeklyReport function to return a boolean indicating success or failure if week already has a report 2024-03-21 12:31:16 +01:00
Davenludd
d8a73329a1 Refactor error handling in SubmitWeeklyReport handler 2024-03-21 12:31:16 +01:00
Mattias
1b20173ece Remove unused import and add DisplayUnsignedReports component 2024-03-21 12:10:53 +01:00
Mattias
4b6c93a202 Add DisplayUnsignedReports component and update route for otherUsersTimeReports 2024-03-21 12:10:43 +01:00
Mattias
70e6cbf12e Update project members page layout and remove unnecessary links 2024-03-21 11:30:07 +01:00
Davenludd
856ae40900 Refactor input fields to handle empty values 2024-03-21 11:10:37 +01:00
Melker
ec362cfa3a GetProjectTimesHandler 2024-03-21 10:51:47 +01:00
Mattias
e2d2310275 Add ViewOtherTimeReport component to PMOtherUsersTR page 2024-03-21 10:31:24 +01:00
Mattias
85be4c79d6 Refactor getProjectMembers function in ProjectMembers component 2024-03-21 10:05:51 +01:00
Davenludd
ae0208ff23 Add project navigation based on user and pm role 2024-03-21 10:02:59 +01:00
Mattias
14133a9f22 CleanUp, making lint happy 2024-03-21 09:59:12 +01:00
Mattias
c415539904 Refactor ProjectMembers component to use API for fetching project members 2024-03-21 09:36:08 +01:00
Davenludd
01a6f9e61d Add comment NewWeeklyReport 2024-03-21 08:57:56 +01:00
Davenludd
faf998e652 Merge branch 'dev' into gruppDM 2024-03-21 08:56:07 +01:00
Peter KW
c9e28e9b5b New type to match fetch 2024-03-21 03:39:53 +01:00
Peter KW
b2db9c54ca Add member functionality added 2024-03-21 03:39:18 +01:00
Peter KW
c5bc6c1c58 Add user to project component 2024-03-21 03:37:37 +01:00
Peter KW
2694beb0e8 AddUserToProject API 2024-03-21 03:36:57 +01:00
Peter KW
a73432669c New path 2024-03-21 03:36:30 +01:00
pavel Hamawand
e9eb2e9ab6 checks 2024-03-21 02:51:28 +01:00
dDogge
2d4ff7e087 Fully implemented UpdateWeeklyReport for database, handler and corresponding tests 2024-03-21 02:48:55 +01:00
pavel Hamawand
baf11f19d6 added token 2024-03-21 02:47:51 +01:00
Imbus
b484346031 Better integration test target 2024-03-21 02:26:30 +01:00
pavel Hamawand
1950112202 implementing changeUsername component 2024-03-21 02:22:23 +01:00
pavel Hamawand
3e11b87eee Modify the implementation of the changeUserName method in the api object 2024-03-21 01:58:57 +01:00
pavel Hamawand
9e2a3cca81 Update the method signature in the API interface to use StrNameChange 2024-03-21 01:56:27 +01:00
Imbus
ed88220a47 Fix containerfile permission errors 2024-03-21 01:24:17 +01:00
Imbus
d4547e997c Merge branch 'dev' 2024-03-21 00:48:44 +01:00
Imbus
89ba0415f7 Merge branch 'melle' into dev 2024-03-21 00:44:07 +01:00
dDogge
13720d633f Fixed test_change_user_name and added test_list_users_project once again 2024-03-21 00:41:40 +01:00
Imbus
a3bf3a04ac Merge branch 'melle' of github.com:imbus64/TTime into melle 2024-03-21 00:33:27 +01:00
Melker
6bc09c656a GetProjectTimes och tester till det 2024-03-21 00:32:38 +01:00
dDogge
44f6b31056 Handler for ChangeUserName changed and corresponding test added 2024-03-21 00:17:08 +01:00
Melker
e7acfb37b1 GetProjectTimes och tester till det 2024-03-21 00:09:15 +01:00
Imbus
a6d7ee2de6 Merge branch 'dev' 2024-03-21 00:03:51 +01:00
Imbus
acdee28eb0 Fix for EditWeeklyReport component 2024-03-21 00:03:15 +01:00
Imbus
03f6edd320 Merge branch 'gruppPP' into frontend 2024-03-20 23:55:29 +01:00
Peter KW
e63b45f38a Changes so that it shows relevant info depending on if managing project member or a system user 2024-03-20 23:49:09 +01:00
Peter KW
e8262ed5e0 Now shows users in project when clicked 2024-03-20 23:45:50 +01:00
Peter KW
6dfc31832c Fixes to design 2024-03-20 23:44:36 +01:00
Peter KW
b2333d8cbd Removed unused file 2024-03-20 23:43:13 +01:00
al8763be
e839d02cfd Merge branch 'dev' into frontend 2024-03-20 23:37:56 +01:00
al8763be
3f2e104078 Merge branch 'dev' of https://github.com/imbus64/TTime into dev 2024-03-20 23:32:07 +01:00
borean
28935f285b fixed typo 2024-03-20 23:25:50 +01:00
al8763be
01b934969a fixed dep array 2024-03-20 23:24:28 +01:00
Imbus
b7523cf04d Merge branch 'dev' into imbs 2024-03-20 23:20:04 +01:00
Imbus
740289decc Merge branch 'dev' into imbs 2024-03-20 22:52:13 +01:00
Davenludd
a4bd2c9315 Merge branch 'dev' into gruppDM 2024-03-20 22:50:56 +01:00
Mattias
1a87effc3a Changes to EditWeeklyReport component 2024-03-20 22:49:54 +01:00
Davenludd
d0209094a9 Remove unused variable in EditWeeklyReport component 2024-03-20 22:10:53 +01:00
Davenludd
f91f0ff8f5 Merge branch 'dev' into gruppDM 2024-03-20 22:05:04 +01:00
Davenludd
c995dc52ad Merge branch 'dev' into gruppDM 2024-03-20 22:04:11 +01:00
Davenludd
900797facc Fix initial state values and handle empty input values in NewWeeklyReport component 2024-03-20 21:53:27 +01:00
Davenludd
b9b17bf229 Merge branch 'dev' into gruppDM 2024-03-20 21:21:59 +01:00
Peter KW
e4a0246b84 Merge branch 'frontend' into gruppPP 2024-03-20 18:41:01 +01:00
Peter KW
9b30b82237 Gets all project members and displays them properly now 2024-03-20 18:38:37 +01:00
Peter KW
bca12151b7 Added new type to match fetch for project members 2024-03-20 18:36:45 +01:00
Peter KW
8250c92694 Fixed so that it uses new type 2024-03-20 18:36:10 +01:00
Peter KW
d1be752ac3 getAllUsersProject uses new type 2024-03-20 18:35:02 +01:00
Davenludd
bf59503517 Update background images in LoginPage.css 2024-03-20 17:24:54 +01:00
Peter KW
cd452459c2 getAllUsersProject API, fetches all users in a project 2024-03-20 17:02:36 +01:00
Peter KW
056ca3660d GetUsersInProject component 2024-03-20 16:59:54 +01:00
Davenludd
e4f5fbda44 Fix useEffect dependency in AllTimeReportsInProject component 2024-03-20 16:46:44 +01:00
Mattias
b99de71c38 Update links in PMProjectMembers component 2024-03-20 16:41:32 +01:00
Mattias
0befc4c7d1 Minor fix 2024-03-20 16:36:39 +01:00
Mattias
a3fc71f4bf Changed path in main 2024-03-20 16:36:32 +01:00
Mattias
4030031ce9 New comp TimePerActivity 2024-03-20 16:36:24 +01:00
Mattias
dd02c2c5c6 Brand new TimePerRole component deluxe edition 2024-03-20 16:24:38 +01:00
Mattias
287e97fe6f Updated path in main 2024-03-20 16:24:10 +01:00
pavel Hamawand
75e835895a minor fix 2024-03-20 15:48:45 +01:00
pavel Hamawand
d94f21ab67 minor fix 2024-03-20 15:47:34 +01:00
pavel Hamawand
ebb009bd20 BackButton 2024-03-20 15:33:56 +01:00
pavel Hamawand
32d9cc4684 backButton 2024-03-20 15:33:26 +01:00
pavel Hamawand
d3938f76e1 backButton 2024-03-20 15:32:40 +01:00
Davenludd
8084f722b5 Fixies in API and component code 2024-03-20 15:32:30 +01:00
Davenludd
caa793d036 Fix week validation in NewWeeklyReport component for safari and firefox 2024-03-20 15:32:14 +01:00
pavel Hamawand
196446be1f Backbutton 2024-03-20 15:31:18 +01:00
pavel Hamawand
0076a9f4bb backButton 2024-03-20 15:30:16 +01:00
Davenludd
847073c6f8 Merge branch 'BumBranch' into gruppDM 2024-03-20 15:21:26 +01:00
Davenludd
54e42cd2a8 Add support for week input in Chrome and Edge browsers 2024-03-20 15:19:09 +01:00
Davenludd
eddf678f3a Merge branch 'BumBranch' into gruppDM 2024-03-20 14:48:32 +01:00
Peter KW
33b269e0c9 Now uses GetProjects 2024-03-20 12:25:59 +01:00
Peter KW
cea2b6c03c GetProjects compo 2024-03-20 12:25:07 +01:00
Peter KW
ae96f67630 Merge branch 'dev' into gruppPP 2024-03-20 11:55:05 +01:00
Peter KW
6317c7674c Now uses projectListAdmin to show projects 2024-03-20 11:53:27 +01:00
Peter KW
e271794b57 List for showing all projects as admin 2024-03-20 11:51:50 +01:00
Peter KW
a5adec82e2 Modul for showing projectinfo *not finished* 2024-03-20 11:51:04 +01:00
Johanna
1974607fc7 Formating 2024-03-19 22:04:20 +01:00
Johanna
63fd2e9b6f //fix parse in NewWeeklyReport 2024-03-19 21:47:16 +01:00
59 changed files with 2394 additions and 472 deletions

3
.gitignore vendored
View file

@ -15,6 +15,9 @@ backend/*.png
backend/*.jpg backend/*.jpg
backend/*.svg backend/*.svg
/go.work.sum
/package-lock.json
# Test binary, built with `go test -c` # Test binary, built with `go test -c`
*.test *.test

View file

@ -23,10 +23,13 @@ load-release file:
# Tests every part of the project # Tests every part of the project
testall: testall:
cd frontend && npm install
cd frontend && npm test cd frontend && npm test
cd frontend && npm run lint cd frontend && npm run lint
cd frontend && npm run build
cd backend && make test cd backend && make test
cd backend && make lint cd backend && make lint
cd backend && make itest
# Cleans up everything related to the project # Cleans up everything related to the project
clean: remove-podman-containers clean: remove-podman-containers

View file

@ -13,10 +13,13 @@ remove-podman-containers:
# Tests every part of the project # Tests every part of the project
testall: testall:
cd frontend && npm install
cd frontend && npm test cd frontend && npm test
cd frontend && npm run lint cd frontend && npm run lint
cd frontend && npm run build
cd backend && make test cd backend && make test
cd backend && make lint cd backend && make lint
cd backend && make itest
# Cleans up everything related to the project # Cleans up everything related to the project
clean: remove-podman-containers clean: remove-podman-containers

View file

@ -8,17 +8,19 @@ GOGET = $(GOCMD) get
# SQLite database filename # SQLite database filename
DB_FILE = db.sqlite3 DB_FILE = db.sqlite3
PROC_NAME = ttime_server
# Directory containing migration SQL scripts # Directory containing migration SQL scripts
MIGRATIONS_DIR = internal/database/migrations MIGRATIONS_DIR = internal/database/migrations
SAMPLE_DATA_DIR = internal/database/sample_data SAMPLE_DATA_DIR = internal/database/sample_data
# Build target # Build target
build: build:
$(GOBUILD) -o bin/server main.go $(GOBUILD) -o bin/$(PROC_NAME) main.go
# Run target # Run target
run: build run: build
./bin/server ./bin/$(PROC_NAME)
watch: build watch: build
watchexec -c -w . -r make run watchexec -c -w . -r make run
@ -37,6 +39,16 @@ clean:
test: db.sqlite3 test: db.sqlite3
$(GOTEST) ./... -count=1 $(GOTEST) ./... -count=1
# Integration test target
.PHONY: itest
itest:
pgrep $(PROC_NAME) && echo "Server already running" && exit 1 || true
make build
./bin/$(PROC_NAME) >/dev/null 2>&1 &
sleep 1 # Adjust if needed
python ../testing.py
pkill $(PROC_NAME)
# Get dependencies target # Get dependencies target
deps: deps:
$(GOGET) -v ./... $(GOGET) -v ./...

View file

@ -40,7 +40,9 @@ type Database interface {
SignWeeklyReport(reportId int, projectManagerId int) error SignWeeklyReport(reportId int, projectManagerId int) error
IsSiteAdmin(username string) (bool, error) IsSiteAdmin(username string) (bool, error)
IsProjectManager(username string, projectname string) (bool, error) IsProjectManager(username string, projectname string) (bool, error)
GetTotalTimePerActivity(projectName string) (map[string]int, error) GetProjectTimes(projectName string) (map[string]int, error)
UpdateWeeklyReport(projectName string, userName string, week int, developmentTime int, meetingTime int, adminTime int, ownWorkTime int, studyTime int, testingTime int) error
RemoveProject(projectname string) error
} }
// This struct is a wrapper type that holds the database connection // This struct is a wrapper type that holds the database connection
@ -498,6 +500,26 @@ func (d *Db) IsProjectManager(username string, projectname string) (bool, error)
return manager, err return manager, err
} }
func (d *Db) UpdateWeeklyReport(projectName string, userName string, week int, developmentTime int, meetingTime int, adminTime int, ownWorkTime int, studyTime int, testingTime int) error {
query := `
UPDATE weekly_reports
SET
development_time = ?,
meeting_time = ?,
admin_time = ?,
own_work_time = ?,
study_time = ?,
testing_time = ?
WHERE
user_id = (SELECT id FROM users WHERE username = ?)
AND project_id = (SELECT id FROM projects WHERE name = ?)
AND week = ?
`
_, err := d.Exec(query, developmentTime, meetingTime, adminTime, ownWorkTime, studyTime, testingTime, userName, projectName, week)
return err
}
// MigrateSampleData applies sample data to the database. // MigrateSampleData applies sample data to the database.
func (d *Db) MigrateSampleData() error { func (d *Db) MigrateSampleData() error {
// Insert sample data // Insert sample data
@ -537,11 +559,11 @@ func (d *Db) MigrateSampleData() error {
return nil return nil
} }
func (d *Db) GetTotalTimePerActivity(projectName string) (map[string]int, error) { // GetProjectTimes retrieves a map with times per "Activity" for a given project
func (d *Db) GetProjectTimes(projectName string) (map[string]int, error) {
query := ` query := `
SELECT development_time, meeting_time, admin_time, own_work_time, study_time, testing_time SELECT development_time, meeting_time, admin_time, own_work_time, study_time, testing_time
FROM weekly_reports FROM weekly_reports
JOIN projects ON weekly_reports.project_id = projects.id JOIN projects ON weekly_reports.project_id = projects.id
WHERE projects.name = ? WHERE projects.name = ?
` `
@ -574,3 +596,8 @@ func (d *Db) GetTotalTimePerActivity(projectName string) (map[string]int, error)
return totalTime, nil return totalTime, nil
} }
func (d *Db) RemoveProject(projectname string) error {
_, err := d.Exec("DELETE FROM projects WHERE name = ?", projectname)
return err
}

View file

@ -481,6 +481,11 @@ func TestGetUnsignedWeeklyReports(t *testing.T) {
t.Error("AddUser failed:", err) t.Error("AddUser failed:", err)
} }
err = db.AddUser("testuser1", "password")
if err != nil {
t.Error("AddUser failed:", err)
}
err = db.AddProject("testproject", "description", "testuser") err = db.AddProject("testproject", "description", "testuser")
if err != nil { if err != nil {
t.Error("AddProject failed:", err) t.Error("AddProject failed:", err)
@ -491,6 +496,11 @@ func TestGetUnsignedWeeklyReports(t *testing.T) {
t.Error("AddWeeklyReport failed:", err) t.Error("AddWeeklyReport failed:", err)
} }
err = db.AddWeeklyReport("testproject", "testuser1", 1, 1, 1, 1, 1, 1, 1)
if err != nil {
t.Error("AddWeeklyReport failed:", err)
}
reports, err := db.GetUnsignedWeeklyReports("testproject") reports, err := db.GetUnsignedWeeklyReports("testproject")
if err != nil { if err != nil {
t.Error("GetUnsignedWeeklyReports failed:", err) t.Error("GetUnsignedWeeklyReports failed:", err)
@ -760,27 +770,90 @@ func TestIsProjectManager(t *testing.T) {
} }
} }
func TestGetTotalTimePerActivity(t *testing.T) { func TestGetProjectTimes(t *testing.T) {
// Initialize your test database connection // Initialize
db, err := setupState() db, err := setupState()
if err != nil { if err != nil {
t.Error("setupState failed:", err) t.Error("setupState failed:", err)
return
} }
// Run the query to get total time per activity // Create a user
totalTime, err := db.GetTotalTimePerActivity("projecttest") user := "TeaUser"
password := "Vanilla"
err = db.AddUser(user, password)
if err != nil {
t.Error("AddUser failed:", err)
return
}
// Create a project
projectName := "ProjectVanilla"
projectDescription := "When tea tastes its best"
err = db.AddProject(projectName, projectDescription, user) // Fix the variable name here
if err != nil {
t.Error("AddProject failed:", err)
return
}
// Tests the func in db.go
totalTime, err := db.GetProjectTimes(projectName)
if err != nil { if err != nil {
t.Error("GetTotalTimePerActivity failed:", err) t.Error("GetTotalTimePerActivity failed:", err)
return
} }
// Check if the totalTime map is not nil // Check if the totalTime map is not nil
if totalTime == nil { if totalTime == nil {
t.Error("Expected non-nil totalTime map, got nil") t.Error("Expected non-nil totalTime map, got nil")
return
} }
// ska lägga till fler assertions // Define the expected valeus
} expectedTotalTime := map[string]int{
"development": 0,
"meeting": 0,
"admin": 0,
"own_work": 0,
"study": 0,
"testing": 0,
}
// Compare the expectedTotalTime with the totalTime retrieved from the database
for activity, expectedTime := range expectedTotalTime {
if totalTime[activity] != expectedTime {
t.Errorf("Expected %s time to be %d, got %d", activity, expectedTime, totalTime[activity])
}
}
// Insert some data into the database for different activities
err = db.AddWeeklyReport(projectName, user, 1, 1, 3, 2, 1, 4, 5)
if err != nil {
t.Error("Failed to insert data into the database:", err)
return
}
newTotalTime, err := db.GetProjectTimes(projectName)
if err != nil {
t.Error("GetTotalTimePerActivity failed:", err)
return
}
newExpectedTotalTime := map[string]int{
"development": 1,
"meeting": 3,
"admin": 2,
"own_work": 1,
"study": 4,
"testing": 5,
}
for activity, newExpectedTime := range newExpectedTotalTime {
if newTotalTime[activity] != newExpectedTime {
t.Errorf("Expected %s time to be %d, got %d", activity, newExpectedTime, newTotalTime[activity])
}
}
}
func TestEnsureManagerOfCreatedProject(t *testing.T) { func TestEnsureManagerOfCreatedProject(t *testing.T) {
db, err := setupState() db, err := setupState()
if err != nil { if err != nil {
@ -814,3 +887,81 @@ func TestEnsureManagerOfCreatedProject(t *testing.T) {
t.Error("Expected testuser to be a project manager, but it's not.") t.Error("Expected testuser to be a project manager, but it's not.")
} }
} }
// TestUpdateWeeklyReport tests the UpdateWeeklyReport function of the database
func TestUpdateWeeklyReport(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)
}
// Add a weekly report
err = db.AddWeeklyReport("testproject", "testuser", 1, 1, 1, 1, 1, 1, 1)
if err != nil {
t.Error("AddWeeklyReport failed:", err)
}
// Update the weekly report
err = db.UpdateWeeklyReport("testproject", "testuser", 1, 2, 2, 2, 2, 2, 2)
if err != nil {
t.Error("UpdateWeeklyReport failed:", err)
}
// Retrieve the updated report
updatedReport, err := db.GetWeeklyReport("testuser", "testproject", 1)
if err != nil {
t.Error("GetWeeklyReport failed:", err)
}
// Check if the report was updated correctly
if updatedReport.DevelopmentTime != 2 ||
updatedReport.MeetingTime != 2 ||
updatedReport.AdminTime != 2 ||
updatedReport.OwnWorkTime != 2 ||
updatedReport.StudyTime != 2 ||
updatedReport.TestingTime != 2 {
t.Error("UpdateWeeklyReport failed: report not updated correctly")
}
}
func TestRemoveProject(t *testing.T) {
db, err := setupAdvancedState()
if err != nil {
t.Error("setupState failed:", err)
}
// Promote user to Admin
err = db.PromoteToAdmin("demouser")
if err != nil {
t.Error("PromoteToAdmin failed:", err)
}
// Remove project
err = db.RemoveProject("projecttest")
if err != nil {
t.Error("RemoveProject failed:", err)
}
// Check if the project was removed
projects, err := db.GetAllProjects()
if err != nil {
t.Error("GetAllProjects failed:", err)
}
if len(projects) != 0 {
t.Error("RemoveProject failed: expected 0, got", len(projects))
}
}

View file

@ -7,6 +7,8 @@ VALUES ("user", "123");
INSERT OR IGNORE INTO users(username, password) INSERT OR IGNORE INTO users(username, password)
VALUES ("user2", "123"); VALUES ("user2", "123");
INSERT OR IGNORE INTO site_admin VALUES (1);
INSERT OR IGNORE INTO projects(name,description,owner_user_id) INSERT OR IGNORE INTO projects(name,description,owner_user_id)
VALUES ("projecttest","test project", 1); VALUES ("projecttest","test project", 1);

View file

@ -29,6 +29,8 @@ type GlobalState interface {
ChangeUserName(c *fiber.Ctx) error // WIP ChangeUserName(c *fiber.Ctx) error // WIP
GetAllUsersProject(c *fiber.Ctx) error // WIP GetAllUsersProject(c *fiber.Ctx) error // WIP
GetUnsignedReports(c *fiber.Ctx) error // GetUnsignedReports(c *fiber.Ctx) error //
UpdateWeeklyReport(c *fiber.Ctx) error
RemoveProject(c *fiber.Ctx) error
} }
// "Constructor" // "Constructor"

View file

@ -233,3 +233,83 @@ func (gs *GState) IsProjectManagerHandler(c *fiber.Ctx) error {
// Return the result as JSON // Return the result as JSON
return c.JSON(fiber.Map{"isProjectManager": isManager}) return c.JSON(fiber.Map{"isProjectManager": isManager})
} }
func (gs *GState) GetProjectTimesHandler(c *fiber.Ctx) error {
// Get the username from the token
user := c.Locals("user").(*jwt.Token)
claims := user.Claims.(jwt.MapClaims)
username := claims["name"].(string)
// Get project
projectName := c.Params("projectName")
if projectName == "" {
log.Info("No project name provided")
return c.Status(400).SendString("No project name provided")
}
// Get all users in the project and roles
userProjects, err := gs.Db.GetAllUsersProject(projectName)
if err != nil {
log.Info("Error getting users in project:", err)
return c.Status(500).SendString(err.Error())
}
// If the user is member
isMember := false
for _, userProject := range userProjects {
if userProject.Username == username {
isMember = true
break
}
}
// If the user is admin
if !isMember {
isAdmin, err := gs.Db.IsSiteAdmin(username)
if err != nil {
log.Info("Error checking admin status:", err)
return c.Status(500).SendString(err.Error())
}
if !isAdmin {
log.Info("User is neither a project member nor a site admin:", username)
return c.Status(403).SendString("User is neither a project member nor a site admin")
}
}
// Get project times
projectTimes, err := gs.Db.GetProjectTimes(projectName)
if err != nil {
log.Info("Error getting project times:", err)
return c.Status(500).SendString(err.Error())
}
// Return project times as JSON
log.Info("Returning project times for project:", projectName)
return c.JSON(projectTimes)
}
func (gs *GState) RemoveProject(c *fiber.Ctx) error {
user := c.Locals("user").(*jwt.Token)
claims := user.Claims.(jwt.MapClaims)
username := claims["name"].(string)
// Check if the user is a site admin
isAdmin, err := 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 not a site admin:", username)
return c.Status(403).SendString("User is not a site admin")
}
projectName := c.Params("projectName")
if err := gs.Db.RemoveProject(projectName); err != nil {
return c.Status(500).SendString((err.Error()))
}
return c.Status(200).SendString("Project deleted")
}

View file

@ -122,7 +122,7 @@ func (gs *GState) GetUnsignedReports(c *fiber.Ctx) error {
projectManagerUsername := claims["name"].(string) projectManagerUsername := claims["name"].(string)
// Extract project name and week from query parameters // Extract project name and week from query parameters
projectName := c.Query("projectName") projectName := c.Params("projectName")
log.Info("Getting unsigned reports for") log.Info("Getting unsigned reports for")
@ -177,3 +177,37 @@ func (gs *GState) GetWeeklyReportsUserHandler(c *fiber.Ctx) error {
// Return the list of reports as JSON // Return the list of reports as JSON
return c.JSON(reports) return c.JSON(reports)
} }
func (gs *GState) UpdateWeeklyReport(c *fiber.Ctx) error {
// Extract the necessary parameters from the token
user := c.Locals("user").(*jwt.Token)
claims := user.Claims.(jwt.MapClaims)
username := claims["name"].(string)
// Parse the request body into an UpdateWeeklyReport struct
var updateReport types.UpdateWeeklyReport
if err := c.BodyParser(&updateReport); err != nil {
log.Info("Error parsing weekly report")
return c.Status(400).SendString(err.Error())
}
// Make sure all the fields of the report are valid
if updateReport.Week < 1 || updateReport.Week > 52 {
log.Info("Invalid week number")
return c.Status(400).SendString("Invalid week number")
}
if updateReport.DevelopmentTime < 0 || updateReport.MeetingTime < 0 || updateReport.AdminTime < 0 || updateReport.OwnWorkTime < 0 || updateReport.StudyTime < 0 || updateReport.TestingTime < 0 {
log.Info("Invalid time report")
return c.Status(400).SendString("Invalid time report")
}
// Update the weekly report in the database
if err := gs.Db.UpdateWeeklyReport(updateReport.ProjectName, username, updateReport.Week, updateReport.DevelopmentTime, updateReport.MeetingTime, updateReport.AdminTime, updateReport.OwnWorkTime, updateReport.StudyTime, updateReport.TestingTime); err != nil {
log.Info("Error updating weekly report in db:", err)
return c.Status(500).SendString(err.Error())
}
log.Info("Weekly report updated")
return c.Status(200).SendString("Weekly report updated")
}

View file

@ -59,9 +59,9 @@ func (gs *GState) UserDelete(c *fiber.Ctx) error {
// Read username from Locals // Read username from Locals
auth_username := c.Locals("user").(*jwt.Token).Claims.(jwt.MapClaims)["name"].(string) auth_username := c.Locals("user").(*jwt.Token).Claims.(jwt.MapClaims)["name"].(string)
if username != auth_username { if username == auth_username {
log.Info("User tried to delete another user") log.Info("User tried to delete itself")
return c.Status(403).SendString("You can only delete yourself") return c.Status(403).SendString("You can't delete yourself")
} }
if err := gs.Db.RemoveUser(username); err != nil { if err := gs.Db.RemoveUser(username); err != nil {
@ -207,8 +207,8 @@ func (gs *GState) GetAllUsersProject(c *fiber.Ctx) error {
// @Accept json // @Accept json
// @Produce plain // @Produce plain
// @Param NewUser body types.NewUser true "user info" // @Param NewUser body types.NewUser true "user info"
// @Success 200 {json} json "Successfully prometed user" // @Success 200 {json} json "Successfully promoted user"
// @Failure 400 {string} string "bad request" // @Failure 400 {string} string "Bad request"
// @Failure 401 {string} string "Unauthorized" // @Failure 401 {string} string "Unauthorized"
// @Failure 500 {string} string "Internal server error" // @Failure 500 {string} string "Internal server error"
// @Router /promoteToAdmin [post] // @Router /promoteToAdmin [post]
@ -234,33 +234,33 @@ func (gs *GState) PromoteToAdmin(c *fiber.Ctx) error {
return c.SendStatus(fiber.StatusOK) return c.SendStatus(fiber.StatusOK)
} }
// Changes a users name in the database // ChangeUserName changes a user's username in the database
func (gs *GState) ChangeUserName(c *fiber.Ctx) error { func (gs *GState) ChangeUserName(c *fiber.Ctx) error {
// Check token and get username of current user
//check token and get username of current user
user := c.Locals("user").(*jwt.Token) user := c.Locals("user").(*jwt.Token)
claims := user.Claims.(jwt.MapClaims) claims := user.Claims.(jwt.MapClaims)
projectManagerUsername := claims["name"].(string) adminUsername := claims["name"].(string)
log.Info(projectManagerUsername) log.Info(adminUsername)
// Extract the necessary parameters from the request // Extract the necessary parameters from the request
data := new(types.NameChange) data := new(types.StrNameChange)
if err := c.BodyParser(data); err != nil { if err := c.BodyParser(data); err != nil {
log.Info("error parsing username, project or role") log.Info("Error parsing username")
return c.Status(400).SendString(err.Error()) return c.Status(400).SendString(err.Error())
} }
// dubble diping and checcking if current user is // Check if the current user is an admin
isAdmin, err := gs.Db.IsSiteAdmin(adminUsername)
if ismanager, err := gs.Db.IsProjectManager(projectManagerUsername, c.Params(data.Name)); err != nil { if err != nil {
log.Warn("Error checking if projectmanager:", err) log.Warn("Error checking if admin:", err)
return c.Status(500).SendString(err.Error()) return c.Status(500).SendString(err.Error())
} else if !ismanager { } else if !isAdmin {
log.Warn("tried changing name when not projectmanager:", err) log.Warn("Tried changing name when not admin")
return c.Status(401).SendString("you can not change name when not projectmanager") return c.Status(401).SendString("You cannot change name unless you are an admin")
} }
// Change the user's name within the project in the database // Change the user's name in the database
if err := gs.Db.ChangeUserName(projectManagerUsername, data.Name); err != nil { if err := gs.Db.ChangeUserName(data.PrevName, data.NewName); err != nil {
return c.Status(500).SendString(err.Error()) return c.Status(500).SendString(err.Error())
} }

View file

@ -65,3 +65,24 @@ type WeeklyReport struct {
// The project manager who signed it // The project manager who signed it
SignedBy *int `json:"signedBy" db:"signed_by"` SignedBy *int `json:"signedBy" db:"signed_by"`
} }
type UpdateWeeklyReport struct {
// The name of the project, as it appears in the database
ProjectName string `json:"projectName"`
// The name of the user
UserName string `json:"userName"`
// The week number
Week int `json:"week"`
// Total time spent on development
DevelopmentTime int `json:"developmentTime"`
// Total time spent in meetings
MeetingTime int `json:"meetingTime"`
// Total time spent on administrative tasks
AdminTime int `json:"adminTime"`
// Total time spent on personal projects
OwnWorkTime int `json:"ownWorkTime"`
// Total time spent on studying
StudyTime int `json:"studyTime"`
// Total time spent on testing
TestingTime int `json:"testingTime"`
}

View file

@ -33,6 +33,12 @@ import (
// @externalDocs.description OpenAPI // @externalDocs.description OpenAPI
// @externalDocs.url https://swagger.io/resources/open-api/ // @externalDocs.url https://swagger.io/resources/open-api/
/**
Main function for starting the server and initializing configurations.
Reads configuration from file, pretty prints it, connects to the database,
migrates it, and sets up routes for the server.
*/
func main() { func main() {
conf, err := config.ReadConfigFromFile("config.toml") conf, err := config.ReadConfigFromFile("config.toml")
if err != nil { if err != nil {
@ -92,7 +98,7 @@ func main() {
server.Get("/api/project/:projectId", gs.GetProject) server.Get("/api/project/:projectId", gs.GetProject)
server.Get("/api/project/getAllUsers", gs.GetAllUsersProject) server.Get("/api/project/getAllUsers", gs.GetAllUsersProject)
server.Get("/api/getWeeklyReport", gs.GetWeeklyReport) server.Get("/api/getWeeklyReport", gs.GetWeeklyReport)
server.Get("/api/getUnsignedReports", gs.GetUnsignedReports) server.Get("/api/getUnsignedReports/:projectName", gs.GetUnsignedReports)
server.Post("/api/signReport", gs.SignReport) server.Post("/api/signReport", gs.SignReport)
server.Put("/api/addUserToProject", gs.AddUserToProjectHandler) server.Put("/api/addUserToProject", gs.AddUserToProjectHandler)
server.Put("/api/changeUserName", gs.ChangeUserName) server.Put("/api/changeUserName", gs.ChangeUserName)
@ -102,6 +108,8 @@ func main() {
server.Get("/api/checkIfProjectManager/:projectName", gs.IsProjectManagerHandler) server.Get("/api/checkIfProjectManager/:projectName", gs.IsProjectManagerHandler)
server.Post("/api/ProjectRoleChange", gs.ProjectRoleChange) server.Post("/api/ProjectRoleChange", gs.ProjectRoleChange)
server.Get("/api/getUsersProject/:projectName", gs.ListAllUsersProject) server.Get("/api/getUsersProject/:projectName", gs.ListAllUsersProject)
server.Put("/api/updateWeeklyReport", gs.UpdateWeeklyReport)
server.Delete("/api/removeProject/:projectName", gs.RemoveProject)
// Announce the port we are listening on and start the server // Announce the port we are listening on and start the server
err = server.Listen(fmt.Sprintf(":%d", conf.Port)) err = server.Listen(fmt.Sprintf(":%d", conf.Port))

View file

@ -13,7 +13,6 @@ FROM docker.io/golang:alpine as go
RUN apk add gcompat RUN apk add gcompat
RUN apk add gcc RUN apk add gcc
RUN apk add musl-dev RUN apk add musl-dev
RUN apk add make
RUN apk add sqlite RUN apk add sqlite
WORKDIR /build WORKDIR /build
ADD backend/go.mod backend/go.sum ./ ADD backend/go.mod backend/go.sum ./
@ -24,9 +23,7 @@ RUN go mod download
# Add the source code # Add the source code
ADD backend . ADD backend .
RUN make migrate RUN go build -o server
# RUN go build -o server
RUN CGO_ENABLED=1 GOOS=linux go build -a -installsuffix cgo -o ./server ./main.go RUN CGO_ENABLED=1 GOOS=linux go build -a -installsuffix cgo -o ./server ./main.go
# Strip the binary for a smaller image # Strip the binary for a smaller image
@ -37,6 +34,7 @@ FROM docker.io/alpine:latest as runner
RUN adduser -D nonroot RUN adduser -D nonroot
RUN addgroup nonroot nonroot RUN addgroup nonroot nonroot
WORKDIR /app WORKDIR /app
RUN chown nonroot:nonroot /app
# Copy the frontend SPA build into public # Copy the frontend SPA build into public
COPY --from=client /build/dist static COPY --from=client /build/dist static
@ -44,9 +42,6 @@ COPY --from=client /build/dist static
# Copy the server binary # Copy the server binary
COPY --from=go /build/server server COPY --from=go /build/server server
# Copy the database
COPY --from=go /build/db.sqlite3 db.sqlite3
# Expose port 8080 # Expose port 8080
EXPOSE 8080 EXPOSE 8080

View file

@ -4,7 +4,10 @@ import {
User, User,
Project, Project,
NewProject, NewProject,
UserProjectMember,
WeeklyReport, WeeklyReport,
StrNameChange,
NewProjMember,
} from "../Types/goTypes"; } from "../Types/goTypes";
/** /**
@ -46,7 +49,6 @@ interface API {
* @returns {Promise<APIResponse<boolean>>} A promise containing the API response indicating if the user is a project manager. * @returns {Promise<APIResponse<boolean>>} A promise containing the API response indicating if the user is a project manager.
*/ */
checkIfProjectManager( checkIfProjectManager(
username: string,
projectName: string, projectName: string,
token: string, token: string,
): Promise<APIResponse<boolean>>; ): Promise<APIResponse<boolean>>;
@ -84,7 +86,7 @@ interface API {
submitWeeklyReport( submitWeeklyReport(
weeklyReport: NewWeeklyReport, weeklyReport: NewWeeklyReport,
token: string, token: string,
): Promise<APIResponse<NewWeeklyReport>>; ): Promise<APIResponse<string>>;
/** Gets a weekly report for a specific user, project and week /** Gets a weekly report for a specific user, project and week
* @param {string} projectName The name of the project. * @param {string} projectName The name of the project.
@ -127,6 +129,30 @@ interface API {
* @returns {Promise<APIResponse<string[]>>} A promise resolving to an API response containing the list of users. * @returns {Promise<APIResponse<string[]>>} A promise resolving to an API response containing the list of users.
*/ */
getAllUsers(token: string): Promise<APIResponse<string[]>>; getAllUsers(token: string): Promise<APIResponse<string[]>>;
/** Gets all users in a project from name*/
getAllUsersProject(
projectName: string,
token: string,
): Promise<APIResponse<UserProjectMember[]>>;
/**
* Changes the username of a user in the database.
* @param {StrNameChange} data The object containing the previous and new username.
* @param {string} token The authentication token.
* @returns {Promise<APIResponse<void>>} A promise resolving to an API response.
*/
changeUserName(
data: StrNameChange,
token: string,
): Promise<APIResponse<void>>;
addUserToProject(
user: NewProjMember,
token: string,
): Promise<APIResponse<NewProjMember>>;
removeProject(
projectName: string,
token: string,
): Promise<APIResponse<string>>;
} }
/** An instance of the API */ /** An instance of the API */
@ -164,19 +190,17 @@ export const api: API = {
): Promise<APIResponse<User>> { ): Promise<APIResponse<User>> {
try { try {
const response = await fetch(`/api/userdelete/${username}`, { const response = await fetch(`/api/userdelete/${username}`, {
method: "POST", method: "DELETE",
headers: { headers: {
"Content-Type": "application/json", "Content-Type": "application/json",
Authorization: "Bearer " + token, Authorization: "Bearer " + token,
}, },
body: JSON.stringify(username), body: JSON.stringify(username),
}); });
if (!response.ok) { if (!response.ok) {
return { success: false, message: "Failed to remove user" }; return { success: false, message: "Could not remove user" };
} else { } else {
const data = (await response.json()) as User; return { success: true };
return { success: true, data };
} }
} catch (e) { } catch (e) {
return { success: false, message: "Failed to remove user" }; return { success: false, message: "Failed to remove user" };
@ -184,19 +208,20 @@ export const api: API = {
}, },
async checkIfProjectManager( async checkIfProjectManager(
username: string,
projectName: string, projectName: string,
token: string, token: string,
): Promise<APIResponse<boolean>> { ): Promise<APIResponse<boolean>> {
try { try {
const response = await fetch("/api/checkIfProjectManager", { const response = await fetch(
method: "GET", `/api/checkIfProjectManager/${projectName}`,
headers: { {
"Content-Type": "application/json", method: "GET",
Authorization: "Bearer " + token, headers: {
"Content-Type": "application/json",
Authorization: "Bearer " + token,
},
}, },
body: JSON.stringify({ username, projectName }), );
});
if (!response.ok) { if (!response.ok) {
return { return {
@ -208,7 +233,7 @@ export const api: API = {
return { success: true, data }; return { success: true, data };
} }
} catch (e) { } catch (e) {
return { success: false, message: "fuck" }; return { success: false, message: "Failed to check if project manager" };
} }
}, },
@ -237,6 +262,30 @@ export const api: API = {
} }
}, },
async addUserToProject(
user: NewProjMember,
token: string,
): Promise<APIResponse<NewProjMember>> {
try {
const response = await fetch("/api/addUserToProject", {
method: "PUT",
headers: {
"Content-Type": "application/json",
Authorization: "Bearer " + token,
},
body: JSON.stringify(user),
});
if (!response.ok) {
return { success: false, message: "Failed to add member" };
} else {
return { success: true, message: "Added member" };
}
} catch (e) {
return { success: false, message: "Failed to add member" };
}
},
async renewToken(token: string): Promise<APIResponse<string>> { async renewToken(token: string): Promise<APIResponse<string>> {
try { try {
const response = await fetch("/api/loginrenew", { const response = await fetch("/api/loginrenew", {
@ -288,7 +337,7 @@ export const api: API = {
async submitWeeklyReport( async submitWeeklyReport(
weeklyReport: NewWeeklyReport, weeklyReport: NewWeeklyReport,
token: string, token: string,
): Promise<APIResponse<NewWeeklyReport>> { ): Promise<APIResponse<string>> {
try { try {
const response = await fetch("/api/submitWeeklyReport", { const response = await fetch("/api/submitWeeklyReport", {
method: "POST", method: "POST",
@ -306,8 +355,8 @@ export const api: API = {
}; };
} }
const data = (await response.json()) as NewWeeklyReport; const data = await response.text();
return { success: true, data }; return { success: true, message: data };
} catch (e) { } catch (e) {
return { return {
success: false, success: false,
@ -448,4 +497,88 @@ export const api: API = {
}); });
} }
}, },
//Gets all users in a project
async getAllUsersProject(
projectName: string,
token: string,
): Promise<APIResponse<UserProjectMember[]>> {
try {
const response = await fetch(`/api/getUsersProject/${projectName}`, {
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 UserProjectMember[];
return Promise.resolve({ success: true, data });
}
} catch (e) {
return Promise.resolve({
success: false,
message: "API is not ok",
});
}
},
async changeUserName(
data: StrNameChange,
token: string,
): Promise<APIResponse<void>> {
try {
const response = await fetch("/api/changeUserName", {
method: "PUT",
headers: {
"Content-Type": "application/json",
Authorization: "Bearer " + token,
},
body: JSON.stringify(data),
});
if (!response.ok) {
return { success: false, message: "Failed to change username" };
} else {
return { success: true };
}
} catch (e) {
return { success: false, message: "Failed to change username" };
}
},
async removeProject(
projectName: string,
token: string,
): Promise<APIResponse<string>> {
try {
const response = await fetch(`/api/projectdelete/${projectName}`, {
method: "DELETE",
headers: {
"Content-Type": "application/json",
Authorization: "Bearer " + token,
},
});
if (!response.ok) {
return Promise.resolve({
success: false,
message: "Failed to remove project",
});
} else {
const data = await response.text();
return Promise.resolve({ success: true, message: data });
}
} catch (e) {
return Promise.resolve({
success: false,
message: "Failed to remove project",
});
}
},
}; };

View file

@ -0,0 +1,39 @@
import { APIResponse, api } from "../API/API";
import { NewProjMember } from "../Types/goTypes";
/**
* Tries to add a member to a project
* @param {Object} props - A NewProjMember
* @returns {boolean} True if added, false if not
*/
function AddMember(props: { memberToAdd: NewProjMember }): boolean {
let added = false;
if (
props.memberToAdd.username === "" ||
props.memberToAdd.role === "" ||
props.memberToAdd.projectname === ""
) {
alert("All fields must be filled before adding");
return added;
}
api
.addUserToProject(
props.memberToAdd,
localStorage.getItem("accessToken") ?? "",
)
.then((response: APIResponse<NewProjMember>) => {
if (response.success) {
alert("Member added");
added = true;
} else {
alert("Member not added");
console.error(response.message);
}
})
.catch((error) => {
console.error("An error occurred during member add:", error);
});
return added;
}
export default AddMember;

View file

@ -0,0 +1,92 @@
import { useState } from "react";
import { NewProjMember } from "../Types/goTypes";
import Button from "./Button";
import GetAllUsers from "./GetAllUsers";
import AddMember from "./AddMember";
import BackButton from "./BackButton";
/**
* Provides UI for adding a member to a project.
* @returns {JSX.Element} - Returns the component UI for adding a member
*/
function AddUserToProject(): JSX.Element {
const [name, setName] = useState("");
const [users, setUsers] = useState<string[]>([]);
const [role, setRole] = useState("");
GetAllUsers({ setUsersProp: setUsers });
const handleClick = (): boolean => {
const newMember: NewProjMember = {
username: name,
projectname: localStorage.getItem("projectName") ?? "",
role: role,
};
return AddMember({ memberToAdd: newMember });
};
return (
<div className="border-4 border-black bg-white flex flex-col items-center justify-center rounded-3xl content-center pl-20 pr-20 h-[75vh] w-[50vh]">
<p className="pb-4 mb-2 text-center font-bold text-[18px]">
User chosen: [{name}]
</p>
<p className="pb-4 mb-2 text-center font-bold text-[18px]">
Role chosen: [{role}]
</p>
<p className="pb-4 mb-2 text-center font-bold text-[18px]">
Project chosen: [{localStorage.getItem("projectName") ?? ""}]
</p>
<p className="p-1">Choose role:</p>
<div className="border-2 border-black p-2 rounded-xl text-center h-[10h] w-[16vh]">
<ul className="text-center items-center font-medium space-y-2">
<li
className="h-[10h] w-[14vh] items-start p-1 border-2 border-black rounded-full bg-orange-200 hover:bg-orange-600 hover:text-slate-100 hover:cursor-pointer"
onClick={() => {
setRole("member");
}}
>
{"Member"}
</li>
<li
className="h-[10h] w-[14vh] items-start p-1 border-2 border-black rounded-full bg-orange-200 hover:bg-orange-600 hover:text-slate-100 hover:cursor-pointer"
onClick={() => {
setRole("project_manager");
}}
>
{"Project manager"}
</li>
</ul>
</div>
<p className="p-1">Choose user:</p>
<div className="border-2 border-black p-2 rounded-xl text-center overflow-scroll h-[26vh] w-[26vh]">
<ul className="text-center font-medium space-y-2">
<div></div>
{users.map((user) => (
<li
className="items-start p-1 border-2 border-black rounded-full bg-orange-200 hover:bg-orange-600 hover:text-slate-100 hover:cursor-pointer"
key={user}
value={user}
onClick={() => {
setName(user);
}}
>
<span>{user}</span>
</li>
))}
</ul>
</div>
<div className="flex space-x-5 items-center justify-between">
<Button
text="Add"
onClick={(): void => {
handleClick();
}}
type="submit"
/>
<BackButton />
</div>
<p className="text-center text-gray-500 text-xs"></p>
</div>
);
}
export default AddUserToProject;

View file

@ -13,24 +13,24 @@ function AllTimeReportsInProject(): JSX.Element {
const { projectName } = useParams(); const { projectName } = useParams();
const [weeklyReports, setWeeklyReports] = useState<WeeklyReport[]>([]); 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 // Call getProjects when the component mounts
useEffect(() => { useEffect(() => {
const getWeeklyReports = async (): Promise<void> => {
const token = localStorage.getItem("accessToken") ?? "";
const response = await api.getWeeklyReportsForUser(
projectName ?? "",
token,
);
console.log(response);
if (response.success) {
setWeeklyReports(response.data ?? []);
} else {
console.error(response.message);
}
};
void getWeeklyReports(); void getWeeklyReports();
}, []); }, [projectName]);
return ( return (
<> <>

View file

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

View file

@ -1,23 +1,48 @@
import React, { useState } from "react"; import React, { useState } from "react";
import InputField from "./InputField"; import InputField from "./InputField";
import { api } from "../API/API";
function ChangeUsername(): JSX.Element { function ChangeUsername(): JSX.Element {
const [newUsername, setNewUsername] = useState(""); const [newUsername, setNewUsername] = useState("");
const [errorMessage, setErrorMessage] = useState("");
const handleChange = (e: React.ChangeEvent<HTMLInputElement>): void => { const handleChange = (e: React.ChangeEvent<HTMLInputElement>): void => {
setNewUsername(e.target.value); setNewUsername(e.target.value);
}; };
// const handleSubmit = async (): Promise<void> => { const handleSubmit = async (): Promise<void> => {
// try { try {
// // Call the API function to update the username // Call the API function to change the username
// await api.updateUsername(newUsername); const token = localStorage.getItem("accessToken");
// // Optionally, add a success message or redirect the user if (!token) {
// } catch (error) { throw new Error("Access token not found");
// console.error("Error updating username:", error); }
// // Optionally, handle the error
// } const response = await api.changeUserName(
// }; { prevName: "currentName", newName: newUsername },
token,
);
if (response.success) {
// Optionally, add a success message or redirect the user
console.log("Username changed successfully");
} else {
// Handle the error message
console.error("Failed to change username:", response.message);
setErrorMessage(response.message ?? "Failed to change username");
}
} catch (error) {
console.error("Error changing username:", error);
// Optionally, handle the error
setErrorMessage("Failed to change username");
}
};
const handleButtonClick = (): void => {
handleSubmit().catch((error) => {
console.error("Error in handleSubmit:", error);
});
};
return ( return (
<div> <div>
@ -27,6 +52,8 @@ function ChangeUsername(): JSX.Element {
value={newUsername} value={newUsername}
onChange={handleChange} onChange={handleChange}
/> />
{errorMessage && <div>{errorMessage}</div>}
<button onClick={handleButtonClick}>Update Username</button>
</div> </div>
); );
} }

View file

@ -11,7 +11,6 @@ import { api, APIResponse } from "../API/API";
*/ */
function DeleteUser(props: { usernameToDelete: string }): boolean { function DeleteUser(props: { usernameToDelete: string }): boolean {
//console.log(props.usernameToDelete); FOR DEBUG
let removed = false; let removed = false;
api api
.removeUser( .removeUser(
@ -20,12 +19,16 @@ function DeleteUser(props: { usernameToDelete: string }): boolean {
) )
.then((response: APIResponse<User>) => { .then((response: APIResponse<User>) => {
if (response.success) { if (response.success) {
alert("User has been deleted!");
location.reload();
removed = true; removed = true;
} else { } else {
alert("User has not been deleted");
console.error(response.message); console.error(response.message);
} }
}) })
.catch((error) => { .catch((error) => {
alert("User has not been deleted");
console.error("An error occurred during creation:", error); console.error("An error occurred during creation:", error);
}); });
return removed; return removed;

View file

@ -0,0 +1,129 @@
import { useState, useEffect } from "react";
import { Link, useParams } from "react-router-dom";
interface UnsignedReports {
projectName: string;
username: string;
week: number;
signed: boolean;
}
/**
* Renders a component that displays the projects a user is a part of and links to the projects start-page.
* @returns The JSX element representing the component.
*/
function DisplayUserProject(): JSX.Element {
const { projectName } = useParams();
const [unsignedReports, setUnsignedReports] = useState<UnsignedReports[]>([]);
//const navigate = useNavigate();
// const getUnsignedReports = async (): Promise<void> => {
// const token = localStorage.getItem("accessToken") ?? "";
// const response = await api.getUserProjects(token);
// console.log(response);
// if (response.success) {
// setUnsignedReports(response.data ?? []);
// } else {
// console.error(response.message);
// }
// };
// const handleReportClick = async (projectName: string): Promise<void> => {
// const username = localStorage.getItem("username") ?? "";
// const token = localStorage.getItem("accessToken") ?? "";
// const response = await api.checkIfProjectManager(
// username,
// projectName,
// token,
// );
// if (response.success) {
// if (response.data) {
// navigate(`/PMProjectPage/${projectName}`);
// } else {
// navigate(`/project/${projectName}`);
// }
// } else {
// // handle error
// console.error(response.message);
// }
// };
const getUnsignedReports = async (): Promise<void> => {
// Simulate a delay
await Promise.resolve();
// Use mock data
const reports: UnsignedReports[] = [
{
projectName: "projecttest",
username: "user1",
week: 2,
signed: false,
},
{
projectName: "projecttest",
username: "user2",
week: 2,
signed: false,
},
{
projectName: "projecttest",
username: "user3",
week: 2,
signed: false,
},
{
projectName: "projecttest",
username: "user4",
week: 2,
signed: false,
},
];
// Set the state with the mock data
setUnsignedReports(reports);
};
// Call getProjects when the component mounts
useEffect(() => {
void getUnsignedReports();
}, []);
return (
<>
<h1 className="font-bold text-[30px] mb-[20px]">
All Unsigned Reports In: {projectName}{" "}
</h1>
<div className="border-4 border-black bg-white flex flex-col items-center justify-center min-h-[65vh] h-fit w-[70vw] rounded-3xl content-center overflow-scroll space-y-[10vh] p-[30px] text-[20px]">
{unsignedReports.map(
(unsignedReport: UnsignedReports, index: number) => (
<h1 key={index} className="border-b-2 border-black w-full">
<div className="flex justify-between">
<div className="flex">
<h1>{unsignedReport.username}</h1>
<span className="ml-6 mr-2 font-bold">Week:</span>
<h1>{unsignedReport.week}</h1>
<span className="ml-6 mr-2 font-bold">Signed:</span>
<h1>NO</h1>
</div>
<div className="flex">
<div className="ml-auto flex space-x-4">
<Link
to={`/PMViewUnsignedReport/${projectName}/${unsignedReport.username}/${unsignedReport.week}`}
>
<h1 className="underline cursor-pointer font-bold">
View Report
</h1>
</Link>
</div>
</div>
</div>
</h1>
),
)}
</div>
</>
);
}
export default DisplayUserProject;

View file

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

View file

@ -18,46 +18,47 @@ export default function GetWeeklyReport(): JSX.Element {
const [testingTime, setTestingTime] = useState(0); const [testingTime, setTestingTime] = useState(0);
const token = localStorage.getItem("accessToken") ?? ""; const token = localStorage.getItem("accessToken") ?? "";
const username = localStorage.getItem("username") ?? ""; const { projectName, fetchedWeek } = useParams<{
const { projectName } = useParams(); projectName: string;
const { fetchedWeek } = useParams(); fetchedWeek: string;
}>();
const fetchWeeklyReport = async (): Promise<void> => { console.log(projectName, fetchedWeek);
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(() => { useEffect(() => {
const fetchWeeklyReport = async (): Promise<void> => {
const response = await api.getWeeklyReport(
projectName ?? "",
fetchedWeek ?? "",
token,
);
if (response.success) {
const report: WeeklyReport = response.data ?? {
reportId: 0,
userId: 0,
projectId: 0,
week: 0,
developmentTime: 0,
meetingTime: 0,
adminTime: 0,
ownWorkTime: 0,
studyTime: 0,
testingTime: 0,
};
setWeek(report.week);
setDevelopmentTime(report.developmentTime);
setMeetingTime(report.meetingTime);
setAdminTime(report.adminTime);
setOwnWorkTime(report.ownWorkTime);
setStudyTime(report.studyTime);
setTestingTime(report.testingTime);
} else {
console.error("Failed to fetch weekly report:", response.message);
}
};
void fetchWeeklyReport(); void fetchWeeklyReport();
}); }, [projectName, fetchedWeek, token]);
const handleNewWeeklyReport = async (): Promise<void> => { const handleNewWeeklyReport = async (): Promise<void> => {
const newWeeklyReport: NewWeeklyReport = { const newWeeklyReport: NewWeeklyReport = {
@ -78,6 +79,7 @@ export default function GetWeeklyReport(): JSX.Element {
return ( return (
<> <>
<h1 className="font-bold text-[30px] mb-[20px]">Edit Time Report</h1>
<div className="border-4 border-black bg-white flex flex-col justify-start min-h-[65vh] h-fit w-[50vw] rounded-3xl overflow-scroll space-y-[2vh] p-[30px] items-center"> <div className="border-4 border-black bg-white flex flex-col justify-start min-h-[65vh] h-fit w-[50vw] rounded-3xl overflow-scroll space-y-[2vh] p-[30px] items-center">
<form <form
onSubmit={(e) => { onSubmit={(e) => {
@ -92,24 +94,10 @@ export default function GetWeeklyReport(): JSX.Element {
}} }}
> >
<div className="flex flex-col items-center"> <div className="flex flex-col items-center">
<input <div className="flex flex-col w-1/2 border-b-2 border-black items-center justify-center">
className="w-fill h-[5vh] font-sans text-[3vh] pl-[1vw] rounded-full text-center pt-[1vh] pb-[1vh] border-2 border-black" <h1 className="font-bold text-[30px]"> Week: {week}</h1>
type="week" </div>
placeholder="Week"
value={
week === 0 ? "" : `2024-W${week.toString().padStart(2, "0")}`
}
onChange={(e) => {
const weekNumber = parseInt(e.target.value.split("-W")[1]);
setWeek(weekNumber);
}}
onKeyDown={(event) => {
event.preventDefault();
}}
onPaste={(event) => {
event.preventDefault();
}}
/>
<table className="w-full text-center divide-y divide-x divide-white text-[30px]"> <table className="w-full text-center divide-y divide-x divide-white text-[30px]">
<thead> <thead>
<tr> <tr>
@ -129,9 +117,14 @@ export default function GetWeeklyReport(): JSX.Element {
type="number" type="number"
min="0" min="0"
className="border-2 border-black rounded-md text-center w-1/2" className="border-2 border-black rounded-md text-center w-1/2"
value={developmentTime} value={developmentTime === 0 ? "" : developmentTime}
onChange={(e) => { onChange={(e) => {
setDevelopmentTime(parseInt(e.target.value)); if (e.target.value === "") {
setDevelopmentTime(0);
return;
} else {
setDevelopmentTime(parseInt(e.target.value));
}
}} }}
onKeyDown={(event) => { onKeyDown={(event) => {
const keyValue = event.key; const keyValue = event.key;
@ -148,9 +141,14 @@ export default function GetWeeklyReport(): JSX.Element {
type="number" type="number"
min="0" min="0"
className="border-2 border-black rounded-md text-center w-1/2" className="border-2 border-black rounded-md text-center w-1/2"
value={meetingTime} value={meetingTime === 0 ? "" : meetingTime}
onChange={(e) => { onChange={(e) => {
setMeetingTime(parseInt(e.target.value)); if (e.target.value === "") {
setMeetingTime(0);
return;
} else {
setMeetingTime(parseInt(e.target.value));
}
}} }}
onKeyDown={(event) => { onKeyDown={(event) => {
const keyValue = event.key; const keyValue = event.key;
@ -167,9 +165,14 @@ export default function GetWeeklyReport(): JSX.Element {
type="number" type="number"
min="0" min="0"
className="border-2 border-black rounded-md text-center w-1/2" className="border-2 border-black rounded-md text-center w-1/2"
value={adminTime} value={adminTime === 0 ? "" : adminTime}
onChange={(e) => { onChange={(e) => {
setAdminTime(parseInt(e.target.value)); if (e.target.value === "") {
setAdminTime(0);
return;
} else {
setAdminTime(parseInt(e.target.value));
}
}} }}
onKeyDown={(event) => { onKeyDown={(event) => {
const keyValue = event.key; const keyValue = event.key;
@ -186,9 +189,14 @@ export default function GetWeeklyReport(): JSX.Element {
type="number" type="number"
min="0" min="0"
className="border-2 border-black rounded-md text-center w-1/2" className="border-2 border-black rounded-md text-center w-1/2"
value={ownWorkTime} value={ownWorkTime === 0 ? "" : ownWorkTime}
onChange={(e) => { onChange={(e) => {
setOwnWorkTime(parseInt(e.target.value)); if (e.target.value === "") {
setOwnWorkTime(0);
return;
} else {
setOwnWorkTime(parseInt(e.target.value));
}
}} }}
onKeyDown={(event) => { onKeyDown={(event) => {
const keyValue = event.key; const keyValue = event.key;
@ -205,9 +213,14 @@ export default function GetWeeklyReport(): JSX.Element {
type="number" type="number"
min="0" min="0"
className="border-2 border-black rounded-md text-center w-1/2" className="border-2 border-black rounded-md text-center w-1/2"
value={studyTime} value={studyTime === 0 ? "" : studyTime}
onChange={(e) => { onChange={(e) => {
setStudyTime(parseInt(e.target.value)); if (e.target.value === "") {
setStudyTime(0);
return;
} else {
setStudyTime(parseInt(e.target.value));
}
}} }}
onKeyDown={(event) => { onKeyDown={(event) => {
const keyValue = event.key; const keyValue = event.key;
@ -224,9 +237,14 @@ export default function GetWeeklyReport(): JSX.Element {
type="number" type="number"
min="0" min="0"
className="border-2 border-black rounded-md text-center w-1/2" className="border-2 border-black rounded-md text-center w-1/2"
value={testingTime} value={testingTime === 0 ? "" : testingTime}
onChange={(e) => { onChange={(e) => {
setTestingTime(parseInt(e.target.value)); if (e.target.value === "") {
setTestingTime(0);
return;
} else {
setTestingTime(parseInt(e.target.value));
}
}} }}
onKeyDown={(event) => { onKeyDown={(event) => {
const keyValue = event.key; const keyValue = event.key;

View file

@ -0,0 +1,37 @@
import { Dispatch, useEffect } from "react";
import { Project } from "../Types/goTypes";
import { api } from "../API/API";
/**
* Gets all projects that user is a member of
* @param props - A setStateAction for the array you want to put projects in
* @returns {void} Nothing
* @example
* const [projects, setProjects] = useState<Project[]>([]);
* GetAllUsers({ setProjectsProp: setProjects });
*/
function GetProjects(props: {
setProjectsProp: Dispatch<React.SetStateAction<Project[]>>;
}): void {
const setProjects: Dispatch<React.SetStateAction<Project[]>> =
props.setProjectsProp;
useEffect(() => {
const fetchUsers = async (): Promise<void> => {
try {
const token = localStorage.getItem("accessToken") ?? "";
const response = await api.getUserProjects(token);
if (response.success) {
setProjects(response.data ?? []);
} else {
console.error("Failed to fetch projects:", response.message);
}
} catch (error) {
console.error("Error fetching projects:", error);
}
};
void fetchUsers();
}, [setProjects]);
}
export default GetProjects;

View file

@ -0,0 +1,37 @@
import { Dispatch, useEffect } from "react";
import { UserProjectMember } from "../Types/goTypes";
import { api } from "../API/API";
/**
* Gets all projects that user is a member of
* @param props - A setStateAction for the array you want to put projects in
* @returns {void} Nothing
* @example
* const [projects, setProjects] = useState<Project[]>([]);
* GetAllUsers({ setProjectsProp: setProjects });
*/
function GetUsersInProject(props: {
projectName: string;
setUsersProp: Dispatch<React.SetStateAction<UserProjectMember[]>>;
}): void {
const setUsers: Dispatch<React.SetStateAction<UserProjectMember[]>> =
props.setUsersProp;
useEffect(() => {
const fetchUsers = async (): Promise<void> => {
try {
const token = localStorage.getItem("accessToken") ?? "";
const response = await api.getAllUsersProject(props.projectName, token);
if (response.success) {
setUsers(response.data ?? []);
} else {
console.error("Failed to fetch projects:", response.message);
}
} catch (error) {
console.error("Error fetching projects:", error);
}
};
void fetchUsers();
}, [props.projectName, setUsers]);
}
export default GetUsersInProject;

View file

@ -12,66 +12,103 @@ import Button from "./Button";
*/ */
export default function NewWeeklyReport(): JSX.Element { export default function NewWeeklyReport(): JSX.Element {
const [week, setWeek] = useState<number>(0); const [week, setWeek] = useState<number>(0);
const [developmentTime, setDevelopmentTime] = useState<number>(); const [developmentTime, setDevelopmentTime] = useState<number>(0);
const [meetingTime, setMeetingTime] = useState<number>(); const [meetingTime, setMeetingTime] = useState<number>(0);
const [adminTime, setAdminTime] = useState<number>(); const [adminTime, setAdminTime] = useState<number>(0);
const [ownWorkTime, setOwnWorkTime] = useState<number>(); const [ownWorkTime, setOwnWorkTime] = useState<number>(0);
const [studyTime, setStudyTime] = useState<number>(); const [studyTime, setStudyTime] = useState<number>(0);
const [testingTime, setTestingTime] = useState<number>(); const [testingTime, setTestingTime] = useState<number>(0);
const { projectName } = useParams(); const { projectName } = useParams();
const token = localStorage.getItem("accessToken") ?? ""; const token = localStorage.getItem("accessToken") ?? "";
const handleNewWeeklyReport = async (): Promise<void> => { const handleNewWeeklyReport = async (): Promise<boolean> => {
const newWeeklyReport: NewWeeklyReport = { const newWeeklyReport: NewWeeklyReport = {
projectName: projectName ?? "", projectName: projectName ?? "",
week: week, week: week,
developmentTime: developmentTime ?? 0, developmentTime: developmentTime,
meetingTime: meetingTime ?? 0, meetingTime: meetingTime,
adminTime: adminTime ?? 0, adminTime: adminTime,
ownWorkTime: ownWorkTime ?? 0, ownWorkTime: ownWorkTime,
studyTime: studyTime ?? 0, studyTime: studyTime,
testingTime: testingTime ?? 0, testingTime: testingTime,
}; };
await api.submitWeeklyReport(newWeeklyReport, token); const response = await api.submitWeeklyReport(newWeeklyReport, token);
console.log(response);
if (response.success) {
return true;
} else {
return false;
}
}; };
const navigate = useNavigate(); const navigate = useNavigate();
// Check if the browser is Chrome or Edge
const isChromeOrEdge = /Chrome|Edg/.test(navigator.userAgent);
return ( return (
<> <>
<div className="border-4 border-black bg-white flex flex-col justify-start min-h-[65vh] h-fit w-[50vw] rounded-3xl overflow-scroll space-y-[2vh] p-[30px] items-center"> <div className="border-4 border-black bg-white flex flex-col justify-start min-h-[65vh] h-fit w-[50vw] rounded-3xl overflow-scroll space-y-[2vh] p-[30px] items-center">
<form <form
onSubmit={(e) => { onSubmit={(e) => {
if (week === 0) {
alert("Please enter a week number");
e.preventDefault();
return;
}
e.preventDefault(); e.preventDefault();
void handleNewWeeklyReport(); void (async (): Promise<void> => {
navigate(-1); if (week === 0 || week > 53 || week < 1) {
alert("Please enter a valid week number");
return;
}
const success = await handleNewWeeklyReport();
if (!success) {
alert(
"A Time Report for this week already exists, please go to the edit page to edit it or change week number.",
);
return;
}
alert("Weekly report submitted successfully");
navigate(-1);
})();
}} }}
> >
<div className="flex flex-col items-center"> <div className="flex flex-col items-center">
<input {isChromeOrEdge ? (
className="w-fill h-[5vh] font-sans text-[3vh] pl-[1vw] rounded-full text-center pt-[1vh] pb-[1vh] border-2 border-black" <input
type="week" className="w-fill h-[5vh] font-sans text-[3vh] pl-[1vw] rounded-full text-center pt-[1vh] pb-[1vh] border-2 border-black"
placeholder="Week" type="week"
onChange={(e) => { placeholder="Week"
const weekNumber = parseInt(e.target.value.split("-W")[1]); onChange={(e) => {
setWeek(weekNumber); const weekNumber = parseInt(e.target.value.split("-W")[1]);
}} setWeek(weekNumber);
onKeyDown={(event) => { }}
const keyValue = event.key; onKeyDown={(event) => {
if (!/\d/.test(keyValue) && keyValue !== "Backspace") const keyValue = event.key;
if (!/\d/.test(keyValue) && keyValue !== "Backspace")
event.preventDefault();
}}
onPaste={(event) => {
event.preventDefault(); event.preventDefault();
}} }}
onPaste={(event) => { />
event.preventDefault(); ) : (
}} <input
/> className="w-fill h-[5vh] font-sans text-[3vh] pl-[1vw] rounded-full text-center pt-[1vh] pb-[1vh] border-2 border-black"
type="text"
placeholder="Week (Numbers Only)"
onChange={(e) => {
const weekNumber = parseInt(e.target.value);
setWeek(weekNumber);
}}
onKeyDown={(event) => {
const keyValue = event.key;
if (!/\d/.test(keyValue) && keyValue !== "Backspace")
event.preventDefault();
}}
onPaste={(event) => {
event.preventDefault();
}}
/>
)}
<table className="w-full text-center divide-y divide-x divide-white text-[30px]"> <table className="w-full text-center divide-y divide-x divide-white text-[30px]">
<thead> <thead>
<tr> <tr>
@ -91,9 +128,14 @@ export default function NewWeeklyReport(): JSX.Element {
type="number" type="number"
min="0" min="0"
className="border-2 border-black rounded-md text-center w-1/2" className="border-2 border-black rounded-md text-center w-1/2"
value={developmentTime} value={developmentTime === 0 ? "" : developmentTime}
onChange={(e) => { onChange={(e) => {
setDevelopmentTime(parseInt(e.target.value)); if (e.target.value === "") {
setDevelopmentTime(0);
return;
} else {
setDevelopmentTime(parseInt(e.target.value));
}
}} }}
onKeyDown={(event) => { onKeyDown={(event) => {
const keyValue = event.key; const keyValue = event.key;
@ -110,9 +152,14 @@ export default function NewWeeklyReport(): JSX.Element {
type="number" type="number"
min="0" min="0"
className="border-2 border-black rounded-md text-center w-1/2" className="border-2 border-black rounded-md text-center w-1/2"
value={meetingTime} value={meetingTime === 0 ? "" : meetingTime}
onChange={(e) => { onChange={(e) => {
setMeetingTime(parseInt(e.target.value)); if (e.target.value === "") {
setMeetingTime(0);
return;
} else {
setMeetingTime(parseInt(e.target.value));
}
}} }}
onKeyDown={(event) => { onKeyDown={(event) => {
const keyValue = event.key; const keyValue = event.key;
@ -129,9 +176,14 @@ export default function NewWeeklyReport(): JSX.Element {
type="number" type="number"
min="0" min="0"
className="border-2 border-black rounded-md text-center w-1/2" className="border-2 border-black rounded-md text-center w-1/2"
value={adminTime} value={adminTime === 0 ? "" : adminTime}
onChange={(e) => { onChange={(e) => {
setAdminTime(parseInt(e.target.value)); if (e.target.value === "") {
setAdminTime(0);
return;
} else {
setAdminTime(parseInt(e.target.value));
}
}} }}
onKeyDown={(event) => { onKeyDown={(event) => {
const keyValue = event.key; const keyValue = event.key;
@ -148,9 +200,14 @@ export default function NewWeeklyReport(): JSX.Element {
type="number" type="number"
min="0" min="0"
className="border-2 border-black rounded-md text-center w-1/2" className="border-2 border-black rounded-md text-center w-1/2"
value={ownWorkTime} value={ownWorkTime === 0 ? "" : ownWorkTime}
onChange={(e) => { onChange={(e) => {
setOwnWorkTime(parseInt(e.target.value)); if (e.target.value === "") {
setOwnWorkTime(0);
return;
} else {
setOwnWorkTime(parseInt(e.target.value));
}
}} }}
onKeyDown={(event) => { onKeyDown={(event) => {
const keyValue = event.key; const keyValue = event.key;
@ -167,9 +224,14 @@ export default function NewWeeklyReport(): JSX.Element {
type="number" type="number"
min="0" min="0"
className="border-2 border-black rounded-md text-center w-1/2" className="border-2 border-black rounded-md text-center w-1/2"
value={studyTime} value={studyTime === 0 ? "" : studyTime}
onChange={(e) => { onChange={(e) => {
setStudyTime(parseInt(e.target.value)); if (e.target.value === "") {
setStudyTime(0);
return;
} else {
setStudyTime(parseInt(e.target.value));
}
}} }}
onKeyDown={(event) => { onKeyDown={(event) => {
const keyValue = event.key; const keyValue = event.key;
@ -186,9 +248,14 @@ export default function NewWeeklyReport(): JSX.Element {
type="number" type="number"
min="0" min="0"
className="border-2 border-black rounded-md text-center w-1/2" className="border-2 border-black rounded-md text-center w-1/2"
value={testingTime} value={testingTime === 0 ? "" : testingTime}
onChange={(e) => { onChange={(e) => {
setTestingTime(parseInt(e.target.value)); if (e.target.value === "") {
setTestingTime(0);
return;
} else {
setTestingTime(parseInt(e.target.value));
}
}} }}
onKeyDown={(event) => { onKeyDown={(event) => {
const keyValue = event.key; const keyValue = event.key;

View file

@ -0,0 +1,153 @@
import { useState, useEffect } from "react";
import { WeeklyReport } from "../Types/goTypes";
import { api } from "../API/API";
import { useParams } from "react-router-dom";
/**
* Renders the component for editing a weekly report.
* @returns JSX.Element
*/
//This component does not yet work as intended. It is supposed to display the weekly report of a user in a project.
export default function OtherUsersTR(): JSX.Element {
const [week, setWeek] = useState(0);
const [developmentTime, setDevelopmentTime] = useState(0);
const [meetingTime, setMeetingTime] = useState(0);
const [adminTime, setAdminTime] = useState(0);
const [ownWorkTime, setOwnWorkTime] = useState(0);
const [studyTime, setStudyTime] = useState(0);
const [testingTime, setTestingTime] = useState(0);
const token = localStorage.getItem("accessToken") ?? "";
const { projectName } = useParams();
const { username } = useParams();
const { fetchedWeek } = useParams();
useEffect(() => {
const fetchUsersWeeklyReport = async (): Promise<void> => {
const response = await api.getWeeklyReport(
projectName ?? "",
fetchedWeek?.toString() ?? "0",
token,
);
if (response.success) {
const report: WeeklyReport = response.data ?? {
reportId: 0,
userId: 0,
projectId: 0,
week: 0,
developmentTime: 0,
meetingTime: 0,
adminTime: 0,
ownWorkTime: 0,
studyTime: 0,
testingTime: 0,
};
setWeek(report.week);
setDevelopmentTime(report.developmentTime);
setMeetingTime(report.meetingTime);
setAdminTime(report.adminTime);
setOwnWorkTime(report.ownWorkTime);
setStudyTime(report.studyTime);
setTestingTime(report.testingTime);
} else {
console.error("Failed to fetch weekly report:", response.message);
}
};
void fetchUsersWeeklyReport();
});
return (
<>
<h1 className="text-[30px] font-bold">{username}&apos;s Report</h1>
<div className="border-4 border-black bg-white flex flex-col justify-start min-h-[65vh] h-fit w-[50vw] rounded-3xl overflow-scroll space-y-[2vh] p-[30px] items-center">
<div className="flex flex-col items-center">
<div className="flex flex-col w-1/2 border-b-2 border-black items-center justify-center">
<h1 className="font-bold text-[30px]"> Week: {week}</h1>
</div>
<table className="w-full text-center divide-y divide-x divide-white text-[30px]">
<thead>
<tr>
<th className="w-1/2 py-2 border-b-2 border-black">Activity</th>
<th className="w-1/2 py-2 border-b-2 border-black">
Total Time (min)
</th>
</tr>
</thead>
<tbody className="divide-y divide-black">
<tr className="h-[10vh]">
<td>Development</td>
<td>
<input
type="text"
min="0"
className="border-2 border-black rounded-md text-center w-1/2"
value={developmentTime === 0 ? "" : developmentTime}
/>
</td>
</tr>
<tr className="h-[10vh]">
<td>Meeting</td>
<td>
<input
type="text"
min="0"
className="border-2 border-black rounded-md text-center w-1/2"
value={meetingTime === 0 ? "" : meetingTime}
/>
</td>
</tr>
<tr className="h-[10vh]">
<td>Administration</td>
<td>
<input
type="text"
min="0"
className="border-2 border-black rounded-md text-center w-1/2"
value={adminTime === 0 ? "" : adminTime}
/>
</td>
</tr>
<tr className="h-[10vh]">
<td>Own Work</td>
<td>
<input
type="text"
min="0"
className="border-2 border-black rounded-md text-center w-1/2"
value={ownWorkTime === 0 ? "" : ownWorkTime}
/>
</td>
</tr>
<tr className="h-[10vh]">
<td>Studies</td>
<td>
<input
type="text"
min="0"
className="border-2 border-black rounded-md text-center w-1/2"
value={studyTime === 0 ? "" : studyTime}
/>
</td>
</tr>
<tr className="h-[10vh]">
<td>Testing</td>
<td>
<input
type="text"
min="0"
className="border-2 border-black rounded-md text-center w-1/2"
value={testingTime === 0 ? "" : testingTime}
/>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</>
);
}

View file

@ -0,0 +1,79 @@
import { useState } from "react";
import Button from "./Button";
import { UserProjectMember } from "../Types/goTypes";
import GetUsersInProject from "./GetUsersInProject";
import { Link } from "react-router-dom";
function ProjectInfoModal(props: {
isVisible: boolean;
projectname: string;
onClose: () => void;
onClick: (username: string) => void;
}): JSX.Element {
const [users, setUsers] = useState<UserProjectMember[]>([]);
GetUsersInProject({ projectName: props.projectname, setUsersProp: setUsers });
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-2xl text-center h-[47vh] w-[40] flex flex-col">
<div className="pl-20 pr-20">
<h1 className="font-bold text-[32px] mb-[20px]">
{localStorage.getItem("projectName") ?? ""}
</h1>
<h2 className="font-bold text-[24px] mb-[20px]">Project members:</h2>
<div className="border-2 border-black p-2 rounded-lg text-center overflow-scroll h-[26vh]">
<ul className="text-left font-medium space-y-2">
<div></div>
{users.map((user) => (
<li
className="items-start p-1 border-2 border-black rounded-lg bg-orange-200 hover:bg-orange-600 hover:text-slate-100 hover:cursor-pointer"
key={user.Username}
onClick={() => {
props.onClick(user.Username);
}}
>
<span>
Name: {user.Username}
<div></div>
Role: {user.UserRole}
</span>
</li>
))}
</ul>
</div>
</div>
<div className="space-x-16">
<Button
text={"Delete"}
onClick={function (): void {
//DELETE PROJECT
}}
type="button"
/>
<Link to={"/adminProjectAddMember"}>
<Button
text={"Add Member"}
onClick={function (): void {
return;
}}
type="button"
/>
</Link>
<Button
text={"Close"}
onClick={function (): void {
props.onClose();
}}
type="button"
/>
</div>
</div>
</div>
);
}
export default ProjectInfoModal;

View file

@ -0,0 +1,81 @@
import { useState } from "react";
import { NewProject } from "../Types/goTypes";
import ProjectInfoModal from "./ProjectInfoModal";
import UserInfoModal from "./UserInfoModal";
/**
* A list of projects for admin manage projects page, that sets an onClick
* function for eact project <li> element, which displays a modul with
* user info.
* @param props - An array of projects to display
* @returns {JSX.Element} The project list
* @example
* const projects: NewProject[] = [{ name: "Project", description: "New" }];
* return <ProjectListAdmin projects={projects} />
*/
export function ProjectListAdmin(props: {
projects: NewProject[];
}): JSX.Element {
const [projectModalVisible, setProjectModalVisible] = useState(false);
const [projectname, setProjectname] = useState("");
const [userModalVisible, setUserModalVisible] = useState(false);
const [username, setUsername] = useState("");
const handleClickUser = (username: string): void => {
setUsername(username);
setUserModalVisible(true);
};
const handleClickProject = (projectname: string): void => {
setProjectname(projectname);
localStorage.setItem("projectName", projectname);
setProjectModalVisible(true);
};
const handleCloseProject = (): void => {
setProjectname("");
setProjectModalVisible(false);
};
const handleCloseUser = (): void => {
setProjectname("");
setUserModalVisible(false);
};
return (
<>
<ProjectInfoModal
onClose={handleCloseProject}
onClick={handleClickUser}
isVisible={projectModalVisible}
projectname={projectname}
/>
<UserInfoModal
manageMember={true}
onClose={handleCloseUser}
//TODO: CHANGE TO REMOVE USER FROM PROJECT
onDelete={() => {
return;
}}
isVisible={userModalVisible}
username={username}
/>
<div>
<ul className="font-bold underline text-[30px] cursor-pointer padding">
{props.projects.map((project) => (
<li
className="pt-5"
key={project.name}
onClick={() => {
handleClickProject(project.name);
}}
>
{project.name}
</li>
))}
</ul>
</div>
</>
);
}

View file

@ -1,91 +1,55 @@
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import { Link, useParams } from "react-router-dom"; import { Link, useParams } from "react-router-dom";
import { api } from "../API/API";
import { UserProjectMember } from "../Types/goTypes";
function ProjectMembers(): JSX.Element { function ProjectMembers(): JSX.Element {
const { projectName } = useParams(); const { projectName } = useParams();
const [projectMembers, setProjectMembers] = useState<ProjectMember[]>([]); const [projectMembers, setProjectMembers] = useState<UserProjectMember[]>([]);
// 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(() => { useEffect(() => {
const getProjectMembers = async (): Promise<void> => {
const token = localStorage.getItem("accessToken") ?? "";
const response = await api.getAllUsersProject(projectName ?? "", token);
console.log(response);
if (response.success) {
setProjectMembers(response.data ?? []);
} else {
console.error(response.message);
}
};
void getProjectMembers(); void getProjectMembers();
}); }, [projectName]);
interface ProjectMember {
Username: string;
UserRole: string;
}
return ( return (
<> <>
<h1 className="font-bold text-[30px] mb-[20px]">
All Members In: {projectName}{" "}
</h1>
<div className="border-4 border-black bg-white flex flex-col items-center justify-center min-h-[65vh] h-fit w-[70vw] rounded-3xl content-center overflow-scroll space-y-[10vh] p-[30px] text-[20px]"> <div className="border-4 border-black bg-white flex flex-col items-center justify-center min-h-[65vh] h-fit w-[70vw] rounded-3xl content-center overflow-scroll space-y-[10vh] p-[30px] text-[20px]">
{projectMembers.map((projectMember, index) => ( {projectMembers.map((projectMember: ProjectMember, index: number) => (
<h1 key={index} className="border-b-2 border-black w-full"> <h1 key={index} className="border-b-2 border-black w-full">
<div className="flex justify-between"> <div className="flex justify-between">
<div className="flex"> <div className="flex">
<h1>{projectMember.username}</h1> <h1>{projectMember.Username}</h1>
<span className="ml-6 mr-2 font-bold">Role:</span> <span className="ml-6 mr-2 font-bold">Role:</span>
<h1>{projectMember.role}</h1> <h1>{projectMember.UserRole}</h1>
</div> </div>
<div className="flex"> <div className="flex">
<div className="ml-auto flex space-x-4"> <div className="ml-auto flex space-x-4">
<Link <Link
to={`/viewReports/${projectName}/${projectMember.username}`} to={`/otherUsersTimeReports/${projectName}/${projectMember.Username}`}
> >
<h1 className="underline cursor-pointer font-bold"> <h1 className="underline cursor-pointer font-bold">
View Reports View Reports
</h1> </h1>
</Link> </Link>
<Link
to={`/changeRole/${projectName}/${projectMember.username}`}
>
<h1 className="underline cursor-pointer font-bold">
Change Role
</h1>
</Link>
</div> </div>
</div> </div>
</div> </div>

View file

@ -4,7 +4,6 @@ import { api } from "../API/API";
import Logo from "../assets/Logo.svg"; import Logo from "../assets/Logo.svg";
import Button from "./Button"; import Button from "./Button";
import InputField from "./InputField"; import InputField from "./InputField";
import { useNavigate } from "react-router-dom";
/** /**
* Renders a registration form for the admin to add new users in. * Renders a registration form for the admin to add new users in.
@ -15,8 +14,6 @@ export default function Register(): JSX.Element {
const [password, setPassword] = useState<string>(); const [password, setPassword] = useState<string>();
const [errMessage, setErrMessage] = useState<string>(); const [errMessage, setErrMessage] = useState<string>();
const nav = useNavigate();
const handleRegister = async (): Promise<void> => { const handleRegister = async (): Promise<void> => {
const newUser: NewUser = { const newUser: NewUser = {
username: username ?? "", username: username ?? "",
@ -24,8 +21,9 @@ export default function Register(): JSX.Element {
}; };
const response = await api.registerUser(newUser); const response = await api.registerUser(newUser);
if (response.success) { if (response.success) {
nav("/"); // Instantly navigate to the login page alert("User added!");
} else { } else {
alert("User not added");
setErrMessage(response.message ?? "Unknown error"); setErrMessage(response.message ?? "Unknown error");
console.error(errMessage); console.error(errMessage);
} }

View file

@ -0,0 +1,175 @@
import { useState, useEffect } from "react";
import { useParams } from "react-router-dom";
/**
* Renders the component for showing total time per role in a project.
* @returns JSX.Element
*/
export default function TimePerRole(): JSX.Element {
const [developmentTime, setDevelopmentTime] = useState<number>();
const [meetingTime, setMeetingTime] = useState<number>();
const [adminTime, setAdminTime] = useState<number>();
const [ownWorkTime, setOwnWorkTime] = useState<number>();
const [studyTime, setStudyTime] = useState<number>();
const [testingTime, setTestingTime] = useState<number>();
// const token = localStorage.getItem("accessToken") ?? "";
// const username = localStorage.getItem("username") ?? "";
const { projectName } = useParams();
// const fetchTimePerRole = async (): Promise<void> => {
// const response = await api.getTimePerRole(
// username,
// projectName ?? "",
// token,
// );
// {
// if (response.success) {
// const report: TimePerRole = response.data ?? {
// PManagerTime: 0,
// SManagerTime: 0,
// DeveloperTime: 0,
// TesterTime: 0,
// };
// } else {
// console.error("Failed to fetch weekly report:", response.message);
// }
// }
interface TimePerActivity {
developmentTime: number;
meetingTime: number;
adminTime: number;
ownWorkTime: number;
studyTime: number;
testingTime: number;
}
const fetchTimePerActivity = async (): Promise<void> => {
// Use mock data
const report: TimePerActivity = {
developmentTime: 100,
meetingTime: 200,
adminTime: 300,
ownWorkTime: 50,
studyTime: 75,
testingTime: 110,
};
// Set the state with the mock data
setDevelopmentTime(report.developmentTime);
setMeetingTime(report.meetingTime);
setAdminTime(report.adminTime);
setOwnWorkTime(report.ownWorkTime);
setStudyTime(report.studyTime);
setTestingTime(report.testingTime);
await Promise.resolve();
};
useEffect(() => {
void fetchTimePerActivity();
});
return (
<>
<h1 className="font-bold text-[30px] mb-[20px]">
Total Time Per Activity In: {projectName}{" "}
</h1>
<div className="border-4 border-black bg-white flex flex-col justify-start min-h-[65vh] h-fit w-[50vw] rounded-3xl overflow-scroll space-y-[2vh] p-[30px] items-center">
<div className="flex flex-col items-center">
<table className="w-full text-center divide-y divide-x divide-white text-[30px]">
<thead>
<tr>
<th className="w-1/2 py-2 border-b-2 border-black">Activity</th>
<th className="w-1/2 py-2 border-b-2 border-black">
Total Time (min)
</th>
</tr>
</thead>
<tbody className="divide-y divide-black">
<tr className="h-[10vh]">
<td>Development</td>
<td>
<input
type="string"
className="border-2 border-black rounded-md text-center w-1/2"
value={developmentTime}
onKeyDown={(event) => {
event.preventDefault();
}}
/>
</td>
</tr>
<tr className="h-[10vh]">
<td>Meeting</td>
<td>
<input
type="string"
className="border-2 border-black rounded-md text-center w-1/2"
value={meetingTime}
onKeyDown={(event) => {
event.preventDefault();
}}
/>
</td>
</tr>
<tr className="h-[10vh]">
<td>Administration</td>
<td>
<input
type="string"
className="border-2 border-black rounded-md text-center w-1/2"
value={adminTime}
onKeyDown={(event) => {
event.preventDefault();
}}
/>
</td>
</tr>
<tr className="h-[10vh]">
<td>Own Work</td>
<td>
<input
type="string"
className="border-2 border-black rounded-md text-center w-1/2"
value={ownWorkTime}
onKeyDown={(event) => {
event.preventDefault();
}}
/>
</td>
</tr>
<tr className="h-[10vh]">
<td>Studies</td>
<td>
<input
type="string"
className="border-2 border-black rounded-md text-center w-1/2"
value={studyTime}
onKeyDown={(event) => {
event.preventDefault();
}}
/>
</td>
</tr>
<tr className="h-[10vh]">
<td>Testing</td>
<td>
<input
type="string"
className="border-2 border-black rounded-md text-center w-1/2"
value={testingTime}
onKeyDown={(event) => {
event.preventDefault();
}}
/>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</>
);
}

View file

@ -0,0 +1,141 @@
import { useState, useEffect } from "react";
import { useParams } from "react-router-dom";
/**
* Renders the component for showing total time per role in a project.
* @returns JSX.Element
*/
export default function TimePerRole(): JSX.Element {
const [PManagerTime, setPManagerTime] = useState(0);
const [SManagerTime, setSManagerTime] = useState(0);
const [DeveloperTime, setDeveloperTime] = useState(0);
const [TesterTime, setTesterTime] = useState(0);
// const token = localStorage.getItem("accessToken") ?? "";
// const username = localStorage.getItem("username") ?? "";
const { projectName } = useParams();
// const fetchTimePerRole = async (): Promise<void> => {
// const response = await api.getTimePerRole(
// username,
// projectName ?? "",
// token,
// );
// {
// if (response.success) {
// const report: TimePerRole = response.data ?? {
// PManagerTime: 0,
// SManagerTime: 0,
// DeveloperTime: 0,
// TesterTime: 0,
// };
// } else {
// console.error("Failed to fetch weekly report:", response.message);
// }
// }
interface TimePerRole {
PManager: number;
SManager: number;
Developer: number;
Tester: number;
}
const fetchTimePerRole = async (): Promise<void> => {
// Use mock data
const report: TimePerRole = {
PManager: 120,
SManager: 80,
Developer: 200,
Tester: 150,
};
// Set the state with the mock data
setPManagerTime(report.PManager);
setSManagerTime(report.SManager);
setDeveloperTime(report.Developer);
setTesterTime(report.Tester);
await Promise.resolve();
};
useEffect(() => {
void fetchTimePerRole();
});
return (
<>
<h1 className="font-bold text-[30px] mb-[20px]">
Total Time Per Role In: {projectName}{" "}
</h1>
<div className="border-4 border-black bg-white flex flex-col justify-start min-h-[65vh] h-fit w-[50vw] rounded-3xl overflow-scroll space-y-[2vh] p-[30px] items-center">
<div className="flex flex-col items-center">
<table className="w-full text-center divide-y divide-x divide-white text-[30px]">
<thead>
<tr>
<th className="w-1/2 py-2 border-b-2 border-black">Role</th>
<th className="w-1/2 py-2 border-b-2 border-black">
Total Time (min)
</th>
</tr>
</thead>
<tbody className="divide-y divide-black">
<tr className="h-[10vh]">
<td>Project Manager</td>
<td>
<input
type="string"
className="border-2 border-black rounded-md text-center w-1/2"
value={PManagerTime}
onKeyDown={(event) => {
event.preventDefault();
}}
/>
</td>
</tr>
<tr className="h-[10vh]">
<td>System Manager</td>
<td>
<input
type="string"
className="border-2 border-black rounded-md text-center w-1/2"
value={SManagerTime}
onKeyDown={(event) => {
event.preventDefault();
}}
/>
</td>
</tr>
<tr className="h-[10vh]">
<td>Administration</td>
<td>
<input
type="string"
className="border-2 border-black rounded-md text-center w-1/2"
value={DeveloperTime}
onKeyDown={(event) => {
event.preventDefault();
}}
/>
</td>
</tr>
<tr className="h-[10vh]">
<td>Own Work</td>
<td>
<input
type="string"
className="border-2 border-black rounded-md text-center w-1/2"
value={TesterTime}
onKeyDown={(event) => {
event.preventDefault();
}}
/>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</>
);
}

View file

@ -5,23 +5,38 @@ import UserProjectListAdmin from "./UserProjectListAdmin";
function UserInfoModal(props: { function UserInfoModal(props: {
isVisible: boolean; isVisible: boolean;
manageMember: boolean;
username: string; username: string;
onClose: () => void; onClose: () => void;
onDelete: (username: string) => void;
}): JSX.Element { }): JSX.Element {
if (!props.isVisible) return <></>; if (!props.isVisible) return <></>;
const ManageUserOrMember = (check: boolean): JSX.Element => {
if (check) {
return (
<Link to="/AdminChangeRole">
<p className="mb-[20px] hover:font-bold hover:cursor-pointer underline">
(Change Role)
</p>
</Link>
);
}
return (
<Link to="/AdminChangeUserName">
<p className="mb-[20px] hover:font-bold hover:cursor-pointer underline">
(Change Username)
</p>
</Link>
);
};
return ( return (
<div <div
className="fixed inset-0 bg-black bg-opacity-30 backdrop-blur-sm className="fixed inset-0 bg-black bg-opacity-30 backdrop-blur-sm
flex justify-center items-center" flex justify-center items-center"
> >
<div className="border-4 border-black bg-white p-2 rounded-lg text-center"> <div className="border-4 border-black bg-white p-2 rounded-lg text-center flex flex-col">
<p className="font-bold text-[30px]">{props.username}</p> <p className="font-bold text-[30px]">{props.username}</p>
<Link to="/AdminChangeUserName"> {ManageUserOrMember(props.manageMember)}
<p className="mb-[20px] hover:font-bold hover:cursor-pointer underline">
(Change Username)
</p>
</Link>
<div> <div>
<h2 className="font-bold text-[22px] mb-[20px]"> <h2 className="font-bold text-[22px] mb-[20px]">
Member of these projects: Member of these projects:
@ -34,7 +49,13 @@ function UserInfoModal(props: {
<Button <Button
text={"Delete"} text={"Delete"}
onClick={function (): void { onClick={function (): void {
DeleteUser({ usernameToDelete: props.username }); if (
window.confirm("Are you sure you want to delete this user?")
) {
DeleteUser({
usernameToDelete: props.username,
});
}
}} }}
type="button" type="button"
/> />

View file

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

View file

@ -0,0 +1,188 @@
import { useState, useEffect } from "react";
import { WeeklyReport, NewWeeklyReport } from "../Types/goTypes";
import { api } from "../API/API";
import { useNavigate, useParams } from "react-router-dom";
import Button from "./Button";
/**
* Renders the component for editing a weekly report.
* @returns JSX.Element
*/
//This component does not yet work as intended. It is supposed to display the weekly report of a user in a project.
export default function GetOtherUsersReport(): JSX.Element {
const [week, setWeek] = useState(0);
const [developmentTime, setDevelopmentTime] = useState(0);
const [meetingTime, setMeetingTime] = useState(0);
const [adminTime, setAdminTime] = useState(0);
const [ownWorkTime, setOwnWorkTime] = useState(0);
const [studyTime, setStudyTime] = useState(0);
const [testingTime, setTestingTime] = useState(0);
const token = localStorage.getItem("accessToken") ?? "";
const { projectName } = useParams();
const { username } = useParams();
const { fetchedWeek } = useParams();
useEffect(() => {
const fetchUsersWeeklyReport = async (): Promise<void> => {
const response = await api.getWeeklyReport(
projectName ?? "",
fetchedWeek?.toString() ?? "0",
token,
);
if (response.success) {
const report: WeeklyReport = response.data ?? {
reportId: 0,
userId: 0,
projectId: 0,
week: 0,
developmentTime: 0,
meetingTime: 0,
adminTime: 0,
ownWorkTime: 0,
studyTime: 0,
testingTime: 0,
};
setWeek(report.week);
setDevelopmentTime(report.developmentTime);
setMeetingTime(report.meetingTime);
setAdminTime(report.adminTime);
setOwnWorkTime(report.ownWorkTime);
setStudyTime(report.studyTime);
setTestingTime(report.testingTime);
} else {
console.error("Failed to fetch weekly report:", response.message);
}
};
void fetchUsersWeeklyReport();
});
const handleSignWeeklyReport = async (): Promise<void> => {
const newWeeklyReport: NewWeeklyReport = {
projectName: projectName ?? "",
week,
developmentTime,
meetingTime,
adminTime,
ownWorkTime,
studyTime,
testingTime,
};
await api.submitWeeklyReport(newWeeklyReport, token);
};
const navigate = useNavigate();
return (
<>
<h1 className="text-[30px] font-bold">{username}&apos;s Report</h1>
<div className="border-4 border-black bg-white flex flex-col justify-start min-h-[65vh] h-fit w-[50vw] rounded-3xl overflow-scroll space-y-[2vh] p-[30px] items-center">
<form
onSubmit={(e) => {
e.preventDefault();
void handleSignWeeklyReport();
navigate(-1);
}}
>
<div className="flex flex-col items-center">
<div className="flex flex-col w-1/2 border-b-2 border-black items-center justify-center">
<h1 className="font-bold text-[30px]"> Week: {week}</h1>
</div>
<table className="w-full text-center divide-y divide-x divide-white text-[30px]">
<thead>
<tr>
<th className="w-1/2 py-2 border-b-2 border-black">
Activity
</th>
<th className="w-1/2 py-2 border-b-2 border-black">
Total Time (min)
</th>
</tr>
</thead>
<tbody className="divide-y divide-black">
<tr className="h-[10vh]">
<td>Development</td>
<td>
<input
type="text"
min="0"
className="border-2 border-black rounded-md text-center w-1/2"
value={developmentTime === 0 ? "" : developmentTime}
/>
</td>
</tr>
<tr className="h-[10vh]">
<td>Meeting</td>
<td>
<input
type="text"
min="0"
className="border-2 border-black rounded-md text-center w-1/2"
value={meetingTime === 0 ? "" : meetingTime}
/>
</td>
</tr>
<tr className="h-[10vh]">
<td>Administration</td>
<td>
<input
type="text"
min="0"
className="border-2 border-black rounded-md text-center w-1/2"
value={adminTime === 0 ? "" : adminTime}
/>
</td>
</tr>
<tr className="h-[10vh]">
<td>Own Work</td>
<td>
<input
type="text"
min="0"
className="border-2 border-black rounded-md text-center w-1/2"
value={ownWorkTime === 0 ? "" : ownWorkTime}
/>
</td>
</tr>
<tr className="h-[10vh]">
<td>Studies</td>
<td>
<input
type="text"
min="0"
className="border-2 border-black rounded-md text-center w-1/2"
value={studyTime === 0 ? "" : studyTime}
/>
</td>
</tr>
<tr className="h-[10vh]">
<td>Testing</td>
<td>
<input
type="text"
min="0"
className="border-2 border-black rounded-md text-center w-1/2"
value={testingTime === 0 ? "" : testingTime}
/>
</td>
</tr>
</tbody>
</table>
<Button
text="Sign Report"
onClick={(): void => {
return;
}}
type="submit"
/>
</div>
</form>
</div>
</>
);
}

View file

@ -2,9 +2,22 @@ import { Link } from "react-router-dom";
import BackButton from "../../Components/BackButton"; import BackButton from "../../Components/BackButton";
import BasicWindow from "../../Components/BasicWindow"; import BasicWindow from "../../Components/BasicWindow";
import Button from "../../Components/Button"; import Button from "../../Components/Button";
import { ProjectListAdmin } from "../../Components/ProjectListAdmin";
import { Project } from "../../Types/goTypes";
import GetProjects from "../../Components/GetProjects";
import { useState } from "react";
function AdminManageProjects(): JSX.Element { function AdminManageProjects(): JSX.Element {
const content = <></>; const [projects, setProjects] = useState<Project[]>([]);
GetProjects({ setProjectsProp: setProjects });
const content = (
<>
<h1 className="font-bold text-[30px] mb-[20px]">Manage Projects</h1>
<div className="border-4 border-black bg-white flex flex-col items-center h-[65vh] w-[50vw] rounded-3xl content-center overflow-scroll space-y-[10vh] p-[30px]">
<ProjectListAdmin projects={projects} />
</div>
</>
);
const buttons = ( const buttons = (
<> <>

View file

@ -1,27 +1,10 @@
import AddUserToProject from "../../Components/AddUserToProject";
import BasicWindow from "../../Components/BasicWindow"; import BasicWindow from "../../Components/BasicWindow";
import Button from "../../Components/Button";
function AdminProjectAddMember(): JSX.Element { function AdminProjectAddMember(): JSX.Element {
const content = <></>; const content = <AddUserToProject />;
const buttons = ( const buttons = <></>;
<>
<Button
text="Add"
onClick={(): void => {
return;
}}
type="button"
/>
<Button
text="Back"
onClick={(): void => {
return;
}}
type="button"
/>
</>
);
return <BasicWindow content={content} buttons={buttons} />; return <BasicWindow content={content} buttons={buttons} />;
} }

View file

@ -1,3 +1,4 @@
import BackButton from "../../Components/BackButton";
import BasicWindow from "../../Components/BasicWindow"; import BasicWindow from "../../Components/BasicWindow";
import Button from "../../Components/Button"; import Button from "../../Components/Button";
@ -13,13 +14,7 @@ function AdminProjectChangeUserRole(): JSX.Element {
}} }}
type="button" type="button"
/> />
<Button <BackButton />
text="Back"
onClick={(): void => {
return;
}}
type="button"
/>
</> </>
); );

View file

@ -1,3 +1,4 @@
import BackButton from "../../Components/BackButton";
import BasicWindow from "../../Components/BasicWindow"; import BasicWindow from "../../Components/BasicWindow";
import Button from "../../Components/Button"; import Button from "../../Components/Button";
@ -13,13 +14,7 @@ function AdminProjectManageMembers(): JSX.Element {
}} }}
type="button" type="button"
/> />
<Button <BackButton />
text="Back"
onClick={(): void => {
return;
}}
type="button"
/>
</> </>
); );

View file

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

View file

@ -1,18 +1,12 @@
import BackButton from "../../Components/BackButton";
import BasicWindow from "../../Components/BasicWindow"; import BasicWindow from "../../Components/BasicWindow";
import Button from "../../Components/Button";
function AdminProjectStatistics(): JSX.Element { function AdminProjectStatistics(): JSX.Element {
const content = <></>; const content = <></>;
const buttons = ( const buttons = (
<> <>
<Button <BackButton />
text="Back"
onClick={(): void => {
return;
}}
type="button"
/>
</> </>
); );

View file

@ -1,3 +1,4 @@
import BackButton from "../../Components/BackButton";
import BasicWindow from "../../Components/BasicWindow"; import BasicWindow from "../../Components/BasicWindow";
import Button from "../../Components/Button"; import Button from "../../Components/Button";
@ -13,13 +14,7 @@ function AdminProjectViewMemberInfo(): JSX.Element {
}} }}
type="button" type="button"
/> />
<Button <BackButton />
text="Back"
onClick={(): void => {
return;
}}
type="button"
/>
</> </>
); );

View file

@ -4,23 +4,23 @@ body{
@keyframes backgroundTransition { @keyframes backgroundTransition {
0% { 0% {
background-image: url('src/assets/1.jpg'); background-image: url('../assets/1.jpg');
animation-timing-function: ease-out; animation-timing-function: ease-out;
} }
25% { 25% {
background-image: url('src/assets/2.jpg'); background-image: url('../assets/2.jpg');
animation-timing-function: ease-in; animation-timing-function: ease-in;
} }
50% { 50% {
background-image: url('src/assets/3.jpg'); background-image: url('../assets/3.jpg');
animation-timing-function: ease-out; animation-timing-function: ease-out;
} }
75% { 75% {
background-image: url('src/assets/4.jpg'); background-image: url('../assets/4.jpg');
animation-timing-function: ease-in; animation-timing-function: ease-in;
} }
100% { 100% {
background-image: url('src/assets/1.jpg'); background-image: url('../assets/1.jpg');
animation-timing-function: ease-out; animation-timing-function: ease-out;
} }
} }

View file

@ -1,8 +1,13 @@
import BasicWindow from "../../Components/BasicWindow"; import BasicWindow from "../../Components/BasicWindow";
import BackButton from "../../Components/BackButton"; import BackButton from "../../Components/BackButton";
import AllTimeReportsInProjectOtherUser from "../../Components/AllTimeReportsInProjectOtherUser";
function PMOtherUsersTR(): JSX.Element { function PMOtherUsersTR(): JSX.Element {
const content = <></>; const content = (
<>
<AllTimeReportsInProjectOtherUser />
</>
);
const buttons = ( const buttons = (
<> <>

View file

@ -8,16 +8,13 @@ function PMProjectMembers(): JSX.Element {
const { projectName } = useParams(); const { projectName } = useParams();
const content = ( const content = (
<> <>
<h1 className="font-bold text-[30px] mb-[20px]">
All Members In: {projectName}{" "}
</h1>
<ProjectMembers /> <ProjectMembers />
</> </>
); );
const buttons = ( const buttons = (
<> <>
<Link to="/PM-time-activity"> <Link to={`/PMtimeactivity/${projectName}`}>
<Button <Button
text="Time / Activity" text="Time / Activity"
onClick={(): void => { onClick={(): void => {
@ -26,15 +23,6 @@ function PMProjectMembers(): JSX.Element {
type={"button"} type={"button"}
/> />
</Link> </Link>
<Link to="/PM-time-role">
<Button
text="Time / Role"
onClick={(): void => {
return;
}}
type={"button"}
/>
</Link>
<BackButton /> <BackButton />
</> </>
); );

View file

@ -1,14 +1,11 @@
import BackButton from "../../Components/BackButton"; import BackButton from "../../Components/BackButton";
import BasicWindow from "../../Components/BasicWindow"; import BasicWindow from "../../Components/BasicWindow";
import TimeReport from "../../Components/NewWeeklyReport"; import TimePerActivity from "../../Components/TimePerActivity";
function PMTotalTimeActivity(): JSX.Element { function PMTotalTimeActivity(): JSX.Element {
const content = ( const content = (
<> <>
<h1 className="font-bold text-[30px] mb-[20px]"> <TimePerActivity />
Total Time Per Activity
</h1>
<TimeReport />
</> </>
); );

View file

@ -1,8 +1,13 @@
import BasicWindow from "../../Components/BasicWindow"; import BasicWindow from "../../Components/BasicWindow";
import BackButton from "../../Components/BackButton"; import BackButton from "../../Components/BackButton";
import TimePerRole from "../../Components/TimePerRole";
function PMTotalTimeRole(): JSX.Element { function PMTotalTimeRole(): JSX.Element {
const content = <></>; const content = (
<>
<TimePerRole />
</>
);
const buttons = ( const buttons = (
<> <>

View file

@ -1,8 +1,13 @@
import BasicWindow from "../../Components/BasicWindow"; import BasicWindow from "../../Components/BasicWindow";
import BackButton from "../../Components/BackButton"; import BackButton from "../../Components/BackButton";
import DisplayUnsignedReports from "../../Components/DisplayUnsignedReports";
function PMUnsignedReports(): JSX.Element { function PMUnsignedReports(): JSX.Element {
const content = <></>; const content = (
<>
<DisplayUnsignedReports />
</>
);
const buttons = ( const buttons = (
<> <>

View file

@ -0,0 +1,20 @@
import BasicWindow from "../../Components/BasicWindow";
import BackButton from "../../Components/BackButton";
import OtherUsersTR from "../../Components/OtherUsersTR";
function PMViewOtherUsersTR(): JSX.Element {
const content = (
<>
<OtherUsersTR />
</>
);
const buttons = (
<>
<BackButton />
</>
);
return <BasicWindow content={content} buttons={buttons} />;
}
export default PMViewOtherUsersTR;

View file

@ -1,34 +1,16 @@
import BackButton from "../../Components/BackButton"; import BackButton from "../../Components/BackButton";
import BasicWindow from "../../Components/BasicWindow"; import BasicWindow from "../../Components/BasicWindow";
import Button from "../../Components/Button"; import ViewOtherTimeReport from "../../Components/ViewOtherTimeReport";
import TimeReport from "../../Components/NewWeeklyReport";
function PMViewUnsignedReport(): JSX.Element { function PMViewUnsignedReport(): JSX.Element {
const content = ( const content = (
<> <>
<h1 className="font-bold text-[30px] mb-[20px]"> <ViewOtherTimeReport />
Username&apos;s Time Report
</h1>
<TimeReport />
</> </>
); );
const buttons = ( const buttons = (
<> <>
<Button
text="Sign"
onClick={(): void => {
return;
}}
type="button"
/>
<Button
text="Save"
onClick={(): void => {
return;
}}
type="button"
/>
<BackButton /> <BackButton />
</> </>
); );

View file

@ -5,7 +5,6 @@ import EditWeeklyReport from "../../Components/EditWeeklyReport";
function UserEditTimeReportPage(): JSX.Element { function UserEditTimeReportPage(): JSX.Element {
const content = ( const content = (
<> <>
<h1 className="font-bold text-[30px] mb-[20px]">Edit Time Report</h1>
<EditWeeklyReport /> <EditWeeklyReport />
</> </>
); );

View file

@ -151,9 +151,16 @@ export interface NewProject {
*/ */
export interface RoleChange { export interface RoleChange {
username: string; username: string;
role: 'project_manager' | 'user'; role: "project_manager" | "user";
projectname: string; projectname: string;
} }
export interface NewProjMember {
username: string;
projectname: string;
role: string;
}
export interface NameChange { export interface NameChange {
id: number /* int */; id: number /* int */;
name: string; name: string;
@ -184,6 +191,11 @@ export interface PublicUser {
userId: string; userId: string;
username: string; username: string;
} }
export interface UserProjectMember {
Username: string;
UserRole: string;
}
/** /**
* wrapper type for token * wrapper type for token
*/ */

View file

@ -31,6 +31,7 @@ import AdminProjectViewMemberInfo from "./Pages/AdminPages/AdminProjectViewMembe
import AdminProjectPage from "./Pages/AdminPages/AdminProjectPage.tsx"; import AdminProjectPage from "./Pages/AdminPages/AdminProjectPage.tsx";
import NotFoundPage from "./Pages/NotFoundPage.tsx"; import NotFoundPage from "./Pages/NotFoundPage.tsx";
import UnauthorizedPage from "./Pages/UnauthorizedPage.tsx"; import UnauthorizedPage from "./Pages/UnauthorizedPage.tsx";
import PMViewOtherUsersTR from "./Pages/ProjectManagerPages/PMViewOtherUsersTR.tsx";
// This is where the routes are mounted // This is where the routes are mounted
const router = createBrowserRouter([ const router = createBrowserRouter([
@ -60,7 +61,7 @@ const router = createBrowserRouter([
element: <UserViewTimeReportsPage />, element: <UserViewTimeReportsPage />,
}, },
{ {
path: "/editTimeReport/:projectName/:weekNumber", path: "/editTimeReport/:projectName/:fetchedWeek",
element: <UserEditTimeReportPage />, element: <UserEditTimeReportPage />,
}, },
{ {
@ -68,9 +69,13 @@ const router = createBrowserRouter([
element: <PMChangeRole />, element: <PMChangeRole />,
}, },
{ {
path: "/otherUsersTimeReports", path: "/otherUsersTimeReports/:projectName/:username",
element: <PMOtherUsersTR />, element: <PMOtherUsersTR />,
}, },
{
path: "/editOthersTR/:projectName/:username/:fetchedWeek",
element: <PMViewOtherUsersTR />,
},
{ {
path: "/projectMembers/:projectName", path: "/projectMembers/:projectName",
element: <PMProjectMembers />, element: <PMProjectMembers />,
@ -80,11 +85,11 @@ const router = createBrowserRouter([
element: <PMProjectPage />, element: <PMProjectPage />,
}, },
{ {
path: "/PMTimeActivity", path: "/PMTimeActivity/:projectName",
element: <PMTotalTimeActivity />, element: <PMTotalTimeActivity />,
}, },
{ {
path: "/PMTimeRole", path: "/PMTimeRole/:projectName",
element: <PMTotalTimeRole />, element: <PMTotalTimeRole />,
}, },
{ {
@ -92,7 +97,7 @@ const router = createBrowserRouter([
element: <PMUnsignedReports />, element: <PMUnsignedReports />,
}, },
{ {
path: "/PMViewUnsignedReport", path: "/PMViewUnsignedReport/:projectName/:username/:fetchedWeek",
element: <PMViewUnsignedReport />, element: <PMViewUnsignedReport />,
}, },
{ {

View file

@ -1,28 +0,0 @@
github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8=
github.com/klauspost/cpuid/v2 v2.2.3/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY=
github.com/philhofer/fwd v1.1.2/go.mod h1:qkPdfjR2SIEbspLqpe1tO4n5yICnr2DY7mqEx2tUTP0=
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
github.com/tinylib/msgp v1.1.8/go.mod h1:qkpG+2ldGg4xRFmx+jfTvZPxfGFhi64BcnL9vkCm/Tw=
github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4=
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs=
golang.org/x/exp v0.0.0-20231108232855-2478ac86f678/go.mod h1:zk2irFbV9DP96SEBUUAy67IdHUaZuSnrz1n472HUCLE=
golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg=
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE=
golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58=
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
lukechampine.com/uint128 v1.2.0/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk=
modernc.org/cc/v3 v3.41.0/go.mod h1:Ni4zjJYJ04CDOhG7dn640WGfwBzfE0ecX8TyMB0Fv0Y=
modernc.org/ccgo/v3 v3.16.15/go.mod h1:yT7B+/E2m43tmMOT51GMoM98/MtHIcQQSleGnddkUNI=
modernc.org/opt v0.1.3/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0=
sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8=

6
package-lock.json generated
View file

@ -1,6 +0,0 @@
{
"name": "TTime",
"lockfileVersion": 3,
"requires": true,
"packages": {}
}

View file

@ -2,7 +2,7 @@ import requests
import string import string
import random import random
debug_output = False debug_output = True
def gprint(*args, **kwargs): def gprint(*args, **kwargs):
print("\033[92m", *args, "\033[00m", **kwargs) print("\033[92m", *args, "\033[00m", **kwargs)
@ -41,7 +41,10 @@ getWeeklyReportsUserPath = base_url + "/api/getWeeklyReportsUser"
checkIfProjectManagerPath = base_url + "/api/checkIfProjectManager" checkIfProjectManagerPath = base_url + "/api/checkIfProjectManager"
ProjectRoleChangePath = base_url + "/api/ProjectRoleChange" ProjectRoleChangePath = base_url + "/api/ProjectRoleChange"
getUsersProjectPath = base_url + "/api/getUsersProject" getUsersProjectPath = base_url + "/api/getUsersProject"
getUsignedReportsPath = base_url + "/api/getUsignedReports" getUnsignedReportsPath = base_url + "/api/getUnsignedReports"
getChangeUserNamePath = base_url + "/api/changeUserName"
getUpdateWeeklyReportPath = base_url + "/api/updateWeeklyReport"
removeProjectPath = base_url + "/api/removeProject"
#ta bort auth i handlern för att få testet att gå igenom #ta bort auth i handlern för att få testet att gå igenom
def test_ProjectRoleChange(): def test_ProjectRoleChange():
@ -330,45 +333,6 @@ def test_get_weekly_reports_user():
assert response.status_code == 200, "Get weekly reports for user failed" assert response.status_code == 200, "Get weekly reports for user failed"
gprint("test_get_weekly_reports_user successful") gprint("test_get_weekly_reports_user successful")
def test_get_usigned_reports():
# Log in as the user
token = login(username, "always_same").json()["token"]
response = requests.post(
submitReportPath,
json={
"projectName": projectName,
"week": 3,
"developmentTime": 10,
"meetingTime": 5,
"adminTime": 5,
"ownWorkTime": 10,
"studyTime": 10,
"testingTime": 10,
},
headers={"Authorization": "Bearer " + token},
)
dprint(response.text)
assert response.status_code == 200, "Submit report failed"
# 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"
response = requests.get(
getUsignedReportsPath + "/" + projectName,
headers={"Authorization": "Bearer " + token},
)
dprint(response.text)
assert response.status_code == 200, "Get unsigned reports for user failed"
gprint("test_get_usigned_reports successful")
# Test function to check if a user is a project manager # Test function to check if a user is a project manager
@ -409,9 +373,151 @@ def test_ensure_manager_of_created_project():
assert response.json()["isProjectManager"] == True, "User is not project manager" assert response.json()["isProjectManager"] == True, "User is not project manager"
gprint("test_ensure_admin_of_created_project successful") gprint("test_ensure_admin_of_created_project successful")
def test_change_user_name():
# Register a new user
new_user = randomString()
register(new_user, "password")
# Log in as the new user
token = login(new_user, "password").json()["token"]
# Register a new admin
admin_username = randomString()
admin_password = "admin_password"
dprint(
"Registering with username: ", admin_username, " and password: ", admin_password
)
response = requests.post(
registerPath, json={"username": admin_username, "password": admin_password}
)
admin_token = login(admin_username, admin_password).json()["token"]
# Promote to admin
response = requests.post(
promoteToAdminPath,
json={"username": admin_username},
headers={"Authorization": "Bearer " + admin_token},
)
# Change the user's name
response = requests.put(
getChangeUserNamePath,
json={"prevName": new_user, "newName": randomString()},
headers={"Authorization": "Bearer " + admin_token},
)
# Check if the change was successful
assert response.status_code == 200, "Change user name failed"
gprint("test_change_user_name successful")
def test_list_all_users_project():
# Log in as a user who is a member of the project
admin_username = randomString()
admin_password = "admin_password2"
dprint(
"Registering with username: ", admin_username, " and password: ", admin_password
)
response = requests.post(
registerPath, json={"username": admin_username, "password": admin_password}
)
dprint(response.text)
# Log in as the admin
admin_token = login(admin_username, admin_password).json()["token"]
response = requests.post(
promoteToAdminPath,
json={"username": admin_username},
headers={"Authorization": "Bearer " + admin_token},
)
# Make a request to list all users associated with the project
response = requests.get(
getUsersProjectPath + "/" + projectName,
headers={"Authorization": "Bearer " + admin_token},
)
assert response.status_code == 200, "List all users project failed"
gprint("test_list_all_users_project sucessful")
def test_update_weekly_report():
# Log in as the user
token = login(username, "always_same").json()["token"]
# Prepare the JSON data for updating the weekly report
update_data = {
"projectName": projectName,
"userName": username,
"week": 1,
"developmentTime": 8,
"meetingTime": 6,
"adminTime": 4,
"ownWorkTime": 11,
"studyTime": 8,
"testingTime": 18,
}
# Send a request to update the weekly report
response = requests.put(
getUpdateWeeklyReportPath,
json=update_data,
headers={"Authorization": "Bearer " + token},
)
# Check if the update was successful
assert response.status_code == 200, "Update weekly report failed"
gprint("test_update_weekly_report successful")
def test_remove_project():
admin_username = randomString()
admin_password = "admin_password2"
dprint(
"Registering with username: ", admin_username, " and password: ", admin_password
)
response = requests.post(
registerPath, json={"username": admin_username, "password": admin_password}
)
dprint(response.text)
# Log in as the admin
admin_token = login(admin_username, admin_password).json()["token"]
response = requests.post(
promoteToAdminPath,
json={"username": admin_username},
headers={"Authorization": "Bearer " + admin_token},
)
# Create a new project
new_project = randomString()
response = requests.post(
addProjectPath,
json={"name": new_project, "description": "This is a project"},
headers={"Authorization": "Bearer " + admin_token},
)
assert response.status_code == 200, "Add project failed"
# Remove the project
response = requests.delete(
removeProjectPath + "/" + new_project,
headers={"Authorization": "Bearer " + admin_token},
)
assert response.status_code == 200, "Remove project failed"
gprint("test_remove_project successful")
def test_get_unsigned_reports():
# Log in as the user
token = login("user2", "123").json()["token"]
# Make a request to get all unsigned reports
response = requests.get(
getUnsignedReportsPath + "/" + projectName,
headers={"Authorization": "Bearer " + token},
)
assert response.status_code == 200, "Get unsigned reports failed"
gprint("test_get_unsigned_reports successful")
if __name__ == "__main__": if __name__ == "__main__":
test_get_usigned_reports() test_remove_project()
test_get_user_projects() test_get_user_projects()
test_create_user() test_create_user()
test_login() test_login()
@ -424,5 +530,9 @@ if __name__ == "__main__":
test_get_weekly_reports_user() test_get_weekly_reports_user()
test_check_if_project_manager() test_check_if_project_manager()
test_ProjectRoleChange() test_ProjectRoleChange()
#test_list_all_users_project()
test_ensure_manager_of_created_project() test_ensure_manager_of_created_project()
test_get_unsigned_reports()
test_list_all_users_project()
test_change_user_name()
test_update_weekly_report()