consolidate all repos to one for archive

This commit is contained in:
2025-01-28 13:46:42 +01:00
commit a6610fbc7a
5350 changed files with 2705721 additions and 0 deletions

62
projektna_naloga/frontend/.gitignore vendored Normal file
View File

@@ -0,0 +1,62 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
/node_modules
/.pnp
.pnp.js
# testing
/coverage
# production
/build
# misc
.DS_Store
.env.local
.env.development.local
.env.test.local
.env.production.local
npm-debug.log*
yarn-debug.log*
yarn-error.log*
.DS_STORE
node_modules
scripts/flow/*/.flowconfig
.flowconfig
*~
*.pyc
.grunt
_SpecRunner.html
__benchmarks__
build/
remote-repo/
coverage/
.module-cache
fixtures/dom/public/react-dom.js
fixtures/dom/public/react.js
test/the-files-to-test.generated.js
*.log*
chrome-user-data
*.sublime-project
*.sublime-workspace
.idea
*.iml
.vscode
*.swp
*.swo
packages/react-devtools-core/dist
packages/react-devtools-extensions/chrome/build
packages/react-devtools-extensions/chrome/*.crx
packages/react-devtools-extensions/chrome/*.pem
packages/react-devtools-extensions/firefox/build
packages/react-devtools-extensions/firefox/*.xpi
packages/react-devtools-extensions/firefox/*.pem
packages/react-devtools-extensions/shared/build
packages/react-devtools-extensions/.tempUserDataDir
packages/react-devtools-inline/dist
packages/react-devtools-shell/dist
packages/react-devtools-timeline/dist

View File

@@ -0,0 +1,3 @@
# Frontend of Highway analysis website
The website uses React framework to develope the frontend.

17630
projektna_naloga/frontend/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,45 @@
{
"name": "frontend",
"version": "0.1.0",
"private": true,
"dependencies": {
"@testing-library/jest-dom": "^5.16.5",
"@testing-library/react": "^13.4.0",
"@testing-library/user-event": "^13.5.0",
"bootstrap": "^5.2.3",
"chart.js": "^4.3.0",
"leaflet": "^1.9.3",
"react": "^18.2.0",
"react-bootstrap": "^2.7.4",
"react-chartjs-2": "^5.2.0",
"react-dom": "^18.2.0",
"react-leaflet": "^4.2.1",
"react-router-dom": "^6.11.1",
"react-scripts": "5.0.1",
"web-vitals": "^2.1.4"
},
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject"
},
"eslintConfig": {
"extends": [
"react-app",
"react-app/jest"
]
},
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

View File

@@ -0,0 +1,47 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<meta
name="description"
content="Web site created using create-react-app"
/>
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
<!--
manifest.json provides metadata used when your web app is installed on a
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
-->
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
<!--
Notice the use of %PUBLIC_URL% in the tags above.
It will be replaced with the URL of the `public` folder during the build.
Only files inside the `public` folder can be referenced from the HTML.
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
work correctly both with client-side routing and a non-root public URL.
Learn how to configure a non-root public URL by running `npm run build`.
-->
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.6.3/jquery.min.js"></script>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-KK94CHFLLe+nY2dmCWGMq91rCGa5gtU4mk92HdvYe+M/SXH301p5ILy+dN9+nJOZ" crossorigin="anonymous">
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha3/dist/js/bootstrap.bundle.min.js" integrity="sha384-ENjdO4Dr2bkBIFxQpeoTz1HIcje39Wm4jDKdf19U8gI4ddQ3GYNS7NTKfAdVQSZe" crossorigin="anonymous"></script>
<title>Highway Tracker</title>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
<!--
This HTML file is a template.
If you open it directly in the browser, you will see an empty page.
You can add webfonts, meta tags, or analytics to this file.
The build step will place the bundled scripts into the <body> tag.
To begin the development, run `npm start` or `yarn start`.
To create a production bundle, use `npm run build` or `yarn build`.
-->
</body>
</html>

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.4 KiB

View File

@@ -0,0 +1,25 @@
{
"short_name": "React App",
"name": "Create React App Sample",
"icons": [
{
"src": "favicon.ico",
"sizes": "64x64 32x32 24x24 16x16",
"type": "image/x-icon"
},
{
"src": "logo192.png",
"type": "image/png",
"sizes": "192x192"
},
{
"src": "logo512.png",
"type": "image/png",
"sizes": "512x512"
}
],
"start_url": ".",
"display": "standalone",
"theme_color": "#000000",
"background_color": "#ffffff"
}

View File

@@ -0,0 +1,3 @@
# https://www.robotstxt.org/robotstxt.html
User-agent: *
Disallow:

View File

@@ -0,0 +1,51 @@
import { useState, useEffect } from 'react';
import { BrowserRouter, Routes, Route } from 'react-router-dom';
import { UserContext } from "./userContext";
import { ReactDOM } from 'react';
import Header from './components/Header';
import MapComponent from './components/Map';
import Login from './components/Login';
import Register from './components/Register';
import Logout from './components/Logout';
import Profile from './components/Profile';
import Saved from './components/Saved';
import Detail from './components/Detail';
function App() {
const [user, setUser] = useState(localStorage.user ? JSON.parse(localStorage.user) : null);
const updateUserData = (userInfo) => {
localStorage.setItem("user", JSON.stringify(userInfo));
setUser(userInfo);
}
return (
<BrowserRouter>
<UserContext.Provider value={{
user: user,
setUserContext: updateUserData
}}>
<div className="App" style={{ backgroundImage: 'url("https://cdn.wallpapersafari.com/93/52/fRFtzX.jpg")', backgroundRepeat: "no-repeat", backgroundSize: "cover", minHeight: "100vh" }}>
<Header title="Highway Tracker"></Header>
<Routes>
<Route path="/" exact element={<MapComponent />}></Route>
{/* <Route path="/login" exact element={<Login />}></Route>
<Route path="/register" element={<Register />}></Route>
<Route path="/save" element={<Saved />}></Route>
<Route path="/profile" element={<Profile />}></Route>
<Route path="/logout" element={<Logout />}></Route> */}
<Route path="/details/:id" element={<Detail />}></Route>
</Routes>
</div>
<footer className="footer mt-auto py-3 bg-dark fixed-bottom">
<div className="container text-center">
<span style={{ color: "grey" }}>Highway Tracker - All rights reserved</span>
</div>
</footer>
</UserContext.Provider>
</BrowserRouter>
);
}
export default App;

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

