Merge branch 'BumBranch' into frontend

This commit is contained in:
al8763be 2024-03-17 15:10:19 +01:00
commit 179a5196e9
8 changed files with 449 additions and 127 deletions

View file

@ -1,5 +1,6 @@
import { NewProject, Project } from "../Types/Project"; import { NewProject, Project } from "../Types/Project";
import { NewUser, User } from "../Types/Users"; import { NewUser, User } from "../Types/Users";
import { NewWeeklyReport } from "../Types/goTypes";
// This type of pattern should be hard to misuse // This type of pattern should be hard to misuse
interface APIResponse<T> { interface APIResponse<T> {
@ -20,8 +21,20 @@ interface API {
project: NewProject, project: NewProject,
token: string, token: string,
): Promise<APIResponse<Project>>; ): Promise<APIResponse<Project>>;
/** Submit a weekly report */
submitWeeklyReport(
project: NewWeeklyReport,
token: string,
): Promise<APIResponse<Project>>;
/** Renew the token */ /** Renew the token */
renewToken(token: string): Promise<APIResponse<string>>; renewToken(token: string): Promise<APIResponse<string>>;
/** Gets all the projects of a user*/
getUserProjects(
username: string,
token: string,
): Promise<APIResponse<Project[]>>;
/** Login */
login(NewUser: NewUser): Promise<APIResponse<JSON>>;
} }
// Export an instance of the API // Export an instance of the API
@ -117,4 +130,86 @@ export const api: API = {
return { success: false, message: "Failed to renew token" }; return { success: false, message: "Failed to renew token" };
} }
}, },
async getUserProjects(token: string): Promise<APIResponse<Project[]>> {
try {
const response = await fetch("/api/getUserProjects", {
method: "GET",
headers: {
"Content-Type": "application/json",
Authorization: "Bearer " + token,
},
});
if (!response.ok) {
return Promise.resolve({
success: false,
message: "Failed to get user projects",
});
} else {
const data = (await response.json()) as Project[];
return Promise.resolve({ success: true, data });
}
} catch (e) {
return Promise.resolve({
success: false,
message: "Failed to get user projects",
});
}
},
async submitWeeklyReport(
project: NewWeeklyReport,
token: string,
): Promise<APIResponse<Project>> {
try {
return fetch("/api/submitWeeklyReport", {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: "Bearer " + token,
},
body: JSON.stringify(project),
})
.then((response) => {
if (!response.ok) {
return {
success: false,
message: "Failed to submit weekly report",
};
} else {
return response.json();
}
})
.then((data: Project) => {
return { success: true, data };
});
} catch (e) {
return Promise.resolve({
success: false,
message: "Failed to submit weekly report",
});
}
},
async login(NewUser: NewUser): Promise<APIResponse<JSON>> {
try {
const response = await fetch("/api/login", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(NewUser),
});
if (!response.ok) {
return { success: false, message: "Failed to login" };
} else {
const data = (await response.json()) as JSON;
return { success: true, data };
}
} catch (e) {
return Promise.resolve({ success: false, message: "Failed to login" });
}
},
}; };

View file

