chore(ui): add train-station page
This commit is contained in:
parent
fc5458f435
commit
d0c3d3088d
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
</>
|
||||
);
|
||||
}
|
||||
};
|
|
@ -0,0 +1,5 @@
|
|||
import L from "leaflet";
|
||||
import iconUrl from "leaflet/dist/images/marker-icon.png";
|
||||
export const icon = L.icon({
|
||||
iconUrl,
|
||||
});
|
|
@ -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
|
||||
};
|
|
@ -0,0 +1,4 @@
|
|||
/*set up the height of*/
|
||||
.leaflet-container {
|
||||
height: 45vh;
|
||||
}
|
|
@ -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='© <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
|
||||
};
|
|
@ -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
|
||||
};
|
|
@ -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
|
||||
};
|
|
@ -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;
|
|
@ -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
|
||||
};
|
|
@ -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>
|
||||
</>
|
||||
);
|
||||
|
|
|
@ -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>
|
||||
</>
|
||||
);
|
||||
};
|
|
@ -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/>
|
||||
</>
|
||||
);
|
||||
};
|
|
@ -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",
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue