Ignore:
Timestamp:
12/12/24 17:06:06 (5 weeks ago)
Author:
stefan toskovski <stefantoska84@…>
Branches:
main
Parents:
d565449
Message:

Pred finalna verzija

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";
     1import React, { useState, useEffect, useRef } from "react";
     2import ReactDOM from "react-dom";
    23import searchIcon from "../../assets/search_icon.png";
    34import routeIcon from "../../assets/route_icon.png";
     
    56import styles from "./SearchBar.module.css";
    67
    7 function SearchBar(props) {
     8function SearchBar({ map, handleDirectionsSubmit, setIsPanelOpen, setSelectedRoom, availableShapes,handleFloorChange }) {
    89  const [isExpanded, setIsExpanded] = useState(false);
    910  const [from, setFrom] = useState("");
    1011  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);
    1117
    1218  const toggleExpanded = () => {
     
    1420  };
    1521
    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    }
    1828
     29
     30    console.log("FOUND ROOM: " + foundRoom)
     31    map.highlightShape(from);
     32    setSelectedRoom(foundRoom);
     33    setIsPanelOpen(true);
    1934  }
    2035
    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  };
    2647
    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    );
    4295  };
    4396
    4497  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>
    96161  );
    97162}
  • imaps-frontend/src/components/SearchBar/SearchBar.module.css

    rd565449 r0c6b92a  
    11.wrapper {
     2  position: relative;
    23  display: flex;
    34  justify-content: center;
    4   margin: 0px auto;
     5  margin: 0 auto;
     6  z-index: 105;
    57  max-width: 600px;
    68  width: 100%;
     9  padding: 10px;
    710}
    811
    912.searchBar,
    1013.directionsContainer {
     14  position: relative;
    1115  display: flex;
    12   align-items: end; /* Ensure all items remain aligned in the center */
    13   width: 100%;
     16  align-items: center;
     17  /*width: 100%;*/
    1418  background-color: white;
    1519  padding: 10px;
     
    1721  border-radius: 30px;
    1822  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;
    2024}
    2125
     
    2832  outline: none;
    2933  margin-right: 10px;
    30   min-width: 0; /* Ensure the input field remains flexible */
     34  min-width: 0;
     35
    3136}
     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
    3264
    3365.buttons {
     
    5082  font-size: 16px;
    5183  transition: background-color 0.3s ease;
    52   width: auto;
    5384}
    5485
     
    6293}
    6394
    64 /* Expanded view for the direction inputs */
    6595.directionsContainer {
    66   flex-direction: column;
    6796  gap: 10px;
    6897}
     
    73102  gap: 10px;
    74103}
    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.