@@ -0,0 +1,13 @@
import React from "react";
import { Bar } from "react-chartjs-2";
import {Chart as ChartJS} from "chart.js/auto"
function BarChart({chartData}) {
// options = {} can be added as a second parameter to Bar to customize the chart
return (
<Bar data={chartData}/>
);
}
export default BarChart;

View File

@@ -0,0 +1,38 @@
import React from 'react';
import { Card, Container, Row, Col } from 'react-bootstrap';
const Boxes = () => {
return (
<div style={{ marginTop: "5px" }}>
<Container>
<Row>
<Col className="align-items-center">
<Card className="text-center">
<Card.Body className="bg-success">
</Card.Body>
<span><b>LOW</b></span>
</Card>
</Col>
<Col className="align-items-center">
<Card className="text-center">
<Card.Body className="bg-warning">
</Card.Body>
<span><b>MEDIUM</b></span>
</Card>
</Col>
<Col className="align-items-center">
<Card className="text-center">
<Card.Body className="bg-danger">
</Card.Body>
<span><b>HIGH</b></span>
</Card>
</Col>
</Row>
</Container>
</div>
);
};
export default Boxes;

View File

@@ -0,0 +1,189 @@
import React, { useEffect, useState } from "react";
import { useParams } from 'react-router-dom';
import BarChart from "./BarChart.js";
import { Card } from 'react-bootstrap';
import { Spinner } from 'react-bootstrap';
import { UserContext } from '../userContext';
import { MapContainer, TileLayer, Circle } from 'react-leaflet';
import Boxes from "./Boxes.js";
import 'leaflet/dist/leaflet.css';
function Detail(param) {
const [carAmount, setCarAmount] = useState([]);
const [carData, setCarData] = useState([]);
const [lastHourCarAmount, setLastHourCarAmount] = useState(0);
// Possible states for dataChange: "day", "month", "year"
const [dataChange, setDataChange] = useState("day");
const { id } = useParams();
const [cardTitle, setCardTitle] = useState("Today");
const [locationData, setLocationData] = useState({});
const [coordinates, setCoordinates] = React.useState([46.05730669203195, 14.504340106047238]);
useEffect(() => {
const getCarAmount = async () => {
var response = new Response();
var data = [];
var label = [];
// Getting the current date (year, month, day)
const currentData = new Date();
const currentYear = currentData.getFullYear();
const currentMonth = currentData.getMonth() + 1;
const currentDay = currentData.getDate();
if (dataChange === "day") {
// The date variables are then used for the fetch request
response = await fetch(`/api/data/year/${currentYear}/month/${currentMonth}/day/${currentDay}/location/${id}`);
data = await response.json();
label = data.map((data) => data.hour + ":00")
setLastHourCarAmount(data[data.length - 1].car_count);
setCardTitle("Today");
}
else if (dataChange === "month") {
response = await fetch(`/api/data/year/${currentYear}/month/${currentMonth}/location/${id}`);
data = await response.json();
label = data.map((data) => data.day + "");
setCardTitle("Last 30 days");
}
if (response.ok) {
setCarAmount(data);
setCarData({
labels: label,
datasets: [{
label: "Car Amount",
data: data.map((data) => data.car_count),
backgroundColor: ["green", "red", "blue"],
borderColor: ["black"],
borderWidth: 2
}]
});
}
}
const getLocationData = async () => {
const response = await fetch(`/api/location/${id}`);
const data = await response.json();
if (response.ok) {
setLocationData(data);
setCoordinates([data.cord_N, data.cord_E]);
}
}
getCarAmount();
getLocationData();
}, [id, dataChange]);
const SaveLocation = async () => {
const response = await fetch(`/api/users/addLocation/${locationData.location_id}`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' }
});
if (response.status === 400) {
const data = await response.json();
if (data.message === 'Location already added') {
alert('Location already saved');
}
}
};
const StatusCheck = (amount) => {
if (amount <= 100) {
return 'green';
} else if (amount <= 250) {
return 'yellow';
} else {
return 'red';
}
};
if (Object.keys(carData).length === 0 || Object.keys(locationData).length === 0) {
return (
<div className="d-flex justify-content-center align-items-center" style={{ height: '100vh' }}>
<Spinner animation="border" role="status" variant="success" />
</div>
);
}
return (
<div className="container-fluid">
<h2 style={{ color: 'white' }} className="d-flex justify-content-center">Location Details</h2>
{/* The bar chart is displayed here */}
<div className="row">
<div className="col-lg">
<Card style={{ width: '100%' }}>
<Card.Body>
<Card.Title>{cardTitle}</Card.Title>
<div className="d-flex justify-content-center">
<BarChart chartData={carData} />
</div>
<div className="d-flex justify-content-center mt-4">
<button className="btn btn-primary mx-2" onClick={() => setDataChange('day')}>
Day
</button>
<button className="btn btn-primary mx-2" onClick={() => setDataChange('month')}>
Month
</button>
</div>
</Card.Body>
</Card>
</div>
{/* The location details are displayed here */}
<div className="col-lg">
<Card style={{ width: '100%' }}>
<Card.Body>
<h4>Location: {locationData.name}</h4>
<div style={{ display: "flex", justifyContent: "center" }}>
<img src={locationData.img_urls[0]} className="img-fluid rounded" style={{ border: "5px solid black" }} alt="Camera view" />
</div>
{/* Only show the button if the user is logged in */}
<UserContext.Consumer>
{context => (
context.user ?
<div style={{ display: "flex", justifyContent: "center", marginTop: "5px" }}>
<button className="btn btn-primary mx-2" onClick={() => SaveLocation()}>Save location</button>
</div>
:
<></>
)}
</UserContext.Consumer>
</Card.Body>
</Card>
</div>
{/* The map is displayed here */}
<div className="col-lg">
<Card style={{ width: '100%' }}>
<Card.Body>
<h4>Last hour traffic status</h4>
<MapContainer center={coordinates} zoom={16} scrollWheelZoom={false} zoomControl={false} style={{ height: "50vh" }}>
<TileLayer url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png" />
<Circle
key={locationData._id}
center={[locationData.cord_N, locationData.cord_E]}
pathOptions={{ color: StatusCheck(lastHourCarAmount), fillColor: StatusCheck(lastHourCarAmount), fillOpacity: 0.5 }}
radius={100}
>
</Circle>
</MapContainer>
<Boxes />
</Card.Body>
</Card>
</div>
</div>
</div>
);
}
export default Detail;

