Compare commits

..

No commits in common. "741ad50ccfa8cdbdbfe2b034823b294f44594d4b" and "e03727613d25761f526d5a501c8af3bed408e7cb" have entirely different histories.

39 changed files with 453 additions and 1057 deletions

View file

@ -2,7 +2,6 @@ package database
import ( import (
"embed" "embed"
"errors"
"path/filepath" "path/filepath"
"ttime/internal/types" "ttime/internal/types"
@ -31,7 +30,6 @@ type Database interface {
GetProject(projectId int) (types.Project, error) GetProject(projectId int) (types.Project, error)
GetUserRole(username string, projectname string) (string, error) GetUserRole(username string, projectname string) (string, error)
GetWeeklyReport(username string, projectName string, week int) (types.WeeklyReport, error) GetWeeklyReport(username string, projectName string, week int) (types.WeeklyReport, error)
SignWeeklyReport(reportId int, projectManagerId int) error
} }
// This struct is a wrapper type that holds the database connection // This struct is a wrapper type that holds the database connection
@ -272,8 +270,7 @@ func (d *Db) GetWeeklyReport(username string, projectName string, week int) (typ
admin_time, admin_time,
own_work_time, own_work_time,
study_time, study_time,
testing_time, testing_time
signed_by
FROM FROM
weekly_reports weekly_reports
WHERE WHERE
@ -285,34 +282,6 @@ func (d *Db) GetWeeklyReport(username string, projectName string, week int) (typ
return report, err return report, err
} }
// SignWeeklyReport signs a weekly report by updating the signed_by field
// with the provided project manager's ID, but only if the project manager
// is in the same project as the report
func (d *Db) SignWeeklyReport(reportId int, projectManagerId int) error {
// Retrieve the project ID associated with the report
var reportProjectID int
err := d.Get(&reportProjectID, "SELECT project_id FROM weekly_reports WHERE report_id = ?", reportId)
if err != nil {
return err
}
// Retrieve the project ID associated with the project manager
var managerProjectID int
err = d.Get(&managerProjectID, "SELECT project_id FROM user_roles WHERE user_id = ? AND p_role = 'project_manager'", projectManagerId)
if err != nil {
return err
}
// Check if the project manager is in the same project as the report
if reportProjectID != managerProjectID {
return errors.New("project manager doesn't have permission to sign the report")
}
// Update the signed_by field of the specified report
_, err = d.Exec("UPDATE weekly_reports SET signed_by = ? WHERE report_id = ?", projectManagerId, reportId)
return err
}
// Reads a directory of migration files and applies them to the database. // Reads a directory of migration files and applies them to the database.
// This will eventually be used on an embedded directory // This will eventually be used on an embedded directory
func (d *Db) Migrate() error { func (d *Db) Migrate() error {

View file

@ -1,7 +1,6 @@
package database package database
import ( import (
"fmt"
"testing" "testing"
) )
@ -411,128 +410,3 @@ func TestGetWeeklyReport(t *testing.T) {
} }
// Check other fields similarly // Check other fields similarly
} }
func TestSignWeeklyReport(t *testing.T) {
db, err := setupState()
if err != nil {
t.Error("setupState failed:", err)
}
// Add project manager
err = db.AddUser("projectManager", "password")
if err != nil {
t.Error("AddUser failed:", err)
}
// Add a regular user
err = db.AddUser("testuser", "password")
if err != nil {
t.Error("AddUser failed:", err)
}
// Add project
err = db.AddProject("testproject", "description", "projectManager")
if err != nil {
t.Error("AddProject failed:", err)
}
// Add both regular users as members to the project
err = db.AddUserToProject("testuser", "testproject", "member")
if err != nil {
t.Error("AddUserToProject failed:", err)
}
err = db.AddUserToProject("projectManager", "testproject", "project_manager")
if err != nil {
t.Error("AddUserToProject failed:", err)
}
// Add a weekly report for one of the regular users
err = db.AddWeeklyReport("testproject", "testuser", 1, 1, 1, 1, 1, 1, 1)
if err != nil {
t.Error("AddWeeklyReport failed:", err)
}
// Retrieve the added report
report, err := db.GetWeeklyReport("testuser", "testproject", 1)
if err != nil {
t.Error("GetWeeklyReport failed:", err)
}
// Print project manager's ID
projectManagerID, err := db.GetUserId("projectManager")
if err != nil {
t.Error("GetUserId failed:", err)
}
fmt.Println("Project Manager's ID:", projectManagerID)
// Sign the report with the project manager
err = db.SignWeeklyReport(report.ReportId, projectManagerID)
if err != nil {
t.Error("SignWeeklyReport failed:", err)
}
// Retrieve the report again to check if it's signed
signedReport, err := db.GetWeeklyReport("testuser", "testproject", 1)
if err != nil {
t.Error("GetWeeklyReport failed:", err)
}
// Ensure the report is signed by the project manager
if *signedReport.SignedBy != projectManagerID {
t.Errorf("Expected SignedBy to be %d, got %d", projectManagerID, *signedReport.SignedBy)
}
}
func TestSignWeeklyReportByAnotherProjectManager(t *testing.T) {
db, err := setupState()
if err != nil {
t.Error("setupState failed:", err)
}
// Add project manager
err = db.AddUser("projectManager", "password")
if err != nil {
t.Error("AddUser failed:", err)
}
// Add a regular user
err = db.AddUser("testuser", "password")
if err != nil {
t.Error("AddUser failed:", err)
}
// Add project
err = db.AddProject("testproject", "description", "projectManager")
if err != nil {
t.Error("AddProject failed:", err)
}
// Add the regular user as a member to the project
err = db.AddUserToProject("testuser", "testproject", "member")
if err != nil {
t.Error("AddUserToProject failed:", err)
}
// Add a weekly report for the regular user
err = db.AddWeeklyReport("testproject", "testuser", 1, 1, 1, 1, 1, 1, 1)
if err != nil {
t.Error("AddWeeklyReport failed:", err)
}
// Retrieve the added report
report, err := db.GetWeeklyReport("testuser", "testproject", 1)
if err != nil {
t.Error("GetWeeklyReport failed:", err)
}
anotherManagerID, err := db.GetUserId("projectManager")
if err != nil {
t.Error("GetUserId failed:", err)
}
err = db.SignWeeklyReport(report.ReportId, anotherManagerID)
if err == nil {
t.Error("Expected SignWeeklyReport to fail with a project manager who is not in the project, but it didn't")
}
}

