added tests for backend, router dom to frontend

This commit is contained in:
QkoSad
2024-12-03 16:09:25 +02:00
parent 8e4317abde
commit cb7b3ad94c
1142 changed files with 3599 additions and 573520 deletions
+69 -1
View File
@@ -13,7 +13,8 @@
"axios": "^1.7.7",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-google-charts": "^5.2.1"
"react-google-charts": "^5.2.1",
"react-router-dom": "^7.0.1"
},
"devDependencies": {
"@eslint/js": "^9.13.0",
@@ -1671,6 +1672,12 @@
"@babel/types": "^7.20.7"
}
},
"node_modules/@types/cookie": {
"version": "0.6.0",
"resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.6.0.tgz",
"integrity": "sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==",
"license": "MIT"
},
"node_modules/@types/estree": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz",
@@ -2249,6 +2256,15 @@
"dev": true,
"license": "MIT"
},
"node_modules/cookie": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/cookie/-/cookie-1.0.2.tgz",
"integrity": "sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA==",
"license": "MIT",
"engines": {
"node": ">=18"
}
},
"node_modules/cosmiconfig": {
"version": "7.1.0",
"resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.1.0.tgz",
@@ -3481,6 +3497,46 @@
"node": ">=0.10.0"
}
},
"node_modules/react-router": {
"version": "7.0.1",
"resolved": "https://registry.npmjs.org/react-router/-/react-router-7.0.1.tgz",
"integrity": "sha512-WVAhv9oWCNsja5AkK6KLpXJDSJCQizOIyOd4vvB/+eHGbYx5vkhcmcmwWjQ9yqkRClogi+xjEg9fNEOd5EX/tw==",
"license": "MIT",
"dependencies": {
"@types/cookie": "^0.6.0",
"cookie": "^1.0.1",
"set-cookie-parser": "^2.6.0",
"turbo-stream": "2.4.0"
},
"engines": {
"node": ">=20.0.0"
},
"peerDependencies": {
"react": ">=18",
"react-dom": ">=18"
},
"peerDependenciesMeta": {
"react-dom": {
"optional": true
}
}
},
"node_modules/react-router-dom": {
"version": "7.0.1",
"resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.0.1.tgz",
"integrity": "sha512-duBzwAAiIabhFPZfDjcYpJ+f08TMbPMETgq254GWne2NW1ZwRHhZLj7tpSp8KGb7JvZzlLcjGUnqLxpZQVEPng==",
"license": "MIT",
"dependencies": {
"react-router": "7.0.1"
},
"engines": {
"node": ">=20.0.0"
},
"peerDependencies": {
"react": ">=18",
"react-dom": ">=18"
}
},
"node_modules/react-transition-group": {
"version": "4.4.5",
"resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz",
@@ -3621,6 +3677,12 @@
"semver": "bin/semver.js"
}
},
"node_modules/set-cookie-parser": {
"version": "2.7.1",
"resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.1.tgz",
"integrity": "sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ==",
"license": "MIT"
},
"node_modules/shebang-command": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
@@ -3733,6 +3795,12 @@
"typescript": ">=4.2.0"
}
},
"node_modules/turbo-stream": {
"version": "2.4.0",
"resolved": "https://registry.npmjs.org/turbo-stream/-/turbo-stream-2.4.0.tgz",
"integrity": "sha512-FHncC10WpBd2eOmGwpmQsWLDoK4cqsA/UT/GqNoaKOQnT8uzhtCbg3EoUDMvqpOSAI0S26mr0rkjzbOO6S3v1g==",
"license": "ISC"
},
"node_modules/type-check": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
+2 -1
View File
@@ -15,7 +15,8 @@
"axios": "^1.7.7",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-google-charts": "^5.2.1"
"react-google-charts": "^5.2.1",
"react-router-dom": "^7.0.1"
},
"devDependencies": {
"@eslint/js": "^9.13.0",
-1
View File
@@ -1 +0,0 @@
+40 -10
View File
@@ -1,20 +1,50 @@
import "./App.css";
import { useEffect, useState } from "react";
import { BrowserRouter as Router, Route, Routes } from "react-router-dom";
import LeftSide from "./components/LeftSide";
import RightSide from "./components/RightSide";
import Login from "./components/Login";
import Grid from "@mui/material/Grid2";
import { useState } from "react";
import Register from "./components/Register";
import CreateLog from "./components/CreateLog";
import Nav from "./components/Nav";
function App() {
const [authorized, setAuthorized] = useState(false);
const [reset, setReset] = useState(true);
useEffect(() => {
if (localStorage.getItem("token") !== null) setAuthorized(true);
else setAuthorized(false);
}, [authorized]);
return (
<Grid container spacing={2}>
<Grid size={{ lg: 12, xl: 6 }}>
<LeftSide reset={reset} setReset={setReset} />
</Grid>
<Grid size={6}>
<RightSide reset={reset} />
</Grid>
</Grid>
<Router>
<Nav authorized={authorized} setAuthorized={setAuthorized} />
<Routes>
<Route
path="/"
element={
<>
<Grid container spacing={2}>
<Grid size={{ lg: 12, xl: 6 }}>
<LeftSide reset={reset} setReset={setReset} />
</Grid>
<Grid size={6}>
<RightSide reset={reset} />
</Grid>
</Grid>
</>
}
/>
<Route
path="/login"
element={<Login setAuthorized={setAuthorized} />}
/>
<Route path="/register" element={<Register />} />
<Route
path="/createlog"
element={<CreateLog authorized={authorized} />}
/>
</Routes>
</Router>
);
}
+74
View File
@@ -0,0 +1,74 @@
import { useEffect, useState } from "react";
import api from "../utils/api";
interface FormInput {
project: string;
time: string;
date: string;
}
const CreateLog = ({ authorized }: { authorized: boolean }) => {
const [params, setParams] = useState<FormInput>({
project: "",
time: "",
date: "",
});
const [token, setToken] = useState<String>("");
useEffect(() => {
let tmp = localStorage.getItem("token");
if (tmp !== null) setToken(tmp);
});
const onSubmit = async (e: React.FormEvent) => {
e.preventDefault();
if (token !== null) {
const resp = await api.post(
"/createlog",
{
project: params.project,
time: params.time,
date: params.date,
},
{
headers: {
token: token.toString(),
},
},
);
console.log(resp);
}
};
const onChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setParams({ ...params, [e.target.name]: e.target.value });
};
if (authorized)
return (
<>
<form onSubmit={onSubmit}>
<label>Project:</label>
<input
type="text"
name="project"
value={params.project}
onChange={onChange}
/>
<label>Time:</label>
<input
type="text"
name="time"
value={params.time}
onChange={onChange}
/>
<label>Date:</label>
<input
type="text"
name="date"
value={params.date}
onChange={onChange}
/>
<button type="submit">Create Log</button>
</form>
</>
);
else return <>You are not Logged In</>;
};
export default CreateLog;
+24 -60
View File
@@ -20,6 +20,8 @@ interface User {
user: number;
}
//TODO date input should send error when there is no data in the response
// offset should not increase if we don't get new data
const LeftSide = ({
reset,
setReset,
@@ -32,14 +34,14 @@ const LeftSide = ({
const [params, setParams] = useState({
offset: 0,
sortby: "f_name",
from: "2000-01-01",
to: "2028-01-01",
from: "2020-01-01",
to: "2020-01-31",
order: true,
});
// date buttons
const [date, setDate] = useState({
from: "2021-01-01",
to: "2028-01-01",
from: "2020-01-01",
to: "2020-01-31",
});
useEffect(() => {
@@ -54,8 +56,9 @@ const LeftSide = ({
setReset(true);
resetData();
}
fetchData();
// TODO this sends too many request when reseting
console.log(users);
}, [reset, params]);
const viewProjectHours = (userid: number) => {
@@ -67,6 +70,15 @@ const LeftSide = ({
fetchHours();
};
const sortBy = (sortby: string) => {
setParams({
...params,
sortby: sortby,
order: !params.order,
offset: 0,
});
};
if (!users) return <></>;
return (
<Grid container spacing={2}>
@@ -84,7 +96,7 @@ const LeftSide = ({
type="date"
id="to"
name="to"
value={params.to}
value={date.to}
onChange={(event) => setDate({ ...date, to: event.target.value })}
/>
<button
@@ -121,70 +133,22 @@ const LeftSide = ({
<TableHead>
<TableRow>
<TableCell>
<Button
onClick={() =>
setParams({
...params,
sortby: "f_name",
order: !params.order,
})
}
>
First Name
</Button>
<Button onClick={() => sortBy("f_name")}>First Name</Button>
</TableCell>
<TableCell>
<Button
onClick={() =>
setParams({
...params,
sortby: "l_name",
order: !params.order,
})
}
>
Last Name
</Button>
<Button onClick={() => sortBy("l_name")}>Last Name</Button>
</TableCell>
<TableCell>
<Button
onClick={() =>
setParams({ ...params, sortby: "mail", order: !params.order })
}
>
Email
</Button>
<Button onClick={() => sortBy("mail")}>Email</Button>
</TableCell>
<TableCell>
<Button
onClick={() =>
setParams({
...params,
sortby: "project",
order: !params.order,
})
}
>
Project
</Button>
<Button onClick={() => sortBy("project")}>Project</Button>
</TableCell>
<TableCell>
<Button
onClick={() =>
setParams({ ...params, sortby: "date", order: !params.order })
}
>
Date
</Button>
<Button onClick={() => sortBy("date")}>Date</Button>
</TableCell>
<TableCell>
<Button
onClick={() =>
setParams({ ...params, sortby: "time", order: !params.order })
}
>
Hours
</Button>
<Button onClick={() => sortBy("time")}>Hours</Button>
</TableCell>
<TableCell />
</TableRow>
+45
View File
@@ -0,0 +1,45 @@
import { useState } from "react";
import api from "../utils/api";
import { useNavigate } from "react-router-dom";
const Login = ({ setAuthorized }: { setAuthorized: Function }) => {
const [mail, setMail] = useState("");
const [password, setPassword] = useState("");
const navigate = useNavigate();
const onClick = async () => {
const resp = await api.post("/login", { mail, password });
console.log(resp);
console.log(resp.status);
if (resp.status === 200) {
localStorage.setItem(
"token",
JSON.stringify(resp.data).replace(/"/g, ""),
);
setAuthorized(true);
navigate("/");
} else {
alert(resp);
}
};
return (
<>
<label>Email:</label>
<input
type="text"
value={mail}
onChange={(e) => setMail(e.target.value)}
/>
<br />
<label>Password:</label>
<input
type="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
/>
<br />
<button onClick={onClick}>Login</button>
</>
);
};
export default Login;
+37
View File
@@ -0,0 +1,37 @@
import { useNavigate } from "react-router-dom";
function Nav({
authorized,
setAuthorized,
}: {
authorized: boolean;
setAuthorized: Function;
}) {
const navigate = useNavigate();
const onClick = (path: string) => {
navigate(path);
};
return (
<div>
<button onClick={() => onClick("/")}>Home</button>
{authorized ? (
<>
<button onClick={() => onClick("/createlog")}>Create Log</button>
<button
onClick={() => {
localStorage.removeItem("token");
setAuthorized(false);
}}
>
Logout
</button>
</>
) : (
<>
<button onClick={() => onClick("/login")}>Login</button>
<button onClick={() => onClick("/register")}>Register </button>
</>
)}
</div>
);
}
export default Nav;
+107
View File
@@ -0,0 +1,107 @@
import { useState } from "react";
import api from "../utils/api";
import { useNavigate } from "react-router-dom";
interface FormInput {
f_name: string;
l_name: string;
mail: string;
password: string;
password2: string;
}
const Register = () => {
const [params, setParams] = useState<FormInput>({
f_name: "",
l_name: "",
mail: "",
password: "",
password2: "",
});
const navigate = useNavigate();
const onSubmit = async (e: React.FormEvent) => {
e.preventDefault();
if (params.password !== params.password2) {
alert("Password dont match");
return;
}
if (params.f_name.length > 30 || params.f_name.length < 2) {
alert("fname wrong size");
return;
}
if (params.l_name.length > 30 || params.l_name.length < 2) {
alert("l name wrong size");
return;
}
if (params.mail.length > 50 || params.mail.length < 6) {
alert("mail wrong size");
return;
}
if (params.password.length > 30 || params.password.length < 10) {
alert("password wrong size");
return;
}
const resp = await api.post("/register", {
f_name: params.f_name,
l_name: params.l_name,
mail: params.mail,
password: params.password,
});
if (resp.status === 200) {
navigate("/login");
} else {
alert(resp);
}
};
const onChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setParams({ ...params, [e.target.name]: e.target.value });
};
return (
<>
<form onSubmit={onSubmit}>
<label>First name:</label>
<input
type="text"
name="f_name"
value={params.f_name}
onChange={onChange}
/>
<br />
<label>Last name:</label>
<input
type="text"
name="l_name"
value={params.l_name}
onChange={onChange}
/>
<br />
<label>email:</label>
<input
type="text"
name="mail"
value={params.mail}
onChange={onChange}
/>
<br />
<label>Password:</label>
<input
type="password"
name="password"
value={params.password}
onChange={onChange}
/>
<br />
<label>Repat Password:</label>
<input
type="password"
name="password2"
value={params.password2}
onChange={onChange}
/>
<br />
<button type="submit">Register</button>
</form>
</>
);
};
export default Register;
+4 -3
View File
@@ -2,14 +2,15 @@ import { useEffect, useState } from "react";
import api from "../utils/api";
import { Chart } from "react-google-charts";
// TODO whenr reset website there is split seccond of chat with no data, fix it
const RightSide = ({ reset }: { reset: boolean }) => {
// graph date
const [chartData, setChartData] = useState<String[][]>();
// date input field
const [date, setDate] = useState({ from: "2000-01-01", to: "2100-01-01" });
const [date, setDate] = useState({ from: "2020-01-01", to: "2020-01-31" });
const [dateToSubmit, setDateToSubmit] = useState({
from: "2000-01-01",
to: "2100-01-01",
from: "2020-01-01",
to: "2020-01-31",
});
// radio button
const [filter, setFilter] = useState("project");
+4 -8
View File
@@ -13,14 +13,6 @@
-moz-osx-font-smoothing: grayscale;
}
a {
font-weight: 500;
color: #646cff;
text-decoration: inherit;
}
a:hover {
color: #535bf2;
}
body {
margin: 0;
@@ -46,9 +38,11 @@ button {
cursor: pointer;
transition: border-color 0.25s;
}
button:hover {
border-color: #646cff;
}
button:focus,
button:focus-visible {
outline: 4px auto -webkit-focus-ring-color;
@@ -59,9 +53,11 @@ button:focus-visible {
color: #213547;
background-color: #ffffff;
}
a:hover {
color: #747bff;
}
button {
background-color: #f9f9f9;
}
+1 -1
View File
@@ -2,7 +2,7 @@ import axios from "axios";
const api = axios.create({
baseURL: "http://localhost:5000/api",
timeout: 1000,
timeout: 10000,
headers: {
"Content-Type": "application/json",
},