diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx
index d271a25..5396588 100644
--- a/frontend/src/App.jsx
+++ b/frontend/src/App.jsx
@@ -8,11 +8,11 @@ const TRANSIENT_DATA_API = "https://281bc6mcm5.execute-api.us-east-1.amazonaws.c
const PERMANENT_DATA_API = "https://a6y312dpuj.execute-api.us-east-1.amazonaws.com/permanent_data";
const dataSources = [
- { id: "IrishRailTrains", name: "Irish Rail Trains", url: `${TRANSIENT_DATA_API}?objectType=IrishRailTrain` },
- { id: "IrishRailStations", name: "Irish Rail Stations", url: `${PERMANENT_DATA_API}?objectType=IrishRailStation` },
- { id: "LuasStops", name: "Luas Stops", url: `${PERMANENT_DATA_API}?objectType=LuasStop` },
- { id: "BusStops", name: "Bus Stops", url: `${PERMANENT_DATA_API}?objectType=BusStop` },
- { id: "Buses", name: "Buses", url: `${TRANSIENT_DATA_API}?objectType=Bus` },
+ { id: "irish-rail-trains", name: "Irish Rail Trains", url: `${TRANSIENT_DATA_API}?objectType=IrishRailTrain` },
+ { id: "irish-rail-stations", name: "Irish Rail Stations", url: `${PERMANENT_DATA_API}?objectType=IrishRailStation` },
+ { id: "luas-stops", name: "Luas Stops", url: `${PERMANENT_DATA_API}?objectType=LuasStop` },
+ { id: "bus-stops", name: "Bus Stops", url: `${PERMANENT_DATA_API}?objectType=BusStop` },
+ { id: "buses", name: "Buses", url: `${TRANSIENT_DATA_API}?objectType=Bus` },
];
function App() {
@@ -36,19 +36,37 @@ function App() {
};
}, [searchInput]);
- const fetchData = async () => {
+ const fetchData = async (enabledSources) => {
setLoading(true);
try {
const newMarkers = (await Promise.all(
dataSources
- .filter(({ id }) => selectedSources.includes(id))
+ .filter(({ id }) => enabledSources.includes(id))
.map(({ url }) => fetch(url).then((res) => res.json()))
))
.flat()
.map((item) => {
+ const showMainline = enabledSources.includes("mainline");
+ const showSuburban = enabledSources.includes("suburban");
+ const showDart = enabledSources.includes("dart");
+ const showRunning = enabledSources.includes("running");
+ const showNotYetRunning = enabledSources.includes("not-yet-running");
+ const showTerminated = enabledSources.includes("terminated");
+ const showEarly = enabledSources.includes("early");
+ const showOnTime = enabledSources.includes("on-time");
+ const showLate = enabledSources.includes("late");
+
+ const showRedLine = enabledSources.includes("red-line");
+ const showGreenLine = enabledSources.includes("green-line");
+ const showParkAndRide = enabledSources.includes("park-and-ride");
+ const showCycleAndRide = enabledSources.includes("cycle-and-ride");
+ const showEnabled = enabledSources.includes("enabled");
+ const showDisabled = enabledSources.includes("disabled");
+
let icon = item.objectType;
let popupContent;
let objectTitle;
+ let display = false;
let markerText = "";
switch (item.objectType) {
@@ -77,21 +95,57 @@ function App() {
case "R":
trainStatus = "Running";
break;
+
+ case "T":
+ trainStatus = "Terminated";
+ break;
+
+ case "N":
+ trainStatus = "Not yet running";
+ break;
+
default:
- trainStatus = "Not running";
+ trainStatus = "Unknown";
}
const splitMessage = item.trainPublicMessage.split("\\n");
- const match = splitMessage[1].match(/\((.*?)\)/);
- const punctuality = match ? match[1] : "N/A";
+ const match = splitMessage[1].match(/(-?\d+)\s+mins\s+late/);
+ const punctuality = match ? parseInt(match[1], 10) : NaN;
+ let latenessMessage;
+ let punctualityStr;
+
+ if (punctuality < 0) {
+ punctualityStr = "early";
+ } else if (punctuality === 0) {
+ punctualityStr = "On time";
+ } else if (punctuality > 0) {
+ punctualityStr = "late";
+ } else {
+ punctualityStr = "N/A";
+ }
// set icon depending on lateness of train and type
- if (trainStatus === "Not running") {
- icon += "NotRunning";
- } else if (punctuality.charAt(0) === "-" || punctuality.charAt(0) === "0") {
+ if (punctualityStr === "early") {
+ latenessMessage = -punctuality + " minute" + (punctuality === 1 ? "" : "s") + " early";
icon += "OnTime";
- } else {
- icon += "Late";
+ }
+ else if (punctualityStr === "On time") {
+ latenessMessage = punctualityStr;
+ icon += "OnTime";
+ }
+ else if (punctualityStr === "late") {
+ latenessMessage = punctuality + " minute" + (punctuality === 1 ? "" : "s") + " late";
+
+ if (trainStatus === "Running") {
+ icon += "Late";
+ }
+ else {
+ icon += "NotRunning";
+ }
+ }
+ else {
+ latenessMessage = "On time";
+ icon += "NotRunning";
}
popupContent = (
@@ -103,12 +157,19 @@ function App() {
Status: {trainStatus}
Direction: {item.trainDirection}
Update: {splitMessage[2]}
- Punctuality: {punctuality}
+ Punctuality: {latenessMessage}
);
markerText = item.trainPublicMessage + " " + item.trainDirection;
+ display =
+ ((item.latitude !== "0" && item.longitude !== "0") && // filter out trains with no location data
+ ((showMainline && trainType == "Mainline") || (showSuburban && trainType == "Suburban") || (showDart && trainType == "DART")) &&
+ ((showRunning && trainStatus == "Running") || (showNotYetRunning && trainStatus == "Not yet running") || (showTerminated && trainStatus == "Terminated")) &&
+ ((trainStatus == "Running" && showEarly && punctualityStr == "early") || (trainStatus == "Running" && showOnTime && punctualityStr == "On time") || (trainStatus == "Running" && showLate && punctualityStr == "late")
+ || (trainStatus == "Not yet running" && showNotYetRunning) || (trainStatus == "Terminated" && showTerminated)));
+
break;
case "IrishRailStation":
@@ -123,7 +184,10 @@ function App() {
);
+
markerText = item.trainStationCode + " " + item.trainStationDesc;
+ display = (item.latitude !== "0" && item.longitude !== "0");
+
break;
case "Bus":
@@ -140,7 +204,10 @@ function App() {
);
+
markerText = item.busRouteAgencyName + " " + item.busRouteShortName + " " + item.busRouteLongName;
+ display = (item.latitude !== "0" && item.longitude !== "0");
+
break;
case "BusStop":
@@ -155,7 +222,10 @@ function App() {
);
+
markerText = item.busStopName;
+ display = (item.latitude !== "0" && item.longitude !== "0");
+
break;
case "LuasStop":
@@ -165,9 +235,11 @@ function App() {
switch (item.luasStopLineID) {
case "1":
luasLine = "Green Line";
+ icon += "Green";
break;
case "2":
luasLine = "Red Line";
+ icon += "Red";
break;
default:
luasLine = "N/A";
@@ -175,7 +247,16 @@ function App() {
popupContent = (
);
- markerText = item.luasStopIrishName + " " + item.luasStopName;
+
+ markerText = item.luasStopIrishName + " " + item.luasStopName + " " + luasLine;
+ display = (
+ (item.latitude !== "0" && item.longitude !== "0") &&
+ (showGreenLine && luasLine === "Green Line" || showRedLine && luasLine === "Red Line") &&
+ (showEnabled && item.luasStopIsEnabled === "1" || showDisabled && item.luasStopIsEnabled === "0") &&
+ (!showCycleAndRide || (showCycleAndRide && item.luasStopIsCycleAndRide === "1")) &&
+ (!showParkAndRide || (showParkAndRide && item.luasStopIsParkAndRide === "1"))
+ );
+
break;
default:
@@ -192,8 +273,10 @@ function App() {
popup: popupContent,
icon: icon,
markerText: markerText.toLowerCase(),
+ display: display
};
- });
+ })
+ .filter((marker) => marker.display);
setMarkers(newMarkers);
} catch (error) {
diff --git a/frontend/src/components/Sidebar.jsx b/frontend/src/components/Sidebar.jsx
index ceb7b05..ad14e9b 100644
--- a/frontend/src/components/Sidebar.jsx
+++ b/frontend/src/components/Sidebar.jsx
@@ -2,27 +2,153 @@ import React, { useState, useEffect } from "react";
import PropTypes from 'prop-types';
import Cookies from "js-cookie";
+const menuData = [
+ {
+ id: "irish-rail",
+ name: "Irish Rail",
+ children: [
+ {
+ id: "irish-rail-trains",
+ name: "Irish Rail Trains",
+ children: [
+ { id: "mainline", name: "Mainline" },
+ { id: "suburban", name: "Suburban" },
+ { id: "dart", name: "DART" },
+ { id: "running", name: "Running" },
+ { id: "not-yet-running", name: "Not yet running" },
+ { id: "terminated", name: "Terminated" },
+ { id: "early", name: "Early" },
+ { id: "on-time", name: "On-time" },
+ { id: "late", name: "Late" },
+ ],
+ },
+ { id: "irish-rail-stations", name: "Irish Rail Stations" },
+ ],
+ },
+ {
+ id: "bus",
+ name: "Bus",
+ children: [
+ { id: "buses", name: "Buses" },
+ { id: "bus-stops", name: "Bus Stops" },
+ ],
+ },
+ {
+ id: "luas-stops",
+ name: "Luas Stops",
+ children: [
+ { id: "red-line", name: "Red Line" },
+ { id: "green-line", name: "Green Line" },
+ { id: "enabled", name: "Enabled" },
+ { id: "disabled", name: "Disabled" },
+ { id: "park-and-ride", name: "Must be Park & Ride" },
+ { id: "cycle-and-ride", name: "Must be Cycle & Ride" },
+ ],
+ },
+];
+
+const getAllDefaultCheckedIds = (data) => {
+ const ids = [];
+ const traverse = (items, isTopLevel = true) => {
+ items.forEach((item) => {
+ if (!isTopLevel && item.id !== "cycle-and-ride" && item.id !== "park-and-ride") {
+ ids.push(item.id); // Check non-top-level items by default
+ }
+ if (item.children) {
+ traverse(item.children, false); // Child items are not top-level
+ }
+ });
+ };
+ traverse(data);
+ return ids;
+};
+
+const CheckboxItem = ({ item, selectedSources, setSelectedSources, enabledSources, setEnabledSources, level = 0, parentChecked = true }) => {
+ const isChecked = selectedSources.includes(item.id);
+ const isDisabled = !parentChecked; // Disable if any parent is not checked
+ const isEnabled = isChecked && parentChecked; // Only enabled if checked and parent is checked
+
+ const handleCheckboxChange = () => {
+ setSelectedSources((prev) =>
+ isChecked
+ ? prev.filter((id) => id !== item.id)
+ : [...prev, item.id]
+ );
+ };
+
+ // Track enabled sources based on parent and own state
+ useEffect(() => {
+ setEnabledSources((prev) => {
+ const newEnabledSources = new Set(prev);
+ if (isEnabled) newEnabledSources.add(item.id);
+ else newEnabledSources.delete(item.id);
+ return Array.from(newEnabledSources);
+ });
+ }, [isEnabled, item.id, setEnabledSources]);
+
+ const hasChildren = item.children && item.children.length > 0;
+ const isTopLevel = level === 0;
+
+ return (
+
+
+
+
+
+ {hasChildren && (
+
+ {item.children.map((child) => (
+
+ ))}
+
+ )}
+
+ );
+};
+
const Sidebar = ({ selectedSources, setSelectedSources, clusteringEnabled, setClusteringEnabled, fetchData }) => {
const [isOpen, setIsOpen] = useState(true);
- const dataSources = [
- { id: "IrishRailTrains", name: "Irish Rail Trains" },
- { id: "IrishRailStations", name: "Irish Rail Stations" },
- { id: "LuasStops", name: "Luas Stops" },
- { id: "BusStops", name: "Bus Stops" },
- { id: "Buses", name: "Buses" },
- ];
+ const [enabledSources, setEnabledSources] = useState([]); // New state to track enabled sources
- // Load selected sources from cookies on component mount
+ // Load selected sources from cookies or set all as default checked
useEffect(() => {
const savedSources = Cookies.get("selectedSources");
if (savedSources) {
setSelectedSources(JSON.parse(savedSources));
+ } else {
+ const allDefaultChecked = getAllDefaultCheckedIds(menuData);
+ setSelectedSources(allDefaultChecked);
}
}, [setSelectedSources]);
const handleSubmit = () => {
Cookies.set("selectedSources", JSON.stringify(selectedSources), { expires: 7 });
- fetchData();
+ fetchData(enabledSources); // Use enabledSources for data fetching
};
return (
@@ -34,39 +160,22 @@ const Sidebar = ({ selectedSources, setSelectedSources, clusteringEnabled, setCl
height: isOpen ? "auto" : "40px", display: "flex", flexDirection: "column",
alignItems: "center", zIndex: 1000, overflow: "hidden", justifyContent: "center"
}}>
-