View file

@ -16,7 +16,6 @@ type GlobalState interface {
GetUserProjects(c *fiber.Ctx) error // To get all projects GetUserProjects(c *fiber.Ctx) error // To get all projects
SubmitWeeklyReport(c *fiber.Ctx) error SubmitWeeklyReport(c *fiber.Ctx) error
GetWeeklyReport(c *fiber.Ctx) error GetWeeklyReport(c *fiber.Ctx) error
SignReport(c *fiber.Ctx) error
// GetProject(c *fiber.Ctx) error // To get a specific project // GetProject(c *fiber.Ctx) error // To get a specific project
// UpdateProject(c *fiber.Ctx) error // To update a project // UpdateProject(c *fiber.Ctx) error // To update a project
// DeleteProject(c *fiber.Ctx) error // To delete a project // DeleteProject(c *fiber.Ctx) error // To delete a project

View file

@ -37,16 +37,13 @@ func (gs *GState) SubmitWeeklyReport(c *fiber.Ctx) error {
// Handler for retrieving weekly report // Handler for retrieving weekly report
func (gs *GState) GetWeeklyReport(c *fiber.Ctx) error { func (gs *GState) GetWeeklyReport(c *fiber.Ctx) error {
// Extract the necessary parameters from the request // Extract the necessary parameters from the request
println("GetWeeklyReport")
user := c.Locals("user").(*jwt.Token) user := c.Locals("user").(*jwt.Token)
claims := user.Claims.(jwt.MapClaims) claims := user.Claims.(jwt.MapClaims)
username := claims["name"].(string) username := claims["name"].(string)
// Extract project name and week from query parameters // Extract project name and week from query parameters
projectName := c.Query("projectName") projectName := c.Query("projectName")
println(projectName)
week := c.Query("week") week := c.Query("week")
println(week)
// Convert week to integer // Convert week to integer
weekInt, err := strconv.Atoi(week) weekInt, err := strconv.Atoi(week)
@ -63,31 +60,3 @@ func (gs *GState) GetWeeklyReport(c *fiber.Ctx) error {
// Return the retrieved weekly report // Return the retrieved weekly report
return c.JSON(report) return c.JSON(report)
} }
func (gs *GState) SignReport(c *fiber.Ctx) error {
// Extract the necessary parameters from the token
user := c.Locals("user").(*jwt.Token)
claims := user.Claims.(jwt.MapClaims)
managerUsername := claims["name"].(string)
// Extract the report ID and project manager ID from request parameters
reportID, err := strconv.Atoi(c.Params("reportId"))
if err != nil {
return c.Status(400).SendString("Invalid report ID")
}
// Call the database function to get the project manager ID
managerID, err := gs.Db.GetUserId(managerUsername)
if err != nil {
return c.Status(500).SendString("Failed to get project manager ID")
}
// Call the database function to sign the weekly report
err = gs.Db.SignWeeklyReport(reportID, managerID)
if err != nil {
return c.Status(500).SendString("Failed to sign the weekly report: " + err.Error())
}
// Return success response
return c.Status(200).SendString("Weekly report signed successfully")
}

View file

@ -41,6 +41,4 @@ type WeeklyReport struct {
StudyTime int `json:"studyTime" db:"study_time"` StudyTime int `json:"studyTime" db:"study_time"`
// Total time spent on testing // Total time spent on testing
TestingTime int `json:"testingTime" db:"testing_time"` TestingTime int `json:"testingTime" db:"testing_time"`
// The project manager who signed it
SignedBy *int `json:"signedBy" db:"signed_by"`
} }

View file

@ -78,7 +78,6 @@ func main() {
server.Post("/api/loginrenew", gs.LoginRenew) server.Post("/api/loginrenew", gs.LoginRenew)
server.Delete("/api/userdelete/:username", gs.UserDelete) // Perhaps just use POST to avoid headaches server.Delete("/api/userdelete/:username", gs.UserDelete) // Perhaps just use POST to avoid headaches
server.Post("/api/project", gs.CreateProject) server.Post("/api/project", gs.CreateProject)
server.Get("/api/getWeeklyReport", gs.GetWeeklyReport)
// Announce the port we are listening on and start the server // Announce the port we are listening on and start the server
err = server.Listen(fmt.Sprintf(":%d", conf.Port)) err = server.Listen(fmt.Sprintf(":%d", conf.Port))

View file

@ -1,13 +1,8 @@
import { import { NewProject, Project } from "../Types/Project";
NewWeeklyReport, import { NewUser, User } from "../Types/Users";
NewUser,
User,
Project,
NewProject,
} from "../Types/goTypes";
// This type of pattern should be hard to misuse // This type of pattern should be hard to misuse
export interface APIResponse<T> { interface APIResponse<T> {
success: boolean; success: boolean;
message?: string; message?: string;
data?: T; data?: T;
@ -20,32 +15,13 @@ interface API {
registerUser(user: NewUser): Promise<APIResponse<User>>; registerUser(user: NewUser): Promise<APIResponse<User>>;
/** Remove a user */ /** Remove a user */
removeUser(username: string, token: string): Promise<APIResponse<User>>; removeUser(username: string, token: string): Promise<APIResponse<User>>;
/** Login */
login(NewUser: NewUser): Promise<APIResponse<string>>;
/** Renew the token */
renewToken(token: string): Promise<APIResponse<string>>;
/** Create a project */ /** Create a project */
createProject( createProject(
project: NewProject, project: NewProject,
token: string, token: string,
): Promise<APIResponse<Project>>; ): Promise<APIResponse<Project>>;
/** Submit a weekly report */ /** Renew the token */
submitWeeklyReport( renewToken(token: string): Promise<APIResponse<string>>;
project: NewWeeklyReport,
token: string,
): Promise<APIResponse<NewWeeklyReport>>;
/**Gets a weekly report*/
getWeeklyReport(
username: string,
projectName: string,
week: string,
token: string,
): Promise<APIResponse<NewWeeklyReport>>;
/** Gets all the projects of a user*/
getUserProjects(
username: string,
token: string,
): Promise<APIResponse<Project[]>>;
} }
// Export an instance of the API // Export an instance of the API
@ -61,19 +37,13 @@ export const api: API = {
}); });
if (!response.ok) { if (!response.ok) {
return { return { success: false, message: "Failed to register user" };
success: false,
message: "Failed to register user: " + response.status,
};
} else { } else {
// const data = (await response.json()) as User; // The API does not currently return the user const data = (await response.json()) as User;
return { success: true }; return { success: true, data };
} }
} catch (e) { } catch (e) {
return { return { success: false, message: "Failed to register user" };
success: false,
message: "Unknown error while registering user",
};
} }
}, },
@ -147,110 +117,4 @@ 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(
weeklyReport: NewWeeklyReport,
token: string,
): Promise<APIResponse<NewWeeklyReport>> {
try {
const response = await fetch("/api/submitWeeklyReport", {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: "Bearer " + token,
},
body: JSON.stringify(weeklyReport),
});
if (!response.ok) {
return {
success: false,
message: "Failed to submit weekly report",
};
}
const data = (await response.json()) as NewWeeklyReport;
return { success: true, data };
} catch (e) {
return {
success: false,
message: "Failed to submit weekly report",
};
}
},
async getWeeklyReport(
username: string,
projectName: string,
week: string,
token: string,
): Promise<APIResponse<NewWeeklyReport>> {
try {
const response = await fetch("/api/getWeeklyReport", {
method: "GET",
headers: {
"Content-Type": "application/json",
Authorization: "Bearer " + token,
},
body: JSON.stringify({ username, projectName, week }),
});
if (!response.ok) {
return { success: false, message: "Failed to get weekly report" };
} else {
const data = (await response.json()) as NewWeeklyReport;
return { success: true, data };
}
} catch (e) {
return { success: false, message: "Failed to get weekly report" };
}
},
async login(NewUser: NewUser): Promise<APIResponse<string>> {
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 { token: string }; // Update the type of 'data'
return { success: true, data: data.token };
}
} catch (e) {
return Promise.resolve({ success: false, message: "Failed to login" });
}
},
}; };

