Vitron-Front/app/components/explore/SearchSection.tsx
2026-04-29 01:44:16 +03:30

249 lines
8.1 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { useEffect, useState, memo, useMemo } from "react";
import TabContainer from "./TabContainer";
import { useNavigate } from "@remix-run/react";
import { onSearch } from "../../utils/helpers";
import { Search, X } from "lucide-react";
import { useDebounce } from "../../hooks/useDebounce";
import { useSearchAutocomplete } from "../../requestHandler/use-search-hooks";
import { AutocompleteSuggestionTypeEnum } from "../../../src/api/types";
const SearchSection = memo(function SearchSection({
searchValue,
}: {
searchValue: string;
}) {
const tabs = useMemo(() => ["همه نتایج", "محصولات", "فروشگاه‌ها"], []);
const [activeSearchTab, setActiveSearchTab] = useState(0);
return (
<div className="flex flex-col gap-6 w-full">
<TabContainer
activeTab={activeSearchTab}
setActiveTab={setActiveSearchTab}
tabs={tabs}
/>
<div className="px-4 flex flex-col gap-6 w-full">
{searchValue ? (
<SearchSuggestions
activeSearchTab={activeSearchTab}
searchValue={searchValue}
/>
) : (
<SearchHistorySection />
)}
</div>
</div>
);
});
const SearchSuggestions = memo(function SearchSuggestions({
activeSearchTab,
searchValue,
}: {
activeSearchTab: number;
searchValue: string;
}) {
const navigate = useNavigate();
const debouncedSearchValue = useDebounce(searchValue, 500); // Reduced from 800ms to 500ms
const { data: autocompleteData, isLoading } =
useSearchAutocomplete(debouncedSearchValue);
// Memoize filtered suggestions to avoid recalculation on every render
const filterSuggestions = useMemo(() => {
if (!autocompleteData?.suggestions) return [];
return autocompleteData.suggestions
.filter((sug) => {
return (
activeSearchTab === 0 ||
(sug.type === AutocompleteSuggestionTypeEnum.Product &&
activeSearchTab === 1) ||
(sug.type === AutocompleteSuggestionTypeEnum.Seller &&
activeSearchTab === 2)
);
})
.map((sug) => ({
...sug,
text:
sug.type === AutocompleteSuggestionTypeEnum.Product
? sug.text
: sug.name,
}));
}, [autocompleteData?.suggestions, activeSearchTab]);
if (isLoading) {
return (
<div className="flex w-full h-[44px] justify-center items-center">
<p className="text-B12 text-GRAY">در حال جستجو...</p>
</div>
);
}
return (
<div className="flex flex-col w-full">
{filterSuggestions.length > 0 ? (
<>
{filterSuggestions.map((sug, index) => {
const matchIndex = sug.text
? sug.text
.toLowerCase()
.indexOf(searchValue ? searchValue.toLowerCase() : "")
: -1;
const beforeMatch =
matchIndex >= 0 ? sug.text?.substring(0, matchIndex) : "";
const matchText =
matchIndex >= 0
? sug.text?.substring(
matchIndex,
matchIndex + searchValue.length
)
: sug.text;
const afterMatch =
matchIndex >= 0
? sug.text?.substring(matchIndex + searchValue.length)
: "";
return (
<div
key={index}
role="button"
tabIndex={index}
onKeyDown={() => {}}
className="flex w-full h-[44px] justify-between items-center cursor-pointer hover:bg-WHITE2"
>
{sug.type === AutocompleteSuggestionTypeEnum.Product ? (
<div
onClick={() => {
if (sug.text) {
onSearch(sug.text, navigate);
}
}}
onKeyDown={() => {}}
role="button"
tabIndex={index}
className="flex w-full items-center gap-2"
>
<Search className="w-4 h-4 text-BLACK2" />
<p className="text-B12 font-bold">
<span className="text-GRAY">{beforeMatch}</span>
<span className="text-BLACK2">{matchText}</span>
<span className="text-GRAY">{afterMatch}</span>
</p>
</div>
) : (
<div
onClick={() => {
if (sug.id) {
navigate(`/seller/${sug.id}`);
}
}}
className="flex w-full items-center gap-2"
onKeyDown={() => {}}
role="button"
tabIndex={index}
>
<Search className="w-4 h-4 text-BLACK2" />
<p className="text-B12 font-bold">
<span className="text-GRAY">{beforeMatch}</span>
<span className="text-BLACK2">{matchText}</span>
<span className="text-GRAY">{afterMatch}</span>
</p>
</div>
)}
</div>
);
})}
</>
) : (
// Empty state
<div
onClick={() => {
onSearch(searchValue, navigate);
}}
onKeyDown={() => {}}
role="button"
tabIndex={0}
className="flex w-full items-center gap-2"
>
<Search className="w-4 h-4 text-BLACK2" />
<p className="text-B12 font-bold">{searchValue}</p>
</div>
)}
</div>
);
});
const SearchHistorySection = () => {
const navigate = useNavigate();
const [searchHistory, setSearchHistory] = useState<string[]>([]);
useEffect(() => {
const history = localStorage.getItem("searchValues");
if (history) {
setSearchHistory(history.split("&&"));
}
}, []);
const deleteSearchHistory = (searchValue: string) => {
localStorage.setItem(
"searchValues",
searchHistory.filter((value) => value !== searchValue).join("&&") || ""
);
setSearchHistory(searchHistory.filter((value) => value !== searchValue));
};
const deleteAllSearchHistory = () => {
localStorage.removeItem("searchValues");
setSearchHistory([]);
};
return (
<div className="flex flex-col w-full">
{searchHistory.length > 0 ? (
<>
<div className="flex w-full h-[44px] justify-between items-center">
<p className="text-B12 font-bold">تاریخچه جستجو</p>
{searchHistory.length > 0 && (
<button
onClick={deleteAllSearchHistory}
className="text-R10 text-BLUE hover:underline cursor-pointer"
>
حذف همه
</button>
)}
</div>
{searchHistory.map((searchValue, index) => {
return (
<div
key={index}
role="button"
tabIndex={index}
onKeyDown={() => {}}
className="flex w-full h-[44px] justify-between items-center"
>
<div
onClick={() => {
onSearch(searchValue, navigate);
}}
onKeyDown={() => {}}
role="button"
tabIndex={index}
className="flex w-full items-center gap-2"
>
<Search className="w-4 h-4 text-BLACK2" />
<p className="text-B12 font-bold text-BLACK2">
{searchValue}
</p>
</div>
<button
onClick={() => {
deleteSearchHistory(searchValue);
}}
className="hover:underline cursor-pointer"
>
<X className="w-4 h-4 text-BLACK2" />
</button>
</div>
);
})}
</>
) : // Empty state
null}
</div>
);
};
export default SearchSection;