From 56a4bfe27e64e2640baec26fc211adf5f4814343 Mon Sep 17 00:00:00 2001 From: Andrew Date: Sun, 6 Apr 2025 03:48:14 +0100 Subject: [PATCH] [frontend]: Prevent user from deselecting all filters in a group --- frontend/package-lock.json | 14 ++++ frontend/package.json | 1 + frontend/src/App.jsx | 110 +++++++++++++++------------- frontend/src/components/Sidebar.jsx | 34 +++++++-- 4 files changed, 102 insertions(+), 57 deletions(-) diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 0937479..4b2fe99 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -23,6 +23,7 @@ "react-leaflet": "^5.0.0", "react-leaflet-markercluster": "^5.0.0-rc.0", "react-router-dom": "^7.3.0", + "react-toastify": "^11.0.5", "recharts": "^2.15.1", "tailwindcss": "^4.0.9" }, @@ -4459,6 +4460,19 @@ "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, + "node_modules/react-toastify": { + "version": "11.0.5", + "resolved": "https://registry.npmjs.org/react-toastify/-/react-toastify-11.0.5.tgz", + "integrity": "sha512-EpqHBGvnSTtHYhCPLxML05NLY2ZX0JURbAdNYa6BUkk+amz4wbKBQvoKQAB0ardvSarUBuY4Q4s1sluAzZwkmA==", + "license": "MIT", + "dependencies": { + "clsx": "^2.1.1" + }, + "peerDependencies": { + "react": "^18 || ^19", + "react-dom": "^18 || ^19" + } + }, "node_modules/react-transition-group": { "version": "4.4.5", "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz", diff --git a/frontend/package.json b/frontend/package.json index cfcf2cb..c66a9c5 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -25,6 +25,7 @@ "react-leaflet": "^5.0.0", "react-leaflet-markercluster": "^5.0.0-rc.0", "react-router-dom": "^7.3.0", + "react-toastify": "^11.0.5", "recharts": "^2.15.1", "tailwindcss": "^4.0.9" }, diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index 0defcb6..c72565c 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -2,6 +2,9 @@ import React, { useState, useEffect, useMemo, useRef } from "react"; import { BrowserRouter as Router, Route, Routes } from "react-router-dom"; import Cookies from "js-cookie"; +import { ToastContainer } from 'react-toastify'; +import 'react-toastify/dist/ReactToastify.css'; + import Navbar from "./components/Navbar"; import Statistics from "./components/Statistics.jsx"; import Help from "./components/Help.jsx"; @@ -441,62 +444,65 @@ function App() { }, [memoizedFilteredMarkers]); return ( - - - - - {loading && } -
- handleSearchChange(e)} - placeholder="Search..." + <> + + + + + {loading && } +
-
- -
- + handleSearchChange(e)} + placeholder="Search..." + style={{ + width: "250px", fontSize: "16px", + top: "6vh", marginTop: "5vh", + padding: "10px", background: "rgba(255, 255, 255, 0.9)", color: "black", + borderRadius: "10px", overflow: "hidden" + }} /> +
+ +
+ +
- - } - /> - } /> - } /> -
-
+ } + /> + } /> + } /> + + + + ); } export default App; \ No newline at end of file diff --git a/frontend/src/components/Sidebar.jsx b/frontend/src/components/Sidebar.jsx index f7eb267..e5beb72 100644 --- a/frontend/src/components/Sidebar.jsx +++ b/frontend/src/components/Sidebar.jsx @@ -1,6 +1,7 @@ import React, { useState, useEffect, useRef } from "react"; import PropTypes from 'prop-types'; import Cookies from "js-cookie"; +import { toast } from 'react-toastify'; const menuData = [ { @@ -19,7 +20,7 @@ const menuData = [ { id: "terminated", name: "Terminated", endsec: true }, { id: "early", name: "Early" }, { id: "on-time", name: "On-time" }, - { id: "late", name: "Late" }, + { id: "late", name: "Late", endsec: true }, ], }, { id: "irish-rail-stations", name: "Irish Rail Stations" }, @@ -42,11 +43,22 @@ const menuData = [ { id: "enabled", name: "Enabled" }, { id: "disabled", name: "Disabled", endsec: true }, { id: "park-and-ride", name: "Must be Park & Ride" }, - { id: "cycle-and-ride", name: "Must be Cycle & Ride" }, + { id: "cycle-and-ride", name: "Must be Cycle & Ride", endsec: true }, ], }, ]; +const sectionGroups = [ + ["irish-rail", "bus", "luas-stops"], + ["irish-rail-trains", "irish-rail-stations"], + ["mainline", "suburban", "dart"], + ["running", "not-yet-running", "terminated"], + ["early", "on-time", "late"], + ["buses", "bus-stops"], + ["red-line", "green-line"], + ["enabled", "disabled"], +]; + const customDefaultChecked = ["mainline","suburban","dart","running","not-yet-running","terminated","early","on-time","late","disabled","buses","irish-rail-trains","luas-stops","enabled","green-line","red-line","irish-rail","bus"] const getAllDefaultCheckedIds = (data) => { @@ -66,14 +78,26 @@ const getAllDefaultCheckedIds = (data) => { }; const CheckboxItem = ({ item, selectedSources, setSelectedSources, enabledSources, setEnabledSources, level = 0, parentChecked = true }) => { - console.log("item id: " + item.id); - console.log(selectedSources.includes(item.id)); - 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 = () => { + if (isChecked) { + if (item.id != "park-and-ride" && item.id != "cycle-and-ride") { + // Find which section this item is in + const section = sectionGroups.find(group => group.includes(item.id)); + + if (section.length > 1) { + const selectedInSection = section.filter(id => selectedSources.includes(id)); + if (selectedInSection.length === 1 && selectedInSection[0] === item.id) { + toast.warn("At least one item in this section must be selected"); + return; // Don't allow unchecking the last one + } + } + } + } + setSelectedSources((prev) => isChecked ? prev.filter((id) => id !== item.id)