changed some color and fix some styles

This commit is contained in:
@jcarymuhammedow
2026-02-07 16:06:33 +05:00
parent 022c7290b4
commit b27b8436d1
34 changed files with 999 additions and 368 deletions

View File

@@ -0,0 +1,237 @@
"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>
);
}

View File

@@ -1,6 +1,11 @@
import { useQuery } from "@tanstack/react-query";
import { apiClient } from "@/lib/api";
import type { SearchResponse, SearchParams } from "../types";
import type {
Product,
PaginatedResponse,
ProductFilters,
} from "@/lib/types/api";
export function useSearchProducts(params: SearchParams) {
const { q, barcode } = params;
@@ -10,14 +15,14 @@ export function useSearchProducts(params: SearchParams) {
queryFn: async () => {
if (barcode) {
const response = await apiClient.get<SearchResponse>(
`/search-product-barcode?barcode=${barcode}`
`/search-product-barcode?barcode=${barcode}`,
);
return response.data;
}
if (q) {
const response = await apiClient.get<SearchResponse>(
`/search-product?q=${encodeURIComponent(q)}`
`/search-product?q=${encodeURIComponent(q)}`,
);
return response.data;
}
@@ -27,4 +32,48 @@ export function useSearchProducts(params: SearchParams) {
enabled: !!(q && q.length > 0) || !!barcode,
staleTime: 1000 * 60 * 5,
});
}
}
export function useFilteredSearchProducts(
q: string,
filters: ProductFilters,
options?: { enabled?: boolean },
) {
return useQuery({
queryKey: ["search-filtered", q, filters],
queryFn: async () => {
const params: Record<string, any> = {
q,
page: filters.page || 1,
per_page: filters.limit || 12,
};
if (filters.brands && filters.brands.length > 0) {
params.brands = filters.brands.join(",");
}
if (filters.categories && filters.categories.length > 0) {
params.categories = filters.categories.join(",");
}
if (filters.min_price !== undefined) {
params.min_price = filters.min_price;
}
if (filters.max_price !== undefined) {
params.max_price = filters.max_price;
}
const response = await apiClient.get<PaginatedResponse<Product>>(
"/search-product",
{ params },
);
return {
data: response.data.data || [],
pagination: response.data.pagination || {},
};
},
enabled: options?.enabled !== false && !!q,
});
}