Changeset 0c6b92a for imaps-frontend/src/components/SearchBar
- Timestamp:
- 12/12/24 17:06:06 (5 weeks ago)
- Branches:
- main
- Parents:
- d565449
- Location:
- imaps-frontend/src/components/SearchBar
- Files:
-
- 2 edited
Legend:
- Unmodified
- Added
- Removed
-
imaps-frontend/src/components/SearchBar/SearchBar.jsx
rd565449 r0c6b92a 1 import React, { useState } from "react"; 1 import React, { useState, useEffect, useRef } from "react"; 2 import ReactDOM from "react-dom"; 2 3 import searchIcon from "../../assets/search_icon.png"; 3 4 import routeIcon from "../../assets/route_icon.png"; … … 5 6 import styles from "./SearchBar.module.css"; 6 7 7 function SearchBar( props) {8 function SearchBar({ map, handleDirectionsSubmit, setIsPanelOpen, setSelectedRoom, availableShapes,handleFloorChange }) { 8 9 const [isExpanded, setIsExpanded] = useState(false); 9 10 const [from, setFrom] = useState(""); 10 11 const [to, setTo] = useState(""); 12 const [availableOptions, setAvailableOptions] = useState([]); 13 const [filteredOptions, setFilteredOptions] = useState([]); 14 const [dropdownVisible, setDropdownVisible] = useState(false); 15 const [inputFieldType, setInputFieldType] = useState(""); 16 const dropdownRef = useRef(null); 11 17 12 18 const toggleExpanded = () => { … … 14 20 }; 15 21 16 function searchRoom(){ 17 props.map.search(); 22 function searchRoom() { 23 let foundRoom = availableShapes.find(sh => sh.info.name === from) 24 console.log("map fnum",map.floorNum) 25 if(foundRoom.floorNum !== map.floorNum){ 26 handleFloorChange(foundRoom.floorNum); 27 } 18 28 29 30 console.log("FOUND ROOM: " + foundRoom) 31 map.highlightShape(from); 32 setSelectedRoom(foundRoom); 33 setIsPanelOpen(true); 19 34 } 20 35 21 const handleDirectionsSubmit = () => { 22 console.log(`From: ${from}, To: ${to}`); 23 const url = new URL('http://localhost:8080/api/public/navigate'); 24 url.searchParams.append('from', from); 25 url.searchParams.append('to', to); 36 const handleInputFocus = (field) => { 37 if (availableOptions.length === 0 && map) { 38 setAvailableOptions( 39 availableShapes 40 .filter((sh) => sh.className === "RenderedRoom") 41 .map((shape) => shape.info.name) 42 ); 43 } 44 setDropdownVisible(true); 45 setInputFieldType(field); 46 }; 26 47 27 fetch(url) 28 .then(response => { 29 if (!response.ok) { 30 throw new Error('Network response was not ok'); 31 } 32 return response.json(); 33 }) 34 .then(data => { 35 console.log('Success:', data); 36 const points = data.map(item => item.coordinates); 37 props.map.drawRoute(points); 38 }) 39 .catch(error => { 40 console.error('Error:', error); 41 }); 48 const handleInputChange = (setter) => (event) => { 49 const value = event.target.value; 50 setter(value); 51 setDropdownVisible(true); 52 53 const filtered = availableOptions.filter((option) => 54 option.toLowerCase().includes(value.toLowerCase()) 55 ); 56 setFilteredOptions(filtered); 57 }; 58 59 const handleOptionClick = (option) => { 60 if (inputFieldType === "from") { 61 setFrom(option); 62 } else if (inputFieldType === "to") { 63 setTo(option); 64 } 65 setDropdownVisible(false); 66 }; 67 68 const renderDropdown = () => { 69 if (!dropdownVisible || filteredOptions.length === 0) return null; 70 71 const position = dropdownRef.current?.getBoundingClientRect() || { top: 0, left: 0, width: 0 }; 72 73 return ReactDOM.createPortal( 74 <ul 75 className={styles.dropdown} 76 style={{ 77 position: "absolute", 78 top: position.top + position.height, 79 left: position.left, 80 width: position.width, 81 }} 82 > 83 {filteredOptions.map((option, index) => ( 84 <li 85 key={index} 86 className={styles.dropdownItem} 87 onClick={() => handleOptionClick(option)} 88 > 89 {option} 90 </li> 91 ))} 92 </ul>, 93 document.body // Portal renders outside the parent hierarchy 94 ); 42 95 }; 43 96 44 97 return ( 45 <div className={styles.wrapper}> 46 {/* Regular search bar */} 47 {!isExpanded ? ( 48 <div className={styles.searchBar}> 49 <input 50 type="search" 51 className={styles.inputField} 52 placeholder="Search location" 53 aria-label="Search" 54 /> 55 <div className={styles.buttons}> 56 <button type="button" className={styles.iconButton} onClick={searchRoom}> 57 <img src={searchIcon} alt="Search Icon" /> 58 </button> 59 <button type="button" className={styles.iconButton} onClick={toggleExpanded}> 60 <img src={routeIcon} alt="Route Icon" /> 61 </button> 62 </div> 63 </div> 64 ) : ( 65 /* Expanded view for directions */ 66 <div className={styles.directionsContainer}> 67 <div className={styles.directionsInputs}> 68 <input 69 type="text" 70 placeholder="From" 71 aria-label="From" 72 value={from} 73 onChange={(e) => setFrom(e.target.value)} 74 className={styles.inputField} 75 /> 76 <input 77 type="text" 78 placeholder="To" 79 aria-label="To" 80 value={to} 81 onChange={(e) => setTo(e.target.value)} 82 className={styles.inputField} 83 /> 84 </div> 85 <div className={styles.buttons}> 86 <button type="button" className={styles.iconButton} onClick={handleDirectionsSubmit}> 87 <img src={searchIcon} alt="Submit Directions" /> 88 </button> 89 <button type="button" className={styles.iconButton} onClick={toggleExpanded}> 90 <img src={closeIcon} alt="Close Icon" /> 91 </button> 92 </div> 93 </div> 94 )} 95 </div> 98 <div className={styles.wrapper}> 99 {!isExpanded ? ( 100 <div className={styles.searchBar}> 101 <input 102 type="search" 103 className={styles.inputField} 104 placeholder="Search location" 105 aria-label="Search" 106 ref={dropdownRef} // Attach the input to calculate dropdown position 107 onFocus={() => handleInputFocus("from")} 108 onChange={handleInputChange(setFrom)} 109 value={from} 110 /> 111 {renderDropdown()} 112 <div className={styles.buttons}> 113 <button type="button" className={styles.iconButton} onClick={searchRoom}> 114 <img src={searchIcon} alt="Search Icon" /> 115 </button> 116 <button type="button" className={styles.iconButton} onClick={toggleExpanded}> 117 <img src={routeIcon} alt="Route Icon" /> 118 </button> 119 </div> 120 </div> 121 ) : ( 122 <div className={styles.directionsContainer}> 123 <div className={styles.directionsInputs}> 124 <input 125 type="text" 126 placeholder="From" 127 aria-label="From" 128 value={from} 129 onFocus={() => handleInputFocus("from")} 130 onChange={handleInputChange(setFrom)} 131 className={styles.inputField} 132 ref={inputFieldType === "from" ? dropdownRef : null} 133 /> 134 <input 135 type="text" 136 placeholder="To" 137 aria-label="To" 138 value={to} 139 onFocus={() => handleInputFocus("to")} 140 onChange={handleInputChange(setTo)} 141 className={styles.inputField} 142 ref={inputFieldType === "to" ? dropdownRef : null} 143 /> 144 {renderDropdown()} 145 </div> 146 <div className={styles.buttons}> 147 <button 148 type="button" 149 className={styles.iconButton} 150 onClick={() => handleDirectionsSubmit(from, to)} 151 > 152 <img src={searchIcon} alt="Submit Directions" /> 153 </button> 154 <button type="button" className={styles.iconButton} onClick={toggleExpanded}> 155 <img src={closeIcon} alt="Close Icon" /> 156 </button> 157 </div> 158 </div> 159 )} 160 </div> 96 161 ); 97 162 } -
imaps-frontend/src/components/SearchBar/SearchBar.module.css
rd565449 r0c6b92a 1 1 .wrapper { 2 position: relative; 2 3 display: flex; 3 4 justify-content: center; 4 margin: 0px auto; 5 margin: 0 auto; 6 z-index: 105; 5 7 max-width: 600px; 6 8 width: 100%; 9 padding: 10px; 7 10 } 8 11 9 12 .searchBar, 10 13 .directionsContainer { 14 position: relative; 11 15 display: flex; 12 align-items: end; /* Ensure all items remain aligned in the center */13 width: 100%;16 align-items: center; 17 /*width: 100%;*/ 14 18 background-color: white; 15 19 padding: 10px; … … 17 21 border-radius: 30px; 18 22 box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); 19 flex-wrap: nowrap; /* Prevent items from wrapping to the next line */23 flex-wrap: nowrap; 20 24 } 21 25 … … 28 32 outline: none; 29 33 margin-right: 10px; 30 min-width: 0; /* Ensure the input field remains flexible */ 34 min-width: 0; 35 31 36 } 37 38 .dropdown { 39 position: absolute; /* Position absolute to detach from wrapper flow */ 40 top: 100%; /* Place below the search bar */ 41 left: 0; 42 background-color: white; 43 border: 1px solid #ddd; 44 border-radius: 5px; 45 width: 100%; /* Match the search bar width */ 46 max-height: 200px; 47 overflow-y: auto; 48 z-index: 9999; /* Highest level to ensure it's above everything */ 49 box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2); 50 } 51 52 53 .dropdownItem { 54 padding: 10px; 55 cursor: pointer; 56 transition: background-color 0.3s ease, color 0.3s ease; 57 } 58 59 .dropdownItem:hover { 60 background-color: #007bff; /* Highlight color */ 61 color: white; /* Ensure text is readable */ 62 } 63 32 64 33 65 .buttons { … … 50 82 font-size: 16px; 51 83 transition: background-color 0.3s ease; 52 width: auto;53 84 } 54 85 … … 62 93 } 63 94 64 /* Expanded view for the direction inputs */65 95 .directionsContainer { 66 flex-direction: column;67 96 gap: 10px; 68 97 } … … 73 102 gap: 10px; 74 103 } 75 76 .directionsInputs .inputField {77 flex: 1;78 }79 80 /* Media query for responsive design */81 @media (max-width: 768px) {82 .wrapper {83 max-width: 100%;84 padding: 10px;85 }86 87 /* Keep buttons and input fields inline */88 .searchBar,89 .directionsContainer {90 flex-direction: row; /* Keep items in a row */91 align-items: center; /* Keep buttons and inputs on the same line */92 flex-wrap: nowrap; /* Prevent items from wrapping */93 padding: 8px;94 }95 96 .inputField {97 width: 100%;98 flex-grow: 1;99 margin-right: 10px;100 }101 102 .buttons {103 width: auto;104 gap: 5px;105 }106 107 .iconButton {108 width: auto;109 padding: 10px;110 }111 }
Note:
See TracChangeset
for help on using the changeset viewer.