feat: Search UI

- Search UI with dropdown results
- Client class to handle Product Search actions and results
- Integrated Search bar into all-products and item group pages
- Run db search without redisearch
- Cleanup: [Search] change decorator names and variables
- Sider fixes
diff --git a/erpnext/templates/generators/item_group.html b/erpnext/templates/generators/item_group.html
index 20ad7fb..0b2fb03 100644
--- a/erpnext/templates/generators/item_group.html
+++ b/erpnext/templates/generators/item_group.html
@@ -2,7 +2,16 @@
 {% extends "templates/web.html" %}
 
 {% block header %}
-<!-- <h2>{{ title }}</h2> -->
+<div class="row mb-6" style="width: 65vw">
+	<div class="mb-6 col-4 order-1">{{ title }}</div>
+
+	<div class="input-group mb-6 col-8 order-2">
+		<div class="dropdown w-100" id="dropdownMenuSearch">
+			<input type="search" name="query" id="search-box" class="form-control" placeholder="Search for products..." aria-label="Product" aria-describedby="button-addon2">
+			<!-- Results dropdown rendered in product_search.js -->
+		</div>
+	</div>
+</div>
 {% endblock header %}
 
 {% block script %}
@@ -19,7 +28,7 @@
 <div class="item-group-content" itemscope itemtype="http://schema.org/Product"
 	data-item-group="{{ name }}">
 	<div class="item-group-slideshow">
-		{% if slideshow %}<!-- slideshow -->
+		{% if slideshow %} <!-- slideshow -->
 			{{ web_block(
 				"Hero Slider",
 				values=slideshow,
@@ -28,8 +37,8 @@
 				add_bottom_padding=0,
 			) }}
 		{% endif %}
-		<h2 class="mt-3">{{ title }}</h2>
-		{% if description %}<!-- description -->
+
+		{% if description %} <!-- description -->
 		<div class="item-group-description text-muted mb-5" itemprop="description">{{ description or ""}}</div>
 		{% endif %}
 	</div>
diff --git a/erpnext/templates/includes/cart.js b/erpnext/templates/includes/cart.js
index 28fe882..c766dfd 100644
--- a/erpnext/templates/includes/cart.js
+++ b/erpnext/templates/includes/cart.js
@@ -163,7 +163,7 @@
 				item_code: item_code,
 				qty: 0
 			});
-		})
+		});
 	},
 
 	render_tax_row: function($cart_taxes, doc, shipping_rules) {
diff --git a/erpnext/templates/pages/product_search.py b/erpnext/templates/pages/product_search.py
index 12967fa..190abfd 100644
--- a/erpnext/templates/pages/product_search.py
+++ b/erpnext/templates/pages/product_search.py
@@ -7,7 +7,6 @@
 from erpnext.setup.doctype.item_group.item_group import get_item_for_list_in_html
 from erpnext.e_commerce.shopping_cart.product_info import set_product_info_for_website
 
-# For SEARCH -------
 from redisearch import AutoCompleter, Client, Query
 from erpnext.e_commerce.website_item_indexing import (
 	is_search_module_loaded,
@@ -16,7 +15,6 @@
 	WEBSITE_ITEM_CATEGORY_AUTOCOMPLETE,
 	make_key
 )
-# -----------------
 
 no_cache = 1
 
@@ -36,30 +34,29 @@
 def get_product_data(search=None, start=0, limit=12):
 	# limit = 12 because we show 12 items in the grid view
 	# base query
-	query = """select I.name, I.item_name, I.item_code, I.route, I.image, I.website_image, I.thumbnail, I.item_group,
-			I.description, I.web_long_description as website_description, I.is_stock_item,
-			case when (S.actual_qty - S.reserved_qty) > 0 then 1 else 0 end as in_stock, I.website_warehouse,
-			I.has_batch_no
-		from `tabItem` I
-		left join tabBin S on I.item_code = S.item_code and I.website_warehouse = S.warehouse
-		where (I.show_in_website = 1)
-			and I.disabled = 0
-			and (I.end_of_life is null or I.end_of_life='0000-00-00' or I.end_of_life > %(today)s)"""
+	query = """
+		Select
+			web_item_name, item_name, item_code, brand, route,
+			website_image, thumbnail, item_group,
+			description, web_long_description as website_description,
+			website_warehouse, ranking
+		from `tabWebsite Item`
+		where published = 1
+		"""
 
 	# search term condition
 	if search:
-		query += """ and (I.web_long_description like %(search)s
-				or I.description like %(search)s
-				or I.item_name like %(search)s
-				or I.name like %(search)s)"""
+		query += """ and (item_name like %(search)s
+				or web_item_name like %(search)s
+				or brand like %(search)s
+				or web_long_description like %(search)s)"""
 		search = "%" + cstr(search) + "%"
 
 	# order by
-	query += """ order by I.weightage desc, in_stock desc, I.modified desc limit %s, %s""" % (cint(start), cint(limit))
+	query += """ order by ranking asc, modified desc limit %s, %s""" % (cint(start), cint(limit))
 
 	return frappe.db.sql(query, {
-		"search": search,
-		"today": nowdate()
+		"search": search
 	}, as_dict=1)
 
 @frappe.whitelist(allow_guest=True)
@@ -112,11 +109,19 @@
 
 @frappe.whitelist(allow_guest=True)
 def get_category_suggestions(query):
-	search_results = {"from_redisearch": True, "results": []}
+	search_results = {"results": []}
 
 	if not is_search_module_loaded():
-		# Redisearch module not loaded
-		search_results["from_redisearch"] = False
+		# Redisearch module not loaded, query db
+		categories = frappe.db.get_all(
+			"Item Group",
+			filters={
+				"name": ["like", "%{0}%".format(query)],
+				"show_in_website": 1
+			},
+			fields=["name", "route"]
+		)
+		search_results['results'] = categories
 		return search_results
 
 	if not query: