Merge branch 'dev' into BumBranch

This commit is contained in:
al8763be 2024-03-20 19:39:05 +01:00
commit be3adb09ae
45 changed files with 902 additions and 261 deletions

View file

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

View file

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

View file

@ -39,6 +39,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)
} }
// This struct is a wrapper type that holds the database connection // This struct is a wrapper type that holds the database connection
@ -66,10 +69,8 @@ const addWeeklyReport = `WITH UserLookup AS (SELECT id FROM users WHERE username
ProjectLookup AS (SELECT id FROM projects WHERE name = ?) ProjectLookup AS (SELECT id FROM projects WHERE name = ?)
INSERT INTO weekly_reports (project_id, user_id, week, development_time, meeting_time, admin_time, own_work_time, study_time, testing_time) INSERT INTO weekly_reports (project_id, user_id, week, development_time, meeting_time, admin_time, own_work_time, study_time, testing_time)
VALUES ((SELECT id FROM ProjectLookup), (SELECT id FROM UserLookup),?, ?, ?, ?, ?, ?, ?);` VALUES ((SELECT id FROM ProjectLookup), (SELECT id FROM UserLookup),?, ?, ?, ?, ?, ?, ?);`
const addUserToProject = "INSERT INTO user_roles (user_id, project_id, p_role) VALUES (?, ?, ?)" // WIP const addUserToProject = "INSERT INTO user_roles (user_id, project_id, p_role) VALUES (?, ?, ?)"
const changeUserRole = "UPDATE user_roles SET p_role = ? WHERE user_id = ? AND project_id = ?" const changeUserRole = "UPDATE user_roles SET p_role = ? WHERE user_id = ? AND project_id = ?"
const changeUserName = "UPDATE user SET username = ? WHERE user_id = ?" // WIP
const getProjectsForUser = `SELECT p.id, p.name, p.description FROM projects p const getProjectsForUser = `SELECT p.id, p.name, p.description FROM projects p
JOIN user_roles ur ON p.id = ur.project_id JOIN user_roles ur ON p.id = ur.project_id
JOIN users u ON ur.user_id = u.id JOIN users u ON ur.user_id = u.id
@ -133,7 +134,7 @@ func (d *Db) AddWeeklyReport(projectName string, userName string, week int, deve
} }
// AddUserToProject adds a user to a project with a specified role. // AddUserToProject adds a user to a project with a specified role.
func (d *Db) AddUserToProject(username string, projectname string, role string) error { // WIP func (d *Db) AddUserToProject(username string, projectname string, role string) error {
var userid int var userid int
userid, err := d.GetUserId(username) userid, err := d.GetUserId(username)
if err != nil { if err != nil {
@ -171,18 +172,11 @@ func (d *Db) ChangeUserRole(username string, projectname string, role string) er
return err3 return err3
} }
// ChangeUserRole changes the role of a user within a project. // ChangeUserName changes the username of a user.
func (d *Db) ChangeUserName(username string, newname string) error { func (d *Db) ChangeUserName(username string, newname string) error {
// Get the user ID // Execute the SQL query to update the username
var userid int _, err := d.Exec("UPDATE users SET username = ? WHERE username = ?", newname, username)
userid, err := d.GetUserId(username) return err
if err != nil {
panic(err)
}
// Execute the SQL query to change the user's role
_, err2 := d.Exec(changeUserName, username, userid)
return err2
} }
// GetUserRole retrieves the role of a user within a project. // GetUserRole retrieves the role of a user within a project.
@ -528,3 +522,41 @@ func (d *Db) MigrateSampleData() error {
return nil return nil
} }
func (d *Db) GetTotalTimePerActivity(projectName string) (map[string]int, error) {
query := `
SELECT development_time, meeting_time, admin_time, own_work_time, study_time, testing_time
FROM weekly_reports
JOIN projects ON weekly_reports.project_id = projects.id
WHERE projects.name = ?
`
rows, err := d.DB.Query(query, projectName)
if err != nil {
return nil, err
}
defer rows.Close()
totalTime := make(map[string]int)
for rows.Next() {
var developmentTime, meetingTime, adminTime, ownWorkTime, studyTime, testingTime int
if err := rows.Scan(&developmentTime, &meetingTime, &adminTime, &ownWorkTime, &studyTime, &testingTime); err != nil {
return nil, err
}
totalTime["development"] += developmentTime
totalTime["meeting"] += meetingTime
totalTime["admin"] += adminTime
totalTime["own_work"] += ownWorkTime
totalTime["study"] += studyTime
totalTime["testing"] += testingTime
}
if err := rows.Err(); err != nil {
return nil, err
}
return totalTime, nil
}

View file

@ -7,6 +7,7 @@ import (
// Tests are not guaranteed to be sequential // Tests are not guaranteed to be sequential
// setupState initializes a database instance with necessary setup for testing
func setupState() (Database, error) { func setupState() (Database, error) {
db := DbConnect(":memory:") db := DbConnect(":memory:")
err := db.Migrate() err := db.Migrate()
@ -16,11 +17,13 @@ func setupState() (Database, error) {
return db, nil return db, nil
} }
// TestDbConnect tests the connection to the database
func TestDbConnect(t *testing.T) { func TestDbConnect(t *testing.T) {
db := DbConnect(":memory:") db := DbConnect(":memory:")
_ = db _ = db
} }
// TestDbAddUser tests the AddUser function of the database
func TestDbAddUser(t *testing.T) { func TestDbAddUser(t *testing.T) {
db, err := setupState() db, err := setupState()
if err != nil { if err != nil {
@ -32,6 +35,7 @@ func TestDbAddUser(t *testing.T) {
} }
} }
// TestDbGetUserId tests the GetUserID function of the database
func TestDbGetUserId(t *testing.T) { func TestDbGetUserId(t *testing.T) {
db, err := setupState() db, err := setupState()
if err != nil { if err != nil {
@ -52,6 +56,7 @@ func TestDbGetUserId(t *testing.T) {
} }
} }
// TestDbAddProject tests the AddProject function of the database
func TestDbAddProject(t *testing.T) { func TestDbAddProject(t *testing.T) {
db, err := setupState() db, err := setupState()
if err != nil { if err != nil {
@ -64,6 +69,7 @@ func TestDbAddProject(t *testing.T) {
} }
} }
// TestDbRemoveUser tests the RemoveUser function of the database
func TestDbRemoveUser(t *testing.T) { func TestDbRemoveUser(t *testing.T) {
db, err := setupState() db, err := setupState()
if err != nil { if err != nil {
@ -76,6 +82,7 @@ func TestDbRemoveUser(t *testing.T) {
} }
} }
// TestPromoteToAdmin tests the PromoteToAdmin function of the database
func TestPromoteToAdmin(t *testing.T) { func TestPromoteToAdmin(t *testing.T) {
db, err := setupState() db, err := setupState()
if err != nil { if err != nil {
@ -93,6 +100,7 @@ func TestPromoteToAdmin(t *testing.T) {
} }
} }
// TestAddWeeklyReport tests the AddWeeklyReport function of the database
func TestAddWeeklyReport(t *testing.T) { func TestAddWeeklyReport(t *testing.T) {
db, err := setupState() db, err := setupState()
if err != nil { if err != nil {
@ -115,6 +123,7 @@ func TestAddWeeklyReport(t *testing.T) {
} }
} }
// TestAddUserToProject tests the AddUseToProject function of the database
func TestAddUserToProject(t *testing.T) { func TestAddUserToProject(t *testing.T) {
db, err := setupState() db, err := setupState()
if err != nil { if err != nil {
@ -142,6 +151,7 @@ func TestAddUserToProject(t *testing.T) {
} }
} }
// TestChangeUserRole tests the ChangeUserRole function of the database
func TestChangeUserRole(t *testing.T) { func TestChangeUserRole(t *testing.T) {
db, err := setupState() db, err := setupState()
if err != nil { if err != nil {
@ -186,6 +196,7 @@ func TestChangeUserRole(t *testing.T) {
} }
// TestGetAllUsersProject tests the GetAllUsersProject function of the database
func TestGetAllUsersProject(t *testing.T) { func TestGetAllUsersProject(t *testing.T) {
db, err := setupState() db, err := setupState()
if err != nil { if err != nil {
@ -252,6 +263,7 @@ func TestGetAllUsersProject(t *testing.T) {
} }
} }
// TestGetAllUsersApplication tests the GetAllUsersApplicsation function of the database
func TestGetAllUsersApplication(t *testing.T) { func TestGetAllUsersApplication(t *testing.T) {
db, err := setupState() db, err := setupState()
if err != nil { if err != nil {
@ -298,6 +310,7 @@ func TestGetAllUsersApplication(t *testing.T) {
} }
} }
// TestGetProjectsForUser tests the GetProjectsForUser function of the database
func TestGetProjectsForUser(t *testing.T) { func TestGetProjectsForUser(t *testing.T) {
db, err := setupState() db, err := setupState()
if err != nil { if err != nil {
@ -338,6 +351,7 @@ func TestGetProjectsForUser(t *testing.T) {
} }
} }
// TestAddProject tests AddProject function of the database
func TestAddProject(t *testing.T) { func TestAddProject(t *testing.T) {
db, err := setupState() db, err := setupState()
if err != nil { if err != nil {
@ -373,6 +387,7 @@ func TestAddProject(t *testing.T) {
} }
} }
// TestGetWeeklyReport tests GetWeeklyReport function of the database
func TestGetWeeklyReport(t *testing.T) { func TestGetWeeklyReport(t *testing.T) {
db, err := setupState() db, err := setupState()
if err != nil { if err != nil {
@ -412,6 +427,7 @@ func TestGetWeeklyReport(t *testing.T) {
// Check other fields similarly // Check other fields similarly
} }
// TestSignWeeklyReport tests SignWeeklyReport function of the database
func TestSignWeeklyReport(t *testing.T) { func TestSignWeeklyReport(t *testing.T) {
db, err := setupState() db, err := setupState()
if err != nil { if err != nil {
@ -484,6 +500,7 @@ func TestSignWeeklyReport(t *testing.T) {
} }
} }
// TestSignWeeklyReportByAnotherProjectManager tests the scenario where a project manager attempts to sign a weekly report for a user who is not assigned to their project
func TestSignWeeklyReportByAnotherProjectManager(t *testing.T) { func TestSignWeeklyReportByAnotherProjectManager(t *testing.T) {
db, err := setupState() db, err := setupState()
if err != nil { if err != nil {
@ -537,6 +554,7 @@ func TestSignWeeklyReportByAnotherProjectManager(t *testing.T) {
} }
} }
// TestGetProject tests GetProject function of the database
func TestGetProject(t *testing.T) { func TestGetProject(t *testing.T) {
db, err := setupState() db, err := setupState()
if err != nil { if err != nil {
@ -657,3 +675,52 @@ func TestIsProjectManager(t *testing.T) {
t.Error("Expected projectManager to be a project manager, but it's not.") t.Error("Expected projectManager to be a project manager, but it's not.")
} }
} }
func TestGetTotalTimePerActivity(t *testing.T) {
// Initialize your test database connection
db, err := setupState()
if err != nil {
t.Error("setupState failed:", err)
}
// Run the query to get total time per activity
totalTime, err := db.GetTotalTimePerActivity("projecttest")
if err != nil {
t.Error("GetTotalTimePerActivity failed:", err)
}
// Check if the totalTime map is not nil
if totalTime == nil {
t.Error("Expected non-nil totalTime map, got nil")
}
// ska lägga till fler assertions
}
func TestEnsureManagerOfCreatedProject(t *testing.T) {
db, err := setupState()
if err != nil {
t.Error("setupState failed:", err)
}
// Add a user
err = db.AddUser("testuser", "password")
if err != nil {
t.Error("AddUser failed:", err)
}
// Add a project
err = db.AddProject("testproject", "description", "testuser")
if err != nil {
t.Error("AddProject failed:", err)
}
managerState, err := db.IsProjectManager("testuser", "testproject")
if err != nil {
t.Error("IsProjectManager failed:", err)
}
if !managerState {
t.Error("Expected testuser to be a project manager, but it's not.")
}
}

View file

@ -212,8 +212,12 @@ func (gs *GState) AddUserToProjectHandler(c *fiber.Ctx) error {
// IsProjectManagerHandler is a handler that checks if a user is a project manager for a given project // IsProjectManagerHandler is a handler that checks if a user is a project manager for a given project
func (gs *GState) IsProjectManagerHandler(c *fiber.Ctx) error { func (gs *GState) IsProjectManagerHandler(c *fiber.Ctx) error {
// Get the username from the token
user := c.Locals("user").(*jwt.Token)
claims := user.Claims.(jwt.MapClaims)
username := claims["name"].(string)
// Extract necessary parameters from the request query string // Extract necessary parameters from the request query string
username := c.Query("username")
projectName := c.Query("projectName") projectName := c.Query("projectName")
// Check if the user is a project manager for the specified project // Check if the user is a project manager for the specified project

View file

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

View file

@ -256,7 +256,7 @@ func (gs *GState) ChangeUserName(c *fiber.Ctx) error {
return c.Status(500).SendString(err.Error()) return c.Status(500).SendString(err.Error())
} else if !ismanager { } else if !ismanager {
log.Warn("tried changing name when not projectmanager:", err) log.Warn("tried changing name when not projectmanager:", err)
return c.Status(401).SendString("you can not change name when not projectManager") return c.Status(401).SendString("you can not change name when not projectmanager")
} }
// Change the user's name within the project in the database // Change the user's name within the project in the database

View file

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

View file

@ -97,8 +97,8 @@ func main() {
server.Put("/api/changeUserName", gs.ChangeUserName) server.Put("/api/changeUserName", gs.ChangeUserName)
server.Post("/api/promoteToAdmin", gs.PromoteToAdmin) server.Post("/api/promoteToAdmin", gs.PromoteToAdmin)
server.Get("/api/users/all", gs.ListAllUsers) server.Get("/api/users/all", gs.ListAllUsers)
server.Get("/api/getWeeklyReportsUser", gs.GetWeeklyReportsUserHandler) server.Get("/api/getWeeklyReportsUser/:projectName", gs.GetWeeklyReportsUserHandler)
server.Get("api/checkIfProjectManager", gs.IsProjectManagerHandler) server.Get("/api/checkIfProjectManager/: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)

View file

@ -49,8 +49,15 @@ interface API {
week: string, week: string,
token: string, token: string,
): Promise<APIResponse<WeeklyReport>>; ): Promise<APIResponse<WeeklyReport>>;
/**
* Returns all the weekly reports for a user in a particular project
* The username is derived from the token
*
* @param {string} projectName The name of the project
* @param {string} token The token of the user
* @returns {APIResponse<WeeklyReport[]>} A list of weekly reports
*/
getWeeklyReportsForUser( getWeeklyReportsForUser(
username: string,
projectName: string, projectName: string,
token: string, token: string,
): Promise<APIResponse<WeeklyReport[]>>; ): Promise<APIResponse<WeeklyReport[]>>;
@ -58,6 +65,8 @@ interface API {
getUserProjects(token: string): Promise<APIResponse<Project[]>>; getUserProjects(token: string): Promise<APIResponse<Project[]>>;
/** Gets a project from id*/ /** Gets a project from id*/
getProject(id: number): Promise<APIResponse<Project>>; getProject(id: number): Promise<APIResponse<Project>>;
/** Gets a project from id*/
getAllUsers(token: string): Promise<APIResponse<string[]>>;
} }
// Export an instance of the API // Export an instance of the API
@ -94,7 +103,7 @@ export const api: API = {
token: string, token: string,
): Promise<APIResponse<User>> { ): Promise<APIResponse<User>> {
try { try {
const response = await fetch("/api/userdelete", { const response = await fetch(`/api/userdelete/${username}`, {
method: "POST", method: "POST",
headers: { headers: {
"Content-Type": "application/json", "Content-Type": "application/json",
@ -275,12 +284,11 @@ export const api: API = {
}, },
async getWeeklyReportsForUser( async getWeeklyReportsForUser(
username: string,
projectName: string, projectName: string,
token: string, token: string,
): Promise<APIResponse<WeeklyReport[]>> { ): Promise<APIResponse<WeeklyReport[]>> {
try { try {
const response = await fetch(`/api/getWeeklyReportsUser?username=${username}&projectName=${projectName}`, { const response = await fetch(`/api/getWeeklyReportsUser/${projectName}`, {
method: "GET", method: "GET",
headers: { headers: {
"Content-Type": "application/json", "Content-Type": "application/json",
@ -291,7 +299,9 @@ export const api: API = {
if (!response.ok) { if (!response.ok) {
return { return {
success: false, success: false,
message: "Failed to get weekly reports for project", message:
"Failed to get weekly reports for project: Response code " +
response.status,
}; };
} else { } else {
const data = (await response.json()) as WeeklyReport[]; const data = (await response.json()) as WeeklyReport[];
@ -300,7 +310,7 @@ export const api: API = {
} catch (e) { } catch (e) {
return { return {
success: false, success: false,
message: "fucked again", message: "Failed to get weekly reports for project, unknown error",
}; };
} }
}, },
@ -351,4 +361,32 @@ export const api: API = {
}; };
} }
}, },
// Gets all users
async getAllUsers(token: string): Promise<APIResponse<string[]>> {
try {
const response = await fetch("/api/users/all", {
method: "GET",
headers: {
"Content-Type": "application/json",
Authorization: "Bearer " + token,
},
});
if (!response.ok) {
return Promise.resolve({
success: false,
message: "Failed to get users",
});
} else {
const data = (await response.json()) as string[];
return Promise.resolve({ success: true, data });
}
} catch (e) {
return Promise.resolve({
success: false,
message: "API is not ok",
});
}
},
}; };

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,11 +1,17 @@
//info: New weekly report form component to create a new weekly report to
//sumbit development time, meeting time, admin time, own work time, study time and testing time
import { useState } from "react"; import { useState } from "react";
import type { NewWeeklyReport } from "../Types/goTypes"; import type { NewWeeklyReport } from "../Types/goTypes";
import { api } from "../API/API"; import { api } from "../API/API";
import { useNavigate, useParams } from "react-router-dom"; import { useNavigate, useParams } from "react-router-dom";
import Button from "./Button"; import Button from "./Button";
/**
* Renders a form for creating a new weekly report.
* @returns The JSX element representing the new weekly report form.
*/
export default function NewWeeklyReport(): JSX.Element { export default function NewWeeklyReport(): JSX.Element {
const [week, setWeek] = useState<number>(); const [week, setWeek] = useState<number>(0);
const [developmentTime, setDevelopmentTime] = useState<number>(); const [developmentTime, setDevelopmentTime] = useState<number>();
const [meetingTime, setMeetingTime] = useState<number>(); const [meetingTime, setMeetingTime] = useState<number>();
const [adminTime, setAdminTime] = useState<number>(); const [adminTime, setAdminTime] = useState<number>();
@ -19,7 +25,7 @@ export default function NewWeeklyReport(): JSX.Element {
const handleNewWeeklyReport = async (): Promise<void> => { const handleNewWeeklyReport = async (): Promise<void> => {
const newWeeklyReport: NewWeeklyReport = { const newWeeklyReport: NewWeeklyReport = {
projectName: projectName ?? "", projectName: projectName ?? "",
week: week ?? 0, week: week,
developmentTime: developmentTime ?? 0, developmentTime: developmentTime ?? 0,
meetingTime: meetingTime ?? 0, meetingTime: meetingTime ?? 0,
adminTime: adminTime ?? 0, adminTime: adminTime ?? 0,
@ -45,7 +51,7 @@ export default function NewWeeklyReport(): JSX.Element {
} }
e.preventDefault(); e.preventDefault();
void handleNewWeeklyReport(); void handleNewWeeklyReport();
navigate("/project"); navigate(-1);
}} }}
> >
<div className="flex flex-col items-center"> <div className="flex flex-col items-center">

View file

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

View file

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

View file

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

View file

@ -1,14 +1,6 @@
import { useState } from "react"; import { useState } from "react";
import { PublicUser } from "../Types/goTypes";
import UserInfoModal from "./UserInfoModal"; import UserInfoModal from "./UserInfoModal";
/**
* The props for the UserProps component
*/
interface UserProps {
users: PublicUser[];
}
/** /**
* 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
* function for eact user <li> element, which displays a modul with * function for eact user <li> element, which displays a modul with
@ -20,7 +12,7 @@ interface UserProps {
* return <UserList users={users} />; * return <UserList users={users} />;
*/ */
export function UserListAdmin(props: UserProps): JSX.Element { export function UserListAdmin(props: { users: string[] }): JSX.Element {
const [modalVisible, setModalVisible] = useState(false); const [modalVisible, setModalVisible] = useState(false);
const [username, setUsername] = useState(""); const [username, setUsername] = useState("");
@ -46,12 +38,12 @@ export function UserListAdmin(props: UserProps): JSX.Element {
{props.users.map((user) => ( {props.users.map((user) => (
<li <li
className="pt-5" className="pt-5"
key={user.userId} key={user}
onClick={() => { onClick={() => {
handleClick(user.username); handleClick(user);
}} }}
> >
{user.username} {user}
</li> </li>
))} ))}
</ul> </ul>

View file

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

View file

@ -1,9 +1,14 @@
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 ChangeUsername from "../../Components/ChangeUsername";
function AdminChangeUsername(): JSX.Element { function AdminChangeUsername(): JSX.Element {
const content = <></>; const content = (
<>
<ChangeUsername />
</>
);
const buttons = ( const buttons = (
<> <>

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -30,6 +30,7 @@ import AdminProjectStatistics from "./Pages/AdminPages/AdminProjectStatistics.ts
import AdminProjectViewMemberInfo from "./Pages/AdminPages/AdminProjectViewMemberInfo.tsx"; import AdminProjectViewMemberInfo from "./Pages/AdminPages/AdminProjectViewMemberInfo.tsx";
import AdminProjectPage from "./Pages/AdminPages/AdminProjectPage.tsx"; import AdminProjectPage from "./Pages/AdminPages/AdminProjectPage.tsx";
import NotFoundPage from "./Pages/NotFoundPage.tsx"; import NotFoundPage from "./Pages/NotFoundPage.tsx";
import UnauthorizedPage from "./Pages/UnauthorizedPage.tsx";
// This is where the routes are mounted // This is where the routes are mounted
const router = createBrowserRouter([ const router = createBrowserRouter([
@ -42,10 +43,6 @@ const router = createBrowserRouter([
path: "/admin", path: "/admin",
element: <AdminMenuPage />, element: <AdminMenuPage />,
}, },
{
path: "/pm",
element: <YourProjectsPage />,
},
{ {
path: "/yourProjects", path: "/yourProjects",
element: <YourProjectsPage />, element: <YourProjectsPage />,
@ -59,15 +56,15 @@ const router = createBrowserRouter([
element: <UserNewTimeReportPage />, element: <UserNewTimeReportPage />,
}, },
{ {
path: "/projectPage/:projectName", path: "/timeReports/:projectName",
element: <UserViewTimeReportsPage />, element: <UserViewTimeReportsPage />,
}, },
{ {
path: "/editTimeReport", path: "/editTimeReport/:projectName/:weekNumber",
element: <UserEditTimeReportPage />, element: <UserEditTimeReportPage />,
}, },
{ {
path: "/changeRole", path: "/changeRole/:projectName/:username",
element: <PMChangeRole />, element: <PMChangeRole />,
}, },
{ {
@ -75,11 +72,11 @@ const router = createBrowserRouter([
element: <PMOtherUsersTR />, element: <PMOtherUsersTR />,
}, },
{ {
path: "/projectMembers", path: "/projectMembers/:projectName",
element: <PMProjectMembers />, element: <PMProjectMembers />,
}, },
{ {
path: "/PMProjectPage", path: "/PMProjectPage/:projectName",
element: <PMProjectPage />, element: <PMProjectPage />,
}, },
{ {
@ -91,7 +88,7 @@ const router = createBrowserRouter([
element: <PMTotalTimeRole />, element: <PMTotalTimeRole />,
}, },
{ {
path: "/PMUnsignedReports", path: "/unsignedReports/:projectName",
element: <PMUnsignedReports />, element: <PMUnsignedReports />,
}, },
{ {
@ -146,6 +143,10 @@ const router = createBrowserRouter([
path: "/adminManageUser", path: "/adminManageUser",
element: <AdminManageUsers />, element: <AdminManageUsers />,
}, },
{
path: "/unauthorized",
element: <UnauthorizedPage />,
},
]); ]);
// Semi-hacky way to get the root element // Semi-hacky way to get the root element

View file

@ -314,9 +314,8 @@ def test_get_weekly_reports_user():
# Get weekly reports for the user in the project # Get weekly reports for the user in the project
response = requests.get( response = requests.get(
getWeeklyReportsUserPath, getWeeklyReportsUserPath + "/" + projectName,
headers={"Authorization": "Bearer " + token}, headers={"Authorization": "Bearer " + token},
params={"username": username, "projectName": projectName},
) )
dprint(response.text) dprint(response.text)
@ -330,9 +329,8 @@ def test_check_if_project_manager():
# Check if the user is a project manager for the project # Check if the user is a project manager for the project
response = requests.get( response = requests.get(
checkIfProjectManagerPath, checkIfProjectManagerPath + "/" + projectName,
headers={"Authorization": "Bearer " + token}, headers={"Authorization": "Bearer " + token},
params={"username": username, "projectName": projectName},
) )
dprint(response.text) dprint(response.text)