View file

@ -1,18 +0,0 @@
import { useNavigate } from "react-router-dom";
function BackButton(): JSX.Element {
const navigate = useNavigate();
const goBack = (): void => {
navigate(-1);
};
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;"
>
Back
</button>
);
}
export default BackButton;

View file

@ -1,24 +0,0 @@
import { useEffect } from "react";
const BackgroundAnimation = (): JSX.Element => {
useEffect(() => {
const images = [
"src/assets/1.jpg",
"src/assets/2.jpg",
"src/assets/3.jpg",
"src/assets/4.jpg",
];
// Pre-load images
for (const i of images) {
console.log(i);
}
// Start animation
document.body.style.animation = "backgroundTransition 30s infinite";
}, []);
return <></>;
};
export default BackgroundAnimation;

View file

@ -1,41 +0,0 @@
/**
* A customizable input field
* @param props - Settings for the field
* @returns {JSX.Element} The input field
* @example
* <InputField
* type="text"
* label="Example"
* onChange={(e) => {
* setExample(e.target.value);
* }}
* value={example}
* />
*/
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 InputField;

View file

@ -1,55 +0,0 @@
import { NewUser } from "../Types/goTypes";
import { api, APIResponse } from "../API/API";
import { Dispatch, SetStateAction } from "react";
/*
* Checks if user is in database with api.login and then sets proper authority level
* TODO: change so that it checks for user type (admin, user, pm) somehow instead
**/
function LoginCheck(props: {
username: string;
password: string;
setAuthority: Dispatch<SetStateAction<number>>;
}): number {
const user: NewUser = {
username: props.username,
password: props.password,
};
api
.login(user)
.then((response: APIResponse<string>) => {
if (response.success) {
if (response.data !== undefined) {
const token = response.data;
//TODO: change so that it checks for user type (admin, user, pm) instead
if (token !== "" && props.username === "admin") {
props.setAuthority((prevAuth) => {
prevAuth = 1;
return prevAuth;
});
} else if (token !== "" && props.username === "pm") {
props.setAuthority((prevAuth) => {
prevAuth = 2;
return prevAuth;
});
} else if (token !== "" && props.username === "user") {
props.setAuthority((prevAuth) => {
prevAuth = 3;
return prevAuth;
});
}
} else {
console.error("Token was undefined");
}
} else {
console.error("Token could not be fetched");
}
})
.catch((error) => {
console.error("An error occurred during login:", error);
});
return 0;
}
export default LoginCheck;

View file

