add release search, adding and removing to collection create page

This commit is contained in:
Kentai Radiquum 2024-08-16 15:32:22 +05:00
parent 1073ac4253
commit 530fc1aad0
Signed by: Radiquum
GPG key ID: 858E8EE696525EED
3 changed files with 308 additions and 62 deletions

View file

@ -19,11 +19,19 @@ export const ReleaseLink169 = (props: any) => {
user_list = profile_lists[profile_list_status];
}
return (
<Link href={`/release/${props.id}`}>
<Link
href={`/release/${props.id}`}
className={props.isLinkDisabled ? "pointer-events-none" : ""}
aria-disabled={props.isLinkDisabled}
tabIndex={props.isLinkDisabled ? -1 : undefined}
>
<div className="w-full aspect-video group">
<div className="relative w-full h-full overflow-hidden bg-center bg-no-repeat bg-cover rounded-sm group-hover:animate-bg_zoom animate-bg_zoom_rev group-hover:[background-size:110%] " style={{
<div
className="relative w-full h-full overflow-hidden bg-center bg-no-repeat bg-cover rounded-sm group-hover:animate-bg_zoom animate-bg_zoom_rev group-hover:[background-size:110%] "
style={{
backgroundImage: `linear-gradient(to bottom, rgba(0, 0, 0, 0.1) 0%, rgba(0, 0, 0, 0.9) 100%), url(${props.image})`,
}}>
}}
>
<div className="absolute flex flex-wrap items-start justify-start gap-0.5 sm:gap-1 left-2 top-2">
<Chip
bg_color={

View file

@ -19,62 +19,65 @@ export const ReleaseLinkPoster = (props: any) => {
user_list = profile_lists[profile_list_status];
}
return (
<Link href={`/release/${props.id}`}>
<div className="flex flex-col w-full h-full gap-4 lg:flex-row">
<div
className="relative w-full h-64 gap-8 p-4 overflow-hidden bg-white bg-center bg-no-repeat bg-cover border border-gray-200 rounded-lg shadow-md lg:min-w-[300px] lg:min-h-[385px] lg:max-w-[300px] lg:max-h-[385px] lg:bg-top dark:border-gray-700 dark:bg-gray-800"
style={{
backgroundImage: `linear-gradient(to bottom, rgba(0, 0, 0, 0.1) 0%, rgba(0, 0, 0, 0.9) 100%), url(${props.image})`,
}}
>
<div className="flex flex-wrap gap-1">
<Link
href={`/release/${props.id}`}
className={props.isLinkDisabled ? "pointer-events-none" : ""}
aria-disabled={props.isLinkDisabled}
tabIndex={props.isLinkDisabled ? -1 : undefined}
>
<div
className="relative w-full h-64 gap-8 p-2 overflow-hidden bg-white bg-center bg-no-repeat bg-cover border border-gray-200 rounded-lg shadow-md lg:min-w-[300px] lg:min-h-[385px] lg:max-w-[300px] lg:max-h-[385px] lg:bg-top dark:border-gray-700 dark:bg-gray-800"
style={{
backgroundImage: `linear-gradient(to bottom, rgba(0, 0, 0, 0.1) 0%, rgba(0, 0, 0, 0.9) 100%), url(${props.image})`,
}}
>
<div className="flex flex-wrap gap-1">
<Chip
bg_color={
props.grade.toFixed(1) == 0
? "hidden"
: props.grade.toFixed(1) < 2
? "bg-red-500"
: props.grade.toFixed(1) < 3
? "bg-orange-500"
: props.grade.toFixed(1) < 4
? "bg-yellow-500"
: "bg-green-500"
}
name={props.grade.toFixed(1)}
/>
{props.status ? (
<Chip name={props.status.name} />
) : (
<Chip
bg_color={
props.grade.toFixed(1) == 0
? "hidden"
: props.grade.toFixed(1) < 2
? "bg-red-500"
: props.grade.toFixed(1) < 3
? "bg-orange-500"
: props.grade.toFixed(1) < 4
? "bg-yellow-500"
: "bg-green-500"
name={
props.status_id == 1
? "Завершено"
: props.status_id == 2
? "Онгоинг"
: "Анонс"
}
name={props.grade.toFixed(1)}
/>
{props.status ? (
<Chip name={props.status.name} />
) : (
<Chip
name={
props.status_id == 1
? "Завершено"
: props.status_id == 2
? "Онгоинг"
: "Анонс"
}
/>
)}
<Chip
name={props.episodes_released && props.episodes_released}
name_2={
props.episodes_total ? props.episodes_total + " эп." : "? эп."
}
devider="/"
/>
</div>
<div className="absolute flex flex-col gap-2 text-white bottom-4">
{props.title_ru && (
<p className="text-xl font-bold text-white md:text-2xl">
{props.title_ru}
</p>
)}
{props.title_original && (
<p className="text-sm text-gray-300 md:text-base">
{props.title_original}
</p>
)}
</div>
)}
<Chip
name={props.episodes_released && props.episodes_released}
name_2={
props.episodes_total ? props.episodes_total + " эп." : "? эп."
}
devider="/"
/>
</div>
<div className="absolute flex flex-col gap-2 text-white bottom-4 left-2 right-2">
{props.title_ru && (
<p className="text-xl font-bold text-white md:text-2xl">
{props.title_ru}
</p>
)}
{props.title_original && (
<p className="text-sm text-gray-300 md:text-base">
{props.title_original}
</p>
)}
</div>
</div>
</Link>

View file

@ -1,6 +1,8 @@
"use client";
import useSWR from "swr";
import useSWRInfinite from "swr/infinite";
import { useUserStore } from "#/store/auth";
import { useEffect, useState } from "react";
import { useEffect, useState, useCallback } from "react";
import { useSearchParams, useRouter } from "next/navigation";
import { ENDPOINTS } from "#/api/config";
import {
@ -11,7 +13,24 @@ import {
Textarea,
FileInput,
Label,
Modal,
} from "flowbite-react";
import { ReleaseSection } from "#/components/ReleaseSection/ReleaseSection";
import { ReleaseLink } from "#/components/ReleaseLink/ReleaseLink";
const fetcher = async (url: string) => {
const res = await fetch(url);
if (!res.ok) {
const error = new Error(
`An error occurred while fetching the data. status: ${res.status}`
);
error.message = await res.json();
throw error;
}
return res.json();
};
export const CreateCollectionPage = () => {
const userStore = useUserStore();
@ -31,6 +50,9 @@ export const CreateCollectionPage = () => {
title: 0,
description: 0,
});
const [addedReleases, setAddedReleases] = useState([]);
const [addedReleasesIds, setAddedReleasesIds] = useState([]);
const [releasesEditModalOpen, setReleasesEditModalOpen] = useState(false);
const collection_id = searchParams.get("id") || null;
const mode = searchParams.get("mode") || null;
@ -105,6 +127,21 @@ export const CreateCollectionPage = () => {
});
}
function _deleteRelease(release: any) {
let releasesArray = [];
let idsArray = [];
for (let i = 0; i < addedReleases.length; i++) {
if (addedReleases[i].id != release.id) {
releasesArray.push(addedReleases[i]);
idsArray.push(addedReleasesIds[i]);
}
}
setAddedReleases(releasesArray);
setAddedReleasesIds(idsArray);
}
return (
<main className="container pt-2 pb-16 mx-auto sm:pt-4 sm:pb-0">
<Card>
@ -112,14 +149,14 @@ export const CreateCollectionPage = () => {
{edit ? "Редактирование коллекции" : "Создание коллекции"}
</p>
<form
className="flex flex-wrap items-center w-full gap-2"
className="flex flex-col w-full gap-2 lg:items-center lg:flex-row"
onSubmit={(e) => submit(e)}
>
<Label
htmlFor="dropzone-file"
className="flex flex-col items-center w-[600px] h-[337px] border-2 border-gray-300 border-dashed rounded-lg cursor-pointer bg-gray-50 hover:bg-gray-100 dark:border-gray-600 dark:bg-gray-700 dark:hover:border-gray-500 dark:hover:bg-gray-600"
className="flex flex-col items-center w-full sm:max-w-[600px] h-[337px] border-2 border-gray-300 border-dashed rounded-lg cursor-pointer bg-gray-50 hover:bg-gray-100 dark:border-gray-600 dark:bg-gray-700 dark:hover:border-gray-500 dark:hover:bg-gray-600"
>
<div className="flex flex-col items-center justify-center w-[595px] h-[inherit] rounded-[inherit] pt-5 pb-6 overflow-hidden">
<div className="flex flex-col items-center justify-center max-w-[595px] h-[inherit] rounded-[inherit] pt-5 pb-6 overflow-hidden">
{!imageUrl ? (
<>
<svg
@ -180,7 +217,9 @@ export const CreateCollectionPage = () => {
value={collectionInfo.title}
maxLength={60}
/>
<p className="text-sm text-gray-500 dark:text-gray-300">{stringLength.title}/60</p>
<p className="text-sm text-gray-500 dark:text-gray-300">
{stringLength.title}/60
</p>
<div className="block mt-2 mb-2">
<Label
htmlFor="description"
@ -196,7 +235,9 @@ export const CreateCollectionPage = () => {
value={collectionInfo.description}
maxLength={1000}
/>
<p className="text-sm text-gray-500 dark:text-gray-300">{stringLength.description}/1000</p>
<p className="text-sm text-gray-500 dark:text-gray-300">
{stringLength.description}/1000
</p>
<div className="mt-2">
<div className="flex items-center gap-1">
<Checkbox
@ -214,6 +255,200 @@ export const CreateCollectionPage = () => {
</div>
</form>
</Card>
<div className="mt-4">
<div className="flex justify-between px-4 py-2 border-b-2 border-black dark:border-white">
<h1 className="font-bold text-md sm:text-xl md:text-lg xl:text-xl">
{"Релизов в коллекции: " + addedReleases.length}/100
</h1>
<Button
color={"blue"}
size={"xs"}
onClick={() => setReleasesEditModalOpen(!releasesEditModalOpen)}
>
Добавить
</Button>
</div>
<div className="m-4">
<div className="grid justify-center sm:grid-cols-[repeat(auto-fit,minmax(400px,1fr))] grid-cols-[100%] gap-2 min-w-full">
{addedReleases.map((release) => {
return (
<div
key={release.id}
className="relative w-full h-full aspect-video group"
>
<button
className="absolute inset-0 z-10 text-black transition-opacity bg-white opacity-0 group-hover:opacity-75"
onClick={() => _deleteRelease(release)}
>
Удалить
</button>
<ReleaseLink {...release} isLinkDisabled={true} />
</div>
);
})}
{addedReleases.length == 1 && <div></div>}
</div>
</div>
</div>
<ReleasesEditModal
isOpen={releasesEditModalOpen}
setIsOpen={setReleasesEditModalOpen}
releases={addedReleases}
releasesIds={addedReleasesIds}
setReleases={setAddedReleases}
setReleasesIds={setAddedReleasesIds}
/>
</main>
);
};
export const ReleasesEditModal = (props: {
isOpen: boolean;
setIsOpen: any;
releases: any;
setReleases: any;
releasesIds: any;
setReleasesIds: any;
}) => {
const [query, setQuery] = useState("");
const getKey = (pageIndex: number, previousPageData: any) => {
if (previousPageData && !previousPageData.releases.length) return null;
const url = new URL("/api/search", window.location.origin);
url.searchParams.set("page", pageIndex.toString());
if (!query) return null
url.searchParams.set("q", query);
return url.toString();
};
const { data, error, isLoading, size, setSize } = useSWRInfinite(
getKey,
fetcher,
{ initialSize: 2, revalidateFirstPage: false }
);
const [content, setContent] = useState([]);
useEffect(() => {
if (data) {
let allReleases = [];
for (let i = 0; i < data.length; i++) {
allReleases.push(...data[i].releases);
}
setContent(allReleases);
}
}, [data]);
const [currentRef, setCurrentRef] = useState<any>(null);
const modalRef = useCallback((ref) => {
setCurrentRef(ref);
}, []);
const [scrollPosition, setScrollPosition] = useState(0);
function handleScroll() {
const height = currentRef.scrollHeight - currentRef.clientHeight;
const windowScroll = currentRef.scrollTop;
const scrolled = (windowScroll / height) * 100;
setScrollPosition(Math.floor(scrolled));
}
useEffect(() => {
if (scrollPosition >= 95 && scrollPosition <= 96) {
setSize(size + 1);
}
}, [scrollPosition]);
function _addRelease(release: any) {
if (props.releasesIds.length == 100) {
alert("Достигнуто максимальное количество релизов в коллекции - 100");
return;
}
if (props.releasesIds.includes(release.id)) {
alert("Релиз уже добавлен в коллекцию");
return;
}
props.setReleases([...props.releases, release]);
props.setReleasesIds([...props.releasesIds, release.id]);
}
return (
<Modal
dismissible
show={props.isOpen}
onClose={() => props.setIsOpen(false)}
size={"7xl"}
>
<Modal.Header>Изменить релизы в коллекции</Modal.Header>
<div
onScroll={handleScroll}
ref={modalRef}
className="px-4 py-4 overflow-auto"
>
<form
className="max-w-full mx-auto"
onSubmit={(e) => {
e.preventDefault();
props.setReleases([]);
setQuery(e.target[0].value.trim());
}}
>
<label
htmlFor="default-search"
className="mb-2 text-sm font-medium text-gray-900 sr-only dark:text-white"
>
Поиск
</label>
<div className="relative">
<div className="absolute inset-y-0 flex items-center pointer-events-none start-0 ps-3">
<svg
className="w-4 h-4 text-gray-500 dark:text-gray-400"
aria-hidden="true"
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 20 20"
>
<path
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
d="m19 19-4-4m0-7A7 7 0 1 1 1 8a7 7 0 0 1 14 0Z"
/>
</svg>
</div>
<input
type="search"
id="default-search"
className="block w-full p-4 text-sm text-gray-900 border border-gray-300 rounded-lg ps-10 bg-gray-50 focus:ring-blue-500 focus:border-blue-500 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
placeholder="Поиск аниме..."
required
defaultValue={query || ""}
/>
<button
type="submit"
className="text-white absolute end-2.5 bottom-2.5 bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:outline-none focus:ring-blue-300 font-medium rounded-lg text-sm px-4 py-2 dark:bg-blue-600 dark:hover:bg-blue-700 dark:focus:ring-blue-800"
>
Поиск
</button>
</div>
</form>
<div className="flex flex-wrap gap-1 mt-2">
{content.map((release) => {
return (
<button
key={release.id}
className=""
onClick={() => _addRelease(release)}
>
<ReleaseLink type="poster" {...release} isLinkDisabled={true} />
</button>
);
})}
{content.length == 1 && <div></div>}
</div>
</div>
</Modal>
);
};