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),
("projecttest4", "Description for projecttest4", 1),
("projecttest5", "Description for projecttest5", 1),
("projecttest6", "Description for projecttest6", 1),
("projecttest7", "Description for projecttest7", 1),
("projecttest8", "Description for projecttest8", 1),
("projecttest9", "Description for projecttest9", 1),
("projecttest10", "Description for projecttest10", 1),
("projecttest11", "Description for projecttest11", 1),
("projecttest12", "Description for projecttest12", 1),
("projecttest13", "Description for projecttest13", 1),
("projecttest14", "Description for projecttest14", 1),
("projecttest15", "Description for projecttest15", 1);
INSERT OR IGNORE INTO user_roles(user_id,project_id,p_role) INSERT OR IGNORE INTO users(username, password)
VALUES (1,1,"project_manager"), VALUES ("user2", "123");
(1,2,"project_manager"),
(1,3,"project_manager"),
(1,4,"project_manager"),
(1,5,"project_manager"),
(1,6,"project_manager"),
(1,7,"project_manager"),
(1,8,"project_manager"),
(1,9,"project_manager"),
(1,10,"project_manager"),
(1,11,"project_manager"),
(1,12,"project_manager"),
(1,13,"project_manager"),
(1,14,"project_manager"),
(1,15,"project_manager"),
(2,1,"project_manager"),
(2,2,"member"),
(2,3,"member"),
(2,4,"member"),
(2,5,"member"),
(2,6,"member"),
(2,7,"member"),
(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)
VALUES (2, 1, 12, 100, 50, 30, 150, 80, 20, 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 site_admin VALUES (1);
INSERT OR IGNORE INTO projects(name,description,owner_user_id)
VALUES ("projecttest","test project", 1);
INSERT OR IGNORE INTO projects(name,description,owner_user_id)
VALUES ("projecttest2","test project2", 1);
INSERT OR IGNORE INTO projects(name,description,owner_user_id)
VALUES ("projecttest3","test project3", 1);
INSERT OR IGNORE INTO user_roles(user_id,project_id,p_role)
VALUES (1,1,"project_manager");
INSERT OR IGNORE INTO user_roles(user_id,project_id,p_role)
VALUES (1,2,"project_manager");
INSERT OR IGNORE INTO user_roles(user_id,project_id,p_role)
VALUES (1,3,"project_manager");
INSERT OR IGNORE INTO user_roles(user_id,project_id,p_role)
VALUES (2,1,"member");
INSERT OR IGNORE INTO user_roles(user_id,project_id,p_role)
VALUES (3,1,"member");
INSERT OR IGNORE INTO user_roles(user_id,project_id,p_role)
VALUES (3,2,"member");
INSERT OR IGNORE INTO user_roles(user_id,project_id,p_role)
VALUES (3,3,"member");
INSERT OR IGNORE INTO user_roles(user_id,project_id,p_role)
VALUES (2,1,"project_manager");
INSERT OR IGNORE INTO weekly_reports (user_id, project_id, week, development_time, meeting_time, admin_time, own_work_time, study_time, testing_time, signed_by)
VALUES (2, 1, 12, 20, 10, 5, 30, 15, 10, NULL);
INSERT OR IGNORE INTO weekly_reports (user_id, project_id, week, development_time, meeting_time, admin_time, own_work_time, study_time, testing_time, signed_by)
VALUES (3, 1, 12, 20, 10, 5, 30, 15, 10, NULL);
INSERT OR IGNORE INTO weekly_reports (user_id, project_id, week, development_time, meeting_time, admin_time, own_work_time, study_time, testing_time, signed_by)
VALUES (3, 1, 14, 20, 10, 5, 30, 15, 10, NULL);
INSERT OR IGNORE INTO weekly_reports (user_id, project_id, week, development_time, meeting_time, admin_time, own_work_time, study_time, testing_time, signed_by)
VALUES (3, 2, 12, 20, 10, 5, 30, 15, 10, NULL);
INSERT OR IGNORE INTO weekly_reports (user_id, project_id, week, development_time, meeting_time, admin_time, own_work_time, study_time, testing_time, signed_by)
VALUES (3, 3, 12, 20, 10, 5, 30, 15, 10, NULL);

View file

@ -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
label="Name"
type="text"
value={name}
onChange={(e) => { onChange={(e) => {
e.preventDefault(); e.preventDefault();
setName(e.target.value); setName(e.target.value);
}} }}
/> />
<div className="p-2"></div> <InputField
<DescriptionInput label="Description"
desc={description} type="text"
value={description}
onChange={(e) => { onChange={(e) => {
e.preventDefault(); e.preventDefault();
setDescription(e.target.value); setDescription(e.target.value);
}} }}
placeholder={"Description (Optional)"}
/> />
<div className="flex self-center mt-4 justify-between"> </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,37 +60,22 @@ 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) => {
return search.toLowerCase() === ""
? user
: user.toLowerCase().includes(search.toLowerCase());
})
.map((user) => (
<li <li
className={ className={
names.includes(user) names.includes(user)
? "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" ? "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 transition-all hover:text-white hover:cursor-pointer" : "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} key={user}
value={user} value={user}
@ -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,32 +31,35 @@ 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">
<label>
<input <input
type="radio" type="radio"
value="project_manager" value="project_manager"
checked={selectedRole === "project_manager"} checked={selectedRole === "project_manager"}
onChange={handleRoleChange} onChange={handleRoleChange}
className="m-2" className="ml-2 mr-2 mb-3"
/> />
Project manager Project manager
</label> </label>
<br /> </div>
<label className="hover:cursor-pointer hover:font-bold"> <div className="hover:font-bold">
<label>
<input <input
type="radio" type="radio"
value="member" value="member"
checked={selectedRole === "member"} checked={selectedRole === "member"}
onChange={handleRoleChange} onChange={handleRoleChange}
className="m-2 hover:cursor-pointer" className="ml-2 mr-2"
/> />
Member Member
</label> </label>
</div> </div>
</div>
<Button <Button
text="Change" text="Change"
onClick={(): void => { onClick={(): void => {

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,18 +284,11 @@ 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 => {
@ -334,7 +296,6 @@ export default function GetWeeklyReport(): JSX.Element {
}} }}
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,79 +28,35 @@ 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)
</p>
{showInput && (
<>
<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>
</div> <div className="border-2 border-black rounded-lg h-[8vh] text-left divide-y-2 flex flex-col overflow-auto mx-10">
</> <p className="p-2">Number of members: {users.length}</p>
)} <p className="p-2">
<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);
}}
/>
<ul className="border-2 border-black mt-2 p-2 rounded-2xl text-left overflow-auto h-[24vh] font-medium space-y-2">
{users
.filter((user) => {
return search.toLowerCase() === ""
? user.Username
: user.Username.toLowerCase().includes(
search.toLowerCase(),
);
})
.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" 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} key={user.Username}
onClick={() => { onClick={() => {
props.onClick(user.Username, user.UserRole); props.onClick(user.Username);
}} }}
> >
<span> <span>

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,28 +56,13 @@ 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"}
value={search}
onChange={(e) => {
setSearch(e.target.value);
}}
/>
<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
.filter((project) => {
return search.toLowerCase() === ""
? project.name
: project.name.toLowerCase().includes(search.toLowerCase());
})
.map((project) => (
<li <li
className="hover:font-extrabold hover:underline p-1" className="pt-5"
key={project.name} key={project.name}
onClick={() => { onClick={() => {
handleClickProject(project.name); handleClickProject(project.name);

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,32 +11,13 @@ 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") {
return null; // Skip rendering for admin user
}
return (
<h1 key={index} className="border-b-2 border-black w-full"> <h1 key={index} className="border-b-2 border-black w-full">
<div className="flex justify-between"> <div className="flex justify-between">
<div className="flex"> <div className="flex">
@ -47,24 +27,10 @@ function ProjectMembers(): JSX.Element {
</div> </div>
<div className="flex"> <div className="flex">
<div className="ml-auto flex space-x-4"> <div className="ml-auto flex space-x-4">
{projectMember.Username !==
localStorage.getItem("username") && (
<h1
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 <Link
to={`/otherUsersTimeReports/${projectName}/${projectMember.Username}`} to={`/otherUsersTimeReports/${projectName}/${projectMember.Username}`}
> >
<h1 className="cursor-pointer font-bold hover:font-extrabold hover:underline"> <h1 className="underline cursor-pointer font-bold">
View Reports View Reports
</h1> </h1>
</Link> </Link>
@ -72,8 +38,7 @@ function ProjectMembers(): JSX.Element {
</div> </div>
</div> </div>
</h1> </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"
type="text"
value={username ?? ""}
onChange={(e) => { onChange={(e) => {
setUsername(e.target.value); setUsername(e.target.value);
}} }}
/> />
<div className="py-2" /> <InputField
<PasswordInput label="Password"
password={password} type="password"
value={password ?? ""}
onChange={(e) => { onChange={(e) => {
setPassword(e.target.value); setPassword(e.target.value);
}} }}
/> />
</div>
<div className="flex self-center justify-between"> <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 (
!alphanumeric.test(newUsername) ||
newUsername.length > usernameUpLimit ||
newUsername.length < usernameLowLimit
) {
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 = { const nameChange: StrNameChange = {
prevName: props.username, prevName: props.username,
newName: newUsername.replace(/ /g, ""), newName: newUsername.replace(/ /g, ""),
}; };
ChangeUsername({ nameChange: nameChange }); 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
? "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 (Change 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,31 +28,16 @@ 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"}
value={search}
onChange={(e) => {
setSearch(e.target.value);
}}
/>
<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
.filter((user) => {
return search.toLowerCase() === ""
? user
: user.toLowerCase().includes(search.toLowerCase());
})
.map((user) => (
<li <li
className="hover:font-extrabold hover:underline p-1" className="pt-5"
key={user} key={user}
onClick={() => { onClick={() => {
handleClick(user); handleClick(user);

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 />,