@ -1,55 +0,0 @@
import { Dispatch, FormEventHandler, SetStateAction } from "react";
import Button from "./Button";
import InputField from "./InputField";
/**
* A login field complete with input fields
* and a button for submitting the information
* @param props - Settings
* @returns {JSX.Element} A login component
* @example
* <Login
* handleSubmit={handleSubmit}
* setUsername={setUsername}
* setPassword={setPassword}
* username={username}
* password={password}
* />
*/
function Login(props: {
handleSubmit: FormEventHandler<HTMLFormElement>;
setUsername: Dispatch<SetStateAction<string>>;
setPassword: Dispatch<SetStateAction<string>>;
username: string;
password: string;
}): JSX.Element {
return (
<form className="flex flex-col items-center" onSubmit={props.handleSubmit}>
<InputField
type="text"
label="Username"
onChange={(e) => {
props.setUsername(e.target.value);
}}
value={props.username}
/>
<InputField
type="password"
label="Password"
onChange={(e) => {
props.setPassword(e.target.value);
}}
value={props.password}
/>
<Button
text="Login"
onClick={(): void => {
return;
}}
type={"submit"}
/>
</form>
);
}
export default Login;

View file

@ -1,35 +0,0 @@
import { Link } from "react-router-dom";
import { Project } from "../Types/goTypes";
/**
* The props for the ProjectsProps component
*/
interface ProjectProps {
projects: Project[];
}
/**
* A list of projects for users, that links the user to the right project page
* thanks to the state property
* @param props - The projects to display
* @returns {JSX.Element} The project list
* @example
* const projects = [{ id: 1, name: "Random name" }];
* return <ProjectList projects={projects} />;
*/
export function ProjectListUser(props: ProjectProps): JSX.Element {
return (
<div>
<ul className="font-bold underline text-[30px] cursor-pointer">
{props.projects.map((project) => (
<Link to="/project" key={project.id} state={project.name}>
<li className="pt-5" key={project.id}>
{project.name}
</li>
</Link>
))}
</ul>
</div>
);
}

View file

@ -1,58 +1,20 @@
import { useState } from "react"; import { useState } from "react";
import { NewUser } from "../Types/goTypes"; import { NewUser } from "../Types/Users";
import { api } from "../API/API"; import { api } from "../API/API";
import Logo from "../assets/Logo.svg"; import Logo from "../assets/Logo.svg";
import Button from "./Button"; import Button from "./Button";
import { useNavigate } from "react-router-dom";
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<string>(); const [username, setUsername] = useState("");
const [password, setPassword] = useState<string>(); const [password, setPassword] = useState("");
const [errMessage, setErrMessage] = useState<string>();
const nav = useNavigate();
const handleRegister = async (): Promise<void> => { const handleRegister = async (): Promise<void> => {
const newUser: NewUser = { const newUser: NewUser = { userName: username, password };
username: username ?? "", await api.registerUser(newUser); // TODO: Handle errors
password: password ?? "",
};
const response = await api.registerUser(newUser);
if (response.success) {
nav("/"); // Instantly navigate to the login page
} else {
setErrMessage(response.message ?? "Unknown error");
}
}; };
return ( return (
<div className="flex flex-col h-fit w-screen items-center justify-center"> <div className="flex flex-col h-screen 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"
@ -69,22 +31,6 @@ 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>
<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 className="mb-4"> <div className="mb-4">
<label <label
className="block text-gray-700 text-sm font-sans font-bold mb-2" className="block text-gray-700 text-sm font-sans font-bold mb-2"
@ -121,7 +67,6 @@ export default function Register(): JSX.Element {
}} }}
/> />
</div> </div>
{errMessage && <p className="text-red-500 text-xs">{errMessage}</p>}
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
<Button <Button
text="Register" text="Register"

View file

@ -1,207 +1,59 @@
import { useState } from "react"; function NewTimeReport(): JSX.Element {
import { api } from "../API/API"; const activities = [
import { useNavigate } from "react-router-dom"; "Development",
import Button from "./Button"; "Meeting",
import { NewWeeklyReport } from "../Types/goTypes"; "Administration",
"Own Work",
export default function NewTimeReport(): JSX.Element { "Studies",
const [projectName, setProjectName] = useState<string>("projectName"); // TODO: Get from backend "Testing",
const [week, setWeek] = useState<number>(NaN); ];
const [development, setDevelopment] = useState<number>(NaN);
const [meeting, setMeeting] = useState<number>(NaN);
const [administration, setAdministration] = useState<number>(NaN);
const [ownwork, setOwnWork] = useState<number>(NaN);
const [studies, setStudies] = useState<number>(NaN);
const [testing, setTesting] = useState<number>(NaN);
const handleNewTimeReport = async (): Promise<void> => {
const newTimeReport: NewWeeklyReport = {
projectName,
week,
developmentTime: development,
meetingTime: meeting,
adminTime: administration,
ownWorkTime: ownwork,
studyTime: studies,
testingTime: testing,
};
await Promise.resolve();
await api.submitWeeklyReport(newTimeReport, "token");
};
const navigate = useNavigate();
setProjectName("Something Reasonable"); // This should obviously not be used here
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">
<form <input
onSubmit={(e) => { className="w-fill h-[5vh] font-sans text-[3vh] pl-[1vw] rounded-full text-center pt-[1vh] pb-[1vh] border-2 border-black"
if (!week) { type="week"
alert("Please enter a week number"); placeholder="Week"
e.preventDefault(); onKeyDown={(event) => {
return; event.preventDefault();
}
e.preventDefault();
void handleNewTimeReport();
navigate("/project");
}} }}
> onPaste={(event) => {
<div className="flex flex-col items-center"> event.preventDefault();
<input }}
className="w-fill h-[5vh] font-sans text-[3vh] pl-[1vw] rounded-full text-center pt-[1vh] pb-[1vh] border-2 border-black" />
type="week" <table className="w-full text-center divide-y divide-x divide-white text-[30px]">
placeholder="Week" <thead>
onChange={(e) => { <tr>
const weekNumber = parseInt(e.target.value.split("-W")[1]); <th className="w-1/2 py-2 border-b-2 border-black">Activity</th>
setWeek(weekNumber); <th className="w-1/2 py-2 border-b-2 border-black">
}} Total Time (min)
onKeyDown={(event) => { </th>
event.preventDefault(); </tr>
}} </thead>
onPaste={(event) => { <tbody className="divide-y divide-black">
event.preventDefault(); {activities.map((activity, index) => (
}} <tr key={index} className="h-[10vh]">
/> <td>{activity}</td>
<table className="w-full text-center divide-y divide-x divide-white text-[30px]"> <td>
<thead> <input
<tr> type="number"
<th className="w-1/2 py-2 border-b-2 border-black"> min="0"
Activity className="border-2 border-black rounded-md text-center w-1/2"
</th> onKeyDown={(event) => {
<th className="w-1/2 py-2 border-b-2 border-black"> const keyValue = event.key;
Total Time (min) if (!/\d/.test(keyValue) && keyValue !== "Backspace")
</th> event.preventDefault();
</tr> }}
</thead> />
<tbody className="divide-y divide-black"> </td>
<tr className="h-[10vh]"> </tr>
<td>Development</td> ))}
<td> </tbody>
<input </table>
type="number"
min="0"
className="border-2 border-black rounded-md text-center w-1/2"
value={development}
onChange={(e) => {
setDevelopment(parseInt(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(parseInt(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(parseInt(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(parseInt(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(parseInt(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(parseInt(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,35 +0,0 @@
import { Link } from "react-router-dom";
import { User } from "../Types/goTypes";
/**
* The props for the UserProps component
*/
interface UserProps {
users: User[];
}
/**
* A list of users for admin manage users page, that links admin to the right user page
* thanks to the state property
* @param props - The users to display
* @returns {JSX.Element} The user list
* @example
* const users = [{ id: 1, userName: "Random name" }];
* return <UserList users={users} />;
*/
export function UserListAdmin(props: UserProps): JSX.Element {
return (
<div>
<ul className="font-bold underline text-[30px] cursor-pointer padding">
{props.users.map((user) => (
<Link to="/admin-view-user" key={user.userId} state={user.username}>
<li className="pt-5" key={user.userId}>
{user.username}
</li>
</Link>
))}
</ul>
</div>
);
}

View file

@ -1,16 +1,18 @@
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

@ -1,38 +1,25 @@
import BasicWindow from "../../Components/BasicWindow"; import BasicWindow from "../../Components/BasicWindow";
import Button from "../../Components/Button"; import Button from "../../Components/Button";
import BackButton from "../../Components/BackButton";
import { UserListAdmin } from "../../Components/UserListAdmin";
import { User } from "../../Types/Users";
import { useNavigate } from "react-router-dom";
function AdminManageUsers(): JSX.Element { function AdminManageUsers(): JSX.Element {
//TODO: Change so that it reads users from database const content = <></>;
const users: User[] = [];
for (let i = 1; i <= 20; i++) {
users.push({ id: i, userName: "Example User " + i });
}
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 buttons = ( const buttons = (
<> <>
<Button <Button
text="Add User" text="Add User"
onClick={(): void => { onClick={(): void => {
navigate("/admin-add-user"); return;
}}
type="button"
/>
<Button
text="Back"
onClick={(): void => {
return;
}} }}
type="button" type="button"
/> />
<BackButton />
</> </>
); );

View file

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

View file

@ -1,17 +1,8 @@
import { useLocation } from "react-router-dom";
import BasicWindow from "../../Components/BasicWindow"; import BasicWindow from "../../Components/BasicWindow";
import Button from "../../Components/Button"; import Button from "../../Components/Button";
import BackButton from "../../Components/BackButton";
function AdminViewUserInfo(): JSX.Element { function AdminViewUserInfo(): JSX.Element {
const content = ( const content = <></>;
<>
<h1 className="font-bold text-[30px] mb-[20px]">{useLocation().state}</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]">
<p>Put relevant info on user from database here</p>
</div>
</>
);
const buttons = ( const buttons = (
<> <>
@ -22,7 +13,13 @@ function AdminViewUserInfo(): JSX.Element {
}} }}
type="button" type="button"
/> />
<BackButton /> <Button
text="Back"
onClick={(): void => {
return;
}}
type="button"
/>
</> </>
); );

View file

@ -1,20 +0,0 @@
import { useState } from "react";
import LoginPage from "./LoginPage";
import { useNavigate } from "react-router-dom";
function App(): JSX.Element {
const navigate = useNavigate();
const [authority, setAuthority] = useState(0);
if (authority === 1) {
navigate("/admin");
} else if (authority === 2) {
navigate("/pm");
} else if (authority === 3) {
navigate("/user");
}
return <LoginPage setAuthority={setAuthority} />;
}
export default App;

View file

@ -1,32 +1,36 @@
import Button from "../Components/Button";
import Logo from "/src/assets/Logo.svg"; import Logo from "/src/assets/Logo.svg";
import "./LoginPage.css"; import "./LoginPage.css";
import { Dispatch, FormEvent, SetStateAction, useState } from "react"; import { useEffect } from "react";
import BackgroundAnimation from "../Components/BackgroundAnimation"; import { Link } from "react-router-dom";
import LoginField from "../Components/LoginField";
import LoginCheck from "../Components/LoginCheck";
function LoginPage(props: { const PreloadBackgroundAnimation = (): JSX.Element => {
setAuthority: Dispatch<SetStateAction<number>>; useEffect(() => {
}): JSX.Element { const images = [
const [username, setUsername] = useState(""); "src/assets/1.jpg",
const [password, setPassword] = useState(""); "src/assets/2.jpg",
"src/assets/3.jpg",
"src/assets/4.jpg",
];
/* On submit (enter or button click) check if username and password match any user // Pre-load images
and if so, redirect to correct page */ for (const i of images) {
function handleSubmit(event: FormEvent<HTMLFormElement>): void { console.log(i);
event.preventDefault(); }
LoginCheck({
username: username,
password: password,
setAuthority: props.setAuthority,
});
}
// Start animation
document.body.style.animation = "backgroundTransition 30s infinite";
}, []);
return <></>;
};
function LoginPage(): JSX.Element {
return ( return (
<> <>
<BackgroundAnimation /> <PreloadBackgroundAnimation />
<div <div
className="flex flex-col h-screen items-center justify-center bg-cover bg-fixed" className="flex flex-col h-screen w-screen items-center justify-center"
style={{ style={{
animation: "backgroundTransition 30s infinite", animation: "backgroundTransition 30s infinite",
backgroundSize: "cover", backgroundSize: "cover",
@ -47,13 +51,34 @@ function LoginPage(props: {
{" "} {" "}
Please log in to continue{" "} Please log in to continue{" "}
</h2> </h2>
<LoginField <input
handleSubmit={handleSubmit} className="border-2 border-black mb-3 rounded-lg w-[20vw] p-1"
setUsername={setUsername} type="text"
setPassword={setPassword} placeholder="Username"
username={username}
password={password}
/> />
<input
className="border-2 border-black mb-3 rounded-lg w-[20vw] p-1"
type="password"
placeholder="Password"
/>
<Link to="/your-projects">
<Button
text="Login"
onClick={(): void => {
return;
}}
type="button"
/>
</Link>
<Link to="/register">
<Button
text="Register new user"
onClick={(): void => {
return;
}}
type="button"
/>
</Link>
</div> </div>
</div> </div>
</> </>

View file

@ -1,6 +1,5 @@
import BasicWindow from "../../Components/BasicWindow"; import BasicWindow from "../../Components/BasicWindow";
import Button from "../../Components/Button"; import Button from "../../Components/Button";
import BackButton from "../../Components/BackButton";
function ChangeRole(): JSX.Element { function ChangeRole(): JSX.Element {
const content = <></>; const content = <></>;
@ -14,7 +13,13 @@ function ChangeRole(): JSX.Element {
}} }}
type="button" type="button"
/> />
<BackButton /> <Button
text="Back"
onClick={(): void => {
return;
}}
type="button"
/>
</> </>
); );

View file

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

View file

@ -1,32 +1,32 @@
import BasicWindow from "../../Components/BasicWindow"; import BasicWindow from "../../Components/BasicWindow";
import Button from "../../Components/Button"; import Button from "../../Components/Button";
import BackButton from "../../Components/BackButton";
import { Link } from "react-router-dom";
function PMProjectMembers(): JSX.Element { function PMProjectMembers(): JSX.Element {
const content = <></>; const content = <></>;
const buttons = ( const buttons = (
<> <>
<Link to="/PM-time-activity"> <Button
<Button text="Time / Activity"
text="Time / Activity" onClick={(): void => {
onClick={(): void => { return;
return; }}
}} type="button"
type={"button"} />
/> <Button
</Link> text="Time / Role"
<Link to="/PM-time-role"> onClick={(): void => {
<Button return;
text="Time / Role" }}
onClick={(): void => { type="button"
return; />
}} <Button
type={"button"} text="Back"
/> onClick={(): void => {
</Link> return;
<BackButton /> }}
type="button"
/>
</> </>
); );

View file

@ -1,36 +1,39 @@
import { Link } from "react-router-dom";
import BasicWindow from "../../Components/BasicWindow"; import BasicWindow from "../../Components/BasicWindow";
import { JSX } from "react/jsx-runtime"; import Button from "../../Components/Button";
function PMProjectPage(): JSX.Element { function PMProjectPage(): JSX.Element {
const content = ( const content = (
<> <>
<h1 className="font-bold text-[30px] mb-[20px]">ProjectNameExample</h1> <h1 className="font-bold text-[30px] mb-[20px]">ProjectNameExample</h1>
<div className="border-4 border-black bg-white flex flex-col items-center justify-center min-h-[65vh] h-fit w-[50vw] rounded-3xl content-center overflow-scroll space-y-[5vh] p-[30px]"> <div className="border-4 border-black bg-white flex flex-col items-center justify-center min-h-[65vh] h-fit w-[50vw] rounded-3xl content-center overflow-scroll space-y-[5vh] p-[30px]">
<Link to="/project-page"> <h1 className="font-bold underline text-[30px] cursor-pointer">
<h1 className="font-bold underline text-[30px] cursor-pointer"> Your Time Reports
Your Time Reports </h1>
</h1> <h1 className="font-bold underline text-[30px] cursor-pointer">
</Link> New Time Report
<Link to="/new-time-report"> </h1>
<h1 className="font-bold underline text-[30px] cursor-pointer"> <h1 className="font-bold underline text-[30px] cursor-pointer">
New Time Report Statistics
</h1> </h1>
</Link> <h1 className="font-bold underline text-[30px] cursor-pointer">
<Link to="/project-members"> Unsigned Time Reports
<h1 className="font-bold underline text-[30px] cursor-pointer"> </h1>
Statistics
</h1>
</Link>
<Link to="/PM-unsigned-reports">
<h1 className="font-bold underline text-[30px] cursor-pointer">
Unsigned Time Reports
</h1>
</Link>
</div> </div>
</> </>
); );
return <BasicWindow username="Admin" content={content} buttons={undefined} />; const buttons = (
<>
<Button
text="Back"
onClick={(): void => {
return;
}}
type="button"
/>
</>
);
return <BasicWindow username="Admin" content={content} buttons={buttons} />;
} }
export default PMProjectPage; export default PMProjectPage;

View file

@ -1,6 +1,6 @@
import BasicWindow from "../../Components/BasicWindow"; import BasicWindow from "../../Components/BasicWindow";
import Button from "../../Components/Button";
import TimeReport from "../../Components/TimeReport"; import TimeReport from "../../Components/TimeReport";
import BackButton from "../../Components/BackButton";
function PMTotalTimeActivity(): JSX.Element { function PMTotalTimeActivity(): JSX.Element {
const content = ( const content = (
@ -14,7 +14,13 @@ function PMTotalTimeActivity(): JSX.Element {
const buttons = ( const buttons = (
<> <>
<BackButton /> <Button
text="Back"
onClick={(): void => {
return;
}}
type="button"
/>
</> </>
); );

View file

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

View file

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

View file

@ -1,7 +1,6 @@
import BasicWindow from "../../Components/BasicWindow"; import BasicWindow from "../../Components/BasicWindow";
import Button from "../../Components/Button"; import Button from "../../Components/Button";
import TimeReport from "../../Components/TimeReport"; import TimeReport from "../../Components/TimeReport";
import BackButton from "../../Components/BackButton";
function PMViewUnsignedReport(): JSX.Element { function PMViewUnsignedReport(): JSX.Element {
const content = ( const content = (
@ -29,7 +28,13 @@ function PMViewUnsignedReport(): JSX.Element {
}} }}
type="button" type="button"
/> />
<BackButton /> <Button
text="Back"
onClick={(): void => {
return;
}}
type="button"
/>
</> </>
); );

View file

@ -1,7 +1,6 @@
import BasicWindow from "../../Components/BasicWindow"; import BasicWindow from "../../Components/BasicWindow";
import Button from "../../Components/Button"; import Button from "../../Components/Button";
import NewTimeReport from "../../Components/TimeReport"; import NewTimeReport from "../../Components/TimeReport";
import BackButton from "../../Components/BackButton";
function UserEditTimeReportPage(): JSX.Element { function UserEditTimeReportPage(): JSX.Element {
const content = ( const content = (
@ -20,7 +19,13 @@ function UserEditTimeReportPage(): JSX.Element {
}} }}
type="button" type="button"
/> />
<BackButton /> <Button
text="Back"
onClick={(): void => {
return;
}}
type="button"
/>
</> </>
); );

View file

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

View file

@ -1,17 +1,15 @@
import { Link, useLocation } from "react-router-dom"; import { Link } from "react-router-dom";
import BasicWindow from "../../Components/BasicWindow"; import BasicWindow from "../../Components/BasicWindow";
import BackButton from "../../Components/BackButton"; import Button from "../../Components/Button";
function UserProjectPage(): JSX.Element { function UserProjectPage(): JSX.Element {
const content = ( const content = (
<> <>
<h1 className="font-bold text-[30px] mb-[20px]">{useLocation().state}</h1> <h1 className="font-bold text-[30px] mb-[20px]">ProjectNameExample</h1>
<div className="border-4 border-black bg-white flex flex-col items-center justify-center min-h-[65vh] h-fit w-[50vw] rounded-3xl content-center overflow-scroll space-y-[10vh] p-[30px]"> <div className="border-4 border-black bg-white flex flex-col items-center justify-center min-h-[65vh] h-fit w-[50vw] rounded-3xl content-center overflow-scroll space-y-[10vh] p-[30px]">
<Link to="/project-page"> <h1 className="font-bold underline text-[30px] cursor-pointer">
<h1 className="font-bold underline text-[30px] cursor-pointer"> Your Time Reports
Your Time Reports </h1>
</h1>
</Link>
<Link to="/new-time-report"> <Link to="/new-time-report">
<h1 className="font-bold underline text-[30px] cursor-pointer"> <h1 className="font-bold underline text-[30px] cursor-pointer">
New Time Report New Time Report
@ -23,7 +21,15 @@ function UserProjectPage(): JSX.Element {
const buttons = ( const buttons = (
<> <>
<BackButton /> <Link to="/your-projects">
<Button
text="Back"
onClick={(): void => {
return;
}}
type="button"
/>
</Link>
</> </>
); );

View file

@ -1,17 +1,18 @@
import BasicWindow from "../../Components/BasicWindow"; import BasicWindow from "../../Components/BasicWindow";
import BackButton from "../../Components/BackButton"; import Button from "../../Components/Button";
function UserViewTimeReportsPage(): JSX.Element { function UserViewTimeReportsPage(): JSX.Element {
const content = ( const content = <></>;
<>
<h1 className="font-bold text-[30px] mb-[20px]">Your Time Reports</h1>
{/* Här kan du inkludera logiken för att visa användarens tidrapporter */}
</>
);
const buttons = ( const buttons = (
<> <>
<BackButton /> <Button
text="Back"
onClick={(): void => {
return;
}}
type="button"
/>
</> </>
); );

View file

@ -1,25 +1,25 @@
import { Link } from "react-router-dom";
import BasicWindow from "../Components/BasicWindow"; import BasicWindow from "../Components/BasicWindow";
import { ProjectListUser } from "../Components/ProjectListUser";
import { Project } from "../Types/Project";
function YourProjectsPage(): JSX.Element { function YourProjectsPage(): JSX.Element {
//TODO: Change so that it reads projects from database
const projects: Project[] = [];
for (let i = 1; i <= 20; i++) {
projects.push({
id: i,
name: "Example Project " + i,
description: "good",
created: "now",
owner: "me",
});
}
const content = ( const content = (
<> <>
<h1 className="font-bold text-[30px] mb-[20px]">Your Projects</h1> <h1 className="font-bold text-[30px] mb-[20px]">Your 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]"> <div className="border-4 border-black bg-white flex flex-col items-center justify-between min-h-[65vh] h-fit w-[50vw] rounded-3xl content-center overflow-scroll space-y-[10px] p-[30px]">
<ProjectListUser projects={projects} /> <Link to="/project">
<h1 className="underline text-[24px] cursor-pointer font-bold">
ProjectNameExample
</h1>
</Link>
<h1 className="underline text-[24px] cursor-pointer font-bold">
ProjectNameExample2
</h1>
<h1 className="underline text-[24px] cursor-pointer font-bold">
ProjectNameExample3
</h1>
<h1 className="underline text-[24px] cursor-pointer font-bold">
ProjectNameExample4
</h1>
</div> </div>
</> </>
); );

View file

@ -0,0 +1,13 @@
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

@ -0,0 +1,11 @@
// 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

@ -2,23 +2,152 @@ import React from "react";
import ReactDOM from "react-dom/client"; import ReactDOM from "react-dom/client";
import "./index.css"; import "./index.css";
import { createBrowserRouter, RouterProvider } from "react-router-dom"; import { createBrowserRouter, RouterProvider } from "react-router-dom";
import App from "./Pages/App"; import LoginPage from "./Pages/LoginPage.tsx";
import AdminMenuPage from "./Pages/AdminPages/AdminMenuPage"; import YourProjectsPage from "./Pages/YourProjectsPage.tsx";
import YourProjectsPage from "./Pages/YourProjectsPage"; import UserProjectPage from "./Pages/UserPages/UserProjectPage.tsx";
import Register from "./Components/Register.tsx";
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 PMChangeRole from "./Pages/ProjectManagerPages/PMChangeRole.tsx";
import PMOtherUsersTR from "./Pages/ProjectManagerPages/PMOtherUsersTR.tsx";
import PMProjectMembers from "./Pages/ProjectManagerPages/PMProjectMembers.tsx";
import PMProjectPage from "./Pages/ProjectManagerPages/PMProjectPage.tsx";
import PMTotalTimeActivity from "./Pages/ProjectManagerPages/PMTotalTimeActivity.tsx";
import PMTotalTimeRole from "./Pages/ProjectManagerPages/PMTotalTimeRole.tsx";
import PMUnsignedReports from "./Pages/ProjectManagerPages/PMUnsignedReports.tsx";
import PMViewUnsignedReport from "./Pages/ProjectManagerPages/PMViewUnsignedReport.tsx";
import AdminManageUsers from "./Pages/AdminPages/AdminManageUsers.tsx";
import AdminViewUserInfo from "./Pages/AdminPages/AdminViewUserInfo.tsx";
import AdminManageProjects from "./Pages/AdminPages/AdminManageProjects.tsx";
import AdminAddProject from "./Pages/AdminPages/AdminAddProject.tsx";
import AdminAddUser from "./Pages/AdminPages/AdminAddUser.tsx";
import AdminChangeUsername from "./Pages/AdminPages/AdminChangeUsername.tsx";
import AdminProjectAddMember from "./Pages/AdminPages/AdminProjectAddMember.tsx";
import AdminProjectChangeUserRole from "./Pages/AdminPages/AdminProjectChangeUserRole.tsx";
import AdminProjectManageMembers from "./Pages/AdminPages/AdminProjectManageMembers.tsx";
import AdminProjectStatistics from "./Pages/AdminPages/AdminProjectStatistics.tsx";
import AdminProjectViewMemberInfo from "./Pages/AdminPages/AdminProjectViewMemberInfo.tsx";
import AdminProjectPage from "./Pages/AdminPages/AdminProjectPage.tsx";
// This is where the routes are mounted // This is where the routes are mounted
const router = createBrowserRouter([ const router = createBrowserRouter([
{ {
path: "/", path: "/",
element: <App />, element: <LoginPage />,
}, },
{ {
path: "/admin", path: "/your-projects",
element: <YourProjectsPage />,
},
{
path: "/edit-time-report",
element: <UserEditTimeReportPage />,
},
{
path: "/new-time-report",
element: <UserNewTimeReportPage />,
},
{
path: "/project",
element: <UserProjectPage />,
},
{
path: "/register",
element: <Register />,
},
{
path: "/admin-menu",
element: <AdminMenuPage />, element: <AdminMenuPage />,
}, },
{ {
path: "/pm", path: "/project-page",
element: <YourProjectsPage />, 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 />,
}, },
]); ]);

View file

@ -21,7 +21,6 @@ registerPath = base_url + "/api/register"
loginPath = base_url + "/api/login" loginPath = base_url + "/api/login"
addProjectPath = base_url + "/api/project" addProjectPath = base_url + "/api/project"
submitReportPath = base_url + "/api/submitReport" submitReportPath = base_url + "/api/submitReport"
getWeeklyReportPath = base_url + "/api/getWeeklyReport"
# Posts the username and password to the register endpoint # Posts the username and password to the register endpoint
@ -75,7 +74,7 @@ def test_submit_report():
response = requests.post( response = requests.post(
submitReportPath, submitReportPath,
json={ json={
"projectName": projectName, "projectName": "report1",
"week": 1, "week": 1,
"developmentTime": 10, "developmentTime": 10,
"meetingTime": 5, "meetingTime": 5,
@ -90,18 +89,9 @@ def test_submit_report():
assert response.status_code == 200, "Submit report failed" assert response.status_code == 200, "Submit report failed"
print("Submit report successful") print("Submit report successful")
def test_get_weekly_report():
token = login(username, "always_same").json()["token"]
response = requests.get(
getWeeklyReportPath,
headers={"Authorization": "Bearer " + token},
params={"username": username, "projectName": projectName , "week": 1}
)
print(response.text)
if __name__ == "__main__": if __name__ == "__main__":
test_create_user() test_create_user()
test_login() test_login()
test_add_project() test_add_project()
test_submit_report() test_submit_report()
test_get_weekly_report()