Compare commits

...

40 commits

Author SHA1 Message Date
Imbus
a81020f660 Removing verbose flag from testing 2024-04-04 23:26:53 +02:00
Imbus
7e88bd69c1 Killing process if tests fail in itest target 2024-04-04 23:24:38 +02:00
Imbus
d65bbc897d Fixing logic error in paths related to getting reports 2024-04-04 23:23:11 +02:00
Davenludd
7964e0d1d7 Merge branch 'gruppDM' into dev 2024-04-04 23:04:38 +02:00
Davenludd
591d0201cf Update AllTimeReportsInProjectOtherUser component to display signed status correct 2024-04-04 23:03:30 +02:00
Imbus
f7f29241b3 Fixing itest makefile target 2024-04-04 23:02:19 +02:00
Imbus
1f4abc2a6a Linter fixes and proper error handling in PromoteToPm 2024-04-04 23:02:10 +02:00
Davenludd
7635791f09 Merge branch 'frontend' into gruppDM 2024-04-04 22:40:59 +02:00
Imbus
68e6f94d13 Fixing report signing database interface 2024-04-04 22:39:55 +02:00
Davenludd
91bccba871 Add username parameter and error alerts in GetOtherUsersReport component 2024-04-04 22:20:09 +02:00
Davenludd
a6f3fc4a1c Fixed DisplayUserProject component to fetch and display usernames 2024-04-04 22:19:35 +02:00
Davenludd
544383809b Merge branch 'frontend' into gruppDM 2024-04-04 20:10:57 +02:00
Imbus
abfb79b991 getUserName path implemented 2024-04-04 19:54:31 +02:00
Peter KW
db6fdf3c29 Small design fix 2024-04-04 12:27:38 +02:00
Peter KW
5bc7aadfa3 Merge remote-tracking branch 'origin/frontend' into gruppPP 2024-04-04 12:22:55 +02:00
Peter KW
2d8d200340 Can now remove users from projects 2024-04-04 12:22:28 +02:00
Peter KW
0cb6af03e4 Merge remote-tracking branch 'origin/dev' into gruppPP 2024-04-04 12:00:55 +02:00
Davenludd
f75e6e4a6e Update useEffect dependencies in AllTimeReportsInProjectOtherUser component 2024-04-04 11:55:04 +02:00
Imbus
10d06767c4 Fix for javascript optional parameter formatting error 2024-04-04 11:55:04 +02:00
Imbus
ee49bbde69 Docs 2024-04-04 11:55:04 +02:00
Davenludd
266aaa482c Fix initial state values in TimePerRole component 2024-04-04 11:55:04 +02:00
Davenludd
194e1d52a8 Fix input fields to be read-only in OtherUsersTR and initialize state variables in TimePerRole 2024-04-04 11:55:04 +02:00
Davenludd
82e72432f6 Update API method to get all weekly reports 2024-04-04 11:55:04 +02:00
Imbus
620073f688 Logic error in getAllWeeklyReports fixed 2024-04-04 11:55:04 +02:00
Imbus
7313a27b31 Rename 2024-04-04 11:55:04 +02:00
Davenludd
d7789ab844 Refactor API call in AllTimeReportsInProjectOtherUser component 2024-04-04 11:55:04 +02:00
Imbus
4319f30799 Tests for getAllWeeklyReports 2024-04-04 11:55:03 +02:00
Imbus
2ca8c60418 TS Api for getAllWeeklyReport 2024-04-04 11:55:03 +02:00
Imbus
0a951ecd2b Rename, fix and testing for getAllWeeklyReports path 2024-04-04 11:55:03 +02:00
Peter KW
13eb6597a7 Some fixes to how they handle names and inputs 2024-04-04 11:54:34 +02:00
Peter KW
9a0f855d2b Fixes to adding members 2024-04-04 11:26:39 +02:00
Davenludd
7c995573ef Update error message in NewWeeklyReport component 2024-04-04 11:19:35 +02:00
Mattias
a5ea74c996 Add useNavigate hook and handle navigation based on user "role" 2024-04-04 10:29:25 +02:00
Davenludd
a0ff329845 Update useEffect dependencies in AllTimeReportsInProjectOtherUser component 2024-04-03 18:36:17 +02:00
Davenludd
91671b2b63 Merge branch 'frontend' into gruppDM 2024-04-03 18:07:18 +02:00
Davenludd
66a064c9f8 Fix initial state values in TimePerRole component 2024-04-03 18:05:30 +02:00
Davenludd
be963b2281 Fix input fields to be read-only in OtherUsersTR and initialize state variables in TimePerRole 2024-04-03 18:05:19 +02:00
Davenludd
c0d36f8df1 Update API method to get all weekly reports 2024-04-03 18:05:07 +02:00
Davenludd
520e2f74f0 Merge branch 'frontend' into gruppDM 2024-04-03 17:45:32 +02:00
Davenludd
8cc0458e1b Refactor API call in AllTimeReportsInProjectOtherUser component 2024-04-03 17:44:26 +02:00
26 changed files with 344 additions and 178 deletions

