249 lines
8.1 KiB
TypeScript
249 lines
8.1 KiB
TypeScript
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;
|