Skip to content
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

Added Complete Functionality to Search Bar #797

Merged
merged 2 commits into from
Nov 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions backend/controllers/shop/utilityController.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
const Product = require('../../model/shop/product')

const searchProducts = async (req, res) => {
const { query } = req.query;

if (!query) {
return res.status(400).json({ error: 'Query parameter is required' });
}

try {
// Perform a case-insensitive, partial match search on the product's name and description
const products = await Product.find({
$or: [
{ name: { $regex: query, $options: 'i' } }, // 'i' for case-insensitive
{ description: { $regex: query, $options: 'i' } }
]
})
.populate('category reviews variants brand seller') // Populate related fields if needed
.limit(4) // Limit to top 4 results
.exec();

res.json(products);
} catch (error) {
console.error('Error fetching search results:', error);
res.status(500).json({ error: 'Internal server error' });
}
};

module.exports = {
searchProducts,
};
7 changes: 7 additions & 0 deletions backend/routes/shop.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ const variantController = require('../controllers/shop/variantController');
const cartController = require('../controllers/shop/cartController')
const wishlistController = require('../controllers/shop/wishlistController');
const extendedUserController = require('../controllers/shop/profileController');
const UtilityController = require('../controllers/shop/utilityController');
/**
* Product Routes
*/
Expand Down Expand Up @@ -62,6 +63,7 @@ router.delete('/reviews/:id', reviewController.deleteReview);
/**
* Variant Routes
*/

router.get('/variants', variantController.getAllVariants);
router.get('/variants/:id', variantController.getVariantById);
router.post('/variants', variantController.createVariant);
Expand Down Expand Up @@ -96,5 +98,10 @@ router.get('/profile', extendedUserController.getAllExtendedUsers);// Get all ex
router.put('/profile/:id', extendedUserController.updateExtendedUser);// Update an existing extended user
router.delete('/profile/:id', extendedUserController.deleteExtendedUser);// Delete an extended user

/**
* Utility Routes
*/

router.get('/search', UtilityController.searchProducts);

module.exports = router;
21 changes: 5 additions & 16 deletions frontend/src/AgroShopAI/components/ShopNavbar.jsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import React, { useState, useEffect, useRef } from 'react';
import { Link } from 'react-router-dom';
import LOGO from "/favicon2.png";

import SearchBar from './sub-components/SearchBar';

function ShopNavbar() {
const [isDropdownOpen, setIsDropdownOpen] = useState(false);
Expand Down Expand Up @@ -54,7 +54,7 @@ function ShopNavbar() {

{/* Logo Section */}
<Link
to="/"
to="/agroshop"
className="flex items-center gap-2 group transition-all duration-300 ease-in-out transform hover:scale-105"
>
<img
Expand Down Expand Up @@ -87,14 +87,8 @@ function ShopNavbar() {
</div>

{/* Search Bar */}
<div className="flex-1 mx-4 hidden md:flex">
<input
type="text"
placeholder="Search for products..."
className="w-full px-3 py-2 rounded-l-md focus:outline-none text-black"
/>
<button className="bg-white text-green-600 px-4 rounded-r-md hover:bg-gray-200">Search</button>
</div>
<div className="hidden md:flex"></div>
<SearchBar />

{/* Right Corner Tabs */}
<div className="ml-auto flex items-center space-x-6">
Expand Down Expand Up @@ -146,12 +140,7 @@ function ShopNavbar() {
/>
)}
<div className="flex md:hidden mt-2 w-full">
<input
type="text"
placeholder="Search..."
className="w-full px-3 py-2 rounded-md text-black focus:outline-none"
/>
<button className="bg-white text-green-600 px-4 rounded-md ml-2 hover:bg-gray-200">Search</button>
<SearchBar/>
</div>
</nav>
);
Expand Down
122 changes: 122 additions & 0 deletions frontend/src/AgroShopAI/components/sub-components/SearchBar.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
import React, { useState, useRef, useEffect } from 'react';
import { useNavigate } from 'react-router-dom'; // Import useNavigate

const SearchBar = () => {
const [query, setQuery] = useState('');
const [results, setResults] = useState([]);
const [loading, setLoading] = useState(false);
const [isDropdownOpen, setIsDropdownOpen] = useState(false);
const dropdownRef = useRef(null);
const navigate = useNavigate(); // Initialize navigate
const debounceDelay = 300; // Delay in milliseconds
const timeoutRef = useRef(null); // Ref to store the timeout

// Close dropdown if clicking outside of it
useEffect(() => {
const handleClickOutside = (event) => {
if (dropdownRef.current && !dropdownRef.current.contains(event.target)) {
setIsDropdownOpen(false);
}
};
document.addEventListener('mousedown', handleClickOutside);
return () => document.removeEventListener('mousedown', handleClickOutside);
}, []);

// Handle input change and search
const handleChange = (e) => {
setQuery(e.target.value);
setIsDropdownOpen(true);

// Clear previous timeout
if (timeoutRef.current) {
clearTimeout(timeoutRef.current);
}

// Set a new timeout to trigger the search
timeoutRef.current = setTimeout(() => {
handleSearch(e.target.value);
}, debounceDelay);
};

const handleSearch = async (searchQuery) => {
if (!searchQuery) {
setResults([]);
setIsDropdownOpen(false);
return;
}

setLoading(true);

try {
const response = await fetch(`${import.meta.env.VITE_BACKEND_BASE_URL}api/search?query=${searchQuery}`);
const data = await response.json();
setResults(data);
} catch (error) {
console.error('Error fetching search results:', error);
setResults([]);
}
setLoading(false);
};

// Handle click on a search result
const handleResultClick = (productId) => {
// Redirect to the product details page using the product ID
navigate(`/agroshop/product/${productId}`);
setIsDropdownOpen(false); // Close dropdown after selection
};

return (
<div className="relative flex-1 mx-4 hidden md:flex">
{/* Search Input and Button */}
<input
type="text"
placeholder="Search..."
value={query}
onChange={handleChange}
className="w-full px-3 py-2 rounded-l-md focus:outline-none text-black"
/>
<button
onClick={() => handleSearch(query)} // Allow manual search
disabled={!query || loading}
className="bg-white text-green-600 px-4 rounded-r-md hover:bg-gray-200"
>
{loading ? 'Searching...' : 'Search'}
</button>

{/* Dropdown for Search Results */}
{isDropdownOpen && results.length > 0 && (
<div
ref={dropdownRef}
className="absolute top-full left-0 mt-2 w-full bg-white text-black rounded-lg shadow-lg transition-all duration-200"
>
{loading ? (
<p className="text-center text-gray-500 py-2">Loading...</p>
) : (
results.map((product) => (
<div
key={product._id}
className="flex items-center p-4 border-b border-gray-200 rounded-lg hover:bg-gray-100 cursor-pointer"
onClick={() => handleResultClick(product._id)} // Handle click
>
{/* Display the first image */}
{product.images && product.images[0] && (
<img
src={product.images[0]}
alt={product.name}
className="w-16 h-16 rounded-md mr-4 object-cover"
/>
)}
<div className="flex flex-col">
<h3 className="text-lg font-semibold">{product.name}</h3>
<span className="text-gray-600 text-sm">{product.brand?.name || 'Unknown Brand'}</span>
</div>
</div>
))
)}
</div>
)}
</div>
);
};

export default SearchBar;
Loading