-
Notifications
You must be signed in to change notification settings - Fork 3
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
#266 implement search functionality fe #271
Changes from 4 commits
bd98124
b48a398
63a5006
8d3cfc3
d65e2a7
a15ef48
5d1111a
1a18900
38adb47
21b28bd
77bc849
72c3028
d678197
0bb7e00
9bd0cd1
97acbc4
b1a06a9
4378e56
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,15 +1,39 @@ | ||
import css from './SearchBox.module.css'; | ||
import { useState } from 'react'; | ||
import icon_search from './search-icon.svg'; | ||
import { useNavigate } from 'react-router-dom'; | ||
import css from './SearchBox.module.css'; | ||
|
||
function SearchBox() { | ||
const navigate = useNavigate(); | ||
|
||
const [searchTerm, setSearchTerm] = useState(''); | ||
const searchPage = 'search'; | ||
|
||
function SearchBox () { | ||
return ( | ||
<div className={css['header-search-box']}> | ||
<div className={css['header-search-form']}> | ||
<input className={css['header-search-form__input']} placeholder="Пошук"></input> | ||
</div> | ||
<span className={css['header-search-form__addon']}><img src={icon_search} alt=""/></span> | ||
</div> | ||
); | ||
const handleSearch = (searchTerm, searchPage) => { | ||
if (searchTerm.trim() !== '') { | ||
navigate(`/${searchPage}/?name=${searchTerm}`); | ||
} | ||
}; | ||
return ( | ||
<div className={css['header-search-box']}> | ||
<div className={css['header-search-form']}> | ||
<input | ||
className={css['header-search-form__input']} | ||
value={searchTerm} | ||
type="text" | ||
placeholder="Пошук" | ||
onChange={(e) => setSearchTerm(e.target.value)} | ||
/> | ||
</div> | ||
<button | ||
onClick={() => handleSearch(searchTerm, searchPage)} | ||
className={css['header-search-form__addon']} | ||
disabled={!searchTerm.trim()} | ||
> | ||
<img src={icon_search} alt="" /> | ||
</button> | ||
</div> | ||
); | ||
} | ||
|
||
export default SearchBox; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,4 @@ | ||
.header-search-box { | ||
/* .header-search-box { | ||
display: flex; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Uncomment or remove There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thank you for feedback. Done. |
||
align-items: center; | ||
flex-shrink: 0; | ||
|
@@ -53,4 +53,60 @@ | |
textarea:focus, .header-search-form__input:focus{ | ||
border: none; | ||
outline: none; | ||
} */ | ||
|
||
.header-search-box { | ||
display: flex; | ||
align-items: center; | ||
flex-shrink: 0; | ||
width: 264px; | ||
|
||
border-radius: 2px; | ||
border: 1px solid #d9d9d9; | ||
background: #fff; | ||
} | ||
|
||
.header-search-form { | ||
display: flex; | ||
padding: 4px 12px; | ||
align-items: flex-start; | ||
gap: 10px; | ||
flex: 1 0 0; | ||
} | ||
|
||
.header-search-form__input { | ||
display: flex; | ||
padding: 1px 0px; | ||
align-items: flex-start; | ||
gap: 10px; | ||
flex: 1 0 0; | ||
border: none; | ||
} | ||
|
||
.header-search-form__addon { | ||
background-color: transparent; | ||
cursor: pointer; | ||
border: 1px solid rgba(0, 0, 0, 0.25); | ||
} | ||
|
||
.header-search-form__addon img { | ||
width: 20px; | ||
height: 20px; | ||
} | ||
|
||
.header-search-form__input::placeholder { | ||
color: rgba(0, 0, 0, 0.25); | ||
font-feature-settings: 'calt' off; | ||
font-family: Inter; | ||
font-size: 14px; | ||
font-style: normal; | ||
font-weight: 400; | ||
line-height: 20px; | ||
letter-spacing: -0.14px; | ||
} | ||
|
||
textarea:focus, | ||
.header-search-form__input:focus { | ||
border: none; | ||
outline: none; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,142 @@ | ||
import axios from 'axios'; | ||
import { useState, useEffect } from 'react'; | ||
import { useLocation } from 'react-router-dom'; | ||
import BreadCrumbs from '../BreadCrumbs/BreadCrumbs'; | ||
import SearchResults from './search_field/SearchResults'; | ||
import frame42 from './img/frame42.png'; | ||
import link_to_left from './img/link_to_left.svg'; | ||
import link_to_right from './img/link_to_right.svg'; | ||
import './search_page.css'; | ||
|
||
const ITEMS_PER_PAGE = 6; | ||
|
||
export function Search(props) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. prop types There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thank you for feedback. Done |
||
const [searchResults, setSearchResults] = useState([]); | ||
const [searchPerformed, setSearchPerformed] = useState(false); | ||
const [error, setError] = useState(null); | ||
|
||
const location = useLocation(); | ||
const searchParams = new URLSearchParams(location.search); | ||
const searchTerm = searchParams.get('name'); | ||
const servedAddress = process.env.REACT_APP_BASE_API_URL; | ||
const searchUrl = 'search'; | ||
|
||
useEffect(() => { | ||
if (searchTerm) { | ||
// Make an AJAX request to Django API to get search results | ||
axios | ||
.get(`${servedAddress}/api/search/?name=${searchTerm}`) | ||
.then((response) => { | ||
setSearchResults(response.data); | ||
setSearchPerformed(true); | ||
setError(null); // Clear error on successful response | ||
}) | ||
.catch((error) => { | ||
console.error('Error fetching search results:', error); | ||
setError(error.response ? error.response.data : 'An error occurred'); | ||
}); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think we can handle it using swr for the consistency There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thank you for feedback. Done |
||
} | ||
}, [searchTerm, servedAddress, searchUrl]); | ||
|
||
const [currentPage, setCurrentPage] = useState(1); | ||
const totalItems = searchResults.length; | ||
const totalPages = Math.ceil(totalItems / ITEMS_PER_PAGE); | ||
|
||
const startIndex = (currentPage - 1) * ITEMS_PER_PAGE; | ||
const endIndex = startIndex + ITEMS_PER_PAGE; | ||
const displayedResults = searchResults.slice(startIndex, endIndex); | ||
|
||
const handlePageChange = (newPage) => { | ||
setCurrentPage(newPage); | ||
}; | ||
|
||
return ( | ||
<div className="main_block_outer"> | ||
<BreadCrumbs currentPage="Пошук" /> | ||
<div className="main_block"> | ||
<img className="frame-img-right" src={frame42} alt="frame" /> | ||
<div className="new-companies-search_count"> | ||
{searchResults && ( | ||
<h3 className="search_results_text"> | ||
РЕЗУЛЬТАТІВ ЗА ПОШУКОМ{' '} | ||
<span className="search_field_entered_value">{searchTerm}</span> :{' '} | ||
{searchResults.length > 0 ? searchResults.length : 0} | ||
</h3> | ||
)} | ||
<br /> | ||
</div> | ||
<div className="new-companies-main"> | ||
{!error && searchResults.length > 0 ? ( | ||
<> | ||
<SearchResults | ||
results={searchResults} | ||
searchPerformed={searchPerformed} | ||
displayedResults={displayedResults} | ||
isAuthorized={props.isAuthorized} | ||
/> | ||
<br /> | ||
</> | ||
) : ( | ||
<p className="search_result_error"> | ||
Пошук не дав результатів: компанії з іменем{' '} | ||
<span className=".search_result_error search_result_error_search_value"> | ||
{searchTerm} | ||
</span>{' '} | ||
не було виявлено на даний момент | ||
</p> | ||
)} | ||
</div> | ||
<div className="new-companies-result_pages"> | ||
{totalPages > 1 && ( | ||
<div className="pagination"> | ||
{currentPage > 1 && ( | ||
<button onClick={() => handlePageChange(currentPage - 1)}> | ||
<img src={link_to_left} alt="Link to Left" /> | ||
</button> | ||
)} | ||
{currentPage > 1 && ( | ||
<> | ||
<button onClick={() => handlePageChange(1)}>1</button> | ||
{currentPage > 2 && <span className="ellipsis">...</span>} | ||
</> | ||
)} | ||
{Array.from({ length: totalPages }, (_, i) => { | ||
if ( | ||
i === 2 || | ||
i === totalPages || | ||
(i >= currentPage - 1 && i <= currentPage) | ||
) { | ||
return ( | ||
<button | ||
key={i} | ||
onClick={() => handlePageChange(i + 1)} | ||
className={currentPage === i + 1 ? 'active' : ''} | ||
> | ||
{i + 1} | ||
</button> | ||
); | ||
} | ||
return null; | ||
})} | ||
{currentPage < totalPages - 1 && ( | ||
<> | ||
{currentPage < totalPages - 1 && ( | ||
<span className="ellipsis">...</span> | ||
)} | ||
<button onClick={() => handlePageChange(totalPages)}> | ||
{totalPages} | ||
</button> | ||
</> | ||
)} | ||
{currentPage < totalPages && ( | ||
<button onClick={() => handlePageChange(currentPage + 1)}> | ||
<img src={link_to_right} alt="Link to Right" /> | ||
</button> | ||
)} | ||
</div> | ||
)} | ||
</div> | ||
</div> | ||
</div> | ||
); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
import React from 'react'; | ||
import ReactDOM from 'react-dom/client'; | ||
import './index.css'; | ||
import App from './App'; | ||
import reportWebVitals from './reportWebVitals'; | ||
|
||
const root = ReactDOM.createRoot(document.getElementById('root')); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do we really need to create a new react root for the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thank you for feedback. Done. |
||
root.render( | ||
<React.StrictMode> | ||
<App /> | ||
</React.StrictMode> | ||
); | ||
|
||
// If you want to start measuring performance in your app, pass a function | ||
// to log results (for example: reportWebVitals(console.log)) | ||
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals | ||
reportWebVitals(); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
import MainCompanies from './companies/Companies'; | ||
import './Text.css'; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Use module css There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thank you for feedback. Done. |
||
|
||
const SearchResults = ({ results, displayedResults, isAuthorized }) => { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. prop types There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thank you for feedback. Done |
||
let error = null; | ||
|
||
if (results && results.error) { | ||
error = results.error; | ||
} | ||
|
||
return ( | ||
<div> | ||
{!error && ( | ||
<> | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. No need for the fragment if you have only one child There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thank you for feedback. Done. |
||
<div className="new-companies-block"> | ||
<div className="row"> | ||
{displayedResults.map((result, resultIndex) => ( | ||
<div key={resultIndex} className="col-md-4"> | ||
<MainCompanies | ||
companyData={result} | ||
isAuthorized={isAuthorized} | ||
/> | ||
</div> | ||
))} | ||
</div> | ||
</div> | ||
</> | ||
)} | ||
</div> | ||
); | ||
}; | ||
|
||
export default SearchResults; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I also would expect to run a search from the keyboard on the enter press
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you explain it, please?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you for feedback. Done.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I mean when I heat
Enter
button from the keyboard I would expect to run a searchThere was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done