[frontend]: Add navbar and statistics page
This commit is contained in:
68
frontend/package-lock.json
generated
68
frontend/package-lock.json
generated
@ -19,6 +19,7 @@
|
|||||||
"react-helmet": "^6.1.0",
|
"react-helmet": "^6.1.0",
|
||||||
"react-leaflet": "^5.0.0-rc.2",
|
"react-leaflet": "^5.0.0-rc.2",
|
||||||
"react-leaflet-markercluster": "^5.0.0-rc.0",
|
"react-leaflet-markercluster": "^5.0.0-rc.0",
|
||||||
|
"react-router-dom": "^7.3.0",
|
||||||
"tailwindcss": "^4.0.9"
|
"tailwindcss": "^4.0.9"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
@ -1349,6 +1350,12 @@
|
|||||||
"vite": "^5.2.0 || ^6"
|
"vite": "^5.2.0 || ^6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"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": {
|
"node_modules/@types/estree": {
|
||||||
"version": "1.0.6",
|
"version": "1.0.6",
|
||||||
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz",
|
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz",
|
||||||
@ -1831,6 +1838,15 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"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/cross-spawn": {
|
"node_modules/cross-spawn": {
|
||||||
"version": "7.0.6",
|
"version": "7.0.6",
|
||||||
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
|
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
|
||||||
@ -4124,6 +4140,46 @@
|
|||||||
"react-leaflet": "^5.0.0"
|
"react-leaflet": "^5.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/react-router": {
|
||||||
|
"version": "7.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-router/-/react-router-7.3.0.tgz",
|
||||||
|
"integrity": "sha512-466f2W7HIWaNXTKM5nHTqNxLrHTyXybm7R0eBlVSt0k/u55tTCDO194OIx/NrYD4TS5SXKTNekXfT37kMKUjgw==",
|
||||||
|
"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.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.3.0.tgz",
|
||||||
|
"integrity": "sha512-z7Q5FTiHGgQfEurX/FBinkOXhWREJIAB2RiU24lvcBa82PxUpwqvs/PAXb9lJyPjTs2jrl6UkLvCZVGJPeNuuQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"react-router": "7.3.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=20.0.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": ">=18",
|
||||||
|
"react-dom": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/reflect.getprototypeof": {
|
"node_modules/reflect.getprototypeof": {
|
||||||
"version": "1.0.10",
|
"version": "1.0.10",
|
||||||
"resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz",
|
"resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz",
|
||||||
@ -4305,6 +4361,12 @@
|
|||||||
"semver": "bin/semver.js"
|
"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/set-function-length": {
|
"node_modules/set-function-length": {
|
||||||
"version": "1.2.2",
|
"version": "1.2.2",
|
||||||
"resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz",
|
"resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz",
|
||||||
@ -4614,6 +4676,12 @@
|
|||||||
"node": ">=6"
|
"node": ">=6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"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": {
|
"node_modules/type-check": {
|
||||||
"version": "0.4.0",
|
"version": "0.4.0",
|
||||||
"resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
|
"resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
|
||||||
|
@ -21,6 +21,7 @@
|
|||||||
"react-helmet": "^6.1.0",
|
"react-helmet": "^6.1.0",
|
||||||
"react-leaflet": "^5.0.0-rc.2",
|
"react-leaflet": "^5.0.0-rc.2",
|
||||||
"react-leaflet-markercluster": "^5.0.0-rc.0",
|
"react-leaflet-markercluster": "^5.0.0-rc.0",
|
||||||
|
"react-router-dom": "^7.3.0",
|
||||||
"tailwindcss": "^4.0.9"
|
"tailwindcss": "^4.0.9"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
@ -1,8 +1,12 @@
|
|||||||
import React, { useState, useEffect, useMemo } from "react";
|
import React, { useState, useEffect, useMemo } from "react";
|
||||||
|
import { BrowserRouter as Router, Route, Routes } from "react-router-dom";
|
||||||
|
|
||||||
import Sidebar from "./components/Sidebar";
|
import Sidebar from "./components/Sidebar";
|
||||||
import MapComponent from "./components/MapComponent";
|
import MapComponent from "./components/MapComponent";
|
||||||
import LoadingOverlay from "./components/LoadingOverlay";
|
import LoadingOverlay from "./components/LoadingOverlay";
|
||||||
import LuasPopup from "./components/LuasPopup";
|
import LuasPopup from "./components/LuasPopup";
|
||||||
|
import Navbar from "./components/Navbar";
|
||||||
|
import Statistics from "./components/Statistics.jsx";
|
||||||
|
|
||||||
const TRANSIENT_DATA_API = "https://281bc6mcm5.execute-api.us-east-1.amazonaws.com/transient_data";
|
const TRANSIENT_DATA_API = "https://281bc6mcm5.execute-api.us-east-1.amazonaws.com/transient_data";
|
||||||
const PERMANENT_DATA_API = "https://a6y312dpuj.execute-api.us-east-1.amazonaws.com/permanent_data";
|
const PERMANENT_DATA_API = "https://a6y312dpuj.execute-api.us-east-1.amazonaws.com/permanent_data";
|
||||||
@ -300,11 +304,14 @@ function App() {
|
|||||||
}, [searchTerm, markers]);
|
}, [searchTerm, markers]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div style={{ height: "100vh", width: "100vw", display: "flex", position: "relative" }}>
|
<Router>
|
||||||
{loading && <LoadingOverlay message={"Loading data..."}/>}
|
<Navbar />
|
||||||
|
<Routes>
|
||||||
{/* SEARCH BOX */}
|
<Route
|
||||||
<div
|
path="/"
|
||||||
|
element={
|
||||||
|
<div style={{ height: "100vh", width: "100vw", display: "flex", position: "relative", paddingTop: "5vh" }}>
|
||||||
|
{loading && <LoadingOverlay message={"Loading data..."} />} <div
|
||||||
style={{
|
style={{
|
||||||
position: "absolute",
|
position: "absolute",
|
||||||
top: "1vh",
|
top: "1vh",
|
||||||
@ -322,6 +329,7 @@ function App() {
|
|||||||
placeholder="Search..."
|
placeholder="Search..."
|
||||||
style={{
|
style={{
|
||||||
width: "250px", fontSize: "16px",
|
width: "250px", fontSize: "16px",
|
||||||
|
top: "6vh", marginTop: "5vh",
|
||||||
padding: "10px", background: "rgba(255, 255, 255, 0.9)", color: "black",
|
padding: "10px", background: "rgba(255, 255, 255, 0.9)", color: "black",
|
||||||
borderRadius: "10px", overflow: "hidden"
|
borderRadius: "10px", overflow: "hidden"
|
||||||
}}
|
}}
|
||||||
@ -339,7 +347,11 @@ function App() {
|
|||||||
<MapComponent markers={filteredMarkers} clusteringEnabled={clusteringEnabled} />
|
<MapComponent markers={filteredMarkers} clusteringEnabled={clusteringEnabled} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<Route path="/statistics" element={<Statistics />} />
|
||||||
|
</Routes>
|
||||||
|
</Router>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default App;
|
export default App;
|
@ -41,7 +41,7 @@ const LuasPopup = ({ item, objectTitle, luasLine }) => {
|
|||||||
<li><b>Cycle & ride?:</b> {(item.luasStopIsCycleAndRide === "1") ? "Yes" : "No"}</li>
|
<li><b>Cycle & ride?:</b> {(item.luasStopIsCycleAndRide === "1") ? "Yes" : "No"}</li>
|
||||||
<li><b>Operational?:</b> {(item.luasStopIsEnabled === "1") ? "Yes" : "No"}</li>
|
<li><b>Operational?:</b> {(item.luasStopIsEnabled === "1") ? "Yes" : "No"}</li>
|
||||||
</ul>
|
</ul>
|
||||||
<button onClick={fetchLuasData} style={{ padding: "5px", marginTop: "5px", cursor: "pointer" }}>
|
<button onClick={fetchLuasData} style={{ padding: "5px", marginTop: "5px", cursor: "pointer", color: "white" }}>
|
||||||
Load Luas Schedule
|
Load Luas Schedule
|
||||||
</button>
|
</button>
|
||||||
<div dangerouslySetInnerHTML={{ __html: luasInfo }} style={{ marginTop: "10px" }}></div>
|
<div dangerouslySetInnerHTML={{ __html: luasInfo }} style={{ marginTop: "10px" }}></div>
|
||||||
|
71
frontend/src/components/Navbar.jsx
Normal file
71
frontend/src/components/Navbar.jsx
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
import React from "react";
|
||||||
|
import { Link } from "react-router-dom";
|
||||||
|
|
||||||
|
const Navbar = () => {
|
||||||
|
return (
|
||||||
|
<nav
|
||||||
|
style={{
|
||||||
|
position: "fixed",
|
||||||
|
top: "0",
|
||||||
|
right: "0",
|
||||||
|
left: "0",
|
||||||
|
height: "5vh",
|
||||||
|
background: "rgba(255, 255, 255, 0.9)",
|
||||||
|
color: "black",
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
justifyContent: "space-between",
|
||||||
|
padding: "0 20px",
|
||||||
|
transition: "height 0.2s ease-in-out, padding 0.2s ease-in-out",
|
||||||
|
zIndex: 1200,
|
||||||
|
boxShadow: "0 2px 5px rgba(0, 0, 0, 0.1)"
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div style={{display: "flex", alignItems: "center", gap: "10px"}}>
|
||||||
|
<img
|
||||||
|
src="/ticket.png" // Ensure this path is correct
|
||||||
|
alt="Iompar"
|
||||||
|
style={{width: "24px", height: "24px", borderRadius: "5px"}}
|
||||||
|
/>
|
||||||
|
<div style={{fontSize: "18px", fontWeight: "bold"}}>
|
||||||
|
<Link to="/" style={{textDecoration: "none", color: "black"}}>
|
||||||
|
Iompar
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style={{display: "flex", gap: "20px"}}>
|
||||||
|
<Link
|
||||||
|
to="/"
|
||||||
|
style={{
|
||||||
|
textDecoration: "none",
|
||||||
|
color: "black",
|
||||||
|
padding: "5px 10px",
|
||||||
|
borderRadius: "5px",
|
||||||
|
transition: "background 0.2s",
|
||||||
|
}}
|
||||||
|
onMouseEnter={(e) => (e.target.style.background = "rgba(0, 0, 0, 0.1)")}
|
||||||
|
onMouseLeave={(e) => (e.target.style.background = "transparent")}
|
||||||
|
>
|
||||||
|
Home
|
||||||
|
</Link>
|
||||||
|
<Link
|
||||||
|
to="/statistics"
|
||||||
|
style={{
|
||||||
|
textDecoration: "none",
|
||||||
|
color: "black",
|
||||||
|
padding: "5px 10px",
|
||||||
|
borderRadius: "5px",
|
||||||
|
transition: "background 0.2s",
|
||||||
|
}}
|
||||||
|
onMouseEnter={(e) => (e.target.style.background = "rgba(0, 0, 0, 0.1)")}
|
||||||
|
onMouseLeave={(e) => (e.target.style.background = "transparent")}
|
||||||
|
>
|
||||||
|
Statistics
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Navbar;
|
@ -153,7 +153,7 @@ const Sidebar = ({ selectedSources, setSelectedSources, clusteringEnabled, setCl
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div style={{
|
<div style={{
|
||||||
position: "absolute", top: window.innerWidth < 900 ? "8vh" : "1vh", right: "1vh",
|
position: "absolute", top: window.innerWidth < 900 ? "8vh" : "6vh", right: "1vh",
|
||||||
width: "250px", minWidth: "50px",
|
width: "250px", minWidth: "50px",
|
||||||
padding: isOpen ? "10px" : "5px 10px", background: "rgba(255, 255, 255, 0.9)", color: "black",
|
padding: isOpen ? "10px" : "5px 10px", background: "rgba(255, 255, 255, 0.9)", color: "black",
|
||||||
borderRadius: "10px", transition: "height 0.2s ease-in-out, padding 0.2s ease-in-out",
|
borderRadius: "10px", transition: "height 0.2s ease-in-out, padding 0.2s ease-in-out",
|
||||||
@ -165,7 +165,6 @@ const Sidebar = ({ selectedSources, setSelectedSources, clusteringEnabled, setCl
|
|||||||
</button>
|
</button>
|
||||||
{isOpen && (
|
{isOpen && (
|
||||||
<div style={{ display: "flex", flexDirection: "column", alignItems: "flex-start", width: "100%" }}>
|
<div style={{ display: "flex", flexDirection: "column", alignItems: "flex-start", width: "100%" }}>
|
||||||
<h3>Select Data Sources</h3>
|
|
||||||
{menuData.map((item) => (
|
{menuData.map((item) => (
|
||||||
<CheckboxItem
|
<CheckboxItem
|
||||||
key={item.id}
|
key={item.id}
|
||||||
@ -176,7 +175,7 @@ const Sidebar = ({ selectedSources, setSelectedSources, clusteringEnabled, setCl
|
|||||||
setEnabledSources={setEnabledSources}
|
setEnabledSources={setEnabledSources}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
<button onClick={handleSubmit} style={{ marginTop: "10px" }}>Submit</button>
|
<button onClick={handleSubmit} style={{ marginTop: "10px", color: "white" }}>Submit</button>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
12
frontend/src/components/Statistics.jsx
Normal file
12
frontend/src/components/Statistics.jsx
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
import React from "react";
|
||||||
|
|
||||||
|
const Statistics = () => {
|
||||||
|
return (
|
||||||
|
<div style={{height: "100vh", width: "100vw", display: "flex", position: "relative", paddingTop: "5vh"}}>
|
||||||
|
<h1 className="text-2xl font-bold mb-4">About This Application</h1>
|
||||||
|
<p>This application provides real-time and permanent data on Irish Rail, Luas, and bus services.</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Statistics;
|
@ -1,7 +1,5 @@
|
|||||||
/* @tailwind base;
|
@import "tailwindcss";
|
||||||
@tailwind components;
|
|
||||||
@tailwind utilities;
|
|
||||||
*/
|
|
||||||
:root {
|
:root {
|
||||||
font-family: system-ui, Avenir, Helvetica, Arial, sans-serif;
|
font-family: system-ui, Avenir, Helvetica, Arial, sans-serif;
|
||||||
line-height: 1.5;
|
line-height: 1.5;
|
||||||
@ -84,3 +82,16 @@ button:focus-visible {
|
|||||||
.leaflet-container {
|
.leaflet-container {
|
||||||
height: 100vh;
|
height: 100vh;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
h3 {
|
||||||
|
font-size: 1.4em;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
body, html, #root {
|
||||||
|
height: 100%;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
box-sizing: border-box;
|
||||||
|
overflow-x: hidden;
|
||||||
|
}
|
@ -1,7 +1,11 @@
|
|||||||
import { defineConfig } from 'vite'
|
import { defineConfig } from 'vite'
|
||||||
import react from '@vitejs/plugin-react-swc'
|
import react from '@vitejs/plugin-react-swc'
|
||||||
|
import tailwindcss from '@tailwindcss/vite'
|
||||||
|
|
||||||
// https://vite.dev/config/
|
// https://vite.dev/config/
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
plugins: [react()],
|
plugins: [
|
||||||
|
react(),
|
||||||
|
tailwindcss()
|
||||||
|
],
|
||||||
})
|
})
|
||||||
|
Reference in New Issue
Block a user