added tests for backend, router dom to frontend
This commit is contained in:
Generated
+69
-1
@@ -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",
|
||||
|
||||
@@ -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 +0,0 @@
|
||||
|
||||
+40
-10
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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");
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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",
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user