From 27554eb3738859278817bfb9133b391fd3e0d7db Mon Sep 17 00:00:00 2001 From: Radiquum Date: Wed, 20 Aug 2025 05:50:49 +0500 Subject: [PATCH] change how filters work --- app/api/config.ts | 8 +- app/pages/Search.tsx | 878 ++++++++++++++++++++++++++----------------- 2 files changed, 541 insertions(+), 345 deletions(-) diff --git a/app/api/config.ts b/app/api/config.ts index 853a361..957a4c9 100644 --- a/app/api/config.ts +++ b/app/api/config.ts @@ -51,7 +51,13 @@ export const ENDPOINTS = { } }, filter: `${API_PREFIX}/filter`, - search: `${API_URL}/search`, + search: { + profileList: `${API_PREFIX}/search/profile/list/`, + profileHistory: `${API_PREFIX}/search/history/`, + profileFavoriteCollection: `${API_PREFIX}/search/favoriteCollections/`, + profiles: `${API_PREFIX}/search/profiles/`, + releases: `${API_PREFIX}/search/releases/`, + }, statistic: { addHistory: `${API_PREFIX}/history/add`, markWatched: `${API_PREFIX}/episode/watch`, diff --git a/app/pages/Search.tsx b/app/pages/Search.tsx index bba699e..b3878d6 100644 --- a/app/pages/Search.tsx +++ b/app/pages/Search.tsx @@ -1,196 +1,155 @@ "use client"; -import useSWRInfinite from "swr/infinite"; -import { ReleaseSection } from "#/components/ReleaseSection/ReleaseSection"; -import { RelatedSection } from "#/components/RelatedSection/RelatedSection"; -import { Spinner } from "#/components/Spinner/Spinner"; + import { useState, useEffect } from "react"; -import { useScrollPosition } from "#/hooks/useScrollPosition"; -import { useRouter } from "next/navigation"; import { useSearchParams } from "next/navigation"; -import { useUserStore } from "../store/auth"; -import { Button, Dropdown, DropdownItem, Modal, ModalBody, ModalFooter, ModalHeader } from "flowbite-react"; -import { CollectionsSection } from "#/components/CollectionsSection/CollectionsSection"; -import { UserSection } from "#/components/UserSection/UserSection"; -import { useSWRfetcher } from "#/api/utils"; +import { useRouter } from "next/navigation"; -const ListsMapping = { - watching: { - name: "Смотрю", - id: 1, - }, - planned: { - name: "В планах", - id: 2, - }, - watched: { - name: "Просмотрено", - id: 3, - }, - delayed: { - name: "Отложено", - id: 4, - }, - abandoned: { - name: "Заброшено", - id: 5, - }, -}; +import { + Button, + Dropdown, + DropdownItem, + Modal, + ModalBody, + ModalFooter, + ModalHeader, +} from "flowbite-react"; +import { useUserStore } from "#/store/auth"; -const TagMapping = { - name: { - name: "Названию", - id: 0, +const whereMapping = [ + { + id: "releases", + label: "Релизах", + auth: false, }, - studio: { - name: "Студии", - id: 1, + { + id: "profiles", + label: "Профилях", + auth: false, }, - director: { - name: "Режиссёру", - id: 2, + { + id: "list", + label: "Списках", + auth: true, }, - author: { - name: "Автору", - id: 3, + { + id: "history", + label: "Истории", + auth: true, }, - tag: { - name: "Тегу", - id: 4, + { + id: "favorites", + label: "Избранном", + auth: true, }, -}; + { + id: "collections", + label: "Коллекциях", + auth: true, + }, +]; -const WhereMapping = { - releases: "Релизах", - list: "Списках", - history: "Истории", - favorites: "Избранном", - collections: "Коллекциях", - profiles: "Профилях", +const searchByMapping = { + releases: [ + { + id: "name", + label: "Названию", + value: 0, + }, + { + id: "studio", + label: "Студии", + value: 1, + }, + { + id: "director", + label: "Режиссёру", + value: 2, + }, + { + id: "author", + label: "Автору", + value: 3, + }, + { + id: "tag", + label: "Тегу", + value: 4, + }, + ], + list: [ + { + id: "watching", + label: "Смотрю", + value: 1, + }, + { + id: "planned", + label: "В планах", + value: 2, + }, + { + id: "watched", + label: "Просмотрено", + value: 3, + }, + { + id: "delayed", + label: "Отложено", + value: 4, + }, + { + id: "abandoned", + label: "Заброшено", + value: 5, + }, + ], + none: [{ id: "none", label: "Нет", value: 0 }], }; export function SearchPage() { const router = useRouter(); const searchParams = useSearchParams(); - const [query, setQuery] = useState(searchParams.get("q") || ""); - const [searchVal, setSearchVal] = useState(searchParams.get("q") || ""); - const [where, setWhere] = useState(searchParams.get("where") || "releases"); - const [searchBy, setSearchBy] = useState( - searchParams.get("searchBy") || "name" - ); - const [list, setList] = useState(searchParams.get("list") || "watching"); + const userStore = useUserStore(); + const [query, setQuery] = useState(searchParams.get("query") || ""); + const [params, setParams] = useState(null); const [filtersModalOpen, setFiltersModalOpen] = useState(false); - const userStore = useUserStore(); - - const getKey = (pageIndex: number, previousPageData: any) => { - if (where == "releases") { - if (previousPageData && !previousPageData.releases.length) return null; - } else { - if (previousPageData && !previousPageData.content.length) return null; - } - - const url = new URL("/api/search", window.location.origin); - url.searchParams.set("page", pageIndex.toString()); - - if (userStore.token) { - url.searchParams.set("token", userStore.token); - } - - if (where) { - url.searchParams.set("where", where); - } - - if (where == "list" && list && ListsMapping.hasOwnProperty(list)) { - url.searchParams.set("list", ListsMapping[list].id); - } - - url.searchParams.set("searchBy", TagMapping[searchBy].id); - - if (query) { - url.searchParams.set("q", query); - return url.toString(); - } - return; - }; - - const { data, error, isLoading, size, setSize } = useSWRInfinite( - getKey, - useSWRfetcher, - { initialSize: 2, revalidateFirstPage: false } - ); - - const [content, setContent] = useState(null); useEffect(() => { - if (data) { - let allReleases = []; - if (where == "releases") { - for (let i = 0; i < data.length; i++) { - allReleases.push(...data[i].releases); - } - } else { - for (let i = 0; i < data.length; i++) { - allReleases.push(...data[i].content); - } - } - setContent(allReleases); + let _parsed = null; + try { + _parsed = JSON.parse(searchParams.get("params")); + } catch { + _parsed = { + where: "releases", + searchBy: "name", + }; } + setParams(_parsed); // eslint-disable-next-line react-hooks/exhaustive-deps - }, [data]); - - const scrollPosition = useScrollPosition(); - useEffect(() => { - if (scrollPosition >= 98 && scrollPosition <= 99) { - setSize(size + 1); - } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [scrollPosition]); - - function _executeSearch(value: string) { - const Params = new URLSearchParams(window.location.search); - Params.set("q", value); - const url = new URL(`/search?${Params.toString()}`, window.location.origin); - setContent(null); - setQuery(value); - router.push(url.toString()); - } + }, []); useEffect(() => { - if (searchVal && searchVal.length % 4 == 1) { - _executeSearch(searchVal.trim()); - } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [searchVal]); + if (!params) return; - if (error) - return ( -
-
-

Ошибка

-

- Произошла ошибка поиска. Попробуйте обновить страницу или зайдите - позже. -

-
-
- ); + const url = new URL(`/search`, window.location.origin); + url.searchParams.set("params", JSON.stringify(params)); + router.replace(url.toString()); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [params]); + + if (!params) return <>; return ( - <> -
-
{ - e.preventDefault(); - _executeSearch(searchVal.trim()); - }} - > +
+
+
-
+
setSearchVal(e.target.value)} + // value={searchVal} + // onChange={(e) => setSearchVal(e.target.value)} />
- +
-
- {data && data[0].related && } - {content ? - content.length > 0 ? - <> - {where == "collections" ? - - : where == "profiles" ? - - : - } - - :
- -

Странно, аниме не найдено, попробуйте другой запрос...

-
- : isLoading && ( -
- -
- ) - } - {!content && !isLoading && !query && ( -
- -

Введите ваш запрос что-бы найти любимый тайтл

-
- )} -
- {( - data && - data.length > 1 && - (where == "releases" ? - data[data.length - 1].releases.length == 25 - : data[data.length - 1].content.length == 25) - ) ? - - : ""} + +

query: {query}

+

params: {JSON.stringify(params)}

{}} + params={params} + setParams={setParams} /> - +
); } const FiltersModal = (props: { isOpen: boolean; setIsOpen: any; - where: string; - setWhere: any; - list: string; - setList: any; isAuth: boolean; - searchBy: string; - setSearchBy: any; setContent: any; + params: any; + setParams: any; }) => { - const router = useRouter(); - const [where, setWhere] = useState(props.where); - const [list, setList] = useState(props.list); - const [searchBy, setSearchBy] = useState(props.searchBy); - - function _cancel() { - setWhere(props.where); - setList(props.list); - setSearchBy(props.searchBy); - props.setIsOpen(false); - } - - function _accept() { - const Params = new URLSearchParams(window.location.search); - - if (props.where != where) { - Params.set("where", where); - props.setWhere(where); - } - - if (where == "list") { - Params.set("list", list); - props.setList(list); - } else { - Params.delete("list"); - } - - if (!["profiles", "collections"].includes(where)) { - Params.set("searchBy", searchBy); - props.setSearchBy(searchBy); - } else { - Params.delete("searchBy"); - props.setSearchBy("name"); - } - - props.setContent(null); - - const url = new URL(`/search?${Params.toString()}`, window.location.origin); - router.push(url.toString()); - } + if (!props.params) return <>; return ( - _cancel()}> + props.setIsOpen(false)}> Фильтры -
-
-

Искать в

- - {Object.keys(WhereMapping).map((item) => { - if ( - ["list", "history", "collections", "favorites"].includes( - item - ) && - !props.isAuth - ) { - return <>; - } else { - return ( - setWhere(item)} - key={`where--${item}`} +
+
+

Искать в

+ item.id == props.params.where).label + } + color="blue" + > + {whereMapping.map((item) => { + return item.auth && !props.isAuth ? + <> + : + searchByMapping[item.id] ? + props.setParams({ + where: item.id, + searchBy: searchByMapping[item.id][0].id, + }) + : props.setParams({ where: item.id, searchBy: "none" }) + } + key={`where--${item.id}`} > - {WhereMapping[item]} - - ); - } + {item.label} + ; })}
+ {searchByMapping[props.params.where] ? +
+

Искать по

+ item.id == props.params.searchBy + ).label + } + color="blue" + > + {searchByMapping[props.params.where].map((item) => { + return ( + + props.setParams({ + where: props.params.where, + searchBy: item.id, + }) + } + key={`searchBy--${item.id}`} + > + {item.label} + + ); + })} + +
+ : <>}
- {props.isAuth && where == "list" && ListsMapping.hasOwnProperty(list) ? -
-
-

Список

- - {Object.keys(ListsMapping).map((item) => { - return ( - setList(item)} - key={`list--${item}`} - > - {ListsMapping[item].name} - - ); - })} - -
-
- : ""} - {!["profiles", "collections"].includes(where) ? -
-
-

Искать по

- - {Object.keys(TagMapping).map((item) => { - return ( - setSearchBy(item)} - key={`tag--${item}`} - > - {TagMapping[item].name} - - ); - })} - -
-
- : ""} - -
- - -
-
+ ); }; + +// import useSWRInfinite from "swr/infinite"; +// import { ReleaseSection } from "#/components/ReleaseSection/ReleaseSection"; +// import { RelatedSection } from "#/components/RelatedSection/RelatedSection"; +// import { Spinner } from "#/components/Spinner/Spinner"; +// import { useScrollPosition } from "#/hooks/useScrollPosition"; +// import { useUserStore } from "../store/auth"; +// import { Button, Dropdown, DropdownItem, Modal, ModalBody, ModalFooter, ModalHeader } from "flowbite-react"; +// import { CollectionsSection } from "#/components/CollectionsSection/CollectionsSection"; +// import { UserSection } from "#/components/UserSection/UserSection"; +// import { useSWRfetcher } from "#/api/utils"; + +// export function SearchPage() { +// const router = useRouter(); + +// const [searchVal, setSearchVal] = useState(searchParams.get("q") || ""); +// const [where, setWhere] = useState(searchParams.get("where") || "releases"); + +// const [list, setList] = useState(searchParams.get("list") || "watching"); +// const [filtersModalOpen, setFiltersModalOpen] = useState(false); + +// + +// const getKey = (pageIndex: number, previousPageData: any) => { +// if (where == "releases") { +// if (previousPageData && !previousPageData.releases.length) return null; +// } else { +// if (previousPageData && !previousPageData.content.length) return null; +// } + +// const url = new URL("/api/search", window.location.origin); +// url.searchParams.set("page", pageIndex.toString()); + +// if (userStore.token) { +// url.searchParams.set("token", userStore.token); +// } + +// if (where) { +// url.searchParams.set("where", where); +// } + +// if (where == "list" && list && ListsMapping.hasOwnProperty(list)) { +// url.searchParams.set("list", ListsMapping[list].id); +// } + +// url.searchParams.set("searchBy", TagMapping[searchBy].id); + +// if (query) { +// url.searchParams.set("q", query); +// return url.toString(); +// } +// return; +// }; + +// const { data, error, isLoading, size, setSize } = useSWRInfinite( +// getKey, +// useSWRfetcher, +// { initialSize: 2, revalidateFirstPage: false } +// ); + +// const [content, setContent] = useState(null); +// useEffect(() => { +// if (data) { +// let allReleases = []; +// if (where == "releases") { +// for (let i = 0; i < data.length; i++) { +// allReleases.push(...data[i].releases); +// } +// } else { +// for (let i = 0; i < data.length; i++) { +// allReleases.push(...data[i].content); +// } +// } +// setContent(allReleases); +// } +// // eslint-disable-next-line react-hooks/exhaustive-deps +// }, [data]); + +// const scrollPosition = useScrollPosition(); +// useEffect(() => { +// if (scrollPosition >= 98 && scrollPosition <= 99) { +// setSize(size + 1); +// } +// // eslint-disable-next-line react-hooks/exhaustive-deps +// }, [scrollPosition]); + +// function _executeSearch(value: string) { +// const Params = new URLSearchParams(window.location.search); +// Params.set("q", value); +// const url = new URL(`/search?${Params.toString()}`, window.location.origin); +// setContent(null); +// setQuery(value); +// router.push(url.toString()); +// } + +// useEffect(() => { +// if (searchVal && searchVal.length % 4 == 1) { +// _executeSearch(searchVal.trim()); +// } +// // eslint-disable-next-line react-hooks/exhaustive-deps +// }, [searchVal]); + +// if (error) +// return ( +//
+//
+//

