Compare commits
65 commits
Author | SHA1 | Date | |
---|---|---|---|
|
2f8e7547da | ||
|
3be2319bce | ||
|
8948067514 | ||
|
99eb5f17b5 | ||
|
97ea6713a4 | ||
|
2bc3b1b665 | ||
|
bbac1ed4c8 | ||
|
eeb17eabb1 | ||
|
a5a94c7839 | ||
|
128221a6ed | ||
|
90d24ad7c4 | ||
|
cdc070e5a6 | ||
|
5033b657a4 | ||
|
1fd167d412 | ||
|
e1aefb9934 | ||
|
3cd2c7ba40 | ||
|
f8ca68dbea | ||
|
9148ede977 | ||
|
78231eaae9 | ||
|
482c732b3d | ||
|
a25413affa | ||
|
d1b6866c39 | ||
|
c96c0d7b4d | ||
|
97f810fce2 | ||
|
8e60ba405b | ||
|
6c64dfcb0e | ||
|
ac5e67d77f | ||
|
48dd53b56b | ||
|
980ac19199 | ||
|
b6d9b51865 | ||
|
564b2e6f58 | ||
|
f66b6a0f0a | ||
|
2f3f7261a0 | ||
|
a5aac2065d | ||
|
395e855257 | ||
|
0e2b62f5ba | ||
|
bcac9c020e | ||
|
ebe0f380c5 | ||
|
6ca7f0d31f | ||
|
88c6757bd3 | ||
|
35a9c750d1 | ||
|
b71da1a698 | ||
|
9ec7261a94 | ||
|
d9af77a8f7 | ||
|
43d83ca7b3 | ||
|
2692127fdf | ||
|
1e0b32d32d | ||
|
72d27bb644 | ||
|
385ceba248 | ||
|
0d4d77633d | ||
|
c8553d4eae | ||
|
059a322d9e | ||
|
7ec207bf03 | ||
|
ff07bd1ed6 | ||
|
56e566ea9b | ||
|
5e104ec7f9 | ||
|
1631c8b6b0 | ||
|
a266a6f7fc | ||
|
b56e4ed76e | ||
|
f61449dea1 | ||
|
fd7c609e5d | ||
|
9725a0fc48 | ||
|
6d0775586e | ||
|
badeb84282 | ||
|
e63377effa |
46 changed files with 1437 additions and 332 deletions
|
@ -1,58 +1,220 @@
|
|||
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 users(username, password)
|
||||
VALUES ("user", "123");
|
||||
INSERT OR IGNORE INTO projects(name, description, owner_user_id)
|
||||
VALUES ("projecttest1", "Description for projecttest1", 1),
|
||||
("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 users(username, password)
|
||||
VALUES ("user2", "123");
|
||||
INSERT OR IGNORE INTO user_roles(user_id,project_id,p_role)
|
||||
VALUES (1,1,"project_manager"),
|
||||
(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 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);
|
||||
|
|
|
@ -126,7 +126,7 @@ func main() {
|
|||
api.Delete("/removeUserFromProject/:projectName", projects.RemoveUserFromProject)
|
||||
api.Delete("/removeProject/:projectName", projects.RemoveProject)
|
||||
api.Delete("/project/:projectID", projects.DeleteProject)
|
||||
api.Put("/ChangeProjectName/:projectName", projects.ChangeProjectName)
|
||||
api.Put("/changeProjectName/:projectName", projects.ChangeProjectName)
|
||||
|
||||
// All report related routes
|
||||
// reportGroup := api.Group("/report") // Not currently in use
|
||||
|
|
|
@ -701,7 +701,11 @@ export const api: API = {
|
|||
});
|
||||
|
||||
if (!response.ok) {
|
||||
return { success: false, message: "Failed to login" };
|
||||
return {
|
||||
success: false,
|
||||
data: `${response.status}`,
|
||||
message: "Failed to login",
|
||||
};
|
||||
} else {
|
||||
const data = (await response.json()) as { token: string }; // Update the type of 'data'
|
||||
return { success: true, data: data.token };
|
||||
|
@ -1000,8 +1004,8 @@ export const api: API = {
|
|||
}
|
||||
},
|
||||
async getStatistics(
|
||||
token: string,
|
||||
projectName: string,
|
||||
token: string,
|
||||
userName?: string,
|
||||
): Promise<APIResponse<Statistics>> {
|
||||
try {
|
||||
|
@ -1061,7 +1065,7 @@ export const api: API = {
|
|||
): Promise<APIResponse<string>> {
|
||||
try {
|
||||
const response = await fetch(
|
||||
`/api/changePassword/${username}?newPassword=${newPassword}`,
|
||||
`/api/changeUserPassword/${username}?newPassword=${newPassword}`,
|
||||
{
|
||||
method: "PUT",
|
||||
headers: {
|
||||
|
|
|
@ -1,9 +1,13 @@
|
|||
import { useState } from "react";
|
||||
import { api } from "../API/API";
|
||||
import { NewProject } from "../Types/goTypes";
|
||||
import InputField from "./InputField";
|
||||
import Logo from "../assets/Logo.svg";
|
||||
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.
|
||||
|
@ -12,11 +16,26 @@ import Button from "./Button";
|
|||
function AddProject(): JSX.Element {
|
||||
const [name, setName] = useState("");
|
||||
const [description, setDescription] = useState("");
|
||||
const navigate = useNavigate();
|
||||
|
||||
/**
|
||||
* Tries to add a project to the system
|
||||
*/
|
||||
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 = {
|
||||
name: name.replace(/ /g, ""),
|
||||
description: description.trim(),
|
||||
|
@ -30,6 +49,7 @@ function AddProject(): JSX.Element {
|
|||
alert(`${project.name} added!`);
|
||||
setDescription("");
|
||||
setName("");
|
||||
navigate("/admin");
|
||||
} else {
|
||||
alert("Project not added, name could be taken");
|
||||
console.error(response.message);
|
||||
|
@ -44,7 +64,7 @@ function AddProject(): JSX.Element {
|
|||
<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">
|
||||
<form
|
||||
className="bg-white rounded px-8 pt-6 pb-8 mb-4 items-center justify-center flex flex-col w-fit h-fit"
|
||||
className="bg-white rounded px-8 pt-6 pb-8 mb-4 justify-center flex flex-col w-fit h-fit"
|
||||
onSubmit={(e) => {
|
||||
e.preventDefault();
|
||||
void handleCreateProject();
|
||||
|
@ -52,33 +72,29 @@ function AddProject(): JSX.Element {
|
|||
>
|
||||
<img
|
||||
src={Logo}
|
||||
className="logo w-[7vw] mb-10 mt-10"
|
||||
className="logo w-[7vw] self-center mb-10 mt-10"
|
||||
alt="TTIME Logo"
|
||||
/>
|
||||
<h3 className="pb-4 mb-2 text-center font-bold text-[18px]">
|
||||
Create a new project
|
||||
</h3>
|
||||
<div className="space-y-3">
|
||||
<InputField
|
||||
label="Name"
|
||||
type="text"
|
||||
value={name}
|
||||
onChange={(e) => {
|
||||
e.preventDefault();
|
||||
setName(e.target.value);
|
||||
}}
|
||||
/>
|
||||
<InputField
|
||||
label="Description"
|
||||
type="text"
|
||||
value={description}
|
||||
onChange={(e) => {
|
||||
e.preventDefault();
|
||||
setDescription(e.target.value);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex items-center justify-between">
|
||||
<ProjectNameInput
|
||||
name={name}
|
||||
onChange={(e) => {
|
||||
e.preventDefault();
|
||||
setName(e.target.value);
|
||||
}}
|
||||
/>
|
||||
<div className="p-2"></div>
|
||||
<DescriptionInput
|
||||
desc={description}
|
||||
onChange={(e) => {
|
||||
e.preventDefault();
|
||||
setDescription(e.target.value);
|
||||
}}
|
||||
placeholder={"Description (Optional)"}
|
||||
/>
|
||||
<div className="flex self-center mt-4 justify-between">
|
||||
<Button
|
||||
text="Create"
|
||||
onClick={(): void => {
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
import { useEffect, useState } from "react";
|
||||
import Button from "./Button";
|
||||
import AddMember, { AddMemberInfo } from "./AddMember";
|
||||
import BackButton from "./BackButton";
|
||||
import GetUsersInProject, { ProjectMember } from "./GetUsersInProject";
|
||||
import GetAllUsers from "./GetAllUsers";
|
||||
import InputField from "./InputField";
|
||||
|
||||
/**
|
||||
* Provides UI for adding a member to a project.
|
||||
|
@ -13,6 +13,7 @@ function AddUserToProject(props: { projectName: string }): JSX.Element {
|
|||
const [names, setNames] = useState<string[]>([]);
|
||||
const [users, setUsers] = useState<string[]>([]);
|
||||
const [usersProj, setUsersProj] = useState<ProjectMember[]>([]);
|
||||
const [search, setSearch] = useState("");
|
||||
|
||||
// Gets all users and project members for filtering
|
||||
GetAllUsers({ setUsersProp: setUsers });
|
||||
|
@ -36,8 +37,10 @@ function AddUserToProject(props: { projectName: string }): JSX.Element {
|
|||
|
||||
// Attempts to add all of the selected users to the project
|
||||
const handleAddClick = async (): Promise<void> => {
|
||||
if (names.length === 0)
|
||||
if (names.length === 0) {
|
||||
alert("You have to choose at least one user to add");
|
||||
return;
|
||||
}
|
||||
for (const name of names) {
|
||||
const newMember: AddMemberInfo = {
|
||||
userName: name,
|
||||
|
@ -60,32 +63,47 @@ function AddUserToProject(props: { projectName: string }): JSX.Element {
|
|||
};
|
||||
|
||||
return (
|
||||
<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">
|
||||
<div className="border-4 border-black bg-white flex flex-col items-center py-10 px-20 rounded-3xl content-center overflow-auto">
|
||||
<h1 className="text-center font-bold text-[36px] pb-10">
|
||||
{props.projectName}
|
||||
</h1>
|
||||
<p className="p-1 text-center font-bold text-[26px]">
|
||||
Choose users to add:
|
||||
</p>
|
||||
<div className="border-2 border-black pl-2 pr-2 pb-2 rounded-xl text-center overflow-auto h-[26vh] w-[26vh]">
|
||||
<ul className="text-center font-medium space-y-2">
|
||||
|
||||
<div>
|
||||
<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>
|
||||
{users.map((user) => (
|
||||
<li
|
||||
className={
|
||||
names.includes(user)
|
||||
? "items-start p-1 border-2 border-transparent rounded-full bg-orange-500 hover:bg-orange-600 text-white hover:cursor-pointer ring-2 ring-black"
|
||||
: "items-start p-1 border-2 border-black rounded-full bg-orange-200 hover:bg-orange-400 hover:text-slate-100 hover:cursor-pointer"
|
||||
}
|
||||
key={user}
|
||||
value={user}
|
||||
onClick={() => {
|
||||
handleUserClick(user);
|
||||
}}
|
||||
>
|
||||
<span>{user}</span>
|
||||
</li>
|
||||
))}
|
||||
{users
|
||||
.filter((user) => {
|
||||
return search.toLowerCase() === ""
|
||||
? user
|
||||
: user.toLowerCase().includes(search.toLowerCase());
|
||||
})
|
||||
.map((user) => (
|
||||
<li
|
||||
className={
|
||||
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-black rounded-full bg-orange-200 hover:bg-orange-400 transition-all hover:text-white hover:cursor-pointer"
|
||||
}
|
||||
key={user}
|
||||
value={user}
|
||||
onClick={() => {
|
||||
handleUserClick(user);
|
||||
}}
|
||||
>
|
||||
<span>{user}</span>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
<p className="pt-10 pb-5 underline text-center font-bold text-[18px]">
|
||||
|
@ -99,9 +117,7 @@ function AddUserToProject(props: { projectName: string }): JSX.Element {
|
|||
}}
|
||||
type="button"
|
||||
/>
|
||||
<BackButton />
|
||||
</div>
|
||||
<p className="text-center text-gray-500 text-xs"></p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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]">
|
||||
{weeklyReports.map((newWeeklyReport, index) => (
|
||||
<Link
|
||||
to={`/editTimeReport/${projectName}/${newWeeklyReport.week}`}
|
||||
to={`/editTimeReport/${projectName}/${newWeeklyReport.week}/${newWeeklyReport.signedBy ? "signed" : "unsigned"}`}
|
||||
key={index}
|
||||
className="border-b-2 border-black w-full"
|
||||
className="border-b-2 border-black w-full cursor-pointer hover:font-extrabold"
|
||||
>
|
||||
<div className="flex justify-between">
|
||||
<h1>
|
||||
|
|
|
@ -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]">
|
||||
{weeklyReports.map((newWeeklyReport, index) => (
|
||||
<Link
|
||||
to={`/editOthersTR/${projectName}/${username}/${newWeeklyReport.week}`}
|
||||
to={`/editOthersTR/${projectName}/${username}/${newWeeklyReport.week}/${newWeeklyReport.signedBy ? "signed" : "unsigned"}`}
|
||||
key={index}
|
||||
className="border-b-2 border-black w-full"
|
||||
className="border-b-2 border-black w-full hover:font-extrabold"
|
||||
>
|
||||
<div className="flex justify-between">
|
||||
<h1>
|
||||
|
|
36
frontend/src/Components/ChangeProjectName.tsx
Normal file
36
frontend/src/Components/ChangeProjectName.tsx
Normal file
|
@ -0,0 +1,36 @@
|
|||
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);
|
||||
});
|
||||
}
|
|
@ -5,6 +5,7 @@ import ChangeRole, { ProjectRoleChange } from "./ChangeRole";
|
|||
export default function ChangeRoleView(props: {
|
||||
projectName: string;
|
||||
username: string;
|
||||
currentRole: string;
|
||||
}): JSX.Element {
|
||||
const [selectedRole, setSelectedRole] = useState<
|
||||
"project_manager" | "member" | ""
|
||||
|
@ -21,7 +22,12 @@ export default function ChangeRoleView(props: {
|
|||
};
|
||||
|
||||
const handleSubmit = (event: React.FormEvent<HTMLFormElement>): void => {
|
||||
console.log("Cur: " + props.currentRole + " " + "new: " + selectedRole);
|
||||
event.preventDefault();
|
||||
if (selectedRole === props.currentRole) {
|
||||
alert(`Already ${props.currentRole}, nothing changed`);
|
||||
return;
|
||||
}
|
||||
const roleChangeInfo: ProjectRoleChange = {
|
||||
username: props.username,
|
||||
projectname: props.projectName,
|
||||
|
@ -31,34 +37,31 @@ export default function ChangeRoleView(props: {
|
|||
};
|
||||
|
||||
return (
|
||||
<div className="overflow-auto rounded-lg">
|
||||
<div className="overflow-auto">
|
||||
<h1 className="font-bold text-[20px]">Select role:</h1>
|
||||
<form onSubmit={handleSubmit}>
|
||||
<div className="h-[7vh] self-start text-left font-medium overflow-auto border-2 border-black rounded-lg p-2">
|
||||
<div className="hover:font-bold">
|
||||
<label>
|
||||
<input
|
||||
type="radio"
|
||||
value="project_manager"
|
||||
checked={selectedRole === "project_manager"}
|
||||
onChange={handleRoleChange}
|
||||
className="ml-2 mr-2 mb-3"
|
||||
/>
|
||||
Project manager
|
||||
</label>
|
||||
</div>
|
||||
<div className="hover:font-bold">
|
||||
<label>
|
||||
<input
|
||||
type="radio"
|
||||
value="member"
|
||||
checked={selectedRole === "member"}
|
||||
onChange={handleRoleChange}
|
||||
className="ml-2 mr-2"
|
||||
/>
|
||||
Member
|
||||
</label>
|
||||
</div>
|
||||
<div className="py-1 px-1 w-full self-start text-left font-medium overflow-auto border-2 border-black rounded-2xl">
|
||||
<label className="hover:cursor-pointer hover:font-bold">
|
||||
<input
|
||||
type="radio"
|
||||
value="project_manager"
|
||||
checked={selectedRole === "project_manager"}
|
||||
onChange={handleRoleChange}
|
||||
className="m-2"
|
||||
/>
|
||||
Project manager
|
||||
</label>
|
||||
<br />
|
||||
<label className="hover:cursor-pointer hover:font-bold">
|
||||
<input
|
||||
type="radio"
|
||||
value="member"
|
||||
checked={selectedRole === "member"}
|
||||
onChange={handleRoleChange}
|
||||
className="m-2 hover:cursor-pointer"
|
||||
/>
|
||||
Member
|
||||
</label>
|
||||
</div>
|
||||
<Button
|
||||
text="Change"
|
||||
|
|
36
frontend/src/Components/ChangeUserPassword.tsx
Normal file
36
frontend/src/Components/ChangeUserPassword.tsx
Normal file
|
@ -0,0 +1,36 @@
|
|||
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);
|
||||
});
|
||||
}
|
|
@ -9,6 +9,10 @@ function ChangeUsername(props: { nameChange: StrNameChange }): void {
|
|||
alert("You have to give a new name\n\nName not changed");
|
||||
return;
|
||||
}
|
||||
if (props.nameChange.prevName === localStorage.getItem("username")) {
|
||||
alert("You cannot change admin name");
|
||||
return;
|
||||
}
|
||||
api
|
||||
.changeUserName(props.nameChange, localStorage.getItem("accessToken") ?? "")
|
||||
.then((response: APIResponse<void>) => {
|
||||
|
|
|
@ -65,7 +65,7 @@ function DisplayUserProject(): JSX.Element {
|
|||
<Link
|
||||
to={`/PMViewUnsignedReport/${projectName}/${usernames[index]}/${unsignedReport.week}`}
|
||||
>
|
||||
<h1 className="underline cursor-pointer font-bold">
|
||||
<h1 className="cursor-pointer font-bold hover:font-extrabold hover:underline">
|
||||
View Report
|
||||
</h1>
|
||||
</Link>
|
||||
|
|
|
@ -45,7 +45,7 @@ function DisplayUserProject(): JSX.Element {
|
|||
onClick={() => void handleProjectClick(project.name)}
|
||||
key={project.id}
|
||||
>
|
||||
<h1 className="font-bold underline text-[30px] cursor-pointer">
|
||||
<h1 className="font-bold hover:underline text-[30px] cursor-pointer hover:font-extrabold">
|
||||
{project.name}
|
||||
</h1>
|
||||
</div>
|
||||
|
|
|
@ -18,12 +18,13 @@ export default function GetWeeklyReport(): JSX.Element {
|
|||
const [testingTime, setTestingTime] = useState(0);
|
||||
|
||||
const token = localStorage.getItem("accessToken") ?? "";
|
||||
const { projectName, fetchedWeek } = useParams<{
|
||||
const { projectName, fetchedWeek, signedOrUnsigned } = useParams<{
|
||||
projectName: string;
|
||||
fetchedWeek: string;
|
||||
signedOrUnsigned: string;
|
||||
}>();
|
||||
const username = localStorage.getItem("userName") ?? "";
|
||||
console.log(projectName, fetchedWeek);
|
||||
console.log(projectName, fetchedWeek, signedOrUnsigned);
|
||||
|
||||
useEffect(() => {
|
||||
const fetchWeeklyReport = async (): Promise<void> => {
|
||||
|
@ -59,7 +60,7 @@ export default function GetWeeklyReport(): JSX.Element {
|
|||
};
|
||||
|
||||
void fetchWeeklyReport();
|
||||
}, [projectName, fetchedWeek, token]);
|
||||
}, [projectName, fetchedWeek, signedOrUnsigned, token]);
|
||||
|
||||
const handleUpdateWeeklyReport = async (): Promise<void> => {
|
||||
const updateWeeklyReport: UpdateWeeklyReport = {
|
||||
|
@ -139,6 +140,12 @@ export default function GetWeeklyReport(): JSX.Element {
|
|||
)
|
||||
event.preventDefault();
|
||||
}}
|
||||
onClick={() => {
|
||||
if (signedOrUnsigned === "signed") {
|
||||
alert("You cannot edit a signed report.");
|
||||
}
|
||||
}}
|
||||
readOnly={signedOrUnsigned === "signed"}
|
||||
/>
|
||||
</td>
|
||||
</tr>
|
||||
|
@ -168,6 +175,12 @@ export default function GetWeeklyReport(): JSX.Element {
|
|||
)
|
||||
event.preventDefault();
|
||||
}}
|
||||
onClick={() => {
|
||||
if (signedOrUnsigned === "signed") {
|
||||
alert("You cannot edit a signed report.");
|
||||
}
|
||||
}}
|
||||
readOnly={signedOrUnsigned === "signed"}
|
||||
/>
|
||||
</td>
|
||||
</tr>
|
||||
|
@ -197,6 +210,12 @@ export default function GetWeeklyReport(): JSX.Element {
|
|||
)
|
||||
event.preventDefault();
|
||||
}}
|
||||
onClick={() => {
|
||||
if (signedOrUnsigned === "signed") {
|
||||
alert("You cannot edit a signed report.");
|
||||
}
|
||||
}}
|
||||
readOnly={signedOrUnsigned === "signed"}
|
||||
/>
|
||||
</td>
|
||||
</tr>
|
||||
|
@ -226,6 +245,12 @@ export default function GetWeeklyReport(): JSX.Element {
|
|||
)
|
||||
event.preventDefault();
|
||||
}}
|
||||
onClick={() => {
|
||||
if (signedOrUnsigned === "signed") {
|
||||
alert("You cannot edit a signed report.");
|
||||
}
|
||||
}}
|
||||
readOnly={signedOrUnsigned === "signed"}
|
||||
/>
|
||||
</td>
|
||||
</tr>
|
||||
|
@ -255,6 +280,12 @@ export default function GetWeeklyReport(): JSX.Element {
|
|||
)
|
||||
event.preventDefault();
|
||||
}}
|
||||
onClick={() => {
|
||||
if (signedOrUnsigned === "signed") {
|
||||
alert("You cannot edit a signed report.");
|
||||
}
|
||||
}}
|
||||
readOnly={signedOrUnsigned === "signed"}
|
||||
/>
|
||||
</td>
|
||||
</tr>
|
||||
|
@ -284,18 +315,26 @@ export default function GetWeeklyReport(): JSX.Element {
|
|||
)
|
||||
event.preventDefault();
|
||||
}}
|
||||
onClick={() => {
|
||||
if (signedOrUnsigned === "signed") {
|
||||
alert("You cannot edit a signed report.");
|
||||
}
|
||||
}}
|
||||
readOnly={signedOrUnsigned === "signed"}
|
||||
/>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<Button
|
||||
text="Submit changes"
|
||||
onClick={(): void => {
|
||||
return;
|
||||
}}
|
||||
type="submit"
|
||||
/>
|
||||
{signedOrUnsigned !== "signed" && (
|
||||
<Button
|
||||
text="Submit changes"
|
||||
onClick={(): void => {
|
||||
return;
|
||||
}}
|
||||
type="submit"
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
|
|
@ -4,19 +4,21 @@
|
|||
* @returns {JSX.Element} The input field
|
||||
* @example
|
||||
* <InputField
|
||||
* type="text"
|
||||
* label="Example"
|
||||
* placeholder="New placeholder"
|
||||
* type="text"
|
||||
* value={example}
|
||||
* onChange={(e) => {
|
||||
* setExample(e.target.value);
|
||||
* }}
|
||||
* value={example}
|
||||
* />
|
||||
*/
|
||||
function InputField(props: {
|
||||
label: string;
|
||||
type: string;
|
||||
value: string;
|
||||
onChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
|
||||
label?: string;
|
||||
placeholder?: string;
|
||||
type?: string;
|
||||
value?: string;
|
||||
onChange?: (e: React.ChangeEvent<HTMLInputElement>) => void;
|
||||
}): JSX.Element {
|
||||
return (
|
||||
<div className="">
|
||||
|
@ -30,7 +32,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"
|
||||
id={props.label}
|
||||
type={props.type}
|
||||
placeholder={props.label}
|
||||
placeholder={props.placeholder}
|
||||
value={props.value}
|
||||
onChange={props.onChange}
|
||||
/>
|
||||
|
|
38
frontend/src/Components/Inputs/DescriptionInput.tsx
Normal file
38
frontend/src/Components/Inputs/DescriptionInput.tsx
Normal file
|
@ -0,0 +1,38 @@
|
|||
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>
|
||||
</>
|
||||
);
|
||||
}
|
44
frontend/src/Components/Inputs/PasswordInput.tsx
Normal file
44
frontend/src/Components/Inputs/PasswordInput.tsx
Normal file
|
@ -0,0 +1,44 @@
|
|||
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>
|
||||
</>
|
||||
);
|
||||
}
|
48
frontend/src/Components/Inputs/ProjectNameInput.tsx
Normal file
48
frontend/src/Components/Inputs/ProjectNameInput.tsx
Normal file
|
@ -0,0 +1,48 @@
|
|||
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>
|
||||
</>
|
||||
);
|
||||
}
|
50
frontend/src/Components/Inputs/UsernameInput.tsx
Normal file
50
frontend/src/Components/Inputs/UsernameInput.tsx
Normal file
|
@ -0,0 +1,50 @@
|
|||
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>
|
||||
</>
|
||||
);
|
||||
}
|
|
@ -11,6 +11,10 @@ function LoginCheck(props: {
|
|||
password: string;
|
||||
setAuthority: Dispatch<SetStateAction<number>>;
|
||||
}): void {
|
||||
if (props.username === "" || props.password === "") {
|
||||
alert("Please enter username and password to login");
|
||||
return;
|
||||
}
|
||||
const user: NewUser = {
|
||||
username: props.username,
|
||||
password: props.password,
|
||||
|
@ -42,7 +46,15 @@ function LoginCheck(props: {
|
|||
console.error("Token was undefined");
|
||||
}
|
||||
} else {
|
||||
console.error("Token could not be fetched/No such user");
|
||||
if (response.data === "500") {
|
||||
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) => {
|
||||
|
|
|
@ -33,6 +33,7 @@ function Login(props: {
|
|||
props.setUsername(e.target.value);
|
||||
}}
|
||||
value={props.username}
|
||||
placeholder={"Username"}
|
||||
/>
|
||||
<InputField
|
||||
type="password"
|
||||
|
@ -41,6 +42,7 @@ function Login(props: {
|
|||
props.setPassword(e.target.value);
|
||||
}}
|
||||
value={props.password}
|
||||
placeholder={"Password"}
|
||||
/>
|
||||
</div>
|
||||
<Button
|
||||
|
|
|
@ -1,12 +1,13 @@
|
|||
import Button from "./Button";
|
||||
import UserProjectListAdmin from "./UserProjectListAdmin";
|
||||
import { useState } from "react";
|
||||
import ChangeRoleView from "./ChangeRoleView";
|
||||
import RemoveUserFromProj from "./RemoveUserFromProj";
|
||||
import ChangeRoleInput from "./ChangeRoleView";
|
||||
|
||||
function MemberInfoModal(props: {
|
||||
projectName: string;
|
||||
username: string;
|
||||
role: string;
|
||||
onClose: () => void;
|
||||
}): JSX.Element {
|
||||
const [showRoles, setShowRoles] = useState(false);
|
||||
|
@ -20,22 +21,24 @@ function MemberInfoModal(props: {
|
|||
};
|
||||
return (
|
||||
<div
|
||||
className="fixed inset-10 bg-opacity-30 backdrop-blur-sm
|
||||
className="fixed inset-0 bg-opacity-30 backdrop-blur-sm
|
||||
flex justify-center items-center"
|
||||
>
|
||||
<div className="border-4 border-black bg-white rounded-lg text-center flex flex-col">
|
||||
<div className="border-4 border-black bg-white rounded-2xl text-center flex flex-col">
|
||||
<div className="mx-10">
|
||||
<p className="font-bold text-[30px]">{props.username}</p>
|
||||
<p className="font-bold text-[20px]">{props.role}</p>
|
||||
<p
|
||||
className="hover:font-bold hover:cursor-pointer underline"
|
||||
className="hover:font-bold hover:cursor-pointer underline mb-2 mt-1"
|
||||
onClick={handleChangeRole}
|
||||
>
|
||||
(Change Role)
|
||||
</p>
|
||||
{showRoles && (
|
||||
<ChangeRoleView
|
||||
<ChangeRoleInput
|
||||
projectName={props.projectName}
|
||||
username={props.username}
|
||||
currentRole={props.role}
|
||||
/>
|
||||
)}
|
||||
<h2 className="font-bold text-[20px]">Member of these projects:</h2>
|
||||
|
|
24
frontend/src/Components/NavButton.tsx
Normal file
24
frontend/src/Components/NavButton.tsx
Normal file
|
@ -0,0 +1,24 @@
|
|||
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>
|
||||
);
|
||||
}
|
|
@ -1,7 +1,8 @@
|
|||
import { useState, useEffect } from "react";
|
||||
import { WeeklyReport } from "../Types/goTypes";
|
||||
import { api } from "../API/API";
|
||||
import { useParams } from "react-router-dom";
|
||||
import { useParams, useNavigate } from "react-router-dom";
|
||||
import Button from "./Button";
|
||||
|
||||
/**
|
||||
* Renders the component for editing a weekly report.
|
||||
|
@ -17,11 +18,14 @@ export default function OtherUsersTR(): JSX.Element {
|
|||
const [ownWorkTime, setOwnWorkTime] = useState(0);
|
||||
const [studyTime, setStudyTime] = useState(0);
|
||||
const [testingTime, setTestingTime] = useState(0);
|
||||
const [reportId, setReportId] = useState(0);
|
||||
|
||||
const token = localStorage.getItem("accessToken") ?? "";
|
||||
const { projectName } = useParams();
|
||||
const { username } = useParams();
|
||||
const { fetchedWeek } = useParams();
|
||||
const { signedOrUnsigned } = useParams();
|
||||
console.log(projectName, username, fetchedWeek, signedOrUnsigned);
|
||||
|
||||
useEffect(() => {
|
||||
const fetchUsersWeeklyReport = async (): Promise<void> => {
|
||||
|
@ -45,6 +49,7 @@ export default function OtherUsersTR(): JSX.Element {
|
|||
studyTime: 0,
|
||||
testingTime: 0,
|
||||
};
|
||||
setReportId(report.reportId);
|
||||
setWeek(report.week);
|
||||
setDevelopmentTime(report.developmentTime);
|
||||
setMeetingTime(report.meetingTime);
|
||||
|
@ -60,6 +65,27 @@ export default function OtherUsersTR(): JSX.Element {
|
|||
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 (
|
||||
<>
|
||||
<h1 className="text-[30px] font-bold">{username}'s Report</h1>
|
||||
|
@ -153,6 +179,48 @@ export default function OtherUsersTR(): JSX.Element {
|
|||
</tr>
|
||||
</tbody>
|
||||
</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>
|
||||
</>
|
||||
|
|
|
@ -8,22 +8,22 @@ function PMProjectMenu(): JSX.Element {
|
|||
<h1 className="font-bold text-[30px] mb-[20px]">{projectName}</h1>
|
||||
<div className="border-4 border-black bg-white flex flex-col items-center justify-center min-h-[65vh] h-fit w-[50vw] rounded-3xl content-center overflow-scroll space-y-[5vh] p-[30px]">
|
||||
<Link to={`/timeReports/${projectName}/`}>
|
||||
<h1 className="font-bold underline text-[30px] cursor-pointer">
|
||||
<h1 className="font-bold hover:underline text-[30px] cursor-pointer hover:font-extrabold">
|
||||
Your Time Reports
|
||||
</h1>
|
||||
</Link>
|
||||
<Link to={`/newTimeReport/${projectName}`}>
|
||||
<h1 className="font-bold underline text-[30px] cursor-pointer">
|
||||
<h1 className="font-bold hover:underline text-[30px] cursor-pointer hover:font-extrabold">
|
||||
New Time Report
|
||||
</h1>
|
||||
</Link>
|
||||
<Link to={`/projectMembers/${projectName}`}>
|
||||
<h1 className="font-bold underline text-[30px] cursor-pointer">
|
||||
<h1 className="font-bold hover:underline text-[30px] cursor-pointer hover:font-extrabold">
|
||||
Statistics
|
||||
</h1>
|
||||
</Link>
|
||||
<Link to={`/unsignedReports/${projectName}`}>
|
||||
<h1 className="font-bold underline text-[30px] cursor-pointer">
|
||||
<h1 className="font-bold hover:underline text-[30px] cursor-pointer hover:font-extrabold">
|
||||
Unsigned Time Reports
|
||||
</h1>
|
||||
</Link>
|
||||
|
|
|
@ -4,19 +4,62 @@ import GetUsersInProject, { ProjectMember } from "./GetUsersInProject";
|
|||
import { Link } from "react-router-dom";
|
||||
import GetProjectTimes, { projectTimes } from "./GetProjectTimes";
|
||||
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: {
|
||||
projectname: string;
|
||||
onClose: () => void;
|
||||
onClick: (username: string) => void;
|
||||
onClick: (username: string, userRole: string) => void;
|
||||
}): JSX.Element {
|
||||
const [showInput, setShowInput] = useState(false);
|
||||
const [users, setUsers] = useState<ProjectMember[]>([]);
|
||||
const [times, setTimes] = useState<projectTimes>();
|
||||
const [search, setSearch] = useState("");
|
||||
const [newProjName, setNewProjName] = useState("");
|
||||
const totalTime = useRef(0);
|
||||
GetUsersInProject({ projectName: props.projectname, setUsersProp: setUsers });
|
||||
|
||||
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(() => {
|
||||
if (times?.totalTime !== undefined) {
|
||||
totalTime.current = times.totalTime;
|
||||
|
@ -28,44 +71,88 @@ function ProjectInfoModal(props: {
|
|||
className="fixed inset-0 bg-black bg-opacity-30 backdrop-blur-sm
|
||||
flex justify-center items-center"
|
||||
>
|
||||
<div className="border-4 border-black bg-white p-2 rounded-2xl text-center h-[61vh] w-[40] overflow-auto">
|
||||
<div className="border-4 border-black bg-white p-2 rounded-2xl text-center h-[64vh] w-[40] overflow-auto">
|
||||
<div className="pl-10 pr-10">
|
||||
<h1 className="font-bold text-[32px] mb-[20px]">
|
||||
{props.projectname}
|
||||
</h1>
|
||||
<div className="p-1 text-center">
|
||||
<h2 className="text-[20px] font-bold">Statistics:</h2>
|
||||
</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">
|
||||
<h1 className="font-bold text-[32px]">{props.projectname}</h1>
|
||||
<p
|
||||
className="mb-4 hover:font-bold hover:cursor-pointer hover:underline"
|
||||
onClick={handleChangeNameView}
|
||||
>
|
||||
(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>
|
||||
</>
|
||||
)}
|
||||
<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:{" "}
|
||||
{Math.floor(totalTime.current / 60 / 24) + " d "}
|
||||
{Math.floor((totalTime.current / 60) % 24) + " h "}
|
||||
{(totalTime.current % 60) + " m "}
|
||||
</p>
|
||||
</div>
|
||||
<div className="h-[6vh] p-7 text-center">
|
||||
<h2 className="text-[20px] font-bold">Project members:</h2>
|
||||
</div>
|
||||
<div className="border-2 border-black p-2 rounded-lg text-center overflow-auto h-[24vh] mx-10">
|
||||
<ul className="text-left font-medium space-y-2">
|
||||
<div></div>
|
||||
{users.map((user) => (
|
||||
<li
|
||||
className="items-start p-1 border-2 border-black rounded-lg bg-orange-200 hover:bg-orange-600 hover:text-slate-100 hover:cursor-pointer"
|
||||
key={user.Username}
|
||||
onClick={() => {
|
||||
props.onClick(user.Username);
|
||||
}}
|
||||
>
|
||||
<span>
|
||||
Name: {user.Username}
|
||||
<div></div>
|
||||
Role: {user.UserRole}
|
||||
</span>
|
||||
</li>
|
||||
))}
|
||||
<h3 className="pt-7 text-[20px] font-bold">Project members:</h3>
|
||||
<div className="">
|
||||
<InputField
|
||||
placeholder={"Search member"}
|
||||
type={"Text"}
|
||||
value={search}
|
||||
onChange={(e) => {
|
||||
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
|
||||
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>
|
||||
</div>
|
||||
<div className="space-x-5 my-2">
|
||||
|
|
|
@ -2,6 +2,7 @@ import { useState } from "react";
|
|||
import { NewProject } from "../Types/goTypes";
|
||||
import ProjectInfoModal from "./ProjectInfoModal";
|
||||
import MemberInfoModal from "./MemberInfoModal";
|
||||
import InputField from "./InputField";
|
||||
|
||||
/**
|
||||
* A list of projects for admin manage projects page, that sets an onClick
|
||||
|
@ -21,9 +22,12 @@ export function ProjectListAdmin(props: {
|
|||
const [projectName, setProjectName] = useState("");
|
||||
const [userModalVisible, setUserModalVisible] = useState(false);
|
||||
const [username, setUsername] = useState("");
|
||||
const [userRole, setUserRole] = useState("");
|
||||
const [search, setSearch] = useState("");
|
||||
|
||||
const handleClickUser = (username: string): void => {
|
||||
const handleClickUser = (username: string, userRole: string): void => {
|
||||
setUsername(username);
|
||||
setUserRole(userRole);
|
||||
setUserModalVisible(true);
|
||||
};
|
||||
|
||||
|
@ -39,11 +43,13 @@ export function ProjectListAdmin(props: {
|
|||
|
||||
const handleCloseUser = (): void => {
|
||||
setUsername("");
|
||||
setUserRole("");
|
||||
setUserModalVisible(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<h1 className="font-bold text-[30px] mb-[20px]">Manage Projects</h1>
|
||||
{projectModalVisible && (
|
||||
<ProjectInfoModal
|
||||
onClose={handleCloseProject}
|
||||
|
@ -56,21 +62,36 @@ export function ProjectListAdmin(props: {
|
|||
onClose={handleCloseUser}
|
||||
username={username}
|
||||
projectName={projectName}
|
||||
role={userRole}
|
||||
/>
|
||||
)}
|
||||
<div>
|
||||
<ul className="font-bold underline text-[30px] cursor-pointer padding">
|
||||
{props.projects.map((project) => (
|
||||
<li
|
||||
className="pt-5"
|
||||
key={project.name}
|
||||
onClick={() => {
|
||||
handleClickProject(project.name);
|
||||
}}
|
||||
>
|
||||
{project.name}
|
||||
</li>
|
||||
))}
|
||||
<InputField
|
||||
placeholder={"Search"}
|
||||
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
|
||||
className="hover:font-extrabold hover:underline p-1"
|
||||
key={project.name}
|
||||
onClick={() => {
|
||||
handleClickProject(project.name);
|
||||
}}
|
||||
>
|
||||
{project.name}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
</>
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import { useState } from "react";
|
||||
import { Link, useParams } from "react-router-dom";
|
||||
import GetUsersInProject, { ProjectMember } from "./GetUsersInProject";
|
||||
import { api } from "../API/API";
|
||||
|
||||
function ProjectMembers(): JSX.Element {
|
||||
const { projectName } = useParams();
|
||||
|
@ -11,34 +12,68 @@ function ProjectMembers(): JSX.Element {
|
|||
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 (
|
||||
<>
|
||||
<h1 className="font-bold text-[30px] mb-[20px]">
|
||||
All Members In: {projectName}{" "}
|
||||
</h1>
|
||||
<div className="border-4 border-black bg-white flex flex-col items-center justify-center min-h-[65vh] h-fit w-[70vw] rounded-3xl content-center overflow-scroll space-y-[10vh] p-[30px] text-[20px]">
|
||||
{projectMembers.map((projectMember: ProjectMember, index: number) => (
|
||||
<h1 key={index} className="border-b-2 border-black w-full">
|
||||
<div className="flex justify-between">
|
||||
<div className="flex">
|
||||
<h1>{projectMember.Username}</h1>
|
||||
<span className="ml-6 mr-2 font-bold">Role:</span>
|
||||
<h1>{projectMember.UserRole}</h1>
|
||||
</div>
|
||||
<div className="flex">
|
||||
<div className="ml-auto flex space-x-4">
|
||||
<Link
|
||||
to={`/otherUsersTimeReports/${projectName}/${projectMember.Username}`}
|
||||
>
|
||||
<h1 className="underline cursor-pointer font-bold">
|
||||
View Reports
|
||||
</h1>
|
||||
</Link>
|
||||
{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">
|
||||
<div className="flex justify-between">
|
||||
<div className="flex">
|
||||
<h1>{projectMember.Username}</h1>
|
||||
<span className="ml-6 mr-2 font-bold">Role:</span>
|
||||
<h1>{projectMember.UserRole}</h1>
|
||||
</div>
|
||||
<div className="flex">
|
||||
<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
|
||||
to={`/otherUsersTimeReports/${projectName}/${projectMember.Username}`}
|
||||
>
|
||||
<h1 className="cursor-pointer font-bold hover:font-extrabold hover:underline">
|
||||
View Reports
|
||||
</h1>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</h1>
|
||||
))}
|
||||
</h1>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
|
|
|
@ -3,25 +3,44 @@ import { NewUser } from "../Types/goTypes";
|
|||
import { api } from "../API/API";
|
||||
import Logo from "../assets/Logo.svg";
|
||||
import Button from "./Button";
|
||||
import InputField from "./InputField";
|
||||
import UsernameInput from "./Inputs/UsernameInput";
|
||||
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.
|
||||
* @returns The JSX element representing the registration form.
|
||||
*/
|
||||
export default function Register(): JSX.Element {
|
||||
const [username, setUsername] = useState<string>();
|
||||
const [password, setPassword] = useState<string>();
|
||||
const [errMessage, setErrMessage] = useState<string>();
|
||||
const [username, setUsername] = useState<string>("");
|
||||
const [password, setPassword] = useState<string>("");
|
||||
const [errMessage, setErrMessage] = useState<string>("");
|
||||
|
||||
const handleRegister = async (): Promise<void> => {
|
||||
if (username === "" || password === "") {
|
||||
alert("Must provide username and password");
|
||||
if (
|
||||
username.length > usernameUpLimit ||
|
||||
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;
|
||||
}
|
||||
const newUser: NewUser = {
|
||||
username: username?.replace(/ /g, "") ?? "",
|
||||
password: password ?? "",
|
||||
username: username,
|
||||
password: password,
|
||||
};
|
||||
const response = await api.registerUser(newUser);
|
||||
if (response.success) {
|
||||
|
@ -39,7 +58,7 @@ export default function Register(): JSX.Element {
|
|||
<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">
|
||||
<form
|
||||
className="bg-white rounded px-8 pt-6 pb-8 mb-4 items-center justify-center flex flex-col w-fit h-fit"
|
||||
className="bg-white rounded px-8 pt-6 pb-8 mb-4 justify-center flex flex-col w-fit h-fit"
|
||||
onSubmit={(e) => {
|
||||
e.preventDefault();
|
||||
void handleRegister();
|
||||
|
@ -47,31 +66,28 @@ export default function Register(): JSX.Element {
|
|||
>
|
||||
<img
|
||||
src={Logo}
|
||||
className="logo w-[7vw] mb-10 mt-10"
|
||||
className="logo self-center w-[7vw] mb-10 mt-10"
|
||||
alt="TTIME Logo"
|
||||
/>
|
||||
<h3 className="pb-4 mb-2 text-center font-bold text-[18px]">
|
||||
Register New User
|
||||
</h3>
|
||||
<div className="space-y-3">
|
||||
<InputField
|
||||
label="Username"
|
||||
type="text"
|
||||
value={username ?? ""}
|
||||
onChange={(e) => {
|
||||
setUsername(e.target.value);
|
||||
}}
|
||||
/>
|
||||
<InputField
|
||||
label="Password"
|
||||
type="password"
|
||||
value={password ?? ""}
|
||||
onChange={(e) => {
|
||||
setPassword(e.target.value);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex items-center justify-between">
|
||||
|
||||
<UsernameInput
|
||||
username={username}
|
||||
onChange={(e) => {
|
||||
setUsername(e.target.value);
|
||||
}}
|
||||
/>
|
||||
<div className="py-2" />
|
||||
<PasswordInput
|
||||
password={password}
|
||||
onChange={(e) => {
|
||||
setPassword(e.target.value);
|
||||
}}
|
||||
/>
|
||||
|
||||
<div className="flex self-center justify-between">
|
||||
<Button
|
||||
text="Register"
|
||||
onClick={(): void => {
|
||||
|
|
|
@ -14,6 +14,7 @@ export default function TimePerRole(): JSX.Element {
|
|||
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();
|
||||
|
@ -49,7 +50,7 @@ export default function TimePerRole(): JSX.Element {
|
|||
return (
|
||||
<>
|
||||
<h1 className="font-bold text-[30px] mb-[20px]">
|
||||
Total Time Per Activity In: {projectName}{" "}
|
||||
Total Time Per Activity For All Members 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">
|
||||
|
@ -129,6 +130,12 @@ export default function TimePerRole(): JSX.Element {
|
|||
/>
|
||||
</td>
|
||||
</tr>
|
||||
<tr className="h-[10vh] font-bold font-">
|
||||
<td>In Total:</td>
|
||||
<td>
|
||||
<h1>{total}</h1>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
|
|
@ -2,35 +2,106 @@ import Button from "./Button";
|
|||
import DeleteUser from "./DeleteUser";
|
||||
import UserProjectListAdmin from "./UserProjectListAdmin";
|
||||
import { useState } from "react";
|
||||
import InputField from "./InputField";
|
||||
import ChangeUsername from "./ChangeUsername";
|
||||
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: {
|
||||
isVisible: boolean;
|
||||
username: string;
|
||||
onClose: () => void;
|
||||
}): JSX.Element {
|
||||
const [showInput, setShowInput] = useState(false);
|
||||
const [showNameInput, setShowNameInput] = useState(false);
|
||||
const [showPwordInput, setShowPwordInput] = useState(false);
|
||||
const [newUsername, setNewUsername] = useState("");
|
||||
const [newPassword, setNewPassword] = useState("");
|
||||
if (!props.isVisible) {
|
||||
return <></>;
|
||||
}
|
||||
|
||||
const handleChangeNameView = (): void => {
|
||||
if (showInput) {
|
||||
setShowInput(false);
|
||||
/*
|
||||
* Switches name input between visible/invisible
|
||||
* and makes password input invisible
|
||||
*/
|
||||
const handleShowNameInput = (): void => {
|
||||
if (showPwordInput) setShowPwordInput(false);
|
||||
if (showNameInput) {
|
||||
setShowNameInput(false);
|
||||
setNewUsername("");
|
||||
} else {
|
||||
setShowInput(true);
|
||||
setShowNameInput(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 nameChange: StrNameChange = {
|
||||
prevName: props.username,
|
||||
newName: newUsername.replace(/ /g, ""),
|
||||
};
|
||||
ChangeUsername({ nameChange: nameChange });
|
||||
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 = {
|
||||
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 (
|
||||
|
@ -38,23 +109,37 @@ function UserInfoModal(props: {
|
|||
className="fixed inset-0 bg-black bg-opacity-30 backdrop-blur-sm
|
||||
flex justify-center items-center"
|
||||
>
|
||||
<div className="border-4 border-black bg-white rounded-lg text-center flex flex-col">
|
||||
<div className="border-4 border-black bg-white rounded-2xl text-center flex flex-col">
|
||||
<div className="mx-10">
|
||||
<p className="font-bold text-[30px]">{props.username}</p>
|
||||
<p
|
||||
className="mb-[10px] hover:font-bold hover:cursor-pointer underline"
|
||||
onClick={handleChangeNameView}
|
||||
>
|
||||
(Change Username)
|
||||
<p className="mt-2 font-bold text-[20px]">Change:</p>
|
||||
<p className="mt-2 space-x-3 mb-[10px]">
|
||||
<span
|
||||
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
|
||||
</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>
|
||||
{showInput && (
|
||||
<div>
|
||||
<InputField
|
||||
label={"New username"}
|
||||
type={"text"}
|
||||
value={newUsername}
|
||||
onChange={function (e): void {
|
||||
e.defaultPrevented;
|
||||
{showNameInput && (
|
||||
<div className="mt-7">
|
||||
<UsernameInput
|
||||
username={newUsername}
|
||||
onChange={(e) => {
|
||||
setNewUsername(e.target.value);
|
||||
}}
|
||||
/>
|
||||
|
@ -67,6 +152,23 @@ function UserInfoModal(props: {
|
|||
/>
|
||||
</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>
|
||||
<UserProjectListAdmin username={props.username} />
|
||||
<div className="items-center space-x-6">
|
||||
|
@ -87,7 +189,9 @@ function UserInfoModal(props: {
|
|||
text={"Close"}
|
||||
onClick={function (): void {
|
||||
setNewUsername("");
|
||||
setShowInput(false);
|
||||
setNewPassword("");
|
||||
setShowNameInput(false);
|
||||
setShowPwordInput(false);
|
||||
props.onClose();
|
||||
}}
|
||||
type="button"
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import { useState } from "react";
|
||||
import UserInfoModal from "./UserInfoModal";
|
||||
import InputField from "./InputField";
|
||||
|
||||
/**
|
||||
* A list of users for admin manage users page, that sets an onClick
|
||||
|
@ -15,6 +16,7 @@ import UserInfoModal from "./UserInfoModal";
|
|||
export function UserListAdmin(props: { users: string[] }): JSX.Element {
|
||||
const [modalVisible, setModalVisible] = useState(false);
|
||||
const [username, setUsername] = useState("");
|
||||
const [search, setSearch] = useState("");
|
||||
|
||||
const handleClick = (username: string): void => {
|
||||
setUsername(username);
|
||||
|
@ -28,24 +30,39 @@ export function UserListAdmin(props: { users: string[] }): JSX.Element {
|
|||
|
||||
return (
|
||||
<>
|
||||
<h1 className="font-bold text-[30px] mb-[20px]">Manage Users</h1>
|
||||
<UserInfoModal
|
||||
onClose={handleClose}
|
||||
isVisible={modalVisible}
|
||||
username={username}
|
||||
/>
|
||||
<div>
|
||||
<ul className="font-bold underline text-[30px] cursor-pointer padding">
|
||||
{props.users.map((user) => (
|
||||
<li
|
||||
className="pt-5"
|
||||
key={user}
|
||||
onClick={() => {
|
||||
handleClick(user);
|
||||
}}
|
||||
>
|
||||
{user}
|
||||
</li>
|
||||
))}
|
||||
<InputField
|
||||
placeholder={"Search"}
|
||||
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
|
||||
className="hover:font-extrabold hover:underline p-1"
|
||||
key={user}
|
||||
onClick={() => {
|
||||
handleClick(user);
|
||||
}}
|
||||
>
|
||||
{user}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
</>
|
||||
|
|
|
@ -8,7 +8,7 @@ function UserProjectListAdmin(props: { username: string }): JSX.Element {
|
|||
GetProjects({ setProjectsProp: setProjects, username: props.username });
|
||||
|
||||
return (
|
||||
<div className="border-2 border-black bg-white rounded-lg text-left overflow-auto h-[15vh] font-medium">
|
||||
<div className="border-2 border-black bg-white rounded-2xl text-left overflow-auto h-[15vh] font-medium">
|
||||
<ul className="divide-y-2">
|
||||
{projects.map((project) => (
|
||||
<li className="mx-2 my-1" key={project.id}>
|
||||
|
|
|
@ -16,12 +16,12 @@ function UserProjectMenu(): JSX.Element {
|
|||
<h1 className="font-bold text-[30px] mb-[20px]">{projectName}</h1>
|
||||
<div className="border-4 border-black bg-white flex flex-col items-center justify-center min-h-[65vh] h-fit w-[50vw] rounded-3xl content-center overflow-scroll space-y-[10vh] p-[30px]">
|
||||
<Link to={`/timeReports/${projectName}/`}>
|
||||
<h1 className="font-bold underline text-[30px] cursor-pointer">
|
||||
<h1 className="font-bold hover:underline text-[30px] cursor-pointer hover:font-extrabold">
|
||||
Your Time Reports
|
||||
</h1>
|
||||
</Link>
|
||||
<Link to={`/newTimeReport/${projectName}`}>
|
||||
<h1 className="font-bold underline text-[30px] cursor-pointer">
|
||||
<h1 className="font-bold hover:underline text-[30px] cursor-pointer hover:font-extrabold">
|
||||
New Time Report
|
||||
</h1>
|
||||
</Link>
|
||||
|
|
150
frontend/src/Components/UserStatistics.tsx
Normal file
150
frontend/src/Components/UserStatistics.tsx
Normal file
|
@ -0,0 +1,150 @@
|
|||
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>
|
||||
</>
|
||||
);
|
||||
}
|
36
frontend/src/Data/constants.ts
Normal file
36
frontend/src/Data/constants.ts
Normal file
|
@ -0,0 +1,36 @@
|
|||
//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;
|
9
frontend/src/Data/regex.ts
Normal file
9
frontend/src/Data/regex.ts
Normal file
|
@ -0,0 +1,9 @@
|
|||
/**
|
||||
* Only alphanumerical characters
|
||||
*/
|
||||
export const alphanumeric = /^[a-zA-Z0-9]+$/;
|
||||
|
||||
/**
|
||||
* Only lowercase letters
|
||||
*/
|
||||
export const lowercase = /^[a-z]+$/;
|
|
@ -1,11 +1,11 @@
|
|||
import { Link } from "react-router-dom";
|
||||
import BackButton from "../../Components/BackButton";
|
||||
import BasicWindow from "../../Components/BasicWindow";
|
||||
import Button from "../../Components/Button";
|
||||
import { ProjectListAdmin } from "../../Components/ProjectListAdmin";
|
||||
import { Project } from "../../Types/goTypes";
|
||||
import GetProjects from "../../Components/GetProjects";
|
||||
import { useState } from "react";
|
||||
import NavButton from "../../Components/NavButton";
|
||||
|
||||
function AdminManageProjects(): JSX.Element {
|
||||
const [projects, setProjects] = useState<Project[]>([]);
|
||||
|
@ -13,14 +13,7 @@ function AdminManageProjects(): JSX.Element {
|
|||
setProjectsProp: setProjects,
|
||||
username: localStorage.getItem("username") ?? "",
|
||||
});
|
||||
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 content = <ProjectListAdmin projects={projects} />;
|
||||
|
||||
const buttons = (
|
||||
<>
|
||||
|
@ -33,7 +26,7 @@ function AdminManageProjects(): JSX.Element {
|
|||
type="button"
|
||||
/>
|
||||
</Link>
|
||||
<BackButton />
|
||||
<NavButton navTo="/admin" label={"Back"} />
|
||||
</>
|
||||
);
|
||||
|
||||
|
|
|
@ -12,14 +12,7 @@ function AdminManageUsers(): JSX.Element {
|
|||
|
||||
const navigate = useNavigate();
|
||||
|
||||
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 content = <UserListAdmin users={users} />;
|
||||
|
||||
const buttons = (
|
||||
<>
|
||||
|
|
|
@ -5,14 +5,14 @@ function AdminMenuPage(): JSX.Element {
|
|||
const content = (
|
||||
<>
|
||||
<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-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-auto space-y-[10vh] p-[30px]">
|
||||
<Link to="/adminManageUser">
|
||||
<h1 className="font-bold underline text-[30px] cursor-pointer">
|
||||
<h1 className="font-bold hover:underline text-[30px] cursor-pointer hover:font-extrabold">
|
||||
Manage Users
|
||||
</h1>
|
||||
</Link>
|
||||
<Link to="/adminManageProject">
|
||||
<h1 className="font-bold underline text-[30px] cursor-pointer">
|
||||
<h1 className="font-bold hover:underline text-[30px] cursor-pointer hover:font-extrabold">
|
||||
Manage Projects
|
||||
</h1>
|
||||
</Link>
|
||||
|
|
|
@ -1,11 +1,12 @@
|
|||
import { useLocation } from "react-router-dom";
|
||||
import AddUserToProject from "../../Components/AddUserToProject";
|
||||
import BasicWindow from "../../Components/BasicWindow";
|
||||
import BackButton from "../../Components/BackButton";
|
||||
|
||||
function AdminProjectAddMember(): JSX.Element {
|
||||
const projectName = useLocation().search.slice(1);
|
||||
const content = <AddUserToProject projectName={projectName} />;
|
||||
const buttons = <></>;
|
||||
const buttons = <BackButton />;
|
||||
return <BasicWindow content={content} buttons={buttons} />;
|
||||
}
|
||||
export default AdminProjectAddMember;
|
||||
|
|
|
@ -1,8 +1,12 @@
|
|||
import BasicWindow from "../../Components/BasicWindow";
|
||||
import BackButton from "../../Components/BackButton";
|
||||
import AllTimeReportsInProjectOtherUser from "../../Components/AllTimeReportsInProjectOtherUser";
|
||||
import Button from "../../Components/Button";
|
||||
import { useParams, Link } from "react-router-dom";
|
||||
|
||||
function PMOtherUsersTR(): JSX.Element {
|
||||
const { projectName } = useParams();
|
||||
const { username } = useParams();
|
||||
const content = (
|
||||
<>
|
||||
<AllTimeReportsInProjectOtherUser />
|
||||
|
@ -11,6 +15,15 @@ function PMOtherUsersTR(): JSX.Element {
|
|||
|
||||
const buttons = (
|
||||
<>
|
||||
<Link to={`/viewStatistics/${projectName}/${username}`}>
|
||||
<Button
|
||||
text={`Statistics: ${username}`}
|
||||
onClick={(): void => {
|
||||
return;
|
||||
}}
|
||||
type={"button"}
|
||||
/>
|
||||
</Link>
|
||||
<BackButton />
|
||||
</>
|
||||
);
|
||||
|
|
|
@ -16,7 +16,7 @@ function PMProjectMembers(): JSX.Element {
|
|||
<>
|
||||
<Link to={`/PMtimeactivity/${projectName}`}>
|
||||
<Button
|
||||
text="Time / Activity"
|
||||
text="Statistics"
|
||||
onClick={(): void => {
|
||||
return;
|
||||
}}
|
||||
|
|
|
@ -1,8 +1,13 @@
|
|||
import BackButton from "../../Components/BackButton";
|
||||
import BasicWindow from "../../Components/BasicWindow";
|
||||
import UserStatistics from "../../Components/UserStatistics";
|
||||
|
||||
function AdminProjectStatistics(): JSX.Element {
|
||||
const content = <></>;
|
||||
function UserNewTimeReportPage(): JSX.Element {
|
||||
const content = (
|
||||
<>
|
||||
<UserStatistics />
|
||||
</>
|
||||
);
|
||||
|
||||
const buttons = (
|
||||
<>
|
||||
|
@ -12,4 +17,4 @@ function AdminProjectStatistics(): JSX.Element {
|
|||
|
||||
return <BasicWindow content={content} buttons={buttons} />;
|
||||
}
|
||||
export default AdminProjectStatistics;
|
||||
export default UserNewTimeReportPage;
|
|
@ -1,10 +1,12 @@
|
|||
import BasicWindow from "../../Components/BasicWindow";
|
||||
import BackButton from "../../Components/BackButton";
|
||||
import { useParams } from "react-router-dom";
|
||||
import { useParams, Link } from "react-router-dom";
|
||||
import AllTimeReportsInProject from "../../Components/AllTimeReportsInProject";
|
||||
import Button from "../../Components/Button";
|
||||
|
||||
function UserViewTimeReportsPage(): JSX.Element {
|
||||
const { projectName } = useParams();
|
||||
const username = localStorage.getItem("username");
|
||||
|
||||
const content = (
|
||||
<>
|
||||
|
@ -17,6 +19,15 @@ function UserViewTimeReportsPage(): JSX.Element {
|
|||
|
||||
const buttons = (
|
||||
<>
|
||||
<Link to={`/viewStatistics/${projectName}/${username}`}>
|
||||
<Button
|
||||
text="Statistics"
|
||||
onClick={(): void => {
|
||||
return;
|
||||
}}
|
||||
type={"button"}
|
||||
/>
|
||||
</Link>
|
||||
<BackButton />
|
||||
</>
|
||||
);
|
||||
|
|
|
@ -9,6 +9,7 @@ import AdminMenuPage from "./Pages/AdminPages/AdminMenuPage.tsx";
|
|||
import UserEditTimeReportPage from "./Pages/UserPages/UserEditTimeReportPage.tsx";
|
||||
import UserNewTimeReportPage from "./Pages/UserPages/UserNewTimeReportPage.tsx";
|
||||
import UserViewTimeReportsPage from "./Pages/UserPages/UserViewTimeReportsPage.tsx";
|
||||
import UserViewStatistics from "./Pages/UserPages/UserViewStatistics.tsx";
|
||||
import PMChangeRole from "./Pages/ProjectManagerPages/PMChangeRole.tsx";
|
||||
import PMOtherUsersTR from "./Pages/ProjectManagerPages/PMOtherUsersTR.tsx";
|
||||
import PMProjectMembers from "./Pages/ProjectManagerPages/PMProjectMembers.tsx";
|
||||
|
@ -22,7 +23,6 @@ import AdminManageProjects from "./Pages/AdminPages/AdminManageProjects.tsx";
|
|||
import AdminAddProject from "./Pages/AdminPages/AdminAddProject.tsx";
|
||||
import AdminAddUser from "./Pages/AdminPages/AdminAddUser.tsx";
|
||||
import AdminProjectAddMember from "./Pages/AdminPages/AdminProjectAddMember.tsx";
|
||||
import AdminProjectStatistics from "./Pages/AdminPages/AdminProjectStatistics.tsx";
|
||||
import NotFoundPage from "./Pages/NotFoundPage.tsx";
|
||||
import UnauthorizedPage from "./Pages/UnauthorizedPage.tsx";
|
||||
import PMViewOtherUsersTR from "./Pages/ProjectManagerPages/PMViewOtherUsersTR.tsx";
|
||||
|
@ -55,9 +55,13 @@ const router = createBrowserRouter([
|
|||
element: <UserViewTimeReportsPage />,
|
||||
},
|
||||
{
|
||||
path: "/editTimeReport/:projectName/:fetchedWeek",
|
||||
path: "/editTimeReport/:projectName/:fetchedWeek/:signedOrUnsigned",
|
||||
element: <UserEditTimeReportPage />,
|
||||
},
|
||||
{
|
||||
path: "/viewStatistics/:projectName/:username",
|
||||
element: <UserViewStatistics />,
|
||||
},
|
||||
{
|
||||
path: "/changeRole/:projectName/:username",
|
||||
element: <PMChangeRole />,
|
||||
|
@ -67,7 +71,7 @@ const router = createBrowserRouter([
|
|||
element: <PMOtherUsersTR />,
|
||||
},
|
||||
{
|
||||
path: "/editOthersTR/:projectName/:username/:fetchedWeek",
|
||||
path: "/editOthersTR/:projectName/:username/:fetchedWeek/:signedOrUnsigned",
|
||||
element: <PMViewOtherUsersTR />,
|
||||
},
|
||||
{
|
||||
|
@ -98,10 +102,6 @@ const router = createBrowserRouter([
|
|||
path: "/adminProjectAddMember",
|
||||
element: <AdminProjectAddMember />,
|
||||
},
|
||||
{
|
||||
path: "/adminProjectStatistics",
|
||||
element: <AdminProjectStatistics />,
|
||||
},
|
||||
{
|
||||
path: "/addProject",
|
||||
element: <AdminAddProject />,
|
||||
|
|
Loading…
Reference in a new issue