238 lines
6.8 KiB
TypeScript
238 lines
6.8 KiB
TypeScript
"use client";
|
|
|
|
import { useEffect, useState, useMemo, useCallback } from "react";
|
|
import { ScrollArea } from "@/components/ui/scroll-area";
|
|
import { Skeleton } from "@/components/ui/skeleton";
|
|
import { useTranslations } from "next-intl";
|
|
import type { Product } from "@/lib/types/api";
|
|
import { useFilteredSearchProducts } from "@/features/search/hooks/useSearch";
|
|
import { useCategoryFilters } from "@/features/category/hooks/useCategories";
|
|
import CategoryFilters from "@/features/category/components/CategoryFilters";
|
|
import CategoryProductsGrid from "@/features/category/components/CategoryProductsGrid";
|
|
import CategoryFiltersSheet from "@/features/category/components/CategoryFiltersSheet";
|
|
import ErrorPage from "@/components/ErrorPage";
|
|
|
|
interface SearchPageClientProps {
|
|
params: { locale: string };
|
|
searchParams: { q?: string };
|
|
}
|
|
|
|
export default function SearchPageClient({
|
|
params,
|
|
searchParams,
|
|
}: SearchPageClientProps) {
|
|
const q = searchParams.q || "";
|
|
const t = useTranslations();
|
|
const [isSheetOpen, setIsSheetOpen] = useState(false);
|
|
|
|
// State management
|
|
const [currentPage, setCurrentPage] = useState(1);
|
|
const [allProducts, setAllProducts] = useState<Product[]>([]);
|
|
const [priceSort, setPriceSort] = useState<
|
|
"none" | "lowToHigh" | "highToLow"
|
|
>("none");
|
|
const [priceRange, setPriceRange] = useState<[number, number]>([0, 100000]);
|
|
const [selectedBrands, setSelectedBrands] = useState<Set<number>>(new Set());
|
|
const [selectedFilterCategories, setSelectedFilterCategories] = useState<
|
|
Set<number>
|
|
>(new Set());
|
|
|
|
// Fetch filters (we use generic filters for search page)
|
|
const {
|
|
data: filtersData,
|
|
isLoading: filtersLoading,
|
|
isError: filtersError,
|
|
} = useCategoryFilters(undefined);
|
|
|
|
// Build filter params
|
|
const filterParams = useMemo(() => {
|
|
const params: any = {
|
|
page: currentPage,
|
|
limit: 12,
|
|
};
|
|
|
|
if (selectedBrands.size > 0) {
|
|
params.brands = Array.from(selectedBrands);
|
|
}
|
|
|
|
if (selectedFilterCategories.size > 0) {
|
|
params.categories = Array.from(selectedFilterCategories);
|
|
}
|
|
|
|
params.min_price = priceRange[0];
|
|
params.max_price = priceRange[1];
|
|
|
|
return params;
|
|
}, [currentPage, selectedBrands, selectedFilterCategories, priceRange]);
|
|
|
|
// Fetch filtered search products
|
|
const {
|
|
data: productsData,
|
|
isFetching,
|
|
isError: productsError,
|
|
} = useFilteredSearchProducts(q, filterParams);
|
|
|
|
// Reset on search term change
|
|
useEffect(() => {
|
|
setAllProducts([]);
|
|
setCurrentPage(1);
|
|
setSelectedBrands(new Set());
|
|
setSelectedFilterCategories(new Set());
|
|
setPriceRange([0, 100000]);
|
|
setPriceSort("none");
|
|
}, [q]);
|
|
|
|
// Update products list
|
|
useEffect(() => {
|
|
if (productsData?.data) {
|
|
setAllProducts((prev) => {
|
|
if (currentPage === 1) {
|
|
return productsData.data;
|
|
}
|
|
|
|
const existingIds = new Set(prev.map((p) => p.id));
|
|
const newProducts = productsData.data.filter(
|
|
(p: Product) => !existingIds.has(p.id),
|
|
);
|
|
|
|
if (newProducts.length === 0) {
|
|
return prev;
|
|
}
|
|
|
|
return [...prev, ...newProducts];
|
|
});
|
|
}
|
|
}, [productsData?.data, currentPage]);
|
|
|
|
const hasMore = useMemo(() => {
|
|
if (!productsData?.pagination) return false;
|
|
if (productsData.pagination.next_page_url) return true;
|
|
if (
|
|
productsData.pagination.current_page &&
|
|
productsData.pagination.last_page
|
|
) {
|
|
return (
|
|
productsData.pagination.current_page < productsData.pagination.last_page
|
|
);
|
|
}
|
|
return productsData.pagination.hasMorePages ?? false;
|
|
}, [productsData?.pagination]);
|
|
|
|
const loadMoreData = useCallback(() => {
|
|
if (!hasMore || isFetching) return;
|
|
setCurrentPage((prev) => prev + 1);
|
|
}, [hasMore, isFetching]);
|
|
|
|
const sortedProducts = useMemo(() => {
|
|
const products = [...allProducts];
|
|
if (priceSort === "lowToHigh") {
|
|
return products.sort(
|
|
(a, b) =>
|
|
parseFloat(a.price_amount || "0") - parseFloat(b.price_amount || "0"),
|
|
);
|
|
}
|
|
if (priceSort === "highToLow") {
|
|
return products.sort(
|
|
(a, b) =>
|
|
parseFloat(b.price_amount || "0") - parseFloat(a.price_amount || "0"),
|
|
);
|
|
}
|
|
return products;
|
|
}, [allProducts, priceSort]);
|
|
|
|
// Filter handlers
|
|
const handleBrandToggle = useCallback((brandId: number) => {
|
|
setSelectedBrands((prev) => {
|
|
const newSet = new Set(prev);
|
|
newSet.has(brandId) ? newSet.delete(brandId) : newSet.add(brandId);
|
|
return newSet;
|
|
});
|
|
setCurrentPage(1);
|
|
setAllProducts([]);
|
|
}, []);
|
|
|
|
const handleCategoryToggle = useCallback((categoryId: number) => {
|
|
setSelectedFilterCategories((prev) => {
|
|
const newSet = new Set(prev);
|
|
newSet.has(categoryId)
|
|
? newSet.delete(categoryId)
|
|
: newSet.add(categoryId);
|
|
return newSet;
|
|
});
|
|
setCurrentPage(1);
|
|
setAllProducts([]);
|
|
}, []);
|
|
|
|
const handlePriceChange = useCallback((values: number[]) => {
|
|
setPriceRange([values[0], values[1]]);
|
|
setCurrentPage(1);
|
|
setAllProducts([]);
|
|
}, []);
|
|
|
|
const handlePriceSortChange = useCallback(
|
|
(sortType: "none" | "lowToHigh" | "highToLow") => {
|
|
setPriceSort(sortType);
|
|
},
|
|
[],
|
|
);
|
|
|
|
const resetFilters = useCallback(() => {
|
|
setSelectedBrands(new Set());
|
|
setSelectedFilterCategories(new Set());
|
|
setPriceRange([0, 100000]);
|
|
setPriceSort("none");
|
|
setCurrentPage(1);
|
|
setAllProducts([]);
|
|
}, []);
|
|
|
|
const filterTranslations = useMemo(
|
|
() => ({
|
|
category: t("category"),
|
|
brands: t("brands"),
|
|
sort: t("sort"),
|
|
default: t("default"),
|
|
price_low_to_high: t("price_low_to_high"),
|
|
price_high_to_low: t("price_high_to_low"),
|
|
price: t("price"),
|
|
price_from: t("price_from"),
|
|
price_to: t("price_to"),
|
|
reset: t("reset"),
|
|
}),
|
|
[t],
|
|
);
|
|
|
|
if (productsError) {
|
|
return <ErrorPage />;
|
|
}
|
|
|
|
return (
|
|
<div className="flex flex-col mx-auto max-w-[1504px] px-2 md:px-4 lg:px-6 pb-12">
|
|
<div className="bg-white p-4 rounded-t-lg mb-0">
|
|
<h2 className="text-2xl md:text-3xl font-bold">
|
|
{t("search_results")}: <span className="text-gray-500">"{q}"</span>
|
|
</h2>
|
|
<p className="text-gray-500 mt-1">
|
|
{productsData?.pagination?.total || allProducts.length}{" "}
|
|
{t("products_found")}
|
|
</p>
|
|
</div>
|
|
|
|
<div className="flex p-2 md:p-4 gap-4 bg-white rounded-b-lg">
|
|
{/* Products Grid */}
|
|
<div className="flex-1 bg-white rounded-lg mb-6">
|
|
<CategoryProductsGrid
|
|
products={sortedProducts}
|
|
hasMore={hasMore}
|
|
onLoadMore={loadMoreData}
|
|
isFetching={isFetching}
|
|
translations={{
|
|
loading: t("common.loading"),
|
|
no_results: t("no_results"),
|
|
}}
|
|
/>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|