[frontend]: Add non-data source conditional filters
This commit is contained in:
@ -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 PERMANENT_DATA_API = "https://a6y312dpuj.execute-api.us-east-1.amazonaws.com/permanent_data";
|
||||||
|
|
||||||
const dataSources = [
|
const dataSources = [
|
||||||
{ id: "IrishRailTrains", name: "Irish Rail Trains", url: `${TRANSIENT_DATA_API}?objectType=IrishRailTrain` },
|
{ id: "irish-rail-trains", name: "Irish Rail Trains", url: `${TRANSIENT_DATA_API}?objectType=IrishRailTrain` },
|
||||||
{ id: "IrishRailStations", name: "Irish Rail Stations", url: `${PERMANENT_DATA_API}?objectType=IrishRailStation` },
|
{ id: "irish-rail-stations", name: "Irish Rail Stations", url: `${PERMANENT_DATA_API}?objectType=IrishRailStation` },
|
||||||
{ id: "LuasStops", name: "Luas Stops", url: `${PERMANENT_DATA_API}?objectType=LuasStop` },
|
{ id: "luas-stops", name: "Luas Stops", url: `${PERMANENT_DATA_API}?objectType=LuasStop` },
|
||||||
{ id: "BusStops", name: "Bus Stops", url: `${PERMANENT_DATA_API}?objectType=BusStop` },
|
{ id: "bus-stops", name: "Bus Stops", url: `${PERMANENT_DATA_API}?objectType=BusStop` },
|
||||||
{ id: "Buses", name: "Buses", url: `${TRANSIENT_DATA_API}?objectType=Bus` },
|
{ id: "buses", name: "Buses", url: `${TRANSIENT_DATA_API}?objectType=Bus` },
|
||||||
];
|
];
|
||||||
|
|
||||||
function App() {
|
function App() {
|
||||||
@ -36,19 +36,37 @@ function App() {
|
|||||||
};
|
};
|
||||||
}, [searchInput]);
|
}, [searchInput]);
|
||||||
|
|
||||||
const fetchData = async () => {
|
const fetchData = async (enabledSources) => {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
try {
|
try {
|
||||||
const newMarkers = (await Promise.all(
|
const newMarkers = (await Promise.all(
|
||||||
dataSources
|
dataSources
|
||||||
.filter(({ id }) => selectedSources.includes(id))
|
.filter(({ id }) => enabledSources.includes(id))
|
||||||
.map(({ url }) => fetch(url).then((res) => res.json()))
|
.map(({ url }) => fetch(url).then((res) => res.json()))
|
||||||
))
|
))
|
||||||
.flat()
|
.flat()
|
||||||
.map((item) => {
|
.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 icon = item.objectType;
|
||||||
let popupContent;
|
let popupContent;
|
||||||
let objectTitle;
|
let objectTitle;
|
||||||
|
let display = false;
|
||||||
let markerText = "";
|
let markerText = "";
|
||||||
|
|
||||||
switch (item.objectType) {
|
switch (item.objectType) {
|
||||||
@ -77,21 +95,57 @@ function App() {
|
|||||||
case "R":
|
case "R":
|
||||||
trainStatus = "Running";
|
trainStatus = "Running";
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case "T":
|
||||||
|
trainStatus = "Terminated";
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "N":
|
||||||
|
trainStatus = "Not yet running";
|
||||||
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
trainStatus = "Not running";
|
trainStatus = "Unknown";
|
||||||
}
|
}
|
||||||
|
|
||||||
const splitMessage = item.trainPublicMessage.split("\\n");
|
const splitMessage = item.trainPublicMessage.split("\\n");
|
||||||
const match = splitMessage[1].match(/\((.*?)\)/);
|
const match = splitMessage[1].match(/(-?\d+)\s+mins\s+late/);
|
||||||
const punctuality = match ? match[1] : "N/A";
|
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
|
// set icon depending on lateness of train and type
|
||||||
if (trainStatus === "Not running") {
|
if (punctualityStr === "early") {
|
||||||
icon += "NotRunning";
|
latenessMessage = -punctuality + " minute" + (punctuality === 1 ? "" : "s") + " early";
|
||||||
} else if (punctuality.charAt(0) === "-" || punctuality.charAt(0) === "0") {
|
|
||||||
icon += "OnTime";
|
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 = (
|
popupContent = (
|
||||||
@ -103,12 +157,19 @@ function App() {
|
|||||||
<li><b>Status:</b> {trainStatus}</li>
|
<li><b>Status:</b> {trainStatus}</li>
|
||||||
<li><b>Direction:</b> {item.trainDirection}</li>
|
<li><b>Direction:</b> {item.trainDirection}</li>
|
||||||
<li><b>Update:</b> {splitMessage[2]}</li>
|
<li><b>Update:</b> {splitMessage[2]}</li>
|
||||||
<li><b>Punctuality:</b> {punctuality}</li>
|
<li><b>Punctuality:</b> {latenessMessage}</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
markerText = item.trainPublicMessage + " " + item.trainDirection;
|
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;
|
break;
|
||||||
|
|
||||||
case "IrishRailStation":
|
case "IrishRailStation":
|
||||||
@ -123,7 +184,10 @@ function App() {
|
|||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
markerText = item.trainStationCode + " " + item.trainStationDesc;
|
markerText = item.trainStationCode + " " + item.trainStationDesc;
|
||||||
|
display = (item.latitude !== "0" && item.longitude !== "0");
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case "Bus":
|
case "Bus":
|
||||||
@ -140,7 +204,10 @@ function App() {
|
|||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
markerText = item.busRouteAgencyName + " " + item.busRouteShortName + " " + item.busRouteLongName;
|
markerText = item.busRouteAgencyName + " " + item.busRouteShortName + " " + item.busRouteLongName;
|
||||||
|
display = (item.latitude !== "0" && item.longitude !== "0");
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case "BusStop":
|
case "BusStop":
|
||||||
@ -155,7 +222,10 @@ function App() {
|
|||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
markerText = item.busStopName;
|
markerText = item.busStopName;
|
||||||
|
display = (item.latitude !== "0" && item.longitude !== "0");
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case "LuasStop":
|
case "LuasStop":
|
||||||
@ -165,9 +235,11 @@ function App() {
|
|||||||
switch (item.luasStopLineID) {
|
switch (item.luasStopLineID) {
|
||||||
case "1":
|
case "1":
|
||||||
luasLine = "Green Line";
|
luasLine = "Green Line";
|
||||||
|
icon += "Green";
|
||||||
break;
|
break;
|
||||||
case "2":
|
case "2":
|
||||||
luasLine = "Red Line";
|
luasLine = "Red Line";
|
||||||
|
icon += "Red";
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
luasLine = "N/A";
|
luasLine = "N/A";
|
||||||
@ -175,7 +247,16 @@ function App() {
|
|||||||
popupContent = (
|
popupContent = (
|
||||||
<LuasPopup item={item} objectTitle={objectTitle} luasLine={luasLine} />
|
<LuasPopup item={item} objectTitle={objectTitle} luasLine={luasLine} />
|
||||||
);
|
);
|
||||||
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;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
@ -192,8 +273,10 @@ function App() {
|
|||||||
popup: popupContent,
|
popup: popupContent,
|
||||||
icon: icon,
|
icon: icon,
|
||||||
markerText: markerText.toLowerCase(),
|
markerText: markerText.toLowerCase(),
|
||||||
|
display: display
|
||||||
};
|
};
|
||||||
});
|
})
|
||||||
|
.filter((marker) => marker.display);
|
||||||
|
|
||||||
setMarkers(newMarkers);
|
setMarkers(newMarkers);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
@ -2,27 +2,153 @@ import React, { useState, useEffect } from "react";
|
|||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import Cookies from "js-cookie";
|
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 (
|
||||||
|
<div style={{ paddingLeft: `${level * 20}px` }}>
|
||||||
|
<div
|
||||||
|
key={item.id}
|
||||||
|
style={{
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
gap: "8px",
|
||||||
|
color: isDisabled ? "gray" : "black",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
id={item.id}
|
||||||
|
checked={isChecked}
|
||||||
|
onChange={handleCheckboxChange}
|
||||||
|
disabled={isDisabled} // Disable if any parent is not checked
|
||||||
|
/>
|
||||||
|
<label htmlFor={item.id} style={{ cursor: isDisabled ? "not-allowed" : "pointer" }}>
|
||||||
|
{item.name}
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
{hasChildren && (
|
||||||
|
<div>
|
||||||
|
{item.children.map((child) => (
|
||||||
|
<CheckboxItem
|
||||||
|
key={child.id}
|
||||||
|
item={child}
|
||||||
|
selectedSources={selectedSources}
|
||||||
|
setSelectedSources={setSelectedSources}
|
||||||
|
enabledSources={enabledSources}
|
||||||
|
setEnabledSources={setEnabledSources}
|
||||||
|
level={level + 1}
|
||||||
|
parentChecked={isEnabled} // Pass true only if all parents are checked
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
const Sidebar = ({ selectedSources, setSelectedSources, clusteringEnabled, setClusteringEnabled, fetchData }) => {
|
const Sidebar = ({ selectedSources, setSelectedSources, clusteringEnabled, setClusteringEnabled, fetchData }) => {
|
||||||
const [isOpen, setIsOpen] = useState(true);
|
const [isOpen, setIsOpen] = useState(true);
|
||||||
const dataSources = [
|
const [enabledSources, setEnabledSources] = useState([]); // New state to track enabled sources
|
||||||
{ 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" },
|
|
||||||
];
|
|
||||||
|
|
||||||
// Load selected sources from cookies on component mount
|
// Load selected sources from cookies or set all as default checked
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const savedSources = Cookies.get("selectedSources");
|
const savedSources = Cookies.get("selectedSources");
|
||||||
if (savedSources) {
|
if (savedSources) {
|
||||||
setSelectedSources(JSON.parse(savedSources));
|
setSelectedSources(JSON.parse(savedSources));
|
||||||
|
} else {
|
||||||
|
const allDefaultChecked = getAllDefaultCheckedIds(menuData);
|
||||||
|
setSelectedSources(allDefaultChecked);
|
||||||
}
|
}
|
||||||
}, [setSelectedSources]);
|
}, [setSelectedSources]);
|
||||||
|
|
||||||
const handleSubmit = () => {
|
const handleSubmit = () => {
|
||||||
Cookies.set("selectedSources", JSON.stringify(selectedSources), { expires: 7 });
|
Cookies.set("selectedSources", JSON.stringify(selectedSources), { expires: 7 });
|
||||||
fetchData();
|
fetchData(enabledSources); // Use enabledSources for data fetching
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -34,39 +160,22 @@ const Sidebar = ({ selectedSources, setSelectedSources, clusteringEnabled, setCl
|
|||||||
height: isOpen ? "auto" : "40px", display: "flex", flexDirection: "column",
|
height: isOpen ? "auto" : "40px", display: "flex", flexDirection: "column",
|
||||||
alignItems: "center", zIndex: 1000, overflow: "hidden", justifyContent: "center"
|
alignItems: "center", zIndex: 1000, overflow: "hidden", justifyContent: "center"
|
||||||
}}>
|
}}>
|
||||||
<button onClick={() => setIsOpen(!isOpen)} style={{
|
<button onClick={() => setIsOpen(!isOpen)} style={{ background: "none", border: "none", color: "black" }}>
|
||||||
background: "none", border: "none", color: "black",
|
|
||||||
fontSize: "16px", cursor: "pointer", display: "flex",
|
|
||||||
alignItems: "center", width: "100%", justifyContent: "center",
|
|
||||||
padding: "8px 10px", fontWeight: "bold"
|
|
||||||
}}>
|
|
||||||
{isOpen ? "▼ Filters" : "▶ Filters"}
|
{isOpen ? "▼ Filters" : "▶ Filters"}
|
||||||
</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>
|
<h3>Select Data Sources</h3>
|
||||||
{dataSources.map(({ id, name }) => (
|
{menuData.map((item) => (
|
||||||
<div key={id} style={{ display: "flex", alignItems: "center", gap: "8px" }}>
|
<CheckboxItem
|
||||||
<input
|
key={item.id}
|
||||||
type="checkbox"
|
item={item}
|
||||||
id={id}
|
selectedSources={selectedSources}
|
||||||
checked={selectedSources.includes(id)}
|
setSelectedSources={setSelectedSources}
|
||||||
onChange={() =>
|
enabledSources={enabledSources}
|
||||||
setSelectedSources((prev) => prev.includes(id) ? prev.filter((s) => s !== id) : [...prev, id])
|
setEnabledSources={setEnabledSources}
|
||||||
}
|
|
||||||
/>
|
|
||||||
<label htmlFor={id}>{name}</label>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
<div style={{ marginTop: "10px", display: "flex", alignItems: "center", gap: "8px" }}>
|
|
||||||
<input
|
|
||||||
type="checkbox"
|
|
||||||
id="toggleClustering"
|
|
||||||
checked={clusteringEnabled}
|
|
||||||
onChange={() => setClusteringEnabled(!clusteringEnabled)}
|
|
||||||
/>
|
/>
|
||||||
<label htmlFor="toggleClustering">Cluster overlapping icons</label>
|
))}
|
||||||
</div>
|
|
||||||
<button onClick={handleSubmit} style={{ marginTop: "10px" }}>Submit</button>
|
<button onClick={handleSubmit} style={{ marginTop: "10px" }}>Submit</button>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@ -82,4 +191,4 @@ Sidebar.propTypes = {
|
|||||||
fetchData: PropTypes.func.isRequired,
|
fetchData: PropTypes.func.isRequired,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default Sidebar;
|
export default Sidebar;
|
||||||
|
Reference in New Issue
Block a user