freshyo/apps/user-ui/app/(drawer)/(tabs)/home/search-results/index.tsx
2026-03-10 10:03:49 +05:30

151 lines
4.5 KiB
TypeScript

import React, { useState, useRef, useEffect, useCallback, useMemo } from "react";
import { View, Dimensions } from "react-native";
import { useRouter, useLocalSearchParams } from "expo-router";
import {
tw,
useManualRefresh,
useMarkDataFetchers,
MyFlatList,
MyText,
SearchBar,
} from "common-ui";
import MaterialIcons from "@expo/vector-icons/MaterialIcons";
import Fuse from "fuse.js";
import { useAllProducts } from "@/src/hooks/prominent-api-hooks";
import ProductCard from "@/components/ProductCard";
import FloatingCartBar from "@/components/floating-cart-bar";
// Debounce hook for search
function useDebounce<T>(value: T, delay: number): T {
const [debouncedValue, setDebouncedValue] = useState<T>(value);
useEffect(() => {
const timer = setTimeout(() => {
setDebouncedValue(value);
}, delay);
return () => {
clearTimeout(timer);
};
}, [value, delay]);
return debouncedValue;
}
const { width: screenWidth } = Dimensions.get("window");
const itemWidth = (screenWidth - 48) / 2;
export default function SearchResults() {
const router = useRouter();
const { q } = useLocalSearchParams();
const query = (q as string) || "";
const [inputQuery, setInputQuery] = useState(query);
const searchInputRef = useRef<any>(null);
// Debounce the search query for automatic search
const debouncedQuery = useDebounce(inputQuery, 300);
useEffect(() => {
// Focus with requestAnimationFrame for better timing
requestAnimationFrame(() => {
searchInputRef.current?.focus();
});
}, []);
const { data: productsData, isLoading, error, refetch } = useAllProducts();
const allProducts = productsData?.products || [];
// Client-side search filtering using Fuse.js
const products = useMemo(() => {
if (!debouncedQuery.trim()) return allProducts;
const fuse = new Fuse(allProducts, {
keys: [
'name',
'shortDescription',
],
threshold: 0.3,
includeScore: true,
shouldSort: true,
});
const fuseResults = fuse.search(debouncedQuery);
return fuseResults.map(result => result.item);
}, [allProducts, debouncedQuery]);
useManualRefresh(() => {
refetch();
});
useMarkDataFetchers(() => {
refetch();
});
const handleSearch = useCallback(() => {
// Search is now automatic via debounce, but keep this for manual submit
}, []);
return (
<View style={tw`flex-1 bg-gray-50`}>
<MyFlatList
data={products}
numColumns={2}
renderItem={({ item }) => (
<ProductCard
item={item}
itemWidth={itemWidth}
onPress={() =>
router.push(`/(drawer)/(tabs)/home/product-detail/${item.id}`)
}
showDeliveryInfo={false}
useAddToCartDialog={true}
/>
)}
keyExtractor={(item) => item.id.toString()}
columnWrapperStyle={{ gap: 16, justifyContent: "center" }}
contentContainerStyle={[tw`pb-24`, { gap: 16 }]}
ListEmptyComponent={
isLoading ? (
<View style={tw`flex-1 justify-center items-center py-12 px-4`}>
<MyText style={tw`text-gray-500 font-medium`}>Loading products...</MyText>
</View>
) : (
<View style={tw`flex-1 justify-center items-center py-12 px-4`}>
<MaterialIcons name="search-off" size={64} color="#e5e7eb" />
<MyText style={tw`text-gray-500 mt-4 text-center text-lg font-medium`}>
No products found
</MyText>
{debouncedQuery && (
<MyText style={tw`text-gray-400 mt-2 text-center`}>
Try adjusting your search for "{debouncedQuery}"
</MyText>
)}
</View>
)
}
ListHeaderComponent={
<View style={tw`pt-4 pb-2 px-4`}>
<SearchBar
ref={searchInputRef}
value={inputQuery}
onChangeText={setInputQuery}
onSubmitEditing={handleSearch}
returnKeyType="search"
/>
<View style={tw`flex-row justify-between items-center mb-2`}>
<MyText style={tw`text-lg font-bold text-gray-900`}>
{debouncedQuery
? `Search Results for "${debouncedQuery}"`
: "All Products"}
</MyText>
</View>
</View>
}
/>
<View style={tw`absolute bottom-2 left-4 right-4`}>
<FloatingCartBar />
</View>
</View>
);
}