Compare commits

..

No commits in common. "master" and "dev" have entirely different histories.
master ... dev

46 changed files with 332 additions and 1437 deletions

View file

@ -1,220 +1,58 @@
INSERT OR IGNORE INTO users(username, password) INSERT OR IGNORE INTO users(username, password)
VALUES ("admin", "123"), VALUES ("admin", "123");
("user", "123"),
("user2", "123"),
("John", "123"),
("Emma", "123"),
("Michael", "123"),
("Liam", "123"),
("Oliver", "123"),
("Amelia", "123"),
("Benjamin", "123"),
("Mia", "123"),
("Elijah", "123"),
("Charlotte", "123"),
("Henry", "123"),
("Harper", "123"),
("Lucas", "123"),
("Emily", "123"),
("Alexander", "123"),
("Daniel", "123"),
("Ella", "123"),
("Matthew", "123"),
("Madison", "123"),
("Samuel", "123"),
("Avery", "123"),
("Sofia", "123"),
("David", "123"),
("Victoria", "123"),
("Jackson", "123"),
("Abigail", "123"),
("Gabriel", "123"),
("Luna", "123"),
("Wyatt", "123"),
("Chloe", "123"),
("Nora", "123"),
("Joshua", "123"),
("Hazel", "123"),
("Riley", "123"),
("Scarlett", "123"),
("Aria", "123"),
("Carter", "123"),
("Grace", "123"),
("Jayden", "123"),
("Hannah", "123"),
("Zoe", "123"),
("Luke", "123"),
("Sophia", "123"),
("Jack", "123"),
("Isabella", "123"),
("William", "123"),
("Mason", "123"),
("Evelyn", "123"),
("James", "123"),
("Cynthia", "123"),
("Abraham", "123"),
("Ava", "123"),
("Aiden", "123"),
("Natalie", "123"),
("Lily", "123"),
("Olivia", "123"),
("Alexander", "123"),
("Ethan", "123"),
("Mila", "123"),
("Evelyn", "123"),
("Logan", "123"),
("Riley", "123"),
("Grace", "123"),
("Arnold", "123"),
("Connor", "123"),
("Samantha", "123"),
("Emma", "123"),
("Sarah", "123"),
("Nathan", "123"),
("Layla", "123"),
("Ryan", "123"),
("Zoey", "123"),
("Megan", "123"),
("Christian", "123"),
("Eva", "123"),
("Isaac", "123"),
("Michaela", "123"),
("Caroline", "123"),
("Elijah", "123"),
("Elena", "123"),
("Julian", "123"),
("Sophie", "123"),
("Gabriella", "123"),
("Cole", "123"),
("Hannah", "123"),
("Lucy", "123"),
("Katherine", "123"),
("Benjamin", "123"),
("Ella", "123"),
("Evan", "123");
INSERT OR IGNORE INTO projects(name, description, owner_user_id) INSERT OR IGNORE INTO users(username, password)
VALUES ("projecttest1", "Description for projecttest1", 1), VALUES ("user", "123");
("projecttest2", "Description for projecttest2", 1),
("projecttest3", "Description for projecttest3", 1), INSERT OR IGNORE INTO users(username, password)
("projecttest4", "Description for projecttest4", 1), VALUES ("user2", "123");
("projecttest5", "Description for projecttest5", 1),
("projecttest6", "Description for projecttest6", 1), INSERT OR IGNORE INTO site_admin VALUES (1);
("projecttest7", "Description for projecttest7", 1),
("projecttest8", "Description for projecttest8", 1), INSERT OR IGNORE INTO projects(name,description,owner_user_id)
("projecttest9", "Description for projecttest9", 1), VALUES ("projecttest","test project", 1);
("projecttest10", "Description for projecttest10", 1),
("projecttest11", "Description for projecttest11", 1), INSERT OR IGNORE INTO projects(name,description,owner_user_id)
("projecttest12", "Description for projecttest12", 1), VALUES ("projecttest2","test project2", 1);
("projecttest13", "Description for projecttest13", 1),
("projecttest14", "Description for projecttest14", 1), INSERT OR IGNORE INTO projects(name,description,owner_user_id)
("projecttest15", "Description for projecttest15", 1); VALUES ("projecttest3","test project3", 1);
INSERT OR IGNORE INTO user_roles(user_id,project_id,p_role) INSERT OR IGNORE INTO user_roles(user_id,project_id,p_role)
VALUES (1,1,"project_manager"), VALUES (1,1,"project_manager");
(1,2,"project_manager"),
(1,3,"project_manager"), INSERT OR IGNORE INTO user_roles(user_id,project_id,p_role)
(1,4,"project_manager"), VALUES (1,2,"project_manager");
(1,5,"project_manager"),
(1,6,"project_manager"), INSERT OR IGNORE INTO user_roles(user_id,project_id,p_role)
(1,7,"project_manager"), VALUES (1,3,"project_manager");
(1,8,"project_manager"),
(1,9,"project_manager"), INSERT OR IGNORE INTO user_roles(user_id,project_id,p_role)
(1,10,"project_manager"), VALUES (2,1,"member");
(1,11,"project_manager"),
(1,12,"project_manager"), INSERT OR IGNORE INTO user_roles(user_id,project_id,p_role)
(1,13,"project_manager"), VALUES (3,1,"member");
(1,14,"project_manager"),
(1,15,"project_manager"), INSERT OR IGNORE INTO user_roles(user_id,project_id,p_role)
(2,1,"project_manager"), VALUES (3,2,"member");
(2,2,"member"),
(2,3,"member"), INSERT OR IGNORE INTO user_roles(user_id,project_id,p_role)
(2,4,"member"), VALUES (3,3,"member");
(2,5,"member"),
(2,6,"member"), INSERT OR IGNORE INTO user_roles(user_id,project_id,p_role)
(2,7,"member"), VALUES (2,1,"project_manager");
(2,8,"member"),
(2,9,"member"),
(2,10,"member"),
(2,11,"member"),
(2,12,"member"),
(2,13,"member"),
(2,14,"member"),
(2,15,"member"),
(3,1,"member"),
(3,2,"member"),
(3,3,"member"),
(3,4,"member"),
(3,5,"member"),
(3,6,"member"),
(3,7,"member"),
(3,8,"member"),
(3,9,"member"),
(3,10,"member"),
(3,11,"member"),
(3,12,"member"),
(3,13,"member"),
(3,14,"member"),
(3,15,"member"),
(4,1,"member"),
(4,2,"member"),
(4,3,"member"),
(4,4,"member"),
(4,5,"member"),
(4,6,"member"),
(4,7,"member"),
(4,8,"member"),
(4,9,"member"),
(4,10,"member"),
(4,11,"member"),
(4,12,"member"),
(4,13,"member"),
(4,14,"member"),
(4,15,"member"),
(5,1,"member"),
(5,2,"member"),
(5,3,"member"),
(5,4,"member"),
(5,5,"member"),
(5,6,"member"),
(5,7,"member"),
(5,8,"member"),
(5,9,"member"),
(5,10,"member"),
(5,11,"member"),
(5,12,"member"),
(5,13,"member"),
(5,14,"member"),
(5,15,"member");
INSERT OR IGNORE INTO weekly_reports (user_id, project_id, week, development_time, meeting_time, admin_time, own_work_time, study_time, testing_time, signed_by) INSERT OR IGNORE INTO weekly_reports (user_id, project_id, week, development_time, meeting_time, admin_time, own_work_time, study_time, testing_time, signed_by)
VALUES (2, 1, 12, 100, 50, 30, 150, 80, 20, NULL), VALUES (2, 1, 12, 20, 10, 5, 30, 15, 10, NULL);
(3, 1, 12, 200, 80, 20, 200, 100, 30, NULL),
(3, 1, 14, 150, 70, 40, 180, 90, 25, NULL),
(3, 2, 12, 120, 60, 35, 160, 85, 15, NULL),
(3, 3, 12, 180, 90, 25, 190, 110, 40, NULL),
(2, 1, 13, 130, 70, 40, 170, 95, 35, NULL),
(3, 1, 15, 140, 60, 50, 200, 120, 30, NULL),
(2, 2, 11, 110, 50, 45, 140, 70, 25, NULL),
(3, 3, 14, 170, 80, 30, 180, 100, 35, NULL),
(3, 3, 15, 200, 100, 20, 220, 130, 45, NULL),
(2, 4, 12, 120, 60, 40, 160, 80, 30, NULL),
(3, 5, 14, 150, 70, 30, 180, 90, 25, NULL),
(3, 5, 15, 180, 90, 20, 190, 110, 35, NULL),
(2, 6, 11, 100, 50, 35, 130, 60, 20, NULL),
(3, 7, 14, 170, 80, 25, 180, 100, 30, NULL),
(2, 8, 12, 130, 70, 30, 170, 90, 25, NULL),
(2, 8, 13, 150, 80, 20, 180, 110, 35, NULL),
(3, 9, 12, 140, 60, 40, 180, 100, 30, NULL),
(3, 10, 11, 120, 50, 45, 150, 70, 25, NULL),
(2, 11, 13, 110, 60, 35, 140, 80, 30, NULL),
(3, 12, 12, 160, 70, 30, 180, 100, 35, NULL),
(3, 12, 13, 180, 90, 25, 190, 110, 40, NULL),
(3, 12, 14, 200, 100, 20, 220, 130, 45, NULL),
(2, 13, 11, 100, 50, 45, 130, 60, 20, NULL),
(2, 13, 12, 120, 60, 40, 160, 80, 30, NULL),
(3, 14, 13, 140, 70, 30, 160, 90, 35, NULL),
(3, 15, 12, 150, 80, 25, 180, 100, 30, NULL),
(3, 15, 13, 170, 90, 20, 190, 110, 35, NULL);
INSERT OR IGNORE INTO site_admin VALUES (1); INSERT OR IGNORE INTO weekly_reports (user_id, project_id, week, development_time, meeting_time, admin_time, own_work_time, study_time, testing_time, signed_by)
VALUES (3, 1, 12, 20, 10, 5, 30, 15, 10, NULL);
INSERT OR IGNORE INTO weekly_reports (user_id, project_id, week, development_time, meeting_time, admin_time, own_work_time, study_time, testing_time, signed_by)
VALUES (3, 1, 14, 20, 10, 5, 30, 15, 10, NULL);
INSERT OR IGNORE INTO weekly_reports (user_id, project_id, week, development_time, meeting_time, admin_time, own_work_time, study_time, testing_time, signed_by)
VALUES (3, 2, 12, 20, 10, 5, 30, 15, 10, NULL);
INSERT OR IGNORE INTO weekly_reports (user_id, project_id, week, development_time, meeting_time, admin_time, own_work_time, study_time, testing_time, signed_by)
VALUES (3, 3, 12, 20, 10, 5, 30, 15, 10, NULL);

View file

@ -126,7 +126,7 @@ func main() {
api.Delete("/removeUserFromProject/:projectName", projects.RemoveUserFromProject) api.Delete("/removeUserFromProject/:projectName", projects.RemoveUserFromProject)
api.Delete("/removeProject/:projectName", projects.RemoveProject) api.Delete("/removeProject/:projectName", projects.RemoveProject)
api.Delete("/project/:projectID", projects.DeleteProject) api.Delete("/project/:projectID", projects.DeleteProject)
api.Put("/changeProjectName/:projectName", projects.ChangeProjectName) api.Put("/ChangeProjectName/:projectName", projects.ChangeProjectName)
// All report related routes // All report related routes
// reportGroup := api.Group("/report") // Not currently in use // reportGroup := api.Group("/report") // Not currently in use

View file

@ -701,11 +701,7 @@ export const api: API = {
}); });
if (!response.ok) { if (!response.ok) {
return { return { success: false, message: "Failed to login" };
success: false,
data: `${response.status}`,
message: "Failed to login",
};
} else { } else {
const data = (await response.json()) as { token: string }; // Update the type of 'data' const data = (await response.json()) as { token: string }; // Update the type of 'data'
return { success: true, data: data.token }; return { success: true, data: data.token };
@ -1004,8 +1000,8 @@ export const api: API = {
} }
}, },
async getStatistics( async getStatistics(
projectName: string,
token: string, token: string,
projectName: string,
userName?: string, userName?: string,
): Promise<APIResponse<Statistics>> { ): Promise<APIResponse<Statistics>> {
try { try {
@ -1065,7 +1061,7 @@ export const api: API = {
): Promise<APIResponse<string>> { ): Promise<APIResponse<string>> {
try { try {
const response = await fetch( const response = await fetch(
`/api/changeUserPassword/${username}?newPassword=${newPassword}`, `/api/changePassword/${username}?newPassword=${newPassword}`,
{ {
method: "PUT", method: "PUT",
headers: { headers: {

View file

@ -1,13 +1,9 @@
import { useState } from "react"; import { useState } from "react";
import { api } from "../API/API"; import { api } from "../API/API";
import { NewProject } from "../Types/goTypes"; import { NewProject } from "../Types/goTypes";
import InputField from "./InputField";
import Logo from "../assets/Logo.svg"; import Logo from "../assets/Logo.svg";
import Button from "./Button"; import Button from "./Button";
import { useNavigate } from "react-router-dom";
import ProjectNameInput from "./Inputs/ProjectNameInput";
import DescriptionInput from "./Inputs/DescriptionInput";
import { alphanumeric } from "../Data/regex";
import { projNameHighLimit, projNameLowLimit } from "../Data/constants";
/** /**
* Provides UI for adding a project to the system. * Provides UI for adding a project to the system.
@ -16,26 +12,11 @@ import { projNameHighLimit, projNameLowLimit } from "../Data/constants";
function AddProject(): JSX.Element { function AddProject(): JSX.Element {
const [name, setName] = useState(""); const [name, setName] = useState("");
const [description, setDescription] = useState(""); const [description, setDescription] = useState("");
const navigate = useNavigate();
/** /**
* Tries to add a project to the system * Tries to add a project to the system
*/ */
const handleCreateProject = async (): Promise<void> => { const handleCreateProject = async (): Promise<void> => {
if (
!alphanumeric.test(name) ||
name.length > projNameHighLimit ||
name.length < projNameLowLimit
) {
alert(
"Please provide valid project name: \n-Between 10-99 characters \n-No special characters (.-!?/*)",
);
return;
}
if (description.length > projNameHighLimit) {
alert("Please provide valid description: \n-Max 100 characters");
return;
}
const project: NewProject = { const project: NewProject = {
name: name.replace(/ /g, ""), name: name.replace(/ /g, ""),
description: description.trim(), description: description.trim(),
@ -49,7 +30,6 @@ function AddProject(): JSX.Element {
alert(`${project.name} added!`); alert(`${project.name} added!`);
setDescription(""); setDescription("");
setName(""); setName("");
navigate("/admin");
} else { } else {
alert("Project not added, name could be taken"); alert("Project not added, name could be taken");
console.error(response.message); console.error(response.message);
@ -64,7 +44,7 @@ function AddProject(): JSX.Element {
<div className="flex flex-col h-fit w-screen items-center justify-center"> <div className="flex flex-col h-fit w-screen items-center justify-center">
<div className="border-4 border-black bg-white flex flex-col items-center justify-center h-fit w-fit rounded-3xl content-center pl-20 pr-20"> <div className="border-4 border-black bg-white flex flex-col items-center justify-center h-fit w-fit rounded-3xl content-center pl-20 pr-20">
<form <form
className="bg-white rounded px-8 pt-6 pb-8 mb-4 justify-center flex flex-col w-fit h-fit" className="bg-white rounded px-8 pt-6 pb-8 mb-4 items-center justify-center flex flex-col w-fit h-fit"
onSubmit={(e) => { onSubmit={(e) => {
e.preventDefault(); e.preventDefault();
void handleCreateProject(); void handleCreateProject();
@ -72,29 +52,33 @@ function AddProject(): JSX.Element {
> >
<img <img
src={Logo} src={Logo}
className="logo w-[7vw] self-center mb-10 mt-10" className="logo w-[7vw] mb-10 mt-10"
alt="TTIME Logo" alt="TTIME Logo"
/> />
<h3 className="pb-4 mb-2 text-center font-bold text-[18px]"> <h3 className="pb-4 mb-2 text-center font-bold text-[18px]">
Create a new project Create a new project
</h3> </h3>
<ProjectNameInput <div className="space-y-3">
name={name} <InputField
onChange={(e) => { label="Name"
e.preventDefault(); type="text"
setName(e.target.value); value={name}
}} onChange={(e) => {
/> e.preventDefault();
<div className="p-2"></div> setName(e.target.value);
<DescriptionInput }}
desc={description} />
onChange={(e) => { <InputField
e.preventDefault(); label="Description"
setDescription(e.target.value); type="text"
}} value={description}
placeholder={"Description (Optional)"} onChange={(e) => {
/> e.preventDefault();
<div className="flex self-center mt-4 justify-between"> setDescription(e.target.value);
}}
/>
</div>
<div className="flex items-center justify-between">
<Button <Button
text="Create" text="Create"
onClick={(): void => { onClick={(): void => {

View file

@ -1,9 +1,9 @@
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import Button from "./Button"; import Button from "./Button";
import AddMember, { AddMemberInfo } from "./AddMember"; import AddMember, { AddMemberInfo } from "./AddMember";
import BackButton from "./BackButton";
import GetUsersInProject, { ProjectMember } from "./GetUsersInProject"; import GetUsersInProject, { ProjectMember } from "./GetUsersInProject";
import GetAllUsers from "./GetAllUsers"; import GetAllUsers from "./GetAllUsers";
import InputField from "./InputField";
/** /**
* Provides UI for adding a member to a project. * Provides UI for adding a member to a project.
@ -13,7 +13,6 @@ function AddUserToProject(props: { projectName: string }): JSX.Element {
const [names, setNames] = useState<string[]>([]); const [names, setNames] = useState<string[]>([]);
const [users, setUsers] = useState<string[]>([]); const [users, setUsers] = useState<string[]>([]);
const [usersProj, setUsersProj] = useState<ProjectMember[]>([]); const [usersProj, setUsersProj] = useState<ProjectMember[]>([]);
const [search, setSearch] = useState("");
// Gets all users and project members for filtering // Gets all users and project members for filtering
GetAllUsers({ setUsersProp: setUsers }); GetAllUsers({ setUsersProp: setUsers });
@ -37,10 +36,8 @@ function AddUserToProject(props: { projectName: string }): JSX.Element {
// Attempts to add all of the selected users to the project // Attempts to add all of the selected users to the project
const handleAddClick = async (): Promise<void> => { const handleAddClick = async (): Promise<void> => {
if (names.length === 0) { if (names.length === 0)
alert("You have to choose at least one user to add"); alert("You have to choose at least one user to add");
return;
}
for (const name of names) { for (const name of names) {
const newMember: AddMemberInfo = { const newMember: AddMemberInfo = {
userName: name, userName: name,
@ -63,47 +60,32 @@ function AddUserToProject(props: { projectName: string }): JSX.Element {
}; };
return ( return (
<div className="border-4 border-black bg-white flex flex-col items-center py-10 px-20 rounded-3xl content-center overflow-auto"> <div className="border-4 border-black bg-white flex flex-col items-center 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"> <h1 className="text-center font-bold text-[36px] pb-10">
{props.projectName} {props.projectName}
</h1> </h1>
<p className="p-1 text-center font-bold text-[26px]"> <p className="p-1 text-center font-bold text-[26px]">
Choose users to add: Choose users to add:
</p> </p>
<div className="border-2 border-black pl-2 pr-2 pb-2 rounded-xl text-center overflow-auto h-[26vh] w-[26vh]">
<div> <ul className="text-center font-medium space-y-2">
<InputField
placeholder={"Search users"}
type={"Text"}
value={search}
onChange={(e) => {
setSearch(e.target.value);
}}
/>
<ul className="font-medium space-y-2 border-2 border-black mt-2 px-2 pb-2 rounded-2xl text-center overflow-auto h-[26vh] w-[34vh]">
<div></div> <div></div>
{users {users.map((user) => (
.filter((user) => { <li
return search.toLowerCase() === "" className={
? user names.includes(user)
: user.toLowerCase().includes(search.toLowerCase()); ? "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"
.map((user) => ( }
<li key={user}
className={ value={user}
names.includes(user) onClick={() => {
? "items-start p-1 border-2 border-transparent rounded-full bg-orange-500 transition-all hover:bg-orange-600 text-white hover:cursor-pointer ring-2 ring-black" handleUserClick(user);
: "items-start p-1 border-2 border-black rounded-full bg-orange-200 hover:bg-orange-400 transition-all hover:text-white hover:cursor-pointer" }}
} >
key={user} <span>{user}</span>
value={user} </li>
onClick={() => { ))}
handleUserClick(user);
}}
>
<span>{user}</span>
</li>
))}
</ul> </ul>
</div> </div>
<p className="pt-10 pb-5 underline text-center font-bold text-[18px]"> <p className="pt-10 pb-5 underline text-center font-bold text-[18px]">
@ -117,7 +99,9 @@ function AddUserToProject(props: { projectName: string }): JSX.Element {
}} }}
type="button" type="button"
/> />
<BackButton />
</div> </div>
<p className="text-center text-gray-500 text-xs"></p>
</div> </div>
); );
} }

View file

@ -37,9 +37,9 @@ function AllTimeReportsInProject(): JSX.Element {
<div className="border-4 border-black bg-white flex flex-col items-center justify-center min-h-[65vh] h-fit w-[50vw] rounded-3xl content-center overflow-scroll space-y-[10vh] p-[30px] text-[30px]"> <div className="border-4 border-black bg-white flex flex-col items-center justify-center min-h-[65vh] h-fit w-[50vw] rounded-3xl content-center overflow-scroll space-y-[10vh] p-[30px] text-[30px]">
{weeklyReports.map((newWeeklyReport, index) => ( {weeklyReports.map((newWeeklyReport, index) => (
<Link <Link
to={`/editTimeReport/${projectName}/${newWeeklyReport.week}/${newWeeklyReport.signedBy ? "signed" : "unsigned"}`} to={`/editTimeReport/${projectName}/${newWeeklyReport.week}`}
key={index} key={index}
className="border-b-2 border-black w-full cursor-pointer hover:font-extrabold" className="border-b-2 border-black w-full"
> >
<div className="flex justify-between"> <div className="flex justify-between">
<h1> <h1>

View file

@ -39,9 +39,9 @@ function AllTimeReportsInProject(): JSX.Element {
<div className="border-4 border-black bg-white flex flex-col items-center justify-center min-h-[65vh] h-fit w-[50vw] rounded-3xl content-center overflow-scroll space-y-[10vh] p-[30px] text-[30px]"> <div className="border-4 border-black bg-white flex flex-col items-center justify-center min-h-[65vh] h-fit w-[50vw] rounded-3xl content-center overflow-scroll space-y-[10vh] p-[30px] text-[30px]">
{weeklyReports.map((newWeeklyReport, index) => ( {weeklyReports.map((newWeeklyReport, index) => (
<Link <Link
to={`/editOthersTR/${projectName}/${username}/${newWeeklyReport.week}/${newWeeklyReport.signedBy ? "signed" : "unsigned"}`} to={`/editOthersTR/${projectName}/${username}/${newWeeklyReport.week}`}
key={index} key={index}
className="border-b-2 border-black w-full hover:font-extrabold" className="border-b-2 border-black w-full"
> >
<div className="flex justify-between"> <div className="flex justify-between">
<h1> <h1>

View file

@ -1,36 +0,0 @@
import { APIResponse, api } from "../API/API";
/**
* Changes the name of a project
* @param {string} props.projectName - Current project name
* @param {string} props.newProjectName - New project name
* @returns {void} - Nothing
*/
export default function ChangeProjectName(props: {
projectName: string;
newProjectName: string;
}): void {
if (props.projectName === "" || props.projectName === props.newProjectName) {
alert("You have to give a new name\n\nName not changed");
return;
}
api
.changeProjectName(
props.projectName,
props.newProjectName,
localStorage.getItem("accessToken") ?? "",
)
.then((response: APIResponse<string>) => {
if (response.success) {
alert("Name changed successfully");
location.reload();
} else {
alert("Name not changed, name could be taken");
console.error(response.message);
}
})
.catch((error) => {
alert("Name not changed");
console.error("An error occurred during change:", error);
});
}

View file

@ -5,7 +5,6 @@ import ChangeRole, { ProjectRoleChange } from "./ChangeRole";
export default function ChangeRoleView(props: { export default function ChangeRoleView(props: {
projectName: string; projectName: string;
username: string; username: string;
currentRole: string;
}): JSX.Element { }): JSX.Element {
const [selectedRole, setSelectedRole] = useState< const [selectedRole, setSelectedRole] = useState<
"project_manager" | "member" | "" "project_manager" | "member" | ""
@ -22,12 +21,7 @@ export default function ChangeRoleView(props: {
}; };
const handleSubmit = (event: React.FormEvent<HTMLFormElement>): void => { const handleSubmit = (event: React.FormEvent<HTMLFormElement>): void => {
console.log("Cur: " + props.currentRole + " " + "new: " + selectedRole);
event.preventDefault(); event.preventDefault();
if (selectedRole === props.currentRole) {
alert(`Already ${props.currentRole}, nothing changed`);
return;
}
const roleChangeInfo: ProjectRoleChange = { const roleChangeInfo: ProjectRoleChange = {
username: props.username, username: props.username,
projectname: props.projectName, projectname: props.projectName,
@ -37,31 +31,34 @@ export default function ChangeRoleView(props: {
}; };
return ( return (
<div className="overflow-auto"> <div className="overflow-auto rounded-lg">
<h1 className="font-bold text-[20px]">Select role:</h1> <h1 className="font-bold text-[20px]">Select role:</h1>
<form onSubmit={handleSubmit}> <form onSubmit={handleSubmit}>
<div className="py-1 px-1 w-full self-start text-left font-medium overflow-auto border-2 border-black rounded-2xl"> <div className="h-[7vh] self-start text-left font-medium overflow-auto border-2 border-black rounded-lg p-2">
<label className="hover:cursor-pointer hover:font-bold"> <div className="hover:font-bold">
<input <label>
type="radio" <input
value="project_manager" type="radio"
checked={selectedRole === "project_manager"} value="project_manager"
onChange={handleRoleChange} checked={selectedRole === "project_manager"}
className="m-2" onChange={handleRoleChange}
/> className="ml-2 mr-2 mb-3"
Project manager />
</label> Project manager
<br /> </label>
<label className="hover:cursor-pointer hover:font-bold"> </div>
<input <div className="hover:font-bold">
type="radio" <label>
value="member" <input
checked={selectedRole === "member"} type="radio"
onChange={handleRoleChange} value="member"
className="m-2 hover:cursor-pointer" checked={selectedRole === "member"}
/> onChange={handleRoleChange}
Member className="ml-2 mr-2"
</label> />
Member
</label>
</div>
</div> </div>
<Button <Button
text="Change" text="Change"

View file

@ -1,36 +0,0 @@
import { APIResponse, api } from "../API/API";
/**
* Changes the password of a user
* @param {string} props.username - The username of the user
* @param {string} props.newPassword - The new password
* @returns {void} - Nothing
*/
export default function ChangeUserPassword(props: {
username: string;
newPassword: string;
}): void {
if (props.username === localStorage.getItem("username")) {
alert("You cannot change admin password");
return;
}
api
.changeUserPassword(
props.username,
props.newPassword,
localStorage.getItem("accessToken") ?? "",
)
.then((response: APIResponse<string>) => {
if (response.success) {
alert("Password changed successfully");
location.reload();
} else {
alert("Password not changed");
console.error(response.message);
}
})
.catch((error) => {
alert("Password not changed");
console.error("An error occurred during change:", error);
});
}

View file

@ -9,10 +9,6 @@ function ChangeUsername(props: { nameChange: StrNameChange }): void {
alert("You have to give a new name\n\nName not changed"); alert("You have to give a new name\n\nName not changed");
return; return;
} }
if (props.nameChange.prevName === localStorage.getItem("username")) {
alert("You cannot change admin name");
return;
}
api api
.changeUserName(props.nameChange, localStorage.getItem("accessToken") ?? "") .changeUserName(props.nameChange, localStorage.getItem("accessToken") ?? "")
.then((response: APIResponse<void>) => { .then((response: APIResponse<void>) => {

View file

@ -65,7 +65,7 @@ function DisplayUserProject(): JSX.Element {
<Link <Link
to={`/PMViewUnsignedReport/${projectName}/${usernames[index]}/${unsignedReport.week}`} to={`/PMViewUnsignedReport/${projectName}/${usernames[index]}/${unsignedReport.week}`}
> >
<h1 className="cursor-pointer font-bold hover:font-extrabold hover:underline"> <h1 className="underline cursor-pointer font-bold">
View Report View Report
</h1> </h1>
</Link> </Link>

View file

@ -45,7 +45,7 @@ function DisplayUserProject(): JSX.Element {
onClick={() => void handleProjectClick(project.name)} onClick={() => void handleProjectClick(project.name)}
key={project.id} key={project.id}
> >
<h1 className="font-bold hover:underline text-[30px] cursor-pointer hover:font-extrabold"> <h1 className="font-bold underline text-[30px] cursor-pointer">
{project.name} {project.name}
</h1> </h1>
</div> </div>

View file

@ -18,13 +18,12 @@ export default function GetWeeklyReport(): JSX.Element {
const [testingTime, setTestingTime] = useState(0); const [testingTime, setTestingTime] = useState(0);
const token = localStorage.getItem("accessToken") ?? ""; const token = localStorage.getItem("accessToken") ?? "";
const { projectName, fetchedWeek, signedOrUnsigned } = useParams<{ const { projectName, fetchedWeek } = useParams<{
projectName: string; projectName: string;
fetchedWeek: string; fetchedWeek: string;
signedOrUnsigned: string;
}>(); }>();
const username = localStorage.getItem("userName") ?? ""; const username = localStorage.getItem("userName") ?? "";
console.log(projectName, fetchedWeek, signedOrUnsigned); console.log(projectName, fetchedWeek);
useEffect(() => { useEffect(() => {
const fetchWeeklyReport = async (): Promise<void> => { const fetchWeeklyReport = async (): Promise<void> => {
@ -60,7 +59,7 @@ export default function GetWeeklyReport(): JSX.Element {
}; };
void fetchWeeklyReport(); void fetchWeeklyReport();
}, [projectName, fetchedWeek, signedOrUnsigned, token]); }, [projectName, fetchedWeek, token]);
const handleUpdateWeeklyReport = async (): Promise<void> => { const handleUpdateWeeklyReport = async (): Promise<void> => {
const updateWeeklyReport: UpdateWeeklyReport = { const updateWeeklyReport: UpdateWeeklyReport = {
@ -140,12 +139,6 @@ export default function GetWeeklyReport(): JSX.Element {
) )
event.preventDefault(); event.preventDefault();
}} }}
onClick={() => {
if (signedOrUnsigned === "signed") {
alert("You cannot edit a signed report.");
}
}}
readOnly={signedOrUnsigned === "signed"}
/> />
</td> </td>
</tr> </tr>
@ -175,12 +168,6 @@ export default function GetWeeklyReport(): JSX.Element {
) )
event.preventDefault(); event.preventDefault();
}} }}
onClick={() => {
if (signedOrUnsigned === "signed") {
alert("You cannot edit a signed report.");
}
}}
readOnly={signedOrUnsigned === "signed"}
/> />
</td> </td>
</tr> </tr>
@ -210,12 +197,6 @@ export default function GetWeeklyReport(): JSX.Element {
) )
event.preventDefault(); event.preventDefault();
}} }}
onClick={() => {
if (signedOrUnsigned === "signed") {
alert("You cannot edit a signed report.");
}
}}
readOnly={signedOrUnsigned === "signed"}
/> />
</td> </td>
</tr> </tr>
@ -245,12 +226,6 @@ export default function GetWeeklyReport(): JSX.Element {
) )
event.preventDefault(); event.preventDefault();
}} }}
onClick={() => {
if (signedOrUnsigned === "signed") {
alert("You cannot edit a signed report.");
}
}}
readOnly={signedOrUnsigned === "signed"}
/> />
</td> </td>
</tr> </tr>
@ -280,12 +255,6 @@ export default function GetWeeklyReport(): JSX.Element {
) )
event.preventDefault(); event.preventDefault();
}} }}
onClick={() => {
if (signedOrUnsigned === "signed") {
alert("You cannot edit a signed report.");
}
}}
readOnly={signedOrUnsigned === "signed"}
/> />
</td> </td>
</tr> </tr>
@ -315,26 +284,18 @@ export default function GetWeeklyReport(): JSX.Element {
) )
event.preventDefault(); event.preventDefault();
}} }}
onClick={() => {
if (signedOrUnsigned === "signed") {
alert("You cannot edit a signed report.");
}
}}
readOnly={signedOrUnsigned === "signed"}
/> />
</td> </td>
</tr> </tr>
</tbody> </tbody>
</table> </table>
{signedOrUnsigned !== "signed" && ( <Button
<Button text="Submit changes"
text="Submit changes" onClick={(): void => {
onClick={(): void => { return;
return; }}
}} type="submit"
type="submit" />
/>
)}
</div> </div>
</form> </form>
</div> </div>

View file

@ -4,21 +4,19 @@
* @returns {JSX.Element} The input field * @returns {JSX.Element} The input field
* @example * @example
* <InputField * <InputField
* label="Example"
* placeholder="New placeholder"
* type="text" * type="text"
* value={example} * label="Example"
* onChange={(e) => { * onChange={(e) => {
* setExample(e.target.value); * setExample(e.target.value);
* }} * }}
* value={example}
* /> * />
*/ */
function InputField(props: { function InputField(props: {
label?: string; label: string;
placeholder?: string; type: string;
type?: string; value: string;
value?: string; onChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
onChange?: (e: React.ChangeEvent<HTMLInputElement>) => void;
}): JSX.Element { }): JSX.Element {
return ( return (
<div className=""> <div className="">
@ -32,7 +30,7 @@ function InputField(props: {
className="appearance-none border-2 border-black rounded-2xl w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline" className="appearance-none border-2 border-black rounded-2xl w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline"
id={props.label} id={props.label}
type={props.type} type={props.type}
placeholder={props.placeholder} placeholder={props.label}
value={props.value} value={props.value}
onChange={props.onChange} onChange={props.onChange}
/> />

View file

@ -1,38 +0,0 @@
import { projDescHighLimit, projDescLowLimit } from "../../Data/constants";
export default function DescriptionInput(props: {
desc: string;
placeholder: string;
onChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
}): JSX.Element {
return (
<>
<input
className={
props.desc.length <= 100
? "border-2 border-green-500 dark:border-green-500 focus-visible:border-green-500 outline-none rounded-2xl w-full py-2 px-3 text-gray-700 leading-tight"
: "border-2 border-red-600 dark:border-red-600 focus:border-red-600 outline-none rounded-2xl w-full py-2 px-3 text-gray-700 leading-tight"
}
spellCheck="true"
id="New desc"
type="text"
placeholder={props.placeholder}
value={props.desc}
onChange={props.onChange}
/>
<div className="my-1">
{props.desc.length > projDescHighLimit && (
<p className="text-red-600 pl-2 text-[13px] text-left">
Description must be under 100 characters
</p>
)}
{props.desc.length <= projDescHighLimit &&
props.desc.length > projDescLowLimit && (
<p className="text-green-500 pl-2 text-[13px] text-left">
Valid project description!
</p>
)}
</div>
</>
);
}

View file

@ -1,44 +0,0 @@
import { passwordLength } from "../../Data/constants";
import { lowercase } from "../../Data/regex";
export default function PasswordInput(props: {
password: string;
onChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
}): JSX.Element {
const password = props.password;
return (
<>
<input
className={
password.length === passwordLength && lowercase.test(password)
? "border-2 border-green-500 dark:border-green-500 focus-visible:border-green-500 outline-none rounded-2xl w-full py-2 px-3 text-gray-700 leading-tight"
: "border-2 border-red-600 dark:border-red-600 focus:border-red-600 outline-none rounded-2xl w-full py-2 px-3 text-gray-700 leading-tight"
}
spellCheck="false"
id="New password"
type="password"
placeholder="Password"
value={password}
onChange={props.onChange}
/>
<div className="my-1">
{password.length === passwordLength &&
lowercase.test(props.password) && (
<p className="text-green-500 pl-2 text-[13px] text-left">
Valid password!
</p>
)}
{password.length !== passwordLength && (
<p className="text-red-600 pl-2 text-[13px] text-left">
Password must be 6 characters
</p>
)}
{!lowercase.test(password) && password !== "" && (
<p className="text-red-600 pl-2 text-[13px] text-left">
No number, uppercase or special <br /> characters allowed
</p>
)}
</div>
</>
);
}

View file

@ -1,48 +0,0 @@
import { projNameHighLimit, projNameLowLimit } from "../../Data/constants";
import { alphanumeric } from "../../Data/regex";
export default function ProjectNameInput(props: {
name: string;
onChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
}): JSX.Element {
const name = props.name;
return (
<>
<input
className={
name.length >= projNameLowLimit &&
name.length <= projNameHighLimit &&
alphanumeric.test(name)
? "border-2 border-green-500 dark:border-green-500 focus-visible:border-green-500 outline-none rounded-2xl w-full py-2 px-3 text-gray-700 leading-tight"
: "border-2 border-red-600 dark:border-red-600 focus:border-red-600 outline-none rounded-2xl w-full py-2 px-3 text-gray-700 leading-tight"
}
spellCheck="false"
id="New name"
type="text"
placeholder="Project name"
value={name}
onChange={props.onChange}
/>
<div className="my-1">
{!alphanumeric.test(name) && name !== "" && (
<p className="text-red-600 pl-2 text-[13px] text-left">
No special characters allowed
</p>
)}
{(name.length < projNameLowLimit ||
name.length > projNameHighLimit) && (
<p className="text-red-600 pl-2 text-[13px] text-left">
Project name must be 10-99 characters
</p>
)}
{alphanumeric.test(props.name) &&
name.length >= projNameLowLimit &&
name.length <= projNameHighLimit && (
<p className="text-green-500 pl-2 text-[13px] text-left">
Valid project name!
</p>
)}
</div>
</>
);
}

View file

@ -1,50 +0,0 @@
import { usernameLowLimit, usernameUpLimit } from "../../Data/constants";
import { alphanumeric } from "../../Data/regex";
export default function UsernameInput(props: {
username: string;
onChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
}): JSX.Element {
const username = props.username;
return (
<>
<input
className={
username.length >= usernameLowLimit &&
username.length <= usernameUpLimit &&
alphanumeric.test(props.username)
? "border-2 border-green-500 dark:border-green-500 focus-visible:border-green-500 outline-none rounded-2xl w-full py-2 px-3 text-gray-700 leading-tight"
: "border-2 border-red-600 dark:border-red-600 focus:border-red-600 outline-none rounded-2xl w-full py-2 px-3 text-gray-700 leading-tight"
}
spellCheck="false"
id="New username"
type="text"
placeholder="Username"
value={username}
onChange={props.onChange}
/>
<div className="my-1">
{alphanumeric.test(username) &&
username.length >= usernameLowLimit &&
username.length <= usernameUpLimit && (
<p className="text-green-500 pl-2 text-[13px] text-left">
Valid username!
</p>
)}
{!alphanumeric.test(username) && username !== "" && (
<p className="text-red-600 pl-2 text-[13px] text-left">
No special characters allowed
</p>
)}
{!(
username.length >= usernameLowLimit &&
username.length <= usernameUpLimit
) && (
<p className="text-red-600 pl-2 text-[13px] text-left">
Username must be 5-10 characters
</p>
)}
</div>
</>
);
}

View file

@ -11,10 +11,6 @@ function LoginCheck(props: {
password: string; password: string;
setAuthority: Dispatch<SetStateAction<number>>; setAuthority: Dispatch<SetStateAction<number>>;
}): void { }): void {
if (props.username === "" || props.password === "") {
alert("Please enter username and password to login");
return;
}
const user: NewUser = { const user: NewUser = {
username: props.username, username: props.username,
password: props.password, password: props.password,
@ -46,15 +42,7 @@ function LoginCheck(props: {
console.error("Token was undefined"); console.error("Token was undefined");
} }
} else { } else {
if (response.data === "500") { console.error("Token could not be fetched/No such user");
console.error(response.message);
alert("No connection/Error");
} else {
console.error(
"Token could not be fetched/No such user" + response.message,
);
alert("Incorrect login information");
}
} }
}) })
.catch((error) => { .catch((error) => {

View file

@ -33,7 +33,6 @@ function Login(props: {
props.setUsername(e.target.value); props.setUsername(e.target.value);
}} }}
value={props.username} value={props.username}
placeholder={"Username"}
/> />
<InputField <InputField
type="password" type="password"
@ -42,7 +41,6 @@ function Login(props: {
props.setPassword(e.target.value); props.setPassword(e.target.value);
}} }}
value={props.password} value={props.password}
placeholder={"Password"}
/> />
</div> </div>
<Button <Button

View file

@ -1,13 +1,12 @@
import Button from "./Button"; import Button from "./Button";
import UserProjectListAdmin from "./UserProjectListAdmin"; import UserProjectListAdmin from "./UserProjectListAdmin";
import { useState } from "react"; import { useState } from "react";
import ChangeRoleView from "./ChangeRoleView";
import RemoveUserFromProj from "./RemoveUserFromProj"; import RemoveUserFromProj from "./RemoveUserFromProj";
import ChangeRoleInput from "./ChangeRoleView";
function MemberInfoModal(props: { function MemberInfoModal(props: {
projectName: string; projectName: string;
username: string; username: string;
role: string;
onClose: () => void; onClose: () => void;
}): JSX.Element { }): JSX.Element {
const [showRoles, setShowRoles] = useState(false); const [showRoles, setShowRoles] = useState(false);
@ -21,24 +20,22 @@ function MemberInfoModal(props: {
}; };
return ( return (
<div <div
className="fixed inset-0 bg-opacity-30 backdrop-blur-sm className="fixed inset-10 bg-opacity-30 backdrop-blur-sm
flex justify-center items-center" flex justify-center items-center"
> >
<div className="border-4 border-black bg-white rounded-2xl text-center flex flex-col"> <div className="border-4 border-black bg-white rounded-lg text-center flex flex-col">
<div className="mx-10"> <div className="mx-10">
<p className="font-bold text-[30px]">{props.username}</p> <p className="font-bold text-[30px]">{props.username}</p>
<p className="font-bold text-[20px]">{props.role}</p>
<p <p
className="hover:font-bold hover:cursor-pointer underline mb-2 mt-1" className="hover:font-bold hover:cursor-pointer underline"
onClick={handleChangeRole} onClick={handleChangeRole}
> >
(Change Role) (Change Role)
</p> </p>
{showRoles && ( {showRoles && (
<ChangeRoleInput <ChangeRoleView
projectName={props.projectName} projectName={props.projectName}
username={props.username} username={props.username}
currentRole={props.role}
/> />
)} )}
<h2 className="font-bold text-[20px]">Member of these projects:</h2> <h2 className="font-bold text-[20px]">Member of these projects:</h2>

View file

@ -1,24 +0,0 @@
import { useNavigate } from "react-router-dom";
/**
* Renders a navigation button component for navigating to
* different page
* @returns The JSX element representing the navigation button.
*/
export default function NavButton(props: {
navTo: string;
label: string;
}): JSX.Element {
const navigate = useNavigate();
const goBack = (): void => {
navigate(props.navTo);
};
return (
<button
onClick={goBack}
className="inline-block py-1 px-8 font-bold bg-orange-500 text-white border-2 border-black rounded-full cursor-pointer mt-5 mb-5 transition-colors duration-10 hover:bg-orange-600 hover:text-gray-300 font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; font-size: 4vh;"
>
{props.label}
</button>
);
}

View file

@ -1,8 +1,7 @@
import { useState, useEffect } from "react"; import { useState, useEffect } from "react";
import { WeeklyReport } from "../Types/goTypes"; import { WeeklyReport } from "../Types/goTypes";
import { api } from "../API/API"; import { api } from "../API/API";
import { useParams, useNavigate } from "react-router-dom"; import { useParams } from "react-router-dom";
import Button from "./Button";
/** /**
* Renders the component for editing a weekly report. * Renders the component for editing a weekly report.
@ -18,14 +17,11 @@ export default function OtherUsersTR(): JSX.Element {
const [ownWorkTime, setOwnWorkTime] = useState(0); const [ownWorkTime, setOwnWorkTime] = useState(0);
const [studyTime, setStudyTime] = useState(0); const [studyTime, setStudyTime] = useState(0);
const [testingTime, setTestingTime] = useState(0); const [testingTime, setTestingTime] = useState(0);
const [reportId, setReportId] = useState(0);
const token = localStorage.getItem("accessToken") ?? ""; const token = localStorage.getItem("accessToken") ?? "";
const { projectName } = useParams(); const { projectName } = useParams();
const { username } = useParams(); const { username } = useParams();
const { fetchedWeek } = useParams(); const { fetchedWeek } = useParams();
const { signedOrUnsigned } = useParams();
console.log(projectName, username, fetchedWeek, signedOrUnsigned);
useEffect(() => { useEffect(() => {
const fetchUsersWeeklyReport = async (): Promise<void> => { const fetchUsersWeeklyReport = async (): Promise<void> => {
@ -49,7 +45,6 @@ export default function OtherUsersTR(): JSX.Element {
studyTime: 0, studyTime: 0,
testingTime: 0, testingTime: 0,
}; };
setReportId(report.reportId);
setWeek(report.week); setWeek(report.week);
setDevelopmentTime(report.developmentTime); setDevelopmentTime(report.developmentTime);
setMeetingTime(report.meetingTime); setMeetingTime(report.meetingTime);
@ -65,27 +60,6 @@ export default function OtherUsersTR(): JSX.Element {
void fetchUsersWeeklyReport(); void fetchUsersWeeklyReport();
}); });
const handleUnsignWeeklyReport = async (): Promise<boolean> => {
const response = await api.unsignReport(reportId, token);
console.log(response);
console.log(reportId);
if (response.success) {
return true;
} else {
return false;
}
};
const handleDeleteWeeklyReport = async (): Promise<boolean> => {
const response = await api.deleteWeeklyReport(reportId, token);
console.log(response);
if (response.success) {
return true;
}
return false;
};
const navigate = useNavigate();
return ( return (
<> <>
<h1 className="text-[30px] font-bold">{username}&apos;s Report</h1> <h1 className="text-[30px] font-bold">{username}&apos;s Report</h1>
@ -179,48 +153,6 @@ export default function OtherUsersTR(): JSX.Element {
</tr> </tr>
</tbody> </tbody>
</table> </table>
<div className="flex space-x-4">
{signedOrUnsigned === "signed" && (
<Button
text="Unsign Report"
onClick={(): void => {
void (async (): Promise<void> => {
const success = await handleUnsignWeeklyReport();
if (success) {
alert("Report successfully unsigned!");
navigate(-1);
} else {
alert("Failed to unsign report");
return;
}
})();
}}
type={"button"}
/>
)}
<Button
text="Delete Time Report"
onClick={(): void => {
void (async (): Promise<void> => {
const confirmDelete = window.confirm(
"Are you sure you want to delete this report? This action cannot be undone.",
);
if (!confirmDelete) {
return;
}
const success = await handleDeleteWeeklyReport();
if (success) {
alert("Report successfully deleted!");
navigate(-1);
} else {
alert("Failed to delete report");
return;
}
})();
}}
type={"button"}
/>
</div>
</div> </div>
</div> </div>
</> </>

View file

@ -8,22 +8,22 @@ function PMProjectMenu(): JSX.Element {
<h1 className="font-bold text-[30px] mb-[20px]">{projectName}</h1> <h1 className="font-bold text-[30px] mb-[20px]">{projectName}</h1>
<div className="border-4 border-black bg-white flex flex-col items-center justify-center min-h-[65vh] h-fit w-[50vw] rounded-3xl content-center overflow-scroll space-y-[5vh] p-[30px]"> <div className="border-4 border-black bg-white flex flex-col items-center justify-center min-h-[65vh] h-fit w-[50vw] rounded-3xl content-center overflow-scroll space-y-[5vh] p-[30px]">
<Link to={`/timeReports/${projectName}/`}> <Link to={`/timeReports/${projectName}/`}>
<h1 className="font-bold hover:underline text-[30px] cursor-pointer hover:font-extrabold"> <h1 className="font-bold underline text-[30px] cursor-pointer">
Your Time Reports Your Time Reports
</h1> </h1>
</Link> </Link>
<Link to={`/newTimeReport/${projectName}`}> <Link to={`/newTimeReport/${projectName}`}>
<h1 className="font-bold hover:underline text-[30px] cursor-pointer hover:font-extrabold"> <h1 className="font-bold underline text-[30px] cursor-pointer">
New Time Report New Time Report
</h1> </h1>
</Link> </Link>
<Link to={`/projectMembers/${projectName}`}> <Link to={`/projectMembers/${projectName}`}>
<h1 className="font-bold hover:underline text-[30px] cursor-pointer hover:font-extrabold"> <h1 className="font-bold underline text-[30px] cursor-pointer">
Statistics Statistics
</h1> </h1>
</Link> </Link>
<Link to={`/unsignedReports/${projectName}`}> <Link to={`/unsignedReports/${projectName}`}>
<h1 className="font-bold hover:underline text-[30px] cursor-pointer hover:font-extrabold"> <h1 className="font-bold underline text-[30px] cursor-pointer">
Unsigned Time Reports Unsigned Time Reports
</h1> </h1>
</Link> </Link>

View file

@ -4,62 +4,19 @@ import GetUsersInProject, { ProjectMember } from "./GetUsersInProject";
import { Link } from "react-router-dom"; import { Link } from "react-router-dom";
import GetProjectTimes, { projectTimes } from "./GetProjectTimes"; import GetProjectTimes, { projectTimes } from "./GetProjectTimes";
import DeleteProject from "./DeleteProject"; import DeleteProject from "./DeleteProject";
import InputField from "./InputField";
import ProjectNameInput from "./Inputs/ProjectNameInput";
import { alphanumeric } from "../Data/regex";
import { projNameHighLimit, projNameLowLimit } from "../Data/constants";
import ChangeProjectName from "./ChangeProjectName";
function ProjectInfoModal(props: { function ProjectInfoModal(props: {
projectname: string; projectname: string;
onClose: () => void; onClose: () => void;
onClick: (username: string, userRole: string) => void; onClick: (username: string) => void;
}): JSX.Element { }): JSX.Element {
const [showInput, setShowInput] = useState(false);
const [users, setUsers] = useState<ProjectMember[]>([]); const [users, setUsers] = useState<ProjectMember[]>([]);
const [times, setTimes] = useState<projectTimes>(); const [times, setTimes] = useState<projectTimes>();
const [search, setSearch] = useState("");
const [newProjName, setNewProjName] = useState("");
const totalTime = useRef(0); const totalTime = useRef(0);
GetUsersInProject({ projectName: props.projectname, setUsersProp: setUsers }); GetUsersInProject({ projectName: props.projectname, setUsersProp: setUsers });
GetProjectTimes({ setTimesProp: setTimes, projectName: props.projectname }); GetProjectTimes({ setTimesProp: setTimes, projectName: props.projectname });
const handleChangeNameView = (): void => {
if (showInput) {
setNewProjName("");
setShowInput(false);
} else {
setShowInput(true);
}
};
const handleClickChangeName = (): void => {
if (
newProjName.length > projNameHighLimit ||
newProjName.length < projNameLowLimit ||
!alphanumeric.test(newProjName)
) {
alert(
"Please provide valid project name: \n-Between 10-99 characters \n-No special characters (.-!?/*)",
);
return;
}
if (
confirm(
`Are you sure you want to change name of ${props.projectname} to ${newProjName}?`,
)
) {
ChangeProjectName({
projectName: props.projectname,
newProjectName: newProjName,
});
} else {
alert("Name was not changed!");
}
};
useEffect(() => { useEffect(() => {
if (times?.totalTime !== undefined) { if (times?.totalTime !== undefined) {
totalTime.current = times.totalTime; totalTime.current = times.totalTime;
@ -71,88 +28,44 @@ function ProjectInfoModal(props: {
className="fixed inset-0 bg-black bg-opacity-30 backdrop-blur-sm className="fixed inset-0 bg-black bg-opacity-30 backdrop-blur-sm
flex justify-center items-center" flex justify-center items-center"
> >
<div className="border-4 border-black bg-white p-2 rounded-2xl text-center h-[64vh] w-[40] overflow-auto"> <div className="border-4 border-black bg-white p-2 rounded-2xl text-center h-[61vh] w-[40] overflow-auto">
<div className="pl-10 pr-10"> <div className="pl-10 pr-10">
<h1 className="font-bold text-[32px]">{props.projectname}</h1> <h1 className="font-bold text-[32px] mb-[20px]">
<p {props.projectname}
className="mb-4 hover:font-bold hover:cursor-pointer hover:underline" </h1>
onClick={handleChangeNameView} <div className="p-1 text-center">
> <h2 className="text-[20px] font-bold">Statistics:</h2>
(Change project name) </div>
</p> <div className="border-2 border-black rounded-lg h-[8vh] text-left divide-y-2 flex flex-col overflow-auto mx-10">
{showInput && ( <p className="p-2">Number of members: {users.length}</p>
<> <p className="p-2">
<h2 className="text-[20px] font-bold pb-2">Change name:</h2>
<div className="border-2 rounded-2xl border-black px-6 pt-6 pb-1 mb-7">
<ProjectNameInput
name={newProjName}
onChange={function (e): void {
setNewProjName(e.target.value);
}}
/>
<div className="px-6 grid grid-cols-2 gap-10">
<Button
text={"Change"}
onClick={function (): void {
handleClickChangeName();
}}
type={"submit"}
/>
<Button
text={"Close"}
onClick={function (): void {
handleChangeNameView();
}}
type={"submit"}
/>
</div>
</div>
</>
)}
<h2 className="text-[20px] font-bold pb-2">Statistics:</h2>
<div className="border-2 border-black rounded-2xl px-2 py-1 text-left divide-y-2 flex flex-col overflow-auto">
<p>Number of members: {users.length}</p>
<p>
Total time reported:{" "} Total time reported:{" "}
{Math.floor(totalTime.current / 60 / 24) + " d "} {Math.floor(totalTime.current / 60 / 24) + " d "}
{Math.floor((totalTime.current / 60) % 24) + " h "} {Math.floor((totalTime.current / 60) % 24) + " h "}
{(totalTime.current % 60) + " m "} {(totalTime.current % 60) + " m "}
</p> </p>
</div> </div>
<h3 className="pt-7 text-[20px] font-bold">Project members:</h3> <div className="h-[6vh] p-7 text-center">
<div className=""> <h2 className="text-[20px] font-bold">Project members:</h2>
<InputField </div>
placeholder={"Search member"} <div className="border-2 border-black p-2 rounded-lg text-center overflow-auto h-[24vh] mx-10">
type={"Text"} <ul className="text-left font-medium space-y-2">
value={search} <div></div>
onChange={(e) => { {users.map((user) => (
setSearch(e.target.value); <li
}} className="items-start p-1 border-2 border-black rounded-lg bg-orange-200 hover:bg-orange-600 hover:text-slate-100 hover:cursor-pointer"
/> key={user.Username}
<ul className="border-2 border-black mt-2 p-2 rounded-2xl text-left overflow-auto h-[24vh] font-medium space-y-2"> onClick={() => {
{users props.onClick(user.Username);
.filter((user) => { }}
return search.toLowerCase() === "" >
? user.Username <span>
: user.Username.toLowerCase().includes( Name: {user.Username}
search.toLowerCase(), <div></div>
); Role: {user.UserRole}
}) </span>
.map((user) => ( </li>
<li ))}
className="items-start px-2 py-1 border-2 border-black rounded-2xl bg-orange-200 transition-all hover:bg-orange-600 hover:text-white hover:cursor-pointer"
key={user.Username}
onClick={() => {
props.onClick(user.Username, user.UserRole);
}}
>
<span>
Name: {user.Username}
<div></div>
Role: {user.UserRole}
</span>
</li>
))}
</ul> </ul>
</div> </div>
<div className="space-x-5 my-2"> <div className="space-x-5 my-2">

View file

@ -2,7 +2,6 @@ import { useState } from "react";
import { NewProject } from "../Types/goTypes"; import { NewProject } from "../Types/goTypes";
import ProjectInfoModal from "./ProjectInfoModal"; import ProjectInfoModal from "./ProjectInfoModal";
import MemberInfoModal from "./MemberInfoModal"; import MemberInfoModal from "./MemberInfoModal";
import InputField from "./InputField";
/** /**
* A list of projects for admin manage projects page, that sets an onClick * A list of projects for admin manage projects page, that sets an onClick
@ -22,12 +21,9 @@ export function ProjectListAdmin(props: {
const [projectName, setProjectName] = useState(""); const [projectName, setProjectName] = useState("");
const [userModalVisible, setUserModalVisible] = useState(false); const [userModalVisible, setUserModalVisible] = useState(false);
const [username, setUsername] = useState(""); const [username, setUsername] = useState("");
const [userRole, setUserRole] = useState("");
const [search, setSearch] = useState("");
const handleClickUser = (username: string, userRole: string): void => { const handleClickUser = (username: string): void => {
setUsername(username); setUsername(username);
setUserRole(userRole);
setUserModalVisible(true); setUserModalVisible(true);
}; };
@ -43,13 +39,11 @@ export function ProjectListAdmin(props: {
const handleCloseUser = (): void => { const handleCloseUser = (): void => {
setUsername(""); setUsername("");
setUserRole("");
setUserModalVisible(false); setUserModalVisible(false);
}; };
return ( return (
<> <>
<h1 className="font-bold text-[30px] mb-[20px]">Manage Projects</h1>
{projectModalVisible && ( {projectModalVisible && (
<ProjectInfoModal <ProjectInfoModal
onClose={handleCloseProject} onClose={handleCloseProject}
@ -62,36 +56,21 @@ export function ProjectListAdmin(props: {
onClose={handleCloseUser} onClose={handleCloseUser}
username={username} username={username}
projectName={projectName} projectName={projectName}
role={userRole}
/> />
)} )}
<div> <div>
<InputField <ul className="font-bold underline text-[30px] cursor-pointer padding">
placeholder={"Search"} {props.projects.map((project) => (
type={"Text"} <li
value={search} className="pt-5"
onChange={(e) => { key={project.name}
setSearch(e.target.value); onClick={() => {
}} handleClickProject(project.name);
/> }}
<ul className="mt-3 border-2 text-left border-black rounded-2xl px-2 divide-y divide-gray-300 font-semibold text-[30px] cursor-pointer overflow-auto h-[60vh] w-[40vw]"> >
{props.projects {project.name}
.filter((project) => { </li>
return search.toLowerCase() === "" ))}
? project.name
: project.name.toLowerCase().includes(search.toLowerCase());
})
.map((project) => (
<li
className="hover:font-extrabold hover:underline p-1"
key={project.name}
onClick={() => {
handleClickProject(project.name);
}}
>
{project.name}
</li>
))}
</ul> </ul>
</div> </div>
</> </>

View file

@ -1,7 +1,6 @@
import { useState } from "react"; import { useState } from "react";
import { Link, useParams } from "react-router-dom"; import { Link, useParams } from "react-router-dom";
import GetUsersInProject, { ProjectMember } from "./GetUsersInProject"; import GetUsersInProject, { ProjectMember } from "./GetUsersInProject";
import { api } from "../API/API";
function ProjectMembers(): JSX.Element { function ProjectMembers(): JSX.Element {
const { projectName } = useParams(); const { projectName } = useParams();
@ -12,68 +11,34 @@ function ProjectMembers(): JSX.Element {
setUsersProp: setProjectMembers, setUsersProp: setProjectMembers,
}); });
const handleUserDeleteClick = async (username: string): Promise<void> => {
const token = localStorage.getItem("accessToken") ?? "";
const response = await api.removeUserFromProject(
username,
projectName ?? "",
token,
);
console.log(response.data);
// Remove the deleted user from the state
setProjectMembers((prevMembers) =>
prevMembers.filter((member) => member.Username !== username),
);
};
return ( return (
<> <>
<h1 className="font-bold text-[30px] mb-[20px]"> <h1 className="font-bold text-[30px] mb-[20px]">
All Members In: {projectName}{" "} All Members In: {projectName}{" "}
</h1> </h1>
<div className="border-4 border-black bg-white flex flex-col items-center justify-center min-h-[65vh] h-fit w-[70vw] rounded-3xl content-center overflow-scroll space-y-[10vh] p-[30px] text-[20px]"> <div className="border-4 border-black bg-white flex flex-col items-center justify-center min-h-[65vh] h-fit w-[70vw] rounded-3xl content-center overflow-scroll space-y-[10vh] p-[30px] text-[20px]">
{projectMembers.map((projectMember: ProjectMember, index: number) => { {projectMembers.map((projectMember: ProjectMember, index: number) => (
if (projectMember.Username === "admin") { <h1 key={index} className="border-b-2 border-black w-full">
return null; // Skip rendering for admin user <div className="flex justify-between">
} <div className="flex">
return ( <h1>{projectMember.Username}</h1>
<h1 key={index} className="border-b-2 border-black w-full"> <span className="ml-6 mr-2 font-bold">Role:</span>
<div className="flex justify-between"> <h1>{projectMember.UserRole}</h1>
<div className="flex"> </div>
<h1>{projectMember.Username}</h1> <div className="flex">
<span className="ml-6 mr-2 font-bold">Role:</span> <div className="ml-auto flex space-x-4">
<h1>{projectMember.UserRole}</h1> <Link
</div> to={`/otherUsersTimeReports/${projectName}/${projectMember.Username}`}
<div className="flex"> >
<div className="ml-auto flex space-x-4"> <h1 className="underline cursor-pointer font-bold">
{projectMember.Username !== View Reports
localStorage.getItem("username") && ( </h1>
<h1 </Link>
className="cursor-pointer font-bold hover:font-extrabold hover:underline"
onClick={() => {
confirm(
"Are you sure you want to delete this user? This action cannot be undone.",
) &&
void handleUserDeleteClick(projectMember.Username);
}}
>
Delete User
</h1>
)}
<Link
to={`/otherUsersTimeReports/${projectName}/${projectMember.Username}`}
>
<h1 className="cursor-pointer font-bold hover:font-extrabold hover:underline">
View Reports
</h1>
</Link>
</div>
</div> </div>
</div> </div>
</h1> </div>
); </h1>
})} ))}
</div> </div>
</> </>
); );

View file

@ -3,44 +3,25 @@ import { NewUser } from "../Types/goTypes";
import { api } from "../API/API"; import { api } from "../API/API";
import Logo from "../assets/Logo.svg"; import Logo from "../assets/Logo.svg";
import Button from "./Button"; import Button from "./Button";
import UsernameInput from "./Inputs/UsernameInput"; import InputField from "./InputField";
import PasswordInput from "./Inputs/PasswordInput";
import { alphanumeric, lowercase } from "../Data/regex";
import {
passwordLength,
usernameLowLimit,
usernameUpLimit,
} from "../Data/constants";
/** /**
* Renders a registration form for the admin to add new users in. * Renders a registration form for the admin to add new users in.
* @returns The JSX element representing the registration form. * @returns The JSX element representing the registration form.
*/ */
export default function Register(): JSX.Element { export default function Register(): JSX.Element {
const [username, setUsername] = useState<string>(""); const [username, setUsername] = useState<string>();
const [password, setPassword] = useState<string>(""); const [password, setPassword] = useState<string>();
const [errMessage, setErrMessage] = useState<string>(""); const [errMessage, setErrMessage] = useState<string>();
const handleRegister = async (): Promise<void> => { const handleRegister = async (): Promise<void> => {
if ( if (username === "" || password === "") {
username.length > usernameUpLimit || alert("Must provide username and password");
username.length < usernameLowLimit ||
!alphanumeric.test(username)
) {
alert(
"Please provide valid username: \n-Between 5-10 characters \n-No special characters (.-!?/*)",
);
return;
}
if (password.length !== passwordLength || !lowercase.test(password)) {
alert(
"Please provide valid password: \n-Exactly 6 characters \n-No uppercase letters \n-No numbers \n-No special characters (.-!?/*)",
);
return; return;
} }
const newUser: NewUser = { const newUser: NewUser = {
username: username, username: username?.replace(/ /g, "") ?? "",
password: password, password: password ?? "",
}; };
const response = await api.registerUser(newUser); const response = await api.registerUser(newUser);
if (response.success) { if (response.success) {
@ -58,7 +39,7 @@ export default function Register(): JSX.Element {
<div className="flex flex-col h-fit w-screen items-center justify-center"> <div className="flex flex-col h-fit w-screen items-center justify-center">
<div className="border-4 border-black bg-white flex flex-col items-center justify-center h-fit w-fit rounded-3xl content-center pl-20 pr-20"> <div className="border-4 border-black bg-white flex flex-col items-center justify-center h-fit w-fit rounded-3xl content-center pl-20 pr-20">
<form <form
className="bg-white rounded px-8 pt-6 pb-8 mb-4 justify-center flex flex-col w-fit h-fit" className="bg-white rounded px-8 pt-6 pb-8 mb-4 items-center justify-center flex flex-col w-fit h-fit"
onSubmit={(e) => { onSubmit={(e) => {
e.preventDefault(); e.preventDefault();
void handleRegister(); void handleRegister();
@ -66,28 +47,31 @@ export default function Register(): JSX.Element {
> >
<img <img
src={Logo} src={Logo}
className="logo self-center w-[7vw] mb-10 mt-10" className="logo w-[7vw] mb-10 mt-10"
alt="TTIME Logo" alt="TTIME Logo"
/> />
<h3 className="pb-4 mb-2 text-center font-bold text-[18px]"> <h3 className="pb-4 mb-2 text-center font-bold text-[18px]">
Register New User Register New User
</h3> </h3>
<div className="space-y-3">
<UsernameInput <InputField
username={username} label="Username"
onChange={(e) => { type="text"
setUsername(e.target.value); value={username ?? ""}
}} onChange={(e) => {
/> setUsername(e.target.value);
<div className="py-2" /> }}
<PasswordInput />
password={password} <InputField
onChange={(e) => { label="Password"
setPassword(e.target.value); type="password"
}} value={password ?? ""}
/> onChange={(e) => {
setPassword(e.target.value);
<div className="flex self-center justify-between"> }}
/>
</div>
<div className="flex items-center justify-between">
<Button <Button
text="Register" text="Register"
onClick={(): void => { onClick={(): void => {

View file

@ -14,7 +14,6 @@ export default function TimePerRole(): JSX.Element {
const [own_work, setOwnWork] = useState<number>(0); const [own_work, setOwnWork] = useState<number>(0);
const [study, setStudy] = useState<number>(0); const [study, setStudy] = useState<number>(0);
const [testing, setTesting] = useState<number>(0); const [testing, setTesting] = useState<number>(0);
const total = development + meeting + admin + own_work + study + testing;
const token = localStorage.getItem("accessToken") ?? ""; const token = localStorage.getItem("accessToken") ?? "";
const { projectName } = useParams(); const { projectName } = useParams();
@ -50,7 +49,7 @@ export default function TimePerRole(): JSX.Element {
return ( return (
<> <>
<h1 className="font-bold text-[30px] mb-[20px]"> <h1 className="font-bold text-[30px] mb-[20px]">
Total Time Per Activity For All Members In: {projectName}{" "} Total Time Per Activity In: {projectName}{" "}
</h1> </h1>
<div className="border-4 border-black bg-white flex flex-col justify-start min-h-[65vh] h-fit w-[50vw] rounded-3xl overflow-scroll space-y-[2vh] p-[30px] items-center"> <div className="border-4 border-black bg-white flex flex-col justify-start min-h-[65vh] h-fit w-[50vw] rounded-3xl overflow-scroll space-y-[2vh] p-[30px] items-center">
<div className="flex flex-col items-center"> <div className="flex flex-col items-center">
@ -130,12 +129,6 @@ export default function TimePerRole(): JSX.Element {
/> />
</td> </td>
</tr> </tr>
<tr className="h-[10vh] font-bold font-">
<td>In Total:</td>
<td>
<h1>{total}</h1>
</td>
</tr>
</tbody> </tbody>
</table> </table>
</div> </div>

View file

@ -2,106 +2,35 @@ import Button from "./Button";
import DeleteUser from "./DeleteUser"; import DeleteUser from "./DeleteUser";
import UserProjectListAdmin from "./UserProjectListAdmin"; import UserProjectListAdmin from "./UserProjectListAdmin";
import { useState } from "react"; import { useState } from "react";
import InputField from "./InputField";
import ChangeUsername from "./ChangeUsername"; import ChangeUsername from "./ChangeUsername";
import { StrNameChange } from "../Types/goTypes"; import { StrNameChange } from "../Types/goTypes";
import UsernameInput from "./Inputs/UsernameInput";
import PasswordInput from "./Inputs/PasswordInput";
import { alphanumeric, lowercase } from "../Data/regex";
import {
passwordLength,
usernameLowLimit,
usernameUpLimit,
} from "../Data/constants";
import ChangeUserPassword from "./ChangeUserPassword";
function UserInfoModal(props: { function UserInfoModal(props: {
isVisible: boolean; isVisible: boolean;
username: string; username: string;
onClose: () => void; onClose: () => void;
}): JSX.Element { }): JSX.Element {
const [showNameInput, setShowNameInput] = useState(false); const [showInput, setShowInput] = useState(false);
const [showPwordInput, setShowPwordInput] = useState(false);
const [newUsername, setNewUsername] = useState(""); const [newUsername, setNewUsername] = useState("");
const [newPassword, setNewPassword] = useState("");
if (!props.isVisible) { if (!props.isVisible) {
return <></>; return <></>;
} }
/* const handleChangeNameView = (): void => {
* Switches name input between visible/invisible if (showInput) {
* and makes password input invisible setShowInput(false);
*/
const handleShowNameInput = (): void => {
if (showPwordInput) setShowPwordInput(false);
if (showNameInput) {
setShowNameInput(false);
setNewUsername("");
} else { } else {
setShowNameInput(true); setShowInput(true);
setNewPassword("");
} }
}; };
/*
* Switches password input between visible/invisible
* and makes username input invisible
*/
const handleShowPwordInput = (): void => {
if (showNameInput) setShowNameInput(false);
if (showPwordInput) {
setShowPwordInput(false);
setNewPassword("");
} else {
setShowPwordInput(true);
setNewUsername("");
}
};
// Handles name change and checks if new name meets requirements
const handleClickChangeName = (): void => { const handleClickChangeName = (): void => {
if ( const nameChange: StrNameChange = {
!alphanumeric.test(newUsername) || prevName: props.username,
newUsername.length > usernameUpLimit || newName: newUsername.replace(/ /g, ""),
newUsername.length < usernameLowLimit };
) { ChangeUsername({ nameChange: nameChange });
alert(
"Please provide valid username: \n-Between 5-10 characters \n-No special characters (.-!?/*)",
);
return;
}
if (
confirm(
`Do you really want to change username of ${props.username} to ${newUsername}?`,
)
) {
const nameChange: StrNameChange = {
prevName: props.username,
newName: newUsername.replace(/ /g, ""),
};
ChangeUsername({ nameChange: nameChange });
} else {
alert("Name was not changed!");
}
};
// Handles password change and checks if new password meets requirements
const handleClickChangePassword = (): void => {
if (newPassword.length !== passwordLength || !lowercase.test(newPassword)) {
alert(
"Please provide valid password: \n-Exactly 6 characters \n-No uppercase letters \n-No numbers \n-No special characters (.-!?/*)",
);
return;
}
if (
confirm(`Are you sure you want to change password of ${props.username}?`)
) {
ChangeUserPassword({
username: props.username,
newPassword: newPassword,
});
} else {
alert("Password was not changed!");
}
}; };
return ( return (
@ -109,37 +38,23 @@ function UserInfoModal(props: {
className="fixed inset-0 bg-black bg-opacity-30 backdrop-blur-sm className="fixed inset-0 bg-black bg-opacity-30 backdrop-blur-sm
flex justify-center items-center" flex justify-center items-center"
> >
<div className="border-4 border-black bg-white rounded-2xl text-center flex flex-col"> <div className="border-4 border-black bg-white rounded-lg text-center flex flex-col">
<div className="mx-10"> <div className="mx-10">
<p className="font-bold text-[30px]">{props.username}</p> <p className="font-bold text-[30px]">{props.username}</p>
<p className="mt-2 font-bold text-[20px]">Change:</p> <p
<p className="mt-2 space-x-3 mb-[10px]"> className="mb-[10px] hover:font-bold hover:cursor-pointer underline"
<span onClick={handleChangeNameView}
className={ >
showNameInput (Change Username)
? "items-start font-semibold py-1 px-2 border-2 border-transparent rounded-full bg-orange-500 transition-all hover:bg-orange-600 text-white hover:cursor-pointer ring-2 ring-black"
: "items-start font-medium py-1 px-2 border-2 border-gray-500 text-white rounded-full bg-orange-300 hover:bg-orange-400 transition-all hover:text-gray-100 hover:border-gray-600 hover:cursor-pointer"
}
onClick={handleShowNameInput}
>
Username
</span>{" "}
<span
className={
showPwordInput
? "items-start font-semibold py-1 px-2 border-2 border-transparent rounded-full bg-orange-500 transition-all hover:bg-orange-600 text-white hover:cursor-pointer ring-2 ring-black"
: "items-start font-medium py-1 px-2 border-2 border-gray-500 text-white rounded-full bg-orange-300 hover:bg-orange-400 transition-all hover:text-gray-100 hover:border-gray-600 hover:cursor-pointer"
}
onClick={handleShowPwordInput}
>
Password
</span>
</p> </p>
{showNameInput && ( {showInput && (
<div className="mt-7"> <div>
<UsernameInput <InputField
username={newUsername} label={"New username"}
onChange={(e) => { type={"text"}
value={newUsername}
onChange={function (e): void {
e.defaultPrevented;
setNewUsername(e.target.value); setNewUsername(e.target.value);
}} }}
/> />
@ -152,23 +67,6 @@ function UserInfoModal(props: {
/> />
</div> </div>
)} )}
{showPwordInput && (
<div className="mt-7">
<PasswordInput
password={newPassword}
onChange={(e) => {
setNewPassword(e.target.value);
}}
/>
<Button
text={"Change"}
onClick={function (): void {
handleClickChangePassword();
}}
type={"submit"}
/>
</div>
)}
<h2 className="font-bold text-[20px]">Member of these projects:</h2> <h2 className="font-bold text-[20px]">Member of these projects:</h2>
<UserProjectListAdmin username={props.username} /> <UserProjectListAdmin username={props.username} />
<div className="items-center space-x-6"> <div className="items-center space-x-6">
@ -189,9 +87,7 @@ function UserInfoModal(props: {
text={"Close"} text={"Close"}
onClick={function (): void { onClick={function (): void {
setNewUsername(""); setNewUsername("");
setNewPassword(""); setShowInput(false);
setShowNameInput(false);
setShowPwordInput(false);
props.onClose(); props.onClose();
}} }}
type="button" type="button"

View file

@ -1,6 +1,5 @@
import { useState } from "react"; import { useState } from "react";
import UserInfoModal from "./UserInfoModal"; import UserInfoModal from "./UserInfoModal";
import InputField from "./InputField";
/** /**
* A list of users for admin manage users page, that sets an onClick * A list of users for admin manage users page, that sets an onClick
@ -16,7 +15,6 @@ import InputField from "./InputField";
export function UserListAdmin(props: { users: string[] }): JSX.Element { export function UserListAdmin(props: { users: string[] }): JSX.Element {
const [modalVisible, setModalVisible] = useState(false); const [modalVisible, setModalVisible] = useState(false);
const [username, setUsername] = useState(""); const [username, setUsername] = useState("");
const [search, setSearch] = useState("");
const handleClick = (username: string): void => { const handleClick = (username: string): void => {
setUsername(username); setUsername(username);
@ -30,39 +28,24 @@ export function UserListAdmin(props: { users: string[] }): JSX.Element {
return ( return (
<> <>
<h1 className="font-bold text-[30px] mb-[20px]">Manage Users</h1>
<UserInfoModal <UserInfoModal
onClose={handleClose} onClose={handleClose}
isVisible={modalVisible} isVisible={modalVisible}
username={username} username={username}
/> />
<div> <div>
<InputField <ul className="font-bold underline text-[30px] cursor-pointer padding">
placeholder={"Search"} {props.users.map((user) => (
type={"Text"} <li
value={search} className="pt-5"
onChange={(e) => { key={user}
setSearch(e.target.value); onClick={() => {
}} handleClick(user);
/> }}
<ul className="mt-3 border-2 text-left border-black rounded-2xl px-2 divide-y divide-gray-300 font-semibold text-[30px] transition-all cursor-pointer overflow-auto h-[60vh] w-[40vw]"> >
{props.users {user}
.filter((user) => { </li>
return search.toLowerCase() === "" ))}
? user
: user.toLowerCase().includes(search.toLowerCase());
})
.map((user) => (
<li
className="hover:font-extrabold hover:underline p-1"
key={user}
onClick={() => {
handleClick(user);
}}
>
{user}
</li>
))}
</ul> </ul>
</div> </div>
</> </>

View file

@ -8,7 +8,7 @@ function UserProjectListAdmin(props: { username: string }): JSX.Element {
GetProjects({ setProjectsProp: setProjects, username: props.username }); GetProjects({ setProjectsProp: setProjects, username: props.username });
return ( return (
<div className="border-2 border-black bg-white rounded-2xl text-left overflow-auto h-[15vh] font-medium"> <div className="border-2 border-black bg-white rounded-lg text-left overflow-auto h-[15vh] font-medium">
<ul className="divide-y-2"> <ul className="divide-y-2">
{projects.map((project) => ( {projects.map((project) => (
<li className="mx-2 my-1" key={project.id}> <li className="mx-2 my-1" key={project.id}>

View file

@ -16,12 +16,12 @@ function UserProjectMenu(): JSX.Element {
<h1 className="font-bold text-[30px] mb-[20px]">{projectName}</h1> <h1 className="font-bold text-[30px] mb-[20px]">{projectName}</h1>
<div className="border-4 border-black bg-white flex flex-col items-center justify-center min-h-[65vh] h-fit w-[50vw] rounded-3xl content-center overflow-scroll space-y-[10vh] p-[30px]"> <div className="border-4 border-black bg-white flex flex-col items-center justify-center min-h-[65vh] h-fit w-[50vw] rounded-3xl content-center overflow-scroll space-y-[10vh] p-[30px]">
<Link to={`/timeReports/${projectName}/`}> <Link to={`/timeReports/${projectName}/`}>
<h1 className="font-bold hover:underline text-[30px] cursor-pointer hover:font-extrabold"> <h1 className="font-bold underline text-[30px] cursor-pointer">
Your Time Reports Your Time Reports
</h1> </h1>
</Link> </Link>
<Link to={`/newTimeReport/${projectName}`}> <Link to={`/newTimeReport/${projectName}`}>
<h1 className="font-bold hover:underline text-[30px] cursor-pointer hover:font-extrabold"> <h1 className="font-bold underline text-[30px] cursor-pointer">
New Time Report New Time Report
</h1> </h1>
</Link> </Link>

View file

@ -1,150 +0,0 @@
import { useState, useEffect } from "react";
import { useParams } from "react-router-dom";
import { api } from "../API/API";
import { Statistics } from "../Types/goTypes";
/**
* Renders the component for showing total time per role in a project.
* @returns JSX.Element
*/
export default function UserStatistics(): JSX.Element {
const [development, setDevelopment] = useState<number>(0);
const [meeting, setMeeting] = useState<number>(0);
const [admin, setAdmin] = useState<number>(0);
const [own_work, setOwnWork] = useState<number>(0);
const [study, setStudy] = useState<number>(0);
const [testing, setTesting] = useState<number>(0);
const total = development + meeting + admin + own_work + study + testing;
const token = localStorage.getItem("accessToken") ?? "";
const { projectName } = useParams();
const { username } = useParams();
const fetchTimePerActivity = async (): Promise<void> => {
const response = await api.getStatistics(
projectName ?? "",
token,
username ?? "",
);
{
if (response.success) {
const statistics: Statistics = response.data ?? {
totalDevelopmentTime: 0,
totalMeetingTime: 0,
totalAdminTime: 0,
totalOwnWorkTime: 0,
totalStudyTime: 0,
totalTestingTime: 0,
};
setDevelopment(statistics.totalDevelopmentTime);
setMeeting(statistics.totalMeetingTime);
setAdmin(statistics.totalAdminTime);
setOwnWork(statistics.totalOwnWorkTime);
setStudy(statistics.totalStudyTime);
setTesting(statistics.totalTestingTime);
} else {
console.error("Failed to fetch weekly report:", response.message);
}
}
};
useEffect(() => {
void fetchTimePerActivity();
});
return (
<>
<h1 className="font-bold text-[30px] mb-[20px]">
Total Time In: {projectName}{" "}
</h1>
<div className="border-4 border-black bg-white flex flex-col justify-start min-h-[65vh] h-fit w-[50vw] rounded-3xl overflow-scroll space-y-[2vh] p-[30px] items-center">
<div className="flex flex-col items-center">
<table className="w-full text-center divide-y divide-x divide-white text-[30px]">
<thead>
<tr>
<th className="w-1/2 py-2 border-b-2 border-black">Activity</th>
<th className="w-1/2 py-2 border-b-2 border-black">
Total Time (min)
</th>
</tr>
</thead>
<tbody className="divide-y divide-black">
<tr className="h-[10vh]">
<td>Development</td>
<td>
<input
type="string"
className="border-2 border-black rounded-md text-center w-1/2"
value={development}
readOnly
/>
</td>
</tr>
<tr className="h-[10vh]">
<td>Meeting</td>
<td>
<input
type="string"
className="border-2 border-black rounded-md text-center w-1/2"
value={meeting}
readOnly
/>
</td>
</tr>
<tr className="h-[10vh]">
<td>Administration</td>
<td>
<input
type="string"
className="border-2 border-black rounded-md text-center w-1/2"
value={admin}
readOnly
/>
</td>
</tr>
<tr className="h-[10vh]">
<td>Own Work</td>
<td>
<input
type="string"
className="border-2 border-black rounded-md text-center w-1/2"
value={own_work}
readOnly
/>
</td>
</tr>
<tr className="h-[10vh]">
<td>Studies</td>
<td>
<input
type="string"
className="border-2 border-black rounded-md text-center w-1/2"
value={study}
readOnly
/>
</td>
</tr>
<tr className="h-[10vh]">
<td>Testing</td>
<td>
<input
type="string"
className="border-2 border-black rounded-md text-center w-1/2"
value={testing}
readOnly
/>
</td>
</tr>
<tr className="h-[10vh] font-bold font-">
<td>In Total:</td>
<td>
<h1>{total}</h1>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</>
);
}

View file

@ -1,36 +0,0 @@
//Different character limits certain strings
/**
* Allowed character length for password
*/
export const passwordLength = 6;
/**
* Lower limit for username length
*/
export const usernameLowLimit = 5;
/**
* Upper limit for password length
*/
export const usernameUpLimit = 10;
/**
* Lower limit for project name length
*/
export const projNameLowLimit = 10;
/**
* Upper limit for project name length
*/
export const projNameHighLimit = 99;
/**
* Upper limit for project description length
*/
export const projDescLowLimit = 0;
/**
* Upper limit for project description length
*/
export const projDescHighLimit = 99;

View file

@ -1,9 +0,0 @@
/**
* Only alphanumerical characters
*/
export const alphanumeric = /^[a-zA-Z0-9]+$/;
/**
* Only lowercase letters
*/
export const lowercase = /^[a-z]+$/;

View file

@ -1,11 +1,11 @@
import { Link } from "react-router-dom"; import { Link } from "react-router-dom";
import BackButton from "../../Components/BackButton";
import BasicWindow from "../../Components/BasicWindow"; import BasicWindow from "../../Components/BasicWindow";
import Button from "../../Components/Button"; import Button from "../../Components/Button";
import { ProjectListAdmin } from "../../Components/ProjectListAdmin"; import { ProjectListAdmin } from "../../Components/ProjectListAdmin";
import { Project } from "../../Types/goTypes"; import { Project } from "../../Types/goTypes";
import GetProjects from "../../Components/GetProjects"; import GetProjects from "../../Components/GetProjects";
import { useState } from "react"; import { useState } from "react";
import NavButton from "../../Components/NavButton";
function AdminManageProjects(): JSX.Element { function AdminManageProjects(): JSX.Element {
const [projects, setProjects] = useState<Project[]>([]); const [projects, setProjects] = useState<Project[]>([]);
@ -13,7 +13,14 @@ function AdminManageProjects(): JSX.Element {
setProjectsProp: setProjects, setProjectsProp: setProjects,
username: localStorage.getItem("username") ?? "", username: localStorage.getItem("username") ?? "",
}); });
const content = <ProjectListAdmin projects={projects} />; const content = (
<>
<h1 className="font-bold text-[30px] mb-[20px]">Manage Projects</h1>
<div className="border-4 border-black bg-white flex flex-col items-center h-[65vh] w-[50vw] rounded-3xl content-center overflow-scroll space-y-[10vh] p-[30px]">
<ProjectListAdmin projects={projects} />
</div>
</>
);
const buttons = ( const buttons = (
<> <>
@ -26,7 +33,7 @@ function AdminManageProjects(): JSX.Element {
type="button" type="button"
/> />
</Link> </Link>
<NavButton navTo="/admin" label={"Back"} /> <BackButton />
</> </>
); );

View file

@ -12,7 +12,14 @@ function AdminManageUsers(): JSX.Element {
const navigate = useNavigate(); const navigate = useNavigate();
const content = <UserListAdmin users={users} />; const content = (
<>
<h1 className="font-bold text-[30px] mb-[20px]">Manage Users</h1>
<div className="border-4 border-black bg-white flex flex-col items-center h-[65vh] w-[50vw] rounded-3xl content-center overflow-scroll space-y-[10vh] p-[30px]">
<UserListAdmin users={users} />
</div>
</>
);
const buttons = ( const buttons = (
<> <>

View file

@ -5,14 +5,14 @@ function AdminMenuPage(): JSX.Element {
const content = ( const content = (
<> <>
<h1 className="font-bold text-[30px] mb-[20px]">Administrator Menu</h1> <h1 className="font-bold text-[30px] mb-[20px]">Administrator Menu</h1>
<div className="border-4 border-black bg-white flex flex-col items-center justify-center min-h-[65vh] h-fit w-[50vw] rounded-3xl content-center overflow-auto space-y-[10vh] p-[30px]"> <div className="border-4 border-black bg-white flex flex-col items-center justify-center min-h-[65vh] h-fit w-[50vw] rounded-3xl content-center overflow-scroll space-y-[10vh] p-[30px]">
<Link to="/adminManageUser"> <Link to="/adminManageUser">
<h1 className="font-bold hover:underline text-[30px] cursor-pointer hover:font-extrabold"> <h1 className="font-bold underline text-[30px] cursor-pointer">
Manage Users Manage Users
</h1> </h1>
</Link> </Link>
<Link to="/adminManageProject"> <Link to="/adminManageProject">
<h1 className="font-bold hover:underline text-[30px] cursor-pointer hover:font-extrabold"> <h1 className="font-bold underline text-[30px] cursor-pointer">
Manage Projects Manage Projects
</h1> </h1>
</Link> </Link>

View file

@ -1,12 +1,11 @@
import { useLocation } from "react-router-dom"; import { useLocation } from "react-router-dom";
import AddUserToProject from "../../Components/AddUserToProject"; import AddUserToProject from "../../Components/AddUserToProject";
import BasicWindow from "../../Components/BasicWindow"; import BasicWindow from "../../Components/BasicWindow";
import BackButton from "../../Components/BackButton";
function AdminProjectAddMember(): JSX.Element { function AdminProjectAddMember(): JSX.Element {
const projectName = useLocation().search.slice(1); const projectName = useLocation().search.slice(1);
const content = <AddUserToProject projectName={projectName} />; const content = <AddUserToProject projectName={projectName} />;
const buttons = <BackButton />; const buttons = <></>;
return <BasicWindow content={content} buttons={buttons} />; return <BasicWindow content={content} buttons={buttons} />;
} }
export default AdminProjectAddMember; export default AdminProjectAddMember;

View file

@ -1,13 +1,8 @@
import BackButton from "../../Components/BackButton"; import BackButton from "../../Components/BackButton";
import BasicWindow from "../../Components/BasicWindow"; import BasicWindow from "../../Components/BasicWindow";
import UserStatistics from "../../Components/UserStatistics";
function UserNewTimeReportPage(): JSX.Element { function AdminProjectStatistics(): JSX.Element {
const content = ( const content = <></>;
<>
<UserStatistics />
</>
);
const buttons = ( const buttons = (
<> <>
@ -17,4 +12,4 @@ function UserNewTimeReportPage(): JSX.Element {
return <BasicWindow content={content} buttons={buttons} />; return <BasicWindow content={content} buttons={buttons} />;
} }
export default UserNewTimeReportPage; export default AdminProjectStatistics;

View file

@ -1,12 +1,8 @@
import BasicWindow from "../../Components/BasicWindow"; import BasicWindow from "../../Components/BasicWindow";
import BackButton from "../../Components/BackButton"; import BackButton from "../../Components/BackButton";
import AllTimeReportsInProjectOtherUser from "../../Components/AllTimeReportsInProjectOtherUser"; import AllTimeReportsInProjectOtherUser from "../../Components/AllTimeReportsInProjectOtherUser";
import Button from "../../Components/Button";
import { useParams, Link } from "react-router-dom";
function PMOtherUsersTR(): JSX.Element { function PMOtherUsersTR(): JSX.Element {
const { projectName } = useParams();
const { username } = useParams();
const content = ( const content = (
<> <>
<AllTimeReportsInProjectOtherUser /> <AllTimeReportsInProjectOtherUser />
@ -15,15 +11,6 @@ function PMOtherUsersTR(): JSX.Element {
const buttons = ( const buttons = (
<> <>
<Link to={`/viewStatistics/${projectName}/${username}`}>
<Button
text={`Statistics: ${username}`}
onClick={(): void => {
return;
}}
type={"button"}
/>
</Link>
<BackButton /> <BackButton />
</> </>
); );

View file

@ -16,7 +16,7 @@ function PMProjectMembers(): JSX.Element {
<> <>
<Link to={`/PMtimeactivity/${projectName}`}> <Link to={`/PMtimeactivity/${projectName}`}>
<Button <Button
text="Statistics" text="Time / Activity"
onClick={(): void => { onClick={(): void => {
return; return;
}} }}

View file

@ -1,12 +1,10 @@
import BasicWindow from "../../Components/BasicWindow"; import BasicWindow from "../../Components/BasicWindow";
import BackButton from "../../Components/BackButton"; import BackButton from "../../Components/BackButton";
import { useParams, Link } from "react-router-dom"; import { useParams } from "react-router-dom";
import AllTimeReportsInProject from "../../Components/AllTimeReportsInProject"; import AllTimeReportsInProject from "../../Components/AllTimeReportsInProject";
import Button from "../../Components/Button";
function UserViewTimeReportsPage(): JSX.Element { function UserViewTimeReportsPage(): JSX.Element {
const { projectName } = useParams(); const { projectName } = useParams();
const username = localStorage.getItem("username");
const content = ( const content = (
<> <>
@ -19,15 +17,6 @@ function UserViewTimeReportsPage(): JSX.Element {
const buttons = ( const buttons = (
<> <>
<Link to={`/viewStatistics/${projectName}/${username}`}>
<Button
text="Statistics"
onClick={(): void => {
return;
}}
type={"button"}
/>
</Link>
<BackButton /> <BackButton />
</> </>
); );

View file

@ -9,7 +9,6 @@ import AdminMenuPage from "./Pages/AdminPages/AdminMenuPage.tsx";
import UserEditTimeReportPage from "./Pages/UserPages/UserEditTimeReportPage.tsx"; import UserEditTimeReportPage from "./Pages/UserPages/UserEditTimeReportPage.tsx";
import UserNewTimeReportPage from "./Pages/UserPages/UserNewTimeReportPage.tsx"; import UserNewTimeReportPage from "./Pages/UserPages/UserNewTimeReportPage.tsx";
import UserViewTimeReportsPage from "./Pages/UserPages/UserViewTimeReportsPage.tsx"; import UserViewTimeReportsPage from "./Pages/UserPages/UserViewTimeReportsPage.tsx";
import UserViewStatistics from "./Pages/UserPages/UserViewStatistics.tsx";
import PMChangeRole from "./Pages/ProjectManagerPages/PMChangeRole.tsx"; import PMChangeRole from "./Pages/ProjectManagerPages/PMChangeRole.tsx";
import PMOtherUsersTR from "./Pages/ProjectManagerPages/PMOtherUsersTR.tsx"; import PMOtherUsersTR from "./Pages/ProjectManagerPages/PMOtherUsersTR.tsx";
import PMProjectMembers from "./Pages/ProjectManagerPages/PMProjectMembers.tsx"; import PMProjectMembers from "./Pages/ProjectManagerPages/PMProjectMembers.tsx";
@ -23,6 +22,7 @@ import AdminManageProjects from "./Pages/AdminPages/AdminManageProjects.tsx";
import AdminAddProject from "./Pages/AdminPages/AdminAddProject.tsx"; import AdminAddProject from "./Pages/AdminPages/AdminAddProject.tsx";
import AdminAddUser from "./Pages/AdminPages/AdminAddUser.tsx"; import AdminAddUser from "./Pages/AdminPages/AdminAddUser.tsx";
import AdminProjectAddMember from "./Pages/AdminPages/AdminProjectAddMember.tsx"; import AdminProjectAddMember from "./Pages/AdminPages/AdminProjectAddMember.tsx";
import AdminProjectStatistics from "./Pages/AdminPages/AdminProjectStatistics.tsx";
import NotFoundPage from "./Pages/NotFoundPage.tsx"; import NotFoundPage from "./Pages/NotFoundPage.tsx";
import UnauthorizedPage from "./Pages/UnauthorizedPage.tsx"; import UnauthorizedPage from "./Pages/UnauthorizedPage.tsx";
import PMViewOtherUsersTR from "./Pages/ProjectManagerPages/PMViewOtherUsersTR.tsx"; import PMViewOtherUsersTR from "./Pages/ProjectManagerPages/PMViewOtherUsersTR.tsx";
@ -55,13 +55,9 @@ const router = createBrowserRouter([
element: <UserViewTimeReportsPage />, element: <UserViewTimeReportsPage />,
}, },
{ {
path: "/editTimeReport/:projectName/:fetchedWeek/:signedOrUnsigned", path: "/editTimeReport/:projectName/:fetchedWeek",
element: <UserEditTimeReportPage />, element: <UserEditTimeReportPage />,
}, },
{
path: "/viewStatistics/:projectName/:username",
element: <UserViewStatistics />,
},
{ {
path: "/changeRole/:projectName/:username", path: "/changeRole/:projectName/:username",
element: <PMChangeRole />, element: <PMChangeRole />,
@ -71,7 +67,7 @@ const router = createBrowserRouter([
element: <PMOtherUsersTR />, element: <PMOtherUsersTR />,
}, },
{ {
path: "/editOthersTR/:projectName/:username/:fetchedWeek/:signedOrUnsigned", path: "/editOthersTR/:projectName/:username/:fetchedWeek",
element: <PMViewOtherUsersTR />, element: <PMViewOtherUsersTR />,
}, },
{ {
@ -102,6 +98,10 @@ const router = createBrowserRouter([
path: "/adminProjectAddMember", path: "/adminProjectAddMember",
element: <AdminProjectAddMember />, element: <AdminProjectAddMember />,
}, },
{
path: "/adminProjectStatistics",
element: <AdminProjectStatistics />,
},
{ {
path: "/addProject", path: "/addProject",
element: <AdminAddProject />, element: <AdminAddProject />,