View file

@ -47,7 +47,7 @@ itest:
make build
./bin/$(PROC_NAME) >/dev/null 2>&1 &
sleep 1 # Adjust if needed
python ../testing.py
python ../testing/testing.py || pkill $(PROC_NAME)
pkill $(PROC_NAME)
# Get dependencies target

View file

@ -44,6 +44,7 @@ type Database interface {
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
GetUserName(id int) (string, error)
}
// This struct is a wrapper type that holds the database connection
@ -349,9 +350,14 @@ func (d *Db) SignWeeklyReport(reportId int, projectManagerId int) error {
return err
}
managerQuery := `SELECT project_id FROM user_roles
WHERE user_id = ?
AND project_id = (SELECT project_id FROM weekly_reports WHERE report_id = ?)
AND p_role = 'project_manager'`
// Retrieve the project ID associated with the project manager
var managerProjectID int
err = d.Get(&managerProjectID, "SELECT project_id FROM user_roles WHERE user_id = ? AND p_role = 'project_manager'", projectManagerId)
err = d.Get(&managerProjectID, managerQuery, projectManagerId, reportId)
if err != nil {
return err
}
@ -611,3 +617,9 @@ func (d *Db) RemoveProject(projectname string) error {
_, err := d.Exec("DELETE FROM projects WHERE name = ?", projectname)
return err
}
func (d *Db) GetUserName(id int) (string, error) {
var username string
err := d.Get(&username, "SELECT username FROM users WHERE id = ?", id)
return username, err
}

View file