View File

@@ -0,0 +1,62 @@
import { useContext } from "react";
import { UserContext } from "../userContext";
import { Link } from "react-router-dom";
import icon from "../assets/icon.png";
function Header(props) {
return (
<header className="navbar navbar-expand-lg navbar-dark bg-dark">
<div className="container">
<Link to="/" className="navbar-brand d-flex align-items-center">
<img src={icon} width="30" height="30" className="mr-2" alt="" />
<span>{props.title}</span>
</Link>
<button
className="navbar-toggler"
type="button"
data-bs-toggle="collapse"
data-bs-target="#navbarNav"
aria-controls="navbarNav"
aria-expanded="false"
aria-label="Toggle navigation"
>
<span className="navbar-toggler-icon"></span>
</button>
<div className="collapse navbar-collapse" id="navbarNav">
<ul className="navbar-nav ml-auto">
<li className="nav-item">
<Link to="/" className="nav-link">Map</Link>
</li>
{/* <UserContext.Consumer>
{context => (
context.user ?
<>
<li className="nav-item">
<Link to="/save" className="nav-link">Saved</Link>
</li>
<li className="nav-item">
<Link to="/profile" className="nav-link">Profile</Link>
</li>
<li className="nav-item">
<Link to="/logout" className="nav-link">Logout</Link>
</li>
</>
:
<>
<li className="nav-item">
<Link to="/login" className="nav-link">Login</Link>
</li>
<li className="nav-item">
<Link to="/register" className="nav-link">Register</Link>
</li>
</>
)}
</UserContext.Consumer> */}
</ul>
</div>
</div>
</header>
);
}
export default Header;