Ошибка

+//

+// Произошла ошибка поиска. Попробуйте обновить страницу или зайдите +// позже. +//

+//
+//
+// ); + +// return ( +// <> +//
+//
{ +// e.preventDefault(); +// _executeSearch(searchVal.trim()); +// }} +// > + +//
+// +//
+//
+// {data && data[0].related && } +// {content ? +// content.length > 0 ? +// <> +// {where == "collections" ? +// +// : where == "profiles" ? +// +// : +// } +// +// :
+// +//

Странно, аниме не найдено, попробуйте другой запрос...

+//
+// : isLoading && ( +//
+// +//
+// ) +// } +// {!content && !isLoading && !query && ( +//
+// +//

Введите ваш запрос что-бы найти любимый тайтл

+//
+// )} +//
+// {( +// data && +// data.length > 1 && +// (where == "releases" ? +// data[data.length - 1].releases.length == 25 +// : data[data.length - 1].content.length == 25) +// ) ? +// +// : ""} +// +// +// ); +// } + +// const FiltersModal = (props: { +// isOpen: boolean; +// setIsOpen: any; +// where: string; +// setWhere: any; +// list: string; +// setList: any; +// isAuth: boolean; +// searchBy: string; +// setSearchBy: any; +// setContent: any; +// }) => { +// const router = useRouter(); +// const [where, setWhere] = useState(props.where); +// const [list, setList] = useState(props.list); +// const [searchBy, setSearchBy] = useState(props.searchBy); + +// function _cancel() { +// setWhere(props.where); +// setList(props.list); +// setSearchBy(props.searchBy); +// props.setIsOpen(false); +// } + +// function _accept() { +// const Params = new URLSearchParams(window.location.search); + +// if (props.where != where) { +// Params.set("where", where); +// props.setWhere(where); +// } + +// if (where == "list") { +// Params.set("list", list); +// props.setList(list); +// } else { +// Params.delete("list"); +// } + +// if (!["profiles", "collections"].includes(where)) { +// Params.set("searchBy", searchBy); +// props.setSearchBy(searchBy); +// } else { +// Params.delete("searchBy"); +// props.setSearchBy("name"); +// } + +// props.setContent(null); + +// const url = new URL(`/search?${Params.toString()}`, window.location.origin); +// router.push(url.toString()); +// } + +// return ( +// _cancel()}> +// Фильтры +// +//
+//
+//

Искать в

+// +// {Object.keys(WhereMapping).map((item) => { +// if ( +// ["list", "history", "collections", "favorites"].includes( +// item +// ) && +// !props.isAuth +// ) { +// return <>; +// } else { +// return ( +// setWhere(item)} +// key={`where--${item}`} +// > +// {WhereMapping[item]} +// +// ); +// } +// })} +// +//
+//
+// {props.isAuth && where == "list" && ListsMapping.hasOwnProperty(list) ? +//
+//
+//

Список

+// +// {Object.keys(ListsMapping).map((item) => { +// return ( +// setList(item)} +// key={`list--${item}`} +// > +// {ListsMapping[item].name} +// +// ); +// })} +// +//
+//
+// : ""} +// {!["profiles", "collections"].includes(where) ? +//
+//
+//

Искать по

+// +// {Object.keys(TagMapping).map((item) => { +// return ( +// setSearchBy(item)} +// key={`tag--${item}`} +// > +// {TagMapping[item].name} +// +// ); +// })} +// +//
+//
+// : ""} +//
+// +//
+// +// +//
+//
+//
+// ); +// };