nyanimedb/modules/frontend/src/pages/TitlesPage/TitlesPage.tsx
Iron_Felix 9cb3f94e27
All checks were successful
Build and Deploy Go App / build (push) Successful in 6m42s
Build and Deploy Go App / deploy (push) Successful in 40s
feat: gif fot waiting
2025-12-05 23:53:29 +03:00

169 lines
5.6 KiB
TypeScript
Raw 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 } from "react";
import { ListView } from "../../components/ListView/ListView";
import { SearchBar } from "../../components/SearchBar/SearchBar";
import { TitlesSortBox } from "../../components/TitlesSortBox/TitlesSortBox";
// import { DefaultService } from "../../api/services/DefaultService";
import { TitleCardSquare } from "../../components/cards/TitleCardSquare";
import { TitleCardHorizontal } from "../../components/cards/TitleCardHorizontal";
import { getTitles, type CursorObj, type Title, type TitleSort } from "../../api";
import { LayoutSwitch } from "../../components/LayoutSwitch/LayoutSwitch";
import { Link } from "react-router-dom";
import { type TitlesFilter, TitlesFilterPanel } from "../../components/TitlesFilterPanel/TitlesFilterPanel";
const PAGE_SIZE = 10;
export default function TitlesPage() {
const [titles, setTitles] = useState<Title[]>([]);
const [nextPage, setNextPage] = useState<Title[]>([]);
const [cursor, setCursor] = useState<CursorObj | null>(null);
const [search, setSearch] = useState("");
const [loading, setLoading] = useState(true);
const [loadingMore, setLoadingMore] = useState(false);
const [sort, setSort] = useState<TitleSort>("id");
const [sortForward, setSortForward] = useState(true);
const [layout, setLayout] = useState<"square" | "horizontal">("square");
const [filters, setFilters] = useState<TitlesFilter>({
extSearch: false,
status: "",
rating: "",
releaseYear: "",
releaseSeason: "",
});
const fetchPage = async (cursorObj: CursorObj | null) => {
const cursorStr = cursorObj
? btoa(JSON.stringify(cursorObj)).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "")
: undefined;
const response = await getTitles({
query: {
cursor: cursorStr,
sort: sort,
sort_forward: sortForward,
ext_search: filters.extSearch,
word: search.trim() || undefined,
status: filters.status ? [filters.status] : undefined,
rating: filters.rating || undefined,
release_year: filters.releaseYear || undefined,
release_season: filters.releaseSeason || undefined,
limit: PAGE_SIZE,
offset: PAGE_SIZE,
fields: "all",
},
});
return {
items: response.data?.data ?? [],
nextCursor: response.data?.cursor ?? null,
};
};
// Инициализация: загружаем сразу две страницы
useEffect(() => {
const initLoad = async () => {
setLoading(true);
setTitles([]);
setNextPage([]);
setCursor(null);
const firstPage = await fetchPage(null);
const secondPage = firstPage.nextCursor ? await fetchPage(firstPage.nextCursor) : { items: [], nextCursor: null };
setTitles(firstPage.items);
setNextPage(secondPage.items);
setCursor(secondPage.nextCursor);
setLoading(false);
};
initLoad();
}, [search, sort, sortForward, filters]);
const handleLoadMore = async () => {
if (nextPage.length === 0) {
setLoadingMore(false);
return;
}
setLoadingMore(true);
setTitles(prev => [...prev, ...nextPage]);
setNextPage([]);
// Подгружаем следующую страницу с сервера
if (cursor) {
try {
const next = await fetchPage(cursor);
if (next.items.length > 0) {
setNextPage(next.items);
}
setCursor(next.nextCursor);
} catch (err) {
console.error(err);
}
}
// Любой сценарий выключаем loadingMore
setLoadingMore(false);
};
return (
<div className="w-full min-h-screen bg-gray-50 p-6 flex flex-col items-center">
<h1 className="text-4xl font-bold mb-6 text-center text-black">Titles</h1>
<div className="w-full sm:w-4/5 flex flex-col sm:flex-row gap-4 mb-6 items-center">
<SearchBar placeholder="Search titles..." search={search} setSearch={setSearch} />
<LayoutSwitch layout={layout} setLayout={setLayout} />
<TitlesSortBox
sort={sort}
setSort={setSort}
sortForward={sortForward}
setSortForward={setSortForward}
/>
</div>
<TitlesFilterPanel filters={filters} setFilters={setFilters} />
{loading && (
<div className="mt-20 flex flex-col items-center justify-center space-y-4 font-medium text-black">
<span>Loading...</span>
<img
src="https://images.steamusercontent.com/ugc/920301026407341369/69CBEF69DED504CD8CC7838D370061089F4D81BD/?imw=5000&imh=5000&ima=fit&impolicy=Letterbox&imcolor=%23000000&letterbox=false"
alt="Loading animation"
className="w-16 h-16 object-contain"
/>
</div>
)}
{!loading && titles.length === 0 && (
<div className="mt-20 font-medium text-black">No titles found.</div>
)}
{titles.length > 0 && (
<>
<ListView<Title>
items={titles}
layout={layout}
hasMore={!!cursor || nextPage.length > 1}
loadingMore={loadingMore}
onLoadMore={handleLoadMore}
renderItem={(title, layout) => (
<Link to={`/titles/${title.id}`} key={title.id} className="block">
{layout === "square" ? <TitleCardSquare title={title} /> : <TitleCardHorizontal title={title} />}
</Link>
)}
/>
{!cursor && nextPage.length == 0 && (
<div className="mt-6 font-medium text-black">
Результатов больше нет, было найдено {titles.length} тайтлов.
</div>
)}
</>
)}
</div>
);
}