chore(ui): add train-station page

This commit is contained in:
dancingCycle 2023-06-09 11:54:16 +02:00
parent fc5458f435
commit d0c3d3088d
15 changed files with 431 additions and 4 deletions

View File

@ -1,13 +1,16 @@
import React from 'react';
import { BrowserRouter as Router,Route, Routes } from 'react-router-dom';
import { BrowserRouter as Router, Link, Route, Routes } from 'react-router-dom';
import Home from './pages/home';
import TrainStation from './pages/train-station';
export default function App(){
return (
<div className='App'>
<Router>
<Routes>
<Route path='/' element={<Home/>}/>
<Route path='/train-station' element={<TrainStation/>}/>
</Routes>
</Router>
</div>

View File

@ -0,0 +1,56 @@
import React, { useEffect, useState } from 'react';
import axios from 'axios';
import config from '../utils/config';
import Map from './map';
import Table from './table';
/*destructure props object*/
export default function Fetch () {
const [array, setArray] = useState([]);
/*fetch array in a JavaScript function*/
const fetch = async () => {
try {
/*TODO handle errors: https://www.valentinog.com/blog/await-react/*/
//fetch data only if user selection is unequal default value
const address = `${config.API}train-station/info`;
//console.log("Fetch:fetch(): address: "+address);
const res = await axios.get(address);
//console.log("Fetch:fetch(): res.length: "+res.data.length);
setArray((array) => res.data);
} catch (err) {
console.error('err.message: ' + err.message);
setArray((array) => []);
}
};
useEffect(() => {
/*effect goes here*/
fetch();
/*use an empty dependency array to ensure the hook is running only once*/
/*TODO study dependency array: https://reactjs.org/docs/hooks-effect.html*/
}, []);
if (array !== undefined || array !== null || array.length > 0) {
/*return a React element*/
return (
<>
<Map
array={array}
/>
<Table
array={array}
/>
</>
);
}else{
return (
<>
<p>Loading...</p>
</>
);
}
};

View File

@ -0,0 +1,5 @@
import L from "leaflet";
import iconUrl from "leaflet/dist/images/marker-icon.png";
export const icon = L.icon({
iconUrl,
});

26
ui/app/components/map.jsx Normal file
View File

@ -0,0 +1,26 @@
import React from 'react';
import PropTypes from 'prop-types';
import EntitiesMap from './map/entities-map';
/*destructure props object*/
export default function Map ({array}){
if (array !== undefined || array !== null || array.length > 0) {
/*return a React element*/
return (
<>
<h2>Map of Train Stations</h2>
<EntitiesMap entities={array} />
</>
);
}else{
return (
<>
<p>Map loading...</p>
</>
);
}
};
Map.propTypes = {
array: PropTypes.array
};

View File

@ -0,0 +1,4 @@
/*set up the height of*/
.leaflet-container {
height: 45vh;
}

View File

@ -0,0 +1,50 @@
import React from 'react';
import PropTypes from 'prop-types';
import {MapContainer,Polyline,TileLayer} from 'react-leaflet';
/*JS module import (vs cdn or style link)*/
import 'leaflet/dist/leaflet.css'
import './entities-map.css';
import EntitiesMarker from './entities-marker';
export default function EntitiesMap({entities}) {
/*lat and lon of Braunschweig,DE*/
const position = [52.26594, 10.52673]
return (
<>
<MapContainer
center={position}
zoom={8}
minZoom={4}
maxZoom={18}
maxBounds={[[-60, -180], [85, 180]]}
scrollWheelZoom={true}
>
<TileLayer
attribution='&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
/>
{
entities.map(function(value,key) {
if(value[4]!==undefined && value[4]!==null && value[3]!==undefined && value[3]!==null){
return <EntitiesMarker
key={value[0]}
index={value[0]}
id={value[0]}
lat={value[4]}
lon={value[3]}
name={value[2]}
/>;
}
})
}
</MapContainer>
</>
);
}
EntitiesMap.propTypes = {
entities: PropTypes.array
};

View File

@ -0,0 +1,47 @@
import React from 'react';
import PropTypes from 'prop-types';
import {Marker} from 'react-leaflet';
import PopupElem from './entities-popup';
import { icon } from "../icons/icon-default";
export default function EntitiesMarker({id,lat,lon,name}){
if(lat===undefined || lat===null || lon===undefined || lon===null){
console.error('lat or lon undefined or null');
return null;
}else{
if(lat===undefined||lat===null){
//TODO Handle issue!
console.error('ERROR: lat undefined or null');
return null;
}else if(lon===undefined||lon===null){
//TODO Handle issue!
console.error('ERROR: lon undefined or null');
return null;
}else{
return(
<>
<Marker
key={id}
position={[lat,lon]}
icon={icon}
>
<PopupElem
id={id}
lat={lat}
lon={lon}
name={name}
/>
</Marker>
</>
);
}
}
};
EntitiesMarker.propTypes = {
id: PropTypes.string,
lat: PropTypes.string,
lon: PropTypes.string,
name: PropTypes.string
};

View File

@ -0,0 +1,22 @@
import React from 'react';
import PropTypes from 'prop-types';
import {Popup} from 'react-leaflet';
export default function EntitiesPopup({id,lat,lon,name}){
return (
<>
<Popup>
id: {id} <br/>
lat: {lat} <br/>
lon: {lon} <br/>
name: {name}
</Popup>
</>
);
};
EntitiesPopup.propTypes = {
id: PropTypes.string,
lat: PropTypes.string,
lon: PropTypes.string,
name: PropTypes.string
};

View File

@ -0,0 +1,28 @@
import React from 'react';
import PropTypes from 'prop-types';
function TableEntries ({array}) {
if (array !== undefined || array !== null || array.length > 0) {
//iterate over array
return array.map((item, index) => {
return (
<tr
key={item[0]}
>
<td>{item[0]}</td>
<td>{item[1]}</td>
<td>{item[2]}</td>
<td>{item[3]}</td>
<td>{item[4]}</td>
</tr>
);
});
}else{
//data is empty
return null;
}
}
TableEntries.propTypes = {
array: PropTypes.array
};
export default TableEntries;

View File

@ -0,0 +1,40 @@
import React from 'react';
import PropTypes from 'prop-types';
import TableEntries from './table-entries';
/*destructure props object*/
export default function Table ({array}){
if (array !== undefined || array !== null || array.length > 0) {
/*return a React element*/
return (
<>
<h2>Table</h2>
<table>
<thead>
<tr>
<th>id</th>
<th>railway</th>
<th>name</th>
<th>lon</th>
<th>lat</th>
</tr>
</thead>
<tbody>
<TableEntries array={array} />
</tbody>
</table>
</>
);
}else{
return (
<>
<h2>Table</h2>
<p>Table loading...</p>
</>
);
}
};
Table.propTypes = {
array: PropTypes.array
};

View File

@ -1,4 +1,5 @@
import React, { useState, useEffect } from 'react'
import { Link} from 'react-router-dom';
import '../style.css';
import {getBusStopCount, getTrainStationCount} from '../utils/api';
@ -34,15 +35,25 @@ export default function Home(){
return (
<>
<Link
to='/'
>
<button>
Home
</button>
</Link>
<h1>RVB Display</h1>
<h2>
Wellcome to the RVB Display!
</h2>
<h2>Wellcome to the RVB Display!</h2>
<h3>
Current number of bus stops in the RVB area: {busStopCount}
</h3>
<h3>
Current number of train stations in the RVB area: {trainStationCount}
<Link
to={'/train-station'}
>
<button>Map of Train Stations</button>
</Link>
</h3>
</>
);

19
ui/app/pages/map.jsx Normal file
View File

@ -0,0 +1,19 @@
import React from 'react'
import { Link} from 'react-router-dom';
export default function Map() {
return(
<>
<Link
to='/'
>
<button>
Home
</button>
</Link>
<h1>RVB Display</h1>
<h2>Wellcome to the RVB Display!</h2>
<h3>Map</h3>
</>
);
};

View File

@ -0,0 +1,20 @@
import React from 'react'
import { Link} from 'react-router-dom';
import Fetch from '../components/fetch';
export default function TrainStation() {
return(
<>
<Link
to='/'
>
<button>
Home
</button>
</Link>
<h1>RVB Display</h1>
<Fetch/>
</>
);
};

93
ui/package-lock.json generated
View File

@ -10,8 +10,11 @@
"license": "GPL-3.0-or-later",
"dependencies": {
"axios": "1.3.6",
"leaflet": "^1.9.4",
"prop-types": "^15.8.1",
"react": "18.2.0",
"react-dom": "18.2.0",
"react-leaflet": "^4.2.1",
"react-router-dom": "6.10.0"
},
"devDependencies": {
@ -1811,6 +1814,16 @@
"integrity": "sha512-Hcv+nVC0kZnQ3tD9GVu5xSMR4VVYOteQIr/hwFPVEvPdlXqgGEuRjiheChHgdM+JyqdgNcmzZOX/tnl0JOiI7A==",
"dev": true
},
"node_modules/@react-leaflet/core": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/@react-leaflet/core/-/core-2.1.0.tgz",
"integrity": "sha512-Qk7Pfu8BSarKGqILj4x7bCSZ1pjuAPZ+qmRwH5S7mDS91VSbVVsJSrW4qA+GPrro8t69gFYVMWb1Zc4yFmPiVg==",
"peerDependencies": {
"leaflet": "^1.9.0",
"react": "^18.0.0",
"react-dom": "^18.0.0"
}
},
"node_modules/@remix-run/router": {
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.5.0.tgz",
@ -4306,6 +4319,11 @@
"shell-quote": "^1.7.3"
}
},
"node_modules/leaflet": {
"version": "1.9.4",
"resolved": "https://registry.npmjs.org/leaflet/-/leaflet-1.9.4.tgz",
"integrity": "sha512-nxS1ynzJOmOlHp+iL3FyWqK89GtNL8U8rvlMOsQdTTssxZwCXh8N2NB3GDQOL+YR3XnWyZAxwQixURb+FA74PA=="
},
"node_modules/loader-runner": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.0.tgz",
@ -4620,6 +4638,14 @@
"url": "https://github.com/fb55/nth-check?sponsor=1"
}
},
"node_modules/object-assign": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
"integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/object-inspect": {
"version": "1.12.3",
"resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.3.tgz",
@ -4966,6 +4992,16 @@
"integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==",
"dev": true
},
"node_modules/prop-types": {
"version": "15.8.1",
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
"integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==",
"dependencies": {
"loose-envify": "^1.4.0",
"object-assign": "^4.1.1",
"react-is": "^16.13.1"
}
},
"node_modules/proxy-addr": {
"version": "2.0.7",
"resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz",
@ -5082,6 +5118,24 @@
"react": "^18.2.0"
}
},
"node_modules/react-is": {
"version": "16.13.1",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
},
"node_modules/react-leaflet": {
"version": "4.2.1",
"resolved": "https://registry.npmjs.org/react-leaflet/-/react-leaflet-4.2.1.tgz",
"integrity": "sha512-p9chkvhcKrWn/H/1FFeVSqLdReGwn2qmiobOQGO3BifX+/vV/39qhY8dGqbdcPh1e6jxh/QHriLXr7a4eLFK4Q==",
"dependencies": {
"@react-leaflet/core": "^2.1.0"
},
"peerDependencies": {
"leaflet": "^1.9.0",
"react": "^18.0.0",
"react-dom": "^18.0.0"
}
},
"node_modules/react-router": {
"version": "6.10.0",
"resolved": "https://registry.npmjs.org/react-router/-/react-router-6.10.0.tgz",
@ -7673,6 +7727,12 @@
"integrity": "sha512-Hcv+nVC0kZnQ3tD9GVu5xSMR4VVYOteQIr/hwFPVEvPdlXqgGEuRjiheChHgdM+JyqdgNcmzZOX/tnl0JOiI7A==",
"dev": true
},
"@react-leaflet/core": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/@react-leaflet/core/-/core-2.1.0.tgz",
"integrity": "sha512-Qk7Pfu8BSarKGqILj4x7bCSZ1pjuAPZ+qmRwH5S7mDS91VSbVVsJSrW4qA+GPrro8t69gFYVMWb1Zc4yFmPiVg==",
"requires": {}
},
"@remix-run/router": {
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.5.0.tgz",
@ -9603,6 +9663,11 @@
"shell-quote": "^1.7.3"
}
},
"leaflet": {
"version": "1.9.4",
"resolved": "https://registry.npmjs.org/leaflet/-/leaflet-1.9.4.tgz",
"integrity": "sha512-nxS1ynzJOmOlHp+iL3FyWqK89GtNL8U8rvlMOsQdTTssxZwCXh8N2NB3GDQOL+YR3XnWyZAxwQixURb+FA74PA=="
},
"loader-runner": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.0.tgz",
@ -9839,6 +9904,11 @@
"boolbase": "^1.0.0"
}
},
"object-assign": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
"integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg=="
},
"object-inspect": {
"version": "1.12.3",
"resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.3.tgz",
@ -10085,6 +10155,16 @@
"integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==",
"dev": true
},
"prop-types": {
"version": "15.8.1",
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
"integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==",
"requires": {
"loose-envify": "^1.4.0",
"object-assign": "^4.1.1",
"react-is": "^16.13.1"
}
},
"proxy-addr": {
"version": "2.0.7",
"resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz",
@ -10175,6 +10255,19 @@
"scheduler": "^0.23.0"
}
},
"react-is": {
"version": "16.13.1",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
},
"react-leaflet": {
"version": "4.2.1",
"resolved": "https://registry.npmjs.org/react-leaflet/-/react-leaflet-4.2.1.tgz",
"integrity": "sha512-p9chkvhcKrWn/H/1FFeVSqLdReGwn2qmiobOQGO3BifX+/vV/39qhY8dGqbdcPh1e6jxh/QHriLXr7a4eLFK4Q==",
"requires": {
"@react-leaflet/core": "^2.1.0"
}
},
"react-router": {
"version": "6.10.0",
"resolved": "https://registry.npmjs.org/react-router/-/react-router-6.10.0.tgz",

View File

@ -34,8 +34,11 @@
},
"dependencies": {
"axios": "1.3.6",
"leaflet": "1.9.4",
"prop-types": "15.8.1",
"react": "18.2.0",
"react-dom": "18.2.0",
"react-leaflet": "4.2.1",
"react-router-dom": "6.10.0"
}
}