@ -44,6 +44,10 @@ func PromoteToPm(c *fiber.Ctx) error {
// Add the user to the project with the specified role
err = db.GetDb(c).ChangeUserRole(new_pm_name, project, "project_manager")
if err != nil {
log.Info("Error promoting user to project manager:", err)
return c.Status(500).SendString(err.Error())
}
// Return success message
log.Info("User : ", new_pm_name, " promoted to project manager in project: ", project)

View file

@ -38,7 +38,7 @@ func GetAllWeeklyReports(c *fiber.Ctx) error {
return c.Status(500).SendString(err.Error())
}
if pm == false && target_user != username {
if !(pm || target_user == username) {
log.Info("Unauthorized access")
return c.Status(403).SendString("Unauthorized access")
}

View file

@ -47,7 +47,7 @@ func GetWeeklyReport(c *fiber.Ctx) error {
return c.Status(500).SendString(err.Error())
}
if pm == false && target_user != username {
if !(pm || target_user == username) {
log.Info("Unauthorized access")
return c.Status(403).SendString("Unauthorized access")
}

View file

@ -0,0 +1,32 @@
package users
import (
"strconv"
db "ttime/internal/database"
"github.com/gofiber/fiber/v2"
)
// Return the username of a user given their user id
func GetUserName(c *fiber.Ctx) error {
// Check the query params for userId
user_id_string := c.Query("userId")
if user_id_string == "" {
return c.Status(400).SendString("Missing user id")
}
// Convert to int
user_id, err := strconv.Atoi(user_id_string)
if err != nil {
return c.Status(400).SendString("Invalid user id")
}
// Get the username from the database
username, err := db.GetDb(c).GetUserName(user_id)
if err != nil {
return c.Status(500).SendString(err.Error())
}
// Send the nuclear launch codes to north korea
return c.JSON(fiber.Map{"username": username})
}

View file

@ -103,6 +103,7 @@ func main() {
// userGroup := api.Group("/user") // Not currently in use
api.Get("/users/all", users.ListAllUsers)
api.Get("/project/getAllUsers", users.GetAllUsersProject)
api.Get("/username", users.GetUserName)
api.Post("/login", users.Login)
api.Post("/register", users.Register)
api.Post("/loginrenew", users.LoginRenew)

View file

@ -1,4 +1,4 @@
import { NewProjMember } from "../Components/AddMember";
import { AddMemberInfo } from "../Components/AddMember";
import { ProjectRoleChange } from "../Components/ChangeRole";
import { projectTimes } from "../Components/GetProjectTimes";
import { ProjectMember } from "../Components/GetUsersInProject";
@ -197,7 +197,7 @@ interface API {
): Promise<APIResponse<void>>;
addUserToProject(
user: NewProjMember,
addMemberInfo: AddMemberInfo,
token: string,
): Promise<APIResponse<void>>;
@ -233,6 +233,12 @@ interface API {
projectName: string,
token: string,
): Promise<APIResponse<string>>;
/**
* Get the username from the id
* @param {number} id The id of the user
* @param {string} token Your token
*/
getUsername(id: number, token: string): Promise<APIResponse<string>>;
}
/** An instance of the API */
@ -342,18 +348,20 @@ export const api: API = {
},
async addUserToProject(
user: NewProjMember,
addMemberInfo: AddMemberInfo,
token: string,
): Promise<APIResponse<void>> {
try {
const response = await fetch("/api/addUserToProject", {
const response = await fetch(
`/api/addUserToProject/${addMemberInfo.projectName}/?userName=${addMemberInfo.userName}`,
{
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" };
@ -868,4 +876,25 @@ export const api: API = {
}
return { success: true, message: "User promoted to project manager" };
},
async getUsername(id: number, token: string): Promise<APIResponse<string>> {
try {
const response = await fetch(`/api/username?userId=${id}`, {
method: "GET",
headers: {
"Content-Type": "application/json",
Authorization: "Bearer " + token,
},
});
if (!response.ok) {
return { success: false, message: "Failed to get username" };
} else {
const data = (await response.json()) as string;
return { success: true, data };
}
} catch (e) {
return { success: false, message: "Failed to get username" };
}
},
};

View file

@ -1,44 +1,35 @@
import { APIResponse, api } from "../API/API";
import { api } from "../API/API";
export interface NewProjMember {
username: string;
role: string;
projectname: string;
export interface AddMemberInfo {
userName: string;
projectName: string;
}
/**
* Tries to add a member to a project
* @param {Object} props - A NewProjMember
* @returns {boolean} True if added, false if not
* @param {AddMemberInfo} props.membertoAdd - Contains user's name and project's name
* @returns {Promise<void>}
*/
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;
async function AddMember(props: { memberToAdd: AddMemberInfo }): Promise<void> {
if (props.memberToAdd.userName === "") {
alert("You must choose at least one user to add");
return;
}
api
.addUserToProject(
try {
const response = await api.addUserToProject(
props.memberToAdd,
localStorage.getItem("accessToken") ?? "",
)
.then((response: APIResponse<void>) => {
);
if (response.success) {
alert("Member added");
added = true;
alert(`[${props.memberToAdd.userName}] added`);
} else {
alert("Member not added");
alert(`[${props.memberToAdd.userName}] not added`);
console.error(response.message);
}
})
.catch((error) => {
} catch (error) {
alert(`[${props.memberToAdd.userName}] not added`);
console.error("An error occurred during member add:", error);
});
return added;
}
}
export default AddMember;

View file

@ -1,37 +1,10 @@
import { useState } from "react";
import { APIResponse, api } from "../API/API";
import { api } from "../API/API";
import { NewProject } from "../Types/goTypes";
import InputField from "./InputField";
import Logo from "../assets/Logo.svg";
import Button from "./Button";
/**
* Tries to add a project to the system
* @param {Object} props - Project name and description
* @returns {boolean} True if created, false if not
*/
function CreateProject(props: { name: string; description: string }): void {
const project: NewProject = {
name: props.name,
description: props.description,
};
api
.createProject(project, localStorage.getItem("accessToken") ?? "")
.then((response: APIResponse<void>) => {
if (response.success) {
alert("Project added!");
} else {
alert("Project NOT added!");
console.error(response.message);
}
})
.catch((error) => {
alert("Project NOT added!");
console.error("An error occurred during creation:", error);
});
}
/**
* Provides UI for adding a project to the system.
* @returns {JSX.Element} - Returns the component UI for adding a project
@ -40,6 +13,33 @@ function AddProject(): JSX.Element {
const [name, setName] = useState("");
const [description, setDescription] = useState("");
/**
* Tries to add a project to the system
*/
const handleCreateProject = async (): Promise<void> => {
const project: NewProject = {
name: name.replace(/ /g, ""),
description: description.trim(),
};
try {
const response = await api.createProject(
project,
localStorage.getItem("accessToken") ?? "",
);
if (response.success) {
alert(`${project.name} added!`);
setDescription("");
setName("");
} else {
alert("Project not added, name could be taken");
console.error(response.message);
}
} catch (error) {
alert("Project not added");
console.error(error);
}
};
return (
<div className="flex flex-col h-fit w-screen items-center justify-center">
<div className="border-4 border-black bg-white flex flex-col items-center justify-center h-fit w-fit rounded-3xl content-center pl-20 pr-20">
@ -47,10 +47,7 @@ function AddProject(): JSX.Element {
className="bg-white rounded px-8 pt-6 pb-8 mb-4 items-center justify-center flex flex-col w-fit h-fit"
onSubmit={(e) => {
e.preventDefault();
CreateProject({
name: name,
description: description,
});
void handleCreateProject();
}}
>
<img

View file

@ -1,71 +1,86 @@
import { useState } from "react";
import { useEffect, useState } from "react";
import Button from "./Button";
import GetAllUsers from "./GetAllUsers";
import AddMember, { NewProjMember } from "./AddMember";
import AddMember, { AddMemberInfo } from "./AddMember";
import BackButton from "./BackButton";
import GetUsersInProject, { ProjectMember } from "./GetUsersInProject";
import GetAllUsers from "./GetAllUsers";
/**
* Provides UI for adding a member to a project.
* @returns {JSX.Element} - Returns the component UI for adding a member
*/
function AddUserToProject(props: { projectName: string }): JSX.Element {
const [name, setName] = useState("");
const [names, setNames] = useState<string[]>([]);
const [users, setUsers] = useState<string[]>([]);
const [role, setRole] = useState("");
GetAllUsers({ setUsersProp: setUsers });
const [usersProj, setUsersProj] = useState<ProjectMember[]>([]);
const handleClick = (): boolean => {
const newMember: NewProjMember = {
username: name,
projectname: props.projectName,
role: role,
// Gets all users and project members for filtering
GetAllUsers({ setUsersProp: setUsers });
GetUsersInProject({
setUsersProp: setUsersProj,
projectName: props.projectName,
});
/*
* Filters the members from users so that users who are already
* members are not shown
*/
useEffect(() => {
setUsers((prevUsers) => {
const filteredUsers = prevUsers.filter(
(user) =>
!usersProj.some((projectUser) => projectUser.Username === user),
);
return filteredUsers;
});
}, [usersProj]);
// Attempts to add all of the selected users to the project
const handleAddClick = async (): Promise<void> => {
if (names.length === 0)
alert("You have to choose at least one user to add");
for (const name of names) {
const newMember: AddMemberInfo = {
userName: name,
projectName: props.projectName,
};
return AddMember({ memberToAdd: newMember });
await AddMember({ memberToAdd: newMember });
}
setNames([]);
location.reload();
};
// Updates the names that have been selected
const handleUserClick = (user: string): void => {
setNames((prevNames): string[] => {
if (!prevNames.includes(user)) {
return [...prevNames, user];
}
return prevNames.filter((name) => name !== user);
});
};
return (
<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}]
<div className="border-4 border-black bg-white flex flex-col items-center pt-10 rounded-3xl content-center pl-20 pr-20 h-[63vh] w-[50] overflow-auto">
<h1 className="text-center font-bold text-[36px] pb-10">
{props.projectName}
</h1>
<p className="p-1 text-center font-bold text-[26px]">
Choose users to add:
</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: [{props.projectName}]
</p>
<p className="p-1">Choose role:</p>
<div className="border-2 border-black p-2 rounded-xl text-center h-[10h] w-[16] overflow-auto">
<ul className="text-center items-center font-medium space-y-2">
<li
className="h-[10] w-[14] items-start px-2 py-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-[10] w-[14] items-start px-2 py-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]">
<div className="border-2 border-black pl-2 pr-2 pb-2 rounded-xl text-center overflow-auto 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"
className={
names.includes(user)
? "items-start p-1 border-2 border-transparent rounded-full bg-orange-500 hover:bg-orange-600 text-white hover:cursor-pointer ring-2 ring-black"
: "items-start p-1 border-2 border-black rounded-full bg-orange-200 hover:bg-orange-400 hover:text-slate-100 hover:cursor-pointer"
}
key={user}
value={user}
onClick={() => {
setName(user);
handleUserClick(user);
}}
>
<span>{user}</span>
@ -73,13 +88,16 @@ function AddUserToProject(props: { projectName: string }): JSX.Element {
))}
</ul>
</div>
<div className="flex space-x-5 items-center justify-between">
<p className="pt-10 pb-5 underline text-center font-bold text-[18px]">
Number of users to be added: {names.length}
</p>
<div className="space-x-10 items-center">
<Button
text="Add"
onClick={(): void => {
handleClick();
void handleAddClick();
}}
type="submit"
type="button"
/>
<BackButton />
</div>

View file

@ -17,7 +17,7 @@ function AllTimeReportsInProject(): JSX.Element {
useEffect(() => {
const getWeeklyReports = async (): Promise<void> => {
const token = localStorage.getItem("accessToken") ?? "";
const response = await api.getWeeklyReportsForUser(
const response = await api.getAllWeeklyReportsForUser(
projectName ?? "",
token,
);

View file

@ -17,10 +17,10 @@ function AllTimeReportsInProject(): JSX.Element {
useEffect(() => {
const getWeeklyReports = async (): Promise<void> => {
const token = localStorage.getItem("accessToken") ?? "";
const response = await api.getWeeklyReportsForDifferentUser(
const response = await api.getAllWeeklyReportsForUser(
projectName ?? "",
username ?? "",
token,
username ?? "",
);
console.log(response);
if (response.success) {
@ -31,7 +31,7 @@ function AllTimeReportsInProject(): JSX.Element {
};
void getWeeklyReports();
}, []);
}, [projectName, username]);
return (
<>
@ -60,7 +60,7 @@ function AllTimeReportsInProject(): JSX.Element {
</h1>
<h1>
<span className="font-bold">{"Signed: "}</span>
NO
{newWeeklyReport.signedBy ? "YES" : "NO"}
</h1>
</div>
</Link>

View file

@ -2,7 +2,7 @@ import { useState } from "react";
import Button from "./Button";
import ChangeRole, { ProjectRoleChange } from "./ChangeRole";
export default function ChangeRoles(props: {
export default function ChangeRoleView(props: {
projectName: string;
username: string;
}): JSX.Element {

View file

@ -2,8 +2,11 @@ import { APIResponse, api } from "../API/API";
import { StrNameChange } from "../Types/goTypes";
function ChangeUsername(props: { nameChange: StrNameChange }): void {
if (props.nameChange.newName === "") {
alert("You have to select a new name");
if (
props.nameChange.newName === "" ||
props.nameChange.newName === props.nameChange.prevName
) {
alert("You have to give a new name\n\nName not changed");
return;
}
api
@ -13,7 +16,7 @@ function ChangeUsername(props: { nameChange: StrNameChange }): void {
alert("Name changed successfully");
location.reload();
} else {
alert("Name not changed");
alert("Name not changed, name could be taken");
console.error(response.message);
}
})

View file

@ -3,17 +3,14 @@ import { Link, useParams } from "react-router-dom";
import { api } from "../API/API";
import { WeeklyReport } from "../Types/goTypes";
/**
* 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<WeeklyReport[]>([]);
//const navigate = useNavigate();
const [usernames, setUsernames] = useState<string[]>([]);
const token = localStorage.getItem("accessToken") ?? "";
useEffect(() => {
const getUnsignedReports = async (): Promise<void> => {
const token = localStorage.getItem("accessToken") ?? "";
const response = await api.getUnsignedReportsInProject(
projectName ?? "",
token,
@ -21,13 +18,21 @@ function DisplayUserProject(): JSX.Element {
console.log(response);
if (response.success) {
setUnsignedReports(response.data ?? []);
const usernamesPromises = (response.data ?? []).map((report) =>
api.getUsername(report.userId, token),
);
const usernamesResponses = await Promise.all(usernamesPromises);
const usernames = usernamesResponses.map(
(res) => (res.data as { username?: string }).username ?? "",
);
setUsernames(usernames);
} else {
console.error(response.message);
}
};
void getUnsignedReports();
}, [projectName]); // Include 'projectName' in the dependency array
}, [projectName, token]);
return (
<>
@ -39,8 +44,8 @@ function DisplayUserProject(): JSX.Element {
<h1 key={index} className="border-b-2 border-black w-full">
<div className="flex justify-between">
<div className="flex">
<span className="ml-6 mr-2 font-bold">UserID:</span>
<h1>{unsignedReport.userId}</h1>
<span className="ml-6 mr-2 font-bold">Username:</span>
<h1>{usernames[index]}</h1>{" "}
<span className="ml-6 mr-2 font-bold">Week:</span>
<h1>{unsignedReport.week}</h1>
<span className="ml-6 mr-2 font-bold">Total Time:</span>
@ -58,7 +63,7 @@ function DisplayUserProject(): JSX.Element {
<div className="flex">
<div className="ml-auto flex space-x-4">
<Link
to={`/PMViewUnsignedReport/${projectName}/${unsignedReport.userId}/${unsignedReport.week}`}
to={`/PMViewUnsignedReport/${projectName}/${usernames[index]}/${unsignedReport.week}`}
>
<h1 className="underline cursor-pointer font-bold">
View Report

View file

@ -1,6 +1,6 @@
//info: Header component to display the header of the page including the logo and user information where thr user can logout
import { useState } from "react";
import { Link } from "react-router-dom";
import { Link, useNavigate } from "react-router-dom";
import backgroundImage from "../assets/1.jpg";
/**
@ -9,23 +9,33 @@ import backgroundImage from "../assets/1.jpg";
*/
function Header(): JSX.Element {
const [isOpen, setIsOpen] = useState(false);
const username = localStorage.getItem("username");
const navigate = useNavigate();
const handleLogout = (): void => {
localStorage.clear();
};
const handleNavigation = (): void => {
if (username === "admin") {
navigate("/admin");
} else {
navigate("/yourProjects");
}
};
return (
<header
className="fixed top-0 left-0 right-0 border-[1.75px] border-black text-black p-3 pl-5 flex items-center justify-between bg-cover"
style={{ backgroundImage: `url(${backgroundImage})` }}
>
<Link to="/your-projects">
<div onClick={handleNavigation}>
<img
src="/src/assets/Logo.svg"
alt="TTIME Logo"
className="w-11 h-14 cursor-pointer"
/>
</Link>
</div>
<div
className="relative"

View file

@ -1,8 +1,8 @@
import Button from "./Button";
import DeleteUser from "./DeleteUser";
import UserProjectListAdmin from "./UserProjectListAdmin";
import { useState } from "react";
import ChangeRoleView from "./ChangeRoleView";
import RemoveUserFromProj from "./RemoveUserFromProj";
function MemberInfoModal(props: {
projectName: string;
@ -20,7 +20,7 @@ function MemberInfoModal(props: {
};
return (
<div
className="fixed inset-0 bg-black bg-opacity-30 backdrop-blur-sm
className="fixed inset-10 bg-opacity-30 backdrop-blur-sm
flex justify-center items-center"
>
<div className="border-4 border-black bg-white rounded-lg text-center flex flex-col">
@ -42,13 +42,16 @@ function MemberInfoModal(props: {
<UserProjectListAdmin username={props.username} />
<div className="items-center space-x-6">
<Button
text={"Delete"}
text={"Remove"}
onClick={function (): void {
if (
window.confirm("Are you sure you want to delete this user?")
window.confirm(
"Are you sure you want to remove this user from the project?",
)
) {
DeleteUser({
usernameToDelete: props.username,
RemoveUserFromProj({
userToRemove: props.username,
projectName: props.projectName,
});
}
}}

View file

@ -62,7 +62,7 @@ export default function NewWeeklyReport(): JSX.Element {
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.",
"Error occurred! Your connection to the server might be lost or a time report for this week already exists, please check your connection or go to the edit page to edit your report or change week number.",
);
return;
}

View file

@ -29,6 +29,7 @@ export default function OtherUsersTR(): JSX.Element {
projectName ?? "",
fetchedWeek?.toString() ?? "0",
token,
username ?? "",
);
if (response.success) {
@ -86,6 +87,7 @@ export default function OtherUsersTR(): JSX.Element {
min="0"
className="border-2 border-black rounded-md text-center w-1/2"
value={developmentTime === 0 ? "" : developmentTime}
readOnly
/>
</td>
</tr>
@ -97,6 +99,7 @@ export default function OtherUsersTR(): JSX.Element {
min="0"
className="border-2 border-black rounded-md text-center w-1/2"
value={meetingTime === 0 ? "" : meetingTime}
readOnly
/>
</td>
</tr>
@ -108,6 +111,7 @@ export default function OtherUsersTR(): JSX.Element {
min="0"
className="border-2 border-black rounded-md text-center w-1/2"
value={adminTime === 0 ? "" : adminTime}
readOnly
/>
</td>
</tr>
@ -119,6 +123,7 @@ export default function OtherUsersTR(): JSX.Element {
min="0"
className="border-2 border-black rounded-md text-center w-1/2"
value={ownWorkTime === 0 ? "" : ownWorkTime}
readOnly
/>
</td>
</tr>
@ -130,6 +135,7 @@ export default function OtherUsersTR(): JSX.Element {
min="0"
className="border-2 border-black rounded-md text-center w-1/2"
value={studyTime === 0 ? "" : studyTime}
readOnly
/>
</td>
</tr>
@ -141,6 +147,7 @@ export default function OtherUsersTR(): JSX.Element {
min="0"
className="border-2 border-black rounded-md text-center w-1/2"
value={testingTime === 0 ? "" : testingTime}
readOnly
/>
</td>
</tr>

View file

@ -15,17 +15,21 @@ export default function Register(): JSX.Element {
const [errMessage, setErrMessage] = useState<string>();
const handleRegister = async (): Promise<void> => {
if (username === "" || password === "") {
alert("Must provide username and password");
return;
}
const newUser: NewUser = {
username: username ?? "",
username: username?.replace(/ /g, "") ?? "",
password: password ?? "",
};
const response = await api.registerUser(newUser);
if (response.success) {
alert("User added!");
alert(`${newUser.username} added!`);
setPassword("");
setUsername("");
} else {
alert("User not added");
alert("User not added, name could be taken");
setErrMessage(response.message ?? "Unknown error");
console.error(errMessage);
}

View file

@ -0,0 +1,41 @@
import { api, APIResponse } from "../API/API";
/**
* Removes a user from a project
* @param {string} props.usernameToDelete - The username of user to remove
* @param {string} props.projectName - Project to remove user from
* @returns {void}
* @example
* const exampleUsername = "user";
* const exampleProjectName "project";
* RemoveUserFromProj({ userToRemove: exampleUsername, projectName: exampleProjectName });
*/
export default function RemoveUserFromProj(props: {
userToRemove: string;
projectName: string;
}): void {
if (props.userToRemove === localStorage.getItem("username")) {
alert("Cannot remove yourself");
return;
}
api
.removeUserFromProject(
props.userToRemove,
props.projectName,
localStorage.getItem("accessToken") ?? "",
)
.then((response: APIResponse<void>) => {
if (response.success) {
alert(`${props.userToRemove} has been removed!`);
location.reload();
} else {
alert(`${props.userToRemove} has not been removed due to an error`);
console.error(response.message);
}
})
.catch((error) => {
alert(`${props.userToRemove} has not been removed due to an error`);
console.error("An error occurred during deletion:", error);
});
}

View file

@ -8,12 +8,12 @@ import { projectTimes } from "./GetProjectTimes";
* @returns JSX.Element
*/
export default function TimePerRole(): JSX.Element {
const [development, setDevelopment] = useState<number>();
const [meeting, setMeeting] = useState<number>();
const [admin, setAdmin] = useState<number>();
const [own_work, setOwnWork] = useState<number>();
const [study, setStudy] = useState<number>();
const [testing, setTesting] = useState<number>();
const [development, setDevelopment] = useState<number>(0);
const [meeting, setMeeting] = useState<number>(0);
const [admin, setAdmin] = useState<number>(0);
const [own_work, setOwnWork] = useState<number>(0);
const [study, setStudy] = useState<number>(0);
const [testing, setTesting] = useState<number>(0);
const token = localStorage.getItem("accessToken") ?? "";
const { projectName } = useParams();

View file

@ -28,7 +28,7 @@ function UserInfoModal(props: {
const handleClickChangeName = (): void => {
const nameChange: StrNameChange = {
prevName: props.username,
newName: newUsername,
newName: newUsername.replace(/ /g, ""),
};
ChangeUsername({ nameChange: nameChange });
};

View file

@ -31,8 +31,9 @@ export default function GetOtherUsersReport(): JSX.Element {
projectName ?? "",
fetchedWeek?.toString() ?? "0",
token,
username ?? "",
);
console.log(response);
if (response.success) {
const report: WeeklyReport = response.data ?? {
reportId: 0,
@ -62,25 +63,33 @@ export default function GetOtherUsersReport(): JSX.Element {
void fetchUsersWeeklyReport();
});
const handleSignWeeklyReport = async (): Promise<void> => {
await api.signReport(reportId, token);
const handleSignWeeklyReport = async (): Promise<boolean> => {
const response = await api.signReport(reportId, token);
if (response.success) {
return true;
} else {
return false;
}
};
const navigate = useNavigate();
return (
<>
<h1 className="text-[30px] font-bold">
{" "}
UserId: {username}&apos;s Report
</h1>
<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();
void (async (): Promise<void> => {
const success = await handleSignWeeklyReport();
if (!success) {
alert("Failed to sign report!");
return;
}
alert("Report successfully signed!");
navigate(-1);
})();
}}
>
<div className="flex flex-col items-center">

View file

@ -35,7 +35,7 @@ getUpdateWeeklyReportPath = base_url + "/api/updateWeeklyReport"
removeProjectPath = base_url + "/api/removeProject"
promoteToPmPath = base_url + "/api/promoteToPm"
debug_output = True
debug_output = False
def gprint(*args, **kwargs):