[frontend]: Usability tips
This commit is contained in:
@ -41,6 +41,7 @@ const defaultFavourites = {
|
|||||||
function App() {
|
function App() {
|
||||||
const [favourites, setFavourites] = useState(defaultFavourites);
|
const [favourites, setFavourites] = useState(defaultFavourites);
|
||||||
const [showFaovouritesOnly, setShowFavouritesOnly] = useState(false);
|
const [showFaovouritesOnly, setShowFavouritesOnly] = useState(false);
|
||||||
|
const searchInputRef = useRef(null);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
try {
|
try {
|
||||||
@ -108,6 +109,22 @@ function App() {
|
|||||||
}
|
}
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const handleKeyDown = (e) => {
|
||||||
|
if ((e.ctrlKey || e.metaKey) && e.key === "k") {
|
||||||
|
e.preventDefault(); // prevent browser search
|
||||||
|
if (searchInputRef.current) {
|
||||||
|
searchInputRef.current.focus();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
window.addEventListener("keydown", handleKeyDown);
|
||||||
|
return () => {
|
||||||
|
window.removeEventListener("keydown", handleKeyDown);
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
const handleSearchChange = (e) => {
|
const handleSearchChange = (e) => {
|
||||||
const value = e.target.value;
|
const value = e.target.value;
|
||||||
let timeout = 300;
|
let timeout = 300;
|
||||||
@ -451,31 +468,59 @@ function App() {
|
|||||||
<Route
|
<Route
|
||||||
path="/"
|
path="/"
|
||||||
element={
|
element={
|
||||||
<div style={{ height: "100vh", width: "100vw", display: "flex", position: "relative", paddingTop: "5vh" }}>
|
<div style={{
|
||||||
{loading && <LoadingOverlay message={"Loading data..."} />}
|
height: "100vh",
|
||||||
|
width: "100vw",
|
||||||
|
display: "flex",
|
||||||
|
position: "relative",
|
||||||
|
paddingTop: "5vh"
|
||||||
|
}}>
|
||||||
|
{loading && <LoadingOverlay message={"Loading data..."}/>}
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
position: "absolute",
|
position: "absolute",
|
||||||
top: "1vh",
|
top: "6vh",
|
||||||
height: "5vh",
|
height: "5vh",
|
||||||
width: "250px", minWidth: "50px",
|
width: "250px", minWidth: "50px",
|
||||||
left: "50%",
|
left: "50%",
|
||||||
transform: "translateX(-50%)",
|
transform: "translateX(-50%)",
|
||||||
zIndex: 1000,
|
zIndex: 1000,
|
||||||
...(window.innerWidth < 800 ? { top: "auto", bottom: "10vh" } : {})
|
...(window.innerWidth < 800 ? {top: "auto", bottom: "4vh"} : {})
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<input
|
<div style={{position: "relative"}}>
|
||||||
type="text"
|
<input
|
||||||
onChange={(e) => handleSearchChange(e)}
|
ref={searchInputRef}
|
||||||
placeholder="Search..."
|
type="text"
|
||||||
style={{
|
onChange={(e) => handleSearchChange(e)}
|
||||||
width: "250px", fontSize: "16px",
|
placeholder="Search..."
|
||||||
top: "6vh", marginTop: "5vh",
|
style={{
|
||||||
padding: "10px", background: "rgba(255, 255, 255, 0.9)", color: "black",
|
width: "100%",
|
||||||
borderRadius: "10px", overflow: "hidden"
|
fontSize: "16px",
|
||||||
}}
|
padding: "10px 40px 10px 10px", // space for badge
|
||||||
/>
|
background: "rgba(255, 255, 255, 0.9)",
|
||||||
|
color: "black",
|
||||||
|
borderRadius: "10px",
|
||||||
|
border: "1px solid #ccc",
|
||||||
|
overflow: "hidden",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<span style={{
|
||||||
|
position: "absolute",
|
||||||
|
right: "10px",
|
||||||
|
top: "50%",
|
||||||
|
transform: "translateY(-50%)",
|
||||||
|
background: "#f0f0f0",
|
||||||
|
border: "1px solid #ccc",
|
||||||
|
borderRadius: "6px",
|
||||||
|
fontSize: "12px",
|
||||||
|
padding: "2px 6px",
|
||||||
|
color: "#333",
|
||||||
|
fontFamily: "monospace"
|
||||||
|
}}>
|
||||||
|
Ctrl+K
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<Sidebar
|
<Sidebar
|
||||||
selectedSources={selectedSources}
|
selectedSources={selectedSources}
|
||||||
@ -486,23 +531,25 @@ function App() {
|
|||||||
userLocationAvailable={userLocationAvailable}
|
userLocationAvailable={userLocationAvailable}
|
||||||
showFavouritesOnly={showFaovouritesOnly}
|
showFavouritesOnly={showFaovouritesOnly}
|
||||||
setShowFavouritesOnly={setShowFavouritesOnly}
|
setShowFavouritesOnly={setShowFavouritesOnly}
|
||||||
|
favourites={favourites}
|
||||||
/>
|
/>
|
||||||
<div style={{ flex: 1 }}>
|
<div style={{flex: 1}}>
|
||||||
<MapComponent
|
<MapComponent
|
||||||
markers={filteredMarkers}
|
markers={filteredMarkers}
|
||||||
clusteringEnabled={clusteringEnabled}
|
clusteringEnabled={clusteringEnabled}
|
||||||
userLocationAvailable={userLocationAvailable}
|
userLocationAvailable={userLocationAvailable}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<Route path="/statistics" element={<Statistics />} />
|
<Route path="/statistics" element={<Statistics/>}/>
|
||||||
<Route path="/help" element={<Help />} />
|
<Route path="/help" element={<Help/>}/>
|
||||||
</Routes>
|
</Routes>
|
||||||
</Router>
|
</Router>
|
||||||
<ToastContainer position="bottom-right"/>
|
<ToastContainer position="bottom-right"/>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default App;
|
export default App;
|
@ -138,6 +138,19 @@ const CheckboxItem = ({ item, selectedSources, setSelectedSources, enabledSource
|
|||||||
/>
|
/>
|
||||||
<label htmlFor={item.id} style={{ cursor: isDisabled ? "not-allowed" : "pointer" }}>
|
<label htmlFor={item.id} style={{ cursor: isDisabled ? "not-allowed" : "pointer" }}>
|
||||||
{item.name}
|
{item.name}
|
||||||
|
{item.id === "luas-stops" && (
|
||||||
|
<span
|
||||||
|
title="To view live Luas data, click on a stop and click 'Load Inbound/Outbound Trams'"
|
||||||
|
style={{
|
||||||
|
display: "inline-block",
|
||||||
|
color: "#666",
|
||||||
|
fontSize: "14px",
|
||||||
|
cursor: "help",
|
||||||
|
marginLeft: "4px"
|
||||||
|
}}
|
||||||
|
> 🛈
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
{hasChildren && (
|
{hasChildren && (
|
||||||
@ -164,7 +177,7 @@ const CheckboxItem = ({ item, selectedSources, setSelectedSources, enabledSource
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const Sidebar = ({ selectedSources, setSelectedSources, clusteringEnabled, setClusteringEnabled, fetchData, userLocationAvailable, showFavouritesOnly, setShowFavouritesOnly }) => {
|
const Sidebar = ({ selectedSources, setSelectedSources, clusteringEnabled, setClusteringEnabled, fetchData, userLocationAvailable, showFavouritesOnly, setShowFavouritesOnly, favourites }) => {
|
||||||
const [isOpen, setIsOpen] = useState(true);
|
const [isOpen, setIsOpen] = useState(true);
|
||||||
const [enabledSources, setEnabledSources] = useState([]); // New state to track enabled sources
|
const [enabledSources, setEnabledSources] = useState([]); // New state to track enabled sources
|
||||||
const [numberInputValue, setNumberInputValue] = useState(""); // State to manage number input value
|
const [numberInputValue, setNumberInputValue] = useState(""); // State to manage number input value
|
||||||
@ -196,6 +209,12 @@ const Sidebar = ({ selectedSources, setSelectedSources, clusteringEnabled, setCl
|
|||||||
const handleSubmit = () => {
|
const handleSubmit = () => {
|
||||||
Cookies.set("selectedSources", JSON.stringify(selectedSources), { expires: 365 });
|
Cookies.set("selectedSources", JSON.stringify(selectedSources), { expires: 365 });
|
||||||
Cookies.set("numberInputValue", numberInputValue, { expires: 365 }); // Save numberInputValue to cookie
|
Cookies.set("numberInputValue", numberInputValue, { expires: 365 }); // Save numberInputValue to cookie
|
||||||
|
|
||||||
|
if (showFavouritesOnly && (!favourites.length || favourites.length < 1)) {
|
||||||
|
toast.warn("You haven't added any favourites yet!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
fetchData(enabledSources, numberInputValue); // Use enabledSources for data fetching
|
fetchData(enabledSources, numberInputValue); // Use enabledSources for data fetching
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user