Merge branch 'frontend' into gruppPP

This commit is contained in:
Peter KW 2024-04-14 11:58:16 +02:00
commit b6d9b51865
25 changed files with 1732 additions and 990 deletions

File diff suppressed because it is too large Load diff

View file

@ -11,6 +11,7 @@ import {
NewProject,
WeeklyReport,
StrNameChange,
Statistics,
} from "../Types/goTypes";
/**
@ -221,6 +222,15 @@ interface API {
*/
signReport(reportId: number, token: string): Promise<APIResponse<string>>;
/**
* Unsigns a report. Keep in mind that the user which the token belongs to must be
* the project manager of the project the report belongs to.
*
* @param {number} reportId The id of the report to sign
* @param {string} token The authentication token
*/
unsignReport(reportId: number, token: string): Promise<APIResponse<string>>;
/**
* Promotes a user to project manager within a project.
*
@ -233,6 +243,33 @@ interface API {
projectName: string,
token: string,
): Promise<APIResponse<string>>;
/**
* Get the username from the id
* @param {number} id The id of the user
* @param {string} token Your token
*/
getUsername(id: number, token: string): Promise<APIResponse<string>>;
/**
* Deletes a WeeklyReport from the database
* @param {number} reportId The id of the report to delete
* @param {string} token The authentication token
*/
deleteWeeklyReport(
reportId: number,
token: string,
): Promise<APIResponse<string>>;
/**
* Retrieves the total time spent on a project for a particular user (the user is determined by the token)
*
* @param {string} projectName The name of the project
* @param {string} token The authentication token
*/
getStatistics(
projectName: string,
token: string,
): Promise<APIResponse<Statistics>>;
}
/** An instance of the API */
@ -844,6 +881,29 @@ export const api: API = {
}
},
async unsignReport(
reportId: number,
token: string,
): Promise<APIResponse<string>> {
try {
const response = await fetch(`/api/unsignReport/${reportId}`, {
method: "PUT",
headers: {
"Content-Type": "application/json",
Authorization: "Bearer " + token,
},
});
if (!response.ok) {
return { success: false, message: "Failed to unsign report" };
} else {
return { success: true, message: "Report unsigned" };
}
} catch (e) {
return { success: false, message: "Failed to unsign report" };
}
},
async promoteToPm(
userName: string,
projectName: string,
@ -874,4 +934,74 @@ export const api: API = {
}
return { success: true, message: "User promoted to project manager" };
},
async getUsername(id: number, token: string): Promise<APIResponse<string>> {
try {
const response = await fetch(`/api/username?userId=${id}`, {
method: "GET",
headers: {
"Content-Type": "application/json",
Authorization: "Bearer " + token,
},
});
if (!response.ok) {
return { success: false, message: "Failed to get username" };
} else {
const data = (await response.json()) as string;
return { success: true, data };
}
} catch (e) {
return { success: false, message: "Failed to get username" };
}
},
async deleteWeeklyReport(
reportId: number,
token: string,
): Promise<APIResponse<string>> {
try {
const response = await fetch(`/api/deleteReport/${reportId}`, {
method: "DELETE",
headers: {
"Content-Type": "application/json",
Authorization: "Bearer " + token,
},
});
if (!response.ok) {
return { success: false, message: "Failed to delete report" };
} else {
return { success: true, message: "Report deleted" };
}
} catch (e) {
return { success: false, message: "Failed to delete report" };
}
},
async getStatistics(
token: string,
projectName: string,
): Promise<APIResponse<Statistics>> {
try {
const response = await fetch(
`/api/getStatistics/?projectName=${projectName}`,
{
method: "GET",
headers: {
"Content-Type": "application/json",
Authorization: "Bearer " + token,
},
},
);
if (!response.ok) {
return { success: false, message: "Failed to get statistics" };
} else {
const data = (await response.json()) as Statistics;
return { success: true, data };
}
} catch (e) {
return { success: false, message: "Failed to get statistics" };
}
},
};

View file

@ -60,7 +60,7 @@ function AllTimeReportsInProject(): JSX.Element {
</h1>
<h1>
<span className="font-bold">{"Signed: "}</span>
NO
{newWeeklyReport.signedBy ? "YES" : "NO"}
</h1>
</div>
</Link>

View file

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

View file

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

View file

@ -62,7 +62,7 @@ export default function NewWeeklyReport(): JSX.Element {
const success = await handleNewWeeklyReport();
if (!success) {
alert(
"A Time Report for this week already exists, please go to the edit page to edit it or change week number.",
"Error occurred! Your connection to the server might be lost or a time report for this week already exists, please check your connection or go to the edit page to edit your report or change week number.",
);
return;
}

View file

@ -31,8 +31,9 @@ export default function GetOtherUsersReport(): JSX.Element {
projectName ?? "",
fetchedWeek?.toString() ?? "0",
token,
username ?? "",
);
console.log(response);
if (response.success) {
const report: WeeklyReport = response.data ?? {
reportId: 0,
@ -62,25 +63,33 @@ export default function GetOtherUsersReport(): JSX.Element {
void fetchUsersWeeklyReport();
});
const handleSignWeeklyReport = async (): Promise<void> => {
await api.signReport(reportId, token);
const handleSignWeeklyReport = async (): Promise<boolean> => {
const response = await api.signReport(reportId, token);
if (response.success) {
return true;
} else {
return false;
}
};
const navigate = useNavigate();
return (
<>
<h1 className="text-[30px] font-bold">
{" "}
UserId: {username}&apos;s Report
</h1>
<h1 className="text-[30px] font-bold"> {username}&apos;s Report</h1>
<div className="border-4 border-black bg-white flex flex-col justify-start min-h-[65vh] h-fit w-[50vw] rounded-3xl overflow-scroll space-y-[2vh] p-[30px] items-center">
<form
onSubmit={(e) => {
e.preventDefault();
void handleSignWeeklyReport();
alert("Report successfully signed!");
navigate(-1);
void (async (): Promise<void> => {
const success = await handleSignWeeklyReport();
if (!success) {
alert("Failed to sign report!");
return;
}
alert("Report successfully signed!");
navigate(-1);
})();
}}
>
<div className="flex flex-col items-center">

View file

@ -124,6 +124,14 @@ export interface WeeklyReport {
*/
signedBy?: number /* int */;
}
export interface Statistics {
totalDevelopmentTime: number /* int */;
totalMeetingTime: number /* int */;
totalAdminTime: number /* int */;
totalOwnWorkTime: number /* int */;
totalStudyTime: number /* int */;
totalTestingTime: number /* int */;
}
export interface UpdateWeeklyReport {
/**
* The name of the project, as it appears in the database