import React, { useEffect, useRef, useState } from "react" import { getCookie, setCookie } from "cookies-next" import { useRouter } from "next/router" import { useSnapshot } from "valtio" import { useTranslation } from "react-i18next" import InfiniteScroll from "react-infinite-scroll-component" import { appState } from "~utils/appState" import api from "~utils/api" import * as Dialog from "@radix-ui/react-dialog" import CharacterSearchFilterBar from "~components/CharacterSearchFilterBar" import WeaponSearchFilterBar from "~components/WeaponSearchFilterBar" import SummonSearchFilterBar from "~components/SummonSearchFilterBar" import CharacterResult from "~components/CharacterResult" import WeaponResult from "~components/WeaponResult" import SummonResult from "~components/SummonResult" import "./index.scss" import CrossIcon from "~public/icons/Cross.svg" import cloneDeep from "lodash.clonedeep" interface Props { send: (object: Character | Weapon | Summon, position: number) => any placeholderText: string fromPosition: number object: "weapons" | "characters" | "summons" children: React.ReactNode } const SearchModal = (props: Props) => { // Set up snapshot of app state let { grid, search } = useSnapshot(appState) // Set up router const router = useRouter() const locale = router.locale // Set up translation const { t } = useTranslation("common") let searchInput = React.createRef() let scrollContainer = React.createRef() const [firstLoad, setFirstLoad] = useState(true) const [objects, setObjects] = useState<{ [id: number]: GridCharacter | GridWeapon | GridSummon }>() const [filters, setFilters] = useState<{ [key: string]: number[] }>() const [open, setOpen] = useState(false) const [query, setQuery] = useState("") const [results, setResults] = useState<(Weapon | Summon | Character)[]>([]) // Pagination states const [recordCount, setRecordCount] = useState(0) const [currentPage, setCurrentPage] = useState(1) const [totalPages, setTotalPages] = useState(1) useEffect(() => { setObjects(grid[props.object]) }, [grid, props.object]) useEffect(() => { if (searchInput.current) searchInput.current.focus() }, [searchInput]) function inputChanged(event: React.ChangeEvent) { const text = event.target.value if (text.length) { setQuery(text) } else { setQuery("") } } function fetchResults({ replace = false }: { replace?: boolean }) { api .search({ object: props.object, query: query, filters: filters, locale: locale, page: currentPage, }) .then((response) => { setTotalPages(response.data.total_pages) setRecordCount(response.data.count) if (replace) { replaceResults(response.data.count, response.data.results) } else { appendResults(response.data.results) } }) .catch((error) => { console.error(error) }) } function replaceResults( count: number, list: Weapon[] | Summon[] | Character[] ) { if (count > 0) { setResults(list) } else { setResults([]) } } function appendResults(list: Weapon[] | Summon[] | Character[]) { setResults([...results, ...list]) } function storeRecentResult(result: Character | Weapon | Summon) { const key = `recent_${props.object}` const cookie = getCookie(key) const cookieObj: Character[] | Weapon[] | Summon[] = cookie ? JSON.parse(cookie as string) : [] let recents: Character[] | Weapon[] | Summon[] = [] if (props.object === "weapons") { recents = cloneDeep(cookieObj as Weapon[]) || [] if (!recents.find((item) => item.granblue_id === result.granblue_id)) { recents.unshift(result as Weapon) } } else if (props.object === "summons") { recents = cloneDeep(cookieObj as Summon[]) || [] if (!recents.find((item) => item.granblue_id === result.granblue_id)) { recents.unshift(result as Summon) } } if (recents && recents.length > 5) recents.pop() setCookie(`recent_${props.object}`, recents, { path: "/" }) sendData(result) } function sendData(result: Character | Weapon | Summon) { props.send(result, props.fromPosition) openChange() } function receiveFilters(filters: { [key: string]: number[] }) { setCurrentPage(1) setResults([]) setFilters(filters) } useEffect(() => { // Current page changed if (open && currentPage > 1) { fetchResults({ replace: false }) } else if (open && currentPage == 1) { fetchResults({ replace: true }) } }, [currentPage]) useEffect(() => { // Filters changed const key = `recent_${props.object}` const cookie = getCookie(key) const cookieObj: Weapon[] | Summon[] | Character[] = cookie ? JSON.parse(cookie as string) : [] if (open) { if (firstLoad && cookieObj && cookieObj.length > 0) { setResults(cookieObj) setRecordCount(cookieObj.length) setFirstLoad(false) } else { setCurrentPage(1) fetchResults({ replace: true }) } } }, [filters]) useEffect(() => { // Query changed if (open && query.length != 1) { setCurrentPage(1) fetchResults({ replace: true }) } }, [query]) function renderResults() { let jsx switch (props.object) { case "weapons": jsx = renderWeaponSearchResults() break case "summons": jsx = renderSummonSearchResults(results) break case "characters": jsx = renderCharacterSearchResults(results) break } return ( 0 ? results.length : 0} next={() => setCurrentPage(currentPage + 1)} hasMore={totalPages > currentPage} scrollableTarget="Results" loader={
Loading...
} > {jsx}
) } function renderWeaponSearchResults() { let jsx: React.ReactNode const castResults: Weapon[] = results as Weapon[] if (castResults && Object.keys(castResults).length > 0) { jsx = castResults.map((result: Weapon) => { return ( { storeRecentResult(result) }} /> ) }) } return jsx } function renderSummonSearchResults(results: { [key: string]: any }) { let jsx: React.ReactNode const castResults: Summon[] = results as Summon[] if (castResults && Object.keys(castResults).length > 0) { jsx = castResults.map((result: Summon) => { return ( { storeRecentResult(result) }} /> ) }) } return jsx } function renderCharacterSearchResults(results: { [key: string]: any }) { let jsx: React.ReactNode const castResults: Character[] = results as Character[] if (castResults && Object.keys(castResults).length > 0) { jsx = castResults.map((result: Character) => { return ( { storeRecentResult(result) }} /> ) }) } return jsx } function openChange() { if (open) { setQuery("") setFirstLoad(true) setResults([]) setRecordCount(0) setCurrentPage(1) setOpen(false) } else { setOpen(true) } } return ( {props.children}
{t("search.result_count", { record_count: recordCount })}
{open ? renderResults() : ""}
) } export default SearchModal