@ -4,6 +4,32 @@ import { api } from "../API/API";
import Logo from "../assets/Logo.svg"; import Logo from "../assets/Logo.svg";
import Button from "./Button"; import Button from "./Button";
function InputField(props: {
label: string;
type: string;
value: string;
onChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
}): JSX.Element {
return (
<div className="mb-4">
<label
className="block text-gray-700 text-sm font-sans font-bold mb-2"
htmlFor={props.label}
>
{props.label}
</label>
<input
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}
value={props.value}
onChange={props.onChange}
/>
</div>
);
}
export default function Register(): JSX.Element { export default function Register(): JSX.Element {
const [username, setUsername] = useState(""); const [username, setUsername] = useState("");
const [password, setPassword] = useState(""); const [password, setPassword] = useState("");
@ -14,7 +40,7 @@ export default function Register(): JSX.Element {
}; };
return ( return (
<div className="flex flex-col h-screen w-screen items-center justify-center"> <div className="flex flex-col h-fit w-screen items-center justify-center">
<div className="border-4 border-black bg-white flex flex-col items-center justify-center h-fit w-fit rounded-3xl content-center pl-20 pr-20"> <div className="border-4 border-black bg-white flex flex-col items-center justify-center h-fit w-fit rounded-3xl content-center pl-20 pr-20">
<form <form
className="bg-white rounded px-8 pt-6 pb-8 mb-4 items-center justify-center flex flex-col w-fit h-fit" className="bg-white rounded px-8 pt-6 pb-8 mb-4 items-center justify-center flex flex-col w-fit h-fit"
@ -31,42 +57,22 @@ export default function Register(): JSX.Element {
<h3 className="pb-4 mb-2 text-center font-bold text-[18px]"> <h3 className="pb-4 mb-2 text-center font-bold text-[18px]">
Register New User Register New User
</h3> </h3>
<div className="mb-4"> <InputField
<label label="Username"
className="block text-gray-700 text-sm font-sans font-bold mb-2" type="text"
htmlFor="username" value={username}
> onChange={(e) => {
Username setUsername(e.target.value);
</label> }}
<input />
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" <InputField
id="username" label="Password"
type="text" type="password"
placeholder="Username" value={password}
value={username} onChange={(e) => {
onChange={(e) => { setPassword(e.target.value);
setUsername(e.target.value); }}
}} />
/>
</div>
<div className="mb-6">
<label
className="block text-gray-700 text-sm font-sans font-bold mb-2"
htmlFor="password"
>
Password
</label>
<input
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="password"
type="password"
placeholder="Choose your password"
value={password}
onChange={(e) => {
setPassword(e.target.value);
}}
/>
</div>
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
<Button <Button
text="Register" text="Register"

View file

@ -1,59 +1,203 @@
function NewTimeReport(): JSX.Element { import { useState } from "react";
const activities = [ import { TimeReport } from "../Types/TimeReport";
"Development", import { api } from "../API/API";
"Meeting", import { useNavigate } from "react-router-dom";
"Administration", import Button from "./Button";
"Own Work",
"Studies", export default function NewTimeReport(): JSX.Element {
"Testing", const [week, setWeek] = useState("");
]; const [development, setDevelopment] = useState("0");
const [meeting, setMeeting] = useState("0");
const [administration, setAdministration] = useState("0");
const [ownwork, setOwnWork] = useState("0");
const [studies, setStudies] = useState("0");
const [testing, setTesting] = useState("0");
const handleNewTimeReport = async (): Promise<void> => {
const newTimeReport: TimeReport = {
week,
development,
meeting,
administration,
ownwork,
studies,
testing,
};
await Promise.resolve();
// await api.registerTimeReport(newTimeReport); This needs to be implemented!
};
const navigate = useNavigate();
return ( return (
<> <>
<div className="border-4 border-black bg-white flex flex-col justify-start min-h-[65vh] h-fit w-[50vw] rounded-3xl overflow-scroll space-y-[2vh] p-[30px] items-center"> <div className="border-4 border-black bg-white flex flex-col justify-start min-h-[65vh] h-fit w-[50vw] rounded-3xl overflow-scroll space-y-[2vh] p-[30px] items-center">
<input <form
className="w-fill h-[5vh] font-sans text-[3vh] pl-[1vw] rounded-full text-center pt-[1vh] pb-[1vh] border-2 border-black" onSubmit={(e) => {
type="week" if (week === "") {
placeholder="Week" alert("Please enter a week number");
onKeyDown={(event) => { e.preventDefault();
event.preventDefault(); return;
}
e.preventDefault();
void handleNewTimeReport();
navigate("/project");
}} }}
onPaste={(event) => { >
event.preventDefault(); <div className="flex flex-col items-center">
}} <input
/> className="w-fill h-[5vh] font-sans text-[3vh] pl-[1vw] rounded-full text-center pt-[1vh] pb-[1vh] border-2 border-black"
<table className="w-full text-center divide-y divide-x divide-white text-[30px]"> type="week"
<thead> placeholder="Week"
<tr> onChange={(e) => {
<th className="w-1/2 py-2 border-b-2 border-black">Activity</th> const weekNumber = e.target.value.split("-W")[1];
<th className="w-1/2 py-2 border-b-2 border-black"> setWeek(weekNumber);
Total Time (min) }}
</th> onKeyDown={(event) => {
</tr> event.preventDefault();
</thead> }}
<tbody className="divide-y divide-black"> onPaste={(event) => {
{activities.map((activity, index) => ( event.preventDefault();
<tr key={index} className="h-[10vh]"> }}
<td>{activity}</td> />
<td> <table className="w-full text-center divide-y divide-x divide-white text-[30px]">
<input <thead>
type="number" <tr>
min="0" <th className="w-1/2 py-2 border-b-2 border-black">
className="border-2 border-black rounded-md text-center w-1/2" Activity
onKeyDown={(event) => { </th>
const keyValue = event.key; <th className="w-1/2 py-2 border-b-2 border-black">
if (!/\d/.test(keyValue) && keyValue !== "Backspace") Total Time (min)
event.preventDefault(); </th>
}} </tr>
/> </thead>
</td> <tbody className="divide-y divide-black">
</tr> <tr className="h-[10vh]">
))} <td>Development</td>
</tbody> <td>
</table> <input
type="number"
min="0"
className="border-2 border-black rounded-md text-center w-1/2"
value={development}
onChange={(e) => {
setDevelopment(e.target.value);
}}
onKeyDown={(event) => {
const keyValue = event.key;
if (!/\d/.test(keyValue) && keyValue !== "Backspace")
event.preventDefault();
}}
/>
</td>
</tr>
<tr className="h-[10vh]">
<td>Meeting</td>
<td>
<input
type="number"
min="0"
className="border-2 border-black rounded-md text-center w-1/2"
value={meeting}
onChange={(e) => {
setMeeting(e.target.value);
}}
onKeyDown={(event) => {
const keyValue = event.key;
if (!/\d/.test(keyValue) && keyValue !== "Backspace")
event.preventDefault();
}}
/>
</td>
</tr>
<tr className="h-[10vh]">
<td>Administration</td>
<td>
<input
type="number"
min="0"
className="border-2 border-black rounded-md text-center w-1/2"
value={administration}
onChange={(e) => {
setAdministration(e.target.value);
}}
onKeyDown={(event) => {
const keyValue = event.key;
if (!/\d/.test(keyValue) && keyValue !== "Backspace")
event.preventDefault();
}}
/>
</td>
</tr>
<tr className="h-[10vh]">
<td>Own Work</td>
<td>
<input
type="number"
min="0"
className="border-2 border-black rounded-md text-center w-1/2"
value={ownwork}
onChange={(e) => {
setOwnWork(e.target.value);
}}
onKeyDown={(event) => {
const keyValue = event.key;
if (!/\d/.test(keyValue) && keyValue !== "Backspace")
event.preventDefault();
}}
/>
</td>
</tr>
<tr className="h-[10vh]">
<td>Studies</td>
<td>
<input
type="number"
min="0"
className="border-2 border-black rounded-md text-center w-1/2"
value={studies}
onChange={(e) => {
setStudies(e.target.value);
}}
onKeyDown={(event) => {
const keyValue = event.key;
if (!/\d/.test(keyValue) && keyValue !== "Backspace")
event.preventDefault();
}}
/>
</td>
</tr>
<tr className="h-[10vh]">
<td>Testing</td>
<td>
<input
type="number"
min="0"
className="border-2 border-black rounded-md text-center w-1/2"
value={testing}
onChange={(e) => {
setTesting(e.target.value);
}}
onKeyDown={(event) => {
const keyValue = event.key;
if (!/\d/.test(keyValue) && keyValue !== "Backspace")
event.preventDefault();
}}
/>
</td>
</tr>
</tbody>
</table>
<Button
text="Submit"
onClick={(): void => {
return;
}}
type="submit"
/>
</div>
</form>
</div> </div>
</> </>
); );
} }
export default NewTimeReport;

View file

@ -1,18 +1,16 @@
import BasicWindow from "../../Components/BasicWindow"; import BasicWindow from "../../Components/BasicWindow";
import Button from "../../Components/Button"; import Button from "../../Components/Button";
import Register from "../../Components/Register";
function AdminAddUser(): JSX.Element { function AdminAddUser(): JSX.Element {
const content = <></>; const content = (
<>
<Register />
</>
);
const buttons = ( const buttons = (
<> <>
<Button
text="Finish"
onClick={(): void => {
return;
}}
type="button"
/>
<Button <Button
text="Back" text="Back"
onClick={(): void => { onClick={(): void => {

View file

@ -13,14 +13,15 @@ function UserNewTimeReportPage(): JSX.Element {
const buttons = ( const buttons = (
<> <>
<Button <Link to="/project">
text="Submit" <Button
onClick={(): void => { text="Back"
return; onClick={(): void => {
}} return;
type="button" }}
/> type="button"
<BackButton /> />
</Link>
</> </>
); );

View file

@ -1,13 +0,0 @@
export interface Project {
id: number;
name: string;
description: string;
owner: string;
created: string; // This is a date
}
export interface NewProject {
name: string;
description: string;
owner: string;
}

View file

@ -1,11 +0,0 @@
// This is how the API responds
export interface User {
id: number;
userName: string;
}
// Used to create a new user
export interface NewUser {
userName: string;
password: string;
}

View file

@ -23,6 +23,108 @@ const router = createBrowserRouter([
{ {
path: "/user", path: "/user",
element: <YourProjectsPage />, element: <YourProjectsPage />,
path: "/edit-time-report",
element: <UserEditTimeReportPage />,
},
{
path: "/new-time-report",
element: <UserNewTimeReportPage />,
},
{
path: "/project",
element: <UserProjectPage />,
},
{
path: "/register",
element: <Register />,
},
{
path: "/project-page",
element: <UserViewTimeReportsPage />,
},
{
path: "/change-role",
element: <PMChangeRole />,
},
{
path: "/other-users-time-reports",
element: <PMOtherUsersTR />,
},
{
path: "/project-members",
element: <PMProjectMembers />,
},
{
path: "/PM-project-page",
element: <PMProjectPage />,
},
{
path: "/PM-time-activity",
element: <PMTotalTimeActivity />,
},
{
path: "/PM-time-role",
element: <PMTotalTimeRole />,
},
{
path: "/PM-unsigned-reports",
element: <PMUnsignedReports />,
},
{
path: "/PM-view-unsigned-report",
element: <PMViewUnsignedReport />,
},
{
path: "/admin-add-project",
element: <AdminAddProject />,
},
{
path: "/admin-add-user",
element: <AdminAddUser />,
},
{
path: "/admin-change-username",
element: <AdminChangeUsername />,
},
{
path: "/admin-manage-projects",
element: <AdminManageProjects />,
},
{
path: "/admin-manage-users",
element: <AdminManageUsers />,
},
{
path: "/admin-menu",
element: <AdminMenuPage />,
},
{
path: "/admin-project-add-member",
element: <AdminProjectAddMember />,
},
{
path: "/admin-project-change-user-role",
element: <AdminProjectChangeUserRole />,
},
{
path: "/admin-project-manage-members",
element: <AdminProjectManageMembers />,
},
{
path: "/admin-project-page",
element: <AdminProjectPage />,
},
{
path: "/admin-project-statistics",
element: <AdminProjectStatistics />,
},
{
path: "/admin-project-view-members",
element: <AdminProjectViewMemberInfo />,
},
{
path: "/admin-view-user",
element: <AdminViewUserInfo />,
}, },
]); ]);