View File

@@ -0,0 +1,74 @@
import { useContext, useState } from 'react';
import { UserContext } from '../userContext';
import { Navigate } from 'react-router-dom';
function Login() {
const [username, setUsername] = useState("");
const [password, setPassword] = useState("");
const [error, setError] = useState("");
const userContext = useContext(UserContext);
async function Login(e) {
e.preventDefault();
const res = await fetch("/api/users/login", {
method: "POST",
credentials: "include",
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
username: username,
password: password
})
});
const data = await res.json();
if (data._id !== undefined) {
userContext.setUserContext(data);
} else {
setUsername("");
setPassword("");
setError("Invalid username or password");
}
}
return (
<form className="container mt-5" onSubmit={Login}>
{userContext.user ? <Navigate replace to="/" /> : ""}
<div className="mb-3">
<label htmlFor="username" className="form-label" style={{ color: "white" }}>Username</label>
<input
type="text"
className="form-control"
id="username"
name="username"
placeholder="Username"
value={username}
onChange={(e) => setUsername(e.target.value)}
/>
</div>
<div className="mb-3">
<label htmlFor="password" className="form-label" style={{ color: "white" }}>Password</label>
<input
type="password"
className="form-control"
id="password"
name="password"
placeholder="Password"
value={password}
onChange={(e) => setPassword(e.target.value)}
/>
</div>
<div className="mb-3">
<input
type="submit"
className="btn btn-primary"
name="submit"
value="Log in"
/>
</div>
<div className="mb-3">
<label>{error}</label>
</div>
</form>
);
}
export default Login;

View File

@@ -0,0 +1,20 @@
import { useEffect, useContext } from 'react';
import { UserContext } from '../userContext';
import { Navigate } from 'react-router-dom';
function Logout() {
const userContext = useContext(UserContext);
useEffect(function () {
const logout = async function () {
userContext.setUserContext(null);
const res = await fetch("/api/users/logout");
}
logout();
}, []);
return (
<Navigate replace to="/" />
);
}
export default Logout;

View File

