dijkstra-frontend/src/components/HomeView.jsx

302 lines
6.7 KiB
JavaScript

import React, { useState, useEffect } from 'react';
import { Layout, Select, Button } from 'antd';
import styled from 'styled-components';
import { fetchRoads, fetchCities, getShortestPath } from '../utils/requests';
import Map from './Map';
import 'antd/dist/antd.css';
import arrow from '../assets/arrow-right.png';
const { Header, Content } = Layout;
const { Option } = Select;
let highlightInitial = {
bruges: '#00856E',
ghent: '#00856E',
antwerp: '#00856E',
mechelen: '#00856E',
tournai: '#00856E',
brussels: '#00856E',
mons: '#00856E',
namur: '#00856E',
liege: '#00856E',
arlon: '#00856E',
};
function HomeView() {
const [startingPoint, setStartingPoint] = useState(1);
const [destination, setDestination] = useState(9);
const [errorSelect, setError] = useState({ message: '', flag: false });
const [shortestPath, setShortestPath] = useState();
const [cities, setCities] = useState([]);
const [highlighted, setHighlighted] = useState(highlightInitial);
useEffect(() => {
fetchCities()
.then((data) => setCities(data))
.catch((error) => console.log(error));
}, []);
const [roads, setRoads] = useState([]);
useEffect(() => {
fetchRoads()
.then((data) => setRoads(data))
.catch((error) => console.log(error));
}, []);
const [graph, setGraph] = useState({ nodes: [], edges: [] });
useEffect(() => {
let nodes = cities.map((node) => ({
id: node.id,
label: node.name,
title: node.name,
}));
let edges = roads.map((edge) => ({
from: edge.start_city_id,
to: edge.end_city_id,
length: edge.distance * 2,
}));
setGraph({ nodes, edges });
}, [cities, roads, shortestPath]);
function handleStart(city_id) {
// eslint-disable-next-line
let [startingPoint] = cities.filter((city) => city.id == city_id);
setStartingPoint(startingPoint);
// Check if start and destination are the same
if (startingPoint === destination) {
setError({
message: 'The start and destination must be different.',
flag: true,
});
return;
}
// Reset the error message and shown path
setHighlighted(highlightInitial);
setShortestPath();
setError({ flag: false });
}
function handleDestination(city_id) {
// eslint-disable-next-line
let [destination] = cities.filter((city) => city.id == city_id);
setDestination(destination);
// Check if start and destination are the same
if (startingPoint === destination) {
setError({
message: 'The start and destination must be different.',
flag: true,
});
return;
}
// Reset the error message and shown path
setHighlighted(highlightInitial);
setShortestPath();
setError({ flag: false });
}
function handleSubmit() {
if (startingPoint === destination) {
setError({
message: 'Please select a start and a destination',
flag: true,
});
} else if (startingPoint && destination) {
getShortestPath(startingPoint.id, destination.id)
.then((data) => {
setShortestPath(data);
// Add cities to highlight
let updatedgHighlighted = {};
data.path.forEach((city) => {
updatedgHighlighted[city.name] = '#E63318';
});
setHighlighted(updatedgHighlighted);
})
.catch((error) => console.log(error));
setError({ flag: false });
} else {
setError({
message: 'Please select a start and a destination',
flag: true,
});
}
}
return (
<>
<Header
style={{ backgroundColor: '#ededed', padding: '0 0 5px 0', display: 'flex', justifyContent: 'center' }}
>
<div style={{ margin: '0 auto', maxWidth: '800px' }}>
<InlineH1>Dijkstra | </InlineH1>
<InlineP>Find the shortest path between cities</InlineP>
</div>
</Header>
<Content>
<div
style={{
display: 'flex',
justifyContent: 'center',
margin: '3vh 0 0 0',
}}
>
<h3>
Dockerized NodeJS backend (
<a href='https://git.ruihildt.xyz/ruihildt/dijkstra-backend'>
Git Source
</a>
) & React Frontend (
<a href='https://git.ruihildt.xyz/ruihildt/dijkstra-frontend'>
Git Source
</a>{' '}
) communicating through a rest API
</h3>
</div>
<Main
style={{
display: 'flex',
flexFlow: 'row wrap',
justifyContent: 'space-between',
}}
>
<div style={{ margin: '3vh 1vw' }}>
<Section>
<h2>Starting Point</h2>
<div>
<StyledSelect
defaultValue='Belgium'
disabled
></StyledSelect>
<Select
defaultValue='Select a city'
onChange={handleStart}
style={{ width: 139 }}
>
{cities.map((city) => (
<Option key={city.id}>
{city.name}
</Option>
))}
</Select>
</div>
</Section>
<Section>
<h2>Destination</h2>
<div>
<StyledSelect
defaultValue='Belgium'
disabled
></StyledSelect>
<Select
defaultValue='Select a city'
onChange={handleDestination}
style={{ width: 139 }}
>
{cities.map((city) => (
<Option key={city.id}>
{city.name}
</Option>
))}
</Select>
</div>
</Section>
<Section>
<Button type='primary' onClick={handleSubmit}>
Get shortest path between the cities
</Button>
{errorSelect.flag && <p>{errorSelect.message}</p>}
</Section>
</div>
<div>
{graph.nodes.length > 0 && (
<div>
<div style={{ width: '600px' }}>
<Map highlighted={highlighted} />
</div>
</div>
)}
{shortestPath && (
<Section>
<h2>
Shortest path from {startingPoint.name} to{' '}
{destination.name} ({shortestPath.distance}
km)
</h2>
<StyledP>
{shortestPath.path.map((city) => (
<StyledSpan
key={city.id}
className='cityPath'
>
{city.name}
</StyledSpan>
))}
</StyledP>
</Section>
)}
</div>
</Main>
</Content>
</>
);
}
export default HomeView;
// STYLED COMPONENTS
const InlineP = styled.p`
display: inline;
`;
const InlineH1 = styled.h1`
display: inline;
`;
const Main = styled.main`
max-width: 1000px;
margin: 0 auto;
`;
const Section = styled.section`
margin: 20px 0;
`;
const StyledSelect = styled(Select)`
margin-right: 10px;
`;
const StyledSpan = styled.span`
::after {
content: '';
background-image: url(${arrow});
background-size: 13px 17px;
display: inline-block;
width: 13px;
height: 17px;
margin-right: 10px;
margin-left: 10px;
padding-top: 10px;
bottom: -10px;
}
font-size: 1.5rem;
color: #40a9ff;
`;
const StyledP = styled.p`
span:last-child {
::after {
background-image: none;
}
}
`;