@@ -0,0 +1,117 @@
import React, { useEffect } from 'react';
import { MapContainer, TileLayer, Circle } from 'react-leaflet';
import 'leaflet/dist/leaflet.css';
import { Spinner } from 'react-bootstrap';
import SideView from './SideView';
function MapComponent() {
const [coordinates, setCoordinates] = React.useState([46.05730669203195, 14.504340106047238]);
const [locations, setLocations] = React.useState([]);
const [carAmount, setCarAmount] = React.useState([]);
const [isClicked, setIsClicked] = React.useState(false);
const [clickedCircleData, setClickedCircleData] = React.useState(null);
useEffect(() => {
const getLocations = async () => {
const response = await fetch('/api/location');
const data = await response.json();
if (response.ok) {
setLocations(data);
}
}
getLocations();
const getCarAmount = async () => {
// Getting the current date (year, month, day)
var currentDate = new Date();
currentDate.setTime(currentDate.getTime() - 60 * 60 * 1000);
const currentYear = currentDate.getFullYear();
const currentMonth = currentDate.getMonth() + 1;
const currentDay = currentDate.getDate();
const lastHour = currentDate.getHours();
const response = await fetch(`/api/data/year/${currentYear}/month/${currentMonth}/day/${currentDay}/hour/${lastHour}`);
const data = await response.json();
if (response.ok) {
setCarAmount(data);
}
}
getCarAmount();
}, []);
const onClick = (e, location) => {
setClickedCircleData(location);
setIsClicked(true);
};
const StatusCheck = (amount) => {
if (amount <= 100) {
return 'green';
} else if (amount <= 250) {
return 'yellow';
} else {
return 'red';
}
};
const locationMathching = (location) => {
return StatusCheck(carAmount[location].car_count);
};
if (carAmount.length === 0) {
return (
<div className="container-fluid">
<div className="row">
<div className="col-md-8">
<MapContainer center={coordinates} zoom={12} scrollWheelZoom={true} style={{ height: "90vh" }}>
<TileLayer url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png" />
</MapContainer>
</div>
<div className="col-sm d-flex align-items-center justify-content-center" style={{ marginBottom: "50px" }}>
<h1 className="text-justify text-center" style={{ color: 'white' }}><i>Waiting for camera data...</i></h1>
<div className="d-flex justify-content-center align-items-center" style={{ height: '100vh' }}>
<Spinner animation="border" role="status" variant="light" />
</div>
</div>
</div>
</div>
);
}
return (
<div className="container-fluid">
<div className="row">
<div className="col-md-8">
<MapContainer center={coordinates} zoom={12} scrollWheelZoom={true} style={{ height: "90vh" }}>
<TileLayer url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png" />
{locations.filter(location => location.location_id !== 999).map(location => (
<Circle
key={location._id}
center={[location.cord_N, location.cord_E]}
pathOptions={{ color: locationMathching(location.location_id), fillColor: locationMathching(location.location_id), fillOpacity: 0.5 }}
radius={200}
eventHandlers={{ click: (e) => onClick(e, location) }}
>
</Circle>
))}
</MapContainer>
</div>
{isClicked ? (
<SideView locationData={clickedCircleData} carAmountDat={carAmount} />
) : (
<div className="col-sm d-flex align-items-center justify-content-center" style={{ marginBottom: "50px" }}>
<h1 className="text-justify text-center" style={{ color: 'white' }}><i>Select a camera to view details.</i></h1>
</div>
)}
</div>
</div>
);
}
export default MapComponent;

View File

@@ -0,0 +1,36 @@
import { useContext, useEffect, useState } from 'react';
import { UserContext } from '../userContext';
import { Navigate } from 'react-router-dom';
function Profile() {
const userContext = useContext(UserContext);
const [profile, setProfile] = useState({});
useEffect(function () {
const getProfile = async function () {
const res = await fetch("/api/users/profile", { credentials: "include" });
const data = await res.json();
setProfile(data);
}
getProfile();
}, []);
return (
<>
{!userContext.user ? <Navigate replace to="/login" /> : ""}
<div className="container">
<h1 style={{ color: "white" }}>User profile</h1>
<div className="card">
<div className="card-body">
<h5 className="card-title">Username: {profile.username}</h5>
<h5 className="card-text">Email: {profile.email}</h5>
</div>
</div>
</div>
</>
);
}
export default Profile;

View File

@@ -0,0 +1,54 @@
import { useState } from 'react';
function Register() {
const [username, setUsername] = useState([]);
const [password, setPassword] = useState([]);
const [email, setEmail] = useState([]);
const [error, setError] = useState([]);
async function Register(e) {
e.preventDefault();
const res = await fetch("/api/users", {
method: 'POST',
credentials: 'include',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
email: email,
username: username,
password: password
})
});
const data = await res.json();
if (data._id !== undefined) {
window.location.href = "/";
}
else {
setUsername("");
setPassword("");
setEmail("");
setError("Registration failed");
}
}
return (
<form className="container" onSubmit={Register}>
<div className="mb-3">
<label htmlFor="email" className="form-label" style={{ color: "white" }}>Email</label>
<input type="text" className="form-control" id="email" name="email" value={email} onChange={(e) => setEmail(e.target.value)} placeholder="Email" />
</div>
<div className="mb-3">
<label htmlFor="username" className="form-label" style={{ color: "white" }}>Username</label>
<input type="text" className="form-control" id="username" name="username" value={username} onChange={(e) => setUsername(e.target.value)} placeholder="Username" />
</div>
<div className="mb-3">
<label htmlFor="password" className="form-label" style={{ color: "white" }}>Password</label>
<input type="password" className="form-control" id="password" name="password" value={password} onChange={(e) => setPassword(e.target.value)} placeholder="Password" />
</div>
<button type="submit" className="btn btn-primary">Register</button>
<label>{error}</label>
</form>
);
}
export default Register;

View File

@@ -0,0 +1,85 @@
import React, { useEffect, useState } from "react";
import { Link } from 'react-router-dom';
import { Card, Spinner } from 'react-bootstrap';
function Saved() {
const [savedLocations, setSavedLocations] = useState([]);
const [locationData, setLocationData] = useState([]);
useEffect(() => {
const getSavedLocations = async () => {
const response = await fetch(`/api/users/savedLocation`);
const data = await response.json();
setSavedLocations(data);
};
const getLocationData = async () => {
const response = await fetch(`/api/location`);
const data = await response.json();
if (response.ok) {
setLocationData(data);
}
};
getSavedLocations();
getLocationData();
}, []);
const merge = () => {
if (savedLocations.length > 0 && locationData.length > 0) {
var mergedLocations = [];
for (var i = 0; i < savedLocations.length; i++) {
mergedLocations.push(locationData[savedLocations[i]]);
}
return mergedLocations;
}
};
if (Object.keys(locationData).length === 0) {
return (
<div className="d-flex justify-content-center align-items-center" style={{ height: '100vh' }}>
<Spinner animation="border" role="status" variant="success" />
</div>
);
}
return (
<div className="container">
<h2 style={{ color: 'white' }} className="d-flex justify-content-center">Saved Locations</h2>
{savedLocations.length === 0 ? (
<div className="d-flex justify-content-center">
<Card style={{ width: '700px' }}>
<Card.Body>
<h4 className="text-center">No saved locations!</h4>
</Card.Body>
</Card>
</div>
) : (
<div className="d-flex justify-content-center">
<ul>
{merge().map((location) => (
<li className="list-group-item" style={{ margin: "5px" }} key={location._id}>
<Card style={{ width: '700px' }}>
<Card.Body className="d-flex justify-content-between align-items-center">
<div>
<Card.Title>{location.name}</Card.Title>
</div>
<div>
<button className="btn btn-primary" type="button">
<Link to={'/details/' + location.location_id} className="nav-link" style={{ textDecoration: 'none', color: 'inherit' }}>View details</Link>
</button>
</div>
</Card.Body>
</Card>
</li>
))}
</ul>
</div>
)}
</div>
);
}
export default Saved;

View File

@@ -0,0 +1,51 @@
import React, { useEffect } from "react";
import { Link } from 'react-router-dom';
import { Spinner } from 'react-bootstrap';
function SideView({ locationData, carAmountDat }) {
if (Object.keys(carAmountDat).length === 0) {
return (
<div className="d-flex justify-content-center align-items-center" style={{ height: '100vh' }}>
<Spinner animation="border" role="status" variant="success" />
</div>
);
}
const GetLastHourCarAmount = () => {
const clickedLocation = carAmountDat[locationData.location_id];
if (clickedLocation) {
return clickedLocation.car_count;
} else {
return 0;
}
};
// Use the data to display relevant information
return (
<div className="col-md d-flex justify-content-center align-items-center">
{locationData && (
<div className="card">
<div className="card-body">
<h1 className="card-title text-center">{locationData.name}</h1>
<h5 className="card-subtitle mb-2 text-muted text-center"><i>Camera view:</i></h5>
<div className="d-flex justify-content-center">
<img src={locationData.img_urls[0]} className="img-fluid rounded" style={{ border: "5px solid black" }} alt="Camera view" />
</div>
<hr />
<div className="text-center">
<h5>Last hour car amount: {GetLastHourCarAmount()}</h5>
<button className="btn btn-primary" type="button" style={{ marginBottom: "30px" }}>
<Link to={'details/' + locationData.location_id} className="nav-link" style={{ textDecoration: 'none', color: 'inherit' }}>View more details</Link>
</button>
</div>
</div>
</div>
)}
</div>
);
}
export default SideView;

View File

@@ -0,0 +1,13 @@
body {
margin: 0;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
code {
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
monospace;
}

View File

@@ -0,0 +1,11 @@
import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<React.StrictMode>
<App />
</React.StrictMode>
);

View File

@@ -0,0 +1,6 @@
import { createContext } from 'react';
export const UserContext = createContext({
user: null,
setUserContext: () => { }
});